switchroom 0.15.45 → 0.16.5

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 (150) hide show
  1. package/dist/agent-scheduler/index.js +56 -15
  2. package/dist/auth-broker/index.js +383 -97
  3. package/dist/cli/autoaccept-poll.js +4842 -35
  4. package/dist/cli/drive-write-pretool.mjs +7 -4
  5. package/dist/cli/notion-write-pretool.mjs +35 -4
  6. package/dist/cli/self-improve-apply-guard-pretool.mjs +626 -0
  7. package/dist/cli/self-improve-stop.mjs +428 -0
  8. package/dist/cli/switchroom.js +2894 -841
  9. package/dist/host-control/main.js +2685 -207
  10. package/dist/vault/approvals/kernel-server.js +7453 -7413
  11. package/dist/vault/broker/server.js +11428 -11388
  12. package/examples/minimal.yaml +1 -0
  13. package/examples/switchroom.yaml +1 -0
  14. package/package.json +3 -3
  15. package/profiles/_base/start.sh.hbs +97 -1
  16. package/profiles/_shared/execution-discipline.md.hbs +18 -0
  17. package/profiles/default/CLAUDE.md.hbs +0 -19
  18. package/telegram-plugin/.claude-plugin/plugin.json +2 -2
  19. package/telegram-plugin/answer-stream-flag.ts +12 -49
  20. package/telegram-plugin/answer-stream.ts +5 -150
  21. package/telegram-plugin/auth-snapshot-format.ts +280 -48
  22. package/telegram-plugin/auto-fallback-fleet.ts +44 -1
  23. package/telegram-plugin/context-exhaustion.ts +12 -0
  24. package/telegram-plugin/demo-mask.ts +154 -0
  25. package/telegram-plugin/dist/bridge/bridge.js +55 -12
  26. package/telegram-plugin/dist/gateway/gateway.js +2938 -977
  27. package/telegram-plugin/dist/server.js +55 -12
  28. package/telegram-plugin/docs/waiting-ux-spec.md +2 -2
  29. package/telegram-plugin/draft-stream.ts +47 -410
  30. package/telegram-plugin/final-answer-detect.ts +17 -12
  31. package/telegram-plugin/fleet-fallback-resume.ts +131 -0
  32. package/telegram-plugin/format.ts +56 -19
  33. package/telegram-plugin/gateway/auth-add-flow.ts +332 -127
  34. package/telegram-plugin/gateway/auth-broker-client.ts +2 -2
  35. package/telegram-plugin/gateway/auth-command.ts +70 -14
  36. package/telegram-plugin/gateway/clean-shutdown-marker.ts +44 -0
  37. package/telegram-plugin/gateway/config-approval-handler.test.ts +91 -4
  38. package/telegram-plugin/gateway/config-approval-handler.ts +94 -13
  39. package/telegram-plugin/gateway/current-turn-map.ts +188 -0
  40. package/telegram-plugin/gateway/disconnect-flush.ts +3 -1
  41. package/telegram-plugin/gateway/effort-command.ts +8 -3
  42. package/telegram-plugin/gateway/emission-authority.ts +369 -0
  43. package/telegram-plugin/gateway/feed-open-gate.ts +292 -0
  44. package/telegram-plugin/gateway/gateway.ts +1857 -292
  45. package/telegram-plugin/gateway/inject-handler.test.ts +2 -1
  46. package/telegram-plugin/gateway/model-command.ts +115 -4
  47. package/telegram-plugin/gateway/ms365-write-approval.test.ts +4 -4
  48. package/telegram-plugin/gateway/represent-guard.ts +72 -0
  49. package/telegram-plugin/gateway/status-surface-log.test.ts +5 -4
  50. package/telegram-plugin/gateway/status-surface-log.ts +14 -3
  51. package/telegram-plugin/history.ts +33 -11
  52. package/telegram-plugin/hooks/repo-context-pretool.mjs +26 -0
  53. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +5 -0
  54. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +8 -0
  55. package/telegram-plugin/hooks/tool-label-pretool.mjs +39 -15
  56. package/telegram-plugin/issues-card.ts +4 -0
  57. package/telegram-plugin/model-unavailable.ts +124 -0
  58. package/telegram-plugin/narrative-dedup.ts +69 -0
  59. package/telegram-plugin/over-ping-safety-net.ts +70 -4
  60. package/telegram-plugin/package.json +3 -3
  61. package/telegram-plugin/pending-work-progress.ts +12 -0
  62. package/telegram-plugin/permission-rule.ts +32 -5
  63. package/telegram-plugin/permission-title.ts +152 -9
  64. package/telegram-plugin/quota-check.ts +13 -0
  65. package/telegram-plugin/quota-watch.ts +135 -7
  66. package/telegram-plugin/registry/turns-schema.test.ts +24 -0
  67. package/telegram-plugin/registry/turns-schema.ts +9 -0
  68. package/telegram-plugin/runtime-metrics.ts +13 -0
  69. package/telegram-plugin/session-tail.ts +96 -11
  70. package/telegram-plugin/silence-poke.ts +170 -24
  71. package/telegram-plugin/slot-banner-driver.ts +3 -0
  72. package/telegram-plugin/status-no-truncate.ts +44 -0
  73. package/telegram-plugin/status-reactions.ts +20 -3
  74. package/telegram-plugin/stream-controller.ts +4 -23
  75. package/telegram-plugin/stream-reply-handler.ts +6 -24
  76. package/telegram-plugin/streaming-metrics.ts +91 -0
  77. package/telegram-plugin/subagent-watcher.ts +212 -66
  78. package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +47 -0
  79. package/telegram-plugin/tests/answer-stream-dedup.test.ts +9 -26
  80. package/telegram-plugin/tests/answer-stream-flag.test.ts +25 -58
  81. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +41 -51
  82. package/telegram-plugin/tests/answer-stream.test.ts +2 -411
  83. package/telegram-plugin/tests/auth-add-flow.test.ts +488 -253
  84. package/telegram-plugin/tests/auth-command-format2.test.ts +71 -1
  85. package/telegram-plugin/tests/auth-snapshot-format.test.ts +376 -6
  86. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +120 -0
  87. package/telegram-plugin/tests/cross-turn-card-gate.test.ts +424 -0
  88. package/telegram-plugin/tests/demo-mask.test.ts +127 -0
  89. package/telegram-plugin/tests/draft-stream.test.ts +0 -827
  90. package/telegram-plugin/tests/emission-authority-card-drain-gate.test.ts +236 -0
  91. package/telegram-plugin/tests/emission-authority-facade.test.ts +488 -0
  92. package/telegram-plugin/tests/emission-authority-open-gate.test.ts +179 -0
  93. package/telegram-plugin/tests/emission-authority-ping-gate.test.ts +395 -0
  94. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +177 -0
  95. package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +146 -0
  96. package/telegram-plugin/tests/feed-open-gate.test.ts +259 -0
  97. package/telegram-plugin/tests/feed-survival.test.ts +526 -0
  98. package/telegram-plugin/tests/fleet-fallback-resume.test.ts +197 -0
  99. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +117 -0
  100. package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +4 -11
  101. package/telegram-plugin/tests/history.test.ts +60 -0
  102. package/telegram-plugin/tests/model-command.test.ts +134 -0
  103. package/telegram-plugin/tests/model-unavailable.test.ts +118 -0
  104. package/telegram-plugin/tests/narrative-dedup.test.ts +118 -0
  105. package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +285 -0
  106. package/telegram-plugin/tests/over-ping-final-answer-decoupling.test.ts +194 -0
  107. package/telegram-plugin/tests/over-ping-safety-net.test.ts +2 -2
  108. package/telegram-plugin/tests/per-topic-current-turn.test.ts +373 -0
  109. package/telegram-plugin/tests/permission-card-origin-kill-switch.test.ts +42 -0
  110. package/telegram-plugin/tests/permission-rule.test.ts +17 -0
  111. package/telegram-plugin/tests/permission-title.test.ts +206 -17
  112. package/telegram-plugin/tests/quota-watch.test.ts +252 -9
  113. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +6 -1
  114. package/telegram-plugin/tests/repo-context-pretool.test.ts +62 -0
  115. package/telegram-plugin/tests/represent-guard.test.ts +162 -0
  116. package/telegram-plugin/tests/session-tail.test.ts +147 -3
  117. package/telegram-plugin/tests/silence-liveness-wiring.test.ts +18 -0
  118. package/telegram-plugin/tests/status-card-budget-parity.test.ts +72 -0
  119. package/telegram-plugin/tests/status-surface-log.test.ts +146 -0
  120. package/telegram-plugin/tests/subagent-watcher-clip-narrative.test.ts +58 -0
  121. package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +102 -0
  122. package/telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts +225 -0
  123. package/telegram-plugin/tests/subagent-watcher.test.ts +147 -0
  124. package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +597 -0
  125. package/telegram-plugin/tests/telegram-format.test.ts +101 -6
  126. package/telegram-plugin/tests/tool-activity-summary.test.ts +550 -15
  127. package/telegram-plugin/tests/tool-label-pretool.test.ts +73 -0
  128. package/telegram-plugin/tests/tool-label-sidecar.test.ts +44 -0
  129. package/telegram-plugin/tests/tool-labels.test.ts +67 -0
  130. package/telegram-plugin/tests/turn-liveness-floor.test.ts +196 -0
  131. package/telegram-plugin/tests/turn-liveness-invariant.test.ts +340 -0
  132. package/telegram-plugin/tests/welcome-text.test.ts +32 -3
  133. package/telegram-plugin/tests/worker-activity-feed.test.ts +470 -22
  134. package/telegram-plugin/tool-activity-summary.ts +375 -58
  135. package/telegram-plugin/turn-liveness-floor.ts +240 -0
  136. package/telegram-plugin/uat/assertions.ts +115 -0
  137. package/telegram-plugin/uat/driver.ts +68 -0
  138. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +119 -133
  139. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +94 -0
  140. package/telegram-plugin/uat/scenarios/jtbd-cross-turn-card-dm.test.ts +109 -0
  141. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-thinkgap-dm.test.ts +478 -0
  142. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +396 -0
  143. package/telegram-plugin/uat/scenarios/jtbd-liveness-feed-open-dm.test.ts +202 -0
  144. package/telegram-plugin/uat/scenarios/jtbd-reply-is-last-dm.test.ts +202 -0
  145. package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +93 -87
  146. package/telegram-plugin/welcome-text.ts +13 -1
  147. package/telegram-plugin/worker-activity-feed.ts +157 -82
  148. package/telegram-plugin/draft-transport.ts +0 -122
  149. package/telegram-plugin/tests/draft-retirement-wiring.test.ts +0 -82
  150. package/telegram-plugin/tests/draft-transport.test.ts +0 -211
