switchroom 0.18.11 → 0.18.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +37 -5
- package/dist/auth-broker/index.js +116 -78
- package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
- package/dist/cli/ms-365-write-pretool.mjs +31 -8
- package/dist/cli/notion-write-pretool.mjs +38 -6
- package/dist/cli/skill-validate-pretool.mjs +144 -2847
- package/dist/cli/switchroom.js +3529 -4543
- package/dist/cli/ui/index.html +163 -17
- package/dist/host-control/main.js +605 -2847
- package/dist/vault/approvals/kernel-server.js +120 -13
- package/dist/vault/broker/server.js +259 -157
- package/package.json +3 -4
- package/profiles/_base/start.sh.hbs +65 -0
- package/profiles/_shared/vault-protocol.md.hbs +3 -1
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/bridge/bridge.ts +37 -0
- package/telegram-plugin/bridge/inbound-dedup.ts +101 -0
- package/telegram-plugin/dist/bridge/bridge.js +122 -4
- package/telegram-plugin/dist/gateway/gateway.js +4213 -3288
- package/telegram-plugin/dist/server.js +139 -5
- package/telegram-plugin/flood-circuit-breaker.ts +493 -21
- package/telegram-plugin/format.ts +19 -0
- package/telegram-plugin/gateway/approval-hold.ts +602 -0
- package/telegram-plugin/gateway/auth-command.ts +92 -2
- package/telegram-plugin/gateway/auth-loopback-relay.ts +670 -0
- package/telegram-plugin/gateway/boot-card.ts +12 -5
- package/telegram-plugin/gateway/callback-query-handlers.ts +88 -1
- package/telegram-plugin/gateway/config-approval-handler.ts +6 -1
- package/telegram-plugin/gateway/disconnect-flush.ts +19 -0
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +251 -0
- package/telegram-plugin/gateway/dm-pin-sweep.ts +178 -0
- package/telegram-plugin/gateway/gateway.ts +1695 -230
- package/telegram-plugin/gateway/hostd-dispatch.ts +23 -0
- package/telegram-plugin/gateway/idle-clear.ts +90 -6
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +26 -5
- package/telegram-plugin/gateway/inject-handler.ts +8 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +46 -3
- package/telegram-plugin/gateway/ipc-server.ts +43 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +145 -37
- package/telegram-plugin/gateway/model-command.ts +9 -3
- package/telegram-plugin/gateway/pending-session-command.ts +13 -1
- package/telegram-plugin/gateway/permission-ttl-sweep.ts +66 -0
- package/telegram-plugin/gateway/pre-approval-check.ts +74 -0
- package/telegram-plugin/gateway/queued-card-store.ts +217 -0
- package/telegram-plugin/gateway/session-model-file.ts +26 -1
- package/telegram-plugin/gateway/turn-end-gate-backstop.ts +59 -0
- package/telegram-plugin/gateway/turn-end-gate.ts +95 -0
- package/telegram-plugin/gateway/turn-typing-loop.ts +10 -2
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +13 -0
- package/telegram-plugin/history.ts +51 -0
- package/telegram-plugin/hooks/dispatch-claim-scan.mjs +259 -0
- package/telegram-plugin/hooks/dispatch-claim-stop.mjs +129 -0
- package/telegram-plugin/hooks/hooks.json +9 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +303 -2
- package/telegram-plugin/model-unavailable.ts +41 -11
- package/telegram-plugin/operator-events.ts +23 -0
- package/telegram-plugin/outbound-field-redact.ts +69 -0
- package/telegram-plugin/package.json +0 -1
- package/telegram-plugin/permission-rule.ts +1 -0
- package/telegram-plugin/permission-title.ts +1 -0
- package/telegram-plugin/render/render.ts +32 -14
- package/telegram-plugin/retry-api-call.ts +212 -2
- package/telegram-plugin/scoped-approval.ts +11 -2
- package/telegram-plugin/secret-detect/chunker.ts +18 -4
- package/telegram-plugin/secret-detect/index.ts +12 -56
- package/telegram-plugin/send-gate-degraded.test.ts +574 -0
- package/telegram-plugin/send-gate-observability.test.ts +470 -0
- package/telegram-plugin/send-gate-observability.ts +355 -0
- package/telegram-plugin/send-gate.test.ts +717 -0
- package/telegram-plugin/send-gate.ts +1056 -0
- package/telegram-plugin/session-tail.ts +82 -7
- package/telegram-plugin/shared/bot-runtime.ts +17 -5
- package/telegram-plugin/shared/gw-trace-gate.ts +105 -0
- package/telegram-plugin/status-pin-driver.ts +52 -7
- package/telegram-plugin/status-pin.ts +81 -0
- package/telegram-plugin/subagent-watcher.ts +173 -18
- package/telegram-plugin/tests/activity-card-wiring.test.ts +18 -5
- package/telegram-plugin/tests/approval-hold-harness.ts +425 -0
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +327 -0
- package/telegram-plugin/tests/approval-hold-record.test.ts +531 -0
- package/telegram-plugin/tests/approval-hold-redeliver.test.ts +602 -0
- package/telegram-plugin/tests/auth-loopback-relay.test.ts +533 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +53 -7
- package/telegram-plugin/tests/busy-key-reaper.test.ts +1 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
- package/telegram-plugin/tests/dispatch-claim-scan.test.ts +250 -0
- package/telegram-plugin/tests/flood-breaker-blindness.test.ts +213 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +224 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +29 -1
- package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +66 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
- package/telegram-plugin/tests/gw-trace-gate.test.ts +105 -0
- package/telegram-plugin/tests/history.test.ts +115 -0
- package/telegram-plugin/tests/idle-clear.test.ts +233 -3
- package/telegram-plugin/tests/inbound-dedup.test.ts +93 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +448 -0
- package/telegram-plugin/tests/ipc-server-check-pre-approved.test.ts +194 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +123 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +1 -1
- package/telegram-plugin/tests/model-command.test.ts +14 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
- package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
- package/telegram-plugin/tests/pending-session-command.test.ts +21 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +30 -5
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +8 -7
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +1 -1
- package/telegram-plugin/tests/pre-approval-check.test.ts +148 -0
- package/telegram-plugin/tests/queued-card-store.test.ts +232 -0
- package/telegram-plugin/tests/reaction-flush-turn-gated.test.ts +100 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
- package/telegram-plugin/tests/render/render.test.ts +88 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +398 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
- package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
- package/telegram-plugin/tests/session-model-file.test.ts +50 -0
- package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +35 -14
- package/telegram-plugin/tests/status-pin.test.ts +275 -1
- package/telegram-plugin/tests/subagent-watcher-deferral-log-ratelimit.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
- package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +92 -0
- package/telegram-plugin/tests/turn-end-gate.test.ts +137 -0
- package/telegram-plugin/tests/typing-emitter.test.ts +586 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +20 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
- package/telegram-plugin/typing-emitter.ts +224 -0
- package/telegram-plugin/uat/scenarios/jtbd-feel-like-a-colleague-dm.test.ts +136 -0
- package/telegram-plugin/welcome-text.ts +42 -0
- package/telegram-plugin/worktree-watch-cwds.ts +194 -5
- package/vendor/hindsight-memory/scripts/drain_pending.py +22 -6
- package/vendor/hindsight-memory/scripts/lib/client.py +12 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +38 -3
- package/vendor/hindsight-memory/scripts/lib/pending.py +36 -9
- package/vendor/hindsight-memory/scripts/session_end.py +14 -3
- package/vendor/hindsight-memory/scripts/session_start.py +21 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +38 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +68 -0
- package/vendor/hindsight-memory/tests/test_pending.py +44 -0
- package/vendor/hindsight-memory/tests/test_session_end_pending.py +38 -0
- package/vendor/hindsight-memory/tests/test_session_start_drain.py +155 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +0 -56
- package/telegram-plugin/channel-envelope-safety.ts +0 -56
- package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
|
@@ -4116,7 +4116,12 @@ var init_schema = __esm(() => {
|
|
|
4116
4116
|
AgentSoulSchema = exports_external.object({
|
|
4117
4117
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
4118
4118
|
style: exports_external.string().describe("Communication style description"),
|
|
4119
|
-
|
|
4119
|
+
creature: exports_external.string().optional().describe("Persona creature/form (e.g., 'owl', 'octopus')"),
|
|
4120
|
+
vibe: exports_external.string().optional().describe("One-line personality vibe (e.g., 'calm, precise')"),
|
|
4121
|
+
expertise: exports_external.string().optional().describe("Domain expertise summary rendered into the persona"),
|
|
4122
|
+
emoji: exports_external.string().optional().describe("Signature emoji for the persona (e.g., '\uD83E\uDD89')"),
|
|
4123
|
+
boundaries: exports_external.string().optional().describe("Behavioral boundaries and disclaimers"),
|
|
4124
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).default("generalist").describe("Persona-shape discriminator the per-agent CLAUDE.md template can " + "branch on (e.g., 'you are an executive assistant' vs 'a senior " + "engineer'). No runtime behavior yet — template work lands in a " + "later issue. Cascade: override (per-agent wins over default).")
|
|
4120
4125
|
}).optional();
|
|
4121
4126
|
AgentToolsSchema = exports_external.object({
|
|
4122
4127
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
@@ -4281,6 +4286,11 @@ var init_schema = __esm(() => {
|
|
|
4281
4286
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
4282
4287
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short — the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
4283
4288
|
}).optional().describe("Interrupt timing — how a `!`-prefix interrupt behaves when it lands " + "mid-tool-call. Off by default (fire immediately). Cascades from " + "defaults.channels.telegram.interrupt."),
|
|
4289
|
+
button_choice_confirmation: exports_external.object({
|
|
4290
|
+
enabled: exports_external.boolean().default(false).describe("When true, tapping an agent-emitted inline_keyboard button annotates " + "the source message body with a '✅ You chose: <label> · HH:MM' line so " + "the chat surface is self-documenting. Only applies to single-use " + "keyboards (re-tappable keyboards are never annotated). Requires the " + "agent's parseMode to be the default 'html'. Opt-in (default false). " + "Limitation: the annotated body is rebuilt from the message's plain " + "text, so any formatting entities (bold, links, code, ...) on the " + "original message are lost when the annotation is applied."),
|
|
4291
|
+
format: exports_external.string().default("✅ You chose: {label} · {time}").describe("Template for the annotation line. `{label}` is replaced with the " + "tapped button's text (newlines stripped, HTML-escaped, trimmed to 60 " + "chars) and `{time}` with HH:MM in the chosen timezone. Note: retap " + "dedup (replacing a prior annotation instead of appending another " + "line) only recognizes the DEFAULT format shape — a custom format " + "accumulates one line per retap."),
|
|
4292
|
+
timezone: exports_external.enum(["gateway", "utc"]).default("gateway").describe("Timezone for the {time} placeholder. 'gateway' uses the gateway " + "process's local time; 'utc' uses UTC. Per-user timezones are out of " + "scope for v1.")
|
|
4293
|
+
}).optional().describe("Auto-confirm 'You chose: X' annotation on inline_keyboard taps (#789). " + "Cascades from defaults.channels.telegram.button_choice_confirmation."),
|
|
4284
4294
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token, " + "'linear' = Linear-Signature bare-hex HMAC-SHA256 of the raw body). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
4285
4295
|
webhook_dispatch: exports_external.object({
|
|
4286
4296
|
github: exports_external.array(webhookDispatchRule).optional(),
|
|
@@ -4429,7 +4439,12 @@ var init_schema = __esm(() => {
|
|
|
4429
4439
|
soul: exports_external.object({
|
|
4430
4440
|
name: exports_external.string().optional(),
|
|
4431
4441
|
style: exports_external.string().optional(),
|
|
4432
|
-
|
|
4442
|
+
creature: exports_external.string().optional(),
|
|
4443
|
+
vibe: exports_external.string().optional(),
|
|
4444
|
+
expertise: exports_external.string().optional(),
|
|
4445
|
+
emoji: exports_external.string().optional(),
|
|
4446
|
+
boundaries: exports_external.string().optional(),
|
|
4447
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).optional()
|
|
4433
4448
|
}).optional(),
|
|
4434
4449
|
tools: exports_external.object({
|
|
4435
4450
|
allow: exports_external.array(exports_external.string()).optional(),
|
|
@@ -4626,7 +4641,8 @@ var init_schema = __esm(() => {
|
|
|
4626
4641
|
autoUnlockCredentialPath: exports_external.string().default("~/.switchroom/vault-auto-unlock").describe("Path to the machine-bound auto-unlock blob (see " + "src/vault/auto-unlock.ts for the format). Default lives under " + "~/.switchroom so it can be bind-mounted into the vault-broker " + "container by docker compose. Tilde-expansion happens " + "at read time."),
|
|
4627
4642
|
approvalAuth: exports_external.enum(["passphrase", "telegram-id"]).default("passphrase").describe("Posture for tap-to-Approve on vault grant cards. `passphrase` " + "(default) prompts the operator to type the vault passphrase on " + "every Approve — two-factor (Telegram ID + passphrase). " + "`telegram-id` mints immediately on Approve with no passphrase " + "prompt — single-factor (Telegram ID only); REQUIRES " + "`autoUnlock: true` so the broker already holds the passphrase. " + "Trades a factor of security for smoother UX; opt-in only."),
|
|
4628
4643
|
postureMintAgents: exports_external.array(exports_external.string().min(1)).default([]).describe("Per-agent opt-in for posture-attested broker calls (`mint_grant` / " + "`list_grants` / `put` with `attest_via_posture: true`). Only agents " + "whose names are in this list can use the silent-mint path under " + "`approvalAuth: telegram-id`. Default `[]` — no agent can self-mint " + "until the operator explicitly opts it in. The request's `agent` " + "field must also equal the calling peer's resolved agent name " + "(broker rejects cross-agent posture mints). When `approvalAuth` is " + "`passphrase` this list is ignored — passphrase attestation still " + "works as before. Each entry is an agent slug exactly as it appears " + "under `agents:` in this config."),
|
|
4629
|
-
adminOnlyKeys: exports_external.array(exports_external.string().min(1)).default([]).describe("Vault keys held to a higher approval bar: only the admin operator " + "(`access.allowFrom[0]`) may approve a grant for them, and they can " + "NEVER be minted via posture attestation — granting one requires the " + "operator passphrase (so an agent, even one on `postureMintAgents`, " + "cannot self-grant it). Entries are exact key names or `*` globs, " + "e.g. `stripe/*`, `*/oauth-token`, `microsoft/ken-tokens` (`*` matches " + "any run of characters incl. `/`; case-sensitive). Default `[]` — no " + "key is admin-only. Posture may RETAIN an admin-only key across a " + "union re-mint but never ADD one. Takes effect on broker + gateway " + "restart (broker has no ACL hot-reload).")
|
|
4644
|
+
adminOnlyKeys: exports_external.array(exports_external.string().min(1)).default([]).describe("Vault keys held to a higher approval bar: only the admin operator " + "(`access.allowFrom[0]`) may approve a grant for them, and they can " + "NEVER be minted via posture attestation — granting one requires the " + "operator passphrase (so an agent, even one on `postureMintAgents`, " + "cannot self-grant it). Entries are exact key names or `*` globs, " + "e.g. `stripe/*`, `*/oauth-token`, `microsoft/ken-tokens` (`*` matches " + "any run of characters incl. `/`; case-sensitive). Default `[]` — no " + "key is admin-only. Posture may RETAIN an admin-only key across a " + "union re-mint but never ADD one. Takes effect on broker + gateway " + "restart (broker has no ACL hot-reload)."),
|
|
4645
|
+
auditFailClosed: exports_external.boolean().default(false).describe("sec WS10-F3 (#1420) — fail-CLOSED on an audit-append failure. When " + "`false` (default, backward-compatible) the broker fails OPEN: if a " + "vault-audit.log append fails it logs to stderr, bumps the durable " + "fail-open counter, and STILL releases the secret. When `true`, a " + "failed audit append DENIES the secret release with " + "`AUDIT_UNAVAILABLE` — no secret leaves the broker without a durable " + "audit row. The env var `SWITCHROOM_VAULT_AUDIT_FAIL_CLOSED` " + "(`1`/`true` or `0`/`false`) overrides this at runtime. Either way " + "the fail-open counter is surfaced by `switchroom doctor`. Takes " + "effect on broker restart.")
|
|
4630
4646
|
}).default({}).superRefine((broker, ctx) => {
|
|
4631
4647
|
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
4632
4648
|
ctx.addIssue({
|
|
@@ -4665,7 +4681,9 @@ var init_schema = __esm(() => {
|
|
|
4665
4681
|
});
|
|
4666
4682
|
HostdConfigSchema = exports_external.object({
|
|
4667
4683
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
4668
|
-
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat.")
|
|
4684
|
+
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat."),
|
|
4685
|
+
operator_attest_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the operator-passphrase 2nd factor on hostd's " + "mutating verbs (#1841, RFC host-control-daemon.md §5.4). Default " + "false — attestation is ACCEPTED-and-audited whenever present, but " + "no verb REQUIRES it (behaviour is byte-identical to today, so " + "existing fleets and the Telegram approval-card flow are unchanged). " + "When true, the verbs in `operator_attest_required_verbs` demand a " + "valid operator-passphrase attestation IN ADDITION to admin: hostd " + "forwards the passphrase to the vault broker over its own admin-client " + "connection (`/run/switchroom/broker/hostd/sock`) and treats a broker " + "DENIED as a gate failure. hostd never holds the passphrase. Leave " + "this off until BOTH the gateway passphrase-forward and the " + "cross-compose broker socket are deployed, or gated verbs will fail."),
|
|
4686
|
+
operator_attest_required_verbs: exports_external.array(exports_external.string().min(1)).default(["update_apply", "apply", "rollout"]).describe("Which mutating hostd verbs REQUIRE a valid operator-passphrase " + "attestation when `operator_attest_enabled` is true (#1841). Default " + "is the RFC §5.4 fleet-mutation set (`update_apply`, `apply`, " + "`rollout`). Verbs NOT in this list still accept-and-audit an " + "attestation when one is supplied, but never require it. Ignored " + "entirely when `operator_attest_enabled` is false.")
|
|
4669
4687
|
});
|
|
4670
4688
|
CronEgressSchema = exports_external.object({
|
|
4671
4689
|
allowed_hosts: exports_external.array(exports_external.string().min(1)).default([]).describe("Hosts a poll may reach (exact, https-only). loopback/private/IP-literal are always rejected."),
|
|
@@ -4760,6 +4778,13 @@ var init_schema = __esm(() => {
|
|
|
4760
4778
|
});
|
|
4761
4779
|
});
|
|
4762
4780
|
|
|
4781
|
+
// src/util/atomic.ts
|
|
4782
|
+
import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
|
|
4783
|
+
var TMP_OPEN_FLAGS;
|
|
4784
|
+
var init_atomic = __esm(() => {
|
|
4785
|
+
TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
|
|
4786
|
+
});
|
|
4787
|
+
|
|
4763
4788
|
// src/config/merge.ts
|
|
4764
4789
|
function dedupe(items) {
|
|
4765
4790
|
const seen = new Set;
|
|
@@ -4897,6 +4922,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
4897
4922
|
if (v !== undefined)
|
|
4898
4923
|
combined[k] = v;
|
|
4899
4924
|
}
|
|
4925
|
+
const baseSoul = base;
|
|
4926
|
+
const overrideSoul = override;
|
|
4927
|
+
if (baseSoul.shape !== undefined && (overrideSoul.shape === undefined || overrideSoul.shape === "generalist")) {
|
|
4928
|
+
combined.shape = baseSoul.shape;
|
|
4929
|
+
}
|
|
4900
4930
|
merged.soul = combined;
|
|
4901
4931
|
}
|
|
4902
4932
|
if (defaults.memory || merged.memory) {
|
|
@@ -18226,6 +18256,13 @@ var ApprovalLookupRequestSchema = exports_external.object({
|
|
|
18226
18256
|
action: exports_external.string().min(1),
|
|
18227
18257
|
current_approver_set: exports_external.array(exports_external.string())
|
|
18228
18258
|
});
|
|
18259
|
+
var ApprovalLookupByRequestRequestSchema = exports_external.object({
|
|
18260
|
+
v: exports_external.literal(1),
|
|
18261
|
+
op: exports_external.literal("approval_lookup_by_request"),
|
|
18262
|
+
agent_unit: exports_external.string().min(1),
|
|
18263
|
+
request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
|
|
18264
|
+
current_approver_set: exports_external.array(exports_external.string())
|
|
18265
|
+
});
|
|
18229
18266
|
var ApprovalConsumeRequestSchema = exports_external.object({
|
|
18230
18267
|
v: exports_external.literal(1),
|
|
18231
18268
|
op: exports_external.literal("approval_consume"),
|
|
@@ -18280,6 +18317,7 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
|
|
|
18280
18317
|
RevokeGrantRequestSchema,
|
|
18281
18318
|
ApprovalRequestRequestSchema,
|
|
18282
18319
|
ApprovalLookupRequestSchema,
|
|
18320
|
+
ApprovalLookupByRequestRequestSchema,
|
|
18283
18321
|
ApprovalConsumeRequestSchema,
|
|
18284
18322
|
ApprovalRevokeRequestSchema,
|
|
18285
18323
|
ApprovalListRequestSchema,
|
|
@@ -18302,7 +18340,8 @@ var ErrorCode = exports_external.enum([
|
|
|
18302
18340
|
"DENIED",
|
|
18303
18341
|
"UNKNOWN_KEY",
|
|
18304
18342
|
"BAD_REQUEST",
|
|
18305
|
-
"INTERNAL"
|
|
18343
|
+
"INTERNAL",
|
|
18344
|
+
"AUDIT_UNAVAILABLE"
|
|
18306
18345
|
]);
|
|
18307
18346
|
var OkEntryResponseSchema = exports_external.object({
|
|
18308
18347
|
ok: exports_external.literal(true),
|
|
@@ -18640,6 +18679,30 @@ function lookupDecision(db, query, opts = {}, now = Date.now()) {
|
|
|
18640
18679
|
ORDER BY granted_at DESC LIMIT 1`).get(query.agent_unit, query.scope, query.action);
|
|
18641
18680
|
if (!row)
|
|
18642
18681
|
return { state: "no_decision" };
|
|
18682
|
+
return evaluateDecisionRow(db, row, currentCanonical, opts, now);
|
|
18683
|
+
}
|
|
18684
|
+
function lookupDecisionByRequestId(db, query, opts = {}, now = Date.now()) {
|
|
18685
|
+
const currentCanonical = canonicalizeApproverSet(query.current_approver_set);
|
|
18686
|
+
const nonceRow = db.query(`SELECT * FROM approval_nonces WHERE request_id = ?`).get(query.request_id);
|
|
18687
|
+
if (!nonceRow)
|
|
18688
|
+
return { state: "no_decision" };
|
|
18689
|
+
const nonce = nonceFromRow(nonceRow);
|
|
18690
|
+
if (nonce.agent_unit !== query.agent_unit)
|
|
18691
|
+
return { state: "no_decision" };
|
|
18692
|
+
if (nonce.decision_id === null) {
|
|
18693
|
+
if (nonce.expires_at < now)
|
|
18694
|
+
return { state: "expired" };
|
|
18695
|
+
return { state: "pending", request_id: nonce.request_id };
|
|
18696
|
+
}
|
|
18697
|
+
const decRow = db.query(`SELECT * FROM approval_decisions WHERE id = ?`).get(nonce.decision_id);
|
|
18698
|
+
if (!decRow)
|
|
18699
|
+
return { state: "expired" };
|
|
18700
|
+
if (decRow.revoked_at != null) {
|
|
18701
|
+
return { state: "denied", decision: rowToDecision(decRow) };
|
|
18702
|
+
}
|
|
18703
|
+
return evaluateDecisionRow(db, decRow, currentCanonical, opts, now);
|
|
18704
|
+
}
|
|
18705
|
+
function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
|
|
18643
18706
|
const decision = rowToDecision(row);
|
|
18644
18707
|
if (decision.decision === "deny_perm") {
|
|
18645
18708
|
return { state: "denied", decision };
|
|
@@ -18836,6 +18899,7 @@ init_schema();
|
|
|
18836
18899
|
|
|
18837
18900
|
// src/agents/scaffold.ts
|
|
18838
18901
|
import { join as join4, resolve as resolve5 } from "node:path";
|
|
18902
|
+
init_atomic();
|
|
18839
18903
|
init_schema();
|
|
18840
18904
|
|
|
18841
18905
|
// src/config/users.ts
|
|
@@ -18901,18 +18965,22 @@ var tails = new Map;
|
|
|
18901
18965
|
|
|
18902
18966
|
// src/agents/inject.ts
|
|
18903
18967
|
var INJECT_COMMANDS = new Map([
|
|
18904
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
|
|
18905
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
|
|
18906
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
|
|
18907
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
|
|
18908
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
|
|
18909
|
-
["/
|
|
18968
|
+
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18969
|
+
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18970
|
+
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18971
|
+
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18972
|
+
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18973
|
+
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18974
|
+
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
18975
|
+
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18976
|
+
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
18910
18977
|
[
|
|
18911
18978
|
"/clear",
|
|
18912
18979
|
{
|
|
18913
18980
|
description: "Clear session screen",
|
|
18914
18981
|
expectsOutput: false,
|
|
18915
|
-
silentNote: "context cleared — fresh slate"
|
|
18982
|
+
silentNote: "context cleared — fresh slate",
|
|
18983
|
+
argsAllowed: false
|
|
18916
18984
|
}
|
|
18917
18985
|
],
|
|
18918
18986
|
[
|
|
@@ -18920,7 +18988,8 @@ var INJECT_COMMANDS = new Map([
|
|
|
18920
18988
|
{
|
|
18921
18989
|
description: "Compact conversation history",
|
|
18922
18990
|
expectsOutput: false,
|
|
18923
|
-
silentNote: "compaction runs silently"
|
|
18991
|
+
silentNote: "compaction runs silently",
|
|
18992
|
+
argsAllowed: false
|
|
18924
18993
|
}
|
|
18925
18994
|
]
|
|
18926
18995
|
]);
|
|
@@ -18930,6 +18999,15 @@ var INJECT_BLOCKED = new Map([
|
|
|
18930
18999
|
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
18931
19000
|
["/exit", { reason: "would kill the agent process" }],
|
|
18932
19001
|
["/quit", { reason: "would kill the agent process" }],
|
|
19002
|
+
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
19003
|
+
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
19004
|
+
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
19005
|
+
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
19006
|
+
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
19007
|
+
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
19008
|
+
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
19009
|
+
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
19010
|
+
["/bug", { reason: "submits a bug report over the network" }],
|
|
18933
19011
|
[
|
|
18934
19012
|
"/effort",
|
|
18935
19013
|
{
|
|
@@ -18947,6 +19025,7 @@ var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
|
18947
19025
|
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
18948
19026
|
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
18949
19027
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
19028
|
+
var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
|
|
18950
19029
|
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
18951
19030
|
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
18952
19031
|
|
|
@@ -19004,6 +19083,7 @@ init_paths();
|
|
|
19004
19083
|
|
|
19005
19084
|
// src/agents/scaffold.ts
|
|
19006
19085
|
var REPO_ROOT = resolve5(import.meta.dirname, "../..");
|
|
19086
|
+
var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
|
|
19007
19087
|
var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
|
|
19008
19088
|
"permissions",
|
|
19009
19089
|
"mcpServers",
|
|
@@ -19261,6 +19341,33 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
|
|
|
19261
19341
|
socket.write(encodeResponse({ ok: true, state: r.state, decision }));
|
|
19262
19342
|
return;
|
|
19263
19343
|
}
|
|
19344
|
+
if (req.op === "approval_lookup_by_request") {
|
|
19345
|
+
const acl = checkApprovalAclByAgent(agent, req.agent_unit);
|
|
19346
|
+
if (!acl.allow) {
|
|
19347
|
+
socket.write(encodeResponse(errorResponse("DENIED", acl.reason)));
|
|
19348
|
+
return;
|
|
19349
|
+
}
|
|
19350
|
+
const r = lookupDecisionByRequestId(db, {
|
|
19351
|
+
agent_unit: req.agent_unit,
|
|
19352
|
+
request_id: req.request_id,
|
|
19353
|
+
current_approver_set: req.current_approver_set
|
|
19354
|
+
});
|
|
19355
|
+
const decision = r.state === "granted" || r.state === "denied" ? {
|
|
19356
|
+
id: r.decision.id,
|
|
19357
|
+
agent_unit: r.decision.agent_unit,
|
|
19358
|
+
scope: r.decision.scope,
|
|
19359
|
+
action: r.decision.action,
|
|
19360
|
+
decision: r.decision.decision,
|
|
19361
|
+
granted_at: r.decision.granted_at,
|
|
19362
|
+
granted_by_user_id: r.decision.granted_by_user_id,
|
|
19363
|
+
ttl_expires_at: r.decision.ttl_expires_at,
|
|
19364
|
+
last_used_at: r.decision.last_used_at,
|
|
19365
|
+
revoked_at: r.decision.revoked_at,
|
|
19366
|
+
revoke_reason: r.decision.revoke_reason
|
|
19367
|
+
} : null;
|
|
19368
|
+
socket.write(encodeResponse({ ok: true, state: r.state, decision }));
|
|
19369
|
+
return;
|
|
19370
|
+
}
|
|
19264
19371
|
if (req.op === "approval_consume") {
|
|
19265
19372
|
const peek = getNonce(db, req.request_id);
|
|
19266
19373
|
if (peek !== null) {
|