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.
- package/dist/agent-scheduler/index.js +1 -0
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/autoaccept-poll.js +140 -33
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +1172 -812
- package/dist/host-control/main.js +2 -1
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +3 -3
- package/profiles/_base/cron-session.sh.hbs +55 -16
- package/profiles/_base/start.sh.hbs +146 -50
- package/profiles/default/CLAUDE.md.hbs +1 -1
- package/skills/switchroom-runtime/SKILL.md +2 -0
- package/telegram-plugin/dist/bridge/bridge.js +22 -0
- package/telegram-plugin/dist/gateway/gateway.js +2965 -862
- package/telegram-plugin/dist/server.js +24 -0
- package/telegram-plugin/flood-circuit-breaker.ts +123 -0
- package/telegram-plugin/gateway/activity-card-store.ts +63 -18
- package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
- package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
- package/telegram-plugin/gateway/boot-card.ts +27 -0
- package/telegram-plugin/gateway/busy-ack.ts +106 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
- package/telegram-plugin/gateway/gateway.ts +1618 -198
- package/telegram-plugin/gateway/inbound-spool.ts +2 -1
- package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
- package/telegram-plugin/gateway/inject-handler.ts +17 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
- package/telegram-plugin/gateway/ipc-server.ts +40 -0
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
- package/telegram-plugin/gateway/model-command.ts +227 -54
- package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
- package/telegram-plugin/gateway/pending-card-store.ts +173 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
- package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
- package/telegram-plugin/gateway/session-model-file.ts +198 -0
- package/telegram-plugin/gateway/session-model-source.ts +73 -0
- package/telegram-plugin/gateway/status-pin-store.ts +82 -22
- package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
- package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
- package/telegram-plugin/hooks/hooks.json +10 -10
- package/telegram-plugin/hooks/run-hook.sh +84 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
- package/telegram-plugin/model-label.ts +69 -0
- package/telegram-plugin/model-unavailable.ts +26 -0
- package/telegram-plugin/operator-events.ts +24 -0
- package/telegram-plugin/permission-diff.ts +128 -0
- package/telegram-plugin/pty-partial-handler.ts +39 -0
- package/telegram-plugin/registry/subagents-schema.ts +80 -1
- package/telegram-plugin/registry/subagents.test.ts +90 -0
- package/telegram-plugin/render/rich-render.ts +79 -1
- package/telegram-plugin/retry-api-call.ts +62 -0
- package/telegram-plugin/session-tail.ts +28 -0
- package/telegram-plugin/shared/bot-runtime.ts +8 -1
- package/telegram-plugin/silence-poke.ts +14 -0
- package/telegram-plugin/silent-end.ts +49 -4
- package/telegram-plugin/stream-controller.ts +156 -38
- package/telegram-plugin/subagent-watcher.ts +222 -37
- package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
- package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
- package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
- package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
- package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
- package/telegram-plugin/tests/busy-ack.test.ts +121 -0
- package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
- package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +179 -25
- package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
- package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +203 -43
- package/telegram-plugin/tests/model-label.test.ts +64 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
- package/telegram-plugin/tests/operator-events.test.ts +1 -0
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
- package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
- package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
- package/telegram-plugin/tests/permission-diff.test.ts +111 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
- package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
- package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
- package/telegram-plugin/tests/session-model-file.test.ts +132 -0
- package/telegram-plugin/tests/session-model-source.test.ts +67 -0
- package/telegram-plugin/tests/session-tail.test.ts +64 -0
- package/telegram-plugin/tests/silent-end.test.ts +46 -1
- package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
- package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
- package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
- package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
- package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +54 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
- package/telegram-plugin/tests/voice-send.test.ts +308 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +11 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
- package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
- package/telegram-plugin/tool-activity-summary.ts +22 -2
- package/telegram-plugin/typing-wrap.ts +72 -25
- package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
- package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
- package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
- package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
- package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
- package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
- package/telegram-plugin/voice-ondemand.ts +25 -1
- package/telegram-plugin/voice-send.ts +154 -0
- 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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
|
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 ??
|
|
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 ??
|
|
574
|
-
const connect = opts._connect ?? ((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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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();
|