switchroom 0.19.19 → 0.19.22

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 (53) hide show
  1. package/dist/auth-broker/index.js +53 -0
  2. package/dist/cli/switchroom.js +2444 -1264
  3. package/dist/host-control/main.js +54 -1
  4. package/dist/vault/approvals/kernel-server.js +53 -0
  5. package/dist/vault/broker/server.js +53 -0
  6. package/package.json +4 -2
  7. package/skills/switchroom-release/SKILL.md +103 -20
  8. package/telegram-plugin/card-format.ts +92 -3
  9. package/telegram-plugin/dist/gateway/gateway.js +769 -172
  10. package/telegram-plugin/edit-flood-fuse.ts +477 -0
  11. package/telegram-plugin/format.ts +19 -7
  12. package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
  13. package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
  14. package/telegram-plugin/gateway/gateway.ts +66 -56
  15. package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
  16. package/telegram-plugin/gateway/narrative-lane.ts +49 -3
  17. package/telegram-plugin/gateway/status-pin-api.ts +145 -0
  18. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
  19. package/telegram-plugin/retry-api-call.ts +15 -2
  20. package/telegram-plugin/send-gate.ts +1 -1
  21. package/telegram-plugin/status-no-truncate.ts +64 -1
  22. package/telegram-plugin/status-pin-driver.ts +50 -27
  23. package/telegram-plugin/status-pin.ts +43 -5
  24. package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
  25. package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
  26. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
  27. package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
  28. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  29. package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
  30. package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
  31. package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
  32. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
  33. package/telegram-plugin/tests/status-pin.test.ts +106 -5
  34. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
  35. package/telegram-plugin/tests/tool-activity-summary.test.ts +19 -10
  36. package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
  37. package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
  38. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
  39. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
  40. package/telegram-plugin/tool-activity-summary.ts +85 -13
  41. package/telegram-plugin/worker-activity-feed.ts +5 -1
  42. package/vendor/hindsight-memory/scripts/drain_pending.py +193 -25
  43. package/vendor/hindsight-memory/scripts/lib/pending.py +84 -5
  44. package/vendor/hindsight-memory/scripts/lib/retain_split.py +21 -10
  45. package/vendor/hindsight-memory/scripts/recall.py +74 -5
  46. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +158 -4
  47. package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
  48. package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
  49. package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
  50. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
  51. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
  52. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +19 -11
  53. package/vendor/hindsight-memory/tests/test_drain_pending.py +28 -2
@@ -15,6 +15,11 @@ import {
15
15
  type SessionActivityHeader,
16
16
  } from "../tool-activity-summary.js";
17
17
  import { STATUS_ROLLING_LINES, STATUS_LINE_MAX } from "../status-no-truncate.js";
18
+ import { COLLAPSE_SAFE_SEPARATOR } from "../card-format.js";
19
+
20
+ /** The pinned-card collapse separator (#3666) every status-card line now
21
+ * carries before its hard break. Aliased for readable goldens. */
22
+ const NB = COLLAPSE_SAFE_SEPARATOR;
18
23
 
19
24
  describe("describeToolUse — friendly per-tool rendering (draft-mirror)", () => {
20
25
  it("Bash uses the model-authored description verbatim, never the command", () => {
@@ -95,10 +100,10 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
95
100
  "**→ Reading gateway.ts**",
96
101
  );
97
102
  expect(appendActivityLine(lines, "mcp__hindsight__reflect", { query: "x" })).toBe(
98
- "~~_✓ Reading gateway.ts_~~ \n**→ Searching memory**",
103
+ `~~_✓ Reading gateway.ts_~~${NB} \n**→ Searching memory**`,
99
104
  );
100
105
  expect(appendActivityLine(lines, "Bash", { command: "ls", description: "List workspace" })).toBe(
101
- "~~_✓ Reading gateway.ts_~~ \n~~_✓ Searching memory_~~ \n**→ List workspace**",
106
+ `~~_✓ Reading gateway.ts_~~${NB} \n~~_✓ Searching memory_~~${NB} \n**→ List workspace**`,
102
107
  );
103
108
  });
104
109
 
@@ -121,7 +126,7 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
121
126
  const lines = Array.from({ length: total }, (_, i) => `Action ${i + 1}`);
122
127
  const out = renderActivityFeed(lines)!;
123
128
  const hidden = total - STATUS_ROLLING_LINES;
124
- expect(out.startsWith(`_✓ +${hidden} earlier…_ \n`)).toBe(true);
129
+ expect(out.startsWith(`_✓ +${hidden} earlier…_${NB} \n`)).toBe(true);
125
130
  // Only the last STATUS_ROLLING_LINES actions are shown; older ones collapsed.
126
131
  const firstVisible = total - STATUS_ROLLING_LINES + 1;
127
132
  expect(out).toContain(`~~_✓ Action ${firstVisible}_~~`);
@@ -156,7 +161,7 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
156
161
  const lines = ["Reading a.ts", "Searching memory", "Running a command"];
157
162
  const out = renderActivityFeed(lines, true)!;
158
163
  expect(out).toBe(
159
- "~~_✓ Reading a.ts_~~ \n~~_✓ Searching memory_~~ \n~~_✓ Running a command_~~",
164
+ `~~_✓ Reading a.ts_~~${NB} \n~~_✓ Searching memory_~~${NB} \n~~_✓ Running a command_~~`,
160
165
  );
161
166
  expect(out).not.toContain("→"); // no in-progress arrow anywhere
162
167
  });