@@ -0,0 +1,154 @@
1
+ /**
2
+ * demo-mask — PII masking for the `demo` suffix on Telegram status
3
+ * commands (`/usage demo`, `/auth demo`, `/status demo`, `/whoami demo`).
4
+ *
5
+ * Purpose: when an operator records a screen-recording / screenshot of a
6
+ * status command, a trailing `demo` token masks the MUST-MASK PII tier so
7
+ * real account emails, the operator's Telegram handle/id, and live vault key
8
+ * names never end up in a published clip. This is per-command and opt-in —
9
+ * the default output is unchanged and still shows the real values.
10
+ *
11
+ * Scope is deliberately narrow (the MUST-MASK tier only):
12
+ * 1. Email addresses (account labels) → maskEmail
13
+ * 2. Telegram username / sender id → maskUsername
14
+ * 3. Vault key names → maskVaultKey
15
+ *
16
+ * Explicitly OUT of scope (NOT masked): agent names, MCP server names,
17
+ * model ids, host paths, fleet topology. Those are not PII and masking
18
+ * them would make a demo recording confusingly unrepresentative.
19
+ *
20
+ * Determinism contract: every function is a pure-ish mapping with a
21
+ * process-lifetime stable cache. The SAME input always maps to the SAME
22
+ * masked output within a process; DISTINCT inputs map to DISTINCT outputs
23
+ * (within the size of each pool — pools are large enough for realistic
24
+ * fleets, and overflow falls back to a stable suffixed form so distinctness
25
+ * still holds). The mapping is intentionally NOT stable across process
26
+ * restarts — it does not need to be, and a per-process map keeps the module
27
+ * dependency-free (no persisted state, no hashing of secrets to disk).
28
+ */
29
+
30
+ // ── stable assignment helper ─────────────────────────────────────────
31
+
32
+ /**
33
+ * Assign a stable masked value for `input` using a per-pool Map. The first
34
+ * time an input is seen it claims the next pool entry (or a deterministic
35
+ * overflow form once the pool is exhausted); subsequent calls return the
36
+ * same value. Distinct inputs never collide.
37
+ */
38
+ function stableAssign(
39
+ input: string,
40
+ cache: Map<string, string>,
41
+ pool: readonly string[],
42
+ overflow: (index: number) => string,
43
+ ): string {
44
+ const existing = cache.get(input);
45
+ if (existing !== undefined) return existing;
46
+ const index = cache.size;
47
+ const value = index < pool.length ? pool[index]! : overflow(index);
48
+ cache.set(input, value);
49
+ return value;
50
+ }
51
+
52
+ // ── emails ───────────────────────────────────────────────────────────
53
+
54
+ /**
55
+ * Fixed pool of realistic fake emails. Named after computing pioneers so a
56
+ * demo clip reads naturally without exposing the operator's real accounts.
57
+ * All on `example.com` (RFC 2606 reserved — guaranteed not a real inbox).
58
+ */
59
+ const EMAIL_POOL: readonly string[] = [
60
+ 'ada@example.com',
61
+ 'grace@example.com',
62
+ 'linus@example.com',
63
+ 'hopper@example.com',
64
+ 'turing@example.com',
65
+ 'lovelace@example.com',
66
+ 'dijkstra@example.com',
67
+ 'knuth@example.com',
68
+ 'ritchie@example.com',
69
+ 'torvalds@example.com',
70
+ ];
71
+
72
+ const emailCache = new Map<string, string>();
73
+
74
+ /**
75
+ * Map an account label to a stable realistic fake.
76
+ *
77
+ * Email-shaped labels map to the email pool. A non-email label (some
78
+ * operators use a plain nickname as an account label) still gets a stable
79
+ * fake email — masking a label that MIGHT carry a name to an obviously-fake
80
+ * address is the safe choice for a recording.
81
+ */
82
+ export function maskEmail(label: string): string {
83
+ return stableAssign(
84
+ label,
85
+ emailCache,
86
+ EMAIL_POOL,
87
+ (i) => `demo${i + 1}@example.com`,
88
+ );
89
+ }
90
+
91
+ // ── telegram username / sender id ────────────────────────────────────
92
+
93
+ /**
94
+ * Fixed pool of fake Telegram handles. First input → `@demo_user`, second
95
+ * distinct input → `@demo_user2`, etc., so a recording shows a believable
96
+ * handle and a second account (if any) stays distinct.
97
+ */
98
+ const USERNAME_POOL: readonly string[] = [
99
+ '@demo_user',
100
+ '@demo_user2',
101
+ '@demo_user3',
102
+ '@demo_user4',
103
+ '@demo_user5',
104
+ ];
105
+
106
+ const usernameCache = new Map<string, string>();
107
+
108
+ /**
109
+ * Map a `@handle` or a numeric sender id to a stable fake handle. The input
110
+ * may be `@realhandle` (when the user has a username) or a bare numeric id
111
+ * (when they don't) — both map into the same pool, so the masked output is
112
+ * always a `@demo_user…` handle regardless of the input shape. This also
113
+ * normalises the id case to a friendlier handle for the recording.
114
+ */
115
+ export function maskUsername(tag: string): string {
116
+ return stableAssign(
117
+ tag,
118
+ usernameCache,
119
+ USERNAME_POOL,
120
+ (i) => `@demo_user${i + 1}`,
121
+ );
122
+ }
123
+
124
+ // ── vault key names ──────────────────────────────────────────────────
125
+
126
+ const vaultKeyCache = new Map<string, string>();
127
+
128
+ /**
129
+ * Map a real vault key name (e.g. `coolify/api-token`, `github/token`) to a
130
+ * stable masked form (`demo/secret-1`, `demo/secret-2`, …). Keeps the
131
+ * `namespace/key` shape so a recording still reads like a real vault listing
132
+ * without revealing which services the operator has wired.
133
+ */
134
+ export function maskVaultKey(name: string): string {
135
+ return stableAssign(
136
+ name,
137
+ vaultKeyCache,
138
+ [],
139
+ (i) => `demo/secret-${i + 1}`,
140
+ );
141
+ }
142
+
143
+ // ── test-only reset ──────────────────────────────────────────────────
144
+
145
+ /**
146
+ * Clear all stable-assignment caches. Test-only — lets a test assert the
147
+ * pool-ordering from a known-empty state without cross-test contamination.
148
+ * Not used by production code paths.
149
+ */
150
+ export function __resetDemoMaskCachesForTest(): void {
151
+ emailCache.clear();
152
+ usernameCache.clear();
153
+ vaultKeyCache.clear();
154
+ }
@@ -23199,6 +23199,23 @@ function extractToolResultErrorText(content) {
23199
23199
  }
