switchroom 0.15.37 → 0.15.39

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 (73) hide show
  1. package/dist/agent-scheduler/index.js +89 -89
  2. package/dist/auth-broker/index.js +89 -89
  3. package/dist/cli/autoaccept-poll.js +13 -7
  4. package/dist/cli/drive-write-pretool.mjs +10 -10
  5. package/dist/cli/notion-write-pretool.mjs +91 -91
  6. package/dist/cli/skill-validate-pretool.mjs +72 -72
  7. package/dist/cli/switchroom.js +857 -572
  8. package/dist/cli/ui/index.html +87 -17
  9. package/dist/host-control/main.js +158 -158
  10. package/dist/vault/approvals/kernel-server.js +91 -91
  11. package/dist/vault/broker/server.js +92 -92
  12. package/package.json +1 -1
  13. package/profiles/_base/cron-session.sh.hbs +1 -1
  14. package/profiles/_base/start.sh.hbs +1 -1
  15. package/profiles/default/CLAUDE.md.hbs +2 -0
  16. package/skills/switchroom-manage/SKILL.md +1 -1
  17. package/skills/switchroom-runtime/SKILL.md +1 -1
  18. package/telegram-plugin/answer-stream.ts +1 -1
  19. package/telegram-plugin/bridge/bridge.ts +18 -1
  20. package/telegram-plugin/bridge/ipc-client.ts +4 -1
  21. package/telegram-plugin/bridge/tool-filter.ts +77 -0
  22. package/telegram-plugin/chat-lock.ts +1 -1
  23. package/telegram-plugin/credits-watch.ts +1 -1
  24. package/telegram-plugin/dist/bridge/bridge.js +141 -115
  25. package/telegram-plugin/dist/gateway/gateway.js +318 -207
  26. package/telegram-plugin/dist/server.js +193 -164
  27. package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
  28. package/telegram-plugin/gateway/boot-card.ts +5 -1
  29. package/telegram-plugin/gateway/boot-probes.ts +62 -0
  30. package/telegram-plugin/gateway/cron-session.ts +1 -1
  31. package/telegram-plugin/gateway/gateway.ts +133 -12
  32. package/telegram-plugin/gateway/grant-restart.ts +1 -1
  33. package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
  34. package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
  35. package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
  36. package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
  37. package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
  38. package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
  39. package/telegram-plugin/gateway/permission-timeout.ts +70 -0
  40. package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
  41. package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
  42. package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
  43. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
  44. package/telegram-plugin/interrupt-marker.ts +1 -1
  45. package/telegram-plugin/over-ping-safety-net.ts +1 -1
  46. package/telegram-plugin/scoped-approval.ts +1 -1
  47. package/telegram-plugin/secret-detect/vault-error.ts +1 -1
  48. package/telegram-plugin/silence-poke.ts +2 -2
  49. package/telegram-plugin/silent-reply-anchor.ts +1 -1
  50. package/telegram-plugin/slot-banner-driver.ts +1 -1
  51. package/telegram-plugin/startup-reset.ts +1 -1
  52. package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
  53. package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
  54. package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
  55. package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
  56. package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
  57. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
  58. package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
  59. package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
  60. package/telegram-plugin/tests/silence-poke.test.ts +1 -1
  61. package/telegram-plugin/tests/tool-filter.test.ts +87 -0
  62. package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
  63. package/telegram-plugin/turn-flush-safety.ts +1 -1
  64. package/telegram-plugin/uat/assertions.ts +1 -1
  65. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
  66. package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
  67. package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
  68. package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
  69. package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
  70. package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
  71. package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
  72. package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
  73. package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.37",
3
+ "version": "0.15.39",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # Tier-1 cheap cron SESSION launcher — docs/rfcs/cheap-cron-sessions.md §2.2.
2
+ # Tier-1 cheap cron SESSION launcher — reference/rfcs/cheap-cron-sessions.md §2.2.
3
3
  #
4
4
  # A SECOND interactive `claude` (no -p — compliance pillar 3) in the agent
5
5
  # container, dedicated to cheap cron fires. It registers to the SAME gateway
