fix: 消除会话列表解析阻塞并按需加载工具详情
This commit is contained in:
@@ -49,7 +49,7 @@ async function connect(port) {
|
||||
assert(auth.success);
|
||||
return { ws, messages, token: auth.token };
|
||||
}
|
||||
function createFixture(root) {
|
||||
function createFixture(root, options = {}) {
|
||||
const sessionsDir = path.join(root, 'sessions');
|
||||
const homeDir = path.join(root, 'home');
|
||||
fs.mkdirSync(sessionsDir, { recursive: true });
|
||||
@@ -66,6 +66,18 @@ function createFixture(root) {
|
||||
return session;
|
||||
}
|
||||
seed(A); seed(B); seed('http-corrupt-chunk'); seed('http-corrupt-manifest');
|
||||
if (options.largeTools !== false) {
|
||||
const large = seed('http-large-tools', 17);
|
||||
large.messages[15].toolCalls = Array.from({ length: 100 }, (_, index) => ({
|
||||
id: 'large-tool-' + index, name: 'exec_command', kind: 'command_execution', done: true,
|
||||
input: { command: '读取完整工具记录', payload: '输入'.repeat(10000) },
|
||||
result: '结果起点' + '完整输出'.repeat(10000) + '结果终点', meta: { exitCode: 0 },
|
||||
}));
|
||||
large.messages[3].toolCalls = [{ id: 'older-tool', name: 'read_file', done: true,
|
||||
input: { path: '/隔离夹具' }, result: '早期完整结果'.repeat(10000) }];
|
||||
delete large.messages[3].id;
|
||||
store.save(large);
|
||||
}
|
||||
const corruptPath = path.join(sessionsDir, '_history', 'http-corrupt-chunk');
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(corruptPath, 'manifest.json'), 'utf8'));
|
||||
fs.writeFileSync(path.join(corruptPath, 'chunks', manifest.entries.at(-1).file), '{破损块');
|
||||
@@ -97,6 +109,8 @@ function createFixture(root) {
|
||||
assert(mock.includes(timer));
|
||||
fs.writeFileSync(mockPath, mock.replace(timer, [
|
||||
"send({method:'item/agentMessage/delta',params:{threadId:thread.id,turnId,itemId:'agent-msg',delta:" + JSON.stringify(STREAM) + '}});',
|
||||
"send({method:'item/started',params:{threadId:thread.id,turnId,item:{id:'large-runtime-tool',type:'commandExecution',command:'运行工具夹具',status:'inProgress'}}});",
|
||||
"send({method:'item/completed',params:{threadId:thread.id,turnId,item:{id:'large-runtime-tool',type:'commandExecution',command:'运行工具夹具',aggregatedOutput:'运行结果起点'+'完整运行输出'.repeat(100000)+'运行结果终点',exitCode:0,status:'completed'}}});",
|
||||
"thread.timer = setInterval(() => { if (!require('fs').existsSync(" + JSON.stringify(completeFlag) + ')) return;',
|
||||
'clearInterval(thread.timer); completeTurn(thread, turnId, text); }, 50);',
|
||||
].join('\n')));
|
||||
@@ -150,6 +164,21 @@ async function runApi(port, fixture) {
|
||||
assert.equal(result.payload.sessionId, id);
|
||||
}
|
||||
assert.equal((await query('http-legacy-only')).payload.messages.length, 12);
|
||||
const large = (await query('http-large-tools')).payload;
|
||||
const summary = large.messages.find(message => message.toolCalls?.length === 100);
|
||||
assert(summary, '首屏必须保留全部100个工具概要');
|
||||
assert(Buffer.byteLength(JSON.stringify(large)) < 100000, '多兆工具输出不能绑在首屏');
|
||||
assert(!summary.toolCalls[0].result);
|
||||
const detailSuffix = ref => '?toolDetail=' + encodeURIComponent(JSON.stringify(ref));
|
||||
const detail = await query('http-large-tools', detailSuffix(summary.toolCalls[0].detailRef));
|
||||
assert.deepEqual(detail.payload.tool, fixture.store.load('http-large-tools').messages[15].toolCalls[0], '输入输出详情必须完整');
|
||||
assert.equal((await query(B, detailSuffix(summary.toolCalls[0].detailRef))).response.status, 400, '不能跨会话读取');
|
||||
assert.equal((await query('http-large-tools', detailSuffix({ ...summary.toolCalls[0].detailRef, signature: '过期' }))).response.status, 409);
|
||||
assert.equal((await query('http-large-tools', '?toolDetail=broken')).response.status, 400);
|
||||
assert.equal((await query('http-large-tools', detailSuffix(summary.toolCalls[0].detailRef), false)).response.status, 401);
|
||||
const older = (await query('http-large-tools', '/history?before=' + large.historyCursor)).payload;
|
||||
const olderTool = older.messages.find(message => message.toolCalls?.length)?.toolCalls[0];
|
||||
assert.equal((await query('http-large-tools', detailSuffix(olderTool.detailRef))).payload.tool.result, '早期完整结果'.repeat(10000));
|
||||
connection = await connect(port);
|
||||
connection.ws.send(JSON.stringify({ type: 'subscribe_session', sessionId: B,
|
||||
requestId: 'subscribe-before-commit', historyRevision: first.payload.historyRevision }));
|
||||
@@ -174,6 +203,18 @@ async function runApi(port, fixture) {
|
||||
assert.equal(running.historyPending, false);
|
||||
assert.equal(running.messages.length, 12);
|
||||
assert.equal(running.runtime.type, 'resume_generating');
|
||||
const runtime = await waitFor(async () => {
|
||||
const snapshot = (await query(A)).payload.runtime;
|
||||
return snapshot?.toolCalls?.some(tool => tool.kind === 'command_execution' && tool.done && tool.detailRef) ? snapshot : null;
|
||||
}, '运行工具概要缺失');
|
||||
assert(Buffer.byteLength(JSON.stringify(runtime)) < 10000, '运行快照不能捆绑完整工具输出');
|
||||
const runtimeTool = runtime.toolCalls.find(tool => tool.kind === 'command_execution' && tool.done && tool.detailRef);
|
||||
const runtimeDetail = (await query(A, detailSuffix(runtimeTool.detailRef))).payload.tool;
|
||||
assert(runtimeDetail.result.includes('运行结果终点'), '运行工具详情不能被预览限制裁剪');
|
||||
connection.ws.send(JSON.stringify({ type: 'subscribe_session', sessionId: A, requestId: 'compact-runtime' }));
|
||||
const resumed = await waitFor(() => connection.messages.find(msg => msg.type === 'resume_generating' && msg.requestId === 'compact-runtime'), '订阅运行快照缺失');
|
||||
assert(Buffer.byteLength(JSON.stringify(resumed)) < 10000, 'WS不能再次重放完整工具输出');
|
||||
assert(resumed.toolCalls.some(tool => tool.detailRef));
|
||||
assert(!connection.messages.some(msg => msg.type === 'session_history_chunk'));
|
||||
return { connection, token, query };
|
||||
} catch (error) {
|
||||
@@ -216,4 +257,6 @@ async function main() {
|
||||
log.end();
|
||||
}
|
||||
}
|
||||
main().catch(error => { console.error(error.stack || error); process.exitCode = 1; });
|
||||
if (require.main === module) main().catch(error => { console.error(error.stack || error); process.exitCode = 1; });
|
||||
|
||||
module.exports = { createFixture, waitFor, freePort, stopChild, connect, sleep, PASSWORD };
|
||||
|
||||
Reference in New Issue
Block a user