23200
23200
  return "";
23201
23201
  }
23202
+ function projectAssistantTextBlocks(content, make) {
23203
+ const out = new Map;
23204
+ let lastToolUseIdx = -1;
23205
+ content.forEach((c, i) => {
23206
+ if (c.type === "tool_use")
23207
+ lastToolUseIdx = i;
23208
+ });
23209
+ content.forEach((c, i) => {
23210
+ if (c.type !== "text")
23211
+ return;
23212
+ const text = c.text ?? "";
23213
+ if (text.trim().length === 0)
23214
+ return;
23215
+ out.set(i, make(text, i, i > lastToolUseIdx));
23216
+ });
23217
+ return out;
23218
+ }
23202
23219
  function projectTranscriptLine(line) {
23203
23220
  if (line.length > MAX_JSONL_LINE_BYTES)
23204
23221
  return [];
@@ -23229,7 +23246,8 @@ function projectTranscriptLine(line) {
23229
23246
  if (!Array.isArray(content))
23230
23247
  return [];
23231
23248
  const events = [];
23232
- for (const c of content) {
23249
+ const textEvents = projectAssistantTextBlocks(content, (text, blockIndex, lastInMessage) => ({ kind: "text", text, blockIndex, lastInMessage }));
23250
+ content.forEach((c, i) => {
23233
23251
  const ct = c.type;
23234
23252
  if (ct === "thinking") {
23235
23253
  events.push({ kind: "thinking" });
@@ -23242,10 +23260,11 @@ function projectTranscriptLine(line) {
23242
23260
  input: input && typeof input === "object" ? input : undefined
23243
23261
  });
23244
23262
  } else if (ct === "text") {
23245
- const text = c.text ?? "";
23246
- events.push({ kind: "text", text });
23263
+ const ev = textEvents.get(i);
23264
+ if (ev != null)
23265
+ events.push(ev);
23247
23266
  }
23248
- }
23267
+ });
23249
23268
  return events;
23250
23269
  }
