switchroom 0.15.36 → 0.15.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/agent-scheduler/index.js +10 -9
  2. package/dist/auth-broker/index.js +9 -9
  3. package/dist/cli/autoaccept-poll.js +13 -7
  4. package/dist/cli/notion-write-pretool.mjs +9 -9
  5. package/dist/cli/switchroom.js +480 -217
  6. package/dist/cli/ui/index.html +87 -17
  7. package/dist/host-control/main.js +10 -10
  8. package/dist/vault/approvals/kernel-server.js +9 -9
  9. package/dist/vault/broker/server.js +9 -9
  10. package/package.json +1 -1
  11. package/profiles/_base/cron-session.sh.hbs +1 -1
  12. package/profiles/_base/start.sh.hbs +1 -1
  13. package/profiles/_shared/agent-self-service.md.hbs +25 -0
  14. package/skills/switchroom-manage/SKILL.md +1 -1
  15. package/skills/switchroom-runtime/SKILL.md +1 -1
  16. package/telegram-plugin/answer-stream.ts +1 -1
  17. package/telegram-plugin/bridge/bridge.ts +50 -1
  18. package/telegram-plugin/bridge/ipc-client.ts +4 -1
  19. package/telegram-plugin/bridge/tool-filter.ts +77 -0
  20. package/telegram-plugin/chat-lock.ts +1 -1
  21. package/telegram-plugin/credits-watch.ts +1 -1
  22. package/telegram-plugin/dist/bridge/bridge.js +60 -3
  23. package/telegram-plugin/dist/gateway/gateway.js +753 -207
  24. package/telegram-plugin/dist/server.js +64 -4
  25. package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
  26. package/telegram-plugin/gateway/boot-card.ts +5 -1
  27. package/telegram-plugin/gateway/boot-probes.ts +62 -0
  28. package/telegram-plugin/gateway/cron-session.ts +1 -1
  29. package/telegram-plugin/gateway/gateway.ts +254 -15
  30. package/telegram-plugin/gateway/grant-restart.ts +1 -1
  31. package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
  32. package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
  33. package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
  34. package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
  35. package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
  36. package/telegram-plugin/gateway/linear-activity.ts +56 -0
  37. package/telegram-plugin/gateway/linear-auth-watch.ts +102 -0
  38. package/telegram-plugin/gateway/linear-setup.ts +196 -0
  39. package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
  40. package/telegram-plugin/gateway/permission-timeout.ts +70 -0
  41. package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
  42. package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
  43. package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
  44. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
  45. package/telegram-plugin/interrupt-marker.ts +1 -1
  46. package/telegram-plugin/over-ping-safety-net.ts +1 -1
  47. package/telegram-plugin/scoped-approval.ts +1 -1
  48. package/telegram-plugin/secret-detect/vault-error.ts +1 -1
  49. package/telegram-plugin/silence-poke.ts +2 -2
  50. package/telegram-plugin/silent-reply-anchor.ts +1 -1
  51. package/telegram-plugin/slot-banner-driver.ts +1 -1
  52. package/telegram-plugin/startup-reset.ts +1 -1
  53. package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
  54. package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
  55. package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
  56. package/telegram-plugin/tests/linear-agent-activity.test.ts +77 -0
  57. package/telegram-plugin/tests/linear-agent-setup.test.ts +132 -0
  58. package/telegram-plugin/tests/linear-auth-watch.test.ts +79 -0
  59. package/telegram-plugin/tests/linear-create-issue.test.ts +3 -1
  60. package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
  61. package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
  62. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
  63. package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
  64. package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
  65. package/telegram-plugin/tests/silence-poke.test.ts +1 -1
  66. package/telegram-plugin/tests/tool-filter.test.ts +87 -0
  67. package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
  68. package/telegram-plugin/turn-flush-safety.ts +1 -1
  69. package/telegram-plugin/uat/assertions.ts +1 -1
  70. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
  71. package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
  72. package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
  73. package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
  74. package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
  75. package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
  76. package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
  77. package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
  78. package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Unit tests for the switchroom-telegram tool-surface right-sizing (P4):
