switchroom 0.18.11 → 0.18.13

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 +37 -5
  2. package/dist/auth-broker/index.js +116 -78
  3. package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
  4. package/dist/cli/ms-365-write-pretool.mjs +31 -8
  5. package/dist/cli/notion-write-pretool.mjs +38 -6
  6. package/dist/cli/skill-validate-pretool.mjs +144 -2847
  7. package/dist/cli/switchroom.js +3529 -4543
  8. package/dist/cli/ui/index.html +163 -17
  9. package/dist/host-control/main.js +605 -2847
  10. package/dist/vault/approvals/kernel-server.js +120 -13
  11. package/dist/vault/broker/server.js +259 -157
  12. package/package.json +3 -4
  13. package/profiles/_base/start.sh.hbs +65 -0
  14. package/profiles/_shared/vault-protocol.md.hbs +3 -1
  15. package/profiles/coding/CLAUDE.md.hbs +1 -1
  16. package/profiles/default/CLAUDE.md.hbs +2 -2
  17. package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
  18. package/profiles/health-coach/CLAUDE.md.hbs +1 -1
  19. package/telegram-plugin/bridge/bridge.ts +37 -0
  20. package/telegram-plugin/bridge/inbound-dedup.ts +101 -0
  21. package/telegram-plugin/dist/bridge/bridge.js +122 -4
  22. package/telegram-plugin/dist/gateway/gateway.js +4213 -3288
  23. package/telegram-plugin/dist/server.js +139 -5
  24. package/telegram-plugin/flood-circuit-breaker.ts +493 -21
  25. package/telegram-plugin/format.ts +19 -0
  26. package/telegram-plugin/gateway/approval-hold.ts +602 -0
  27. package/telegram-plugin/gateway/auth-command.ts +92 -2
  28. package/telegram-plugin/gateway/auth-loopback-relay.ts +670 -0
  29. package/telegram-plugin/gateway/boot-card.ts +12 -5
  30. package/telegram-plugin/gateway/callback-query-handlers.ts +88 -1
  31. package/telegram-plugin/gateway/config-approval-handler.ts +6 -1
  32. package/telegram-plugin/gateway/disconnect-flush.ts +19 -0
  33. package/telegram-plugin/gateway/dm-pin-sweep.test.ts +251 -0
  34. package/telegram-plugin/gateway/dm-pin-sweep.ts +178 -0
  35. package/telegram-plugin/gateway/gateway.ts +1695 -230
  36. package/telegram-plugin/gateway/hostd-dispatch.ts +23 -0
  37. package/telegram-plugin/gateway/idle-clear.ts +90 -6
  38. package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +26 -5
  39. package/telegram-plugin/gateway/inject-handler.ts +8 -0
  40. package/telegram-plugin/gateway/ipc-protocol.ts +46 -3
  41. package/telegram-plugin/gateway/ipc-server.ts +43 -0
  42. package/telegram-plugin/gateway/mental-model-propose-resolve.ts +145 -37
  43. package/telegram-plugin/gateway/model-command.ts +9 -3
  44. package/telegram-plugin/gateway/pending-session-command.ts +13 -1
  45. package/telegram-plugin/gateway/permission-ttl-sweep.ts +66 -0
  46. package/telegram-plugin/gateway/pre-approval-check.ts +74 -0
  47. package/telegram-plugin/gateway/queued-card-store.ts +217 -0
  48. package/telegram-plugin/gateway/session-model-file.ts +26 -1
  49. package/telegram-plugin/gateway/turn-end-gate-backstop.ts +59 -0
  50. package/telegram-plugin/gateway/turn-end-gate.ts +95 -0
  51. package/telegram-plugin/gateway/turn-typing-loop.ts +10 -2
  52. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +13 -0
  53. package/telegram-plugin/history.ts +51 -0
  54. package/telegram-plugin/hooks/dispatch-claim-scan.mjs +259 -0
  55. package/telegram-plugin/hooks/dispatch-claim-stop.mjs +129 -0
  56. package/telegram-plugin/hooks/hooks.json +9 -0
  57. package/telegram-plugin/inline-keyboard-callbacks.ts +303 -2
  58. package/telegram-plugin/model-unavailable.ts +41 -11
  59. package/telegram-plugin/operator-events.ts +23 -0
  60. package/telegram-plugin/outbound-field-redact.ts +69 -0
  61. package/telegram-plugin/package.json +0 -1
  62. package/telegram-plugin/permission-rule.ts +1 -0
  63. package/telegram-plugin/permission-title.ts +1 -0
  64. package/telegram-plugin/render/render.ts +32 -14
  65. package/telegram-plugin/retry-api-call.ts +212 -2
  66. package/telegram-plugin/scoped-approval.ts +11 -2
  67. package/telegram-plugin/secret-detect/chunker.ts +18 -4
  68. package/telegram-plugin/secret-detect/index.ts +12 -56
  69. package/telegram-plugin/send-gate-degraded.test.ts +574 -0
  70. package/telegram-plugin/send-gate-observability.test.ts +470 -0
  71. package/telegram-plugin/send-gate-observability.ts +355 -0
  72. package/telegram-plugin/send-gate.test.ts +717 -0
  73. package/telegram-plugin/send-gate.ts +1056 -0
  74. package/telegram-plugin/session-tail.ts +82 -7
  75. package/telegram-plugin/shared/bot-runtime.ts +17 -5
  76. package/telegram-plugin/shared/gw-trace-gate.ts +105 -0
  77. package/telegram-plugin/status-pin-driver.ts +52 -7
  78. package/telegram-plugin/status-pin.ts +81 -0
  79. package/telegram-plugin/subagent-watcher.ts +173 -18
  80. package/telegram-plugin/tests/activity-card-wiring.test.ts +18 -5
  81. package/telegram-plugin/tests/approval-hold-harness.ts +425 -0
  82. package/telegram-plugin/tests/approval-hold-outcome.test.ts +327 -0
  83. package/telegram-plugin/tests/approval-hold-record.test.ts +531 -0
  84. package/telegram-plugin/tests/approval-hold-redeliver.test.ts +602 -0
  85. package/telegram-plugin/tests/auth-loopback-relay.test.ts +533 -0
  86. package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +53 -7
  87. package/telegram-plugin/tests/busy-key-reaper.test.ts +1 -0
  88. package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
  89. package/telegram-plugin/tests/dispatch-claim-scan.test.ts +250 -0
  90. package/telegram-plugin/tests/flood-breaker-blindness.test.ts +213 -0
  91. package/telegram-plugin/tests/flood-windows-persistence.test.ts +224 -0
  92. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
  93. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +29 -1
  94. package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +66 -0
  95. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
  96. package/telegram-plugin/tests/gw-trace-gate.test.ts +105 -0
  97. package/telegram-plugin/tests/history.test.ts +115 -0
  98. package/telegram-plugin/tests/idle-clear.test.ts +233 -3
  99. package/telegram-plugin/tests/inbound-dedup.test.ts +93 -0
  100. package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
  101. package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +448 -0
  102. package/telegram-plugin/tests/ipc-server-check-pre-approved.test.ts +194 -0
  103. package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +123 -0
  104. package/telegram-plugin/tests/missed-approvals-wiring.test.ts +1 -1
  105. package/telegram-plugin/tests/model-command.test.ts +14 -0
  106. package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
  107. package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
  108. package/telegram-plugin/tests/pending-session-command.test.ts +21 -0
  109. package/telegram-plugin/tests/permission-card-routing.test.ts +30 -5
  110. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +8 -7
  111. package/telegram-plugin/tests/permission-rearm-wiring.test.ts +1 -1
  112. package/telegram-plugin/tests/pre-approval-check.test.ts +148 -0
  113. package/telegram-plugin/tests/queued-card-store.test.ts +232 -0
  114. package/telegram-plugin/tests/reaction-flush-turn-gated.test.ts +100 -0
  115. package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
  116. package/telegram-plugin/tests/render/render.test.ts +88 -0
  117. package/telegram-plugin/tests/retry-api-call.test.ts +398 -0
  118. package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
  119. package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
  120. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
  121. package/telegram-plugin/tests/session-model-file.test.ts +50 -0
  122. package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
  123. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +35 -14
  124. package/telegram-plugin/tests/status-pin.test.ts +275 -1
  125. package/telegram-plugin/tests/subagent-watcher-deferral-log-ratelimit.test.ts +316 -0
  126. package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
  127. package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +92 -0
  128. package/telegram-plugin/tests/turn-end-gate.test.ts +137 -0
  129. package/telegram-plugin/tests/typing-emitter.test.ts +586 -0
  130. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +20 -0
  131. package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
  132. package/telegram-plugin/typing-emitter.ts +224 -0
  133. package/telegram-plugin/uat/scenarios/jtbd-feel-like-a-colleague-dm.test.ts +136 -0
  134. package/telegram-plugin/welcome-text.ts +42 -0
  135. package/telegram-plugin/worktree-watch-cwds.ts +194 -5
  136. package/vendor/hindsight-memory/scripts/drain_pending.py +22 -6
  137. package/vendor/hindsight-memory/scripts/lib/client.py +12 -5
  138. package/vendor/hindsight-memory/scripts/lib/directives.py +38 -3
  139. package/vendor/hindsight-memory/scripts/lib/pending.py +36 -9
  140. package/vendor/hindsight-memory/scripts/session_end.py +14 -3
  141. package/vendor/hindsight-memory/scripts/session_start.py +21 -0
  142. package/vendor/hindsight-memory/scripts/tests/test_directives.py +38 -0
  143. package/vendor/hindsight-memory/tests/test_drain_pending.py +68 -0
  144. package/vendor/hindsight-memory/tests/test_pending.py +44 -0
  145. package/vendor/hindsight-memory/tests/test_session_end_pending.py +38 -0
  146. package/vendor/hindsight-memory/tests/test_session_start_drain.py +155 -0
  147. package/telegram-plugin/channel-envelope-safety.test.ts +0 -56
  148. package/telegram-plugin/channel-envelope-safety.ts +0 -56
  149. package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
  150. package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