23251
23270
  if (type === "user") {
@@ -23329,7 +23348,14 @@ function projectSubagentLine(line, agentId, state) {
23329
23348
  if (!Array.isArray(content))
23330
23349
  return [];
23331
23350
  const events = [];
23332
- for (const c of content) {
23351
+ const textEvents = projectAssistantTextBlocks(content, (text, blockIndex, lastInMessage) => ({
23352
+ kind: "sub_agent_text",
23353
+ agentId,
23354
+ text,
23355
+ blockIndex,
23356
+ lastInMessage
23357
+ }));
23358
+ content.forEach((c, i) => {
23333
23359
  const ct = c.type;
23334
23360
  if (ct === "tool_use") {
23335
23361
  const name = c.name ?? "";
@@ -23345,10 +23371,11 @@ function projectSubagentLine(line, agentId, state) {
23345
23371
  });
23346
23372
  }
23347
23373
  } else if (ct === "text") {
23348
- const text = c.text ?? "";
23349
- events.push({ kind: "sub_agent_text", agentId, text });
23374
+ const ev = textEvents.get(i);
23375
+ if (ev != null)
23376
+ events.push(ev);
23350
23377
  }
23351
- }
23378
+ });
23352
23379
  const stopReason = message?.stop_reason;
23353
23380
  if (stopReason === "end_turn") {
23354
23381
  events.push({ kind: "sub_agent_turn_end", agentId });
@@ -24490,10 +24517,26 @@ var BROAD_ONLY_TOOLS = new Set([
24490
24517
  function resolveSkillName(input) {
24491
24518
  return readString(input, "skill") ?? readString(input, "skill_name") ?? readString(input, "skillName") ?? readString(input, "name") ?? skillBasenameFromPath(input);
24492
24519
  }
24493
- function filePathFrom(input) {
24494
- if (!input)
24520
+ function filePathFrom(input, rawPreview) {
24521
+ if (input) {
24522
+ const p = readString(input, "file_path") ?? readString(input, "notebook_path");
24523
+ if (p)
24524
+ return p;
24525
+ }
24526
+ if (rawPreview)
24527
+ return extractFilePathFromRaw(rawPreview);
24528
+ return null;
24529
+ }
24530
+ function extractFilePathFromRaw(raw) {
24531
+ const m = /"(?:file_path|notebook_path)"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(raw);
24532
+ if (!m)
24495
24533
  return null;
24496
- return readString(input, "file_path") ?? readString(input, "notebook_path");
24534
+ try {
24535
+ const value = JSON.parse(`"${m[1]}"`);
24536
+ return typeof value === "string" && value.length > 0 ? value : null;
24537
+ } catch {
24538
+ return null;
24539
+ }
24497
24540
  }
24498
24541
  function bashFirstToken(command) {
24499
24542
  const m = /^\s*([^\s|&;<>()`$]+)/.exec(command);
@@ -24558,7 +24601,7 @@ function matchesAllowRule(rule, toolName, inputPreview) {
24558
24601
  return bashFirstToken(cmd) === m[1];
24559
24602
  }
24560
24603
  if (FILE_TOOLS.has(ruleTool)) {
24561
- return filePathFrom(input) === arg;
24604
+ return filePathFrom(input, inputPreview) === arg;
24562
24605
  }
24563
24606
  return false;
24564
24607
  }