@liy/agent-runner 0.1.0 → 0.2.1
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/README.md +4 -2
- package/dist/bin/agent-runner.d.ts +8 -0
- package/dist/bin/agent-runner.js +177 -53
- package/dist/bin/agent-runner.js.map +2 -2
- package/dist/harness-drivers/pi-rpc.d.ts +110 -0
- package/dist/harness-drivers/shared.d.ts +4 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +165 -51
- package/dist/index.js.map +2 -2
- package/package.json +14 -10
|
@@ -1,5 +1,115 @@
|
|
|
1
|
+
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import type { ResolvedAgentRunnerHarnessConfig } from "../config.js";
|
|
2
3
|
import type { HarnessSpawn, TaskAgentHarnessDriver } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pi extension runtime error event emitted by RPC mode.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Pi 0.74.0 emits this record from RPC mode but does not include it in the
|
|
9
|
+
* exported `AgentSessionEvent` union. Keep it structural so downstream Mote UI
|
|
10
|
+
* code can still switch on the upstream `extension_error` spelling.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface PiExtensionErrorEvent {
|
|
15
|
+
/**
|
|
16
|
+
* Upstream Pi event discriminator.
|
|
17
|
+
*/
|
|
18
|
+
type: "extension_error";
|
|
19
|
+
/**
|
|
20
|
+
* Extension file path that raised the error.
|
|
21
|
+
*/
|
|
22
|
+
extensionPath: string;
|
|
23
|
+
/**
|
|
24
|
+
* Extension event name being handled when the error occurred.
|
|
25
|
+
*/
|
|
26
|
+
event: string;
|
|
27
|
+
/**
|
|
28
|
+
* Human-readable extension error text.
|
|
29
|
+
*/
|
|
30
|
+
error: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Native Pi event forwarded by the Pi RPC harness driver.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export type PiEvent = AgentSessionEvent | PiExtensionErrorEvent;
|
|
38
|
+
/**
|
|
39
|
+
* Native Pi assistant-message stream event.
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export type PiAssistantMessageEvent = Extract<PiEvent, {
|
|
44
|
+
type: "message_update";
|
|
45
|
+
}>["assistantMessageEvent"];
|
|
46
|
+
/**
|
|
47
|
+
* Structural copy of Pi RPC extension UI requests.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* The Pi package exports this type from an internal path in 0.74.0 but not from
|
|
51
|
+
* its public package root. Mote forwards the request opaquely and does not
|
|
52
|
+
* implement response handling in this pass.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export type PiExtensionUiRequest = {
|
|
57
|
+
type: "extension_ui_request";
|
|
58
|
+
id: string;
|
|
59
|
+
method: "select";
|
|
60
|
+
title: string;
|
|
61
|
+
options: string[];
|
|
62
|
+
timeout?: number;
|
|
63
|
+
} | {
|
|
64
|
+
type: "extension_ui_request";
|
|
65
|
+
id: string;
|
|
66
|
+
method: "confirm";
|
|
67
|
+
title: string;
|
|
68
|
+
message: string;
|
|
69
|
+
timeout?: number;
|
|
70
|
+
} | {
|
|
71
|
+
type: "extension_ui_request";
|
|
72
|
+
id: string;
|
|
73
|
+
method: "input";
|
|
74
|
+
title: string;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
timeout?: number;
|
|
77
|
+
} | {
|
|
78
|
+
type: "extension_ui_request";
|
|
79
|
+
id: string;
|
|
80
|
+
method: "editor";
|
|
81
|
+
title: string;
|
|
82
|
+
prefill?: string;
|
|
83
|
+
} | {
|
|
84
|
+
type: "extension_ui_request";
|
|
85
|
+
id: string;
|
|
86
|
+
method: "notify";
|
|
87
|
+
message: string;
|
|
88
|
+
notifyType?: "info" | "warning" | "error";
|
|
89
|
+
} | {
|
|
90
|
+
type: "extension_ui_request";
|
|
91
|
+
id: string;
|
|
92
|
+
method: "setStatus";
|
|
93
|
+
statusKey: string;
|
|
94
|
+
statusText: string | undefined;
|
|
95
|
+
} | {
|
|
96
|
+
type: "extension_ui_request";
|
|
97
|
+
id: string;
|
|
98
|
+
method: "setWidget";
|
|
99
|
+
widgetKey: string;
|
|
100
|
+
widgetLines: string[] | undefined;
|
|
101
|
+
widgetPlacement?: "aboveEditor" | "belowEditor";
|
|
102
|
+
} | {
|
|
103
|
+
type: "extension_ui_request";
|
|
104
|
+
id: string;
|
|
105
|
+
method: "setTitle";
|
|
106
|
+
title: string;
|
|
107
|
+
} | {
|
|
108
|
+
type: "extension_ui_request";
|
|
109
|
+
id: string;
|
|
110
|
+
method: "set_editor_text";
|
|
111
|
+
text: string;
|
|
112
|
+
};
|
|
3
113
|
/**
|
|
4
114
|
* Create the Pi RPC driver adapter.
|
|
5
115
|
*
|
|
@@ -63,15 +63,16 @@ export declare class JsonlByteBufferParser {
|
|
|
63
63
|
*/
|
|
64
64
|
export declare function forwardJsonlByteStream(stream: Readable, onLine: (line: string) => void): Promise<void>;
|
|
65
65
|
/**
|
|
66
|
-
* Forward one generic harness output line as
|
|
66
|
+
* Forward one generic harness output line as redacted subprocess stream text.
|
|
67
67
|
*
|
|
68
68
|
* @param channel - Connected control channel.
|
|
69
|
+
* @param taskId - Runtime task id for control-channel correlation.
|
|
70
|
+
* @param harness - Harness driver id that produced the line.
|
|
69
71
|
* @param line - Raw harness output line.
|
|
70
72
|
* @param streamName - Source stream name.
|
|
71
73
|
* @param sanitizer - Output sanitizer.
|
|
72
|
-
* @param driver - Optional driver id to include in forwarded payloads.
|
|
73
74
|
*/
|
|
74
|
-
export declare function forwardHarnessLine(channel: ControlChannel, line: string, streamName: "stdout" | "stderr", sanitizer: HarnessOutputSanitizer
|
|
75
|
+
export declare function forwardHarnessLine(channel: ControlChannel, taskId: string, harness: string, line: string, streamName: "stdout" | "stderr", sanitizer: HarnessOutputSanitizer): void;
|
|
75
76
|
/**
|
|
76
77
|
* Wait for one child process to close.
|
|
77
78
|
*
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire as ___liy_agent_runnerCreateRequire } from "node:module";var require = ___liy_agent_runnerCreateRequire(import.meta.url);
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __export = (target, all) => {
|
|
3
4
|
for (var name in all)
|
|
@@ -15511,25 +15512,17 @@ function forwardJsonlByteStream(stream, onLine) {
|
|
|
15511
15512
|
});
|
|
15512
15513
|
});
|
|
15513
15514
|
}
|
|
15514
|
-
function forwardHarnessLine(channel, line, streamName, sanitizer
|
|
15515
|
+
function forwardHarnessLine(channel, taskId, harness, line, streamName, sanitizer) {
|
|
15515
15516
|
const trimmed = sanitizer.redactText(line.trim());
|
|
15516
15517
|
if (!trimmed) {
|
|
15517
15518
|
return;
|
|
15518
15519
|
}
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
}));
|
|
15526
|
-
} catch {
|
|
15527
|
-
channel.send(createExecuteTaskRpcNotification("task.log", {
|
|
15528
|
-
...driver ? { driver } : {},
|
|
15529
|
-
stream: streamName,
|
|
15530
|
-
message: trimmed
|
|
15531
|
-
}));
|
|
15532
|
-
}
|
|
15520
|
+
channel.send(createExecuteTaskRpcNotification(`harness.${streamName}`, {
|
|
15521
|
+
taskId,
|
|
15522
|
+
harness,
|
|
15523
|
+
stream: streamName,
|
|
15524
|
+
message: trimmed
|
|
15525
|
+
}));
|
|
15533
15526
|
}
|
|
15534
15527
|
function waitForChildClose(child) {
|
|
15535
15528
|
return new Promise((resolve, reject) => {
|
|
@@ -15609,10 +15602,10 @@ function startCodexJsonlHarness(input, spawnProcess) {
|
|
|
15609
15602
|
let cleanupTermination = () => void 0;
|
|
15610
15603
|
child.stdin.end(input.prompt);
|
|
15611
15604
|
const stdoutDone = forwardHarnessLines(child.stdout, (line) => {
|
|
15612
|
-
forwardHarnessLine(input.channel, line, "stdout", sanitizer);
|
|
15605
|
+
forwardHarnessLine(input.channel, input.spec.taskId, "codex-jsonl", line, "stdout", sanitizer);
|
|
15613
15606
|
});
|
|
15614
15607
|
const stderrDone = forwardHarnessLines(child.stderr, (line) => {
|
|
15615
|
-
forwardHarnessLine(input.channel, line, "stderr", sanitizer);
|
|
15608
|
+
forwardHarnessLine(input.channel, input.spec.taskId, "codex-jsonl", line, "stderr", sanitizer);
|
|
15616
15609
|
});
|
|
15617
15610
|
const result = waitForChildClose(child).then(async (closeResult) => {
|
|
15618
15611
|
cleanupTermination();
|
|
@@ -15689,6 +15682,7 @@ function buildPiRpcArgs(harness) {
|
|
|
15689
15682
|
model,
|
|
15690
15683
|
"--thinking",
|
|
15691
15684
|
thinking,
|
|
15685
|
+
"--no-session",
|
|
15692
15686
|
"--no-extensions",
|
|
15693
15687
|
"--no-prompt-templates",
|
|
15694
15688
|
"--no-themes",
|
|
@@ -15762,11 +15756,11 @@ function startPiRpcHarness(input, spawnProcess) {
|
|
|
15762
15756
|
cleanupTermination();
|
|
15763
15757
|
await Promise.all([stdoutDone, stderrDone]);
|
|
15764
15758
|
if (!agentEndReached && terminal.exitCode !== 0) {
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15759
|
+
forwardPiHarnessStreamLine(
|
|
15760
|
+
input,
|
|
15761
|
+
"stderr",
|
|
15762
|
+
`pi-rpc process closed before agent_end with ${formatHarnessExit(terminal)}`
|
|
15763
|
+
);
|
|
15770
15764
|
}
|
|
15771
15765
|
return terminal;
|
|
15772
15766
|
})();
|
|
@@ -15788,37 +15782,57 @@ function handlePiRpcStdoutLine(input) {
|
|
|
15788
15782
|
try {
|
|
15789
15783
|
parsed = JSON.parse(trimmed);
|
|
15790
15784
|
} catch {
|
|
15791
|
-
input.input
|
|
15792
|
-
driver: "pi-rpc",
|
|
15793
|
-
stream: "stdout",
|
|
15794
|
-
message: input.sanitizer.redactText(trimmed)
|
|
15795
|
-
}));
|
|
15785
|
+
forwardPiHarnessStreamLine(input.input, "stdout", input.sanitizer.redactText(trimmed));
|
|
15796
15786
|
return;
|
|
15797
15787
|
}
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15788
|
+
if (isPiRpcResponse(parsed)) {
|
|
15789
|
+
if (parsed.command === "prompt" && parsed.success === false) {
|
|
15790
|
+
input.onPromptRejected(new Error(`Pi RPC prompt was rejected: ${formatPiRpcError(parsed, input.sanitizer.redactText)}`));
|
|
15791
|
+
}
|
|
15792
|
+
return;
|
|
15793
|
+
}
|
|
15794
|
+
if (isPiExtensionUiRequest(parsed)) {
|
|
15795
|
+
forwardPiExtensionUiRequest(input.input, input.sanitizer.redactJson(parsed));
|
|
15805
15796
|
return;
|
|
15806
15797
|
}
|
|
15798
|
+
forwardPiEvent(input.input, input.sanitizer.redactJson(parsed));
|
|
15807
15799
|
if (parsed.type === "agent_end") {
|
|
15808
15800
|
input.onAgentEnd();
|
|
15809
15801
|
}
|
|
15810
15802
|
}
|
|
15811
|
-
function
|
|
15812
|
-
|
|
15803
|
+
function forwardPiEvent(input, event) {
|
|
15804
|
+
input.channel.send(createExecuteTaskRpcNotification("pi.event", {
|
|
15805
|
+
taskId: input.spec.taskId,
|
|
15806
|
+
event
|
|
15807
|
+
}));
|
|
15808
|
+
}
|
|
15809
|
+
function forwardPiExtensionUiRequest(input, request) {
|
|
15810
|
+
input.channel.send(createExecuteTaskRpcNotification("pi.extension_ui_request", {
|
|
15811
|
+
taskId: input.spec.taskId,
|
|
15812
|
+
request
|
|
15813
|
+
}));
|
|
15814
|
+
}
|
|
15815
|
+
function isPiRpcResponse(parsed) {
|
|
15816
|
+
return parsed.type === "response" && typeof parsed.command === "string";
|
|
15817
|
+
}
|
|
15818
|
+
function isPiExtensionUiRequest(parsed) {
|
|
15819
|
+
return parsed.type === "extension_ui_request" && typeof parsed.id === "string";
|
|
15820
|
+
}
|
|
15821
|
+
function forwardPiHarnessStreamLine(input, stream, message) {
|
|
15822
|
+
const trimmed = message.trim();
|
|
15813
15823
|
if (!trimmed) {
|
|
15814
15824
|
return;
|
|
15815
15825
|
}
|
|
15816
|
-
input.channel.send(createExecuteTaskRpcNotification(
|
|
15817
|
-
|
|
15818
|
-
|
|
15826
|
+
input.channel.send(createExecuteTaskRpcNotification(`harness.${stream}`, {
|
|
15827
|
+
taskId: input.spec.taskId,
|
|
15828
|
+
harness: "pi-rpc",
|
|
15829
|
+
stream,
|
|
15819
15830
|
message: trimmed
|
|
15820
15831
|
}));
|
|
15821
15832
|
}
|
|
15833
|
+
function forwardPiStderrLine(input, line, sanitizer) {
|
|
15834
|
+
forwardPiHarnessStreamLine(input, "stderr", sanitizer.redactText(line));
|
|
15835
|
+
}
|
|
15822
15836
|
function writePiRpcCommand(child, command) {
|
|
15823
15837
|
child.stdin.write(`${JSON.stringify(command)}
|
|
15824
15838
|
`);
|
|
@@ -15876,30 +15890,41 @@ async function runTaskAgentRunner(spec, options = {}) {
|
|
|
15876
15890
|
const harness = resolveAgentRunnerHarness(config2, spec.agentRunner);
|
|
15877
15891
|
const channel = await openControlChannel(spec.rpcUrl);
|
|
15878
15892
|
try {
|
|
15879
|
-
channel
|
|
15893
|
+
sendTaskStatus(channel, {
|
|
15880
15894
|
taskId: spec.taskId,
|
|
15881
|
-
|
|
15882
|
-
})
|
|
15895
|
+
status: "running"
|
|
15896
|
+
});
|
|
15883
15897
|
const completion = await runHarnessAndReadCompletion({
|
|
15884
15898
|
spec,
|
|
15885
15899
|
harness,
|
|
15886
15900
|
channel,
|
|
15901
|
+
...spec.timeouts?.taskMs !== void 0 ? { timeoutMs: spec.timeouts.taskMs } : {},
|
|
15887
15902
|
...options.env ? { env: options.env } : {}
|
|
15888
15903
|
});
|
|
15889
|
-
channel
|
|
15904
|
+
sendTaskStatus(channel, {
|
|
15890
15905
|
taskId: spec.taskId,
|
|
15891
15906
|
...completion
|
|
15892
|
-
})
|
|
15907
|
+
});
|
|
15893
15908
|
return completion;
|
|
15909
|
+
} catch (error48) {
|
|
15910
|
+
const completion = createFailedCompletion(error48);
|
|
15911
|
+
await writeTaskCompletion({
|
|
15912
|
+
paths,
|
|
15913
|
+
completion
|
|
15914
|
+
}).catch(() => void 0);
|
|
15915
|
+
try {
|
|
15916
|
+
sendTaskStatus(channel, {
|
|
15917
|
+
taskId: spec.taskId,
|
|
15918
|
+
...completion
|
|
15919
|
+
});
|
|
15920
|
+
} catch {
|
|
15921
|
+
}
|
|
15922
|
+
throw error48;
|
|
15894
15923
|
} finally {
|
|
15895
15924
|
channel.close();
|
|
15896
15925
|
}
|
|
15897
15926
|
} catch (error48) {
|
|
15898
|
-
const completion =
|
|
15899
|
-
status: "failed",
|
|
15900
|
-
summary: error48 instanceof Error ? error48.message : String(error48),
|
|
15901
|
-
artifactIds: []
|
|
15902
|
-
};
|
|
15927
|
+
const completion = createFailedCompletion(error48);
|
|
15903
15928
|
await writeTaskCompletion({
|
|
15904
15929
|
paths,
|
|
15905
15930
|
completion
|
|
@@ -15907,6 +15932,16 @@ async function runTaskAgentRunner(spec, options = {}) {
|
|
|
15907
15932
|
throw error48;
|
|
15908
15933
|
}
|
|
15909
15934
|
}
|
|
15935
|
+
function sendTaskStatus(channel, params) {
|
|
15936
|
+
channel.send(createExecuteTaskRpcNotification("task.status", params));
|
|
15937
|
+
}
|
|
15938
|
+
function createFailedCompletion(error48) {
|
|
15939
|
+
return {
|
|
15940
|
+
status: "failed",
|
|
15941
|
+
summary: error48 instanceof Error ? error48.message : String(error48),
|
|
15942
|
+
artifactIds: []
|
|
15943
|
+
};
|
|
15944
|
+
}
|
|
15910
15945
|
function createHarnessEnvironment(input) {
|
|
15911
15946
|
const baseEnv = input.env ?? process.env;
|
|
15912
15947
|
const harnessEnv = {};
|
|
@@ -16001,7 +16036,28 @@ async function runHarnessAndReadCompletion(input) {
|
|
|
16001
16036
|
...input.env ? { env: input.env } : {}
|
|
16002
16037
|
})
|
|
16003
16038
|
});
|
|
16004
|
-
const result = await
|
|
16039
|
+
const result = await waitForHarnessResult({
|
|
16040
|
+
run: activeRun,
|
|
16041
|
+
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {}
|
|
16042
|
+
});
|
|
16043
|
+
if (result === "timeout") {
|
|
16044
|
+
activeRun.cancel();
|
|
16045
|
+
await waitForHarnessCleanup(activeRun.result);
|
|
16046
|
+
const recovered = await readTaskCompletion(paths).catch(() => void 0);
|
|
16047
|
+
if (recovered) {
|
|
16048
|
+
await validateTaskArtifactFiles({
|
|
16049
|
+
paths,
|
|
16050
|
+
completion: recovered
|
|
16051
|
+
});
|
|
16052
|
+
return recovered;
|
|
16053
|
+
}
|
|
16054
|
+
const completion2 = createTimeoutCompletion(input.timeoutMs);
|
|
16055
|
+
await writeTaskCompletion({
|
|
16056
|
+
paths,
|
|
16057
|
+
completion: completion2
|
|
16058
|
+
});
|
|
16059
|
+
return completion2;
|
|
16060
|
+
}
|
|
16005
16061
|
if (cancelRequested) {
|
|
16006
16062
|
const completion2 = {
|
|
16007
16063
|
status: "cancelled",
|
|
@@ -16014,7 +16070,14 @@ async function runHarnessAndReadCompletion(input) {
|
|
|
16014
16070
|
});
|
|
16015
16071
|
return completion2;
|
|
16016
16072
|
}
|
|
16017
|
-
const completion = await readTaskCompletion(paths)
|
|
16073
|
+
const completion = await readTaskCompletion(paths).catch(async (error48) => {
|
|
16074
|
+
const fallback = createMissingCompletionFailure(error48);
|
|
16075
|
+
await writeTaskCompletion({
|
|
16076
|
+
paths,
|
|
16077
|
+
completion: fallback
|
|
16078
|
+
});
|
|
16079
|
+
return fallback;
|
|
16080
|
+
});
|
|
16018
16081
|
await validateTaskArtifactFiles({
|
|
16019
16082
|
paths,
|
|
16020
16083
|
completion
|
|
@@ -16024,6 +16087,46 @@ async function runHarnessAndReadCompletion(input) {
|
|
|
16024
16087
|
}
|
|
16025
16088
|
return completion;
|
|
16026
16089
|
}
|
|
16090
|
+
async function waitForHarnessResult(input) {
|
|
16091
|
+
const timeoutMs = normalizeTimeoutMs(input.timeoutMs);
|
|
16092
|
+
if (timeoutMs === void 0) {
|
|
16093
|
+
return input.run.result;
|
|
16094
|
+
}
|
|
16095
|
+
let timer;
|
|
16096
|
+
try {
|
|
16097
|
+
return await Promise.race([
|
|
16098
|
+
input.run.result,
|
|
16099
|
+
new Promise((resolve) => {
|
|
16100
|
+
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
16101
|
+
})
|
|
16102
|
+
]);
|
|
16103
|
+
} finally {
|
|
16104
|
+
if (timer) {
|
|
16105
|
+
clearTimeout(timer);
|
|
16106
|
+
}
|
|
16107
|
+
}
|
|
16108
|
+
}
|
|
16109
|
+
async function waitForHarnessCleanup(result) {
|
|
16110
|
+
await Promise.race([
|
|
16111
|
+
result.catch(() => void 0),
|
|
16112
|
+
sleep(1e4)
|
|
16113
|
+
]);
|
|
16114
|
+
}
|
|
16115
|
+
function createTimeoutCompletion(timeoutMs) {
|
|
16116
|
+
return {
|
|
16117
|
+
status: "failed",
|
|
16118
|
+
summary: timeoutMs === void 0 ? "execute_task harness timed out" : `execute_task harness timed out after ${timeoutMs}ms`,
|
|
16119
|
+
artifactIds: []
|
|
16120
|
+
};
|
|
16121
|
+
}
|
|
16122
|
+
function createMissingCompletionFailure(error48) {
|
|
16123
|
+
const detail = error48 instanceof Error ? error48.message : String(error48);
|
|
16124
|
+
return {
|
|
16125
|
+
status: "failed",
|
|
16126
|
+
summary: `harness ended without writing state/completion.json: ${detail}`,
|
|
16127
|
+
artifactIds: []
|
|
16128
|
+
};
|
|
16129
|
+
}
|
|
16027
16130
|
function validateRunnerSpec(spec) {
|
|
16028
16131
|
if ("harness" in spec) {
|
|
16029
16132
|
throw new Error("task spec harness is no longer supported; use task spec agentRunner");
|
|
@@ -16037,6 +16140,9 @@ function validateRunnerSpec(spec) {
|
|
|
16037
16140
|
if (!spec.rpcUrl?.trim()) {
|
|
16038
16141
|
throw new Error("task spec rpcUrl is required");
|
|
16039
16142
|
}
|
|
16143
|
+
if (spec.timeouts?.taskMs !== void 0 && normalizeTimeoutMs(spec.timeouts.taskMs) === void 0) {
|
|
16144
|
+
throw new Error("task spec timeouts.taskMs must be a positive finite number");
|
|
16145
|
+
}
|
|
16040
16146
|
if (spec.agentRunner?.harness !== void 0 && typeof spec.agentRunner.harness !== "string") {
|
|
16041
16147
|
throw new Error("task spec agentRunner.harness must be a string");
|
|
16042
16148
|
}
|
|
@@ -16049,6 +16155,14 @@ function validateRunnerSpec(spec) {
|
|
|
16049
16155
|
}
|
|
16050
16156
|
assertNoForbiddenSpecKeys(spec);
|
|
16051
16157
|
}
|
|
16158
|
+
function normalizeTimeoutMs(value) {
|
|
16159
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
16160
|
+
}
|
|
16161
|
+
function sleep(ms) {
|
|
16162
|
+
return new Promise((resolve) => {
|
|
16163
|
+
setTimeout(resolve, ms);
|
|
16164
|
+
});
|
|
16165
|
+
}
|
|
16052
16166
|
async function openControlChannel(url2) {
|
|
16053
16167
|
const WebSocketCtor = globalThis.WebSocket;
|
|
16054
16168
|
if (!WebSocketCtor) {
|