@@ -164,7 +164,7 @@ if [ "$SWITCHROOM_RUNTIME" = "docker" ] && [ -z "$SWITCHROOM_DOCKER_TMUX_INNER"
164
164
  fi
165
165
 
166
166
  {{#if cronSessionEnabled}}
167
- # 4) cheap cron SESSION (Tier 1, docs/rfcs/cheap-cron-sessions.md §2.2).
167
+ # 4) cheap cron SESSION (Tier 1, reference/rfcs/cheap-cron-sessions.md §2.2).
168
168
  # A SECOND interactive claude (no -p) dedicated to context:fresh cron
169
169
  # fires, registering to the gateway as the cron-suffixed bridge. This
170
170
  # block is rendered ONLY for an agent that has a Tier-1 cron entry; for
@@ -28,6 +28,8 @@ You are operating in the **{{topicName}}** {{#if topicEmoji}}{{topicEmoji}} {{/i
28
28
  - Prefer `trash` over `rm` when available (recoverable beats gone forever).
29
29
  - Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
30
30
  - Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
31
+ - **Batch foreseeable approvals; don't drip surprises.** When you can already see that several actions will each need the user's approval, tell them up front which approvals are coming and why. Request independent ones together so they can decide once; for dependent ones (one's input comes from another), say what you're doing first and what approval comes next — a permission card should never arrive out of the blue.
32
+ - **A timed-out approval isn't a denial.** If a request came back denied only because the user was away (a timeout, not an explicit "no"), don't silently abandon it. When they're back, remind them it's still pending and re-offer it if they still want it.
31
33
 
32
34
  ## Execution Bias
33
35
 
@@ -38,7 +38,7 @@ When the user says "add a new agent", "add an agent to my switchroom setup", or
38
38
 
39
39
  ### Anthropic accounts (one OAuth, many agents)
40
40
 
41
- The auth model treats the Anthropic account as the unit of authentication: one OAuth flow per account, then every agent in the fleet inherits the fleet-wide active account. The `switchroom-auth-broker` daemon owns the refresh loop and is the sole writer of every `credentials.json`. See `docs/auth.md` for the operator guide and `reference/share-auth-across-the-fleet.md` for the design.
41
+ The auth model treats the Anthropic account as the unit of authentication: one OAuth flow per account, then every agent in the fleet inherits the fleet-wide active account. The `switchroom-auth-broker` daemon owns the refresh loop and is the sole writer of every `credentials.json`. See `docs/auth.md` for the operator guide and `reference/jobs/share-auth-across-the-fleet.md` for the design.
42
42
 
43
43
  **Bootstrap flow when the user wants to share one Pro/Max subscription across agents:**
44
44
 
@@ -145,7 +145,7 @@ Doubled `!!` (typo / emphasis) reaches you verbatim. Empty `!` gets a "Send your
145
145
 
146
146
  ## "status?" / "still there?" — UX-failure signal
147
147
 
148
- **Trigger:** the user sends a short, low-content message asking whether you're alive — "status?", "still there?", "any update?", "you working?". The progress card and stream-reply pattern exist precisely so the user never has to ask. When you see one of those messages, treat it as a defect signal: something about the in-flight turn made the user feel uncertain. The product expectation (per `reference/know-what-my-agent-is-doing.md`) is that this rate trends to zero.
148
+ **Trigger:** the user sends a short, low-content message asking whether you're alive — "status?", "still there?", "any update?", "you working?". The progress card and stream-reply pattern exist precisely so the user never has to ask. When you see one of those messages, treat it as a defect signal: something about the in-flight turn made the user feel uncertain. The product expectation (per `reference/jobs/know-what-my-agent-is-doing.md`) is that this rate trends to zero.
149
149
 
150
150
  Your response should:
151
151
 
@@ -11,7 +11,7 @@ import {
11
11
  * Answer-lane incremental streaming for long Telegram replies.
12
12
  *
13
13
  * This module implements the "narrative" liveness layer described in
14
- * `reference/know-what-my-agent-is-doing.md`:
14
+ * `reference/jobs/know-what-my-agent-is-doing.md`:
15
15
  *
16
16
  * ambient → 👀 ack reaction
17
17
  * structured → progress card (existing, via stream-reply-handler.ts lane:'progress')
@@ -27,6 +27,7 @@ import {
27
27
  type PtyTailHandle,
28
28
  } from '../pty-tail.js'
29
29
  import { createIpcClient, type IpcClientHandle } from './ipc-client.js'
30
+ import { buildEffectiveToolSchemas, LINEAR_ENV } from './tool-filter.js'
30
31
  import type { InboundMessage, PermissionEvent, StatusEvent } from '../gateway/ipc-protocol.js'
31
32
  import { matchesAllowRule } from '../permission-rule.js'
32
33
 
@@ -544,7 +545,17 @@ const TOOL_SCHEMAS = [
544
545
  },
545
546
  ]
546
547
 
547
- mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }))
548
+ // Tool-surface right-sizing (P4): connection-gate linear_* + per-tool
549
+ // alwaysLoad pins for the hot path. See tool-filter.ts for the rationale.
550
+ // Computed once at startup — SWITCHROOM_TELEGRAM_LINEAR is fixed for the
551
+ // process lifetime (set by the gateway in .mcp.json when Linear is wired).
552
+ const EFFECTIVE_TOOL_SCHEMAS = buildEffectiveToolSchemas(TOOL_SCHEMAS, {
553
+ linearEnabled: process.env[LINEAR_ENV] === '1',
554
+ })
555
+
556
+ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
557
+ tools: EFFECTIVE_TOOL_SCHEMAS,
558
+ }))
548
559
 
