switchroom 0.15.36 → 0.15.38
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 +10 -9
- package/dist/auth-broker/index.js +9 -9
- package/dist/cli/autoaccept-poll.js +13 -7
- package/dist/cli/notion-write-pretool.mjs +9 -9
- package/dist/cli/switchroom.js +480 -217
- package/dist/cli/ui/index.html +87 -17
- package/dist/host-control/main.js +10 -10
- package/dist/vault/approvals/kernel-server.js +9 -9
- package/dist/vault/broker/server.js +9 -9
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +1 -1
- package/profiles/_base/start.sh.hbs +1 -1
- package/profiles/_shared/agent-self-service.md.hbs +25 -0
- package/skills/switchroom-manage/SKILL.md +1 -1
- package/skills/switchroom-runtime/SKILL.md +1 -1
- package/telegram-plugin/answer-stream.ts +1 -1
- package/telegram-plugin/bridge/bridge.ts +50 -1
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/bridge/tool-filter.ts +77 -0
- package/telegram-plugin/chat-lock.ts +1 -1
- package/telegram-plugin/credits-watch.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +60 -3
- package/telegram-plugin/dist/gateway/gateway.js +753 -207
- package/telegram-plugin/dist/server.js +64 -4
- package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
- package/telegram-plugin/gateway/boot-card.ts +5 -1
- package/telegram-plugin/gateway/boot-probes.ts +62 -0
- package/telegram-plugin/gateway/cron-session.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +254 -15
- package/telegram-plugin/gateway/grant-restart.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
- package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
- package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
- package/telegram-plugin/gateway/linear-activity.ts +56 -0
- package/telegram-plugin/gateway/linear-auth-watch.ts +102 -0
- package/telegram-plugin/gateway/linear-setup.ts +196 -0
- package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
- package/telegram-plugin/gateway/permission-timeout.ts +70 -0
- package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
- package/telegram-plugin/interrupt-marker.ts +1 -1
- package/telegram-plugin/over-ping-safety-net.ts +1 -1
- package/telegram-plugin/scoped-approval.ts +1 -1
- package/telegram-plugin/secret-detect/vault-error.ts +1 -1
- package/telegram-plugin/silence-poke.ts +2 -2
- package/telegram-plugin/silent-reply-anchor.ts +1 -1
- package/telegram-plugin/slot-banner-driver.ts +1 -1
- package/telegram-plugin/startup-reset.ts +1 -1
- package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
- package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
- package/telegram-plugin/tests/linear-agent-activity.test.ts +77 -0
- package/telegram-plugin/tests/linear-agent-setup.test.ts +132 -0
- package/telegram-plugin/tests/linear-auth-watch.test.ts +79 -0
- package/telegram-plugin/tests/linear-create-issue.test.ts +3 -1
- package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
- package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
- package/telegram-plugin/tests/silence-poke.test.ts +1 -1
- package/telegram-plugin/tests/tool-filter.test.ts +87 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
- package/telegram-plugin/turn-flush-safety.ts +1 -1
- package/telegram-plugin/uat/assertions.ts +1 -1
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
- package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
|
@@ -11001,7 +11001,7 @@ var ActionSpecSchema = exports_external.discriminatedUnion("type", [
|
|
|
11001
11001
|
var ScheduleEntrySchema = exports_external.object({
|
|
11002
11002
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
11003
11003
|
prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
|
|
11004
|
-
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (
|
|
11004
|
+
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (reference/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates — zero " + "tokens, no session. poll/prompt tiering is on by default " + "(SWITCHROOM_CHEAP_CRON=0 is the kill-switch); an action is model-free " + "regardless (the kill-switch governs model tiering, not deterministic " + "actions)."),
|
|
11005
11005
|
poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
|
|
11006
11006
|
action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
|
|
11007
11007
|
model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) — the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
|
|
@@ -11010,7 +11010,7 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
11010
11010
|
topic: exports_external.union([
|
|
11011
11011
|
exports_external.string().min(1, "topic alias must be non-empty"),
|
|
11012
11012
|
exports_external.number().int().positive("topic ID must be a positive integer")
|
|
11013
|
-
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load — typos surface immediately. " + "See
|
|
11013
|
+
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load — typos surface immediately. " + "See reference/rfcs/supergroup-mode.md.")
|
|
11014
11014
|
}).superRefine((entry, ctx) => {
|
|
11015
11015
|
const kind = entry.kind ?? "prompt";
|
|
11016
11016
|
if (kind === "poll" && !entry.poll) {
|
|
@@ -11183,15 +11183,15 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11183
11183
|
webhook_rate_limit: exports_external.object({
|
|
11184
11184
|
rpm: exports_external.number().int().positive()
|
|
11185
11185
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
|
|
11186
|
-
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See
|
|
11187
|
-
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See
|
|
11186
|
+
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See reference/rfcs/webhook-via-gateway-socket.md."),
|
|
11187
|
+
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See reference/rfcs/webhook-cloudflare-edge-lock.md."),
|
|
11188
11188
|
linear_agent: exports_external.object({
|
|
11189
11189
|
enabled: exports_external.boolean(),
|
|
11190
11190
|
token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
|
|
11191
11191
|
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
|
|
11192
11192
|
default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
|
|
11193
11193
|
}).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
|
|
11194
|
-
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See
|
|
11194
|
+
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See reference/rfcs/supergroup-mode.md."),
|
|
11195
11195
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11196
11196
|
topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
11197
11197
|
}).optional().superRefine((tg, ctx) => {
|
|
@@ -11417,7 +11417,7 @@ var AgentSchema = exports_external.object({
|
|
|
11417
11417
|
drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent — they live at the top level."),
|
|
11418
11418
|
google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides — currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent — they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
|
|
11419
11419
|
microsoft_workspace: AgentMicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Per-agent Microsoft Workspace " + "override — pins the Microsoft account this agent reads via the " + "auth-broker (must be a key in top-level `microsoft_accounts:` with " + "this agent in its `enabled_for[]`) and optionally overrides org_mode. " + "microsoft_client_id/secret are not per-agent."),
|
|
11420
|
-
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC
|
|
11420
|
+
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Per-agent Notion access. " + "Presence opts the agent IN (launcher scaffolded, MCP entry emitted, " + "broker grants the integration token). Optional `databases:` filter " + "narrows which DBs this agent may read/write — names must resolve in " + "top-level notion_workspace.databases. Absence opts the agent OUT."),
|
|
11421
11421
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
11422
11422
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
11423
11423
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
@@ -11552,9 +11552,9 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11552
11552
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
11553
11553
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
11554
11554
|
microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration — OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
|
|
11555
|
-
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC
|
|
11555
|
+
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Top-level Notion integration " + "config — vault key for the integration token, friendly-name → " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
|
|
11556
11556
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
11557
|
-
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (
|
|
11557
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
11558
11558
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
11559
11559
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
11560
11560
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
@@ -11576,7 +11576,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11576
11576
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11577
11577
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
11578
11578
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
11579
|
-
cron: CronConfigSchema.optional().describe("Cheap-cron settings (
|
|
11579
|
+
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
11580
11580
|
});
|
|
11581
11581
|
|
|
11582
11582
|
// src/config/paths.ts
|
|
@@ -13386,6 +13386,7 @@ function resolveOutboundTopic(config, event) {
|
|
|
13386
13386
|
}
|
|
13387
13387
|
case "boot":
|
|
13388
13388
|
case "compact-watchdog":
|
|
13389
|
+
case "linear-auth":
|
|
13389
13390
|
if (!inSupergroupMode)
|
|
13390
13391
|
return;
|
|
13391
13392
|
return aliasToId(cfg, ALERTS_ALIAS) ?? cfg.default_topic_id;
|
|
@@ -11001,7 +11001,7 @@ var ActionSpecSchema = exports_external.discriminatedUnion("type", [
|
|
|
11001
11001
|
var ScheduleEntrySchema = exports_external.object({
|
|
11002
11002
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
11003
11003
|
prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
|
|
11004
|
-
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (
|
|
11004
|
+
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (reference/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates — zero " + "tokens, no session. poll/prompt tiering is on by default " + "(SWITCHROOM_CHEAP_CRON=0 is the kill-switch); an action is model-free " + "regardless (the kill-switch governs model tiering, not deterministic " + "actions)."),
|
|
11005
11005
|
poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
|
|
11006
11006
|
action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
|
|
11007
11007
|
model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) — the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
|
|
@@ -11010,7 +11010,7 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
11010
11010
|
topic: exports_external.union([
|
|
11011
11011
|
exports_external.string().min(1, "topic alias must be non-empty"),
|
|
11012
11012
|
exports_external.number().int().positive("topic ID must be a positive integer")
|
|
11013
|
-
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load — typos surface immediately. " + "See
|
|
11013
|
+
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load — typos surface immediately. " + "See reference/rfcs/supergroup-mode.md.")
|
|
11014
11014
|
}).superRefine((entry, ctx) => {
|
|
11015
11015
|
const kind = entry.kind ?? "prompt";
|
|
11016
11016
|
if (kind === "poll" && !entry.poll) {
|
|
@@ -11183,15 +11183,15 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11183
11183
|
webhook_rate_limit: exports_external.object({
|
|
11184
11184
|
rpm: exports_external.number().int().positive()
|
|
11185
11185
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
|
|
11186
|
-
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See
|
|
11187
|
-
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See
|
|
11186
|
+
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See reference/rfcs/webhook-via-gateway-socket.md."),
|
|
11187
|
+
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See reference/rfcs/webhook-cloudflare-edge-lock.md."),
|
|
11188
11188
|
linear_agent: exports_external.object({
|
|
11189
11189
|
enabled: exports_external.boolean(),
|
|
11190
11190
|
token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
|
|
11191
11191
|
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
|
|
11192
11192
|
default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
|
|
11193
11193
|
}).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
|
|
11194
|
-
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See
|
|
11194
|
+
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See reference/rfcs/supergroup-mode.md."),
|
|
11195
11195
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11196
11196
|
topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
11197
11197
|
}).optional().superRefine((tg, ctx) => {
|
|
@@ -11417,7 +11417,7 @@ var AgentSchema = exports_external.object({
|
|
|
11417
11417
|
drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent — they live at the top level."),
|
|
11418
11418
|
google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides — currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent — they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
|
|
11419
11419
|
microsoft_workspace: AgentMicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Per-agent Microsoft Workspace " + "override — pins the Microsoft account this agent reads via the " + "auth-broker (must be a key in top-level `microsoft_accounts:` with " + "this agent in its `enabled_for[]`) and optionally overrides org_mode. " + "microsoft_client_id/secret are not per-agent."),
|
|
11420
|
-
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC
|
|
11420
|
+
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Per-agent Notion access. " + "Presence opts the agent IN (launcher scaffolded, MCP entry emitted, " + "broker grants the integration token). Optional `databases:` filter " + "narrows which DBs this agent may read/write — names must resolve in " + "top-level notion_workspace.databases. Absence opts the agent OUT."),
|
|
11421
11421
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
11422
11422
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
11423
11423
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
@@ -11552,9 +11552,9 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11552
11552
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
11553
11553
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
11554
11554
|
microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration — OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
|
|
11555
|
-
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC
|
|
11555
|
+
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Top-level Notion integration " + "config — vault key for the integration token, friendly-name → " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
|
|
11556
11556
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
11557
|
-
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (
|
|
11557
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
11558
11558
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
11559
11559
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
11560
11560
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
@@ -11576,7 +11576,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11576
11576
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11577
11577
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
11578
11578
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
11579
|
-
cron: CronConfigSchema.optional().describe("Cheap-cron settings (
|
|
11579
|
+
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
11580
11580
|
});
|
|
11581
11581
|
|
|
11582
11582
|
// src/config/paths.ts
|
|
@@ -40,7 +40,8 @@ var PROMPTS = [
|
|
|
40
40
|
keys: ["Enter"]
|
|
41
41
|
}
|
|
42
42
|
];
|
|
43
|
-
var
|
|
43
|
+
var REPL_READY_SIGNATURE = /\u2190 for agents|accept edits on|\? for shortcuts|esc to interrupt/;
|
|
44
|
+
var DEFAULT_BOOT_HARD_CAP_MS = 600000;
|
|
44
45
|
var DEFAULT_POLL_MS = 250;
|
|
45
46
|
function defaultSleep(ms) {
|
|
46
47
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -70,8 +71,9 @@ function sendKeys(agentName, keys) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
async function runAutoaccept(opts) {
|
|
73
|
-
const
|
|
74
|
+
const bootHardCapMs = opts.bootHardCapMs ?? DEFAULT_BOOT_HARD_CAP_MS;
|
|
74
75
|
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
76
|
+
const replReady = opts.replReadyMatch ?? REPL_READY_SIGNATURE;
|
|
75
77
|
const rules = (opts.prompts ?? PROMPTS).map((r) => ({
|
|
76
78
|
rule: r,
|
|
77
79
|
fired: 0,
|
|
@@ -81,10 +83,11 @@ async function runAutoaccept(opts) {
|
|
|
81
83
|
const now = opts.now ?? Date.now;
|
|
82
84
|
const sleep = opts.sleep ?? defaultSleep;
|
|
83
85
|
const maxPolls = opts.maxPolls ?? Number.POSITIVE_INFINITY;
|
|
84
|
-
|
|
86
|
+
const startTime = now();
|
|
85
87
|
let polls = 0;
|
|
86
88
|
while (polls < maxPolls) {
|
|
87
89
|
polls++;
|
|
90
|
+
const t = now();
|
|
88
91
|
const text = capturePane(opts.agentName);
|
|
89
92
|
let matchedThisPoll = false;
|
|
90
93
|
if (text) {
|
|
@@ -100,10 +103,13 @@ async function runAutoaccept(opts) {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
|
-
if (matchedThisPoll) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (!matchedThisPoll) {
|
|
107
|
+
if (text && replReady.test(text)) {
|
|
108
|
+
return { fired, reason: "repl-ready" };
|
|
109
|
+
}
|
|
110
|
+
if (t - startTime >= bootHardCapMs) {
|
|
111
|
+
return { fired, reason: "idle-timeout" };
|
|
112
|
+
}
|
|
107
113
|
}
|
|
108
114
|
await sleep(pollIntervalMs);
|
|
109
115
|
}
|
|
@@ -11749,7 +11749,7 @@ var ActionSpecSchema = exports_external.discriminatedUnion("type", [
|
|
|
11749
11749
|
var ScheduleEntrySchema = exports_external.object({
|
|
11750
11750
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
11751
11751
|
prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
|
|
11752
|
-
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (
|
|
11752
|
+
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (reference/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates \u2014 zero " + "tokens, no session. poll/prompt tiering is on by default " + "(SWITCHROOM_CHEAP_CRON=0 is the kill-switch); an action is model-free " + "regardless (the kill-switch governs model tiering, not deterministic " + "actions)."),
|
|
11753
11753
|
poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
|
|
11754
11754
|
action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
|
|
11755
11755
|
model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) \u2014 the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
|
|
@@ -11758,7 +11758,7 @@ var ScheduleEntrySchema = exports_external.object({
|
|
|
11758
11758
|
topic: exports_external.union([
|
|
11759
11759
|
exports_external.string().min(1, "topic alias must be non-empty"),
|
|
11760
11760
|
exports_external.number().int().positive("topic ID must be a positive integer")
|
|
11761
|
-
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load \u2014 typos surface immediately. " + "See
|
|
11761
|
+
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load \u2014 typos surface immediately. " + "See reference/rfcs/supergroup-mode.md.")
|
|
11762
11762
|
}).superRefine((entry, ctx) => {
|
|
11763
11763
|
const kind = entry.kind ?? "prompt";
|
|
11764
11764
|
if (kind === "poll" && !entry.poll) {
|
|
@@ -11931,15 +11931,15 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11931
11931
|
webhook_rate_limit: exports_external.object({
|
|
11932
11932
|
rpm: exports_external.number().int().positive()
|
|
11933
11933
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
|
|
11934
|
-
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See
|
|
11935
|
-
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See
|
|
11934
|
+
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See reference/rfcs/webhook-via-gateway-socket.md."),
|
|
11935
|
+
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See reference/rfcs/webhook-cloudflare-edge-lock.md."),
|
|
11936
11936
|
linear_agent: exports_external.object({
|
|
11937
11937
|
enabled: exports_external.boolean(),
|
|
11938
11938
|
token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
|
|
11939
11939
|
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
|
|
11940
11940
|
default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
|
|
11941
11941
|
}).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
|
|
11942
|
-
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See
|
|
11942
|
+
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See reference/rfcs/supergroup-mode.md."),
|
|
11943
11943
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11944
11944
|
topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
|
|
11945
11945
|
}).optional().superRefine((tg, ctx) => {
|
|
@@ -12165,7 +12165,7 @@ var AgentSchema = exports_external.object({
|
|
|
12165
12165
|
drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent \u2014 they live at the top level."),
|
|
12166
12166
|
google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides \u2014 currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent \u2014 they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
|
|
12167
12167
|
microsoft_workspace: AgentMicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Per-agent Microsoft Workspace " + "override \u2014 pins the Microsoft account this agent reads via the " + "auth-broker (must be a key in top-level `microsoft_accounts:` with " + "this agent in its `enabled_for[]`) and optionally overrides org_mode. " + "microsoft_client_id/secret are not per-agent."),
|
|
12168
|
-
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC
|
|
12168
|
+
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Per-agent Notion access. " + "Presence opts the agent IN (launcher scaffolded, MCP entry emitted, " + "broker grants the integration token). Optional `databases:` filter " + "narrows which DBs this agent may read/write \u2014 names must resolve in " + "top-level notion_workspace.databases. Absence opts the agent OUT."),
|
|
12169
12169
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
12170
12170
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
12171
12171
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
@@ -12300,9 +12300,9 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
12300
12300
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
12301
12301
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration \u2014 " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
12302
12302
|
microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration \u2014 OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
|
|
12303
|
-
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC
|
|
12303
|
+
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Top-level Notion integration " + "config \u2014 vault key for the integration token, friendly-name \u2192 " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
|
|
12304
12304
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
12305
|
-
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (
|
|
12305
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
12306
12306
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
12307
12307
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container \u2014 then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
12308
12308
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
@@ -12324,7 +12324,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
12324
12324
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
12325
12325
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
12326
12326
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
12327
|
-
cron: CronConfigSchema.optional().describe("Cheap-cron settings (
|
|
12327
|
+
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (\u00a76.1). Required to enable any http-diff poll; not agent-writable.")
|
|
12328
12328
|
});
|
|
12329
12329
|
|
|
12330
12330
|
// src/config/paths.ts
|