switchroom 0.17.10 → 0.18.6
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/bin/workspace-dynamic-hook.sh +12 -13
- package/dist/agent-scheduler/index.js +29 -2
- package/dist/auth-broker/index.js +6163 -152
- package/dist/cli/notion-write-pretool.mjs +31 -3
- package/dist/cli/switchroom.js +695 -526
- package/dist/host-control/main.js +6184 -173
- package/dist/vault/approvals/kernel-server.js +5893 -165
- package/dist/vault/broker/server.js +6666 -921
- package/package.json +1 -1
- package/profiles/_base/settings.json.hbs +2 -2
- package/profiles/_base/start.sh.hbs +170 -21
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/skills/switchroom-release/SKILL.md +78 -0
- package/telegram-plugin/auth-snapshot-format.ts +37 -25
- package/telegram-plugin/context-exhaustion.ts +124 -0
- package/telegram-plugin/dist/gateway/gateway.js +25025 -9203
- package/telegram-plugin/gateway/activity-card-store.ts +76 -0
- package/telegram-plugin/gateway/gateway.ts +740 -106
- package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
- package/telegram-plugin/gateway/model-command.ts +70 -10
- package/telegram-plugin/gateway/resolve-person.ts +304 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +21 -1
- package/telegram-plugin/hooks/silent-end-scan.mjs +164 -40
- package/telegram-plugin/operator-events.ts +21 -0
- package/telegram-plugin/package.json +6 -0
- package/telegram-plugin/pending-work-progress.ts +42 -7
- package/telegram-plugin/quota-bar-format.ts +360 -0
- package/telegram-plugin/quota-watch.ts +4 -6
- package/telegram-plugin/registry/turns-schema.test.ts +97 -0
- package/telegram-plugin/registry/turns-schema.ts +78 -0
- package/telegram-plugin/render/ir.ts +209 -0
- package/telegram-plugin/render/parse.ts +363 -0
- package/telegram-plugin/render/render.ts +440 -0
- package/telegram-plugin/render/rich-render.ts +72 -0
- package/telegram-plugin/stream-controller.ts +14 -3
- package/telegram-plugin/subagent-watcher.ts +27 -9
- package/telegram-plugin/tests/activity-card-store.test.ts +94 -0
- package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +51 -16
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +11 -0
- package/telegram-plugin/tests/feed-survival.test.ts +39 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
- package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
- package/telegram-plugin/tests/model-command.test.ts +193 -16
- package/telegram-plugin/tests/narrative-render.test.ts +125 -0
- package/telegram-plugin/tests/operator-events.test.ts +16 -0
- package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
- package/telegram-plugin/tests/pending-work-progress.test.ts +116 -3
- package/telegram-plugin/tests/quota-bar-format.test.ts +444 -0
- package/telegram-plugin/tests/quota-watch.test.ts +1 -4
- package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
- package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
- package/telegram-plugin/tests/render/parse.test.ts +393 -0
- package/telegram-plugin/tests/render/render.test.ts +436 -0
- package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
- package/telegram-plugin/tests/resolve-person.test.ts +290 -0
- package/telegram-plugin/tests/silent-end-interrupt-stop-integration.test.ts +53 -0
- package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +138 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +61 -0
- package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +19 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +97 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +98 -3
- package/telegram-plugin/turn-liveness-floor.ts +35 -1
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
- package/telegram-plugin/worker-activity-feed.ts +220 -15
- package/telegram-plugin/worktree-watch-cwds.ts +92 -17
- package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
- package/vendor/hindsight-memory/scripts/lib/config.py +9 -2
- package/vendor/hindsight-memory/scripts/recall.py +64 -6
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
- package/vendor/hindsight-memory/tests/test_client.py +43 -0
- package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
- package/profiles/default/CLAUDE.md +0 -116
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
|
@@ -48,3 +48,127 @@ export function shouldArmOrphanedReplyTimeout(params: {
|
|
|
48
48
|
!params.progressCardActive
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default "recently streaming" window (ms) for the orphaned-reply liveness
|
|
54
|
+
* stamp. The gateway overrides this from
|
|
55
|
+
* SWITCHROOM_ORPHANED_REPLY_STREAM_WINDOW_MS; this constant is the fallback and
|
|
56
|
+
* the value the LivenessTracker tests pin against.
|
|
57
|
+
*/
|
|
58
|
+
export const ORPHANED_REPLY_STREAM_WINDOW_MS = 120_000
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result of a fuse-expiry decision.
|
|
62
|
+
*
|
|
63
|
+
* - `rearm` — re-arm the fuse (keep the turn alive) instead of
|
|
64
|
+
* firing the synthetic turn_end backstop.
|
|
65
|
+
* - `countsAgainstCap`— this rearm (or the fire it turned into at the cap) was
|
|
66
|
+
* a working/recently-streaming rearm, i.e. subject to the
|
|
67
|
+
* ORPHANED_REPLY_MAX_REARMS cap. Human-wait rearms are
|
|
68
|
+
* uncapped and report `false` here.
|
|
69
|
+
*/
|
|
70
|
+
export interface OrphanedReplyExpiryDecision {
|
|
71
|
+
rearm: boolean
|
|
72
|
+
countsAgainstCap: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Per-turn liveness tracker for the orphaned-reply backstop.
|
|
77
|
+
*
|
|
78
|
+
* WHY THIS EXISTS (the thinking-pause fix):
|
|
79
|
+
* The orphaned-reply fuse (ORPHANED_REPLY_TIMEOUT_MS = 30 s) used to be reset
|
|
80
|
+
* ONLY by `tool_label` and `text` stream events. During a long model
|
|
81
|
+
* reasoning pause the gateway sees NO such events (thinking events carry no
|
|
82
|
+
* text), so the fuse ran down and force-ended a genuinely-live turn mid-work
|
|
83
|
+
* ("orphaned-reply timeout (30000ms) — forcing backstop" ~1 ms before
|
|
84
|
+
* turn_end, with minutes of real work still to come).
|
|
85
|
+
*
|
|
86
|
+
* THE FIX:
|
|
87
|
+
* Stamp `lastStreamEventAt` on ANY genuine stream event (via onStreamEvent,
|
|
88
|
+
* called from the gateway dispatcher entry). If a genuine event arrived
|
|
89
|
+
* within `windowMs` (default 120 s) the turn is "recently streaming" and the
|
|
90
|
+
* fuse re-arms instead of firing — a reasoning pause is survivable while a
|
|
91
|
+
* genuine multi-minute hang (no events at all) still fires.
|
|
92
|
+
*
|
|
93
|
+
* This is a small, pure, side-effect-free seam so the decision logic is unit
|
|
94
|
+
* testable without the full gateway. The gateway holds one instance per
|
|
95
|
+
* CurrentTurn (so per-turn identity / supersession semantics are unchanged) and
|
|
96
|
+
* delegates: the dispatcher calls onStreamEvent; the fire callback calls
|
|
97
|
+
* decideOnExpiry; the defensive turn_end guard reads recentlyStreaming.
|
|
98
|
+
*/
|
|
99
|
+
export class LivenessTracker {
|
|
100
|
+
/** Wall-clock (ms) of the last genuine stream event for this turn. */
|
|
101
|
+
lastStreamEventAt: number
|
|
102
|
+
/**
|
|
103
|
+
* How many times the fuse re-armed on a working / recently-streaming
|
|
104
|
+
* expiry. Bounded by ORPHANED_REPLY_MAX_REARMS. Zeroed by onStreamEvent
|
|
105
|
+
* whenever a genuine stream event lands, so the cap only bites CONSECUTIVE
|
|
106
|
+
* silent expiries (a genuinely wedged single tool with no stream at all).
|
|
107
|
+
*/
|
|
108
|
+
orphanedReplyRearmCount = 0
|
|
109
|
+
|
|
110
|
+
constructor(startedAt: number) {
|
|
111
|
+
this.lastStreamEventAt = startedAt
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Stamp liveness for a genuine stream event and reset the rearm counter.
|
|
116
|
+
*
|
|
117
|
+
* F4 predicate: everything EXCEPT the synthetic `turn_end` re-entry
|
|
118
|
+
* (durationMs === -1, the fire callback's own re-dispatch) counts as a
|
|
119
|
+
* genuine stream event. Stamping a REAL turn_end (durationMs >= 0) is
|
|
120
|
+
* harmless — the turn is ending anyway.
|
|
121
|
+
*
|
|
122
|
+
* The counter reset lives HERE (driven by the dispatcher), NOT in the fuse
|
|
123
|
+
* re-arm path — otherwise every rearm would zero its own counter and nullify
|
|
124
|
+
* the 20-cap.
|
|
125
|
+
*/
|
|
126
|
+
onStreamEvent(kind: string, durationMs: number | undefined, now: number): void {
|
|
127
|
+
if (kind === 'turn_end' && durationMs === -1) return
|
|
128
|
+
this.lastStreamEventAt = now
|
|
129
|
+
this.orphanedReplyRearmCount = 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** True iff a genuine stream event landed within `windowMs` of `now`. */
|
|
133
|
+
recentlyStreaming(now: number, windowMs: number): boolean {
|
|
134
|
+
return now - this.lastStreamEventAt < windowMs
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Decide what to do when the orphaned-reply fuse expires.
|
|
139
|
+
*
|
|
140
|
+
* Rearm when the turn is working OR recently streaming OR a human is being
|
|
141
|
+
* waited on. Working / recently-streaming rearms count against `maxRearms`
|
|
142
|
+
* (so a genuinely wedged single tool that never streams still surfaces after
|
|
143
|
+
* the cap). Human-wait rearms are uncapped (the human simply hasn't tapped
|
|
144
|
+
* yet). Once the cap is hit with nothing else keeping the turn alive, fire
|
|
145
|
+
* (rearm:false) — matching the existing fail-safe.
|
|
146
|
+
*
|
|
147
|
+
* Mutates `orphanedReplyRearmCount` on each rearm.
|
|
148
|
+
*/
|
|
149
|
+
decideOnExpiry(opts: {
|
|
150
|
+
working: boolean
|
|
151
|
+
humanWaiting: boolean
|
|
152
|
+
now: number
|
|
153
|
+
windowMs: number
|
|
154
|
+
maxRearms: number
|
|
155
|
+
}): OrphanedReplyExpiryDecision {
|
|
156
|
+
// Human-wait rearms are uncapped (unchanged from prior behaviour).
|
|
157
|
+
if (opts.humanWaiting) {
|
|
158
|
+
this.orphanedReplyRearmCount++
|
|
159
|
+
return { rearm: true, countsAgainstCap: false }
|
|
160
|
+
}
|
|
161
|
+
const recently = this.recentlyStreaming(opts.now, opts.windowMs)
|
|
162
|
+
if (opts.working || recently) {
|
|
163
|
+
if (this.orphanedReplyRearmCount < opts.maxRearms) {
|
|
164
|
+
this.orphanedReplyRearmCount++
|
|
165
|
+
return { rearm: true, countsAgainstCap: true }
|
|
166
|
+
}
|
|
167
|
+
// Cap reached with only working/recently-streaming keeping it alive:
|
|
168
|
+
// fire to surface the wedge.
|
|
169
|
+
return { rearm: false, countsAgainstCap: true }
|
|
170
|
+
}
|
|
171
|
+
// Nothing keeping the turn alive → fire (fail-safe).
|
|
172
|
+
return { rearm: false, countsAgainstCap: false }
|
|
173
|
+
}
|
|
174
|
+
}
|