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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analytics-posthog.ts — gateway-side PostHog client.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `src/analytics/posthog.ts` (the CLI's client) but sized for a
|
|
5
|
+
* long-lived gateway process: default batching instead of immediate-flush.
|
|
6
|
+
* Honours the same env vars (SWITCHROOM_POSTHOG_KEY, SWITCHROOM_POSTHOG_HOST,
|
|
7
|
+
* SWITCHROOM_TELEMETRY_DISABLED) so an operator opt-out applies fleet-wide.
|
|
8
|
+
*
|
|
9
|
+
* Distinct ID lineage:
|
|
10
|
+
* 1. SWITCHROOM_ANALYTICS_ID env var — set by compose.ts from the host's
|
|
11
|
+
* ~/.switchroom/analytics-id so per-agent runtime events merge with
|
|
12
|
+
* the same user's CLI events in PostHog.
|
|
13
|
+
* 2. Per-agent fallback UUID at /state/agent/analytics-id when the env
|
|
14
|
+
* var is missing (e.g. legacy compose). Persists across restarts.
|
|
15
|
+
*
|
|
16
|
+
* Every event auto-stamps `agent` and `switchroom_version` so dashboards
|
|
17
|
+
* can slice by agent without each call-site repeating the property.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { PostHog } from 'posthog-node'
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
22
|
+
import { dirname, join } from 'node:path'
|
|
23
|
+
import { randomUUID } from 'node:crypto'
|
|
24
|
+
|
|
25
|
+
const DEFAULT_KEY = 'phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd'
|
|
26
|
+
const DEFAULT_HOST = 'https://us.i.posthog.com'
|
|
27
|
+
|
|
28
|
+
let client: PostHog | null = null
|
|
29
|
+
let initialized = false
|
|
30
|
+
let cachedDistinctId: string | null = null
|
|
31
|
+
let globalHandlersInstalled = false
|
|
32
|
+
|
|
33
|
+
function telemetryDisabled(): boolean {
|
|
34
|
+
const v = process.env.SWITCHROOM_TELEMETRY_DISABLED
|
|
35
|
+
return v === '1' || v === 'true'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function agentName(): string {
|
|
39
|
+
return process.env.SWITCHROOM_AGENT_NAME ?? 'unknown'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function switchroomVersion(): string {
|
|
43
|
+
return process.env.SWITCHROOM_VERSION ?? 'unknown'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getDistinctId(): string {
|
|
47
|
+
if (cachedDistinctId) return cachedDistinctId
|
|
48
|
+
const envId = process.env.SWITCHROOM_ANALYTICS_ID
|
|
49
|
+
if (envId && envId.trim() !== '') {
|
|
50
|
+
cachedDistinctId = envId.trim()
|
|
51
|
+
return cachedDistinctId
|
|
52
|
+
}
|
|
53
|
+
const fallbackPath = join(
|
|
54
|
+
process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? '/state/agent',
|
|
55
|
+
'analytics-id',
|
|
56
|
+
)
|
|
57
|
+
try {
|
|
58
|
+
if (existsSync(fallbackPath)) {
|
|
59
|
+
const existing = readFileSync(fallbackPath, 'utf-8').trim()
|
|
60
|
+
if (existing) {
|
|
61
|
+
cachedDistinctId = existing
|
|
62
|
+
return existing
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// fall through to fresh uuid
|
|
67
|
+
}
|
|
68
|
+
const id = randomUUID()
|
|
69
|
+
cachedDistinctId = id
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(dirname(fallbackPath), { recursive: true })
|
|
72
|
+
writeFileSync(fallbackPath, id, 'utf-8')
|
|
73
|
+
} catch {
|
|
74
|
+
// non-fatal — fresh uuid next boot is acceptable
|
|
75
|
+
}
|
|
76
|
+
return id
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getPostHog(): PostHog | null {
|
|
80
|
+
if (initialized) return client
|
|
81
|
+
initialized = true
|
|
82
|
+
if (telemetryDisabled()) return null
|
|
83
|
+
const apiKey = process.env.SWITCHROOM_POSTHOG_KEY ?? DEFAULT_KEY
|
|
84
|
+
const host = process.env.SWITCHROOM_POSTHOG_HOST ?? DEFAULT_HOST
|
|
85
|
+
if (!apiKey) return null
|
|
86
|
+
try {
|
|
87
|
+
client = new PostHog(apiKey, {
|
|
88
|
+
host,
|
|
89
|
+
// Long-lived gateway: rely on default batching instead of the
|
|
90
|
+
// immediate-flush the short-lived CLI uses.
|
|
91
|
+
enableExceptionAutocapture: false,
|
|
92
|
+
// IP is considered PII in our telemetry policy (see docs/posthog.md).
|
|
93
|
+
disableGeoip: true,
|
|
94
|
+
})
|
|
95
|
+
} catch {
|
|
96
|
+
client = null
|
|
97
|
+
}
|
|
98
|
+
return client
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function captureEvent(
|
|
102
|
+
event: string,
|
|
103
|
+
properties: Record<string, unknown> = {},
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const ph = getPostHog()
|
|
106
|
+
if (!ph) return
|
|
107
|
+
try {
|
|
108
|
+
ph.capture({
|
|
109
|
+
distinctId: getDistinctId(),
|
|
110
|
+
event,
|
|
111
|
+
properties: {
|
|
112
|
+
agent: agentName(),
|
|
113
|
+
switchroom_version: switchroomVersion(),
|
|
114
|
+
source: 'gateway',
|
|
115
|
+
...properties,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
} catch {
|
|
119
|
+
// Telemetry must never break the gateway.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function captureException(
|
|
124
|
+
error: unknown,
|
|
125
|
+
properties: Record<string, unknown> = {},
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
const ph = getPostHog()
|
|
128
|
+
if (!ph) return
|
|
129
|
+
try {
|
|
130
|
+
await ph.captureException(error, getDistinctId(), {
|
|
131
|
+
agent: agentName(),
|
|
132
|
+
switchroom_version: switchroomVersion(),
|
|
133
|
+
source: 'gateway',
|
|
134
|
+
...properties,
|
|
135
|
+
})
|
|
136
|
+
} catch {
|
|
137
|
+
// Telemetry must never break the gateway.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function shutdownAnalytics(): Promise<void> {
|
|
142
|
+
if (!client) return
|
|
143
|
+
try {
|
|
144
|
+
await client.shutdown(2000)
|
|
145
|
+
} catch {
|
|
146
|
+
// ignore
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Install process-level handlers for uncaught exceptions and unhandled
|
|
152
|
+
* rejections so they're reported to PostHog before the process dies.
|
|
153
|
+
*
|
|
154
|
+
* Mirrors the CLI's `installGlobalErrorHandlers()` so runtime errors land
|
|
155
|
+
* in the same Switchroom Errors dashboard as CLI errors, tagged
|
|
156
|
+
* `source: 'gateway'`.
|
|
157
|
+
*
|
|
158
|
+
* The gateway already exits non-zero on fatal errors (see the polling
|
|
159
|
+
* IIFE at the bottom of gateway.ts). We DO NOT re-exit here for
|
|
160
|
+
* unhandledRejection — Node's default is to keep running and we want
|
|
161
|
+
* the gateway to keep polling. For uncaughtException we DO exit, because
|
|
162
|
+
* Node's default-since-v15 is to exit anyway after listeners return.
|
|
163
|
+
*/
|
|
164
|
+
export function installGlobalErrorHandlers(): void {
|
|
165
|
+
if (globalHandlersInstalled) return
|
|
166
|
+
globalHandlersInstalled = true
|
|
167
|
+
|
|
168
|
+
const FLUSH_TIMEOUT_MS = 2000
|
|
169
|
+
|
|
170
|
+
const flushWithTimeout = async (
|
|
171
|
+
error: unknown,
|
|
172
|
+
kind: 'uncaughtException' | 'unhandledRejection',
|
|
173
|
+
): Promise<void> => {
|
|
174
|
+
await Promise.race([
|
|
175
|
+
captureException(error, { kind }),
|
|
176
|
+
new Promise<void>((resolve) => setTimeout(resolve, FLUSH_TIMEOUT_MS)),
|
|
177
|
+
])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
process.on('uncaughtException', (err) => {
|
|
181
|
+
process.stderr.write(`telegram gateway: uncaughtException: ${err}\n`)
|
|
182
|
+
void flushWithTimeout(err, 'uncaughtException').finally(() => {
|
|
183
|
+
process.exit(1)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
process.on('unhandledRejection', (reason) => {
|
|
188
|
+
process.stderr.write(`telegram gateway: unhandledRejection: ${reason}\n`)
|
|
189
|
+
void flushWithTimeout(reason, 'unhandledRejection')
|
|
190
|
+
})
|
|
191
|
+
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../pty-tail.js'
|
|
29
29
|
import { createIpcClient, type IpcClientHandle } from './ipc-client.js'
|
|
30
30
|
import type { InboundMessage, PermissionEvent, StatusEvent } from '../gateway/ipc-protocol.js'
|
|
31
|
+
import { matchesAllowRule } from '../permission-rule.js'
|
|
31
32
|
|
|
32
33
|
installPluginLogger()
|
|
33
34
|
|
|
@@ -108,6 +109,7 @@ const TOOL_SCHEMAS = [
|
|
|
108
109
|
disable_web_page_preview: { type: 'boolean', description: 'Disable link preview thumbnails. Default: true.' },
|
|
109
110
|
protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
|
|
110
111
|
quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
|
|
112
|
+
disable_notification: { type: 'boolean', description: 'When true, Telegram delivers the message silently — no device ping for this user. Default false (pings). Use true for mid-turn updates ("still working through X") so only the final answer pings. Always omit (or pass false) on the final answer of a turn.' },
|
|
111
113
|
inline_keyboard: {
|
|
112
114
|
type: 'array',
|
|
113
115
|
description: 'Optional 2D array of tappable buttons rendered under the message. Outer array = rows; inner array = buttons in each row (max 8 per row, 8 rows). Each button needs a `text` (label, max 64 chars) plus EXACTLY ONE of: `url` (opens link in browser; must start with http(s):// or tg://) or `callback_data` (string, max 58 chars; tap is delivered to this agent as an inbound channel event with meta.button_callback_data=<the data> and the original button_text). Use buttons for single-tap approval/triage flows like [Approve] [Hold]; one tap on mobile beats asking the user to type YES/NO. By default a tap shows a brief "✓ received" toast and removes the entire keyboard so the user can\'t double-fire — override per-button via `ack_text` (custom toast text, max 200 chars) and `single_use: false` (preserve the keyboard so e.g. a [Refresh] button stays tappable).',
|
|
@@ -146,6 +148,7 @@ const TOOL_SCHEMAS = [
|
|
|
146
148
|
quote: { type: 'boolean', description: 'Opt out of the default quote-reply behavior. Default: true. Ignored when reply_to is explicitly set.' },
|
|
147
149
|
protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
|
|
148
150
|
quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
|
|
151
|
+
disable_notification: { type: 'boolean', description: 'When true, the INITIAL message send is silent (no device ping). Has no effect on subsequent edits — Telegram never pings on editMessageText. Default false. Use for mid-turn stream starts you do not want to ping; omit on the final answer.' },
|
|
149
152
|
inline_keyboard: {
|
|
150
153
|
type: 'array',
|
|
151
154
|
description: '2D array of tappable buttons under the final message. Same shape and constraints as `reply.inline_keyboard` — each button has `text` and EXACTLY ONE of `url` or `callback_data`, plus optional `ack_text` (custom tap-toast; default "✓ received") and `single_use` (default true; set false to keep the keyboard tappable after a tap). Tap on a callback_data button is delivered to this agent as an inbound channel event with meta.button_callback_data set.',
|
|
@@ -418,6 +421,23 @@ const TOOL_SCHEMAS = [
|
|
|
418
421
|
required: ['chat_id', 'key', 'value'],
|
|
419
422
|
},
|
|
420
423
|
},
|
|
424
|
+
{
|
|
425
|
+
name: 'vault_request_access',
|
|
426
|
+
description:
|
|
427
|
+
'Ask the operator (via Telegram approval card) to grant this agent read or write access to a vault key it does not yet have. Use this when you hit `VAULT-BROKER-DENIED` or when you know upfront that an upcoming task needs a key you lack. Renders a [Approve] [Deny] card; on approve, the broker mints a scoped grant token and writes it to the agent\'s `.vault-token` file. You CANNOT mint or self-elevate; only the operator can tap Approve. After firing this tool, END YOUR TURN cleanly — the gateway will inject a fresh inbound message (with `<channel source="vault_grant_approved">`) when the operator approves, kicking off a new turn where you can resume the original task. Do NOT call this for keys you already have access to (use the normal `vault:<key>` reference) and do NOT spam-request (the operator sees every card).',
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: 'object',
|
|
430
|
+
properties: {
|
|
431
|
+
chat_id: { type: 'string', description: 'Chat to render the approval card in (use the chat_id of the user message that triggered the workflow).' },
|
|
432
|
+
key: { type: 'string', description: 'Vault key the agent wants access to (matches the key shown in the VAULT-BROKER-DENIED error, e.g. `fatsecret/credentials`).' },
|
|
433
|
+
scope: { type: 'string', enum: ['read', 'write'], description: 'Access scope: "read" (default) for `vault:<key>` references; "write" if the agent needs to put new values.' },
|
|
434
|
+
reason: { type: 'string', description: 'Short human-readable rationale rendered on the card (e.g. "to look up today\'s food log entries"). Helps the operator decide.' },
|
|
435
|
+
duration: { type: 'string', description: 'Requested grant TTL, like "30d" or "12h". Default 30d, capped at 90d. Beyond 90d the operator should use the host CLI explicitly.' },
|
|
436
|
+
message_thread_id: { type: 'string', description: 'Forum topic thread ID. Auto-applied from the last inbound message if not specified.' },
|
|
437
|
+
},
|
|
438
|
+
required: ['chat_id', 'key'],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
421
441
|
]
|
|
422
442
|
|
|
423
443
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }))
|
|
@@ -469,6 +489,22 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
469
489
|
// approval. Forward them to the gateway which renders inline keyboard
|
|
470
490
|
// buttons in the user's Telegram chat. The gateway sends the decision
|
|
471
491
|
// back as a PermissionEvent which we relay to Claude Code (see onPermission).
|
|
492
|
+
//
|
|
493
|
+
// #1138: session-scoped always-allow cache. When the operator taps
|
|
494
|
+
// "🔁 Always allow" the gateway calls `switchroom agent grant` which
|
|
495
|
+
// updates settings.json on disk, but the running claude process won't
|
|
496
|
+
// re-read that file — so a sub-agent (Task tool) dispatched later in
|
|
497
|
+
// the same session still hits the popup. To close that gap the gateway
|
|
498
|
+
// also broadcasts the resolved `rule` on the `permission` event and we
|
|
499
|
+
// stash it here; subsequent `permission_request` notifications whose
|
|
500
|
+
// (tool_name, input_preview) match a cached rule are auto-allowed
|
|
501
|
+
// without a round-trip to Telegram. The cache lives for the bridge's
|
|
502
|
+
// lifetime — which is the claude session's lifetime — so on the next
|
|
503
|
+
// boot the now-persisted `tools.allow` entry takes over and this cache
|
|
504
|
+
// is rebuilt as the operator approves things again. Parent claude and
|
|
505
|
+
// every Task-tool sub-agent share the same bridge process, so a rule
|
|
506
|
+
// added by either is honoured by all.
|
|
507
|
+
const sessionAllowRules = new Set<string>()
|
|
472
508
|
|
|
473
509
|
mcp.setNotificationHandler(
|
|
474
510
|
z.object({
|
|
@@ -481,6 +517,28 @@ mcp.setNotificationHandler(
|
|
|
481
517
|
}),
|
|
482
518
|
}),
|
|
483
519
|
async ({ params }) => {
|
|
520
|
+
// Cache hit? Auto-allow without bothering the gateway. We deliver
|
|
521
|
+
// the same `notifications/claude/channel/permission` shape claude
|
|
522
|
+
// would otherwise receive after a Telegram tap, so the call site
|
|
523
|
+
// is indistinguishable. We still notify the gateway out-of-band
|
|
524
|
+
// (via a permission_request that the gateway short-circuits on
|
|
525
|
+
// its side would be ideal, but for now skipping the forward is
|
|
526
|
+
// safe: pendingPermissions is a gateway-side bookkeeping map only,
|
|
527
|
+
// and nothing else depends on seeing this request_id).
|
|
528
|
+
for (const rule of sessionAllowRules) {
|
|
529
|
+
if (matchesAllowRule(rule, params.tool_name, params.input_preview)) {
|
|
530
|
+
process.stderr.write(
|
|
531
|
+
`telegram bridge: session-cached allow for ${params.tool_name} ` +
|
|
532
|
+
`(rule="${rule}", request_id=${params.request_id})\n`,
|
|
533
|
+
)
|
|
534
|
+
onPermission({
|
|
535
|
+
type: 'permission',
|
|
536
|
+
requestId: params.request_id,
|
|
537
|
+
behavior: 'allow',
|
|
538
|
+
})
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
}
|
|
484
542
|
if (!ipc || !ipc.isConnected()) {
|
|
485
543
|
process.stderr.write('telegram bridge: permission_request received but not connected to gateway\n')
|
|
486
544
|
return
|
|
@@ -495,6 +553,7 @@ mcp.setNotificationHandler(
|
|
|
495
553
|
},
|
|
496
554
|
)
|
|
497
555
|
|
|
556
|
+
|
|
498
557
|
// ─── IPC client ──────────────────────────────────────────────────────────
|
|
499
558
|
|
|
500
559
|
let ipc: IpcClientHandle | null = null
|
|
@@ -513,6 +572,16 @@ function onInbound(msg: InboundMessage): void {
|
|
|
513
572
|
}
|
|
514
573
|
|
|
515
574
|
function onPermission(msg: PermissionEvent): void {
|
|
575
|
+
// #1138: stash the rule the gateway resolved on "Always allow" so we
|
|
576
|
+
// can short-circuit later matching permission_request notifications
|
|
577
|
+
// (from the parent claude or any Task-dispatched sub-agent in the
|
|
578
|
+
// same session). The gateway only sets `rule` when it has also
|
|
579
|
+
// persisted the rule to settings.json, so a process restart will
|
|
580
|
+
// pick up the same set of rules from disk — the cache is purely a
|
|
581
|
+
// mid-session bridge between the disk write and the next agent boot.
|
|
582
|
+
if (msg.rule) {
|
|
583
|
+
sessionAllowRules.add(msg.rule)
|
|
584
|
+
}
|
|
516
585
|
mcp.notification({
|
|
517
586
|
method: 'notifications/claude/channel/permission',
|
|
518
587
|
params: {
|
|
@@ -23,7 +23,10 @@ export function validateGatewayMessage(msg: unknown): msg is GatewayToClient {
|
|
|
23
23
|
return typeof m.chatId === "string" && typeof m.text === "string";
|
|
24
24
|
case "permission":
|
|
25
25
|
return typeof m.requestId === "string"
|
|
26
|
-
&& (m.behavior === "allow" || m.behavior === "deny")
|
|
26
|
+
&& (m.behavior === "allow" || m.behavior === "deny")
|
|
27
|
+
// `rule` is optional (only sent on "🔁 Always allow"); when present
|
|
28
|
+
// it must be a non-empty string. #1138 wire extension.
|
|
29
|
+
&& (m.rule === undefined || (typeof m.rule === "string" && m.rule.length > 0));
|
|
27
30
|
case "status":
|
|
28
31
|
return typeof m.status === "string";
|
|
29
32
|
case "tool_call_result":
|