mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
Merge 12c38dbdf2 into 53fb175855
This commit is contained in:
commit
bfc04a2ecd
2 changed files with 73 additions and 1 deletions
|
|
@ -782,7 +782,16 @@ async function consumeDaemonRun({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endStatus === 'canceled') return;
|
if (endStatus === 'canceled') {
|
||||||
|
// OpenCode and other agents can emit useful output before the daemon
|
||||||
|
// SIGTERM's the child (shutdown, user cancel, long-run timeout). The
|
||||||
|
// run status is still `canceled`, but abandoning `acc` here drops the
|
||||||
|
// assistant text and skips the onDone file-diff path in ProjectView.
|
||||||
|
if (acc.trim()) {
|
||||||
|
handlers.onDone(acc);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Trust the server's authoritative success declaration. When the server
|
// Trust the server's authoritative success declaration. When the server
|
||||||
// explicitly sets `status: 'succeeded'` (either in the SSE end payload
|
// explicitly sets `status: 'succeeded'` (either in the SSE end payload
|
||||||
|
|
|
||||||
|
|
@ -709,6 +709,69 @@ describe('streamViaDaemon', () => {
|
||||||
expect(handlers.onDone).not.toHaveBeenCalled();
|
expect(handlers.onDone).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves streamed output when a run ends as canceled', async () => {
|
||||||
|
const handlers = createDaemonHandlers();
|
||||||
|
vi.stubGlobal(
|
||||||
|
'fetch',
|
||||||
|
vi.fn()
|
||||||
|
.mockResolvedValueOnce(jsonResponse({ runId: 'run-1' }))
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
sseResponse(
|
||||||
|
[
|
||||||
|
'event: stdout',
|
||||||
|
'data: {"chunk":"partial output"}',
|
||||||
|
'',
|
||||||
|
'event: end',
|
||||||
|
'data: {"code":null,"signal":"SIGTERM","status":"canceled"}',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await streamViaDaemon({
|
||||||
|
agentId: 'mock',
|
||||||
|
history: [{ id: '1', role: 'user', content: 'hello' }],
|
||||||
|
systemPrompt: '',
|
||||||
|
signal: new AbortController().signal,
|
||||||
|
handlers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handlers.onDone).toHaveBeenCalledWith('partial output');
|
||||||
|
expect(handlers.onError).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not call onDone when a canceled run produced no output', async () => {
|
||||||
|
const handlers = createDaemonHandlers();
|
||||||
|
vi.stubGlobal(
|
||||||
|
'fetch',
|
||||||
|
vi.fn()
|
||||||
|
.mockResolvedValueOnce(jsonResponse({ runId: 'run-1' }))
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
sseResponse(
|
||||||
|
[
|
||||||
|
'event: end',
|
||||||
|
'data: {"code":null,"signal":"SIGTERM","status":"canceled"}',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await streamViaDaemon({
|
||||||
|
agentId: 'mock',
|
||||||
|
history: [{ id: '1', role: 'user', content: 'hello' }],
|
||||||
|
systemPrompt: '',
|
||||||
|
signal: new AbortController().signal,
|
||||||
|
handlers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handlers.onDone).not.toHaveBeenCalled();
|
||||||
|
expect(handlers.onError).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps the daemon run alive when the browser-side stream aborts', async () => {
|
it('keeps the daemon run alive when the browser-side stream aborts', async () => {
|
||||||
const handlers = createDaemonHandlers();
|
const handlers = createDaemonHandlers();
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue