@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 CHANGED
@@ -191,8 +191,10 @@ pi --mode rpc --provider deepseek --model deepseek-v4-flash --thinking high --no
191
191
  ```
192
192
 
193
193
  The driver writes one Pi RPC `prompt` command to stdin, parses Pi stdout as
194
- byte-buffered JSONL, forwards Pi events as `task.progress`, and cancels with
195
- Pi `abort`, then `SIGTERM`, then `SIGKILL` after five seconds.
194
+ byte-buffered JSONL, forwards native Pi records as `pi.event` or
195
+ `pi.extension_ui_request`, routes malformed stdout/stderr as
196
+ `harness.stdout`/`harness.stderr`, and cancels with Pi `abort`, then
197
+ `SIGTERM`, then `SIGKILL` after five seconds.
196
198
 
197
199
  Direct DeepSeek token example:
198
200
 
@@ -32,3 +32,11 @@ export declare function shouldPrintHelp(argv: string[]): boolean;
32
32
  * @returns Help text.
33
33
  */
34
34
  export declare function formatHelp(): string;
35
+ /**
36
+ * Check whether this module is executing as the Node entrypoint.
37
+ *
38
+ * @param moduleUrl - Current module URL.
39
+ * @param argvPath - Process entrypoint path, defaulting to `process.argv[1]`.
40
+ * @returns Whether the module should run the CLI.
41
+ */
42
+ export declare function isEntrypoint(moduleUrl: string, argvPath?: string | undefined): boolean;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as ___liy_agent_runnerCreateRequire } from "node:module";var require = ___liy_agent_runnerCreateRequire(import.meta.url);
2
3
  var __defProp = Object.defineProperty;
3
4
  var __export = (target, all) => {
4
5
  for (var name in all)
@@ -6,6 +7,7 @@ var __export = (target, all) => {
6
7
  };
7
8
 
8
9
  // src/bin/agent-runner.ts
10
+ import { realpathSync } from "node:fs";
9
11
  import { resolve } from "node:path";
10
12
  import { fileURLToPath } from "node:url";
11
13
 
@@ -15334,25 +15336,17 @@ function forwardJsonlByteStream(stream, onLine) {
15334
15336
  });
15335
15337
  });
15336
15338
  }
15337
- function forwardHarnessLine(channel, line, streamName, sanitizer, driver) {
15339
+ function forwardHarnessLine(channel, taskId, harness, line, streamName, sanitizer) {
15338
15340
  const trimmed = sanitizer.redactText(line.trim());
15339
15341
  if (!trimmed) {
15340
15342
  return;
15341
15343
  }
15342
- try {
15343
- const parsed = JSON.parse(trimmed);
15344
- channel.send(createExecuteTaskRpcNotification("task.progress", {
15345
- ...driver ? { driver } : {},
15346
- stream: streamName,
15347
- event: sanitizer.redactJson(parsed)
15348
- }));
15349
- } catch {
15350
- channel.send(createExecuteTaskRpcNotification("task.log", {
15351
- ...driver ? { driver } : {},
15352
- stream: streamName,
15353
- message: trimmed
15354
- }));
15355
- }
15344
+ channel.send(createExecuteTaskRpcNotification(`harness.${streamName}`, {
15345
+ taskId,
15346
+ harness,
15347
+ stream: streamName,
15348
+ message: trimmed
15349
+ }));
15356
15350
  }
15357
15351
  function waitForChildClose(child) {
15358
15352
  return new Promise((resolve2, reject) => {
@@ -15432,10 +15426,10 @@ function startCodexJsonlHarness(input, spawnProcess) {
15432
15426
  let cleanupTermination = () => void 0;
15433
15427
  child.stdin.end(input.prompt);
15434
15428
  const stdoutDone = forwardHarnessLines(child.stdout, (line) => {
15435
- forwardHarnessLine(input.channel, line, "stdout", sanitizer);
15429
+ forwardHarnessLine(input.channel, input.spec.taskId, "codex-jsonl", line, "stdout", sanitizer);
15436
15430
  });
15437
15431
  const stderrDone = forwardHarnessLines(child.stderr, (line) => {
15438
- forwardHarnessLine(input.channel, line, "stderr", sanitizer);
15432
+ forwardHarnessLine(input.channel, input.spec.taskId, "codex-jsonl", line, "stderr", sanitizer);
15439
15433
  });
15440
15434
  const result = waitForChildClose(child).then(async (closeResult) => {
15441
15435
  cleanupTermination();
@@ -15512,6 +15506,7 @@ function buildPiRpcArgs(harness) {
15512
15506
  model,
15513
15507
  "--thinking",
15514
15508
  thinking,
15509
+ "--no-session",
15515
15510
  "--no-extensions",
15516
15511
  "--no-prompt-templates",
15517
15512
  "--no-themes",
@@ -15585,11 +15580,11 @@ function startPiRpcHarness(input, spawnProcess) {
15585
15580
  cleanupTermination();
15586
15581
  await Promise.all([stdoutDone, stderrDone]);
15587
15582
  if (!agentEndReached && terminal.exitCode !== 0) {
15588
- input.channel.send(createExecuteTaskRpcNotification("task.log", {
15589
- driver: "pi-rpc",
15590
- stream: "stderr",
15591
- message: `pi-rpc process closed before agent_end with ${formatHarnessExit(terminal)}`
15592
- }));
15583
+ forwardPiHarnessStreamLine(
15584
+ input,
15585
+ "stderr",
15586
+ `pi-rpc process closed before agent_end with ${formatHarnessExit(terminal)}`
15587
+ );
15593
15588
  }
15594
15589
  return terminal;
15595
15590
  })();
@@ -15611,37 +15606,57 @@ function handlePiRpcStdoutLine(input) {
15611
15606
  try {
15612
15607
  parsed = JSON.parse(trimmed);
15613
15608
  } catch {
15614
- input.input.channel.send(createExecuteTaskRpcNotification("task.log", {
15615
- driver: "pi-rpc",
15616
- stream: "stdout",
15617
- message: input.sanitizer.redactText(trimmed)
15618
- }));
15609
+ forwardPiHarnessStreamLine(input.input, "stdout", input.sanitizer.redactText(trimmed));
15619
15610
  return;
15620
15611
  }
15621
- input.input.channel.send(createExecuteTaskRpcNotification("task.progress", {
15622
- driver: "pi-rpc",
15623
- stream: "stdout",
15624
- event: input.sanitizer.redactJson(parsed)
15625
- }));
15626
- if (parsed.type === "response" && parsed.command === "prompt" && parsed.success === false) {
15627
- input.onPromptRejected(new Error(`Pi RPC prompt was rejected: ${formatPiRpcError(parsed, input.sanitizer.redactText)}`));
15612
+ if (isPiRpcResponse(parsed)) {
15613
+ if (parsed.command === "prompt" && parsed.success === false) {
15614
+ input.onPromptRejected(new Error(`Pi RPC prompt was rejected: ${formatPiRpcError(parsed, input.sanitizer.redactText)}`));
15615
+ }
15616
+ return;
15617
+ }
15618
+ if (isPiExtensionUiRequest(parsed)) {
15619
+ forwardPiExtensionUiRequest(input.input, input.sanitizer.redactJson(parsed));
15628
15620
  return;
15629
15621
  }
15622
+ forwardPiEvent(input.input, input.sanitizer.redactJson(parsed));
15630
15623
  if (parsed.type === "agent_end") {
15631
15624
  input.onAgentEnd();
15632
15625
  }
15633
15626
  }
15634
- function forwardPiStderrLine(input, line, sanitizer) {
15635
- const trimmed = sanitizer.redactText(line.trim());
15627
+ function forwardPiEvent(input, event) {
15628
+ input.channel.send(createExecuteTaskRpcNotification("pi.event", {
15629
+ taskId: input.spec.taskId,
15630
+ event
15631
+ }));
15632
+ }
15633
+ function forwardPiExtensionUiRequest(input, request) {
15634
+ input.channel.send(createExecuteTaskRpcNotification("pi.extension_ui_request", {
15635
+ taskId: input.spec.taskId,
15636
+ request
15637
+ }));
15638
+ }
15639
+ function isPiRpcResponse(parsed) {
15640
+ return parsed.type === "response" && typeof parsed.command === "string";
15641
+ }
15642
+ function isPiExtensionUiRequest(parsed) {
15643
+ return parsed.type === "extension_ui_request" && typeof parsed.id === "string";
15644
+ }
15645
+ function forwardPiHarnessStreamLine(input, stream, message) {
15646
+ const trimmed = message.trim();
15636
15647
  if (!trimmed) {
15637
15648
  return;
15638
15649
  }
15639
- input.channel.send(createExecuteTaskRpcNotification("task.log", {
15640
- driver: "pi-rpc",
15641
- stream: "stderr",
15650
+ input.channel.send(createExecuteTaskRpcNotification(`harness.${stream}`, {
15651
+ taskId: input.spec.taskId,
15652
+ harness: "pi-rpc",
15653
+ stream,
15642
15654
  message: trimmed
15643
15655
  }));
15644
15656
  }
15657
+ function forwardPiStderrLine(input, line, sanitizer) {
15658
+ forwardPiHarnessStreamLine(input, "stderr", sanitizer.redactText(line));
15659
+ }
15645
15660
  function writePiRpcCommand(child, command) {
15646
15661
  child.stdin.write(`${JSON.stringify(command)}
