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.
- package/README.md +2 -2
- package/dist/cli/switchroom.js +905 -758
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +111 -34
- package/skills/switchroom-runtime/SKILL.md +2 -0
- package/telegram-plugin/dist/gateway/gateway.js +46273 -44324
- package/telegram-plugin/flood-circuit-breaker.ts +123 -0
- package/telegram-plugin/gateway/activity-card-store.ts +63 -18
- package/telegram-plugin/gateway/approval-card-stores.ts +99 -0
- package/telegram-plugin/gateway/boot-card.ts +27 -0
- package/telegram-plugin/gateway/bot-commands-ops-info.ts +194 -0
- package/telegram-plugin/gateway/busy-ack.ts +106 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +2660 -0
- package/telegram-plugin/gateway/gateway.ts +1169 -3043
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +181 -23
- package/telegram-plugin/gateway/inbound-delivery-machine.ts +8 -0
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
- package/telegram-plugin/gateway/model-command.ts +23 -11
- package/telegram-plugin/gateway/outbound-send-path.ts +375 -0
- package/telegram-plugin/gateway/pending-state-stores.ts +106 -0
- package/telegram-plugin/gateway/register-bot-commands.ts +30 -0
- package/telegram-plugin/gateway/session-model-file.ts +198 -0
- package/telegram-plugin/gateway/status-pin-store.ts +82 -22
- package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
- package/telegram-plugin/hooks/hooks.json +10 -10
- package/telegram-plugin/hooks/run-hook.sh +84 -0
- package/telegram-plugin/model-unavailable.ts +26 -0
- package/telegram-plugin/pty-partial-handler.ts +39 -0
- package/telegram-plugin/render/rich-render.ts +79 -1
- package/telegram-plugin/retry-api-call.ts +62 -0
- package/telegram-plugin/shared/bot-runtime.ts +8 -1
- package/telegram-plugin/silence-poke.ts +14 -0
- package/telegram-plugin/stream-controller.ts +156 -38
- package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
- package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
- package/telegram-plugin/tests/approval-card-stores.test.ts +124 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
- package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
- package/telegram-plugin/tests/busy-ack.test.ts +121 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +701 -0
- package/telegram-plugin/tests/emission-determinism-wiring.test.ts +11 -4
- package/telegram-plugin/tests/fixtures/cutover-killswitch-probe.ts +75 -0
- package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +5 -1
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +177 -25
- package/telegram-plugin/tests/inbound-delivery-cutover-flip.test.ts +418 -0
- package/telegram-plugin/tests/inbound-delivery-dispatch-equivalence.test.ts +348 -0
- package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +141 -52
- package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -1
- package/telegram-plugin/tests/model-command.test.ts +2 -2
- package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
- package/telegram-plugin/tests/outbound-send-chunks.test.ts +304 -0
- package/telegram-plugin/tests/outbound-send-path.test.ts +222 -0
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +34 -15
- package/telegram-plugin/tests/pending-state-stores.test.ts +235 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
- package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
- package/telegram-plugin/tests/session-model-file.test.ts +132 -0
- package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +18 -4
- package/telegram-plugin/tests/vault-approval-posture.test.ts +15 -7
- package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +8 -4
- package/telegram-plugin/tests/vault-grant-union.test.ts +8 -4
- package/telegram-plugin/tests/vault-grant-wizard.test.ts +8 -1
- package/telegram-plugin/tests/vault-grants-revoke.test.ts +8 -1
- package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +8 -4
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +8 -4
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +8 -4
- package/telegram-plugin/tests/voice-send.test.ts +308 -0
- package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
- package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
- package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
- package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
- package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
- package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
- package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
- package/telegram-plugin/voice-ondemand.ts +25 -1
- 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
|
+
}
|