fix: align SDK wait and protocol contracts

This commit is contained in:
Peter Steinberger
2026-04-29 23:58:38 +01:00
parent 204d200be3
commit 29de89a8d9
5 changed files with 52 additions and 6 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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") {

View File

@@ -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" },