switchroom 0.7.15 → 0.10.0
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/README.md +51 -59
- package/bin/run-hook.sh +27 -11
- package/bin/timezone-hook.sh +9 -7
- package/dist/agent-scheduler/index.js +410 -133
- package/dist/auth-broker/index.js +13932 -0
- package/dist/cli/switchroom.js +26937 -5601
- package/dist/host-control/main.js +12702 -0
- package/dist/vault/approvals/kernel-server.js +467 -184
- package/dist/vault/broker/server.js +1430 -724
- package/examples/minimal.yaml +63 -0
- package/examples/personal-google-workspace-mcp/.env.example +34 -0
- package/examples/personal-google-workspace-mcp/README.md +194 -0
- package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
- package/examples/switchroom.yaml +220 -0
- package/package.json +7 -4
- package/profiles/_base/settings.json.hbs +20 -5
- package/profiles/_base/start.sh.hbs +16 -3
- package/profiles/_shared/agent-self-service.md.hbs +126 -0
- package/profiles/_shared/telegram-style.md.hbs +20 -90
- package/profiles/_shared/vault-protocol.md.hbs +68 -0
- package/profiles/default/CLAUDE.md +50 -96
- package/profiles/default/CLAUDE.md.hbs +36 -6
- package/profiles/default/workspace/SOUL.md.hbs +12 -5
- package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
- package/skills/buildkite-agent-runtime/SKILL.md +44 -11
- package/skills/buildkite-api/SKILL.md +31 -8
- package/skills/buildkite-cli/SKILL.md +27 -9
- package/skills/buildkite-migration/SKILL.md +22 -9
- package/skills/buildkite-pipelines/SKILL.md +26 -9
- package/skills/buildkite-secure-delivery/SKILL.md +23 -9
- package/skills/buildkite-test-engine/SKILL.md +25 -8
- package/skills/docx/SKILL.md +1 -1
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/file-bug/SKILL.md +34 -6
- package/skills/humanizer/SKILL.md +15 -0
- package/skills/humanizer-calibrate/SKILL.md +7 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/pdf/SKILL.md +1 -1
- package/skills/pptx/SKILL.md +1 -1
- package/skills/skill-creator/SKILL.md +21 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/skills/switchroom-cli/SKILL.md +63 -64
- package/skills/switchroom-health/SKILL.md +23 -10
- package/skills/switchroom-install/SKILL.md +3 -3
- package/skills/switchroom-manage/SKILL.md +26 -19
- package/skills/switchroom-runtime/SKILL.md +191 -0
- package/skills/switchroom-status/SKILL.md +27 -2
- package/skills/telegram-test-harness/SKILL.md +3 -0
- package/skills/token-helpers/SKILL.md +24 -1
- package/skills/webapp-testing/SKILL.md +31 -1
- package/skills/xlsx/SKILL.md +1 -1
- package/telegram-plugin/admin-commands/index.ts +7 -5
- package/telegram-plugin/analytics-posthog.ts +191 -0
- package/telegram-plugin/bridge/bridge.ts +69 -0
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/dist/bridge/bridge.js +194 -119
- package/telegram-plugin/dist/gateway/gateway.js +23611 -19671
- package/telegram-plugin/dist/server.js +245 -189
- package/telegram-plugin/first-paint.ts +3 -24
- package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
- package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
- package/telegram-plugin/gateway/auth-command.ts +794 -0
- package/telegram-plugin/gateway/auth-line.ts +123 -0
- package/telegram-plugin/gateway/boot-card.ts +169 -40
- package/telegram-plugin/gateway/boot-issue-cache.ts +308 -0
- package/telegram-plugin/gateway/boot-probes.ts +166 -123
- package/telegram-plugin/gateway/boot-reason.ts +41 -7
- package/telegram-plugin/gateway/boot-version.ts +66 -0
- package/telegram-plugin/gateway/gateway.ts +3499 -1885
- package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +18 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +106 -0
- package/telegram-plugin/gateway/quarantine.ts +69 -0
- package/telegram-plugin/gateway/quota-cache.ts +9 -4
- package/telegram-plugin/gateway/reaction-trigger.ts +401 -0
- package/telegram-plugin/gateway/recent-denials.test.ts +103 -0
- package/telegram-plugin/gateway/recent-denials.ts +77 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +109 -31
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +125 -0
- package/telegram-plugin/history.ts +91 -0
- package/telegram-plugin/hooks/hooks.json +10 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +130 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +19 -2
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +22 -2
- package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
- package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
- package/telegram-plugin/inbound-classifier.ts +50 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +136 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/package.json +4 -2
- package/telegram-plugin/permission-rule.ts +51 -0
- package/telegram-plugin/permission-title.ts +56 -0
- package/telegram-plugin/quota-check.ts +19 -41
- package/telegram-plugin/registry/reaper.ts +223 -0
- package/telegram-plugin/retry-api-call.ts +80 -0
- package/telegram-plugin/runtime-metrics.ts +177 -0
- package/telegram-plugin/scripts/build.mjs +0 -1
- package/telegram-plugin/secret-detect/index.ts +24 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +64 -12
- package/telegram-plugin/secret-detect/vault-error.ts +78 -11
- package/telegram-plugin/secret-detect/vault-write.ts +14 -2
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/session-tail.ts +6 -1
- package/telegram-plugin/shared/bot-runtime.ts +5 -4
- package/telegram-plugin/silence-poke.ts +420 -0
- package/telegram-plugin/silent-end.ts +174 -0
- package/telegram-plugin/stream-controller.ts +13 -0
- package/telegram-plugin/stream-reply-handler.ts +7 -0
- package/telegram-plugin/subagent-watcher.ts +213 -4
- package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
- package/telegram-plugin/tests/boot-card-issue-dedup.test.ts +247 -0
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +182 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +65 -2
- package/telegram-plugin/tests/boot-card-render.test.ts +146 -0
- package/telegram-plugin/tests/boot-card-silent-on-operator.test.ts +103 -0
- package/telegram-plugin/tests/boot-probes.test.ts +216 -10
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/finalize-callback.test.ts +190 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +26 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +12 -3
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +104 -0
- package/telegram-plugin/tests/history-reaper.test.ts +378 -0
- package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
- package/telegram-plugin/tests/inbound-classifier.test.ts +76 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +267 -0
- package/telegram-plugin/tests/issues-card.test.ts +49 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +132 -0
- package/telegram-plugin/tests/permission-rule.test.ts +80 -1
- package/telegram-plugin/tests/permission-title.test.ts +31 -0
- package/telegram-plugin/tests/quota-check.test.ts +5 -35
- package/telegram-plugin/tests/races.test.ts +179 -0
- package/telegram-plugin/tests/reaction-trigger-flow.test.ts +353 -0
- package/telegram-plugin/tests/reaction-trigger.test.ts +397 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +152 -1
- package/telegram-plugin/tests/runtime-metrics.test.ts +145 -0
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +155 -0
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +133 -0
- package/telegram-plugin/tests/secret-detect-false-positives.test.ts +137 -0
- package/telegram-plugin/tests/silence-poke.test.ts +493 -0
- package/telegram-plugin/tests/silent-end.test.ts +206 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +107 -0
- package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +224 -0
- package/telegram-plugin/tests/subagent-watcher-stall-terminal.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +263 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +81 -0
- package/telegram-plugin/tests/vault-approval-posture.test.ts +256 -0
- package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +73 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +226 -0
- package/telegram-plugin/tests/vault-grant-union.test.ts +130 -0
- package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +140 -0
- package/telegram-plugin/tests/vault-posture-quarantine.test.ts +104 -0
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +114 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +106 -0
- package/telegram-plugin/turn-signal-tracker.ts +100 -24
- package/telegram-plugin/uat/SETUP.md +210 -35
- package/telegram-plugin/uat/assertions.ts +264 -37
- package/telegram-plugin/uat/driver-info.ts +57 -0
- package/telegram-plugin/uat/driver.ts +590 -51
- package/telegram-plugin/uat/harness.ts +140 -94
- package/telegram-plugin/uat/load-env.test.ts +72 -0
- package/telegram-plugin/uat/load-env.ts +48 -0
- package/telegram-plugin/uat/login.ts +96 -53
- package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
- package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
- package/telegram-plugin/uat/runners/report.ts +150 -0
- package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
- package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
- package/telegram-plugin/uat/runners/scorer.ts +106 -0
- package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
- package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
- package/telegram-plugin/uat/scenarios/ask-user-button-tap-dm.test.ts +141 -0
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +255 -0
- package/telegram-plugin/uat/scenarios/fuzz-human-style-dm.test.ts +275 -0
- package/telegram-plugin/uat/scenarios/fuzz-random-prompts-dm.test.ts +146 -0
- package/telegram-plugin/uat/scenarios/fuzz-status-ask-dm.test.ts +486 -0
- package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +100 -0
- package/telegram-plugin/uat/scenarios/jtbd-soft-commit-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-status-query-dm.test.ts +49 -0
- package/telegram-plugin/uat/scenarios/location-inbound-dm.test.ts +65 -0
- package/telegram-plugin/uat/scenarios/midturn-silent-dm.test.ts +175 -0
- package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +142 -0
- package/telegram-plugin/uat/scenarios/reactions-trigger-turn-dm.test.ts +96 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-deletes-original-dm.test.ts +123 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-no-false-positive-dm.test.ts +87 -0
- package/telegram-plugin/uat/scenarios/silence-poke-soft-dm.test.ts +155 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +95 -0
- package/telegram-plugin/uat/scenarios/smoke-dm-reply.test.ts +57 -0
- package/telegram-plugin/uat/scenarios/subagent-watcher-no-rerun-dm.test.ts +135 -0
- package/telegram-plugin/uat/scenarios/vault-approval-posture-telegram-id-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/vault-audit-allow-dm.test.ts +108 -0
- package/telegram-plugin/uat/scenarios/vault-grant-auto-resume-dm.test.ts +121 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-concurrent-dm.test.ts +161 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-end-to-end-dm.test.ts +158 -0
- package/telegram-plugin/uat/scenarios/voice-inbound-dm.test.ts +65 -0
- package/telegram-plugin/vault-approval-posture.ts +42 -0
- package/telegram-plugin/welcome-text.ts +1 -0
- package/telegram-plugin/active-pins-sweep.ts +0 -204
- package/telegram-plugin/active-pins.ts +0 -146
- package/telegram-plugin/auth-dashboard.ts +0 -1104
- package/telegram-plugin/auth-slot-parser.ts +0 -497
- package/telegram-plugin/card-event-log.ts +0 -138
- package/telegram-plugin/dist/foreman/foreman.js +0 -31106
- package/telegram-plugin/docs/multi-agent-card-design.md +0 -847
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +0 -144
- package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
- package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
- package/telegram-plugin/foreman/foreman.ts +0 -1165
- package/telegram-plugin/foreman/setup-flow.ts +0 -345
- package/telegram-plugin/foreman/setup-state.ts +0 -239
- package/telegram-plugin/foreman/state.ts +0 -203
- package/telegram-plugin/pin-event-log.ts +0 -76
- package/telegram-plugin/progress-card-driver.ts +0 -2886
- package/telegram-plugin/progress-card-pin-manager.ts +0 -589
- package/telegram-plugin/progress-card-pin-watchdog.ts +0 -98
- package/telegram-plugin/progress-card.ts +0 -1409
- package/telegram-plugin/tests/HARNESS.md +0 -340
- package/telegram-plugin/tests/_progress-card-harness.ts +0 -109
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +0 -211
- package/telegram-plugin/tests/active-pins-sweep.test.ts +0 -309
- package/telegram-plugin/tests/active-pins.test.ts +0 -187
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
- package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
- package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
- package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +0 -201
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
- package/telegram-plugin/tests/card-event-log.test.ts +0 -145
- package/telegram-plugin/tests/first-paint.test.ts +0 -257
- package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
- package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
- package/telegram-plugin/tests/foreman-state.test.ts +0 -164
- package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +0 -243
- package/telegram-plugin/tests/pin-event-log.test.ts +0 -124
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +0 -73
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +0 -272
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +0 -258
- package/telegram-plugin/tests/progress-card-delay-842.test.ts +0 -160
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +0 -81
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +0 -80
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +0 -215
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +0 -123
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +0 -76
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +0 -62
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +0 -84
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +0 -139
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +0 -773
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +0 -66
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +0 -64
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +0 -190
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +0 -146
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +0 -123
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +0 -82
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +0 -114
- package/telegram-plugin/tests/real-gateway-harness.ts +0 -699
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +0 -313
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +0 -299
- package/telegram-plugin/tests/real-gateway-spec.test.ts +0 -487
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +0 -101
- package/telegram-plugin/tests/setup-flow.test.ts +0 -510
- package/telegram-plugin/tests/setup-state.test.ts +0 -146
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +0 -116
- package/telegram-plugin/tests/turn-end-regressions.test.ts +0 -489
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +0 -218
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +0 -131
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +0 -120
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +0 -116
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +0 -87
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +0 -211
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +0 -62
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +0 -110
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +0 -128
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +0 -58
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +0 -133
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +0 -155
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +0 -117
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +0 -187
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +0 -149
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +0 -114
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +0 -105
- package/telegram-plugin/tests/waiting-ux-harness.ts +0 -381
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +0 -233
- package/telegram-plugin/turn-flush-prose-recovery.ts +0 -40
- package/telegram-plugin/two-zone-card.ts +0 -269
- package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +0 -61
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-card auth row formatter (RFC H §7.3).
|
|
3
|
+
*
|
|
4
|
+
* The old auth-dashboard exported `formatAccountQuotaLine` + an
|
|
5
|
+
* `AccountSummary` shape that the boot card consumed for its
|
|
6
|
+
* "Accounts (N)" section. Both source-of-truth and shape moved to
|
|
7
|
+
* the auth-broker's `list-state` response. This module reformats that
|
|
8
|
+
* response into the same one-line-per-account block the boot card
|
|
9
|
+
* used to render — visual output unchanged, data source is now the
|
|
10
|
+
* broker.
|
|
11
|
+
*
|
|
12
|
+
* Inputs: a `list-state` data shape (see
|
|
13
|
+
* `src/auth/broker/protocol.ts` → `ListStateDataSchema`) plus the
|
|
14
|
+
* caller agent's name.
|
|
15
|
+
*
|
|
16
|
+
* Output: an array of HTML-safe lines. Empty array when there's
|
|
17
|
+
* nothing to show — preserves the boot-card's silent-when-healthy
|
|
18
|
+
* default.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ListStateData, AccountState } from '../../src/auth/broker/client.js'
|
|
22
|
+
|
|
23
|
+
export type { ListStateData, AccountState }
|
|
24
|
+
|
|
25
|
+
// Local HTML-escape (mirrors the helper formerly co-located in
|
|
26
|
+
// auth-dashboard.ts so we keep the same escaping discipline without
|
|
27
|
+
// pulling in a heavier util).
|
|
28
|
+
function escapeHtml(s: string): string {
|
|
29
|
+
return s
|
|
30
|
+
.replace(/&/g, '&')
|
|
31
|
+
.replace(/</g, '<')
|
|
32
|
+
.replace(/>/g, '>')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Format a duration in ms as a short relative string ("1h 22m", "12s"). */
|
|
36
|
+
function formatRelativeMs(ms: number): string {
|
|
37
|
+
if (ms <= 0) return '0s'
|
|
38
|
+
const totalSec = Math.floor(ms / 1000)
|
|
39
|
+
const days = Math.floor(totalSec / 86400)
|
|
40
|
+
const hours = Math.floor((totalSec % 86400) / 3600)
|
|
41
|
+
const mins = Math.floor((totalSec % 3600) / 60)
|
|
42
|
+
const secs = totalSec % 60
|
|
43
|
+
if (days > 0) return `${days}d ${hours}h`
|
|
44
|
+
if (hours > 0) return `${hours}h ${mins}m`
|
|
45
|
+
if (mins > 0) return `${mins}m ${secs}s`
|
|
46
|
+
return `${secs}s`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render the per-account quota inline for one account row. Returns
|
|
51
|
+
* null when there's nothing quota-shaped to say (account is healthy
|
|
52
|
+
* and we have no reset countdown to surface).
|
|
53
|
+
*/
|
|
54
|
+
export function formatAuthQuotaLine(acc: AccountState, now: number = Date.now()): string | null {
|
|
55
|
+
if (acc.exhausted) {
|
|
56
|
+
const until = acc.exhausted_until
|
|
57
|
+
if (until != null && until > now) {
|
|
58
|
+
return `<i>exhausted · resets in ${formatRelativeMs(until - now)}</i>`
|
|
59
|
+
}
|
|
60
|
+
return `<i>exhausted</i>`
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Boot-card auth-row block.
|
|
67
|
+
*
|
|
68
|
+
* Strategy:
|
|
69
|
+
* 1. Determine *which* account is active for `agentName` (per-agent
|
|
70
|
+
* override wins over fleet-active).
|
|
71
|
+
* 2. Emit one row for that account marked with `▶` plus a
|
|
72
|
+
* best-effort quota suffix.
|
|
73
|
+
* 3. Emit one row per other account in `fallback_order` marked with
|
|
74
|
+
* `↳` so the operator sees the rollover plan at a glance.
|
|
75
|
+
*
|
|
76
|
+
* Returns an empty array when `state` is empty (no accounts) — the
|
|
77
|
+
* boot card's silent-when-healthy contract.
|
|
78
|
+
*/
|
|
79
|
+
export function renderAuthLine(
|
|
80
|
+
state: ListStateData,
|
|
81
|
+
agentName: string,
|
|
82
|
+
now: number = Date.now(),
|
|
83
|
+
): string[] {
|
|
84
|
+
if (!state || state.accounts.length === 0) return []
|
|
85
|
+
|
|
86
|
+
const agentEntry = state.agents.find((a) => a.name === agentName)
|
|
87
|
+
const activeLabel = agentEntry?.override ?? agentEntry?.account ?? state.active
|
|
88
|
+
|
|
89
|
+
// Stable display order: active first, then `fallback_order` minus
|
|
90
|
+
// the active label, then any remaining accounts (defensive — should
|
|
91
|
+
// be empty in steady state) in account-list order.
|
|
92
|
+
const seen = new Set<string>()
|
|
93
|
+
const order: string[] = []
|
|
94
|
+
if (activeLabel) {
|
|
95
|
+
order.push(activeLabel)
|
|
96
|
+
seen.add(activeLabel)
|
|
97
|
+
}
|
|
98
|
+
for (const label of state.fallback_order) {
|
|
99
|
+
if (!seen.has(label)) {
|
|
100
|
+
order.push(label)
|
|
101
|
+
seen.add(label)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const acc of state.accounts) {
|
|
105
|
+
if (!seen.has(acc.label)) {
|
|
106
|
+
order.push(acc.label)
|
|
107
|
+
seen.add(acc.label)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const byLabel = new Map(state.accounts.map((a) => [a.label, a]))
|
|
112
|
+
const rows: string[] = []
|
|
113
|
+
rows.push(`<b>Accounts (${state.accounts.length})</b>`)
|
|
114
|
+
for (const label of order) {
|
|
115
|
+
const acc = byLabel.get(label)
|
|
116
|
+
if (!acc) continue
|
|
117
|
+
const marker = label === activeLabel ? '▶' : '↳'
|
|
118
|
+
const labelHtml = `<code>${escapeHtml(acc.label)}</code>`
|
|
119
|
+
const quotaLine = formatAuthQuotaLine(acc, now)
|
|
120
|
+
rows.push(quotaLine ? `${marker} ${labelHtml} ${quotaLine}` : `${marker} ${labelHtml}`)
|
|
121
|
+
}
|
|
122
|
+
return rows
|
|
123
|
+
}
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import type { ProbeResult, GatewayRuntimeInfo } from './boot-probes.js'
|
|
36
|
-
import type {
|
|
37
|
-
import {
|
|
36
|
+
import type { ListStateData } from './auth-line.js'
|
|
37
|
+
import { renderAuthLine } from './auth-line.js'
|
|
38
38
|
import {
|
|
39
39
|
probeAccount,
|
|
40
40
|
probeAgentProcess,
|
|
@@ -50,6 +50,12 @@ import {
|
|
|
50
50
|
AGENT_LIVE_POLL_INTERVAL_MS,
|
|
51
51
|
} from './boot-probes.js'
|
|
52
52
|
import { escapeHtml } from '../card-format.js'
|
|
53
|
+
import {
|
|
54
|
+
loadCache as loadBootIssueCache,
|
|
55
|
+
diffProbes as diffBootProbes,
|
|
56
|
+
applyAndSave as saveBootIssueCache,
|
|
57
|
+
type ProbeDiffMap,
|
|
58
|
+
} from './boot-issue-cache.js'
|
|
53
59
|
import { join } from 'path'
|
|
54
60
|
import { loadConfig as _loadSwitchroomConfig } from '../../src/config/loader.js'
|
|
55
61
|
|
|
@@ -243,6 +249,10 @@ const REASON_LABEL: Record<RestartReason, string> = {
|
|
|
243
249
|
|
|
244
250
|
export interface RenderBootCardOpts {
|
|
245
251
|
agentName: string
|
|
252
|
+
/** Lowercase slug used for systemd unit names. Falls back to
|
|
253
|
+
* `agentName` when omitted — matches the same fallback used by
|
|
254
|
+
* `runAllProbes` for systemd targets. */
|
|
255
|
+
agentSlug?: string
|
|
246
256
|
/** Pre-formatted version string, e.g. "v0.3.0+44" or "v0.3.0 · #143 · 2h ago". */
|
|
247
257
|
version: string
|
|
248
258
|
/** Probe results (only present after the settle window). When absent or
|
|
@@ -264,9 +274,21 @@ export interface RenderBootCardOpts {
|
|
|
264
274
|
* silent-when-healthy contract for callers that don't pass account
|
|
265
275
|
* data (tests, harnesses, gateways without the auth model).
|
|
266
276
|
*
|
|
267
|
-
*
|
|
277
|
+
* Post-RFC H (auth-broker rewire): this carries the broker's
|
|
278
|
+
* `list-state` shape — `renderAuthLine` consumes it to emit the
|
|
279
|
+
* same one-line-per-account rows as before. Callers that pass
|
|
280
|
+
* `null`/`undefined` get no section. Closes #708.
|
|
268
281
|
*/
|
|
269
|
-
accounts?:
|
|
282
|
+
accounts?: ListStateData | null
|
|
283
|
+
/** Probe keys for which the prior boot saw degraded/fail and this boot
|
|
284
|
+
* sees ok. Rendered as a small ✅ line above the degraded section so
|
|
285
|
+
* the user gets positive-feedback that a known issue is gone. */
|
|
286
|
+
resolvedRows?: ReadonlyArray<ProbeKey>
|
|
287
|
+
/** Probe keys whose degraded/fail row is hidden on this boot because
|
|
288
|
+
* the user has seen the same fingerprint for too many consecutive
|
|
289
|
+
* boots (snooze). The renderer skips the corresponding probe row.
|
|
290
|
+
* See `boot-issue-cache.ts`. */
|
|
291
|
+
snoozeRows?: ReadonlyArray<ProbeKey>
|
|
270
292
|
/** Clock injection point for tests; defaults to `new Date()`. */
|
|
271
293
|
now?: Date
|
|
272
294
|
}
|
|
@@ -279,12 +301,42 @@ export interface RenderBootCardOpts {
|
|
|
279
301
|
* user only needs to know the agent came back up. Anything red catches
|
|
280
302
|
* the eye; everything else stays out of the way.
|
|
281
303
|
*/
|
|
304
|
+
/**
|
|
305
|
+
* Render a probe's `nextStep` hint as Telegram HTML. The hint is
|
|
306
|
+
* authored as plain text with backtick-quoted commands (one shell idiom
|
|
307
|
+
* across the codebase — search "Run `switchroom"). We translate those
|
|
308
|
+
* to <code> spans and escape everything else, so commands stay tap-to-
|
|
309
|
+
* copy on mobile without bleeding raw HTML through.
|
|
310
|
+
*/
|
|
311
|
+
function renderNextStep(text: string): string {
|
|
312
|
+
const parts = text.split('`')
|
|
313
|
+
// Odd-count backticks means an unterminated <code> span — fall back to
|
|
314
|
+
// plain-escaped text rather than rendering the trailing tail inside a
|
|
315
|
+
// code block. Author error, not user-input, but defensive is cheap.
|
|
316
|
+
if (parts.length % 2 === 0) return escapeHtml(text)
|
|
317
|
+
return parts.map((p, i) => (i % 2 === 0 ? escapeHtml(p) : `<code>${escapeHtml(p)}</code>`)).join('')
|
|
318
|
+
}
|
|
319
|
+
|
|
282
320
|
export function renderBootCard(opts: RenderBootCardOpts): string {
|
|
283
321
|
const { agentName, version, probes, restartReason, restartAgeMs } = opts
|
|
322
|
+
const agentSlug = opts.agentSlug ?? agentName
|
|
284
323
|
const ackEmoji = restartReason ? REASON_EMOJI[restartReason] : '✅'
|
|
285
324
|
const ack = `${ackEmoji} <b>${escapeHtml(agentName)}</b> back up · ${escapeHtml(version)}`
|
|
286
325
|
|
|
287
326
|
const degradedRows: string[] = []
|
|
327
|
+
const snoozeSet = new Set<ProbeKey>(opts.snoozeRows ?? [])
|
|
328
|
+
|
|
329
|
+
// Resolved rows (issue dedup, this PR) — render ✅ entries for probes
|
|
330
|
+
// that were degraded/fail on the previous boot and are now ok. Small
|
|
331
|
+
// positive-feedback signal so the user sees their fix worked instead
|
|
332
|
+
// of guessing from the absence of a row.
|
|
333
|
+
if (opts.resolvedRows && opts.resolvedRows.length > 0) {
|
|
334
|
+
for (const key of opts.resolvedRows) {
|
|
335
|
+
const lbl = PROBE_LABELS[key]
|
|
336
|
+
if (!lbl) continue
|
|
337
|
+
degradedRows.push(`✅ <b>${escapeHtml(lbl)}</b> resolved`)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
288
340
|
|
|
289
341
|
// Crash recovery: surface explicitly so the user can tell whether
|
|
290
342
|
// their next message will land on a fresh process. The agent-crashed
|
|
@@ -295,25 +347,49 @@ export function renderBootCard(opts: RenderBootCardOpts): string {
|
|
|
295
347
|
? ` · ${(restartAgeMs / 1000).toFixed(1)}s ago`
|
|
296
348
|
: ''
|
|
297
349
|
degradedRows.push(`⚠️ <b>Restart</b> ${escapeHtml(REASON_LABEL.crash)}${ageStr}`)
|
|
350
|
+
// Principle 1: every failure carries its next step. The crash row
|
|
351
|
+
// tells the user how to inspect why.
|
|
352
|
+
degradedRows.push(` ↳ Tail logs: <code>journalctl --user -u switchroom-${escapeHtml(agentSlug)} -n 100</code>`)
|
|
298
353
|
}
|
|
299
354
|
|
|
300
355
|
// Probe rows — only those that surfaced as degraded/fail. Healthy
|
|
301
|
-
// (`ok`) probes don't render at all.
|
|
356
|
+
// (`ok`) probes don't render at all. When a probe carries a nextStep,
|
|
357
|
+
// it renders as an indented continuation line beneath the row — see
|
|
358
|
+
// `reference/principles.md` principle 1 ("If they need the docs, we've
|
|
359
|
+
// failed"): every failure surface tells the user what to run.
|
|
302
360
|
if (probes) {
|
|
303
361
|
for (const key of PROBE_KEYS) {
|
|
304
362
|
const r = probes[key]
|
|
305
363
|
if (!r) continue
|
|
306
364
|
if (r.status === 'ok') continue
|
|
365
|
+
// Snoozed rows (issue dedup, this PR) — the user has seen the
|
|
366
|
+
// same fingerprint enough consecutive boots that we hide the row.
|
|
367
|
+
// The cache still tracks it; if the fingerprint changes (new
|
|
368
|
+
// failure mode) the snooze resets and the row reappears.
|
|
369
|
+
if (snoozeSet.has(key)) continue
|
|
307
370
|
const dot = DOT[r.status] ?? DOT.fail
|
|
371
|
+
// The "Still: " prefix is reserved for rows the user has seen
|
|
372
|
+
// before (consecutiveBoots > 1) but hasn't been snoozed yet.
|
|
373
|
+
// We can't compute that from probes alone — the caller signals
|
|
374
|
+
// it implicitly: a degraded/fail row that's NOT in snoozeRows
|
|
375
|
+
// and NOT in resolvedRows is either novel or still-being-shown.
|
|
376
|
+
// We surface the existing row format unchanged here; the
|
|
377
|
+
// "Still:" / "New:" distinction is conveyed by which rows the
|
|
378
|
+
// user does or doesn't see across consecutive boots.
|
|
308
379
|
degradedRows.push(`${dot} <b>${PROBE_LABELS[key]}</b> ${escapeHtml(r.detail)}`)
|
|
380
|
+
if (r.nextStep) {
|
|
381
|
+
degradedRows.push(` ↳ ${renderNextStep(r.nextStep)}`)
|
|
382
|
+
}
|
|
309
383
|
}
|
|
310
384
|
}
|
|
311
385
|
|
|
312
|
-
// Per-account
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
const accountRows =
|
|
386
|
+
// Per-account auth section (issue #708, RFC H rewire) — one line
|
|
387
|
+
// per known account with the active account marked. Renders
|
|
388
|
+
// alongside the ack line so users see headroom without running
|
|
389
|
+
// /auth or /usage. Source of truth: auth-broker list-state.
|
|
390
|
+
const accountRows = opts.accounts
|
|
391
|
+
? renderAuthLine(opts.accounts, agentName, (opts.now ?? new Date()).getTime())
|
|
392
|
+
: []
|
|
317
393
|
|
|
318
394
|
const sections: string[] = [ack]
|
|
319
395
|
if (degradedRows.length > 0) sections.push('', ...degradedRows)
|
|
@@ -323,31 +399,12 @@ export function renderBootCard(opts: RenderBootCardOpts): string {
|
|
|
323
399
|
}
|
|
324
400
|
|
|
325
401
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* Reuses the dashboard's `formatAccountQuotaLine` so the two surfaces
|
|
331
|
-
* speak with one voice.
|
|
402
|
+
* Re-export the broker-fed auth-row renderer under its historical
|
|
403
|
+
* name so direct callers (tests, harnesses) keep working without
|
|
404
|
+
* importing two modules. New code should import `renderAuthLine`
|
|
405
|
+
* from `./auth-line.js` directly.
|
|
332
406
|
*/
|
|
333
|
-
export
|
|
334
|
-
accounts: ReadonlyArray<AccountSummary> | undefined,
|
|
335
|
-
now: Date,
|
|
336
|
-
): string[] {
|
|
337
|
-
if (!accounts || accounts.length === 0) return []
|
|
338
|
-
const rows: string[] = []
|
|
339
|
-
rows.push(`<b>Accounts (${accounts.length})</b>`)
|
|
340
|
-
const nowMs = now.getTime()
|
|
341
|
-
for (const a of accounts) {
|
|
342
|
-
const marker = a.activeForThisAgent ? '▶' : '↳'
|
|
343
|
-
const labelHtml = `<code>${escapeHtml(a.label)}</code>`
|
|
344
|
-
// formatAccountQuotaLine returns HTML (with <i> tags) so we don't
|
|
345
|
-
// re-escape — pass it through verbatim.
|
|
346
|
-
const quotaLine = formatAccountQuotaLine(a, nowMs)
|
|
347
|
-
rows.push(quotaLine ? `${marker} ${labelHtml} ${quotaLine}` : `${marker} ${labelHtml}`)
|
|
348
|
-
}
|
|
349
|
-
return rows
|
|
350
|
-
}
|
|
407
|
+
export { renderAuthLine as renderAccountRows } from './auth-line.js'
|
|
351
408
|
|
|
352
409
|
// ─── Probe orchestration ─────────────────────────────────────────────────────
|
|
353
410
|
|
|
@@ -368,6 +425,15 @@ export interface RunProbesOpts {
|
|
|
368
425
|
restartReason?: RestartReason
|
|
369
426
|
/** Age of the restart marker in ms — shown in the crash row. */
|
|
370
427
|
restartAgeMs?: number
|
|
428
|
+
/** Free-form reason text from `clean-shutdown.json` (e.g.
|
|
429
|
+
* `"operator: switchroom update"`, `"user: /restart from chat"`).
|
|
430
|
+
* Used to silence the boot-card notification for operator-initiated
|
|
431
|
+
* redeploys: routine fleet updates shouldn't ping every user every
|
|
432
|
+
* time. `user:` reasons (and crash / fresh) still notify normally —
|
|
433
|
+
* the user asked for that restart, so the boot card should
|
|
434
|
+
* announce. The text is also passed through unchanged so future
|
|
435
|
+
* surfaces can render it. */
|
|
436
|
+
restartReasonDetail?: string
|
|
371
437
|
/** Override fetch for tests. */
|
|
372
438
|
fetchImpl?: typeof fetch
|
|
373
439
|
/** Override settle window for tests; production uses SETTLE_WINDOW_MS. */
|
|
@@ -400,12 +466,21 @@ export interface RunProbesOpts {
|
|
|
400
466
|
* during the post-settle re-render so the first paint stays fast.
|
|
401
467
|
*/
|
|
402
468
|
loadAccounts?: () =>
|
|
403
|
-
|
|
|
469
|
+
| ListStateData
|
|
404
470
|
| null
|
|
405
|
-
| Promise<
|
|
471
|
+
| Promise<ListStateData | null>
|
|
406
472
|
/** When true, resolve the agent PID via cgroup walk instead of MainPID
|
|
407
473
|
* (which is the tmux server pid under tmux supervisor). */
|
|
408
474
|
tmuxSupervisor?: boolean
|
|
475
|
+
/** Path to the per-agent boot-issue cache file. When set, the
|
|
476
|
+
* post-settle render applies snooze + resolved-row dedup against
|
|
477
|
+
* prior boots (see `boot-issue-cache.ts`). Omit to disable dedup
|
|
478
|
+
* entirely (legacy behaviour). */
|
|
479
|
+
bootIssueCachePath?: string
|
|
480
|
+
/** Override snoozeBoots threshold for tests. */
|
|
481
|
+
snoozeBoots?: number
|
|
482
|
+
/** Override snoozeMs threshold for tests. */
|
|
483
|
+
snoozeMs?: number
|
|
409
484
|
/** When true, the gateway is running inside an agent docker container.
|
|
410
485
|
* Probes that depend on systemctl (Agent, Crons) switch to /proc walks
|
|
411
486
|
* and externally-managed surface text instead of execing systemctl
|
|
@@ -424,7 +499,7 @@ export async function runAllProbes(opts: RunProbesOpts): Promise<ProbeMap> {
|
|
|
424
499
|
const slug = opts.agentSlug ?? opts.agentName
|
|
425
500
|
|
|
426
501
|
await Promise.allSettled([
|
|
427
|
-
probeAccount(opts.agentDir).then(r => { probes.account = r }),
|
|
502
|
+
probeAccount(opts.agentDir, { agentName: opts.agentSlug ?? opts.agentName }).then(r => { probes.account = r }),
|
|
428
503
|
probeAgentProcess(slug, { execFileImpl: opts.probeExecFileImpl, tmuxSupervisor: opts.tmuxSupervisor, dockerMode: opts.dockerMode }).then(r => { probes.agent = r }),
|
|
429
504
|
probeGateway(opts.gatewayInfo).then(r => { probes.gateway = r }),
|
|
430
505
|
probeQuota(claudeDir, opts.agentDir, opts.fetchImpl).then(r => { probes.quota = r }),
|
|
@@ -432,7 +507,7 @@ export async function runAllProbes(opts: RunProbesOpts): Promise<ProbeMap> {
|
|
|
432
507
|
probeScheduler(slug, { dockerMode: opts.dockerMode }).then(r => { probes.scheduler = r }),
|
|
433
508
|
probeBroker(undefined, { dockerMode: opts.dockerMode }).then(r => { probes.broker = r }),
|
|
434
509
|
probeKernel(undefined, { dockerMode: opts.dockerMode }).then(r => { probes.kernel = r }),
|
|
435
|
-
probeSkills(opts.agentDir).then(r => { probes.skills = r }),
|
|
510
|
+
probeSkills(opts.agentDir, { agentName: opts.agentSlug ?? opts.agentName }).then(r => { probes.skills = r }),
|
|
436
511
|
])
|
|
437
512
|
|
|
438
513
|
return probes
|
|
@@ -461,11 +536,24 @@ export async function startBootCard(
|
|
|
461
536
|
// confirmation that the agent is back without waiting on probes.
|
|
462
537
|
const ackText = renderBootCard({
|
|
463
538
|
agentName: opts.agentName,
|
|
539
|
+
agentSlug: opts.agentSlug,
|
|
464
540
|
version: opts.version,
|
|
465
541
|
restartReason: opts.restartReason,
|
|
466
542
|
restartAgeMs: opts.restartAgeMs,
|
|
467
543
|
})
|
|
468
544
|
|
|
545
|
+
// Silence the notification for operator-initiated redeploys. A
|
|
546
|
+
// routine `switchroom update` should land in the chat as a record
|
|
547
|
+
// but not buzz every user's phone — every agent posts a card, so
|
|
548
|
+
// a fleet update with N agents produces N notifications otherwise.
|
|
549
|
+
// We key on the reason-text prefix `operator:` (today only
|
|
550
|
+
// `operator: switchroom update` writes this) so user-initiated
|
|
551
|
+
// restarts (`user: /restart from chat`, `cli: switchroom restart`)
|
|
552
|
+
// and unplanned events (crash, fresh, planned-marker) keep their
|
|
553
|
+
// normal notification behaviour — the user explicitly asked for
|
|
554
|
+
// those, or they need to know something went wrong.
|
|
555
|
+
const silentBootCard = opts.restartReasonDetail?.startsWith('operator:') === true
|
|
556
|
+
|
|
469
557
|
let messageId: number
|
|
470
558
|
try {
|
|
471
559
|
const sent = await bot.sendMessage(chatId, ackText, {
|
|
@@ -473,9 +561,10 @@ export async function startBootCard(
|
|
|
473
561
|
link_preview_options: { is_disabled: true },
|
|
474
562
|
...(threadId != null ? { message_thread_id: threadId } : {}),
|
|
475
563
|
...(ackMessageId != null ? { reply_parameters: { message_id: ackMessageId } } : {}),
|
|
564
|
+
...(silentBootCard ? { disable_notification: true } : {}),
|
|
476
565
|
})
|
|
477
566
|
messageId = sent.message_id
|
|
478
|
-
logger(`telegram gateway: boot-card: posted msgId=${messageId} chatId=${chatId} reason=${opts.restartReason ?? '-'}\n`)
|
|
567
|
+
logger(`telegram gateway: boot-card: posted msgId=${messageId} chatId=${chatId} reason=${opts.restartReason ?? '-'} reason_detail=${opts.restartReasonDetail ?? '-'} silent=${silentBootCard}\n`)
|
|
479
568
|
} catch (err: unknown) {
|
|
480
569
|
logger(`telegram gateway: boot-card: failed to post ack: ${(err as Error)?.message ?? String(err)}\n`)
|
|
481
570
|
return { messageId: -1, complete: () => {} }
|
|
@@ -501,7 +590,7 @@ export async function startBootCard(
|
|
|
501
590
|
// Per-account rows (issue #708). Loaded best-effort
|
|
502
591
|
// alongside probes; failures are swallowed so the card still
|
|
503
592
|
// renders correctly with no accounts section.
|
|
504
|
-
let accountRows:
|
|
593
|
+
let accountRows: ListStateData | null = null
|
|
505
594
|
if (opts.loadAccounts) {
|
|
506
595
|
try {
|
|
507
596
|
accountRows = await opts.loadAccounts()
|
|
@@ -514,14 +603,49 @@ export async function startBootCard(
|
|
|
514
603
|
}
|
|
515
604
|
}
|
|
516
605
|
|
|
606
|
+
// Issue-dedup diff: when a cache path is configured, compare this
|
|
607
|
+
// boot's probe outcomes against the cached fingerprints to derive
|
|
608
|
+
// resolvedRows (was bad, now ok) and snoozeRows (same fingerprint
|
|
609
|
+
// shown too many consecutive boots). One disk read up-front, one
|
|
610
|
+
// disk write on the way out — no second write from the live-watch
|
|
611
|
+
// loop below (which reuses the same masks).
|
|
612
|
+
let diff: ProbeDiffMap = {}
|
|
613
|
+
let resolvedRows: import('./boot-card.js').ProbeKey[] = []
|
|
614
|
+
let snoozeRows: import('./boot-card.js').ProbeKey[] = []
|
|
615
|
+
if (opts.bootIssueCachePath) {
|
|
616
|
+
try {
|
|
617
|
+
const cache = loadBootIssueCache(opts.bootIssueCachePath)
|
|
618
|
+
diff = diffBootProbes(probes, cache, {
|
|
619
|
+
snoozeBoots: opts.snoozeBoots,
|
|
620
|
+
snoozeMs: opts.snoozeMs,
|
|
621
|
+
})
|
|
622
|
+
for (const [k, d] of Object.entries(diff) as [import('./boot-card.js').ProbeKey, NonNullable<ProbeDiffMap[import('./boot-card.js').ProbeKey]>][]) {
|
|
623
|
+
if (d.resolved) resolvedRows.push(k)
|
|
624
|
+
if (d.snoozed) snoozeRows.push(k)
|
|
625
|
+
}
|
|
626
|
+
// Persist once. The live-watch loop reuses the same mask in
|
|
627
|
+
// memory; it does NOT re-write the cache.
|
|
628
|
+
saveBootIssueCache(opts.bootIssueCachePath, cache, diff)
|
|
629
|
+
} catch (diffErr: unknown) {
|
|
630
|
+
logger(
|
|
631
|
+
`telegram gateway: boot-card: issue-dedup diff failed: ${
|
|
632
|
+
(diffErr as Error)?.message ?? String(diffErr)
|
|
633
|
+
}\n`,
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
517
638
|
// Render with current probe state and edit if anything changed.
|
|
518
639
|
let currentText = renderBootCard({
|
|
519
640
|
agentName: opts.agentName,
|
|
641
|
+
agentSlug: opts.agentSlug,
|
|
520
642
|
version: opts.version,
|
|
521
643
|
probes,
|
|
522
644
|
restartReason: opts.restartReason,
|
|
523
645
|
restartAgeMs: opts.restartAgeMs,
|
|
524
646
|
...(accountRows ? { accounts: accountRows } : {}),
|
|
647
|
+
...(resolvedRows.length > 0 ? { resolvedRows } : {}),
|
|
648
|
+
...(snoozeRows.length > 0 ? { snoozeRows } : {}),
|
|
525
649
|
})
|
|
526
650
|
|
|
527
651
|
if (currentText !== ackText) {
|
|
@@ -563,11 +687,16 @@ export async function startBootCard(
|
|
|
563
687
|
const updatedProbes: ProbeMap = { ...probes, agent: agentResult }
|
|
564
688
|
const updatedText = renderBootCard({
|
|
565
689
|
agentName: opts.agentName,
|
|
690
|
+
agentSlug: opts.agentSlug,
|
|
566
691
|
version: opts.version,
|
|
567
692
|
probes: updatedProbes,
|
|
568
693
|
restartReason: opts.restartReason,
|
|
569
694
|
restartAgeMs: opts.restartAgeMs,
|
|
570
695
|
...(accountRows ? { accounts: accountRows } : {}),
|
|
696
|
+
// Reuse the same masks computed once above — no second
|
|
697
|
+
// cache write from the live-watch loop.
|
|
698
|
+
...(resolvedRows.length > 0 ? { resolvedRows } : {}),
|
|
699
|
+
...(snoozeRows.length > 0 ? { snoozeRows } : {}),
|
|
571
700
|
})
|
|
572
701
|
|
|
573
702
|
if (updatedText === currentText) continue
|