svamp-cli 0.2.326 → 0.2.327

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 (28) hide show
  1. package/dist/{adminCommands-D1NzA4Hb.mjs → adminCommands-BVXEj5XN.mjs} +1 -1
  2. package/dist/{agentCommands-DUHUBzrj.mjs → agentCommands-BiSVzb5D.mjs} +5 -5
  3. package/dist/{auth-DogM3PPm.mjs → auth-D0ji0kh3.mjs} +1 -1
  4. package/dist/{cli-PWoHWBkF.mjs → cli-CQlIsRxF.mjs} +77 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-Dm9KNB-p.mjs → commands-BkzYDwHO.mjs} +3 -3
  7. package/dist/{commands-D-4ry2I6.mjs → commands-C4z-QQdO.mjs} +11 -11
  8. package/dist/{commands-CBk4dLUP.mjs → commands-CPZdcLvm.mjs} +3 -3
  9. package/dist/{commands-Dt_MecCB.mjs → commands-CXJmXwQo.mjs} +1 -1
  10. package/dist/{commands-Ck4pP86Q.mjs → commands-Cegv66fj.mjs} +2 -2
  11. package/dist/{commands-B5bC9eXz.mjs → commands-D5R0AmXt.mjs} +3 -3
  12. package/dist/{commands-CqqQYRzg.mjs → commands-D61SEgo0.mjs} +2 -2
  13. package/dist/{commands-CxfUr2Qr.mjs → commands-DexO3t-N.mjs} +1 -1
  14. package/dist/{fleet-Bzg3YxM4.mjs → fleet-CCVC-NiE.mjs} +2 -2
  15. package/dist/{headlessCli-DvJI9DRh.mjs → headlessCli-CFgDFTPd.mjs} +2 -2
  16. package/dist/{httpServer-1XjB2h3K.mjs → httpServer-Be1Hcddr.mjs} +59 -38
  17. package/dist/index.mjs +1 -1
  18. package/dist/{notifyCommands-BpMErHcv.mjs → notifyCommands-BfLeyHId.mjs} +1 -1
  19. package/dist/package-TcR2vlFS.mjs +64 -0
  20. package/dist/{rpc-Qcj4M_LQ.mjs → rpc-DXY3KqiA.mjs} +1 -1
  21. package/dist/{rpc-27xBajCe.mjs → rpc-hdw0ZlCj.mjs} +1 -1
  22. package/dist/{run-CocRShOR.mjs → run-CewXMfIf.mjs} +1 -1
  23. package/dist/{run-BBBr4jkI.mjs → run-DdTcDN3e.mjs} +190 -42
  24. package/dist/{scheduler-DjAVEyn3.mjs → scheduler-3LUL1jFD.mjs} +1 -1
  25. package/dist/{serveCommands-BK-wWc_i.mjs → serveCommands-DUJ4eojk.mjs} +10 -10
  26. package/dist/{sideband-5ck3CDWM.mjs → sideband-BRHu4M46.mjs} +1 -1
  27. package/package.json +3 -3
  28. package/dist/package-D_BgajkI.mjs +0 -64
@@ -3001,6 +3001,13 @@ function assertNonAdminMountDirSafe(directory, homeDir, resolveReal = defaultRes
3001
3001
  }
3002
3002
  }
3003
3003
  }
