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,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostd dispatch helpers for the gateway's self-restart slash-commands
|
|
3
|
+
* (#1175 RFC C, Phase 2). When the operator has opted into
|
|
4
|
+
* `host_control.enabled: true`, /restart, /new, /reset, and
|
|
5
|
+
* /update apply route through the per-agent hostd UDS instead of the
|
|
6
|
+
* in-container `spawnSwitchroomDetached` shellout.
|
|
7
|
+
*
|
|
8
|
+
* Rationale: in docker-mode (the v0.7+ default) the agent container
|
|
9
|
+
* has no docker binary and no `/var/run/docker.sock` — so the
|
|
10
|
+
* spawn-path verbs fail with exit-127 the moment they touch compose.
|
|
11
|
+
* Hostd runs on the host with the docker socket mounted, so the verbs
|
|
12
|
+
* actually work.
|
|
13
|
+
*
|
|
14
|
+
* Extracted from gateway.ts for unit-testability — gateway.ts itself
|
|
15
|
+
* has too many boot-time side-effects to import directly in a test.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { randomBytes } from "node:crypto";
|
|
19
|
+
import { hostdRequest } from "../../src/host-control/client.js";
|
|
20
|
+
import type {
|
|
21
|
+
HostdRequest,
|
|
22
|
+
HostdResponse,
|
|
23
|
+
} from "../../src/host-control/protocol.js";
|
|
24
|
+
import { loadConfig as loadSwitchroomConfig } from "../../src/config/loader.js";
|
|
25
|
+
|
|
26
|
+
let _hostdEnabled: boolean | undefined;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads `host_control.enabled` from the resolved switchroom config.
|
|
30
|
+
* Cached for the gateway's lifetime — config doesn't change without a
|
|
31
|
+
* restart, and the file-read isn't free.
|
|
32
|
+
*
|
|
33
|
+
* Best-effort: if the config can't be loaded (gateway running in a
|
|
34
|
+
* dir where loadConfig fails), returns false so the dispatch helper
|
|
35
|
+
* falls through to the legacy spawn path.
|
|
36
|
+
*/
|
|
37
|
+
export function isHostdEnabled(): boolean {
|
|
38
|
+
if (_hostdEnabled !== undefined) return _hostdEnabled;
|
|
39
|
+
try {
|
|
40
|
+
const cfg = loadSwitchroomConfig();
|
|
41
|
+
_hostdEnabled = cfg.host_control?.enabled === true;
|
|
42
|
+
} catch {
|
|
43
|
+
_hostdEnabled = false;
|
|
44
|
+
}
|
|
45
|
+
return _hostdEnabled;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal Reset the cache so tests can swap config and re-probe. */
|
|
49
|
+
export function _resetHostdEnabledCache(): void {
|
|
50
|
+
_hostdEnabled = undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function hostdSocketPath(agentName: string): string {
|
|
54
|
+
return `/run/switchroom/hostd/${agentName}/sock`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* True only when (a) host_control is enabled in config AND (b) the
|
|
59
|
+
* per-agent socket is bound on disk. Distinct from "will the wire call
|
|
60
|
+
* succeed" — that's only knowable after attempting it.
|
|
61
|
+
*
|
|
62
|
+
* Callers use this to decide *whether to skip docker-availability
|
|
63
|
+
* preflight guards* (since hostd doesn't need in-container docker).
|
|
64
|
+
*/
|
|
65
|
+
export function hostdWillBeUsed(agentName: string): boolean {
|
|
66
|
+
if (!isHostdEnabled()) return false;
|
|
67
|
+
return existsSync(hostdSocketPath(agentName));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Send one request to the per-agent hostd socket.
|
|
72
|
+
*
|
|
73
|
+
* Returns:
|
|
74
|
+
* - `"not-configured"` — hostd is disabled in config OR the per-agent
|
|
75
|
+
* socket isn't bound. Callers should fall back to the legacy
|
|
76
|
+
* `spawnSwitchroomDetached` path.
|
|
77
|
+
* - `HostdResponse` — hostd was contacted. Callers branch on
|
|
78
|
+
* `resp.result`. Wire errors (ECONNREFUSED, timeout, bad frame)
|
|
79
|
+
* are synthesized into a `result: "error"` response so callers
|
|
80
|
+
* don't need a separate try/catch around the failure.
|
|
81
|
+
*
|
|
82
|
+
* Deliberately no silent fallback to spawn when hostd is configured-on
|
|
83
|
+
* but returns error/denied: the operator opted in, so masking failures
|
|
84
|
+
* would just confuse them about why the verb didn't actually run.
|
|
85
|
+
*/
|
|
86
|
+
export async function tryHostdDispatch(
|
|
87
|
+
agentName: string,
|
|
88
|
+
req: HostdRequest,
|
|
89
|
+
): Promise<HostdResponse | "not-configured"> {
|
|
90
|
+
if (!isHostdEnabled()) return "not-configured";
|
|
91
|
+
const sockPath = hostdSocketPath(agentName);
|
|
92
|
+
if (!existsSync(sockPath)) return "not-configured";
|
|
93
|
+
try {
|
|
94
|
+
return await hostdRequest(
|
|
95
|
+
{ socketPath: sockPath, timeoutMs: 5000 },
|
|
96
|
+
req,
|
|
97
|
+
);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`telegram gateway: hostd dispatch failed ` +
|
|
101
|
+
`(request_id=${req.request_id} op=${req.op}): ` +
|
|
102
|
+
`${(err as Error).message}\n`,
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
v: 1,
|
|
106
|
+
request_id: req.request_id,
|
|
107
|
+
result: "error",
|
|
108
|
+
exit_code: null,
|
|
109
|
+
duration_ms: 0,
|
|
110
|
+
error: `hostd wire error: ${(err as Error).message}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function hostdRequestId(prefix: string): string {
|
|
116
|
+
return `${prefix}-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
117
|
+
}
|
|
@@ -18,6 +18,24 @@ export interface PermissionEvent {
|
|
|
18
18
|
type: "permission";
|
|
19
19
|
requestId: string;
|
|
20
20
|
behavior: "allow" | "deny";
|
|
21
|
+
/**
|
|
22
|
+
* Session-scoped always-allow rule. Only set when the operator taps
|
|
23
|
+
* "🔁 Always allow" — the gateway already persists the rule to
|
|
24
|
+
* switchroom.yaml + settings.json via `switchroom agent grant`, but
|
|
25
|
+
* those writes only kick in on the NEXT agent boot. This field carries
|
|
26
|
+
* the rule to the running bridge so it can short-circuit future
|
|
27
|
+
* `permission_request` notifications (from the parent claude AND any
|
|
28
|
+
* sub-agents dispatched via the Task tool, which share the same MCP
|
|
29
|
+
* server / bridge process) within the current session.
|
|
30
|
+
*
|
|
31
|
+
* Issue #1138: without this, a sub-agent dispatched after the operator
|
|
32
|
+
* tapped "Always allow" still hit the popup, because Claude Code reads
|
|
33
|
+
* `.claude/settings.json` once at boot.
|
|
34
|
+
*
|
|
35
|
+
* Format matches `resolveAlwaysAllowRule`'s output: bare tool name
|
|
36
|
+
* (`Edit`), `Skill(<name>)`, or `mcp__<server>__<tool>`.
|
|
37
|
+
*/
|
|
38
|
+
rule?: string;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
export interface StatusEvent {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent buffer for synthetic inbounds the gateway couldn't deliver
|
|
3
|
+
* because no live IPC client was registered for the agent at send-time.
|
|
4
|
+
*
|
|
5
|
+
* Background: `ipcServer.sendToAgent(agent, msg)` returns `false` when
|
|
6
|
+
* the agent's bridge isn't connected. Before this buffer existed, the
|
|
7
|
+
* gateway logged the failure and dropped the message — root cause of
|
|
8
|
+
* issue #1150 (operator taps Approve on a vault_request_access card,
|
|
9
|
+
* grant lands, but the `vault_grant_approved` inbound that wakes the
|
|
10
|
+
* agent never arrives if the bridge happens to be reconnecting in
|
|
11
|
+
* that exact 100ms window).
|
|
12
|
+
*
|
|
13
|
+
* Contract:
|
|
14
|
+
* - `push(agent, msg)` is best-effort and synchronous. Bounded:
|
|
15
|
+
* a slow / dead bridge can't fill memory.
|
|
16
|
+
* - `drain(agent)` returns ALL pending messages for `agent` in
|
|
17
|
+
* insertion order and removes them from the buffer. Called from
|
|
18
|
+
* `onClientRegistered` so a fresh bridge picks up the missed
|
|
19
|
+
* wake-ups before doing anything else.
|
|
20
|
+
* - In-memory only. Survives across IPC disconnect/reconnect within
|
|
21
|
+
* a single gateway-process lifetime, but NOT a gateway restart.
|
|
22
|
+
* A gateway crash mid-buffer means lost wake-ups; the silence-
|
|
23
|
+
* poke ladder catches this downstream so the worst-case is a
|
|
24
|
+
* 5-minute delay, not a permanent stall.
|
|
25
|
+
*
|
|
26
|
+
* Per-agent cap prevents a never-reconnecting bridge from leaking
|
|
27
|
+
* unbounded memory. When the cap is hit, the OLDEST entry is dropped
|
|
28
|
+
* — the assumption is the freshest wake-up is the most relevant. A
|
|
29
|
+
* dropped entry is logged via the provided logger.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { InboundMessage } from './ipc-protocol.js'
|
|
33
|
+
|
|
34
|
+
/** Default cap per agent. Tuned for `should fit a reasonable backlog of
|
|
35
|
+
* approval cards stacked while bridge is offline` but no more. */
|
|
36
|
+
export const DEFAULT_PENDING_INBOUND_CAP = 32
|
|
37
|
+
|
|
38
|
+
export interface PendingInboundBuffer {
|
|
39
|
+
/** Append `msg` to `agent`'s queue. Returns true if accepted, false if
|
|
40
|
+
* the cap forced an eviction (the message is STILL accepted; `false`
|
|
41
|
+
* signals "tail dropped to make room"). */
|
|
42
|
+
push: (agent: string, msg: InboundMessage) => boolean
|
|
43
|
+
/** Pop and return all pending messages for `agent`. Empty array when
|
|
44
|
+
* none. Idempotent. */
|
|
45
|
+
drain: (agent: string) => InboundMessage[]
|
|
46
|
+
/** Test-only: current depth for `agent`. */
|
|
47
|
+
depth: (agent: string) => number
|
|
48
|
+
/** Test-only: total depth across all agents. */
|
|
49
|
+
totalDepth: () => number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface PendingInboundBufferOptions {
|
|
53
|
+
capPerAgent?: number
|
|
54
|
+
log?: (line: string) => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createPendingInboundBuffer(
|
|
58
|
+
opts: PendingInboundBufferOptions = {},
|
|
59
|
+
): PendingInboundBuffer {
|
|
60
|
+
const cap = opts.capPerAgent ?? DEFAULT_PENDING_INBOUND_CAP
|
|
61
|
+
const log = opts.log ?? ((line: string) => process.stderr.write(line))
|
|
62
|
+
const queues = new Map<string, InboundMessage[]>()
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
push(agent, msg) {
|
|
66
|
+
let q = queues.get(agent)
|
|
67
|
+
if (q == null) {
|
|
68
|
+
q = []
|
|
69
|
+
queues.set(agent, q)
|
|
70
|
+
}
|
|
71
|
+
let evicted = false
|
|
72
|
+
if (q.length >= cap) {
|
|
73
|
+
const dropped = q.shift()
|
|
74
|
+
evicted = true
|
|
75
|
+
log(
|
|
76
|
+
`pending-inbound-buffer: agent=${agent} cap=${cap} reached — ` +
|
|
77
|
+
`dropped oldest entry source=${dropped?.meta?.source ?? '-'} ts=${dropped?.ts ?? '-'}\n`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
q.push(msg)
|
|
81
|
+
log(
|
|
82
|
+
`pending-inbound-buffer: agent=${agent} buffered source=${msg.meta?.source ?? '-'} ` +
|
|
83
|
+
`depth_after=${q.length} evicted=${evicted}\n`,
|
|
84
|
+
)
|
|
85
|
+
return !evicted
|
|
86
|
+
},
|
|
87
|
+
drain(agent) {
|
|
88
|
+
const q = queues.get(agent)
|
|
89
|
+
if (q == null || q.length === 0) return []
|
|
90
|
+
queues.delete(agent)
|
|
91
|
+
log(
|
|
92
|
+
`pending-inbound-buffer: drained agent=${agent} count=${q.length} ` +
|
|
93
|
+
`sources=[${q.map((m) => m.meta?.source ?? '-').join(',')}]\n`,
|
|
94
|
+
)
|
|
95
|
+
return q
|
|
96
|
+
},
|
|
97
|
+
depth(agent) {
|
|
98
|
+
return queues.get(agent)?.length ?? 0
|
|
99
|
+
},
|
|
100
|
+
totalDepth() {
|
|
101
|
+
let n = 0
|
|
102
|
+
for (const q of queues.values()) n += q.length
|
|
103
|
+
return n
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway-side writer for the agent quarantine marker (#1076).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the on-disk contract owned by `src/agents/quarantine.ts` —
|
|
5
|
+
* the host-side host CLI reads the marker, the gateway writes it. We
|
|
6
|
+
* keep a tiny copy here (writer only) because the gateway is bundled
|
|
7
|
+
* separately and can't import from `src/`.
|
|
8
|
+
*
|
|
9
|
+
* The schema MUST stay in sync with `src/agents/quarantine.ts`:
|
|
10
|
+
*
|
|
11
|
+
* { v: 1, reason: "startup.unauthorized", ts: <ms>, detail?: <string> }
|
|
12
|
+
*
|
|
13
|
+
* Any change to the shape should land in both files in the same PR
|
|
14
|
+
* (or be guarded by `v`). See the src module's docstring for the full
|
|
15
|
+
* threat-model and operator remediation.
|
|
16
|
+
*
|
|
17
|
+
* SECURITY: never write the bot token (or any secret material) into
|
|
18
|
+
* the marker. The detail field is for the API description ("Unauthorized")
|
|
19
|
+
* and similar non-secret context.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
23
|
+
import { join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
export const QUARANTINE_FILENAME = 'quarantine.json'
|
|
26
|
+
|
|
27
|
+
export type QuarantineReason =
|
|
28
|
+
| 'startup.unauthorized'
|
|
29
|
+
// Config-class refusal-to-boot. Added 2026-05-13 after the
|
|
30
|
+
// vault-posture init started throwing as an unhandled rejection
|
|
31
|
+
// when the operator declared telegram-id posture but the auto-
|
|
32
|
+
// unlock blob couldn't be read. Pre-fix that path produced a tight
|
|
33
|
+
// restart loop ($n/60s -> hit supervisor cap -> stop) and posted
|
|
34
|
+
// an "agent-crashed" event per restart. The right outcome is a
|
|
35
|
+
// single EX_CONFIG exit at the first failure → supervisor
|
|
36
|
+
// quarantines → operator sees one clean error.
|
|
37
|
+
| 'startup.config_error'
|
|
38
|
+
|
|
39
|
+
export interface QuarantineMarker {
|
|
40
|
+
v: 1
|
|
41
|
+
reason: QuarantineReason
|
|
42
|
+
ts: number
|
|
43
|
+
detail?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Write the quarantine marker into a Telegram state dir (typically
|
|
48
|
+
* `process.env.TELEGRAM_STATE_DIR`). Idempotent — overwrites any
|
|
49
|
+
* existing marker. Creates the parent dir if missing.
|
|
50
|
+
*/
|
|
51
|
+
export function writeQuarantineMarker(
|
|
52
|
+
telegramStateDir: string,
|
|
53
|
+
reason: QuarantineReason,
|
|
54
|
+
detail?: string,
|
|
55
|
+
nowFn: () => number = Date.now,
|
|
56
|
+
): void {
|
|
57
|
+
mkdirSync(telegramStateDir, { recursive: true, mode: 0o700 })
|
|
58
|
+
const marker: QuarantineMarker = {
|
|
59
|
+
v: 1,
|
|
60
|
+
reason,
|
|
61
|
+
ts: nowFn(),
|
|
62
|
+
detail,
|
|
63
|
+
}
|
|
64
|
+
writeFileSync(
|
|
65
|
+
join(telegramStateDir, QUARANTINE_FILENAME),
|
|
66
|
+
JSON.stringify(marker) + '\n',
|
|
67
|
+
'utf-8',
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -37,9 +37,14 @@ export const DEFAULT_TTL_MS = 5 * 60 * 1000 // 5 min
|
|
|
37
37
|
* next scheduled boot gets a live result.
|
|
38
38
|
*/
|
|
39
39
|
export const RATE_LIMIT_TTL_MS = 30 * 1000 // 30 s
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
|
|
41
|
+
// Resolved lazily so tests can set SWITCHROOM_QUOTA_CACHE_PATH per-test
|
|
42
|
+
// (the env var was previously read at module-import time, which made the
|
|
43
|
+
// override in test setUp a no-op once the module was cached).
|
|
44
|
+
export function defaultCachePath(): string {
|
|
45
|
+
return process.env.SWITCHROOM_QUOTA_CACHE_PATH
|
|
42
46
|
?? join(process.env.HOME ?? '/tmp', '.switchroom', 'quota-cache.json')
|
|
47
|
+
}
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* Read a cached probe result if one exists and is still within TTL.
|
|
@@ -54,7 +59,7 @@ export function readQuotaCache(opts: {
|
|
|
54
59
|
path?: string
|
|
55
60
|
now?: number
|
|
56
61
|
} = {}): ProbeResult | null {
|
|
57
|
-
const path = opts.path ??
|
|
62
|
+
const path = opts.path ?? defaultCachePath()
|
|
58
63
|
const now = opts.now ?? Date.now()
|
|
59
64
|
|
|
60
65
|
if (!existsSync(path)) return null
|
|
@@ -102,7 +107,7 @@ export function writeQuotaCache(
|
|
|
102
107
|
// Don't cache hard failures — let the next boot retry clean.
|
|
103
108
|
if (result.status === 'fail') return
|
|
104
109
|
|
|
105
|
-
const path = opts.path ??
|
|
110
|
+
const path = opts.path ?? defaultCachePath()
|
|
106
111
|
// Rate-limit results use a shorter TTL: long enough to absorb a fleet
|
|
107
112
|
// restart burst, short enough that subsequent boots get a live probe.
|
|
108
113
|
// Use the structured `rateLimited` field rather than string-matching on
|