@nk070281sjv/cli 2.3.4 → 2.3.6

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 +467 -95
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3553,14 +3553,14 @@ var require_emoji_regex = __commonJS({
3553
3553
  var require_string_width = __commonJS({
3554
3554
  "../../node_modules/.pnpm/string-width@4.2.3/node_modules/string-width/index.js"(exports, module) {
3555
3555
  "use strict";
3556
- var stripAnsi2 = require_strip_ansi();
3556
+ var stripAnsi3 = require_strip_ansi();
3557
3557
  var isFullwidthCodePoint2 = require_is_fullwidth_code_point();
3558
3558
  var emojiRegex2 = require_emoji_regex();
3559
3559
  var stringWidth2 = (string) => {
3560
3560
  if (typeof string !== "string" || string.length === 0) {
3561
3561
  return 0;
3562
3562
  }
3563
- string = stripAnsi2(string);
3563
+ string = stripAnsi3(string);
3564
3564
  if (string.length === 0) {
3565
3565
  return 0;
3566
3566
  }
@@ -4695,7 +4695,7 @@ var require_wrap_ansi = __commonJS({
4695
4695
  "../../node_modules/.pnpm/wrap-ansi@6.2.0/node_modules/wrap-ansi/index.js"(exports, module) {
4696
4696
  "use strict";
4697
4697
  var stringWidth2 = require_string_width();
4698
- var stripAnsi2 = require_strip_ansi();
4698
+ var stripAnsi3 = require_strip_ansi();
4699
4699
  var ansiStyles3 = require_ansi_styles();
4700
4700
  var ESCAPES3 = /* @__PURE__ */ new Set([
4701
4701
  "\x1B",
@@ -4707,7 +4707,7 @@ var require_wrap_ansi = __commonJS({
4707
4707
  var wrapWord2 = (rows, word, columns) => {
4708
4708
  const characters = [...word];
4709
4709
  let isInsideEscape = false;
4710
- let visible = stringWidth2(stripAnsi2(rows[rows.length - 1]));
4710
+ let visible = stringWidth2(stripAnsi3(rows[rows.length - 1]));
4711
4711
  for (const [index, character] of characters.entries()) {
4712
4712
  const characterLength = stringWidth2(character);
4713
4713
  if (visible + characterLength <= columns) {
@@ -30779,7 +30779,7 @@ ${hint}
30779
30779
  }
30780
30780
 
30781
30781
  // src/lib/version.ts
30782
- var CLI_VERSION = true ? "2.3.4" : createRequire(import.meta.url)("../../package.json").version;
30782
+ var CLI_VERSION = true ? "2.3.6" : createRequire(import.meta.url)("../../package.json").version;
30783
30783
 
30784
30784
  // src/lib/deps.ts
30785
30785
  init_src();
@@ -37513,6 +37513,12 @@ function buildOpenCodeRunArgs(input) {
37513
37513
  "--model",
37514
37514
  input.model
37515
37515
  ];
37516
+ if (input.attachUrl) {
37517
+ base.push("--attach", input.attachUrl, "--dir", input.cwd);
37518
+ }
37519
+ if (input.pure) {
37520
+ base.push("--pure");
37521
+ }
37516
37522
  const limit = input.promptArgvLimit ?? DEFAULT_PROMPT_ARGV_LIMIT;
37517
37523
  if (input.prompt.length <= limit) {
37518
37524
  return [...base, input.prompt];
@@ -37531,20 +37537,49 @@ function parseOpenCodeJsonEvents(stdout) {
37531
37537
  }
37532
37538
  return events;
37533
37539
  }
37540
+ function extractOpenCodeText(events) {
37541
+ return events.map((event) => {
37542
+ if (!isRecord(event)) return "";
37543
+ const part = event.part;
37544
+ if (!isRecord(part) || part.type !== "text") return "";
37545
+ return typeof part.text === "string" ? part.text : "";
37546
+ }).join("").trimStart();
37547
+ }
37534
37548
  var OpenCodeProcessRunner = class {
37535
37549
  spawn;
37536
37550
  promptArgvLimit;
37551
+ serveReadyTimeoutMs;
37552
+ pure;
37553
+ server;
37537
37554
  constructor(options = {}) {
37538
37555
  this.spawn = options.spawn ?? spawnBinary;
37539
37556
  this.promptArgvLimit = options.promptArgvLimit ?? DEFAULT_PROMPT_ARGV_LIMIT;
37557
+ this.serveReadyTimeoutMs = options.serveReadyTimeoutMs ?? 15e3;
37558
+ this.pure = options.pure ?? true;
37540
37559
  }
37541
37560
  async run(request, signal) {
37542
37561
  const prompt = await readFile2(request.promptPath, "utf-8");
37562
+ let attachUrl;
37563
+ try {
37564
+ attachUrl = await this.ensureServer(request.cwd, signal);
37565
+ } catch (error) {
37566
+ return {
37567
+ id: request.id,
37568
+ exitCode: -1,
37569
+ stdout: "",
37570
+ stderr: error instanceof Error ? error.message : "Failed to start OpenCode server.",
37571
+ ndjsonEvents: [],
37572
+ outputText: ""
37573
+ };
37574
+ }
37543
37575
  const args = buildOpenCodeRunArgs({
37544
37576
  agent: request.agent ?? "plan",
37545
37577
  model: request.model,
37546
37578
  prompt,
37547
37579
  promptPath: request.promptPath,
37580
+ cwd: request.cwd,
37581
+ attachUrl,
37582
+ pure: this.pure,
37548
37583
  promptArgvLimit: this.promptArgvLimit
37549
37584
  });
37550
37585
  return new Promise((resolve4) => {
@@ -37554,7 +37589,8 @@ var OpenCodeProcessRunner = class {
37554
37589
  exitCode: -1,
37555
37590
  stdout: "",
37556
37591
  stderr: "OpenCode process aborted before spawn.",
37557
- ndjsonEvents: []
37592
+ ndjsonEvents: [],
37593
+ outputText: ""
37558
37594
  });
37559
37595
  return;
37560
37596
  }
@@ -37584,26 +37620,147 @@ var OpenCodeProcessRunner = class {
37584
37620
  stderr += chunk;
37585
37621
  });
37586
37622
  child.on("error", (err) => {
37623
+ const ndjsonEvents = parseOpenCodeJsonEvents(stdout);
37587
37624
  settle({
37588
37625
  id: request.id,
37589
37626
  exitCode: -1,
37590
37627
  stdout,
37591
37628
  stderr: stderr || err.message,
37592
- ndjsonEvents: parseOpenCodeJsonEvents(stdout)
37629
+ ndjsonEvents,
37630
+ outputText: extractOpenCodeText(ndjsonEvents)
37593
37631
  });
37594
37632
  });
37595
37633
  child.on("close", (code) => {
37634
+ const ndjsonEvents = parseOpenCodeJsonEvents(stdout);
37596
37635
  settle({
37597
37636
  id: request.id,
37598
37637
  exitCode: code ?? -1,
37599
37638
  stdout,
37600
37639
  stderr,
37601
- ndjsonEvents: parseOpenCodeJsonEvents(stdout)
37640
+ ndjsonEvents,
37641
+ outputText: extractOpenCodeText(ndjsonEvents)
37602
37642
  });
37603
37643
  });
37604
37644
  });
37605
37645
  }
37646
+ async close() {
37647
+ if (!this.server) return;
37648
+ const { child } = this.server;
37649
+ this.server = void 0;
37650
+ if (child.exitCode !== null || child.killed) return;
37651
+ await new Promise((resolve4) => {
37652
+ const timer = setTimeout(() => {
37653
+ child.kill("SIGKILL");
37654
+ resolve4();
37655
+ }, 1500);
37656
+ child.once("close", () => {
37657
+ clearTimeout(timer);
37658
+ resolve4();
37659
+ });
37660
+ child.kill();
37661
+ });
37662
+ }
37663
+ ensureServer(cwd, signal) {
37664
+ if (signal.aborted) {
37665
+ return Promise.reject(new Error("OpenCode server start aborted before spawn."));
37666
+ }
37667
+ if (this.server) {
37668
+ if (this.server.cwd !== cwd) {
37669
+ return Promise.reject(
37670
+ new Error(
37671
+ `OpenCode process runner cannot reuse one server across cwd values: ${this.server.cwd} vs ${cwd}.`
37672
+ )
37673
+ );
37674
+ }
37675
+ return this.server.ready;
37676
+ }
37677
+ const child = this.spawn(
37678
+ "opencode",
37679
+ [
37680
+ "serve",
37681
+ "--hostname",
37682
+ "127.0.0.1",
37683
+ "--port",
37684
+ "0",
37685
+ ...this.pure ? ["--pure"] : []
37686
+ ],
37687
+ {
37688
+ cwd,
37689
+ stdio: ["ignore", "pipe", "pipe"]
37690
+ }
37691
+ );
37692
+ const server = {
37693
+ cwd,
37694
+ child,
37695
+ ready: Promise.resolve(""),
37696
+ stdout: "",
37697
+ stderr: ""
37698
+ };
37699
+ this.server = server;
37700
+ server.ready = new Promise((resolve4, reject) => {
37701
+ let settled = false;
37702
+ const cleanup = () => {
37703
+ clearTimeout(timer);
37704
+ signal.removeEventListener("abort", abort);
37705
+ };
37706
+ const fail5 = (error) => {
37707
+ if (settled) return;
37708
+ settled = true;
37709
+ cleanup();
37710
+ this.server = void 0;
37711
+ child.kill();
37712
+ reject(error);
37713
+ };
37714
+ const succeed = (url) => {
37715
+ if (settled) return;
37716
+ settled = true;
37717
+ cleanup();
37718
+ resolve4(url);
37719
+ };
37720
+ const inspect = () => {
37721
+ const url = parseServeUrl(`${server.stdout}
37722
+ ${server.stderr}`);
37723
+ if (url) succeed(url);
37724
+ };
37725
+ const abort = () => fail5(new Error("OpenCode server start aborted."));
37726
+ const timer = setTimeout(() => {
37727
+ fail5(
37728
+ new Error(
37729
+ `Timed out waiting for OpenCode server to start. stdout: ${server.stdout.trim()} stderr: ${server.stderr.trim()}`
37730
+ )
37731
+ );
37732
+ }, this.serveReadyTimeoutMs);
37733
+ signal.addEventListener("abort", abort, { once: true });
37734
+ child.stdout?.setEncoding("utf-8");
37735
+ child.stderr?.setEncoding("utf-8");
37736
+ child.stdout?.on("data", (chunk) => {
37737
+ server.stdout += chunk;
37738
+ inspect();
37739
+ });
37740
+ child.stderr?.on("data", (chunk) => {
37741
+ server.stderr += chunk;
37742
+ inspect();
37743
+ });
37744
+ child.on("error", (error) => fail5(error));
37745
+ child.on("close", (code) => {
37746
+ if (!settled) {
37747
+ fail5(
37748
+ new Error(
37749
+ `OpenCode server exited before it was ready with code ${code ?? -1}. stdout: ${server.stdout.trim()} stderr: ${server.stderr.trim()}`
37750
+ )
37751
+ );
37752
+ }
37753
+ });
37754
+ });
37755
+ return server.ready;
37756
+ }
37606
37757
  };
37758
+ function parseServeUrl(output) {
37759
+ return output.match(/opencode server listening on (https?:\/\/\S+)/)?.[1];
37760
+ }
37761
+ function isRecord(value) {
37762
+ return typeof value === "object" && value !== null;
37763
+ }
37607
37764
 
37608
37765
  // src/lib/agent-orchestrator/prompt-writer.ts
37609
37766
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
@@ -37675,11 +37832,21 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
37675
37832
  `Round number: ${context.round}`,
37676
37833
  "Phase: reviews",
37677
37834
  "",
37678
- "Input files:",
37679
- `- ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
37680
- `- ${relative3(context.roundDir, context.contextPath)}`,
37681
- `- ${relative3(context.roundDir, context.reviewBriefPath)}`,
37682
- ...context.requirementsPath ? [`- ${relative3(context.roundDir, context.requirementsPath)}`] : [],
37835
+ "Input files to read before reviewing:",
37836
+ `- ${context.discoveredStandardsPath}`,
37837
+ `- ${context.contextPath}`,
37838
+ `- ${context.reviewBriefPath}`,
37839
+ ...context.requirementsPath ? [`- ${context.requirementsPath}`] : [],
37840
+ "",
37841
+ "Input path rules:",
37842
+ "- Read exactly the files listed above; they are session-scoped snapshots.",
37843
+ "- Do not look for legacy root files such as .ocr/context.md, .ocr/discovered-standards.md, or .ocr/review-brief.md.",
37844
+ "- If a listed file is missing, report that failure in stdout instead of searching for substitutes.",
37845
+ "- Relative equivalents from the round directory:",
37846
+ ` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
37847
+ ` - ${relative3(context.roundDir, context.contextPath)}`,
37848
+ ` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
37849
+ ...context.requirementsPath ? [` - ${relative3(context.roundDir, context.requirementsPath)}`] : [],
37683
37850
  "",
37684
37851
  "Output contract:",
37685
37852
  `- Return review markdown through stdout for ${reviewPath}.`,
@@ -37705,13 +37872,25 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
37705
37872
  `Round number: ${context.round}`,
37706
37873
  `Phase: ${stage}`,
37707
37874
  "",
37708
- "Input files:",
37709
- `- ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
37710
- `- ${relative3(context.roundDir, context.contextPath)}`,
37711
- `- ${relative3(context.roundDir, context.reviewBriefPath)}`,
37712
- "- reviews/*.md",
37713
- ...stage !== "aggregation" ? ["- aggregation.md"] : [],
37714
- ...stage === "synthesis" ? ["- validation.md"] : [],
37875
+ "Input files to read before processing:",
37876
+ `- ${context.discoveredStandardsPath}`,
37877
+ `- ${context.contextPath}`,
37878
+ `- ${context.reviewBriefPath}`,
37879
+ `- ${join26(context.roundDir, "reviews")}`,
37880
+ ...stage !== "aggregation" ? [join26(context.roundDir, "aggregation.md")] : [],
37881
+ ...stage === "synthesis" ? [join26(context.roundDir, "validation.md")] : [],
37882
+ "",
37883
+ "Input path rules:",
37884
+ "- Read exactly the files and directories listed above; they are session-scoped snapshots.",
37885
+ "- Do not look for legacy root files such as .ocr/context.md, .ocr/discovered-standards.md, or .ocr/review-brief.md.",
37886
+ "- If a listed file is missing, report that failure in stdout instead of searching for substitutes.",
37887
+ "- Relative equivalents from the round directory:",
37888
+ ` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
37889
+ ` - ${relative3(context.roundDir, context.contextPath)}`,
37890
+ ` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
37891
+ " - reviews/",
37892
+ ...stage !== "aggregation" ? [" - aggregation.md"] : [],
37893
+ ...stage === "synthesis" ? [" - validation.md"] : [],
37715
37894
  "",
37716
37895
  "Output contract:",
37717
37896
  `- Return markdown through stdout for ${artifactPath}.`,
@@ -37740,6 +37919,7 @@ function schemaHint(stage) {
37740
37919
  // src/lib/agent-orchestrator/review-orchestrator.ts
37741
37920
  async function runReviewerPhase(input) {
37742
37921
  const journal = input.journal ?? new NoopAgentLifecycleJournal();
37922
+ const emit = input.emit ?? (() => void 0);
37743
37923
  const controller = new AbortController();
37744
37924
  const reviewerRequests = input.resolvedTeam.reviewers.map((reviewer) => {
37745
37925
  if (!reviewer.model.trim()) {
@@ -37768,7 +37948,26 @@ async function runReviewerPhase(input) {
37768
37948
  phase: "reviews"
37769
37949
  });
37770
37950
  await journal.beat(agentSessionId);
37951
+ const startLogPaths = await writeProcessStartLog(input.context.roundDir, request);
37952
+ emit({
37953
+ event: "agent:start",
37954
+ phase: "reviews",
37955
+ id: request.id,
37956
+ model: request.model,
37957
+ prompt_path: request.promptPath,
37958
+ meta_log: startLogPaths.meta
37959
+ });
37771
37960
  const result = await input.runner.run(request, controller.signal);
37961
+ const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
37962
+ emit({
37963
+ event: "agent:complete",
37964
+ phase: "reviews",
37965
+ id: request.id,
37966
+ exit_code: result.exitCode,
37967
+ stdout_log: logPaths.stdout,
37968
+ stderr_log: logPaths.stderr,
37969
+ meta_log: logPaths.meta
37970
+ });
37772
37971
  const vendorId = extractVendorSessionId(result.ndjsonEvents);
37773
37972
  if (vendorId) await journal.bindVendorId(agentSessionId, vendorId);
37774
37973
  await journal.endInstance(agentSessionId, {
@@ -37789,6 +37988,8 @@ async function runReviewerPhase(input) {
37789
37988
  );
37790
37989
  const failed = results.find((result) => result.exitCode !== 0);
37791
37990
  if (failed) {
37991
+ const failedRequest = requests.find((request) => request.id === failed.id);
37992
+ const diagnostic = buildFailureDiagnostic(failed, failedRequest);
37792
37993
  await writeRoundArtifact(
37793
37994
  input.context.roundDir,
37794
37995
  "failure-summary.json",
@@ -37798,7 +37999,12 @@ async function runReviewerPhase(input) {
37798
37999
  phase: "reviews",
37799
38000
  failed_id: failed.id,
37800
38001
  exit_code: failed.exitCode,
38002
+ model: failedRequest?.model,
38003
+ prompt_path: failedRequest?.promptPath,
38004
+ cwd: failedRequest?.cwd,
38005
+ diagnostic,
37801
38006
  stderr: failed.stderr,
38007
+ stdout_excerpt: excerpt(failed.stdout),
37802
38008
  results: sortResults(results, requests).map((result) => ({
37803
38009
  id: result.id,
37804
38010
  exit_code: result.exitCode
@@ -37813,7 +38019,14 @@ async function runReviewerPhase(input) {
37813
38019
  failedId: failed.id,
37814
38020
  errors: [
37815
38021
  `Reviewer ${failed.id} failed with exit code ${failed.exitCode}.`,
37816
- ...failed.stderr.trim() ? [failed.stderr.trim()] : []
38022
+ diagnostic.summary,
38023
+ ...diagnostic.hint ? [diagnostic.hint] : [],
38024
+ ...failedRequest ? [
38025
+ `Model: ${failedRequest.model}`,
38026
+ `Prompt: ${failedRequest.promptPath}`
38027
+ ] : [],
38028
+ ...diagnostic.stderr_excerpt ? [`stderr: ${diagnostic.stderr_excerpt}`] : [],
38029
+ ...diagnostic.stdout_excerpt ? [`stdout: ${diagnostic.stdout_excerpt}`] : []
37817
38030
  ],
37818
38031
  results: sortResults(results, requests)
37819
38032
  };
@@ -37825,7 +38038,7 @@ async function runReviewerPhase(input) {
37825
38038
  for (const result of sortResults(results, requests)) {
37826
38039
  const reviewer = byId.get(result.id);
37827
38040
  if (!reviewer) continue;
37828
- await writeRoundArtifact(input.context.roundDir, reviewer.reviewPath, result.stdout);
38041
+ await writeRoundArtifact(input.context.roundDir, reviewer.reviewPath, processOutput(result));
37829
38042
  }
37830
38043
  return { ok: true, results: sortResults(results, requests) };
37831
38044
  }
@@ -37833,75 +38046,82 @@ async function runOpenCodeProcessAgentReview(input) {
37833
38046
  const runner = input.runner ?? new OpenCodeProcessRunner();
37834
38047
  const journal = input.journal ?? new NoopAgentLifecycleJournal();
37835
38048
  const emit = input.emit ?? (() => void 0);
37836
- emit({ event: "context:start", session_id: input.sessionId, round: input.round });
37837
- const context = await prepareReviewContext({
37838
- sessionId: input.sessionId,
37839
- sessionDir: input.sessionDir,
37840
- round: input.round,
37841
- requirements: input.requirements
37842
- });
37843
- const snapshot = await writePromptSnapshots({
37844
- context,
37845
- reviewers: input.reviewers,
37846
- pipelineAgents: input.pipelineAgents
37847
- });
37848
- emit({
37849
- event: "prompts:written",
37850
- session_id: input.sessionId,
37851
- round: input.round,
37852
- reviewers: snapshot.resolvedTeam.reviewers.length
37853
- });
37854
- emit({ event: "reviews:start", session_id: input.sessionId, round: input.round });
37855
- const reviewerResult = await runReviewerPhase({
37856
- context,
37857
- resolvedTeam: snapshot.resolvedTeam,
37858
- cwd: input.cwd,
37859
- runner,
37860
- journal
37861
- });
37862
- if (!reviewerResult.ok) {
38049
+ try {
38050
+ emit({ event: "context:start", session_id: input.sessionId, round: input.round });
38051
+ const context = await prepareReviewContext({
38052
+ sessionId: input.sessionId,
38053
+ sessionDir: input.sessionDir,
38054
+ round: input.round,
38055
+ requirements: input.requirements
38056
+ });
38057
+ const snapshot = await writePromptSnapshots({
38058
+ context,
38059
+ reviewers: input.reviewers,
38060
+ pipelineAgents: input.pipelineAgents
38061
+ });
37863
38062
  emit({
37864
- event: "reviews:failed",
38063
+ event: "prompts:written",
37865
38064
  session_id: input.sessionId,
37866
- failed_id: reviewerResult.failedId
38065
+ round: input.round,
38066
+ reviewers: snapshot.resolvedTeam.reviewers.length
37867
38067
  });
37868
- return {
37869
- ok: false,
38068
+ emit({ event: "reviews:start", session_id: input.sessionId, round: input.round });
38069
+ const reviewerResult = await runReviewerPhase({
38070
+ context,
37870
38071
  resolvedTeam: snapshot.resolvedTeam,
37871
- errors: reviewerResult.errors
37872
- };
37873
- }
37874
- emit({ event: "reviews:complete", session_id: input.sessionId });
37875
- emit({ event: "pipeline:start", session_id: input.sessionId, round: input.round });
37876
- const pipelineResult = await runPipelineStages({
37877
- context,
37878
- resolvedTeam: snapshot.resolvedTeam,
37879
- cwd: input.cwd,
37880
- runner,
37881
- journal
37882
- });
37883
- if (!pipelineResult.ok) {
37884
- emit({
37885
- event: "pipeline:failed",
37886
- session_id: input.sessionId,
37887
- failed_stage: pipelineResult.failedStage
38072
+ cwd: input.cwd,
38073
+ runner,
38074
+ journal,
38075
+ emit
37888
38076
  });
38077
+ if (!reviewerResult.ok) {
38078
+ emit({
38079
+ event: "reviews:failed",
38080
+ session_id: input.sessionId,
38081
+ failed_id: reviewerResult.failedId
38082
+ });
38083
+ return {
38084
+ ok: false,
38085
+ resolvedTeam: snapshot.resolvedTeam,
38086
+ errors: reviewerResult.errors
38087
+ };
38088
+ }
38089
+ emit({ event: "reviews:complete", session_id: input.sessionId });
38090
+ emit({ event: "pipeline:start", session_id: input.sessionId, round: input.round });
38091
+ const pipelineResult = await runPipelineStages({
38092
+ context,
38093
+ resolvedTeam: snapshot.resolvedTeam,
38094
+ cwd: input.cwd,
38095
+ runner,
38096
+ journal,
38097
+ emit
38098
+ });
38099
+ if (!pipelineResult.ok) {
38100
+ emit({
38101
+ event: "pipeline:failed",
38102
+ session_id: input.sessionId,
38103
+ failed_stage: pipelineResult.failedStage
38104
+ });
38105
+ return {
38106
+ ok: false,
38107
+ resolvedTeam: snapshot.resolvedTeam,
38108
+ errors: pipelineResult.errors
38109
+ };
38110
+ }
38111
+ emit({ event: "pipeline:complete", session_id: input.sessionId });
37889
38112
  return {
37890
- ok: false,
38113
+ ok: true,
37891
38114
  resolvedTeam: snapshot.resolvedTeam,
37892
- errors: pipelineResult.errors
38115
+ reviewerResults: reviewerResult.results,
38116
+ pipelineResults: pipelineResult.results
37893
38117
  };
38118
+ } finally {
38119
+ await runner.close?.();
37894
38120
  }
37895
- emit({ event: "pipeline:complete", session_id: input.sessionId });
37896
- return {
37897
- ok: true,
37898
- resolvedTeam: snapshot.resolvedTeam,
37899
- reviewerResults: reviewerResult.results,
37900
- pipelineResults: pipelineResult.results
37901
- };
37902
38121
  }
37903
38122
  async function runPipelineStages(input) {
37904
38123
  const journal = input.journal ?? new NoopAgentLifecycleJournal();
38124
+ const emit = input.emit ?? (() => void 0);
37905
38125
  const missingReviews = input.resolvedTeam.reviewers.map((reviewer) => reviewer.reviewPath).filter((reviewPath) => !isNonEmptyFile(join27(input.context.roundDir, reviewPath)));
37906
38126
  if (missingReviews.length > 0) {
37907
38127
  return {
@@ -37923,36 +38143,59 @@ async function runPipelineStages(input) {
37923
38143
  phase: stage
37924
38144
  });
37925
38145
  await journal.beat(agentSessionId);
37926
- const result = await input.runner.run(
37927
- {
37928
- id: stage,
37929
- model: ref.model,
37930
- promptPath: join27(input.context.roundDir, ref.promptPath),
37931
- cwd: input.cwd,
37932
- phase: stage
37933
- },
37934
- new AbortController().signal
37935
- );
38146
+ const request = {
38147
+ id: stage,
38148
+ model: ref.model,
38149
+ promptPath: join27(input.context.roundDir, ref.promptPath),
38150
+ cwd: input.cwd,
38151
+ phase: stage
38152
+ };
38153
+ const startLogPaths = await writeProcessStartLog(input.context.roundDir, request);
38154
+ emit({
38155
+ event: "agent:start",
38156
+ phase: stage,
38157
+ id: request.id,
38158
+ model: request.model,
38159
+ prompt_path: request.promptPath,
38160
+ meta_log: startLogPaths.meta
38161
+ });
38162
+ const result = await input.runner.run(request, new AbortController().signal);
38163
+ const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
38164
+ emit({
38165
+ event: "agent:complete",
38166
+ phase: stage,
38167
+ id: request.id,
38168
+ exit_code: result.exitCode,
38169
+ stdout_log: logPaths.stdout,
38170
+ stderr_log: logPaths.stderr,
38171
+ meta_log: logPaths.meta
38172
+ });
37936
38173
  results.push(result);
37937
38174
  const vendorId = extractVendorSessionId(result.ndjsonEvents);
37938
38175
  if (vendorId) await journal.bindVendorId(agentSessionId, vendorId);
37939
38176
  if (result.exitCode !== 0) {
38177
+ const diagnostic = buildFailureDiagnostic(result, request);
37940
38178
  await journal.endInstance(agentSessionId, {
37941
38179
  exitCode: result.exitCode,
37942
- note: result.stderr.trim() || void 0
38180
+ note: diagnostic.summary
37943
38181
  });
37944
- await writePipelineFailure(input.context.roundDir, stage, result, results);
38182
+ await writePipelineFailure(input.context.roundDir, stage, result, results, [], diagnostic);
37945
38183
  return {
37946
38184
  ok: false,
37947
38185
  failedStage: stage,
37948
38186
  errors: [
37949
38187
  `Pipeline stage ${stage} failed with exit code ${result.exitCode}.`,
37950
- ...result.stderr.trim() ? [result.stderr.trim()] : []
38188
+ diagnostic.summary,
38189
+ ...diagnostic.hint ? [diagnostic.hint] : [],
38190
+ `Model: ${request.model}`,
38191
+ `Prompt: ${request.promptPath}`,
38192
+ ...diagnostic.stderr_excerpt ? [`stderr: ${diagnostic.stderr_excerpt}`] : [],
38193
+ ...diagnostic.stdout_excerpt ? [`stdout: ${diagnostic.stdout_excerpt}`] : []
37951
38194
  ],
37952
38195
  results
37953
38196
  };
37954
38197
  }
37955
- await writeRoundArtifact(input.context.roundDir, ref.artifactPath, result.stdout);
38198
+ await writeRoundArtifact(input.context.roundDir, ref.artifactPath, processOutput(result));
37956
38199
  const validationErrors = validateStageArtifact(input.context.roundDir, stage, ref.artifactPath);
37957
38200
  if (validationErrors.length > 0) {
37958
38201
  await journal.endInstance(agentSessionId, {
@@ -37985,7 +38228,71 @@ function validateStageArtifact(roundDir, stage, artifactPath) {
37985
38228
  function isNonEmptyFile(path2) {
37986
38229
  return existsSync22(path2) && statSync5(path2).isFile() && statSync5(path2).size > 0;
37987
38230
  }
37988
- async function writePipelineFailure(roundDir, stage, result, results, validationErrors = []) {
38231
+ async function writeProcessStartLog(roundDir, request) {
38232
+ const metaPath = `process-logs/${request.id}.meta.json`;
38233
+ await writeRoundArtifact(
38234
+ roundDir,
38235
+ metaPath,
38236
+ JSON.stringify(
38237
+ {
38238
+ schema_version: 1,
38239
+ id: request.id,
38240
+ phase: request.phase,
38241
+ model: request.model,
38242
+ prompt_path: request.promptPath,
38243
+ status: "running",
38244
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
38245
+ },
38246
+ null,
38247
+ 2
38248
+ )
38249
+ );
38250
+ return { meta: metaPath };
38251
+ }
38252
+ async function writeProcessLogs(roundDir, request, result) {
38253
+ const prefix = `process-logs/${request.id}`;
38254
+ const stdoutPath = `${prefix}.stdout.ndjson`;
38255
+ const stderrPath = `${prefix}.stderr.log`;
38256
+ const metaPath = `${prefix}.meta.json`;
38257
+ await writeRoundArtifact(roundDir, stdoutPath, result.stdout);
38258
+ await writeRoundArtifact(roundDir, stderrPath, result.stderr);
38259
+ await writeRoundArtifact(
38260
+ roundDir,
38261
+ metaPath,
38262
+ JSON.stringify(
38263
+ {
38264
+ schema_version: 1,
38265
+ id: request.id,
38266
+ phase: request.phase,
38267
+ model: request.model,
38268
+ prompt_path: request.promptPath,
38269
+ status: "completed",
38270
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
38271
+ exit_code: result.exitCode,
38272
+ output_text_bytes: Buffer.byteLength(processOutput(result), "utf-8"),
38273
+ stdout_bytes: Buffer.byteLength(result.stdout, "utf-8"),
38274
+ stderr_bytes: Buffer.byteLength(result.stderr, "utf-8"),
38275
+ opencode_session_ids: extractUniqueSessionIds(result.ndjsonEvents)
38276
+ },
38277
+ null,
38278
+ 2
38279
+ )
38280
+ );
38281
+ return { stdout: stdoutPath, stderr: stderrPath, meta: metaPath };
38282
+ }
38283
+ function processOutput(result) {
38284
+ return result.outputText ?? result.stdout;
38285
+ }
38286
+ function extractUniqueSessionIds(events) {
38287
+ const ids = /* @__PURE__ */ new Set();
38288
+ for (const event of events) {
38289
+ if (!isRecord2(event)) continue;
38290
+ const sessionID = event.sessionID;
38291
+ if (typeof sessionID === "string") ids.add(sessionID);
38292
+ }
38293
+ return [...ids];
38294
+ }
38295
+ async function writePipelineFailure(roundDir, stage, result, results, validationErrors = [], diagnostic = buildFailureDiagnostic(result)) {
37989
38296
  await writeRoundArtifact(
37990
38297
  roundDir,
37991
38298
  "failure-summary.json",
@@ -37995,7 +38302,9 @@ async function writePipelineFailure(roundDir, stage, result, results, validation
37995
38302
  phase: stage,
37996
38303
  failed_id: result.id,
37997
38304
  exit_code: result.exitCode,
38305
+ diagnostic,
37998
38306
  stderr: result.stderr,
38307
+ stdout_excerpt: excerpt(result.stdout),
37999
38308
  validation_errors: validationErrors,
38000
38309
  results: results.map((item) => ({
38001
38310
  id: item.id,
@@ -38007,6 +38316,63 @@ async function writePipelineFailure(roundDir, stage, result, results, validation
38007
38316
  )
38008
38317
  );
38009
38318
  }
38319
+ function buildFailureDiagnostic(result, request) {
38320
+ const stderr = stripAnsi2(result.stderr).trim();
38321
+ const stdoutError = extractOpenCodeErrorMessage(result.ndjsonEvents);
38322
+ const combined = [stderr, stdoutError].filter(Boolean).join("\n");
38323
+ const model = request?.model ?? "";
38324
+ if (/session not found/i.test(combined)) {
38325
+ return {
38326
+ summary: 'OpenCode reported "Session not found" while running this process agent. This is an OpenCode subprocess/session error, not an OCR decision to simulate or skip reviewers.',
38327
+ hint: "OCR uses a persistent `opencode serve` process and runs agents through `opencode run --attach`. Inspect the saved prompt path and OpenCode logs to isolate the OpenCode session failure.",
38328
+ stderr_excerpt: excerpt(stderr),
38329
+ stdout_excerpt: excerpt(result.stdout)
38330
+ };
38331
+ }
38332
+ if (/Unexpected server error/i.test(combined)) {
38333
+ const hint = model && !model.includes("/") ? `The configured model "${model}" does not include an OpenCode provider prefix. Use the exact id from \`opencode models\`, for example \`lumi-deep-seek/DeepSeek-V4-Pro\`.` : `Verify that the configured model "${model}" exists in \`opencode models\` and that the provider is available.`;
38334
+ return {
38335
+ summary: stdoutError || "OpenCode returned an unexpected server error.",
38336
+ hint,
38337
+ stderr_excerpt: excerpt(stderr),
38338
+ stdout_excerpt: excerpt(result.stdout)
38339
+ };
38340
+ }
38341
+ if (combined) {
38342
+ return {
38343
+ summary: combined,
38344
+ stderr_excerpt: excerpt(stderr),
38345
+ stdout_excerpt: excerpt(result.stdout)
38346
+ };
38347
+ }
38348
+ return {
38349
+ summary: "The OpenCode subprocess exited without a useful error message.",
38350
+ hint: "Inspect failure-summary.json and rerun the saved prompt manually with the recorded model to reproduce the failure.",
38351
+ stdout_excerpt: excerpt(result.stdout)
38352
+ };
38353
+ }
38354
+ function extractOpenCodeErrorMessage(events) {
38355
+ for (const event of events) {
38356
+ if (!isRecord2(event) || event.type !== "error") continue;
38357
+ const error = event.error;
38358
+ if (!isRecord2(error)) continue;
38359
+ const data = error.data;
38360
+ if (isRecord2(data) && typeof data.message === "string") return data.message;
38361
+ if (typeof error.message === "string") return error.message;
38362
+ }
38363
+ return void 0;
38364
+ }
38365
+ function excerpt(value, limit = 1200) {
38366
+ const stripped = stripAnsi2(value ?? "").trim();
38367
+ if (!stripped) return void 0;
38368
+ return stripped.length > limit ? `${stripped.slice(0, limit)}...` : stripped;
38369
+ }
38370
+ function stripAnsi2(value) {
38371
+ return value.replace(/\u001b\[[0-9;]*m/g, "");
38372
+ }
38373
+ function isRecord2(value) {
38374
+ return typeof value === "object" && value !== null;
38375
+ }
38010
38376
 
38011
38377
  // src/commands/review.ts
38012
38378
  function fail3(message) {
@@ -38075,7 +38441,13 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
38075
38441
  });
38076
38442
  } else {
38077
38443
  if (!sessionId) {
38078
- throw new Error("run-agents requires --session-id unless --fresh is set");
38444
+ throw new Error(
38445
+ [
38446
+ "run-agents requires either --fresh or --session-id.",
38447
+ "Start a new process-agent review with: ocr review run-agents --fresh",
38448
+ "Resume a prepared workflow round with: ocr review run-agents --session-id <id> --round <n>"
38449
+ ].join("\n")
38450
+ );
38079
38451
  }
38080
38452
  const session = getSession(db, sessionId);
38081
38453
  if (!session) throw new Error(`Workflow session not found: ${sessionId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nk070281sjv/cli",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@inquirer/prompts": "^7.2.0",
40
- "@nk070281sjv/agents": "2.3.4",
40
+ "@nk070281sjv/agents": "2.3.6",
41
41
  "chalk": "^5.4.1",
42
42
  "chokidar": "^4.0.3",
43
43
  "commander": "^13.0.0",