@@ -195,7 +200,7 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
195
200
  describe("liveSuffix (heartbeat)", () => {
196
201
  it("appends the suffix to the newest in-progress line only", () => {
197
202
  expect(renderActivityFeed(["Reading a.ts", "Running a command"], false, " · 18s")).toBe(
198
- "~~_✓ Reading a.ts_~~ \n**→ Running a command · 18s**",
203
+ `~~_✓ Reading a.ts_~~${NB} \n**→ Running a command · 18s**`,
199
204
  );
200
205
  });
201
206
  it("single live line gets the suffix", () => {
@@ -207,7 +212,7 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
207
212
  const out = renderActivityFeed(["Reading a.ts", "Running a command"], true, " · 18s")!;
208
213
  expect(out).not.toContain("·");
209
214
  expect(out).not.toContain("→");
210
- expect(out).toBe("~~_✓ Reading a.ts_~~ \n~~_✓ Running a command_~~");
215
+ expect(out).toBe(`~~_✓ Reading a.ts_~~${NB} \n~~_✓ Running a command_~~`);
211
216
  });
212
217
  it("default empty suffix is byte-identical to no suffix", () => {
213
218
  expect(renderActivityFeed(["Reading a.ts"], false, "")).toBe(
@@ -222,7 +227,7 @@ describe("appendActivityLabel — precomputed label feed (tool_label path)", ()
222
227
  const lines: string[] = [];
223
228
  expect(appendActivityLabel(lines, "Searching memory")).toBe("**→ Searching memory**");
224
229
  expect(appendActivityLabel(lines, "List workspace")).toBe(
225
- "~~_✓ Searching memory_~~ \n**→ List workspace**",
230
+ `~~_✓ Searching memory_~~${NB} \n**→ List workspace**`,
226
231
  );
227
232
  // consecutive dup collapses
228
233
  appendActivityLabel(lines, "List workspace");
@@ -417,7 +422,7 @@ describe("renderActivityFeedWithNested — foreground sub-agent nesting (Model A
417
422
  it("stepCount footer appears on final=true with no children (delegates to flat render)", () => {
418
423
  const out = renderActivityFeedWithNested(["Reading a.ts"], [], true, "", 3)!;
419
424
  expect(out).toContain("_✓ 3 steps_");
420
- expect(out).toBe("~~_✓ Reading a.ts_~~ \n_✓ 3 steps_");
425
+ expect(out).toBe(`~~_✓ Reading a.ts_~~${NB} \n_✓ 3 steps_`);
421
426
  });
422
427
 
423
428
  // Liveness-driven feed open (dark-turn fix): a turn that emits no tool_label
@@ -1052,7 +1057,11 @@ describe("status-card body stacks like a reply (#2669 rich-message renderer)", (
1052
1057
  it("multiple step bullets stack on their own lines (no soft-break collapse)", () => {
1053
1058
  const out = renderActivityFeed(["Reading a.ts", "Searching memory", "Running tests"], true)!;
1054
1059
  const lines = out.split(" \n");
1055
- expect(lines).toEqual(["~~_✓ Reading a.ts_~~", "~~_✓ Searching memory_~~", "~~_✓ Running tests_~~"]);
1060
+ expect(lines).toEqual([
1061
+ `~~_✓ Reading a.ts_~~${NB}`,
1062
+ `~~_✓ Searching memory_~~${NB}`,
1063
+ "~~_✓ Running tests_~~",
1064
+ ]);
1056
1065
  // Not a single lone `\n` survives to collapse two bullets.
1057
1066
  expect(out).not.toMatch(/(?<! {2})\n/);
1058
1067
  });
@@ -1076,6 +1085,6 @@ describe("status-card body stacks like a reply (#2669 rich-message renderer)", (
1076
1085
  // prose by escapeStepLine; the inline-markup-survives case is covered by
1077
1086
  // the stackCardLines unit test, which operates on pre-rendered lines.)
1078
1087
  const out = renderActivityFeed(["Reading a.ts", "Running tests"], false)!;
1079
- expect(out).toBe("~~_✓ Reading a.ts_~~ \n**→ Running tests**");
1088
+ expect(out).toBe(`~~_✓ Reading a.ts_~~${NB} \n**→ Running tests**`);
1080
1089
  });
1081
1090
  });
@@ -101,7 +101,12 @@ describe('handleVaultRequestAccessCallback — posture branch', () => {
101
101
  it('passphrase-mode branch unchanged: cache lookup + prompt still present', () => {
102
102
  const approveBlock = sliceAccessApproveBlock()
103
103
  expect(approveBlock).toMatch(/vaultPassphraseCache\.get\(pending\.chat_id\)/)
104
- expect(approveBlock).toMatch(/Reply with your passphrase/i)
104
+ // #3627: the prompt COPY moved into the shared
105
+ // `buildAccessPassphrasePromptText` builder (so the first prompt and the
106
+ // wrong-passphrase re-prompt can't drift). The passphrase-mode branch
107
+ // must still route into it.
108
+ expect(approveBlock).toMatch(/sendAccessPassphrasePrompt\(/)
109
+ expect(gatewaySrc).toMatch(/Reply with your passphrase/i)
105
110
  expect(approveBlock).toMatch(/passphrase-for-access-approve/)
106
111
  // Pinned: the queued-drain path passes the typed passphrase via
107
112
  // the new attestation shape `{ kind: 'passphrase', passphrase }`.