3004
+ function hiddenSegmentOf(relPath) {
3005
+ for (const seg of relPath.split(/[\\/]+/)) {
3006
+ if (!seg || seg === ".") continue;
3007
+ if (seg.startsWith(".")) return seg;
3008
+ }
3009
+ return null;
3010
+ }
3004
3011
  function defaultResolveReal(p) {
3005
3012
  try {
3006
3013
  return fs.realpathSync(p);
@@ -3203,8 +3210,19 @@ class ServeManager {
3203
3210
  * Extract a channel capability token from the request, if one is present. A channel caller
3204
3211
  * key is `ck_`-prefixed; we ONLY return a token that carries that prefix so a Hypha JWT
3205
3212
  * bearer (used by the email-identity path) never collides with the capability authorizer.
3206
- * Accepted carriers: `Authorization: Bearer ck_…` header, or `?key=ck_…` query param.
3207
- * Returns null when no capability token is presented. NEVER log the returned value.
3213
+ * Accepted carriers: `Authorization: Bearer ck_…` header (any verb), or `?key=ck_…` query
3214
+ * param (READ verbs only). Returns null when no capability token is presented.
3215
+ * NEVER log the returned value.
3216
+ *
3217
+ * #1047: the query carrier is restricted to GET/HEAD. A query string is a poor place for a
3218
+ * credential — it lands in browser history, in the `Referer` of any HTML the gateway serves,
3219
+ * and in every tunnel/intermediary access log on the path (and the gateway is reached over an
3220
+ * frps tunnel whose logs the user does not control). This is the same exposure class #1006
3221
+ * closed for the Hypha JWT in download URLs. It is kept for reads because a browser-embeddable
3222
+ * URL genuinely needs it (an `<img src>` or a download link cannot set a header), but a leaked
3223
+ * URL must not also confer PUT/DELETE over the whole project. The generated skill already
3224
+ * documents only the Authorization form (see `fileSection` in channel/store.ts), so this
3225
+ * narrows an unused carrier rather than breaking the documented path.
3208
3226
  */
3209
3227
  extractCapabilityToken(req, url) {
3210
3228
  const auth = req.headers["authorization"];
@@ -3212,6 +3230,8 @@ class ServeManager {
3212
3230
  const m = /^Bearer\s+(\S+)$/i.exec(auth.trim());
3213
3231
  if (m && m[1].startsWith("ck_")) return m[1];
3214
3232
  }
3233
+ const method = (req.method || "GET").toUpperCase();
3234
+ if (method !== "GET" && method !== "HEAD") return null;
3215
3235
  const q = url.searchParams.get("key");
3216
3236
  if (q && q.startsWith("ck_")) return q;
3217
3237
  return null;
@@ -3857,6 +3877,14 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
3857
3877
  }
3858
3878
  const rootDir = target.cwd;
3859
3879
  const normRel = this.toSessionRelative(rootDir, m[2] || "");
3880
+ if (capabilityGranted) {
3881
+ const badSeg = hiddenSegmentOf(normRel);
3882
+ if (badSeg) {
3883
+ this.log(`Files-gateway DENY '${sessionId}': channel capability may not reach a hidden path (${badSeg})`);
3884
+ deny(403, `Forbidden: channel file access may not reach hidden paths (${badSeg})`);
3885
+ return;
3886
+ }
3887
+ }
3860
3888
  if (method === "GET" || method === "HEAD") {
3861
3889
  if (url.searchParams.get("list") === "1") {
3862
3890
  const lex = this.resolveContainedPath(rootDir, normRel);
@@ -3878,6 +3906,7 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
3878
3906
  }
3879
3907
  return { name: d.name, isDir, size, mtime };
3880
3908
  });
3909
+ if (capabilityGranted) entries = entries.filter((e) => !e.name.startsWith("."));
3881
3910
  } catch (err) {
3882
3911
  deny(err?.code === "ENOENT" ? 404 : 500, `Listing failed: ${err?.message || err}`);
3883
3912
  return;
@@ -4458,6 +4487,7 @@ var serveManager = /*#__PURE__*/Object.freeze({
4458
4487
  ServeManager: ServeManager,
4459
4488
  assertNonAdminMountDirSafe: assertNonAdminMountDirSafe,
4460
4489
  buildLinkSubdomain: buildLinkSubdomain,
4490
+ hiddenSegmentOf: hiddenSegmentOf,
4461
4491
  resolveMountOwnerEmail: resolveMountOwnerEmail,
4462
4492
  sanitizeMountForRole: sanitizeMountForRole
4463
4493
  });
@@ -4636,8 +4666,13 @@ function applyPromptCacheTtl(spawnEnv) {
4636
4666
  const cacheOverride = spawnEnv.ENABLE_PROMPT_CACHING_1H ?? process.env.ENABLE_PROMPT_CACHING_1H ?? spawnEnv.FORCE_PROMPT_CACHING_5M ?? process.env.FORCE_PROMPT_CACHING_5M;
4637
4667
  if (cacheOverride === void 0) spawnEnv.ENABLE_PROMPT_CACHING_1H = "1";
4638
4668
  }
4669
+ function applyTodoTools(spawnEnv) {
4670
+ const override = spawnEnv.CLAUDE_CODE_ENABLE_TODO_TOOLS ?? process.env.CLAUDE_CODE_ENABLE_TODO_TOOLS;
4671
+ if (override === void 0) spawnEnv.CLAUDE_CODE_ENABLE_TODO_TOOLS = "1";
4672
+ }
4639
4673
  function applyClaudeProxyEnv(spawnEnv) {
4640
4674
  const mode = currentMode();
4675
+ applyTodoTools(spawnEnv);
4641
4676
  if (mode === "hypha") {
4642
4677
  const proxyUrl = resolveHyphaProxyUrl();
4643
4678
  if (!proxyUrl) {
@@ -5692,20 +5727,22 @@ function channelKeyGrantsFileAccess(channels, token) {
5692
5727
  }
5693
5728
  };
5694
5729
  for (const ch of channels) {
5730
+ if (ch.enabled === false) continue;
5695
5731
  if (!ch.fileAccess?.enabled) continue;
5696
5732
  if (eq(ch.identity?.shared_key)) return true;
5697
5733
  for (const caller of ch.identity?.callers || []) if (eq(caller.key)) return true;
5698
5734
  }
5699
5735
  return false;
5700
5736
  }
5737
+ const _channelListCache = /* @__PURE__ */ new Map();
5738
+ const _CHANNEL_CACHE_TTL_MS = 1e3;
5739
+ function invalidateChannelCache(dir) {
5740
+ _channelListCache.delete(dir);
5741
+ }
5701
5742
  class ChannelStore {
5702
5743
  dir;
5703
5744
  constructor(projectDir) {
5704
5745
  this.dir = join$1(projectDir, ".svamp", "channels");
5705
- try {
5706
- mkdirSync$1(this.dir, { recursive: true });
5707
- } catch {
5708
- }
5709
5746
  }
5710
5747
  // #0982: reject a traversing/oversized id at the path boundary itself, so no
5711
5748
  // caller (RPC, HTTP, CLI) can reach outside this.dir regardless of its own checks.
@@ -5728,17 +5765,41 @@ class ChannelStore {
5728
5765
  const tmp = `${this._path(c.id)}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
5729
5766
  writeFileSync$1(tmp, JSON.stringify(c, null, 2), { mode: 384 });
5730
5767
  renameSync(tmp, this._path(c.id));
5768
+ invalidateChannelCache(this.dir);
5731
5769
  return c;
5732
5770
  }
5771
+ /**
5772
+ * All channels in this project, parsed. Cached per directory and revalidated with ONE statSync
5773
+ * — see the #1049/#1068 note above for why the uncached version was a daemon-wide stall.
5774
+ */
5733
5775
  list() {
5734
- if (!existsSync(this.dir)) return [];
5735
- return readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
5736
- try {
5737
- return normalizeBind(JSON.parse(readFileSync(join$1(this.dir, f), "utf8")));
5738
- } catch {
5739
- return null;
5740
- }
5741
- }).filter((c) => !!c);
5776
+ let st;
5777
+ try {
5778
+ const s = statSync(this.dir);
5779
+ st = { mtimeMs: s.mtimeMs, size: s.size };
5780
+ } catch {
5781
+ _channelListCache.delete(this.dir);
5782
+ return [];
5783
+ }
5784
+ const hit = _channelListCache.get(this.dir);
5785
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size && Date.now() - hit.at < _CHANNEL_CACHE_TTL_MS) {
5786
+ return hit.channels;
5787
+ }
5788
+ let channels;
5789
+ try {
5790
+ channels = readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
5791
+ try {
5792
+ return normalizeBind(JSON.parse(readFileSync(join$1(this.dir, f), "utf8")));
5793
+ } catch {
5794
+ return null;
5795
+ }
5796
+ }).filter((c) => !!c);
5797
+ } catch {
5798
+ _channelListCache.delete(this.dir);
5799
+ return [];
5800
+ }
5801
+ _channelListCache.set(this.dir, { mtimeMs: st.mtimeMs, size: st.size, at: Date.now(), channels });
5802
+ return channels;
5742
5803
  }
5743
5804
  get(id) {
5744
5805
  try {
@@ -5756,6 +5817,7 @@ class ChannelStore {
5756
5817
  const p = this._path(id);
5757
5818
  if (existsSync(p)) {
5758
5819
  rmSync$1(p);
5820
+ invalidateChannelCache(this.dir);
5759
5821
  return true;
5760
5822
  }
5761
5823
  return false;
@@ -5796,6 +5858,28 @@ class ChannelStore {
5796
5858
  return caller;
5797
5859
  });
5798
5860
  }
5861
+ /**
5862
+ * #1036: revoke ONE caller's key without destroying the channel.
5863
+ *
5864
+ * Before this, the only removal primitive was remove(id) — so withdrawing a single leaked key
5865
+ * meant deleting the whole channel and every other caller with it. That is a bad enough trade
5866
+ * that in practice a leaked key just stayed live, which matters much more now that a caller key
5867
+ * can carry whole-project file access (see channelKeyGrantsFileAccess).
5868
+ *
5869
+ * Same per-channel lock as the sibling mutators (#0679), so a concurrent addCaller cannot
5870
+ * resurrect the revoked entry by writing a stale base. Returns true when a caller was removed.
5871
+ */
5872
+ removeCaller(id, name) {
5873
+ return withFileLock(this._lock(id), () => {
5874
+ const c = this.get(id);
5875
+ if (!c?.identity?.callers) return false;
5876
+ const before = c.identity.callers.length;
5877
+ c.identity.callers = c.identity.callers.filter((x) => x.name !== name);
5878
+ if (c.identity.callers.length === before) return false;
5879
+ this._writeChannel(c);
5880
+ return true;
5881
+ });
5882
+ }
5799
5883
  }
5800
5884
  function routingSession(channel, ctx) {
5801
5885
  const mode = bindMode(channel);
@@ -6435,6 +6519,9 @@ function getParamNames(fn) {
6435
6519
  const terminalSessions = /* @__PURE__ */ new Map();
6436
6520
  let ptyModule = null;
6437
6521
  const TERMINAL_IDLE_MS = 30 * 60 * 1e3;
6522
+ const TERMINAL_CLIENT_ABSENT_MS = 4 * 60 * 60 * 1e3;
6523
+ const TERMINAL_BUFFER_MAX_BYTES = 1024 * 1024;
6524
+ const TERMINAL_MAX_SESSIONS = 64;
6438
6525
  let terminalReaper = null;
6439
6526
  function ensureTerminalReaper() {
6440
6527
  if (terminalReaper) return;
@@ -6442,7 +6529,8 @@ function ensureTerminalReaper() {
6442
6529
  const now = Date.now();
6443
6530
  for (const [id, s] of terminalSessions) {
6444
6531
  if (s.exited) continue;
6445
- if (now - s.lastActivity > TERMINAL_IDLE_MS) {
6532
+ const abandoned = now - (s.lastClientActivity ?? s.lastActivity) > TERMINAL_CLIENT_ABSENT_MS;
6533
+ if (now - s.lastActivity > TERMINAL_IDLE_MS || abandoned) {
6446
6534
  try {
6447
6535
  s.pty.kill();
6448
6536
  } catch {
@@ -7296,8 +7384,29 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7296
7384
  rows,
7297
7385
  createdAt: Date.now(),
7298
7386
  lastActivity: Date.now(),
7387
+ lastClientActivity: Date.now(),
7388
+ outputBytes: 0,
7299
7389
  cwd
7300
7390
  };
7391
+ if (terminalSessions.size >= TERMINAL_MAX_SESSIONS) {
7392
+ let victim = null, oldest = Infinity;
7393
+ for (const [id, s] of terminalSessions) {
7394
+ const seen = s.lastClientActivity ?? s.lastActivity;
7395
+ if (seen < oldest) {
7396
+ oldest = seen;
7397
+ victim = id;
7398
+ }
7399
+ }
7400
+ if (victim) {
7401
+ const v = terminalSessions.get(victim);
7402
+ try {
7403
+ v?.pty.kill();
7404
+ } catch {
7405
+ }
7406
+ terminalSessions.delete(victim);
7407
+ console.log(`[terminal] evicted idlest session ${victim} \u2014 at the ${TERMINAL_MAX_SESSIONS}-PTY ceiling`);
7408
+ }
7409
+ }
7301
7410
  terminalSessions.set(sessionId, session);
7302
7411
  ensureTerminalReaper();
7303
7412
  let outputBatch = "";
@@ -7308,8 +7417,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7308
7417
  const batch = outputBatch;
7309
7418
  outputBatch = "";
7310
7419
  session.outputBuffer.push(batch);
7311
- if (session.outputBuffer.length > 1e3) {
7312
- session.outputBuffer.splice(0, session.outputBuffer.length - 500);
7420
+ session.outputBytes += batch.length;
7421
+ while (session.outputBytes > TERMINAL_BUFFER_MAX_BYTES && session.outputBuffer.length > 1) {
7422
+ session.outputBytes -= (session.outputBuffer.shift() || "").length;
7313
7423
  }
7314
7424
  if (terminalClient) {
7315
7425
  try {
@@ -7358,6 +7468,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7358
7468
  const filtered = filterTerminalResponses(params.data);
7359
7469
  if (filtered) session.pty.write(filtered);
7360
7470
  session.lastActivity = Date.now();
7471
+ session.lastClientActivity = Date.now();
7361
7472
  return { success: true };
7362
7473
  },
7363
7474
  /** Resize a terminal session. */
@@ -7379,7 +7490,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7379
7490
  const session = terminalSessions.get(params.sessionId);
7380
7491
  if (!session) throw new Error(`Terminal session ${params.sessionId} not found`);
7381
7492
  const output = session.outputBuffer.splice(0).join("");
7493
+ session.outputBytes = 0;
7382
7494
  session.lastActivity = Date.now();
7495
+ session.lastClientActivity = Date.now();
7383
7496
  const result = {
7384
7497
  output,
7385
7498
  exited: session.exited
@@ -8189,7 +8302,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8189
8302
  }
8190
8303
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
8191
8304
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
8192
- const { toolsForRole } = await import('./sideband-5ck3CDWM.mjs');
8305
+ const { toolsForRole } = await import('./sideband-BRHu4M46.mjs');
8193
8306
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
8194
8307
  return fmt(r2);
8195
8308
  }
@@ -8294,7 +8407,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8294
8407
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8295
8408
  }
8296
8409
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8297
- const { queryCore } = await import('./commands-CxfUr2Qr.mjs');
8410
+ const { queryCore } = await import('./commands-DexO3t-N.mjs');
8298
8411
  const timeout = c.reply?.timeout_sec || 120;
8299
8412
  let result;
8300
8413
  try {
@@ -9442,6 +9555,7 @@ function loadMessagesFromDiskReverse(messagesDir, beforeSeq, limit) {
9442
9555
  }
9443
9556
  }
9444
9557
  const messageCountCache = /* @__PURE__ */ new Map();
9558
+ const MESSAGE_COUNT_CACHE_MAX = 2e3;
9445
9559
  function countMessagesOnDisk(messagesDir, fallbackInMemory) {
9446
9560
  const filePath = join$1(messagesDir, "messages.jsonl");
9447
9561
  if (!existsSync(filePath)) return fallbackInMemory;
@@ -9469,7 +9583,13 @@ function countMessagesOnDisk(messagesDir, fallbackInMemory) {
9469
9583
  for (let i = 0; i < data.length; i++) if (data.charCodeAt(i) === 10) count++;
9470
9584
  if (data.length > 0 && data[data.length - 1] !== "\n") count++;
9471
9585
  }
9586
+ messageCountCache.delete(filePath);
9472
9587
  messageCountCache.set(filePath, { size: st.size, mtimeMs: st.mtimeMs, count });
9588
+ while (messageCountCache.size > MESSAGE_COUNT_CACHE_MAX) {
9589
+ const oldest = messageCountCache.keys().next();
9590
+ if (oldest.done) break;
9591
+ messageCountCache.delete(oldest.value);
9592
+ }
9473
9593
  return count;
9474
9594
  } catch {
9475
9595
  return fallbackInMemory;
@@ -10820,7 +10940,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
10820
10940
  // ── Listener Registration ──
10821
10941
  registerListener: async (callback, context) => {
10822
10942
  authorizeRequest(context, metadata.sharing, "view");
10943
+ const MAX_LISTENERS = 64;
10823
10944
  listeners.push(callback);
10945
+ while (listeners.length > MAX_LISTENERS) listeners.shift();
10824
10946
  const replayMessages = messages.slice(-50);
10825
10947
  const REPLAY_MESSAGE_TIMEOUT_MS = 1e4;
10826
10948
  for (const msg of replayMessages) {
@@ -13698,6 +13820,25 @@ var acpAgentConfig = /*#__PURE__*/Object.freeze({
13698
13820
  resolveAcpAgentConfig: resolveAcpAgentConfig
13699
13821
  });
13700
13822
 
13823
+ const COMPLETED_REQUESTS_MAX = 500;
13824
+ function recordCompletedRequest(existing, id, entry, max = COMPLETED_REQUESTS_MAX) {
13825
+ const out = { ...existing || {} };
13826
+ delete out[id];
13827
+ const slim = {
13828
+ completedAt: typeof entry.completedAt === "number" ? entry.completedAt : Date.now(),
13829
+ status: String(entry.status ?? "")
13830
+ };
13831
+ if (typeof entry.tool === "string") slim.tool = entry.tool;
13832
+ if (entry.reason !== void 0) slim.reason = entry.reason;
13833
+ if (entry.mode !== void 0) slim.mode = entry.mode;
13834
+ if (entry.decision !== void 0) slim.decision = entry.decision;
13835
+ if (Array.isArray(entry.allowedTools)) slim.allowedTools = entry.allowedTools;
13836
+ out[id] = slim;
13837
+ const keys = Object.keys(out);
13838
+ for (let i = 0; i < keys.length - max; i++) delete out[keys[i]];
13839
+ return out;
13840
+ }
13841
+
13701
13842
  function applyPermissionResolution(sessionService, requestId, approved) {
13702
13843
  const reqs = { ...sessionService._agentState?.requests };
13703
13844
  if (!(requestId in reqs)) return;
@@ -13707,14 +13848,12 @@ function applyPermissionResolution(sessionService, requestId, approved) {
13707
13848
  sessionService.updateAgentState({
13708
13849
  controlledByUser: false,
13709
13850
  requests: reqs,
13710
- completedRequests: {
13711
- ...completedReqs,
13712
- [requestId]: {
13713
- ...existingReq || {},
13714
- completedAt: Date.now(),
13715
- status: approved ? "approved" : "denied"
13716
- }
13717
- }
13851
+ // #1041: bounded + slimmed, same as the Claude permission path in daemon/run.ts.
13852
+ completedRequests: recordCompletedRequest(completedReqs, requestId, {
13853
+ ...existingReq || {},
13854
+ completedAt: Date.now(),
13855
+ status: approved ? "approved" : "denied"
13856
+ })
13718
13857
  });
13719
13858
  }
13720
13859
  function bridgeAcpToSession(backend, sessionService, getMetadata, setMetadata, log, onTurnEnd, getModelLabel) {
@@ -18035,6 +18174,9 @@ function shouldRunZombieProbe(args) {
18035
18174
  function nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures) {
18036
18175
  return consecutiveHeartbeatFailures + 1;
18037
18176
  }
18177
+ function shouldRunServiceProbe(args) {
18178
+ return !args.inGrace && !args.zombieProbeRan;
18179
+ }
18038
18180
  function shouldForceReconnect(consecutiveHeartbeatFailures) {
18039
18181
  if (consecutiveHeartbeatFailures < 2) return false;
18040
18182
  return consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0;
@@ -20082,7 +20224,7 @@ async function startDaemon(options) {
20082
20224
  try {
20083
20225
  const dir = loadSessionIndex()[sessionId]?.directory;
20084
20226
  if (!dir) return;
20085
- const { reconcileServiceLinks } = await import('./agentCommands-DUHUBzrj.mjs');
20227
+ const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20086
20228
  const configPath = getSvampConfigPath(dir, sessionId);
20087
20229
  const config = readSvampConfig(configPath);
20088
20230
  const entries = Array.from(urls.entries());
@@ -20104,7 +20246,7 @@ async function startDaemon(options) {
20104
20246
  try {
20105
20247
  const dir = loadSessionIndex()[sessionId]?.directory;
20106
20248
  if (!dir) return;
20107
- const { reconcileServiceLinks } = await import('./agentCommands-DUHUBzrj.mjs');
20249
+ const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20108
20250
  const configPath = getSvampConfigPath(dir, sessionId);
20109
20251
  const config = readSvampConfig(configPath);
20110
20252
  const incoming = [{
@@ -20125,7 +20267,7 @@ async function startDaemon(options) {
20125
20267
  try {
20126
20268
  const dir = loadSessionIndex()[sessionId]?.directory;
20127
20269
  if (!dir) return;
20128
- const { dropServiceLinks } = await import('./agentCommands-DUHUBzrj.mjs');
20270
+ const { dropServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20129
20271
  const configPath = getSvampConfigPath(dir, sessionId);
20130
20272
  const config = readSvampConfig(configPath);
20131
20273
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -21388,15 +21530,19 @@ ${parts.join("\n")}`);
21388
21530
  sessionService.updateAgentState({
21389
21531
  controlledByUser: false,
21390
21532
  requests: reqs,
21391
- completedRequests: {
21392
- ...sessionService._agentState?.completedRequests,
21393
- [correlationId]: {
21533
+ // #1041: bounded + slimmed. `arguments: toolInput` used to
21534
+ // be stored here — the whole tool input, e.g. a full Write
21535
+ // body — in a map that grew forever and is re-walked by the
21536
+ // reducer on every streamed batch.
21537
+ completedRequests: recordCompletedRequest(
21538
+ sessionService._agentState?.completedRequests,
21539
+ correlationId,
21540
+ {
21394
21541
  tool: toolName,
21395
- arguments: toolInput,
21396
21542
  completedAt: Date.now(),
21397
21543
  status: result.behavior === "allow" ? "approved" : "denied"
21398
21544
  }
21399
- }
21545
+ )
21400
21546
  });
21401
21547
  }).catch((err) => {
21402
21548
  logger.log(`[Session ${sessionId}] Permission handler error (request ${requestId}): ${err}`);
@@ -22497,11 +22643,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22497
22643
  });
22498
22644
  },
22499
22645
  onIssue: async (params) => {
22500
- const { issueRpc } = await import('./rpc-Qcj4M_LQ.mjs');
22646
+ const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
22501
22647
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22502
22648
  },
22503
22649
  onWorkflow: async (params) => {
22504
- const { workflowRpc } = await import('./rpc-27xBajCe.mjs');
22650
+ const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
22505
22651
  return workflowRpc(params?.cwd || directory, params || {});
22506
22652
  },
22507
22653
  onRipgrep: async (args, cwd) => {
@@ -23207,11 +23353,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23207
23353
  });
23208
23354
  },
