switchroom 0.18.6 → 0.18.8

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 (116) hide show
  1. package/dist/agent-scheduler/index.js +1 -0
  2. package/dist/auth-broker/index.js +1 -0
  3. package/dist/cli/autoaccept-poll.js +140 -33
  4. package/dist/cli/notion-write-pretool.mjs +1 -0
  5. package/dist/cli/switchroom.js +1172 -812
  6. package/dist/host-control/main.js +2 -1
  7. package/dist/vault/approvals/kernel-server.js +1 -0
  8. package/dist/vault/broker/server.js +1 -0
  9. package/package.json +3 -3
  10. package/profiles/_base/cron-session.sh.hbs +55 -16
  11. package/profiles/_base/start.sh.hbs +146 -50
  12. package/profiles/default/CLAUDE.md.hbs +1 -1
  13. package/skills/switchroom-runtime/SKILL.md +2 -0
  14. package/telegram-plugin/dist/bridge/bridge.js +22 -0
  15. package/telegram-plugin/dist/gateway/gateway.js +2965 -862
  16. package/telegram-plugin/dist/server.js +24 -0
  17. package/telegram-plugin/flood-circuit-breaker.ts +123 -0
  18. package/telegram-plugin/gateway/activity-card-store.ts +63 -18
  19. package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
  20. package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
  21. package/telegram-plugin/gateway/boot-card.ts +27 -0
  22. package/telegram-plugin/gateway/busy-ack.ts +106 -0
  23. package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
  24. package/telegram-plugin/gateway/gateway.ts +1618 -198
  25. package/telegram-plugin/gateway/inbound-spool.ts +2 -1
  26. package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
  27. package/telegram-plugin/gateway/inject-handler.ts +17 -0
  28. package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
  29. package/telegram-plugin/gateway/ipc-server.ts +40 -0
  30. package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
  31. package/telegram-plugin/gateway/model-command.ts +227 -54
  32. package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
  33. package/telegram-plugin/gateway/pending-card-store.ts +173 -0
  34. package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
  35. package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
  36. package/telegram-plugin/gateway/session-model-file.ts +198 -0
  37. package/telegram-plugin/gateway/session-model-source.ts +73 -0
  38. package/telegram-plugin/gateway/status-pin-store.ts +82 -22
  39. package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
  40. package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
  41. package/telegram-plugin/hooks/hooks.json +10 -10
  42. package/telegram-plugin/hooks/run-hook.sh +84 -0
  43. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
  44. package/telegram-plugin/model-label.ts +69 -0
  45. package/telegram-plugin/model-unavailable.ts +26 -0
  46. package/telegram-plugin/operator-events.ts +24 -0
  47. package/telegram-plugin/permission-diff.ts +128 -0
  48. package/telegram-plugin/pty-partial-handler.ts +39 -0
  49. package/telegram-plugin/registry/subagents-schema.ts +80 -1
  50. package/telegram-plugin/registry/subagents.test.ts +90 -0
  51. package/telegram-plugin/render/rich-render.ts +79 -1
  52. package/telegram-plugin/retry-api-call.ts +62 -0
  53. package/telegram-plugin/session-tail.ts +28 -0
  54. package/telegram-plugin/shared/bot-runtime.ts +8 -1
  55. package/telegram-plugin/silence-poke.ts +14 -0
  56. package/telegram-plugin/silent-end.ts +49 -4
  57. package/telegram-plugin/stream-controller.ts +156 -38
  58. package/telegram-plugin/subagent-watcher.ts +222 -37
  59. package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
  60. package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
  61. package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
  62. package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
  63. package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
  64. package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
  65. package/telegram-plugin/tests/busy-ack.test.ts +121 -0
  66. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
  67. package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
  68. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
  69. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +179 -25
  70. package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
  71. package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
  72. package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
  73. package/telegram-plugin/tests/model-command.test.ts +203 -43
  74. package/telegram-plugin/tests/model-label.test.ts +64 -0
  75. package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
  76. package/telegram-plugin/tests/operator-events.test.ts +1 -0
  77. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
  78. package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
  79. package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
  80. package/telegram-plugin/tests/permission-diff.test.ts +111 -0
  81. package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
  82. package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
  83. package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
  84. package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
  85. package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
  86. package/telegram-plugin/tests/session-model-file.test.ts +132 -0
  87. package/telegram-plugin/tests/session-model-source.test.ts +67 -0
  88. package/telegram-plugin/tests/session-tail.test.ts +64 -0
  89. package/telegram-plugin/tests/silent-end.test.ts +46 -1
  90. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
  91. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  92. package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
  93. package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
  94. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
  95. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
  96. package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
  97. package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
  98. package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
  99. package/telegram-plugin/tests/subagent-watcher.test.ts +54 -0
  100. package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
  101. package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
  102. package/telegram-plugin/tests/voice-send.test.ts +308 -0
  103. package/telegram-plugin/tests/worker-activity-feed.test.ts +11 -0
  104. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
  105. package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
  106. package/telegram-plugin/tool-activity-summary.ts +22 -2
  107. package/telegram-plugin/typing-wrap.ts +72 -25
  108. package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
  109. package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
  110. package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
  111. package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
  112. package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
  113. package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
  114. package/telegram-plugin/voice-ondemand.ts +25 -1
  115. package/telegram-plugin/voice-send.ts +154 -0
  116. package/telegram-plugin/worker-activity-feed.ts +9 -0
