switchroom 0.15.12 → 0.15.14

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.
Files changed (28) hide show
  1. package/dist/agent-scheduler/index.js +324 -14
  2. package/dist/auth-broker/index.js +61 -4
  3. package/dist/cli/notion-write-pretool.mjs +61 -4
  4. package/dist/cli/switchroom.js +402 -113
  5. package/dist/host-control/main.js +61 -4
  6. package/dist/vault/approvals/kernel-server.js +62 -5
  7. package/dist/vault/broker/server.js +62 -5
  8. package/package.json +1 -1
  9. package/profiles/_base/cron-session.sh.hbs +30 -13
  10. package/profiles/_shared/agent-self-service.md.hbs +37 -0
  11. package/telegram-plugin/bridge/bridge.ts +38 -1
  12. package/telegram-plugin/dist/bridge/bridge.js +31 -1
  13. package/telegram-plugin/dist/gateway/gateway.js +536 -53
  14. package/telegram-plugin/dist/server.js +31 -1
  15. package/telegram-plugin/gateway/gateway.ts +169 -6
  16. package/telegram-plugin/gateway/ipc-protocol.ts +31 -1
  17. package/telegram-plugin/gateway/ipc-server.ts +29 -0
  18. package/telegram-plugin/gateway/linear-activity.ts +145 -0
  19. package/telegram-plugin/runtime-metrics.ts +14 -0
  20. package/telegram-plugin/scoped-approval.ts +253 -0
  21. package/telegram-plugin/tests/bridge-liveness-override.test.ts +21 -0
  22. package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +54 -0
  23. package/telegram-plugin/tests/linear-agent-activity.test.ts +1 -1
  24. package/telegram-plugin/tests/linear-create-issue.test.ts +211 -0
  25. package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +13 -0
  26. package/telegram-plugin/tests/runtime-metrics.test.ts +9 -0
  27. package/telegram-plugin/tests/scoped-approval.test.ts +254 -0
  28. package/telegram-plugin/tests/send-outbound-wiring.test.ts +63 -0
@@ -11737,11 +11737,29 @@ var PollSpecSchema = exports_external.discriminatedUnion("type", [
11737
11737
  HttpDiffPollSchema,
11738
11738
  TelegramReactionsPollSchema
11739
11739
  ]);
