switchroom 0.18.11 → 0.18.12
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 +29 -5
- package/dist/auth-broker/index.js +53 -13
- package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
- package/dist/cli/notion-write-pretool.mjs +29 -5
- package/dist/cli/switchroom.js +2453 -1293
- package/dist/cli/ui/index.html +163 -17
- package/dist/host-control/main.js +504 -100
- package/dist/vault/approvals/kernel-server.js +53 -13
- package/dist/vault/broker/server.js +162 -114
- 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 +73 -1
- package/telegram-plugin/dist/gateway/gateway.js +3602 -1007
- package/telegram-plugin/dist/server.js +74 -2
- package/telegram-plugin/flood-circuit-breaker.ts +493 -21
- package/telegram-plugin/gateway/approval-hold.ts +583 -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 +76 -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 +1482 -165
- 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/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 +209 -2
- package/telegram-plugin/operator-events.ts +23 -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/retry-api-call.ts +212 -2
- package/telegram-plugin/send-gate-degraded.test.ts +443 -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 +698 -0
- package/telegram-plugin/send-gate.ts +982 -0
- 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 +102 -2
- 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 +296 -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/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/gw-trace-gate.test.ts +105 -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/inline-keyboard-callbacks.test.ts +284 -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/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/retry-api-call.test.ts +398 -0
- package/telegram-plugin/tests/session-model-file.test.ts +50 -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/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/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/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
|
@@ -11059,7 +11059,12 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
11059
11059
|
var AgentSoulSchema = exports_external.object({
|
|
11060
11060
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
11061
11061
|
style: exports_external.string().describe("Communication style description"),
|
|
11062
|
-
|
|
11062
|
+
creature: exports_external.string().optional().describe("Persona creature/form (e.g., 'owl', 'octopus')"),
|
|
11063
|
+
vibe: exports_external.string().optional().describe("One-line personality vibe (e.g., 'calm, precise')"),
|
|
11064
|
+
expertise: exports_external.string().optional().describe("Domain expertise summary rendered into the persona"),
|
|
11065
|
+
emoji: exports_external.string().optional().describe("Signature emoji for the persona (e.g., '\uD83E\uDD89')"),
|
|
11066
|
+
boundaries: exports_external.string().optional().describe("Behavioral boundaries and disclaimers"),
|
|
11067
|
+
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).")
|
|
11063
11068
|
}).optional();
|
|
11064
11069
|
var AgentToolsSchema = exports_external.object({
|
|
11065
11070
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
@@ -11224,6 +11229,11 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11224
11229
|
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."),
|
|
11225
11230
|
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.")
|
|
11226
11231
|
}).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."),
|
|
11232
|
+
button_choice_confirmation: exports_external.object({
|
|
11233
|
+
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."),
|
|
11234
|
+
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."),
|
|
11235
|
+
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.")
|
|
11236
|
+
}).optional().describe("Auto-confirm 'You chose: X' annotation on inline_keyboard taps (#789). " + "Cascades from defaults.channels.telegram.button_choice_confirmation."),
|
|
11227
11237
|
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.)"),
|
|
11228
11238
|
webhook_dispatch: exports_external.object({
|
|
11229
11239
|
github: exports_external.array(webhookDispatchRule).optional(),
|
|
@@ -11372,7 +11382,12 @@ var profileFields = {
|
|
|
11372
11382
|
soul: exports_external.object({
|
|
11373
11383
|
name: exports_external.string().optional(),
|
|
11374
11384
|
style: exports_external.string().optional(),
|
|
11375
|
-
|
|
11385
|
+
creature: exports_external.string().optional(),
|
|
11386
|
+
vibe: exports_external.string().optional(),
|
|
11387
|
+
expertise: exports_external.string().optional(),
|
|
11388
|
+
emoji: exports_external.string().optional(),
|
|
11389
|
+
boundaries: exports_external.string().optional(),
|
|
11390
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).optional()
|
|
11376
11391
|
}).optional(),
|
|
11377
11392
|
tools: exports_external.object({
|
|
11378
11393
|
allow: exports_external.array(exports_external.string()).optional(),
|
|
@@ -11569,7 +11584,8 @@ var VaultConfigSchema = exports_external.object({
|
|
|
11569
11584
|
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."),
|
|
11570
11585
|
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."),
|
|
11571
11586
|
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."),
|
|
11572
|
-
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).")
|
|
11587
|
+
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)."),
|
|
11588
|
+
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.")
|
|
11573
11589
|
}).default({}).superRefine((broker, ctx) => {
|
|
11574
11590
|
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
11575
11591
|
ctx.addIssue({
|
|
@@ -11608,7 +11624,9 @@ var FleetHealthConfigSchema = exports_external.object({
|
|
|
11608
11624
|
});
|
|
11609
11625
|
var HostdConfigSchema = exports_external.object({
|
|
11610
11626
|
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."),
|
|
11611
|
-
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.")
|
|
11627
|
+
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."),
|
|
11628
|
+
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."),
|
|
11629
|
+
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.")
|
|
11612
11630
|
});
|
|
11613
11631
|
var CronEgressSchema = exports_external.object({
|
|
11614
11632
|
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."),
|
|
@@ -12020,6 +12038,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12020
12038
|
if (v !== undefined)
|
|
12021
12039
|
combined[k] = v;
|
|
12022
12040
|
}
|
|
12041
|
+
const baseSoul = base;
|
|
12042
|
+
const overrideSoul = override;
|
|
12043
|
+
if (baseSoul.shape !== undefined && (overrideSoul.shape === undefined || overrideSoul.shape === "generalist")) {
|
|
12044
|
+
combined.shape = baseSoul.shape;
|
|
12045
|
+
}
|
|
12023
12046
|
merged.soul = combined;
|
|
12024
12047
|
}
|
|
12025
12048
|
if (defaults.memory || merged.memory) {
|
|
@@ -13173,7 +13196,8 @@ var ErrorCode = exports_external.enum([
|
|
|
13173
13196
|
"DENIED",
|
|
13174
13197
|
"UNKNOWN_KEY",
|
|
13175
13198
|
"BAD_REQUEST",
|
|
13176
|
-
"INTERNAL"
|
|
13199
|
+
"INTERNAL",
|
|
13200
|
+
"AUDIT_UNAVAILABLE"
|
|
13177
13201
|
]);
|
|
13178
13202
|
var OkEntryResponseSchema = exports_external.object({
|
|
13179
13203
|
ok: exports_external.literal(true),
|
|
@@ -16688,7 +16688,12 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
16688
16688
|
var AgentSoulSchema = exports_external.object({
|
|
16689
16689
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
16690
16690
|
style: exports_external.string().describe("Communication style description"),
|
|
16691
|
-
|
|
16691
|
+
creature: exports_external.string().optional().describe("Persona creature/form (e.g., 'owl', 'octopus')"),
|
|
16692
|
+
vibe: exports_external.string().optional().describe("One-line personality vibe (e.g., 'calm, precise')"),
|
|
16693
|
+
expertise: exports_external.string().optional().describe("Domain expertise summary rendered into the persona"),
|
|
16694
|
+
emoji: exports_external.string().optional().describe("Signature emoji for the persona (e.g., '\uD83E\uDD89')"),
|
|
16695
|
+
boundaries: exports_external.string().optional().describe("Behavioral boundaries and disclaimers"),
|
|
16696
|
+
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).")
|
|
16692
16697
|
}).optional();
|
|
16693
16698
|
var AgentToolsSchema = exports_external.object({
|
|
16694
16699
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
@@ -16853,6 +16858,11 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
16853
16858
|
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."),
|
|
16854
16859
|
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.")
|
|
16855
16860
|
}).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."),
|
|
16861
|
+
button_choice_confirmation: exports_external.object({
|
|
16862
|
+
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."),
|
|
16863
|
+
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."),
|
|
16864
|
+
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.")
|
|
16865
|
+
}).optional().describe("Auto-confirm 'You chose: X' annotation on inline_keyboard taps (#789). " + "Cascades from defaults.channels.telegram.button_choice_confirmation."),
|
|
16856
16866
|
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.)"),
|
|
16857
16867
|
webhook_dispatch: exports_external.object({
|
|
16858
16868
|
github: exports_external.array(webhookDispatchRule).optional(),
|
|
@@ -17001,7 +17011,12 @@ var profileFields = {
|
|
|
17001
17011
|
soul: exports_external.object({
|
|
17002
17012
|
name: exports_external.string().optional(),
|
|
17003
17013
|
style: exports_external.string().optional(),
|
|
17004
|
-
|
|
17014
|
+
creature: exports_external.string().optional(),
|
|
17015
|
+
vibe: exports_external.string().optional(),
|
|
17016
|
+
expertise: exports_external.string().optional(),
|
|
17017
|
+
emoji: exports_external.string().optional(),
|
|
17018
|
+
boundaries: exports_external.string().optional(),
|
|
17019
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).optional()
|
|
17005
17020
|
}).optional(),
|
|
17006
17021
|
tools: exports_external.object({
|
|
17007
17022
|
allow: exports_external.array(exports_external.string()).optional(),
|
|
@@ -17198,7 +17213,8 @@ var VaultConfigSchema = exports_external.object({
|
|
|
17198
17213
|
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."),
|
|
17199
17214
|
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."),
|
|
17200
17215
|
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."),
|
|
17201
|
-
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).")
|
|
17216
|
+
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)."),
|
|
17217
|
+
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.")
|
|
17202
17218
|
}).default({}).superRefine((broker, ctx) => {
|
|
17203
17219
|
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
17204
17220
|
ctx.addIssue({
|
|
@@ -17237,7 +17253,9 @@ var FleetHealthConfigSchema = exports_external.object({
|
|
|
17237
17253
|
});
|
|
17238
17254
|
var HostdConfigSchema = exports_external.object({
|
|
17239
17255
|
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."),
|
|
17240
|
-
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.")
|
|
17256
|
+
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."),
|
|
17257
|
+
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."),
|
|
17258
|
+
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.")
|
|
17241
17259
|
});
|
|
17242
17260
|
var CronEgressSchema = exports_external.object({
|
|
17243
17261
|
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."),
|
|
@@ -17650,6 +17668,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
17650
17668
|
if (v !== undefined)
|
|
17651
17669
|
combined[k] = v;
|
|
17652
17670
|
}
|
|
17671
|
+
const baseSoul = base;
|
|
17672
|
+
const overrideSoul = override;
|
|
17673
|
+
if (baseSoul.shape !== undefined && (overrideSoul.shape === undefined || overrideSoul.shape === "generalist")) {
|
|
17674
|
+
combined.shape = baseSoul.shape;
|
|
17675
|
+
}
|
|
17653
17676
|
merged.soul = combined;
|
|
17654
17677
|
}
|
|
17655
17678
|
if (defaults.memory || merged.memory) {
|
|
@@ -18149,18 +18172,22 @@ var tails = new Map;
|
|
|
18149
18172
|
|
|
18150
18173
|
// src/agents/inject.ts
|
|
18151
18174
|
var INJECT_COMMANDS = new Map([
|
|
18152
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
|
|
18153
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
|
|
18154
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
|
|
18155
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
|
|
18156
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
|
|
18157
|
-
["/
|
|
18175
|
+
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18176
|
+
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18177
|
+
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18178
|
+
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18179
|
+
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18180
|
+
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18181
|
+
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
18182
|
+
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18183
|
+
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
18158
18184
|
[
|
|
18159
18185
|
"/clear",
|
|
18160
18186
|
{
|
|
18161
18187
|
description: "Clear session screen",
|
|
18162
18188
|
expectsOutput: false,
|
|
18163
|
-
silentNote: "context cleared — fresh slate"
|
|
18189
|
+
silentNote: "context cleared — fresh slate",
|
|
18190
|
+
argsAllowed: false
|
|
18164
18191
|
}
|
|
18165
18192
|
],
|
|
18166
18193
|
[
|
|
@@ -18168,7 +18195,8 @@ var INJECT_COMMANDS = new Map([
|
|
|
18168
18195
|
{
|
|
18169
18196
|
description: "Compact conversation history",
|
|
18170
18197
|
expectsOutput: false,
|
|
18171
|
-
silentNote: "compaction runs silently"
|
|
18198
|
+
silentNote: "compaction runs silently",
|
|
18199
|
+
argsAllowed: false
|
|
18172
18200
|
}
|
|
18173
18201
|
]
|
|
18174
18202
|
]);
|
|
@@ -18178,6 +18206,15 @@ var INJECT_BLOCKED = new Map([
|
|
|
18178
18206
|
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
18179
18207
|
["/exit", { reason: "would kill the agent process" }],
|
|
18180
18208
|
["/quit", { reason: "would kill the agent process" }],
|
|
18209
|
+
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
18210
|
+
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
18211
|
+
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
18212
|
+
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
18213
|
+
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
18214
|
+
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
18215
|
+
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
18216
|
+
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
18217
|
+
["/bug", { reason: "submits a bug report over the network" }],
|
|
18181
18218
|
[
|
|
18182
18219
|
"/effort",
|
|
18183
18220
|
{
|
|
@@ -18195,6 +18232,7 @@ var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
|
18195
18232
|
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
18196
18233
|
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
18197
18234
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
18235
|
+
var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
|
|
18198
18236
|
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)';
|
|
18199
18237
|
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
18200
18238
|
|
|
@@ -18428,7 +18466,8 @@ var ErrorCode = exports_external.enum([
|
|
|
18428
18466
|
"DENIED",
|
|
18429
18467
|
"UNKNOWN_KEY",
|
|
18430
18468
|
"BAD_REQUEST",
|
|
18431
|
-
"INTERNAL"
|
|
18469
|
+
"INTERNAL",
|
|
18470
|
+
"AUDIT_UNAVAILABLE"
|
|
18432
18471
|
]);
|
|
18433
18472
|
var OkEntryResponseSchema = exports_external.object({
|
|
18434
18473
|
ok: exports_external.literal(true),
|
|
@@ -18575,6 +18614,7 @@ var materializedDirs = new Set;
|
|
|
18575
18614
|
|
|
18576
18615
|
// src/agents/scaffold.ts
|
|
18577
18616
|
var REPO_ROOT = resolve5(import.meta.dirname, "../..");
|
|
18617
|
+
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.";
|
|
18578
18618
|
var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
|
|
18579
18619
|
"permissions",
|
|
18580
18620
|
"mcpServers",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/cli/hindsight-mental-model-pretool.ts
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
var MENTAL_MODEL_WRITE_TOOLS = new Set([
|
|
4
|
+
"mcp__hindsight__create_mental_model",
|
|
5
|
+
"mcp__hindsight__update_mental_model",
|
|
6
|
+
"mcp__hindsight__delete_mental_model",
|
|
7
|
+
"mcp__hindsight__refresh_mental_model"
|
|
8
|
+
]);
|
|
9
|
+
var DENY_MESSAGE = "Mental-model writes are operator-approved. Use " + "`mcp__switchroom-telegram__mental_model_propose` instead \u2014 it posts an " + "approval card and persists on approval.";
|
|
10
|
+
function readStdin() {
|
|
11
|
+
try {
|
|
12
|
+
return readFileSync(0, "utf8");
|
|
13
|
+
} catch {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function allow() {
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
function block(reason) {
|
|
21
|
+
process.stdout.write(JSON.stringify({ decision: "block", reason }));
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
function main() {
|
|
25
|
+
const raw = readStdin().trim();
|
|
26
|
+
if (!raw)
|
|
27
|
+
allow();
|
|
28
|
+
let event;
|
|
29
|
+
try {
|
|
30
|
+
event = JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
allow();
|
|
33
|
+
}
|
|
34
|
+
const toolName = typeof event.tool_name === "string" ? event.tool_name : "";
|
|
35
|
+
if (MENTAL_MODEL_WRITE_TOOLS.has(toolName))
|
|
36
|
+
block(DENY_MESSAGE);
|
|
37
|
+
allow();
|
|
38
|
+
}
|
|
39
|
+
main();
|
|
@@ -11104,7 +11104,8 @@ var init_protocol = __esm(() => {
|
|
|
11104
11104
|
"DENIED",
|
|
11105
11105
|
"UNKNOWN_KEY",
|
|
11106
11106
|
"BAD_REQUEST",
|
|
11107
|
-
"INTERNAL"
|
|
11107
|
+
"INTERNAL",
|
|
11108
|
+
"AUDIT_UNAVAILABLE"
|
|
11108
11109
|
]);
|
|
11109
11110
|
OkEntryResponseSchema = exports_external.object({
|
|
11110
11111
|
ok: exports_external.literal(true),
|
|
@@ -11808,7 +11809,12 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
11808
11809
|
var AgentSoulSchema = exports_external.object({
|
|
11809
11810
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
11810
11811
|
style: exports_external.string().describe("Communication style description"),
|
|
11811
|
-
|
|
11812
|
+
creature: exports_external.string().optional().describe("Persona creature/form (e.g., 'owl', 'octopus')"),
|
|
11813
|
+
vibe: exports_external.string().optional().describe("One-line personality vibe (e.g., 'calm, precise')"),
|
|
11814
|
+
expertise: exports_external.string().optional().describe("Domain expertise summary rendered into the persona"),
|
|
11815
|
+
emoji: exports_external.string().optional().describe("Signature emoji for the persona (e.g., '\uD83E\uDD89')"),
|
|
11816
|
+
boundaries: exports_external.string().optional().describe("Behavioral boundaries and disclaimers"),
|
|
11817
|
+
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 \u2014 template work lands in a " + "later issue. Cascade: override (per-agent wins over default).")
|
|
11812
11818
|
}).optional();
|
|
11813
11819
|
var AgentToolsSchema = exports_external.object({
|
|
11814
11820
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
@@ -11973,6 +11979,11 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11973
11979
|
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."),
|
|
11974
11980
|
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 \u2014 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.")
|
|
11975
11981
|
}).optional().describe("Interrupt timing \u2014 how a `!`-prefix interrupt behaves when it lands " + "mid-tool-call. Off by default (fire immediately). Cascades from " + "defaults.channels.telegram.interrupt."),
|
|
11982
|
+
button_choice_confirmation: exports_external.object({
|
|
11983
|
+
enabled: exports_external.boolean().default(false).describe("When true, tapping an agent-emitted inline_keyboard button annotates " + "the source message body with a '\u2705 You chose: <label> \u00b7 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."),
|
|
11984
|
+
format: exports_external.string().default("\u2705 You chose: {label} \u00b7 {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 \u2014 a custom format " + "accumulates one line per retap."),
|
|
11985
|
+
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.")
|
|
11986
|
+
}).optional().describe("Auto-confirm 'You chose: X' annotation on inline_keyboard taps (#789). " + "Cascades from defaults.channels.telegram.button_choice_confirmation."),
|
|
11976
11987
|
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 \u2014 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 \u2014 see #577.)"),
|
|
11977
11988
|
webhook_dispatch: exports_external.object({
|
|
11978
11989
|
github: exports_external.array(webhookDispatchRule).optional(),
|
|
@@ -12121,7 +12132,12 @@ var profileFields = {
|
|
|
12121
12132
|
soul: exports_external.object({
|
|
12122
12133
|
name: exports_external.string().optional(),
|
|
12123
12134
|
style: exports_external.string().optional(),
|
|
12124
|
-
|
|
12135
|
+
creature: exports_external.string().optional(),
|
|
12136
|
+
vibe: exports_external.string().optional(),
|
|
12137
|
+
expertise: exports_external.string().optional(),
|
|
12138
|
+
emoji: exports_external.string().optional(),
|
|
12139
|
+
boundaries: exports_external.string().optional(),
|
|
12140
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).optional()
|
|
12125
12141
|
}).optional(),
|
|
12126
12142
|
tools: exports_external.object({
|
|
12127
12143
|
allow: exports_external.array(exports_external.string()).optional(),
|
|
@@ -12318,7 +12334,8 @@ var VaultConfigSchema = exports_external.object({
|
|
|
12318
12334
|
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."),
|
|
12319
12335
|
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 \u2014 two-factor (Telegram ID + passphrase). " + "`telegram-id` mints immediately on Approve with no passphrase " + "prompt \u2014 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."),
|
|
12320
12336
|
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 `[]` \u2014 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 \u2014 passphrase attestation still " + "works as before. Each entry is an agent slug exactly as it appears " + "under `agents:` in this config."),
|
|
12321
|
-
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 \u2014 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 `[]` \u2014 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).")
|
|
12337
|
+
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 \u2014 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 `[]` \u2014 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)."),
|
|
12338
|
+
auditFailClosed: exports_external.boolean().default(false).describe("sec WS10-F3 (#1420) \u2014 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` \u2014 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.")
|
|
12322
12339
|
}).default({}).superRefine((broker, ctx) => {
|
|
12323
12340
|
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
12324
12341
|
ctx.addIssue({
|
|
@@ -12357,7 +12374,9 @@ var FleetHealthConfigSchema = exports_external.object({
|
|
|
12357
12374
|
});
|
|
12358
12375
|
var HostdConfigSchema = exports_external.object({
|
|
12359
12376
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit \u00a73). Default false \u2014 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."),
|
|
12360
|
-
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 \u00a75). 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 \u2014 so a looping agent is throttled rather than spamming the chat.")
|
|
12377
|
+
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 \u00a75). 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 \u2014 so a looping agent is throttled rather than spamming the chat."),
|
|
12378
|
+
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 \u00a75.4). Default " + "false \u2014 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."),
|
|
12379
|
+
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 \u00a75.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.")
|
|
12361
12380
|
});
|
|
12362
12381
|
var CronEgressSchema = exports_external.object({
|
|
12363
12382
|
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."),
|
|
@@ -12771,6 +12790,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12771
12790
|
if (v !== undefined)
|
|
12772
12791
|
combined[k] = v;
|
|
12773
12792
|
}
|
|
12793
|
+
const baseSoul = base;
|
|
12794
|
+
const overrideSoul = override;
|
|
12795
|
+
if (baseSoul.shape !== undefined && (overrideSoul.shape === undefined || overrideSoul.shape === "generalist")) {
|
|
12796
|
+
combined.shape = baseSoul.shape;
|
|
12797
|
+
}
|
|
12774
12798
|
merged.soul = combined;
|
|
12775
12799
|
}
|
|
12776
12800
|
if (defaults.memory || merged.memory) {
|