svamp-cli 0.2.91 → 0.2.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agentCommands-Cq5-Dj_E.mjs → agentCommands-5UkFbjCr.mjs} +2 -2
- package/dist/{auth-DPhc_hNc.mjs → auth-DIlY0Nww.mjs} +1 -3
- package/dist/cli.mjs +66 -51
- package/dist/{commands-LfAaLGbG.mjs → commands-BjtQRoGi.mjs} +1 -3
- package/dist/{commands-kGkSBEMq.mjs → commands-D-m22ZpN.mjs} +1 -3
- package/dist/{commands-DemyBiUh.mjs → commands-Do8wtW5P.mjs} +1 -3
- package/dist/{commands-C6HpuYOB.mjs → commands-POgHLHF-.mjs} +2 -4
- package/dist/{commands-B9ssOqX0.mjs → commands-yzDiaiRy.mjs} +5 -5
- package/dist/{fleet-DRQS1weD.mjs → fleet-ChKjkV1V.mjs} +1 -3
- package/dist/{frpc-BBNWJChC.mjs → frpc-glQKHUBu.mjs} +1 -3
- package/dist/headlessCli-D2ZOlMNL.mjs +332 -0
- package/dist/index.mjs +1 -3
- package/dist/{package-GxcmHBEB.mjs → package-Caxptr7d.mjs} +2 -2
- package/dist/{run-YihZemmc.mjs → run-CuHAFrIm.mjs} +1 -3
- package/dist/{run-DWr_h85y.mjs → run-CvIUzIMP.mjs} +115 -345
- package/dist/{serveCommands-Js_HSnLs.mjs → serveCommands-1TJus4ZX.mjs} +5 -5
- package/dist/{serveManager-Dn9GyzAG.mjs → serveManager-CoVwgYS_.mjs} +2 -4
- package/dist/sideband-7glSvpTW.mjs +79 -0
- package/package.json +2 -2
|
@@ -6,9 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { execFile, spawn as spawn$1, execSync as execSync$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
8
|
import { existsSync, readFileSync, mkdirSync as mkdirSync$1, readdirSync, writeFileSync as writeFileSync$1, renameSync as renameSync$1, rmSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
9
|
-
import vm from 'node:vm';
|
|
10
9
|
import { exec, spawn, execSync, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
11
|
-
import { WebSocket } from 'ws';
|
|
12
10
|
import { promisify } from 'util';
|
|
13
11
|
import { randomBytes, randomUUID, createHash } from 'node:crypto';
|
|
14
12
|
import { join as join$1 } from 'node:path';
|
|
@@ -380,54 +378,6 @@ function buildSkillsPromptSection(skills) {
|
|
|
380
378
|
].join("\n");
|
|
381
379
|
}
|
|
382
380
|
|
|
383
|
-
function buildSvampApi(deps) {
|
|
384
|
-
return {
|
|
385
|
-
bash: (command, opts) => deps.runBash(command, opts),
|
|
386
|
-
context: () => deps.getContext(),
|
|
387
|
-
ask: (question) => deps.askSession(question),
|
|
388
|
-
summarize: (question) => deps.summarizeSession(question),
|
|
389
|
-
send: (message) => deps.sessionSend(message)
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
async function runJs(code, deps, opts = {}) {
|
|
393
|
-
return runJsWithApi(code, buildSvampApi(deps), opts);
|
|
394
|
-
}
|
|
395
|
-
async function runJsWithApi(code, api, opts = {}) {
|
|
396
|
-
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
397
|
-
const logs = [];
|
|
398
|
-
const capture = (...a) => {
|
|
399
|
-
logs.push(a.map((x) => typeof x === "string" ? x : JSON.stringify(x)).join(" "));
|
|
400
|
-
};
|
|
401
|
-
const sandbox = /* @__PURE__ */ Object.create(null);
|
|
402
|
-
sandbox.svamp = api;
|
|
403
|
-
sandbox.console = { log: capture, error: capture, warn: capture, info: capture };
|
|
404
|
-
const context = vm.createContext(sandbox, { name: "wise-run_js" });
|
|
405
|
-
const wrapped = `(async () => {
|
|
406
|
-
${code}
|
|
407
|
-
})()`;
|
|
408
|
-
let script;
|
|
409
|
-
try {
|
|
410
|
-
script = new vm.Script(wrapped, { filename: "wise-run_js.js" });
|
|
411
|
-
} catch (e) {
|
|
412
|
-
return { result: void 0, logs, error: "compile error: " + e.message };
|
|
413
|
-
}
|
|
414
|
-
let timer;
|
|
415
|
-
try {
|
|
416
|
-
const ran = script.runInContext(context, { timeout: timeoutMs });
|
|
417
|
-
const result = await Promise.race([
|
|
418
|
-
Promise.resolve(ran),
|
|
419
|
-
new Promise((_, reject) => {
|
|
420
|
-
timer = setTimeout(() => reject(new Error("run_js timed out")), timeoutMs);
|
|
421
|
-
})
|
|
422
|
-
]);
|
|
423
|
-
return { result, logs };
|
|
424
|
-
} catch (e) {
|
|
425
|
-
return { result: void 0, logs, error: e.message };
|
|
426
|
-
} finally {
|
|
427
|
-
if (timer) clearTimeout(timer);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
381
|
const READ_ONLY_TOOLS = ["get_context", "ask_session", "summarize_session", "use_skill"];
|
|
432
382
|
const str$1 = (v) => v == null ? "" : String(v);
|
|
433
383
|
function buildTools(deps, skills) {
|
|
@@ -483,16 +433,6 @@ function buildTools(deps, skills) {
|
|
|
483
433
|
}
|
|
484
434
|
return "(sent to the coding agent)";
|
|
485
435
|
}
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
name: "run_js",
|
|
489
|
-
readOnly: false,
|
|
490
|
-
description: "Run JavaScript with an async `svamp` API to read state and compose several steps in one call: svamp.bash(cmd), svamp.context(), svamp.ask(q), svamp.summarize(q), svamp.send(msg). Use `await` and `return` a value; console.log is captured. Sandboxed, ~5s limit.",
|
|
491
|
-
parameters: { type: "object", properties: { code: { type: "string", description: "JS body; may use await and return." } }, required: ["code"], additionalProperties: false },
|
|
492
|
-
run: async (a) => {
|
|
493
|
-
const r = await runJs(str$1(a?.code), deps, { timeoutMs: 5e3 });
|
|
494
|
-
return JSON.stringify({ result: r.result, logs: r.logs, error: r.error });
|
|
495
|
-
}
|
|
496
436
|
}
|
|
497
437
|
];
|
|
498
438
|
}
|
|
@@ -619,7 +559,6 @@ You are WISE Agent, a fast, text-mode companion to the deep coding agent (Claude
|
|
|
619
559
|
- summarize_session \u2014 a cheap subagent that summarizes the deep agent's transcript for a specific question.
|
|
620
560
|
- use_skill \u2014 load a project skill's full steps by name, then carry them out with run_bash.
|
|
621
561
|
- run_bash \u2014 run a shell command on the session's machine (when granted).
|
|
622
|
-
- run_js \u2014 JavaScript with an async \`svamp\` API to compose several steps in one call (when granted).
|
|
623
562
|
- send_to_session \u2014 hand a clear, reformulated instruction to the deep coding agent (when granted); pass wait=true to block for its reply.
|
|
624
563
|
|
|
625
564
|
# Instructions
|
|
@@ -733,7 +672,7 @@ async function runWiseAgent(args) {
|
|
|
733
672
|
}
|
|
734
673
|
|
|
735
674
|
const MACHINE_READ_ONLY = ["daemon_status", "list_sessions", "read_session", "use_skill"];
|
|
736
|
-
const MACHINE_MUTATING = ["run_bash", "
|
|
675
|
+
const MACHINE_MUTATING = ["run_bash", "send_to_session", "spawn_session"];
|
|
737
676
|
const ALL_MACHINE_TOOLS = [...MACHINE_READ_ONLY, ...MACHINE_MUTATING];
|
|
738
677
|
const str = (v) => v == null ? "" : String(v);
|
|
739
678
|
function machineToolsForRole(role) {
|
|
@@ -741,16 +680,6 @@ function machineToolsForRole(role) {
|
|
|
741
680
|
if (role === "interact") return [...MACHINE_READ_ONLY, "send_to_session"];
|
|
742
681
|
return [...MACHINE_READ_ONLY];
|
|
743
682
|
}
|
|
744
|
-
function machineSvampApi(deps) {
|
|
745
|
-
return {
|
|
746
|
-
bash: (command, opts) => deps.runBash(command, opts),
|
|
747
|
-
status: () => deps.daemonStatus(),
|
|
748
|
-
listSessions: () => deps.listSessions(),
|
|
749
|
-
readSession: (id, opts) => deps.readSession(id, opts),
|
|
750
|
-
sendToSession: (id, message) => deps.sendToSession(id, message),
|
|
751
|
-
spawnSession: (directory) => deps.spawnSession(directory)
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
683
|
function buildMachineTools(deps, skills) {
|
|
755
684
|
return [
|
|
756
685
|
{
|
|
@@ -804,16 +733,6 @@ function buildMachineTools(deps, skills) {
|
|
|
804
733
|
description: "Start a new agent session in a directory on this machine.",
|
|
805
734
|
parameters: { type: "object", properties: { directory: { type: "string" } }, required: ["directory"], additionalProperties: false },
|
|
806
735
|
run: async (a) => JSON.stringify(await deps.spawnSession(str(a?.directory)))
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
name: "run_js",
|
|
810
|
-
readOnly: false,
|
|
811
|
-
description: "Run JavaScript with an async `svamp` machine API to compose steps: svamp.bash(cmd), svamp.status(), svamp.listSessions(), svamp.readSession(id), svamp.sendToSession(id,msg), svamp.spawnSession(dir). Use await + return; console.log captured. Sandboxed, ~5s.",
|
|
812
|
-
parameters: { type: "object", properties: { code: { type: "string" } }, required: ["code"], additionalProperties: false },
|
|
813
|
-
run: async (a) => {
|
|
814
|
-
const r = await runJsWithApi(str(a?.code), machineSvampApi(deps), { timeoutMs: 5e3 });
|
|
815
|
-
return JSON.stringify({ result: r.result, logs: r.logs, error: r.error });
|
|
816
|
-
}
|
|
817
736
|
}
|
|
818
737
|
];
|
|
819
738
|
}
|
|
@@ -848,7 +767,7 @@ You are WISE in machine-manager (global) mode \u2014 a fast cockpit over the sva
|
|
|
848
767
|
# Tools
|
|
849
768
|
- daemon_status \u2014 daemon health / version / session count. Use first for machine status.
|
|
850
769
|
- list_sessions / read_session \u2014 see all sessions and inspect any one.
|
|
851
|
-
- run_bash
|
|
770
|
+
- run_bash \u2014 run quick commands / scripts on the machine.
|
|
852
771
|
- send_to_session(id, message) \u2014 delegate a task to a specific session's Claude agent.
|
|
853
772
|
- spawn_session(directory) \u2014 start a new session.
|
|
854
773
|
- use_skill \u2014 load a machine-level procedure.
|
|
@@ -1021,126 +940,6 @@ function buildSessionDeps(rpc, opts = {}) {
|
|
|
1021
940
|
};
|
|
1022
941
|
}
|
|
1023
942
|
|
|
1024
|
-
function toolsForRole(role) {
|
|
1025
|
-
if (role === "admin") return [...READ_ONLY_TOOLS, "run_bash", "send_to_session", "run_js"];
|
|
1026
|
-
if (role === "interact") return [...READ_ONLY_TOOLS, "send_to_session"];
|
|
1027
|
-
return [...READ_ONLY_TOOLS];
|
|
1028
|
-
}
|
|
1029
|
-
function toRealtimeTools(tools) {
|
|
1030
|
-
return tools.map((t) => ({ type: "function", name: t.name, description: t.description, parameters: t.parameters }));
|
|
1031
|
-
}
|
|
1032
|
-
function buildVoiceSessionUpdate(instructions, tools, opts) {
|
|
1033
|
-
return {
|
|
1034
|
-
type: "session.update",
|
|
1035
|
-
session: {
|
|
1036
|
-
instructions,
|
|
1037
|
-
tools: toRealtimeTools(tools),
|
|
1038
|
-
tool_choice: "auto",
|
|
1039
|
-
...opts?.voice ? { audio: { output: { voice: opts.voice } } } : {}
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
async function handleRealtimeEvent(ev, tools, send, nameByCallId) {
|
|
1044
|
-
if (ev?.type === "response.output_item.added" && ev.item?.type === "function_call") {
|
|
1045
|
-
if (nameByCallId && ev.item.call_id) nameByCallId.set(ev.item.call_id, ev.item.name);
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1048
|
-
if (!ev || ev.type !== "response.function_call_arguments.done") return null;
|
|
1049
|
-
const callId = ev.call_id;
|
|
1050
|
-
const name = ev.name || nameByCallId && nameByCallId.get(callId) || "";
|
|
1051
|
-
let args = {};
|
|
1052
|
-
try {
|
|
1053
|
-
args = ev.arguments ? JSON.parse(ev.arguments) : {};
|
|
1054
|
-
} catch {
|
|
1055
|
-
}
|
|
1056
|
-
const tool = tools.find((t) => t.name === name);
|
|
1057
|
-
let output;
|
|
1058
|
-
if (!tool) {
|
|
1059
|
-
output = `error: tool "${name}" is not available to this caller`;
|
|
1060
|
-
} else {
|
|
1061
|
-
try {
|
|
1062
|
-
output = await tool.run(args);
|
|
1063
|
-
} catch (e) {
|
|
1064
|
-
output = `error: ${e?.message || e}`;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
send({ type: "conversation.item.create", item: { type: "function_call_output", call_id: callId, output } });
|
|
1068
|
-
send({ type: "response.create" });
|
|
1069
|
-
return { name, output };
|
|
1070
|
-
}
|
|
1071
|
-
async function initVoiceSession(init) {
|
|
1072
|
-
const ctx = await loadWiseAgentContext(init.deps, init.config);
|
|
1073
|
-
const instructions = buildWiseAgentInstructions(ctx, init.config);
|
|
1074
|
-
const granted = gateTools(buildTools(init.deps, ctx.skills), init.config, init.sender.name);
|
|
1075
|
-
return { tools: granted, sessionUpdate: buildVoiceSessionUpdate(instructions, granted, { voice: init.voice }) };
|
|
1076
|
-
}
|
|
1077
|
-
async function initMachineVoiceSession(init) {
|
|
1078
|
-
const ctx = await loadMachineContext(init.deps);
|
|
1079
|
-
const instructions = buildMachineInstructions(ctx);
|
|
1080
|
-
const allow = new Set(machineToolsForRole(init.role ?? "admin"));
|
|
1081
|
-
const granted = buildMachineTools(init.deps, ctx.skills).filter((t) => allow.has(t.name));
|
|
1082
|
-
return { tools: granted, sessionUpdate: buildVoiceSessionUpdate(instructions, granted, { voice: init.voice }) };
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
var sideband = /*#__PURE__*/Object.freeze({
|
|
1086
|
-
__proto__: null,
|
|
1087
|
-
buildVoiceSessionUpdate: buildVoiceSessionUpdate,
|
|
1088
|
-
handleRealtimeEvent: handleRealtimeEvent,
|
|
1089
|
-
initMachineVoiceSession: initMachineVoiceSession,
|
|
1090
|
-
initVoiceSession: initVoiceSession,
|
|
1091
|
-
toRealtimeTools: toRealtimeTools,
|
|
1092
|
-
toolsForRole: toolsForRole
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
const realFactory = (url, headers) => new WebSocket(url, { headers });
|
|
1096
|
-
let _testFactory;
|
|
1097
|
-
async function openSideband(opts) {
|
|
1098
|
-
const base = (opts.baseUrl || "https://api.openai.com").replace(/^http/, "ws").replace(/\/+$/, "");
|
|
1099
|
-
const url = `${base}/v1/realtime?call_id=${encodeURIComponent(opts.callId)}`;
|
|
1100
|
-
const { tools, sessionUpdate } = opts.prepared ?? await initVoiceSession(opts.init);
|
|
1101
|
-
const ws = (opts.wsFactory || _testFactory || realFactory)(url, { Authorization: `Bearer ${opts.apiKey}` });
|
|
1102
|
-
const send = (e) => {
|
|
1103
|
-
try {
|
|
1104
|
-
ws.send(JSON.stringify(e));
|
|
1105
|
-
} catch {
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
const nameByCallId = /* @__PURE__ */ new Map();
|
|
1109
|
-
ws.on("open", () => send(sessionUpdate));
|
|
1110
|
-
const wantTools = new Set(tools.map((t) => t.name));
|
|
1111
|
-
let reasserts = 0;
|
|
1112
|
-
ws.on("message", async (data) => {
|
|
1113
|
-
let ev;
|
|
1114
|
-
try {
|
|
1115
|
-
ev = JSON.parse(typeof data === "string" ? data : data?.toString?.() ?? "");
|
|
1116
|
-
} catch {
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
if (ev?.type === "session.updated" && wantTools.size) {
|
|
1120
|
-
const have = new Set((ev.session?.tools || []).map((t) => t?.name));
|
|
1121
|
-
const missing = [...wantTools].some((n) => !have.has(n));
|
|
1122
|
-
if (missing && reasserts < 10) {
|
|
1123
|
-
reasserts++;
|
|
1124
|
-
send(sessionUpdate);
|
|
1125
|
-
return;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
try {
|
|
1129
|
-
await handleRealtimeEvent(ev, tools, send, nameByCallId);
|
|
1130
|
-
} catch (e) {
|
|
1131
|
-
opts.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
|
-
ws.on("close", () => opts.onClose?.());
|
|
1135
|
-
ws.on("error", (e) => opts.onError?.(e instanceof Error ? e : new Error(String(e))));
|
|
1136
|
-
return { callId: opts.callId, close: () => {
|
|
1137
|
-
try {
|
|
1138
|
-
ws.close();
|
|
1139
|
-
} catch {
|
|
1140
|
-
}
|
|
1141
|
-
} };
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
943
|
const execFileAsync$1 = promisify(execFile);
|
|
1145
944
|
function parseEtime(s) {
|
|
1146
945
|
if (!s) return 0;
|
|
@@ -1265,6 +1064,31 @@ function filterTerminalResponses(data) {
|
|
|
1265
1064
|
function getMachineMetadataPath(svampHomeDir) {
|
|
1266
1065
|
return join(svampHomeDir, "machine-metadata.json");
|
|
1267
1066
|
}
|
|
1067
|
+
async function mintRealtimeEphemeralKey(baseUrl, apiKey, opts) {
|
|
1068
|
+
const realtimeBase = baseUrl || "https://api.openai.com";
|
|
1069
|
+
const ctrl = new AbortController();
|
|
1070
|
+
const timer = setTimeout(() => ctrl.abort(), 15e3);
|
|
1071
|
+
try {
|
|
1072
|
+
const response = await fetch(`${realtimeBase}/v1/realtime/client_secrets`, {
|
|
1073
|
+
method: "POST",
|
|
1074
|
+
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1075
|
+
body: JSON.stringify({
|
|
1076
|
+
session: {
|
|
1077
|
+
type: "realtime",
|
|
1078
|
+
model: opts.model || "gpt-realtime-mini",
|
|
1079
|
+
...opts.voice ? { audio: { output: { voice: opts.voice } } } : {}
|
|
1080
|
+
}
|
|
1081
|
+
}),
|
|
1082
|
+
signal: ctrl.signal
|
|
1083
|
+
});
|
|
1084
|
+
if (!response.ok) throw new Error(`OpenAI client_secrets error: ${response.status}`);
|
|
1085
|
+
const result = await response.json();
|
|
1086
|
+
if (!result.value) throw new Error("client_secrets returned no value");
|
|
1087
|
+
return result.value;
|
|
1088
|
+
} finally {
|
|
1089
|
+
clearTimeout(timer);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1268
1092
|
function loadPersistedMachineMetadata(svampHomeDir) {
|
|
1269
1093
|
try {
|
|
1270
1094
|
const data = readFileSync$1(getMachineMetadataPath(svampHomeDir), "utf-8");
|
|
@@ -1294,7 +1118,6 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1294
1118
|
lastInboundRpcAt = Date.now();
|
|
1295
1119
|
};
|
|
1296
1120
|
const listeners = [];
|
|
1297
|
-
const voiceSidebands = /* @__PURE__ */ new Map();
|
|
1298
1121
|
const removeListener = (listener, reason) => {
|
|
1299
1122
|
const idx = listeners.indexOf(listener);
|
|
1300
1123
|
if (idx >= 0) {
|
|
@@ -2230,7 +2053,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2230
2053
|
const tunnels = handlers.tunnels;
|
|
2231
2054
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
2232
2055
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
2233
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
2056
|
+
const { FrpcTunnel } = await import('./frpc-glQKHUBu.mjs');
|
|
2234
2057
|
const tunnel = new FrpcTunnel({
|
|
2235
2058
|
name: params.name,
|
|
2236
2059
|
ports: params.ports,
|
|
@@ -2380,123 +2203,13 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2380
2203
|
if (misconfig || !resolved.apiKey) {
|
|
2381
2204
|
return { success: false, error: misconfig || "WISE voice is not configured: no OpenAI API key on this machine. Run `svamp wise-agent auth use-openai <KEY>` (or set OPENAI_API_KEY), then `svamp daemon restart`." };
|
|
2382
2205
|
}
|
|
2383
|
-
const realtimeBase = resolved.baseUrl || "https://api.openai.com";
|
|
2384
2206
|
try {
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
let response;
|
|
2388
|
-
try {
|
|
2389
|
-
response = await fetch(`${realtimeBase}/v1/realtime/client_secrets`, {
|
|
2390
|
-
method: "POST",
|
|
2391
|
-
headers: {
|
|
2392
|
-
"Authorization": `Bearer ${resolved.apiKey}`,
|
|
2393
|
-
"Content-Type": "application/json"
|
|
2394
|
-
},
|
|
2395
|
-
body: JSON.stringify({
|
|
2396
|
-
session: {
|
|
2397
|
-
type: "realtime",
|
|
2398
|
-
model: params.model || "gpt-realtime-mini",
|
|
2399
|
-
...params.voice ? { audio: { output: { voice: params.voice } } } : {}
|
|
2400
|
-
}
|
|
2401
|
-
}),
|
|
2402
|
-
signal: wisCtrl.signal
|
|
2403
|
-
});
|
|
2404
|
-
} finally {
|
|
2405
|
-
clearTimeout(wisTimer);
|
|
2406
|
-
}
|
|
2407
|
-
if (!response.ok) {
|
|
2408
|
-
return { success: false, error: `OpenAI API error: ${response.status}` };
|
|
2409
|
-
}
|
|
2410
|
-
const result = await response.json();
|
|
2411
|
-
return { success: true, clientSecret: result.value };
|
|
2207
|
+
const clientSecret = await mintRealtimeEphemeralKey(resolved.baseUrl, resolved.apiKey, { model: params.model, voice: params.voice });
|
|
2208
|
+
return { success: true, clientSecret };
|
|
2412
2209
|
} catch (error) {
|
|
2413
2210
|
return { success: false, error: error instanceof Error ? error.message : "Failed to create token" };
|
|
2414
2211
|
}
|
|
2415
2212
|
},
|
|
2416
|
-
// Attach the daemon's server-side control to an in-flight Realtime voice
|
|
2417
|
-
// session (browser holds the WebRTC audio leg). The browser passes the
|
|
2418
|
-
// `call_id` it got from OpenAI's SDP answer; the daemon opens the sideband
|
|
2419
|
-
// WS and runs WISE tools server-side, gated by the caller's session role.
|
|
2420
|
-
wiseAttachSideband: async (params, context) => {
|
|
2421
|
-
trackInbound();
|
|
2422
|
-
if (context && (!context.user || context.user.is_anonymous)) {
|
|
2423
|
-
return { success: false, error: "Sign in to use WISE voice." };
|
|
2424
|
-
}
|
|
2425
|
-
if (!params?.callId) return { success: false, error: "callId is required" };
|
|
2426
|
-
const resolved = resolveModel({ provider: "openai" }, process.env);
|
|
2427
|
-
const misconfig = describeMisconfiguration(resolved);
|
|
2428
|
-
if (misconfig || !resolved.apiKey) {
|
|
2429
|
-
return { success: false, error: misconfig || "WISE voice is not configured on this machine." };
|
|
2430
|
-
}
|
|
2431
|
-
const userKey = (context?.user?.email || context?.user?.id || "anon").toLowerCase();
|
|
2432
|
-
const senderName = context?.user?.email || context?.user?.id || "user";
|
|
2433
|
-
let prepared;
|
|
2434
|
-
let role;
|
|
2435
|
-
if (params.sessionId) {
|
|
2436
|
-
const rpc = handlers.getSessionRPCHandlers?.(params.sessionId);
|
|
2437
|
-
if (!rpc) return { success: false, error: "Session not found on this machine" };
|
|
2438
|
-
let cwd;
|
|
2439
|
-
let owner;
|
|
2440
|
-
try {
|
|
2441
|
-
role = (await rpc.getEffectiveRole(context))?.role ?? null;
|
|
2442
|
-
if (!role) return { success: false, error: "Not authorized for this session" };
|
|
2443
|
-
const metaRes = await rpc.getMetadata(context);
|
|
2444
|
-
cwd = metaRes?.metadata?.path;
|
|
2445
|
-
owner = metaRes?.metadata?.sharing?.owner;
|
|
2446
|
-
} catch {
|
|
2447
|
-
return { success: false, error: "Not authorized for this session" };
|
|
2448
|
-
}
|
|
2449
|
-
try {
|
|
2450
|
-
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
2451
|
-
prepared = await initVoiceSession({ deps, config: { tools: toolsForRole(role) }, sender: { name: senderName, kind: "user", verified: true }, voice: params.voice });
|
|
2452
|
-
} catch (e) {
|
|
2453
|
-
return { success: false, error: e?.message || "Failed to prepare voice session" };
|
|
2454
|
-
}
|
|
2455
|
-
} else {
|
|
2456
|
-
role = getEffectiveRole(context, currentMetadata.sharing);
|
|
2457
|
-
if (!role) return { success: false, error: "Not authorized on this machine" };
|
|
2458
|
-
try {
|
|
2459
|
-
const machineDeps = buildMachineDeps(
|
|
2460
|
-
{
|
|
2461
|
-
getSessionIds: handlers.getSessionIds,
|
|
2462
|
-
getSessionRPCHandlers: handlers.getSessionRPCHandlers,
|
|
2463
|
-
getTrackedSessions: handlers.getTrackedSessions,
|
|
2464
|
-
spawnSession: handlers.spawnSession,
|
|
2465
|
-
getDaemonStatus: () => ({ status: currentDaemonState.status, machineId, pid: currentDaemonState.pid })
|
|
2466
|
-
},
|
|
2467
|
-
{ cwd: process.env.HOME, ownerEmail: currentMetadata.sharing?.owner }
|
|
2468
|
-
);
|
|
2469
|
-
prepared = await initMachineVoiceSession({ deps: machineDeps, role, voice: params.voice });
|
|
2470
|
-
} catch (e) {
|
|
2471
|
-
return { success: false, error: e?.message || "Failed to prepare voice session" };
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
voiceSidebands.get(userKey)?.close();
|
|
2475
|
-
voiceSidebands.delete(userKey);
|
|
2476
|
-
try {
|
|
2477
|
-
const handle = await openSideband({
|
|
2478
|
-
callId: params.callId,
|
|
2479
|
-
apiKey: resolved.apiKey,
|
|
2480
|
-
baseUrl: resolved.baseUrl,
|
|
2481
|
-
prepared,
|
|
2482
|
-
onClose: () => {
|
|
2483
|
-
if (voiceSidebands.get(userKey)?.callId === params.callId) voiceSidebands.delete(userKey);
|
|
2484
|
-
}
|
|
2485
|
-
});
|
|
2486
|
-
voiceSidebands.set(userKey, handle);
|
|
2487
|
-
return { success: true, role, scope: params.sessionId ? "session" : "machine" };
|
|
2488
|
-
} catch (e) {
|
|
2489
|
-
return { success: false, error: e?.message || "Failed to attach voice sideband" };
|
|
2490
|
-
}
|
|
2491
|
-
},
|
|
2492
|
-
// Explicit teardown of the caller's active voice sideband (e.g. on hang-up).
|
|
2493
|
-
wiseReleaseVoice: async (_params, context) => {
|
|
2494
|
-
trackInbound();
|
|
2495
|
-
const userKey = (context?.user?.email || context?.user?.id || "anon").toLowerCase();
|
|
2496
|
-
voiceSidebands.get(userKey)?.close();
|
|
2497
|
-
voiceSidebands.delete(userKey);
|
|
2498
|
-
return { success: true };
|
|
2499
|
-
},
|
|
2500
2213
|
// Text WISE turn. GLOBAL (machine-manager) by default; pass sessionId to
|
|
2501
2214
|
// scope to a session. Runs server-side and returns the reply synchronously.
|
|
2502
2215
|
wiseAsk: async (params, context) => {
|
|
@@ -2528,8 +2241,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2528
2241
|
}
|
|
2529
2242
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
2530
2243
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
2531
|
-
const { toolsForRole
|
|
2532
|
-
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools:
|
|
2244
|
+
const { toolsForRole } = await import('./sideband-7glSvpTW.mjs');
|
|
2245
|
+
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
2533
2246
|
return fmt(r2);
|
|
2534
2247
|
}
|
|
2535
2248
|
const role = getEffectiveRole(context, currentMetadata.sharing);
|
|
@@ -2587,6 +2300,19 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2587
2300
|
if (!rpc?.channelDescribe) return { error: "channel not found" };
|
|
2588
2301
|
return rpc.channelDescribe(kwargs.channel);
|
|
2589
2302
|
},
|
|
2303
|
+
// Clean GET endpoint for the self-contained skill markdown. Param is
|
|
2304
|
+
// named `channel` (not `kwargs`) so the Hypha HTTP gateway maps a plain
|
|
2305
|
+
// `?channel=<id>` query → a tidy, shareable SKILL.md URL.
|
|
2306
|
+
skill: async (channel) => {
|
|
2307
|
+
const id = channel;
|
|
2308
|
+
if (!id) return "# error\nMissing ?channel=<id>";
|
|
2309
|
+
const rpc = await findChannelOwner(id);
|
|
2310
|
+
if (!rpc?.channelDescribe) return `# error
|
|
2311
|
+
channel "${id}" not found`;
|
|
2312
|
+
const d = await rpc.channelDescribe(id);
|
|
2313
|
+
return d?.skill?.body || `# error
|
|
2314
|
+
${d?.error || "not found"}`;
|
|
2315
|
+
},
|
|
2590
2316
|
send: async (kwargs = {}, context) => {
|
|
2591
2317
|
trackInbound();
|
|
2592
2318
|
const rpc = await findChannelOwner(kwargs.channel);
|
|
@@ -2624,13 +2350,6 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2624
2350
|
for (const listener of toRemove) {
|
|
2625
2351
|
removeListener(listener, "disconnect");
|
|
2626
2352
|
}
|
|
2627
|
-
for (const h of voiceSidebands.values()) {
|
|
2628
|
-
try {
|
|
2629
|
-
h.close();
|
|
2630
|
-
} catch {
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
voiceSidebands.clear();
|
|
2634
2353
|
await server.unregisterService(serviceInfo.id);
|
|
2635
2354
|
await server.unregisterService(channelsServiceInfo.id).catch(() => {
|
|
2636
2355
|
});
|
|
@@ -2955,7 +2674,7 @@ function validateChannel(c) {
|
|
|
2955
2674
|
if (c.action?.kind === "loop" && m === "caller-supplied" && !c.identity?.shared_key)
|
|
2956
2675
|
errs.push("a caller-supplied channel without a shared_key may not use a loop action (unauthenticated task injection)");
|
|
2957
2676
|
if (c.action?.kind === "agent" && m === "caller-supplied" && !c.identity?.shared_key) {
|
|
2958
|
-
const MUTATING = ["run_bash", "send_to_session"
|
|
2677
|
+
const MUTATING = ["run_bash", "send_to_session"];
|
|
2959
2678
|
const ag = c.action.agent || {};
|
|
2960
2679
|
const grantsMutating = (ag.tools || []).some((t) => MUTATING.includes(t)) || Object.values(ag.per_caller || {}).some((p) => (p?.tools || []).some((t) => MUTATING.includes(t)));
|
|
2961
2680
|
if (grantsMutating) errs.push("a caller-supplied agent channel without a shared_key may not grant run_bash/send_to_session");
|
|
@@ -3041,25 +2760,53 @@ class ChannelStore {
|
|
|
3041
2760
|
return caller;
|
|
3042
2761
|
}
|
|
3043
2762
|
}
|
|
3044
|
-
function
|
|
3045
|
-
const
|
|
2763
|
+
function gatewayBase(channelsServiceId, baseUrl) {
|
|
2764
|
+
const slash = channelsServiceId.indexOf("/");
|
|
2765
|
+
if (slash < 0) return `${baseUrl.replace(/\/$/, "")}/services/${channelsServiceId}`;
|
|
2766
|
+
const ws = channelsServiceId.slice(0, slash);
|
|
2767
|
+
const clientSvc = channelsServiceId.slice(slash + 1);
|
|
2768
|
+
return `${baseUrl.replace(/\/$/, "")}/${ws}/services/${clientSvc}`;
|
|
2769
|
+
}
|
|
2770
|
+
function generateSkillBody(channel, ctx) {
|
|
2771
|
+
const svc = ctx?.channelsServiceId || "<workspace>/<machine>:channels";
|
|
2772
|
+
const base = ctx?.baseUrl || "https://hypha.aicell.io";
|
|
2773
|
+
const gw = ctx?.channelsServiceId ? gatewayBase(svc, base) : `${base}/<workspace>/services/<machine>:channels`;
|
|
2774
|
+
const skillUrl = `${gw}/skill?channel=${channel.id}`;
|
|
2775
|
+
const sendUrl = `${gw}/send`;
|
|
2776
|
+
const key = ctx?.key || "<your-key>";
|
|
3046
2777
|
const isAgent = channel.action?.kind === "agent";
|
|
3047
|
-
const
|
|
3048
|
-
|
|
3049
|
-
|
|
2778
|
+
const hyphaOpen = (channel.identity?.hypha_allow || []).length > 0;
|
|
2779
|
+
const name = channel.skill?.name || channel.name;
|
|
2780
|
+
const desc = channel.skill?.description || channel.description || `Send a message to the "${channel.name}" channel.`;
|
|
2781
|
+
const replyNote = isAgent ? `This is a **WISE Agent** channel: \`send()\` runs a fast assistant against the session's tools/skills and returns its answer synchronously in the result \`reply\`.` : `Delivery is fire-and-forget \u2014 the message lands in the agent's inbox, tagged with your verified identity (\`from\`).`;
|
|
2782
|
+
const rpcLine = hyphaOpen ? `**Hypha RPC** \u2014 preferred. Your verified Hypha identity is accepted, no key needed:` : `**Hypha RPC** \u2014 verified identity:`;
|
|
3050
2783
|
return `---
|
|
3051
|
-
name: ${
|
|
3052
|
-
description: ${
|
|
2784
|
+
name: ${name}
|
|
2785
|
+
description: ${desc}
|
|
3053
2786
|
---
|
|
3054
|
-
# ${
|
|
2787
|
+
# ${name}
|
|
3055
2788
|
${channel.description || ""}
|
|
3056
2789
|
|
|
3057
|
-
Self-contained guide for messaging
|
|
3058
|
-
|
|
3059
|
-
${
|
|
2790
|
+
Self-contained guide for messaging the **${channel.name}** channel. ${replyNote}
|
|
2791
|
+
This skill (with every value below already filled in) is served at:
|
|
2792
|
+
${skillUrl}
|
|
2793
|
+
|
|
2794
|
+
${rpcLine}
|
|
2795
|
+
\`\`\`js
|
|
2796
|
+
await get_service("${svc}").send({
|
|
2797
|
+
channel: "${channel.id}",
|
|
2798
|
+
message: "your message here",
|
|
2799
|
+
from: "your-name",
|
|
2800
|
+
});
|
|
2801
|
+
\`\`\`
|
|
2802
|
+
|
|
2803
|
+
**HTTP** \u2014 any client, no Hypha SDK needed (Hypha gateway wraps args under \`kwargs\`):
|
|
2804
|
+
\`\`\`
|
|
2805
|
+
POST ${sendUrl}
|
|
2806
|
+
Content-Type: application/json
|
|
3060
2807
|
|
|
3061
|
-
|
|
3062
|
-
|
|
2808
|
+
{"kwargs": {"channel": "${channel.id}", "message": "your message here", "from": "your-name", "key": "${key}"}}
|
|
2809
|
+
\`\`\``;
|
|
3063
2810
|
}
|
|
3064
2811
|
|
|
3065
2812
|
function resolveSender(channel, input = {}) {
|
|
@@ -3337,6 +3084,19 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3337
3084
|
callbacks.onMetadataUpdate?.(metadata);
|
|
3338
3085
|
};
|
|
3339
3086
|
const channelStore = new ChannelStore(initialMetadata.path);
|
|
3087
|
+
const cfg = server?.config || {};
|
|
3088
|
+
const channelsServiceId = cfg.workspace && cfg.client_id ? `${cfg.workspace}/${cfg.client_id}:channels` : void 0;
|
|
3089
|
+
const channelsBaseUrl = cfg.public_base_url || process.env.HYPHA_SERVER_URL || "https://hypha.aicell.io";
|
|
3090
|
+
const skillCtxFor = (c) => ({
|
|
3091
|
+
channelsServiceId,
|
|
3092
|
+
baseUrl: channelsBaseUrl,
|
|
3093
|
+
key: c.identity?.shared_key || c.identity?.callers?.[0]?.key
|
|
3094
|
+
});
|
|
3095
|
+
const skillUrlsFor = (c) => {
|
|
3096
|
+
if (!channelsServiceId) return { skillUrl: void 0, sendUrl: void 0 };
|
|
3097
|
+
const gw = gatewayBase(channelsServiceId, channelsBaseUrl);
|
|
3098
|
+
return { skillUrl: `${gw}/describe?channel=${c.id}`, sendUrl: `${gw}/send` };
|
|
3099
|
+
};
|
|
3340
3100
|
const syncChannelsToMetadata = () => {
|
|
3341
3101
|
metadata.channels = channelStore.list();
|
|
3342
3102
|
metadataVersion++;
|
|
@@ -3550,7 +3310,17 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3550
3310
|
authorizeRequest(context, metadata.sharing, "view");
|
|
3551
3311
|
const c = channelStore.get(id);
|
|
3552
3312
|
if (!c) return { error: "not found" };
|
|
3553
|
-
|
|
3313
|
+
const { skillUrl, sendUrl } = skillUrlsFor(c);
|
|
3314
|
+
return {
|
|
3315
|
+
skill: generateSkillBody(c, skillCtxFor(c)),
|
|
3316
|
+
channelsServiceId,
|
|
3317
|
+
channelId: c.id,
|
|
3318
|
+
name: c.name,
|
|
3319
|
+
skillUrl,
|
|
3320
|
+
sendUrl,
|
|
3321
|
+
key: c.identity?.shared_key || c.identity?.callers?.[0]?.key,
|
|
3322
|
+
hyphaKeyless: (c.identity?.hypha_allow || []).length > 0
|
|
3323
|
+
};
|
|
3554
3324
|
},
|
|
3555
3325
|
// Public channel discovery (no session authz — channels are deliberately
|
|
3556
3326
|
// published endpoints; each send() is gated by the channel's OWN identity).
|
|
@@ -3560,7 +3330,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3560
3330
|
channelDescribe: async (id) => {
|
|
3561
3331
|
const c = channelStore.get(id);
|
|
3562
3332
|
if (!c || c.enabled === false) return { error: "not found" };
|
|
3563
|
-
return { ...channelPublicView(c), skill: { body: generateSkillBody(c) } };
|
|
3333
|
+
return { ...channelPublicView(c), skill: { body: generateSkillBody(c, skillCtxFor(c)) } };
|
|
3564
3334
|
},
|
|
3565
3335
|
channelSend: async (params, context) => {
|
|
3566
3336
|
const c = channelStore.get(params.channel);
|
|
@@ -10207,7 +9977,7 @@ async function startDaemon(options) {
|
|
|
10207
9977
|
const list = loadExposedTunnels().filter((t) => t.name !== name);
|
|
10208
9978
|
saveExposedTunnels(list);
|
|
10209
9979
|
}
|
|
10210
|
-
const { ServeManager } = await import('./serveManager-
|
|
9980
|
+
const { ServeManager } = await import('./serveManager-CoVwgYS_.mjs');
|
|
10211
9981
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
10212
9982
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
10213
9983
|
});
|
|
@@ -12835,7 +12605,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12835
12605
|
const specs = loadExposedTunnels();
|
|
12836
12606
|
if (specs.length === 0) return;
|
|
12837
12607
|
logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
|
|
12838
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
12608
|
+
const { FrpcTunnel } = await import('./frpc-glQKHUBu.mjs');
|
|
12839
12609
|
for (const spec of specs) {
|
|
12840
12610
|
if (tunnels.has(spec.name)) continue;
|
|
12841
12611
|
try {
|
|
@@ -13607,4 +13377,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
13607
13377
|
writeStopMarker: writeStopMarker
|
|
13608
13378
|
});
|
|
13609
13379
|
|
|
13610
|
-
export {
|
|
13380
|
+
export { loadMachineContext as A, buildMachineInstructions as B, machineToolsForRole as C, buildMachineTools as D, resolveModel as E, normalizeAllowedUser as F, loadSecurityContextConfig as G, resolveSecurityContext as H, buildSecurityContextFromFlags as I, mergeSecurityContexts as J, buildSessionShareUrl as K, computeOutboundHop as L, buildMachineShareUrl as M, describeMisconfiguration as N, buildMachineDeps as O, generateHookSettings as P, DefaultTransport$1 as Q, RoutineStore as R, ServeAuth as S, acpBackend as T, acpAgentConfig as U, codexMcpBackend as V, GeminiTransport$1 as W, claudeAuth as X, instanceConfig as Y, api as Z, run as _, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, clearStopMarker as e, stopMarkerExists as f, getHyphaServerUrl$1 as g, getFrpsSubdomainHost as h, getFrpsServerPort as i, getFrpsServerAddr as j, getHyphaServerUrl as k, hasCookieToken as l, RoutineRunner as m, getSkillsServer as n, getSkillsWorkspaceName as o, parseFrontmatter as p, getSkillsCollectionName as q, registerMachineService as r, startDaemon as s, fetchWithTimeout as t, searchSkills as u, SKILLS_DIR as v, getSkillInfo as w, downloadSkillFile as x, listSkillFiles as y, READ_ONLY_TOOLS as z };
|