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,106 @@
1
+ // #2995 — mid-flight busy ack.
2
+ //
3
+ // A mid-turn inbound that is neither a steer nor an interrupt is buffered
4
+ // until the running turn goes idle (`buffer-until-idle`). When the turn is
5
+ // sitting inside ONE long blocking tool call (`gh pr checks --watch`, a
6
+ // long `sleep`, a slow build), that wait can be minutes — from the phone
7
+ // the question reads as ignored. The user's message got only a 👀 reaction
8
+ // and then silence until the blocking step returned (observed live
9
+ // 2026-07-10: a trivial "are any agents working?" waited 2 minutes behind
10
+ // a `--watch`).
11
+ //
12
+ // This module is the PURE half of the fix: a deterministic, model-free
13
+ // decision — should the gateway post a silent "⏳ Queued — currently
14
+ // inside <tool> …" card into the inbound's own chat/topic right now? No
15
+ // tokens, no model calls; the gateway supplies live readings (delivery-gate
16
+ // decision, tool-flight state, current step age, dedupe state) and this
17
+ // module answers. Extracted so the policy is unit-testable without the
18
+ // gateway IIFE (same pattern as `interrupt-defer.ts` / `feed-reopen-gate.ts`).
19
+ //
20
+ // Wording contract: the buffered-path card MUST say "Queued" — the
21
+ // steer-or-queue job spec (`reference/jobs/steer-or-queue-mid-flight.md`)
22
+ // requires the chosen classification to be visible in the chat, never
23
+ // inferred. A steer-path card must NOT say "Queued" (it wasn't queued);
24
+ // it says the steer was noted and will fold in at the step boundary.
25
+
26
+ /** How the inbound was classified/handled by the delivery gate. Only the
27
+ * two mid-turn shapes are ack-eligible; a plain fresh-turn `deliver`
28
+ * needs no busy ack (the turn starts immediately). */
29
+ export type BusyAckGateDecision = 'buffer-until-idle' | 'steer' | 'deliver'
30
+
31
+ /**
32
+ * Minimum age of the CURRENT tool step before a busy ack fires. Below
33
+ * this the step is about to return anyway and the buffered inbound will
34
+ * flush in a beat — an ack would fire when the agent was seconds from
35
+ * answering (the job spec's explicit Bad bullet). 12s sits well above the
36
+ * common fast-tool envelope (reads/greps/short bashes finish in <5s) and
37
+ * well below the "reads as ignored" horizon (~30s+); it also matches the
38
+ * UAT latency promise (visible ack <10s of the ping, for a step that has
39
+ * already proven itself long).
40
+ */
41
+ export const BUSY_ACK_STEP_AGE_THRESHOLD_MS = 12_000
42
+
43
+ export interface BusyAckDecisionInput {
44
+ /** Delivery-gate outcome for this inbound ('steer' when it was
45
+ * delivered mid-turn as a steering amend). */
46
+ gateDecision: BusyAckGateDecision
47
+ /** Live `ToolFlightTracker.isMidToolCall()` reading — at least one
48
+ * top-level tool call currently open. */
49
+ midToolCall: boolean
50
+ /** Age (ms) of the LONGEST-running in-flight tool step, or null when no
51
+ * step is tracked (e.g. the turn is thinking between tools). */
52
+ stepAgeMs: number | null
53
+ /** True when a busy-ack/queued-status card already exists (or was
54
+ * already posted this turn) for the inbound's chat/topic — at most one
55
+ * card per turn per chat/topic; a second ping gets no second card. */
56
+ alreadyAcked: boolean
57
+ }
58
+
59
+ /**
60
+ * Pure decision: post the busy-ack card now?
61
+ *
62
+ * - only for mid-turn shapes (buffered, or delivered as a steer)
63
+ * - only while genuinely mid-tool-call
64
+ * - only once the current step is older than the threshold (a young
65
+ * step returns soon; the normal flush covers it)
66
+ * - at most once per turn per chat/topic (dedupe)
67
+ */
68
+ export function shouldPostBusyAck(input: BusyAckDecisionInput): boolean {
69
+ if (input.gateDecision !== 'buffer-until-idle' && input.gateDecision !== 'steer') return false
70
+ if (!input.midToolCall) return false
71
+ if (input.stepAgeMs == null || input.stepAgeMs < BUSY_ACK_STEP_AGE_THRESHOLD_MS) return false
72
+ if (input.alreadyAcked) return false
73
+ return true
74
+ }
75
+
76
+ export interface BusyAckTextInput {
77
+ gateDecision: 'buffer-until-idle' | 'steer'
78
+ /** Bare tool name as tracked (e.g. "Bash"). Null when unknown. */
79
+ toolName: string | null
80
+ /** Natural-language descriptor from the PreToolUse sidecar's
81
+ * `toolLabel()` (e.g. `sleep 90`), or null. */
82
+ toolLabel: string | null
83
+ }
84
+
85
+ /**
86
+ * Render the card text. Deterministic, no model. The buffered variant
87
+ * says "Queued" (classification-visibility invariant); the steer variant
88
+ * says the steer is noted — never "Queued".
89
+ *
90
+ * Deliberately carries NO elapsed figure: the card is posted once and
91
+ * never re-rendered while the blocking step runs, so any point-in-time
92
+ * number ("2m elapsed") would silently go stale on screen — a decaying
93
+ * claim on a card whose whole point is honesty. The activity name alone
94
+ * is time-invariant.
95
+ */
96
+ export function formatBusyAckText(input: BusyAckTextInput): string {
97
+ const name = input.toolName ?? 'a long-running step'
98
+ const activity =
99
+ input.toolLabel != null && input.toolLabel.length > 0
100
+ ? `${name}: ${input.toolLabel}`
101
+ : name
102
+ if (input.gateDecision === 'steer') {
103
+ return `⏳ Steer noted — currently inside \`${activity}\`; I'll fold it in when this step finishes.`
104
+ }
105
+ return `⏳ Queued — currently inside \`${activity}\`; I'll answer when this step finishes.`
106
+ }