You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/08/29 00:53:04 UTC
(maka) branch main updated: fix(desktop): offer safe resume after completed tool timeout (#4075)
This is an automated email from the ASF dual-hosted git repository.
M4n5ter pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new b699a2e4b fix(desktop): offer safe resume after completed tool timeout (#4075)
b699a2e4b is described below
commit b699a2e4b39c8b3d95fe60aecbeac5c86c880f15
Author: Guangdong Liu <li...@gmail.com>
AuthorDate: Sat Aug 29 08:52:59 2026 +0800
fix(desktop): offer safe resume after completed tool timeout (#4075)
---
.../src/main/__tests__/interrupted-resume.test.ts | 104 +++++++++++++++++++++
.../__tests__/session-status-presentation.test.ts | 1 +
apps/desktop/src/renderer/interrupted-resume.ts | 18 +++-
3 files changed, 119 insertions(+), 4 deletions(-)
diff --git a/apps/desktop/src/main/__tests__/interrupted-resume.test.ts b/apps/desktop/src/main/__tests__/interrupted-resume.test.ts
new file mode 100644
index 000000000..5a066e95a
--- /dev/null
+++ b/apps/desktop/src/main/__tests__/interrupted-resume.test.ts
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { materializeTurns } from '@maka/ui';
+import { latestInterruptedResumeTurnId } from '../../renderer/interrupted-resume.js';
+
+describe('latest interrupted resume candidate', () => {
+ it('recognizes a timeout after a completed tool result', () => {
+ assert.equal(
+ latestInterruptedResumeTurnId([
+ {
+ turnId: 'turn-1',
+ status: 'failed',
+ errorClass: 'timeout',
+ tools: [{ status: 'completed' }],
+ },
+ ]),
+ 'turn-1',
+ );
+ });
+
+ it('does not offer continuation for an incomplete or errored tool', () => {
+ assert.equal(
+ latestInterruptedResumeTurnId([
+ {
+ turnId: 'turn-1',
+ status: 'failed',
+ errorClass: 'timeout',
+ tools: [{ status: 'running' }],
+ },
+ ]),
+ undefined,
+ );
+ assert.equal(
+ latestInterruptedResumeTurnId([
+ {
+ turnId: 'turn-1',
+ status: 'failed',
+ errorClass: 'timeout',
+ tools: [{ status: 'completed' }, { status: 'interrupted' }],
+ },
+ ]),
+ undefined,
+ );
+ assert.equal(
+ latestInterruptedResumeTurnId([
+ {
+ turnId: 'turn-1',
+ status: 'failed',
+ errorClass: 'timeout',
+ tools: [{ status: 'errored' }],
+ },
+ ]),
+ undefined,
+ );
+ });
+
+ it('rejects the mixed shape produced by materializeTurns', () => {
+ const [turn] = materializeTurns([
+ { type: 'user', id: 'user-1', turnId: 'turn-1', ts: 1, text: 'inspect' },
+ {
+ type: 'turn_state',
+ id: 'state-1',
+ turnId: 'turn-1',
+ ts: 2,
+ status: 'failed',
+ errorClass: 'timeout',
+ partialOutputRetained: false,
+ },
+ { type: 'tool_call', id: 'call-1', turnId: 'turn-1', ts: 3, toolName: 'Read', args: {} },
+ {
+ type: 'tool_result',
+ id: 'result-1',
+ turnId: 'turn-1',
+ ts: 4,
+ toolUseId: 'call-1',
+ isError: false,
+ content: { kind: 'text', text: 'ok' },
+ },
+ { type: 'tool_call', id: 'call-2', turnId: 'turn-1', ts: 5, toolName: 'Read', args: {} },
+ ]);
+
+ assert.deepEqual(turn?.tools.map((tool) => tool.status), ['completed', 'interrupted']);
+ assert.equal(latestInterruptedResumeTurnId(turn ? [turn] : []), undefined);
+ });
+});
diff --git a/apps/desktop/src/main/__tests__/session-status-presentation.test.ts b/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
index 8b193d7d2..7f2473c70 100644
--- a/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
+++ b/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
@@ -94,4 +94,5 @@ describe('failed turn execution state', () => {
/éšććç/,
);
});
+
});
diff --git a/apps/desktop/src/renderer/interrupted-resume.ts b/apps/desktop/src/renderer/interrupted-resume.ts
index 5760fc0b6..d3421aa6d 100644
--- a/apps/desktop/src/renderer/interrupted-resume.ts
+++ b/apps/desktop/src/renderer/interrupted-resume.ts
@@ -21,14 +21,24 @@ export interface InterruptedResumeTurn {
turnId: string;
status: string;
errorClass?: string;
+ /** Tool activity already has a durable result in the rendered Turn. */
+ tools?: readonly { status: string }[];
}
export function latestInterruptedResumeTurnId(
turns: readonly InterruptedResumeTurn[],
): string | undefined {
const latestTurn = turns.at(-1);
- return latestTurn?.status === 'failed'
- && latestTurn.errorClass?.toLowerCase() === 'app_restarted'
- ? latestTurn.turnId
- : undefined;
+ if (latestTurn?.status !== 'failed') return undefined;
+ const errorClass = latestTurn.errorClass?.toLowerCase();
+ if (errorClass === 'app_restarted') return latestTurn.turnId;
+ if (
+ errorClass?.includes('timeout') &&
+ latestTurn.tools !== undefined &&
+ latestTurn.tools.length > 0 &&
+ latestTurn.tools.every((tool) => tool.status === 'completed')
+ ) {
+ return latestTurn.turnId;
+ }
+ return undefined;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org