3
+ * connection gating of linear_* (A) + per-tool alwaysLoad pins for the hot
4
+ * path (B). Pure function — no bridge.ts import (which has side effects).
5
+ */
6
+
7
+ import { describe, it, expect } from 'bun:test'
8
+ import {
9
+ buildEffectiveToolSchemas,
10
+ ALWAYS_LOAD_TOOLS,
11
+ LINEAR_TOOLS,
12
+ type NamedTool,
13
+ } from '../bridge/tool-filter.js'
14
+
15
+ // A representative slice mirroring the real TOOL_SCHEMAS names.
16
+ const SAMPLE: NamedTool[] = [
17
+ { name: 'reply', description: 'r' },
18
+ { name: 'stream_reply', description: 's' },
19
+ { name: 'get_recent_messages', description: 'g' },
20
+ { name: 'react', description: 'k' },
21
+ { name: 'edit_message', description: 'e' },
22
+ { name: 'send_typing', description: 't' },
23
+ { name: 'download_attachment', description: 'd' },
24
+ { name: 'ask_user', description: 'a' }, // cold
25
+ { name: 'send_gif', description: 'gif' }, // cold
26
+ { name: 'vault_request_access', description: 'v' }, // cold
27
+ { name: 'linear_agent_activity', description: 'la' },
28
+ { name: 'linear_create_issue', description: 'lc' },
29
+ { name: 'linear_agent_setup', description: 'ls' },
30
+ ]
31
+
32
+ const names = (tools: NamedTool[]) => tools.map((t) => t.name)
33
+ const metaOf = (tools: Array<NamedTool & { _meta?: unknown }>, n: string) =>
34
+ tools.find((t) => t.name === n)?._meta
35
+
36
+ describe('buildEffectiveToolSchemas — connection gating (A)', () => {
37
+ it('drops all linear_* tools when Linear is NOT enabled', () => {
38
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: false })
39
+ for (const t of LINEAR_TOOLS) expect(names(out)).not.toContain(t)
40
+ // non-linear tools all survive
41
+ expect(names(out)).toContain('reply')
42
+ expect(names(out)).toContain('ask_user')
43
+ expect(out.length).toBe(SAMPLE.length - LINEAR_TOOLS.size)
44
+ })
45
+
46
+ it('keeps linear_* tools when Linear IS enabled', () => {
47
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: true })
48
+ for (const t of LINEAR_TOOLS) expect(names(out)).toContain(t)
49
+ expect(out.length).toBe(SAMPLE.length)
50
+ })
51
+ })
52
+
53
+ describe('buildEffectiveToolSchemas — per-tool deferral pins (B)', () => {
54
+ it('pins exactly the hot tools with _meta anthropic/alwaysLoad', () => {
55
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: true })
56
+ for (const hot of ALWAYS_LOAD_TOOLS) {
57
+ expect(metaOf(out, hot)).toEqual({ 'anthropic/alwaysLoad': true })
58
+ }
59
+ })
60
+
61
+ it('the reply path (reply/stream_reply) is ALWAYS pinned — never defers', () => {
62
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: false })
63
+ expect(metaOf(out, 'reply')).toEqual({ 'anthropic/alwaysLoad': true })
64
+ expect(metaOf(out, 'stream_reply')).toEqual({ 'anthropic/alwaysLoad': true })
65
+ })
66
+
67
+ it('cold tools carry NO _meta (so they defer under tool-search)', () => {
68
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: true })
69
+ for (const cold of ['ask_user', 'send_gif', 'vault_request_access', 'linear_create_issue']) {
70
+ expect(metaOf(out, cold)).toBeUndefined()
71
+ }
72
+ })
73
+ })
74
+
75
+ describe('buildEffectiveToolSchemas — purity', () => {
76
+ it('does not mutate the input array or its objects', () => {
77
+ const input: NamedTool[] = [{ name: 'reply' }, { name: 'send_gif' }]
78
+ const snapshot = JSON.stringify(input)
79
+ buildEffectiveToolSchemas(input, { linearEnabled: true })
80
+ expect(JSON.stringify(input)).toBe(snapshot)
81
+ })
82
+
83
+ it('preserves order', () => {
84
+ const out = buildEffectiveToolSchemas(SAMPLE, { linearEnabled: true })
85
+ expect(names(out)).toEqual(names(SAMPLE))
86
+ })
87
+ })
@@ -237,7 +237,7 @@ describe('decideTurnFlush', () => {
237
237
  // Regression guard for the redundant-follow-up-message fix: this reverts
238
238
  // the #1291 post-reply-tail flush, which posted a duplicate recap on
239
239
  // essentially every turn because the model habitually writes a closing
240
- // summary after its final reply. See reference/conversational-pacing.md
240
+ // summary after its final reply. See reference/rfcs/conversational-pacing.md
241
241
  // — "the framework owns the beat; the model authors the words".
242
242
  describe('reply-called turns never flush trailing terminal text', () => {
243
243
  it('skips even when a long substantive tail follows the reply', () => {
@@ -172,7 +172,7 @@ export interface FlushDecisionInput {
172
172
  * message second-guesses an explicit reply and posts a redundant duplicate
173
173
  * on essentially every turn, because the model habitually writes a closing
174
174
  * summary. The framework owns the *beat*; the model authors the *words*
175
- * and emits them via reply (`reference/conversational-pacing.md`).
175
+ * and emits them via reply (`reference/rfcs/conversational-pacing.md`).
176
176
  *
177
177
  * (This reverts the #1291 post-reply-tail flush. Its intent — catch a
178
178
  * soft-commit reply followed by the real answer in terminal text only —
@@ -395,7 +395,7 @@ export async function waitForCardPhase(
395
395
  * The actual card render uses emoji markers in the header: `✅` for
396
396
  * done, `❌` for errors, `⚙️` while working (foreground), `🌀` for
397
397
  * Background (parent done but fleet still running, see #862 /
398
- * reference/conversational-pacing.md),
398
+ * reference/rfcs/conversational-pacing.md),
399
399
  * and `⏳` during the boot-card window. These markers are stable
400
400
  * enough to key on for UAT — finer parsing (checklist items,
401
401
  * sub-agent row content) is out of scope.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Background sub-agent visibility scenario — closes #709 / #776 / #782 / #788
3
- * (the four-issue family analysed in `reference/sub-agent-visibility-rfc.md`).
3
+ * (the four-issue family analysed in `reference/rfcs/sub-agent-visibility.md`).
4
4
  *
5
5
  * Verifies three acceptance criteria from the RFC in a single run because
6
6
  * they share setup:
@@ -149,7 +149,7 @@ const FUZZ_CASES: readonly FuzzCase[] = [
149
149
  // The conservative regex set in `telegram-plugin/inbound-classifier.ts`
150
150
  // captures 10 standalone "ping" patterns that count toward the
151
151
  // primary lagging KPI `inbound_status_query`. Each fire is a JTBD
152
- // failure (`reference/know-what-my-agent-is-doing.md`), so we
152
+ // failure (`reference/jobs/know-what-my-agent-is-doing.md`), so we
153
153
  // want every variant to (a) reach the agent unchanged, (b)
154
154
  // produce a sensible reply (no crash, no loop, no ghosting).
155
155
  // Tracks cause class CC-7 from
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JTBD scenario — guaranteed fast acknowledgement (human-feel UX epic).
3
3
  *
4
- * Serves: `reference/conversational-pacing.md` and the JTBD
4
+ * Serves: `reference/rfcs/conversational-pacing.md` and the JTBD
5
5
  * "talking to my agent feels like talking to a capable person".
6
6
  *
7
7
  * A person you message answers in a beat — "got it", "on it, checking
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JTBD scenario — short happy path: trivial questions reply FAST.
3
3
  *
4
- * Serves: `reference/know-what-my-agent-is-doing.md` — the short-path
4
+ * Serves: `reference/jobs/know-what-my-agent-is-doing.md` — the short-path
5
5
  * contract: a question with no real work should produce a plain reply
6
6
  * with no ceremony (no soft-commit, no progress chunks) within a tight
7
7
  * budget. Users judge agent speed on THIS path more than any other.
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * ## Targets
14
14
  *
15
- * From `reference/conversational-pacing.md` and the post-v0.12.22
15
+ * From `reference/rfcs/conversational-pacing.md` and the post-v0.12.22
16
16
  * baseline measurements:
17
17
  *
18
18
  * - **TTFO p95 (vision target):** < 30s — the published contract.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JTBD scenario — forwarded burst / split paste coalesces into ONE turn.
3
3
  *
4
- * Serves: `reference/steer-or-queue-mid-flight.md` — the "Forwarded
4
+ * Serves: `reference/jobs/steer-or-queue-mid-flight.md` — the "Forwarded
5
5
  * burst / split paste" UAT prompt. When several messages land in quick
6
6
  * succession from the same sender (a forward of 3-4 messages, or a long
7
7
  * paste Telegram split into chunks), inbound coalescing must merge them
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JTBD scenario — memory survives across restart (the "fleet differentiator").
3
3
  *
4
- * Serves: `reference/remember-across-sessions.md` — the JTBD says:
4
+ * Serves: `reference/jobs/remember-across-sessions.md` — the JTBD says:
5
5
  *
6
6
  * *Outcome:* The agent brings back relevant facts, preferences,
7
7
  * decisions, and open threads from past conversations, in the right
@@ -2,7 +2,7 @@
2
2
  * JTBD scenario — rapid follow-ups (steering vs queued classification).
3
3
  *
4
4
  * Live contract codified in `_shared/telegram-style.md.hbs` and
5
- * `reference/steer-or-queue-mid-flight.md` (default-flip commits
5
+ * `reference/jobs/steer-or-queue-mid-flight.md` (default-flip commits
6
6
  * `4fff90bf` + `597a58af`, 2026-04-17):
7
7
  *
8
8
  * - A mid-turn follow-up with NO prefix is `queued="true"` — new
@@ -2,7 +2,7 @@
2
2
  * JTBD scenario — reflective status reaction (#1713).
3
3
  *
4
4
  * Serves the JTBD "know what my agent is actually doing" (see
5
- * `reference/know-what-my-agent-is-doing.md`). The status reaction on
5
+ * `reference/jobs/know-what-my-agent-is-doing.md`). The status reaction on
6
6
  * the user's inbound is the *primary* ambient liveness signal — the
7
7
  * user reads it as "what is the agent doing right now". When it
8
8
  * collapses straight to 👍 mid-turn, the signal evaporates and the
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JTBD scenario — wake-audit content visibility post-restart.
3
3
  *
4
- * Serves: `reference/restart-and-know-what-im-running.md` — the JTBD:
4
+ * Serves: `reference/jobs/restart-and-know-what-im-running.md` — the JTBD:
5
5
  *
6
6
  * *Outcome:* After any restart, the user is told what config is live.
7
7
  * Model, tools, skills, memory backend, auth state. **No need to ask.**