@jaggerxtrm/specialists 3.3.3 → 3.3.4

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/index.js +95 -66
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17888,7 +17888,17 @@ class PiAgentSession {
17888
17888
  if (this._killed)
17889
17889
  return;
17890
17890
  this.proc?.stdin?.end();
17891
- await this._donePromise?.catch(() => {});
17891
+ if (this.proc) {
17892
+ await new Promise((resolve) => {
17893
+ this.proc.on("close", () => resolve());
17894
+ setTimeout(() => {
17895
+ if (this.proc && !this._killed) {
17896
+ this.proc.kill();
17897
+ }
17898
+ resolve();
17899
+ }, 2000);
17900
+ });
17901
+ }
17892
17902
  }
17893
17903
  kill() {
17894
17904
  if (this._killed)
@@ -18267,6 +18277,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18267
18277
  let sessionBackend = model;
18268
18278
  let session;
18269
18279
  let keepAliveActive = false;
18280
+ let sessionClosed = false;
18270
18281
  try {
18271
18282
  session = await this.sessionFactory({
18272
18283
  model,
@@ -18305,6 +18316,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18305
18316
  onResumeReady(resumeFn, closeFn);
18306
18317
  } else {
18307
18318
  await session.close();
18319
+ sessionClosed = true;
18308
18320
  }
18309
18321
  const postScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "post") ?? [];
18310
18322
  for (const script of postScripts)
@@ -18329,7 +18341,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18329
18341
  });
18330
18342
  throw err;