@@ -0,0 +1,137 @@
1
+ // #1667 — gateway-level regression test for the #1664 turn_end re-prompt gate.
2
+ //
3
+ // This drives the REAL decision core the gateway's `case 'turn_end':` block
4
+ // delegates to (`decideTurnEndGate` in `gateway/turn-end-gate.ts`), plus the
5
+ // REAL `decideTurnFlush` that feeds it — not a re-implementation. A future
6
+ // change to the gateway's gate ordering or the `finalAnswerDelivered` swap is
7
+ // caught here, closing the coverage gap the issue calls out
8
+ // (`silent-end.test.ts`'s `simulateTurnEnd` helper re-implements the decision
9
+ // rather than exercising gateway code).
10
+ //
11
+ // This test is a micro-step of the #2996 gateway decomposition: the gate's
12
+ // pure ordering is now importable and pinned, whereas `gateway.ts` itself is
13
+ // not importable in tests (boot side effects at module load — confirmed during
14
+ // the #2996 work, PR #3010).
15
+ //
16
+ // vitest runner (no bun natives): both `turn-end-gate.ts` and
17
+ // `turn-flush-safety.ts` are pure ES modules.
18
+ import { describe, it, expect } from 'vitest'
19
+ import { decideTurnEndGate } from '../gateway/turn-end-gate.js'
20
+ import { decideTurnFlush, type FlushDecision } from '../turn-flush-safety.js'
21
+
22
+ // Mirror the gateway's own upstream computation: turn-flush is only consulted
23
+ // when the answer-stream did NOT already materialise the answer. When it did,
24
+ // the gateway substitutes a synthetic `{kind:'skip', reason:'reply-called'}`.
25
+ const STREAM_MATERIALIZED: FlushDecision = { kind: 'skip', reason: 'reply-called' }
26
+
27
+ // Real `decideTurnFlush` call, matching the gateway's inputs at turn_end.
28
+ function realFlush(opts: {
29
+ replyCalled: boolean
30
+ capturedText: string[]
31
+ chatId?: string | null
32
+ }): FlushDecision {
33
+ return decideTurnFlush({
34
+ chatId: opts.chatId === undefined ? 'c:1' : opts.chatId,
35
+ replyCalled: opts.replyCalled,
36
+ capturedText: opts.capturedText,
37
+ flushEnabled: true,
38
+ })
39
+ }
40
+
41
+ describe('#1667 — turn_end gate (decideTurnEndGate), driving real gateway code', () => {
42
+ // (a) Interim-ack-only turn: reply landed (interim ack) but no final answer
43
+ // delivered, and no substantive trailing prose to flush → re-prompt.
44
+ it('interim-ack-only turn (outbound sent, finalAnswerDelivered=false) → reprompt', () => {
45
+ // reply tool called, so decideTurnFlush skips with reason 'reply-called';
46
+ // the real answer went out as an ephemeral draft, never finalized, so
47
+ // finalAnswerDelivered stayed false.
48
+ const flushDecision = realFlush({ replyCalled: true, capturedText: [] })
49
+ expect(flushDecision).toEqual({ kind: 'skip', reason: 'reply-called' })
50
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: false })
51
+ expect(decision).toBe('reprompt')
52
+ })
53
+
54
+ // (b) Final-answer-reply turn: reply landed AND was the genuine final answer
55
+ // → finalAnswerDelivered true → no re-prompt.
56
+ it('final-answer-reply turn (finalAnswerDelivered=true) → none (no reprompt)', () => {
57
+ const flushDecision = realFlush({ replyCalled: true, capturedText: [] })
58
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: true })
59
+ expect(decision).toBe('none')
60
+ })
61
+
62
+ // (b') Answer-stream materialised the answer: gateway substitutes the
63
+ // synthetic skip AND sets finalAnswerDelivered=true → none.
64
+ it('answer-stream materialized as answer (finalAnswerDelivered=true) → none', () => {
65
+ const decision = decideTurnEndGate({
66
+ flushDecision: STREAM_MATERIALIZED,
67
+ finalAnswerDelivered: true,
68
+ })
69
+ expect(decision).toBe('none')
70
+ })
71
+
72
+ // (c) Zero-outbound turn: no reply, no captured text → decideTurnFlush skips
73
+ // with 'empty-text' (NOT flush, NOT silent-marker), and
74
+ // finalAnswerDelivered is false → still re-prompts (the original #1122
75
+ // case, now the subset of "no final answer delivered").
76
+ it('zero-outbound turn (no reply, empty text) → reprompt', () => {
77
+ const flushDecision = realFlush({ replyCalled: false, capturedText: [] })
78
+ expect(flushDecision).toEqual({ kind: 'skip', reason: 'empty-text' })
79
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: false })
80
+ expect(decision).toBe('reprompt')
81
+ })
82
+
83
+ // (d) Ordering precedence #1 — silent-marker beats the re-prompt gate. A turn
84
+ // whose only output is a NO_REPLY sentinel is legitimately silent and
85
+ // must NOT re-prompt, even though finalAnswerDelivered is false.
86
+ it('silent-marker (NO_REPLY) takes precedence over the reprompt gate', () => {
87
+ const flushDecision = realFlush({ replyCalled: false, capturedText: ['NO_REPLY'] })
88
+ expect(flushDecision).toEqual({ kind: 'skip', reason: 'silent-marker' })
89
+ // finalAnswerDelivered=false would trip 'reprompt' at the tail, but the
90
+ // silent-marker branch returns first.
91
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: false })
92
+ expect(decision).toBe('silent_end')
93
+ })
94
+
95
+ it('silent-marker (HEARTBEAT_OK) → silent_end (no reprompt)', () => {
96
+ const flushDecision = realFlush({ replyCalled: false, capturedText: ['HEARTBEAT_OK'] })
97
+ expect(flushDecision).toEqual({ kind: 'skip', reason: 'silent-marker' })
98
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: false })
99
+ expect(decision).toBe('silent_end')
100
+ })
101
+
102
+ // (d) Ordering precedence #2 — turn-flush beats the re-prompt gate. A turn
103
+ // that skipped the reply tool but left substantive terminal prose is
104
+ // delivered via flush; the gate returns 'flush' even though
105
+ // finalAnswerDelivered is still false at that point (the gateway sets it
106
+ // true inside the flush branch afterwards).
107
+ it('turn-flush (substantive terminal prose, no reply) takes precedence over reprompt', () => {
108
+ const flushDecision = realFlush({
109
+ replyCalled: false,
110
+ capturedText: ['Done — all three services are green.'],
111
+ })
112
+ expect(flushDecision.kind).toBe('flush')
113
+ const decision = decideTurnEndGate({ flushDecision, finalAnswerDelivered: false })
114
+ expect(decision).toBe('flush')
115
+ })
116
+
117
+ // Full documented ordering, asserted as a table so a re-order regresses
118
+ // loudly: silent-marker > flush > (finalAnswerDelivered===false ? reprompt : none).
119
+ it('honors the full documented precedence order', () => {
120
+ const silent: FlushDecision = { kind: 'skip', reason: 'silent-marker' }
121
+ const flush: FlushDecision = { kind: 'flush', text: 'the answer' }
122
+ const replyCalled: FlushDecision = { kind: 'skip', reason: 'reply-called' }
123
+ const emptyText: FlushDecision = { kind: 'skip', reason: 'empty-text' }
124
+
125
+ // silent-marker wins regardless of finalAnswerDelivered.
126
+ expect(decideTurnEndGate({ flushDecision: silent, finalAnswerDelivered: false })).toBe('silent_end')
127
+ expect(decideTurnEndGate({ flushDecision: silent, finalAnswerDelivered: true })).toBe('silent_end')
128
+ // flush wins regardless of finalAnswerDelivered.
129
+ expect(decideTurnEndGate({ flushDecision: flush, finalAnswerDelivered: false })).toBe('flush')
130
+ expect(decideTurnEndGate({ flushDecision: flush, finalAnswerDelivered: true })).toBe('flush')
131
+ // reply-called tail: gated purely on finalAnswerDelivered.
132
+ expect(decideTurnEndGate({ flushDecision: replyCalled, finalAnswerDelivered: false })).toBe('reprompt')
133
+ expect(decideTurnEndGate({ flushDecision: replyCalled, finalAnswerDelivered: true })).toBe('none')
134
+ expect(decideTurnEndGate({ flushDecision: emptyText, finalAnswerDelivered: false })).toBe('reprompt')
135
+ expect(decideTurnEndGate({ flushDecision: emptyText, finalAnswerDelivered: true })).toBe('none')
136
+ })
137
+ })