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 CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-C7CEDmD4.mjs';
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-CF32XIau.mjs');
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-CIgrbviM.mjs');
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-B0d2rUXI.mjs').catch(() => ({ default: { version: "unknown" } }));
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-CgSj6KtU.mjs');
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-C7CEDmD4.mjs').then(function (n) { return n.i; });
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-C7CEDmD4.mjs').then(function (n) { return n.i; });
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-C7CEDmD4.mjs').then(function (n) { return n.j; });
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-C7CEDmD4.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.D; });
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-BodoXVL9.mjs');
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-C3UsqTxi.mjs');
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-C3UsqTxi.mjs');
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-C7CEDmD4.mjs';
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
- } else if (prefixMatches.length === 0) {
185
- const substringMatches = services.filter((s) => {
186
- const id = s.id || s.name || "";
187
- return id.includes(machineId);
188
- });
189
- if (substringMatches.length === 1) {
190
- selectedService = substringMatches[0];
191
- } else {
192
- restoreConsole();
193
- console.error(`No machine found matching: ${machineId}`);
194
- console.error("Available machines:");
195
- for (const s of services) {
196
- console.error(` ${s.id || s.name}`);
197
- }
198
- await server.disconnect();
199
- process.exit(1);
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(`Ambiguous machine ID "${machineId}". Matches:`);
204
- for (const s of prefixMatches) {
205
- console.error(` ${s.id || s.name}`);
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-BodoXVL9.mjs';
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-C7CEDmD4.mjs';
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-C7CEDmD4.mjs';
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.70";
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-C7CEDmD4.mjs';
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
- async function registerSessionService(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
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: false,
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 serviceDefinition = {
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 serviceInfo = await server.registerService(serviceDefinition, { overwrite: true });
1255
- console.log(`[HYPHA SESSION] Session service registered: ${serviceInfo.id}`);
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 (isLoginIssue) {
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
- sessionService.pushMessage({
5773
- type: "assistant",
5774
- content: [{ type: "text", text: `**Error:** ${displayMsg}${hint}${contextInfo}` }]
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 = await registerSessionService(
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 = await registerSessionService(
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: 3e4,
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
- const raw = typeof data === "string" ? data : data.toString("utf8");
56
- this.handleMessage(raw);
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
- this.proxyRequest(msg).catch((err) => {
108
- this.options.onError?.(err);
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
- async proxyRequest(req) {
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 (req.body && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
139
- init.body = Buffer.from(req.body, "base64");
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
- body: bodyBase64
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
- if (err.name === "AbortError") {
160
- this.send({
161
- type: "response",
162
- id: req.id,
163
- status: 504,
164
- headers: { "content-type": "text/plain" },
165
- body: Buffer.from("Tunnel: request timeout").toString("base64")
166
- });
167
- } else {
168
- this.send({
169
- type: "response",
170
- id: req.id,
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.70",
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",