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
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure logic for the `/auth` slot-management sub-verbs (add/use/list/rm).
|
|
3
|
-
*
|
|
4
|
-
* Lives outside gateway.ts + server.ts so it's unit-testable without
|
|
5
|
-
* spinning up a grammy bot. The gateway/server command handlers call
|
|
6
|
-
* `parseAuthSubCommand` to turn a raw /auth argv into a dispatch plan
|
|
7
|
-
* (switchroom CLI args + label + optional post-action hook), then
|
|
8
|
-
* handle that plan via their existing runSwitchroomCommand pipeline.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** Pattern used by slot names throughout switchroom. Matches the shape
|
|
12
|
-
* used by `addAccountStart` and slot-dir naming in src/auth/accounts.ts. */
|
|
13
|
-
const SLOT_NAME_RE = /^[a-zA-Z0-9_-]{1,32}$/;
|
|
14
|
-
|
|
15
|
-
export function assertSafeSlotName(slot: string): void {
|
|
16
|
-
if (!SLOT_NAME_RE.test(slot)) {
|
|
17
|
-
throw new Error(`invalid slot name: ${slot}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Pattern used by global account labels — matches validateAccountLabel
|
|
22
|
-
* in src/auth/account-store.ts. Allows email-shaped labels
|
|
23
|
-
* (`pixsoul@gmail.com`) and gmail-tag forms (`ken+work@example.com`).
|
|
24
|
-
* Excludes `:` (callback_data separator), path separators, shell
|
|
25
|
-
* metas, and whitespace. Max 64 chars. Keep in sync with `LABEL_RE`
|
|
26
|
-
* in account-store.ts and `isSafeAccountLabel` in auth-dashboard.ts. */
|
|
27
|
-
const ACCOUNT_LABEL_RE = /^[A-Za-z0-9._@+-]{1,64}$/;
|
|
28
|
-
|
|
29
|
-
export function assertSafeAccountLabel(label: string): void {
|
|
30
|
-
if (label === '.' || label === '..') {
|
|
31
|
-
throw new Error(`invalid account label: ${label}`);
|
|
32
|
-
}
|
|
33
|
-
if (!ACCOUNT_LABEL_RE.test(label)) {
|
|
34
|
-
throw new Error(`invalid account label: ${label}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Agent-name check mirrored from gateway.ts so the parser doesn't
|
|
39
|
-
* need to import gateway.ts (which has top-level side effects). */
|
|
40
|
-
const AGENT_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
41
|
-
export function assertSafeAgentNameForParser(name: string): void {
|
|
42
|
-
if (name !== 'all' && !AGENT_NAME_RE.test(name)) {
|
|
43
|
-
throw new Error(`invalid agent name: ${name}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type AuthIntent =
|
|
48
|
-
| { kind: 'login' | 'reauth' | 'link'; agent: string; label: string; cliArgs: string[]; registerReauth: boolean }
|
|
49
|
-
| { kind: 'code'; agent: string; code: string; label: string; cliArgs: string[] }
|
|
50
|
-
| { kind: 'cancel'; agent: string; label: string; cliArgs: string[] }
|
|
51
|
-
| { kind: 'status'; label: string; cliArgs: string[] }
|
|
52
|
-
| { kind: 'add'; agent: string; slot?: string; label: string; cliArgs: string[] }
|
|
53
|
-
| { kind: 'use'; agent: string; slot: string; force: boolean; label: string; cliArgs: string[]; restartAgentAfter: true }
|
|
54
|
-
| { kind: 'list'; agent: string; label: string; cliArgs: string[] }
|
|
55
|
-
| { kind: 'rm'; agent: string; slot: string; force: boolean; label: string; cliArgs: string[] }
|
|
56
|
-
// ── New account-shaped verbs (see reference/share-auth-across-the-fleet.md) ──
|
|
57
|
-
| { kind: 'account-add'; account: string; fromAgent: string; label: string; cliArgs: string[] }
|
|
58
|
-
| { kind: 'account-list'; label: string; cliArgs: string[] }
|
|
59
|
-
| { kind: 'account-rm'; account: string; label: string; cliArgs: string[] }
|
|
60
|
-
| { kind: 'account-rename'; oldAccount: string; newAccount: string; label: string; cliArgs: string[] }
|
|
61
|
-
| { kind: 'enable'; account: string; agents: string[]; label: string; cliArgs: string[]; restartAgentsAfter: true }
|
|
62
|
-
| { kind: 'disable'; account: string; agents: string[]; label: string; cliArgs: string[] }
|
|
63
|
-
| { kind: 'share'; account: string; fromAgent: string; label: string; cliArgs: string[]; restartAgentsAfter: true }
|
|
64
|
-
| { kind: 'usage'; message: string }
|
|
65
|
-
| { kind: 'error'; message: string };
|
|
66
|
-
|
|
67
|
-
export const AUTH_VERBS = [
|
|
68
|
-
'login', 'reauth', 'link',
|
|
69
|
-
'code', 'cancel', 'status',
|
|
70
|
-
'add', 'use', 'list', 'rm',
|
|
71
|
-
// New account-shaped verbs
|
|
72
|
-
'account', 'enable', 'disable', 'share',
|
|
73
|
-
] as const;
|
|
74
|
-
|
|
75
|
-
/** Help/usage string shown for unknown subcommands. Keep wording close
|
|
76
|
-
* to the previous inline usage so the help-text asserting tests
|
|
77
|
-
* naturally catch drift. */
|
|
78
|
-
export function usageText(): string {
|
|
79
|
-
return [
|
|
80
|
-
'Usage:',
|
|
81
|
-
'/auth — status dashboard',
|
|
82
|
-
'',
|
|
83
|
-
'Per-agent (legacy slot model):',
|
|
84
|
-
'/auth login [agent] — start OAuth for agent',
|
|
85
|
-
'/auth reauth [agent] — re-auth from scratch',
|
|
86
|
-
'/auth code [agent] <browser-code> — finish OAuth flow',
|
|
87
|
-
'/auth cancel [agent] — cancel pending flow',
|
|
88
|
-
'/auth add [agent] [--slot <name>] — add another slot',
|
|
89
|
-
'/auth use [agent] <slot> [--force] — switch active slot',
|
|
90
|
-
'/auth list [agent] — list slots',
|
|
91
|
-
'/auth rm [agent] <slot> [--force] — remove a slot',
|
|
92
|
-
'',
|
|
93
|
-
'Anthropic accounts (shared across agents):',
|
|
94
|
-
'/auth account add <label> [--from-agent <name>] — promote slot to global account',
|
|
95
|
-
'/auth account list — accounts + agents using each',
|
|
96
|
-
'/auth account rm <label> — remove (refused if enabled)',
|
|
97
|
-
'/auth account rename <old> <new> — rename account + rewrite agents.<name>.auth.accounts lists',
|
|
98
|
-
'/auth enable <label> [agents...|all] — wire account to agent(s); "all" = every agent',
|
|
99
|
-
'/auth disable <label> [agents...|all] — unwire account from agent(s); "all" = every agent',
|
|
100
|
-
'/auth share <label> [--from-agent <name>] — account add + enable on every agent in one step',
|
|
101
|
-
].join('\n');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Turn raw /auth argv into a dispatch intent.
|
|
106
|
-
*
|
|
107
|
-
* `parts` is the whitespace-split tail of the /auth command (no leading
|
|
108
|
-
* "/auth"). `currentAgent` is the agent this gateway process represents.
|
|
109
|
-
* Missing agent arg defaults to `currentAgent` so single-agent setups
|
|
110
|
-
* Just Work without typing the name.
|
|
111
|
-
*/
|
|
112
|
-
export function parseAuthSubCommand(
|
|
113
|
-
parts: string[],
|
|
114
|
-
currentAgent: string,
|
|
115
|
-
): AuthIntent {
|
|
116
|
-
const sub = (parts[0] ?? 'status').toLowerCase();
|
|
117
|
-
|
|
118
|
-
// Existing verbs — kept here so both gateway.ts and server.ts can
|
|
119
|
-
// route them through a single source of truth once they migrate.
|
|
120
|
-
if (sub === 'login' || sub === 'reauth' || sub === 'link') {
|
|
121
|
-
const agent = parts[1] ?? currentAgent;
|
|
122
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
123
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
124
|
-
return {
|
|
125
|
-
kind: sub,
|
|
126
|
-
agent,
|
|
127
|
-
label: `auth ${sub} ${agent}`,
|
|
128
|
-
cliArgs: ['auth', sub, agent],
|
|
129
|
-
registerReauth: sub === 'reauth' || sub === 'login',
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (sub === 'code') {
|
|
134
|
-
let agent = currentAgent; let code = '';
|
|
135
|
-
if (parts.length >= 3) { agent = parts[1]; code = parts.slice(2).join(' '); }
|
|
136
|
-
else if (parts.length === 2) { code = parts[1]; }
|
|
137
|
-
if (!code) return { kind: 'usage', message: 'Usage: /auth code [agent] <browser-code>' };
|
|
138
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
139
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
140
|
-
return { kind: 'code', agent, code, label: `auth code ${agent}`, cliArgs: ['auth', 'code', agent, code] };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (sub === 'cancel') {
|
|
144
|
-
const agent = parts[1] ?? currentAgent;
|
|
145
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
146
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
147
|
-
return { kind: 'cancel', agent, label: `auth cancel ${agent}`, cliArgs: ['auth', 'cancel', agent] };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (sub === 'status') {
|
|
151
|
-
return { kind: 'status', label: 'auth status', cliArgs: ['auth', 'status'] };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// --- New slot-management verbs ---
|
|
155
|
-
|
|
156
|
-
if (sub === 'add') {
|
|
157
|
-
// /auth add [agent] [--slot <name>]
|
|
158
|
-
const rest = parts.slice(1);
|
|
159
|
-
const { flags, positional } = splitFlags(rest, ['--slot']);
|
|
160
|
-
const agent = positional[0] ?? currentAgent;
|
|
161
|
-
// splitFlags returns `string | true | undefined` for value flags
|
|
162
|
-
// (true when the flag is present without a value). For `--slot` we
|
|
163
|
-
// expect a string value; reject the bare-flag form.
|
|
164
|
-
const rawSlot = flags['--slot'];
|
|
165
|
-
const slot = typeof rawSlot === 'string' ? rawSlot : undefined;
|
|
166
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
167
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
168
|
-
if (slot !== undefined) {
|
|
169
|
-
try { assertSafeSlotName(slot); }
|
|
170
|
-
catch { return { kind: 'error', message: 'Invalid slot name. Use [A-Za-z0-9_-], 1-32 chars.' }; }
|
|
171
|
-
}
|
|
172
|
-
const cliArgs = ['auth', 'add', agent];
|
|
173
|
-
if (slot) cliArgs.push('--slot', slot);
|
|
174
|
-
return { kind: 'add', agent, slot, label: `auth add ${agent}`, cliArgs };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (sub === 'use') {
|
|
178
|
-
// /auth use [agent] <slot> [--force]
|
|
179
|
-
const rest = parts.slice(1);
|
|
180
|
-
const { flags, positional } = splitFlags(rest, []);
|
|
181
|
-
if (positional.length === 0) {
|
|
182
|
-
return { kind: 'usage', message: 'Usage: /auth use [agent] <slot> [--force]' };
|
|
183
|
-
}
|
|
184
|
-
const [agent, slot] = positional.length === 1
|
|
185
|
-
? [currentAgent, positional[0]]
|
|
186
|
-
: [positional[0], positional[1]];
|
|
187
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
188
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
189
|
-
try { assertSafeSlotName(slot); }
|
|
190
|
-
catch { return { kind: 'error', message: 'Invalid slot name. Use [A-Za-z0-9_-], 1-32 chars.' }; }
|
|
191
|
-
return {
|
|
192
|
-
kind: 'use', agent, slot,
|
|
193
|
-
force: flags['--force'] === true,
|
|
194
|
-
label: `auth use ${agent} ${slot}`,
|
|
195
|
-
cliArgs: ['auth', 'use', agent, slot],
|
|
196
|
-
restartAgentAfter: true,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (sub === 'list') {
|
|
201
|
-
const agent = parts[1] ?? currentAgent;
|
|
202
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
203
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
204
|
-
return {
|
|
205
|
-
kind: 'list', agent,
|
|
206
|
-
label: `auth list ${agent}`,
|
|
207
|
-
cliArgs: ['auth', 'list', agent, '--json'],
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (sub === 'rm') {
|
|
212
|
-
// /auth rm [agent] <slot> [--force]
|
|
213
|
-
const rest = parts.slice(1);
|
|
214
|
-
const { flags, positional } = splitFlags(rest, ['--force']);
|
|
215
|
-
if (positional.length === 0) {
|
|
216
|
-
return { kind: 'usage', message: 'Usage: /auth rm [agent] <slot> [--force]' };
|
|
217
|
-
}
|
|
218
|
-
const [agent, slot] = positional.length === 1
|
|
219
|
-
? [currentAgent, positional[0]]
|
|
220
|
-
: [positional[0], positional[1]];
|
|
221
|
-
try { assertSafeAgentNameForParser(agent); }
|
|
222
|
-
catch { return { kind: 'error', message: 'Invalid agent name.' }; }
|
|
223
|
-
try { assertSafeSlotName(slot); }
|
|
224
|
-
catch { return { kind: 'error', message: 'Invalid slot name. Use [A-Za-z0-9_-], 1-32 chars.' }; }
|
|
225
|
-
const force = flags['--force'] === true;
|
|
226
|
-
return {
|
|
227
|
-
kind: 'rm', agent, slot, force,
|
|
228
|
-
label: `auth rm ${agent} ${slot}`,
|
|
229
|
-
cliArgs: ['auth', 'rm', agent, slot],
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// --- Account-shaped verbs (see reference/share-auth-across-the-fleet.md) ---
|
|
234
|
-
|
|
235
|
-
if (sub === 'account') {
|
|
236
|
-
const accountSub = (parts[1] ?? 'list').toLowerCase();
|
|
237
|
-
|
|
238
|
-
if (accountSub === 'add') {
|
|
239
|
-
// /auth account add <label> [--from-agent <name>]
|
|
240
|
-
// Default --from-agent to the current agent — that's the common case
|
|
241
|
-
// for a Telegram-only operator who just /auth login'd this agent.
|
|
242
|
-
const rest = parts.slice(2);
|
|
243
|
-
const { flags, positional } = splitFlags(rest, ['--from-agent']);
|
|
244
|
-
const account = positional[0];
|
|
245
|
-
if (!account) {
|
|
246
|
-
return {
|
|
247
|
-
kind: 'usage',
|
|
248
|
-
message: 'Usage: /auth account add <label> [--from-agent <name>]',
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
try { assertSafeAccountLabel(account); }
|
|
252
|
-
catch { return { kind: 'error', message: 'Invalid account label. Use [A-Za-z0-9._@+-], 1-64 chars (email shape OK).' }; }
|
|
253
|
-
const fromAgentRaw = flags['--from-agent'];
|
|
254
|
-
const fromAgent = typeof fromAgentRaw === 'string' ? fromAgentRaw : currentAgent;
|
|
255
|
-
try { assertSafeAgentNameForParser(fromAgent); }
|
|
256
|
-
catch { return { kind: 'error', message: 'Invalid --from-agent value.' }; }
|
|
257
|
-
return {
|
|
258
|
-
kind: 'account-add',
|
|
259
|
-
account,
|
|
260
|
-
fromAgent,
|
|
261
|
-
label: `auth account add ${account}`,
|
|
262
|
-
cliArgs: ['auth', 'account', 'add', account, '--from-agent', fromAgent],
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (accountSub === 'list') {
|
|
267
|
-
return {
|
|
268
|
-
kind: 'account-list',
|
|
269
|
-
label: 'auth account list',
|
|
270
|
-
cliArgs: ['auth', 'account', 'list'],
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (accountSub === 'rm') {
|
|
275
|
-
// /auth account rm <label>
|
|
276
|
-
const account = parts[2];
|
|
277
|
-
if (!account) {
|
|
278
|
-
return { kind: 'usage', message: 'Usage: /auth account rm <label>' };
|
|
279
|
-
}
|
|
280
|
-
try { assertSafeAccountLabel(account); }
|
|
281
|
-
catch { return { kind: 'error', message: 'Invalid account label.' }; }
|
|
282
|
-
return {
|
|
283
|
-
kind: 'account-rm',
|
|
284
|
-
account,
|
|
285
|
-
label: `auth account rm ${account}`,
|
|
286
|
-
cliArgs: ['auth', 'account', 'rm', account],
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (accountSub === 'rename') {
|
|
291
|
-
// /auth account rename <oldLabel> <newLabel>
|
|
292
|
-
const oldAccount = parts[2];
|
|
293
|
-
const newAccount = parts[3];
|
|
294
|
-
if (!oldAccount || !newAccount) {
|
|
295
|
-
return {
|
|
296
|
-
kind: 'usage',
|
|
297
|
-
message: 'Usage: /auth account rename <oldLabel> <newLabel>',
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
try { assertSafeAccountLabel(oldAccount); assertSafeAccountLabel(newAccount); }
|
|
301
|
-
catch { return { kind: 'error', message: 'Invalid account label.' }; }
|
|
302
|
-
if (oldAccount === newAccount) {
|
|
303
|
-
return { kind: 'error', message: `Account "${oldAccount}" already has that name — nothing to do.` };
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
kind: 'account-rename',
|
|
307
|
-
oldAccount,
|
|
308
|
-
newAccount,
|
|
309
|
-
label: `auth account rename ${oldAccount} ${newAccount}`,
|
|
310
|
-
cliArgs: ['auth', 'account', 'rename', oldAccount, newAccount],
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
kind: 'usage',
|
|
316
|
-
message: 'Usage: /auth account add | list | rm | rename (see /auth)',
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (sub === 'enable') {
|
|
321
|
-
// /auth enable <label> [agents...] — defaults to the current agent.
|
|
322
|
-
const rest = parts.slice(1);
|
|
323
|
-
const account = rest[0];
|
|
324
|
-
if (!account) {
|
|
325
|
-
return { kind: 'usage', message: 'Usage: /auth enable <label> [agents...]' };
|
|
326
|
-
}
|
|
327
|
-
try { assertSafeAccountLabel(account); }
|
|
328
|
-
catch { return { kind: 'error', message: 'Invalid account label.' }; }
|
|
329
|
-
const agents = rest.slice(1);
|
|
330
|
-
if (agents.length === 0) agents.push(currentAgent);
|
|
331
|
-
for (const a of agents) {
|
|
332
|
-
try { assertSafeAgentNameForParser(a); }
|
|
333
|
-
catch { return { kind: 'error', message: `Invalid agent name: ${a}` }; }
|
|
334
|
-
}
|
|
335
|
-
return {
|
|
336
|
-
kind: 'enable',
|
|
337
|
-
account,
|
|
338
|
-
agents,
|
|
339
|
-
label: `auth enable ${account} ${agents.join(' ')}`,
|
|
340
|
-
cliArgs: ['auth', 'enable', account, ...agents],
|
|
341
|
-
restartAgentsAfter: true,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (sub === 'disable') {
|
|
346
|
-
// /auth disable <label> [agents...] — defaults to the current agent.
|
|
347
|
-
const rest = parts.slice(1);
|
|
348
|
-
const account = rest[0];
|
|
349
|
-
if (!account) {
|
|
350
|
-
return { kind: 'usage', message: 'Usage: /auth disable <label> [agents...]' };
|
|
351
|
-
}
|
|
352
|
-
try { assertSafeAccountLabel(account); }
|
|
353
|
-
catch { return { kind: 'error', message: 'Invalid account label.' }; }
|
|
354
|
-
const agents = rest.slice(1);
|
|
355
|
-
if (agents.length === 0) agents.push(currentAgent);
|
|
356
|
-
for (const a of agents) {
|
|
357
|
-
try { assertSafeAgentNameForParser(a); }
|
|
358
|
-
catch { return { kind: 'error', message: `Invalid agent name: ${a}` }; }
|
|
359
|
-
}
|
|
360
|
-
return {
|
|
361
|
-
kind: 'disable',
|
|
362
|
-
account,
|
|
363
|
-
agents,
|
|
364
|
-
label: `auth disable ${account} ${agents.join(' ')}`,
|
|
365
|
-
cliArgs: ['auth', 'disable', account, ...agents],
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (sub === 'share') {
|
|
370
|
-
// /auth share <label> [--from-agent <name>] — one-shot: account add + enable
|
|
371
|
-
// on every agent. Defaults --from-agent to the current agent (same shape as
|
|
372
|
-
// /auth account add).
|
|
373
|
-
const rest = parts.slice(1);
|
|
374
|
-
const { flags, positional } = splitFlags(rest, ['--from-agent']);
|
|
375
|
-
const account = positional[0];
|
|
376
|
-
if (!account) {
|
|
377
|
-
return { kind: 'usage', message: 'Usage: /auth share <label> [--from-agent <name>]' };
|
|
378
|
-
}
|
|
379
|
-
try { assertSafeAccountLabel(account); }
|
|
380
|
-
catch { return { kind: 'error', message: 'Invalid account label. Use [A-Za-z0-9._@+-], 1-64 chars (email shape OK).' }; }
|
|
381
|
-
const fromAgentRaw = flags['--from-agent'];
|
|
382
|
-
const fromAgent = typeof fromAgentRaw === 'string' ? fromAgentRaw : currentAgent;
|
|
383
|
-
try { assertSafeAgentNameForParser(fromAgent); }
|
|
384
|
-
catch { return { kind: 'error', message: 'Invalid --from-agent value.' }; }
|
|
385
|
-
return {
|
|
386
|
-
kind: 'share',
|
|
387
|
-
account,
|
|
388
|
-
fromAgent,
|
|
389
|
-
label: `auth share ${account}`,
|
|
390
|
-
cliArgs: ['auth', 'share', account, '--from-agent', fromAgent],
|
|
391
|
-
restartAgentsAfter: true,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return { kind: 'usage', message: usageText() };
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/** Helper to split --flag [value]? from positional args.
|
|
399
|
-
* Value-taking flags are passed in `valueFlags`; bare flags (like
|
|
400
|
-
* --force) show up in `flags` as boolean true.*/
|
|
401
|
-
export function splitFlags(
|
|
402
|
-
parts: string[],
|
|
403
|
-
valueFlags: string[],
|
|
404
|
-
): { flags: Record<string, string | true>; positional: string[] } {
|
|
405
|
-
const flags: Record<string, string | true> = {};
|
|
406
|
-
const positional: string[] = [];
|
|
407
|
-
const valueSet = new Set(valueFlags);
|
|
408
|
-
for (let i = 0; i < parts.length; i++) {
|
|
409
|
-
const p = parts[i];
|
|
410
|
-
if (p.startsWith('--')) {
|
|
411
|
-
if (valueSet.has(p)) {
|
|
412
|
-
const next = parts[i + 1];
|
|
413
|
-
if (next !== undefined && !next.startsWith('--')) { flags[p] = next; i++; }
|
|
414
|
-
else flags[p] = true;
|
|
415
|
-
} else {
|
|
416
|
-
flags[p] = true;
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
positional.push(p);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return { flags, positional };
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** Active + total slot accounting for the rm safety check.
|
|
426
|
-
* Returned from the CLI's --json shape (see src/cli/auth.ts `list`). */
|
|
427
|
-
export type SlotListingFromCli = {
|
|
428
|
-
agent: string;
|
|
429
|
-
slots: Array<{
|
|
430
|
-
slot: string;
|
|
431
|
-
active: boolean;
|
|
432
|
-
health: string;
|
|
433
|
-
expires_at: number | null;
|
|
434
|
-
quota_exhausted_until: number | null;
|
|
435
|
-
}>;
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
/** Check whether a /auth rm is safe. Returns `null` if safe, or an error
|
|
439
|
-
* message if the slot is the only/active slot without --force. */
|
|
440
|
-
export function checkRemoveSafety(
|
|
441
|
-
listing: SlotListingFromCli,
|
|
442
|
-
targetSlot: string,
|
|
443
|
-
force: boolean,
|
|
444
|
-
): string | null {
|
|
445
|
-
if (force) return null;
|
|
446
|
-
if (listing.slots.length <= 1) {
|
|
447
|
-
return `Refusing to remove the only account slot. Add another with /auth add ${listing.agent}, or pass --force to proceed.`;
|
|
448
|
-
}
|
|
449
|
-
const target = listing.slots.find(s => s.slot === targetSlot);
|
|
450
|
-
if (!target) return null; // CLI will error with its own message
|
|
451
|
-
if (target.active) {
|
|
452
|
-
return `Refusing to remove the active slot "${targetSlot}". Switch first with /auth use ${listing.agent} <other-slot>, or pass --force.`;
|
|
453
|
-
}
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/** Format the /auth list CLI --json output as a Telegram HTML block. */
|
|
458
|
-
export function formatSlotList(listing: SlotListingFromCli): string {
|
|
459
|
-
if (!listing.slots || listing.slots.length === 0) {
|
|
460
|
-
return `<i>No slots for <b>${escapeMini(listing.agent)}</b>. Add one with /auth add ${escapeMini(listing.agent)}.</i>`;
|
|
461
|
-
}
|
|
462
|
-
const lines = [`<b>Slots for ${escapeMini(listing.agent)}</b>`];
|
|
463
|
-
for (const s of listing.slots) {
|
|
464
|
-
const active = s.active ? '● ' : ' ';
|
|
465
|
-
const name = `<code>${escapeMini(s.slot)}</code>`;
|
|
466
|
-
const health = healthIcon(s.health) + ' ' + s.health;
|
|
467
|
-
let tail = '';
|
|
468
|
-
if (s.health === 'quota-exhausted' && s.quota_exhausted_until) {
|
|
469
|
-
const mins = Math.max(0, Math.round((s.quota_exhausted_until - Date.now()) / 60_000));
|
|
470
|
-
tail = ` · resets in ~${mins}m`;
|
|
471
|
-
} else if (s.health === 'expired') {
|
|
472
|
-
tail = ' · run /auth reauth';
|
|
473
|
-
}
|
|
474
|
-
lines.push(`${active}${name} ${health}${tail}`);
|
|
475
|
-
}
|
|
476
|
-
return lines.join('\n');
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function healthIcon(health: string): string {
|
|
480
|
-
switch (health) {
|
|
481
|
-
case 'healthy': return '✓';
|
|
482
|
-
case 'quota-exhausted': return '⚠️';
|
|
483
|
-
case 'expired': return '⌛';
|
|
484
|
-
case 'missing': return '✗';
|
|
485
|
-
default: return '·';
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/** Tiny HTML escaper — mirrored from welcome-text.ts so this module
|
|
490
|
-
* stays dependency-free and testable in isolation. */
|
|
491
|
-
function escapeMini(text: string): string {
|
|
492
|
-
return text
|
|
493
|
-
.replace(/&/g, '&')
|
|
494
|
-
.replace(/</g, '<')
|
|
495
|
-
.replace(/>/g, '>')
|
|
496
|
-
.replace(/"/g, '"');
|
|
497
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured logger for the pinned progress-card lifecycle.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors `pin-event-log.ts` in shape: an append-only JSON-line writer with
|
|
5
|
-
* a stable schema. Every meaningful card-driver state transition emits one
|
|
6
|
-
* line so operators can grep / replay days-old sessions and answer "did the
|
|
7
|
-
* card render? when did it finalize? was a sub-agent row ever attached?"
|
|
8
|
-
* without parsing free-form `progress-card:` traces.
|
|
9
|
-
*
|
|
10
|
-
* Output target:
|
|
11
|
-
* - If `$STATE_DIR` is set, `<STATE_DIR>/card-events.jsonl` (append-only).
|
|
12
|
-
* - Otherwise the line is forwarded to stderr (which the plugin-logger
|
|
13
|
-
* captures into `~/.switchroom/logs/telegram-plugin.log`).
|
|
14
|
-
*
|
|
15
|
-
* No rotation in this PR — the file is the durable audit trail and a
|
|
16
|
-
* follow-up can add retention once the size envelope is understood.
|
|
17
|
-
*
|
|
18
|
-
* Pure helper. No globals. The write target is injectable for tests.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { appendFileSync, mkdirSync } from 'fs'
|
|
22
|
-
import { dirname, join } from 'path'
|
|
23
|
-
|
|
24
|
-
export type CardEventName =
|
|
25
|
-
| 'rendered'
|
|
26
|
-
| 'edited'
|
|
27
|
-
| 'finalized'
|
|
28
|
-
| 'suppressed'
|
|
29
|
-
| 'deferred'
|
|
30
|
-
| 'force-completed'
|
|
31
|
-
| 'deleted'
|
|
32
|
-
|
|
33
|
-
export interface CardEvent {
|
|
34
|
-
/** Unix-ms wall clock. */
|
|
35
|
-
ts: number
|
|
36
|
-
/** Agent slug (e.g. SWITCHROOM_AGENT_NAME). Empty string if unknown. */
|
|
37
|
-
agent: string
|
|
38
|
-
/** Telegram chat id as string (matches the rest of the plugin). */
|
|
39
|
-
chatId: string
|
|
40
|
-
/** Driver-assigned per-turn key (chatId:threadId:seq). */
|
|
41
|
-
turnKey: string
|
|
42
|
-
/** The pinned card message_id once known. Optional pre-render. */
|
|
43
|
-
cardMessageId?: number
|
|
44
|
-
event: CardEventName
|
|
45
|
-
/**
|
|
46
|
-
* Free-text qualifier — e.g. the reason a turn was deferred
|
|
47
|
-
* ("in-flight-sub-agents"), the API class for a 4xx abandon, the
|
|
48
|
-
* synthetic kind for a force-complete. Single-line, ≤200 chars.
|
|
49
|
-
*/
|
|
50
|
-
reason?: string
|
|
51
|
-
/** sha1-12 of the rendered HTML, when relevant. Lets us spot edit storms. */
|
|
52
|
-
htmlHash?: string
|
|
53
|
-
/** Sub-agent ids attached to the card at the time of the event. */
|
|
54
|
-
subagents?: string[]
|
|
55
|
-
/** Elapsed ms since turn start, when the call site has it cheaply. */
|
|
56
|
-
durationMs?: number
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type CardEventWriter = (line: string) => void
|
|
60
|
-
|
|
61
|
-
let resolvedPath: string | null | undefined
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Compute the target path once and memoize. `$STATE_DIR` set → write to
|
|
65
|
-
* `<STATE_DIR>/card-events.jsonl`; otherwise return null (the default
|
|
66
|
-
* writer falls back to stderr in that case).
|
|
67
|
-
*
|
|
68
|
-
* Exposed so tests can assert resolution without actually writing.
|
|
69
|
-
*/
|
|
70
|
-
export function resolveCardEventPath(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
71
|
-
const dir = env.STATE_DIR
|
|
72
|
-
if (!dir || dir.length === 0) return null
|
|
73
|
-
return join(dir, 'card-events.jsonl')
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Reset the memoized path. Tests only.
|
|
78
|
-
*/
|
|
79
|
-
export function _resetForTests(): void {
|
|
80
|
-
resolvedPath = undefined
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const defaultWriter: CardEventWriter = (line) => {
|
|
84
|
-
if (resolvedPath === undefined) {
|
|
85
|
-
resolvedPath = resolveCardEventPath()
|
|
86
|
-
}
|
|
87
|
-
const target = resolvedPath
|
|
88
|
-
if (target == null) {
|
|
89
|
-
// Fall back to stderr (the plugin-logger captures stderr into the
|
|
90
|
-
// freeform log). Prefix lets operators grep just like pin-event:.
|
|
91
|
-
try {
|
|
92
|
-
process.stderr.write(`card-event: ${line}`)
|
|
93
|
-
} catch {
|
|
94
|
-
// Never throw from a logger.
|
|
95
|
-
}
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
mkdirSync(dirname(target), { recursive: true })
|
|
100
|
-
appendFileSync(target, line)
|
|
101
|
-
} catch {
|
|
102
|
-
// Best-effort: if the structured sink fails, surface to stderr so the
|
|
103
|
-
// event is at least in the freeform log.
|
|
104
|
-
try {
|
|
105
|
-
process.stderr.write(`card-event: ${line}`)
|
|
106
|
-
} catch {
|
|
107
|
-
// ignore
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function logCardEvent(event: CardEvent, write: CardEventWriter = defaultWriter): void {
|
|
113
|
-
// Drop undefined fields so the JSON output stays compact and grep-friendly.
|
|
114
|
-
const cleaned: Record<string, unknown> = {}
|
|
115
|
-
for (const [k, v] of Object.entries(event)) {
|
|
116
|
-
if (v !== undefined) cleaned[k] = v
|
|
117
|
-
}
|
|
118
|
-
const payload = JSON.stringify(cleaned)
|
|
119
|
-
write(`${payload}\n`)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Convenience constructor — fills `ts` automatically. Most call sites only
|
|
124
|
-
* have agent / chatId / turnKey / event / a few qualifiers; this keeps the
|
|
125
|
-
* boilerplate low.
|
|
126
|
-
*/
|
|
127
|
-
export function emitCardEvent(
|
|
128
|
-
partial: Omit<CardEvent, 'ts'> & { ts?: number },
|
|
129
|
-
write: CardEventWriter = defaultWriter,
|
|
130
|
-
): void {
|
|
131
|
-
logCardEvent(
|
|
132
|
-
{
|
|
133
|
-
ts: partial.ts ?? Date.now(),
|
|
134
|
-
...partial,
|
|
135
|
-
} as CardEvent,
|
|
136
|
-
write,
|
|
137
|
-
)
|
|
138
|
-
}
|