@@ -11155,6 +11155,7 @@ var SessionContinuitySchema = exports_external.object({
11155
11155
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
11156
11156
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
11157
11157
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
11158
+ boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart — a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart — but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
11158
11159
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
11159
11160
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
11160
11161
  }).optional();
@@ -16784,6 +16784,7 @@ var SessionContinuitySchema = exports_external.object({
16784
16784
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
16785
16785
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
16786
16786
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
16787
+ boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart — a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart — but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
16787
16788
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
16788
16789
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
16789
16790
  }).optional();
@@ -209,6 +209,92 @@ function sendKeys2(agentName, keys) {
209
209
  }
210
210
  }
211
211
 
212
+ // src/agents/permission-pending-query.ts
213
+ import { createConnection } from "node:net";
214
+ import { join } from "node:path";
215
+ import { randomUUID } from "node:crypto";
216
+ function resolveGatewaySocketPath() {
217
+ const stateDir = process.env.TELEGRAM_STATE_DIR ?? "/state/agent/telegram";
218
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? join(stateDir, "gateway.sock");
219
+ }
220
+ function queryPendingPermission(agentName, opts = {}) {
221
+ const socketPath = opts.socketPath ?? resolveGatewaySocketPath();
222
+ const connect = opts._connect ?? ((p) => createConnection(p));
223
+ const log = opts._log ?? ((m) => console.error(m));
224
+ const timeoutMs = opts.timeoutMs ?? 2000;
225
+ const correlationId = randomUUID();
226
+ const msg = {
227
+ type: "query_pending_permission",
228
+ agentName,
229
+ correlationId
230
+ };
231
+ const line = JSON.stringify(msg) + `
232
+ `;
233
+ return new Promise((resolve) => {
234
+ let settled = false;
235
+ let sock;
236
+ const done = (r) => {
237
+ if (settled)
238
+ return;
239
+ settled = true;
240
+ clearTimeout(timer);
241
+ try {
242
+ sock?.destroy();
243
+ } catch {}
244
+ resolve(r);
245
+ };
246
+ const timer = setTimeout(() => {
247
+ log(`[permission-pending-query] ${agentName}: timed out after ${timeoutMs}ms \u2014 treating as unreachable`);
248
+ done({ ok: false, reason: "timeout" });
249
+ }, timeoutMs);
250
+ try {
251
+ sock = connect(socketPath);
252
+ } catch (err) {
253
+ log(`[permission-pending-query] ${agentName}: connect threw: ${err.message}`);
254
+ done({ ok: false, reason: `connect threw: ${err.message}` });
255
+ return;
256
+ }
257
+ let buffer = "";
258
+ sock.on("connect", () => {
259
+ try {
260
+ sock.write(line);
261
+ } catch (err) {
262
+ log(`[permission-pending-query] ${agentName}: write threw: ${err.message}`);
263
+ done({ ok: false, reason: `write threw: ${err.message}` });
264
+ }
265
+ });
266
+ sock.on("data", (chunk) => {
267
+ buffer += chunk.toString("utf8");
268
+ let idx;
269
+ while ((idx = buffer.indexOf(`
270
+ `)) >= 0) {
271
+ const rawLine = buffer.slice(0, idx);
272
+ buffer = buffer.slice(idx + 1);
273
+ if (!rawLine.trim())
274
+ continue;
275
+ let parsed;
276
+ try {
277
+ parsed = JSON.parse(rawLine);
278
+ } catch {
279
+ continue;
280
+ }
281
+ const p = parsed;
282
+ if (p && p.type === "pending_permission_status" && p.correlationId === correlationId) {
283
+ done({ ok: true, pending: !!p.pending, requestId: p.requestId });
284
+ return;
285
+ }
286
+ }
287
+ });
288
+ sock.on("error", (err) => {
289
+ log(`[permission-pending-query] ${agentName}: socket error: ${err.message}`);
290
+ done({ ok: false, reason: `socket error: ${err.message}` });
291
+ });
292
+ sock.on("close", () => {
293
+ done({ ok: false, reason: "socket closed before a matching reply arrived" });
294
+ });
295
+ });
296
+ }
297
+
212
298
  // src/agents/wedge-watchdog.ts
