switchroom 0.18.7 → 0.18.9

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 (85) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/switchroom.js +905 -758
  3. package/dist/host-control/main.js +1 -1
  4. package/package.json +1 -1
  5. package/profiles/_base/start.sh.hbs +111 -34
  6. package/skills/switchroom-runtime/SKILL.md +2 -0
  7. package/telegram-plugin/dist/gateway/gateway.js +46273 -44324
  8. package/telegram-plugin/flood-circuit-breaker.ts +123 -0
  9. package/telegram-plugin/gateway/activity-card-store.ts +63 -18
  10. package/telegram-plugin/gateway/approval-card-stores.ts +99 -0
  11. package/telegram-plugin/gateway/boot-card.ts +27 -0
  12. package/telegram-plugin/gateway/bot-commands-ops-info.ts +194 -0
  13. package/telegram-plugin/gateway/busy-ack.ts +106 -0
  14. package/telegram-plugin/gateway/callback-query-handlers.ts +2660 -0
  15. package/telegram-plugin/gateway/gateway.ts +1169 -3043
  16. package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +181 -23
  17. package/telegram-plugin/gateway/inbound-delivery-machine.ts +8 -0
  18. package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
  19. package/telegram-plugin/gateway/model-command.ts +23 -11
  20. package/telegram-plugin/gateway/outbound-send-path.ts +375 -0
  21. package/telegram-plugin/gateway/pending-state-stores.ts +106 -0
  22. package/telegram-plugin/gateway/register-bot-commands.ts +30 -0
  23. package/telegram-plugin/gateway/session-model-file.ts +198 -0
  24. package/telegram-plugin/gateway/status-pin-store.ts +82 -22
  25. package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
  26. package/telegram-plugin/hooks/hooks.json +10 -10
  27. package/telegram-plugin/hooks/run-hook.sh +84 -0
  28. package/telegram-plugin/model-unavailable.ts +26 -0
  29. package/telegram-plugin/pty-partial-handler.ts +39 -0
  30. package/telegram-plugin/render/rich-render.ts +79 -1
  31. package/telegram-plugin/retry-api-call.ts +62 -0
  32. package/telegram-plugin/shared/bot-runtime.ts +8 -1
  33. package/telegram-plugin/silence-poke.ts +14 -0
  34. package/telegram-plugin/stream-controller.ts +156 -38
  35. package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
  36. package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
  37. package/telegram-plugin/tests/approval-card-stores.test.ts +124 -0
  38. package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
  39. package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
  40. package/telegram-plugin/tests/busy-ack.test.ts +121 -0
  41. package/telegram-plugin/tests/callback-query-handlers.test.ts +701 -0
  42. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +11 -4
  43. package/telegram-plugin/tests/fixtures/cutover-killswitch-probe.ts +75 -0
  44. package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
  45. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +5 -1
  46. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +177 -25
  47. package/telegram-plugin/tests/inbound-delivery-cutover-flip.test.ts +418 -0
  48. package/telegram-plugin/tests/inbound-delivery-dispatch-equivalence.test.ts +348 -0
  49. package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +141 -52
  50. package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
  51. package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -1
  52. package/telegram-plugin/tests/model-command.test.ts +2 -2
  53. package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
  54. package/telegram-plugin/tests/outbound-send-chunks.test.ts +304 -0
  55. package/telegram-plugin/tests/outbound-send-path.test.ts +222 -0
  56. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +34 -15
  57. package/telegram-plugin/tests/pending-state-stores.test.ts +235 -0
  58. package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
  59. package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
  60. package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
  61. package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
  62. package/telegram-plugin/tests/session-model-file.test.ts +132 -0
  63. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
  64. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  65. package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
  66. package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
  67. package/telegram-plugin/tests/turn-flush-safety.test.ts +18 -4
  68. package/telegram-plugin/tests/vault-approval-posture.test.ts +15 -7
  69. package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +8 -4
  70. package/telegram-plugin/tests/vault-grant-union.test.ts +8 -4
  71. package/telegram-plugin/tests/vault-grant-wizard.test.ts +8 -1
  72. package/telegram-plugin/tests/vault-grants-revoke.test.ts +8 -1
  73. package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +8 -4
  74. package/telegram-plugin/tests/vault-request-access-tool.test.ts +8 -4
  75. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +8 -4
  76. package/telegram-plugin/tests/voice-send.test.ts +308 -0
  77. package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
  78. package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
  79. package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
  80. package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
  81. package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
  82. package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
  83. package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
  84. package/telegram-plugin/voice-ondemand.ts +25 -1
  85. package/telegram-plugin/voice-send.ts +154 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * #2995 — mid-flight busy ack: pure decision module.
