switchroom 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +2 -0
- package/dist/auth-broker/index.js +13 -0
- package/dist/cli/autoaccept-poll.js +2 -0
- package/dist/cli/drive-write-pretool.mjs +2 -0
- package/dist/cli/ms-365-write-pretool.mjs +2 -0
- package/dist/cli/switchroom.js +404 -245
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/default/CLAUDE.md.hbs +8 -0
- package/skills/mental-model-curator/SKILL.md +68 -2
- package/telegram-plugin/auth-snapshot-format.ts +104 -12
- package/telegram-plugin/dist/bridge/bridge.js +8 -2
- package/telegram-plugin/dist/gateway/gateway.js +1194 -794
- package/telegram-plugin/dist/server.js +8 -2
- package/telegram-plugin/flushed-turn-supersede.ts +117 -13
- package/telegram-plugin/gateway/auth-add-flow.ts +215 -6
- package/telegram-plugin/gateway/auth-command.ts +138 -5
- package/telegram-plugin/gateway/gateway.ts +68 -101
- package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
- package/telegram-plugin/gateway/model-command.ts +203 -1
- package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
- package/telegram-plugin/gateway/session-model-source.ts +90 -10
- package/telegram-plugin/gateway/stream-render.ts +22 -5
- package/telegram-plugin/quota-bar-format.ts +60 -12
- package/telegram-plugin/reply-owner-resolve.ts +76 -11
- package/telegram-plugin/session-tail.ts +27 -3
- package/telegram-plugin/tests/auth-add-flow.test.ts +367 -5
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +41 -0
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +117 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +185 -29
- package/telegram-plugin/tests/model-command.test.ts +220 -0
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +257 -13
- package/telegram-plugin/tests/send-reply-golden.test.ts +154 -0
- package/telegram-plugin/tests/session-model-source.test.ts +142 -0
- package/telegram-plugin/tests/session-tail-first-attach.test.ts +115 -2
- package/vendor/hindsight-memory/CHANGELOG.md +102 -0
- package/vendor/hindsight-memory/README.md +2 -1
- package/vendor/hindsight-memory/hooks/hooks.json +12 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +100 -3
- package/vendor/hindsight-memory/scripts/lib/config.py +150 -1
- package/vendor/hindsight-memory/scripts/lib/content.py +55 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +152 -15
- package/vendor/hindsight-memory/scripts/lib/parallel_recall.py +142 -0
- package/vendor/hindsight-memory/scripts/lib/state.py +31 -0
- package/vendor/hindsight-memory/scripts/recall.py +789 -143
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +22 -1
- package/vendor/hindsight-memory/scripts/retain.py +71 -2
- package/vendor/hindsight-memory/scripts/subagent_retain.py +501 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +169 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +177 -0
- package/vendor/hindsight-memory/scripts/tests/test_lesson_tagging.py +200 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_context_turns_default.py +200 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +477 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +51 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_parallel_deadline.py +409 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_tag_weights.py +96 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +413 -0
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +49 -0
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +439 -0
- package/vendor/hindsight-memory/settings.json +3 -1
|
@@ -7196,6 +7196,67 @@ var init_format = __esm(() => {
|
|
|
7196
7196
|
];
|
|
7197
7197
|
});
|
|
7198
7198
|
|
|
7199
|
+
// ../src/auth/quota.ts
|
|
7200
|
+
function isProbeThin(q) {
|
|
7201
|
+
return q.fiveHourUtilPresent === false && q.sevenDayUtilPresent === false;
|
|
7202
|
+
}
|
|
7203
|
+
function refillNormalizedUtils(q, now) {
|
|
7204
|
+
const nowMs = now.getTime();
|
|
7205
|
+
const fiveHourRefilled = q.fiveHourResetAt != null && q.fiveHourResetAt.getTime() <= nowMs;
|
|
7206
|
+
const sevenDayRefilled = q.sevenDayResetAt != null && q.sevenDayResetAt.getTime() <= nowMs;
|
|
7207
|
+
return {
|
|
7208
|
+
fiveHourUtilizationPct: fiveHourRefilled ? 0 : q.fiveHourUtilizationPct,
|
|
7209
|
+
sevenDayUtilizationPct: sevenDayRefilled ? 0 : q.sevenDayUtilizationPct,
|
|
7210
|
+
fiveHourRefilled,
|
|
7211
|
+
sevenDayRefilled
|
|
7212
|
+
};
|
|
7213
|
+
}
|
|
7214
|
+
|
|
7215
|
+
// demo-mask.ts
|
|
7216
|
+
function stableAssign(input, cache, pool, overflow) {
|
|
7217
|
+
const existing = cache.get(input);
|
|
7218
|
+
if (existing !== undefined)
|
|
7219
|
+
return existing;
|
|
7220
|
+
const index = cache.size;
|
|
7221
|
+
const value = index < pool.length ? pool[index] : overflow(index);
|
|
7222
|
+
cache.set(input, value);
|
|
7223
|
+
return value;
|
|
7224
|
+
}
|
|
7225
|
+
function maskEmail(label) {
|
|
7226
|
+
return stableAssign(label, emailCache, EMAIL_POOL, (i) => `demo${i + 1}@example.com`);
|
|
7227
|
+
}
|
|
7228
|
+
function maskUsername(tag) {
|
|
7229
|
+
return stableAssign(tag, usernameCache, USERNAME_POOL, (i) => `@demo_user${i + 1}`);
|
|
7230
|
+
}
|
|
7231
|
+
function maskVaultKey(name) {
|
|
7232
|
+
return stableAssign(name, vaultKeyCache, [], (i) => `demo/secret-${i + 1}`);
|
|
7233
|
+
}
|
|
7234
|
+
var EMAIL_POOL, emailCache, USERNAME_POOL, usernameCache, vaultKeyCache;
|
|
7235
|
+
var init_demo_mask = __esm(() => {
|
|
7236
|
+
EMAIL_POOL = [
|
|
7237
|
+
"ada@example.com",
|
|
7238
|
+
"grace@example.com",
|
|
7239
|
+
"linus@example.com",
|
|
7240
|
+
"hopper@example.com",
|
|
7241
|
+
"turing@example.com",
|
|
7242
|
+
"lovelace@example.com",
|
|
7243
|
+
"dijkstra@example.com",
|
|
7244
|
+
"knuth@example.com",
|
|
7245
|
+
"ritchie@example.com",
|
|
7246
|
+
"torvalds@example.com"
|
|
7247
|
+
];
|
|
7248
|
+
emailCache = new Map;
|
|
7249
|
+
USERNAME_POOL = [
|
|
7250
|
+
"@demo_user",
|
|
7251
|
+
"@demo_user2",
|
|
7252
|
+
"@demo_user3",
|
|
7253
|
+
"@demo_user4",
|
|
7254
|
+
"@demo_user5"
|
|
7255
|
+
];
|
|
7256
|
+
usernameCache = new Map;
|
|
7257
|
+
vaultKeyCache = new Map;
|
|
7258
|
+
});
|
|
7259
|
+
|
|
7199
7260
|
// text-voice-scrub.ts
|
|
7200
7261
|
function enabled() {
|
|
7201
7262
|
const v = process.env.SWITCHROOM_DISABLE_VOICE_SCRUB;
|
|
@@ -7383,6 +7444,533 @@ var init_card_format = __esm(() => {
|
|
|
7383
7444
|
init_text_voice_scrub();
|
|
7384
7445
|
});
|
|
7385
7446
|
|
|
7447
|
+
// auth-snapshot-format.ts
|
|
7448
|
+
var exports_auth_snapshot_format = {};
|
|
7449
|
+
__export(exports_auth_snapshot_format, {
|
|
7450
|
+
zipProbeResults: () => zipProbeResults,
|
|
7451
|
+
reviveLastQuota: () => reviveLastQuota,
|
|
7452
|
+
renderFallbackAnnouncement: () => renderFallbackAnnouncement,
|
|
7453
|
+
renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat2,
|
|
7454
|
+
recommendation: () => recommendation,
|
|
7455
|
+
formatStatusTime: () => formatStatusTime,
|
|
7456
|
+
formatRelative: () => formatRelative,
|
|
7457
|
+
formatAbsolute: () => formatAbsolute,
|
|
7458
|
+
fmtPct: () => fmtPct,
|
|
7459
|
+
deriveUsageFooterFreshness: () => deriveUsageFooterFreshness,
|
|
7460
|
+
classifyHealth: () => classifyHealth,
|
|
7461
|
+
buildSnapshotsFromState: () => buildSnapshotsFromState,
|
|
7462
|
+
buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
|
|
7463
|
+
buildSnapshotKeyboard: () => buildSnapshotKeyboard,
|
|
7464
|
+
blockedReason: () => blockedReason,
|
|
7465
|
+
bindingWindow: () => bindingWindow,
|
|
7466
|
+
THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT
|
|
7467
|
+
});
|
|
7468
|
+
function classifyHealth(snap, now = new Date) {
|
|
7469
|
+
if (!snap.isActive) {
|
|
7470
|
+
if (snap.entitlementBlocked === true)
|
|
7471
|
+
return "org-blocked";
|
|
7472
|
+
if (snap.inService === false)
|
|
7473
|
+
return "retired";
|
|
7474
|
+
}
|
|
7475
|
+
if (!snap.quota)
|
|
7476
|
+
return "unknown";
|
|
7477
|
+
const q = snap.quota;
|
|
7478
|
+
if (isProbeThin(q))
|
|
7479
|
+
return "unknown";
|
|
7480
|
+
const norm = refillNormalizedUtils(q, now);
|
|
7481
|
+
const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
|
|
7482
|
+
if (max >= 99.5)
|
|
7483
|
+
return "blocked";
|
|
7484
|
+
if (max >= THROTTLING_THRESHOLD_PCT)
|
|
7485
|
+
return "throttling";
|
|
7486
|
+
return "healthy";
|
|
7487
|
+
}
|
|
7488
|
+
function blockedReason(snap, now = new Date) {
|
|
7489
|
+
if (classifyHealth(snap, now) !== "blocked")
|
|
7490
|
+
return null;
|
|
7491
|
+
return "quota-exhausted";
|
|
7492
|
+
}
|
|
7493
|
+
function bindingWindow(q) {
|
|
7494
|
+
if (q.representativeClaim === "seven_day")
|
|
7495
|
+
return "7d";
|
|
7496
|
+
if (q.representativeClaim === "five_hour")
|
|
7497
|
+
return "5h";
|
|
7498
|
+
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
|
|
7499
|
+
}
|
|
7500
|
+
function formatRelative(target, now = new Date) {
|
|
7501
|
+
if (!target)
|
|
7502
|
+
return "\u2014";
|
|
7503
|
+
const deltaMs = target.getTime() - now.getTime();
|
|
7504
|
+
if (deltaMs <= 0)
|
|
7505
|
+
return "now";
|
|
7506
|
+
const totalMin = Math.round(deltaMs / 60000);
|
|
7507
|
+
if (totalMin < 60)
|
|
7508
|
+
return `${totalMin}m`;
|
|
7509
|
+
const h = Math.floor(totalMin / 60);
|
|
7510
|
+
const m = totalMin % 60;
|
|
7511
|
+
if (h < 24)
|
|
7512
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
7513
|
+
const d = Math.floor(h / 24);
|
|
7514
|
+
const rh = h % 24;
|
|
7515
|
+
return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
|
|
7516
|
+
}
|
|
7517
|
+
function formatAbsolute(target, tz = "UTC") {
|
|
7518
|
+
if (!target)
|
|
7519
|
+
return "\u2014";
|
|
7520
|
+
return target.toLocaleString("en-US", {
|
|
7521
|
+
timeZone: tz,
|
|
7522
|
+
weekday: "short",
|
|
7523
|
+
hour: "numeric",
|
|
7524
|
+
minute: "2-digit",
|
|
7525
|
+
hour12: true
|
|
7526
|
+
});
|
|
7527
|
+
}
|
|
7528
|
+
function formatStatusTime(target, now = new Date, tz = "UTC") {
|
|
7529
|
+
if (!target)
|
|
7530
|
+
return "\u2014";
|
|
7531
|
+
const dayFmt = new Intl.DateTimeFormat("en-CA", {
|
|
7532
|
+
timeZone: tz,
|
|
7533
|
+
year: "numeric",
|
|
7534
|
+
month: "2-digit",
|
|
7535
|
+
day: "2-digit"
|
|
7536
|
+
});
|
|
7537
|
+
const timeFmt = new Intl.DateTimeFormat("en-US", {
|
|
7538
|
+
timeZone: tz,
|
|
7539
|
+
hour: "numeric",
|
|
7540
|
+
minute: "2-digit",
|
|
7541
|
+
hour12: true
|
|
7542
|
+
});
|
|
7543
|
+
const timeStr = timeFmt.format(target);
|
|
7544
|
+
if (dayFmt.format(target) === dayFmt.format(now)) {
|
|
7545
|
+
return timeStr;
|
|
7546
|
+
}
|
|
7547
|
+
const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
|
|
7548
|
+
const dateFmt = new Intl.DateTimeFormat("en-US", {
|
|
7549
|
+
timeZone: tz,
|
|
7550
|
+
weekday: "short",
|
|
7551
|
+
...withinWeek ? {} : { day: "numeric", month: "short" }
|
|
7552
|
+
});
|
|
7553
|
+
return `${dateFmt.format(target)} ${timeStr}`;
|
|
7554
|
+
}
|
|
7555
|
+
function fmtPct(pct) {
|
|
7556
|
+
return `${Math.min(100, Math.round(pct))}%`;
|
|
7557
|
+
}
|
|
7558
|
+
function displayLabel(label, opts) {
|
|
7559
|
+
return opts.demo ? maskEmail(label) : label;
|
|
7560
|
+
}
|
|
7561
|
+
function renderAccountRow(snap, opts) {
|
|
7562
|
+
const now = opts.now ?? new Date;
|
|
7563
|
+
const tz = opts.tz ?? "UTC";
|
|
7564
|
+
const lines = [];
|
|
7565
|
+
const marker = snap.isActive ? "\u25cf " : "";
|
|
7566
|
+
const label = displayLabel(snap.label, opts);
|
|
7567
|
+
const health = classifyHealth(snap, now);
|
|
7568
|
+
if (health === "org-blocked" || health === "retired") {
|
|
7569
|
+
const note = health === "org-blocked" ? "DISABLED (org) \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
|
|
7570
|
+
lines.push(`${marker}\`${codeSpanSafe(label)}\` _${note}_`);
|
|
7571
|
+
return lines;
|
|
7572
|
+
}
|
|
7573
|
+
if (!snap.quota) {
|
|
7574
|
+
lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
|
|
7575
|
+
if (snap.quotaError) {
|
|
7576
|
+
lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
|
|
7577
|
+
}
|
|
7578
|
+
return lines;
|
|
7579
|
+
}
|
|
7580
|
+
const q = snap.quota;
|
|
7581
|
+
if (isProbeThin(q)) {
|
|
7582
|
+
lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
|
|
7583
|
+
return lines;
|
|
7584
|
+
}
|
|
7585
|
+
const norm = refillNormalizedUtils(q, now);
|
|
7586
|
+
const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
|
|
7587
|
+
const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
|
|
7588
|
+
lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
|
|
7589
|
+
if (health === "blocked") {
|
|
7590
|
+
const win = bindingWindow(q);
|
|
7591
|
+
const reset = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
7592
|
+
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
7593
|
+
lines.push(reset ? ` _quota exhausted \u2014 back ${formatAbsolute(reset, tz)} (\`in ${formatRelative(reset, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
|
|
7594
|
+
return lines;
|
|
7595
|
+
}
|
|
7596
|
+
const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
|
|
7597
|
+
const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
|
|
7598
|
+
const fiveFirst = fiveResetIn <= sevenResetIn;
|
|
7599
|
+
const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
|
|
7600
|
+
const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
|
|
7601
|
+
lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
|
|
7602
|
+
lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
|
|
7603
|
+
if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
|
|
7604
|
+
lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
|
|
7605
|
+
}
|
|
7606
|
+
return lines;
|
|
7607
|
+
}
|
|
7608
|
+
function formatAgeStamp(atMs, now = new Date) {
|
|
7609
|
+
const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
|
|
7610
|
+
return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
|
|
7611
|
+
}
|
|
7612
|
+
function tableCell(s) {
|
|
7613
|
+
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
7614
|
+
}
|
|
7615
|
+
function pctCells(snap, now) {
|
|
7616
|
+
if (!snap.quota)
|
|
7617
|
+
return { five: "\u2014", seven: "\u2014" };
|
|
7618
|
+
if (isProbeThin(snap.quota))
|
|
7619
|
+
return { five: "?", seven: "?" };
|
|
7620
|
+
const norm = refillNormalizedUtils(snap.quota, now);
|
|
7621
|
+
return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
|
|
7622
|
+
}
|
|
7623
|
+
function windowResetCell(snap, now, tz, win) {
|
|
7624
|
+
if (!snap.quota) {
|
|
7625
|
+
return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
|
|
7626
|
+
}
|
|
7627
|
+
if (isProbeThin(snap.quota))
|
|
7628
|
+
return "quota unknown";
|
|
7629
|
+
const reset = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
|
|
7630
|
+
if (!reset)
|
|
7631
|
+
return "\u2014";
|
|
7632
|
+
return `${formatStatusTime(reset, now, tz)} (in ${formatRelative(reset, now)})`;
|
|
7633
|
+
}
|
|
7634
|
+
function renderAuthSnapshotFormat2(snapshots, opts = {}) {
|
|
7635
|
+
const now = opts.now ?? new Date;
|
|
7636
|
+
const tz = opts.tz ?? "UTC";
|
|
7637
|
+
const lines = [];
|
|
7638
|
+
lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
|
|
7639
|
+
const ordered = [...snapshots].sort((a, b) => {
|
|
7640
|
+
if (a.isActive !== b.isActive)
|
|
7641
|
+
return a.isActive ? -1 : 1;
|
|
7642
|
+
const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
|
|
7643
|
+
if (r !== 0)
|
|
7644
|
+
return r;
|
|
7645
|
+
return a.label.localeCompare(b.label);
|
|
7646
|
+
});
|
|
7647
|
+
if (ordered.length > 0) {
|
|
7648
|
+
lines.push("");
|
|
7649
|
+
lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
|
|
7650
|
+
lines.push("| --- | --- | --- | --- | --- | --- |");
|
|
7651
|
+
for (const s of ordered) {
|
|
7652
|
+
const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
|
|
7653
|
+
const label = displayLabel(s.label, opts);
|
|
7654
|
+
const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
|
|
7655
|
+
const { five, seven } = pctCells(s, now);
|
|
7656
|
+
const fiveReset = windowResetCell(s, now, tz, "5h");
|
|
7657
|
+
const sevenReset = windowResetCell(s, now, tz, "7d");
|
|
7658
|
+
lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7661
|
+
lines.push("");
|
|
7662
|
+
lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
|
|
7663
|
+
if (opts.staleCachedAtMs != null) {
|
|
7664
|
+
lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
|
|
7665
|
+
} else if (opts.liveProbedAtMs != null) {
|
|
7666
|
+
lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
|
|
7667
|
+
} else if (opts.probeFailed) {
|
|
7668
|
+
lines.push("_\u26a0 probe failed \u2014 no live data_");
|
|
7669
|
+
} else {
|
|
7670
|
+
lines.push("_Live_");
|
|
7671
|
+
}
|
|
7672
|
+
return lines.join(`
|
|
7673
|
+
`);
|
|
7674
|
+
}
|
|
7675
|
+
function recommendation(snapshots, now = new Date, demo = false) {
|
|
7676
|
+
const active = snapshots.find((s) => s.isActive);
|
|
7677
|
+
if (!active)
|
|
7678
|
+
return "No active account set.";
|
|
7679
|
+
const activeHealth = classifyHealth(active, now);
|
|
7680
|
+
const inServiceFleet = snapshots.filter((s) => {
|
|
7681
|
+
const h = classifyHealth(s, now);
|
|
7682
|
+
return h !== "retired" && h !== "org-blocked";
|
|
7683
|
+
});
|
|
7684
|
+
const others = inServiceFleet.filter((s) => !s.isActive);
|
|
7685
|
+
const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
|
|
7686
|
+
const lbl = (s) => demo ? maskEmail(s.label) : s.label;
|
|
7687
|
+
const activeLabel = lbl(active);
|
|
7688
|
+
if (activeHealth === "healthy") {
|
|
7689
|
+
return `Recommendation: stay on ${activeLabel}.`;
|
|
7690
|
+
}
|
|
7691
|
+
if (activeHealth === "throttling") {
|
|
7692
|
+
if (healthyAlt) {
|
|
7693
|
+
return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
|
|
7694
|
+
}
|
|
7695
|
+
return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
|
|
7696
|
+
}
|
|
7697
|
+
if (activeHealth === "blocked") {
|
|
7698
|
+
if (healthyAlt) {
|
|
7699
|
+
return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
|
|
7700
|
+
}
|
|
7701
|
+
return summarizeNoHealthyAlt(inServiceFleet, now, demo);
|
|
7702
|
+
}
|
|
7703
|
+
return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
|
|
7704
|
+
}
|
|
7705
|
+
function summarizeNoHealthyAlt(snapshots, now, demo = false) {
|
|
7706
|
+
const mask = (label) => demo ? maskEmail(label) : label;
|
|
7707
|
+
let throttlingLabel = null;
|
|
7708
|
+
let allTrulyBlocked = true;
|
|
7709
|
+
for (const s of snapshots) {
|
|
7710
|
+
const h = classifyHealth(s, now);
|
|
7711
|
+
if (h === "throttling") {
|
|
7712
|
+
if (!throttlingLabel)
|
|
7713
|
+
throttlingLabel = s.label;
|
|
7714
|
+
allTrulyBlocked = false;
|
|
7715
|
+
} else if (h === "healthy" || h === "unknown") {
|
|
7716
|
+
allTrulyBlocked = false;
|
|
7717
|
+
} else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
|
|
7718
|
+
if (s.quota) {
|
|
7719
|
+
const win = bindingWindow(s.quota);
|
|
7720
|
+
const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
|
|
7721
|
+
if (at && at.getTime() > now.getTime())
|
|
7722
|
+
allTrulyBlocked = false;
|
|
7723
|
+
}
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
const earliestRecovery = pickEarliestRecovery(snapshots, now);
|
|
7727
|
+
if (throttlingLabel) {
|
|
7728
|
+
const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
|
|
7729
|
+
return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
|
|
7730
|
+
}
|
|
7731
|
+
if (!allTrulyBlocked) {
|
|
7732
|
+
if (earliestRecovery) {
|
|
7733
|
+
return `All accounts at capacity; soonest refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
|
|
7734
|
+
}
|
|
7735
|
+
return `All accounts at capacity \u2014 waiting on a window refill.`;
|
|
7736
|
+
}
|
|
7737
|
+
if (earliestRecovery) {
|
|
7738
|
+
return `All accounts blocked. Earliest recovery: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
|
|
7739
|
+
}
|
|
7740
|
+
return `All accounts blocked. Run /auth add to attach another subscription.`;
|
|
7741
|
+
}
|
|
7742
|
+
function pickEarliestRecovery(snapshots, now) {
|
|
7743
|
+
let best = null;
|
|
7744
|
+
for (const s of snapshots) {
|
|
7745
|
+
if (!s.quota)
|
|
7746
|
+
continue;
|
|
7747
|
+
if (isProbeThin(s.quota))
|
|
7748
|
+
continue;
|
|
7749
|
+
const win = bindingWindow(s.quota);
|
|
7750
|
+
const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
|
|
7751
|
+
if (!at || at.getTime() <= now.getTime())
|
|
7752
|
+
continue;
|
|
7753
|
+
if (!best || at.getTime() < best.at.getTime()) {
|
|
7754
|
+
best = { label: s.label, at };
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7757
|
+
return best;
|
|
7758
|
+
}
|
|
7759
|
+
function renderFallbackAnnouncement(input) {
|
|
7760
|
+
const now = input.now ?? new Date;
|
|
7761
|
+
const tz = input.tz ?? "UTC";
|
|
7762
|
+
const lines = [];
|
|
7763
|
+
const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
|
|
7764
|
+
const headerLimit = input.cause === "rate-limit" ? "rate limit" : limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
7765
|
+
if (!input.newLabel) {
|
|
7766
|
+
lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
|
|
7767
|
+
lines.push("");
|
|
7768
|
+
lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
|
|
7769
|
+
const fleet = input.fleetSnapshots ?? [];
|
|
7770
|
+
if (fleet.length > 0) {
|
|
7771
|
+
lines.push("");
|
|
7772
|
+
const rowOpts = { now, tz };
|
|
7773
|
+
const healthOrder = [
|
|
7774
|
+
"org-blocked",
|
|
7775
|
+
"blocked",
|
|
7776
|
+
"throttling",
|
|
7777
|
+
"healthy",
|
|
7778
|
+
"unknown",
|
|
7779
|
+
"retired"
|
|
7780
|
+
];
|
|
7781
|
+
const rank = (s) => healthOrder.indexOf(classifyHealth(s, now));
|
|
7782
|
+
const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
|
|
7783
|
+
for (const snap of ordered) {
|
|
7784
|
+
for (const ln of renderAccountRow(snap, rowOpts))
|
|
7785
|
+
lines.push(ln);
|
|
7786
|
+
}
|
|
7787
|
+
const earliest = pickEarliestRecovery(fleet, now);
|
|
7788
|
+
if (earliest) {
|
|
7789
|
+
lines.push("");
|
|
7790
|
+
lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
|
|
7791
|
+
}
|
|
7792
|
+
} else {
|
|
7793
|
+
const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
7794
|
+
if (recovery) {
|
|
7795
|
+
lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
lines.push("");
|
|
7799
|
+
lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
|
|
7800
|
+
return lines.join(`
|
|
7801
|
+
`);
|
|
7802
|
+
}
|
|
7803
|
+
lines.push(`\u2713 **Switched fleet \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
|
|
7804
|
+
lines.push("");
|
|
7805
|
+
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
|
|
7806
|
+
lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
|
|
7807
|
+
lines.push("");
|
|
7808
|
+
{
|
|
7809
|
+
const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
7810
|
+
if (recovery) {
|
|
7811
|
+
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
if (input.newQuota) {
|
|
7815
|
+
const fiveStr = fmtPct(input.newQuota.fiveHourUtilizationPct);
|
|
7816
|
+
const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
|
|
7817
|
+
const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
|
|
7818
|
+
const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
|
|
7819
|
+
lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
|
|
7820
|
+
} else {
|
|
7821
|
+
lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
|
|
7822
|
+
}
|
|
7823
|
+
return lines.join(`
|
|
7824
|
+
`);
|
|
7825
|
+
}
|
|
7826
|
+
function limitWordFor(q) {
|
|
7827
|
+
if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
|
|
7828
|
+
return "7-day";
|
|
7829
|
+
if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
|
|
7830
|
+
return "5-hour";
|
|
7831
|
+
if (q.sevenDayUtilizationPct >= 99)
|
|
7832
|
+
return "7-day";
|
|
7833
|
+
if (q.fiveHourUtilizationPct >= 99)
|
|
7834
|
+
return "5-hour";
|
|
7835
|
+
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
|
|
7836
|
+
}
|
|
7837
|
+
function recoveryAtFor(q) {
|
|
7838
|
+
const word = limitWordFor(q);
|
|
7839
|
+
if (word === "7-day")
|
|
7840
|
+
return q.sevenDayResetAt;
|
|
7841
|
+
if (word === "5-hour")
|
|
7842
|
+
return q.fiveHourResetAt;
|
|
7843
|
+
if (!q.fiveHourResetAt)
|
|
7844
|
+
return q.sevenDayResetAt;
|
|
7845
|
+
if (!q.sevenDayResetAt)
|
|
7846
|
+
return q.fiveHourResetAt;
|
|
7847
|
+
return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
7848
|
+
}
|
|
7849
|
+
function buildSnapshotKeyboard(snapshots, opts = {}) {
|
|
7850
|
+
const max = opts.maxSwitchButtons ?? 3;
|
|
7851
|
+
const now = opts.now ?? new Date;
|
|
7852
|
+
const rows = [];
|
|
7853
|
+
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority(a, now) - switchPriority(b, now)).filter((s) => {
|
|
7854
|
+
const h = classifyHealth(s, now);
|
|
7855
|
+
return h !== "blocked" && h !== "unknown" && h !== "retired" && h !== "org-blocked";
|
|
7856
|
+
}).slice(0, max);
|
|
7857
|
+
for (const t of switchTargets) {
|
|
7858
|
+
rows.push([
|
|
7859
|
+
{
|
|
7860
|
+
text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
|
|
7861
|
+
callbackData: `auth:use:${t.label}`
|
|
7862
|
+
}
|
|
7863
|
+
]);
|
|
7864
|
+
}
|
|
7865
|
+
rows.push([
|
|
7866
|
+
{ text: "\u21bb Refresh", callbackData: opts.demo ? "auth:refresh:demo" : "auth:refresh" },
|
|
7867
|
+
{ text: "/usage", insertText: "/usage" },
|
|
7868
|
+
{ text: "+ Add", insertText: "/auth add " }
|
|
7869
|
+
]);
|
|
7870
|
+
return rows;
|
|
7871
|
+
}
|
|
7872
|
+
function switchPriority(s, now = new Date) {
|
|
7873
|
+
const h = classifyHealth(s, now);
|
|
7874
|
+
if (h === "healthy")
|
|
7875
|
+
return 0;
|
|
7876
|
+
if (h === "throttling")
|
|
7877
|
+
return 1;
|
|
7878
|
+
if (h === "unknown")
|
|
7879
|
+
return 2;
|
|
7880
|
+
if (h === "blocked")
|
|
7881
|
+
return 3;
|
|
7882
|
+
return 4;
|
|
7883
|
+
}
|
|
7884
|
+
function zipProbeResults(labels, results) {
|
|
7885
|
+
let staleCachedAtMs;
|
|
7886
|
+
const quotas = labels.map((label) => {
|
|
7887
|
+
const hit = results.find((r) => r.label === label);
|
|
7888
|
+
if (!hit)
|
|
7889
|
+
return { ok: false, reason: "broker returned no result for account" };
|
|
7890
|
+
if (hit.served === "cache" && hit.capturedAt != null) {
|
|
7891
|
+
staleCachedAtMs = staleCachedAtMs == null ? hit.capturedAt : Math.min(staleCachedAtMs, hit.capturedAt);
|
|
7892
|
+
}
|
|
7893
|
+
return hit.result;
|
|
7894
|
+
});
|
|
7895
|
+
return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
|
|
7896
|
+
}
|
|
7897
|
+
function deriveUsageFooterFreshness(results, staleCachedAtMs, liveProbedAtMs) {
|
|
7898
|
+
if (staleCachedAtMs != null)
|
|
7899
|
+
return { staleCachedAtMs };
|
|
7900
|
+
if (results.some((r) => r.result.ok))
|
|
7901
|
+
return { liveProbedAtMs };
|
|
7902
|
+
return { probeFailed: true };
|
|
7903
|
+
}
|
|
7904
|
+
function buildSnapshotsFromState(state, quotas) {
|
|
7905
|
+
const out = [];
|
|
7906
|
+
for (let i = 0;i < state.accounts.length; i++) {
|
|
7907
|
+
const acc = state.accounts[i];
|
|
7908
|
+
const q = quotas[i];
|
|
7909
|
+
out.push({
|
|
7910
|
+
label: acc.label,
|
|
7911
|
+
isActive: acc.label === state.active,
|
|
7912
|
+
quota: q && q.ok ? q.data : null,
|
|
7913
|
+
quotaError: q && !q.ok ? q.reason : undefined,
|
|
7914
|
+
expiresAtMs: acc.expiresAt,
|
|
7915
|
+
inService: acc.in_service,
|
|
7916
|
+
entitlementBlocked: acc.entitlement_blocked
|
|
7917
|
+
});
|
|
7918
|
+
}
|
|
7919
|
+
return out;
|
|
7920
|
+
}
|
|
7921
|
+
function reviveLastQuota(snap) {
|
|
7922
|
+
if (!snap)
|
|
7923
|
+
return null;
|
|
7924
|
+
return {
|
|
7925
|
+
fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
|
|
7926
|
+
sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
|
|
7927
|
+
fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
|
|
7928
|
+
sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
|
|
7929
|
+
representativeClaim: snap.representativeClaim,
|
|
7930
|
+
overageStatus: snap.overageStatus,
|
|
7931
|
+
overageDisabledReason: snap.overageDisabledReason,
|
|
7932
|
+
fiveHourUtilPresent: snap.fiveHourUtilPresent,
|
|
7933
|
+
sevenDayUtilPresent: snap.sevenDayUtilPresent
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
function buildSnapshotsFromCachedState(state) {
|
|
7937
|
+
return state.accounts.map((acc) => {
|
|
7938
|
+
const lq = acc.last_quota ?? null;
|
|
7939
|
+
return {
|
|
7940
|
+
label: acc.label,
|
|
7941
|
+
isActive: acc.label === state.active,
|
|
7942
|
+
quota: reviveLastQuota(lq),
|
|
7943
|
+
quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
|
|
7944
|
+
expiresAtMs: acc.expiresAt,
|
|
7945
|
+
capturedAtMs: lq?.capturedAt,
|
|
7946
|
+
inService: acc.in_service,
|
|
7947
|
+
entitlementBlocked: acc.entitlement_blocked
|
|
7948
|
+
};
|
|
7949
|
+
});
|
|
7950
|
+
}
|
|
7951
|
+
var THROTTLING_THRESHOLD_PCT = 80, OVERAGE_EXHAUSTED_REASONS, HEALTH_EMOJI, TABLE_HEALTH_RANK;
|
|
7952
|
+
var init_auth_snapshot_format = __esm(() => {
|
|
7953
|
+
init_demo_mask();
|
|
7954
|
+
init_card_format();
|
|
7955
|
+
OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
|
|
7956
|
+
HEALTH_EMOJI = {
|
|
7957
|
+
healthy: "\uD83D\uDFE2",
|
|
7958
|
+
throttling: "\uD83D\uDFE1",
|
|
7959
|
+
blocked: "\uD83D\uDD34",
|
|
7960
|
+
unknown: "\u26aa",
|
|
7961
|
+
"org-blocked": "\u26d4",
|
|
7962
|
+
retired: "\u26ab"
|
|
7963
|
+
};
|
|
7964
|
+
TABLE_HEALTH_RANK = {
|
|
7965
|
+
"org-blocked": 0,
|
|
7966
|
+
blocked: 1,
|
|
7967
|
+
throttling: 2,
|
|
7968
|
+
unknown: 3,
|
|
7969
|
+
healthy: 4,
|
|
7970
|
+
retired: 5
|
|
7971
|
+
};
|
|
7972
|
+
});
|
|
7973
|
+
|
|
7386
7974
|
// secret-detect/patterns.ts
|
|
7387
7975
|
var ANCHORED_PATTERNS, STRUCTURED_PATTERNS, PROVIDER_PATTERNS, ALL_PATTERNS;
|
|
7388
7976
|
var init_patterns = __esm(() => {
|
|
@@ -13034,51 +13622,6 @@ var init_tmux = __esm(() => {
|
|
|
13034
13622
|
MAX_BYTES = 10 * 1024 * 1024;
|
|
13035
13623
|
});
|
|
13036
13624
|
|
|
13037
|
-
// demo-mask.ts
|
|
13038
|
-
function stableAssign(input, cache, pool, overflow) {
|
|
13039
|
-
const existing = cache.get(input);
|
|
13040
|
-
if (existing !== undefined)
|
|
13041
|
-
return existing;
|
|
13042
|
-
const index = cache.size;
|
|
13043
|
-
const value = index < pool.length ? pool[index] : overflow(index);
|
|
13044
|
-
cache.set(input, value);
|
|
13045
|
-
return value;
|
|
13046
|
-
}
|
|
13047
|
-
function maskEmail(label) {
|
|
13048
|
-
return stableAssign(label, emailCache, EMAIL_POOL, (i) => `demo${i + 1}@example.com`);
|
|
13049
|
-
}
|
|
13050
|
-
function maskUsername(tag) {
|
|
13051
|
-
return stableAssign(tag, usernameCache, USERNAME_POOL, (i) => `@demo_user${i + 1}`);
|
|
13052
|
-
}
|
|
13053
|
-
function maskVaultKey(name) {
|
|
13054
|
-
return stableAssign(name, vaultKeyCache, [], (i) => `demo/secret-${i + 1}`);
|
|
13055
|
-
}
|
|
13056
|
-
var EMAIL_POOL, emailCache, USERNAME_POOL, usernameCache, vaultKeyCache;
|
|
13057
|
-
var init_demo_mask = __esm(() => {
|
|
13058
|
-
EMAIL_POOL = [
|
|
13059
|
-
"ada@example.com",
|
|
13060
|
-
"grace@example.com",
|
|
13061
|
-
"linus@example.com",
|
|
13062
|
-
"hopper@example.com",
|
|
13063
|
-
"turing@example.com",
|
|
13064
|
-
"lovelace@example.com",
|
|
13065
|
-
"dijkstra@example.com",
|
|
13066
|
-
"knuth@example.com",
|
|
13067
|
-
"ritchie@example.com",
|
|
13068
|
-
"torvalds@example.com"
|
|
13069
|
-
];
|
|
13070
|
-
emailCache = new Map;
|
|
13071
|
-
USERNAME_POOL = [
|
|
13072
|
-
"@demo_user",
|
|
13073
|
-
"@demo_user2",
|
|
13074
|
-
"@demo_user3",
|
|
13075
|
-
"@demo_user4",
|
|
13076
|
-
"@demo_user5"
|
|
13077
|
-
];
|
|
13078
|
-
usernameCache = new Map;
|
|
13079
|
-
vaultKeyCache = new Map;
|
|
13080
|
-
});
|
|
13081
|
-
|
|
13082
13625
|
// ../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
13083
13626
|
var require_identity = __commonJS((exports) => {
|
|
13084
13627
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -21894,647 +22437,139 @@ function materializeFilesEntry(key, files) {
|
|
|
21894
22437
|
}
|
|
21895
22438
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
21896
22439
|
chmodSync3(dir, 448);
|
|
21897
|
-
const st = statSync6(dir);
|
|
21898
|
-
if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
|
|
21899
|
-
throw new Error(`Refusing to materialize vault entry: ${dir} not owned by caller`);
|
|
21900
|
-
}
|
|
21901
|
-
for (const [filename, { encoding, value }] of Object.entries(files)) {
|
|
21902
|
-
if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
|
|
21903
|
-
throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
|
|
21904
|
-
}
|
|
21905
|
-
const filePath = join5(dir, filename);
|
|
21906
|
-
const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
|
|
21907
|
-
writeFileExclusive(filePath, content);
|
|
21908
|
-
}
|
|
21909
|
-
materializedDirs.add(dir);
|
|
21910
|
-
registerCleanupHook();
|
|
21911
|
-
return dir;
|
|
21912
|
-
}
|
|
21913
|
-
function resolveSingleReference(ref, secrets) {
|
|
21914
|
-
const entry = secrets[ref.key];
|
|
21915
|
-
if (entry === undefined) {
|
|
21916
|
-
throw new Error(`Vault secret not found: ${ref.key}`);
|
|
21917
|
-
}
|
|
21918
|
-
if (ref.filename !== undefined) {
|
|
21919
|
-
if (entry.kind !== "files") {
|
|
21920
|
-
throw new Error(`Vault reference "vault:${ref.key}#${ref.filename}" expected kind="files", got kind="${entry.kind}".`);
|
|
21921
|
-
}
|
|
21922
|
-
const file = entry.files[ref.filename];
|
|
21923
|
-
if (!file) {
|
|
21924
|
-
throw new Error(`Vault secret "${ref.key}" has no file named "${ref.filename}". Available: ${Object.keys(entry.files).join(", ")}`);
|
|
21925
|
-
}
|
|
21926
|
-
return file.encoding === "base64" ? Buffer.from(file.value, "base64").toString("utf8") : file.value;
|
|
21927
|
-
}
|
|
21928
|
-
if (entry.kind === "string" || entry.kind === "binary") {
|
|
21929
|
-
return entry.value;
|
|
21930
|
-
}
|
|
21931
|
-
return materializeFilesEntry(ref.key, entry.files);
|
|
21932
|
-
}
|
|
21933
|
-
function resolveValue(value, secrets) {
|
|
21934
|
-
if (typeof value === "string" && isVaultReference(value)) {
|
|
21935
|
-
const ref = parseVaultReferenceDetailed(value);
|
|
21936
|
-
return resolveSingleReference(ref, secrets);
|
|
21937
|
-
}
|
|
21938
|
-
if (Array.isArray(value)) {
|
|
21939
|
-
return value.map((item) => resolveValue(item, secrets));
|
|
21940
|
-
}
|
|
21941
|
-
if (value !== null && typeof value === "object") {
|
|
21942
|
-
const resolved = {};
|
|
21943
|
-
for (const [k, v] of Object.entries(value)) {
|
|
21944
|
-
resolved[k] = resolveValue(v, secrets);
|
|
21945
|
-
}
|
|
21946
|
-
return resolved;
|
|
21947
|
-
}
|
|
21948
|
-
return value;
|
|
21949
|
-
}
|
|
21950
|
-
async function resolveVaultReferencesViaBroker(config, brokerOpts) {
|
|
21951
|
-
const socketPath = resolveBrokerSocketPath({
|
|
21952
|
-
...brokerOpts,
|
|
21953
|
-
vaultBrokerSocket: config.vault?.broker?.socket ? resolvePath(config.vault.broker.socket) : undefined
|
|
21954
|
-
});
|
|
21955
|
-
const opts = { ...brokerOpts, socket: socketPath };
|
|
21956
|
-
const refs = collectVaultRefs(config);
|
|
21957
|
-
if (refs.size === 0) {
|
|
21958
|
-
return { ok: true, config };
|
|
21959
|
-
}
|
|
21960
|
-
const brokerSecrets = {};
|
|
21961
|
-
let sawDenied = false;
|
|
21962
|
-
let sawUnreachable = false;
|
|
21963
|
-
let sawLocked = false;
|
|
21964
|
-
let sawNotFound = false;
|
|
21965
|
-
const resolverAgentSlug = process.env.SWITCHROOM_AGENT_NAME;
|
|
21966
|
-
const resolverToken = resolverAgentSlug ? readVaultTokenFile(resolverAgentSlug) ?? undefined : undefined;
|
|
21967
|
-
const optsWithToken = resolverToken ? { ...opts, token: resolverToken } : opts;
|
|
21968
|
-
for (const key of refs) {
|
|
21969
|
-
const result = await getViaBrokerStructured(key, optsWithToken);
|
|
21970
|
-
if (result.kind === "ok") {
|
|
21971
|
-
brokerSecrets[key] = result.entry;
|
|
21972
|
-
} else if (result.kind === "unreachable") {
|
|
21973
|
-
sawUnreachable = true;
|
|
21974
|
-
break;
|
|
21975
|
-
} else if (result.kind === "denied") {
|
|
21976
|
-
if (result.code === "LOCKED") {
|
|
21977
|
-
sawLocked = true;
|
|
21978
|
-
} else {
|
|
21979
|
-
sawDenied = true;
|
|
21980
|
-
}
|
|
21981
|
-
} else if (result.kind === "not_found") {
|
|
21982
|
-
sawNotFound = true;
|
|
21983
|
-
}
|
|
21984
|
-
}
|
|
21985
|
-
const resolvedCount = Object.keys(brokerSecrets).length;
|
|
21986
|
-
const allResolved = resolvedCount === refs.size;
|
|
21987
|
-
if (allResolved) {
|
|
21988
|
-
return { ok: true, config: resolveValue(config, brokerSecrets) };
|
|
21989
|
-
}
|
|
21990
|
-
if (sawUnreachable && !sawDenied && !sawLocked && !sawNotFound) {
|
|
21991
|
-
return { ok: false, reason: "unreachable" };
|
|
21992
|
-
}
|
|
21993
|
-
if (sawLocked && !sawDenied && !sawUnreachable && !sawNotFound) {
|
|
21994
|
-
return { ok: false, reason: "locked" };
|
|
21995
|
-
}
|
|
21996
|
-
if (sawDenied && !sawUnreachable && !sawLocked && !sawNotFound) {
|
|
21997
|
-
return { ok: false, reason: "denied" };
|
|
21998
|
-
}
|
|
21999
|
-
if (sawNotFound && !sawDenied && !sawUnreachable && !sawLocked && resolvedCount > 0) {
|
|
22000
|
-
return { ok: true, config: resolveValue(config, brokerSecrets) };
|
|
22001
|
-
}
|
|
22002
|
-
if (sawNotFound && !sawDenied && !sawUnreachable && !sawLocked && resolvedCount === 0) {
|
|
22003
|
-
return { ok: false, reason: "not_found" };
|
|
22004
|
-
}
|
|
22005
|
-
return { ok: false, reason: "unknown" };
|
|
22006
|
-
}
|
|
22007
|
-
function collectVaultRefs(value) {
|
|
22008
|
-
const keys = new Set;
|
|
22009
|
-
function walk(v) {
|
|
22010
|
-
if (typeof v === "string" && isVaultReference(v)) {
|
|
22011
|
-
const ref = parseVaultReferenceDetailed(v);
|
|
22012
|
-
keys.add(ref.key);
|
|
22013
|
-
} else if (Array.isArray(v)) {
|
|
22014
|
-
for (const item of v)
|
|
22015
|
-
walk(item);
|
|
22016
|
-
} else if (v !== null && typeof v === "object") {
|
|
22017
|
-
for (const val of Object.values(v))
|
|
22018
|
-
walk(val);
|
|
22019
|
-
}
|
|
22020
|
-
}
|
|
22021
|
-
walk(value);
|
|
22022
|
-
return keys;
|
|
22023
|
-
}
|
|
22024
|
-
var materializedDirs, cleanupRegistered = false, cachedRoot = null;
|
|
22025
|
-
var init_resolver = __esm(() => {
|
|
22026
|
-
init_vault();
|
|
22027
|
-
init_loader();
|
|
22028
|
-
init_client();
|
|
22029
|
-
materializedDirs = new Set;
|
|
22030
|
-
});
|
|
22031
|
-
|
|
22032
|
-
// ../src/auth/quota.ts
|
|
22033
|
-
function isProbeThin(q) {
|
|
22034
|
-
return q.fiveHourUtilPresent === false && q.sevenDayUtilPresent === false;
|
|
22035
|
-
}
|
|
22036
|
-
function refillNormalizedUtils(q, now) {
|
|
22037
|
-
const nowMs = now.getTime();
|
|
22038
|
-
const fiveHourRefilled = q.fiveHourResetAt != null && q.fiveHourResetAt.getTime() <= nowMs;
|
|
22039
|
-
const sevenDayRefilled = q.sevenDayResetAt != null && q.sevenDayResetAt.getTime() <= nowMs;
|
|
22040
|
-
return {
|
|
22041
|
-
fiveHourUtilizationPct: fiveHourRefilled ? 0 : q.fiveHourUtilizationPct,
|
|
22042
|
-
sevenDayUtilizationPct: sevenDayRefilled ? 0 : q.sevenDayUtilizationPct,
|
|
22043
|
-
fiveHourRefilled,
|
|
22044
|
-
sevenDayRefilled
|
|
22045
|
-
};
|
|
22046
|
-
}
|
|
22047
|
-
|
|
22048
|
-
// auth-snapshot-format.ts
|
|
22049
|
-
var exports_auth_snapshot_format = {};
|
|
22050
|
-
__export(exports_auth_snapshot_format, {
|
|
22051
|
-
zipProbeResults: () => zipProbeResults,
|
|
22052
|
-
reviveLastQuota: () => reviveLastQuota,
|
|
22053
|
-
renderFallbackAnnouncement: () => renderFallbackAnnouncement,
|
|
22054
|
-
renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat2,
|
|
22055
|
-
recommendation: () => recommendation,
|
|
22056
|
-
formatStatusTime: () => formatStatusTime,
|
|
22057
|
-
formatRelative: () => formatRelative,
|
|
22058
|
-
formatAbsolute: () => formatAbsolute,
|
|
22059
|
-
fmtPct: () => fmtPct,
|
|
22060
|
-
deriveUsageFooterFreshness: () => deriveUsageFooterFreshness,
|
|
22061
|
-
classifyHealth: () => classifyHealth,
|
|
22062
|
-
buildSnapshotsFromState: () => buildSnapshotsFromState,
|
|
22063
|
-
buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
|
|
22064
|
-
buildSnapshotKeyboard: () => buildSnapshotKeyboard,
|
|
22065
|
-
blockedReason: () => blockedReason,
|
|
22066
|
-
bindingWindow: () => bindingWindow,
|
|
22067
|
-
THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT
|
|
22068
|
-
});
|
|
22069
|
-
function classifyHealth(snap, now = new Date) {
|
|
22070
|
-
if (!snap.quota)
|
|
22071
|
-
return "unknown";
|
|
22072
|
-
const q = snap.quota;
|
|
22073
|
-
if (isProbeThin(q))
|
|
22074
|
-
return "unknown";
|
|
22075
|
-
const norm = refillNormalizedUtils(q, now);
|
|
22076
|
-
const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
|
|
22077
|
-
if (max >= 99.5)
|
|
22078
|
-
return "blocked";
|
|
22079
|
-
if (max >= THROTTLING_THRESHOLD_PCT)
|
|
22080
|
-
return "throttling";
|
|
22081
|
-
return "healthy";
|
|
22082
|
-
}
|
|
22083
|
-
function blockedReason(snap, now = new Date) {
|
|
22084
|
-
if (classifyHealth(snap, now) !== "blocked")
|
|
22085
|
-
return null;
|
|
22086
|
-
return "quota-exhausted";
|
|
22087
|
-
}
|
|
22088
|
-
function bindingWindow(q) {
|
|
22089
|
-
if (q.representativeClaim === "seven_day")
|
|
22090
|
-
return "7d";
|
|
22091
|
-
if (q.representativeClaim === "five_hour")
|
|
22092
|
-
return "5h";
|
|
22093
|
-
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
|
|
22094
|
-
}
|
|
22095
|
-
function formatRelative(target, now = new Date) {
|
|
22096
|
-
if (!target)
|
|
22097
|
-
return "\u2014";
|
|
22098
|
-
const deltaMs = target.getTime() - now.getTime();
|
|
22099
|
-
if (deltaMs <= 0)
|
|
22100
|
-
return "now";
|
|
22101
|
-
const totalMin = Math.round(deltaMs / 60000);
|
|
22102
|
-
if (totalMin < 60)
|
|
22103
|
-
return `${totalMin}m`;
|
|
22104
|
-
const h = Math.floor(totalMin / 60);
|
|
22105
|
-
const m = totalMin % 60;
|
|
22106
|
-
if (h < 24)
|
|
22107
|
-
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
22108
|
-
const d = Math.floor(h / 24);
|
|
22109
|
-
const rh = h % 24;
|
|
22110
|
-
return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
|
|
22111
|
-
}
|
|
22112
|
-
function formatAbsolute(target, tz = "UTC") {
|
|
22113
|
-
if (!target)
|
|
22114
|
-
return "\u2014";
|
|
22115
|
-
return target.toLocaleString("en-US", {
|
|
22116
|
-
timeZone: tz,
|
|
22117
|
-
weekday: "short",
|
|
22118
|
-
hour: "numeric",
|
|
22119
|
-
minute: "2-digit",
|
|
22120
|
-
hour12: true
|
|
22121
|
-
});
|
|
22122
|
-
}
|
|
22123
|
-
function formatStatusTime(target, now = new Date, tz = "UTC") {
|
|
22124
|
-
if (!target)
|
|
22125
|
-
return "\u2014";
|
|
22126
|
-
const dayFmt = new Intl.DateTimeFormat("en-CA", {
|
|
22127
|
-
timeZone: tz,
|
|
22128
|
-
year: "numeric",
|
|
22129
|
-
month: "2-digit",
|
|
22130
|
-
day: "2-digit"
|
|
22131
|
-
});
|
|
22132
|
-
const timeFmt = new Intl.DateTimeFormat("en-US", {
|
|
22133
|
-
timeZone: tz,
|
|
22134
|
-
hour: "numeric",
|
|
22135
|
-
minute: "2-digit",
|
|
22136
|
-
hour12: true
|
|
22137
|
-
});
|
|
22138
|
-
const timeStr = timeFmt.format(target);
|
|
22139
|
-
if (dayFmt.format(target) === dayFmt.format(now)) {
|
|
22140
|
-
return timeStr;
|
|
22141
|
-
}
|
|
22142
|
-
const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
|
|
22143
|
-
const dateFmt = new Intl.DateTimeFormat("en-US", {
|
|
22144
|
-
timeZone: tz,
|
|
22145
|
-
weekday: "short",
|
|
22146
|
-
...withinWeek ? {} : { day: "numeric", month: "short" }
|
|
22147
|
-
});
|
|
22148
|
-
return `${dateFmt.format(target)} ${timeStr}`;
|
|
22149
|
-
}
|
|
22150
|
-
function fmtPct(pct) {
|
|
22151
|
-
return `${Math.min(100, Math.round(pct))}%`;
|
|
22152
|
-
}
|
|
22153
|
-
function displayLabel(label, opts) {
|
|
22154
|
-
return opts.demo ? maskEmail(label) : label;
|
|
22155
|
-
}
|
|
22156
|
-
function renderAccountRow(snap, opts) {
|
|
22157
|
-
const now = opts.now ?? new Date;
|
|
22158
|
-
const tz = opts.tz ?? "UTC";
|
|
22159
|
-
const lines = [];
|
|
22160
|
-
const marker = snap.isActive ? "\u25cf " : "";
|
|
22161
|
-
const label = displayLabel(snap.label, opts);
|
|
22162
|
-
if (!snap.quota) {
|
|
22163
|
-
lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
|
|
22164
|
-
if (snap.quotaError) {
|
|
22165
|
-
lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
|
|
22166
|
-
}
|
|
22167
|
-
return lines;
|
|
22168
|
-
}
|
|
22169
|
-
const q = snap.quota;
|
|
22170
|
-
if (isProbeThin(q)) {
|
|
22171
|
-
lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
|
|
22172
|
-
return lines;
|
|
22173
|
-
}
|
|
22174
|
-
const norm = refillNormalizedUtils(q, now);
|
|
22175
|
-
const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
|
|
22176
|
-
const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
|
|
22177
|
-
lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
|
|
22178
|
-
const health = classifyHealth(snap, now);
|
|
22179
|
-
if (health === "blocked") {
|
|
22180
|
-
const win = bindingWindow(q);
|
|
22181
|
-
const reset = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
22182
|
-
const winLabel = win === "5h" ? "5-hour" : "7-day";
|
|
22183
|
-
lines.push(reset ? ` _quota exhausted \u2014 back ${formatAbsolute(reset, tz)} (\`in ${formatRelative(reset, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
|
|
22184
|
-
return lines;
|
|
22185
|
-
}
|
|
22186
|
-
const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
|
|
22187
|
-
const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
|
|
22188
|
-
const fiveFirst = fiveResetIn <= sevenResetIn;
|
|
22189
|
-
const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
|
|
22190
|
-
const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
|
|
22191
|
-
lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
|
|
22192
|
-
lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
|
|
22193
|
-
if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
|
|
22194
|
-
lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
|
|
22195
|
-
}
|
|
22196
|
-
return lines;
|
|
22197
|
-
}
|
|
22198
|
-
function formatAgeStamp(atMs, now = new Date) {
|
|
22199
|
-
const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
|
|
22200
|
-
return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
|
|
22201
|
-
}
|
|
22202
|
-
function tableCell(s) {
|
|
22203
|
-
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
22204
|
-
}
|
|
22205
|
-
function pctCells(snap, now) {
|
|
22206
|
-
if (!snap.quota)
|
|
22207
|
-
return { five: "\u2014", seven: "\u2014" };
|
|
22208
|
-
if (isProbeThin(snap.quota))
|
|
22209
|
-
return { five: "?", seven: "?" };
|
|
22210
|
-
const norm = refillNormalizedUtils(snap.quota, now);
|
|
22211
|
-
return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
|
|
22212
|
-
}
|
|
22213
|
-
function windowResetCell(snap, now, tz, win) {
|
|
22214
|
-
if (!snap.quota) {
|
|
22215
|
-
return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
|
|
22216
|
-
}
|
|
22217
|
-
if (isProbeThin(snap.quota))
|
|
22218
|
-
return "quota unknown";
|
|
22219
|
-
const reset = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
|
|
22220
|
-
if (!reset)
|
|
22221
|
-
return "\u2014";
|
|
22222
|
-
return `${formatStatusTime(reset, now, tz)} (in ${formatRelative(reset, now)})`;
|
|
22223
|
-
}
|
|
22224
|
-
function renderAuthSnapshotFormat2(snapshots, opts = {}) {
|
|
22225
|
-
const now = opts.now ?? new Date;
|
|
22226
|
-
const tz = opts.tz ?? "UTC";
|
|
22227
|
-
const lines = [];
|
|
22228
|
-
lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
|
|
22229
|
-
const ordered = [...snapshots].sort((a, b) => {
|
|
22230
|
-
if (a.isActive !== b.isActive)
|
|
22231
|
-
return a.isActive ? -1 : 1;
|
|
22232
|
-
const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
|
|
22233
|
-
if (r !== 0)
|
|
22234
|
-
return r;
|
|
22235
|
-
return a.label.localeCompare(b.label);
|
|
22236
|
-
});
|
|
22237
|
-
if (ordered.length > 0) {
|
|
22238
|
-
lines.push("");
|
|
22239
|
-
lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
|
|
22240
|
-
lines.push("| --- | --- | --- | --- | --- | --- |");
|
|
22241
|
-
for (const s of ordered) {
|
|
22242
|
-
const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
|
|
22243
|
-
const label = displayLabel(s.label, opts);
|
|
22244
|
-
const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
|
|
22245
|
-
const { five, seven } = pctCells(s, now);
|
|
22246
|
-
const fiveReset = windowResetCell(s, now, tz, "5h");
|
|
22247
|
-
const sevenReset = windowResetCell(s, now, tz, "7d");
|
|
22248
|
-
lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
|
|
22249
|
-
}
|
|
22250
|
-
}
|
|
22251
|
-
lines.push("");
|
|
22252
|
-
lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
|
|
22253
|
-
if (opts.staleCachedAtMs != null) {
|
|
22254
|
-
lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
|
|
22255
|
-
} else if (opts.liveProbedAtMs != null) {
|
|
22256
|
-
lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
|
|
22257
|
-
} else if (opts.probeFailed) {
|
|
22258
|
-
lines.push("_\u26a0 probe failed \u2014 no live data_");
|
|
22259
|
-
} else {
|
|
22260
|
-
lines.push("_Live_");
|
|
22261
|
-
}
|
|
22262
|
-
return lines.join(`
|
|
22263
|
-
`);
|
|
22264
|
-
}
|
|
22265
|
-
function recommendation(snapshots, now = new Date, demo = false) {
|
|
22266
|
-
const active = snapshots.find((s) => s.isActive);
|
|
22267
|
-
if (!active)
|
|
22268
|
-
return "No active account set.";
|
|
22269
|
-
const activeHealth = classifyHealth(active, now);
|
|
22270
|
-
const others = snapshots.filter((s) => !s.isActive);
|
|
22271
|
-
const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
|
|
22272
|
-
const lbl = (s) => demo ? maskEmail(s.label) : s.label;
|
|
22273
|
-
const activeLabel = lbl(active);
|
|
22274
|
-
if (activeHealth === "healthy") {
|
|
22275
|
-
return `Recommendation: stay on ${activeLabel}.`;
|
|
22276
|
-
}
|
|
22277
|
-
if (activeHealth === "throttling") {
|
|
22278
|
-
if (healthyAlt) {
|
|
22279
|
-
return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
|
|
22280
|
-
}
|
|
22281
|
-
return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
|
|
22440
|
+
const st = statSync6(dir);
|
|
22441
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
|
|
22442
|
+
throw new Error(`Refusing to materialize vault entry: ${dir} not owned by caller`);
|
|
22282
22443
|
}
|
|
22283
|
-
|
|
22284
|
-
if (
|
|
22285
|
-
|
|
22444
|
+
for (const [filename, { encoding, value }] of Object.entries(files)) {
|
|
22445
|
+
if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
|
|
22446
|
+
throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
|
|
22286
22447
|
}
|
|
22287
|
-
|
|
22448
|
+
const filePath = join5(dir, filename);
|
|
22449
|
+
const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
|
|
22450
|
+
writeFileExclusive(filePath, content);
|
|
22288
22451
|
}
|
|
22289
|
-
|
|
22452
|
+
materializedDirs.add(dir);
|
|
22453
|
+
registerCleanupHook();
|
|
22454
|
+
return dir;
|
|
22290
22455
|
}
|
|
22291
|
-
function
|
|
22292
|
-
const
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
for (const s of snapshots) {
|
|
22296
|
-
const h = classifyHealth(s, now);
|
|
22297
|
-
if (h === "throttling") {
|
|
22298
|
-
if (!throttlingLabel)
|
|
22299
|
-
throttlingLabel = s.label;
|
|
22300
|
-
allTrulyBlocked = false;
|
|
22301
|
-
} else if (h === "healthy" || h === "unknown") {
|
|
22302
|
-
allTrulyBlocked = false;
|
|
22303
|
-
} else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
|
|
22304
|
-
if (s.quota) {
|
|
22305
|
-
const win = bindingWindow(s.quota);
|
|
22306
|
-
const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
|
|
22307
|
-
if (at && at.getTime() > now.getTime())
|
|
22308
|
-
allTrulyBlocked = false;
|
|
22309
|
-
}
|
|
22310
|
-
}
|
|
22311
|
-
}
|
|
22312
|
-
const earliestRecovery = pickEarliestRecovery(snapshots, now);
|
|
22313
|
-
if (throttlingLabel) {
|
|
22314
|
-
const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
|
|
22315
|
-
return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
|
|
22456
|
+
function resolveSingleReference(ref, secrets) {
|
|
22457
|
+
const entry = secrets[ref.key];
|
|
22458
|
+
if (entry === undefined) {
|
|
22459
|
+
throw new Error(`Vault secret not found: ${ref.key}`);
|
|
22316
22460
|
}
|
|
22317
|
-
if (
|
|
22318
|
-
if (
|
|
22319
|
-
|
|
22461
|
+
if (ref.filename !== undefined) {
|
|
22462
|
+
if (entry.kind !== "files") {
|
|
22463
|
+
throw new Error(`Vault reference "vault:${ref.key}#${ref.filename}" expected kind="files", got kind="${entry.kind}".`);
|
|
22320
22464
|
}
|
|
22321
|
-
|
|
22465
|
+
const file = entry.files[ref.filename];
|
|
22466
|
+
if (!file) {
|
|
22467
|
+
throw new Error(`Vault secret "${ref.key}" has no file named "${ref.filename}". Available: ${Object.keys(entry.files).join(", ")}`);
|
|
22468
|
+
}
|
|
22469
|
+
return file.encoding === "base64" ? Buffer.from(file.value, "base64").toString("utf8") : file.value;
|
|
22322
22470
|
}
|
|
22323
|
-
if (
|
|
22324
|
-
return
|
|
22471
|
+
if (entry.kind === "string" || entry.kind === "binary") {
|
|
22472
|
+
return entry.value;
|
|
22325
22473
|
}
|
|
22326
|
-
return
|
|
22474
|
+
return materializeFilesEntry(ref.key, entry.files);
|
|
22327
22475
|
}
|
|
22328
|
-
function
|
|
22329
|
-
|
|
22330
|
-
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
best = { label: s.label, at };
|
|
22476
|
+
function resolveValue(value, secrets) {
|
|
22477
|
+
if (typeof value === "string" && isVaultReference(value)) {
|
|
22478
|
+
const ref = parseVaultReferenceDetailed(value);
|
|
22479
|
+
return resolveSingleReference(ref, secrets);
|
|
22480
|
+
}
|
|
22481
|
+
if (Array.isArray(value)) {
|
|
22482
|
+
return value.map((item) => resolveValue(item, secrets));
|
|
22483
|
+
}
|
|
22484
|
+
if (value !== null && typeof value === "object") {
|
|
22485
|
+
const resolved = {};
|
|
22486
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22487
|
+
resolved[k] = resolveValue(v, secrets);
|
|
22341
22488
|
}
|
|
22489
|
+
return resolved;
|
|
22342
22490
|
}
|
|
22343
|
-
return
|
|
22491
|
+
return value;
|
|
22344
22492
|
}
|
|
22345
|
-
function
|
|
22346
|
-
const
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
const
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22493
|
+
async function resolveVaultReferencesViaBroker(config, brokerOpts) {
|
|
22494
|
+
const socketPath = resolveBrokerSocketPath({
|
|
22495
|
+
...brokerOpts,
|
|
22496
|
+
vaultBrokerSocket: config.vault?.broker?.socket ? resolvePath(config.vault.broker.socket) : undefined
|
|
22497
|
+
});
|
|
22498
|
+
const opts = { ...brokerOpts, socket: socketPath };
|
|
22499
|
+
const refs = collectVaultRefs(config);
|
|
22500
|
+
if (refs.size === 0) {
|
|
22501
|
+
return { ok: true, config };
|
|
22502
|
+
}
|
|
22503
|
+
const brokerSecrets = {};
|
|
22504
|
+
let sawDenied = false;
|
|
22505
|
+
let sawUnreachable = false;
|
|
22506
|
+
let sawLocked = false;
|
|
22507
|
+
let sawNotFound = false;
|
|
22508
|
+
const resolverAgentSlug = process.env.SWITCHROOM_AGENT_NAME;
|
|
22509
|
+
const resolverToken = resolverAgentSlug ? readVaultTokenFile(resolverAgentSlug) ?? undefined : undefined;
|
|
22510
|
+
const optsWithToken = resolverToken ? { ...opts, token: resolverToken } : opts;
|
|
22511
|
+
for (const key of refs) {
|
|
22512
|
+
const result = await getViaBrokerStructured(key, optsWithToken);
|
|
22513
|
+
if (result.kind === "ok") {
|
|
22514
|
+
brokerSecrets[key] = result.entry;
|
|
22515
|
+
} else if (result.kind === "unreachable") {
|
|
22516
|
+
sawUnreachable = true;
|
|
22517
|
+
break;
|
|
22518
|
+
} else if (result.kind === "denied") {
|
|
22519
|
+
if (result.code === "LOCKED") {
|
|
22520
|
+
sawLocked = true;
|
|
22521
|
+
} else {
|
|
22522
|
+
sawDenied = true;
|
|
22375
22523
|
}
|
|
22524
|
+
} else if (result.kind === "not_found") {
|
|
22525
|
+
sawNotFound = true;
|
|
22376
22526
|
}
|
|
22377
|
-
lines.push("");
|
|
22378
|
-
lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
|
|
22379
|
-
return lines.join(`
|
|
22380
|
-
`);
|
|
22381
22527
|
}
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
lines.push("");
|
|
22387
|
-
{
|
|
22388
|
-
const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
22389
|
-
if (recovery) {
|
|
22390
|
-
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
|
|
22391
|
-
}
|
|
22528
|
+
const resolvedCount = Object.keys(brokerSecrets).length;
|
|
22529
|
+
const allResolved = resolvedCount === refs.size;
|
|
22530
|
+
if (allResolved) {
|
|
22531
|
+
return { ok: true, config: resolveValue(config, brokerSecrets) };
|
|
22392
22532
|
}
|
|
22393
|
-
if (
|
|
22394
|
-
|
|
22395
|
-
const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
|
|
22396
|
-
const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
|
|
22397
|
-
const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
|
|
22398
|
-
lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
|
|
22399
|
-
} else {
|
|
22400
|
-
lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
|
|
22533
|
+
if (sawUnreachable && !sawDenied && !sawLocked && !sawNotFound) {
|
|
22534
|
+
return { ok: false, reason: "unreachable" };
|
|
22401
22535
|
}
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
}
|
|
22405
|
-
function limitWordFor(q) {
|
|
22406
|
-
if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
|
|
22407
|
-
return "7-day";
|
|
22408
|
-
if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
|
|
22409
|
-
return "5-hour";
|
|
22410
|
-
if (q.sevenDayUtilizationPct >= 99)
|
|
22411
|
-
return "7-day";
|
|
22412
|
-
if (q.fiveHourUtilizationPct >= 99)
|
|
22413
|
-
return "5-hour";
|
|
22414
|
-
return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
|
|
22415
|
-
}
|
|
22416
|
-
function recoveryAtFor(q) {
|
|
22417
|
-
const word = limitWordFor(q);
|
|
22418
|
-
if (word === "7-day")
|
|
22419
|
-
return q.sevenDayResetAt;
|
|
22420
|
-
if (word === "5-hour")
|
|
22421
|
-
return q.fiveHourResetAt;
|
|
22422
|
-
if (!q.fiveHourResetAt)
|
|
22423
|
-
return q.sevenDayResetAt;
|
|
22424
|
-
if (!q.sevenDayResetAt)
|
|
22425
|
-
return q.fiveHourResetAt;
|
|
22426
|
-
return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
22427
|
-
}
|
|
22428
|
-
function buildSnapshotKeyboard(snapshots, opts = {}) {
|
|
22429
|
-
const max = opts.maxSwitchButtons ?? 3;
|
|
22430
|
-
const now = opts.now ?? new Date;
|
|
22431
|
-
const rows = [];
|
|
22432
|
-
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority(a, now) - switchPriority(b, now)).filter((s) => classifyHealth(s, now) !== "blocked" && classifyHealth(s, now) !== "unknown").slice(0, max);
|
|
22433
|
-
for (const t of switchTargets) {
|
|
22434
|
-
rows.push([
|
|
22435
|
-
{
|
|
22436
|
-
text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
|
|
22437
|
-
callbackData: `auth:use:${t.label}`
|
|
22438
|
-
}
|
|
22439
|
-
]);
|
|
22536
|
+
if (sawLocked && !sawDenied && !sawUnreachable && !sawNotFound) {
|
|
22537
|
+
return { ok: false, reason: "locked" };
|
|
22440
22538
|
}
|
|
22441
|
-
|
|
22442
|
-
{
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
return 0;
|
|
22452
|
-
if (h === "throttling")
|
|
22453
|
-
return 1;
|
|
22454
|
-
if (h === "unknown")
|
|
22455
|
-
return 2;
|
|
22456
|
-
return 3;
|
|
22539
|
+
if (sawDenied && !sawUnreachable && !sawLocked && !sawNotFound) {
|
|
22540
|
+
return { ok: false, reason: "denied" };
|
|
22541
|
+
}
|
|
22542
|
+
if (sawNotFound && !sawDenied && !sawUnreachable && !sawLocked && resolvedCount > 0) {
|
|
22543
|
+
return { ok: true, config: resolveValue(config, brokerSecrets) };
|
|
22544
|
+
}
|
|
22545
|
+
if (sawNotFound && !sawDenied && !sawUnreachable && !sawLocked && resolvedCount === 0) {
|
|
22546
|
+
return { ok: false, reason: "not_found" };
|
|
22547
|
+
}
|
|
22548
|
+
return { ok: false, reason: "unknown" };
|
|
22457
22549
|
}
|
|
22458
|
-
function
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
if (
|
|
22465
|
-
|
|
22550
|
+
function collectVaultRefs(value) {
|
|
22551
|
+
const keys = new Set;
|
|
22552
|
+
function walk(v) {
|
|
22553
|
+
if (typeof v === "string" && isVaultReference(v)) {
|
|
22554
|
+
const ref = parseVaultReferenceDetailed(v);
|
|
22555
|
+
keys.add(ref.key);
|
|
22556
|
+
} else if (Array.isArray(v)) {
|
|
22557
|
+
for (const item of v)
|
|
22558
|
+
walk(item);
|
|
22559
|
+
} else if (v !== null && typeof v === "object") {
|
|
22560
|
+
for (const val of Object.values(v))
|
|
22561
|
+
walk(val);
|
|
22466
22562
|
}
|
|
22467
|
-
return hit.result;
|
|
22468
|
-
});
|
|
22469
|
-
return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
|
|
22470
|
-
}
|
|
22471
|
-
function deriveUsageFooterFreshness(results, staleCachedAtMs, liveProbedAtMs) {
|
|
22472
|
-
if (staleCachedAtMs != null)
|
|
22473
|
-
return { staleCachedAtMs };
|
|
22474
|
-
if (results.some((r) => r.result.ok))
|
|
22475
|
-
return { liveProbedAtMs };
|
|
22476
|
-
return { probeFailed: true };
|
|
22477
|
-
}
|
|
22478
|
-
function buildSnapshotsFromState(state, quotas) {
|
|
22479
|
-
const out = [];
|
|
22480
|
-
for (let i = 0;i < state.accounts.length; i++) {
|
|
22481
|
-
const acc = state.accounts[i];
|
|
22482
|
-
const q = quotas[i];
|
|
22483
|
-
out.push({
|
|
22484
|
-
label: acc.label,
|
|
22485
|
-
isActive: acc.label === state.active,
|
|
22486
|
-
quota: q && q.ok ? q.data : null,
|
|
22487
|
-
quotaError: q && !q.ok ? q.reason : undefined,
|
|
22488
|
-
expiresAtMs: acc.expiresAt
|
|
22489
|
-
});
|
|
22490
22563
|
}
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
function reviveLastQuota(snap) {
|
|
22494
|
-
if (!snap)
|
|
22495
|
-
return null;
|
|
22496
|
-
return {
|
|
22497
|
-
fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
|
|
22498
|
-
sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
|
|
22499
|
-
fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
|
|
22500
|
-
sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
|
|
22501
|
-
representativeClaim: snap.representativeClaim,
|
|
22502
|
-
overageStatus: snap.overageStatus,
|
|
22503
|
-
overageDisabledReason: snap.overageDisabledReason,
|
|
22504
|
-
fiveHourUtilPresent: snap.fiveHourUtilPresent,
|
|
22505
|
-
sevenDayUtilPresent: snap.sevenDayUtilPresent
|
|
22506
|
-
};
|
|
22507
|
-
}
|
|
22508
|
-
function buildSnapshotsFromCachedState(state) {
|
|
22509
|
-
return state.accounts.map((acc) => {
|
|
22510
|
-
const lq = acc.last_quota ?? null;
|
|
22511
|
-
return {
|
|
22512
|
-
label: acc.label,
|
|
22513
|
-
isActive: acc.label === state.active,
|
|
22514
|
-
quota: reviveLastQuota(lq),
|
|
22515
|
-
quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
|
|
22516
|
-
expiresAtMs: acc.expiresAt,
|
|
22517
|
-
capturedAtMs: lq?.capturedAt
|
|
22518
|
-
};
|
|
22519
|
-
});
|
|
22564
|
+
walk(value);
|
|
22565
|
+
return keys;
|
|
22520
22566
|
}
|
|
22521
|
-
var
|
|
22522
|
-
var
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
healthy: "\uD83D\uDFE2",
|
|
22528
|
-
throttling: "\uD83D\uDFE1",
|
|
22529
|
-
blocked: "\uD83D\uDD34",
|
|
22530
|
-
unknown: "\u26aa"
|
|
22531
|
-
};
|
|
22532
|
-
TABLE_HEALTH_RANK = {
|
|
22533
|
-
blocked: 0,
|
|
22534
|
-
throttling: 1,
|
|
22535
|
-
unknown: 2,
|
|
22536
|
-
healthy: 3
|
|
22537
|
-
};
|
|
22567
|
+
var materializedDirs, cleanupRegistered = false, cachedRoot = null;
|
|
22568
|
+
var init_resolver = __esm(() => {
|
|
22569
|
+
init_vault();
|
|
22570
|
+
init_loader();
|
|
22571
|
+
init_client();
|
|
22572
|
+
materializedDirs = new Set;
|
|
22538
22573
|
});
|
|
22539
22574
|
|
|
22540
22575
|
// ../src/auth/broker/protocol.ts
|
|
@@ -22719,6 +22754,8 @@ var init_protocol2 = __esm(() => {
|
|
|
22719
22754
|
label: exports_external.string(),
|
|
22720
22755
|
expiresAt: exports_external.number().optional(),
|
|
22721
22756
|
exhausted: exports_external.boolean(),
|
|
22757
|
+
in_service: exports_external.boolean().optional(),
|
|
22758
|
+
entitlement_blocked: exports_external.boolean().optional(),
|
|
22722
22759
|
exhausted_until: exports_external.number().optional(),
|
|
22723
22760
|
throttled_until: exports_external.number().optional(),
|
|
22724
22761
|
threshold_violations: exports_external.number().int().nonnegative().optional(),
|
|
@@ -36788,6 +36825,12 @@ __export(exports_auth_snapshot_format2, {
|
|
|
36788
36825
|
THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT2
|
|
36789
36826
|
});
|
|
36790
36827
|
function classifyHealth2(snap, now = new Date) {
|
|
36828
|
+
if (!snap.isActive) {
|
|
36829
|
+
if (snap.entitlementBlocked === true)
|
|
36830
|
+
return "org-blocked";
|
|
36831
|
+
if (snap.inService === false)
|
|
36832
|
+
return "retired";
|
|
36833
|
+
}
|
|
36791
36834
|
if (!snap.quota)
|
|
36792
36835
|
return "unknown";
|
|
36793
36836
|
const q = snap.quota;
|
|
@@ -36880,6 +36923,12 @@ function renderAccountRow2(snap, opts) {
|
|
|
36880
36923
|
const lines = [];
|
|
36881
36924
|
const marker = snap.isActive ? "\u25cf " : "";
|
|
36882
36925
|
const label = displayLabel2(snap.label, opts);
|
|
36926
|
+
const health = classifyHealth2(snap, now);
|
|
36927
|
+
if (health === "org-blocked" || health === "retired") {
|
|
36928
|
+
const note = health === "org-blocked" ? "DISABLED (org) \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
|
|
36929
|
+
lines.push(`${marker}\`${codeSpanSafe(label)}\` _${note}_`);
|
|
36930
|
+
return lines;
|
|
36931
|
+
}
|
|
36883
36932
|
if (!snap.quota) {
|
|
36884
36933
|
lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
|
|
36885
36934
|
if (snap.quotaError) {
|
|
@@ -36896,7 +36945,6 @@ function renderAccountRow2(snap, opts) {
|
|
|
36896
36945
|
const fiveStr = fmtPct2(norm.fiveHourUtilizationPct);
|
|
36897
36946
|
const sevenStr = fmtPct2(norm.sevenDayUtilizationPct);
|
|
36898
36947
|
lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
|
|
36899
|
-
const health = classifyHealth2(snap, now);
|
|
36900
36948
|
if (health === "blocked") {
|
|
36901
36949
|
const win = bindingWindow3(q);
|
|
36902
36950
|
const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
|
|
@@ -36988,7 +37036,11 @@ function recommendation2(snapshots, now = new Date, demo = false) {
|
|
|
36988
37036
|
if (!active)
|
|
36989
37037
|
return "No active account set.";
|
|
36990
37038
|
const activeHealth = classifyHealth2(active, now);
|
|
36991
|
-
const
|
|
37039
|
+
const inServiceFleet = snapshots.filter((s) => {
|
|
37040
|
+
const h = classifyHealth2(s, now);
|
|
37041
|
+
return h !== "retired" && h !== "org-blocked";
|
|
37042
|
+
});
|
|
37043
|
+
const others = inServiceFleet.filter((s) => !s.isActive);
|
|
36992
37044
|
const healthyAlt = others.find((s) => classifyHealth2(s, now) === "healthy");
|
|
36993
37045
|
const lbl = (s) => demo ? maskEmail(s.label) : s.label;
|
|
36994
37046
|
const activeLabel = lbl(active);
|
|
@@ -37005,7 +37057,7 @@ function recommendation2(snapshots, now = new Date, demo = false) {
|
|
|
37005
37057
|
if (healthyAlt) {
|
|
37006
37058
|
return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
|
|
37007
37059
|
}
|
|
37008
|
-
return summarizeNoHealthyAlt2(
|
|
37060
|
+
return summarizeNoHealthyAlt2(inServiceFleet, now, demo);
|
|
37009
37061
|
}
|
|
37010
37062
|
return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
|
|
37011
37063
|
}
|
|
@@ -37077,7 +37129,14 @@ function renderFallbackAnnouncement2(input) {
|
|
|
37077
37129
|
if (fleet.length > 0) {
|
|
37078
37130
|
lines.push("");
|
|
37079
37131
|
const rowOpts = { now, tz };
|
|
37080
|
-
const healthOrder = [
|
|
37132
|
+
const healthOrder = [
|
|
37133
|
+
"org-blocked",
|
|
37134
|
+
"blocked",
|
|
37135
|
+
"throttling",
|
|
37136
|
+
"healthy",
|
|
37137
|
+
"unknown",
|
|
37138
|
+
"retired"
|
|
37139
|
+
];
|
|
37081
37140
|
const rank = (s) => healthOrder.indexOf(classifyHealth2(s, now));
|
|
37082
37141
|
const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
|
|
37083
37142
|
for (const snap of ordered) {
|
|
@@ -37150,7 +37209,10 @@ function buildSnapshotKeyboard2(snapshots, opts = {}) {
|
|
|
37150
37209
|
const max = opts.maxSwitchButtons ?? 3;
|
|
37151
37210
|
const now = opts.now ?? new Date;
|
|
37152
37211
|
const rows = [];
|
|
37153
|
-
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a, now) - switchPriority2(b, now)).filter((s) =>
|
|
37212
|
+
const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a, now) - switchPriority2(b, now)).filter((s) => {
|
|
37213
|
+
const h = classifyHealth2(s, now);
|
|
37214
|
+
return h !== "blocked" && h !== "unknown" && h !== "retired" && h !== "org-blocked";
|
|
37215
|
+
}).slice(0, max);
|
|
37154
37216
|
for (const t of switchTargets) {
|
|
37155
37217
|
rows.push([
|
|
37156
37218
|
{
|
|
@@ -37174,7 +37236,9 @@ function switchPriority2(s, now = new Date) {
|
|
|
37174
37236
|
return 1;
|
|
37175
37237
|
if (h === "unknown")
|
|
37176
37238
|
return 2;
|
|
37177
|
-
|
|
37239
|
+
if (h === "blocked")
|
|
37240
|
+
return 3;
|
|
37241
|
+
return 4;
|
|
37178
37242
|
}
|
|
37179
37243
|
function zipProbeResults2(labels, results) {
|
|
37180
37244
|
let staleCachedAtMs;
|
|
@@ -37206,7 +37270,9 @@ function buildSnapshotsFromState2(state7, quotas) {
|
|
|
37206
37270
|
isActive: acc.label === state7.active,
|
|
37207
37271
|
quota: q && q.ok ? q.data : null,
|
|
37208
37272
|
quotaError: q && !q.ok ? q.reason : undefined,
|
|
37209
|
-
expiresAtMs: acc.expiresAt
|
|
37273
|
+
expiresAtMs: acc.expiresAt,
|
|
37274
|
+
inService: acc.in_service,
|
|
37275
|
+
entitlementBlocked: acc.entitlement_blocked
|
|
37210
37276
|
});
|
|
37211
37277
|
}
|
|
37212
37278
|
return out;
|
|
@@ -37235,7 +37301,9 @@ function buildSnapshotsFromCachedState2(state7) {
|
|
|
37235
37301
|
quota: reviveLastQuota2(lq),
|
|
37236
37302
|
quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
|
|
37237
37303
|
expiresAtMs: acc.expiresAt,
|
|
37238
|
-
capturedAtMs: lq?.capturedAt
|
|
37304
|
+
capturedAtMs: lq?.capturedAt,
|
|
37305
|
+
inService: acc.in_service,
|
|
37306
|
+
entitlementBlocked: acc.entitlement_blocked
|
|
37239
37307
|
};
|
|
37240
37308
|
});
|
|
37241
37309
|
}
|
|
@@ -37248,13 +37316,17 @@ var init_auth_snapshot_format2 = __esm(() => {
|
|
|
37248
37316
|
healthy: "\uD83D\uDFE2",
|
|
37249
37317
|
throttling: "\uD83D\uDFE1",
|
|
37250
37318
|
blocked: "\uD83D\uDD34",
|
|
37251
|
-
unknown: "\u26aa"
|
|
37319
|
+
unknown: "\u26aa",
|
|
37320
|
+
"org-blocked": "\u26d4",
|
|
37321
|
+
retired: "\u26ab"
|
|
37252
37322
|
};
|
|
37253
37323
|
TABLE_HEALTH_RANK2 = {
|
|
37254
|
-
blocked: 0,
|
|
37255
|
-
|
|
37256
|
-
|
|
37257
|
-
|
|
37324
|
+
"org-blocked": 0,
|
|
37325
|
+
blocked: 1,
|
|
37326
|
+
throttling: 2,
|
|
37327
|
+
unknown: 3,
|
|
37328
|
+
healthy: 4,
|
|
37329
|
+
retired: 5
|
|
37258
37330
|
};
|
|
37259
37331
|
});
|
|
37260
37332
|
|
|
@@ -37861,9 +37933,13 @@ function buildBar(pct, elapsedFrac) {
|
|
|
37861
37933
|
cells[tickIndex] = "\u2503";
|
|
37862
37934
|
return cells.join("");
|
|
37863
37935
|
}
|
|
37864
|
-
function accountStatus(isActive, exhausted) {
|
|
37936
|
+
function accountStatus(isActive, exhausted, opts = {}) {
|
|
37865
37937
|
if (isActive)
|
|
37866
37938
|
return "active";
|
|
37939
|
+
if (opts.entitlementBlocked === true)
|
|
37940
|
+
return "org-disabled";
|
|
37941
|
+
if (opts.inService === false)
|
|
37942
|
+
return "retired";
|
|
37867
37943
|
if (exhausted)
|
|
37868
37944
|
return "exhausted";
|
|
37869
37945
|
return "idle";
|
|
@@ -37876,10 +37952,15 @@ function formatWindowRow(window2, pct, resetAt, now = new Date) {
|
|
|
37876
37952
|
const timeLeft = formatTimeLeft(resetAt, now);
|
|
37877
37953
|
return `- ${dot} ${window2} \`[${bar}] ${pctStr} / ${timeLeft}\``;
|
|
37878
37954
|
}
|
|
37879
|
-
function renderQuotaBarAccount(label, isActive, exhausted, quota, now = new Date, demo = false) {
|
|
37880
|
-
const status = accountStatus(isActive, exhausted);
|
|
37955
|
+
function renderQuotaBarAccount(label, isActive, exhausted, quota, now = new Date, demo = false, service = {}) {
|
|
37956
|
+
const status = accountStatus(isActive, exhausted, service);
|
|
37881
37957
|
const displayLabel3 = demo ? maskEmail(label) : label;
|
|
37882
|
-
const lines = [`- **${escapeMarkdown(displayLabel3)}** (${status})`];
|
|
37958
|
+
const lines = [`- **${escapeMarkdown(displayLabel3)}** (${STATUS_LABEL[status]})`];
|
|
37959
|
+
if (status === "retired" || status === "org-disabled") {
|
|
37960
|
+
const note = status === "org-disabled" ? "disabled by org \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
|
|
37961
|
+
lines.push(`- \u26ab \`${note}\``);
|
|
37962
|
+
return lines;
|
|
37963
|
+
}
|
|
37883
37964
|
if (!quota || isProbeThin(quota)) {
|
|
37884
37965
|
const reason = !quota ? "no data \u2014 probe failed" : "no data \u2014 thin probe";
|
|
37885
37966
|
lines.push(`- \u26a0\ufe0f 5h \`${reason}\``);
|
|
@@ -37899,9 +37980,17 @@ function renderQuotaBarBlock(snapshots, exhaustedByLabel, opts = {}) {
|
|
|
37899
37980
|
const now = opts.now ?? new Date;
|
|
37900
37981
|
const demo = opts.demo ?? false;
|
|
37901
37982
|
const lines = [];
|
|
37902
|
-
|
|
37983
|
+
const outOfService = (s) => !s.isActive && (s.entitlementBlocked === true || s.inService === false);
|
|
37984
|
+
const ordered = [
|
|
37985
|
+
...snapshots.filter((s) => !outOfService(s)),
|
|
37986
|
+
...snapshots.filter(outOfService)
|
|
37987
|
+
];
|
|
37988
|
+
for (const snap of ordered) {
|
|
37903
37989
|
const exhausted = exhaustedByLabel.get(snap.label) ?? false;
|
|
37904
|
-
lines.push(...renderQuotaBarAccount(snap.label, snap.isActive, exhausted, snap.quota, now, demo
|
|
37990
|
+
lines.push(...renderQuotaBarAccount(snap.label, snap.isActive, exhausted, snap.quota, now, demo, {
|
|
37991
|
+
inService: snap.inService,
|
|
37992
|
+
entitlementBlocked: snap.entitlementBlocked
|
|
37993
|
+
}));
|
|
37905
37994
|
}
|
|
37906
37995
|
return lines.join(`
|
|
37907
37996
|
`);
|
|
@@ -37915,7 +38004,9 @@ function renderQuotaBarBlockFromListState(state7, opts = {}) {
|
|
|
37915
38004
|
quota: reviveLastQuota(acc.last_quota ?? null),
|
|
37916
38005
|
quotaError: acc.last_quota ? undefined : "no cached quota (no probe since broker start)",
|
|
37917
38006
|
expiresAtMs: acc.expiresAt,
|
|
37918
|
-
capturedAtMs: acc.last_quota?.capturedAt
|
|
38007
|
+
capturedAtMs: acc.last_quota?.capturedAt,
|
|
38008
|
+
inService: acc.in_service,
|
|
38009
|
+
entitlementBlocked: acc.entitlement_blocked
|
|
37919
38010
|
}));
|
|
37920
38011
|
return renderQuotaBarBlock(snapshots, exhaustedByLabel, { now });
|
|
37921
38012
|
}
|
|
@@ -37940,7 +38031,7 @@ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
|
|
|
37940
38031
|
return lines.join(`
|
|
37941
38032
|
`);
|
|
37942
38033
|
}
|
|
37943
|
-
var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10;
|
|
38034
|
+
var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10, STATUS_LABEL;
|
|
37944
38035
|
var init_quota_bar_format = __esm(() => {
|
|
37945
38036
|
init_auth_snapshot_format();
|
|
37946
38037
|
init_card_format();
|
|
@@ -37948,6 +38039,13 @@ var init_quota_bar_format = __esm(() => {
|
|
|
37948
38039
|
init_external_spend2();
|
|
37949
38040
|
FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
37950
38041
|
SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
38042
|
+
STATUS_LABEL = {
|
|
38043
|
+
active: "active",
|
|
38044
|
+
"org-disabled": "DISABLED (org)",
|
|
38045
|
+
retired: "retired",
|
|
38046
|
+
exhausted: "exhausted",
|
|
38047
|
+
idle: "idle"
|
|
38048
|
+
};
|
|
37951
38049
|
});
|
|
37952
38050
|
|
|
37953
38051
|
// external-spend.ts
|
|
@@ -40267,6 +40365,23 @@ function normalizeForDedup(text) {
|
|
|
40267
40365
|
|
|
40268
40366
|
// flushed-turn-supersede.ts
|
|
40269
40367
|
var DEFAULT_SUPERSEDE_TTL_MS = 60000;
|
|
40368
|
+
var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS = 32;
|
|
40369
|
+
function normalizeForMatch(text) {
|
|
40370
|
+
return text.replace(/\s+/g, " ").trim();
|
|
40371
|
+
}
|
|
40372
|
+
function flushedAnswerMatchesReply(flushedText, replyText) {
|
|
40373
|
+
const flushed = normalizeForMatch(flushedText);
|
|
40374
|
+
const reply = normalizeForMatch(replyText);
|
|
40375
|
+
if (flushed.length === 0 || reply.length === 0)
|
|
40376
|
+
return false;
|
|
40377
|
+
if (flushed === reply)
|
|
40378
|
+
return true;
|
|
40379
|
+
if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS && flushed.includes(reply))
|
|
40380
|
+
return true;
|
|
40381
|
+
if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS && reply.includes(flushed))
|
|
40382
|
+
return true;
|
|
40383
|
+
return false;
|
|
40384
|
+
}
|
|
40270
40385
|
function decideSupersede(record, args) {
|
|
40271
40386
|
const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
|
|
40272
40387
|
if (record == null)
|
|
@@ -40278,7 +40393,15 @@ function decideSupersede(record, args) {
|
|
|
40278
40393
|
if (!sameTurn) {
|
|
40279
40394
|
return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
|
|
40280
40395
|
}
|
|
40281
|
-
|
|
40396
|
+
if (args.replyText != null && !flushedAnswerMatchesReply(record.text, args.replyText)) {
|
|
40397
|
+
return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
|
|
40398
|
+
}
|
|
40399
|
+
return {
|
|
40400
|
+
supersede: true,
|
|
40401
|
+
deleteMessageIds: [...record.messageIds],
|
|
40402
|
+
reason: "supersede",
|
|
40403
|
+
recordText: record.text
|
|
40404
|
+
};
|
|
40282
40405
|
}
|
|
40283
40406
|
var NULL_TURN_KEY = "<<null-turn>>";
|
|
40284
40407
|
function turnKey(turnId) {
|
|
@@ -40310,7 +40433,12 @@ class FlushedTurnSupersedeRegistry {
|
|
|
40310
40433
|
}
|
|
40311
40434
|
peek(chatId, threadId, args) {
|
|
40312
40435
|
const rec = this.entries.get(makeKey2(chatId, threadId))?.get(turnKey(args.liveTurnId));
|
|
40313
|
-
return decideSupersede(rec, {
|
|
40436
|
+
return decideSupersede(rec, {
|
|
40437
|
+
liveTurnId: args.liveTurnId,
|
|
40438
|
+
replyText: args.replyText,
|
|
40439
|
+
now: args.now,
|
|
40440
|
+
ttlMs: this.ttlMs
|
|
40441
|
+
});
|
|
40314
40442
|
}
|
|
40315
40443
|
take(chatId, threadId, args) {
|
|
40316
40444
|
const lane = makeKey2(chatId, threadId);
|
|
@@ -40899,6 +41027,63 @@ function buildStopReply2(turnInFlight, queuedSessionCmds) {
|
|
|
40899
41027
|
`) };
|
|
40900
41028
|
}
|
|
40901
41029
|
|
|
41030
|
+
// gateway/auth-command.ts
|
|
41031
|
+
init_format();
|
|
41032
|
+
init_auth_snapshot_format();
|
|
41033
|
+
init_demo_mask();
|
|
41034
|
+
var pendingAuthRmFlows = new Map;
|
|
41035
|
+
function isAuthAdmin(args) {
|
|
41036
|
+
return args.isAdmin === true;
|
|
41037
|
+
}
|
|
41038
|
+
var REQUIRED_USAGE_SCOPE = "user:profile";
|
|
41039
|
+
function readdPrecheckError(label, replace, labelExists) {
|
|
41040
|
+
if (replace && !labelExists) {
|
|
41041
|
+
return `**/auth readd:** no account named \`${codeSpanSafe(label)}\` to re-auth. ` + `Run \`/auth show\` for the current list, or \`/auth add ${codeSpanSafe(label)}\` to add it fresh.`;
|
|
41042
|
+
}
|
|
41043
|
+
if (!replace && labelExists) {
|
|
41044
|
+
return `**/auth add:** \`${codeSpanSafe(label)}\` already exists. ` + `Use \`/auth readd ${codeSpanSafe(label)}\` to re-authenticate it in place (e.g. to widen scope).`;
|
|
41045
|
+
}
|
|
41046
|
+
return null;
|
|
41047
|
+
}
|
|
41048
|
+
async function runReaddPrecheck(getClient, label, replace) {
|
|
41049
|
+
try {
|
|
41050
|
+
const client = await getClient();
|
|
41051
|
+
if (!client)
|
|
41052
|
+
return null;
|
|
41053
|
+
const state = await client.listState();
|
|
41054
|
+
const exists = state.accounts.some((a) => a.label === label);
|
|
41055
|
+
return readdPrecheckError(label, replace, exists);
|
|
41056
|
+
} catch {
|
|
41057
|
+
return null;
|
|
41058
|
+
}
|
|
41059
|
+
}
|
|
41060
|
+
function formatGrantedScopesReply(scopes) {
|
|
41061
|
+
const list = Array.isArray(scopes) ? scopes.filter((s) => typeof s === "string") : [];
|
|
41062
|
+
const hasUsageScope = list.includes(REQUIRED_USAGE_SCOPE);
|
|
41063
|
+
if (list.length === 0) {
|
|
41064
|
+
return {
|
|
41065
|
+
text: `
|
|
41066
|
+
\u26a0\ufe0f **No scopes reported** on the new token \u2014 could not confirm \`${REQUIRED_USAGE_SCOPE}\`. ` + `7-day usage reporting may not work. Re-run \`/auth readd\` if usage is missing.`,
|
|
41067
|
+
hasUsageScope: false
|
|
41068
|
+
};
|
|
41069
|
+
}
|
|
41070
|
+
const rendered = list.map((s) => `\`${codeSpanSafe(s)}\``).join(", ");
|
|
41071
|
+
if (hasUsageScope) {
|
|
41072
|
+
return {
|
|
41073
|
+
text: `
|
|
41074
|
+
Granted scopes: ${rendered}
|
|
41075
|
+
\u2713 \`${REQUIRED_USAGE_SCOPE}\` present \u2014 7-day usage reporting is unlocked.`,
|
|
41076
|
+
hasUsageScope: true
|
|
41077
|
+
};
|
|
41078
|
+
}
|
|
41079
|
+
return {
|
|
41080
|
+
text: `
|
|
41081
|
+
Granted scopes: ${rendered}
|
|
41082
|
+
` + `\u26a0\ufe0f **\`${REQUIRED_USAGE_SCOPE}\` is MISSING** \u2014 7-day usage reporting will not work for this account. ` + `This usually means the narrow \`setup-token\` minter was used. Re-run \`/auth readd <label>\` with the broad login to fix.`,
|
|
41083
|
+
hasUsageScope: false
|
|
41084
|
+
};
|
|
41085
|
+
}
|
|
41086
|
+
|
|
40902
41087
|
// gateway/interrupt-defer.ts
|
|
40903
41088
|
class ToolFlightTracker2 {
|
|
40904
41089
|
inFlight = new Set;
|
|
@@ -41944,11 +42129,14 @@ async function interceptAuthAdd(p, deps) {
|
|
|
41944
42129
|
deps.pendingAuthAddFlows.delete(p.interceptKey);
|
|
41945
42130
|
try {
|
|
41946
42131
|
const credentials = await deps.submitAccountAuthCode(pendingAdd, p.text.trim());
|
|
42132
|
+
const replace = pendingAdd.replace === true;
|
|
41947
42133
|
try {
|
|
41948
|
-
await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace
|
|
42134
|
+
await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace });
|
|
41949
42135
|
deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
|
|
41950
|
-
|
|
41951
|
-
|
|
42136
|
+
const scopeReply = formatGrantedScopesReply(credentials.claudeAiOauth?.scopes);
|
|
42137
|
+
const verb = replace ? "re-authenticated" : "added";
|
|
42138
|
+
await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` ${verb}.
|
|
42139
|
+
The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.` + scopeReply.text, { html: true });
|
|
41952
42140
|
} catch (brokerErr) {
|
|
41953
42141
|
deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
|
|
41954
42142
|
await deps.switchroomReply(p.ctx, `**/auth add failed at broker:** ${deps.escapeHtmlForTg(brokerErr?.message ?? String(brokerErr))}`, { html: true });
|
|
@@ -42574,11 +42762,14 @@ async function interceptAuthAdd2(p, deps) {
|
|
|
42574
42762
|
deps.pendingAuthAddFlows.delete(p.interceptKey);
|
|
42575
42763
|
try {
|
|
42576
42764
|
const credentials = await deps.submitAccountAuthCode(pendingAdd, p.text.trim());
|
|
42765
|
+
const replace = pendingAdd.replace === true;
|
|
42577
42766
|
try {
|
|
42578
|
-
await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace
|
|
42767
|
+
await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace });
|
|
42579
42768
|
deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
|
|
42580
|
-
|
|
42581
|
-
|
|
42769
|
+
const scopeReply = formatGrantedScopesReply(credentials.claudeAiOauth?.scopes);
|
|
42770
|
+
const verb = replace ? "re-authenticated" : "added";
|
|
42771
|
+
await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` ${verb}.
|
|
42772
|
+
The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.` + scopeReply.text, { html: true });
|
|
42582
42773
|
} catch (brokerErr) {
|
|
42583
42774
|
deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
|
|
42584
42775
|
await deps.switchroomReply(p.ctx, `**/auth add failed at broker:** ${deps.escapeHtmlForTg(brokerErr?.message ?? String(brokerErr))}`, { html: true });
|
|
@@ -43539,14 +43730,23 @@ function relaunchErrorReply(deps, model, err) {
|
|
|
43539
43730
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43540
43731
|
return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
|
|
43541
43732
|
}
|
|
43733
|
+
function unvalidatedIdCaveat(deps, model) {
|
|
43734
|
+
if (!model.trim().toLowerCase().startsWith("claude-"))
|
|
43735
|
+
return null;
|
|
43736
|
+
return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
|
|
43737
|
+
}
|
|
43542
43738
|
async function scheduleRelaunchReply(deps, model, reason) {
|
|
43543
43739
|
try {
|
|
43544
43740
|
await deps.scheduleModelRelaunch(model, reason);
|
|
43545
43741
|
} catch (err) {
|
|
43546
43742
|
return relaunchErrorReply(deps, model, err);
|
|
43547
43743
|
}
|
|
43548
|
-
|
|
43549
|
-
|
|
43744
|
+
const caveat = unvalidatedIdCaveat(deps, model);
|
|
43745
|
+
return {
|
|
43746
|
+
text: [switchingLine(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE].join(`
|
|
43747
|
+
`),
|
|
43748
|
+
html: true
|
|
43749
|
+
};
|
|
43550
43750
|
}
|
|
43551
43751
|
async function scheduleDefaultRelaunchReply(deps, reason) {
|
|
43552
43752
|
try {
|
|
@@ -50832,16 +51032,28 @@ function formatModelLabel2(model) {
|
|
|
50832
51032
|
}
|
|
50833
51033
|
|
|
50834
51034
|
// gateway/session-model-source.ts
|
|
50835
|
-
function createSessionModelSource() {
|
|
51035
|
+
function createSessionModelSource(options = {}) {
|
|
50836
51036
|
let seq = 0;
|
|
50837
51037
|
let transcript = null;
|
|
50838
51038
|
let override = null;
|
|
51039
|
+
let overrideUnverified = false;
|
|
51040
|
+
let onDivergence = null;
|
|
50839
51041
|
return {
|
|
50840
|
-
noteTranscriptModel(model) {
|
|
51042
|
+
noteTranscriptModel(model, opts) {
|
|
50841
51043
|
transcript = { model, seq: ++seq };
|
|
51044
|
+
if (opts?.replayed === true)
|
|
51045
|
+
return;
|
|
51046
|
+
if (override != null && overrideUnverified) {
|
|
51047
|
+
overrideUnverified = false;
|
|
51048
|
+
const matches = options.servedMatchesRequested;
|
|
51049
|
+
if (matches != null && !matches(override.model, model)) {
|
|
51050
|
+
onDivergence?.({ requested: override.model, served: model });
|
|
51051
|
+
}
|
|
51052
|
+
}
|
|
50842
51053
|
},
|
|
50843
|
-
setOverride(model) {
|
|
51054
|
+
setOverride(model, opts) {
|
|
50844
51055
|
override = model == null ? null : { model, seq: ++seq };
|
|
51056
|
+
overrideUnverified = model != null && opts?.verify === true;
|
|
50845
51057
|
},
|
|
50846
51058
|
getOverride() {
|
|
50847
51059
|
return override?.model ?? null;
|
|
@@ -50855,6 +51067,9 @@ function createSessionModelSource() {
|
|
|
50855
51067
|
return { model: override.model, source: "override" };
|
|
50856
51068
|
}
|
|
50857
51069
|
return { model: transcript.model, source: "transcript" };
|
|
51070
|
+
},
|
|
51071
|
+
setDivergenceHandler(handler) {
|
|
51072
|
+
onDivergence = handler;
|
|
50858
51073
|
}
|
|
50859
51074
|
};
|
|
50860
51075
|
}
|
|
@@ -68866,7 +69081,7 @@ init_format();
|
|
|
68866
69081
|
init_auth_snapshot_format();
|
|
68867
69082
|
init_demo_mask();
|
|
68868
69083
|
var AUTH_RM_CONFIRM_TTL_MS = 60000;
|
|
68869
|
-
var
|
|
69084
|
+
var pendingAuthRmFlows2 = new Map;
|
|
68870
69085
|
var LABEL_RE = /^[A-Za-z0-9._@+-]+$/;
|
|
68871
69086
|
var LABEL_MAX = 64;
|
|
68872
69087
|
function validateAuthAddLabel(label) {
|
|
@@ -68914,14 +69129,24 @@ function parseAuthCommand(text4) {
|
|
|
68914
69129
|
return { kind: "help", reason: "Usage: /auth use <label>" };
|
|
68915
69130
|
return { kind: "use", label };
|
|
68916
69131
|
}
|
|
68917
|
-
case "add":
|
|
68918
|
-
|
|
69132
|
+
case "add":
|
|
69133
|
+
case "readd": {
|
|
69134
|
+
const positional = parts.slice(1).filter((t) => !t.startsWith("--"));
|
|
69135
|
+
const flags = new Set(parts.slice(1).filter((t) => t.startsWith("--")).map((t) => t.toLowerCase()));
|
|
69136
|
+
const label = positional[0];
|
|
69137
|
+
const usage = verb === "readd" ? "Usage: /auth readd <label>" : "Usage: /auth add <label>";
|
|
68919
69138
|
if (!label)
|
|
68920
|
-
return { kind: "help", reason:
|
|
69139
|
+
return { kind: "help", reason: usage };
|
|
68921
69140
|
const err = validateAuthAddLabel(label);
|
|
68922
69141
|
if (err)
|
|
68923
69142
|
return { kind: "help", reason: err };
|
|
68924
|
-
|
|
69143
|
+
for (const f of flags) {
|
|
69144
|
+
if (f !== "--replace") {
|
|
69145
|
+
return { kind: "help", reason: `Unknown flag \`${codeSpanSafe(f)}\` for \`/auth ${verb}\`.` };
|
|
69146
|
+
}
|
|
69147
|
+
}
|
|
69148
|
+
const replace2 = verb === "readd" || flags.has("--replace");
|
|
69149
|
+
return { kind: "add", label, replace: replace2 };
|
|
68925
69150
|
}
|
|
68926
69151
|
case "cancel":
|
|
68927
69152
|
return { kind: "cancel" };
|
|
@@ -69041,6 +69266,7 @@ async function handleAuthCommand(parsed, ctx) {
|
|
|
69041
69266
|
` + ` \`/auth use <label>\` \u2014 admin: swap the fleet to <label>
|
|
69042
69267
|
` + ` \`/auth rotate\` \u2014 admin: cycle to next non-exhausted fallback
|
|
69043
69268
|
` + ` \`/auth add <label>\` \u2014 admin: OAuth-add a new Anthropic account from chat
|
|
69269
|
+
` + ` \`/auth readd <label>\` \u2014 admin: re-auth an EXISTING account in place (widen scope)
|
|
69044
69270
|
` + ` \`/auth google add <email>\` \u2014 admin: Telegram-native Google account add/re-auth
|
|
69045
69271
|
` + ` \`/auth microsoft add <email>\` \u2014 admin: Telegram-native Microsoft account add/re-auth
|
|
69046
69272
|
` + ` \`/auth cancel\` \u2014 abort an \`/auth add\` or provider add in progress
|
|
@@ -69195,7 +69421,7 @@ async function handleAuthCommand(parsed, ctx) {
|
|
|
69195
69421
|
};
|
|
69196
69422
|
}
|
|
69197
69423
|
if (ctx.chatId) {
|
|
69198
|
-
|
|
69424
|
+
pendingAuthRmFlows2.set(ctx.chatId, {
|
|
69199
69425
|
label: parsed.label,
|
|
69200
69426
|
expiresAt: Date.now() + AUTH_RM_CONFIRM_TTL_MS
|
|
69201
69427
|
});
|
|
@@ -69209,11 +69435,11 @@ async function handleAuthCommand(parsed, ctx) {
|
|
|
69209
69435
|
};
|
|
69210
69436
|
}
|
|
69211
69437
|
if (parsed.kind === "rm-confirmed") {
|
|
69212
|
-
const pending = ctx.chatId ?
|
|
69438
|
+
const pending = ctx.chatId ? pendingAuthRmFlows2.get(ctx.chatId) : undefined;
|
|
69213
69439
|
const now = Date.now();
|
|
69214
69440
|
if (!pending || pending.label !== parsed.label || pending.expiresAt <= now) {
|
|
69215
69441
|
if (ctx.chatId && pending && pending.expiresAt <= now) {
|
|
69216
|
-
|
|
69442
|
+
pendingAuthRmFlows2.delete(ctx.chatId);
|
|
69217
69443
|
}
|
|
69218
69444
|
return {
|
|
69219
69445
|
text: `**/auth rm:** no pending confirm for \`${codeSpanSafe(parsed.label)}\` (expired or not started). ` + `Send \`/auth rm ${codeSpanSafe(parsed.label)}\` first.`,
|
|
@@ -69221,7 +69447,7 @@ async function handleAuthCommand(parsed, ctx) {
|
|
|
69221
69447
|
};
|
|
69222
69448
|
}
|
|
69223
69449
|
if (ctx.chatId)
|
|
69224
|
-
|
|
69450
|
+
pendingAuthRmFlows2.delete(ctx.chatId);
|
|
69225
69451
|
try {
|
|
69226
69452
|
const data = await ctx.client.rmAccount(parsed.label);
|
|
69227
69453
|
return {
|
|
@@ -69311,7 +69537,7 @@ ${failures.map((f) => ` ${f}`).join(`
|
|
|
69311
69537
|
html: true
|
|
69312
69538
|
};
|
|
69313
69539
|
}
|
|
69314
|
-
function
|
|
69540
|
+
function isAuthAdmin2(args) {
|
|
69315
69541
|
return args.isAdmin === true;
|
|
69316
69542
|
}
|
|
69317
69543
|
function isAdmin(ctx) {
|
|
@@ -70395,6 +70621,12 @@ function readTokenFromCredentialsFile(credentialsFilePath) {
|
|
|
70395
70621
|
}
|
|
70396
70622
|
}
|
|
70397
70623
|
|
|
70624
|
+
// ../src/auth/via-claude.ts
|
|
70625
|
+
var PRE_PASTE_RULES = [
|
|
70626
|
+
{ name: "theme", match: /Choose.{1,30}text.{1,30}style/, keys: ["Enter"] },
|
|
70627
|
+
{ name: "login-method", match: /Select login method/, keys: ["Enter"] }
|
|
70628
|
+
];
|
|
70629
|
+
|
|
70398
70630
|
// gateway/auth-add-flow.ts
|
|
70399
70631
|
function makeAuthAddTmuxOps(tmuxBin = "tmux") {
|
|
70400
70632
|
return {
|
|
@@ -70420,6 +70652,11 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
|
|
|
70420
70652
|
stdio: ["pipe", "pipe", "pipe"]
|
|
70421
70653
|
});
|
|
70422
70654
|
},
|
|
70655
|
+
sendKey(socket, session, key) {
|
|
70656
|
+
execFileSync4(tmuxBin, ["-L", socket, "send-keys", "-t", session, key], {
|
|
70657
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
70658
|
+
});
|
|
70659
|
+
},
|
|
70423
70660
|
hasSession(socket, session) {
|
|
70424
70661
|
try {
|
|
70425
70662
|
execFileSync4(tmuxBin, ["-L", socket, "has-session", "-t", session], {
|
|
@@ -70492,10 +70729,11 @@ async function startAccountAuthSession(label, opts = {}) {
|
|
|
70492
70729
|
throw new Error('tmux supervisor required for /auth add: SWITCHROOM_TMUX_SUPERVISOR is not set to "1". ' + "Legacy pipe-based setup-token is unsupported (setup-token writes to /dev/tty, not stdout/stderr).");
|
|
70493
70730
|
}
|
|
70494
70731
|
const home2 = opts.home ?? homedir9();
|
|
70495
|
-
const urlTimeoutMs = opts.urlTimeoutMs ??
|
|
70732
|
+
const urlTimeoutMs = opts.urlTimeoutMs ?? 30000;
|
|
70496
70733
|
const agentName3 = opts.agentName ?? process.env.SWITCHROOM_AGENT_NAME ?? "gateway";
|
|
70497
70734
|
const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
|
|
70498
70735
|
const binary = opts.claudeBinary ?? "claude";
|
|
70736
|
+
const mode = opts.mode ?? "via-claude";
|
|
70499
70737
|
const scratchDir = pickScratchDir(label, home2);
|
|
70500
70738
|
mkdirSync23(scratchDir, { recursive: true, mode: 448 });
|
|
70501
70739
|
sweepOrphanSessions(home2, tmux);
|
|
@@ -70516,18 +70754,21 @@ ${tmuxSession}`, "utf8");
|
|
|
70516
70754
|
sessionEnv["CLAUDE_CONFIG_DIR"] = scratchDir;
|
|
70517
70755
|
if (process.env.XDG_CONFIG_HOME)
|
|
70518
70756
|
sessionEnv["XDG_CONFIG_HOME"] = process.env.XDG_CONFIG_HOME;
|
|
70757
|
+
const sessionCmd = mode === "via-claude" ? binary : binary + " setup-token";
|
|
70758
|
+
const minterLabel = mode === "via-claude" ? "claude login" : "claude setup-token";
|
|
70519
70759
|
try {
|
|
70520
|
-
tmux.newSession(tmuxSocket, tmuxSession, sessionEnv,
|
|
70760
|
+
tmux.newSession(tmuxSocket, tmuxSession, sessionEnv, sessionCmd);
|
|
70521
70761
|
} catch (err) {
|
|
70522
70762
|
cleanScratchDir(scratchDir);
|
|
70523
|
-
throw new Error(`Failed to start tmux session for
|
|
70763
|
+
throw new Error(`Failed to start tmux session for ${minterLabel}: ${err.message}`);
|
|
70524
70764
|
}
|
|
70765
|
+
const preFired = new Set;
|
|
70525
70766
|
const loginUrl = await new Promise((resolve7, reject) => {
|
|
70526
70767
|
const deadline = setTimeout(() => {
|
|
70527
70768
|
clearInterval(ticker);
|
|
70528
70769
|
tmux.killSession(tmuxSocket, tmuxSession);
|
|
70529
70770
|
cleanScratchDir(scratchDir);
|
|
70530
|
-
reject(new Error(
|
|
70771
|
+
reject(new Error(`${minterLabel} did not print an OAuth URL within ${urlTimeoutMs}ms`));
|
|
70531
70772
|
}, urlTimeoutMs);
|
|
70532
70773
|
const ticker = setInterval(() => {
|
|
70533
70774
|
const pane = tmux.capture(tmuxSocket, tmuxSession);
|
|
@@ -70535,9 +70776,20 @@ ${tmuxSession}`, "utf8");
|
|
|
70535
70776
|
clearTimeout(deadline);
|
|
70536
70777
|
clearInterval(ticker);
|
|
70537
70778
|
cleanScratchDir(scratchDir);
|
|
70538
|
-
reject(new Error(
|
|
70779
|
+
reject(new Error(`${minterLabel} exited before printing OAuth URL`));
|
|
70539
70780
|
return;
|
|
70540
70781
|
}
|
|
70782
|
+
if (mode === "via-claude") {
|
|
70783
|
+
for (const rule of PRE_PASTE_RULES) {
|
|
70784
|
+
if (preFired.has(rule.name))
|
|
70785
|
+
continue;
|
|
70786
|
+
if (rule.match.test(pane)) {
|
|
70787
|
+
preFired.add(rule.name);
|
|
70788
|
+
for (const key of rule.keys)
|
|
70789
|
+
tmux.sendKey(tmuxSocket, tmuxSession, key);
|
|
70790
|
+
}
|
|
70791
|
+
}
|
|
70792
|
+
}
|
|
70541
70793
|
const url = parseSetupTokenUrl(pane);
|
|
70542
70794
|
if (url) {
|
|
70543
70795
|
clearTimeout(deadline);
|
|
@@ -70546,12 +70798,14 @@ ${tmuxSession}`, "utf8");
|
|
|
70546
70798
|
}
|
|
70547
70799
|
}, 500);
|
|
70548
70800
|
});
|
|
70549
|
-
return { loginUrl, scratchDir, tmuxSocket, tmuxSession };
|
|
70801
|
+
return { loginUrl, scratchDir, tmuxSocket, tmuxSession, mode };
|
|
70550
70802
|
}
|
|
70551
70803
|
async function submitAccountAuthCode(flow3, code2, opts = {}) {
|
|
70552
70804
|
const pollIntervalMs = opts.pollIntervalMs ?? 250;
|
|
70553
70805
|
const pollTimeoutMs = opts.pollTimeoutMs ?? 300000;
|
|
70554
70806
|
const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps();
|
|
70807
|
+
const mode = flow3.mode ?? "via-claude";
|
|
70808
|
+
const blindEnterDelaysMs = opts.blindEnterDelaysMs ?? (mode === "via-claude" ? [1500, 3000, 5000] : []);
|
|
70555
70809
|
const credentialsPath = join26(flow3.scratchDir, ".credentials.json");
|
|
70556
70810
|
try {
|
|
70557
70811
|
tmux.send(flow3.tmuxSocket, flow3.tmuxSession, code2);
|
|
@@ -70559,9 +70813,19 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
|
|
|
70559
70813
|
cleanScratchDir(flow3.scratchDir);
|
|
70560
70814
|
throw new Error(`Failed to submit auth code to tmux session: ${err.message}`);
|
|
70561
70815
|
}
|
|
70816
|
+
const pasteAt = Date.now();
|
|
70817
|
+
const blindEnters = blindEnterDelaysMs.map((d) => ({ at: pasteAt + d, fired: false }));
|
|
70562
70818
|
const deadline = Date.now() + pollTimeoutMs;
|
|
70563
70819
|
while (Date.now() < deadline) {
|
|
70564
70820
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
70821
|
+
for (const be of blindEnters) {
|
|
70822
|
+
if (!be.fired && Date.now() >= be.at) {
|
|
70823
|
+
be.fired = true;
|
|
70824
|
+
try {
|
|
70825
|
+
tmux.sendKey(flow3.tmuxSocket, flow3.tmuxSession, "Enter");
|
|
70826
|
+
} catch {}
|
|
70827
|
+
}
|
|
70828
|
+
}
|
|
70565
70829
|
if (existsSync22(credentialsPath)) {
|
|
70566
70830
|
const token = readTokenFromCredentialsFile(credentialsPath);
|
|
70567
70831
|
if (token) {
|
|
@@ -70591,6 +70855,59 @@ function cancelAccountAuthSession(flow3, tmuxOps) {
|
|
|
70591
70855
|
tmux.killSession(flow3.tmuxSocket, flow3.tmuxSession);
|
|
70592
70856
|
cleanScratchDir(flow3.scratchDir);
|
|
70593
70857
|
}
|
|
70858
|
+
async function handleAuthAddOrCancel(opts) {
|
|
70859
|
+
const { parsed, isAdmin: isAdmin2, currentAgent, chatId, threadId, reply, escapeHtml: escapeHtml5 } = opts;
|
|
70860
|
+
if (!isAuthAdmin({ isAdmin: isAdmin2 })) {
|
|
70861
|
+
await reply(`**Not authorized.** \`/auth ${parsed.kind}\` is admin-only.
|
|
70862
|
+
` + `Set \`admin: true\` on this agent in switchroom.yaml to unlock ` + `(the same flag that gates \`/agents\`, \`/restart\`, ` + `\`/update\` etc.).`);
|
|
70863
|
+
return;
|
|
70864
|
+
}
|
|
70865
|
+
const authAddKey = chatKey(chatId, threadId);
|
|
70866
|
+
if (parsed.kind === "cancel") {
|
|
70867
|
+
const existing = pendingAuthAddFlows.get(authAddKey);
|
|
70868
|
+
if (!existing) {
|
|
70869
|
+
await reply("_No pending `/auth add` flow in this chat._");
|
|
70870
|
+
return;
|
|
70871
|
+
}
|
|
70872
|
+
cancelAccountAuthSession(existing);
|
|
70873
|
+
pendingAuthAddFlows.delete(authAddKey);
|
|
70874
|
+
await reply("Cancelled.");
|
|
70875
|
+
return;
|
|
70876
|
+
}
|
|
70877
|
+
if (pendingAuthAddFlows.has(authAddKey)) {
|
|
70878
|
+
await reply("_An `/auth add` flow is already in progress for this chat. " + "Finish the paste, or send `/auth cancel` to abort._");
|
|
70879
|
+
return;
|
|
70880
|
+
}
|
|
70881
|
+
const precheckErr = await runReaddPrecheck(() => getAuthBrokerClient(currentAgent), parsed.label, parsed.replace);
|
|
70882
|
+
if (precheckErr) {
|
|
70883
|
+
await reply(precheckErr);
|
|
70884
|
+
return;
|
|
70885
|
+
}
|
|
70886
|
+
try {
|
|
70887
|
+
const { loginUrl, scratchDir, tmuxSocket, tmuxSession, mode } = await startAccountAuthSession(parsed.label);
|
|
70888
|
+
pendingAuthAddFlows.set(authAddKey, {
|
|
70889
|
+
label: parsed.label,
|
|
70890
|
+
scratchDir,
|
|
70891
|
+
tmuxSocket,
|
|
70892
|
+
tmuxSession,
|
|
70893
|
+
startedAt: Date.now(),
|
|
70894
|
+
replace: parsed.replace,
|
|
70895
|
+
mode
|
|
70896
|
+
});
|
|
70897
|
+
const verbNoun = parsed.replace ? "Re-authenticating account" : "Adding account";
|
|
70898
|
+
await reply(`**${verbNoun}** \`${parsed.label}\`
|
|
70899
|
+
|
|
70900
|
+
` + `1. Open this URL on your phone:
|
|
70901
|
+
${loginUrl}
|
|
70902
|
+
|
|
70903
|
+
` + `2. Log into Anthropic, copy the code Claude shows.
|
|
70904
|
+
` + `3. Paste it back here.
|
|
70905
|
+
|
|
70906
|
+
` + `Send \`/auth cancel\` to abort.`);
|
|
70907
|
+
} catch (err) {
|
|
70908
|
+
await reply(`**/auth ${parsed.replace ? "readd" : "add"} failed:** ${escapeHtml5(err?.message ?? String(err))}`);
|
|
70909
|
+
}
|
|
70910
|
+
}
|
|
70594
70911
|
|
|
70595
70912
|
// gateway/auth-loopback-relay.ts
|
|
70596
70913
|
import { spawn } from "node:child_process";
|
|
@@ -73430,6 +73747,23 @@ ${input.newReplyText}`;
|
|
|
73430
73747
|
|
|
73431
73748
|
// flushed-turn-supersede.ts
|
|
73432
73749
|
var DEFAULT_SUPERSEDE_TTL_MS2 = 60000;
|
|
73750
|
+
var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
|
|
73751
|
+
function normalizeForMatch2(text4) {
|
|
73752
|
+
return text4.replace(/\s+/g, " ").trim();
|
|
73753
|
+
}
|
|
73754
|
+
function flushedAnswerMatchesReply2(flushedText, replyText) {
|
|
73755
|
+
const flushed = normalizeForMatch2(flushedText);
|
|
73756
|
+
const reply = normalizeForMatch2(replyText);
|
|
73757
|
+
if (flushed.length === 0 || reply.length === 0)
|
|
73758
|
+
return false;
|
|
73759
|
+
if (flushed === reply)
|
|
73760
|
+
return true;
|
|
73761
|
+
if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
|
|
73762
|
+
return true;
|
|
73763
|
+
if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
|
|
73764
|
+
return true;
|
|
73765
|
+
return false;
|
|
73766
|
+
}
|
|
73433
73767
|
function decideSupersede2(record, args) {
|
|
73434
73768
|
const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
|
|
73435
73769
|
if (record == null)
|
|
@@ -73441,7 +73775,15 @@ function decideSupersede2(record, args) {
|
|
|
73441
73775
|
if (!sameTurn) {
|
|
73442
73776
|
return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
|
|
73443
73777
|
}
|
|
73444
|
-
|
|
73778
|
+
if (args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
|
|
73779
|
+
return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
|
|
73780
|
+
}
|
|
73781
|
+
return {
|
|
73782
|
+
supersede: true,
|
|
73783
|
+
deleteMessageIds: [...record.messageIds],
|
|
73784
|
+
reason: "supersede",
|
|
73785
|
+
recordText: record.text
|
|
73786
|
+
};
|
|
73445
73787
|
}
|
|
73446
73788
|
function decideSupersedeCorrection(input) {
|
|
73447
73789
|
const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
|
|
@@ -73480,7 +73822,12 @@ class FlushedTurnSupersedeRegistry2 {
|
|
|
73480
73822
|
}
|
|
73481
73823
|
peek(chatId, threadId, args) {
|
|
73482
73824
|
const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
|
|
73483
|
-
return decideSupersede2(rec, {
|
|
73825
|
+
return decideSupersede2(rec, {
|
|
73826
|
+
liveTurnId: args.liveTurnId,
|
|
73827
|
+
replyText: args.replyText,
|
|
73828
|
+
now: args.now,
|
|
73829
|
+
ttlMs: this.ttlMs
|
|
73830
|
+
});
|
|
73484
73831
|
}
|
|
73485
73832
|
take(chatId, threadId, args) {
|
|
73486
73833
|
const lane = makeKey3(chatId, threadId);
|
|
@@ -73529,7 +73876,9 @@ function decideAnswerLatchSuppression(input) {
|
|
|
73529
73876
|
return false;
|
|
73530
73877
|
if (!input.isLateReply)
|
|
73531
73878
|
return false;
|
|
73532
|
-
|
|
73879
|
+
if (input.replyMatchesFlushedAnswer === false)
|
|
73880
|
+
return false;
|
|
73881
|
+
return input.ownerAnswerDelivered === "flush";
|
|
73533
73882
|
}
|
|
73534
73883
|
|
|
73535
73884
|
// telegraph.ts
|
|
@@ -74130,31 +74479,40 @@ async function sendReply(deps, req) {
|
|
|
74130
74479
|
const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
74131
74480
|
const ownerTurn = resolveReplyOwnerTurn(turn, chat_id, args);
|
|
74132
74481
|
const resolvedTurnId = ownerTurn?.turnId ?? null;
|
|
74133
|
-
const decision = flushedTurnSupersede.take(chat_id, replyThreadId, { liveTurnId: resolvedTurnId, now: Date.now() });
|
|
74482
|
+
const decision = flushedTurnSupersede.take(chat_id, replyThreadId, { liveTurnId: resolvedTurnId, replyText: text4, now: Date.now() });
|
|
74134
74483
|
if (decision.supersede) {
|
|
74135
74484
|
process.stderr.write(`telegram gateway: reply: superseding flushed turn message(s) ` + `chatId=${chat_id} ids=${JSON.stringify(decision.deleteMessageIds)}
|
|
74136
74485
|
`);
|
|
74137
74486
|
supersedeFlushIds = decision.deleteMessageIds;
|
|
74138
|
-
if (ownerTurn != null)
|
|
74139
|
-
ownerTurn.answerDelivered =
|
|
74487
|
+
if (ownerTurn != null) {
|
|
74488
|
+
ownerTurn.answerDelivered = "flush";
|
|
74489
|
+
if (decision.recordText != null)
|
|
74490
|
+
ownerTurn.flushedAnswerText = decision.recordText;
|
|
74491
|
+
}
|
|
74140
74492
|
} else {
|
|
74141
74493
|
const replySubstantive = isSubstantiveFinalReply({
|
|
74142
74494
|
text: rawText,
|
|
74143
74495
|
disableNotification: args.disable_notification === true
|
|
74144
74496
|
});
|
|
74497
|
+
const replyMatchesFlushedAnswer = decision.reason === "new-content" ? false : ownerTurn?.flushedAnswerText != null ? flushedAnswerMatchesReply2(ownerTurn.flushedAnswerText, text4) : null;
|
|
74145
74498
|
const suppressByLatch = decideAnswerLatchSuppression({
|
|
74146
74499
|
superseded: false,
|
|
74147
74500
|
replySubstantive,
|
|
74148
74501
|
isLateReply: turn == null,
|
|
74149
|
-
ownerAnswerDelivered: ownerTurn?.answerDelivered ?? false
|
|
74502
|
+
ownerAnswerDelivered: ownerTurn?.answerDelivered ?? false,
|
|
74503
|
+
replyMatchesFlushedAnswer
|
|
74150
74504
|
});
|
|
74505
|
+
if (decision.reason === "new-content") {
|
|
74506
|
+
process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)}; sending fresh
|
|
74507
|
+
`);
|
|
74508
|
+
}
|
|
74151
74509
|
if (suppressByLatch) {
|
|
74152
74510
|
process.stderr.write(`telegram gateway: reply: suppressed by answer-delivered latch ` + `(flush already delivered this turn's answer) chatId=${chat_id} ` + `ownerTurnId=${JSON.stringify(resolvedTurnId)}
|
|
74153
74511
|
`);
|
|
74154
74512
|
return { content: [{ type: "text", text: "sent (deduped \u2014 answer already delivered via turn-flush)" }] };
|
|
74155
74513
|
}
|
|
74156
74514
|
if (replySubstantive && ownerTurn != null) {
|
|
74157
|
-
ownerTurn.answerDelivered =
|
|
74515
|
+
ownerTurn.answerDelivered = "reply";
|
|
74158
74516
|
}
|
|
74159
74517
|
}
|
|
74160
74518
|
}
|
|
@@ -76129,6 +76487,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
76129
76487
|
finalAnswerSubstantive: false,
|
|
76130
76488
|
finalAnswerEverDelivered: false,
|
|
76131
76489
|
answerDelivered: false,
|
|
76490
|
+
flushedAnswerText: null,
|
|
76132
76491
|
endedAt: null,
|
|
76133
76492
|
firstPingAt: null,
|
|
76134
76493
|
firstPingWasSubstantive: false,
|
|
@@ -76228,7 +76587,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
76228
76587
|
if (turn != null) {
|
|
76229
76588
|
turn.currentModel = ev.model;
|
|
76230
76589
|
}
|
|
76231
|
-
sessionModelSource.noteTranscriptModel(ev.model);
|
|
76590
|
+
sessionModelSource.noteTranscriptModel(ev.model, { replayed: ev.replayed === true });
|
|
76232
76591
|
return;
|
|
76233
76592
|
}
|
|
76234
76593
|
case "usage": {
|
|
@@ -76634,7 +76993,8 @@ function handleSessionEvent(deps, ev) {
|
|
|
76634
76993
|
}
|
|
76635
76994
|
turn.finalAnswerDelivered = true;
|
|
76636
76995
|
turn.finalAnswerSubstantive = true;
|
|
76637
|
-
turn.answerDelivered =
|
|
76996
|
+
turn.answerDelivered = "flush";
|
|
76997
|
+
turn.flushedAnswerText = capturedText;
|
|
76638
76998
|
const backstopLatchClaimed = backstopDeliveryLedger.claim(turn.turnId);
|
|
76639
76999
|
const cardTakeover = progressDriver?.takeOverCard({
|
|
76640
77000
|
chatId: backstopChatId,
|
|
@@ -76706,6 +77066,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
76706
77066
|
backstopCtrl.finalize("error");
|
|
76707
77067
|
backstopDeliveryLedger.release(turn.turnId);
|
|
76708
77068
|
turn.answerDelivered = false;
|
|
77069
|
+
turn.flushedAnswerText = null;
|
|
76709
77070
|
} else if (backstopCtrl) {
|
|
76710
77071
|
backstopCtrl.finalize("done");
|
|
76711
77072
|
}
|
|
@@ -76723,6 +77084,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
76723
77084
|
`);
|
|
76724
77085
|
if (!delivered) {
|
|
76725
77086
|
turn.answerDelivered = false;
|
|
77087
|
+
turn.flushedAnswerText = null;
|
|
76726
77088
|
backstopDeliveryLedger.release(turn.turnId);
|
|
76727
77089
|
if (backstopCtrl)
|
|
76728
77090
|
backstopCtrl.finalize("error");
|
|
@@ -79481,6 +79843,69 @@ function formatModelRelaunchSuppressNotAppliedLog(input) {
|
|
|
79481
79843
|
return "telegram gateway: gw /model relaunch NOT-APPLIED \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=" + input.agent + " target=" + input.target + `
|
|
79482
79844
|
`;
|
|
79483
79845
|
}
|
|
79846
|
+
function resolveModelSwitchBootNotice(input) {
|
|
79847
|
+
const { agent, confirmation, hasSessionModelAlert } = input;
|
|
79848
|
+
if (confirmation.kind === "not-applied" && hasSessionModelAlert) {
|
|
79849
|
+
return {
|
|
79850
|
+
kind: "suppress",
|
|
79851
|
+
log: formatModelRelaunchSuppressNotAppliedLog({ agent, target: confirmation.target })
|
|
79852
|
+
};
|
|
79853
|
+
}
|
|
79854
|
+
return { kind: "card", body: formatModelSwitchConfirmationBody(confirmation) };
|
|
79855
|
+
}
|
|
79856
|
+
function servedModelMatchesRequested(requested, served) {
|
|
79857
|
+
const req = requested.trim().toLowerCase();
|
|
79858
|
+
const srv = served.trim().toLowerCase();
|
|
79859
|
+
if (!srv.startsWith("claude-"))
|
|
79860
|
+
return true;
|
|
79861
|
+
if (MODEL_ALIASES2.includes(req)) {
|
|
79862
|
+
if (req === "default")
|
|
79863
|
+
return true;
|
|
79864
|
+
if (modelFamilyToken(srv) === req)
|
|
79865
|
+
return true;
|
|
79866
|
+
return srv.slice("claude-".length).split("-").includes(req);
|
|
79867
|
+
}
|
|
79868
|
+
if (req.startsWith("claude-")) {
|
|
79869
|
+
return srv === req || srv.startsWith(req + "-");
|
|
79870
|
+
}
|
|
79871
|
+
return true;
|
|
79872
|
+
}
|
|
79873
|
+
function formatServedModelDivergenceLog(input) {
|
|
79874
|
+
return "telegram gateway: gw /model served-model DIVERGENCE agent=" + input.agent + " requested=" + input.requested + " served=" + input.served + ` (--fallback-model substituted: requested id invalid/unknown OR model transiently unavailable)
|
|
79875
|
+
`;
|
|
79876
|
+
}
|
|
79877
|
+
function formatServedModelDivergenceCard(input) {
|
|
79878
|
+
return "\u26a0\ufe0f The first reply was served by `" + input.served + "`, not the requested `" + input.requested + "` \u2014 claude substituted the fallback model. Either the requested id is invalid/unknown, or the model was temporarily unavailable for that call (a transient substitution self-corrects on later replies). If it persists, re-issue `/model <valid id>` or `/model default`. `/status` always shows the model actually serving calls.";
|
|
79879
|
+
}
|
|
79880
|
+
function sendModelBootCard(deps, chat, body, failLabel) {
|
|
79881
|
+
deps.sendCard(chat.chatId, body, {
|
|
79882
|
+
parse_mode: "Markdown",
|
|
79883
|
+
...chat.threadId != null ? { message_thread_id: chat.threadId } : {}
|
|
79884
|
+
}).catch((err) => deps.log(`telegram gateway: ${failLabel} send failed: ${err?.message ?? String(err)}
|
|
79885
|
+
`));
|
|
79886
|
+
}
|
|
79887
|
+
function buildServedModelDivergenceHandler(deps) {
|
|
79888
|
+
return (d) => {
|
|
79889
|
+
deps.log(formatServedModelDivergenceLog({ agent: deps.agent, requested: d.requested, served: d.served }));
|
|
79890
|
+
if (deps.chat == null)
|
|
79891
|
+
return;
|
|
79892
|
+
sendModelBootCard(deps, deps.chat, formatServedModelDivergenceCard(d), "served-model divergence");
|
|
79893
|
+
};
|
|
79894
|
+
}
|
|
79895
|
+
function deliverModelSwitchBootNotice(deps) {
|
|
79896
|
+
if (deps.chat == null)
|
|
79897
|
+
return;
|
|
79898
|
+
const notice = resolveModelSwitchBootNotice({
|
|
79899
|
+
agent: deps.agent,
|
|
79900
|
+
confirmation: deps.confirmation,
|
|
79901
|
+
hasSessionModelAlert: deps.hasSessionModelAlert
|
|
79902
|
+
});
|
|
79903
|
+
if (notice.kind === "suppress") {
|
|
79904
|
+
deps.log(notice.log);
|
|
79905
|
+
return;
|
|
79906
|
+
}
|
|
79907
|
+
sendModelBootCard(deps, deps.chat, notice.body, "model-switch confirmation");
|
|
79908
|
+
}
|
|
79484
79909
|
function resolveStaleAwareBusy(input) {
|
|
79485
79910
|
const turnStale = input.currentTurnActive && input.turnAgeMs !== null && input.turnAgeMs > input.hardTtlMs;
|
|
79486
79911
|
const approvalLive = input.oldestPendingApprovalAgeMs !== null && input.oldestPendingApprovalAgeMs <= input.hardTtlMs;
|
|
@@ -79549,14 +79974,23 @@ function relaunchErrorReply2(deps, model, err) {
|
|
|
79549
79974
|
const msg = err instanceof Error ? err.message : String(err);
|
|
79550
79975
|
return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
|
|
79551
79976
|
}
|
|
79977
|
+
function unvalidatedIdCaveat2(deps, model) {
|
|
79978
|
+
if (!model.trim().toLowerCase().startsWith("claude-"))
|
|
79979
|
+
return null;
|
|
79980
|
+
return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
|
|
79981
|
+
}
|
|
79552
79982
|
async function scheduleRelaunchReply2(deps, model, reason) {
|
|
79553
79983
|
try {
|
|
79554
79984
|
await deps.scheduleModelRelaunch(model, reason);
|
|
79555
79985
|
} catch (err) {
|
|
79556
79986
|
return relaunchErrorReply2(deps, model, err);
|
|
79557
79987
|
}
|
|
79558
|
-
|
|
79559
|
-
|
|
79988
|
+
const caveat = unvalidatedIdCaveat2(deps, model);
|
|
79989
|
+
return {
|
|
79990
|
+
text: [switchingLine2(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE3].join(`
|
|
79991
|
+
`),
|
|
79992
|
+
html: true
|
|
79993
|
+
};
|
|
79560
79994
|
}
|
|
79561
79995
|
async function scheduleDefaultRelaunchReply2(deps, reason) {
|
|
79562
79996
|
try {
|
|
@@ -92701,10 +93135,10 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
|
|
|
92701
93135
|
}
|
|
92702
93136
|
|
|
92703
93137
|
// ../src/build-info.ts
|
|
92704
|
-
var VERSION = "0.19.
|
|
92705
|
-
var COMMIT_SHA = "
|
|
92706
|
-
var COMMIT_DATE = "2026-07-
|
|
92707
|
-
var LATEST_PR =
|
|
93138
|
+
var VERSION = "0.19.3";
|
|
93139
|
+
var COMMIT_SHA = "41896be4";
|
|
93140
|
+
var COMMIT_DATE = "2026-07-20T10:37:39+10:00";
|
|
93141
|
+
var LATEST_PR = 3456;
|
|
92708
93142
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
92709
93143
|
|
|
92710
93144
|
// gateway/boot-version.ts
|
|
@@ -95522,7 +95956,7 @@ var progressUpdateLastSent = new Map;
|
|
|
95522
95956
|
var progressUpdateTurnCount = new Map;
|
|
95523
95957
|
var currentTurn = null;
|
|
95524
95958
|
var currentTurnMap = new CurrentTurnMap;
|
|
95525
|
-
var sessionModelSource = createSessionModelSource();
|
|
95959
|
+
var sessionModelSource = createSessionModelSource({ servedMatchesRequested: servedModelMatchesRequested });
|
|
95526
95960
|
var lastActiveTurnChatId;
|
|
95527
95961
|
function setCurrentTurn(turn, key) {
|
|
95528
95962
|
currentTurnMap.set(turn, key);
|
|
@@ -97314,9 +97748,9 @@ var pendingStateReaper = isGatewayMain ? setInterval(() => {
|
|
|
97314
97748
|
if (now - v > 60000)
|
|
97315
97749
|
lastAuthRefreshAtMs.delete(k);
|
|
97316
97750
|
}
|
|
97317
|
-
for (const [k, v] of
|
|
97751
|
+
for (const [k, v] of pendingAuthRmFlows2) {
|
|
97318
97752
|
if (now >= v.expiresAt)
|
|
97319
|
-
|
|
97753
|
+
pendingAuthRmFlows2.delete(k);
|
|
97320
97754
|
}
|
|
97321
97755
|
pendingVaultOps.sweep(now);
|
|
97322
97756
|
sweepPermissionTtl({
|
|
@@ -104160,7 +104594,7 @@ The gateway will restart as part of the recreate step; watch for the post-restar
|
|
|
104160
104594
|
const me = cfg?.agents?.[getMyAgentName()];
|
|
104161
104595
|
isAdmin2 = me?.admin === true || me?.root === true;
|
|
104162
104596
|
} catch {}
|
|
104163
|
-
if (!
|
|
104597
|
+
if (!isAuthAdmin2({ isAdmin: isAdmin2 })) {
|
|
104164
104598
|
await switchroomReply(ctx, `**Not authorized.** \`/connect\` requires this agent to have \`admin: true\` in switchroom.yaml.`, { html: true });
|
|
104165
104599
|
return;
|
|
104166
104600
|
}
|
|
@@ -104251,52 +104685,19 @@ ${appNote}`), {
|
|
|
104251
104685
|
} catch {}
|
|
104252
104686
|
const chatId = String(ctx.chat?.id ?? "");
|
|
104253
104687
|
if (parsed.kind === "add" || parsed.kind === "cancel") {
|
|
104254
|
-
|
|
104255
|
-
|
|
104256
|
-
|
|
104257
|
-
|
|
104258
|
-
|
|
104259
|
-
|
|
104260
|
-
|
|
104261
|
-
|
|
104262
|
-
|
|
104263
|
-
await switchroomReply(ctx, "_No pending `/auth add` flow in this chat._", { html: true });
|
|
104264
|
-
return;
|
|
104265
|
-
}
|
|
104266
|
-
cancelAccountAuthSession(existing);
|
|
104267
|
-
pendingAuthAddFlows.delete(authAddKey);
|
|
104268
|
-
await switchroomReply(ctx, "Cancelled.", { html: true });
|
|
104269
|
-
return;
|
|
104270
|
-
}
|
|
104271
|
-
if (pendingAuthAddFlows.has(authAddKey)) {
|
|
104272
|
-
await switchroomReply(ctx, "_An `/auth add` flow is already in progress for this chat. Finish the paste, or send `/auth cancel` to abort._", { html: true });
|
|
104273
|
-
return;
|
|
104274
|
-
}
|
|
104275
|
-
try {
|
|
104276
|
-
const { loginUrl, scratchDir, tmuxSocket, tmuxSession } = await startAccountAuthSession(parsed.label);
|
|
104277
|
-
pendingAuthAddFlows.set(authAddKey, {
|
|
104278
|
-
label: parsed.label,
|
|
104279
|
-
scratchDir,
|
|
104280
|
-
tmuxSocket,
|
|
104281
|
-
tmuxSession,
|
|
104282
|
-
startedAt: Date.now()
|
|
104283
|
-
});
|
|
104284
|
-
await switchroomReply(ctx, `**Adding account** \`${parsed.label}\`
|
|
104285
|
-
|
|
104286
|
-
1. Open this URL on your phone:
|
|
104287
|
-
${loginUrl}
|
|
104288
|
-
|
|
104289
|
-
2. Log into Anthropic, copy the code Claude shows.
|
|
104290
|
-
3. Paste it back here.
|
|
104291
|
-
|
|
104292
|
-
Send \`/auth cancel\` to abort.`, { html: true });
|
|
104293
|
-
} catch (err) {
|
|
104294
|
-
await switchroomReply(ctx, `**/auth add failed:** ${escapeHtmlForTg2(err?.message ?? String(err))}`, { html: true });
|
|
104295
|
-
}
|
|
104688
|
+
await handleAuthAddOrCancel({
|
|
104689
|
+
parsed,
|
|
104690
|
+
isAdmin: isAdmin2,
|
|
104691
|
+
currentAgent,
|
|
104692
|
+
chatId,
|
|
104693
|
+
threadId: ctx.message?.message_thread_id ?? null,
|
|
104694
|
+
reply: (text6) => switchroomReply(ctx, text6, { html: true }),
|
|
104695
|
+
escapeHtml: escapeHtmlForTg2
|
|
104696
|
+
});
|
|
104296
104697
|
return;
|
|
104297
104698
|
}
|
|
104298
104699
|
if (parsed.kind === "provider-add" || parsed.kind === "provider-cancel") {
|
|
104299
|
-
if (!
|
|
104700
|
+
if (!isAuthAdmin2({ isAdmin: isAdmin2 })) {
|
|
104300
104701
|
await switchroomReply(ctx, `**Not authorized.** \`/auth ${parsed.provider}\` is admin-only.
|
|
104301
104702
|
Set \`admin: true\` on this agent in switchroom.yaml to unlock.`, { html: true });
|
|
104302
104703
|
return;
|
|
@@ -106080,7 +106481,16 @@ async function startGateway() {
|
|
|
106080
106481
|
return resolveMainModel(raw ?? undefined);
|
|
106081
106482
|
})();
|
|
106082
106483
|
const isApplyBoot = launched.length > 0 && launched !== configured;
|
|
106083
|
-
sessionModelSource.setOverride(isApplyBoot ? launched : null);
|
|
106484
|
+
sessionModelSource.setOverride(isApplyBoot ? launched : null, { verify: true });
|
|
106485
|
+
const modelBootCardDeps = {
|
|
106486
|
+
agent: getMyAgentName(),
|
|
106487
|
+
chat: modelSwitchMarkerChat,
|
|
106488
|
+
log: (line) => process.stderr.write(line),
|
|
106489
|
+
sendCard: (chatId, body, opts) => lockedBot.api.sendMessage(chatId, body, opts)
|
|
106490
|
+
};
|
|
106491
|
+
if (isApplyBoot) {
|
|
106492
|
+
sessionModelSource.setDivergenceHandler(buildServedModelDivergenceHandler(modelBootCardDeps));
|
|
106493
|
+
}
|
|
106084
106494
|
const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
|
|
106085
106495
|
reason: modelSwitchReason,
|
|
106086
106496
|
launched,
|
|
@@ -106093,22 +106503,12 @@ async function startGateway() {
|
|
|
106093
106503
|
confirmation,
|
|
106094
106504
|
isApplyBoot
|
|
106095
106505
|
}));
|
|
106096
|
-
if (confirmation != null
|
|
106097
|
-
|
|
106098
|
-
|
|
106099
|
-
|
|
106100
|
-
|
|
106101
|
-
|
|
106102
|
-
target: confirmation.target
|
|
106103
|
-
}));
|
|
106104
|
-
} else {
|
|
106105
|
-
const body = formatModelSwitchConfirmationBody(confirmation);
|
|
106106
|
-
lockedBot.api.sendMessage(chat.chatId, body, {
|
|
106107
|
-
parse_mode: "Markdown",
|
|
106108
|
-
...chat.threadId != null ? { message_thread_id: chat.threadId } : {}
|
|
106109
|
-
}).catch((err) => process.stderr.write(`telegram gateway: model-switch confirmation send failed: ${err?.message ?? String(err)}
|
|
106110
|
-
`));
|
|
106111
|
-
}
|
|
106506
|
+
if (confirmation != null) {
|
|
106507
|
+
deliverModelSwitchBootNotice({
|
|
106508
|
+
...modelBootCardDeps,
|
|
106509
|
+
confirmation,
|
|
106510
|
+
hasSessionModelAlert: existsSync54(join59(smAgentDir, ".session-model-alert"))
|
|
106511
|
+
});
|
|
106112
106512
|
}
|
|
106113
106513
|
} catch {}
|
|
106114
106514
|
}
|