switchroom 0.14.0 → 0.14.2
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/auth-broker/index.js +16 -1
- package/dist/cli/switchroom.js +1082 -873
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_shared/telegram-style.md.hbs +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +47 -1
- package/telegram-plugin/dist/gateway/gateway.js +983 -537
- package/telegram-plugin/gateway/boot-card.ts +100 -0
- package/telegram-plugin/gateway/config-snapshot.ts +274 -0
- package/telegram-plugin/gateway/gateway.ts +235 -20
- package/telegram-plugin/operator-events.ts +2 -10
- package/telegram-plugin/quota-watch.ts +276 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +133 -1
- package/telegram-plugin/tests/boot-card-render.test.ts +93 -0
- package/telegram-plugin/tests/config-snapshot.test.ts +409 -0
- package/telegram-plugin/tests/operator-events.test.ts +12 -6
- package/telegram-plugin/tests/quota-watch.test.ts +366 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +45 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +48 -0
- package/telegram-plugin/tool-activity-summary.ts +47 -0
- package/telegram-plugin/turn-flush-safety.ts +47 -0
- package/telegram-plugin/uat/assertions.ts +4 -4
|
@@ -28823,6 +28823,115 @@ var init_boot_issue_cache = __esm(() => {
|
|
|
28823
28823
|
EMPTY_CACHE = { schema: 1, probes: {} };
|
|
28824
28824
|
});
|
|
28825
28825
|
|
|
28826
|
+
// gateway/config-snapshot.ts
|
|
28827
|
+
import { createHash as createHash2 } from "crypto";
|
|
28828
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, renameSync as renameSync10 } from "fs";
|
|
28829
|
+
import { dirname as dirname10 } from "path";
|
|
28830
|
+
function hashStringArray(items) {
|
|
28831
|
+
if (!items || items.length === 0)
|
|
28832
|
+
return null;
|
|
28833
|
+
const sorted = [...items].sort();
|
|
28834
|
+
const raw = createHash2("sha256").update(sorted.join("\x00")).digest("hex");
|
|
28835
|
+
return raw.slice(0, 12);
|
|
28836
|
+
}
|
|
28837
|
+
function normalizeModel(model) {
|
|
28838
|
+
if (!model || model.trim().length === 0)
|
|
28839
|
+
return null;
|
|
28840
|
+
return model.trim().toLowerCase();
|
|
28841
|
+
}
|
|
28842
|
+
function captureConfigSnapshot(input) {
|
|
28843
|
+
return {
|
|
28844
|
+
schema: 1,
|
|
28845
|
+
capturedAtMs: (input.now ?? Date.now)(),
|
|
28846
|
+
model: normalizeModel(input.model),
|
|
28847
|
+
toolsHash: hashStringArray(input.toolsAllow ?? null),
|
|
28848
|
+
skillsHash: hashStringArray(input.skills ?? null),
|
|
28849
|
+
memoryBackend: input.memoryCollection?.trim() || null
|
|
28850
|
+
};
|
|
28851
|
+
}
|
|
28852
|
+
function diffSnapshots(current, previous) {
|
|
28853
|
+
if (previous === null)
|
|
28854
|
+
return [];
|
|
28855
|
+
const changes = [];
|
|
28856
|
+
if (current.model !== previous.model) {
|
|
28857
|
+
changes.push({ field: "model", from: previous.model, to: current.model });
|
|
28858
|
+
}
|
|
28859
|
+
if (current.toolsHash !== previous.toolsHash) {
|
|
28860
|
+
changes.push({ field: "tools", from: previous.toolsHash, to: current.toolsHash });
|
|
28861
|
+
}
|
|
28862
|
+
if (current.skillsHash !== previous.skillsHash) {
|
|
28863
|
+
changes.push({ field: "skills", from: previous.skillsHash, to: current.skillsHash });
|
|
28864
|
+
}
|
|
28865
|
+
if (current.memoryBackend !== previous.memoryBackend) {
|
|
28866
|
+
changes.push({
|
|
28867
|
+
field: "memoryBackend",
|
|
28868
|
+
from: previous.memoryBackend,
|
|
28869
|
+
to: current.memoryBackend
|
|
28870
|
+
});
|
|
28871
|
+
}
|
|
28872
|
+
return changes;
|
|
28873
|
+
}
|
|
28874
|
+
function renderConfigChangeDim(dim) {
|
|
28875
|
+
switch (dim.field) {
|
|
28876
|
+
case "model": {
|
|
28877
|
+
const from = escapeHtml8(dim.from ?? "(default)");
|
|
28878
|
+
const to = escapeHtml8(dim.to ?? "(default)");
|
|
28879
|
+
return `\u2699\ufe0f <b>Config</b> model: ${from} \u2192 ${to}`;
|
|
28880
|
+
}
|
|
28881
|
+
case "memoryBackend": {
|
|
28882
|
+
const from = escapeHtml8(dim.from ?? "(default)");
|
|
28883
|
+
const to = escapeHtml8(dim.to ?? "(default)");
|
|
28884
|
+
return `\u2699\ufe0f <b>Config</b> memory backend: ${from} \u2192 ${to}`;
|
|
28885
|
+
}
|
|
28886
|
+
case "tools":
|
|
28887
|
+
return `\u2699\ufe0f <b>Config</b> tools allowlist changed \u2014 run /status for details`;
|
|
28888
|
+
case "skills":
|
|
28889
|
+
return `\u2699\ufe0f <b>Config</b> skills changed \u2014 run /status for details`;
|
|
28890
|
+
}
|
|
28891
|
+
}
|
|
28892
|
+
function loadSnapshot(path, now = Date.now) {
|
|
28893
|
+
if (!existsSync26(path))
|
|
28894
|
+
return null;
|
|
28895
|
+
let raw;
|
|
28896
|
+
try {
|
|
28897
|
+
raw = readFileSync25(path, "utf-8");
|
|
28898
|
+
} catch {
|
|
28899
|
+
return null;
|
|
28900
|
+
}
|
|
28901
|
+
let parsed;
|
|
28902
|
+
try {
|
|
28903
|
+
parsed = JSON.parse(raw);
|
|
28904
|
+
} catch {
|
|
28905
|
+
try {
|
|
28906
|
+
renameSync10(path, `${path}.corrupt-${now()}`);
|
|
28907
|
+
} catch {}
|
|
28908
|
+
return null;
|
|
28909
|
+
}
|
|
28910
|
+
const obj = parsed;
|
|
28911
|
+
if (!obj || obj.schema !== 1)
|
|
28912
|
+
return null;
|
|
28913
|
+
if (typeof obj.capturedAtMs !== "number" || !("model" in obj) || !("toolsHash" in obj) || !("skillsHash" in obj) || !("memoryBackend" in obj)) {
|
|
28914
|
+
return null;
|
|
28915
|
+
}
|
|
28916
|
+
return {
|
|
28917
|
+
schema: 1,
|
|
28918
|
+
capturedAtMs: obj.capturedAtMs,
|
|
28919
|
+
model: obj.model ?? null,
|
|
28920
|
+
toolsHash: obj.toolsHash ?? null,
|
|
28921
|
+
skillsHash: obj.skillsHash ?? null,
|
|
28922
|
+
memoryBackend: obj.memoryBackend ?? null
|
|
28923
|
+
};
|
|
28924
|
+
}
|
|
28925
|
+
function persistSnapshot(path, snapshot) {
|
|
28926
|
+
try {
|
|
28927
|
+
mkdirSync13(dirname10(path), { recursive: true });
|
|
28928
|
+
const tmp = `${path}.tmp`;
|
|
28929
|
+
writeFileSync15(tmp, JSON.stringify(snapshot), { mode: 384 });
|
|
28930
|
+
renameSync10(tmp, path);
|
|
28931
|
+
} catch {}
|
|
28932
|
+
}
|
|
28933
|
+
var init_config_snapshot = () => {};
|
|
28934
|
+
|
|
28826
28935
|
// gateway/boot-card.ts
|
|
28827
28936
|
var exports_boot_card = {};
|
|
28828
28937
|
__export(exports_boot_card, {
|
|
@@ -28899,6 +29008,12 @@ function renderBootCard(opts) {
|
|
|
28899
29008
|
}
|
|
28900
29009
|
}
|
|
28901
29010
|
const accountRows = opts.accounts ? renderAuthLine(opts.accounts, agentName3, (opts.now ?? new Date).getTime()) : [];
|
|
29011
|
+
const configChangeRows = [];
|
|
29012
|
+
if (opts.configChanges && opts.configChanges.length > 0) {
|
|
29013
|
+
for (const dim of opts.configChanges) {
|
|
29014
|
+
configChangeRows.push(renderConfigChangeDim(dim));
|
|
29015
|
+
}
|
|
29016
|
+
}
|
|
28902
29017
|
const sections = [ack];
|
|
28903
29018
|
if (degradedRows.length > 0)
|
|
28904
29019
|
sections.push("", ...degradedRows);
|
|
@@ -28908,6 +29023,9 @@ function renderBootCard(opts) {
|
|
|
28908
29023
|
sections.push("", ...opts.updateOutcomeLine.split(`
|
|
28909
29024
|
`));
|
|
28910
29025
|
}
|
|
29026
|
+
if (configChangeRows.length > 0) {
|
|
29027
|
+
sections.push("", ...configChangeRows);
|
|
29028
|
+
}
|
|
28911
29029
|
if (sections.length === 1)
|
|
28912
29030
|
return ack;
|
|
28913
29031
|
return sections.join(`
|
|
@@ -29011,6 +29129,38 @@ async function startBootCard(chatId, threadId, bot, opts, ackMessageId, log) {
|
|
|
29011
29129
|
applyAndSave(opts.bootIssueCachePath, cache, diff);
|
|
29012
29130
|
} catch (diffErr) {
|
|
29013
29131
|
logger2(`telegram gateway: boot-card: issue-dedup diff failed: ${diffErr?.message ?? String(diffErr)}
|
|
29132
|
+
`);
|
|
29133
|
+
}
|
|
29134
|
+
}
|
|
29135
|
+
let configChanges = [];
|
|
29136
|
+
if (opts.configSnapshotPath) {
|
|
29137
|
+
try {
|
|
29138
|
+
const agentSlug = opts.agentSlug ?? opts.agentName;
|
|
29139
|
+
const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? agentSlug;
|
|
29140
|
+
let currentCfg;
|
|
29141
|
+
try {
|
|
29142
|
+
const loaded = loadConfig();
|
|
29143
|
+
const rawAgent = loaded.agents?.[agentName3] ?? {};
|
|
29144
|
+
const resolved = resolveAgentConfig(loaded.defaults, loaded.profiles, rawAgent);
|
|
29145
|
+
currentCfg = captureConfigSnapshot({
|
|
29146
|
+
agentName: agentName3,
|
|
29147
|
+
model: resolved.model,
|
|
29148
|
+
toolsAllow: resolved.tools?.allow,
|
|
29149
|
+
skills: resolved.skills,
|
|
29150
|
+
memoryCollection: resolved.memory?.collection
|
|
29151
|
+
});
|
|
29152
|
+
} catch {}
|
|
29153
|
+
if (currentCfg != null) {
|
|
29154
|
+
const previousSnapshot = loadSnapshot(opts.configSnapshotPath);
|
|
29155
|
+
configChanges = diffSnapshots(currentCfg, previousSnapshot);
|
|
29156
|
+
persistSnapshot(opts.configSnapshotPath, currentCfg);
|
|
29157
|
+
if (configChanges.length > 0) {
|
|
29158
|
+
logger2(`telegram gateway: boot-card: config-snapshot diff detected ${configChanges.length} change(s): ${configChanges.map((d) => d.field).join(", ")}
|
|
29159
|
+
`);
|
|
29160
|
+
}
|
|
29161
|
+
}
|
|
29162
|
+
} catch (snapErr) {
|
|
29163
|
+
logger2(`telegram gateway: boot-card: config-snapshot diff failed: ${snapErr?.message ?? String(snapErr)}
|
|
29014
29164
|
`);
|
|
29015
29165
|
}
|
|
29016
29166
|
}
|
|
@@ -29024,7 +29174,8 @@ async function startBootCard(chatId, threadId, bot, opts, ackMessageId, log) {
|
|
|
29024
29174
|
...accountRows ? { accounts: accountRows } : {},
|
|
29025
29175
|
...resolvedRows.length > 0 ? { resolvedRows } : {},
|
|
29026
29176
|
...snoozeRows.length > 0 ? { snoozeRows } : {},
|
|
29027
|
-
...opts.updateOutcomeLine ? { updateOutcomeLine: opts.updateOutcomeLine } : {}
|
|
29177
|
+
...opts.updateOutcomeLine ? { updateOutcomeLine: opts.updateOutcomeLine } : {},
|
|
29178
|
+
...configChanges.length > 0 ? { configChanges } : {}
|
|
29028
29179
|
});
|
|
29029
29180
|
if (currentText !== ackText) {
|
|
29030
29181
|
try {
|
|
@@ -29066,7 +29217,8 @@ async function startBootCard(chatId, threadId, bot, opts, ackMessageId, log) {
|
|
|
29066
29217
|
...accountRows ? { accounts: accountRows } : {},
|
|
29067
29218
|
...resolvedRows.length > 0 ? { resolvedRows } : {},
|
|
29068
29219
|
...snoozeRows.length > 0 ? { snoozeRows } : {},
|
|
29069
|
-
...opts.updateOutcomeLine ? { updateOutcomeLine: opts.updateOutcomeLine } : {}
|
|
29220
|
+
...opts.updateOutcomeLine ? { updateOutcomeLine: opts.updateOutcomeLine } : {},
|
|
29221
|
+
...configChanges.length > 0 ? { configChanges } : {}
|
|
29070
29222
|
});
|
|
29071
29223
|
if (updatedText === currentText)
|
|
29072
29224
|
continue;
|
|
@@ -29108,7 +29260,9 @@ var SETTLE_WINDOW_MS = 6000, DOT, PROBE_LABELS, PROBE_KEYS, REASON_EMOJI, REASON
|
|
|
29108
29260
|
var init_boot_card = __esm(() => {
|
|
29109
29261
|
init_boot_probes();
|
|
29110
29262
|
init_boot_issue_cache();
|
|
29263
|
+
init_config_snapshot();
|
|
29111
29264
|
init_loader();
|
|
29265
|
+
init_merge();
|
|
29112
29266
|
DOT = {
|
|
29113
29267
|
ok: "\uD83D\uDFE2",
|
|
29114
29268
|
degraded: "\uD83D\uDFE1",
|
|
@@ -29150,17 +29304,357 @@ var init_boot_card = __esm(() => {
|
|
|
29150
29304
|
};
|
|
29151
29305
|
});
|
|
29152
29306
|
|
|
29307
|
+
// auth-snapshot-format.ts
|
|
29308
|
+
var exports_auth_snapshot_format = {};
|
|
29309
|
+
__export(exports_auth_snapshot_format, {
|
|
29310
|
+
reviveLastQuota: () => reviveLastQuota,
|
|
29311
|
+
renderFallbackAnnouncement: () => renderFallbackAnnouncement2,
|
|
29312
|
+
renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat22,
|
|
29313
|
+
recommendation: () => recommendation2,
|
|
29314
|
+
formatRelative: () => formatRelative2,
|
|
29315
|
+
formatAbsolute: () => formatAbsolute2,
|
|
29316
|
+
fmtPct: () => fmtPct2,
|
|
29317
|
+
classifyHealth: () => classifyHealth2,
|
|
29318
|
+
buildSnapshotsFromState: () => buildSnapshotsFromState2,
|
|
29319
|
+
buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
|
|
29320
|
+
buildSnapshotKeyboard: () => buildSnapshotKeyboard2,
|
|
29321
|
+
bindingWindow: () => bindingWindow3,
|
|
29322
|
+
THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT2
|
|
29323
|
+
});
|
|
29324
|
+
function classifyHealth2(snap) {
|
|
29325
|
+
if (!snap.quota)
|
|
29326
|
+
return "unknown";
|
|
29327
|
+
const q = snap.quota;
|
|
29328
|
+
const max = Math.max(q.fiveHourUtilizationPct, q.sevenDayUtilizationPct);
|
|
29329
|
+
if (max >= 99.5)
|
|
29330
|
+
return "blocked";
|
|
29331
|
+
if (max >= THROTTLING_THRESHOLD_PCT2)
|
|
29332
|
+
return "throttling";
|
|
29333
|
+
return "healthy";
|
|
29334
|
+
}
|
|
29335
|
+
function bindingWindow3(q) {
|
|
29336
|
+
if (q.representativeClaim === "seven_day")
|
|
29337
|
+
return "7d";
|
|
29338
|
+
if (q.representativeClaim === "five_hour")
|
|
29339
|
+
return "5h";
|
|
29340
|
+
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
|
|
29341
|
+
}
|
|
29342
|
+
function formatRelative2(target, now = new Date) {
|
|
29343
|
+
if (!target)
|
|
29344
|
+
return "\u2014";
|
|
29345
|
+
const deltaMs = target.getTime() - now.getTime();
|
|
29346
|
+
if (deltaMs <= 0)
|
|
29347
|
+
return "now";
|
|
29348
|
+
const totalMin = Math.round(deltaMs / 60000);
|
|
29349
|
+
if (totalMin < 60)
|
|
29350
|
+
return `${totalMin}m`;
|
|
29351
|
+
const h = Math.floor(totalMin / 60);
|
|
29352
|
+
const m = totalMin % 60;
|
|
29353
|
+
if (h < 24)
|
|
29354
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
29355
|
+
const d = Math.floor(h / 24);
|
|
29356
|
+
const rh = h % 24;
|
|
29357
|
+
return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
|
|
29358
|
+
}
|
|
29359
|
+
function formatAbsolute2(target, tz = "UTC") {
|
|
29360
|
+
if (!target)
|
|
29361
|
+
return "\u2014";
|
|
29362
|
+
return target.toLocaleString("en-US", {
|
|
29363
|
+
timeZone: tz,
|
|
29364
|
+
weekday: "short",
|
|
29365
|
+
hour: "numeric",
|
|
29366
|
+
minute: "2-digit",
|
|
29367
|
+
hour12: true
|
|
29368
|
+
});
|
|
29369
|
+
}
|
|
29370
|
+
function fmtPct2(pct) {
|
|
29371
|
+
return `${Math.round(pct)}%`;
|
|
29372
|
+
}
|
|
29373
|
+
function groupHeader2(health, count) {
|
|
29374
|
+
const emoji = HEALTH_EMOJI2[health];
|
|
29375
|
+
const title = HEALTH_TITLE2[health];
|
|
29376
|
+
return `${emoji} <b>${title}</b> (${count})`;
|
|
29377
|
+
}
|
|
29378
|
+
function renderAccountRow2(snap, opts) {
|
|
29379
|
+
const now = opts.now ?? new Date;
|
|
29380
|
+
const tz = opts.tz ?? "UTC";
|
|
29381
|
+
const lines = [];
|
|
29382
|
+
const marker = snap.isActive ? "\u25cf " : "";
|
|
29383
|
+
if (!snap.quota) {
|
|
29384
|
+
lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> <i>quota probe failed</i>`);
|
|
29385
|
+
if (snap.quotaError) {
|
|
29386
|
+
lines.push(` <i>${escapeHtml12(snap.quotaError)}</i>`);
|
|
29387
|
+
}
|
|
29388
|
+
return lines;
|
|
29389
|
+
}
|
|
29390
|
+
const q = snap.quota;
|
|
29391
|
+
const fiveStr = fmtPct2(q.fiveHourUtilizationPct);
|
|
29392
|
+
const sevenStr = fmtPct2(q.sevenDayUtilizationPct);
|
|
29393
|
+
lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
|
|
29394
|
+
const health = classifyHealth2(snap);
|
|
29395
|
+
if (health === "blocked") {
|
|
29396
|
+
const win = bindingWindow3(q);
|
|
29397
|
+
const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
29398
|
+
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
29399
|
+
lines.push(` <i>back ${formatAbsolute2(reset2, tz)} (in ${formatRelative2(reset2, now)}, ${winLabel} cap)</i>`);
|
|
29400
|
+
return lines;
|
|
29401
|
+
}
|
|
29402
|
+
const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
|
|
29403
|
+
const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
|
|
29404
|
+
const fiveFirst = fiveResetIn <= sevenResetIn;
|
|
29405
|
+
const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute2(q.fiveHourResetAt, tz)} (in ${formatRelative2(q.fiveHourResetAt, now)})` : "5h refills \u2014";
|
|
29406
|
+
const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute2(q.sevenDayResetAt, tz)} (in ${formatRelative2(q.sevenDayResetAt, now)})` : "7d resets \u2014";
|
|
29407
|
+
lines.push(` <i>${fiveFirst ? fiveSeg : sevenSeg} \u00b7 ${fiveFirst ? sevenSeg : fiveSeg}</i>`);
|
|
29408
|
+
return lines;
|
|
29409
|
+
}
|
|
29410
|
+
function renderAuthSnapshotFormat22(snapshots, opts = {}) {
|
|
29411
|
+
const now = opts.now ?? new Date;
|
|
29412
|
+
const lines = [];
|
|
29413
|
+
lines.push("\uD83D\uDD0B <b>Auth \u2014 fleet status</b>");
|
|
29414
|
+
const order = ["blocked", "throttling", "healthy", "unknown"];
|
|
29415
|
+
const grouped = new Map;
|
|
29416
|
+
for (const s of snapshots) {
|
|
29417
|
+
const h = classifyHealth2(s);
|
|
29418
|
+
if (!grouped.has(h))
|
|
29419
|
+
grouped.set(h, []);
|
|
29420
|
+
grouped.get(h).push(s);
|
|
29421
|
+
}
|
|
29422
|
+
for (const arr of grouped.values()) {
|
|
29423
|
+
arr.sort((a, b) => Number(b.isActive) - Number(a.isActive));
|
|
29424
|
+
}
|
|
29425
|
+
for (const h of order) {
|
|
29426
|
+
const arr = grouped.get(h);
|
|
29427
|
+
if (!arr || arr.length === 0)
|
|
29428
|
+
continue;
|
|
29429
|
+
lines.push("");
|
|
29430
|
+
lines.push(groupHeader2(h, arr.length));
|
|
29431
|
+
for (const s of arr) {
|
|
29432
|
+
for (const ln of renderAccountRow2(s, opts))
|
|
29433
|
+
lines.push(ln);
|
|
29434
|
+
}
|
|
29435
|
+
}
|
|
29436
|
+
lines.push("");
|
|
29437
|
+
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
29438
|
+
lines.push(`<i>${recommendation2(snapshots, now)}</i>`);
|
|
29439
|
+
if (opts.liveProbedAtMs != null) {
|
|
29440
|
+
const ageSec = Math.max(0, Math.round((Date.now() - opts.liveProbedAtMs) / 1000));
|
|
29441
|
+
const ageStr = ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
|
|
29442
|
+
lines.push(`<i>Live \u00b7 refreshed ${ageStr}</i>`);
|
|
29443
|
+
} else {
|
|
29444
|
+
lines.push("<i>Live</i>");
|
|
29445
|
+
}
|
|
29446
|
+
return lines.join(`
|
|
29447
|
+
`);
|
|
29448
|
+
}
|
|
29449
|
+
function recommendation2(snapshots, now = new Date) {
|
|
29450
|
+
const active = snapshots.find((s) => s.isActive);
|
|
29451
|
+
if (!active)
|
|
29452
|
+
return "No active account set.";
|
|
29453
|
+
const activeHealth = classifyHealth2(active);
|
|
29454
|
+
const others = snapshots.filter((s) => !s.isActive);
|
|
29455
|
+
const healthyAlt = others.find((s) => classifyHealth2(s) === "healthy");
|
|
29456
|
+
if (activeHealth === "healthy") {
|
|
29457
|
+
return `Recommendation: stay on ${active.label}.`;
|
|
29458
|
+
}
|
|
29459
|
+
if (activeHealth === "throttling") {
|
|
29460
|
+
if (healthyAlt) {
|
|
29461
|
+
return `Recommendation: active ${active.label} is throttling. Switch to ${healthyAlt.label} for headroom.`;
|
|
29462
|
+
}
|
|
29463
|
+
return `Recommendation: active ${active.label} is throttling; no healthy alternative \u2014 wait for refill.`;
|
|
29464
|
+
}
|
|
29465
|
+
if (activeHealth === "blocked") {
|
|
29466
|
+
if (healthyAlt) {
|
|
29467
|
+
return `Recommendation: active ${active.label} is BLOCKED \u2014 switch to ${healthyAlt.label} now.`;
|
|
29468
|
+
}
|
|
29469
|
+
const earliestRecovery = pickEarliestRecovery2(snapshots, now);
|
|
29470
|
+
if (earliestRecovery) {
|
|
29471
|
+
return `All accounts blocked. Earliest recovery: ${earliestRecovery.label} in ${formatRelative2(earliestRecovery.at, now)}.`;
|
|
29472
|
+
}
|
|
29473
|
+
return `All accounts blocked. Run /auth add to attach another subscription.`;
|
|
29474
|
+
}
|
|
29475
|
+
return `Active ${active.label}: quota probe failed; broker last_seen unknown.`;
|
|
29476
|
+
}
|
|
29477
|
+
function pickEarliestRecovery2(snapshots, now) {
|
|
29478
|
+
let best = null;
|
|
29479
|
+
for (const s of snapshots) {
|
|
29480
|
+
if (!s.quota)
|
|
29481
|
+
continue;
|
|
29482
|
+
const win = bindingWindow3(s.quota);
|
|
29483
|
+
const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
|
|
29484
|
+
if (!at || at.getTime() <= now.getTime())
|
|
29485
|
+
continue;
|
|
29486
|
+
if (!best || at.getTime() < best.at.getTime()) {
|
|
29487
|
+
best = { label: s.label, at };
|
|
29488
|
+
}
|
|
29489
|
+
}
|
|
29490
|
+
return best;
|
|
29491
|
+
}
|
|
29492
|
+
function renderFallbackAnnouncement2(input) {
|
|
29493
|
+
const now = input.now ?? new Date;
|
|
29494
|
+
const tz = input.tz ?? "UTC";
|
|
29495
|
+
const lines = [];
|
|
29496
|
+
const limitWord = input.oldQuota ? limitWordFor2(input.oldQuota) : "quota";
|
|
29497
|
+
const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
29498
|
+
if (!input.newLabel) {
|
|
29499
|
+
lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
|
|
29500
|
+
lines.push("");
|
|
29501
|
+
lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
|
|
29502
|
+
if (input.oldQuota) {
|
|
29503
|
+
const recovery = recoveryAtFor2(input.oldQuota);
|
|
29504
|
+
if (recovery) {
|
|
29505
|
+
lines.push(`${escapeHtml12(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
|
|
29506
|
+
}
|
|
29507
|
+
}
|
|
29508
|
+
lines.push("");
|
|
29509
|
+
lines.push(`Run <code>/auth add <label></code> to attach another subscription, ` + `or <code>/auth refresh</code> to re-probe.`);
|
|
29510
|
+
return lines.join(`
|
|
29511
|
+
`);
|
|
29512
|
+
}
|
|
29513
|
+
lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
|
|
29514
|
+
lines.push("");
|
|
29515
|
+
lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> \u2192 <code>${escapeHtml12(input.newLabel)}</code>`);
|
|
29516
|
+
lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
|
|
29517
|
+
lines.push("");
|
|
29518
|
+
if (input.oldQuota) {
|
|
29519
|
+
const recovery = recoveryAtFor2(input.oldQuota);
|
|
29520
|
+
if (recovery) {
|
|
29521
|
+
lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
|
|
29522
|
+
}
|
|
29523
|
+
}
|
|
29524
|
+
if (input.newQuota) {
|
|
29525
|
+
const fiveStr = fmtPct2(input.newQuota.fiveHourUtilizationPct);
|
|
29526
|
+
const sevenStr = fmtPct2(input.newQuota.sevenDayUtilizationPct);
|
|
29527
|
+
const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT2 && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT2;
|
|
29528
|
+
const headroomStr = hasHeadroom ? "<i>(plenty of headroom)</i>" : "<i>(near limit \u2014 watch this)</i>";
|
|
29529
|
+
lines.push(`<code>${escapeHtml12(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
|
|
29530
|
+
} else {
|
|
29531
|
+
lines.push(`<i>(quota probe for new account is pending \u2014 will reflect on next /auth)</i>`);
|
|
29532
|
+
}
|
|
29533
|
+
return lines.join(`
|
|
29534
|
+
`);
|
|
29535
|
+
}
|
|
29536
|
+
function limitWordFor2(q) {
|
|
29537
|
+
if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
|
|
29538
|
+
return "7-day";
|
|
29539
|
+
if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
|
|
29540
|
+
return "5-hour";
|
|
29541
|
+
if (q.sevenDayUtilizationPct >= 99)
|
|
29542
|
+
return "7-day";
|
|
29543
|
+
if (q.fiveHourUtilizationPct >= 99)
|
|
29544
|
+
return "5-hour";
|
|
29545
|
+
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
|
|
29546
|
+
}
|
|
29547
|
+
function recoveryAtFor2(q) {
|
|
29548
|
+
const word = limitWordFor2(q);
|
|
29549
|
+
if (word === "7-day")
|
|
29550
|
+
return q.sevenDayResetAt;
|
|
29551
|
+
if (word === "5-hour")
|
|
29552
|
+
return q.fiveHourResetAt;
|
|
29553
|
+
if (!q.fiveHourResetAt)
|
|
29554
|
+
return q.sevenDayResetAt;
|
|
29555
|
+
if (!q.sevenDayResetAt)
|
|
29556
|
+
return q.fiveHourResetAt;
|
|
29557
|
+
return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
29558
|
+
}
|
|
29559
|
+
function buildSnapshotKeyboard2(snapshots, opts = {}) {
|
|
29560
|
+
const max = opts.maxSwitchButtons ?? 3;
|
|
29561
|
+
const rows = [];
|
|
29562
|
+
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a) - switchPriority2(b)).filter((s) => classifyHealth2(s) !== "blocked" && classifyHealth2(s) !== "unknown").slice(0, max);
|
|
29563
|
+
for (const t of switchTargets) {
|
|
29564
|
+
rows.push([
|
|
29565
|
+
{
|
|
29566
|
+
text: `Switch fleet \u2192 ${t.label}`,
|
|
29567
|
+
callbackData: `auth:use:${t.label}`
|
|
29568
|
+
}
|
|
29569
|
+
]);
|
|
29570
|
+
}
|
|
29571
|
+
rows.push([
|
|
29572
|
+
{ text: "\u21bb Refresh", callbackData: "auth:refresh" },
|
|
29573
|
+
{ text: "/usage", insertText: "/usage" },
|
|
29574
|
+
{ text: "+ Add", insertText: "/auth add " }
|
|
29575
|
+
]);
|
|
29576
|
+
return rows;
|
|
29577
|
+
}
|
|
29578
|
+
function switchPriority2(s) {
|
|
29579
|
+
const h = classifyHealth2(s);
|
|
29580
|
+
if (h === "healthy")
|
|
29581
|
+
return 0;
|
|
29582
|
+
if (h === "throttling")
|
|
29583
|
+
return 1;
|
|
29584
|
+
if (h === "unknown")
|
|
29585
|
+
return 2;
|
|
29586
|
+
return 3;
|
|
29587
|
+
}
|
|
29588
|
+
function escapeHtml12(s) {
|
|
29589
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
29590
|
+
}
|
|
29591
|
+
function buildSnapshotsFromState2(state4, quotas) {
|
|
29592
|
+
const out = [];
|
|
29593
|
+
for (let i = 0;i < state4.accounts.length; i++) {
|
|
29594
|
+
const acc = state4.accounts[i];
|
|
29595
|
+
const q = quotas[i];
|
|
29596
|
+
out.push({
|
|
29597
|
+
label: acc.label,
|
|
29598
|
+
isActive: acc.label === state4.active,
|
|
29599
|
+
quota: q && q.ok ? q.data : null,
|
|
29600
|
+
quotaError: q && !q.ok ? q.reason : undefined,
|
|
29601
|
+
expiresAtMs: acc.expiresAt
|
|
29602
|
+
});
|
|
29603
|
+
}
|
|
29604
|
+
return out;
|
|
29605
|
+
}
|
|
29606
|
+
function reviveLastQuota(snap) {
|
|
29607
|
+
if (!snap)
|
|
29608
|
+
return null;
|
|
29609
|
+
return {
|
|
29610
|
+
fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
|
|
29611
|
+
sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
|
|
29612
|
+
fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
|
|
29613
|
+
sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
|
|
29614
|
+
representativeClaim: snap.representativeClaim,
|
|
29615
|
+
overageStatus: snap.overageStatus,
|
|
29616
|
+
overageDisabledReason: snap.overageDisabledReason
|
|
29617
|
+
};
|
|
29618
|
+
}
|
|
29619
|
+
function buildSnapshotsFromCachedState(state4) {
|
|
29620
|
+
return state4.accounts.map((acc) => {
|
|
29621
|
+
const lq = acc.last_quota ?? null;
|
|
29622
|
+
return {
|
|
29623
|
+
label: acc.label,
|
|
29624
|
+
isActive: acc.label === state4.active,
|
|
29625
|
+
quota: reviveLastQuota(lq),
|
|
29626
|
+
quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
|
|
29627
|
+
expiresAtMs: acc.expiresAt
|
|
29628
|
+
};
|
|
29629
|
+
});
|
|
29630
|
+
}
|
|
29631
|
+
var THROTTLING_THRESHOLD_PCT2 = 80, HEALTH_EMOJI2, HEALTH_TITLE2;
|
|
29632
|
+
var init_auth_snapshot_format = __esm(() => {
|
|
29633
|
+
HEALTH_EMOJI2 = {
|
|
29634
|
+
healthy: "\uD83D\uDFE2",
|
|
29635
|
+
throttling: "\uD83D\uDFE1",
|
|
29636
|
+
blocked: "\uD83D\uDD34",
|
|
29637
|
+
unknown: "\u26aa"
|
|
29638
|
+
};
|
|
29639
|
+
HEALTH_TITLE2 = {
|
|
29640
|
+
healthy: "HEALTHY",
|
|
29641
|
+
throttling: "THROTTLING",
|
|
29642
|
+
blocked: "BLOCKED",
|
|
29643
|
+
unknown: "UNKNOWN"
|
|
29644
|
+
};
|
|
29645
|
+
});
|
|
29646
|
+
|
|
29153
29647
|
// ../src/vault/flock.ts
|
|
29154
29648
|
var init_flock = () => {};
|
|
29155
29649
|
|
|
29156
29650
|
// ../src/vault/vault.ts
|
|
29157
29651
|
import { randomBytes as randomBytes5, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
|
|
29158
|
-
import {
|
|
29159
|
-
readFileSync as
|
|
29160
|
-
writeFileSync as
|
|
29161
|
-
existsSync as
|
|
29162
|
-
renameSync as
|
|
29163
|
-
mkdirSync as
|
|
29652
|
+
import {
|
|
29653
|
+
readFileSync as readFileSync33,
|
|
29654
|
+
writeFileSync as writeFileSync21,
|
|
29655
|
+
existsSync as existsSync34,
|
|
29656
|
+
renameSync as renameSync12,
|
|
29657
|
+
mkdirSync as mkdirSync20,
|
|
29164
29658
|
unlinkSync as unlinkSync12,
|
|
29165
29659
|
lstatSync,
|
|
29166
29660
|
realpathSync
|
|
@@ -29196,12 +29690,12 @@ function normalizeSecrets(raw) {
|
|
|
29196
29690
|
return out;
|
|
29197
29691
|
}
|
|
29198
29692
|
function openVault(passphrase, vaultPath) {
|
|
29199
|
-
if (!
|
|
29693
|
+
if (!existsSync34(vaultPath)) {
|
|
29200
29694
|
throw new VaultError(`Vault file not found: ${vaultPath}`);
|
|
29201
29695
|
}
|
|
29202
29696
|
let vaultFile;
|
|
29203
29697
|
try {
|
|
29204
|
-
vaultFile = JSON.parse(
|
|
29698
|
+
vaultFile = JSON.parse(readFileSync33(vaultPath, "utf8"));
|
|
29205
29699
|
} catch {
|
|
29206
29700
|
throw new VaultError(`Failed to read vault file: ${vaultPath}`);
|
|
29207
29701
|
}
|
|
@@ -29248,14 +29742,14 @@ var init_vault = __esm(() => {
|
|
|
29248
29742
|
import {
|
|
29249
29743
|
chmodSync as chmodSync4,
|
|
29250
29744
|
closeSync as closeSync7,
|
|
29251
|
-
mkdirSync as
|
|
29745
|
+
mkdirSync as mkdirSync21,
|
|
29252
29746
|
mkdtempSync as mkdtempSync2,
|
|
29253
29747
|
openSync as openSync7,
|
|
29254
29748
|
rmSync as rmSync3,
|
|
29255
29749
|
statSync as statSync11,
|
|
29256
29750
|
writeSync as writeSync2
|
|
29257
29751
|
} from "node:fs";
|
|
29258
|
-
import { join as
|
|
29752
|
+
import { join as join32 } from "node:path";
|
|
29259
29753
|
import { tmpdir } from "node:os";
|
|
29260
29754
|
import { constants as fsConstants } from "node:fs";
|
|
29261
29755
|
function isVaultReference(value) {
|
|
@@ -29307,11 +29801,11 @@ function materializationRoot() {
|
|
|
29307
29801
|
return cachedRoot;
|
|
29308
29802
|
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
29309
29803
|
if (xdg) {
|
|
29310
|
-
const base =
|
|
29311
|
-
|
|
29312
|
-
cachedRoot = mkdtempSync2(
|
|
29804
|
+
const base = join32(xdg, "switchroom", "vault");
|
|
29805
|
+
mkdirSync21(base, { recursive: true, mode: 448 });
|
|
29806
|
+
cachedRoot = mkdtempSync2(join32(base, "run-"));
|
|
29313
29807
|
} else {
|
|
29314
|
-
cachedRoot = mkdtempSync2(
|
|
29808
|
+
cachedRoot = mkdtempSync2(join32(tmpdir(), "switchroom-vault-"));
|
|
29315
29809
|
}
|
|
29316
29810
|
chmodSync4(cachedRoot, 448);
|
|
29317
29811
|
return cachedRoot;
|
|
@@ -29326,13 +29820,13 @@ function writeFileExclusive(filePath, content) {
|
|
|
29326
29820
|
}
|
|
29327
29821
|
}
|
|
29328
29822
|
function materializeFilesEntry(key, files) {
|
|
29329
|
-
const dir =
|
|
29823
|
+
const dir = join32(materializationRoot(), key);
|
|
29330
29824
|
if (materializedDirs.has(dir)) {
|
|
29331
29825
|
try {
|
|
29332
29826
|
rmSync3(dir, { recursive: true, force: true });
|
|
29333
29827
|
} catch {}
|
|
29334
29828
|
}
|
|
29335
|
-
|
|
29829
|
+
mkdirSync21(dir, { recursive: true, mode: 448 });
|
|
29336
29830
|
chmodSync4(dir, 448);
|
|
29337
29831
|
const st = statSync11(dir);
|
|
29338
29832
|
if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
|
|
@@ -29342,7 +29836,7 @@ function materializeFilesEntry(key, files) {
|
|
|
29342
29836
|
if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
|
|
29343
29837
|
throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
|
|
29344
29838
|
}
|
|
29345
|
-
const filePath =
|
|
29839
|
+
const filePath = join32(dir, filename);
|
|
29346
29840
|
const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
|
|
29347
29841
|
writeFileExclusive(filePath, content);
|
|
29348
29842
|
}
|
|
@@ -29475,7 +29969,7 @@ __export(exports_materialize_bot_token, {
|
|
|
29475
29969
|
materializeBotToken: () => materializeBotToken,
|
|
29476
29970
|
BotTokenMaterializeError: () => BotTokenMaterializeError
|
|
29477
29971
|
});
|
|
29478
|
-
import { existsSync as
|
|
29972
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
29479
29973
|
function pickConfiguredToken(config, agentName3) {
|
|
29480
29974
|
if (agentName3) {
|
|
29481
29975
|
const agent = config.agents?.[agentName3];
|
|
@@ -29489,7 +29983,7 @@ function tryDirectVaultRead(ref, config, passphrase) {
|
|
|
29489
29983
|
if (!passphrase)
|
|
29490
29984
|
return null;
|
|
29491
29985
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
29492
|
-
if (!
|
|
29986
|
+
if (!existsSync35(vaultPath))
|
|
29493
29987
|
return null;
|
|
29494
29988
|
try {
|
|
29495
29989
|
const secrets = openVault(passphrase, vaultPath);
|
|
@@ -29607,7 +30101,7 @@ function truncateDiffForCard(unifiedDiff, maxLines = 50, maxChars = 3000) {
|
|
|
29607
30101
|
}
|
|
29608
30102
|
return out === unifiedDiff ? out : out + sentinel;
|
|
29609
30103
|
}
|
|
29610
|
-
function
|
|
30104
|
+
function escapeHtml13(s) {
|
|
29611
30105
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
29612
30106
|
}
|
|
29613
30107
|
function clipReason(reason) {
|
|
@@ -29618,10 +30112,10 @@ function clipReason(reason) {
|
|
|
29618
30112
|
function buildConfigApprovalCardBody(args) {
|
|
29619
30113
|
const safeReason = clipReason(args.reason);
|
|
29620
30114
|
const render = (diff) => `\uD83D\uDEE0 <b>Config edit proposed</b>
|
|
29621
|
-
` + `Agent: <code>${
|
|
29622
|
-
` + `Reason: ${
|
|
30115
|
+
` + `Agent: <code>${escapeHtml13(args.agentName)}</code>
|
|
30116
|
+
` + `Reason: ${escapeHtml13(safeReason)}
|
|
29623
30117
|
|
|
29624
|
-
` + `<pre>${
|
|
30118
|
+
` + `<pre>${escapeHtml13(diff)}</pre>`;
|
|
29625
30119
|
return truncateRawToFit({
|
|
29626
30120
|
raw: args.unifiedDiff,
|
|
29627
30121
|
render,
|
|
@@ -29728,8 +30222,8 @@ async function handleRequestConfigFinalize(_client, msg, deps) {
|
|
|
29728
30222
|
}
|
|
29729
30223
|
pending.delete(msg.requestId);
|
|
29730
30224
|
const body = msg.outcome === "applied" ? `\u2705 <b>Applied</b>${msg.detail ? `
|
|
29731
|
-
${
|
|
29732
|
-
${
|
|
30225
|
+
${escapeHtml13(msg.detail)}` : ""}` : `\u26a0\ufe0f <b>Reconcile failed; rolled back</b>${msg.detail ? `
|
|
30226
|
+
${escapeHtml13(msg.detail)}` : ""}`;
|
|
29733
30227
|
try {
|
|
29734
30228
|
await deps.editCard({
|
|
29735
30229
|
chatId: entry.chatId,
|
|
@@ -29794,7 +30288,7 @@ __export(exports_tmux, {
|
|
|
29794
30288
|
captureAgentPane: () => captureAgentPane
|
|
29795
30289
|
});
|
|
29796
30290
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
29797
|
-
import { mkdirSync as
|
|
30291
|
+
import { mkdirSync as mkdirSync22, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync22 } from "node:fs";
|
|
29798
30292
|
import { resolve as resolve7 } from "node:path";
|
|
29799
30293
|
function captureAgentPane(opts) {
|
|
29800
30294
|
const { agentName: agentName3, agentDir, reason } = opts;
|
|
@@ -29806,7 +30300,7 @@ function captureAgentPane(opts) {
|
|
|
29806
30300
|
const reasonSlug = sanitizeReason(reason);
|
|
29807
30301
|
const outPath = resolve7(outDir, `${ts}-${reasonSlug}.txt`);
|
|
29808
30302
|
try {
|
|
29809
|
-
|
|
30303
|
+
mkdirSync22(outDir, { recursive: true, mode: 493 });
|
|
29810
30304
|
} catch (err) {
|
|
29811
30305
|
const msg = `mkdir crash-reports failed: ${err.message}`;
|
|
29812
30306
|
console.error(`[tmux-capture] ${agentName3}: ${msg}`);
|
|
@@ -29840,7 +30334,7 @@ function captureAgentPane(opts) {
|
|
|
29840
30334
|
` + `
|
|
29841
30335
|
`;
|
|
29842
30336
|
try {
|
|
29843
|
-
|
|
30337
|
+
writeFileSync22(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
|
|
29844
30338
|
mode: 420
|
|
29845
30339
|
});
|
|
29846
30340
|
} catch (err) {
|
|
@@ -29999,366 +30493,53 @@ function registerApprovalsCommands(bot, opts) {
|
|
|
29999
30493
|
return;
|
|
30000
30494
|
}
|
|
30001
30495
|
if (decisions.length === 0) {
|
|
30002
|
-
await ctx.reply(agentFilter ? `No active approvals for <code>${
|
|
30496
|
+
await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml14(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
|
|
30003
30497
|
return;
|
|
30004
30498
|
}
|
|
30005
30499
|
const byAgent = new Map;
|
|
30006
30500
|
for (const d of decisions)
|
|
30007
30501
|
byAgent.set(d.agent_unit, (byAgent.get(d.agent_unit) ?? 0) + 1);
|
|
30008
|
-
const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${
|
|
30009
|
-
`);
|
|
30010
|
-
const detail = decisions.slice(0, 20).map((d) => {
|
|
30011
|
-
const ttl = d.ttl_expires_at === null ? "always" : `until ${new Date(d.ttl_expires_at).toISOString().slice(0, 16).replace("T", " ")}`;
|
|
30012
|
-
return `<code>${escapeHtml12(d.id.slice(0, 8))}</code> ` + `${escapeHtml12(d.agent_unit)} \u2192 ` + `<code>${escapeHtml12(d.scope)}</code> ` + `(${escapeHtml12(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml12(d.id)}`;
|
|
30013
|
-
}).join(`
|
|
30014
|
-
`);
|
|
30015
|
-
await ctx.reply(`<b>Active approvals</b>
|
|
30016
|
-
|
|
30017
|
-
${summary}
|
|
30018
|
-
|
|
30019
|
-
${detail}`, {
|
|
30020
|
-
parse_mode: "HTML"
|
|
30021
|
-
});
|
|
30022
|
-
return;
|
|
30023
|
-
}
|
|
30024
|
-
if (sub === "revoke") {
|
|
30025
|
-
const id = args[1];
|
|
30026
|
-
if (!id) {
|
|
30027
|
-
await ctx.reply("Usage: <code>/approvals revoke <id></code>", {
|
|
30028
|
-
parse_mode: "HTML"
|
|
30029
|
-
});
|
|
30030
|
-
return;
|
|
30031
|
-
}
|
|
30032
|
-
const actor = ctx.from?.id?.toString() ?? "unknown";
|
|
30033
|
-
const ok = await approvalRevoke(id, actor, "manual /approvals revoke");
|
|
30034
|
-
if (ok === null) {
|
|
30035
|
-
await ctx.reply("Approval kernel unreachable.");
|
|
30036
|
-
return;
|
|
30037
|
-
}
|
|
30038
|
-
await ctx.reply(ok ? `Revoked <code>${escapeHtml12(id)}</code>.` : `No such active decision <code>${escapeHtml12(id)}</code>.`, { parse_mode: "HTML" });
|
|
30039
|
-
return;
|
|
30040
|
-
}
|
|
30041
|
-
await ctx.reply(`Unknown subcommand <code>${escapeHtml12(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke <id></code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
|
|
30042
|
-
});
|
|
30043
|
-
}
|
|
30044
|
-
function escapeHtml12(s) {
|
|
30045
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
30046
|
-
}
|
|
30047
|
-
var init_approvals_commands = __esm(() => {
|
|
30048
|
-
init_client3();
|
|
30049
|
-
});
|
|
30050
|
-
|
|
30051
|
-
// auth-snapshot-format.ts
|
|
30052
|
-
var exports_auth_snapshot_format = {};
|
|
30053
|
-
__export(exports_auth_snapshot_format, {
|
|
30054
|
-
renderFallbackAnnouncement: () => renderFallbackAnnouncement2,
|
|
30055
|
-
renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat22,
|
|
30056
|
-
recommendation: () => recommendation2,
|
|
30057
|
-
formatRelative: () => formatRelative2,
|
|
30058
|
-
formatAbsolute: () => formatAbsolute2,
|
|
30059
|
-
fmtPct: () => fmtPct2,
|
|
30060
|
-
classifyHealth: () => classifyHealth2,
|
|
30061
|
-
buildSnapshotsFromState: () => buildSnapshotsFromState2,
|
|
30062
|
-
buildSnapshotKeyboard: () => buildSnapshotKeyboard2,
|
|
30063
|
-
bindingWindow: () => bindingWindow2,
|
|
30064
|
-
THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT2
|
|
30065
|
-
});
|
|
30066
|
-
function classifyHealth2(snap) {
|
|
30067
|
-
if (!snap.quota)
|
|
30068
|
-
return "unknown";
|
|
30069
|
-
const q = snap.quota;
|
|
30070
|
-
const max = Math.max(q.fiveHourUtilizationPct, q.sevenDayUtilizationPct);
|
|
30071
|
-
if (max >= 99.5)
|
|
30072
|
-
return "blocked";
|
|
30073
|
-
if (max >= THROTTLING_THRESHOLD_PCT2)
|
|
30074
|
-
return "throttling";
|
|
30075
|
-
return "healthy";
|
|
30076
|
-
}
|
|
30077
|
-
function bindingWindow2(q) {
|
|
30078
|
-
if (q.representativeClaim === "seven_day")
|
|
30079
|
-
return "7d";
|
|
30080
|
-
if (q.representativeClaim === "five_hour")
|
|
30081
|
-
return "5h";
|
|
30082
|
-
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
|
|
30083
|
-
}
|
|
30084
|
-
function formatRelative2(target, now = new Date) {
|
|
30085
|
-
if (!target)
|
|
30086
|
-
return "\u2014";
|
|
30087
|
-
const deltaMs = target.getTime() - now.getTime();
|
|
30088
|
-
if (deltaMs <= 0)
|
|
30089
|
-
return "now";
|
|
30090
|
-
const totalMin = Math.round(deltaMs / 60000);
|
|
30091
|
-
if (totalMin < 60)
|
|
30092
|
-
return `${totalMin}m`;
|
|
30093
|
-
const h = Math.floor(totalMin / 60);
|
|
30094
|
-
const m = totalMin % 60;
|
|
30095
|
-
if (h < 24)
|
|
30096
|
-
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
30097
|
-
const d = Math.floor(h / 24);
|
|
30098
|
-
const rh = h % 24;
|
|
30099
|
-
return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
|
|
30100
|
-
}
|
|
30101
|
-
function formatAbsolute2(target, tz = "UTC") {
|
|
30102
|
-
if (!target)
|
|
30103
|
-
return "\u2014";
|
|
30104
|
-
return target.toLocaleString("en-US", {
|
|
30105
|
-
timeZone: tz,
|
|
30106
|
-
weekday: "short",
|
|
30107
|
-
hour: "numeric",
|
|
30108
|
-
minute: "2-digit",
|
|
30109
|
-
hour12: true
|
|
30110
|
-
});
|
|
30111
|
-
}
|
|
30112
|
-
function fmtPct2(pct) {
|
|
30113
|
-
return `${Math.round(pct)}%`;
|
|
30114
|
-
}
|
|
30115
|
-
function groupHeader2(health, count) {
|
|
30116
|
-
const emoji = HEALTH_EMOJI2[health];
|
|
30117
|
-
const title = HEALTH_TITLE2[health];
|
|
30118
|
-
return `${emoji} <b>${title}</b> (${count})`;
|
|
30119
|
-
}
|
|
30120
|
-
function renderAccountRow2(snap, opts) {
|
|
30121
|
-
const now = opts.now ?? new Date;
|
|
30122
|
-
const tz = opts.tz ?? "UTC";
|
|
30123
|
-
const lines = [];
|
|
30124
|
-
const marker = snap.isActive ? "\u25cf " : "";
|
|
30125
|
-
if (!snap.quota) {
|
|
30126
|
-
lines.push(`${marker}<code>${escapeHtml13(snap.label)}</code> <i>quota probe failed</i>`);
|
|
30127
|
-
if (snap.quotaError) {
|
|
30128
|
-
lines.push(` <i>${escapeHtml13(snap.quotaError)}</i>`);
|
|
30129
|
-
}
|
|
30130
|
-
return lines;
|
|
30131
|
-
}
|
|
30132
|
-
const q = snap.quota;
|
|
30133
|
-
const fiveStr = fmtPct2(q.fiveHourUtilizationPct);
|
|
30134
|
-
const sevenStr = fmtPct2(q.sevenDayUtilizationPct);
|
|
30135
|
-
lines.push(`${marker}<code>${escapeHtml13(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
|
|
30136
|
-
const health = classifyHealth2(snap);
|
|
30137
|
-
if (health === "blocked") {
|
|
30138
|
-
const win = bindingWindow2(q);
|
|
30139
|
-
const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
30140
|
-
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
30141
|
-
lines.push(` <i>back ${formatAbsolute2(reset2, tz)} (in ${formatRelative2(reset2, now)}, ${winLabel} cap)</i>`);
|
|
30142
|
-
return lines;
|
|
30143
|
-
}
|
|
30144
|
-
const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
|
|
30145
|
-
const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
|
|
30146
|
-
const fiveFirst = fiveResetIn <= sevenResetIn;
|
|
30147
|
-
const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute2(q.fiveHourResetAt, tz)} (in ${formatRelative2(q.fiveHourResetAt, now)})` : "5h refills \u2014";
|
|
30148
|
-
const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute2(q.sevenDayResetAt, tz)} (in ${formatRelative2(q.sevenDayResetAt, now)})` : "7d resets \u2014";
|
|
30149
|
-
lines.push(` <i>${fiveFirst ? fiveSeg : sevenSeg} \u00b7 ${fiveFirst ? sevenSeg : fiveSeg}</i>`);
|
|
30150
|
-
return lines;
|
|
30151
|
-
}
|
|
30152
|
-
function renderAuthSnapshotFormat22(snapshots, opts = {}) {
|
|
30153
|
-
const now = opts.now ?? new Date;
|
|
30154
|
-
const lines = [];
|
|
30155
|
-
lines.push("\uD83D\uDD0B <b>Auth \u2014 fleet status</b>");
|
|
30156
|
-
const order = ["blocked", "throttling", "healthy", "unknown"];
|
|
30157
|
-
const grouped = new Map;
|
|
30158
|
-
for (const s of snapshots) {
|
|
30159
|
-
const h = classifyHealth2(s);
|
|
30160
|
-
if (!grouped.has(h))
|
|
30161
|
-
grouped.set(h, []);
|
|
30162
|
-
grouped.get(h).push(s);
|
|
30163
|
-
}
|
|
30164
|
-
for (const arr of grouped.values()) {
|
|
30165
|
-
arr.sort((a, b) => Number(b.isActive) - Number(a.isActive));
|
|
30166
|
-
}
|
|
30167
|
-
for (const h of order) {
|
|
30168
|
-
const arr = grouped.get(h);
|
|
30169
|
-
if (!arr || arr.length === 0)
|
|
30170
|
-
continue;
|
|
30171
|
-
lines.push("");
|
|
30172
|
-
lines.push(groupHeader2(h, arr.length));
|
|
30173
|
-
for (const s of arr) {
|
|
30174
|
-
for (const ln of renderAccountRow2(s, opts))
|
|
30175
|
-
lines.push(ln);
|
|
30176
|
-
}
|
|
30177
|
-
}
|
|
30178
|
-
lines.push("");
|
|
30179
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
30180
|
-
lines.push(`<i>${recommendation2(snapshots, now)}</i>`);
|
|
30181
|
-
if (opts.liveProbedAtMs != null) {
|
|
30182
|
-
const ageSec = Math.max(0, Math.round((Date.now() - opts.liveProbedAtMs) / 1000));
|
|
30183
|
-
const ageStr = ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
|
|
30184
|
-
lines.push(`<i>Live \u00b7 refreshed ${ageStr}</i>`);
|
|
30185
|
-
} else {
|
|
30186
|
-
lines.push("<i>Live</i>");
|
|
30187
|
-
}
|
|
30188
|
-
return lines.join(`
|
|
30189
|
-
`);
|
|
30190
|
-
}
|
|
30191
|
-
function recommendation2(snapshots, now = new Date) {
|
|
30192
|
-
const active = snapshots.find((s) => s.isActive);
|
|
30193
|
-
if (!active)
|
|
30194
|
-
return "No active account set.";
|
|
30195
|
-
const activeHealth = classifyHealth2(active);
|
|
30196
|
-
const others = snapshots.filter((s) => !s.isActive);
|
|
30197
|
-
const healthyAlt = others.find((s) => classifyHealth2(s) === "healthy");
|
|
30198
|
-
if (activeHealth === "healthy") {
|
|
30199
|
-
return `Recommendation: stay on ${active.label}.`;
|
|
30200
|
-
}
|
|
30201
|
-
if (activeHealth === "throttling") {
|
|
30202
|
-
if (healthyAlt) {
|
|
30203
|
-
return `Recommendation: active ${active.label} is throttling. Switch to ${healthyAlt.label} for headroom.`;
|
|
30204
|
-
}
|
|
30205
|
-
return `Recommendation: active ${active.label} is throttling; no healthy alternative \u2014 wait for refill.`;
|
|
30206
|
-
}
|
|
30207
|
-
if (activeHealth === "blocked") {
|
|
30208
|
-
if (healthyAlt) {
|
|
30209
|
-
return `Recommendation: active ${active.label} is BLOCKED \u2014 switch to ${healthyAlt.label} now.`;
|
|
30210
|
-
}
|
|
30211
|
-
const earliestRecovery = pickEarliestRecovery2(snapshots, now);
|
|
30212
|
-
if (earliestRecovery) {
|
|
30213
|
-
return `All accounts blocked. Earliest recovery: ${earliestRecovery.label} in ${formatRelative2(earliestRecovery.at, now)}.`;
|
|
30214
|
-
}
|
|
30215
|
-
return `All accounts blocked. Run /auth add to attach another subscription.`;
|
|
30216
|
-
}
|
|
30217
|
-
return `Active ${active.label}: quota probe failed; broker last_seen unknown.`;
|
|
30218
|
-
}
|
|
30219
|
-
function pickEarliestRecovery2(snapshots, now) {
|
|
30220
|
-
let best = null;
|
|
30221
|
-
for (const s of snapshots) {
|
|
30222
|
-
if (!s.quota)
|
|
30223
|
-
continue;
|
|
30224
|
-
const win = bindingWindow2(s.quota);
|
|
30225
|
-
const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
|
|
30226
|
-
if (!at || at.getTime() <= now.getTime())
|
|
30227
|
-
continue;
|
|
30228
|
-
if (!best || at.getTime() < best.at.getTime()) {
|
|
30229
|
-
best = { label: s.label, at };
|
|
30230
|
-
}
|
|
30231
|
-
}
|
|
30232
|
-
return best;
|
|
30233
|
-
}
|
|
30234
|
-
function renderFallbackAnnouncement2(input) {
|
|
30235
|
-
const now = input.now ?? new Date;
|
|
30236
|
-
const tz = input.tz ?? "UTC";
|
|
30237
|
-
const lines = [];
|
|
30238
|
-
const limitWord = input.oldQuota ? limitWordFor2(input.oldQuota) : "quota";
|
|
30239
|
-
const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
30240
|
-
if (!input.newLabel) {
|
|
30241
|
-
lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml13(input.oldLabel)}</b>`);
|
|
30242
|
-
lines.push("");
|
|
30243
|
-
lines.push(`Triggered by: agent <b>${escapeHtml13(input.triggerAgent)}</b>`);
|
|
30244
|
-
if (input.oldQuota) {
|
|
30245
|
-
const recovery = recoveryAtFor2(input.oldQuota);
|
|
30246
|
-
if (recovery) {
|
|
30247
|
-
lines.push(`${escapeHtml13(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
|
|
30248
|
-
}
|
|
30249
|
-
}
|
|
30250
|
-
lines.push("");
|
|
30251
|
-
lines.push(`Run <code>/auth add <label></code> to attach another subscription, ` + `or <code>/auth refresh</code> to re-probe.`);
|
|
30252
|
-
return lines.join(`
|
|
30502
|
+
const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml14(a)}</b>: ${n}`).join(`
|
|
30253
30503
|
`);
|
|
30254
|
-
|
|
30255
|
-
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
lines.push(`Triggered by: agent <b>${escapeHtml13(input.triggerAgent)}</b>`);
|
|
30259
|
-
lines.push("");
|
|
30260
|
-
if (input.oldQuota) {
|
|
30261
|
-
const recovery = recoveryAtFor2(input.oldQuota);
|
|
30262
|
-
if (recovery) {
|
|
30263
|
-
lines.push(`<code>${escapeHtml13(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
|
|
30264
|
-
}
|
|
30265
|
-
}
|
|
30266
|
-
if (input.newQuota) {
|
|
30267
|
-
const fiveStr = fmtPct2(input.newQuota.fiveHourUtilizationPct);
|
|
30268
|
-
const sevenStr = fmtPct2(input.newQuota.sevenDayUtilizationPct);
|
|
30269
|
-
const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT2 && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT2;
|
|
30270
|
-
const headroomStr = hasHeadroom ? "<i>(plenty of headroom)</i>" : "<i>(near limit \u2014 watch this)</i>";
|
|
30271
|
-
lines.push(`<code>${escapeHtml13(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
|
|
30272
|
-
} else {
|
|
30273
|
-
lines.push(`<i>(quota probe for new account is pending \u2014 will reflect on next /auth)</i>`);
|
|
30274
|
-
}
|
|
30275
|
-
return lines.join(`
|
|
30504
|
+
const detail = decisions.slice(0, 20).map((d) => {
|
|
30505
|
+
const ttl = d.ttl_expires_at === null ? "always" : `until ${new Date(d.ttl_expires_at).toISOString().slice(0, 16).replace("T", " ")}`;
|
|
30506
|
+
return `<code>${escapeHtml14(d.id.slice(0, 8))}</code> ` + `${escapeHtml14(d.agent_unit)} \u2192 ` + `<code>${escapeHtml14(d.scope)}</code> ` + `(${escapeHtml14(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml14(d.id)}`;
|
|
30507
|
+
}).join(`
|
|
30276
30508
|
`);
|
|
30277
|
-
|
|
30278
|
-
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
|
|
30291
|
-
|
|
30292
|
-
|
|
30293
|
-
if (word === "5-hour")
|
|
30294
|
-
return q.fiveHourResetAt;
|
|
30295
|
-
if (!q.fiveHourResetAt)
|
|
30296
|
-
return q.sevenDayResetAt;
|
|
30297
|
-
if (!q.sevenDayResetAt)
|
|
30298
|
-
return q.fiveHourResetAt;
|
|
30299
|
-
return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
30300
|
-
}
|
|
30301
|
-
function buildSnapshotKeyboard2(snapshots, opts = {}) {
|
|
30302
|
-
const max = opts.maxSwitchButtons ?? 3;
|
|
30303
|
-
const rows = [];
|
|
30304
|
-
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a) - switchPriority2(b)).filter((s) => classifyHealth2(s) !== "blocked" && classifyHealth2(s) !== "unknown").slice(0, max);
|
|
30305
|
-
for (const t of switchTargets) {
|
|
30306
|
-
rows.push([
|
|
30307
|
-
{
|
|
30308
|
-
text: `Switch fleet \u2192 ${t.label}`,
|
|
30309
|
-
callbackData: `auth:use:${t.label}`
|
|
30509
|
+
await ctx.reply(`<b>Active approvals</b>
|
|
30510
|
+
|
|
30511
|
+
${summary}
|
|
30512
|
+
|
|
30513
|
+
${detail}`, {
|
|
30514
|
+
parse_mode: "HTML"
|
|
30515
|
+
});
|
|
30516
|
+
return;
|
|
30517
|
+
}
|
|
30518
|
+
if (sub === "revoke") {
|
|
30519
|
+
const id = args[1];
|
|
30520
|
+
if (!id) {
|
|
30521
|
+
await ctx.reply("Usage: <code>/approvals revoke <id></code>", {
|
|
30522
|
+
parse_mode: "HTML"
|
|
30523
|
+
});
|
|
30524
|
+
return;
|
|
30310
30525
|
}
|
|
30311
|
-
|
|
30312
|
-
|
|
30313
|
-
|
|
30314
|
-
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
|
|
30318
|
-
|
|
30319
|
-
}
|
|
30320
|
-
|
|
30321
|
-
|
|
30322
|
-
if (h === "healthy")
|
|
30323
|
-
return 0;
|
|
30324
|
-
if (h === "throttling")
|
|
30325
|
-
return 1;
|
|
30326
|
-
if (h === "unknown")
|
|
30327
|
-
return 2;
|
|
30328
|
-
return 3;
|
|
30526
|
+
const actor = ctx.from?.id?.toString() ?? "unknown";
|
|
30527
|
+
const ok = await approvalRevoke(id, actor, "manual /approvals revoke");
|
|
30528
|
+
if (ok === null) {
|
|
30529
|
+
await ctx.reply("Approval kernel unreachable.");
|
|
30530
|
+
return;
|
|
30531
|
+
}
|
|
30532
|
+
await ctx.reply(ok ? `Revoked <code>${escapeHtml14(id)}</code>.` : `No such active decision <code>${escapeHtml14(id)}</code>.`, { parse_mode: "HTML" });
|
|
30533
|
+
return;
|
|
30534
|
+
}
|
|
30535
|
+
await ctx.reply(`Unknown subcommand <code>${escapeHtml14(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke <id></code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
|
|
30536
|
+
});
|
|
30329
30537
|
}
|
|
30330
|
-
function
|
|
30538
|
+
function escapeHtml14(s) {
|
|
30331
30539
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
30332
30540
|
}
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
for (let i = 0;i < state4.accounts.length; i++) {
|
|
30336
|
-
const acc = state4.accounts[i];
|
|
30337
|
-
const q = quotas[i];
|
|
30338
|
-
out.push({
|
|
30339
|
-
label: acc.label,
|
|
30340
|
-
isActive: acc.label === state4.active,
|
|
30341
|
-
quota: q && q.ok ? q.data : null,
|
|
30342
|
-
quotaError: q && !q.ok ? q.reason : undefined,
|
|
30343
|
-
expiresAtMs: acc.expiresAt
|
|
30344
|
-
});
|
|
30345
|
-
}
|
|
30346
|
-
return out;
|
|
30347
|
-
}
|
|
30348
|
-
var THROTTLING_THRESHOLD_PCT2 = 80, HEALTH_EMOJI2, HEALTH_TITLE2;
|
|
30349
|
-
var init_auth_snapshot_format = __esm(() => {
|
|
30350
|
-
HEALTH_EMOJI2 = {
|
|
30351
|
-
healthy: "\uD83D\uDFE2",
|
|
30352
|
-
throttling: "\uD83D\uDFE1",
|
|
30353
|
-
blocked: "\uD83D\uDD34",
|
|
30354
|
-
unknown: "\u26aa"
|
|
30355
|
-
};
|
|
30356
|
-
HEALTH_TITLE2 = {
|
|
30357
|
-
healthy: "HEALTHY",
|
|
30358
|
-
throttling: "THROTTLING",
|
|
30359
|
-
blocked: "BLOCKED",
|
|
30360
|
-
unknown: "UNKNOWN"
|
|
30361
|
-
};
|
|
30541
|
+
var init_approvals_commands = __esm(() => {
|
|
30542
|
+
init_client3();
|
|
30362
30543
|
});
|
|
30363
30544
|
|
|
30364
30545
|
// gateway/approval-card.ts
|
|
@@ -30495,23 +30676,23 @@ var import_runner2 = __toESM(require_mod3(), 1);
|
|
|
30495
30676
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
30496
30677
|
import { execFileSync as execFileSync5, execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
30497
30678
|
import {
|
|
30498
|
-
readFileSync as
|
|
30499
|
-
writeFileSync as
|
|
30500
|
-
mkdirSync as
|
|
30679
|
+
readFileSync as readFileSync34,
|
|
30680
|
+
writeFileSync as writeFileSync23,
|
|
30681
|
+
mkdirSync as mkdirSync23,
|
|
30501
30682
|
readdirSync as readdirSync7,
|
|
30502
30683
|
rmSync as rmSync4,
|
|
30503
30684
|
statSync as statSync13,
|
|
30504
|
-
renameSync as
|
|
30685
|
+
renameSync as renameSync13,
|
|
30505
30686
|
realpathSync as realpathSync2,
|
|
30506
30687
|
chmodSync as chmodSync5,
|
|
30507
30688
|
openSync as openSync8,
|
|
30508
30689
|
closeSync as closeSync8,
|
|
30509
|
-
existsSync as
|
|
30690
|
+
existsSync as existsSync36,
|
|
30510
30691
|
unlinkSync as unlinkSync14,
|
|
30511
30692
|
appendFileSync as appendFileSync3
|
|
30512
30693
|
} from "fs";
|
|
30513
30694
|
import { homedir as homedir12 } from "os";
|
|
30514
|
-
import { join as
|
|
30695
|
+
import { join as join33, extname, sep as sep3, basename as basename7 } from "path";
|
|
30515
30696
|
|
|
30516
30697
|
// plugin-logger.ts
|
|
30517
30698
|
import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
|
|
@@ -31790,6 +31971,26 @@ function describeToolUse(toolName, input) {
|
|
|
31790
31971
|
return "Working\u2026";
|
|
31791
31972
|
}
|
|
31792
31973
|
}
|
|
31974
|
+
var MIRROR_MAX_LINES = 6;
|
|
31975
|
+
function appendActivityLine(lines, toolName, input) {
|
|
31976
|
+
const line = describeToolUse(toolName, input);
|
|
31977
|
+
if (line == null)
|
|
31978
|
+
return null;
|
|
31979
|
+
if (lines.length === 0 || lines[lines.length - 1] !== line) {
|
|
31980
|
+
lines.push(line);
|
|
31981
|
+
}
|
|
31982
|
+
return renderActivityFeed(lines);
|
|
31983
|
+
}
|
|
31984
|
+
function renderActivityFeed(lines) {
|
|
31985
|
+
if (lines.length === 0)
|
|
31986
|
+
return null;
|
|
31987
|
+
const shown = lines.slice(-MIRROR_MAX_LINES);
|
|
31988
|
+
const hidden = lines.length - shown.length;
|
|
31989
|
+
const body = shown.map((l) => `\u00b7 ${l}`).join(`
|
|
31990
|
+
`);
|
|
31991
|
+
return hidden > 0 ? `\u00b7 +${hidden} earlier\u2026
|
|
31992
|
+
${body}` : body;
|
|
31993
|
+
}
|
|
31793
31994
|
|
|
31794
31995
|
// tool-labels.ts
|
|
31795
31996
|
var MAX_LABEL_CHARS = 60;
|
|
@@ -38520,6 +38721,7 @@ function isSilentFlushMarker(text) {
|
|
|
38520
38721
|
}
|
|
38521
38722
|
return SILENT_MARKERS.has(trimmed.toUpperCase());
|
|
38522
38723
|
}
|
|
38724
|
+
var TRIVIAL_CONFIRMATIONS = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
38523
38725
|
|
|
38524
38726
|
// answer-stream.ts
|
|
38525
38727
|
var MIN_INITIAL_CHARS = 50;
|
|
@@ -40488,15 +40690,11 @@ function renderOperatorEvent(ev) {
|
|
|
40488
40690
|
text: [
|
|
40489
40691
|
`\uD83D\uDCB3 <b>Credit balance too low</b> for <b>${agent}</b>.`,
|
|
40490
40692
|
detail ? `<i>${detail}</i>` : "",
|
|
40491
|
-
`
|
|
40693
|
+
`Use <code>/auth use <label></code> to switch account slot or <code>/auth add</code> to add one.`
|
|
40492
40694
|
].filter(Boolean).join(`
|
|
40493
40695
|
`),
|
|
40494
40696
|
keyboard: {
|
|
40495
40697
|
inline_keyboard: [
|
|
40496
|
-
[
|
|
40497
|
-
{ text: "\uD83D\uDD04 Swap slot", callback_data: `op:swap-slot:${encodeURIComponent(ev.agent)}` },
|
|
40498
|
-
{ text: "\u2795 Add slot", callback_data: `op:add-slot:${encodeURIComponent(ev.agent)}` }
|
|
40499
|
-
],
|
|
40500
40698
|
[{ text: "\u23f3 Wait", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
40501
40699
|
]
|
|
40502
40700
|
}
|
|
@@ -40506,15 +40704,11 @@ function renderOperatorEvent(ev) {
|
|
|
40506
40704
|
text: [
|
|
40507
40705
|
`\u26a0\ufe0f <b>Quota exhausted</b> for <b>${agent}</b>.`,
|
|
40508
40706
|
detail ? `<i>${detail}</i>` : "",
|
|
40509
|
-
`All account slots are at the usage limit. Switchroom will auto-fallback when another slot is available.`
|
|
40707
|
+
`All account slots are at the usage limit. Switchroom will auto-fallback when another slot is available. Use <code>/auth use <label></code> to switch manually.`
|
|
40510
40708
|
].filter(Boolean).join(`
|
|
40511
40709
|
`),
|
|
40512
40710
|
keyboard: {
|
|
40513
40711
|
inline_keyboard: [
|
|
40514
|
-
[
|
|
40515
|
-
{ text: "\uD83D\uDD04 Swap slot", callback_data: `op:swap-slot:${encodeURIComponent(ev.agent)}` },
|
|
40516
|
-
{ text: "\u2795 Add slot", callback_data: `op:add-slot:${encodeURIComponent(ev.agent)}` }
|
|
40517
|
-
],
|
|
40518
40712
|
[{ text: "\u23f3 Wait", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
40519
40713
|
]
|
|
40520
40714
|
}
|
|
@@ -41812,6 +42006,27 @@ function isSilentFlushMarker2(text) {
|
|
|
41812
42006
|
}
|
|
41813
42007
|
return SILENT_MARKERS2.has(trimmed.toUpperCase());
|
|
41814
42008
|
}
|
|
42009
|
+
var TRIVIAL_CONFIRMATIONS2 = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
42010
|
+
function isTrivialConfirmationLine(line) {
|
|
42011
|
+
let t = line.trim();
|
|
42012
|
+
if (t.length === 0 || t.length > 8)
|
|
42013
|
+
return false;
|
|
42014
|
+
if (/\W$/.test(t))
|
|
42015
|
+
t = t.slice(0, -1);
|
|
42016
|
+
return TRIVIAL_CONFIRMATIONS2.has(t.toUpperCase());
|
|
42017
|
+
}
|
|
42018
|
+
function isCompositeSilentNoise(text) {
|
|
42019
|
+
if (typeof text !== "string")
|
|
42020
|
+
return false;
|
|
42021
|
+
const lines = text.split(`
|
|
42022
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
42023
|
+
if (lines.length === 0)
|
|
42024
|
+
return false;
|
|
42025
|
+
const hasMarker = lines.some((l) => isSilentFlushMarker2(l));
|
|
42026
|
+
if (!hasMarker)
|
|
42027
|
+
return false;
|
|
42028
|
+
return lines.every((l) => isSilentFlushMarker2(l) || isTrivialConfirmationLine(l));
|
|
42029
|
+
}
|
|
41815
42030
|
function decideTurnFlush(input) {
|
|
41816
42031
|
const flushEnabled = input.flushEnabled !== false;
|
|
41817
42032
|
if (!flushEnabled)
|
|
@@ -41826,6 +42041,8 @@ function decideTurnFlush(input) {
|
|
|
41826
42041
|
return { kind: "skip", reason: "empty-text" };
|
|
41827
42042
|
if (isSilentFlushMarker2(joined))
|
|
41828
42043
|
return { kind: "skip", reason: "silent-marker" };
|
|
42044
|
+
if (isCompositeSilentNoise(joined))
|
|
42045
|
+
return { kind: "skip", reason: "silent-marker" };
|
|
41829
42046
|
return { kind: "flush", text: joined };
|
|
41830
42047
|
}
|
|
41831
42048
|
function isTurnFlushSafetyEnabled(env = process.env) {
|
|
@@ -48729,7 +48946,7 @@ function determineRestartReason(opts) {
|
|
|
48729
48946
|
init_boot_card();
|
|
48730
48947
|
|
|
48731
48948
|
// gateway/update-announce.ts
|
|
48732
|
-
import { existsSync as
|
|
48949
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync26 } from "node:fs";
|
|
48733
48950
|
import { join as join24 } from "node:path";
|
|
48734
48951
|
import { homedir as homedir10 } from "node:os";
|
|
48735
48952
|
|
|
@@ -48843,8 +49060,8 @@ function readAndFilter(raw, filters, limit) {
|
|
|
48843
49060
|
var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
|
|
48844
49061
|
function readLastTerminalUpdateAudit(opts = {}) {
|
|
48845
49062
|
const path = opts.auditLogPath ?? defaultAuditLogPath();
|
|
48846
|
-
const exists = opts.exists ??
|
|
48847
|
-
const readFile = opts.readFile ?? ((p) =>
|
|
49063
|
+
const exists = opts.exists ?? existsSync27;
|
|
49064
|
+
const readFile = opts.readFile ?? ((p) => readFileSync26(p, "utf-8"));
|
|
48848
49065
|
if (!exists(path))
|
|
48849
49066
|
return null;
|
|
48850
49067
|
let raw;
|
|
@@ -48908,7 +49125,7 @@ function claimUpdateAnnouncement(requestId, opts = {}) {
|
|
|
48908
49125
|
const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join24(homedir10(), ".switchroom");
|
|
48909
49126
|
const dir = join24(stateDir, "update-announced");
|
|
48910
49127
|
try {
|
|
48911
|
-
|
|
49128
|
+
mkdirSync14(dir, { recursive: true });
|
|
48912
49129
|
} catch {
|
|
48913
49130
|
return false;
|
|
48914
49131
|
}
|
|
@@ -48932,7 +49149,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
|
|
|
48932
49149
|
}
|
|
48933
49150
|
|
|
48934
49151
|
// issues-card.ts
|
|
48935
|
-
import { readFileSync as
|
|
49152
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
|
|
48936
49153
|
var SEVERITY_EMOJI = {
|
|
48937
49154
|
info: "\u2139\ufe0f",
|
|
48938
49155
|
warn: "\u26a0\ufe0f",
|
|
@@ -49024,7 +49241,7 @@ function extractRetryAfterSecs(err) {
|
|
|
49024
49241
|
var COOLDOWN_JITTER_MS = 500;
|
|
49025
49242
|
function readPersistedMessageId(path, log) {
|
|
49026
49243
|
try {
|
|
49027
|
-
const raw =
|
|
49244
|
+
const raw = readFileSync27(path, "utf8");
|
|
49028
49245
|
const parsed = JSON.parse(raw);
|
|
49029
49246
|
const v = parsed.messageId;
|
|
49030
49247
|
if (typeof v === "number" && Number.isInteger(v) && v > 0)
|
|
@@ -49040,7 +49257,7 @@ function readPersistedMessageId(path, log) {
|
|
|
49040
49257
|
}
|
|
49041
49258
|
function writePersistedMessageId(path, messageId, log) {
|
|
49042
49259
|
try {
|
|
49043
|
-
|
|
49260
|
+
writeFileSync16(path, JSON.stringify({ messageId }) + `
|
|
49044
49261
|
`, { mode: 384 });
|
|
49045
49262
|
} catch (err) {
|
|
49046
49263
|
log(`issues-card: persist write failed (${err.message})`);
|
|
@@ -49133,21 +49350,21 @@ function createIssuesCardHandle(opts) {
|
|
|
49133
49350
|
}
|
|
49134
49351
|
|
|
49135
49352
|
// issues-watcher.ts
|
|
49136
|
-
import { existsSync as
|
|
49353
|
+
import { existsSync as existsSync29, statSync as statSync8 } from "node:fs";
|
|
49137
49354
|
import { join as join26 } from "node:path";
|
|
49138
49355
|
|
|
49139
49356
|
// ../src/issues/store.ts
|
|
49140
49357
|
import {
|
|
49141
49358
|
closeSync as closeSync4,
|
|
49142
|
-
existsSync as
|
|
49143
|
-
mkdirSync as
|
|
49359
|
+
existsSync as existsSync28,
|
|
49360
|
+
mkdirSync as mkdirSync15,
|
|
49144
49361
|
openSync as openSync4,
|
|
49145
49362
|
readdirSync as readdirSync5,
|
|
49146
|
-
readFileSync as
|
|
49147
|
-
renameSync as
|
|
49363
|
+
readFileSync as readFileSync28,
|
|
49364
|
+
renameSync as renameSync11,
|
|
49148
49365
|
statSync as statSync7,
|
|
49149
49366
|
unlinkSync as unlinkSync10,
|
|
49150
|
-
writeFileSync as
|
|
49367
|
+
writeFileSync as writeFileSync17,
|
|
49151
49368
|
writeSync
|
|
49152
49369
|
} from "node:fs";
|
|
49153
49370
|
import { join as join25 } from "node:path";
|
|
@@ -49167,11 +49384,11 @@ var ISSUES_FILE = "issues.jsonl";
|
|
|
49167
49384
|
var ISSUES_LOCK = "issues.lock";
|
|
49168
49385
|
function readAll(stateDir) {
|
|
49169
49386
|
const path = join25(stateDir, ISSUES_FILE);
|
|
49170
|
-
if (!
|
|
49387
|
+
if (!existsSync28(path))
|
|
49171
49388
|
return [];
|
|
49172
49389
|
let raw;
|
|
49173
49390
|
try {
|
|
49174
|
-
raw =
|
|
49391
|
+
raw = readFileSync28(path, "utf-8");
|
|
49175
49392
|
} catch {
|
|
49176
49393
|
return [];
|
|
49177
49394
|
}
|
|
@@ -49203,7 +49420,7 @@ function list(stateDir, opts = {}) {
|
|
|
49203
49420
|
});
|
|
49204
49421
|
}
|
|
49205
49422
|
function resolve6(stateDir, fingerprint, nowFn = Date.now) {
|
|
49206
|
-
if (!
|
|
49423
|
+
if (!existsSync28(join25(stateDir, ISSUES_FILE)))
|
|
49207
49424
|
return 0;
|
|
49208
49425
|
return withLock(stateDir, () => {
|
|
49209
49426
|
const all = readAll(stateDir);
|
|
@@ -49227,8 +49444,8 @@ function writeAll(stateDir, events) {
|
|
|
49227
49444
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
49228
49445
|
`) + `
|
|
49229
49446
|
`;
|
|
49230
|
-
|
|
49231
|
-
|
|
49447
|
+
writeFileSync17(tmp, body, "utf-8");
|
|
49448
|
+
renameSync11(tmp, path);
|
|
49232
49449
|
}
|
|
49233
49450
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
49234
49451
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
@@ -49290,7 +49507,7 @@ function withLock(stateDir, fn) {
|
|
|
49290
49507
|
function tryStealStaleLock(lockPath) {
|
|
49291
49508
|
let pidStr;
|
|
49292
49509
|
try {
|
|
49293
|
-
pidStr =
|
|
49510
|
+
pidStr = readFileSync28(lockPath, "utf-8").trim();
|
|
49294
49511
|
} catch {
|
|
49295
49512
|
return true;
|
|
49296
49513
|
}
|
|
@@ -49388,7 +49605,7 @@ function startIssuesWatcher(opts) {
|
|
|
49388
49605
|
};
|
|
49389
49606
|
}
|
|
49390
49607
|
function defaultSignatureProvider(path) {
|
|
49391
|
-
if (!
|
|
49608
|
+
if (!existsSync29(path))
|
|
49392
49609
|
return null;
|
|
49393
49610
|
try {
|
|
49394
49611
|
const stat = statSync8(path);
|
|
@@ -49628,7 +49845,7 @@ function skillBasenameFromPath2(input) {
|
|
|
49628
49845
|
}
|
|
49629
49846
|
|
|
49630
49847
|
// credits-watch.ts
|
|
49631
|
-
import { readFileSync as
|
|
49848
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync16 } from "fs";
|
|
49632
49849
|
import { join as join27 } from "path";
|
|
49633
49850
|
var STATE_FILE = "credits-watch.json";
|
|
49634
49851
|
var FATAL_REASONS = new Set([
|
|
@@ -49642,11 +49859,11 @@ function emptyCreditState() {
|
|
|
49642
49859
|
}
|
|
49643
49860
|
function readClaudeJsonOverage(claudeConfigDir) {
|
|
49644
49861
|
const path = join27(claudeConfigDir, ".claude.json");
|
|
49645
|
-
if (!
|
|
49862
|
+
if (!existsSync30(path))
|
|
49646
49863
|
return null;
|
|
49647
49864
|
let raw;
|
|
49648
49865
|
try {
|
|
49649
|
-
raw =
|
|
49866
|
+
raw = readFileSync29(path, "utf-8");
|
|
49650
49867
|
} catch {
|
|
49651
49868
|
return null;
|
|
49652
49869
|
}
|
|
@@ -49727,10 +49944,10 @@ function escapeHtml10(s) {
|
|
|
49727
49944
|
}
|
|
49728
49945
|
function loadCreditState(stateDir) {
|
|
49729
49946
|
const path = join27(stateDir, STATE_FILE);
|
|
49730
|
-
if (!
|
|
49947
|
+
if (!existsSync30(path))
|
|
49731
49948
|
return emptyCreditState();
|
|
49732
49949
|
try {
|
|
49733
|
-
const raw =
|
|
49950
|
+
const raw = readFileSync29(path, "utf-8");
|
|
49734
49951
|
const parsed = JSON.parse(raw);
|
|
49735
49952
|
if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
|
|
49736
49953
|
return {
|
|
@@ -49742,36 +49959,161 @@ function loadCreditState(stateDir) {
|
|
|
49742
49959
|
return emptyCreditState();
|
|
49743
49960
|
}
|
|
49744
49961
|
function saveCreditState(stateDir, state4) {
|
|
49745
|
-
|
|
49962
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
49746
49963
|
const path = join27(stateDir, STATE_FILE);
|
|
49747
|
-
|
|
49964
|
+
writeFileSync18(path, JSON.stringify(state4, null, 2) + `
|
|
49965
|
+
`, { mode: 384 });
|
|
49966
|
+
}
|
|
49967
|
+
|
|
49968
|
+
// quota-watch.ts
|
|
49969
|
+
import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync31, mkdirSync as mkdirSync17 } from "fs";
|
|
49970
|
+
import { join as join28 } from "path";
|
|
49971
|
+
var STATE_FILE2 = "quota-watch.json";
|
|
49972
|
+
function emptyQuotaWatchState() {
|
|
49973
|
+
return {};
|
|
49974
|
+
}
|
|
49975
|
+
function emptyAccountState() {
|
|
49976
|
+
return { lastNotifiedHealth: null, lastNotifiedAt: 0 };
|
|
49977
|
+
}
|
|
49978
|
+
function evaluateQuotaWatchAccount(args) {
|
|
49979
|
+
const { agentName: agentName3, snap, prev, now } = args;
|
|
49980
|
+
const label = snap.label;
|
|
49981
|
+
const currentHealth = classifyHealth(snap);
|
|
49982
|
+
if (currentHealth === "unknown" || currentHealth === "blocked") {
|
|
49983
|
+
return { kind: "skip", accountLabel: label, reason: `${currentHealth}-not-our-domain` };
|
|
49984
|
+
}
|
|
49985
|
+
const prevHealth = prev.lastNotifiedHealth ?? "healthy";
|
|
49986
|
+
if (currentHealth === prevHealth) {
|
|
49987
|
+
return { kind: "skip", accountLabel: label, reason: "steady-state" };
|
|
49988
|
+
}
|
|
49989
|
+
if (currentHealth === "throttling" && prevHealth === "healthy") {
|
|
49990
|
+
const newState = {
|
|
49991
|
+
lastNotifiedHealth: "throttling",
|
|
49992
|
+
lastNotifiedAt: now
|
|
49993
|
+
};
|
|
49994
|
+
return {
|
|
49995
|
+
kind: "notify",
|
|
49996
|
+
accountLabel: label,
|
|
49997
|
+
message: buildThrottlingMessage(agentName3, snap),
|
|
49998
|
+
newAccountState: newState,
|
|
49999
|
+
transition: "entered-throttling"
|
|
50000
|
+
};
|
|
50001
|
+
}
|
|
50002
|
+
if (currentHealth === "healthy" && prevHealth === "throttling") {
|
|
50003
|
+
const newState = {
|
|
50004
|
+
lastNotifiedHealth: "healthy",
|
|
50005
|
+
lastNotifiedAt: now
|
|
50006
|
+
};
|
|
50007
|
+
return {
|
|
50008
|
+
kind: "notify",
|
|
50009
|
+
accountLabel: label,
|
|
50010
|
+
message: buildRecoveryMessage(agentName3, snap),
|
|
50011
|
+
newAccountState: newState,
|
|
50012
|
+
transition: "recovered-to-healthy"
|
|
50013
|
+
};
|
|
50014
|
+
}
|
|
50015
|
+
return { kind: "skip", accountLabel: label, reason: "no-matching-transition" };
|
|
50016
|
+
}
|
|
50017
|
+
function buildThrottlingMessage(agentName3, snap) {
|
|
50018
|
+
const q = snap.quota;
|
|
50019
|
+
const fiveStr = fmtPct(q.fiveHourUtilizationPct);
|
|
50020
|
+
const sevenStr = fmtPct(q.sevenDayUtilizationPct);
|
|
50021
|
+
const max = Math.max(q.fiveHourUtilizationPct, q.sevenDayUtilizationPct);
|
|
50022
|
+
const win = max === q.fiveHourUtilizationPct ? "5h" : "7d";
|
|
50023
|
+
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
50024
|
+
const resetAt = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
50025
|
+
const resetStr = resetAt ? ` \u00b7 refills in ${formatRelative(resetAt, new Date)}` : "";
|
|
50026
|
+
const activeNote = snap.isActive ? "" : `
|
|
50027
|
+
This is a non-active account. Consider <code>/auth use ${escapeHtml11(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
|
|
50028
|
+
const altNote = snap.isActive ? `
|
|
50029
|
+
Consider <code>/auth use <other-account></code> if you have a healthier account, or wait for the ${winLabel} window to refill${resetStr}.` : "";
|
|
50030
|
+
return [
|
|
50031
|
+
`\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
|
|
50032
|
+
``,
|
|
50033
|
+
`${fiveStr} of 5h \u00b7 ${sevenStr} of 7d`,
|
|
50034
|
+
`Binding window: ${winLabel}${resetStr}`,
|
|
50035
|
+
`${activeNote}${altNote}`,
|
|
50036
|
+
``,
|
|
50037
|
+
`<i>Threshold: ${THROTTLING_THRESHOLD_PCT}% on either window. Source: broker quota cache.</i>`,
|
|
50038
|
+
`<i>Run /auth for full fleet status or /usage for the active account.</i>`
|
|
50039
|
+
].join(`
|
|
50040
|
+
`).replace(/\n\n\n+/g, `
|
|
50041
|
+
|
|
50042
|
+
`).trim();
|
|
50043
|
+
}
|
|
50044
|
+
function buildRecoveryMessage(agentName3, snap) {
|
|
50045
|
+
const q = snap.quota;
|
|
50046
|
+
const utilLine = q ? `Current: ${fmtPct(q.fiveHourUtilizationPct)} of 5h \u00b7 ${fmtPct(q.sevenDayUtilizationPct)} of 7d` : "Current quota data unavailable.";
|
|
50047
|
+
return [
|
|
50048
|
+
`\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
|
|
50049
|
+
``,
|
|
50050
|
+
utilLine,
|
|
50051
|
+
``,
|
|
50052
|
+
`<i>Below ${THROTTLING_THRESHOLD_PCT}% on both windows.</i>`
|
|
50053
|
+
].join(`
|
|
50054
|
+
`);
|
|
50055
|
+
}
|
|
50056
|
+
function escapeHtml11(s) {
|
|
50057
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
50058
|
+
}
|
|
50059
|
+
function loadQuotaWatchState(stateDir) {
|
|
50060
|
+
const path = join28(stateDir, STATE_FILE2);
|
|
50061
|
+
if (!existsSync31(path))
|
|
50062
|
+
return emptyQuotaWatchState();
|
|
50063
|
+
try {
|
|
50064
|
+
const raw = readFileSync30(path, "utf-8");
|
|
50065
|
+
const parsed = JSON.parse(raw);
|
|
50066
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
50067
|
+
return emptyQuotaWatchState();
|
|
50068
|
+
}
|
|
50069
|
+
const result = {};
|
|
50070
|
+
for (const [key, val] of Object.entries(parsed)) {
|
|
50071
|
+
if (val && typeof val === "object" && !Array.isArray(val) && (val.lastNotifiedHealth === null || val.lastNotifiedHealth === "healthy" || val.lastNotifiedHealth === "throttling") && typeof val.lastNotifiedAt === "number" && Number.isFinite(val.lastNotifiedAt)) {
|
|
50072
|
+
result[key] = val;
|
|
50073
|
+
}
|
|
50074
|
+
}
|
|
50075
|
+
return result;
|
|
50076
|
+
} catch {
|
|
50077
|
+
return emptyQuotaWatchState();
|
|
50078
|
+
}
|
|
50079
|
+
}
|
|
50080
|
+
function saveQuotaWatchState(stateDir, state4) {
|
|
50081
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
50082
|
+
const path = join28(stateDir, STATE_FILE2);
|
|
50083
|
+
writeFileSync19(path, JSON.stringify(state4, null, 2) + `
|
|
49748
50084
|
`, { mode: 384 });
|
|
49749
50085
|
}
|
|
50086
|
+
function patchQuotaWatchState(current, accountLabel, accountState) {
|
|
50087
|
+
return { ...current, [accountLabel]: accountState };
|
|
50088
|
+
}
|
|
50089
|
+
|
|
50090
|
+
// gateway/gateway.ts
|
|
50091
|
+
init_auth_snapshot_format();
|
|
49750
50092
|
|
|
49751
50093
|
// gateway/turn-active-marker.ts
|
|
49752
50094
|
import {
|
|
49753
50095
|
closeSync as closeSync5,
|
|
49754
|
-
existsSync as
|
|
49755
|
-
mkdirSync as
|
|
50096
|
+
existsSync as existsSync32,
|
|
50097
|
+
mkdirSync as mkdirSync18,
|
|
49756
50098
|
openSync as openSync5,
|
|
49757
|
-
readFileSync as
|
|
50099
|
+
readFileSync as readFileSync31,
|
|
49758
50100
|
statSync as statSync9,
|
|
49759
50101
|
unlinkSync as unlinkSync11,
|
|
49760
50102
|
utimesSync as utimesSync2,
|
|
49761
|
-
writeFileSync as
|
|
50103
|
+
writeFileSync as writeFileSync20
|
|
49762
50104
|
} from "node:fs";
|
|
49763
|
-
import { join as
|
|
50105
|
+
import { join as join29 } from "node:path";
|
|
49764
50106
|
var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
|
|
49765
50107
|
function writeTurnActiveMarker(stateDir, marker) {
|
|
49766
50108
|
try {
|
|
49767
|
-
|
|
49768
|
-
|
|
50109
|
+
mkdirSync18(stateDir, { recursive: true });
|
|
50110
|
+
writeFileSync20(join29(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
|
|
49769
50111
|
`, { mode: 384 });
|
|
49770
50112
|
} catch {}
|
|
49771
50113
|
}
|
|
49772
50114
|
function touchTurnActiveMarker2(stateDir) {
|
|
49773
|
-
const path =
|
|
49774
|
-
if (!
|
|
50115
|
+
const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
|
|
50116
|
+
if (!existsSync32(path))
|
|
49775
50117
|
return;
|
|
49776
50118
|
const now = new Date;
|
|
49777
50119
|
try {
|
|
@@ -49785,12 +50127,12 @@ function touchTurnActiveMarker2(stateDir) {
|
|
|
49785
50127
|
}
|
|
49786
50128
|
function removeTurnActiveMarker(stateDir) {
|
|
49787
50129
|
try {
|
|
49788
|
-
unlinkSync11(
|
|
50130
|
+
unlinkSync11(join29(stateDir, TURN_ACTIVE_MARKER_FILE2));
|
|
49789
50131
|
} catch {}
|
|
49790
50132
|
}
|
|
49791
50133
|
function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
49792
|
-
const path =
|
|
49793
|
-
if (!
|
|
50134
|
+
const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
|
|
50135
|
+
if (!existsSync32(path))
|
|
49794
50136
|
return false;
|
|
49795
50137
|
const now = opts.now ?? Date.now();
|
|
49796
50138
|
try {
|
|
@@ -49802,7 +50144,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
49802
50144
|
return false;
|
|
49803
50145
|
let payload = null;
|
|
49804
50146
|
try {
|
|
49805
|
-
payload =
|
|
50147
|
+
payload = readFileSync31(path, "utf8");
|
|
49806
50148
|
} catch {}
|
|
49807
50149
|
unlinkSync11(path);
|
|
49808
50150
|
if (opts.onRemove) {
|
|
@@ -49821,10 +50163,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
49821
50163
|
}
|
|
49822
50164
|
|
|
49823
50165
|
// ../src/build-info.ts
|
|
49824
|
-
var VERSION = "0.14.
|
|
49825
|
-
var COMMIT_SHA = "
|
|
49826
|
-
var COMMIT_DATE = "2026-05-
|
|
49827
|
-
var LATEST_PR =
|
|
50166
|
+
var VERSION = "0.14.2";
|
|
50167
|
+
var COMMIT_SHA = "3c7d0238";
|
|
50168
|
+
var COMMIT_DATE = "2026-05-28T07:47:55Z";
|
|
50169
|
+
var LATEST_PR = 1958;
|
|
49828
50170
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
49829
50171
|
|
|
49830
50172
|
// gateway/boot-version.ts
|
|
@@ -49898,11 +50240,11 @@ init_peercred();
|
|
|
49898
50240
|
import * as net4 from "node:net";
|
|
49899
50241
|
import * as fs2 from "node:fs";
|
|
49900
50242
|
import { homedir as homedir11 } from "node:os";
|
|
49901
|
-
import { join as
|
|
50243
|
+
import { join as join30 } from "node:path";
|
|
49902
50244
|
var DEFAULT_TIMEOUT_MS4 = 2000;
|
|
49903
50245
|
var UNLOCK_TIMEOUT_MS = 30000;
|
|
49904
|
-
var LEGACY_SOCKET_PATH2 =
|
|
49905
|
-
var OPERATOR_SOCKET_PATH2 =
|
|
50246
|
+
var LEGACY_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "vault-broker.sock");
|
|
50247
|
+
var OPERATOR_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "broker-operator", "sock");
|
|
49906
50248
|
function defaultBrokerSocketPath2() {
|
|
49907
50249
|
if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
|
|
49908
50250
|
return OPERATOR_SOCKET_PATH2;
|
|
@@ -50124,8 +50466,8 @@ function resolveVaultApprovalPosture(broker) {
|
|
|
50124
50466
|
}
|
|
50125
50467
|
|
|
50126
50468
|
// registry/turns-schema.ts
|
|
50127
|
-
import { chmodSync as chmodSync3, mkdirSync as
|
|
50128
|
-
import { join as
|
|
50469
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync19 } from "fs";
|
|
50470
|
+
import { join as join31 } from "path";
|
|
50129
50471
|
var DatabaseClass2 = null;
|
|
50130
50472
|
function loadDatabaseClass2() {
|
|
50131
50473
|
if (DatabaseClass2 != null)
|
|
@@ -50184,9 +50526,9 @@ function applySchema(db2) {
|
|
|
50184
50526
|
}
|
|
50185
50527
|
function openTurnsDb(agentDir) {
|
|
50186
50528
|
const Database = loadDatabaseClass2();
|
|
50187
|
-
const dir =
|
|
50188
|
-
|
|
50189
|
-
const path =
|
|
50529
|
+
const dir = join31(agentDir, "telegram");
|
|
50530
|
+
mkdirSync19(dir, { recursive: true, mode: 448 });
|
|
50531
|
+
const path = join31(dir, "registry.db");
|
|
50190
50532
|
const db2 = new Database(path, { create: true });
|
|
50191
50533
|
applySchema(db2);
|
|
50192
50534
|
try {
|
|
@@ -50327,11 +50669,11 @@ installGlobalErrorHandlers();
|
|
|
50327
50669
|
process.on("beforeExit", () => {
|
|
50328
50670
|
shutdownAnalytics();
|
|
50329
50671
|
});
|
|
50330
|
-
var STATE_DIR = process.env.TELEGRAM_STATE_DIR ??
|
|
50331
|
-
var ACCESS_FILE =
|
|
50332
|
-
var APPROVED_DIR =
|
|
50333
|
-
var ENV_FILE =
|
|
50334
|
-
var INBOX_DIR =
|
|
50672
|
+
var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join33(homedir12(), ".claude", "channels", "telegram");
|
|
50673
|
+
var ACCESS_FILE = join33(STATE_DIR, "access.json");
|
|
50674
|
+
var APPROVED_DIR = join33(STATE_DIR, "approved");
|
|
50675
|
+
var ENV_FILE = join33(STATE_DIR, ".env");
|
|
50676
|
+
var INBOX_DIR = join33(STATE_DIR, "inbox");
|
|
50335
50677
|
function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
|
|
50336
50678
|
const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
|
|
50337
50679
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
|
|
@@ -50396,7 +50738,7 @@ function formatBootVersion() {
|
|
|
50396
50738
|
}
|
|
50397
50739
|
try {
|
|
50398
50740
|
chmodSync5(ENV_FILE, 384);
|
|
50399
|
-
for (const line of
|
|
50741
|
+
for (const line of readFileSync34(ENV_FILE, "utf8").split(`
|
|
50400
50742
|
`)) {
|
|
50401
50743
|
const m = line.match(/^(\w+)=(.*)$/);
|
|
50402
50744
|
if (m && process.env[m[1]] === undefined)
|
|
@@ -50449,7 +50791,7 @@ installTgPostLogger(bot);
|
|
|
50449
50791
|
var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
|
|
50450
50792
|
var GRAMMY_VERSION = (() => {
|
|
50451
50793
|
try {
|
|
50452
|
-
const raw =
|
|
50794
|
+
const raw = readFileSync34(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
|
|
50453
50795
|
return JSON.parse(raw).version ?? "unknown";
|
|
50454
50796
|
} catch {
|
|
50455
50797
|
return "unknown";
|
|
@@ -50522,7 +50864,7 @@ function assertSendable(f) {
|
|
|
50522
50864
|
} catch {
|
|
50523
50865
|
throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
|
|
50524
50866
|
}
|
|
50525
|
-
const inbox =
|
|
50867
|
+
const inbox = join33(stateReal, "inbox");
|
|
50526
50868
|
if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
|
|
50527
50869
|
throw new Error(`refusing to send channel state: ${f}`);
|
|
50528
50870
|
}
|
|
@@ -50541,7 +50883,7 @@ function assertSendable(f) {
|
|
|
50541
50883
|
}
|
|
50542
50884
|
function readAccessFile() {
|
|
50543
50885
|
try {
|
|
50544
|
-
const raw =
|
|
50886
|
+
const raw = readFileSync34(ACCESS_FILE, "utf8");
|
|
50545
50887
|
const parsed = JSON.parse(raw);
|
|
50546
50888
|
const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
|
|
50547
50889
|
const groups = {};
|
|
@@ -50575,7 +50917,7 @@ function readAccessFile() {
|
|
|
50575
50917
|
if (err.code === "ENOENT")
|
|
50576
50918
|
return defaultAccess();
|
|
50577
50919
|
try {
|
|
50578
|
-
|
|
50920
|
+
renameSync13(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
|
|
50579
50921
|
} catch {}
|
|
50580
50922
|
process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
|
|
50581
50923
|
`);
|
|
@@ -50606,11 +50948,11 @@ function assertAllowedChat(chat_id) {
|
|
|
50606
50948
|
function saveAccess(a) {
|
|
50607
50949
|
if (STATIC)
|
|
50608
50950
|
return;
|
|
50609
|
-
|
|
50951
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
50610
50952
|
const tmp = ACCESS_FILE + ".tmp";
|
|
50611
|
-
|
|
50953
|
+
writeFileSync23(tmp, JSON.stringify(a, null, 2) + `
|
|
50612
50954
|
`, { mode: 384 });
|
|
50613
|
-
|
|
50955
|
+
renameSync13(tmp, ACCESS_FILE);
|
|
50614
50956
|
}
|
|
50615
50957
|
function pruneExpired(a) {
|
|
50616
50958
|
const now = Date.now();
|
|
@@ -50628,7 +50970,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
|
|
|
50628
50970
|
if (HISTORY_ENABLED) {
|
|
50629
50971
|
try {
|
|
50630
50972
|
initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
|
|
50631
|
-
process.stderr.write(`telegram gateway: history capture enabled at ${
|
|
50973
|
+
process.stderr.write(`telegram gateway: history capture enabled at ${join33(STATE_DIR, "history.db")}
|
|
50632
50974
|
`);
|
|
50633
50975
|
} catch (err) {
|
|
50634
50976
|
process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
|
|
@@ -50645,10 +50987,10 @@ try {
|
|
|
50645
50987
|
process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
|
|
50646
50988
|
`);
|
|
50647
50989
|
} else {
|
|
50648
|
-
process.stderr.write(`telegram gateway: turn-registry initialized at ${
|
|
50990
|
+
process.stderr.write(`telegram gateway: turn-registry initialized at ${join33(agentDir, "telegram", "registry.db")}
|
|
50649
50991
|
`);
|
|
50650
50992
|
}
|
|
50651
|
-
const pendingEnvPath =
|
|
50993
|
+
const pendingEnvPath = join33(agentDir, ".pending-turn.env");
|
|
50652
50994
|
try {
|
|
50653
50995
|
const pending2 = findMostRecentInterruptedTurn(turnsDb);
|
|
50654
50996
|
if (pending2 != null) {
|
|
@@ -50662,13 +51004,13 @@ try {
|
|
|
50662
51004
|
`SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
|
|
50663
51005
|
];
|
|
50664
51006
|
const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
|
|
50665
|
-
|
|
51007
|
+
writeFileSync23(pendingEnvTmp, lines.join(`
|
|
50666
51008
|
`) + `
|
|
50667
51009
|
`, { mode: 384 });
|
|
50668
|
-
|
|
51010
|
+
renameSync13(pendingEnvTmp, pendingEnvPath);
|
|
50669
51011
|
process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
|
|
50670
51012
|
`);
|
|
50671
|
-
} else if (
|
|
51013
|
+
} else if (existsSync36(pendingEnvPath)) {
|
|
50672
51014
|
rmSync4(pendingEnvPath, { force: true });
|
|
50673
51015
|
process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
|
|
50674
51016
|
`);
|
|
@@ -50722,7 +51064,7 @@ function checkApprovals() {
|
|
|
50722
51064
|
return;
|
|
50723
51065
|
}
|
|
50724
51066
|
for (const senderId of files) {
|
|
50725
|
-
const file =
|
|
51067
|
+
const file = join33(APPROVED_DIR, senderId);
|
|
50726
51068
|
bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
|
|
50727
51069
|
process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
|
|
50728
51070
|
`);
|
|
@@ -51606,11 +51948,11 @@ var unpinProgressCardForChat = null;
|
|
|
51606
51948
|
var getPinnedProgressCardMessageId = null;
|
|
51607
51949
|
var completeProgressCardTurn = null;
|
|
51608
51950
|
var subagentWatcher = null;
|
|
51609
|
-
var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ??
|
|
51610
|
-
|
|
51611
|
-
var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ??
|
|
51612
|
-
var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ??
|
|
51613
|
-
var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ??
|
|
51951
|
+
var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join33(STATE_DIR, "gateway.sock");
|
|
51952
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
51953
|
+
var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join33(STATE_DIR, "gateway.pid.json");
|
|
51954
|
+
var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join33(STATE_DIR, "gateway-session.json");
|
|
51955
|
+
var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join33(STATE_DIR, "clean-shutdown.json");
|
|
51614
51956
|
var GATEWAY_STARTED_AT_MS = Date.now();
|
|
51615
51957
|
var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
|
|
51616
51958
|
var activeBootCard = null;
|
|
@@ -51639,7 +51981,7 @@ function ensureIssuesCard(chatId, threadId) {
|
|
|
51639
51981
|
bot: botApi,
|
|
51640
51982
|
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
51641
51983
|
`),
|
|
51642
|
-
persistPath:
|
|
51984
|
+
persistPath: join33(stateDir, "issues-card.json")
|
|
51643
51985
|
});
|
|
51644
51986
|
activeIssuesWatcher = startIssuesWatcher({
|
|
51645
51987
|
stateDir,
|
|
@@ -51808,13 +52150,13 @@ startTimer2({
|
|
|
51808
52150
|
}
|
|
51809
52151
|
});
|
|
51810
52152
|
var inboundSpool = STATIC ? undefined : createInboundSpool({
|
|
51811
|
-
path:
|
|
52153
|
+
path: join33(STATE_DIR, "inbound-spool.jsonl"),
|
|
51812
52154
|
fs: {
|
|
51813
52155
|
appendFileSync: (p, d) => appendFileSync3(p, d),
|
|
51814
|
-
readFileSync: (p) =>
|
|
51815
|
-
writeFileSync: (p, d) =>
|
|
51816
|
-
renameSync: (a, b) =>
|
|
51817
|
-
existsSync: (p) =>
|
|
52156
|
+
readFileSync: (p) => readFileSync34(p, "utf8"),
|
|
52157
|
+
writeFileSync: (p, d) => writeFileSync23(p, d),
|
|
52158
|
+
renameSync: (a, b) => renameSync13(a, b),
|
|
52159
|
+
existsSync: (p) => existsSync36(p),
|
|
51818
52160
|
statSizeSync: (p) => statSync13(p).size
|
|
51819
52161
|
}
|
|
51820
52162
|
});
|
|
@@ -51927,11 +52269,12 @@ var ipcServer = createIpcServer({
|
|
|
51927
52269
|
return;
|
|
51928
52270
|
}
|
|
51929
52271
|
})();
|
|
52272
|
+
const resolvedAgentDirForCard = agentDir ?? (process.env.TELEGRAM_STATE_DIR ? __require("path").dirname(process.env.TELEGRAM_STATE_DIR) : "/tmp");
|
|
51930
52273
|
startBootCard(chatId, threadId, botApiForCard, {
|
|
51931
52274
|
agentName: agentDisplayName,
|
|
51932
52275
|
agentSlug,
|
|
51933
52276
|
version: formatBootVersion(),
|
|
51934
|
-
agentDir:
|
|
52277
|
+
agentDir: resolvedAgentDirForCard,
|
|
51935
52278
|
gatewayInfo: { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS },
|
|
51936
52279
|
restartReason: reason,
|
|
51937
52280
|
restartAgeMs: markerAgeMs,
|
|
@@ -51940,6 +52283,7 @@ var ipcServer = createIpcServer({
|
|
|
51940
52283
|
probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
|
|
51941
52284
|
tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
|
|
51942
52285
|
dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
|
|
52286
|
+
configSnapshotPath: join33(resolvedAgentDirForCard, ".config-snapshot.json"),
|
|
51943
52287
|
...updateOutcomeLine ? { updateOutcomeLine } : {}
|
|
51944
52288
|
}, ackMsgId).then((handle) => {
|
|
51945
52289
|
activeBootCard = handle;
|
|
@@ -53304,11 +53648,11 @@ async function executeSendGif(rawArgs) {
|
|
|
53304
53648
|
};
|
|
53305
53649
|
}
|
|
53306
53650
|
async function publishToTelegraph(text, shortName, authorName) {
|
|
53307
|
-
const accountPath =
|
|
53651
|
+
const accountPath = join33(STATE_DIR, "telegraph-account.json");
|
|
53308
53652
|
let account = null;
|
|
53309
53653
|
try {
|
|
53310
|
-
if (
|
|
53311
|
-
const raw =
|
|
53654
|
+
if (existsSync36(accountPath)) {
|
|
53655
|
+
const raw = readFileSync34(accountPath, "utf-8");
|
|
53312
53656
|
const parsed = JSON.parse(raw);
|
|
53313
53657
|
if (parsed.shortName && parsed.accessToken) {
|
|
53314
53658
|
account = parsed;
|
|
@@ -53327,8 +53671,8 @@ async function publishToTelegraph(text, shortName, authorName) {
|
|
|
53327
53671
|
}
|
|
53328
53672
|
account = created.value;
|
|
53329
53673
|
try {
|
|
53330
|
-
|
|
53331
|
-
|
|
53674
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
53675
|
+
writeFileSync23(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
|
|
53332
53676
|
} catch (err) {
|
|
53333
53677
|
process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
|
|
53334
53678
|
`);
|
|
@@ -53570,9 +53914,9 @@ async function executeDownloadAttachment(args) {
|
|
|
53570
53914
|
fileUniqueId: file.file_unique_id,
|
|
53571
53915
|
now: Date.now()
|
|
53572
53916
|
});
|
|
53573
|
-
|
|
53917
|
+
mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
|
|
53574
53918
|
assertInsideInbox(INBOX_DIR, dlPath);
|
|
53575
|
-
|
|
53919
|
+
writeFileSync23(dlPath, buf, { mode: 384 });
|
|
53576
53920
|
return { content: [{ type: "text", text: dlPath }] };
|
|
53577
53921
|
}
|
|
53578
53922
|
async function executeEditMessage(args) {
|
|
@@ -53899,6 +54243,7 @@ function handleSessionEvent(ev) {
|
|
|
53899
54243
|
activityInFlight: null,
|
|
53900
54244
|
activityPendingRender: null,
|
|
53901
54245
|
activityLastSentRender: null,
|
|
54246
|
+
mirrorLines: [],
|
|
53902
54247
|
answerStream: null,
|
|
53903
54248
|
isDm: isDmChatId(ev.chatId)
|
|
53904
54249
|
};
|
|
@@ -53969,7 +54314,7 @@ function handleSessionEvent(ev) {
|
|
|
53969
54314
|
}
|
|
53970
54315
|
}
|
|
53971
54316
|
if (!turn.replyCalled && !isTelegramSurfaceTool(name)) {
|
|
53972
|
-
const rendered = DRAFT_MIRROR_ENABLED ?
|
|
54317
|
+
const rendered = DRAFT_MIRROR_ENABLED ? appendActivityLine(turn.mirrorLines, name, ev.input) : registerAndRender(turn.toolActivity, name);
|
|
53973
54318
|
if (rendered != null) {
|
|
53974
54319
|
turn.activityPendingRender = rendered;
|
|
53975
54320
|
if (turn.activityInFlight == null) {
|
|
@@ -55344,14 +55689,14 @@ function restartMarkerPath() {
|
|
|
55344
55689
|
const agentDir = resolveAgentDirFromEnv();
|
|
55345
55690
|
if (!agentDir)
|
|
55346
55691
|
return null;
|
|
55347
|
-
return
|
|
55692
|
+
return join33(agentDir, "restart-pending.json");
|
|
55348
55693
|
}
|
|
55349
55694
|
function writeRestartMarker(marker) {
|
|
55350
55695
|
const p = restartMarkerPath();
|
|
55351
55696
|
if (!p)
|
|
55352
55697
|
return;
|
|
55353
55698
|
try {
|
|
55354
|
-
|
|
55699
|
+
writeFileSync23(p, JSON.stringify(marker));
|
|
55355
55700
|
lastPlannedRestartAt = Date.now();
|
|
55356
55701
|
process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
|
|
55357
55702
|
`);
|
|
@@ -55370,7 +55715,7 @@ function readRestartMarker() {
|
|
|
55370
55715
|
if (!p)
|
|
55371
55716
|
return null;
|
|
55372
55717
|
try {
|
|
55373
|
-
return JSON.parse(
|
|
55718
|
+
return JSON.parse(readFileSync34(p, "utf8"));
|
|
55374
55719
|
} catch {
|
|
55375
55720
|
return null;
|
|
55376
55721
|
}
|
|
@@ -55468,7 +55813,7 @@ var _dockerReachable;
|
|
|
55468
55813
|
function isDockerReachable() {
|
|
55469
55814
|
if (_dockerReachable !== undefined)
|
|
55470
55815
|
return _dockerReachable;
|
|
55471
|
-
if (!
|
|
55816
|
+
if (!existsSync36("/var/run/docker.sock")) {
|
|
55472
55817
|
_dockerReachable = false;
|
|
55473
55818
|
return _dockerReachable;
|
|
55474
55819
|
}
|
|
@@ -55485,12 +55830,12 @@ function _resetDockerReachableCache() {
|
|
|
55485
55830
|
}
|
|
55486
55831
|
function spawnSwitchroomDetached(args, onFailure) {
|
|
55487
55832
|
const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
|
|
55488
|
-
const logPath =
|
|
55833
|
+
const logPath = join33(STATE_DIR, "detached-spawn.log");
|
|
55489
55834
|
let outFd = null;
|
|
55490
55835
|
try {
|
|
55491
|
-
|
|
55836
|
+
mkdirSync23(STATE_DIR, { recursive: true });
|
|
55492
55837
|
outFd = openSync8(logPath, "a");
|
|
55493
|
-
|
|
55838
|
+
writeFileSync23(logPath, `
|
|
55494
55839
|
[${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
|
|
55495
55840
|
`, { flag: "a" });
|
|
55496
55841
|
} catch {}
|
|
@@ -55516,7 +55861,7 @@ function spawnSwitchroomDetached(args, onFailure) {
|
|
|
55516
55861
|
return;
|
|
55517
55862
|
let tail = "";
|
|
55518
55863
|
try {
|
|
55519
|
-
const full =
|
|
55864
|
+
const full = readFileSync34(logPath, "utf8");
|
|
55520
55865
|
tail = full.split(`
|
|
55521
55866
|
`).slice(-30).join(`
|
|
55522
55867
|
`).trim();
|
|
@@ -55858,10 +56203,10 @@ bot.use(async (ctx, next) => {
|
|
|
55858
56203
|
});
|
|
55859
56204
|
function readRecentDenialsForAgent(agentName3, windowMs, limit) {
|
|
55860
56205
|
try {
|
|
55861
|
-
const auditPath =
|
|
55862
|
-
if (!
|
|
56206
|
+
const auditPath = join33(homedir12(), ".switchroom", "vault-audit.log");
|
|
56207
|
+
if (!existsSync36(auditPath))
|
|
55863
56208
|
return [];
|
|
55864
|
-
const raw =
|
|
56209
|
+
const raw = readFileSync34(auditPath, "utf8");
|
|
55865
56210
|
return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
|
|
55866
56211
|
} catch {
|
|
55867
56212
|
return [];
|
|
@@ -55912,7 +56257,7 @@ async function buildAgentMetadata(agentName3) {
|
|
|
55912
56257
|
try {
|
|
55913
56258
|
const agentDir = resolveAgentDirFromEnv();
|
|
55914
56259
|
if (agentDir) {
|
|
55915
|
-
const raw =
|
|
56260
|
+
const raw = readFileSync34(join33(agentDir, ".claude", ".claude.json"), "utf8");
|
|
55916
56261
|
claudeJson = JSON.parse(raw);
|
|
55917
56262
|
}
|
|
55918
56263
|
} catch {}
|
|
@@ -56126,9 +56471,9 @@ bot.command("restart", async (ctx) => {
|
|
|
56126
56471
|
function flushAgentHandoff(agentDir) {
|
|
56127
56472
|
let removed = 0;
|
|
56128
56473
|
for (const fname of [".handoff.md", ".handoff-topic"]) {
|
|
56129
|
-
const p =
|
|
56474
|
+
const p = join33(agentDir, fname);
|
|
56130
56475
|
try {
|
|
56131
|
-
if (
|
|
56476
|
+
if (existsSync36(p)) {
|
|
56132
56477
|
unlinkSync14(p);
|
|
56133
56478
|
removed++;
|
|
56134
56479
|
}
|
|
@@ -56184,7 +56529,7 @@ async function handleNewOrResetCommand(ctx, kind) {
|
|
|
56184
56529
|
writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
|
|
56185
56530
|
if (agentDir != null) {
|
|
56186
56531
|
try {
|
|
56187
|
-
|
|
56532
|
+
writeFileSync23(join33(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
|
|
56188
56533
|
`, "utf8");
|
|
56189
56534
|
} catch (err) {
|
|
56190
56535
|
process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
|
|
@@ -56543,16 +56888,16 @@ bot.command("interrupt", async (ctx) => {
|
|
|
56543
56888
|
await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
|
|
56544
56889
|
});
|
|
56545
56890
|
var lockoutOps = {
|
|
56546
|
-
readFileSync: (p, enc) =>
|
|
56547
|
-
writeFileSync: (p, data, opts) =>
|
|
56548
|
-
existsSync: (p) =>
|
|
56549
|
-
mkdirSync: (p, opts) =>
|
|
56550
|
-
joinPath: (...parts) =>
|
|
56891
|
+
readFileSync: (p, enc) => readFileSync34(p, enc),
|
|
56892
|
+
writeFileSync: (p, data, opts) => writeFileSync23(p, data, opts),
|
|
56893
|
+
existsSync: (p) => existsSync36(p),
|
|
56894
|
+
mkdirSync: (p, opts) => mkdirSync23(p, opts),
|
|
56895
|
+
joinPath: (...parts) => join33(...parts)
|
|
56551
56896
|
};
|
|
56552
56897
|
var FLEET_FALLBACK_DEDUP_MS = 30000;
|
|
56553
56898
|
function isAuthBrokerSocketReachable() {
|
|
56554
56899
|
try {
|
|
56555
|
-
return
|
|
56900
|
+
return existsSync36(resolveAuthBrokerSocketPath2());
|
|
56556
56901
|
} catch {
|
|
56557
56902
|
return false;
|
|
56558
56903
|
}
|
|
@@ -56613,7 +56958,7 @@ async function runCreditWatch() {
|
|
|
56613
56958
|
if (!agentDir)
|
|
56614
56959
|
return;
|
|
56615
56960
|
const agentName3 = getMyAgentName();
|
|
56616
|
-
const claudeConfigDir =
|
|
56961
|
+
const claudeConfigDir = join33(agentDir, ".claude");
|
|
56617
56962
|
const stateDir = STATE_DIR;
|
|
56618
56963
|
const reason = readClaudeJsonOverage(claudeConfigDir);
|
|
56619
56964
|
const prev = loadCreditState(stateDir);
|
|
@@ -56640,6 +56985,99 @@ async function runCreditWatch() {
|
|
|
56640
56985
|
`);
|
|
56641
56986
|
}
|
|
56642
56987
|
}
|
|
56988
|
+
async function runQuotaWatch() {
|
|
56989
|
+
const agentName3 = getMyAgentName();
|
|
56990
|
+
const stateDir = STATE_DIR;
|
|
56991
|
+
const brokerClient = await getAuthBrokerClient(agentName3);
|
|
56992
|
+
if (!brokerClient) {
|
|
56993
|
+
process.stderr.write(`telegram gateway: quota-watch: broker client unavailable \u2014 skipping
|
|
56994
|
+
`);
|
|
56995
|
+
return;
|
|
56996
|
+
}
|
|
56997
|
+
let listStateData;
|
|
56998
|
+
try {
|
|
56999
|
+
listStateData = await brokerClient.listState();
|
|
57000
|
+
} catch (err) {
|
|
57001
|
+
process.stderr.write(`telegram gateway: quota-watch: listState failed: ${err}
|
|
57002
|
+
`);
|
|
57003
|
+
return;
|
|
57004
|
+
}
|
|
57005
|
+
if (!listStateData.accounts || listStateData.accounts.length === 0) {
|
|
57006
|
+
return;
|
|
57007
|
+
}
|
|
57008
|
+
const snapshots = buildSnapshotsFromCachedState(listStateData);
|
|
57009
|
+
let watchState = loadQuotaWatchState(stateDir);
|
|
57010
|
+
const now = Date.now();
|
|
57011
|
+
const access = loadAccess();
|
|
57012
|
+
const pendingTransitions = [];
|
|
57013
|
+
const labelToSnapIndex = new Map(snapshots.map((s, i) => [s.label, i]));
|
|
57014
|
+
for (const snap of snapshots) {
|
|
57015
|
+
const prev = watchState[snap.label] ?? emptyAccountState();
|
|
57016
|
+
const decision = evaluateQuotaWatchAccount({ agentName: agentName3, snap, prev, now });
|
|
57017
|
+
if (decision.kind !== "skip") {
|
|
57018
|
+
pendingTransitions.push({
|
|
57019
|
+
accountLabel: snap.label,
|
|
57020
|
+
snapIndex: labelToSnapIndex.get(snap.label) ?? -1,
|
|
57021
|
+
decision
|
|
57022
|
+
});
|
|
57023
|
+
}
|
|
57024
|
+
}
|
|
57025
|
+
if (pendingTransitions.length === 0) {
|
|
57026
|
+
return;
|
|
57027
|
+
}
|
|
57028
|
+
const crossingLabels = pendingTransitions.map((t) => t.accountLabel);
|
|
57029
|
+
let freshProbeMap = new Map;
|
|
57030
|
+
try {
|
|
57031
|
+
const probeData = await brokerClient.probeQuota(crossingLabels, 8000);
|
|
57032
|
+
for (const entry of probeData.results) {
|
|
57033
|
+
freshProbeMap.set(entry.label, entry.result);
|
|
57034
|
+
}
|
|
57035
|
+
} catch (err) {
|
|
57036
|
+
process.stderr.write(`telegram gateway: quota-watch: probe for crossing accounts failed: ${err}
|
|
57037
|
+
`);
|
|
57038
|
+
}
|
|
57039
|
+
let mutatedState = watchState;
|
|
57040
|
+
const notifications = [];
|
|
57041
|
+
for (const { accountLabel, snapIndex, decision } of pendingTransitions) {
|
|
57042
|
+
const freshResult = freshProbeMap.get(accountLabel);
|
|
57043
|
+
let enrichedDecision = decision;
|
|
57044
|
+
if (decision.kind !== "notify")
|
|
57045
|
+
continue;
|
|
57046
|
+
if (freshResult && freshResult.ok && snapIndex >= 0) {
|
|
57047
|
+
const enrichedSnap = { ...snapshots[snapIndex], quota: freshResult.data };
|
|
57048
|
+
const prev = watchState[accountLabel] ?? emptyAccountState();
|
|
57049
|
+
const re = evaluateQuotaWatchAccount({ agentName: agentName3, snap: enrichedSnap, prev, now });
|
|
57050
|
+
if (re.kind === "notify" && re.transition === decision.transition) {
|
|
57051
|
+
enrichedDecision = re;
|
|
57052
|
+
} else if (re.kind === "skip") {
|
|
57053
|
+
continue;
|
|
57054
|
+
}
|
|
57055
|
+
}
|
|
57056
|
+
if (enrichedDecision.kind !== "notify")
|
|
57057
|
+
continue;
|
|
57058
|
+
notifications.push({ message: enrichedDecision.message, accountLabel });
|
|
57059
|
+
mutatedState = patchQuotaWatchState(mutatedState, accountLabel, enrichedDecision.newAccountState);
|
|
57060
|
+
}
|
|
57061
|
+
if (notifications.length === 0) {
|
|
57062
|
+
return;
|
|
57063
|
+
}
|
|
57064
|
+
for (const { message, accountLabel } of notifications) {
|
|
57065
|
+
for (const chat_id of access.allowFrom) {
|
|
57066
|
+
await swallowingApiCall(() => bot.api.sendMessage(chat_id, message, {
|
|
57067
|
+
parse_mode: "HTML",
|
|
57068
|
+
link_preview_options: { is_disabled: true }
|
|
57069
|
+
}), { chat_id, verb: "quota-watch.notify" });
|
|
57070
|
+
}
|
|
57071
|
+
process.stderr.write(`telegram gateway: quota-watch: notified transition for account=${accountLabel}
|
|
57072
|
+
`);
|
|
57073
|
+
}
|
|
57074
|
+
try {
|
|
57075
|
+
saveQuotaWatchState(stateDir, mutatedState);
|
|
57076
|
+
} catch (err) {
|
|
57077
|
+
process.stderr.write(`telegram gateway: quota-watch state persist failed: ${err}
|
|
57078
|
+
`);
|
|
57079
|
+
}
|
|
57080
|
+
}
|
|
56643
57081
|
bot.command("auth", async (ctx) => {
|
|
56644
57082
|
const authSenderId = String(ctx.from?.id ?? "");
|
|
56645
57083
|
const authOperatorPrivate = ctx.chat?.type === "private" && loadAccess().allowFrom.includes(authSenderId);
|
|
@@ -56823,10 +57261,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
|
|
|
56823
57261
|
return;
|
|
56824
57262
|
}
|
|
56825
57263
|
const { token, id } = result;
|
|
56826
|
-
const tokenPath =
|
|
57264
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", agentName3, ".vault-token");
|
|
56827
57265
|
try {
|
|
56828
|
-
|
|
56829
|
-
|
|
57266
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", agentName3), { recursive: true });
|
|
57267
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
56830
57268
|
} catch (err) {
|
|
56831
57269
|
await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
|
|
56832
57270
|
<i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
|
|
@@ -56902,10 +57340,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
|
|
|
56902
57340
|
return;
|
|
56903
57341
|
}
|
|
56904
57342
|
const { token, id } = result;
|
|
56905
|
-
const tokenPath =
|
|
57343
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
|
|
56906
57344
|
try {
|
|
56907
|
-
|
|
56908
|
-
|
|
57345
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", pending2.agent), { recursive: true });
|
|
57346
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
56909
57347
|
} catch (err) {
|
|
56910
57348
|
await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
|
|
56911
57349
|
<i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
|
|
@@ -57380,10 +57818,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
|
|
|
57380
57818
|
return;
|
|
57381
57819
|
}
|
|
57382
57820
|
const { token, id } = result;
|
|
57383
|
-
const tokenPath =
|
|
57821
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", state4.agent, ".vault-token");
|
|
57384
57822
|
try {
|
|
57385
|
-
|
|
57386
|
-
|
|
57823
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", state4.agent), { recursive: true });
|
|
57824
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
57387
57825
|
} catch (err) {
|
|
57388
57826
|
await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
|
|
57389
57827
|
return;
|
|
@@ -57731,15 +58169,6 @@ async function handleOperatorEventCallback(ctx, data) {
|
|
|
57731
58169
|
}
|
|
57732
58170
|
return;
|
|
57733
58171
|
}
|
|
57734
|
-
case "swap-slot":
|
|
57735
|
-
case "add-slot": {
|
|
57736
|
-
await ctx.answerCallbackQuery({ text: "Phase 4c will wire this" }).catch(() => {});
|
|
57737
|
-
const cmd = action === "swap-slot" ? `auth use ${agent} <slot-name>` : `auth add ${agent}`;
|
|
57738
|
-
await ctx.reply(`Phase 4c will wire ${action} buttons. Until then, run in terminal: <code>switchroom ${cmd}</code>`, {
|
|
57739
|
-
parse_mode: "HTML"
|
|
57740
|
-
});
|
|
57741
|
-
return;
|
|
57742
|
-
}
|
|
57743
58172
|
default: {
|
|
57744
58173
|
await ctx.answerCallbackQuery({ text: `Unknown action: ${action}` }).catch(() => {});
|
|
57745
58174
|
return;
|
|
@@ -57823,8 +58252,8 @@ async function handleAuthDashboardCallback(ctx) {
|
|
|
57823
58252
|
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
57824
58253
|
});
|
|
57825
58254
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
57826
|
-
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState:
|
|
57827
|
-
const snapshots =
|
|
58255
|
+
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
58256
|
+
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
57828
58257
|
const text = renderAuthSnapshotFormat23(snapshots, {
|
|
57829
58258
|
tz,
|
|
57830
58259
|
now: new Date,
|
|
@@ -58219,9 +58648,9 @@ bot.command("usage", async (ctx) => {
|
|
|
58219
58648
|
const hit = probeResp.results.find((r) => r.label === a.label);
|
|
58220
58649
|
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
58221
58650
|
});
|
|
58222
|
-
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState:
|
|
58651
|
+
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
58223
58652
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
58224
|
-
const snapshots =
|
|
58653
|
+
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
58225
58654
|
const text = renderAuthSnapshotFormat23(snapshots, {
|
|
58226
58655
|
tz,
|
|
58227
58656
|
now: new Date,
|
|
@@ -58240,7 +58669,7 @@ bot.command("usage", async (ctx) => {
|
|
|
58240
58669
|
await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
|
|
58241
58670
|
return;
|
|
58242
58671
|
}
|
|
58243
|
-
const result = await fetchQuota2({ claudeConfigDir:
|
|
58672
|
+
const result = await fetchQuota2({ claudeConfigDir: join33(agentDir, ".claude") });
|
|
58244
58673
|
if (!result.ok) {
|
|
58245
58674
|
await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
|
|
58246
58675
|
return;
|
|
@@ -58743,9 +59172,9 @@ bot.on("message:photo", async (ctx) => {
|
|
|
58743
59172
|
fileUniqueId: best.file_unique_id,
|
|
58744
59173
|
now: Date.now()
|
|
58745
59174
|
});
|
|
58746
|
-
|
|
59175
|
+
mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
|
|
58747
59176
|
assertInsideInbox(INBOX_DIR, dlPath);
|
|
58748
|
-
|
|
59177
|
+
writeFileSync23(dlPath, buf, { mode: 384 });
|
|
58749
59178
|
return dlPath;
|
|
58750
59179
|
} catch (err) {
|
|
58751
59180
|
const msg = err instanceof Error ? err.message : "unknown error";
|
|
@@ -58785,8 +59214,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
|
|
|
58785
59214
|
let apiKey = null;
|
|
58786
59215
|
try {
|
|
58787
59216
|
const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
|
|
58788
|
-
if (
|
|
58789
|
-
apiKey =
|
|
59217
|
+
if (existsSync36(path)) {
|
|
59218
|
+
apiKey = readFileSync34(path, "utf-8").trim();
|
|
58790
59219
|
}
|
|
58791
59220
|
} catch (err) {
|
|
58792
59221
|
process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
|
|
@@ -59642,11 +60071,12 @@ var didOneTimeSetup = false;
|
|
|
59642
60071
|
return;
|
|
59643
60072
|
}
|
|
59644
60073
|
})();
|
|
60074
|
+
const resolvedAgentDirForBootCard = agentDir ?? join33(homedir12(), ".switchroom", "agents", agentSlug);
|
|
59645
60075
|
const handle = await startBootCard(chatId, threadId, botApiForCard, {
|
|
59646
60076
|
agentName: agentDisplayName,
|
|
59647
60077
|
agentSlug,
|
|
59648
60078
|
version: formatBootVersion(),
|
|
59649
|
-
agentDir:
|
|
60079
|
+
agentDir: resolvedAgentDirForBootCard,
|
|
59650
60080
|
gatewayInfo: { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS },
|
|
59651
60081
|
restartReason: reason,
|
|
59652
60082
|
restartAgeMs: markerAgeMs,
|
|
@@ -59655,6 +60085,7 @@ var didOneTimeSetup = false;
|
|
|
59655
60085
|
probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
|
|
59656
60086
|
tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
|
|
59657
60087
|
dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
|
|
60088
|
+
configSnapshotPath: join33(resolvedAgentDirForBootCard, ".config-snapshot.json"),
|
|
59658
60089
|
...updateOutcomeLine ? { updateOutcomeLine } : {}
|
|
59659
60090
|
}, ackMsgId);
|
|
59660
60091
|
activeBootCard = handle;
|
|
@@ -59694,6 +60125,21 @@ var didOneTimeSetup = false;
|
|
|
59694
60125
|
});
|
|
59695
60126
|
}, CREDIT_WATCH_POLL_MS).unref();
|
|
59696
60127
|
}
|
|
60128
|
+
const QUOTA_WATCH_POLL_MS = Number(process.env.SWITCHROOM_QUOTA_WATCH_POLL_MS ?? 900000);
|
|
60129
|
+
if (QUOTA_WATCH_POLL_MS > 0) {
|
|
60130
|
+
setTimeout(() => {
|
|
60131
|
+
runQuotaWatch().catch((err) => {
|
|
60132
|
+
process.stderr.write(`telegram gateway: quota-watch initial run failed: ${err}
|
|
60133
|
+
`);
|
|
60134
|
+
});
|
|
60135
|
+
}, 30000);
|
|
60136
|
+
setInterval(() => {
|
|
60137
|
+
runQuotaWatch().catch((err) => {
|
|
60138
|
+
process.stderr.write(`telegram gateway: quota-watch scheduled run failed: ${err}
|
|
60139
|
+
`);
|
|
60140
|
+
});
|
|
60141
|
+
}, QUOTA_WATCH_POLL_MS).unref();
|
|
60142
|
+
}
|
|
59697
60143
|
const RESTART_WATCHDOG_POLL_MS = Number(process.env.SWITCHROOM_RESTART_WATCHDOG_POLL_MS ?? 30000);
|
|
59698
60144
|
const watchdogAgentName = process.env.SWITCHROOM_AGENT_NAME;
|
|
59699
60145
|
const watchdogDockerMode = process.env.SWITCHROOM_RUNTIME === "docker";
|