@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.
@@ -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 JSON progress or text log.
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, driver?: string): void;
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
@@ -1,3 +1,4 @@
1
1
  export * from "./config.js";
2
2
  export * from "./runtime.js";
3
3
  export * from "./runner.js";
4
+ export type { PiAssistantMessageEvent, PiEvent, PiExtensionErrorEvent, PiExtensionUiRequest, } from "./harness-drivers/pi-rpc.js";
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, driver) {
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
- try {
15520
- const parsed = JSON.parse(trimmed);
15521
- channel.send(createExecuteTaskRpcNotification("task.progress", {
15522
- ...driver ? { driver } : {},
15523
- stream: streamName,
15524
- event: sanitizer.redactJson(parsed)
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
- input.channel.send(createExecuteTaskRpcNotification("task.log", {
15766
- driver: "pi-rpc",
15767
- stream: "stderr",
15768
- message: `pi-rpc process closed before agent_end with ${formatHarnessExit(terminal)}`
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.channel.send(createExecuteTaskRpcNotification("task.log", {
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
- input.input.channel.send(createExecuteTaskRpcNotification("task.progress", {
15799
- driver: "pi-rpc",
15800
- stream: "stdout",
15801
- event: input.sanitizer.redactJson(parsed)
15802
- }));
15803
- if (parsed.type === "response" && parsed.command === "prompt" && parsed.success === false) {
15804
- input.onPromptRejected(new Error(`Pi RPC prompt was rejected: ${formatPiRpcError(parsed, input.sanitizer.redactText)}`));
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 forwardPiStderrLine(input, line, sanitizer) {
15812
- const trimmed = sanitizer.redactText(line.trim());
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("task.log", {
15817
- driver: "pi-rpc",
15818
- stream: "stderr",
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.send(createExecuteTaskRpcNotification("task.started", {
15893
+ sendTaskStatus(channel, {
15880
15894
  taskId: spec.taskId,
15881
- workspaceDir: spec.workspaceDir
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.send(createExecuteTaskRpcNotification(`task.${completion.status}`, {
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 activeRun.result;
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) {