549
560
  // ─── MCP CallTool → IPC forward ─────────────────────────────────────────
550
561
 
@@ -691,6 +702,12 @@ function onPermission(msg: PermissionEvent): void {
691
702
  params: {
692
703
  request_id: msg.requestId,
693
704
  behavior: msg.behavior,
705
+ // `message` (deny only) is rendered by claude's channel as
706
+ // "…the user said: ${message}". We use it to tell the model a deny was
707
+ // a TIMEOUT, not a human denial — so it doesn't retry the identical
708
+ // call and re-raise a duplicate card. Omitted → claude's default
709
+ // "Denied" (safe degradation).
710
+ ...(msg.message ? { message: msg.message } : {}),
694
711
  },
695
712
  }).catch((err) => {
696
713
  process.stderr.write(`telegram bridge: failed to deliver permission to Claude: ${err}\n`)
@@ -26,7 +26,10 @@ export function validateGatewayMessage(msg: unknown): msg is GatewayToClient {
26
26
  && (m.behavior === "allow" || m.behavior === "deny")
27
27
  // `rule` is optional (only sent on "🔁 Always allow"); when present
28
28
  // it must be a non-empty string. #1138 wire extension.
29
- && (m.rule === undefined || (typeof m.rule === "string" && m.rule.length > 0));
29
+ && (m.rule === undefined || (typeof m.rule === "string" && m.rule.length > 0))
30
+ // `message` is optional (deny only — the timeout-vs-denial reason);
31
+ // when present it must be a non-empty string.
32
+ && (m.message === undefined || (typeof m.message === "string" && m.message.length > 0));
30
33
  case "status":
31
34
  return typeof m.status === "string";
32
35
  case "tool_call_result":
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Tool-surface right-sizing for the switchroom-telegram MCP server (P4).
3
+ *
4
+ * switchroom-telegram is the primary interface, so its tool schemas are
5
+ * always-loaded on EVERY agent, every turn (~8k tokens). Two reductions,
6
+ * both native to Claude Code tool-search (honored in 2.1.177; the MCP SDK
7
+ * preserves `_meta` on tools/list — @modelcontextprotocol/sdk ToolSchema
8
+ * has `_meta: z.record(...).optional()`):
9
+ *
10
+ * A. Connection gating — linear_* tools are advertised only when the
11
+ * agent has the Linear connection configured (the gateway sets
12
+ * SWITCHROOM_TELEGRAM_LINEAR=1 from channels.telegram.linear_agent.
13
+ * enabled). A non-Linear agent never carries their schema at all.
14
+ *
15
+ * B. Per-tool deferral — the server is no longer pinned wholesale
16
+ * (scaffold sets alwaysLoad:false). We pin ONLY the load-bearing hot
17
+ * tools via `_meta["anthropic/alwaysLoad"]` so they never defer (the
18
+ * reply path must never pay a ToolSearch round-trip before it can
19
+ * answer); the ~14 cold tools defer and load on first use.
20
+ *
21
+ * Pure + side-effect free so it's unit-testable in isolation (bridge.ts
22
+ * has import-time side effects). Both reductions are non-destructive — every
23
+ * tool the agent is entitled to still works; cold ones just load on demand.
24
+ */
25
+
26
+ /** Minimal shape we need from a tool schema. */
27
+ export interface NamedTool {
28
+ name: string
29
+ [k: string]: unknown
30
+ }
31
+
32
+ /**
33
+ * Hot tools pinned loaded — must never defer. The reply path
34
+ * (reply/stream_reply) plus the frequently-used early-turn ops. Everything
35
+ * NOT in this set defers under tool-search.
36
+ */
37
+ export const ALWAYS_LOAD_TOOLS: ReadonlySet<string> = new Set([
38
+ 'reply',
39
+ 'stream_reply',
40
+ 'get_recent_messages',
41
+ 'react',
42
+ 'edit_message',
43
+ 'send_typing',
44
+ 'download_attachment',
45
+ ])
46
+
47
+ /** Linear tools — advertised only when the Linear connection is configured. */
48
+ export const LINEAR_TOOLS: ReadonlySet<string> = new Set([
49
+ 'linear_agent_activity',
50
+ 'linear_create_issue',
51
+ 'linear_agent_setup',
52
+ ])
53
+
54
+ /** The env var the gateway sets (per .mcp.json) when Linear is configured. */
55
+ export const LINEAR_ENV = 'SWITCHROOM_TELEGRAM_LINEAR'
56
+
57
+ export interface ToolFilterOpts {
58
+ /** True when the Linear connection is configured for this agent. */
59
+ linearEnabled: boolean
60
+ }
61
+
62
+ /**
63
+ * Apply connection gating (A) + per-tool deferral pins (B) to a tool list.
64
+ * Returns a NEW array; inputs are not mutated. Order is preserved.
65
+ */
66
+ export function buildEffectiveToolSchemas<T extends NamedTool>(
67
+ schemas: ReadonlyArray<T>,
68
+ opts: ToolFilterOpts,
69
+ ): Array<T & { _meta?: { 'anthropic/alwaysLoad': true } }> {
70
+ return schemas
71
+ .filter((t) => opts.linearEnabled || !LINEAR_TOOLS.has(t.name))
72
+ .map((t) =>
73
+ ALWAYS_LOAD_TOOLS.has(t.name)
74
+ ? { ...t, _meta: { 'anthropic/alwaysLoad': true as const } }
75
+ : t,
76
+ )
77
+ }
@@ -41,7 +41,7 @@
41
41
  * autoRetry transformer; if two topics in the same chat both burst
42
42
  * past the limit, grammY backs off per the Retry-After header — same
43
43
  * worst-case behavior as the old per-chat lock, just hit differently.
44
- * See docs/rfcs/supergroup-mode.md and the
44
+ * See reference/rfcs/supergroup-mode.md and the
45
45
  * `tests/outbound-ordering.test.ts` 429-isolation row.
46
46
  */
47
47
 
@@ -13,7 +13,7 @@
13
13
  * silently failed (stdout to /dev/null), and the user only noticed
14
14
  * hours later when they wondered why their morning brief never came.
15
15
  * Direct violation of the #1 product principle (silent failure is
16
- * the worst case — see reference/know-what-my-agent-is-doing.md).
16
+ * the worst case — see reference/jobs/know-what-my-agent-is-doing.md).
17
17
  *
18
18
  * This module is a pure decision layer. It reads the file, compares
19
19
  * against the last-notified state on disk, and tells the caller