svamp-cli 0.2.309 → 0.2.311

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.
Files changed (29) hide show
  1. package/dist/{adminCommands-BTLHQXmA.mjs → adminCommands-U5D7i8tD.mjs} +1 -1
  2. package/dist/{agentCommands-DdNUACV8.mjs → agentCommands-BefFTl6g.mjs} +5 -5
  3. package/dist/{auth-Bn8TbaOP.mjs → auth-DgGAjihq.mjs} +1 -1
  4. package/dist/{cli-BaDWWDiz.mjs → cli-B0TyKXyU.mjs} +70 -70
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-Bb0oF3eZ.mjs → commands-BCQbBzYU.mjs} +2 -2
  7. package/dist/{commands-BTN9XxVx.mjs → commands-BoGUxb2o.mjs} +1 -1
  8. package/dist/{commands-C2u988x1.mjs → commands-CEOhve0Y.mjs} +2 -2
  9. package/dist/{commands-DGbLwT_h.mjs → commands-CjVl4amM.mjs} +8 -8
  10. package/dist/{commands-2uOorIjB.mjs → commands-CnqL60f7.mjs} +1 -1
  11. package/dist/{commands-DPvy0iVT.mjs → commands-XQ8-fOSu.mjs} +1 -1
  12. package/dist/{commands-BPgxgkXW.mjs → commands-imLC7CK2.mjs} +2 -2
  13. package/dist/{commands-r86haNJD.mjs → commands-xoalqGUY.mjs} +2 -2
  14. package/dist/{fleet-CeLE0t97.mjs → fleet-BS5eS0Wz.mjs} +2 -2
  15. package/dist/{frpc-Cv6J2Mex.mjs → frpc-DvBVvsZC.mjs} +1 -1
  16. package/dist/{headlessCli-BXc9Fvzb.mjs → headlessCli-C9wTLm1x.mjs} +2 -2
  17. package/dist/index.mjs +1 -1
  18. package/dist/{notifyCommands-iVnI_Lb2.mjs → notifyCommands-0IltLO-S.mjs} +1 -1
  19. package/dist/package-CFhmZcVJ.mjs +64 -0
  20. package/dist/{pinnedClaudeCode-C6MSkqS9.mjs → pinnedClaudeCode-zDzkOTi2.mjs} +1 -1
  21. package/dist/{rpc-CkcBAtcs.mjs → rpc-BLWr4p8g.mjs} +1 -1
  22. package/dist/{rpc-BwxwusfB.mjs → rpc-BXpuORiQ.mjs} +1 -1
  23. package/dist/{run-2Stz5l7J.mjs → run-CMoQtnIj.mjs} +1 -1
  24. package/dist/{run-DLG-_xa7.mjs → run-DtVX2pQN.mjs} +127 -55
  25. package/dist/{scheduler-CurN4UtP.mjs → scheduler-7aLvf5mn.mjs} +1 -1
  26. package/dist/{serveCommands-CtVZhR-V.mjs → serveCommands-D7BieIJ5.mjs} +19 -12
  27. package/dist/{sideband-C_3PVGh-.mjs → sideband-DjjXlVhw.mjs} +1 -1
  28. package/package.json +2 -2
  29. package/dist/package-CeseSmYI.mjs +0 -64
