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