mixdog 0.9.59 → 0.9.61
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/package.json
CHANGED
|
@@ -409,6 +409,32 @@ test('abort before a newline settles the accumulated partial response', async ()
|
|
|
409
409
|
assert.equal(assistants[0].text, 'partial without newline');
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
+
test('abort after tool boundaries does not replay committed progress as one giant assistant row', async () => {
|
|
413
|
+
const harness = makeTurnHarness(async (_text, options) => {
|
|
414
|
+
options.onAssistantText('first progress');
|
|
415
|
+
await options.onToolCall(1, [{
|
|
416
|
+
id: 'call_1',
|
|
417
|
+
name: 'shell',
|
|
418
|
+
input: { command: 'first' },
|
|
419
|
+
}]);
|
|
420
|
+
options.onToolResult({ tool_call_id: 'call_1', content: 'ok' });
|
|
421
|
+
options.onAssistantText('second progress');
|
|
422
|
+
await options.onToolCall(2, [{
|
|
423
|
+
id: 'call_2',
|
|
424
|
+
name: 'shell',
|
|
425
|
+
input: { command: 'second' },
|
|
426
|
+
}]);
|
|
427
|
+
const error = new Error('interrupted');
|
|
428
|
+
error.name = 'SessionClosedError';
|
|
429
|
+
throw error;
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
assert.equal(await harness.runTurn('go'), 'cancelled');
|
|
433
|
+
assert.equal(harness.getState().streamingTail, null);
|
|
434
|
+
const assistants = harness.getState().items.filter((item) => item.kind === 'assistant');
|
|
435
|
+
assert.deepEqual(assistants.map((item) => item.text), ['first progress', 'second progress']);
|
|
436
|
+
});
|
|
437
|
+
|
|
412
438
|
// Codifies pre-existing parity: the old in-items completed-line snapshot survived starved recovery.
|
|
413
439
|
test('starved Esc recovery settles a non-empty tail exactly once', async () => {
|
|
414
440
|
let state = {
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -27537,9 +27537,9 @@ function createRunTurn(bag) {
|
|
|
27537
27537
|
flushStreamBatch();
|
|
27538
27538
|
if (error?.name === "SessionClosedError") {
|
|
27539
27539
|
cancelled = true;
|
|
27540
|
-
if (
|
|
27541
|
-
const id = currentAssistantId || ensureAssistant(
|
|
27542
|
-
settleStreamingTail(id, { text: currentAssistantText
|
|
27540
|
+
if (currentAssistantText.trim()) {
|
|
27541
|
+
const id = currentAssistantId || ensureAssistant(currentAssistantText);
|
|
27542
|
+
settleStreamingTail(id, { text: currentAssistantText });
|
|
27543
27543
|
}
|
|
27544
27544
|
flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true, cancelled: true });
|
|
27545
27545
|
finalizeToolHeaders();
|
package/src/tui/engine/turn.mjs
CHANGED
|
@@ -1278,9 +1278,13 @@ export function createRunTurn(bag) {
|
|
|
1278
1278
|
flushStreamBatch(); // ensure any batched text lands before the error notice
|
|
1279
1279
|
if (error?.name === 'SessionClosedError') {
|
|
1280
1280
|
cancelled = true;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1281
|
+
// Tool boundaries already sealed prior progress segments into their
|
|
1282
|
+
// own rows. On abort, preserve only the still-open segment; replaying
|
|
1283
|
+
// turn-global assistantText creates one giant duplicate after the
|
|
1284
|
+
// cancelled tool card.
|
|
1285
|
+
if (currentAssistantText.trim()) {
|
|
1286
|
+
const id = currentAssistantId || ensureAssistant(currentAssistantText);
|
|
1287
|
+
settleStreamingTail(id, { text: currentAssistantText });
|
|
1284
1288
|
}
|
|
1285
1289
|
// Finalize pending tool cards so they don't stay "Running..." forever
|
|
1286
1290
|
// after cancellation. Without this, the spinner vanishes and TurnDone
|