213
299
  var WEDGE_FOOTER_SIGNATURE = /(?=[\s\S]*[Ee]sc(?:ape)?[^\n]*cancel)(?=[\s\S]*(?:to select|to navigate|\u2191\/\u2193))/;
214
300
  var RATE_LIMIT_MENU_SIGNATURE = /(?=[\s\S]*Stop and wait for)(?=[\s\S]*(?:usage credits|Upgrade your plan|\/rate-limit-options))/;
@@ -384,6 +470,7 @@ async function runWedgeWatchdog(opts) {
384
470
  const manifestStallSignature = opts.manifestStallSignature === null ? null : opts.manifestStallSignature ?? MANIFESTING_SIGNATURE;
385
471
  const manifestStallPolls = opts.manifestStallPolls ?? envInt("SWITCHROOM_WEDGE_MANIFEST_POLLS", DEFAULT_MANIFEST_STALL_POLLS);
386
472
  const requestRestart = opts.requestRestart;
473
+ const queryPendingPermission2 = opts.queryPendingPermission === null ? null : opts.queryPendingPermission ?? queryPendingPermission;
387
474
  const maxPolls = opts.maxPolls ?? Number.POSITIVE_INFINITY;
388
475
  const now = opts.now ?? Date.now;
389
476
  const sleep = opts.sleep ?? defaultSleep2;
@@ -397,6 +484,7 @@ async function runWedgeWatchdog(opts) {
397
484
  let overageCreditSelections = 0;
398
485
  let confirmModalFires = 0;
399
486
  let permissionPromptFires = 0;
487
+ let permissionPromptDeferrals = 0;
400
488
  let restartEscalations = 0;
401
489
  let polls = 0;
402
490
  let confirmModalPresent = 0;
@@ -494,16 +582,32 @@ async function runWedgeWatchdog(opts) {
494
582
  stableCount = 0;
495
583
  lastKey = null;
496
584
  if (permissionPromptPresent >= permissionPromptPolls && now() >= permissionCooldownUntil) {
497
- console.error(`[wedge-watchdog] ${opts.agentName}: dismissing stuck per-tool ` + `permission prompt (Esc == decline == safe DENY) after ` + `${permissionPromptPresent} polls present ` + `(~${Math.round(permissionPromptPresent * pollIntervalMs / 1000)}s) \u2014 ` + `the prompt rendered as an interactive TUI with no TTL auto-deny, ` + `freezing the turn; no human to answer it`);
498
- try {
499
- send(opts.agentName, ["Escape"]);
500
- } catch (err) {
501
- console.error(`[wedge-watchdog] ${opts.agentName}: send threw: ${err.message}`);
585
+ let status = null;
586
+ if (queryPendingPermission2) {
587
+ try {
588
+ status = await queryPendingPermission2(opts.agentName);
589
+ } catch (err) {
590
+ console.error(`[wedge-watchdog] ${opts.agentName}: queryPendingPermission threw: ${err.message} \u2014 treating as unreachable`);
591
+ status = { ok: false, reason: `threw: ${err.message}` };
592
+ }
593
+ }
594
+ if (status && status.ok && status.pending) {
595
+ console.error(`[wedge-watchdog] ${opts.agentName}: permission prompt has live card ` + `${status.requestId ?? "(id unknown)"}, deferring to card/TTL after ` + `${permissionPromptPresent} polls present ` + `(~${Math.round(permissionPromptPresent * pollIntervalMs / 1000)}s) \u2014 NOT sending Esc`);
596
+ permissionPromptDeferrals++;
597
+ permissionCooldownUntil = now() + cooldownMs;
598
+ } else {
599
+ const reason = queryPendingPermission2 == null ? "card-aware check disabled" : status && status.ok ? "gateway reports no pending permission (card-less prompt)" : `gateway unreachable/no answer within budget${status && !status.ok ? ` (${status.reason})` : ""}`;
600
+ console.error(`[wedge-watchdog] ${opts.agentName}: dismissing stuck per-tool ` + `permission prompt (Esc == decline == safe DENY) after ` + `${permissionPromptPresent} polls present ` + `(~${Math.round(permissionPromptPresent * pollIntervalMs / 1000)}s) \u2014 ${reason}`);
601
+ try {
602
+ send(opts.agentName, ["Escape"]);
603
+ } catch (err) {
604
+ console.error(`[wedge-watchdog] ${opts.agentName}: send threw: ${err.message}`);
605
+ }
606
+ fires++;
607
+ permissionPromptFires++;
608
+ permissionCooldownUntil = now() + cooldownMs;
609
+ permissionPromptPresent = 0;
502
610
  }
503
- fires++;
504
- permissionPromptFires++;
505
- permissionCooldownUntil = now() + cooldownMs;
506
- permissionPromptPresent = 0;
507
611
  }
508
612
  } else if (isConfirmModal) {
509
613
  confirmModalPresent++;
@@ -556,6 +660,7 @@ async function runWedgeWatchdog(opts) {
556
660
  overageCreditSelections,
557
661
  confirmModalFires,
558
662
  permissionPromptFires,
663
+ permissionPromptDeferrals,
559
664
  restartEscalations,
560
665
  polls,
561
666
  reason: "max-polls"
@@ -563,15 +668,15 @@ async function runWedgeWatchdog(opts) {
563
668
  }
564
669
 
565
670
  // src/agents/rate-limit-signal.ts
566
- import { createConnection } from "node:net";
567
- import { join } from "node:path";
568
- function resolveGatewaySocketPath() {
671
+ import { createConnection as createConnection2 } from "node:net";
672
+ import { join as join2 } from "node:path";
673
+ function resolveGatewaySocketPath2() {
569
674
  const stateDir = process.env.TELEGRAM_STATE_DIR ?? "/state/agent/telegram";
570
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? join(stateDir, "gateway.sock");
675
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? join2(stateDir, "gateway.sock");
571
676
  }
572
677
  function signalQuotaWall(agentName, resetAt, opts = {}) {
573
- const socketPath = opts.socketPath ?? resolveGatewaySocketPath();
574
- const connect = opts._connect ?? ((p) => createConnection(p));
678
+ const socketPath = opts.socketPath ?? resolveGatewaySocketPath2();
679
+ const connect = opts._connect ?? ((p) => createConnection2(p));
575
680
  const log = opts._log ?? ((m) => console.error(m));
576
681
  const connectTimeoutMs = opts.connectTimeoutMs ?? 5000;
577
682
  const msg = { type: "quota_wall_detected", agentName };
@@ -632,8 +737,8 @@ function signalQuotaWall(agentName, resetAt, opts = {}) {
632
737
  // src/auth/broker/client.ts
633
738
  import * as net from "node:net";
634
739
  import { homedir } from "node:os";
635
- import { randomUUID } from "node:crypto";
636
- import { join as join2 } from "node:path";
740
+ import { randomUUID as randomUUID2 } from "node:crypto";
741
+ import { join as join3 } from "node:path";
637
742
 
638
743
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
639
744
  var exports_external = {};
@@ -4884,7 +4989,7 @@ function reviveDate(v) {
4884
4989
  return Number.isNaN(d.getTime()) ? null : d;
4885
4990
  }
4886
4991
  function operatorSocketPath(home = homedir()) {
4887
- return join2(home, ".switchroom", "state", "auth-broker-operator", "sock");
4992
+ return join3(home, ".switchroom", "state", "auth-broker-operator", "sock");
4888
4993
  }
4889
4994
  function resolveAuthBrokerSocketPath(opts) {
4890
4995
  if (opts?.socket)
@@ -4947,7 +5052,7 @@ class AuthBrokerClient {
4947
5052
  async getCredentials(provider) {
4948
5053
  const base = {
4949
5054
  v: PROTOCOL_VERSION,
4950
- id: randomUUID(),
5055
+ id: randomUUID2(),
4951
5056
  op: "get-credentials"
4952
5057
  };
4953
5058
  const req = provider !== undefined ? { ...base, provider } : base;
@@ -4957,7 +5062,7 @@ class AuthBrokerClient {
4957
5062
  async listState() {
4958
5063
  const data = await this.send({
4959
5064
  v: PROTOCOL_VERSION,
4960
- id: randomUUID(),
5065
+ id: randomUUID2(),
4961
5066
  op: "list-state"
4962
5067
  });
4963
5068
  return data;
@@ -4965,7 +5070,7 @@ class AuthBrokerClient {
4965
5070
  async listGoogleAccounts() {
4966
5071
  const data = await this.send({
4967
5072
  v: PROTOCOL_VERSION,
4968
- id: randomUUID(),
5073
+ id: randomUUID2(),
4969
5074
  op: "list-google-accounts"
4970
5075
  });
4971
5076
  return data;
@@ -4973,7 +5078,7 @@ class AuthBrokerClient {
4973
5078
  async listMicrosoftAccounts() {
4974
5079
  const data = await this.send({
4975
5080
  v: PROTOCOL_VERSION,
4976
- id: randomUUID(),
5081
+ id: randomUUID2(),
4977
5082
  op: "list-microsoft-accounts"
4978
5083
  });
4979
5084
  return data;
@@ -4981,7 +5086,7 @@ class AuthBrokerClient {
4981
5086
  async probeQuota(accounts, timeoutMs, forceLive) {
4982
5087
  const data = await this.send({
4983
5088
  v: PROTOCOL_VERSION,
4984
- id: randomUUID(),
5089
+ id: randomUUID2(),
4985
5090
  op: "probe-quota",
4986
5091
  accounts: [...accounts],
4987
5092
  ...timeoutMs !== undefined ? { timeoutMs } : {},
@@ -4999,21 +5104,21 @@ class AuthBrokerClient {
4999
5104
  async setActive(account) {
5000
5105
  const data = await this.send({
5001
5106
  v: PROTOCOL_VERSION,
5002
- id: randomUUID(),
5107
+ id: randomUUID2(),
5003
5108
  op: "set-active",
5004
5109
  account
5005
5110
  });
5006
5111
  return data;
5007
5112
  }
5008
5113
  async markExhausted(until) {
5009
- const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted" };
5114
+ const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID2(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID2(), op: "mark-exhausted" };
5010
5115
  const data = await this.send(req);
5011
5116
  return data;
5012
5117
  }
5013
5118
  async claimNotification(key, windowMs) {
5014
5119
  const data = await this.send({
5015
5120
  v: PROTOCOL_VERSION,
5016
- id: randomUUID(),
5121
+ id: randomUUID2(),
5017
5122
  op: "claim-notification",
5018
5123
  key,
5019
5124
  windowMs
@@ -5023,7 +5128,7 @@ class AuthBrokerClient {
5023
5128
  async refreshAccount(account) {
5024
5129
  const data = await this.send({
5025
5130
  v: PROTOCOL_VERSION,
5026
- id: randomUUID(),
5131
+ id: randomUUID2(),
5027
5132
  op: "refresh-account",
5028
5133
  account
5029
5134
  });
@@ -5032,7 +5137,7 @@ class AuthBrokerClient {
5032
5137
  async addAccount(label, credentials, replace, provider) {
5033
5138
  const base = {
5034
5139
  v: PROTOCOL_VERSION,
5035
- id: randomUUID(),
5140
+ id: randomUUID2(),
5036
5141
  op: "add-account",
5037
5142
  label,
5038
5143
  credentials
@@ -5045,7 +5150,7 @@ class AuthBrokerClient {
5045
5150
  async rmAccount(label, provider) {
5046
5151
  const base = {
5047
5152
  v: PROTOCOL_VERSION,
5048
- id: randomUUID(),
5153
+ id: randomUUID2(),
5049
5154
  op: "rm-account",
5050
5155
  label
5051
5156
  };
@@ -5056,7 +5161,7 @@ class AuthBrokerClient {
5056
5161
  async setOverride(agent, account) {
5057
5162
  const data = await this.send({
5058
5163
  v: PROTOCOL_VERSION,
5059
- id: randomUUID(),
5164
+ id: randomUUID2(),
5060
5165
  op: "set-override",
5061
5166
  agent,
5062
5167
  account
@@ -5237,8 +5342,9 @@ async function main() {
5237
5342
  }
5238
5343
  const rateLimitDetect = process.env.SWITCHROOM_RATE_LIMIT_DETECT !== "0";
5239
5344
  const overageSelect = rateLimitDetect && process.env.SWITCHROOM_RATE_LIMIT_OVERAGE !== "0";
5345
+ const permissionCardAware = process.env.SWITCHROOM_PERMISSION_CARD_AWARE !== "0";
5240
5346
  try {
5241
- console.error(`[autoaccept-poll] ${agentName}: entering wedge-watchdog (continuous)` + (rateLimitDetect ? " +rate-limit-detect" : " (rate-limit-detect OFF)") + (overageSelect ? " +overage-carveout" : ""));
5347
+ console.error(`[autoaccept-poll] ${agentName}: entering wedge-watchdog (continuous)` + (rateLimitDetect ? " +rate-limit-detect" : " (rate-limit-detect OFF)") + (overageSelect ? " +overage-carveout" : "") + (permissionCardAware ? " +permission-card-aware" : " (permission-card-aware OFF)"));
5242
5348
  const res = await runWedgeWatchdog({
5243
5349
  agentName,
5244
5350
  rateLimitSignature: rateLimitDetect ? undefined : null,
@@ -5246,9 +5352,10 @@ async function main() {
5246
5352
  signalQuotaWall(name, resetAt);
5247
5353
  } : undefined,
5248
5354
  overageDecision: overageSelect ? () => queryActiveOverageServing() : undefined,
5249
- requestRestart: requestWedgeRestart
5355
+ requestRestart: requestWedgeRestart,
5356
+ queryPendingPermission: permissionCardAware ? undefined : null
5250
5357
  });
5251
- console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog returned reason=${res.reason} fires=${res.fires} rateLimitFires=${res.rateLimitFires} overageCreditSelections=${res.overageCreditSelections} confirmModalFires=${res.confirmModalFires} permissionPromptFires=${res.permissionPromptFires} restartEscalations=${res.restartEscalations}`);
5358
+ console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog returned reason=${res.reason} fires=${res.fires} rateLimitFires=${res.rateLimitFires} overageCreditSelections=${res.overageCreditSelections} confirmModalFires=${res.confirmModalFires} permissionPromptFires=${res.permissionPromptFires} permissionPromptDeferrals=${res.permissionPromptDeferrals} restartEscalations=${res.restartEscalations}`);
5252
5359
  } catch (err) {
5253
5360
  console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog unexpected throw: ${err.message}`);
5254
5361
  }
@@ -11904,6 +11904,7 @@ var SessionContinuitySchema = exports_external.object({
11904
11904
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
11905
11905
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
11906
11906
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
11907
+ boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
11907
11908
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
11908
11909
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
11909
11910
  }).optional();