@@ -2379,10 +2379,8 @@ class ServeManager {
2379
2379
  */
2380
2380
  listMounts(sessionId) {
2381
2381
  const all = Array.from(this.mounts.values());
2382
- if (sessionId) {
2383
- return all.filter((m) => m.sessionId === sessionId);
2384
- }
2385
- return all;
2382
+ const filtered = sessionId ? all.filter((m) => m.sessionId === sessionId) : all;
2383
+ return filtered.map((m) => ({ ...m, url: this.getMountUrl(m.name) || void 0 }));
2386
2384
  }
2387
2385
  /** #0649: look up a single mount by name (for ownership checks before remove/replace). */
2388
2386
  getMount(name) {
@@ -2393,10 +2391,8 @@ class ServeManager {
2393
2391
  */
2394
2392
  getInfo() {
2395
2393
  const running = this.proxyServer != null;
2396
- const firstMount = this.mounts.values().next().value;
2397
- const firstUrl = firstMount ? this.getMountUrl(firstMount.name) : null;
2398
2394
  return {
2399
- url: firstUrl,
2395
+ url: null,
2400
2396
  port: running ? this.port : 0,
2401
2397
  authProxyPort: running ? this.port : 0,
2402
2398
  running,
@@ -3151,7 +3147,7 @@ Connection: close\r
3151
3147
  const mount = this.mounts.get(mountName);
3152
3148
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
3153
3149
  try {
3154
- const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
3150
+ const { FrpcTunnel } = await import('./frpc-DvBVvsZC.mjs');
3155
3151
  let tunnel;
3156
3152
  tunnel = new FrpcTunnel({
3157
3153
  name: tunnelName,
@@ -4093,7 +4089,8 @@ function inZone(date, tz) {
4093
4089
  o[x.type] = x.value;
4094
4090
  return o;
4095
4091
  }, {});
4096
- return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
4092
+ const midnight24 = p.hour === "24";
4093
+ return new Date(+p.year, +p.month - 1, +p.day + (midnight24 ? 1 : 0), midnight24 ? 0 : +p.hour, +p.minute);
4097
4094
  } catch {
4098
4095
  return date;
4099
4096
  }
@@ -4926,10 +4923,16 @@ class ChannelOutbox {
4926
4923
  this.emitter.on(channelId, onReply);
4927
4924
  });
4928
4925
  }
4929
- /** Push subscription for SSE: calls onReply for each new reply addressed to `to`. */
4930
- subscribe(channelId, to, onReply) {
4926
+ /**
4927
+ * Push subscription for SSE: calls onReply for each new matching reply.
4928
+ * Same key rule as since()/wait(): correlationId-first (knowing the unguessable reply
4929
+ * key proves ownership of that conversation), identity (`to`) only as the fallback.
4930
+ * Without this, several callers sharing a `to` (e.g. caller-supplied/'anonymous'
4931
+ * senders) would receive each other's replies — the cross-caller leak class of #0786.
4932
+ */
4933
+ subscribe(channelId, to, onReply, correlationId) {
4931
4934
  const handler = (r) => {
4932
- if (r.to === to) onReply(r);
4935
+ if (correlationId ? r.correlationId === correlationId : r.to === to) onReply(r);
4933
4936
  };
4934
4937
  this.emitter.on(channelId, handler);
4935
4938
  return () => this.emitter.off(channelId, handler);
@@ -5020,6 +5023,17 @@ const SESSION_VIEW_METHODS = /* @__PURE__ */ new Set([
5020
5023
  "reregister",
5021
5024
  "recordMeetingActivity"
5022
5025
  ]);
5026
+ const MACHINE_MEMBER_READABLE = /* @__PURE__ */ new Set([
5027
+ "getMetadata",
5028
+ "getAgentState",
5029
+ "getActivityState",
5030
+ "getMessages",
5031
+ "getLatestMessages",
5032
+ "getMessageCount",
5033
+ "getSharing",
5034
+ "listChannels",
5035
+ "registerListener"
5036
+ ]);
5023
5037
  function sessionMethodMinRole(method) {
5024
5038
  const mapped = SESSION_METHOD_MIN_ROLE[method];
5025
5039
  if (mapped) return mapped;
@@ -5362,11 +5376,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5362
5376
  const rpc = handlers.getSessionRPCHandlers?.(sid);
5363
5377
  if (!rpc) continue;
5364
5378
  if (!hasMachineAccess && !await hasExplicitSessionAccess(rpc, "view", context)) continue;
5379
+ const readCtx = hasMachineAccess ? void 0 : context;
5365
5380
  try {
5366
5381
  const [metaResult, stateResult, activity] = await Promise.all([
5367
- rpc.getMetadata(context),
5368
- rpc.getAgentState(context),
5369
- rpc.getActivityState(context).catch(() => ({
5382
+ rpc.getMetadata(readCtx),
5383
+ rpc.getAgentState(readCtx),
5384
+ rpc.getActivityState(readCtx).catch(() => ({
5370
5385
  active: false,
5371
5386
  thinking: false,
5372
5387
  time: Date.now()
@@ -5382,7 +5397,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5382
5397
  thinking: activity.thinking ?? false,
5383
5398
  activeAt: activity.time || Date.now()
5384
5399
  });
5385
- } catch {
5400
+ } catch (err) {
5401
+ console.debug(
5402
+ `[HYPHA MACHINE] getSessions: skipped session ${sid} (${hasMachineAccess ? "machine-access" : "session-shared"}): ${err?.message ?? err}`
5403
+ );
5386
5404
  }
5387
5405
  }
5388
5406
  return sessions;
@@ -5402,8 +5420,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5402
5420
  throw new Error(`Session ${sessionId} not found on this machine`);
5403
5421
  }
5404
5422
  const requiredRole = sessionMethodMinRole(method);
5423
+ let machineAuthorized = false;
5405
5424
  try {
5406
5425
  authorizeRequest(context, currentMetadata.sharing, requiredRole);
5426
+ machineAuthorized = true;
5407
5427
  } catch (machineErr) {
5408
5428
  if (!await hasExplicitSessionAccess(rpc, requiredRole, context)) throw machineErr;
5409
5429
  }
@@ -5411,9 +5431,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5411
5431
  if (typeof handler !== "function") {
5412
5432
  throw new Error(`Unknown session method: ${method}`);
5413
5433
  }
5434
+ const dispatchCtx = machineAuthorized && MACHINE_MEMBER_READABLE.has(method) ? void 0 : context;
5414
5435
  const paramNames = getParamNames(handler);
5415
5436
  const callArgs = paramNames.map(
5416
- (name) => name === "context" ? context : kwargs?.[name] ?? void 0
5437
+ (name) => name === "context" ? dispatchCtx : kwargs?.[name] ?? void 0
5417
5438
  );
5418
5439
  return await handler(...callArgs);
5419
5440
  },
@@ -5427,14 +5448,16 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5427
5448
  if (!rpc) {
5428
5449
  throw new Error(`Session ${sessionId} not found on this machine`);
5429
5450
  }
5451
+ let machineAuthorized = false;
5430
5452
  try {
5431
5453
  authorizeRequest(context, currentMetadata.sharing, "view");
5454
+ machineAuthorized = true;
5432
5455
  } catch (machineErr) {
5433
5456
  if (!await hasExplicitSessionAccess(rpc, "view", context)) {
5434
5457
  throw machineErr;
5435
5458
  }
5436
5459
  }
5437
- return await rpc.registerListener(callback, context);
5460
+ return await rpc.registerListener(callback, machineAuthorized ? void 0 : context);
5438
5461
  },
5439
5462
  // Spawn a new session
5440
5463
  spawnSession: async (options, context) => {
@@ -5810,9 +5833,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5810
5833
  const { exec } = await import('child_process');
5811
5834
  const { homedir } = await import('os');
5812
5835
  return new Promise((resolve) => {
5813
- exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
5836
+ exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
5814
5837
  if (err) {
5815
- resolve({ success: false, stdout: stdout || "", stderr: stderr || err.message, exitCode: err.code ?? 1 });
5838
+ const enobufs = err.code === "ENOBUFS";
5839
+ const errText = enobufs ? `output exceeded the 16 MB buffer and was truncated (the command may have completed): ${stderr || err.message}` : stderr || err.message;
5840
+ resolve({ success: false, stdout: stdout || "", stderr: errText, exitCode: err.code ?? 1 });
5816
5841
  } else {
5817
5842
  resolve({ success: true, stdout, stderr: stderr || "", exitCode: 0 });
5818
5843
  }
@@ -5823,6 +5848,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5823
5848
  /** Start a new terminal PTY session. Returns { sessionId, cols, rows }. */
5824
5849
  terminalStart: async (params = {}, context) => {
5825
5850
  authorizeRequest(context, currentMetadata.sharing, "admin");
5851
+ const terminalClient = context?.from;
5826
5852
  const pty = await getPtyModule();
5827
5853
  const { homedir: getHomedir } = await import('os');
5828
5854
  const cols = params.cols || 80;
@@ -5877,7 +5903,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5877
5903
  server.emit({
5878
5904
  type: "svamp:terminal-output",
5879
5905
  data: { type: "output", content: batch, sessionId },
5880
- to: "*"
5906
+ to: terminalClient ?? "*"
5881
5907
  });
5882
5908
  } catch {
5883
5909
  }
@@ -5896,7 +5922,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5896
5922
  server.emit({
5897
5923
  type: "svamp:terminal-output",
5898
5924
  data: { type: "exit", content: "", sessionId, exitCode, signal },
5899
- to: "*"
5925
+ to: terminalClient ?? "*"
5900
5926
  });
5901
5927
  } catch {
5902
5928
  }
@@ -6254,7 +6280,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6254
6280
  const tunnels = handlers.tunnels;
6255
6281
  if (!tunnels) throw new Error("Tunnel management not available");
6256
6282
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
6257
- const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
6283
+ const { FrpcTunnel } = await import('./frpc-DvBVvsZC.mjs');
6258
6284
  const tunnel = new FrpcTunnel({
6259
6285
  name: params.name,
6260
6286
  ports: params.ports,
@@ -6421,8 +6447,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6421
6447
  const info = sm.getInfo();
6422
6448
  const isAdmin = serveCallerTrusted(context);
6423
6449
  const mounts = (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin));
6424
- const topUrl = isAdmin ? info.url : mounts[0]?.url ?? null;
6425
- return { ...info, url: topUrl, mounts };
6450
+ return { ...info, url: null, mounts };
6426
6451
  },
6427
6452
  /**
6428
6453
  * Aggregate frpc tunnel health for all serve mounts.
@@ -6748,7 +6773,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6748
6773
  }
6749
6774
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6750
6775
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6751
- const { toolsForRole } = await import('./sideband-C_3PVGh-.mjs');
6776
+ const { toolsForRole } = await import('./sideband-DjjXlVhw.mjs');
6752
6777
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6753
6778
  return fmt(r2);
6754
6779
  }
@@ -6853,7 +6878,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6853
6878
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
6854
6879
  }
6855
6880
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6856
- const { queryCore } = await import('./commands-BTN9XxVx.mjs');
6881
+ const { queryCore } = await import('./commands-BoGUxb2o.mjs');
6857
6882
  const timeout = c.reply?.timeout_sec || 120;
6858
6883
  let result;
6859
6884
  try {
@@ -8891,7 +8916,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8891
8916
  const ownerCtx = ownerEmail ? { user: { email: ownerEmail, id: ownerEmail } } : void 0;
8892
8917
  try {
8893
8918
  if (c.system && c.action?.kind !== "agent") {
8894
- const role = ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
8919
+ const role = r.sender.verified && ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
8895
8920
  const envelope = renderMessage(c, {
8896
8921
  sender: r.sender,
8897
8922
  body: { message: params.message },
@@ -15219,6 +15244,7 @@ function runsFile(projectRoot, workflow) {
15219
15244
  const safe = workflow.replace(/[^\w.-]+/g, "_");
15220
15245
  return join$1(runsDir(projectRoot), `${safe}.jsonl`);
15221
15246
  }
15247
+ let _runSeq = 0;
15222
15248
  function shortRunId(seed) {
15223
15249
  let h = 2166136261;
15224
15250
  for (let i = 0; i < seed.length; i++) {
@@ -15444,7 +15470,7 @@ async function runWorkflowInner(projectRoot, wf, opts) {
15444
15470
  });
15445
15471
  const exec = opts.execStep || defaultExecStep;
15446
15472
  const start = now();
15447
- const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}`);
15473
+ const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}:${_runSeq++}`);
15448
15474
  const env = wf.session ? { ...process.env, SVAMP_SESSION_ID: wf.session } : process.env;
15449
15475
  const run = {
15450
15476
  id,
@@ -16332,6 +16358,28 @@ async function writeSessionFileChunk(directory, sessionMetadata, path, content,
16332
16358
  }
16333
16359
  }
16334
16360
 
16361
+ function isStdinWritable(proc) {
16362
+ if (!proc || proc.exitCode !== null) return false;
16363
+ const stdin = proc.stdin;
16364
+ if (!stdin) return false;
16365
+ if (stdin.destroyed === true) return false;
16366
+ if (stdin.writable === false) return false;
16367
+ return true;
16368
+ }
16369
+ function serializeUserFrame(text) {
16370
+ return JSON.stringify({ type: "user", message: { role: "user", content: text } }) + "\n";
16371
+ }
16372
+ function writeUserFrame(proc, text, onError) {
16373
+ if (!isStdinWritable(proc)) return false;
16374
+ try {
16375
+ proc.stdin.write(serializeUserFrame(text));
16376
+ return true;
16377
+ } catch (err) {
16378
+ onError?.(err);
16379
+ return false;
16380
+ }
16381
+ }
16382
+
16335
16383
  const __filename$1 = fileURLToPath(import.meta.url);
16336
16384
  const __dirname$1 = dirname$1(__filename$1);
16337
16385
  const CLAUDE_SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
@@ -17206,7 +17254,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
17206
17254
  const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
17207
17255
  const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
17208
17256
  const nextMax = Math.max(curMax * 2, iter + 10);
17209
- const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-resume ${sessionId} --max ${nextMax}`;
17257
+ const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-extend ${sessionId} --reason "<why more turns are needed>" --turns ${Math.max(10, nextMax - iter)}`;
17210
17258
  sessionService.pushMessage(
17211
17259
  s.phase === "gave_up" ? { type: "message", message: msg, level: "warning" } : { type: "message", message: msg },
17212
17260
  "event"
@@ -17275,7 +17323,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
17275
17323
  maxExtensions
17276
17324
  });
17277
17325
  if (!grant.granted) {
17278
- sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-resume ${sessionId} --max <N>`, level: "warning" }, "event");
17326
+ sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-extend ${sessionId} --reason "<why>" --turns <N>`, level: "warning" }, "event");
17279
17327
  logger.log(`[svampConfig] loop-extend refused (bounded ${grant.used}/${grant.max})`);
17280
17328
  } else {
17281
17329
  const wasStopped = existing.active === false || existing.phase === "done" || existing.phase === "gave_up" || existing.phase === "cancelled";
@@ -17829,7 +17877,7 @@ async function startDaemon(options) {
17829
17877
  try {
17830
17878
  const dir = loadSessionIndex()[sessionId]?.directory;
17831
17879
  if (!dir) return;
17832
- const { reconcileServiceLinks } = await import('./agentCommands-DdNUACV8.mjs');
17880
+ const { reconcileServiceLinks } = await import('./agentCommands-BefFTl6g.mjs');
17833
17881
  const configPath = getSvampConfigPath(dir, sessionId);
17834
17882
  const config = readSvampConfig(configPath);
17835
17883
  const entries = Array.from(urls.entries());
@@ -17847,7 +17895,7 @@ async function startDaemon(options) {
17847
17895
  }
17848
17896
  }
17849
17897
  async function createExposedTunnel(spec) {
17850
- const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
17898
+ const { FrpcTunnel } = await import('./frpc-DvBVvsZC.mjs');
17851
17899
  const tunnel = new FrpcTunnel({
17852
17900
  name: spec.name,
17853
17901
  ports: spec.ports,
@@ -17875,7 +17923,7 @@ async function startDaemon(options) {
17875
17923
  ensureAutoInstalledCommands(logger);
17876
17924
  (async () => {
17877
17925
  try {
17878
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
17926
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
17879
17927
  beginClaudeVersionReconcile((msg) => logger.log(msg));
17880
17928
  } catch (e) {
17881
17929
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -19194,7 +19242,7 @@ ${parts.join("\n")}`);
19194
19242
  auto_resumes: prog.auto_resumes
19195
19243
  });
19196
19244
  const extNote = summarizeExtensions(ls.extensions, maxExt);
19197
- const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" \xB7 or set a new cap: svamp session loop-resume ${sessionId} --max <N>` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
19245
+ const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
19198
19246
  const detail = `${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${extNote ? ` [${extNote}]` : ""}. ${resumeHint}`;
19199
19247
  sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${detail}`, level: "warning" }, "event");
19200
19248
  checkSvampConfig?.();
@@ -19446,6 +19494,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19446
19494
  }
19447
19495
  return child;
19448
19496
  };
19497
+ const writeUserFrameToClaude = (text) => writeUserFrame(claudeProcess, text, (err) => {
19498
+ logger.log(`[Session ${sessionId}] Claude stdin write failed: ${err?.message || err}`);
19499
+ });
19449
19500
  const restartClaudeHandler = async (opts) => {
19450
19501
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
19451
19502
  if (isRestartingClaude || isSwitchingMode) {
@@ -19484,6 +19535,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19484
19535
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
19485
19536
  sessionService.updateMetadata(sessionMetadata);
19486
19537
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
19538
+ if ((sessionMetadata.messageQueue?.length ?? 0) > 0 && !trackedSession?.stopped) {
19539
+ setTimeout(() => processMessageQueueRef?.(), 300);
19540
+ }
19487
19541
  if (beforeRespawnError) {
19488
19542
  return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
19489
19543
  }
@@ -19708,15 +19762,26 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19708
19762
  pendingCompactTurn = isCompactCommand(text);
19709
19763
  if (!claudeProcess || claudeProcess.exitCode !== null) {
19710
19764
  spawnClaude(text, msgMeta);
19765
+ signalProcessing(true);
19766
+ sessionWasProcessing = true;
19767
+ } else if (writeUserFrameToClaude(text)) {
19768
+ signalProcessing(true);
19769
+ sessionWasProcessing = true;
19711
19770
  } else {
19712
- const stdinMsg = JSON.stringify({
19713
- type: "user",
19714
- message: { role: "user", content: text }
19715
- });
19716
- claudeProcess.stdin?.write(stdinMsg + "\n");
19771
+ logger.log(`[Session ${sessionId}] Claude stdin unwritable \u2014 requeueing message for respawn delivery`);
19772
+ const existingQueue = sessionMetadata.messageQueue || [];
19773
+ sessionMetadata = {
19774
+ ...sessionMetadata,
19775
+ messageQueue: [...existingQueue, { id: randomUUID$1(), text, createdAt: Date.now(), alreadyStored: true }]
19776
+ };
19777
+ sessionService.updateMetadata(sessionMetadata);
19778
+ signalProcessing(true);
19779
+ sessionWasProcessing = true;
19780
+ try {
19781
+ claudeProcess.kill("SIGTERM");
19782
+ } catch {
19783
+ }
19717
19784
  }
19718
- signalProcessing(true);
19719
- sessionWasProcessing = true;
19720
19785
  },
19721
19786
  onAbort: () => {
19722
19787
  logger.log(`[Session ${sessionId}] Abort requested`);
@@ -19818,6 +19883,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19818
19883
  isKillingClaude = false;
19819
19884
  if (trackedSession?.stopped) return;
19820
19885
  spawnClaude(void 0, { permissionMode: normalizedMode });
19886
+ if ((sessionMetadata.messageQueue?.length ?? 0) > 0 && !trackedSession?.stopped) {
19887
+ setTimeout(() => processMessageQueueRef?.(), 300);
19888
+ }
19821
19889
  } finally {
19822
19890
  isKillingClaude = false;
19823
19891
  isSwitchingMode = false;
@@ -20081,11 +20149,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20081
20149
  });
20082
20150
  },
20083
20151
  onIssue: async (params) => {
20084
- const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
20152
+ const { issueRpc } = await import('./rpc-BLWr4p8g.mjs');
20085
20153
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
20086
20154
  },
20087
20155
  onWorkflow: async (params) => {
20088
- const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
20156
+ const { workflowRpc } = await import('./rpc-BXpuORiQ.mjs');
20089
20157
  return workflowRpc(params?.cwd || directory, params || {});
20090
20158
  },
20091
20159
  onRipgrep: async (args, cwd) => {
@@ -20231,12 +20299,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20231
20299
  try {
20232
20300
  if (!claudeProcess || claudeProcess.exitCode !== null) {
20233
20301
  spawnClaude(next.text);
20234
- } else {
20235
- const stdinMsg = JSON.stringify({
20236
- type: "user",
20237
- message: { role: "user", content: next.text }
20238
- });
20239
- claudeProcess.stdin?.write(stdinMsg + "\n");
20302
+ } else if (writeUserFrameToClaude(next.text)) {
20240
20303
  if (claudeResumeId && !trackedSession?.stopped) {
20241
20304
  saveSession({
20242
20305
  sessionId,
@@ -20250,6 +20313,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20250
20313
  wasProcessing: true
20251
20314
  });
20252
20315
  }
20316
+ } else {
20317
+ logger.log(`[Session ${sessionId}] Claude stdin unwritable during queue drain \u2014 restoring head + respawning`);
20318
+ sessionMetadata = { ...sessionMetadata, messageQueue: [next, ...remaining] };
20319
+ sessionService.updateMetadata(sessionMetadata);
20320
+ sessionWasProcessing = false;
20321
+ try {
20322
+ claudeProcess.kill("SIGTERM");
20323
+ } catch {
20324
+ }
20253
20325
  }
20254
20326
  } catch (err) {
20255
20327
  logger.log(`[Session ${sessionId}] Error in processMessageQueue spawn: ${err.message}`);
@@ -20755,11 +20827,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20755
20827
  });
20756
20828
  },
20757
20829
  onIssue: async (params) => {
20758
- const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
20830
+ const { issueRpc } = await import('./rpc-BLWr4p8g.mjs');
20759
20831
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
20760
20832
  },
20761
20833
  onWorkflow: async (params) => {
20762
- const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
20834
+ const { workflowRpc } = await import('./rpc-BXpuORiQ.mjs');
20763
20835
  return workflowRpc(params?.cwd || directory, params || {});
20764
20836
  },
20765
20837
  onRipgrep: async (args, cwd) => {
@@ -21089,7 +21161,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
21089
21161
  const iterStop = !isCostCeiling(budgetCheck.kind);
21090
21162
  writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: now, gave_up_reason: `resource budget exhausted \u2014 ${budgetCheck.reason}`, ledger });
21091
21163
  const acpExtNote = summarizeExtensions(ls.extensions, acpMaxExt);
21092
- const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" \xB7 or set a new cap: svamp session loop-resume ${sessionId} --max <N>` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
21164
+ const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
21093
21165
  sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${acpExtNote ? ` [${acpExtNote}]` : ""}. ${acpResumeHint}`, level: "warning" }, "event");
21094
21166
  checkSvampConfig?.();
21095
21167
  return;
@@ -21717,7 +21789,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21717
21789
  }
21718
21790
  if (persistedSessions.length > 0) {
21719
21791
  try {
21720
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
21792
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
21721
21793
  await awaitClaudeVersionReady();
21722
21794
  } catch {
21723
21795
  }
@@ -21922,7 +21994,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21922
21994
  const PING_TIMEOUT_MS = 15e3;
21923
21995
  const POST_RECONNECT_GRACE_MS = 2e4;
21924
21996
  const RECONNECT_JITTER_MS = 2500;
21925
- const { WorkflowScheduler } = await import('./scheduler-CurN4UtP.mjs');
21997
+ const { WorkflowScheduler } = await import('./scheduler-7aLvf5mn.mjs');
21926
21998
  const workflowProjectRoots = () => {
21927
21999
  const dirs = /* @__PURE__ */ new Set();
21928
22000
  for (const s of pidToTrackedSession.values()) {
@@ -1,4 +1,4 @@
1
- import { j as resolveProjectRoot, E as listWorkflows, F as isWorkflowEnabled, G as workflowSchedules, H as inZone, y as runWorkflow, I as cronMatches } from './run-DLG-_xa7.mjs';
1
+ import { j as resolveProjectRoot, E as listWorkflows, F as isWorkflowEnabled, G as workflowSchedules, H as inZone, y as runWorkflow, I as cronMatches } from './run-DtVX2pQN.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-BTN9XxVx.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-BoGUxb2o.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-BTN9XxVx.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-BoGUxb2o.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-BTN9XxVx.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-BoGUxb2o.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-BTN9XxVx.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-BoGUxb2o.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -221,6 +221,7 @@ async function serveList(args, machineId) {
221
221
  const session = m.sessionId ? m.sessionId.slice(0, 8) : "-";
222
222
  console.log(` ${m.name}`);
223
223
  console.log(` Directory: ${m.directory}`);
224
+ console.log(` URL: ${m.url || "-"}`);
224
225
  console.log(` Session: ${session}`);
225
226
  console.log(` Added: ${new Date(m.addedAt).toISOString().slice(0, 16).replace("T", " ")}`);
226
227
  console.log("");
@@ -235,15 +236,15 @@ async function serveList(args, machineId) {
235
236
  }
236
237
  }
237
238
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-BTN9XxVx.mjs');
239
+ const { connectAndGetMachine } = await import('./commands-BoGUxb2o.mjs');
239
240
  const { machine, server } = await connectAndGetMachine(machineId);
240
241
  try {
241
242
  const info = await machine.serveInfo();
242
243
  console.log(`Static file server:`);
243
244
  console.log(` Status: ${info.running ? "running" : "stopped"}`);
244
245
  console.log(` Port: ${info.port || "-"}`);
245
- console.log(` URL: ${info.url || "(not exposed)"}`);
246
246
  console.log(` Mounts: ${info.mountCount}`);
247
+ console.log(` URLs: per-mount \u2014 run 'svamp serve list' to see each mount's own URL`);
247
248
  } catch (err) {
248
249
  console.error(`Error: ${err.message || err}`);
249
250
  process.exit(1);
@@ -254,20 +255,26 @@ async function serveInfo(machineId) {
254
255
  }
255
256
  function printServeHelp() {
256
257
  console.log(`
257
- svamp serve \u2014 Shared static file server
258
+ svamp serve \u2014 Static file server (per-mount subdomains)
258
259
 
259
- Serve local directories via a single daemon-managed HTTP server with one public URL.
260
- Multiple sessions can register different mount points without port conflicts.
260
+ Serve local directories via a daemon-managed HTTP server. Each mount gets its OWN
261
+ public URL on its own subdomain (static-<name>-<token>.svc\u2026) \u2014 there is NO single
262
+ server URL. Multiple sessions register different mounts without port conflicts.
263
+
264
+ IMPORTANT: use each mount's own URL exactly as printed. NEVER build a URL by
265
+ appending a path (e.g. /<name>/) to another mount's URL or to 'serve info' \u2014 that
266
+ lands on a DIFFERENT project's subdomain and serves the wrong files. To get a
267
+ mount's URL, read it from 'svamp serve <name>' (on create) or 'svamp serve list'.
261
268
 
262
269
  Usage:
263
- svamp serve <name> [directory] Add a mount and print its URL (dir defaults to .)
270
+ svamp serve <name> [directory] Add a mount and print ITS OWN url (dir defaults to .)
264
271
  svamp serve add <name> [directory] Same as above
265
272
  svamp serve apply <yaml> Apply a declarative mount config (idempotent).
266
273
  Supports static and managed-process mounts
267
274
  with wake-on-request and idle timeout.
268
275
  svamp serve remove <name> Remove a mount
269
- svamp serve list [--all] [--json] List mounts (default: current session only)
270
- svamp serve info Show server status and URL
276
+ svamp serve list [--all] [--json] List mounts + each mount's own URL (default: this session)
277
+ svamp serve info Show server status (per-mount URLs via 'serve list')
271
278
 
272
279
  Access tiers (default: link):
273
280
  (no flag) Capability URL \u2014 anyone with the URL can view.
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, O as loadMachineContext, P as buildMachineInstructions, Q as machineToolsForRole, T as buildMachineTools } from './run-DLG-_xa7.mjs';
1
+ import { R as READ_ONLY_TOOLS, O as loadMachineContext, P as buildMachineInstructions, Q as machineToolsForRole, T as buildMachineTools } from './run-DtVX2pQN.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.309",
3
+ "version": "0.2.311",
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 bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-sharing-notify-resolve.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.mjs && npx tsx test/test-backend-accounts.mjs && npx tsx test/test-codex-force-model.mjs && npx tsx test/test-backend-oauth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-user-events.mjs && npx tsx test/test-migrate-children.mjs && npx tsx test/test-outpost-install.mjs && npx tsx test/test-outpost-setup.mjs && npx tsx test/test-outpost-pairing.mjs && npx tsx test/test-outpost-coordinator.mjs && npx tsx test/e2e-outpost-transport.mjs && npx tsx test/test-inbox-reconcile.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.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-isolation-disable.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-session-access-fallback.mjs && npx tsx test/test-session-rpc-role-map.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-pinned-codex.mjs && npx tsx test/test-codex-request-timeout.mjs && npx tsx test/test-codex-app-server.mjs && npx tsx test/e2e-codex-midturn-steer.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-listener-backoff.mjs && npx tsx test/test-session-identity-env.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-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.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-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-compact-detect.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-store-lock.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-restore-decision.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-runs-lock.mjs && npx tsx test/test-workflow-idle.mjs && npx tsx test/test-workflow-reconcile.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-ws-upgrade.mjs && npx tsx test/test-serve-auth.mjs && npx tsx test/test-static-file-server.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-outbox-reload.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-store-lock.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-stateless-dispatch-limiter.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-artifact-sync-pagination.mjs && npx tsx test/test-graceful-restart.mjs && npx tsx test/test-flush-exit.mjs && npx tsx test/test-codex-skills.mjs",
23
+ "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-sharing-notify-resolve.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-stdin-delivery.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-claude-auth.mjs && npx tsx test/test-backend-accounts.mjs && npx tsx test/test-codex-force-model.mjs && npx tsx test/test-backend-oauth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-user-events.mjs && npx tsx test/test-migrate-children.mjs && npx tsx test/test-outpost-install.mjs && npx tsx test/test-outpost-setup.mjs && npx tsx test/test-outpost-pairing.mjs && npx tsx test/test-outpost-coordinator.mjs && npx tsx test/e2e-outpost-transport.mjs && npx tsx test/test-inbox-reconcile.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.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-isolation-disable.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-machine-session-visibility.mjs && npx tsx test/test-session-access-fallback.mjs && npx tsx test/test-session-rpc-role-map.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-pinned-codex.mjs && npx tsx test/test-codex-request-timeout.mjs && npx tsx test/test-codex-app-server.mjs && npx tsx test/e2e-codex-midturn-steer.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-listener-backoff.mjs && npx tsx test/test-session-identity-env.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-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.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-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-compact-detect.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-store-lock.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-restore-decision.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-runs-lock.mjs && npx tsx test/test-workflow-idle.mjs && npx tsx test/test-workflow-reconcile.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-ws-upgrade.mjs && npx tsx test/test-serve-auth.mjs && npx tsx test/test-static-file-server.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-outbox-reload.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-store-lock.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-stateless-dispatch-limiter.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-artifact-sync-pagination.mjs && npx tsx test/test-graceful-restart.mjs && npx tsx test/test-flush-exit.mjs && npx tsx test/test-codex-skills.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",