switchroom 0.19.48 → 0.20.0
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 +18 -1
- package/dist/auth-broker/index.js +19 -2
- package/dist/buzz-gateway/index.js +9207 -0
- package/dist/cli/notion-write-pretool.mjs +18 -1
- package/dist/cli/switchroom.js +63 -4
- package/dist/host-control/main.js +20 -3
- package/dist/vault/approvals/kernel-server.js +19 -2
- package/dist/vault/broker/server.js +19 -2
- package/package.json +4 -3
- package/profiles/_base/start.sh.hbs +78 -1
- package/profiles/default/CLAUDE.md.hbs +1 -1
- package/skills/dev-protocol/SKILL.md +30 -1
- package/skills/switchroom-architecture/SKILL.md +5 -0
- package/skills/switchroom-cli/SKILL.md +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +7 -4
- package/telegram-plugin/dist/gateway/gateway.js +1149 -247
- package/telegram-plugin/dist/server.js +7 -4
- package/telegram-plugin/gateway/boot-briefing-builder.ts +458 -0
- package/telegram-plugin/gateway/boot-briefing-wiring.ts +170 -0
- package/telegram-plugin/gateway/buzz-mirror.ts +329 -0
- package/telegram-plugin/gateway/buzz-type-guards.ts +34 -0
- package/telegram-plugin/gateway/channel-route.ts +272 -0
- package/telegram-plugin/gateway/gateway.ts +73 -81
- package/telegram-plugin/gateway/inbound-spool.ts +33 -1
- package/telegram-plugin/gateway/ipc-protocol.ts +81 -2
- package/telegram-plugin/gateway/ipc-server.ts +197 -2
- package/telegram-plugin/gateway/outbound-send-path.ts +37 -1
- package/telegram-plugin/gateway/pending-turn-env.ts +61 -0
- package/telegram-plugin/gateway/stream-render.ts +21 -0
- package/telegram-plugin/gateway/subagent-handback-marker.ts +12 -0
- package/telegram-plugin/gateway/user-failure-notices.ts +172 -0
- package/telegram-plugin/history.ts +15 -0
- package/telegram-plugin/llm-error-present.ts +9 -4
- package/telegram-plugin/model-unavailable.ts +4 -0
- package/telegram-plugin/operator-events.fixtures.json +12 -12
- package/telegram-plugin/operator-events.ts +81 -9
- package/telegram-plugin/session-tail.ts +7 -1
- package/telegram-plugin/tests/boot-briefing-builder.test.ts +604 -0
- package/telegram-plugin/tests/buzz-mirror.test.ts +242 -0
- package/telegram-plugin/tests/buzz-origin-stamp-gate.test.ts +159 -0
- package/telegram-plugin/tests/channel-route.test.ts +306 -0
- package/telegram-plugin/tests/inbound-spool.test.ts +47 -0
- package/telegram-plugin/tests/ipc-server-buzz-dedup.test.ts +124 -0
- package/telegram-plugin/tests/ipc-server-buzz-peer.test.ts +269 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +63 -0
- package/telegram-plugin/tests/operator-events.test.ts +71 -7
- package/telegram-plugin/tests/user-failure-notices.test.ts +165 -0
- package/telegram-plugin/voice-normalize-text.ts +5 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +4 -0
- package/vendor/hindsight-memory/scripts/recall.py +7 -2
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
initBuzzMirror,
|
|
4
|
+
getBuzzMirror,
|
|
5
|
+
maybeBootBuzzMirror,
|
|
6
|
+
__resetBuzzMirrorForTests,
|
|
7
|
+
CORRECTION_DEBOUNCE_MS,
|
|
8
|
+
} from '../gateway/buzz-mirror.js'
|
|
9
|
+
import type { OutboundToBuzzMessage } from '../gateway/ipc-protocol.js'
|
|
10
|
+
|
|
11
|
+
// Hub-side mirror behaviour (Phase 2b). The mirror is DOWNSTREAM of a delivered
|
|
12
|
+
// Telegram copy; a publish is emitted via the attached peer sender. These tests
|
|
13
|
+
// spy the sender to assert WHETHER and WITH WHAT a publish is issued.
|
|
14
|
+
|
|
15
|
+
function mirrorWith(sender: (m: OutboundToBuzzMessage) => boolean, opts?: { defaultChannelId?: string }) {
|
|
16
|
+
const m = initBuzzMirror({
|
|
17
|
+
mode: 'both',
|
|
18
|
+
agentName: 'klanker',
|
|
19
|
+
defaultChannelId: opts?.defaultChannelId ?? 'default-chan',
|
|
20
|
+
})
|
|
21
|
+
m.attachSender(sender)
|
|
22
|
+
return m
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BUZZ_COORDS = { channelId: 'chan-A', eventId: 'evt-1', threadRoot: 'root-1' }
|
|
26
|
+
|
|
27
|
+
describe('BuzzMirror.mirrorReplyDelivered — routing + S1 owner guard', () => {
|
|
28
|
+
beforeEach(() => __resetBuzzMirrorForTests())
|
|
29
|
+
|
|
30
|
+
it('T-6b (hub): a live buzz owner + un-echoed reply + a recent different-origin turn ⇒ NO publish', () => {
|
|
31
|
+
const sender = vi.fn(() => true)
|
|
32
|
+
const m = mirrorWith(sender)
|
|
33
|
+
m.mirrorReplyDelivered({
|
|
34
|
+
scrubbedText: 'answer',
|
|
35
|
+
ownerOriginChannel: 'buzz',
|
|
36
|
+
ownerBuzzCoords: BUZZ_COORDS,
|
|
37
|
+
ownerEchoed: false, // the reply did NOT echo this buzz turn's id
|
|
38
|
+
hasRecentDifferentOriginTurn: true, // a prior Telegram DM turn is live/recent
|
|
39
|
+
telegramMessageKeys: ['555:1001'],
|
|
40
|
+
})
|
|
41
|
+
// The S1 guard must refuse the ambiguous threaded publish — Telegram-only.
|
|
42
|
+
expect(sender).not.toHaveBeenCalled()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('publishes a THREADED reply when the buzz owner id was echoed', () => {
|
|
46
|
+
const sender = vi.fn(() => true)
|
|
47
|
+
const m = mirrorWith(sender)
|
|
48
|
+
m.mirrorReplyDelivered({
|
|
49
|
+
scrubbedText: 'answer',
|
|
50
|
+
ownerOriginChannel: 'buzz',
|
|
51
|
+
ownerBuzzCoords: BUZZ_COORDS,
|
|
52
|
+
ownerEchoed: true,
|
|
53
|
+
hasRecentDifferentOriginTurn: true, // irrelevant once echoed
|
|
54
|
+
telegramMessageKeys: ['555:1001'],
|
|
55
|
+
})
|
|
56
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
57
|
+
const msg = sender.mock.calls[0][0]
|
|
58
|
+
expect(msg.type).toBe('outbound_to_buzz')
|
|
59
|
+
expect(msg.channelId).toBe(BUZZ_COORDS.channelId)
|
|
60
|
+
expect(msg.replyToEventId).toBe(BUZZ_COORDS.eventId)
|
|
61
|
+
expect(msg.threadRootId).toBe(BUZZ_COORDS.threadRoot)
|
|
62
|
+
expect(msg.payload).toEqual({ kind: 'message', text: 'answer' })
|
|
63
|
+
expect(msg.agentName).toBe('klanker')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('publishes a buzz-origin threaded reply when un-echoed but NO different-origin turn exists', () => {
|
|
67
|
+
const sender = vi.fn(() => true)
|
|
68
|
+
const m = mirrorWith(sender)
|
|
69
|
+
m.mirrorReplyDelivered({
|
|
70
|
+
scrubbedText: 'answer',
|
|
71
|
+
ownerOriginChannel: 'buzz',
|
|
72
|
+
ownerBuzzCoords: BUZZ_COORDS,
|
|
73
|
+
ownerEchoed: false,
|
|
74
|
+
hasRecentDifferentOriginTurn: false,
|
|
75
|
+
telegramMessageKeys: ['555:1001'],
|
|
76
|
+
})
|
|
77
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('mirrors a TELEGRAM-origin answer as a fresh top-level post to defaultChannelId', () => {
|
|
81
|
+
const sender = vi.fn(() => true)
|
|
82
|
+
const m = mirrorWith(sender, { defaultChannelId: 'grp-top' })
|
|
83
|
+
m.mirrorReplyDelivered({
|
|
84
|
+
scrubbedText: 'tele answer',
|
|
85
|
+
ownerOriginChannel: 'telegram',
|
|
86
|
+
ownerEchoed: false,
|
|
87
|
+
hasRecentDifferentOriginTurn: false,
|
|
88
|
+
telegramMessageKeys: ['555:2002'],
|
|
89
|
+
})
|
|
90
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
91
|
+
const msg = sender.mock.calls[0][0]
|
|
92
|
+
expect(msg.channelId).toBe('grp-top')
|
|
93
|
+
expect(msg.replyToEventId).toBeUndefined() // top-level, not a thread reply
|
|
94
|
+
expect(msg.threadRootId).toBeUndefined()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('drops a telegram-origin mirror when no default channel is configured', () => {
|
|
98
|
+
const sender = vi.fn(() => true)
|
|
99
|
+
const m = mirrorWith(sender, { defaultChannelId: '' })
|
|
100
|
+
m.mirrorReplyDelivered({
|
|
101
|
+
scrubbedText: 'x',
|
|
102
|
+
ownerOriginChannel: 'telegram',
|
|
103
|
+
ownerEchoed: false,
|
|
104
|
+
hasRecentDifferentOriginTurn: false,
|
|
105
|
+
telegramMessageKeys: ['555:3003'],
|
|
106
|
+
})
|
|
107
|
+
expect(sender).not.toHaveBeenCalled()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('never throws even if the sender throws (the Telegram copy is already delivered)', () => {
|
|
111
|
+
const sender = vi.fn(() => { throw new Error('peer exploded') })
|
|
112
|
+
const m = mirrorWith(sender)
|
|
113
|
+
expect(() =>
|
|
114
|
+
m.mirrorReplyDelivered({
|
|
115
|
+
scrubbedText: 'x',
|
|
116
|
+
ownerOriginChannel: 'telegram',
|
|
117
|
+
ownerEchoed: false,
|
|
118
|
+
hasRecentDifferentOriginTurn: false,
|
|
119
|
+
telegramMessageKeys: ['555:4004'],
|
|
120
|
+
}),
|
|
121
|
+
).not.toThrow()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('BuzzMirror.mirrorCorrection — debounced, only for mirrored messages', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
__resetBuzzMirrorForTests()
|
|
128
|
+
vi.useFakeTimers()
|
|
129
|
+
})
|
|
130
|
+
// restore real timers after each via afterEach-less pattern
|
|
131
|
+
it('no-ops for a Telegram message that was never mirrored to Buzz', () => {
|
|
132
|
+
const sender = vi.fn(() => true)
|
|
133
|
+
const m = mirrorWith(sender)
|
|
134
|
+
m.mirrorCorrection({ telegramMessageKey: '555:9999', scrubbedText: 'fixed' })
|
|
135
|
+
vi.advanceTimersByTime(CORRECTION_DEBOUNCE_MS + 1000)
|
|
136
|
+
expect(sender).not.toHaveBeenCalled()
|
|
137
|
+
vi.useRealTimers()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('publishes a debounced correction for a message recorded via onPublishResult', () => {
|
|
141
|
+
const sender = vi.fn(() => true)
|
|
142
|
+
const m = mirrorWith(sender)
|
|
143
|
+
// First: a delivered buzz publish, then its OK result records the mapping.
|
|
144
|
+
m.mirrorReplyDelivered({
|
|
145
|
+
scrubbedText: 'answer',
|
|
146
|
+
ownerOriginChannel: 'buzz',
|
|
147
|
+
ownerBuzzCoords: BUZZ_COORDS,
|
|
148
|
+
ownerEchoed: true,
|
|
149
|
+
hasRecentDifferentOriginTurn: false,
|
|
150
|
+
telegramMessageKeys: ['555:1001'],
|
|
151
|
+
})
|
|
152
|
+
const correlationId = sender.mock.calls[0][0].correlationId
|
|
153
|
+
m.onPublishResult({ correlationId, ok: true, eventId: 'published-evt' })
|
|
154
|
+
sender.mockClear()
|
|
155
|
+
|
|
156
|
+
m.mirrorCorrection({ telegramMessageKey: '555:1001', scrubbedText: 'corrected' })
|
|
157
|
+
// Debounced: nothing before the window elapses.
|
|
158
|
+
vi.advanceTimersByTime(CORRECTION_DEBOUNCE_MS - 10)
|
|
159
|
+
expect(sender).not.toHaveBeenCalled()
|
|
160
|
+
vi.advanceTimersByTime(20)
|
|
161
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
162
|
+
const corr = sender.mock.calls[0][0]
|
|
163
|
+
expect(corr.payload).toEqual({ kind: 'correction', text: 'corrected', targetEventId: 'published-evt' })
|
|
164
|
+
vi.useRealTimers()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('S4/F6: two rapid edits COALESCE to a SINGLE published correction (last-write-wins)', () => {
|
|
168
|
+
const sender = vi.fn(() => true)
|
|
169
|
+
const m = mirrorWith(sender)
|
|
170
|
+
// Record a mirrored message so there is a Buzz event to correct.
|
|
171
|
+
m.mirrorReplyDelivered({
|
|
172
|
+
scrubbedText: 'answer',
|
|
173
|
+
ownerOriginChannel: 'buzz',
|
|
174
|
+
ownerBuzzCoords: BUZZ_COORDS,
|
|
175
|
+
ownerEchoed: true,
|
|
176
|
+
hasRecentDifferentOriginTurn: false,
|
|
177
|
+
telegramMessageKeys: ['555:1001'],
|
|
178
|
+
})
|
|
179
|
+
const correlationId = sender.mock.calls[0][0].correlationId
|
|
180
|
+
m.onPublishResult({ correlationId, ok: true, eventId: 'published-evt' })
|
|
181
|
+
sender.mockClear()
|
|
182
|
+
|
|
183
|
+
// Edit #1 at t=0 arms a timer for t=DEBOUNCE. Edit #2 arrives INSIDE the
|
|
184
|
+
// window and must CLEAR edit #1's timer (buzz-mirror.ts:192-193) so only the
|
|
185
|
+
// latest text is ever published, and only once.
|
|
186
|
+
m.mirrorCorrection({ telegramMessageKey: '555:1001', scrubbedText: 'first edit' })
|
|
187
|
+
vi.advanceTimersByTime(CORRECTION_DEBOUNCE_MS - 5_000) // still inside the window
|
|
188
|
+
m.mirrorCorrection({ telegramMessageKey: '555:1001', scrubbedText: 'second edit' })
|
|
189
|
+
|
|
190
|
+
// Step PAST edit #1's original deadline. If coalescing were broken, edit #1's
|
|
191
|
+
// timer would fire here — it must NOT (it was cleared).
|
|
192
|
+
vi.advanceTimersByTime(6_000)
|
|
193
|
+
expect(sender).not.toHaveBeenCalled()
|
|
194
|
+
|
|
195
|
+
// Reach edit #2's deadline: EXACTLY ONE correction, carrying the LATEST text.
|
|
196
|
+
vi.advanceTimersByTime(CORRECTION_DEBOUNCE_MS)
|
|
197
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
198
|
+
expect(sender.mock.calls[0][0].payload).toEqual({
|
|
199
|
+
kind: 'correction',
|
|
200
|
+
text: 'second edit',
|
|
201
|
+
targetEventId: 'published-evt',
|
|
202
|
+
})
|
|
203
|
+
vi.useRealTimers()
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('maybeBootBuzzMirror — dark by default (S2)', () => {
|
|
208
|
+
beforeEach(() => __resetBuzzMirrorForTests())
|
|
209
|
+
|
|
210
|
+
it('stays dark (returns null, getBuzzMirror null) when BUZZ_ENABLED is unset', () => {
|
|
211
|
+
const booted = maybeBootBuzzMirror(() => true, {})
|
|
212
|
+
expect(booted).toBeNull()
|
|
213
|
+
expect(getBuzzMirror()).toBeNull()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('stays dark when enabled but mode is off/origin (S2 degrade)', () => {
|
|
217
|
+
expect(maybeBootBuzzMirror(() => true, { BUZZ_ENABLED: '1', BUZZ_MIRROR: 'off' })).toBeNull()
|
|
218
|
+
expect(maybeBootBuzzMirror(() => true, { BUZZ_ENABLED: '1', BUZZ_MIRROR: 'origin' })).toBeNull()
|
|
219
|
+
expect(getBuzzMirror()).toBeNull()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('boots when enabled AND mode both, wiring the sender', () => {
|
|
223
|
+
const sender = vi.fn(() => true)
|
|
224
|
+
const booted = maybeBootBuzzMirror(sender, {
|
|
225
|
+
BUZZ_ENABLED: '1',
|
|
226
|
+
BUZZ_MIRROR: 'both',
|
|
227
|
+
SWITCHROOM_AGENT_NAME: 'klanker',
|
|
228
|
+
BUZZ_CHANNEL_IDS: 'grp-top',
|
|
229
|
+
})
|
|
230
|
+
expect(booted).not.toBeNull()
|
|
231
|
+
expect(getBuzzMirror()).toBe(booted)
|
|
232
|
+
// Sender is wired: a telegram-origin mirror flows to it.
|
|
233
|
+
booted!.mirrorReplyDelivered({
|
|
234
|
+
scrubbedText: 'x',
|
|
235
|
+
ownerOriginChannel: 'telegram',
|
|
236
|
+
ownerEchoed: false,
|
|
237
|
+
hasRecentDifferentOriginTurn: false,
|
|
238
|
+
telegramMessageKeys: ['555:1'],
|
|
239
|
+
})
|
|
240
|
+
expect(sender).toHaveBeenCalledTimes(1)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import {
|
|
4
|
+
handleSessionEvent,
|
|
5
|
+
type StreamRenderDeps,
|
|
6
|
+
} from '../gateway/stream-render.js'
|
|
7
|
+
import { OutboundDedupCache } from '../recent-outbound-dedup.js'
|
|
8
|
+
import { FlushedTurnSupersedeRegistry } from '../flushed-turn-supersede.js'
|
|
9
|
+
import { BackstopDeliveryLedger } from '../gateway/backstop-delivery.js'
|
|
10
|
+
import { redact } from '../secret-detect/redact.js'
|
|
11
|
+
import type { CurrentTurn } from '../gateway/gateway.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Buzz co-channel — Phase 2a MINOR-1 gate guard (behavioural, non-mocked).
|
|
15
|
+
*
|
|
16
|
+
* `stream-render.ts` computes `BUZZ_ORIGIN_STAMP_ACTIVE` at import time from
|
|
17
|
+
* `BUZZ_ENABLED` + the routing kill switch; when it is false the turn ctor
|
|
18
|
+
* stamps a plain Telegram origin WITHOUT calling `parseChannelOrigin`, so the
|
|
19
|
+
* Telegram-only hot path is byte-identical.
|
|
20
|
+
*
|
|
21
|
+
* This runs with `BUZZ_ENABLED` UNSET (the default for every Telegram-only
|
|
22
|
+
* agent), i.e. the gate is OFF. The OUTCOME it pins: a turn whose rawContent
|
|
23
|
+
* carries a REAL buzz `<channel source="buzz" …>` envelope is STILL stamped
|
|
24
|
+
* `telegram` with no coords — proving the parser was not consulted. If someone
|
|
25
|
+
* deleted the gate, the parser would run on this same input and stamp
|
|
26
|
+
* `originChannel: 'buzz'` with coords, and this test would fail. That makes it a
|
|
27
|
+
* true regression guard for the flag-off invariant, not a code-path assertion.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// A real captured buzz double-wrap envelope (same fixture as channel-route T-1c).
|
|
31
|
+
const CHAN = '6d18fdfe-601b-4e6c-82b5-aed8ac002dd4'
|
|
32
|
+
const EVT = '5e472ba250c45ea6f609f71d05e979a6992670ab12fd07781096bdcee6458c6b'
|
|
33
|
+
const PUB = 'fc97c126b783147458e8ea640cd714af5f2a2dd1dc39b27afc3b013df24faf1b'
|
|
34
|
+
const BUZZ_RAW =
|
|
35
|
+
`<channel source="switchroom-telegram" source="buzz" buzz_channel_id="${CHAN}" ` +
|
|
36
|
+
`buzz_event_id="${EVT}" buzz_pubkey="${PUB}" buzz_thread_root="${EVT}" user="buzz:fc97…af1b">\n` +
|
|
37
|
+
`<channel source="buzz" buzz_channel_id="${CHAN}" buzz_event_id="${EVT}" buzz_pubkey="${PUB}" ` +
|
|
38
|
+
`buzz_thread_root="${EVT}" user="buzz:fc97…af1b">[canary] inbound-path test</channel>\n</channel>`
|
|
39
|
+
|
|
40
|
+
const CHAT = '1001'
|
|
41
|
+
|
|
42
|
+
function makeStreamDeps(): { deps: StreamRenderDeps; getTurn: () => CurrentTurn | null } {
|
|
43
|
+
let curTurn: CurrentTurn | null = null
|
|
44
|
+
const key = (c: string, t?: number | null) => `${c}:${t ?? 'main'}`
|
|
45
|
+
const noop = () => {}
|
|
46
|
+
const fakeEA = {
|
|
47
|
+
claimOrDowngradePing: (_i: unknown, _s: unknown, _a: unknown, disabled: () => void) => disabled(),
|
|
48
|
+
markSubstantiveFinalDelivered: (fn: () => void) => fn(),
|
|
49
|
+
finalizeCard: (fn: () => void) => fn(),
|
|
50
|
+
}
|
|
51
|
+
const deps = {
|
|
52
|
+
ANSWER_LANE: { visibleEnabled: false } as unknown,
|
|
53
|
+
CAPTURED_PROSE_DELIVERY_ENABLED: false,
|
|
54
|
+
CONTEXT_EXHAUSTION_COOLDOWN_MS: 600000,
|
|
55
|
+
DELIVERY_CONFIRM_ENABLED: false,
|
|
56
|
+
FEED_REOPEN_AFTER_ACK_ENABLED: false,
|
|
57
|
+
HANDBACK_PRETURN_ENABLED: false,
|
|
58
|
+
HISTORY_ENABLED: false,
|
|
59
|
+
LIVENESS_TERMINAL_HONESTY: true,
|
|
60
|
+
OBLIGATION_LEDGER_ENABLED: false,
|
|
61
|
+
ORPHANED_REPLY_STREAM_WINDOW_MS: 120000,
|
|
62
|
+
SILENCE_LIVENESS_PRODUCTION: false,
|
|
63
|
+
STATE_DIR: tmpdir(),
|
|
64
|
+
TURN_FLUSH_SAFETY_ENABLED: true,
|
|
65
|
+
TURN_PREVIEW_MAX: 200,
|
|
66
|
+
activeDraftStreams: new Map(),
|
|
67
|
+
activeStatusReactions: new Map(),
|
|
68
|
+
activeTurnStartedAt: new Map(),
|
|
69
|
+
backstopDeliveryLedger: new BackstopDeliveryLedger(),
|
|
70
|
+
bot: { api: {} },
|
|
71
|
+
flushedTurnSupersede: new FlushedTurnSupersedeRegistry(),
|
|
72
|
+
idleTracker: { noteEvent: noop },
|
|
73
|
+
lastPtyPreviewByChat: new Map(),
|
|
74
|
+
obligationLedger: { close: noop, noteTurnEnded: noop },
|
|
75
|
+
outboundDedup: new OutboundDedupCache(),
|
|
76
|
+
pendingCrossTurnGate: new Map(),
|
|
77
|
+
preambleSuppressor: { dropNow: noop, flushNow: noop, onText: noop, onTool: noop, reset: noop },
|
|
78
|
+
progressDriver: null,
|
|
79
|
+
reactionTransitionCounts: new Map(),
|
|
80
|
+
suppressPtyPreview: new Set(),
|
|
81
|
+
toolFlightTracker: { inFlightCount: () => 0 },
|
|
82
|
+
deliveryQueue: {},
|
|
83
|
+
handbackPreturnSignal: { tryAdopt: () => null },
|
|
84
|
+
sessionModelSource: { noteTranscriptModel: noop },
|
|
85
|
+
typingWrapper: { drainAll: noop, onToolResult: noop, onToolUse: noop },
|
|
86
|
+
robustApiCall: (fn: () => Promise<unknown>) => fn(),
|
|
87
|
+
swallowingApiCall: async (fn: () => Promise<unknown>) => { try { return await fn() } catch { return undefined } },
|
|
88
|
+
getCurrentTurn: () => curTurn,
|
|
89
|
+
getLastContextExhaustionWarningAt: () => 0,
|
|
90
|
+
setLastContextExhaustionWarningAt: noop,
|
|
91
|
+
getPendingPtyPartial: () => null,
|
|
92
|
+
setPendingPtyPartial: noop,
|
|
93
|
+
setCurrentTurn: (t: CurrentTurn) => { curTurn = t },
|
|
94
|
+
cardDrainGate: (_t: unknown, _ea: unknown, run: () => void) => run(),
|
|
95
|
+
clearActivitySummary: noop,
|
|
96
|
+
clearAnswerReadyFlushTimeout: noop,
|
|
97
|
+
closeActivityLane: noop,
|
|
98
|
+
closeProgressLane: noop,
|
|
99
|
+
completeProgressCardTurn: null,
|
|
100
|
+
composeTurnActivity: () => null,
|
|
101
|
+
confirmMemoryLegibility: noop,
|
|
102
|
+
deliverAnswer: async () => ({ sentIds: [4242], chunkCount: 1, delivered: true, exhausted: false }),
|
|
103
|
+
deliverCapturedProse: async () => {},
|
|
104
|
+
drainActivitySummary: async () => {},
|
|
105
|
+
emissionAuthorityFor: () => fakeEA,
|
|
106
|
+
emitTurnRecord: noop,
|
|
107
|
+
endCurrentTurnAtomic: () => null,
|
|
108
|
+
extractUserPromptPreview: () => null,
|
|
109
|
+
finalizeStatusReaction: noop,
|
|
110
|
+
flushPendingNarrativeAtTurnEnd: noop,
|
|
111
|
+
getPinnedProgressCardMessageId: null,
|
|
112
|
+
handlePtyPartial: noop,
|
|
113
|
+
isDmChatId: () => true,
|
|
114
|
+
isLegitimatelyWorking: () => false,
|
|
115
|
+
makeNarrativeGate: () => ({ show: noop, stage: noop, resolveOnTool: noop, flushAtTurnEnd: noop, teardown: noop }),
|
|
116
|
+
promoteQueuedStatus: noop,
|
|
117
|
+
purgeReactionTracking: noop,
|
|
118
|
+
redactOutboundText: (t: string) => redact(t),
|
|
119
|
+
rememberRecentTurn: noop,
|
|
120
|
+
resetAnswerReadyFlushTimeout: noop,
|
|
121
|
+
resetOrphanedReplyTimeout: noop,
|
|
122
|
+
resolvePendingNarrativeOnTool: noop,
|
|
123
|
+
scheduleEarlyLivenessOpen: noop,
|
|
124
|
+
stagePendingNarrative: noop,
|
|
125
|
+
startTurnTypingLoop: noop,
|
|
126
|
+
statusKey: key,
|
|
127
|
+
streamKey: key,
|
|
128
|
+
surfaceMemoryLegibility: noop,
|
|
129
|
+
turnLiveForItsTopic: () => true,
|
|
130
|
+
turnsDb: null,
|
|
131
|
+
unpinProgressCardForChat: null,
|
|
132
|
+
} as unknown as StreamRenderDeps
|
|
133
|
+
return { deps, getTurn: () => curTurn }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
describe('MINOR-1 — origin stamp gate OFF (default): parser is not consulted', () => {
|
|
137
|
+
it('stamps a buzz-enveloped turn as TELEGRAM when BUZZ_ENABLED is unset', () => {
|
|
138
|
+
// Guard: the process must actually be on the flag-off path for this to mean
|
|
139
|
+
// what it claims. (Every Telegram-only agent runs exactly here.)
|
|
140
|
+
expect(process.env.BUZZ_ENABLED === '1' || process.env.BUZZ_ENABLED === 'true').toBe(false)
|
|
141
|
+
|
|
142
|
+
const { deps, getTurn } = makeStreamDeps()
|
|
143
|
+
handleSessionEvent(deps, {
|
|
144
|
+
kind: 'enqueue',
|
|
145
|
+
chatId: CHAT,
|
|
146
|
+
messageId: null,
|
|
147
|
+
threadId: null,
|
|
148
|
+
rawContent: BUZZ_RAW,
|
|
149
|
+
} as unknown as Parameters<typeof handleSessionEvent>[1])
|
|
150
|
+
|
|
151
|
+
const turn = getTurn()
|
|
152
|
+
expect(turn).not.toBeNull()
|
|
153
|
+
// The OUTCOME: despite a real buzz envelope, the gate-off ctor defaulted to
|
|
154
|
+
// telegram WITHOUT calling parseChannelOrigin. Removing the gate would make
|
|
155
|
+
// this 'buzz' with coords — the regression this test exists to catch.
|
|
156
|
+
expect(turn!.originChannel).toBe('telegram')
|
|
157
|
+
expect(turn!.buzzCoords).toBeUndefined()
|
|
158
|
+
})
|
|
159
|
+
})
|