switchroom 0.15.5 → 0.15.7
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 +25 -20
- package/dist/auth-broker/index.js +25 -20
- package/dist/cli/notion-write-pretool.mjs +25 -20
- package/dist/cli/switchroom.js +201 -47
- package/dist/host-control/main.js +25 -20
- package/dist/vault/approvals/kernel-server.js +26 -21
- package/dist/vault/broker/server.js +26 -21
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +130 -48
|
@@ -11082,6 +11082,25 @@ var SessionContinuitySchema = exports_external.object({
|
|
|
11082
11082
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11083
11083
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
11084
11084
|
}).optional();
|
|
11085
|
+
var webhookDispatchRule = exports_external.object({
|
|
11086
|
+
description: exports_external.string().optional(),
|
|
11087
|
+
match: exports_external.object({
|
|
11088
|
+
event: exports_external.string(),
|
|
11089
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
11090
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11091
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11092
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
11093
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
11094
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
11095
|
+
}).passthrough(),
|
|
11096
|
+
prompt: exports_external.string(),
|
|
11097
|
+
cooldown: exports_external.string().optional(),
|
|
11098
|
+
quiet_hours: exports_external.object({
|
|
11099
|
+
start: exports_external.number().int().min(0).max(23),
|
|
11100
|
+
end: exports_external.number().int().min(0).max(23),
|
|
11101
|
+
tz: exports_external.string().optional()
|
|
11102
|
+
}).optional()
|
|
11103
|
+
});
|
|
11085
11104
|
var TelegramChannelSchema = exports_external.object({
|
|
11086
11105
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
11087
11106
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' — the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -11117,26 +11136,12 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11117
11136
|
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."),
|
|
11118
11137
|
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.")
|
|
11119
11138
|
}).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."),
|
|
11120
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
11139
|
+
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.)"),
|
|
11121
11140
|
webhook_dispatch: exports_external.object({
|
|
11122
|
-
github: exports_external.array(
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
11127
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11128
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11129
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
11130
|
-
}).passthrough(),
|
|
11131
|
-
prompt: exports_external.string(),
|
|
11132
|
-
cooldown: exports_external.string().optional(),
|
|
11133
|
-
quiet_hours: exports_external.object({
|
|
11134
|
-
start: exports_external.number().int().min(0).max(23),
|
|
11135
|
-
end: exports_external.number().int().min(0).max(23),
|
|
11136
|
-
tz: exports_external.string().optional()
|
|
11137
|
-
}).optional()
|
|
11138
|
-
})).optional()
|
|
11139
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11141
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
11142
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
11143
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
11144
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source — 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11140
11145
|
webhook_rate_limit: exports_external.object({
|
|
11141
11146
|
rpm: exports_external.number().int().positive()
|
|
11142
11147
|
}).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."),
|
|
@@ -11313,7 +11318,7 @@ var AgentSchema = exports_external.object({
|
|
|
11313
11318
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
11314
11319
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
11315
11320
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
11316
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11321
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11317
11322
|
voice_in: exports_external.object({
|
|
11318
11323
|
enabled: exports_external.boolean().optional(),
|
|
11319
11324
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -11082,6 +11082,25 @@ var SessionContinuitySchema = exports_external.object({
|
|
|
11082
11082
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11083
11083
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
11084
11084
|
}).optional();
|
|
11085
|
+
var webhookDispatchRule = exports_external.object({
|
|
11086
|
+
description: exports_external.string().optional(),
|
|
11087
|
+
match: exports_external.object({
|
|
11088
|
+
event: exports_external.string(),
|
|
11089
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
11090
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11091
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11092
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
11093
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
11094
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
11095
|
+
}).passthrough(),
|
|
11096
|
+
prompt: exports_external.string(),
|
|
11097
|
+
cooldown: exports_external.string().optional(),
|
|
11098
|
+
quiet_hours: exports_external.object({
|
|
11099
|
+
start: exports_external.number().int().min(0).max(23),
|
|
11100
|
+
end: exports_external.number().int().min(0).max(23),
|
|
11101
|
+
tz: exports_external.string().optional()
|
|
11102
|
+
}).optional()
|
|
11103
|
+
});
|
|
11085
11104
|
var TelegramChannelSchema = exports_external.object({
|
|
11086
11105
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
11087
11106
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' — the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -11117,26 +11136,12 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11117
11136
|
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."),
|
|
11118
11137
|
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.")
|
|
11119
11138
|
}).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."),
|
|
11120
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
11139
|
+
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.)"),
|
|
11121
11140
|
webhook_dispatch: exports_external.object({
|
|
11122
|
-
github: exports_external.array(
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
11127
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11128
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11129
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
11130
|
-
}).passthrough(),
|
|
11131
|
-
prompt: exports_external.string(),
|
|
11132
|
-
cooldown: exports_external.string().optional(),
|
|
11133
|
-
quiet_hours: exports_external.object({
|
|
11134
|
-
start: exports_external.number().int().min(0).max(23),
|
|
11135
|
-
end: exports_external.number().int().min(0).max(23),
|
|
11136
|
-
tz: exports_external.string().optional()
|
|
11137
|
-
}).optional()
|
|
11138
|
-
})).optional()
|
|
11139
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11141
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
11142
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
11143
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
11144
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source — 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11140
11145
|
webhook_rate_limit: exports_external.object({
|
|
11141
11146
|
rpm: exports_external.number().int().positive()
|
|
11142
11147
|
}).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."),
|
|
@@ -11313,7 +11318,7 @@ var AgentSchema = exports_external.object({
|
|
|
11313
11318
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
11314
11319
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
11315
11320
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
11316
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11321
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11317
11322
|
voice_in: exports_external.object({
|
|
11318
11323
|
enabled: exports_external.boolean().optional(),
|
|
11319
11324
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -11830,6 +11830,25 @@ var SessionContinuitySchema = exports_external.object({
|
|
|
11830
11830
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11831
11831
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
11832
11832
|
}).optional();
|
|
11833
|
+
var webhookDispatchRule = exports_external.object({
|
|
11834
|
+
description: exports_external.string().optional(),
|
|
11835
|
+
match: exports_external.object({
|
|
11836
|
+
event: exports_external.string(),
|
|
11837
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
11838
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11839
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11840
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
11841
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
11842
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
11843
|
+
}).passthrough(),
|
|
11844
|
+
prompt: exports_external.string(),
|
|
11845
|
+
cooldown: exports_external.string().optional(),
|
|
11846
|
+
quiet_hours: exports_external.object({
|
|
11847
|
+
start: exports_external.number().int().min(0).max(23),
|
|
11848
|
+
end: exports_external.number().int().min(0).max(23),
|
|
11849
|
+
tz: exports_external.string().optional()
|
|
11850
|
+
}).optional()
|
|
11851
|
+
});
|
|
11833
11852
|
var TelegramChannelSchema = exports_external.object({
|
|
11834
11853
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
11835
11854
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' \u2014 the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -11865,26 +11884,12 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11865
11884
|
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."),
|
|
11866
11885
|
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.")
|
|
11867
11886
|
}).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."),
|
|
11868
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
11887
|
+
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.)"),
|
|
11869
11888
|
webhook_dispatch: exports_external.object({
|
|
11870
|
-
github: exports_external.array(
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
11875
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11876
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11877
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
11878
|
-
}).passthrough(),
|
|
11879
|
-
prompt: exports_external.string(),
|
|
11880
|
-
cooldown: exports_external.string().optional(),
|
|
11881
|
-
quiet_hours: exports_external.object({
|
|
11882
|
-
start: exports_external.number().int().min(0).max(23),
|
|
11883
|
-
end: exports_external.number().int().min(0).max(23),
|
|
11884
|
-
tz: exports_external.string().optional()
|
|
11885
|
-
}).optional()
|
|
11886
|
-
})).optional()
|
|
11887
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11889
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
11890
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
11891
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
11892
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source \u2014 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11888
11893
|
webhook_rate_limit: exports_external.object({
|
|
11889
11894
|
rpm: exports_external.number().int().positive()
|
|
11890
11895
|
}).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."),
|
|
@@ -12061,7 +12066,7 @@ var AgentSchema = exports_external.object({
|
|
|
12061
12066
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (\u2264140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml \u2014 never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
12062
12067
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` \u2014 a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
12063
12068
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
12064
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
12069
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
12065
12070
|
voice_in: exports_external.object({
|
|
12066
12071
|
enabled: exports_external.boolean().optional(),
|
|
12067
12072
|
provider: exports_external.enum(["openai"]).optional(),
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
|
|
|
13520
13520
|
});
|
|
13521
13521
|
|
|
13522
13522
|
// src/config/schema.ts
|
|
13523
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
13523
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
13524
13524
|
var init_schema = __esm(() => {
|
|
13525
13525
|
init_zod();
|
|
13526
13526
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -13646,6 +13646,25 @@ var init_schema = __esm(() => {
|
|
|
13646
13646
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
13647
13647
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
13648
13648
|
}).optional();
|
|
13649
|
+
webhookDispatchRule = exports_external.object({
|
|
13650
|
+
description: exports_external.string().optional(),
|
|
13651
|
+
match: exports_external.object({
|
|
13652
|
+
event: exports_external.string(),
|
|
13653
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
13654
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
13655
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
13656
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
13657
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
13658
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
13659
|
+
}).passthrough(),
|
|
13660
|
+
prompt: exports_external.string(),
|
|
13661
|
+
cooldown: exports_external.string().optional(),
|
|
13662
|
+
quiet_hours: exports_external.object({
|
|
13663
|
+
start: exports_external.number().int().min(0).max(23),
|
|
13664
|
+
end: exports_external.number().int().min(0).max(23),
|
|
13665
|
+
tz: exports_external.string().optional()
|
|
13666
|
+
}).optional()
|
|
13667
|
+
});
|
|
13649
13668
|
TelegramChannelSchema = exports_external.object({
|
|
13650
13669
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
13651
13670
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' \u2014 the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -13681,26 +13700,12 @@ var init_schema = __esm(() => {
|
|
|
13681
13700
|
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."),
|
|
13682
13701
|
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.")
|
|
13683
13702
|
}).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."),
|
|
13684
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
13703
|
+
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.)"),
|
|
13685
13704
|
webhook_dispatch: exports_external.object({
|
|
13686
|
-
github: exports_external.array(
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
13691
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
13692
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
13693
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
13694
|
-
}).passthrough(),
|
|
13695
|
-
prompt: exports_external.string(),
|
|
13696
|
-
cooldown: exports_external.string().optional(),
|
|
13697
|
-
quiet_hours: exports_external.object({
|
|
13698
|
-
start: exports_external.number().int().min(0).max(23),
|
|
13699
|
-
end: exports_external.number().int().min(0).max(23),
|
|
13700
|
-
tz: exports_external.string().optional()
|
|
13701
|
-
}).optional()
|
|
13702
|
-
})).optional()
|
|
13703
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
13705
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
13706
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
13707
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
13708
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source \u2014 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
13704
13709
|
webhook_rate_limit: exports_external.object({
|
|
13705
13710
|
rpm: exports_external.number().int().positive()
|
|
13706
13711
|
}).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."),
|
|
@@ -13877,7 +13882,7 @@ var init_schema = __esm(() => {
|
|
|
13877
13882
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (\u2264140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml \u2014 never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
13878
13883
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` \u2014 a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
13879
13884
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
13880
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
13885
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
13881
13886
|
voice_in: exports_external.object({
|
|
13882
13887
|
enabled: exports_external.boolean().optional(),
|
|
13883
13888
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -50199,8 +50204,8 @@ var {
|
|
|
50199
50204
|
} = import__.default;
|
|
50200
50205
|
|
|
50201
50206
|
// src/build-info.ts
|
|
50202
|
-
var VERSION = "0.15.
|
|
50203
|
-
var COMMIT_SHA = "
|
|
50207
|
+
var VERSION = "0.15.7";
|
|
50208
|
+
var COMMIT_SHA = "c0a8b988";
|
|
50204
50209
|
|
|
50205
50210
|
// src/cli/agent.ts
|
|
50206
50211
|
init_source();
|
|
@@ -66799,6 +66804,7 @@ import { existsSync as existsSync43, readFileSync as readFileSync38, writeFileSy
|
|
|
66799
66804
|
import { join as join38 } from "node:path";
|
|
66800
66805
|
|
|
66801
66806
|
// src/web/webhook-dispatch.ts
|
|
66807
|
+
var DISPATCH_SOURCES = ["github", "generic", "linear"];
|
|
66802
66808
|
function renderTemplate2(template, ctx) {
|
|
66803
66809
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => ctx[key] ?? "");
|
|
66804
66810
|
}
|
|
@@ -66815,12 +66821,86 @@ function buildGithubContext(eventType, payload) {
|
|
|
66815
66821
|
const rawLabels = obj?.labels ?? [];
|
|
66816
66822
|
const labels = rawLabels.map((l) => String(l.name ?? "")).join(", ");
|
|
66817
66823
|
const action = String(payload.action ?? "");
|
|
66818
|
-
|
|
66824
|
+
const assignee = String(obj?.assignee?.login ?? "");
|
|
66825
|
+
return {
|
|
66826
|
+
repo,
|
|
66827
|
+
number,
|
|
66828
|
+
title,
|
|
66829
|
+
html_url,
|
|
66830
|
+
author,
|
|
66831
|
+
labels,
|
|
66832
|
+
action,
|
|
66833
|
+
event: eventType,
|
|
66834
|
+
assignee,
|
|
66835
|
+
body: title
|
|
66836
|
+
};
|
|
66837
|
+
}
|
|
66838
|
+
function buildLinearContext(eventType, payload) {
|
|
66839
|
+
const data = payload.data ?? {};
|
|
66840
|
+
const issue = data.issue ?? {};
|
|
66841
|
+
const number = String(data.identifier ?? issue.identifier ?? "");
|
|
66842
|
+
const title = String(data.title ?? issue.title ?? "");
|
|
66843
|
+
const html_url = String(payload.url ?? "");
|
|
66844
|
+
const action = String(payload.action ?? "");
|
|
66845
|
+
const assignee = String(data.assignee?.displayName ?? data.assignee?.name ?? "");
|
|
66846
|
+
const author = String(data.user?.displayName ?? data.user?.name ?? "");
|
|
66847
|
+
const rawLabels = data.labels ?? [];
|
|
66848
|
+
const labels = rawLabels.map((l) => String(l.name ?? "")).join(", ");
|
|
66849
|
+
const commentBody = String(data.body ?? "");
|
|
66850
|
+
const body = [title, commentBody].filter(Boolean).join(`
|
|
66851
|
+
`);
|
|
66852
|
+
return {
|
|
66853
|
+
repo: "linear",
|
|
66854
|
+
number,
|
|
66855
|
+
title,
|
|
66856
|
+
html_url,
|
|
66857
|
+
author,
|
|
66858
|
+
labels,
|
|
66859
|
+
action,
|
|
66860
|
+
event: eventType,
|
|
66861
|
+
assignee,
|
|
66862
|
+
body
|
|
66863
|
+
};
|
|
66864
|
+
}
|
|
66865
|
+
function buildGenericContext(source, payload) {
|
|
66866
|
+
const title = typeof payload.title === "string" ? payload.title : typeof payload.message === "string" ? payload.message : typeof payload.text === "string" ? payload.text : "";
|
|
66867
|
+
const action = String(payload.action ?? "");
|
|
66868
|
+
const html_url = typeof payload.url === "string" ? payload.url : "";
|
|
66869
|
+
const number = String(payload.id ?? payload.number ?? "");
|
|
66870
|
+
const author = String(payload.author ?? payload.user ?? "");
|
|
66871
|
+
const assignee = String(payload.assignee ?? "");
|
|
66872
|
+
const rawLabels = Array.isArray(payload.labels) ? payload.labels.map((l) => typeof l === "string" ? l : String(l?.name ?? "")) : [];
|
|
66873
|
+
const labels = rawLabels.join(", ");
|
|
66874
|
+
return {
|
|
66875
|
+
repo: source,
|
|
66876
|
+
number,
|
|
66877
|
+
title,
|
|
66878
|
+
html_url,
|
|
66879
|
+
author,
|
|
66880
|
+
labels,
|
|
66881
|
+
action,
|
|
66882
|
+
event: source,
|
|
66883
|
+
assignee,
|
|
66884
|
+
body: title
|
|
66885
|
+
};
|
|
66886
|
+
}
|
|
66887
|
+
function buildContext(source, eventType, payload) {
|
|
66888
|
+
switch (source) {
|
|
66889
|
+
case "linear":
|
|
66890
|
+
return buildLinearContext(eventType, payload);
|
|
66891
|
+
case "generic":
|
|
66892
|
+
return buildGenericContext(eventType, payload);
|
|
66893
|
+
default:
|
|
66894
|
+
return buildGithubContext(eventType, payload);
|
|
66895
|
+
}
|
|
66896
|
+
}
|
|
66897
|
+
function labelSetFromContext(ctx) {
|
|
66898
|
+
return new Set(ctx.labels.split(",").map((l) => l.trim()).filter(Boolean));
|
|
66819
66899
|
}
|
|
66820
|
-
function matchesRule(eventType, payload, matcher) {
|
|
66900
|
+
function matchesRule(source, eventType, payload, matcher) {
|
|
66821
66901
|
if (matcher.event !== eventType)
|
|
66822
66902
|
return false;
|
|
66823
|
-
const ctx =
|
|
66903
|
+
const ctx = buildContext(source, eventType, payload);
|
|
66824
66904
|
if (matcher.actions && matcher.actions.length > 0) {
|
|
66825
66905
|
if (!matcher.actions.includes(ctx.action))
|
|
66826
66906
|
return false;
|
|
@@ -66829,22 +66909,24 @@ function matchesRule(eventType, payload, matcher) {
|
|
|
66829
66909
|
if (matcher.exclude_authors.includes(ctx.author))
|
|
66830
66910
|
return false;
|
|
66831
66911
|
}
|
|
66912
|
+
if (matcher.assignee_any && matcher.assignee_any.length > 0) {
|
|
66913
|
+
if (!matcher.assignee_any.includes(ctx.assignee))
|
|
66914
|
+
return false;
|
|
66915
|
+
}
|
|
66916
|
+
if (matcher.mentions_any && matcher.mentions_any.length > 0) {
|
|
66917
|
+
const hay = ctx.body.toLowerCase();
|
|
66918
|
+
const hasMention = matcher.mentions_any.some((m) => hay.includes(m.toLowerCase()));
|
|
66919
|
+
if (!hasMention)
|
|
66920
|
+
return false;
|
|
66921
|
+
}
|
|
66832
66922
|
if (matcher.labels_any && matcher.labels_any.length > 0) {
|
|
66833
|
-
const
|
|
66834
|
-
|
|
66835
|
-
const rawLabels = (pr ?? issue)?.labels ?? [];
|
|
66836
|
-
const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
|
|
66837
|
-
const hasAny = matcher.labels_any.some((l) => labelNames.has(l));
|
|
66838
|
-
if (!hasAny)
|
|
66923
|
+
const labelNames = labelSetFromContext(ctx);
|
|
66924
|
+
if (!matcher.labels_any.some((l) => labelNames.has(l)))
|
|
66839
66925
|
return false;
|
|
66840
66926
|
}
|
|
66841
66927
|
if (matcher.labels_all && matcher.labels_all.length > 0) {
|
|
66842
|
-
const
|
|
66843
|
-
|
|
66844
|
-
const rawLabels = (pr ?? issue)?.labels ?? [];
|
|
66845
|
-
const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
|
|
66846
|
-
const hasAll = matcher.labels_all.every((l) => labelNames.has(l));
|
|
66847
|
-
if (!hasAll)
|
|
66928
|
+
const labelNames = labelSetFromContext(ctx);
|
|
66929
|
+
if (!matcher.labels_all.every((l) => labelNames.has(l)))
|
|
66848
66930
|
return false;
|
|
66849
66931
|
}
|
|
66850
66932
|
return true;
|
|
@@ -67371,7 +67453,11 @@ function registerDispatchVerb(tg, _program) {
|
|
|
67371
67453
|
} catch (err) {
|
|
67372
67454
|
fail(`Could not read payload file '${opts.payload}': ${err.message}`);
|
|
67373
67455
|
}
|
|
67374
|
-
|
|
67456
|
+
if (!DISPATCH_SOURCES.includes(opts.source)) {
|
|
67457
|
+
console.log(source_default.yellow(`Source '${opts.source}' does not support dispatch (known: ${DISPATCH_SOURCES.join(", ")}).`));
|
|
67458
|
+
return;
|
|
67459
|
+
}
|
|
67460
|
+
const rules = dispatchConfig[opts.source] ?? [];
|
|
67375
67461
|
if (rules.length === 0) {
|
|
67376
67462
|
console.log(source_default.yellow(`No dispatch rules for source '${opts.source}'.`));
|
|
67377
67463
|
return;
|
|
@@ -67380,7 +67466,7 @@ function registerDispatchVerb(tg, _program) {
|
|
|
67380
67466
|
const now = new Date;
|
|
67381
67467
|
for (let i = 0;i < rules.length; i++) {
|
|
67382
67468
|
const rule = rules[i];
|
|
67383
|
-
const matched = matchesRule(opts.event, payload, rule.match);
|
|
67469
|
+
const matched = matchesRule(opts.source, opts.event, payload, rule.match);
|
|
67384
67470
|
const prefix = matched ? source_default.green("\u2713 MATCH") : source_default.dim("\u2717 no match");
|
|
67385
67471
|
const desc = rule.description ? ` \u2014 ${rule.description}` : ` \u2014 rule ${i}`;
|
|
67386
67472
|
console.log(`${prefix} rule ${i}${desc}`);
|
|
@@ -67395,7 +67481,7 @@ function registerDispatchVerb(tg, _program) {
|
|
|
67395
67481
|
const ms = parseDurationMs(rule.cooldown);
|
|
67396
67482
|
console.log(` cooldown: ${rule.cooldown} (${ms}ms) \u2014 state tracked in webhook-cooldown.json`);
|
|
67397
67483
|
}
|
|
67398
|
-
const ctx =
|
|
67484
|
+
const ctx = buildContext(opts.source, opts.event, payload);
|
|
67399
67485
|
const rendered = renderTemplate2(rule.prompt, ctx);
|
|
67400
67486
|
console.log(source_default.bold(" rendered prompt:"));
|
|
67401
67487
|
for (const line of rendered.split(`
|
|
@@ -73801,6 +73887,26 @@ function verifyGithubSignature(body, signatureHeader, secret) {
|
|
|
73801
73887
|
return { ok: false, reason: "signature-mismatch" };
|
|
73802
73888
|
return { ok: true };
|
|
73803
73889
|
}
|
|
73890
|
+
function verifyLinearSignature(body, signatureHeader, secret) {
|
|
73891
|
+
if (!secret || secret.length === 0) {
|
|
73892
|
+
return { ok: false, reason: "no-secret-configured" };
|
|
73893
|
+
}
|
|
73894
|
+
if (!signatureHeader) {
|
|
73895
|
+
return { ok: false, reason: "no-signature-header" };
|
|
73896
|
+
}
|
|
73897
|
+
const provided = signatureHeader.trim();
|
|
73898
|
+
if (!/^[0-9a-f]{64}$/.test(provided)) {
|
|
73899
|
+
return { ok: false, reason: "malformed-hex" };
|
|
73900
|
+
}
|
|
73901
|
+
const expected = createHmac2("sha256", secret).update(body).digest("hex");
|
|
73902
|
+
const a = Buffer.from(provided, "utf-8");
|
|
73903
|
+
const b = Buffer.from(expected, "utf-8");
|
|
73904
|
+
if (a.length !== b.length)
|
|
73905
|
+
return { ok: false, reason: "length-mismatch" };
|
|
73906
|
+
if (!timingSafeEqual(a, b))
|
|
73907
|
+
return { ok: false, reason: "signature-mismatch" };
|
|
73908
|
+
return { ok: true };
|
|
73909
|
+
}
|
|
73804
73910
|
function verifyBearerToken(authHeader, secret) {
|
|
73805
73911
|
if (!secret || secret.length === 0) {
|
|
73806
73912
|
return { ok: false, reason: "no-secret-configured" };
|
|
@@ -73866,6 +73972,41 @@ ${compare2}` : ""}`,
|
|
|
73866
73972
|
};
|
|
73867
73973
|
}
|
|
73868
73974
|
}
|
|
73975
|
+
function renderLinearEvent(eventType, payload) {
|
|
73976
|
+
const action = String(payload.action ?? "");
|
|
73977
|
+
const url = typeof payload.url === "string" ? payload.url : "";
|
|
73978
|
+
const data = payload.data ?? {};
|
|
73979
|
+
switch (eventType) {
|
|
73980
|
+
case "issue": {
|
|
73981
|
+
const id = String(data.identifier ?? "?");
|
|
73982
|
+
const title = String(data.title ?? "");
|
|
73983
|
+
const assignee = data.assignee?.displayName ?? data.assignee?.name ?? "";
|
|
73984
|
+
return {
|
|
73985
|
+
text: `\uD83D\uDCD0 <b>Linear</b> issue ${escapeHtml3(id)} ${escapeHtml3(action)}` + `${assignee ? ` \u2192 @${escapeHtml3(assignee)}` : ""}
|
|
73986
|
+
` + `${escapeHtml3(title)}${url ? `
|
|
73987
|
+
${url}` : ""}`,
|
|
73988
|
+
disableLinkPreview: true
|
|
73989
|
+
};
|
|
73990
|
+
}
|
|
73991
|
+
case "comment": {
|
|
73992
|
+
const issue = data.issue ?? {};
|
|
73993
|
+
const id = String(issue.identifier ?? "?");
|
|
73994
|
+
const body = String(data.body ?? "").slice(0, 200);
|
|
73995
|
+
return {
|
|
73996
|
+
text: `\uD83D\uDCD0 <b>Linear</b> comment ${escapeHtml3(action)} on ${escapeHtml3(id)}
|
|
73997
|
+
` + `${escapeHtml3(body)}${url ? `
|
|
73998
|
+
${url}` : ""}`,
|
|
73999
|
+
disableLinkPreview: true
|
|
74000
|
+
};
|
|
74001
|
+
}
|
|
74002
|
+
default:
|
|
74003
|
+
return {
|
|
74004
|
+
text: `\uD83D\uDCD0 <b>Linear</b> ${escapeHtml3(eventType)} ${escapeHtml3(action)}` + `${url ? `
|
|
74005
|
+
${url}` : ""}`,
|
|
74006
|
+
disableLinkPreview: true
|
|
74007
|
+
};
|
|
74008
|
+
}
|
|
74009
|
+
}
|
|
73869
74010
|
function renderGenericEvent(source, payload) {
|
|
73870
74011
|
const title = typeof payload.title === "string" ? payload.title : typeof payload.message === "string" ? payload.message : typeof payload.text === "string" ? payload.text : null;
|
|
73871
74012
|
const summary = title ?? JSON.stringify(payload).slice(0, 200);
|
|
@@ -73956,7 +74097,7 @@ function verifyEdgeHeader(headerValue, expectedSecret) {
|
|
|
73956
74097
|
}
|
|
73957
74098
|
|
|
73958
74099
|
// src/web/webhook-handler.ts
|
|
73959
|
-
var KNOWN_SOURCES = ["github", "generic"];
|
|
74100
|
+
var KNOWN_SOURCES = ["github", "generic", "linear"];
|
|
73960
74101
|
function jsonReply(status, body, extraHeaders) {
|
|
73961
74102
|
return {
|
|
73962
74103
|
status,
|
|
@@ -74110,6 +74251,9 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74110
74251
|
if (source === "github") {
|
|
74111
74252
|
const sigHeader = args.headers.get("x-hub-signature-256");
|
|
74112
74253
|
verifyResult = verifyGithubSignature(args.body, sigHeader, secret);
|
|
74254
|
+
} else if (source === "linear") {
|
|
74255
|
+
const sigHeader = args.headers.get("linear-signature");
|
|
74256
|
+
verifyResult = verifyLinearSignature(args.body, sigHeader, secret);
|
|
74113
74257
|
} else {
|
|
74114
74258
|
const authHeader = args.headers.get("authorization");
|
|
74115
74259
|
verifyResult = verifyBearerToken(authHeader, secret);
|
|
@@ -74152,8 +74296,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74152
74296
|
`);
|
|
74153
74297
|
return jsonReply(400, { ok: false, error: "malformed json" });
|
|
74154
74298
|
}
|
|
74155
|
-
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : args.source;
|
|
74156
|
-
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
74299
|
+
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
|
|
74300
|
+
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
74157
74301
|
if (args.viaGateway) {
|
|
74158
74302
|
const socketPath = join45(resolveAgentDir(args.agent), "telegram", "webhook.sock");
|
|
74159
74303
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
@@ -81747,7 +81891,7 @@ function registerApplyCommand(program3) {
|
|
|
81747
81891
|
}
|
|
81748
81892
|
}
|
|
81749
81893
|
const buildLocal = !!opts.buildLocal;
|
|
81750
|
-
const
|
|
81894
|
+
const buildContext2 = typeof opts.buildLocal === "string" ? opts.buildLocal : process.cwd();
|
|
81751
81895
|
const releaseOverride = opts.channel ? { channel: opts.channel } : opts.pin ? { pin: opts.pin } : undefined;
|
|
81752
81896
|
if (opts.pin && !/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/.test(opts.pin)) {
|
|
81753
81897
|
console.error(source_default.red(`--pin "${opts.pin}" is invalid. Expected sha-<7-40 hex> or v<semver>.`));
|
|
@@ -81755,7 +81899,7 @@ function registerApplyCommand(program3) {
|
|
|
81755
81899
|
}
|
|
81756
81900
|
const result = await runApply(config, {
|
|
81757
81901
|
buildLocal,
|
|
81758
|
-
buildContext: buildLocal ?
|
|
81902
|
+
buildContext: buildLocal ? buildContext2 : undefined,
|
|
81759
81903
|
outPath: opts.out,
|
|
81760
81904
|
example: opts.example,
|
|
81761
81905
|
nonInteractive: opts.nonInteractive ?? false,
|
|
@@ -85006,6 +85150,16 @@ networks:
|
|
|
85006
85150
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
85007
85151
|
`;
|
|
85008
85152
|
}
|
|
85153
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
|
|
85154
|
+
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
85155
|
+
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
85156
|
+
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
85157
|
+
throw new Error(`switchroom hostd install: refusing to generate \u2014 the host home resolved to ` + `"${resolved}", the in-container mount point of the operator home (never a valid ` + `host bind source). Emitting it would make Docker create empty /host-home dirs on ` + `the host and crash-loop hostd on a missing config mount.
|
|
85158
|
+
|
|
85159
|
+
` + `Recovery: run \`switchroom hostd install\` from the HOST shell (not inside the ` + `hostd container), or set SWITCHROOM_HOST_HOME to the real host home first.`);
|
|
85160
|
+
}
|
|
85161
|
+
return resolved;
|
|
85162
|
+
}
|
|
85009
85163
|
function hostdDir() {
|
|
85010
85164
|
return join82(homedir48(), ".switchroom", "hostd");
|
|
85011
85165
|
}
|
|
@@ -85049,7 +85203,7 @@ async function doInstall(opts, program3) {
|
|
|
85049
85203
|
const composePath = hostdComposePath();
|
|
85050
85204
|
mkdirSync47(dir, { recursive: true });
|
|
85051
85205
|
const yaml = renderHostdComposeFile({
|
|
85052
|
-
hostHome:
|
|
85206
|
+
hostHome: resolveHostdHostHome(),
|
|
85053
85207
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
85054
85208
|
operatorUid: resolveOperatorUid()
|
|
85055
85209
|
});
|
|
@@ -13817,6 +13817,25 @@ var SessionContinuitySchema = exports_external.object({
|
|
|
13817
13817
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
13818
13818
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
13819
13819
|
}).optional();
|
|
13820
|
+
var webhookDispatchRule = exports_external.object({
|
|
13821
|
+
description: exports_external.string().optional(),
|
|
13822
|
+
match: exports_external.object({
|
|
13823
|
+
event: exports_external.string(),
|
|
13824
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
13825
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
13826
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
13827
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
13828
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
13829
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
13830
|
+
}).passthrough(),
|
|
13831
|
+
prompt: exports_external.string(),
|
|
13832
|
+
cooldown: exports_external.string().optional(),
|
|
13833
|
+
quiet_hours: exports_external.object({
|
|
13834
|
+
start: exports_external.number().int().min(0).max(23),
|
|
13835
|
+
end: exports_external.number().int().min(0).max(23),
|
|
13836
|
+
tz: exports_external.string().optional()
|
|
13837
|
+
}).optional()
|
|
13838
|
+
});
|
|
13820
13839
|
var TelegramChannelSchema = exports_external.object({
|
|
13821
13840
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
13822
13841
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' — the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -13852,26 +13871,12 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
13852
13871
|
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."),
|
|
13853
13872
|
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.")
|
|
13854
13873
|
}).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."),
|
|
13855
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
13874
|
+
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.)"),
|
|
13856
13875
|
webhook_dispatch: exports_external.object({
|
|
13857
|
-
github: exports_external.array(
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
13862
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
13863
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
13864
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
13865
|
-
}).passthrough(),
|
|
13866
|
-
prompt: exports_external.string(),
|
|
13867
|
-
cooldown: exports_external.string().optional(),
|
|
13868
|
-
quiet_hours: exports_external.object({
|
|
13869
|
-
start: exports_external.number().int().min(0).max(23),
|
|
13870
|
-
end: exports_external.number().int().min(0).max(23),
|
|
13871
|
-
tz: exports_external.string().optional()
|
|
13872
|
-
}).optional()
|
|
13873
|
-
})).optional()
|
|
13874
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
13876
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
13877
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
13878
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
13879
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source — 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
13875
13880
|
webhook_rate_limit: exports_external.object({
|
|
13876
13881
|
rpm: exports_external.number().int().positive()
|
|
13877
13882
|
}).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."),
|
|
@@ -14048,7 +14053,7 @@ var AgentSchema = exports_external.object({
|
|
|
14048
14053
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
14049
14054
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
14050
14055
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
14051
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
14056
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
14052
14057
|
voice_in: exports_external.object({
|
|
14053
14058
|
enabled: exports_external.boolean().optional(),
|
|
14054
14059
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -11277,7 +11277,7 @@ var init_dist = __esm(() => {
|
|
|
11277
11277
|
});
|
|
11278
11278
|
|
|
11279
11279
|
// src/config/schema.ts
|
|
11280
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
11280
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
11281
11281
|
var init_schema = __esm(() => {
|
|
11282
11282
|
init_zod();
|
|
11283
11283
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -11403,6 +11403,25 @@ var init_schema = __esm(() => {
|
|
|
11403
11403
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11404
11404
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
11405
11405
|
}).optional();
|
|
11406
|
+
webhookDispatchRule = exports_external.object({
|
|
11407
|
+
description: exports_external.string().optional(),
|
|
11408
|
+
match: exports_external.object({
|
|
11409
|
+
event: exports_external.string(),
|
|
11410
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
11411
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11412
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11413
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
11414
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
11415
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
11416
|
+
}).passthrough(),
|
|
11417
|
+
prompt: exports_external.string(),
|
|
11418
|
+
cooldown: exports_external.string().optional(),
|
|
11419
|
+
quiet_hours: exports_external.object({
|
|
11420
|
+
start: exports_external.number().int().min(0).max(23),
|
|
11421
|
+
end: exports_external.number().int().min(0).max(23),
|
|
11422
|
+
tz: exports_external.string().optional()
|
|
11423
|
+
}).optional()
|
|
11424
|
+
});
|
|
11406
11425
|
TelegramChannelSchema = exports_external.object({
|
|
11407
11426
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
11408
11427
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' — the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -11438,26 +11457,12 @@ var init_schema = __esm(() => {
|
|
|
11438
11457
|
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."),
|
|
11439
11458
|
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.")
|
|
11440
11459
|
}).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."),
|
|
11441
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
11460
|
+
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.)"),
|
|
11442
11461
|
webhook_dispatch: exports_external.object({
|
|
11443
|
-
github: exports_external.array(
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
11448
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11449
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11450
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
11451
|
-
}).passthrough(),
|
|
11452
|
-
prompt: exports_external.string(),
|
|
11453
|
-
cooldown: exports_external.string().optional(),
|
|
11454
|
-
quiet_hours: exports_external.object({
|
|
11455
|
-
start: exports_external.number().int().min(0).max(23),
|
|
11456
|
-
end: exports_external.number().int().min(0).max(23),
|
|
11457
|
-
tz: exports_external.string().optional()
|
|
11458
|
-
}).optional()
|
|
11459
|
-
})).optional()
|
|
11460
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11462
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
11463
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
11464
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
11465
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source — 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11461
11466
|
webhook_rate_limit: exports_external.object({
|
|
11462
11467
|
rpm: exports_external.number().int().positive()
|
|
11463
11468
|
}).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."),
|
|
@@ -11634,7 +11639,7 @@ var init_schema = __esm(() => {
|
|
|
11634
11639
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
11635
11640
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
11636
11641
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
11637
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11642
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11638
11643
|
voice_in: exports_external.object({
|
|
11639
11644
|
enabled: exports_external.boolean().optional(),
|
|
11640
11645
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -11277,7 +11277,7 @@ var init_zod = __esm(() => {
|
|
|
11277
11277
|
});
|
|
11278
11278
|
|
|
11279
11279
|
// src/config/schema.ts
|
|
11280
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
11280
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
11281
11281
|
var init_schema = __esm(() => {
|
|
11282
11282
|
init_zod();
|
|
11283
11283
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -11403,6 +11403,25 @@ var init_schema = __esm(() => {
|
|
|
11403
11403
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11404
11404
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
11405
11405
|
}).optional();
|
|
11406
|
+
webhookDispatchRule = exports_external.object({
|
|
11407
|
+
description: exports_external.string().optional(),
|
|
11408
|
+
match: exports_external.object({
|
|
11409
|
+
event: exports_external.string(),
|
|
11410
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
11411
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11412
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11413
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
11414
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
11415
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
11416
|
+
}).passthrough(),
|
|
11417
|
+
prompt: exports_external.string(),
|
|
11418
|
+
cooldown: exports_external.string().optional(),
|
|
11419
|
+
quiet_hours: exports_external.object({
|
|
11420
|
+
start: exports_external.number().int().min(0).max(23),
|
|
11421
|
+
end: exports_external.number().int().min(0).max(23),
|
|
11422
|
+
tz: exports_external.string().optional()
|
|
11423
|
+
}).optional()
|
|
11424
|
+
});
|
|
11406
11425
|
TelegramChannelSchema = exports_external.object({
|
|
11407
11426
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
11408
11427
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' — the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -11438,26 +11457,12 @@ var init_schema = __esm(() => {
|
|
|
11438
11457
|
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."),
|
|
11439
11458
|
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.")
|
|
11440
11459
|
}).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."),
|
|
11441
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
11460
|
+
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.)"),
|
|
11442
11461
|
webhook_dispatch: exports_external.object({
|
|
11443
|
-
github: exports_external.array(
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
11448
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
11449
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
11450
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
11451
|
-
}).passthrough(),
|
|
11452
|
-
prompt: exports_external.string(),
|
|
11453
|
-
cooldown: exports_external.string().optional(),
|
|
11454
|
-
quiet_hours: exports_external.object({
|
|
11455
|
-
start: exports_external.number().int().min(0).max(23),
|
|
11456
|
-
end: exports_external.number().int().min(0).max(23),
|
|
11457
|
-
tz: exports_external.string().optional()
|
|
11458
|
-
}).optional()
|
|
11459
|
-
})).optional()
|
|
11460
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11462
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
11463
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
11464
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
11465
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source — 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11461
11466
|
webhook_rate_limit: exports_external.object({
|
|
11462
11467
|
rpm: exports_external.number().int().positive()
|
|
11463
11468
|
}).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."),
|
|
@@ -11634,7 +11639,7 @@ var init_schema = __esm(() => {
|
|
|
11634
11639
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
11635
11640
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
11636
11641
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
11637
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11642
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
11638
11643
|
voice_in: exports_external.object({
|
|
11639
11644
|
enabled: exports_external.boolean().optional(),
|
|
11640
11645
|
provider: exports_external.enum(["openai"]).optional(),
|
package/package.json
CHANGED
|
@@ -23802,7 +23802,7 @@ var init_dist = __esm(() => {
|
|
|
23802
23802
|
});
|
|
23803
23803
|
|
|
23804
23804
|
// ../src/config/schema.ts
|
|
23805
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
23805
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
23806
23806
|
var init_schema = __esm(() => {
|
|
23807
23807
|
init_zod();
|
|
23808
23808
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -23928,6 +23928,25 @@ var init_schema = __esm(() => {
|
|
|
23928
23928
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
23929
23929
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
23930
23930
|
}).optional();
|
|
23931
|
+
webhookDispatchRule = exports_external.object({
|
|
23932
|
+
description: exports_external.string().optional(),
|
|
23933
|
+
match: exports_external.object({
|
|
23934
|
+
event: exports_external.string(),
|
|
23935
|
+
actions: exports_external.array(exports_external.string()).optional(),
|
|
23936
|
+
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
23937
|
+
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
23938
|
+
exclude_authors: exports_external.array(exports_external.string()).optional(),
|
|
23939
|
+
assignee_any: exports_external.array(exports_external.string()).optional(),
|
|
23940
|
+
mentions_any: exports_external.array(exports_external.string()).optional()
|
|
23941
|
+
}).passthrough(),
|
|
23942
|
+
prompt: exports_external.string(),
|
|
23943
|
+
cooldown: exports_external.string().optional(),
|
|
23944
|
+
quiet_hours: exports_external.object({
|
|
23945
|
+
start: exports_external.number().int().min(0).max(23),
|
|
23946
|
+
end: exports_external.number().int().min(0).max(23),
|
|
23947
|
+
tz: exports_external.string().optional()
|
|
23948
|
+
}).optional()
|
|
23949
|
+
});
|
|
23931
23950
|
TelegramChannelSchema = exports_external.object({
|
|
23932
23951
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
23933
23952
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' \u2014 the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
@@ -23963,26 +23982,12 @@ var init_schema = __esm(() => {
|
|
|
23963
23982
|
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."),
|
|
23964
23983
|
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.")
|
|
23965
23984
|
}).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."),
|
|
23966
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).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). " + "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.)"),
|
|
23985
|
+
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.)"),
|
|
23967
23986
|
webhook_dispatch: exports_external.object({
|
|
23968
|
-
github: exports_external.array(
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
actions: exports_external.array(exports_external.string()).optional(),
|
|
23973
|
-
labels_any: exports_external.array(exports_external.string()).optional(),
|
|
23974
|
-
labels_all: exports_external.array(exports_external.string()).optional(),
|
|
23975
|
-
exclude_authors: exports_external.array(exports_external.string()).optional()
|
|
23976
|
-
}).passthrough(),
|
|
23977
|
-
prompt: exports_external.string(),
|
|
23978
|
-
cooldown: exports_external.string().optional(),
|
|
23979
|
-
quiet_hours: exports_external.object({
|
|
23980
|
-
start: exports_external.number().int().min(0).max(23),
|
|
23981
|
-
end: exports_external.number().int().min(0).max(23),
|
|
23982
|
-
tz: exports_external.string().optional()
|
|
23983
|
-
}).optional()
|
|
23984
|
-
})).optional()
|
|
23985
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
23987
|
+
github: exports_external.array(webhookDispatchRule).optional(),
|
|
23988
|
+
generic: exports_external.array(webhookDispatchRule).optional(),
|
|
23989
|
+
linear: exports_external.array(webhookDispatchRule).optional()
|
|
23990
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Rules are keyed by source \u2014 'github', 'generic', or 'linear' (#2272). " + "Supports cooldowns, quiet hours, label/action matchers, and (for " + "linear/generic) assignee_any / mentions_any matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
23986
23991
|
webhook_rate_limit: exports_external.object({
|
|
23987
23992
|
rpm: exports_external.number().int().positive()
|
|
23988
23993
|
}).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."),
|
|
@@ -24159,7 +24164,7 @@ var init_schema = __esm(() => {
|
|
|
24159
24164
|
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (\u2264140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml \u2014 never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
24160
24165
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` \u2014 a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
24161
24166
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
24162
|
-
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
24167
|
+
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("[DEPRECATED \u2014 moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
24163
24168
|
voice_in: exports_external.object({
|
|
24164
24169
|
enabled: exports_external.boolean().optional(),
|
|
24165
24170
|
provider: exports_external.enum(["openai"]).optional(),
|
|
@@ -46718,6 +46723,7 @@ function createInjectIpcClient(options) {
|
|
|
46718
46723
|
}
|
|
46719
46724
|
|
|
46720
46725
|
// ../src/web/webhook-dispatch.ts
|
|
46726
|
+
var DISPATCH_SOURCES = ["github", "generic", "linear"];
|
|
46721
46727
|
function renderTemplate(template, ctx) {
|
|
46722
46728
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => ctx[key] ?? "");
|
|
46723
46729
|
}
|
|
@@ -46734,12 +46740,86 @@ function buildGithubContext(eventType, payload) {
|
|
|
46734
46740
|
const rawLabels = obj?.labels ?? [];
|
|
46735
46741
|
const labels = rawLabels.map((l) => String(l.name ?? "")).join(", ");
|
|
46736
46742
|
const action = String(payload.action ?? "");
|
|
46737
|
-
|
|
46743
|
+
const assignee = String(obj?.assignee?.login ?? "");
|
|
46744
|
+
return {
|
|
46745
|
+
repo,
|
|
46746
|
+
number,
|
|
46747
|
+
title,
|
|
46748
|
+
html_url,
|
|
46749
|
+
author,
|
|
46750
|
+
labels,
|
|
46751
|
+
action,
|
|
46752
|
+
event: eventType,
|
|
46753
|
+
assignee,
|
|
46754
|
+
body: title
|
|
46755
|
+
};
|
|
46756
|
+
}
|
|
46757
|
+
function buildLinearContext(eventType, payload) {
|
|
46758
|
+
const data = payload.data ?? {};
|
|
46759
|
+
const issue = data.issue ?? {};
|
|
46760
|
+
const number = String(data.identifier ?? issue.identifier ?? "");
|
|
46761
|
+
const title = String(data.title ?? issue.title ?? "");
|
|
46762
|
+
const html_url = String(payload.url ?? "");
|
|
46763
|
+
const action = String(payload.action ?? "");
|
|
46764
|
+
const assignee = String(data.assignee?.displayName ?? data.assignee?.name ?? "");
|
|
46765
|
+
const author = String(data.user?.displayName ?? data.user?.name ?? "");
|
|
46766
|
+
const rawLabels = data.labels ?? [];
|
|
46767
|
+
const labels = rawLabels.map((l) => String(l.name ?? "")).join(", ");
|
|
46768
|
+
const commentBody = String(data.body ?? "");
|
|
46769
|
+
const body = [title, commentBody].filter(Boolean).join(`
|
|
46770
|
+
`);
|
|
46771
|
+
return {
|
|
46772
|
+
repo: "linear",
|
|
46773
|
+
number,
|
|
46774
|
+
title,
|
|
46775
|
+
html_url,
|
|
46776
|
+
author,
|
|
46777
|
+
labels,
|
|
46778
|
+
action,
|
|
46779
|
+
event: eventType,
|
|
46780
|
+
assignee,
|
|
46781
|
+
body
|
|
46782
|
+
};
|
|
46738
46783
|
}
|
|
46739
|
-
function
|
|
46784
|
+
function buildGenericContext(source, payload) {
|
|
46785
|
+
const title = typeof payload.title === "string" ? payload.title : typeof payload.message === "string" ? payload.message : typeof payload.text === "string" ? payload.text : "";
|
|
46786
|
+
const action = String(payload.action ?? "");
|
|
46787
|
+
const html_url = typeof payload.url === "string" ? payload.url : "";
|
|
46788
|
+
const number = String(payload.id ?? payload.number ?? "");
|
|
46789
|
+
const author = String(payload.author ?? payload.user ?? "");
|
|
46790
|
+
const assignee = String(payload.assignee ?? "");
|
|
46791
|
+
const rawLabels = Array.isArray(payload.labels) ? payload.labels.map((l) => typeof l === "string" ? l : String(l?.name ?? "")) : [];
|
|
46792
|
+
const labels = rawLabels.join(", ");
|
|
46793
|
+
return {
|
|
46794
|
+
repo: source,
|
|
46795
|
+
number,
|
|
46796
|
+
title,
|
|
46797
|
+
html_url,
|
|
46798
|
+
author,
|
|
46799
|
+
labels,
|
|
46800
|
+
action,
|
|
46801
|
+
event: source,
|
|
46802
|
+
assignee,
|
|
46803
|
+
body: title
|
|
46804
|
+
};
|
|
46805
|
+
}
|
|
46806
|
+
function buildContext(source, eventType, payload) {
|
|
46807
|
+
switch (source) {
|
|
46808
|
+
case "linear":
|
|
46809
|
+
return buildLinearContext(eventType, payload);
|
|
46810
|
+
case "generic":
|
|
46811
|
+
return buildGenericContext(eventType, payload);
|
|
46812
|
+
default:
|
|
46813
|
+
return buildGithubContext(eventType, payload);
|
|
46814
|
+
}
|
|
46815
|
+
}
|
|
46816
|
+
function labelSetFromContext(ctx) {
|
|
46817
|
+
return new Set(ctx.labels.split(",").map((l) => l.trim()).filter(Boolean));
|
|
46818
|
+
}
|
|
46819
|
+
function matchesRule(source, eventType, payload, matcher) {
|
|
46740
46820
|
if (matcher.event !== eventType)
|
|
46741
46821
|
return false;
|
|
46742
|
-
const ctx =
|
|
46822
|
+
const ctx = buildContext(source, eventType, payload);
|
|
46743
46823
|
if (matcher.actions && matcher.actions.length > 0) {
|
|
46744
46824
|
if (!matcher.actions.includes(ctx.action))
|
|
46745
46825
|
return false;
|
|
@@ -46748,22 +46828,24 @@ function matchesRule(eventType, payload, matcher) {
|
|
|
46748
46828
|
if (matcher.exclude_authors.includes(ctx.author))
|
|
46749
46829
|
return false;
|
|
46750
46830
|
}
|
|
46831
|
+
if (matcher.assignee_any && matcher.assignee_any.length > 0) {
|
|
46832
|
+
if (!matcher.assignee_any.includes(ctx.assignee))
|
|
46833
|
+
return false;
|
|
46834
|
+
}
|
|
46835
|
+
if (matcher.mentions_any && matcher.mentions_any.length > 0) {
|
|
46836
|
+
const hay = ctx.body.toLowerCase();
|
|
46837
|
+
const hasMention = matcher.mentions_any.some((m) => hay.includes(m.toLowerCase()));
|
|
46838
|
+
if (!hasMention)
|
|
46839
|
+
return false;
|
|
46840
|
+
}
|
|
46751
46841
|
if (matcher.labels_any && matcher.labels_any.length > 0) {
|
|
46752
|
-
const
|
|
46753
|
-
|
|
46754
|
-
const rawLabels = (pr ?? issue)?.labels ?? [];
|
|
46755
|
-
const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
|
|
46756
|
-
const hasAny = matcher.labels_any.some((l) => labelNames.has(l));
|
|
46757
|
-
if (!hasAny)
|
|
46842
|
+
const labelNames = labelSetFromContext(ctx);
|
|
46843
|
+
if (!matcher.labels_any.some((l) => labelNames.has(l)))
|
|
46758
46844
|
return false;
|
|
46759
46845
|
}
|
|
46760
46846
|
if (matcher.labels_all && matcher.labels_all.length > 0) {
|
|
46761
|
-
const
|
|
46762
|
-
|
|
46763
|
-
const rawLabels = (pr ?? issue)?.labels ?? [];
|
|
46764
|
-
const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
|
|
46765
|
-
const hasAll = matcher.labels_all.every((l) => labelNames.has(l));
|
|
46766
|
-
if (!hasAll)
|
|
46847
|
+
const labelNames = labelSetFromContext(ctx);
|
|
46848
|
+
if (!matcher.labels_all.every((l) => labelNames.has(l)))
|
|
46767
46849
|
return false;
|
|
46768
46850
|
}
|
|
46769
46851
|
return true;
|
|
@@ -46786,8 +46868,8 @@ function parseDurationMs(d) {
|
|
|
46786
46868
|
return n;
|
|
46787
46869
|
}
|
|
46788
46870
|
}
|
|
46789
|
-
function cooldownKey(eventType, repo, number, ruleIndex) {
|
|
46790
|
-
return `${eventType}:${repo}:${number}:${ruleIndex}`;
|
|
46871
|
+
function cooldownKey(source, eventType, repo, number, ruleIndex) {
|
|
46872
|
+
return `${source}:${eventType}:${repo}:${number}:${ruleIndex}`;
|
|
46791
46873
|
}
|
|
46792
46874
|
function loadCooldownFile(path) {
|
|
46793
46875
|
try {
|
|
@@ -46899,16 +46981,16 @@ function evaluateDispatch(args, deps = {}) {
|
|
|
46899
46981
|
const nowDate = deps.nowDate ?? (() => new Date(now));
|
|
46900
46982
|
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
|
|
46901
46983
|
const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
|
|
46902
|
-
if (args.source
|
|
46984
|
+
if (!DISPATCH_SOURCES.includes(args.source))
|
|
46903
46985
|
return 0;
|
|
46904
|
-
const rules = args.dispatchConfig.
|
|
46986
|
+
const rules = args.dispatchConfig[args.source];
|
|
46905
46987
|
if (!rules || rules.length === 0)
|
|
46906
46988
|
return 0;
|
|
46907
|
-
const ctx =
|
|
46989
|
+
const ctx = buildContext(args.source, args.eventType, args.payload);
|
|
46908
46990
|
let fired = 0;
|
|
46909
46991
|
for (let i = 0;i < rules.length; i++) {
|
|
46910
46992
|
const rule = rules[i];
|
|
46911
|
-
if (!matchesRule(args.eventType, args.payload, rule.match))
|
|
46993
|
+
if (!matchesRule(args.source, args.eventType, args.payload, rule.match))
|
|
46912
46994
|
continue;
|
|
46913
46995
|
if (rule.quiet_hours && isQuietHour(rule.quiet_hours, nowDate())) {
|
|
46914
46996
|
log(`webhook-dispatch: agent='${args.agent}' rule=${i} skipped (quiet hours)
|
|
@@ -46917,7 +46999,7 @@ function evaluateDispatch(args, deps = {}) {
|
|
|
46917
46999
|
}
|
|
46918
47000
|
const cooldownMs = rule.cooldown ? parseDurationMs(rule.cooldown) : 0;
|
|
46919
47001
|
if (cooldownMs > 0) {
|
|
46920
|
-
const ck = cooldownKey(args.eventType, ctx.repo, ctx.number, i);
|
|
47002
|
+
const ck = cooldownKey(args.source, args.eventType, ctx.repo, ctx.number, i);
|
|
46921
47003
|
if (cooldownStore.isCoolingDown(args.agent, ck, cooldownMs, now)) {
|
|
46922
47004
|
log(`webhook-dispatch: agent='${args.agent}' rule=${i} skipped (cooldown)
|
|
46923
47005
|
`);
|
|
@@ -47009,7 +47091,7 @@ function recordWebhookEvent(rec, deps = {}) {
|
|
|
47009
47091
|
const config = (deps.loadConfig ?? loadConfig)();
|
|
47010
47092
|
const rawAgent = config.agents?.[agent];
|
|
47011
47093
|
const dispatchConfig = rawAgent ? resolveAgentConfig(config.defaults, config.profiles, rawAgent).channels?.telegram?.webhook_dispatch : undefined;
|
|
47012
|
-
if (dispatchConfig && rec.source
|
|
47094
|
+
if (dispatchConfig && DISPATCH_SOURCES.includes(rec.source)) {
|
|
47013
47095
|
const target = resolveChannelTarget(config, agent);
|
|
47014
47096
|
if (!target) {
|
|
47015
47097
|
log(`webhook-gateway: agent='${agent}' dispatch skipped \u2014 no chat target (forum_chat_id / chat_id unset)
|
|
@@ -53601,10 +53683,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
53601
53683
|
}
|
|
53602
53684
|
|
|
53603
53685
|
// ../src/build-info.ts
|
|
53604
|
-
var VERSION = "0.15.
|
|
53605
|
-
var COMMIT_SHA = "
|
|
53606
|
-
var COMMIT_DATE = "2026-06-
|
|
53607
|
-
var LATEST_PR =
|
|
53686
|
+
var VERSION = "0.15.7";
|
|
53687
|
+
var COMMIT_SHA = "c0a8b988";
|
|
53688
|
+
var COMMIT_DATE = "2026-06-12T04:28:04Z";
|
|
53689
|
+
var LATEST_PR = 2286;
|
|
53608
53690
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
53609
53691
|
|
|
53610
53692
|
// gateway/boot-version.ts
|