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.
- package/dist/agent-scheduler/index.js +37 -5
- package/dist/auth-broker/index.js +116 -78
- package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
- package/dist/cli/ms-365-write-pretool.mjs +31 -8
- package/dist/cli/notion-write-pretool.mjs +38 -6
- package/dist/cli/skill-validate-pretool.mjs +144 -2847
- package/dist/cli/switchroom.js +3529 -4543
- package/dist/cli/ui/index.html +163 -17
- package/dist/host-control/main.js +605 -2847
- package/dist/vault/approvals/kernel-server.js +120 -13
- package/dist/vault/broker/server.js +259 -157
- package/package.json +3 -4
- package/profiles/_base/start.sh.hbs +65 -0
- package/profiles/_shared/vault-protocol.md.hbs +3 -1
- 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/telegram-plugin/bridge/bridge.ts +37 -0
- package/telegram-plugin/bridge/inbound-dedup.ts +101 -0
- package/telegram-plugin/dist/bridge/bridge.js +122 -4
- package/telegram-plugin/dist/gateway/gateway.js +4213 -3288
- package/telegram-plugin/dist/server.js +139 -5
- package/telegram-plugin/flood-circuit-breaker.ts +493 -21
- package/telegram-plugin/format.ts +19 -0
- package/telegram-plugin/gateway/approval-hold.ts +602 -0
- package/telegram-plugin/gateway/auth-command.ts +92 -2
- package/telegram-plugin/gateway/auth-loopback-relay.ts +670 -0
- package/telegram-plugin/gateway/boot-card.ts +12 -5
- package/telegram-plugin/gateway/callback-query-handlers.ts +88 -1
- package/telegram-plugin/gateway/config-approval-handler.ts +6 -1
- package/telegram-plugin/gateway/disconnect-flush.ts +19 -0
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +251 -0
- package/telegram-plugin/gateway/dm-pin-sweep.ts +178 -0
- package/telegram-plugin/gateway/gateway.ts +1695 -230
- package/telegram-plugin/gateway/hostd-dispatch.ts +23 -0
- package/telegram-plugin/gateway/idle-clear.ts +90 -6
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +26 -5
- package/telegram-plugin/gateway/inject-handler.ts +8 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +46 -3
- package/telegram-plugin/gateway/ipc-server.ts +43 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +145 -37
- package/telegram-plugin/gateway/model-command.ts +9 -3
- package/telegram-plugin/gateway/pending-session-command.ts +13 -1
- package/telegram-plugin/gateway/permission-ttl-sweep.ts +66 -0
- package/telegram-plugin/gateway/pre-approval-check.ts +74 -0
- package/telegram-plugin/gateway/queued-card-store.ts +217 -0
- package/telegram-plugin/gateway/session-model-file.ts +26 -1
- package/telegram-plugin/gateway/turn-end-gate-backstop.ts +59 -0
- package/telegram-plugin/gateway/turn-end-gate.ts +95 -0
- package/telegram-plugin/gateway/turn-typing-loop.ts +10 -2
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +13 -0
- package/telegram-plugin/history.ts +51 -0
- package/telegram-plugin/hooks/dispatch-claim-scan.mjs +259 -0
- package/telegram-plugin/hooks/dispatch-claim-stop.mjs +129 -0
- package/telegram-plugin/hooks/hooks.json +9 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +303 -2
- package/telegram-plugin/model-unavailable.ts +41 -11
- package/telegram-plugin/operator-events.ts +23 -0
- package/telegram-plugin/outbound-field-redact.ts +69 -0
- package/telegram-plugin/package.json +0 -1
- package/telegram-plugin/permission-rule.ts +1 -0
- package/telegram-plugin/permission-title.ts +1 -0
- package/telegram-plugin/render/render.ts +32 -14
- package/telegram-plugin/retry-api-call.ts +212 -2
- package/telegram-plugin/scoped-approval.ts +11 -2
- package/telegram-plugin/secret-detect/chunker.ts +18 -4
- package/telegram-plugin/secret-detect/index.ts +12 -56
- package/telegram-plugin/send-gate-degraded.test.ts +574 -0
- package/telegram-plugin/send-gate-observability.test.ts +470 -0
- package/telegram-plugin/send-gate-observability.ts +355 -0
- package/telegram-plugin/send-gate.test.ts +717 -0
- package/telegram-plugin/send-gate.ts +1056 -0
- package/telegram-plugin/session-tail.ts +82 -7
- package/telegram-plugin/shared/bot-runtime.ts +17 -5
- package/telegram-plugin/shared/gw-trace-gate.ts +105 -0
- package/telegram-plugin/status-pin-driver.ts +52 -7
- package/telegram-plugin/status-pin.ts +81 -0
- package/telegram-plugin/subagent-watcher.ts +173 -18
- package/telegram-plugin/tests/activity-card-wiring.test.ts +18 -5
- package/telegram-plugin/tests/approval-hold-harness.ts +425 -0
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +327 -0
- package/telegram-plugin/tests/approval-hold-record.test.ts +531 -0
- package/telegram-plugin/tests/approval-hold-redeliver.test.ts +602 -0
- package/telegram-plugin/tests/auth-loopback-relay.test.ts +533 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +53 -7
- package/telegram-plugin/tests/busy-key-reaper.test.ts +1 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
- package/telegram-plugin/tests/dispatch-claim-scan.test.ts +250 -0
- package/telegram-plugin/tests/flood-breaker-blindness.test.ts +213 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +224 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +29 -1
- package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +66 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
- package/telegram-plugin/tests/gw-trace-gate.test.ts +105 -0
- package/telegram-plugin/tests/history.test.ts +115 -0
- package/telegram-plugin/tests/idle-clear.test.ts +233 -3
- package/telegram-plugin/tests/inbound-dedup.test.ts +93 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +448 -0
- package/telegram-plugin/tests/ipc-server-check-pre-approved.test.ts +194 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +123 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +1 -1
- package/telegram-plugin/tests/model-command.test.ts +14 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
- package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
- package/telegram-plugin/tests/pending-session-command.test.ts +21 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +30 -5
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +8 -7
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +1 -1
- package/telegram-plugin/tests/pre-approval-check.test.ts +148 -0
- package/telegram-plugin/tests/queued-card-store.test.ts +232 -0
- package/telegram-plugin/tests/reaction-flush-turn-gated.test.ts +100 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
- package/telegram-plugin/tests/render/render.test.ts +88 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +398 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
- package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
- package/telegram-plugin/tests/session-model-file.test.ts +50 -0
- package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +35 -14
- package/telegram-plugin/tests/status-pin.test.ts +275 -1
- package/telegram-plugin/tests/subagent-watcher-deferral-log-ratelimit.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
- package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +92 -0
- package/telegram-plugin/tests/turn-end-gate.test.ts +137 -0
- package/telegram-plugin/tests/typing-emitter.test.ts +586 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +20 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
- package/telegram-plugin/typing-emitter.ts +224 -0
- package/telegram-plugin/uat/scenarios/jtbd-feel-like-a-colleague-dm.test.ts +136 -0
- package/telegram-plugin/welcome-text.ts +42 -0
- package/telegram-plugin/worktree-watch-cwds.ts +194 -5
- package/vendor/hindsight-memory/scripts/drain_pending.py +22 -6
- package/vendor/hindsight-memory/scripts/lib/client.py +12 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +38 -3
- package/vendor/hindsight-memory/scripts/lib/pending.py +36 -9
- package/vendor/hindsight-memory/scripts/session_end.py +14 -3
- package/vendor/hindsight-memory/scripts/session_start.py +21 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +38 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +68 -0
- package/vendor/hindsight-memory/tests/test_pending.py +44 -0
- package/vendor/hindsight-memory/tests/test_session_end_pending.py +38 -0
- package/vendor/hindsight-memory/tests/test_session_start_drain.py +155 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +0 -56
- package/telegram-plugin/channel-envelope-safety.ts +0 -56
- package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
- 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
|
+
})
|