[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
11
electron/capabilities/services/index.cjs
Normal file
11
electron/capabilities/services/index.cjs
Normal file
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const notImplemented = require("./notImplemented.cjs");
|
||||
const vaultService = require("./vaultService.cjs");
|
||||
const portforwardService = require("./portforwardService.cjs");
|
||||
|
||||
module.exports = {
|
||||
...notImplemented,
|
||||
...vaultService,
|
||||
...portforwardService,
|
||||
};
|
||||
18
electron/capabilities/services/notImplemented.cjs
Normal file
18
electron/capabilities/services/notImplemented.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
|
||||
function createNotImplementedResult(capabilityId) {
|
||||
return {
|
||||
ok: false,
|
||||
code: "CAPABILITY_NOT_IMPLEMENTED",
|
||||
error: `Capability "${capabilityId}" is not implemented yet.`,
|
||||
};
|
||||
}
|
||||
|
||||
function createNotImplementedHandler(capabilityId) {
|
||||
return async () => createNotImplementedResult(capabilityId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createNotImplementedResult,
|
||||
createNotImplementedHandler,
|
||||
};
|
||||
65
electron/capabilities/services/portforwardService.cjs
Normal file
65
electron/capabilities/services/portforwardService.cjs
Normal file
@@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
|
||||
const portForwardingBridge = require("../../bridges/portForwardingBridge.cjs");
|
||||
|
||||
/**
|
||||
* Port forwarding domain service. Tunnels live in the configured runtime
|
||||
* (terminal worker in the current architecture); rules live in renderer vault.
|
||||
*/
|
||||
function createPortForwardService(ctx = {}) {
|
||||
const { invokeVaultAgent } = ctx;
|
||||
const listPortForwards = typeof ctx.listPortForwards === "function"
|
||||
? ctx.listPortForwards
|
||||
: () => portForwardingBridge.listPortForwards();
|
||||
|
||||
return {
|
||||
listRules: async () => {
|
||||
if (typeof invokeVaultAgent !== "function") {
|
||||
return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
}
|
||||
return invokeVaultAgent("portforward.rules.list", {});
|
||||
},
|
||||
createRule: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
return invokeVaultAgent("portforward.rules.create", params);
|
||||
},
|
||||
updateRule: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
return invokeVaultAgent("portforward.rules.update", params);
|
||||
},
|
||||
duplicateRule: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
return invokeVaultAgent("portforward.rules.duplicate", params);
|
||||
},
|
||||
deleteRule: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
return invokeVaultAgent("portforward.rules.delete", params);
|
||||
},
|
||||
listTunnels: async () => {
|
||||
const tunnels = await listPortForwards();
|
||||
return { ok: true, tunnels };
|
||||
},
|
||||
start: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") {
|
||||
return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
}
|
||||
return invokeVaultAgent("portforward.start", {
|
||||
ruleId: params.ruleId,
|
||||
chatSessionId: params.chatSessionId,
|
||||
});
|
||||
},
|
||||
stop: async (params = {}) => {
|
||||
if (typeof invokeVaultAgent !== "function") {
|
||||
return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
}
|
||||
return invokeVaultAgent("portforward.stop", {
|
||||
ruleId: params.ruleId,
|
||||
chatSessionId: params.chatSessionId,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPortForwardService,
|
||||
};
|
||||
51
electron/capabilities/services/portforwardService.test.cjs
Normal file
51
electron/capabilities/services/portforwardService.test.cjs
Normal file
@@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const { createPortForwardService } = require("./portforwardService.cjs");
|
||||
|
||||
test("portforward service lists active tunnels through the configured runtime", async () => {
|
||||
const calls = [];
|
||||
const service = createPortForwardService({
|
||||
invokeVaultAgent: async () => ({ ok: true, rules: [] }),
|
||||
listPortForwards: async () => {
|
||||
calls.push("list");
|
||||
return [{ tunnelId: "worker-tunnel", status: "active" }];
|
||||
},
|
||||
});
|
||||
const result = await service.listTunnels();
|
||||
assert.equal(result.ok, true);
|
||||
assert.deepEqual(result.tunnels, [{ tunnelId: "worker-tunnel", status: "active" }]);
|
||||
assert.deepEqual(calls, ["list"]);
|
||||
});
|
||||
|
||||
test("portforward start delegates to vault agent bridge after approval path", async () => {
|
||||
let invokedOp = null;
|
||||
const service = createPortForwardService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
invokedOp = op;
|
||||
return { ok: true, ruleId: params.ruleId };
|
||||
},
|
||||
});
|
||||
const result = await service.start({ ruleId: "rule-1", chatSessionId: "chat-1" });
|
||||
assert.equal(invokedOp, "portforward.start");
|
||||
assert.equal(result.ok, true);
|
||||
});
|
||||
|
||||
test("portforward rule mutations delegate to the renderer vault", async () => {
|
||||
const calls = [];
|
||||
const service = createPortForwardService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
calls.push({ op, params });
|
||||
return { ok: true };
|
||||
},
|
||||
});
|
||||
await service.createRule({ label: "Web" });
|
||||
await service.updateRule({ ruleId: "rule-1", localPort: 8081 });
|
||||
await service.duplicateRule({ ruleId: "rule-1" });
|
||||
await service.deleteRule({ ruleId: "rule-1" });
|
||||
assert.deepEqual(calls.map((call) => call.op), [
|
||||
"portforward.rules.create", "portforward.rules.update", "portforward.rules.duplicate", "portforward.rules.delete",
|
||||
]);
|
||||
});
|
||||
43
electron/capabilities/services/sessionService.cjs
Normal file
43
electron/capabilities/services/sessionService.cjs
Normal file
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
|
||||
function createSessionService(ctx = {}) {
|
||||
const { invokeSessionAgent, validateClose, beforeClose, afterClose, onClosed } = ctx;
|
||||
|
||||
async function close(params = {}, options = {}) {
|
||||
if (!params.sessionId || typeof params.sessionId !== "string") {
|
||||
return { ok: false, error: "sessionId is required." };
|
||||
}
|
||||
if (!options.skipValidation && typeof validateClose === "function") {
|
||||
const validation = validateClose(params);
|
||||
if (validation && validation.ok === false) return validation;
|
||||
}
|
||||
if (typeof invokeSessionAgent !== "function") {
|
||||
return { ok: false, error: "Session close bridge is unavailable." };
|
||||
}
|
||||
|
||||
let result;
|
||||
let closed = false;
|
||||
try {
|
||||
await beforeClose?.(params);
|
||||
result = await invokeSessionAgent("session.close", { sessionId: params.sessionId });
|
||||
if (result?.ok !== false) {
|
||||
await onClosed?.(params.sessionId);
|
||||
closed = true;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
await afterClose?.(params, {
|
||||
closed,
|
||||
notFound: /\bwas not found\b/i.test(result?.error || ""),
|
||||
result,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
close: (params = {}) => close(params),
|
||||
closeTracked: (params = {}) => close(params, { skipValidation: true }),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createSessionService };
|
||||
55
electron/capabilities/services/sessionService.test.cjs
Normal file
55
electron/capabilities/services/sessionService.test.cjs
Normal file
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { createSessionService } = require("./sessionService.cjs");
|
||||
|
||||
test("closeTracked uses the same lifecycle while bypassing user-scope validation", async () => {
|
||||
const events = [];
|
||||
const service = createSessionService({
|
||||
validateClose: () => ({ ok: false, error: "scope was cleaned" }),
|
||||
beforeClose: async () => events.push("before"),
|
||||
invokeSessionAgent: async () => ({ ok: true, status: "closed" }),
|
||||
onClosed: async () => events.push("closed"),
|
||||
afterClose: async (_params, outcome) => events.push(outcome.closed ? "success" : "failed"),
|
||||
});
|
||||
|
||||
const manualResult = await service.close({ sessionId: "session-1" });
|
||||
assert.equal(manualResult.ok, false);
|
||||
assert.deepEqual(events, []);
|
||||
|
||||
const idleResult = await service.closeTracked({ sessionId: "session-1" });
|
||||
assert.equal(idleResult.ok, true);
|
||||
assert.deepEqual(events, ["before", "closed", "success"]);
|
||||
});
|
||||
|
||||
test("failed closes report the outcome so idle tracking can resume", async () => {
|
||||
let outcome = null;
|
||||
const service = createSessionService({
|
||||
invokeSessionAgent: async () => ({ ok: false, error: "renderer unavailable" }),
|
||||
afterClose: async (_params, value) => {
|
||||
outcome = value;
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.closeTracked({ sessionId: "session-1" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(outcome.closed, false);
|
||||
assert.equal(outcome.notFound, false);
|
||||
assert.deepEqual(outcome.result, result);
|
||||
});
|
||||
|
||||
test("already-missing sessions are distinguished from retryable close failures", async () => {
|
||||
let outcome = null;
|
||||
const service = createSessionService({
|
||||
invokeSessionAgent: async () => ({ ok: false, error: 'Session "gone" was not found.' }),
|
||||
afterClose: async (_params, value) => {
|
||||
outcome = value;
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.closeTracked({ sessionId: "gone" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(outcome.closed, false);
|
||||
assert.equal(outcome.notFound, true);
|
||||
});
|
||||
257
electron/capabilities/services/vaultService.cjs
Normal file
257
electron/capabilities/services/vaultService.cjs
Normal file
@@ -0,0 +1,257 @@
|
||||
"use strict";
|
||||
|
||||
/** Matches waitForScriptRun default (1h) plus bridge overhead. */
|
||||
const VAULT_AGENT_SCRIPT_WAIT_TIMEOUT_MS = 3_605_000;
|
||||
|
||||
function parseVaultAgentWaitFlag(raw) {
|
||||
if (raw === undefined || raw === null || raw === "") return false;
|
||||
if (typeof raw === "boolean") return raw;
|
||||
const normalized = String(raw).trim().toLowerCase();
|
||||
return normalized === "true" || normalized === "1" || normalized === "yes";
|
||||
}
|
||||
|
||||
function vaultAgentInvokeOptions(op, params = {}) {
|
||||
if (op !== "snippets.run" && op !== "scripts.run") return undefined;
|
||||
if (!parseVaultAgentWaitFlag(params.wait)) return undefined;
|
||||
return { timeoutMs: VAULT_AGENT_SCRIPT_WAIT_TIMEOUT_MS };
|
||||
}
|
||||
|
||||
/**
|
||||
* Vault domain service. Read-only metadata and notes/snippets are served from
|
||||
* renderer vault state via VaultAgentBridge; credentials never cross the bridge.
|
||||
*/
|
||||
function createVaultService(ctx = {}) {
|
||||
const { invokeVaultAgent } = ctx;
|
||||
|
||||
function requireBridge() {
|
||||
if (typeof invokeVaultAgent !== "function") {
|
||||
return { ok: false, error: "Vault agent bridge is unavailable." };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
getHost: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.get", { hostId: params.hostId });
|
||||
},
|
||||
listHosts: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.list", {});
|
||||
},
|
||||
openHost: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.open", {
|
||||
hostId: params.hostId,
|
||||
chatSessionId: params.chatSessionId,
|
||||
});
|
||||
},
|
||||
createHosts: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("hosts.create", params);
|
||||
},
|
||||
updateHost: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.update", params);
|
||||
},
|
||||
deleteHost: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.delete", { hostId: params.hostId });
|
||||
},
|
||||
importHosts: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.import", params);
|
||||
},
|
||||
getHostNotes: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.notes.get", { hostId: params.hostId });
|
||||
},
|
||||
setHostNotes: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.notes.set", {
|
||||
hostId: params.hostId,
|
||||
notes: params.notes,
|
||||
});
|
||||
},
|
||||
listNotes: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("note.list", {});
|
||||
},
|
||||
getNote: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("note.get", {
|
||||
noteId: params.noteId,
|
||||
offset: params.offset,
|
||||
maxChars: params.maxChars,
|
||||
query: params.query,
|
||||
expectedUpdatedAt: params.expectedUpdatedAt,
|
||||
});
|
||||
},
|
||||
createNote: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("note.create", params);
|
||||
},
|
||||
updateNote: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("note.update", params);
|
||||
},
|
||||
deleteNote: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("note.delete", { noteId: params.noteId });
|
||||
},
|
||||
listIdentities: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("identity.list", {});
|
||||
},
|
||||
listProxyProfiles: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("proxyProfile.list", {});
|
||||
},
|
||||
listGroups: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("group.list", {});
|
||||
},
|
||||
createGroup: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("group.create", params);
|
||||
},
|
||||
updateGroup: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("group.update", params);
|
||||
},
|
||||
deleteGroup: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("group.delete", params);
|
||||
},
|
||||
listSnippets: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.list", {});
|
||||
},
|
||||
getSnippet: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.get", { snippetId: params.snippetId });
|
||||
},
|
||||
runSnippet: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.run", {
|
||||
snippetId: params.snippetId,
|
||||
sessionId: params.sessionId,
|
||||
variables: params.variables,
|
||||
chatSessionId: params.chatSessionId,
|
||||
wait: params.wait,
|
||||
}, vaultAgentInvokeOptions("snippets.run", params));
|
||||
},
|
||||
createSnippet: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.create", params);
|
||||
},
|
||||
updateSnippet: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.update", params);
|
||||
},
|
||||
deleteSnippet: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("snippets.delete", params);
|
||||
},
|
||||
listScripts: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.list", {});
|
||||
},
|
||||
getScript: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.get", { scriptId: params.scriptId, snippetId: params.scriptId });
|
||||
},
|
||||
createScript: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.create", params);
|
||||
},
|
||||
updateScript: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.update", params);
|
||||
},
|
||||
deleteScript: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.delete", params);
|
||||
},
|
||||
runScript: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.run", params, vaultAgentInvokeOptions("scripts.run", params));
|
||||
},
|
||||
getScriptReference: async () => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.reference", {});
|
||||
},
|
||||
listScriptRuns: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.runs.list", params);
|
||||
},
|
||||
stopScriptRun: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.run.stop", params);
|
||||
},
|
||||
pauseScriptRun: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.run.pause", params);
|
||||
},
|
||||
resumeScriptRun: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.run.resume", params);
|
||||
},
|
||||
setScriptTargets: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("scripts.targets.set", params);
|
||||
},
|
||||
listHostConnectScripts: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.connectScripts.list", params);
|
||||
},
|
||||
setHostConnectScripts: async (params = {}) => {
|
||||
const bridgeErr = requireBridge();
|
||||
if (bridgeErr) return bridgeErr;
|
||||
return invokeVaultAgent("host.connectScripts.set", params);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createVaultService,
|
||||
};
|
||||
83
electron/capabilities/services/vaultService.test.cjs
Normal file
83
electron/capabilities/services/vaultService.test.cjs
Normal file
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const { createVaultService } = require("./vaultService.cjs");
|
||||
|
||||
test("vault service delegates host notes read to vault agent bridge", async () => {
|
||||
let invokedOp = null;
|
||||
const service = createVaultService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
invokedOp = op;
|
||||
return { ok: true, hostId: params.hostId, notes: "hello" };
|
||||
},
|
||||
});
|
||||
const result = await service.getHostNotes({ hostId: "host-1" });
|
||||
assert.equal(invokedOp, "host.notes.get");
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.notes, "hello");
|
||||
});
|
||||
|
||||
test("vault service returns bridge unavailable when renderer bridge missing", async () => {
|
||||
const service = createVaultService({});
|
||||
const result = await service.listSnippets();
|
||||
assert.equal(result.ok, false);
|
||||
assert.match(result.error, /unavailable/i);
|
||||
});
|
||||
|
||||
test("vault service delegates host open to vault agent bridge", async () => {
|
||||
let invokedOp = null;
|
||||
let invokedParams = null;
|
||||
const service = createVaultService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
invokedOp = op;
|
||||
invokedParams = params;
|
||||
return { ok: true, sessionId: "sess-1", hostId: params.hostId, status: "connecting" };
|
||||
},
|
||||
});
|
||||
const result = await service.openHost({ hostId: "host-1", chatSessionId: "chat-1" });
|
||||
assert.equal(invokedOp, "host.open");
|
||||
assert.equal(invokedParams.hostId, "host-1");
|
||||
assert.equal(invokedParams.chatSessionId, "chat-1");
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.sessionId, "sess-1");
|
||||
});
|
||||
|
||||
test("vault service delegates host update and delete to vault agent bridge", async () => {
|
||||
const calls = [];
|
||||
const service = createVaultService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
calls.push({ op, params });
|
||||
return { ok: true, hostId: params.hostId };
|
||||
},
|
||||
});
|
||||
|
||||
await service.updateHost({ hostId: "host-1", label: "new" });
|
||||
await service.deleteHost({ hostId: "host-1", ignored: "value" });
|
||||
|
||||
assert.equal(calls[0].op, "host.update");
|
||||
assert.equal(calls[0].params.label, "new");
|
||||
assert.equal(calls[1].op, "host.delete");
|
||||
assert.deepEqual(calls[1].params, { hostId: "host-1" });
|
||||
});
|
||||
|
||||
test("vault service delegates identities, groups, proxies, and note deletion", async () => {
|
||||
const calls = [];
|
||||
const service = createVaultService({
|
||||
invokeVaultAgent: async (op, params) => {
|
||||
calls.push({ op, params });
|
||||
return { ok: true };
|
||||
},
|
||||
});
|
||||
await service.listIdentities();
|
||||
await service.listProxyProfiles();
|
||||
await service.listGroups();
|
||||
await service.createGroup({ path: "prod" });
|
||||
await service.updateGroup({ path: "prod", defaults: "{}" });
|
||||
await service.deleteGroup({ path: "prod" });
|
||||
await service.deleteNote({ noteId: "note-1" });
|
||||
assert.deepEqual(calls.map((call) => call.op), [
|
||||
"identity.list", "proxyProfile.list", "group.list", "group.create", "group.update", "group.delete", "note.delete",
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user