switchroom 0.14.0 → 0.14.1
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 +967 -542
- package/telegram-plugin/gateway/boot-card.ts +100 -0
- package/telegram-plugin/gateway/config-snapshot.ts +274 -0
- package/telegram-plugin/gateway/gateway.ts +221 -14
- 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/turn-flush-safety.test.ts +48 -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
29652
|
import {
|
|
29159
|
-
readFileSync as
|
|
29160
|
-
writeFileSync as
|
|
29161
|
-
existsSync as
|
|
29162
|
-
renameSync as
|
|
29163
|
-
mkdirSync as
|
|
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) {
|
|
@@ -29998,367 +30492,54 @@ function registerApprovalsCommands(bot, opts) {
|
|
|
29998
30492
|
await ctx.reply("Approval kernel unreachable (vault broker not running?).");
|
|
29999
30493
|
return;
|
|
30000
30494
|
}
|
|
30001
|
-
if (decisions.length === 0) {
|
|
30002
|
-
await ctx.reply(agentFilter ? `No active approvals for <code>${
|
|
30003
|
-
return;
|
|
30004
|
-
}
|
|
30005
|
-
const byAgent = new Map;
|
|
30006
|
-
for (const d of decisions)
|
|
30007
|
-
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(`
|
|
30253
|
-
`);
|
|
30254
|
-
}
|
|
30255
|
-
lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml13(input.oldLabel)}</b>`);
|
|
30256
|
-
lines.push("");
|
|
30257
|
-
lines.push(`<code>${escapeHtml13(input.oldLabel)}</code> \u2192 <code>${escapeHtml13(input.newLabel)}</code>`);
|
|
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(`
|
|
30495
|
+
if (decisions.length === 0) {
|
|
30496
|
+
await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml14(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
|
|
30497
|
+
return;
|
|
30498
|
+
}
|
|
30499
|
+
const byAgent = new Map;
|
|
30500
|
+
for (const d of decisions)
|
|
30501
|
+
byAgent.set(d.agent_unit, (byAgent.get(d.agent_unit) ?? 0) + 1);
|
|
30502
|
+
const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml14(a)}</b>: ${n}`).join(`
|
|
30276
30503
|
`);
|
|
30277
|
-
|
|
30278
|
-
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
}
|
|
30289
|
-
|
|
30290
|
-
|
|
30291
|
-
|
|
30292
|
-
|
|
30293
|
-
|
|
30294
|
-
|
|
30295
|
-
|
|
30296
|
-
|
|
30297
|
-
|
|
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}`
|
|
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(`
|
|
30508
|
+
`);
|
|
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";
|
|
@@ -38520,6 +38701,7 @@ function isSilentFlushMarker(text) {
|
|
|
38520
38701
|
}
|
|
38521
38702
|
return SILENT_MARKERS.has(trimmed.toUpperCase());
|
|
38522
38703
|
}
|
|
38704
|
+
var TRIVIAL_CONFIRMATIONS = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
38523
38705
|
|
|
38524
38706
|
// answer-stream.ts
|
|
38525
38707
|
var MIN_INITIAL_CHARS = 50;
|
|
@@ -40488,15 +40670,11 @@ function renderOperatorEvent(ev) {
|
|
|
40488
40670
|
text: [
|
|
40489
40671
|
`\uD83D\uDCB3 <b>Credit balance too low</b> for <b>${agent}</b>.`,
|
|
40490
40672
|
detail ? `<i>${detail}</i>` : "",
|
|
40491
|
-
`
|
|
40673
|
+
`Use <code>/auth use <label></code> to switch account slot or <code>/auth add</code> to add one.`
|
|
40492
40674
|
].filter(Boolean).join(`
|
|
40493
40675
|
`),
|
|
40494
40676
|
keyboard: {
|
|
40495
40677
|
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
40678
|
[{ text: "\u23f3 Wait", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
40501
40679
|
]
|
|
40502
40680
|
}
|
|
@@ -40506,15 +40684,11 @@ function renderOperatorEvent(ev) {
|
|
|
40506
40684
|
text: [
|
|
40507
40685
|
`\u26a0\ufe0f <b>Quota exhausted</b> for <b>${agent}</b>.`,
|
|
40508
40686
|
detail ? `<i>${detail}</i>` : "",
|
|
40509
|
-
`All account slots are at the usage limit. Switchroom will auto-fallback when another slot is available.`
|
|
40687
|
+
`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
40688
|
].filter(Boolean).join(`
|
|
40511
40689
|
`),
|
|
40512
40690
|
keyboard: {
|
|
40513
40691
|
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
40692
|
[{ text: "\u23f3 Wait", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
40519
40693
|
]
|
|
40520
40694
|
}
|
|
@@ -41812,6 +41986,27 @@ function isSilentFlushMarker2(text) {
|
|
|
41812
41986
|
}
|
|
41813
41987
|
return SILENT_MARKERS2.has(trimmed.toUpperCase());
|
|
41814
41988
|
}
|
|
41989
|
+
var TRIVIAL_CONFIRMATIONS2 = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
41990
|
+
function isTrivialConfirmationLine(line) {
|
|
41991
|
+
let t = line.trim();
|
|
41992
|
+
if (t.length === 0 || t.length > 8)
|
|
41993
|
+
return false;
|
|
41994
|
+
if (/\W$/.test(t))
|
|
41995
|
+
t = t.slice(0, -1);
|
|
41996
|
+
return TRIVIAL_CONFIRMATIONS2.has(t.toUpperCase());
|
|
41997
|
+
}
|
|
41998
|
+
function isCompositeSilentNoise(text) {
|
|
41999
|
+
if (typeof text !== "string")
|
|
42000
|
+
return false;
|
|
42001
|
+
const lines = text.split(`
|
|
42002
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
42003
|
+
if (lines.length === 0)
|
|
42004
|
+
return false;
|
|
42005
|
+
const hasMarker = lines.some((l) => isSilentFlushMarker2(l));
|
|
42006
|
+
if (!hasMarker)
|
|
42007
|
+
return false;
|
|
42008
|
+
return lines.every((l) => isSilentFlushMarker2(l) || isTrivialConfirmationLine(l));
|
|
42009
|
+
}
|
|
41815
42010
|
function decideTurnFlush(input) {
|
|
41816
42011
|
const flushEnabled = input.flushEnabled !== false;
|
|
41817
42012
|
if (!flushEnabled)
|
|
@@ -41826,6 +42021,8 @@ function decideTurnFlush(input) {
|
|
|
41826
42021
|
return { kind: "skip", reason: "empty-text" };
|
|
41827
42022
|
if (isSilentFlushMarker2(joined))
|
|
41828
42023
|
return { kind: "skip", reason: "silent-marker" };
|
|
42024
|
+
if (isCompositeSilentNoise(joined))
|
|
42025
|
+
return { kind: "skip", reason: "silent-marker" };
|
|
41829
42026
|
return { kind: "flush", text: joined };
|
|
41830
42027
|
}
|
|
41831
42028
|
function isTurnFlushSafetyEnabled(env = process.env) {
|
|
@@ -48729,7 +48926,7 @@ function determineRestartReason(opts) {
|
|
|
48729
48926
|
init_boot_card();
|
|
48730
48927
|
|
|
48731
48928
|
// gateway/update-announce.ts
|
|
48732
|
-
import { existsSync as
|
|
48929
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync26 } from "node:fs";
|
|
48733
48930
|
import { join as join24 } from "node:path";
|
|
48734
48931
|
import { homedir as homedir10 } from "node:os";
|
|
48735
48932
|
|
|
@@ -48843,8 +49040,8 @@ function readAndFilter(raw, filters, limit) {
|
|
|
48843
49040
|
var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
|
|
48844
49041
|
function readLastTerminalUpdateAudit(opts = {}) {
|
|
48845
49042
|
const path = opts.auditLogPath ?? defaultAuditLogPath();
|
|
48846
|
-
const exists = opts.exists ??
|
|
48847
|
-
const readFile = opts.readFile ?? ((p) =>
|
|
49043
|
+
const exists = opts.exists ?? existsSync27;
|
|
49044
|
+
const readFile = opts.readFile ?? ((p) => readFileSync26(p, "utf-8"));
|
|
48848
49045
|
if (!exists(path))
|
|
48849
49046
|
return null;
|
|
48850
49047
|
let raw;
|
|
@@ -48908,7 +49105,7 @@ function claimUpdateAnnouncement(requestId, opts = {}) {
|
|
|
48908
49105
|
const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join24(homedir10(), ".switchroom");
|
|
48909
49106
|
const dir = join24(stateDir, "update-announced");
|
|
48910
49107
|
try {
|
|
48911
|
-
|
|
49108
|
+
mkdirSync14(dir, { recursive: true });
|
|
48912
49109
|
} catch {
|
|
48913
49110
|
return false;
|
|
48914
49111
|
}
|
|
@@ -48932,7 +49129,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
|
|
|
48932
49129
|
}
|
|
48933
49130
|
|
|
48934
49131
|
// issues-card.ts
|
|
48935
|
-
import { readFileSync as
|
|
49132
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
|
|
48936
49133
|
var SEVERITY_EMOJI = {
|
|
48937
49134
|
info: "\u2139\ufe0f",
|
|
48938
49135
|
warn: "\u26a0\ufe0f",
|
|
@@ -49024,7 +49221,7 @@ function extractRetryAfterSecs(err) {
|
|
|
49024
49221
|
var COOLDOWN_JITTER_MS = 500;
|
|
49025
49222
|
function readPersistedMessageId(path, log) {
|
|
49026
49223
|
try {
|
|
49027
|
-
const raw =
|
|
49224
|
+
const raw = readFileSync27(path, "utf8");
|
|
49028
49225
|
const parsed = JSON.parse(raw);
|
|
49029
49226
|
const v = parsed.messageId;
|
|
49030
49227
|
if (typeof v === "number" && Number.isInteger(v) && v > 0)
|
|
@@ -49040,7 +49237,7 @@ function readPersistedMessageId(path, log) {
|
|
|
49040
49237
|
}
|
|
49041
49238
|
function writePersistedMessageId(path, messageId, log) {
|
|
49042
49239
|
try {
|
|
49043
|
-
|
|
49240
|
+
writeFileSync16(path, JSON.stringify({ messageId }) + `
|
|
49044
49241
|
`, { mode: 384 });
|
|
49045
49242
|
} catch (err) {
|
|
49046
49243
|
log(`issues-card: persist write failed (${err.message})`);
|
|
@@ -49133,21 +49330,21 @@ function createIssuesCardHandle(opts) {
|
|
|
49133
49330
|
}
|
|
49134
49331
|
|
|
49135
49332
|
// issues-watcher.ts
|
|
49136
|
-
import { existsSync as
|
|
49333
|
+
import { existsSync as existsSync29, statSync as statSync8 } from "node:fs";
|
|
49137
49334
|
import { join as join26 } from "node:path";
|
|
49138
49335
|
|
|
49139
49336
|
// ../src/issues/store.ts
|
|
49140
49337
|
import {
|
|
49141
49338
|
closeSync as closeSync4,
|
|
49142
|
-
existsSync as
|
|
49143
|
-
mkdirSync as
|
|
49339
|
+
existsSync as existsSync28,
|
|
49340
|
+
mkdirSync as mkdirSync15,
|
|
49144
49341
|
openSync as openSync4,
|
|
49145
49342
|
readdirSync as readdirSync5,
|
|
49146
|
-
readFileSync as
|
|
49147
|
-
renameSync as
|
|
49343
|
+
readFileSync as readFileSync28,
|
|
49344
|
+
renameSync as renameSync11,
|
|
49148
49345
|
statSync as statSync7,
|
|
49149
49346
|
unlinkSync as unlinkSync10,
|
|
49150
|
-
writeFileSync as
|
|
49347
|
+
writeFileSync as writeFileSync17,
|
|
49151
49348
|
writeSync
|
|
49152
49349
|
} from "node:fs";
|
|
49153
49350
|
import { join as join25 } from "node:path";
|
|
@@ -49167,11 +49364,11 @@ var ISSUES_FILE = "issues.jsonl";
|
|
|
49167
49364
|
var ISSUES_LOCK = "issues.lock";
|
|
49168
49365
|
function readAll(stateDir) {
|
|
49169
49366
|
const path = join25(stateDir, ISSUES_FILE);
|
|
49170
|
-
if (!
|
|
49367
|
+
if (!existsSync28(path))
|
|
49171
49368
|
return [];
|
|
49172
49369
|
let raw;
|
|
49173
49370
|
try {
|
|
49174
|
-
raw =
|
|
49371
|
+
raw = readFileSync28(path, "utf-8");
|
|
49175
49372
|
} catch {
|
|
49176
49373
|
return [];
|
|
49177
49374
|
}
|
|
@@ -49203,7 +49400,7 @@ function list(stateDir, opts = {}) {
|
|
|
49203
49400
|
});
|
|
49204
49401
|
}
|
|
49205
49402
|
function resolve6(stateDir, fingerprint, nowFn = Date.now) {
|
|
49206
|
-
if (!
|
|
49403
|
+
if (!existsSync28(join25(stateDir, ISSUES_FILE)))
|
|
49207
49404
|
return 0;
|
|
49208
49405
|
return withLock(stateDir, () => {
|
|
49209
49406
|
const all = readAll(stateDir);
|
|
@@ -49227,8 +49424,8 @@ function writeAll(stateDir, events) {
|
|
|
49227
49424
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
49228
49425
|
`) + `
|
|
49229
49426
|
`;
|
|
49230
|
-
|
|
49231
|
-
|
|
49427
|
+
writeFileSync17(tmp, body, "utf-8");
|
|
49428
|
+
renameSync11(tmp, path);
|
|
49232
49429
|
}
|
|
49233
49430
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
49234
49431
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
@@ -49290,7 +49487,7 @@ function withLock(stateDir, fn) {
|
|
|
49290
49487
|
function tryStealStaleLock(lockPath) {
|
|
49291
49488
|
let pidStr;
|
|
49292
49489
|
try {
|
|
49293
|
-
pidStr =
|
|
49490
|
+
pidStr = readFileSync28(lockPath, "utf-8").trim();
|
|
49294
49491
|
} catch {
|
|
49295
49492
|
return true;
|
|
49296
49493
|
}
|
|
@@ -49388,7 +49585,7 @@ function startIssuesWatcher(opts) {
|
|
|
49388
49585
|
};
|
|
49389
49586
|
}
|
|
49390
49587
|
function defaultSignatureProvider(path) {
|
|
49391
|
-
if (!
|
|
49588
|
+
if (!existsSync29(path))
|
|
49392
49589
|
return null;
|
|
49393
49590
|
try {
|
|
49394
49591
|
const stat = statSync8(path);
|
|
@@ -49628,7 +49825,7 @@ function skillBasenameFromPath2(input) {
|
|
|
49628
49825
|
}
|
|
49629
49826
|
|
|
49630
49827
|
// credits-watch.ts
|
|
49631
|
-
import { readFileSync as
|
|
49828
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync16 } from "fs";
|
|
49632
49829
|
import { join as join27 } from "path";
|
|
49633
49830
|
var STATE_FILE = "credits-watch.json";
|
|
49634
49831
|
var FATAL_REASONS = new Set([
|
|
@@ -49642,11 +49839,11 @@ function emptyCreditState() {
|
|
|
49642
49839
|
}
|
|
49643
49840
|
function readClaudeJsonOverage(claudeConfigDir) {
|
|
49644
49841
|
const path = join27(claudeConfigDir, ".claude.json");
|
|
49645
|
-
if (!
|
|
49842
|
+
if (!existsSync30(path))
|
|
49646
49843
|
return null;
|
|
49647
49844
|
let raw;
|
|
49648
49845
|
try {
|
|
49649
|
-
raw =
|
|
49846
|
+
raw = readFileSync29(path, "utf-8");
|
|
49650
49847
|
} catch {
|
|
49651
49848
|
return null;
|
|
49652
49849
|
}
|
|
@@ -49727,10 +49924,10 @@ function escapeHtml10(s) {
|
|
|
49727
49924
|
}
|
|
49728
49925
|
function loadCreditState(stateDir) {
|
|
49729
49926
|
const path = join27(stateDir, STATE_FILE);
|
|
49730
|
-
if (!
|
|
49927
|
+
if (!existsSync30(path))
|
|
49731
49928
|
return emptyCreditState();
|
|
49732
49929
|
try {
|
|
49733
|
-
const raw =
|
|
49930
|
+
const raw = readFileSync29(path, "utf-8");
|
|
49734
49931
|
const parsed = JSON.parse(raw);
|
|
49735
49932
|
if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
|
|
49736
49933
|
return {
|
|
@@ -49742,36 +49939,161 @@ function loadCreditState(stateDir) {
|
|
|
49742
49939
|
return emptyCreditState();
|
|
49743
49940
|
}
|
|
49744
49941
|
function saveCreditState(stateDir, state4) {
|
|
49745
|
-
|
|
49942
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
49746
49943
|
const path = join27(stateDir, STATE_FILE);
|
|
49747
|
-
|
|
49944
|
+
writeFileSync18(path, JSON.stringify(state4, null, 2) + `
|
|
49945
|
+
`, { mode: 384 });
|
|
49946
|
+
}
|
|
49947
|
+
|
|
49948
|
+
// quota-watch.ts
|
|
49949
|
+
import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync31, mkdirSync as mkdirSync17 } from "fs";
|
|
49950
|
+
import { join as join28 } from "path";
|
|
49951
|
+
var STATE_FILE2 = "quota-watch.json";
|
|
49952
|
+
function emptyQuotaWatchState() {
|
|
49953
|
+
return {};
|
|
49954
|
+
}
|
|
49955
|
+
function emptyAccountState() {
|
|
49956
|
+
return { lastNotifiedHealth: null, lastNotifiedAt: 0 };
|
|
49957
|
+
}
|
|
49958
|
+
function evaluateQuotaWatchAccount(args) {
|
|
49959
|
+
const { agentName: agentName3, snap, prev, now } = args;
|
|
49960
|
+
const label = snap.label;
|
|
49961
|
+
const currentHealth = classifyHealth(snap);
|
|
49962
|
+
if (currentHealth === "unknown" || currentHealth === "blocked") {
|
|
49963
|
+
return { kind: "skip", accountLabel: label, reason: `${currentHealth}-not-our-domain` };
|
|
49964
|
+
}
|
|
49965
|
+
const prevHealth = prev.lastNotifiedHealth ?? "healthy";
|
|
49966
|
+
if (currentHealth === prevHealth) {
|
|
49967
|
+
return { kind: "skip", accountLabel: label, reason: "steady-state" };
|
|
49968
|
+
}
|
|
49969
|
+
if (currentHealth === "throttling" && prevHealth === "healthy") {
|
|
49970
|
+
const newState = {
|
|
49971
|
+
lastNotifiedHealth: "throttling",
|
|
49972
|
+
lastNotifiedAt: now
|
|
49973
|
+
};
|
|
49974
|
+
return {
|
|
49975
|
+
kind: "notify",
|
|
49976
|
+
accountLabel: label,
|
|
49977
|
+
message: buildThrottlingMessage(agentName3, snap),
|
|
49978
|
+
newAccountState: newState,
|
|
49979
|
+
transition: "entered-throttling"
|
|
49980
|
+
};
|
|
49981
|
+
}
|
|
49982
|
+
if (currentHealth === "healthy" && prevHealth === "throttling") {
|
|
49983
|
+
const newState = {
|
|
49984
|
+
lastNotifiedHealth: "healthy",
|
|
49985
|
+
lastNotifiedAt: now
|
|
49986
|
+
};
|
|
49987
|
+
return {
|
|
49988
|
+
kind: "notify",
|
|
49989
|
+
accountLabel: label,
|
|
49990
|
+
message: buildRecoveryMessage(agentName3, snap),
|
|
49991
|
+
newAccountState: newState,
|
|
49992
|
+
transition: "recovered-to-healthy"
|
|
49993
|
+
};
|
|
49994
|
+
}
|
|
49995
|
+
return { kind: "skip", accountLabel: label, reason: "no-matching-transition" };
|
|
49996
|
+
}
|
|
49997
|
+
function buildThrottlingMessage(agentName3, snap) {
|
|
49998
|
+
const q = snap.quota;
|
|
49999
|
+
const fiveStr = fmtPct(q.fiveHourUtilizationPct);
|
|
50000
|
+
const sevenStr = fmtPct(q.sevenDayUtilizationPct);
|
|
50001
|
+
const max = Math.max(q.fiveHourUtilizationPct, q.sevenDayUtilizationPct);
|
|
50002
|
+
const win = max === q.fiveHourUtilizationPct ? "5h" : "7d";
|
|
50003
|
+
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
50004
|
+
const resetAt = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
50005
|
+
const resetStr = resetAt ? ` \u00b7 refills in ${formatRelative(resetAt, new Date)}` : "";
|
|
50006
|
+
const activeNote = snap.isActive ? "" : `
|
|
50007
|
+
This is a non-active account. Consider <code>/auth use ${escapeHtml11(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
|
|
50008
|
+
const altNote = snap.isActive ? `
|
|
50009
|
+
Consider <code>/auth use <other-account></code> if you have a healthier account, or wait for the ${winLabel} window to refill${resetStr}.` : "";
|
|
50010
|
+
return [
|
|
50011
|
+
`\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
|
|
50012
|
+
``,
|
|
50013
|
+
`${fiveStr} of 5h \u00b7 ${sevenStr} of 7d`,
|
|
50014
|
+
`Binding window: ${winLabel}${resetStr}`,
|
|
50015
|
+
`${activeNote}${altNote}`,
|
|
50016
|
+
``,
|
|
50017
|
+
`<i>Threshold: ${THROTTLING_THRESHOLD_PCT}% on either window. Source: broker quota cache.</i>`,
|
|
50018
|
+
`<i>Run /auth for full fleet status or /usage for the active account.</i>`
|
|
50019
|
+
].join(`
|
|
50020
|
+
`).replace(/\n\n\n+/g, `
|
|
50021
|
+
|
|
50022
|
+
`).trim();
|
|
50023
|
+
}
|
|
50024
|
+
function buildRecoveryMessage(agentName3, snap) {
|
|
50025
|
+
const q = snap.quota;
|
|
50026
|
+
const utilLine = q ? `Current: ${fmtPct(q.fiveHourUtilizationPct)} of 5h \u00b7 ${fmtPct(q.sevenDayUtilizationPct)} of 7d` : "Current quota data unavailable.";
|
|
50027
|
+
return [
|
|
50028
|
+
`\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
|
|
50029
|
+
``,
|
|
50030
|
+
utilLine,
|
|
50031
|
+
``,
|
|
50032
|
+
`<i>Below ${THROTTLING_THRESHOLD_PCT}% on both windows.</i>`
|
|
50033
|
+
].join(`
|
|
50034
|
+
`);
|
|
50035
|
+
}
|
|
50036
|
+
function escapeHtml11(s) {
|
|
50037
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
50038
|
+
}
|
|
50039
|
+
function loadQuotaWatchState(stateDir) {
|
|
50040
|
+
const path = join28(stateDir, STATE_FILE2);
|
|
50041
|
+
if (!existsSync31(path))
|
|
50042
|
+
return emptyQuotaWatchState();
|
|
50043
|
+
try {
|
|
50044
|
+
const raw = readFileSync30(path, "utf-8");
|
|
50045
|
+
const parsed = JSON.parse(raw);
|
|
50046
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
50047
|
+
return emptyQuotaWatchState();
|
|
50048
|
+
}
|
|
50049
|
+
const result = {};
|
|
50050
|
+
for (const [key, val] of Object.entries(parsed)) {
|
|
50051
|
+
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)) {
|
|
50052
|
+
result[key] = val;
|
|
50053
|
+
}
|
|
50054
|
+
}
|
|
50055
|
+
return result;
|
|
50056
|
+
} catch {
|
|
50057
|
+
return emptyQuotaWatchState();
|
|
50058
|
+
}
|
|
50059
|
+
}
|
|
50060
|
+
function saveQuotaWatchState(stateDir, state4) {
|
|
50061
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
50062
|
+
const path = join28(stateDir, STATE_FILE2);
|
|
50063
|
+
writeFileSync19(path, JSON.stringify(state4, null, 2) + `
|
|
49748
50064
|
`, { mode: 384 });
|
|
49749
50065
|
}
|
|
50066
|
+
function patchQuotaWatchState(current, accountLabel, accountState) {
|
|
50067
|
+
return { ...current, [accountLabel]: accountState };
|
|
50068
|
+
}
|
|
50069
|
+
|
|
50070
|
+
// gateway/gateway.ts
|
|
50071
|
+
init_auth_snapshot_format();
|
|
49750
50072
|
|
|
49751
50073
|
// gateway/turn-active-marker.ts
|
|
49752
50074
|
import {
|
|
49753
50075
|
closeSync as closeSync5,
|
|
49754
|
-
existsSync as
|
|
49755
|
-
mkdirSync as
|
|
50076
|
+
existsSync as existsSync32,
|
|
50077
|
+
mkdirSync as mkdirSync18,
|
|
49756
50078
|
openSync as openSync5,
|
|
49757
|
-
readFileSync as
|
|
50079
|
+
readFileSync as readFileSync31,
|
|
49758
50080
|
statSync as statSync9,
|
|
49759
50081
|
unlinkSync as unlinkSync11,
|
|
49760
50082
|
utimesSync as utimesSync2,
|
|
49761
|
-
writeFileSync as
|
|
50083
|
+
writeFileSync as writeFileSync20
|
|
49762
50084
|
} from "node:fs";
|
|
49763
|
-
import { join as
|
|
50085
|
+
import { join as join29 } from "node:path";
|
|
49764
50086
|
var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
|
|
49765
50087
|
function writeTurnActiveMarker(stateDir, marker) {
|
|
49766
50088
|
try {
|
|
49767
|
-
|
|
49768
|
-
|
|
50089
|
+
mkdirSync18(stateDir, { recursive: true });
|
|
50090
|
+
writeFileSync20(join29(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
|
|
49769
50091
|
`, { mode: 384 });
|
|
49770
50092
|
} catch {}
|
|
49771
50093
|
}
|
|
49772
50094
|
function touchTurnActiveMarker2(stateDir) {
|
|
49773
|
-
const path =
|
|
49774
|
-
if (!
|
|
50095
|
+
const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
|
|
50096
|
+
if (!existsSync32(path))
|
|
49775
50097
|
return;
|
|
49776
50098
|
const now = new Date;
|
|
49777
50099
|
try {
|
|
@@ -49785,12 +50107,12 @@ function touchTurnActiveMarker2(stateDir) {
|
|
|
49785
50107
|
}
|
|
49786
50108
|
function removeTurnActiveMarker(stateDir) {
|
|
49787
50109
|
try {
|
|
49788
|
-
unlinkSync11(
|
|
50110
|
+
unlinkSync11(join29(stateDir, TURN_ACTIVE_MARKER_FILE2));
|
|
49789
50111
|
} catch {}
|
|
49790
50112
|
}
|
|
49791
50113
|
function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
49792
|
-
const path =
|
|
49793
|
-
if (!
|
|
50114
|
+
const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
|
|
50115
|
+
if (!existsSync32(path))
|
|
49794
50116
|
return false;
|
|
49795
50117
|
const now = opts.now ?? Date.now();
|
|
49796
50118
|
try {
|
|
@@ -49802,7 +50124,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
49802
50124
|
return false;
|
|
49803
50125
|
let payload = null;
|
|
49804
50126
|
try {
|
|
49805
|
-
payload =
|
|
50127
|
+
payload = readFileSync31(path, "utf8");
|
|
49806
50128
|
} catch {}
|
|
49807
50129
|
unlinkSync11(path);
|
|
49808
50130
|
if (opts.onRemove) {
|
|
@@ -49821,10 +50143,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
49821
50143
|
}
|
|
49822
50144
|
|
|
49823
50145
|
// ../src/build-info.ts
|
|
49824
|
-
var VERSION = "0.14.
|
|
49825
|
-
var COMMIT_SHA = "
|
|
49826
|
-
var COMMIT_DATE = "2026-05-
|
|
49827
|
-
var LATEST_PR =
|
|
50146
|
+
var VERSION = "0.14.1";
|
|
50147
|
+
var COMMIT_SHA = "e51a8794";
|
|
50148
|
+
var COMMIT_DATE = "2026-05-28T07:24:12Z";
|
|
50149
|
+
var LATEST_PR = 1956;
|
|
49828
50150
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
49829
50151
|
|
|
49830
50152
|
// gateway/boot-version.ts
|
|
@@ -49898,11 +50220,11 @@ init_peercred();
|
|
|
49898
50220
|
import * as net4 from "node:net";
|
|
49899
50221
|
import * as fs2 from "node:fs";
|
|
49900
50222
|
import { homedir as homedir11 } from "node:os";
|
|
49901
|
-
import { join as
|
|
50223
|
+
import { join as join30 } from "node:path";
|
|
49902
50224
|
var DEFAULT_TIMEOUT_MS4 = 2000;
|
|
49903
50225
|
var UNLOCK_TIMEOUT_MS = 30000;
|
|
49904
|
-
var LEGACY_SOCKET_PATH2 =
|
|
49905
|
-
var OPERATOR_SOCKET_PATH2 =
|
|
50226
|
+
var LEGACY_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "vault-broker.sock");
|
|
50227
|
+
var OPERATOR_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "broker-operator", "sock");
|
|
49906
50228
|
function defaultBrokerSocketPath2() {
|
|
49907
50229
|
if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
|
|
49908
50230
|
return OPERATOR_SOCKET_PATH2;
|
|
@@ -50124,8 +50446,8 @@ function resolveVaultApprovalPosture(broker) {
|
|
|
50124
50446
|
}
|
|
50125
50447
|
|
|
50126
50448
|
// registry/turns-schema.ts
|
|
50127
|
-
import { chmodSync as chmodSync3, mkdirSync as
|
|
50128
|
-
import { join as
|
|
50449
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync19 } from "fs";
|
|
50450
|
+
import { join as join31 } from "path";
|
|
50129
50451
|
var DatabaseClass2 = null;
|
|
50130
50452
|
function loadDatabaseClass2() {
|
|
50131
50453
|
if (DatabaseClass2 != null)
|
|
@@ -50184,9 +50506,9 @@ function applySchema(db2) {
|
|
|
50184
50506
|
}
|
|
50185
50507
|
function openTurnsDb(agentDir) {
|
|
50186
50508
|
const Database = loadDatabaseClass2();
|
|
50187
|
-
const dir =
|
|
50188
|
-
|
|
50189
|
-
const path =
|
|
50509
|
+
const dir = join31(agentDir, "telegram");
|
|
50510
|
+
mkdirSync19(dir, { recursive: true, mode: 448 });
|
|
50511
|
+
const path = join31(dir, "registry.db");
|
|
50190
50512
|
const db2 = new Database(path, { create: true });
|
|
50191
50513
|
applySchema(db2);
|
|
50192
50514
|
try {
|
|
@@ -50327,11 +50649,11 @@ installGlobalErrorHandlers();
|
|
|
50327
50649
|
process.on("beforeExit", () => {
|
|
50328
50650
|
shutdownAnalytics();
|
|
50329
50651
|
});
|
|
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 =
|
|
50652
|
+
var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join33(homedir12(), ".claude", "channels", "telegram");
|
|
50653
|
+
var ACCESS_FILE = join33(STATE_DIR, "access.json");
|
|
50654
|
+
var APPROVED_DIR = join33(STATE_DIR, "approved");
|
|
50655
|
+
var ENV_FILE = join33(STATE_DIR, ".env");
|
|
50656
|
+
var INBOX_DIR = join33(STATE_DIR, "inbox");
|
|
50335
50657
|
function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
|
|
50336
50658
|
const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
|
|
50337
50659
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
|
|
@@ -50396,7 +50718,7 @@ function formatBootVersion() {
|
|
|
50396
50718
|
}
|
|
50397
50719
|
try {
|
|
50398
50720
|
chmodSync5(ENV_FILE, 384);
|
|
50399
|
-
for (const line of
|
|
50721
|
+
for (const line of readFileSync34(ENV_FILE, "utf8").split(`
|
|
50400
50722
|
`)) {
|
|
50401
50723
|
const m = line.match(/^(\w+)=(.*)$/);
|
|
50402
50724
|
if (m && process.env[m[1]] === undefined)
|
|
@@ -50449,7 +50771,7 @@ installTgPostLogger(bot);
|
|
|
50449
50771
|
var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
|
|
50450
50772
|
var GRAMMY_VERSION = (() => {
|
|
50451
50773
|
try {
|
|
50452
|
-
const raw =
|
|
50774
|
+
const raw = readFileSync34(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
|
|
50453
50775
|
return JSON.parse(raw).version ?? "unknown";
|
|
50454
50776
|
} catch {
|
|
50455
50777
|
return "unknown";
|
|
@@ -50522,7 +50844,7 @@ function assertSendable(f) {
|
|
|
50522
50844
|
} catch {
|
|
50523
50845
|
throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
|
|
50524
50846
|
}
|
|
50525
|
-
const inbox =
|
|
50847
|
+
const inbox = join33(stateReal, "inbox");
|
|
50526
50848
|
if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
|
|
50527
50849
|
throw new Error(`refusing to send channel state: ${f}`);
|
|
50528
50850
|
}
|
|
@@ -50541,7 +50863,7 @@ function assertSendable(f) {
|
|
|
50541
50863
|
}
|
|
50542
50864
|
function readAccessFile() {
|
|
50543
50865
|
try {
|
|
50544
|
-
const raw =
|
|
50866
|
+
const raw = readFileSync34(ACCESS_FILE, "utf8");
|
|
50545
50867
|
const parsed = JSON.parse(raw);
|
|
50546
50868
|
const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
|
|
50547
50869
|
const groups = {};
|
|
@@ -50575,7 +50897,7 @@ function readAccessFile() {
|
|
|
50575
50897
|
if (err.code === "ENOENT")
|
|
50576
50898
|
return defaultAccess();
|
|
50577
50899
|
try {
|
|
50578
|
-
|
|
50900
|
+
renameSync13(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
|
|
50579
50901
|
} catch {}
|
|
50580
50902
|
process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
|
|
50581
50903
|
`);
|
|
@@ -50606,11 +50928,11 @@ function assertAllowedChat(chat_id) {
|
|
|
50606
50928
|
function saveAccess(a) {
|
|
50607
50929
|
if (STATIC)
|
|
50608
50930
|
return;
|
|
50609
|
-
|
|
50931
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
50610
50932
|
const tmp = ACCESS_FILE + ".tmp";
|
|
50611
|
-
|
|
50933
|
+
writeFileSync23(tmp, JSON.stringify(a, null, 2) + `
|
|
50612
50934
|
`, { mode: 384 });
|
|
50613
|
-
|
|
50935
|
+
renameSync13(tmp, ACCESS_FILE);
|
|
50614
50936
|
}
|
|
50615
50937
|
function pruneExpired(a) {
|
|
50616
50938
|
const now = Date.now();
|
|
@@ -50628,7 +50950,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
|
|
|
50628
50950
|
if (HISTORY_ENABLED) {
|
|
50629
50951
|
try {
|
|
50630
50952
|
initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
|
|
50631
|
-
process.stderr.write(`telegram gateway: history capture enabled at ${
|
|
50953
|
+
process.stderr.write(`telegram gateway: history capture enabled at ${join33(STATE_DIR, "history.db")}
|
|
50632
50954
|
`);
|
|
50633
50955
|
} catch (err) {
|
|
50634
50956
|
process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
|
|
@@ -50645,10 +50967,10 @@ try {
|
|
|
50645
50967
|
process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
|
|
50646
50968
|
`);
|
|
50647
50969
|
} else {
|
|
50648
|
-
process.stderr.write(`telegram gateway: turn-registry initialized at ${
|
|
50970
|
+
process.stderr.write(`telegram gateway: turn-registry initialized at ${join33(agentDir, "telegram", "registry.db")}
|
|
50649
50971
|
`);
|
|
50650
50972
|
}
|
|
50651
|
-
const pendingEnvPath =
|
|
50973
|
+
const pendingEnvPath = join33(agentDir, ".pending-turn.env");
|
|
50652
50974
|
try {
|
|
50653
50975
|
const pending2 = findMostRecentInterruptedTurn(turnsDb);
|
|
50654
50976
|
if (pending2 != null) {
|
|
@@ -50662,13 +50984,13 @@ try {
|
|
|
50662
50984
|
`SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
|
|
50663
50985
|
];
|
|
50664
50986
|
const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
|
|
50665
|
-
|
|
50987
|
+
writeFileSync23(pendingEnvTmp, lines.join(`
|
|
50666
50988
|
`) + `
|
|
50667
50989
|
`, { mode: 384 });
|
|
50668
|
-
|
|
50990
|
+
renameSync13(pendingEnvTmp, pendingEnvPath);
|
|
50669
50991
|
process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
|
|
50670
50992
|
`);
|
|
50671
|
-
} else if (
|
|
50993
|
+
} else if (existsSync36(pendingEnvPath)) {
|
|
50672
50994
|
rmSync4(pendingEnvPath, { force: true });
|
|
50673
50995
|
process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
|
|
50674
50996
|
`);
|
|
@@ -50722,7 +51044,7 @@ function checkApprovals() {
|
|
|
50722
51044
|
return;
|
|
50723
51045
|
}
|
|
50724
51046
|
for (const senderId of files) {
|
|
50725
|
-
const file =
|
|
51047
|
+
const file = join33(APPROVED_DIR, senderId);
|
|
50726
51048
|
bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
|
|
50727
51049
|
process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
|
|
50728
51050
|
`);
|
|
@@ -51606,11 +51928,11 @@ var unpinProgressCardForChat = null;
|
|
|
51606
51928
|
var getPinnedProgressCardMessageId = null;
|
|
51607
51929
|
var completeProgressCardTurn = null;
|
|
51608
51930
|
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 ??
|
|
51931
|
+
var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join33(STATE_DIR, "gateway.sock");
|
|
51932
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
51933
|
+
var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join33(STATE_DIR, "gateway.pid.json");
|
|
51934
|
+
var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join33(STATE_DIR, "gateway-session.json");
|
|
51935
|
+
var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join33(STATE_DIR, "clean-shutdown.json");
|
|
51614
51936
|
var GATEWAY_STARTED_AT_MS = Date.now();
|
|
51615
51937
|
var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
|
|
51616
51938
|
var activeBootCard = null;
|
|
@@ -51639,7 +51961,7 @@ function ensureIssuesCard(chatId, threadId) {
|
|
|
51639
51961
|
bot: botApi,
|
|
51640
51962
|
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
51641
51963
|
`),
|
|
51642
|
-
persistPath:
|
|
51964
|
+
persistPath: join33(stateDir, "issues-card.json")
|
|
51643
51965
|
});
|
|
51644
51966
|
activeIssuesWatcher = startIssuesWatcher({
|
|
51645
51967
|
stateDir,
|
|
@@ -51808,13 +52130,13 @@ startTimer2({
|
|
|
51808
52130
|
}
|
|
51809
52131
|
});
|
|
51810
52132
|
var inboundSpool = STATIC ? undefined : createInboundSpool({
|
|
51811
|
-
path:
|
|
52133
|
+
path: join33(STATE_DIR, "inbound-spool.jsonl"),
|
|
51812
52134
|
fs: {
|
|
51813
52135
|
appendFileSync: (p, d) => appendFileSync3(p, d),
|
|
51814
|
-
readFileSync: (p) =>
|
|
51815
|
-
writeFileSync: (p, d) =>
|
|
51816
|
-
renameSync: (a, b) =>
|
|
51817
|
-
existsSync: (p) =>
|
|
52136
|
+
readFileSync: (p) => readFileSync34(p, "utf8"),
|
|
52137
|
+
writeFileSync: (p, d) => writeFileSync23(p, d),
|
|
52138
|
+
renameSync: (a, b) => renameSync13(a, b),
|
|
52139
|
+
existsSync: (p) => existsSync36(p),
|
|
51818
52140
|
statSizeSync: (p) => statSync13(p).size
|
|
51819
52141
|
}
|
|
51820
52142
|
});
|
|
@@ -51927,11 +52249,12 @@ var ipcServer = createIpcServer({
|
|
|
51927
52249
|
return;
|
|
51928
52250
|
}
|
|
51929
52251
|
})();
|
|
52252
|
+
const resolvedAgentDirForCard = agentDir ?? (process.env.TELEGRAM_STATE_DIR ? __require("path").dirname(process.env.TELEGRAM_STATE_DIR) : "/tmp");
|
|
51930
52253
|
startBootCard(chatId, threadId, botApiForCard, {
|
|
51931
52254
|
agentName: agentDisplayName,
|
|
51932
52255
|
agentSlug,
|
|
51933
52256
|
version: formatBootVersion(),
|
|
51934
|
-
agentDir:
|
|
52257
|
+
agentDir: resolvedAgentDirForCard,
|
|
51935
52258
|
gatewayInfo: { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS },
|
|
51936
52259
|
restartReason: reason,
|
|
51937
52260
|
restartAgeMs: markerAgeMs,
|
|
@@ -51940,6 +52263,7 @@ var ipcServer = createIpcServer({
|
|
|
51940
52263
|
probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
|
|
51941
52264
|
tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
|
|
51942
52265
|
dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
|
|
52266
|
+
configSnapshotPath: join33(resolvedAgentDirForCard, ".config-snapshot.json"),
|
|
51943
52267
|
...updateOutcomeLine ? { updateOutcomeLine } : {}
|
|
51944
52268
|
}, ackMsgId).then((handle) => {
|
|
51945
52269
|
activeBootCard = handle;
|
|
@@ -53304,11 +53628,11 @@ async function executeSendGif(rawArgs) {
|
|
|
53304
53628
|
};
|
|
53305
53629
|
}
|
|
53306
53630
|
async function publishToTelegraph(text, shortName, authorName) {
|
|
53307
|
-
const accountPath =
|
|
53631
|
+
const accountPath = join33(STATE_DIR, "telegraph-account.json");
|
|
53308
53632
|
let account = null;
|
|
53309
53633
|
try {
|
|
53310
|
-
if (
|
|
53311
|
-
const raw =
|
|
53634
|
+
if (existsSync36(accountPath)) {
|
|
53635
|
+
const raw = readFileSync34(accountPath, "utf-8");
|
|
53312
53636
|
const parsed = JSON.parse(raw);
|
|
53313
53637
|
if (parsed.shortName && parsed.accessToken) {
|
|
53314
53638
|
account = parsed;
|
|
@@ -53327,8 +53651,8 @@ async function publishToTelegraph(text, shortName, authorName) {
|
|
|
53327
53651
|
}
|
|
53328
53652
|
account = created.value;
|
|
53329
53653
|
try {
|
|
53330
|
-
|
|
53331
|
-
|
|
53654
|
+
mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
|
|
53655
|
+
writeFileSync23(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
|
|
53332
53656
|
} catch (err) {
|
|
53333
53657
|
process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
|
|
53334
53658
|
`);
|
|
@@ -53570,9 +53894,9 @@ async function executeDownloadAttachment(args) {
|
|
|
53570
53894
|
fileUniqueId: file.file_unique_id,
|
|
53571
53895
|
now: Date.now()
|
|
53572
53896
|
});
|
|
53573
|
-
|
|
53897
|
+
mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
|
|
53574
53898
|
assertInsideInbox(INBOX_DIR, dlPath);
|
|
53575
|
-
|
|
53899
|
+
writeFileSync23(dlPath, buf, { mode: 384 });
|
|
53576
53900
|
return { content: [{ type: "text", text: dlPath }] };
|
|
53577
53901
|
}
|
|
53578
53902
|
async function executeEditMessage(args) {
|
|
@@ -55344,14 +55668,14 @@ function restartMarkerPath() {
|
|
|
55344
55668
|
const agentDir = resolveAgentDirFromEnv();
|
|
55345
55669
|
if (!agentDir)
|
|
55346
55670
|
return null;
|
|
55347
|
-
return
|
|
55671
|
+
return join33(agentDir, "restart-pending.json");
|
|
55348
55672
|
}
|
|
55349
55673
|
function writeRestartMarker(marker) {
|
|
55350
55674
|
const p = restartMarkerPath();
|
|
55351
55675
|
if (!p)
|
|
55352
55676
|
return;
|
|
55353
55677
|
try {
|
|
55354
|
-
|
|
55678
|
+
writeFileSync23(p, JSON.stringify(marker));
|
|
55355
55679
|
lastPlannedRestartAt = Date.now();
|
|
55356
55680
|
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
55681
|
`);
|
|
@@ -55370,7 +55694,7 @@ function readRestartMarker() {
|
|
|
55370
55694
|
if (!p)
|
|
55371
55695
|
return null;
|
|
55372
55696
|
try {
|
|
55373
|
-
return JSON.parse(
|
|
55697
|
+
return JSON.parse(readFileSync34(p, "utf8"));
|
|
55374
55698
|
} catch {
|
|
55375
55699
|
return null;
|
|
55376
55700
|
}
|
|
@@ -55468,7 +55792,7 @@ var _dockerReachable;
|
|
|
55468
55792
|
function isDockerReachable() {
|
|
55469
55793
|
if (_dockerReachable !== undefined)
|
|
55470
55794
|
return _dockerReachable;
|
|
55471
|
-
if (!
|
|
55795
|
+
if (!existsSync36("/var/run/docker.sock")) {
|
|
55472
55796
|
_dockerReachable = false;
|
|
55473
55797
|
return _dockerReachable;
|
|
55474
55798
|
}
|
|
@@ -55485,12 +55809,12 @@ function _resetDockerReachableCache() {
|
|
|
55485
55809
|
}
|
|
55486
55810
|
function spawnSwitchroomDetached(args, onFailure) {
|
|
55487
55811
|
const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
|
|
55488
|
-
const logPath =
|
|
55812
|
+
const logPath = join33(STATE_DIR, "detached-spawn.log");
|
|
55489
55813
|
let outFd = null;
|
|
55490
55814
|
try {
|
|
55491
|
-
|
|
55815
|
+
mkdirSync23(STATE_DIR, { recursive: true });
|
|
55492
55816
|
outFd = openSync8(logPath, "a");
|
|
55493
|
-
|
|
55817
|
+
writeFileSync23(logPath, `
|
|
55494
55818
|
[${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
|
|
55495
55819
|
`, { flag: "a" });
|
|
55496
55820
|
} catch {}
|
|
@@ -55516,7 +55840,7 @@ function spawnSwitchroomDetached(args, onFailure) {
|
|
|
55516
55840
|
return;
|
|
55517
55841
|
let tail = "";
|
|
55518
55842
|
try {
|
|
55519
|
-
const full =
|
|
55843
|
+
const full = readFileSync34(logPath, "utf8");
|
|
55520
55844
|
tail = full.split(`
|
|
55521
55845
|
`).slice(-30).join(`
|
|
55522
55846
|
`).trim();
|
|
@@ -55858,10 +56182,10 @@ bot.use(async (ctx, next) => {
|
|
|
55858
56182
|
});
|
|
55859
56183
|
function readRecentDenialsForAgent(agentName3, windowMs, limit) {
|
|
55860
56184
|
try {
|
|
55861
|
-
const auditPath =
|
|
55862
|
-
if (!
|
|
56185
|
+
const auditPath = join33(homedir12(), ".switchroom", "vault-audit.log");
|
|
56186
|
+
if (!existsSync36(auditPath))
|
|
55863
56187
|
return [];
|
|
55864
|
-
const raw =
|
|
56188
|
+
const raw = readFileSync34(auditPath, "utf8");
|
|
55865
56189
|
return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
|
|
55866
56190
|
} catch {
|
|
55867
56191
|
return [];
|
|
@@ -55912,7 +56236,7 @@ async function buildAgentMetadata(agentName3) {
|
|
|
55912
56236
|
try {
|
|
55913
56237
|
const agentDir = resolveAgentDirFromEnv();
|
|
55914
56238
|
if (agentDir) {
|
|
55915
|
-
const raw =
|
|
56239
|
+
const raw = readFileSync34(join33(agentDir, ".claude", ".claude.json"), "utf8");
|
|
55916
56240
|
claudeJson = JSON.parse(raw);
|
|
55917
56241
|
}
|
|
55918
56242
|
} catch {}
|
|
@@ -56126,9 +56450,9 @@ bot.command("restart", async (ctx) => {
|
|
|
56126
56450
|
function flushAgentHandoff(agentDir) {
|
|
56127
56451
|
let removed = 0;
|
|
56128
56452
|
for (const fname of [".handoff.md", ".handoff-topic"]) {
|
|
56129
|
-
const p =
|
|
56453
|
+
const p = join33(agentDir, fname);
|
|
56130
56454
|
try {
|
|
56131
|
-
if (
|
|
56455
|
+
if (existsSync36(p)) {
|
|
56132
56456
|
unlinkSync14(p);
|
|
56133
56457
|
removed++;
|
|
56134
56458
|
}
|
|
@@ -56184,7 +56508,7 @@ async function handleNewOrResetCommand(ctx, kind) {
|
|
|
56184
56508
|
writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
|
|
56185
56509
|
if (agentDir != null) {
|
|
56186
56510
|
try {
|
|
56187
|
-
|
|
56511
|
+
writeFileSync23(join33(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
|
|
56188
56512
|
`, "utf8");
|
|
56189
56513
|
} catch (err) {
|
|
56190
56514
|
process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
|
|
@@ -56543,16 +56867,16 @@ bot.command("interrupt", async (ctx) => {
|
|
|
56543
56867
|
await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
|
|
56544
56868
|
});
|
|
56545
56869
|
var lockoutOps = {
|
|
56546
|
-
readFileSync: (p, enc) =>
|
|
56547
|
-
writeFileSync: (p, data, opts) =>
|
|
56548
|
-
existsSync: (p) =>
|
|
56549
|
-
mkdirSync: (p, opts) =>
|
|
56550
|
-
joinPath: (...parts) =>
|
|
56870
|
+
readFileSync: (p, enc) => readFileSync34(p, enc),
|
|
56871
|
+
writeFileSync: (p, data, opts) => writeFileSync23(p, data, opts),
|
|
56872
|
+
existsSync: (p) => existsSync36(p),
|
|
56873
|
+
mkdirSync: (p, opts) => mkdirSync23(p, opts),
|
|
56874
|
+
joinPath: (...parts) => join33(...parts)
|
|
56551
56875
|
};
|
|
56552
56876
|
var FLEET_FALLBACK_DEDUP_MS = 30000;
|
|
56553
56877
|
function isAuthBrokerSocketReachable() {
|
|
56554
56878
|
try {
|
|
56555
|
-
return
|
|
56879
|
+
return existsSync36(resolveAuthBrokerSocketPath2());
|
|
56556
56880
|
} catch {
|
|
56557
56881
|
return false;
|
|
56558
56882
|
}
|
|
@@ -56613,7 +56937,7 @@ async function runCreditWatch() {
|
|
|
56613
56937
|
if (!agentDir)
|
|
56614
56938
|
return;
|
|
56615
56939
|
const agentName3 = getMyAgentName();
|
|
56616
|
-
const claudeConfigDir =
|
|
56940
|
+
const claudeConfigDir = join33(agentDir, ".claude");
|
|
56617
56941
|
const stateDir = STATE_DIR;
|
|
56618
56942
|
const reason = readClaudeJsonOverage(claudeConfigDir);
|
|
56619
56943
|
const prev = loadCreditState(stateDir);
|
|
@@ -56640,6 +56964,99 @@ async function runCreditWatch() {
|
|
|
56640
56964
|
`);
|
|
56641
56965
|
}
|
|
56642
56966
|
}
|
|
56967
|
+
async function runQuotaWatch() {
|
|
56968
|
+
const agentName3 = getMyAgentName();
|
|
56969
|
+
const stateDir = STATE_DIR;
|
|
56970
|
+
const brokerClient = await getAuthBrokerClient(agentName3);
|
|
56971
|
+
if (!brokerClient) {
|
|
56972
|
+
process.stderr.write(`telegram gateway: quota-watch: broker client unavailable \u2014 skipping
|
|
56973
|
+
`);
|
|
56974
|
+
return;
|
|
56975
|
+
}
|
|
56976
|
+
let listStateData;
|
|
56977
|
+
try {
|
|
56978
|
+
listStateData = await brokerClient.listState();
|
|
56979
|
+
} catch (err) {
|
|
56980
|
+
process.stderr.write(`telegram gateway: quota-watch: listState failed: ${err}
|
|
56981
|
+
`);
|
|
56982
|
+
return;
|
|
56983
|
+
}
|
|
56984
|
+
if (!listStateData.accounts || listStateData.accounts.length === 0) {
|
|
56985
|
+
return;
|
|
56986
|
+
}
|
|
56987
|
+
const snapshots = buildSnapshotsFromCachedState(listStateData);
|
|
56988
|
+
let watchState = loadQuotaWatchState(stateDir);
|
|
56989
|
+
const now = Date.now();
|
|
56990
|
+
const access = loadAccess();
|
|
56991
|
+
const pendingTransitions = [];
|
|
56992
|
+
const labelToSnapIndex = new Map(snapshots.map((s, i) => [s.label, i]));
|
|
56993
|
+
for (const snap of snapshots) {
|
|
56994
|
+
const prev = watchState[snap.label] ?? emptyAccountState();
|
|
56995
|
+
const decision = evaluateQuotaWatchAccount({ agentName: agentName3, snap, prev, now });
|
|
56996
|
+
if (decision.kind !== "skip") {
|
|
56997
|
+
pendingTransitions.push({
|
|
56998
|
+
accountLabel: snap.label,
|
|
56999
|
+
snapIndex: labelToSnapIndex.get(snap.label) ?? -1,
|
|
57000
|
+
decision
|
|
57001
|
+
});
|
|
57002
|
+
}
|
|
57003
|
+
}
|
|
57004
|
+
if (pendingTransitions.length === 0) {
|
|
57005
|
+
return;
|
|
57006
|
+
}
|
|
57007
|
+
const crossingLabels = pendingTransitions.map((t) => t.accountLabel);
|
|
57008
|
+
let freshProbeMap = new Map;
|
|
57009
|
+
try {
|
|
57010
|
+
const probeData = await brokerClient.probeQuota(crossingLabels, 8000);
|
|
57011
|
+
for (const entry of probeData.results) {
|
|
57012
|
+
freshProbeMap.set(entry.label, entry.result);
|
|
57013
|
+
}
|
|
57014
|
+
} catch (err) {
|
|
57015
|
+
process.stderr.write(`telegram gateway: quota-watch: probe for crossing accounts failed: ${err}
|
|
57016
|
+
`);
|
|
57017
|
+
}
|
|
57018
|
+
let mutatedState = watchState;
|
|
57019
|
+
const notifications = [];
|
|
57020
|
+
for (const { accountLabel, snapIndex, decision } of pendingTransitions) {
|
|
57021
|
+
const freshResult = freshProbeMap.get(accountLabel);
|
|
57022
|
+
let enrichedDecision = decision;
|
|
57023
|
+
if (decision.kind !== "notify")
|
|
57024
|
+
continue;
|
|
57025
|
+
if (freshResult && freshResult.ok && snapIndex >= 0) {
|
|
57026
|
+
const enrichedSnap = { ...snapshots[snapIndex], quota: freshResult.data };
|
|
57027
|
+
const prev = watchState[accountLabel] ?? emptyAccountState();
|
|
57028
|
+
const re = evaluateQuotaWatchAccount({ agentName: agentName3, snap: enrichedSnap, prev, now });
|
|
57029
|
+
if (re.kind === "notify" && re.transition === decision.transition) {
|
|
57030
|
+
enrichedDecision = re;
|
|
57031
|
+
} else if (re.kind === "skip") {
|
|
57032
|
+
continue;
|
|
57033
|
+
}
|
|
57034
|
+
}
|
|
57035
|
+
if (enrichedDecision.kind !== "notify")
|
|
57036
|
+
continue;
|
|
57037
|
+
notifications.push({ message: enrichedDecision.message, accountLabel });
|
|
57038
|
+
mutatedState = patchQuotaWatchState(mutatedState, accountLabel, enrichedDecision.newAccountState);
|
|
57039
|
+
}
|
|
57040
|
+
if (notifications.length === 0) {
|
|
57041
|
+
return;
|
|
57042
|
+
}
|
|
57043
|
+
for (const { message, accountLabel } of notifications) {
|
|
57044
|
+
for (const chat_id of access.allowFrom) {
|
|
57045
|
+
await swallowingApiCall(() => bot.api.sendMessage(chat_id, message, {
|
|
57046
|
+
parse_mode: "HTML",
|
|
57047
|
+
link_preview_options: { is_disabled: true }
|
|
57048
|
+
}), { chat_id, verb: "quota-watch.notify" });
|
|
57049
|
+
}
|
|
57050
|
+
process.stderr.write(`telegram gateway: quota-watch: notified transition for account=${accountLabel}
|
|
57051
|
+
`);
|
|
57052
|
+
}
|
|
57053
|
+
try {
|
|
57054
|
+
saveQuotaWatchState(stateDir, mutatedState);
|
|
57055
|
+
} catch (err) {
|
|
57056
|
+
process.stderr.write(`telegram gateway: quota-watch state persist failed: ${err}
|
|
57057
|
+
`);
|
|
57058
|
+
}
|
|
57059
|
+
}
|
|
56643
57060
|
bot.command("auth", async (ctx) => {
|
|
56644
57061
|
const authSenderId = String(ctx.from?.id ?? "");
|
|
56645
57062
|
const authOperatorPrivate = ctx.chat?.type === "private" && loadAccess().allowFrom.includes(authSenderId);
|
|
@@ -56823,10 +57240,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
|
|
|
56823
57240
|
return;
|
|
56824
57241
|
}
|
|
56825
57242
|
const { token, id } = result;
|
|
56826
|
-
const tokenPath =
|
|
57243
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", agentName3, ".vault-token");
|
|
56827
57244
|
try {
|
|
56828
|
-
|
|
56829
|
-
|
|
57245
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", agentName3), { recursive: true });
|
|
57246
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
56830
57247
|
} catch (err) {
|
|
56831
57248
|
await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
|
|
56832
57249
|
<i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
|
|
@@ -56902,10 +57319,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
|
|
|
56902
57319
|
return;
|
|
56903
57320
|
}
|
|
56904
57321
|
const { token, id } = result;
|
|
56905
|
-
const tokenPath =
|
|
57322
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
|
|
56906
57323
|
try {
|
|
56907
|
-
|
|
56908
|
-
|
|
57324
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", pending2.agent), { recursive: true });
|
|
57325
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
56909
57326
|
} catch (err) {
|
|
56910
57327
|
await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
|
|
56911
57328
|
<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 +57797,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
|
|
|
57380
57797
|
return;
|
|
57381
57798
|
}
|
|
57382
57799
|
const { token, id } = result;
|
|
57383
|
-
const tokenPath =
|
|
57800
|
+
const tokenPath = join33(homedir12(), ".switchroom", "agents", state4.agent, ".vault-token");
|
|
57384
57801
|
try {
|
|
57385
|
-
|
|
57386
|
-
|
|
57802
|
+
mkdirSync23(join33(homedir12(), ".switchroom", "agents", state4.agent), { recursive: true });
|
|
57803
|
+
writeFileSync23(tokenPath, token, { mode: 384 });
|
|
57387
57804
|
} catch (err) {
|
|
57388
57805
|
await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
|
|
57389
57806
|
return;
|
|
@@ -57731,15 +58148,6 @@ async function handleOperatorEventCallback(ctx, data) {
|
|
|
57731
58148
|
}
|
|
57732
58149
|
return;
|
|
57733
58150
|
}
|
|
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
58151
|
default: {
|
|
57744
58152
|
await ctx.answerCallbackQuery({ text: `Unknown action: ${action}` }).catch(() => {});
|
|
57745
58153
|
return;
|
|
@@ -57823,8 +58231,8 @@ async function handleAuthDashboardCallback(ctx) {
|
|
|
57823
58231
|
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
57824
58232
|
});
|
|
57825
58233
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
57826
|
-
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState:
|
|
57827
|
-
const snapshots =
|
|
58234
|
+
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
58235
|
+
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
57828
58236
|
const text = renderAuthSnapshotFormat23(snapshots, {
|
|
57829
58237
|
tz,
|
|
57830
58238
|
now: new Date,
|
|
@@ -58219,9 +58627,9 @@ bot.command("usage", async (ctx) => {
|
|
|
58219
58627
|
const hit = probeResp.results.find((r) => r.label === a.label);
|
|
58220
58628
|
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
58221
58629
|
});
|
|
58222
|
-
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState:
|
|
58630
|
+
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
58223
58631
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
58224
|
-
const snapshots =
|
|
58632
|
+
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
58225
58633
|
const text = renderAuthSnapshotFormat23(snapshots, {
|
|
58226
58634
|
tz,
|
|
58227
58635
|
now: new Date,
|
|
@@ -58240,7 +58648,7 @@ bot.command("usage", async (ctx) => {
|
|
|
58240
58648
|
await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
|
|
58241
58649
|
return;
|
|
58242
58650
|
}
|
|
58243
|
-
const result = await fetchQuota2({ claudeConfigDir:
|
|
58651
|
+
const result = await fetchQuota2({ claudeConfigDir: join33(agentDir, ".claude") });
|
|
58244
58652
|
if (!result.ok) {
|
|
58245
58653
|
await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
|
|
58246
58654
|
return;
|
|
@@ -58743,9 +59151,9 @@ bot.on("message:photo", async (ctx) => {
|
|
|
58743
59151
|
fileUniqueId: best.file_unique_id,
|
|
58744
59152
|
now: Date.now()
|
|
58745
59153
|
});
|
|
58746
|
-
|
|
59154
|
+
mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
|
|
58747
59155
|
assertInsideInbox(INBOX_DIR, dlPath);
|
|
58748
|
-
|
|
59156
|
+
writeFileSync23(dlPath, buf, { mode: 384 });
|
|
58749
59157
|
return dlPath;
|
|
58750
59158
|
} catch (err) {
|
|
58751
59159
|
const msg = err instanceof Error ? err.message : "unknown error";
|
|
@@ -58785,8 +59193,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
|
|
|
58785
59193
|
let apiKey = null;
|
|
58786
59194
|
try {
|
|
58787
59195
|
const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
|
|
58788
|
-
if (
|
|
58789
|
-
apiKey =
|
|
59196
|
+
if (existsSync36(path)) {
|
|
59197
|
+
apiKey = readFileSync34(path, "utf-8").trim();
|
|
58790
59198
|
}
|
|
58791
59199
|
} catch (err) {
|
|
58792
59200
|
process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
|
|
@@ -59642,11 +60050,12 @@ var didOneTimeSetup = false;
|
|
|
59642
60050
|
return;
|
|
59643
60051
|
}
|
|
59644
60052
|
})();
|
|
60053
|
+
const resolvedAgentDirForBootCard = agentDir ?? join33(homedir12(), ".switchroom", "agents", agentSlug);
|
|
59645
60054
|
const handle = await startBootCard(chatId, threadId, botApiForCard, {
|
|
59646
60055
|
agentName: agentDisplayName,
|
|
59647
60056
|
agentSlug,
|
|
59648
60057
|
version: formatBootVersion(),
|
|
59649
|
-
agentDir:
|
|
60058
|
+
agentDir: resolvedAgentDirForBootCard,
|
|
59650
60059
|
gatewayInfo: { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS },
|
|
59651
60060
|
restartReason: reason,
|
|
59652
60061
|
restartAgeMs: markerAgeMs,
|
|
@@ -59655,6 +60064,7 @@ var didOneTimeSetup = false;
|
|
|
59655
60064
|
probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
|
|
59656
60065
|
tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
|
|
59657
60066
|
dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
|
|
60067
|
+
configSnapshotPath: join33(resolvedAgentDirForBootCard, ".config-snapshot.json"),
|
|
59658
60068
|
...updateOutcomeLine ? { updateOutcomeLine } : {}
|
|
59659
60069
|
}, ackMsgId);
|
|
59660
60070
|
activeBootCard = handle;
|
|
@@ -59694,6 +60104,21 @@ var didOneTimeSetup = false;
|
|
|
59694
60104
|
});
|
|
59695
60105
|
}, CREDIT_WATCH_POLL_MS).unref();
|
|
59696
60106
|
}
|
|
60107
|
+
const QUOTA_WATCH_POLL_MS = Number(process.env.SWITCHROOM_QUOTA_WATCH_POLL_MS ?? 900000);
|
|
60108
|
+
if (QUOTA_WATCH_POLL_MS > 0) {
|
|
60109
|
+
setTimeout(() => {
|
|
60110
|
+
runQuotaWatch().catch((err) => {
|
|
60111
|
+
process.stderr.write(`telegram gateway: quota-watch initial run failed: ${err}
|
|
60112
|
+
`);
|
|
60113
|
+
});
|
|
60114
|
+
}, 30000);
|
|
60115
|
+
setInterval(() => {
|
|
60116
|
+
runQuotaWatch().catch((err) => {
|
|
60117
|
+
process.stderr.write(`telegram gateway: quota-watch scheduled run failed: ${err}
|
|
60118
|
+
`);
|
|
60119
|
+
});
|
|
60120
|
+
}, QUOTA_WATCH_POLL_MS).unref();
|
|
60121
|
+
}
|
|
59697
60122
|
const RESTART_WATCHDOG_POLL_MS = Number(process.env.SWITCHROOM_RESTART_WATCHDOG_POLL_MS ?? 30000);
|
|
59698
60123
|
const watchdogAgentName = process.env.SWITCHROOM_AGENT_NAME;
|
|
59699
60124
|
const watchdogDockerMode = process.env.SWITCHROOM_RUNTIME === "docker";
|