3
+ *
4
+ * Pins the deterministic policy (`shouldPostBusyAck`) and the rendered
5
+ * card text (`formatBusyAckText`) — the model-free ack a buffered/steered
6
+ * mid-turn inbound gets while the running turn sits inside one long tool
7
+ * step. Behavioral assertions on the RENDERED text (wording contract:
8
+ * "Queued" for the buffered path per the steer-or-queue classification-
9
+ * visibility invariant; never "Queued" for a steer).
10
+ */
11
+
12
+ import { describe, it, expect } from 'vitest'
13
+ import {
14
+ BUSY_ACK_STEP_AGE_THRESHOLD_MS,
15
+ shouldPostBusyAck,
16
+ formatBusyAckText,
17
+ } from '../gateway/busy-ack.js'
18
+
19
+ const base = {
20
+ gateDecision: 'buffer-until-idle' as const,
21
+ midToolCall: true,
22
+ stepAgeMs: BUSY_ACK_STEP_AGE_THRESHOLD_MS + 1,
23
+ alreadyAcked: false,
24
+ }
25
+
26
+ describe('shouldPostBusyAck', () => {
27
+ it('fires for a buffered inbound behind an old tool step', () => {
28
+ expect(shouldPostBusyAck(base)).toBe(true)
29
+ })
30
+
31
+ it('fires for a steer behind an old tool step', () => {
32
+ expect(shouldPostBusyAck({ ...base, gateDecision: 'steer' })).toBe(true)
33
+ })
34
+
35
+ it('never fires for a plain fresh-turn deliver', () => {
36
+ expect(shouldPostBusyAck({ ...base, gateDecision: 'deliver' })).toBe(false)
37
+ })
38
+
39
+ it('never fires when not mid-tool-call (turn thinking between tools)', () => {
40
+ expect(shouldPostBusyAck({ ...base, midToolCall: false })).toBe(false)
41
+ })
42
+
43
+ it('never fires below the step-age threshold (agent seconds from answering)', () => {
44
+ expect(
45
+ shouldPostBusyAck({ ...base, stepAgeMs: BUSY_ACK_STEP_AGE_THRESHOLD_MS - 1 }),
46
+ ).toBe(false)
47
+ // exactly at the threshold → fires (>= semantics)
48
+ expect(
49
+ shouldPostBusyAck({ ...base, stepAgeMs: BUSY_ACK_STEP_AGE_THRESHOLD_MS }),
50
+ ).toBe(true)
51
+ })
52
+
53
+ it('never fires with no tracked step age', () => {
54
+ expect(shouldPostBusyAck({ ...base, stepAgeMs: null })).toBe(false)
55
+ })
56
+
57
+ it('fires at most once per key — a second ping gets no second card', () => {
58
+ // The gateway feeds `alreadyAcked` from its per-turn key set + the live
59
+ // card map; the policy must refuse when either says "already acked".
60
+ expect(shouldPostBusyAck({ ...base, alreadyAcked: true })).toBe(false)
61
+ })
62
+
63
+ it('threshold constant sits between the fast-tool envelope and the ignored horizon', () => {
64
+ expect(BUSY_ACK_STEP_AGE_THRESHOLD_MS).toBeGreaterThanOrEqual(5_000)
65
+ expect(BUSY_ACK_STEP_AGE_THRESHOLD_MS).toBeLessThanOrEqual(30_000)
66
+ })
67
+ })
68
+
69
+ describe('formatBusyAckText — rendered card text', () => {
70
+ it('buffered path says "Queued", names the blocking activity, and promises the answer', () => {
71
+ const text = formatBusyAckText({
72
+ gateDecision: 'buffer-until-idle',
73
+ toolName: 'Bash',
74
+ toolLabel: 'gh pr checks --watch',
75
+ })
76
+ expect(text).toContain('Queued')
77
+ expect(text).toContain('`Bash: gh pr checks --watch`')
78
+ expect(text).toMatch(/I'll answer when this step finishes/)
79
+ })
80
+
81
+ it('carries no point-in-time elapsed figure (a static card must not decay)', () => {
82
+ // The card is posted once and never re-rendered while the blocking
83
+ // step runs — any "(Nm elapsed)" would silently go stale on screen.
84
+ const text = formatBusyAckText({
85
+ gateDecision: 'buffer-until-idle',
86
+ toolName: 'Bash',
87
+ toolLabel: 'sleep 90',
88
+ })
89
+ expect(text).not.toMatch(/elapsed|\b\d+[sm]\b/)
90
+ })
91
+
92
+ it('steer path never says "Queued" (classification-visibility invariant)', () => {
93
+ const text = formatBusyAckText({
94
+ gateDecision: 'steer',
95
+ toolName: 'Bash',
96
+ toolLabel: 'sleep 90',
97
+ })
98
+ expect(text).not.toContain('Queued')
99
+ expect(text).toContain('Steer noted')
100
+ expect(text).toContain('`Bash: sleep 90`')
101
+ })
102
+
103
+ it('degrades honestly when the tool label is unknown', () => {
104
+ const text = formatBusyAckText({
105
+ gateDecision: 'buffer-until-idle',
106
+ toolName: null,
107
+ toolLabel: null,
108
+ })
109
+ expect(text).toContain('Queued')
110
+ expect(text).toContain('a long-running step')
111
+ })
112
+
113
+ it('uses the bare tool name when no label was derived', () => {
114
+ const text = formatBusyAckText({
115
+ gateDecision: 'buffer-until-idle',
116
+ toolName: 'Bash',
117
+ toolLabel: null,
118
+ })
119
+ expect(text).toContain('`Bash`')
120
+ })
121
+ })