@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.
- package/dist/index.js +467 -95
- 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
|
|
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 =
|
|
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
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
`- ${
|
|
37680
|
-
`- ${
|
|
37681
|
-
`- ${
|
|
37682
|
-
...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
|
-
`- ${
|
|
37710
|
-
`- ${
|
|
37711
|
-
`- ${
|
|
37712
|
-
"
|
|
37713
|
-
...stage !== "aggregation" ? ["
|
|
37714
|
-
...stage === "synthesis" ? ["
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37837
|
-
|
|
37838
|
-
|
|
37839
|
-
|
|
37840
|
-
|
|
37841
|
-
|
|
37842
|
-
|
|
37843
|
-
|
|
37844
|
-
|
|
37845
|
-
|
|
37846
|
-
|
|
37847
|
-
|
|
37848
|
-
|
|
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: "
|
|
38063
|
+
event: "prompts:written",
|
|
37865
38064
|
session_id: input.sessionId,
|
|
37866
|
-
|
|
38065
|
+
round: input.round,
|
|
38066
|
+
reviewers: snapshot.resolvedTeam.reviewers.length
|
|
37867
38067
|
});
|
|
37868
|
-
|
|
37869
|
-
|
|
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
|
-
|
|
37872
|
-
|
|
37873
|
-
|
|
37874
|
-
|
|
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:
|
|
38113
|
+
ok: true,
|
|
37891
38114
|
resolvedTeam: snapshot.resolvedTeam,
|
|
37892
|
-
|
|
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
|
|
37927
|
-
|
|
37928
|
-
|
|
37929
|
-
|
|
37930
|
-
|
|
37931
|
-
|
|
37932
|
-
|
|
37933
|
-
|
|
37934
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
40
|
+
"@nk070281sjv/agents": "2.3.6",
|
|
41
41
|
"chalk": "^5.4.1",
|
|
42
42
|
"chokidar": "^4.0.3",
|
|
43
43
|
"commander": "^13.0.0",
|