18331
18343
  } finally {
18332
- if (!keepAliveActive) {
18344
+ if (!keepAliveActive && !sessionClosed) {
18333
18345
  session?.kill();
18334
18346
  }
18335
18347
  }
@@ -18725,10 +18737,13 @@ class Supervisor {
18725
18737
  };
18726
18738
  appendTimelineEvent(createRunStartEvent(runOptions.name));
18727
18739
  const fifoPath = join3(dir, "steer.pipe");
18728
- try {
18729
- execFileSync("mkfifo", [fifoPath]);
18730
- this.updateStatus(id, { fifo_path: fifoPath });
18731
- } catch {}
18740
+ const needsFifo = runOptions.keepAlive;
18741
+ if (needsFifo) {
18742
+ try {
18743
+ execFileSync("mkfifo", [fifoPath]);
18744
+ this.updateStatus(id, { fifo_path: fifoPath });
18745
+ } catch {}
18746
+ }
18732
18747
  let textLogged = false;
18733
18748
  let currentTool = "";
18734
18749
  let currentToolCallId = "";
@@ -18736,6 +18751,8 @@ class Supervisor {
18736
18751
  let steerFn;
18737
18752
  let resumeFn;
18738
18753
  let closeFn;
18754
+ let fifoReadStream;
18755
+ let fifoReadline;
18739
18756
  const sigtermHandler = () => killFn?.();
18740
18757
  process.once("SIGTERM", sigtermHandler);
18741
18758
  try {
@@ -18745,6 +18762,7 @@ class Supervisor {
18745
18762
  currentTool = toolMatch[1];
18746
18763
  this.updateStatus(id, { current_tool: currentTool });
18747
18764
  }
18765
+ this.opts.onProgress?.(delta);
18748
18766
  }, (eventType) => {
18749
18767
  const now = Date.now();
18750
18768
  this.updateStatus(id, {
@@ -18766,41 +18784,43 @@ class Supervisor {
18766
18784
  }, (meta) => {
18767
18785
  this.updateStatus(id, { model: meta.model, backend: meta.backend });
18768
18786
  appendTimelineEvent(createMetaEvent(meta.model, meta.backend));
18787
+ this.opts.onMeta?.(meta);
18769
18788
  }, (fn) => {
18770
18789
  killFn = fn;
18771
18790
  }, (beadId) => {
18772
18791
  this.updateStatus(id, { bead_id: beadId });
18773
18792
  }, (fn) => {
18774
18793
  steerFn = fn;
18775
- if (existsSync4(fifoPath)) {
18776
- const rl = createInterface({ input: createReadStream(fifoPath, { flags: "r+" }) });
18777
- rl.on("line", (line) => {
18778
- try {
18779
- const parsed = JSON.parse(line);
18780
- if (parsed?.type === "steer" && typeof parsed.message === "string") {
18781
- steerFn?.(parsed.message).catch(() => {});
18782
- } else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
18783
- if (resumeFn) {
18784
- this.updateStatus(id, { status: "running", current_event: "starting" });
18785
- resumeFn(parsed.message).then((output) => {
18786
- writeFileSync(this.resultPath(id), output, "utf-8");
18787
- this.updateStatus(id, {
18788
- status: "waiting",
18789
- current_event: "waiting",
18790
- elapsed_s: Math.round((Date.now() - startedAtMs) / 1000),
18791
- last_event_at_ms: Date.now()
18792
- });
18793
- }).catch((err) => {
18794
- this.updateStatus(id, { status: "error", error: err?.message ?? String(err) });
18794
+ if (!needsFifo || !existsSync4(fifoPath))
18795
+ return;
18796
+ fifoReadStream = createReadStream(fifoPath, { flags: "r+" });
18797
+ fifoReadline = createInterface({ input: fifoReadStream });
18798
+ fifoReadline.on("line", (line) => {
18799
+ try {
18800
+ const parsed = JSON.parse(line);
18801
+ if (parsed?.type === "steer" && typeof parsed.message === "string") {
18802
+ steerFn?.(parsed.message).catch(() => {});
18803
+ } else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
18804
+ if (resumeFn) {
18805
+ this.updateStatus(id, { status: "running", current_event: "starting" });
18806
+ resumeFn(parsed.message).then((output) => {
18807
+ writeFileSync(this.resultPath(id), output, "utf-8");
18808
+ this.updateStatus(id, {
18809
+ status: "waiting",
18810
+ current_event: "waiting",
18811
+ elapsed_s: Math.round((Date.now() - startedAtMs) / 1000),
18812
+ last_event_at_ms: Date.now()
18795
18813
  });
18796
- }
18797
- } else if (parsed?.type === "close") {
18798
- closeFn?.().catch(() => {});
18814
+ }).catch((err) => {
18815
+ this.updateStatus(id, { status: "error", error: err?.message ?? String(err) });
18816
+ });
18799
18817
  }
18800
- } catch {}
18801
- });
18802
- rl.on("error", () => {});
18803
- }
18818
+ } else if (parsed?.type === "close") {
18819
+ closeFn?.().catch(() => {});
18820
+ }
18821
+ } catch {}
18822
+ });
18823
+ fifoReadline.on("error", () => {});
18804
18824
  }, (rFn, cFn) => {
18805
18825
  resumeFn = rFn;
18806
18826
  closeFn = cFn;
@@ -18840,6 +18860,12 @@ class Supervisor {
18840
18860
  throw err;
18841
18861
  } finally {
18842
18862
  process.removeListener("SIGTERM", sigtermHandler);
18863
+ try {
18864
+ fifoReadline?.close();
18865
+ } catch {}
18866
+ try {
18867
+ fifoReadStream?.destroy();
18868
+ } catch {}
18843
18869
  try {
18844
18870
  fsyncSync(eventsFd);
18845
18871
  } catch {}
@@ -19601,50 +19627,52 @@ async function run7() {
19601
19627
  circuitBreaker,
19602
19628
  beadsClient
19603
19629
  });
19604
- process.stderr.write(`
19605
- ${bold5(`Running ${cyan3(args.name)}`)}
19606
-
19607
- `);
19608
- let trackingBeadId;
19609
- const result = await runner.run({
19610
- name: args.name,
19611
- prompt,
19612
- variables,
19613
- backendOverride: args.model,
19614
- inputBeadId: args.beadId
19615
- }, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(dim5(`
19630
+ const jobsDir = join10(process.cwd(), ".specialists", "jobs");
19631
+ const supervisor = new Supervisor({
19632
+ runner,
19633
+ runOptions: {
19634
+ name: args.name,
19635
+ prompt,
19636
+ variables,
19637
+ backendOverride: args.model,
19638
+ inputBeadId: args.beadId,
19639
+ keepAlive: args.keepAlive
19640
+ },
19641
+ jobsDir,
19642
+ beadsClient,
19643
+ onProgress: (delta) => process.stdout.write(delta),
19644
+ onMeta: (meta) => process.stderr.write(dim5(`
19616
19645
  [${meta.backend} / ${meta.model}]
19617
19646
 
19618
- `)), (killFn) => {
19619
- process.on("SIGINT", () => {
19620
- process.stderr.write(`
19647
+ `))
19648
+ });
19649
+ process.stderr.write(`
19650
+ ${bold5(`Running ${cyan3(args.name)}`)}
19621
19651
 
19622
- Interrupted.
19623
19652
  `);
19624
- killFn();
19625
- process.exit(130);
19626
- });
19627
- }, (beadId) => {
19628
- trackingBeadId = beadId;
19629
- process.stderr.write(dim5(`
19630
- [bead: ${beadId}]
19631
- `));
19632
- });
19633
- if (result.output && !result.output.endsWith(`
19634
- `))
19635
- process.stdout.write(`
19653
+ let jobId;
19654
+ try {
19655
+ jobId = await supervisor.run();
19656
+ } catch (err) {
19657
+ process.stderr.write(`Error: ${err?.message ?? err}
19636
19658
  `);
19637
- const secs = (result.durationMs / 1000).toFixed(1);
19638
- const effectiveBeadId = args.beadId ?? trackingBeadId;
19659
+ process.exit(1);
19660
+ }
19661
+ const status = supervisor.readStatus(jobId);
19662
+ const secs = ((status?.last_event_at_ms ?? Date.now()) - (status?.started_at_ms ?? Date.now())) / 1000;
19639
19663
  const footer = [
19640
- effectiveBeadId ? `bead ${effectiveBeadId}` : "",
19641
- `${secs}s`,
19642
- dim5(result.model)
19664
+ `job ${jobId}`,
19665
+ status?.bead_id ? `bead ${status.bead_id}` : "",
19666
+ `${secs.toFixed(1)}s`,
19667
+ status?.model ? dim5(`${status.backend}/${status.model}`) : ""
19643
19668
  ].filter(Boolean).join(" ");
19644
19669
  process.stderr.write(`
19645
19670
  ${green5("✓")} ${footer}
19646
19671
 
19647
19672
  `);
19673
+ process.stderr.write(dim5(`Poll: specialists poll ${jobId} --json
19674
+
19675
+ `));
19648
19676
  }
19649
19677
  var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`;
19650
19678
  var init_run = __esm(() => {
@@ -19652,6 +19680,7 @@ var init_run = __esm(() => {
19652
19680
  init_runner();
19653
19681
  init_hooks();
19654
19682
  init_beads();
19683
+ init_supervisor();
19655
19684
  });
19656
19685
 
19657
19686
  // src/cli/format-helpers.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "3.3.3",
3
+ "version": "3.3.4",
4
4
  "description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",