svamp-cli 0.1.70 → 0.1.72
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/cli.mjs +19 -19
- package/dist/{commands-CF32XIau.mjs → commands-BLjcT1Vl.mjs} +2 -2
- package/dist/{commands-BodoXVL9.mjs → commands-COX5-rj3.mjs} +48 -20
- package/dist/{commands-CIgrbviM.mjs → commands-DMhuR7JE.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-B0d2rUXI.mjs → package-BppQHKG7.mjs} +2 -2
- package/dist/{run-CgSj6KtU.mjs → run-D9fkwRb3.mjs} +2 -2
- package/dist/{run-C7CEDmD4.mjs → run-p-RshVII.mjs} +159 -35
- package/dist/{tunnel-C3UsqTxi.mjs → tunnel-C2kqST5d.mjs} +65 -31
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-
|
|
1
|
+
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-p-RshVII.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -106,10 +106,10 @@ async function main() {
|
|
|
106
106
|
} else if (subcommand === "skills") {
|
|
107
107
|
await handleSkillsCommand();
|
|
108
108
|
} else if (subcommand === "service" || subcommand === "svc") {
|
|
109
|
-
const { handleServiceCommand } = await import('./commands-
|
|
109
|
+
const { handleServiceCommand } = await import('./commands-BLjcT1Vl.mjs');
|
|
110
110
|
await handleServiceCommand();
|
|
111
111
|
} else if (subcommand === "process" || subcommand === "proc") {
|
|
112
|
-
const { processCommand } = await import('./commands-
|
|
112
|
+
const { processCommand } = await import('./commands-DMhuR7JE.mjs');
|
|
113
113
|
let machineId;
|
|
114
114
|
const processArgs = args.slice(1);
|
|
115
115
|
const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
|
|
@@ -127,7 +127,7 @@ async function main() {
|
|
|
127
127
|
} else if (!subcommand || subcommand === "start") {
|
|
128
128
|
await handleInteractiveCommand();
|
|
129
129
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
130
|
-
const pkg = await import('./package-
|
|
130
|
+
const pkg = await import('./package-BppQHKG7.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
131
131
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
132
132
|
} else {
|
|
133
133
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -136,7 +136,7 @@ async function main() {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
async function handleInteractiveCommand() {
|
|
139
|
-
const { runInteractive } = await import('./run-
|
|
139
|
+
const { runInteractive } = await import('./run-D9fkwRb3.mjs');
|
|
140
140
|
const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
|
|
141
141
|
let directory = process.cwd();
|
|
142
142
|
let resumeSessionId;
|
|
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
|
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
if (agentArgs[0] === "list") {
|
|
184
|
-
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-
|
|
184
|
+
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-p-RshVII.mjs').then(function (n) { return n.i; });
|
|
185
185
|
console.log("Known agents:");
|
|
186
186
|
for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
|
|
187
187
|
console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
|
|
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
|
|
|
193
193
|
console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
|
-
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-
|
|
196
|
+
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-p-RshVII.mjs').then(function (n) { return n.i; });
|
|
197
197
|
let cwd = process.cwd();
|
|
198
198
|
const filteredArgs = [];
|
|
199
199
|
for (let i = 0; i < agentArgs.length; i++) {
|
|
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
|
|
|
217
217
|
console.log(`Starting ${config.agentName} agent in ${cwd}...`);
|
|
218
218
|
let backend;
|
|
219
219
|
if (KNOWN_MCP_AGENTS[config.agentName]) {
|
|
220
|
-
const { CodexMcpBackend } = await import('./run-
|
|
220
|
+
const { CodexMcpBackend } = await import('./run-p-RshVII.mjs').then(function (n) { return n.j; });
|
|
221
221
|
backend = new CodexMcpBackend({ cwd, log: logFn });
|
|
222
222
|
} else {
|
|
223
|
-
const { AcpBackend } = await import('./run-
|
|
224
|
-
const { GeminiTransport } = await import('./run-
|
|
225
|
-
const { DefaultTransport } = await import('./run-
|
|
223
|
+
const { AcpBackend } = await import('./run-p-RshVII.mjs').then(function (n) { return n.h; });
|
|
224
|
+
const { GeminiTransport } = await import('./run-p-RshVII.mjs').then(function (n) { return n.G; });
|
|
225
|
+
const { DefaultTransport } = await import('./run-p-RshVII.mjs').then(function (n) { return n.D; });
|
|
226
226
|
const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
|
|
227
227
|
backend = new AcpBackend({
|
|
228
228
|
agentName: config.agentName,
|
|
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
|
|
|
340
340
|
printSessionHelp();
|
|
341
341
|
return;
|
|
342
342
|
}
|
|
343
|
-
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-
|
|
343
|
+
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-COX5-rj3.mjs');
|
|
344
344
|
const parseFlagStr = (flag, shortFlag) => {
|
|
345
345
|
for (let i = 1; i < sessionArgs.length; i++) {
|
|
346
346
|
if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
|
|
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
|
|
|
400
400
|
allowDomain.push(sessionArgs[++i]);
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
-
const { parseShareArg } = await import('./commands-
|
|
403
|
+
const { parseShareArg } = await import('./commands-COX5-rj3.mjs');
|
|
404
404
|
const shareEntries = share.map((s) => parseShareArg(s));
|
|
405
405
|
await sessionSpawn(agent, dir, targetMachineId, {
|
|
406
406
|
message,
|
|
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
|
|
|
484
484
|
console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
|
|
485
485
|
process.exit(1);
|
|
486
486
|
}
|
|
487
|
-
const { sessionApprove } = await import('./commands-
|
|
487
|
+
const { sessionApprove } = await import('./commands-COX5-rj3.mjs');
|
|
488
488
|
const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
489
489
|
await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
|
|
490
490
|
json: hasFlag("--json")
|
|
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
|
|
|
494
494
|
console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
|
|
495
495
|
process.exit(1);
|
|
496
496
|
}
|
|
497
|
-
const { sessionDeny } = await import('./commands-
|
|
497
|
+
const { sessionDeny } = await import('./commands-COX5-rj3.mjs');
|
|
498
498
|
const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
499
499
|
await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
|
|
500
500
|
json: hasFlag("--json")
|
|
@@ -597,7 +597,7 @@ async function handleMachineCommand() {
|
|
|
597
597
|
return;
|
|
598
598
|
}
|
|
599
599
|
if (machineSubcommand === "share") {
|
|
600
|
-
const { machineShare } = await import('./commands-
|
|
600
|
+
const { machineShare } = await import('./commands-COX5-rj3.mjs');
|
|
601
601
|
let machineId;
|
|
602
602
|
const shareArgs = [];
|
|
603
603
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
@@ -627,7 +627,7 @@ async function handleMachineCommand() {
|
|
|
627
627
|
}
|
|
628
628
|
await machineShare(machineId, { add, remove, list, configPath, showConfig });
|
|
629
629
|
} else if (machineSubcommand === "exec") {
|
|
630
|
-
const { machineExec } = await import('./commands-
|
|
630
|
+
const { machineExec } = await import('./commands-COX5-rj3.mjs');
|
|
631
631
|
let machineId;
|
|
632
632
|
let cwd;
|
|
633
633
|
const cmdParts = [];
|
|
@@ -647,7 +647,7 @@ async function handleMachineCommand() {
|
|
|
647
647
|
}
|
|
648
648
|
await machineExec(machineId, command, cwd);
|
|
649
649
|
} else if (machineSubcommand === "info") {
|
|
650
|
-
const { machineInfo } = await import('./commands-
|
|
650
|
+
const { machineInfo } = await import('./commands-COX5-rj3.mjs');
|
|
651
651
|
let machineId;
|
|
652
652
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
653
653
|
if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
|
|
@@ -670,7 +670,7 @@ async function handleMachineCommand() {
|
|
|
670
670
|
const { machineNotify } = await import('./agentCommands-C6iGblcL.mjs');
|
|
671
671
|
await machineNotify(message, level);
|
|
672
672
|
} else if (machineSubcommand === "ls") {
|
|
673
|
-
const { machineLs } = await import('./commands-
|
|
673
|
+
const { machineLs } = await import('./commands-COX5-rj3.mjs');
|
|
674
674
|
let machineId;
|
|
675
675
|
let showHidden = false;
|
|
676
676
|
let path;
|
|
@@ -296,7 +296,7 @@ Service is live:`);
|
|
|
296
296
|
}
|
|
297
297
|
} else {
|
|
298
298
|
console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
|
|
299
|
-
const { runTunnel } = await import('./tunnel-
|
|
299
|
+
const { runTunnel } = await import('./tunnel-C2kqST5d.mjs');
|
|
300
300
|
await runTunnel(name, ports);
|
|
301
301
|
}
|
|
302
302
|
} catch (err) {
|
|
@@ -312,7 +312,7 @@ async function serviceTunnel(args) {
|
|
|
312
312
|
console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
|
|
313
313
|
process.exit(1);
|
|
314
314
|
}
|
|
315
|
-
const { runTunnel } = await import('./tunnel-
|
|
315
|
+
const { runTunnel } = await import('./tunnel-C2kqST5d.mjs');
|
|
316
316
|
await runTunnel(name, ports);
|
|
317
317
|
}
|
|
318
318
|
async function handleServiceCommand() {
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-
|
|
5
|
+
import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-p-RshVII.mjs';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'fs/promises';
|
|
8
8
|
import 'fs';
|
|
@@ -181,28 +181,56 @@ async function connectAndGetMachine(machineId) {
|
|
|
181
181
|
});
|
|
182
182
|
if (prefixMatches.length === 1) {
|
|
183
183
|
selectedService = prefixMatches[0];
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!selectedService) {
|
|
187
|
+
const query = machineId.toLowerCase();
|
|
188
|
+
const resolved = [];
|
|
189
|
+
await Promise.all(services.map(async (svc) => {
|
|
190
|
+
const svcId2 = svc.id || svc.name;
|
|
191
|
+
try {
|
|
192
|
+
const machineSvc = await server.getService(svcId2);
|
|
193
|
+
const info = await machineSvc.getMachineInfo();
|
|
194
|
+
resolved.push({
|
|
195
|
+
svc,
|
|
196
|
+
machineId: (info.machineId || svcId2).toLowerCase(),
|
|
197
|
+
host: (info.metadata?.host || "").toLowerCase(),
|
|
198
|
+
displayName: (info.metadata?.displayName || "").toLowerCase()
|
|
199
|
+
});
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
let matches = resolved.filter(
|
|
204
|
+
(r) => r.machineId === query || r.host === query || r.displayName === query
|
|
205
|
+
);
|
|
206
|
+
if (matches.length === 0) {
|
|
207
|
+
matches = resolved.filter(
|
|
208
|
+
(r) => r.machineId.startsWith(query) || r.host.startsWith(query) || r.displayName.startsWith(query)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (matches.length === 0) {
|
|
212
|
+
matches = resolved.filter(
|
|
213
|
+
(r) => r.machineId.includes(query) || r.host.includes(query) || r.displayName.includes(query)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (matches.length === 1) {
|
|
217
|
+
selectedService = matches[0].svc;
|
|
218
|
+
} else if (matches.length > 1) {
|
|
219
|
+
restoreConsole();
|
|
220
|
+
console.error(`Ambiguous machine "${machineId}". Matches:`);
|
|
221
|
+
for (const m of matches) {
|
|
222
|
+
console.error(` ${m.machineId} (${m.host})`);
|
|
200
223
|
}
|
|
224
|
+
await server.disconnect();
|
|
225
|
+
process.exit(1);
|
|
201
226
|
} else {
|
|
202
227
|
restoreConsole();
|
|
203
|
-
console.error(`
|
|
204
|
-
|
|
205
|
-
console.error(
|
|
228
|
+
console.error(`No machine found matching: ${machineId}`);
|
|
229
|
+
if (resolved.length > 0) {
|
|
230
|
+
console.error("Available machines:");
|
|
231
|
+
for (const r of resolved) {
|
|
232
|
+
console.error(` ${r.machineId} (${r.host})`);
|
|
233
|
+
}
|
|
206
234
|
}
|
|
207
235
|
await server.disconnect();
|
|
208
236
|
process.exit(1);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { connectAndGetMachine } from './commands-
|
|
3
|
+
import { connectAndGetMachine } from './commands-COX5-rj3.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:child_process';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'node:os';
|
|
8
|
-
import './run-
|
|
8
|
+
import './run-p-RshVII.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-
|
|
1
|
+
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-p-RshVII.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.1.
|
|
2
|
+
var version = "0.1.72";
|
|
3
3
|
var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
|
|
4
4
|
var author = "Amun AI AB";
|
|
5
5
|
var license = "SEE LICENSE IN LICENSE";
|
|
@@ -19,7 +19,7 @@ var exports$1 = {
|
|
|
19
19
|
var scripts = {
|
|
20
20
|
build: "rm -rf dist && tsc --noEmit && pkgroll",
|
|
21
21
|
typecheck: "tsc --noEmit",
|
|
22
|
-
test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs",
|
|
22
|
+
test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs",
|
|
23
23
|
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
24
24
|
dev: "tsx src/cli.ts",
|
|
25
25
|
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService } from './run-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-p-RshVII.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -666,7 +666,7 @@ async function runInteractive(options) {
|
|
|
666
666
|
if (serverUrl && token) {
|
|
667
667
|
try {
|
|
668
668
|
suppressHyphaLogs();
|
|
669
|
-
server = await connectToHypha({ serverUrl, token, name: "svamp-interactive" });
|
|
669
|
+
server = await connectToHypha({ serverUrl, token, name: "svamp-interactive", transport: "http" });
|
|
670
670
|
log("Connected to Hypha");
|
|
671
671
|
} catch (err) {
|
|
672
672
|
restoreConsoleLogs();
|
|
@@ -45,6 +45,11 @@ async function connectToHypha(config) {
|
|
|
45
45
|
"Timeout connecting to Hypha server (30s). A previous daemon may still be connected. Retrying..."
|
|
46
46
|
)), 3e4))
|
|
47
47
|
]);
|
|
48
|
+
if (!server.on && server.rpc) {
|
|
49
|
+
server.on = server.rpc.on.bind(server.rpc);
|
|
50
|
+
server.off = server.rpc.off.bind(server.rpc);
|
|
51
|
+
server.emit = server.rpc.emit.bind(server.rpc);
|
|
52
|
+
}
|
|
48
53
|
return server;
|
|
49
54
|
}
|
|
50
55
|
function parseWorkspaceFromToken(token) {
|
|
@@ -378,6 +383,72 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
378
383
|
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
379
384
|
return handlers.getTrackedSessions();
|
|
380
385
|
},
|
|
386
|
+
/**
|
|
387
|
+
* Get summary info for all sessions (metadata, agent state, activity).
|
|
388
|
+
* Replaces the need to discover and query N individual session services.
|
|
389
|
+
*/
|
|
390
|
+
getSessions: async (context) => {
|
|
391
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
392
|
+
const sessionIds = handlers.getSessionIds?.() || [];
|
|
393
|
+
const sessions = [];
|
|
394
|
+
for (const sid of sessionIds) {
|
|
395
|
+
const rpc = handlers.getSessionRPCHandlers?.(sid);
|
|
396
|
+
if (!rpc) continue;
|
|
397
|
+
try {
|
|
398
|
+
const [metaResult, stateResult, activity] = await Promise.all([
|
|
399
|
+
rpc.getMetadata(context),
|
|
400
|
+
rpc.getAgentState(context),
|
|
401
|
+
rpc.getActivityState(context).catch(() => ({
|
|
402
|
+
active: false,
|
|
403
|
+
thinking: false,
|
|
404
|
+
time: Date.now()
|
|
405
|
+
}))
|
|
406
|
+
]);
|
|
407
|
+
sessions.push({
|
|
408
|
+
id: sid,
|
|
409
|
+
metadata: metaResult.metadata,
|
|
410
|
+
metadataVersion: metaResult.version,
|
|
411
|
+
agentState: stateResult.agentState,
|
|
412
|
+
agentStateVersion: stateResult.version,
|
|
413
|
+
active: activity.active ?? false,
|
|
414
|
+
thinking: activity.thinking ?? false,
|
|
415
|
+
activeAt: activity.time || Date.now()
|
|
416
|
+
});
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return sessions;
|
|
421
|
+
},
|
|
422
|
+
/**
|
|
423
|
+
* Dispatch an RPC call to a specific session's handler.
|
|
424
|
+
* This consolidates all session RPCs through the machine service,
|
|
425
|
+
* eliminating the need for per-session Hypha service registration.
|
|
426
|
+
*/
|
|
427
|
+
sessionRPC: async (sessionId, method, args, context) => {
|
|
428
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
429
|
+
const rpc = handlers.getSessionRPCHandlers?.(sessionId);
|
|
430
|
+
if (!rpc) {
|
|
431
|
+
throw new Error(`Session ${sessionId} not found on this machine`);
|
|
432
|
+
}
|
|
433
|
+
const handler = rpc[method];
|
|
434
|
+
if (typeof handler !== "function") {
|
|
435
|
+
throw new Error(`Unknown session method: ${method}`);
|
|
436
|
+
}
|
|
437
|
+
const argArray = Array.isArray(args) ? args : args !== void 0 ? [args] : [];
|
|
438
|
+
return await handler(...argArray, context);
|
|
439
|
+
},
|
|
440
|
+
/**
|
|
441
|
+
* Register a listener for a specific session's real-time updates.
|
|
442
|
+
* Delegates to the session store's registerListener.
|
|
443
|
+
*/
|
|
444
|
+
registerSessionListener: async (sessionId, callback, context) => {
|
|
445
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
446
|
+
const rpc = handlers.getSessionRPCHandlers?.(sessionId);
|
|
447
|
+
if (!rpc) {
|
|
448
|
+
throw new Error(`Session ${sessionId} not found on this machine`);
|
|
449
|
+
}
|
|
450
|
+
return await rpc.registerListener(callback, context);
|
|
451
|
+
},
|
|
381
452
|
// Spawn a new session
|
|
382
453
|
spawnSession: async (options, context) => {
|
|
383
454
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
@@ -771,6 +842,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
771
842
|
console.log(`[HYPHA MACHINE] Machine service registered: ${serviceInfo.id}`);
|
|
772
843
|
return {
|
|
773
844
|
serviceInfo,
|
|
845
|
+
notifySessionEvent: notifyListeners,
|
|
774
846
|
updateMetadata: (newMetadata) => {
|
|
775
847
|
currentMetadata = newMetadata;
|
|
776
848
|
metadataVersion++;
|
|
@@ -827,7 +899,7 @@ function appendMessage(messagesDir, sessionId, msg) {
|
|
|
827
899
|
console.error(`[HYPHA SESSION ${sessionId}] Failed to persist message: ${err?.message ?? err}`);
|
|
828
900
|
}
|
|
829
901
|
}
|
|
830
|
-
|
|
902
|
+
function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
|
|
831
903
|
const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
|
|
832
904
|
let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
|
|
833
905
|
let metadata = { ...initialMetadata };
|
|
@@ -835,7 +907,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
835
907
|
let agentState = initialAgentState ? { ...initialAgentState } : null;
|
|
836
908
|
let agentStateVersion = 1;
|
|
837
909
|
let lastActivity = {
|
|
838
|
-
active:
|
|
910
|
+
active: true,
|
|
839
911
|
thinking: false,
|
|
840
912
|
mode: "remote",
|
|
841
913
|
time: Date.now()
|
|
@@ -854,6 +926,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
854
926
|
}
|
|
855
927
|
};
|
|
856
928
|
const notifyListeners = (update) => {
|
|
929
|
+
options?.onSessionEvent?.(update);
|
|
857
930
|
const snapshot = [...listeners];
|
|
858
931
|
for (let i = snapshot.length - 1; i >= 0; i--) {
|
|
859
932
|
const listener = snapshot[i];
|
|
@@ -880,7 +953,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
880
953
|
}
|
|
881
954
|
wrappedContent = { role: "agent", content: { type: "output", data } };
|
|
882
955
|
} else if (role === "event") {
|
|
883
|
-
wrappedContent = { role: "agent", content: { type: "event", data: content } };
|
|
956
|
+
wrappedContent = { role: "agent", content: { type: "event", id: randomUUID(), data: content } };
|
|
884
957
|
} else if (role === "session") {
|
|
885
958
|
wrappedContent = { role: "session", content: { type: "session", data: content } };
|
|
886
959
|
} else {
|
|
@@ -907,11 +980,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
907
980
|
});
|
|
908
981
|
return msg;
|
|
909
982
|
};
|
|
910
|
-
const
|
|
911
|
-
id: `svamp-session-${sessionId}`,
|
|
912
|
-
name: `Svamp Session ${sessionId.slice(0, 8)}`,
|
|
913
|
-
type: "svamp-session",
|
|
914
|
-
config: { visibility: "unlisted", require_context: true },
|
|
983
|
+
const rpcHandlers = {
|
|
915
984
|
// ── Messages ──
|
|
916
985
|
getMessages: async (afterSeq, limit, context) => {
|
|
917
986
|
authorizeRequest(context, metadata.sharing, "view");
|
|
@@ -1251,10 +1320,8 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
1251
1320
|
return { success: true, listenerId: listeners.length - 1 };
|
|
1252
1321
|
}
|
|
1253
1322
|
};
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
return {
|
|
1257
|
-
serviceInfo,
|
|
1323
|
+
const store = {
|
|
1324
|
+
serviceInfo: { id: `svamp-session-${sessionId}` },
|
|
1258
1325
|
pushMessage,
|
|
1259
1326
|
get _agentState() {
|
|
1260
1327
|
return agentState;
|
|
@@ -1313,16 +1380,44 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
1313
1380
|
for (const listener of toRemove) {
|
|
1314
1381
|
removeListener(listener, "disconnect");
|
|
1315
1382
|
}
|
|
1316
|
-
await server.unregisterService(serviceInfo.id);
|
|
1317
1383
|
},
|
|
1318
1384
|
reregister: async () => {
|
|
1319
|
-
try {
|
|
1320
|
-
await server.registerService(serviceDefinition, { overwrite: true });
|
|
1321
|
-
} catch (e) {
|
|
1322
|
-
if (!String(e?.message).includes("already exists")) throw e;
|
|
1323
|
-
}
|
|
1324
1385
|
}
|
|
1325
1386
|
};
|
|
1387
|
+
return { store, rpcHandlers };
|
|
1388
|
+
}
|
|
1389
|
+
async function registerSessionService(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
|
|
1390
|
+
const { store, rpcHandlers } = createSessionStore(
|
|
1391
|
+
server,
|
|
1392
|
+
sessionId,
|
|
1393
|
+
initialMetadata,
|
|
1394
|
+
initialAgentState,
|
|
1395
|
+
callbacks,
|
|
1396
|
+
options
|
|
1397
|
+
);
|
|
1398
|
+
const serviceDefinition = {
|
|
1399
|
+
id: `svamp-session-${sessionId}`,
|
|
1400
|
+
name: `Svamp Session ${sessionId.slice(0, 8)}`,
|
|
1401
|
+
type: "svamp-session",
|
|
1402
|
+
config: { visibility: "unlisted", require_context: true },
|
|
1403
|
+
...rpcHandlers
|
|
1404
|
+
};
|
|
1405
|
+
const serviceInfo = await server.registerService(serviceDefinition, { overwrite: true });
|
|
1406
|
+
console.log(`[HYPHA SESSION] Session service registered: ${serviceInfo.id}`);
|
|
1407
|
+
const originalDisconnect = store.disconnect;
|
|
1408
|
+
store.disconnect = async () => {
|
|
1409
|
+
await originalDisconnect();
|
|
1410
|
+
await server.unregisterService(serviceInfo.id);
|
|
1411
|
+
};
|
|
1412
|
+
store.reregister = async () => {
|
|
1413
|
+
try {
|
|
1414
|
+
await server.registerService(serviceDefinition, { overwrite: true });
|
|
1415
|
+
} catch (e) {
|
|
1416
|
+
if (!String(e?.message).includes("already exists")) throw e;
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
store.serviceInfo = serviceInfo;
|
|
1420
|
+
return store;
|
|
1326
1421
|
}
|
|
1327
1422
|
|
|
1328
1423
|
async function registerDebugService(server, machineId, deps) {
|
|
@@ -5323,6 +5418,7 @@ async function startDaemon(options) {
|
|
|
5323
5418
|
serverUrl: hyphaServerUrl,
|
|
5324
5419
|
token: hyphaToken,
|
|
5325
5420
|
name: `svamp-machine-${machineId}`,
|
|
5421
|
+
transport: "http",
|
|
5326
5422
|
...hyphaClientId ? { clientId: hyphaClientId } : {}
|
|
5327
5423
|
});
|
|
5328
5424
|
logger.log(`Connected to Hypha (workspace: ${server.config.workspace})`);
|
|
@@ -5337,14 +5433,6 @@ async function startDaemon(options) {
|
|
|
5337
5433
|
consecutiveHeartbeatFailures = 0;
|
|
5338
5434
|
lastReconnectAt = Date.now();
|
|
5339
5435
|
}
|
|
5340
|
-
const activeSessions = Array.from(pidToTrackedSession.values()).filter((s) => !s.stopped && s.hyphaService);
|
|
5341
|
-
if (activeSessions.length > 0) {
|
|
5342
|
-
logger.log(`Re-registering ${activeSessions.length} session services after reconnect`);
|
|
5343
|
-
Promise.allSettled(activeSessions.map((s) => s.hyphaService.reregister())).then((results) => {
|
|
5344
|
-
const failed = results.filter((r) => r.status === "rejected").length;
|
|
5345
|
-
if (failed > 0) logger.log(`Warning: ${failed} session service re-registrations failed`);
|
|
5346
|
-
});
|
|
5347
|
-
}
|
|
5348
5436
|
});
|
|
5349
5437
|
const getCurrentChildren = () => {
|
|
5350
5438
|
return Array.from(pidToTrackedSession.values()).map((s) => ({
|
|
@@ -5355,6 +5443,7 @@ async function startDaemon(options) {
|
|
|
5355
5443
|
active: !s.stopped && s.hyphaService != null
|
|
5356
5444
|
}));
|
|
5357
5445
|
};
|
|
5446
|
+
let machineServiceRef = null;
|
|
5358
5447
|
const spawnSession = async (options2) => {
|
|
5359
5448
|
logger.log("Spawning session:", JSON.stringify(options2));
|
|
5360
5449
|
const { directory, approvedNewDirectoryCreation = true, resumeSessionId } = options2;
|
|
@@ -5756,8 +5845,11 @@ async function startDaemon(options) {
|
|
|
5756
5845
|
const lower = resultText.toLowerCase();
|
|
5757
5846
|
const isLoginIssue = lower.includes("login") || lower.includes("logged in") || lower.includes("auth") || lower.includes("api key") || lower.includes("unauthorized");
|
|
5758
5847
|
const isResumeIssue = lower.includes("tool_use.name") || lower.includes("invalid_request") || lower.includes("messages.");
|
|
5848
|
+
const isBillingIssue = lower.includes("credit") || lower.includes("balance") || lower.includes("billing") || lower.includes("quota") || lower.includes("subscription") || lower.includes("payment");
|
|
5759
5849
|
let hint = "";
|
|
5760
|
-
if (
|
|
5850
|
+
if (isBillingIssue) {
|
|
5851
|
+
hint = "\n\nCheck your Claude account credits or subscription at https://console.anthropic.com.";
|
|
5852
|
+
} else if (isLoginIssue) {
|
|
5761
5853
|
hint = "\n\nRun `claude login` in your terminal on the machine running the daemon to re-authenticate.";
|
|
5762
5854
|
} else if (isResumeIssue) {
|
|
5763
5855
|
hint = "\n\nThe conversation history may be corrupted. Try starting a fresh session.";
|
|
@@ -5769,10 +5861,9 @@ async function startDaemon(options) {
|
|
|
5769
5861
|
if (startupNonJsonLines.length > 0) {
|
|
5770
5862
|
contextInfo = "\n\n**Startup output:**\n```\n" + startupNonJsonLines.slice(-10).join("\n") + "\n```";
|
|
5771
5863
|
}
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
}, "agent");
|
|
5864
|
+
const errorText = `${displayMsg}${hint}${contextInfo}`;
|
|
5865
|
+
logger.log(`[Session ${sessionId}] Pushing error to UI: "${displayMsg}"`);
|
|
5866
|
+
sessionService.pushMessage({ type: "message", message: errorText, level: "error" }, "event");
|
|
5776
5867
|
lastErrorMessagePushed = true;
|
|
5777
5868
|
}
|
|
5778
5869
|
}
|
|
@@ -6166,7 +6257,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6166
6257
|
}
|
|
6167
6258
|
}
|
|
6168
6259
|
let processMessageQueueRef;
|
|
6169
|
-
const sessionService =
|
|
6260
|
+
const { store: sessionService, rpcHandlers: sessionRPCHandlers } = createSessionStore(
|
|
6170
6261
|
server,
|
|
6171
6262
|
sessionId,
|
|
6172
6263
|
sessionMetadata,
|
|
@@ -6467,7 +6558,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6467
6558
|
return { success: !!tree, tree };
|
|
6468
6559
|
}
|
|
6469
6560
|
},
|
|
6470
|
-
{ messagesDir: getSessionDir(directory, sessionId) }
|
|
6561
|
+
{ messagesDir: getSessionDir(directory, sessionId), onSessionEvent: (update) => machineServiceRef?.notifySessionEvent(update) }
|
|
6471
6562
|
);
|
|
6472
6563
|
const svampConfig = createSvampConfigChecker(
|
|
6473
6564
|
directory,
|
|
@@ -6554,6 +6645,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6554
6645
|
pid: process.pid,
|
|
6555
6646
|
svampSessionId: sessionId,
|
|
6556
6647
|
hyphaService: sessionService,
|
|
6648
|
+
sessionRPCHandlers,
|
|
6557
6649
|
checkSvampConfig,
|
|
6558
6650
|
cleanupSvampConfig,
|
|
6559
6651
|
directory,
|
|
@@ -6639,7 +6731,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6639
6731
|
const allowedBashLiterals = /* @__PURE__ */ new Set();
|
|
6640
6732
|
const allowedBashPrefixes = /* @__PURE__ */ new Set();
|
|
6641
6733
|
const EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "Write", "NotebookEdit"]);
|
|
6642
|
-
const sessionService =
|
|
6734
|
+
const { store: sessionService, rpcHandlers: sessionRPCHandlersAcp } = createSessionStore(
|
|
6643
6735
|
server,
|
|
6644
6736
|
sessionId,
|
|
6645
6737
|
sessionMetadata,
|
|
@@ -6878,7 +6970,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6878
6970
|
return { success: !!tree, tree };
|
|
6879
6971
|
}
|
|
6880
6972
|
},
|
|
6881
|
-
{ messagesDir: getSessionDir(directory, sessionId) }
|
|
6973
|
+
{ messagesDir: getSessionDir(directory, sessionId), onSessionEvent: (update) => machineServiceRef?.notifySessionEvent(update) }
|
|
6882
6974
|
);
|
|
6883
6975
|
let insideOnTurnEnd = false;
|
|
6884
6976
|
const svampConfigChecker = createSvampConfigChecker(
|
|
@@ -7100,6 +7192,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7100
7192
|
pid: process.pid,
|
|
7101
7193
|
svampSessionId: sessionId,
|
|
7102
7194
|
hyphaService: sessionService,
|
|
7195
|
+
sessionRPCHandlers: sessionRPCHandlersAcp,
|
|
7103
7196
|
checkSvampConfig,
|
|
7104
7197
|
cleanupSvampConfig: svampConfigChecker.cleanup,
|
|
7105
7198
|
directory,
|
|
@@ -7225,10 +7318,28 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7225
7318
|
restartSession,
|
|
7226
7319
|
requestShutdown: () => requestShutdown("hypha-app"),
|
|
7227
7320
|
getTrackedSessions: getCurrentChildren,
|
|
7321
|
+
getSessionRPCHandlers: (sessionId) => {
|
|
7322
|
+
for (const [, session] of pidToTrackedSession) {
|
|
7323
|
+
if (session.svampSessionId === sessionId && session.sessionRPCHandlers) {
|
|
7324
|
+
return session.sessionRPCHandlers;
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
return void 0;
|
|
7328
|
+
},
|
|
7329
|
+
getSessionIds: () => {
|
|
7330
|
+
const ids = [];
|
|
7331
|
+
for (const [, session] of pidToTrackedSession) {
|
|
7332
|
+
if (session.svampSessionId && !session.stopped && session.sessionRPCHandlers) {
|
|
7333
|
+
ids.push(session.svampSessionId);
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
return ids;
|
|
7337
|
+
},
|
|
7228
7338
|
supervisor
|
|
7229
7339
|
}
|
|
7230
7340
|
);
|
|
7231
7341
|
logger.log(`Machine service registered: svamp-machine-${machineId}`);
|
|
7342
|
+
machineServiceRef = machineService;
|
|
7232
7343
|
const artifactSync = new SessionArtifactSync(server, logger.log);
|
|
7233
7344
|
const debugService = await registerDebugService(server, machineId, {
|
|
7234
7345
|
machineId,
|
|
@@ -7270,6 +7381,12 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7270
7381
|
});
|
|
7271
7382
|
if (result.type === "success") {
|
|
7272
7383
|
logger.log(`Restored session ${persisted.sessionId} (resume=${persisted.claudeResumeId}, wasProcessing=${persisted.wasProcessing})`);
|
|
7384
|
+
for (const [, tracked] of pidToTrackedSession) {
|
|
7385
|
+
if (tracked.svampSessionId === persisted.sessionId && tracked.hyphaService) {
|
|
7386
|
+
tracked.hyphaService.sendKeepAlive(false);
|
|
7387
|
+
break;
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7273
7390
|
if (isOrphaned) {
|
|
7274
7391
|
for (const [, tracked] of pidToTrackedSession) {
|
|
7275
7392
|
if (tracked.svampSessionId === persisted.sessionId && tracked.hyphaService) {
|
|
@@ -7500,6 +7617,13 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7500
7617
|
} catch {
|
|
7501
7618
|
}
|
|
7502
7619
|
}
|
|
7620
|
+
if (conn?._reader) {
|
|
7621
|
+
logger.log("Aborting stale HTTP stream to trigger reconnection");
|
|
7622
|
+
try {
|
|
7623
|
+
conn._reader.cancel?.("Stale connection");
|
|
7624
|
+
} catch {
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7503
7627
|
}
|
|
7504
7628
|
if (consecutiveHeartbeatFailures >= MAX_FAILURES) {
|
|
7505
7629
|
logger.log(`Heartbeat failed ${MAX_FAILURES} times. Shutting down.`);
|
|
@@ -14,10 +14,12 @@ class TunnelClient {
|
|
|
14
14
|
requestCount = 0;
|
|
15
15
|
localWebSockets = /* @__PURE__ */ new Map();
|
|
16
16
|
// request_id → local WS connection
|
|
17
|
+
pendingBinaryBody = null;
|
|
18
|
+
// awaiting binary body frame
|
|
17
19
|
constructor(options) {
|
|
18
20
|
this.options = {
|
|
19
21
|
localHost: "localhost",
|
|
20
|
-
requestTimeout:
|
|
22
|
+
requestTimeout: 12e4,
|
|
21
23
|
...options
|
|
22
24
|
};
|
|
23
25
|
this.env = options.env || requireSandboxApiEnv();
|
|
@@ -40,7 +42,10 @@ class TunnelClient {
|
|
|
40
42
|
const url = this.buildWsUrl();
|
|
41
43
|
return new Promise((resolve, reject) => {
|
|
42
44
|
try {
|
|
43
|
-
this.ws = new WebSocket(url
|
|
45
|
+
this.ws = new WebSocket(url, {
|
|
46
|
+
maxPayload: 100 * 1024 * 1024
|
|
47
|
+
// 100MB for large binary bodies
|
|
48
|
+
});
|
|
44
49
|
} catch (err) {
|
|
45
50
|
reject(new Error(`Failed to create WebSocket: ${err.message}`));
|
|
46
51
|
return;
|
|
@@ -51,13 +56,18 @@ class TunnelClient {
|
|
|
51
56
|
this.options.onConnect?.();
|
|
52
57
|
resolve();
|
|
53
58
|
});
|
|
54
|
-
this.ws.on("message", (data) => {
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
this.ws.on("message", (data, isBinary) => {
|
|
60
|
+
if (isBinary) {
|
|
61
|
+
this.handleBinaryBody(data);
|
|
62
|
+
} else {
|
|
63
|
+
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
64
|
+
this.handleMessage(raw);
|
|
65
|
+
}
|
|
57
66
|
});
|
|
58
67
|
this.ws.on("close", () => {
|
|
59
68
|
this.stopPingInterval();
|
|
60
69
|
this.cleanupLocalWebSockets();
|
|
70
|
+
this.pendingBinaryBody = null;
|
|
61
71
|
this.options.onDisconnect?.();
|
|
62
72
|
if (!this.destroyed) {
|
|
63
73
|
this.scheduleReconnect();
|
|
@@ -104,9 +114,13 @@ class TunnelClient {
|
|
|
104
114
|
break;
|
|
105
115
|
case "request":
|
|
106
116
|
this.options.onRequest?.(msg);
|
|
107
|
-
|
|
108
|
-
this.
|
|
109
|
-
}
|
|
117
|
+
if (msg.has_body) {
|
|
118
|
+
this.pendingBinaryBody = msg;
|
|
119
|
+
} else {
|
|
120
|
+
this.proxyRequest(msg, Buffer.alloc(0)).catch((err) => {
|
|
121
|
+
this.options.onError?.(err);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
110
124
|
break;
|
|
111
125
|
case "ws_open":
|
|
112
126
|
this.handleWsOpen(msg).catch((err) => {
|
|
@@ -121,7 +135,15 @@ class TunnelClient {
|
|
|
121
135
|
break;
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
|
-
|
|
138
|
+
handleBinaryBody(data) {
|
|
139
|
+
const req = this.pendingBinaryBody;
|
|
140
|
+
this.pendingBinaryBody = null;
|
|
141
|
+
if (!req) return;
|
|
142
|
+
this.proxyRequest(req, data).catch((err) => {
|
|
143
|
+
this.options.onError?.(err);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async proxyRequest(req, bodyBuffer) {
|
|
125
147
|
this.requestCount++;
|
|
126
148
|
const port = req.port || this.options.ports[0];
|
|
127
149
|
const url = `http://${this.options.localHost}:${port}${req.path}`;
|
|
@@ -135,14 +157,12 @@ class TunnelClient {
|
|
|
135
157
|
headers: forwardHeaders,
|
|
136
158
|
signal: controller.signal
|
|
137
159
|
};
|
|
138
|
-
if (
|
|
139
|
-
init.body =
|
|
160
|
+
if (bodyBuffer.byteLength > 0 && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
|
|
161
|
+
init.body = bodyBuffer;
|
|
140
162
|
}
|
|
141
163
|
try {
|
|
142
164
|
const res = await fetch(url, init);
|
|
143
165
|
clearTimeout(timeout);
|
|
144
|
-
const bodyBuffer = await res.arrayBuffer();
|
|
145
|
-
const bodyBase64 = bodyBuffer.byteLength > 0 ? Buffer.from(bodyBuffer).toString("base64") : void 0;
|
|
146
166
|
const headers = {};
|
|
147
167
|
res.headers.forEach((value, key) => {
|
|
148
168
|
headers[key] = value;
|
|
@@ -152,27 +172,36 @@ class TunnelClient {
|
|
|
152
172
|
id: req.id,
|
|
153
173
|
status: res.status,
|
|
154
174
|
headers,
|
|
155
|
-
|
|
175
|
+
streaming: true
|
|
156
176
|
});
|
|
177
|
+
if (res.body) {
|
|
178
|
+
const reader = res.body.getReader();
|
|
179
|
+
try {
|
|
180
|
+
while (true) {
|
|
181
|
+
const { done, value } = await reader.read();
|
|
182
|
+
if (done) break;
|
|
183
|
+
this.send({ type: "response_chunk", id: req.id });
|
|
184
|
+
this.sendBinary(Buffer.from(value));
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
reader.releaseLock();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this.send({ type: "response_end", id: req.id });
|
|
157
191
|
} catch (err) {
|
|
158
192
|
clearTimeout(timeout);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
status: 502,
|
|
172
|
-
headers: { "content-type": "text/plain" },
|
|
173
|
-
body: Buffer.from(`Tunnel: local service error: ${err.message}`).toString("base64")
|
|
174
|
-
});
|
|
175
|
-
}
|
|
193
|
+
const status = err.name === "AbortError" ? 504 : 502;
|
|
194
|
+
const message = err.name === "AbortError" ? "Tunnel: request timeout" : `Tunnel: local service error: ${err.message}`;
|
|
195
|
+
this.send({
|
|
196
|
+
type: "response",
|
|
197
|
+
id: req.id,
|
|
198
|
+
status,
|
|
199
|
+
headers: { "content-type": "text/plain" },
|
|
200
|
+
streaming: true
|
|
201
|
+
});
|
|
202
|
+
this.send({ type: "response_chunk", id: req.id });
|
|
203
|
+
this.sendBinary(Buffer.from(message));
|
|
204
|
+
this.send({ type: "response_end", id: req.id });
|
|
176
205
|
}
|
|
177
206
|
}
|
|
178
207
|
// ── WebSocket proxying ───────────────────────────────────────────────
|
|
@@ -238,6 +267,11 @@ class TunnelClient {
|
|
|
238
267
|
this.ws.send(JSON.stringify(msg));
|
|
239
268
|
}
|
|
240
269
|
}
|
|
270
|
+
sendBinary(data) {
|
|
271
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
272
|
+
this.ws.send(data);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
241
275
|
startPingInterval() {
|
|
242
276
|
this.stopPingInterval();
|
|
243
277
|
this.pingInterval = setInterval(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svamp-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
|
|
5
5
|
"author": "Amun AI AB",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "rm -rf dist && tsc --noEmit && pkgroll",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs",
|
|
23
|
+
"test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs",
|
|
24
24
|
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
25
25
|
"dev": "tsx src/cli.ts",
|
|
26
26
|
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|