switchroom 0.12.28 → 0.13.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 +81 -80
- package/dist/auth-broker/index.js +81 -80
- package/dist/cli/drive-write-pretool.mjs +10 -10
- package/dist/cli/skill-validate-pretool.mjs +72 -72
- package/dist/cli/switchroom.js +361 -357
- package/dist/host-control/main.js +100 -99
- package/dist/vault/approvals/kernel-server.js +83 -82
- package/dist/vault/broker/server.js +84 -83
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +112 -112
- package/telegram-plugin/dist/gateway/gateway.js +392 -208
- package/telegram-plugin/dist/server.js +160 -160
- package/telegram-plugin/draft-stream.ts +287 -11
- package/telegram-plugin/draft-transport.ts +50 -0
- package/telegram-plugin/gateway/gateway.ts +73 -10
- package/telegram-plugin/gateway/prefix-warmup.ts +123 -0
- package/telegram-plugin/stream-reply-handler.ts +3 -1
- package/telegram-plugin/tests/draft-stream.test.ts +453 -0
- package/telegram-plugin/tests/draft-transport.test.ts +70 -0
- package/telegram-plugin/tests/prefix-warmup.test.ts +175 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the prefix-cache warmup module.
|
|
3
|
+
*
|
|
4
|
+
* Per cold-start TTFO RFC Option A: fire a synthetic inbound on
|
|
5
|
+
* bridge-up so Anthropic's prefix cache is warm by the user's next
|
|
6
|
+
* real message. These tests pin: env gate, cooldown, missing-target
|
|
7
|
+
* skip, message shape (meta.source="warmup", correct text), and
|
|
8
|
+
* cooldown debounce across multiple bridge reconnects.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it, beforeEach, vi } from 'vitest'
|
|
12
|
+
import {
|
|
13
|
+
__resetForTests,
|
|
14
|
+
maybeFireWarmup,
|
|
15
|
+
WARMUP_TEXT,
|
|
16
|
+
} from '../gateway/prefix-warmup'
|
|
17
|
+
import type { WarmupCtx } from '../gateway/prefix-warmup'
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
__resetForTests()
|
|
21
|
+
delete process.env.SWITCHROOM_PREFIX_WARMUP
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function makeCtx(overrides?: Partial<WarmupCtx>): {
|
|
25
|
+
ctx: WarmupCtx
|
|
26
|
+
send: ReturnType<typeof vi.fn>
|
|
27
|
+
logs: string[]
|
|
28
|
+
} {
|
|
29
|
+
const send = vi.fn()
|
|
30
|
+
const logs: string[] = []
|
|
31
|
+
const ctx: WarmupCtx = {
|
|
32
|
+
selfAgent: 'test-agent',
|
|
33
|
+
client: { send, agentName: 'test-agent', id: 'c1', isAlive: () => true, lastHeartbeat: 0, close: () => {} } as never,
|
|
34
|
+
resolveBootTarget: () => ({ chatId: '12345', threadId: undefined }),
|
|
35
|
+
log: (l: string) => logs.push(l),
|
|
36
|
+
now: () => 1_000_000,
|
|
37
|
+
...overrides,
|
|
38
|
+
}
|
|
39
|
+
return { ctx, send, logs }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('maybeFireWarmup — env gate', () => {
|
|
43
|
+
it('skipped when SWITCHROOM_PREFIX_WARMUP is unset', () => {
|
|
44
|
+
const { ctx, send } = makeCtx()
|
|
45
|
+
expect(maybeFireWarmup(ctx)).toBe(false)
|
|
46
|
+
expect(send).not.toHaveBeenCalled()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('skipped when SWITCHROOM_PREFIX_WARMUP is 0', () => {
|
|
50
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '0'
|
|
51
|
+
const { ctx, send } = makeCtx()
|
|
52
|
+
expect(maybeFireWarmup(ctx)).toBe(false)
|
|
53
|
+
expect(send).not.toHaveBeenCalled()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('fires when SWITCHROOM_PREFIX_WARMUP=1', () => {
|
|
57
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
58
|
+
const { ctx, send } = makeCtx()
|
|
59
|
+
expect(maybeFireWarmup(ctx)).toBe(true)
|
|
60
|
+
expect(send).toHaveBeenCalledTimes(1)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('maybeFireWarmup — message shape', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('tags meta.source="warmup"', () => {
|
|
70
|
+
const { ctx, send } = makeCtx()
|
|
71
|
+
maybeFireWarmup(ctx)
|
|
72
|
+
const msg = send.mock.calls[0][0]
|
|
73
|
+
expect(msg.meta.source).toBe('warmup')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('uses synthetic messageId=0 and userId=0', () => {
|
|
77
|
+
const { ctx, send } = makeCtx()
|
|
78
|
+
maybeFireWarmup(ctx)
|
|
79
|
+
const msg = send.mock.calls[0][0]
|
|
80
|
+
expect(msg.messageId).toBe(0)
|
|
81
|
+
expect(msg.userId).toBe(0)
|
|
82
|
+
expect(msg.user).toBe('switchroom-warmup')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('text carries the NO_REPLY instruction', () => {
|
|
86
|
+
const { ctx, send } = makeCtx()
|
|
87
|
+
maybeFireWarmup(ctx)
|
|
88
|
+
const msg = send.mock.calls[0][0]
|
|
89
|
+
expect(msg.text).toBe(WARMUP_TEXT)
|
|
90
|
+
expect(msg.text).toContain('__WARMUP_PING__')
|
|
91
|
+
expect(msg.text).toContain('NO_REPLY')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('routes to the resolved boot chat', () => {
|
|
95
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
96
|
+
const { ctx, send } = makeCtx({
|
|
97
|
+
resolveBootTarget: () => ({ chatId: 'CHAT-9', threadId: 42 }),
|
|
98
|
+
})
|
|
99
|
+
maybeFireWarmup(ctx)
|
|
100
|
+
const msg = send.mock.calls[0][0]
|
|
101
|
+
expect(msg.chatId).toBe('CHAT-9')
|
|
102
|
+
expect(msg.threadId).toBe(42)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('omits threadId when boot target has none', () => {
|
|
106
|
+
const { ctx, send } = makeCtx()
|
|
107
|
+
maybeFireWarmup(ctx)
|
|
108
|
+
const msg = send.mock.calls[0][0]
|
|
109
|
+
expect(msg.threadId).toBeUndefined()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('maybeFireWarmup — cooldown', () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('second call within cooldown is skipped', () => {
|
|
119
|
+
const { ctx, send, logs } = makeCtx()
|
|
120
|
+
expect(maybeFireWarmup(ctx)).toBe(true)
|
|
121
|
+
// Same now() — well within cooldown.
|
|
122
|
+
expect(maybeFireWarmup(ctx)).toBe(false)
|
|
123
|
+
expect(send).toHaveBeenCalledTimes(1)
|
|
124
|
+
expect(logs.some((l) => l.includes('reason=cooldown'))).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('call after cooldown elapses fires again', () => {
|
|
128
|
+
let t = 1_000_000
|
|
129
|
+
const ctx = makeCtx({ now: () => t }).ctx
|
|
130
|
+
expect(maybeFireWarmup(ctx)).toBe(true)
|
|
131
|
+
t += 5 * 60_000 + 1 // just past cooldown
|
|
132
|
+
expect(maybeFireWarmup(ctx)).toBe(true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('cooldown is per-agent', () => {
|
|
136
|
+
const a = makeCtx({ selfAgent: 'agent-a' })
|
|
137
|
+
const b = makeCtx({ selfAgent: 'agent-b' })
|
|
138
|
+
expect(maybeFireWarmup(a.ctx)).toBe(true)
|
|
139
|
+
expect(maybeFireWarmup(b.ctx)).toBe(true)
|
|
140
|
+
expect(maybeFireWarmup(a.ctx)).toBe(false) // a in cooldown
|
|
141
|
+
expect(maybeFireWarmup(b.ctx)).toBe(false) // b in cooldown
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('maybeFireWarmup — missing boot target', () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('skipped when no boot chat resolves', () => {
|
|
151
|
+
const { ctx, send, logs } = makeCtx({ resolveBootTarget: () => null })
|
|
152
|
+
expect(maybeFireWarmup(ctx)).toBe(false)
|
|
153
|
+
expect(send).not.toHaveBeenCalled()
|
|
154
|
+
expect(logs.some((l) => l.includes('reason=no-boot-chat-target'))).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('maybeFireWarmup — send error handling', () => {
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
process.env.SWITCHROOM_PREFIX_WARMUP = '1'
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('caught send throw returns false and does not mark cooldown', () => {
|
|
164
|
+
const send = vi.fn(() => {
|
|
165
|
+
throw new Error('boom')
|
|
166
|
+
})
|
|
167
|
+
const ctx = makeCtx({
|
|
168
|
+
client: { send } as never,
|
|
169
|
+
}).ctx
|
|
170
|
+
expect(maybeFireWarmup(ctx)).toBe(false)
|
|
171
|
+
// Cooldown NOT recorded — next call should attempt again.
|
|
172
|
+
expect(maybeFireWarmup(ctx)).toBe(false) // still throws
|
|
173
|
+
expect(send).toHaveBeenCalledTimes(2)
|
|
174
|
+
})
|
|
175
|
+
})
|