23209
23355
  onIssue: async (params) => {
23210
- const { issueRpc } = await import('./rpc-Qcj4M_LQ.mjs');
23356
+ const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
23211
23357
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23212
23358
  },
23213
23359
  onWorkflow: async (params) => {
23214
- const { workflowRpc } = await import('./rpc-27xBajCe.mjs');
23360
+ const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
23215
23361
  return workflowRpc(params?.cwd || directory, params || {});
23216
23362
  },
23217
23363
  onRipgrep: async (args, cwd) => {
@@ -24119,7 +24265,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24119
24265
  const channelHttpPort = Number(process.env.SVAMP_CHANNEL_HTTP_PORT) || 0;
24120
24266
  if (channelHttpPort > 0) {
24121
24267
  try {
24122
- const { createChannelHttpServer } = await import('./httpServer-1XjB2h3K.mjs');
24268
+ const { createChannelHttpServer } = await import('./httpServer-Be1Hcddr.mjs');
24123
24269
  const channelHttpServer = createChannelHttpServer({
24124
24270
  getSessionIds: () => {
24125
24271
  const ids = [];
@@ -24548,7 +24694,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24548
24694
  const PING_TIMEOUT_MS = 15e3;
24549
24695
  const POST_RECONNECT_GRACE_MS = 2e4;
24550
24696
  const RECONNECT_JITTER_MS = 2500;
24551
- const { WorkflowScheduler } = await import('./scheduler-DjAVEyn3.mjs');
24697
+ const { WorkflowScheduler } = await import('./scheduler-3LUL1jFD.mjs');
24552
24698
  const workflowProjectRoots = () => {
24553
24699
  const dirs = /* @__PURE__ */ new Set();
24554
24700
  for (const s of pidToTrackedSession.values()) {
@@ -24627,7 +24773,9 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24627
24773
  const INBOUND_SILENCE_THRESHOLD_MS = 12e4;
24628
24774
  const inboundSilenceMs = Date.now() - machineService.getLastInboundRpcAt();
24629
24775
  const hasActiveSessions = pidToTrackedSession.size > 0;
24776
+ let zombieProbeRan = false;
24630
24777
  if (shouldRunZombieProbe({ hasActiveSessions, inboundSilenceMs, consecutiveHeartbeatFailures, inGrace, thresholdMs: INBOUND_SILENCE_THRESHOLD_MS })) {
24778
+ zombieProbeRan = true;
24631
24779
  logger.log(`No inbound RPC for ${Math.round(inboundSilenceMs / 1e3)}s with ${pidToTrackedSession.size} active session(s) \u2014 zombie probe`);
24632
24780
  try {
24633
24781
  const machineServiceId = `${server.config.client_id}:default`;
@@ -24640,7 +24788,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24640
24788
  consecutiveHeartbeatFailures = nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures);
24641
24789
  }
24642
24790
  }
24643
- if (!inGrace) {
24791
+ if (shouldRunServiceProbe({ inGrace, zombieProbeRan })) {
24644
24792
  try {
24645
24793
  const pingStart = Date.now();
24646
24794
  const machineServiceId = `${server.config.client_id}:default`;
@@ -1,4 +1,4 @@
1
- import { f as resolveProjectRoot, C as listWorkflows, D as isWorkflowEnabled, E as workflowSchedules, F as inZone, w as runWorkflow, G as cronMatches } from './run-BBBr4jkI.mjs';
1
+ import { f as resolveProjectRoot, C as listWorkflows, D as isWorkflowEnabled, E as workflowSchedules, F as inZone, w as runWorkflow, G as cronMatches } from './run-DdTcDN3e.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
- import { w as wantsHelp } from './cli-PWoHWBkF.mjs';
3
- import './run-BBBr4jkI.mjs';
2
+ import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
3
+ import './run-DdTcDN3e.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -77,7 +77,7 @@ async function handleServeCommand() {
77
77
  }
78
78
  }
79
79
  async function serveAdd(args, machineId) {
80
- const { connectAndGetMachine } = await import('./commands-CxfUr2Qr.mjs');
80
+ const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
81
81
  const pos = positionalArgs(args);
82
82
  const name = pos[0];
83
83
  if (!name) {
@@ -110,7 +110,7 @@ async function serveAdd(args, machineId) {
110
110
  }
111
111
  if (sessionId && result?.url) {
112
112
  try {
113
- const { autoAddSessionLink } = await import('./agentCommands-DUHUBzrj.mjs');
113
+ const { autoAddSessionLink } = await import('./agentCommands-BiSVzb5D.mjs');
114
114
  autoAddSessionLink(String(result.url), name, void 0, { kind: "serve", name });
115
115
  } catch {
116
116
  }
@@ -124,7 +124,7 @@ async function serveAdd(args, machineId) {
124
124
  }
125
125
  }
126
126
  async function serveApply(args, machineId) {
127
- const { connectAndGetMachine } = await import('./commands-CxfUr2Qr.mjs');
127
+ const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
128
128
  const fs = await import('fs');
129
129
  const yaml = await import('yaml');
130
130
  const file = positionalArgs(args)[0];
@@ -206,7 +206,7 @@ async function serveApply(args, machineId) {
206
206
  console.log(`URL: ${result.url}`);
207
207
  if (params.sessionId && result?.url) {
208
208
  try {
209
- const { autoAddSessionLink } = await import('./agentCommands-DUHUBzrj.mjs');
209
+ const { autoAddSessionLink } = await import('./agentCommands-BiSVzb5D.mjs');
210
210
  const prevSession = process.env.SVAMP_SESSION_ID;
211
211
  process.env.SVAMP_SESSION_ID = String(params.sessionId);
212
212
  try {
@@ -227,7 +227,7 @@ async function serveApply(args, machineId) {
227
227
  }
228
228
  }
229
229
  async function serveRemove(args, machineId) {
230
- const { connectAndGetMachine } = await import('./commands-CxfUr2Qr.mjs');
230
+ const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
231
231
  const pos = positionalArgs(args);
232
232
  const name = pos[0];
233
233
  if (!name) {
@@ -237,7 +237,7 @@ async function serveRemove(args, machineId) {
237
237
  const { machine, server } = await connectAndGetMachine(machineId);
238
238
  try {
239
239
  await machine.serveRemove({ name });
240
- const { removeSessionLinkByService } = await import('./agentCommands-DUHUBzrj.mjs');
240
+ const { removeSessionLinkByService } = await import('./agentCommands-BiSVzb5D.mjs');
241
241
  removeSessionLinkByService("serve", name);
242
242
  console.log(`Mount '${name}' removed.`);
243
243
  } catch (err) {
@@ -249,7 +249,7 @@ async function serveRemove(args, machineId) {
249
249
  }
250
250
  }
251
251
  async function serveList(args, machineId) {
252
- const { connectAndGetMachine } = await import('./commands-CxfUr2Qr.mjs');
252
+ const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
253
253
  const all = hasFlag(args, "--all", "-a");
254
254
  const json = hasFlag(args, "--json");
255
255
  const sessionId = getFlag(args, "--session");
@@ -283,7 +283,7 @@ async function serveList(args, machineId) {
283
283
  }
284
284
  }
285
285
  async function serveInfo(machineId) {
286
- const { connectAndGetMachine } = await import('./commands-CxfUr2Qr.mjs');
286
+ const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
287
287
  const { machine, server } = await connectAndGetMachine(machineId);
288
288
  try {
289
289
  const info = await machine.serveInfo();
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, M as loadMachineContext, N as buildMachineInstructions, O as machineToolsForRole, P as buildMachineTools } from './run-BBBr4jkI.mjs';
1
+ import { R as READ_ONLY_TOOLS, M as loadMachineContext, N as buildMachineInstructions, O as machineToolsForRole, P as buildMachineTools } from './run-DdTcDN3e.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';