15647
15662
  `);
@@ -15768,30 +15783,41 @@ async function runTaskAgentRunner(spec, options = {}) {
15768
15783
  const harness = resolveAgentRunnerHarness(config2, spec.agentRunner);
15769
15784
  const channel = await openControlChannel(spec.rpcUrl);
15770
15785
  try {
15771
- channel.send(createExecuteTaskRpcNotification("task.started", {
15786
+ sendTaskStatus(channel, {
15772
15787
  taskId: spec.taskId,
15773
- workspaceDir: spec.workspaceDir
15774
- }));
15788
+ status: "running"
15789
+ });
15775
15790
  const completion = await runHarnessAndReadCompletion({
15776
15791
  spec,
15777
15792
  harness,
15778
15793
  channel,
15794
+ ...spec.timeouts?.taskMs !== void 0 ? { timeoutMs: spec.timeouts.taskMs } : {},
15779
15795
  ...options.env ? { env: options.env } : {}
15780
15796
  });
15781
- channel.send(createExecuteTaskRpcNotification(`task.${completion.status}`, {
15797
+ sendTaskStatus(channel, {
15782
15798
  taskId: spec.taskId,
15783
15799
  ...completion
15784
- }));
15800
+ });
15785
15801
  return completion;
15802
+ } catch (error48) {
15803
+ const completion = createFailedCompletion(error48);
15804
+ await writeTaskCompletion({
15805
+ paths,
15806
+ completion
15807
+ }).catch(() => void 0);
15808
+ try {
15809
+ sendTaskStatus(channel, {
15810
+ taskId: spec.taskId,
15811
+ ...completion
15812
+ });
15813
+ } catch {
15814
+ }
15815
+ throw error48;
15786
15816
  } finally {
15787
15817
  channel.close();
15788
15818
  }
15789
15819
  } catch (error48) {
15790
- const completion = {
15791
- status: "failed",
15792
- summary: error48 instanceof Error ? error48.message : String(error48),
15793
- artifactIds: []
15794
- };
15820
+ const completion = createFailedCompletion(error48);
15795
15821
  await writeTaskCompletion({
15796
15822
  paths,
15797
15823
  completion
@@ -15799,6 +15825,16 @@ async function runTaskAgentRunner(spec, options = {}) {
15799
15825
  throw error48;
15800
15826
  }
15801
15827
  }
15828
+ function sendTaskStatus(channel, params) {
15829
+ channel.send(createExecuteTaskRpcNotification("task.status", params));
15830
+ }
15831
+ function createFailedCompletion(error48) {
15832
+ return {
15833
+ status: "failed",
15834
+ summary: error48 instanceof Error ? error48.message : String(error48),
15835
+ artifactIds: []
15836
+ };
15837
+ }
15802
15838
  function createHarnessEnvironment(input) {
15803
15839
  const baseEnv = input.env ?? process.env;
15804
15840
  const harnessEnv = {};
@@ -15893,7 +15929,28 @@ async function runHarnessAndReadCompletion(input) {
15893
15929
  ...input.env ? { env: input.env } : {}
15894
15930
  })
15895
15931
  });
15896
- const result = await activeRun.result;
15932
+ const result = await waitForHarnessResult({
15933
+ run: activeRun,
15934
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {}
15935
+ });
15936
+ if (result === "timeout") {
15937
+ activeRun.cancel();
15938
+ await waitForHarnessCleanup(activeRun.result);
15939
+ const recovered = await readTaskCompletion(paths).catch(() => void 0);
15940
+ if (recovered) {
15941
+ await validateTaskArtifactFiles({
15942
+ paths,
15943
+ completion: recovered
15944
+ });
15945
+ return recovered;
15946
+ }
15947
+ const completion2 = createTimeoutCompletion(input.timeoutMs);
15948
+ await writeTaskCompletion({
15949
+ paths,
15950
+ completion: completion2
15951
+ });
15952
+ return completion2;
15953
+ }
15897
15954
  if (cancelRequested) {
15898
15955
  const completion2 = {
15899
15956
  status: "cancelled",
@@ -15906,7 +15963,14 @@ async function runHarnessAndReadCompletion(input) {
15906
15963
  });
15907
15964
  return completion2;
15908
15965
  }
15909
- const completion = await readTaskCompletion(paths);
15966
+ const completion = await readTaskCompletion(paths).catch(async (error48) => {
15967
+ const fallback = createMissingCompletionFailure(error48);
15968
+ await writeTaskCompletion({
15969
+ paths,
15970
+ completion: fallback
15971
+ });
15972
+ return fallback;
15973
+ });
15910
15974
  await validateTaskArtifactFiles({
15911
15975
  paths,
15912
15976
  completion
@@ -15916,6 +15980,46 @@ async function runHarnessAndReadCompletion(input) {
15916
15980
  }
15917
15981
  return completion;
15918
15982
  }
15983
+ async function waitForHarnessResult(input) {
15984
+ const timeoutMs = normalizeTimeoutMs(input.timeoutMs);
15985
+ if (timeoutMs === void 0) {
15986
+ return input.run.result;
15987
+ }
15988
+ let timer;
15989
+ try {
15990
+ return await Promise.race([
15991
+ input.run.result,
15992
+ new Promise((resolve2) => {
15993
+ timer = setTimeout(() => resolve2("timeout"), timeoutMs);
15994
+ })
15995
+ ]);
15996
+ } finally {
15997
+ if (timer) {
15998
+ clearTimeout(timer);
15999
+ }
16000
+ }
16001
+ }
16002
+ async function waitForHarnessCleanup(result) {
16003
+ await Promise.race([
16004
+ result.catch(() => void 0),
16005
+ sleep(1e4)
16006
+ ]);
16007
+ }
16008
+ function createTimeoutCompletion(timeoutMs) {
16009
+ return {
16010
+ status: "failed",
16011
+ summary: timeoutMs === void 0 ? "execute_task harness timed out" : `execute_task harness timed out after ${timeoutMs}ms`,
16012
+ artifactIds: []
16013
+ };
16014
+ }
16015
+ function createMissingCompletionFailure(error48) {
16016
+ const detail = error48 instanceof Error ? error48.message : String(error48);
16017
+ return {
16018
+ status: "failed",
16019
+ summary: `harness ended without writing state/completion.json: ${detail}`,
16020
+ artifactIds: []
16021
+ };
16022
+ }
15919
16023
  function validateRunnerSpec(spec) {
15920
16024
  if ("harness" in spec) {
15921
16025
  throw new Error("task spec harness is no longer supported; use task spec agentRunner");
@@ -15929,6 +16033,9 @@ function validateRunnerSpec(spec) {
15929
16033
  if (!spec.rpcUrl?.trim()) {
15930
16034
  throw new Error("task spec rpcUrl is required");
15931
16035
  }
16036
+ if (spec.timeouts?.taskMs !== void 0 && normalizeTimeoutMs(spec.timeouts.taskMs) === void 0) {
16037
+ throw new Error("task spec timeouts.taskMs must be a positive finite number");
16038
+ }
15932
16039
  if (spec.agentRunner?.harness !== void 0 && typeof spec.agentRunner.harness !== "string") {
15933
16040
  throw new Error("task spec agentRunner.harness must be a string");
15934
16041
  }
@@ -15941,6 +16048,14 @@ function validateRunnerSpec(spec) {
15941
16048
  }
15942
16049
  assertNoForbiddenSpecKeys(spec);
15943
16050
  }
16051
+ function normalizeTimeoutMs(value) {
16052
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
16053
+ }
16054
+ function sleep(ms) {
16055
+ return new Promise((resolve2) => {
16056
+ setTimeout(resolve2, ms);
16057
+ });
16058
+ }
15944
16059
  async function openControlChannel(url2) {
15945
16060
  const WebSocketCtor = globalThis.WebSocket;
15946
16061
  if (!WebSocketCtor) {
@@ -16063,11 +16178,20 @@ if (isEntrypoint(import.meta.url)) {
16063
16178
  process.exitCode = 1;
16064
16179
  });
16065
16180
  }
16066
- function isEntrypoint(moduleUrl) {
16067
- return process.argv[1] ? fileURLToPath(moduleUrl) === resolve(process.argv[1]) : false;
16181
+ function isEntrypoint(moduleUrl, argvPath = process.argv[1]) {
16182
+ return argvPath ? realpathEntrypointPath(fileURLToPath(moduleUrl)) === realpathEntrypointPath(argvPath) : false;
16183
+ }
16184
+ function realpathEntrypointPath(path) {
16185
+ const resolved = resolve(path);
16186
+ try {
16187
+ return realpathSync.native(resolved);
16188
+ } catch {
16189
+ return resolved;
16190
+ }
16068
16191
  }
16069
16192
  export {
16070
16193
  formatHelp,
16194
+ isEntrypoint,
16071
16195
  main,
16072
16196
  readConfigPathArg,
16073
16197
  readTaskSpecArg,