@itsaplan/runner 0.3.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +35 -15
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -620,7 +620,7 @@ function stdinText(preset, task) {
620
620
  return "";
621
621
  return presetPrompt(preset, task.systemPrompt, task.prompt);
622
622
  }
623
- async function execute(config, task, onData) {
623
+ async function execute(config, task, opts = {}) {
624
624
  const preset = presetOf(config);
625
625
  const [bin, args] = spawnArgs(config, preset, task);
626
626
  const child = spawn(bin, args, {
@@ -628,19 +628,23 @@ async function execute(config, task, onData) {
628
628
  env: childEnv(config, task),
629
629
  detached: true
630
630
  });
631
+ const kill = () => {
632
+ if (child.pid !== undefined)
633
+ killGroup(child.pid);
634
+ };
631
635
  let timedOut = false;
632
636
  const timer = setTimeout(() => {
633
637
  timedOut = true;
634
- if (child.pid !== undefined)
635
- killGroup(child.pid);
638
+ kill();
636
639
  }, config.timeoutMs);
640
+ opts.signal?.addEventListener("abort", kill, { once: true });
637
641
  let stdout = "";
638
642
  let stderr = "";
639
643
  child.stdout.setEncoding("utf8");
640
644
  child.stderr.setEncoding("utf8");
641
645
  child.stdout.on("data", (chunk) => {
642
646
  stdout = tail2(stdout + chunk, OUTPUT_LIMIT);
643
- onData?.(chunk);
647
+ opts.onData?.(chunk);
644
648
  });
645
649
  child.stderr.on("data", (chunk) => {
646
650
  stderr = tail2(stderr + chunk, ERROR_LIMIT);
@@ -657,6 +661,7 @@ async function execute(config, task, onData) {
657
661
  ({ code, signal } = await exited);
658
662
  } finally {
659
663
  clearTimeout(timer);
664
+ opts.signal?.removeEventListener("abort", kill);
660
665
  }
661
666
  const output = stdout.trim();
662
667
  if (code === 0)
@@ -672,13 +677,14 @@ async function execute(config, task, onData) {
672
677
 
673
678
  // src/chat.ts
674
679
  var FLUSH_MS = 500;
675
- async function answer(config, client, message) {
680
+ async function answer(config, client, message, stop) {
676
681
  let reported = message.sessionId !== null;
677
- const stream = new AnswerStream(config.outputFormat, message.threadId, String(message.id), (events) => {
682
+ const stream = new AnswerStream(config.outputFormat, message.threadId, String(message.id), async (events) => {
678
683
  const started = reported ? undefined : stream.startedSession() ?? undefined;
679
684
  if (started)
680
685
  reported = true;
681
- return client.chatEvents(message.id, events, started);
686
+ if (await client.chatEvents(message.id, events, started))
687
+ stop.abort();
682
688
  });
683
689
  const flushing = setInterval(() => {
684
690
  stream.flush().catch(() => {});
@@ -694,7 +700,9 @@ async function answer(config, client, message) {
694
700
  ITSAPLAN_MESSAGE_ID: String(message.id),
695
701
  ITSAPLAN_SESSION_ID: message.sessionId ?? ""
696
702
  }
697
- }, (chunk) => stream.write(chunk)).finally(() => clearInterval(flushing));
703
+ }, { onData: (chunk) => stream.write(chunk), signal: stop.signal }).finally(() => clearInterval(flushing));
704
+ if (stop.signal.aborted)
705
+ return;
698
706
  if (outcome.status === "success") {
699
707
  await stream.finish(outcome.output);
700
708
  await client.chatResult(message.id, { status: "success" });
@@ -754,18 +762,23 @@ class Client {
754
762
  return body.message;
755
763
  }
756
764
  async chatEvents(messageId, events, sessionId) {
757
- await this.post(`/agent-chats/${messageId}/events`, {
765
+ const res = await this.post(`/agent-chats/${messageId}/events`, {
758
766
  events,
759
767
  ...sessionId && { sessionId }
760
768
  });
769
+ return canceled(res);
761
770
  }
762
771
  async chatHeartbeat(messageId) {
763
- await this.post(`/agent-chats/${messageId}/heartbeat`);
772
+ return canceled(await this.post(`/agent-chats/${messageId}/heartbeat`));
764
773
  }
765
774
  async chatResult(messageId, result) {
766
775
  await this.post(`/agent-chats/${messageId}/result`, result);
767
776
  }
768
777
  }
778
+ async function canceled(res) {
779
+ const body = await res.json().catch(() => ({}));
780
+ return body.canceled === true;
781
+ }
769
782
 
770
783
  // src/cli.ts
771
784
  var HEARTBEAT_MS = 60000;
@@ -814,14 +827,21 @@ async function handle(config, client, log2, run) {
814
827
  }
815
828
  async function handleChat(config, client, log2, message) {
816
829
  log2(`chat ${message.id}: answering`);
830
+ const stop = new AbortController;
817
831
  try {
818
- await withHeartbeat(log2, () => client.chatHeartbeat(message.id), answer(config, client, message));
819
- log2(`chat ${message.id}: answered`);
832
+ await withHeartbeat(log2, async () => {
833
+ if (await client.chatHeartbeat(message.id))
834
+ stop.abort();
835
+ }, answer(config, client, message, stop));
820
836
  } catch (err) {
821
- const text = err instanceof Error ? err.message : String(err);
822
- log2(`chat ${message.id}: runner error — ${text}`);
823
- await client.chatResult(message.id, { status: "failed", error: text }).catch(() => {});
837
+ if (!stop.signal.aborted) {
838
+ const text = err instanceof Error ? err.message : String(err);
839
+ log2(`chat ${message.id}: runner error — ${text}`);
840
+ await client.chatResult(message.id, { status: "failed", error: text }).catch(() => {});
841
+ return;
842
+ }
824
843
  }
844
+ log2(`chat ${message.id}: ${stop.signal.aborted ? "stopped" : "answered"}`);
825
845
  }
826
846
  async function drain(state, log2, concurrency, take, run, onEmpty) {
827
847
  const active = new Set;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itsaplan/runner",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Runs an Itsaplan external agent's queued tasks and answers its chat on your own machine",
5
5
  "keywords": [
6
6
  "itsaplan",