ClaudeCliProtocol.mock.test.js
3.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* ClaudeCliProtocol · mock 模式 smoke test
*/
const { ClaudeCliProtocol } = require('./ClaudeCliProtocol');
describe('ClaudeCliProtocol · mock mode', () => {
it('connect is no-op in mock mode', async () => {
const p = new ClaudeCliProtocol({ name: 'devloop-pm' }, { mock: true });
await p.connect(null, process.cwd());
expect(p.isConnected).toBe(true);
p.disconnect();
});
it('sendPrompt emits chunks + session/result from fixture', async () => {
const p = new ClaudeCliProtocol({ name: 'devloop-pm' }, { mock: true });
await p.connect(null, process.cwd());
const chunks = [];
let result = null;
p.on('session_update', (u) => {
if (u.update?.sessionUpdate === 'agent_message_chunk') {
chunks.push(u.update.content.text);
}
});
p.on('acp_event', (ev) => {
if (ev.method === 'session/result') result = ev.params;
});
await p.sendPrompt('please plan the work');
expect(chunks.length).toBeGreaterThan(0);
expect(result).not.toBeNull();
expect(result.mock).toBe(true);
expect(result.stop_reason).toBe('end_turn');
p.disconnect();
});
it('falls back to echo response when no fixture matches', async () => {
const p = new ClaudeCliProtocol(
{ name: 'no-such-agent-zzz' },
{ mock: true, mockFixtureDir: '/tmp/devloop-mock-empty-' + Date.now() },
);
await p.connect(null, process.cwd());
let result = null;
p.on('acp_event', (ev) => { if (ev.method === 'session/result') result = ev.params; });
await p.sendPrompt('hello');
expect(result.result).toMatch(/mock-ok/);
p.disconnect();
});
});
describe('ClaudeCliProtocol · idle timeout (real-CLI path, non-mock)', () => {
// 复现 T-mt5esage: 子进程已经进入 tool_use(吐出终态判词前的最后一步),
// 但之后再也没有任何 stdout(session/result 迟迟不来)——旧代码 IDLE_TIMEOUT_TOOL_MS=24h
// 会让 sendPrompt() 的 promise 挂 24 小时才被兜住;现在应在 15min 兜住。
it('rejects with a tool-mode timeout after 15min of silence following a tool_use start, not 24h', async () => {
vi.useFakeTimers();
try {
const p = new ClaudeCliProtocol({ name: 'devloop-coder' }, {});
// 伪造一个"已连接"的子进程:sendPrompt 只需要 _child.stdin.write 不抛错
p.isConnected = true;
p._child = { stdin: { write: () => {} } };
const promise = p.sendPrompt('run the tests');
const assertion = expect(promise).rejects.toThrow(/mode=tool/);
// 模拟收到 tool_use content_block_start → 切到 tool 态长超时
p._handleInnerStreamEvent({ type: 'content_block_start', content_block: { type: 'tool_use', id: 't1', name: 'Bash' } });
// 14min59s:还没到 15min 阈值,promise 不应 settle
await vi.advanceTimersByTimeAsync(14 * 60_000 + 59_000);
// 再推 2s 跨过 15min 边界,触发 fireTimeout
await vi.advanceTimersByTimeAsync(2_000);
await assertion;
} finally {
vi.useRealTimers();
}
});
});