From 29de89a8d98cdfb90fbe93922077ea821bafc9b2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Apr 2026 23:58:38 +0100 Subject: [PATCH] fix: align SDK wait and protocol contracts --- .../OpenClawProtocol/GatewayModels.swift | 4 +-- .../OpenClawProtocol/GatewayModels.swift | 4 +-- docs/reference/openclaw-sdk-api-design.md | 7 +++- packages/sdk/src/client.ts | 8 ++++- packages/sdk/src/index.test.ts | 35 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift index f427c918b1b..9f83737c23a 100644 --- a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift @@ -1851,11 +1851,11 @@ public struct SessionsMessagesUnsubscribeParams: Codable, Sendable { } public struct SessionsAbortParams: Codable, Sendable { - public let key: String + public let key: String? public let runid: String? public init( - key: String, + key: String?, runid: String?) { self.key = key diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index f427c918b1b..9f83737c23a 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -1851,11 +1851,11 @@ public struct SessionsMessagesUnsubscribeParams: Codable, Sendable { } public struct SessionsAbortParams: Codable, Sendable { - public let key: String + public let key: String? public let runid: String? public init( - key: String, + key: String?, runid: String?) { self.key = key diff --git a/docs/reference/openclaw-sdk-api-design.md b/docs/reference/openclaw-sdk-api-design.md index cc4107e8eb3..da94270de55 100644 --- a/docs/reference/openclaw-sdk-api-design.md +++ b/docs/reference/openclaw-sdk-api-design.md @@ -146,7 +146,7 @@ have to parse `raw` for normal UI. ```typescript type RunResult = { runId: string; - status: "completed" | "failed" | "cancelled" | "timed_out"; + status: "accepted" | "completed" | "failed" | "cancelled" | "timed_out"; sessionId?: string; sessionKey?: string; taskId?: string; @@ -172,6 +172,11 @@ shape, so current lifecycle-backed runs usually report epoch millisecond numbers while adapters may still surface ISO strings. Rich UI, tool traces, and runtime-native details belong in events and artifacts. +`accepted` is a non-terminal wait result: it means the Gateway wait deadline +expired before the run produced a lifecycle end/error. It must not be treated as +`timed_out`; `timed_out` is reserved for a run that exceeded its own runtime +timeout. + ## Approvals and questions Approvals must be first-class because coding agents constantly cross safety diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 4a4faf166e0..272633b38a1 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -64,7 +64,13 @@ function runStatusFromWaitPayload(payload: unknown): RunResult["status"] { if (status === "ok" || status === "completed" || status === "succeeded") { return "completed"; } - if (status === "timeout" || status === "timed_out") { + if (status === "timeout") { + if (stopReason === "timeout" || stopReason === "timed_out" || record.aborted === true) { + return "timed_out"; + } + return "accepted"; + } + if (status === "timed_out") { return "timed_out"; } if (status === "accepted") { diff --git a/packages/sdk/src/index.test.ts b/packages/sdk/src/index.test.ts index 0960827e636..0952cf62ef2 100644 --- a/packages/sdk/src/index.test.ts +++ b/packages/sdk/src/index.test.ts @@ -136,6 +136,41 @@ describe("OpenClaw SDK", () => { }); }); + it("keeps wait-only deadlines non-terminal", async () => { + const transport = new FakeTransport({ + "agent.wait": { status: "timeout", runId: "run_still_active" }, + }); + const oc = new OpenClaw({ transport }); + + const result = await oc.runs.wait("run_still_active"); + + expect(result).toMatchObject({ + runId: "run_still_active", + status: "accepted", + }); + expect(result.error).toBeUndefined(); + }); + + it("maps terminal runtime timeout snapshots to timed_out", async () => { + const transport = new FakeTransport({ + "agent.wait": { + status: "timeout", + runId: "run_timed_out", + stopReason: "timeout", + error: "agent runtime timeout", + }, + }); + const oc = new OpenClaw({ transport }); + + const result = await oc.runs.wait("run_timed_out"); + + expect(result).toMatchObject({ + runId: "run_timed_out", + status: "timed_out", + error: { message: "agent runtime timeout" }, + }); + }); + it("splits provider-qualified model refs and rejects unsupported run options", async () => { const transport = new FakeTransport({ agent: { status: "accepted", runId: "run_openrouter" },