11740
+ var TelegramMessageActionSchema = exports_external.object({
11741
+ type: exports_external.literal("telegram-message"),
11742
+ text: exports_external.string().min(1).describe("Operator-authored message text. Posts to the AGENT'S OWN chat only " + "(the chat is not configurable \u2014 fenced by construction), into the " + "entry-level `topic` when set. Supports ONLY deterministic placeholders " + "{{date}} / {{time}} (UTC, from the fire clock) and {{agent}}. NO vault " + "secrets (use a webhook action for secret-bearing requests) and NO model " + "output \u2014 the text is fully determined by config."),
11743
+ parse_mode: exports_external.enum(["html", "text"]).default("html").describe("Telegram parse mode for the message body.")
11744
+ });
11745
+ var WebhookActionSchema = exports_external.object({
11746
+ type: exports_external.literal("webhook"),
11747
+ url: exports_external.string().url().describe("Webhook target. SAME egress fence as an http-diff poll (\u00a76.1): https " + "only, host on the operator egress allowlist, loopback/private/link-local " + "rejected, resolved IP DNS-rebind-pinned. Not agent-writable without an " + "operator commit."),
11748
+ method: exports_external.enum(["GET", "POST"]).default("POST"),
11749
+ headers: exports_external.record(exports_external.string()).optional().describe("Static headers; {{secret}} substitution applies."),
11750
+ body: exports_external.string().optional().describe("Static request body; {{secret}} substitution applies."),
11751
+ secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault keys this webhook may inject into headers/body via {{name}}. Each " + "is HOST-PINNED (\u00a76.1): a secret may only be sent to the host it is bound " + "to in operator config, so an approved action cannot exfil it elsewhere.")
11752
+ });
11753
+ var ActionSpecSchema = exports_external.discriminatedUnion("type", [
11754
+ TelegramMessageActionSchema,
11755
+ WebhookActionSchema
11756
+ ]);
11740
11757
  var ScheduleEntrySchema = exports_external.object({
11741
11758
  cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
11742
- prompt: exports_external.string().describe("Prompt to send at the scheduled time (the escalation prompt when kind=poll; templated with {{diff}})."),
11743
- kind: exports_external.enum(["poll", "prompt"]).optional().describe("Tier-0 routing (docs/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. Honoured only when SWITCHROOM_CHEAP_CRON is on."),
11759
+ prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
11760
+ kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (docs/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates \u2014 zero " + "tokens, no session. poll/prompt are honoured only when " + "SWITCHROOM_CHEAP_CRON is on; an action is model-free regardless (the " + "kill-switch governs model tiering, not deterministic actions)."),
11744
11761
  poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
11762
+ action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
11745
11763
  model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) \u2014 the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
11746
11764
  context: exports_external.enum(["fresh", "agent"]).optional().describe("Does this cron need the agent, or just a model? 'fresh' \u2192 a minimal-" + "context cheap cron session (Tier 1). 'agent' \u2192 the agent's live " + "session with full persona/memory (Tier 2). Unset \u2192 inferred from " + "`model` (cheap\u2192fresh, else agent). Honoured only when " + "SWITCHROOM_CHEAP_CRON is on."),
11747
11765
  secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault key names this cron task may read via the vault-broker daemon. " + "Empty by default \u2014 broker requests for unlisted keys are denied. " + "Note: this is misconfiguration protection (a typo in cron-A doesn't " + "accidentally read cron-B's keys) rather than a security boundary \u2014 " + "anyone who can edit cron scripts can also edit switchroom.yaml, and " + "anyone with the vault passphrase can read the vault file directly. " + "See docs/configuration.md for the full framing."),
@@ -11758,13 +11776,41 @@ var ScheduleEntrySchema = exports_external.object({
11758
11776
  message: "kind: poll requires a `poll` spec (http-diff or telegram-reactions)."
11759
11777
  });
11760
11778
  }
11761
- if (kind === "prompt" && entry.poll) {
11779
+ if (kind !== "poll" && entry.poll) {
11762
11780
  ctx.addIssue({
11763
11781
  code: exports_external.ZodIssueCode.custom,
11764
11782
  path: ["poll"],
11765
11783
  message: "`poll` is only valid when kind: poll."
11766
11784
  });
11767
11785
  }
11786
+ if (kind === "action" && !entry.action) {
11787
+ ctx.addIssue({
11788
+ code: exports_external.ZodIssueCode.custom,
11789
+ path: ["action"],
11790
+ message: "kind: action requires an `action` spec (telegram-message or webhook)."
11791
+ });
11792
+ }
11793
+ if (kind !== "action" && entry.action) {
11794
+ ctx.addIssue({
11795
+ code: exports_external.ZodIssueCode.custom,
11796
+ path: ["action"],
11797
+ message: "`action` is only valid when kind: action."
11798
+ });
11799
+ }
11800
+ if (kind !== "action" && (entry.prompt === undefined || entry.prompt.trim() === "")) {
11801
+ ctx.addIssue({
11802
+ code: exports_external.ZodIssueCode.custom,
11803
+ path: ["prompt"],
11804
+ message: `kind: ${kind} requires a non-empty \`prompt\`.`
11805
+ });
11806
+ }
11807
+ if (kind === "action" && entry.prompt !== undefined) {
11808
+ ctx.addIssue({
11809
+ code: exports_external.ZodIssueCode.custom,
11810
+ path: ["prompt"],
11811
+ message: "`prompt` is not valid for kind: action (an action never fires a model)."
11812
+ });
11813
+ }
11768
11814
  });
11769
11815
  var AgentSoulSchema = exports_external.object({
11770
11816
  name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
@@ -11898,7 +11944,8 @@ var TelegramChannelSchema = exports_external.object({
11898
11944
  linear_agent: exports_external.object({
11899
11945
  enabled: exports_external.boolean(),
11900
11946
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11901
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11947
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11948
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11902
11949
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11903
11950
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11904
11951
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -12774,6 +12821,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
12774
12821
  }
12775
12822
  merged.reaction_dispatch = combined;
12776
12823
  }
12824
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
12825
+ if (linearEnabled) {
12826
+ const rd = merged.reaction_dispatch;
12827
+ if (!rd || rd.emojis === undefined) {
12828
+ merged.reaction_dispatch = {
12829
+ enabled: rd?.enabled ?? true,
12830
+ emojis: ["\uD83D\uDC68\u200d\uD83D\uDCBB", "\uD83D\uDCCC"]
12831
+ };
12832
+ }
12833
+ }
12777
12834
  if (defaults.resources || merged.resources) {
12778
12835
  const d = defaults.resources ?? {};
12779
12836
  const a = merged.resources ?? {};