cool-workflow 0.1.90 → 0.1.91
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +31 -0
- package/dist/cli/command-surface.js +55 -8
- package/dist/drive.js +2 -1
- package/dist/orchestrator.js +3 -0
- package/dist/reporter.js +67 -0
- package/dist/term.js +73 -1
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +39 -22
- package/docs/cli-mcp-parity.7.md +2 -0
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +5 -3
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/agent-adapter-core.js +137 -3
- package/scripts/agents/claude-p-agent.js +24 -15
- package/scripts/agents/codex-agent.js +11 -8
- package/scripts/agents/gemini-agent.js +11 -8
- package/scripts/agents/opencode-agent.js +11 -8
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
package/docs/project-index.md
CHANGED
|
@@ -5,11 +5,11 @@ Generated from the current repository code on 2026-06-21 by `npm run sync:projec
|
|
|
5
5
|
## Snapshot
|
|
6
6
|
|
|
7
7
|
- Package: `cool-workflow`
|
|
8
|
-
- Version: `0.1.
|
|
9
|
-
- Source modules: `
|
|
8
|
+
- Version: `0.1.91`
|
|
9
|
+
- Source modules: `68`
|
|
10
10
|
- Workflow apps: `7`
|
|
11
11
|
- Docs: `53`
|
|
12
|
-
- Smoke tests: `
|
|
12
|
+
- Smoke tests: `137`
|
|
13
13
|
- Repository: https://github.com/coo1white/cool-workflow
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
@@ -104,6 +104,7 @@ multi-agent host -> topology -> blackboard/coordinator
|
|
|
104
104
|
- [onramp.ts](../src/onramp.ts)
|
|
105
105
|
- [reclamation.ts](../src/reclamation.ts)
|
|
106
106
|
- [remote-source.ts](../src/remote-source.ts)
|
|
107
|
+
- [reporter.ts](../src/reporter.ts)
|
|
107
108
|
- [result-normalize.ts](../src/result-normalize.ts)
|
|
108
109
|
- [run-export.ts](../src/run-export.ts)
|
|
109
110
|
- [run-registry.ts](../src/run-registry.ts)
|
|
@@ -212,6 +213,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
|
|
|
212
213
|
- [cli-mcp-parity-smoke.js](../test/cli-mcp-parity-smoke.js)
|
|
213
214
|
- [cli-progress-summary-smoke.js](../test/cli-progress-summary-smoke.js)
|
|
214
215
|
- [cli-recoverable-errors-smoke.js](../test/cli-recoverable-errors-smoke.js)
|
|
216
|
+
- [cli-render-smoke.js](../test/cli-render-smoke.js)
|
|
215
217
|
- [clones-gc-smoke.js](../test/clones-gc-smoke.js)
|
|
216
218
|
- [codex-agent-wrapper-smoke.js](../test/codex-agent-wrapper-smoke.js)
|
|
217
219
|
- [concurrency-default-smoke.js](../test/concurrency-default-smoke.js)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
|
|
3
3
|
"identity": {
|
|
4
4
|
"name": "cool-workflow",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.91",
|
|
6
6
|
"license": "BSD-2-Clause",
|
|
7
7
|
"homepage": "https://github.com/coo1white/cool-workflow",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -51,6 +51,129 @@ function trace(line, env = process.env, stderr = process.stderr) {
|
|
|
51
51
|
stderr.write(`${line}\n`);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// ---- live renderer (zero-dep, hand-rolled — kept self-contained so this wrapper stays a
|
|
55
|
+
// copyable "config", not a build-coupled dependency) -----------------------------------
|
|
56
|
+
|
|
57
|
+
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
58
|
+
const ANSI = { reset: "\x1b[0m", dim: "\x1b[2m", green: "\x1b[32m", red: "\x1b[31m", cyan: "\x1b[36m", hideCursor: "\x1b[?25l", showCursor: "\x1b[?25h", clearLine: "\r\x1b[2K" };
|
|
59
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
60
|
+
|
|
61
|
+
function colorOn(env, stderr) {
|
|
62
|
+
if ((env.NO_COLOR ?? "") !== "" || (env.CW_NO_COLOR ?? "") !== "") return false;
|
|
63
|
+
if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "" && env.FORCE_COLOR !== "0") return true;
|
|
64
|
+
return Boolean(stderr.isTTY);
|
|
65
|
+
}
|
|
66
|
+
// Behaviorally IDENTICAL to src/term.ts truncate() — the two copies exist only because the wrapper
|
|
67
|
+
// is a self-contained plain-JS "config" (no import of the TS build). cli-render-smoke cross-checks
|
|
68
|
+
// them on shared cases so this invariant cannot silently drift: maxWidth<=0 → ""; a string that fits
|
|
69
|
+
// returns the ORIGINAL text (ANSI intact); otherwise stripped + sliced + "…".
|
|
70
|
+
function truncate(text, max) {
|
|
71
|
+
if (max <= 0) return "";
|
|
72
|
+
const chars = [...String(text).replace(ANSI_RE, "")];
|
|
73
|
+
if (chars.length <= max) return String(text);
|
|
74
|
+
return max <= 1 ? "…" : `${chars.slice(0, max - 1).join("")}…`;
|
|
75
|
+
}
|
|
76
|
+
function fmtElapsed(ms) {
|
|
77
|
+
const s = ms / 1000;
|
|
78
|
+
return s < 60 ? `${s.toFixed(1)}s` : `${Math.floor(s / 60)}m${String(Math.round(s % 60)).padStart(2, "0")}s`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A single live status line (interactive) or append-only lines (non-TTY), plus an always-on
|
|
82
|
+
* transcript buffer. `action(label)` commits the PREVIOUS action as ✓/✗ then makes `label` the
|
|
83
|
+
* current spinning action — vendor-neutral folding without needing reliable start/end pairing. */
|
|
84
|
+
function createRenderer(opts = {}) {
|
|
85
|
+
const env = opts.env || process.env;
|
|
86
|
+
const stderr = opts.stderr || process.stderr;
|
|
87
|
+
const interactive = streamEnabled(env) && Boolean(stderr.isTTY);
|
|
88
|
+
// Non-TTY default stays SILENT (CW's Rule of Silence). Plain append-only logging in non-TTY
|
|
89
|
+
// is an explicit opt-in for CI debuggability — `CW_AGENT_STREAM=1`, mirroring CW_DRIVE_PROGRESS=1.
|
|
90
|
+
const plain = streamEnabled(env) && !stderr.isTTY && env.CW_AGENT_STREAM === "1";
|
|
91
|
+
const verbose = env.CW_VERBOSE === "1" || env.CW_VERBOSE === "true" || env.CW_OUTPUT === "full";
|
|
92
|
+
const color = colorOn(env, stderr);
|
|
93
|
+
const width = Math.max(20, Math.min(120, Number(stderr.columns) || 80));
|
|
94
|
+
const paint = (code, text) => (color ? `${code}${text}${ANSI.reset}` : text);
|
|
95
|
+
|
|
96
|
+
const transcript = [];
|
|
97
|
+
let live = ""; // current rendered live line (no trailing newline) when interactive
|
|
98
|
+
let timer = null;
|
|
99
|
+
let frame = 0;
|
|
100
|
+
let cursorHidden = false;
|
|
101
|
+
let current = null; // { label, startedAt, failed }
|
|
102
|
+
|
|
103
|
+
const restoreCursor = () => {
|
|
104
|
+
if (cursorHidden) { try { stderr.write(ANSI.showCursor); } catch { /* noop */ } cursorHidden = false; }
|
|
105
|
+
};
|
|
106
|
+
// Cursor hygiene: ALWAYS restore on exit / Ctrl-C / kill, even on crash.
|
|
107
|
+
const onSignal = (sig) => { stop(); process.exit(sig === "SIGINT" ? 130 : 143); };
|
|
108
|
+
if (interactive) {
|
|
109
|
+
process.once("exit", restoreCursor);
|
|
110
|
+
process.once("SIGINT", onSignal);
|
|
111
|
+
process.once("SIGTERM", onSignal);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const renderLive = () => {
|
|
115
|
+
if (!interactive || !current) return;
|
|
116
|
+
if (!cursorHidden) { stderr.write(ANSI.hideCursor); cursorHidden = true; }
|
|
117
|
+
const el = paint(ANSI.dim, fmtElapsed(Date.now() - current.startedAt));
|
|
118
|
+
const sp = paint(ANSI.cyan, SPINNER[frame % SPINNER.length]);
|
|
119
|
+
live = `${sp} ${truncate(current.label, width - 10)} ${el}`;
|
|
120
|
+
stderr.write(`${ANSI.clearLine}${live}`);
|
|
121
|
+
};
|
|
122
|
+
const clearLive = () => { if (interactive && live) { stderr.write(ANSI.clearLine); live = ""; } };
|
|
123
|
+
const ensureTimer = () => {
|
|
124
|
+
if (interactive && !timer) timer = setInterval(() => { frame++; renderLive(); }, 90);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const commit = () => {
|
|
128
|
+
if (!current) return;
|
|
129
|
+
const ms = Date.now() - current.startedAt;
|
|
130
|
+
const glyph = current.failed ? paint(ANSI.red, "✗") : paint(ANSI.green, "✓");
|
|
131
|
+
const doneLine = `${glyph} ${paint(ANSI.dim, `${truncate(current.label, width - 12)} (${fmtElapsed(ms)})`)}`;
|
|
132
|
+
transcript.push(`- ${current.failed ? "✗" : "✓"} ${current.label} (${fmtElapsed(ms)})`);
|
|
133
|
+
if (interactive) { clearLive(); stderr.write(`${doneLine}\n`); }
|
|
134
|
+
else if (plain) stderr.write(`${current.failed ? "✗" : "✓"} ${current.label} (${fmtElapsed(ms)})\n`);
|
|
135
|
+
current = null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
/** Begin a new active action (commits + folds the previous one). */
|
|
140
|
+
action(label) {
|
|
141
|
+
commit();
|
|
142
|
+
current = { label: String(label || "working…"), startedAt: Date.now(), failed: false };
|
|
143
|
+
ensureTimer();
|
|
144
|
+
if (interactive) renderLive();
|
|
145
|
+
else if (plain) stderr.write(`→ ${current.label}\n`);
|
|
146
|
+
},
|
|
147
|
+
/** Mark the current action as failed (it commits as ✗). */
|
|
148
|
+
fail() { if (current) current.failed = true; },
|
|
149
|
+
/** Narration text from the model. Always to the transcript; inline only in --verbose. */
|
|
150
|
+
text(chunk) {
|
|
151
|
+
const t = String(chunk || "").trim();
|
|
152
|
+
if (!t) return;
|
|
153
|
+
transcript.push(t);
|
|
154
|
+
if (!verbose) return;
|
|
155
|
+
if (interactive) { clearLive(); stderr.write(`${paint(ANSI.dim, truncate(t, width))}\n`); renderLive(); }
|
|
156
|
+
else if (plain) stderr.write(`${truncate(t, width)}\n`);
|
|
157
|
+
},
|
|
158
|
+
/** A short status note (e.g. provider summary). Transcript always; inline in --verbose. */
|
|
159
|
+
note(text) { this.text(text); },
|
|
160
|
+
/** Stop the spinner + restore the terminal. Idempotent. */
|
|
161
|
+
finishLive() { stop(); },
|
|
162
|
+
/** Persist the full transcript (narration + tool I/O) regardless of verbosity. */
|
|
163
|
+
writeTranscript(filePath) {
|
|
164
|
+
try { fs.writeFileSync(filePath, `# Agent transcript\n\n${transcript.join("\n")}\n`, "utf8"); } catch { /* advisory */ }
|
|
165
|
+
},
|
|
166
|
+
isVerbose: () => verbose
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function stop() {
|
|
170
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
171
|
+
commit();
|
|
172
|
+
clearLive();
|
|
173
|
+
restoreCursor();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
54
177
|
function shortText(value, max = 100) {
|
|
55
178
|
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
56
179
|
if (!text) return "";
|
|
@@ -109,20 +232,29 @@ function renderJsonEvent(provider, ev, state) {
|
|
|
109
232
|
const usage = usageFromEvent(ev);
|
|
110
233
|
if (usage) state.usage = usage;
|
|
111
234
|
|
|
235
|
+
const r = state.renderer;
|
|
112
236
|
const type = String(ev.type || ev.event || ev.kind || "");
|
|
237
|
+
if (ev.is_error === true && r) r.fail();
|
|
238
|
+
|
|
113
239
|
const toolName = toolNameFromEvent(ev);
|
|
114
240
|
if (toolName || /tool|command/i.test(type)) {
|
|
115
241
|
const arg = shortText(toolArgFromEvent(ev), 80);
|
|
116
|
-
|
|
242
|
+
const label = `${toolName || type}${arg ? ` ${arg}` : ""}`;
|
|
243
|
+
if (r) r.action(label);
|
|
244
|
+
else trace(` -> ${label}`);
|
|
117
245
|
return;
|
|
118
246
|
}
|
|
119
247
|
|
|
120
248
|
const text = textFromEvent(ev);
|
|
121
249
|
if (text && /assistant|message|delta|text|response|output/i.test(type || "text")) {
|
|
122
|
-
|
|
250
|
+
if (r) r.text(text);
|
|
251
|
+
else trace(` ${shortText(text, 240)}`);
|
|
123
252
|
} else if (/turn|step|summary|status/i.test(type)) {
|
|
124
253
|
const status = firstString(ev.status, ev.status_detail, ev.summary, ev.message);
|
|
125
|
-
if (status)
|
|
254
|
+
if (status) {
|
|
255
|
+
if (r) r.note(`${provider}: ${status}`);
|
|
256
|
+
else trace(` . ${provider}: ${shortText(status, 160)}`);
|
|
257
|
+
}
|
|
126
258
|
}
|
|
127
259
|
}
|
|
128
260
|
|
|
@@ -173,6 +305,8 @@ module.exports = {
|
|
|
173
305
|
streamEnabled,
|
|
174
306
|
traceEnabled,
|
|
175
307
|
trace,
|
|
308
|
+
createRenderer,
|
|
309
|
+
truncate, // exported only so cli-render-smoke can assert it stays identical to term.ts truncate()
|
|
176
310
|
parseJsonLines,
|
|
177
311
|
flushJsonLines,
|
|
178
312
|
writeResult,
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
// CW_AGENT_COMMAND="node $(pwd)/scripts/agents/claude-p-agent.js {{input}} {{result}}"
|
|
29
29
|
|
|
30
30
|
const fs = require("node:fs");
|
|
31
|
+
const path = require("node:path");
|
|
31
32
|
const { spawn, spawnSync } = require("node:child_process");
|
|
32
33
|
// Share the ONE canonical result contract with the codex/gemini/opencode
|
|
33
34
|
// wrappers instead of carrying a private copy. A drifted inline copy (ASCII
|
|
34
35
|
// hyphens silently became em-dashes here) meant claude was sent a different
|
|
35
36
|
// instruction text than the other providers for the same contract.
|
|
36
|
-
const { buildPrompt } = require("./agent-adapter-core");
|
|
37
|
+
const { buildPrompt, createRenderer } = require("./agent-adapter-core");
|
|
37
38
|
|
|
38
39
|
const inputPath = process.argv[2];
|
|
39
40
|
const resultPath = process.argv[3];
|
|
@@ -77,11 +78,6 @@ if (!streamEnabled) {
|
|
|
77
78
|
process.exit(0);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// Live trace → stderr only. Concise; one line per meaningful event.
|
|
81
|
-
function trace(line) {
|
|
82
|
-
if (!traceEnabled) return;
|
|
83
|
-
process.stderr.write(`${line}\n`);
|
|
84
|
-
}
|
|
85
81
|
function shortInput(tool, input) {
|
|
86
82
|
if (!input || typeof input !== "object") return "";
|
|
87
83
|
const v = input.file_path || input.path || input.pattern || input.command || input.query || input.url || "";
|
|
@@ -89,20 +85,30 @@ function shortInput(tool, input) {
|
|
|
89
85
|
return s ? ` ${s.length > 80 ? s.slice(0, 77) + "…" : s}` : "";
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
//
|
|
93
|
-
//
|
|
88
|
+
// The live view (spinner + folding actions on a TTY, plain append-only when piped, silent when
|
|
89
|
+
// CW_AGENT_STREAM=0) + cursor hygiene + an always-on-disk transcript live in the shared core.
|
|
90
|
+
void traceEnabled; // superseded by the renderer (which does its own TTY/stream gating)
|
|
91
|
+
const render = createRenderer({ env: process.env, stderr: process.stderr });
|
|
92
|
+
const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
|
|
93
|
+
|
|
94
|
+
// stream-json so claude emits incremental NDJSON events we render live. We CAPTURE claude's
|
|
95
|
+
// own stderr (do NOT inherit) so it can never corrupt the live region; it's surfaced only on a
|
|
96
|
+
// non-zero exit.
|
|
94
97
|
const child = spawn(
|
|
95
98
|
"claude",
|
|
96
99
|
["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Bash"],
|
|
97
|
-
{ stdio: ["ignore", "pipe", "
|
|
100
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
98
101
|
);
|
|
99
102
|
|
|
100
103
|
let model;
|
|
101
104
|
let usage;
|
|
102
105
|
let resultText;
|
|
103
106
|
let buf = "";
|
|
107
|
+
let childStderr = "";
|
|
108
|
+
child.stderr.setEncoding("utf8");
|
|
109
|
+
child.stderr.on("data", (d) => { if (childStderr.length < 1024 * 1024) childStderr += d; });
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
render.action("claude: reading the repo (read-only)…");
|
|
106
112
|
|
|
107
113
|
child.stdout.setEncoding("utf8");
|
|
108
114
|
child.stdout.on("data", (chunk) => {
|
|
@@ -127,27 +133,31 @@ function renderEvent(ev) {
|
|
|
127
133
|
if (!model && typeof ev.message.model === "string") model = ev.message.model;
|
|
128
134
|
for (const part of ev.message.content || []) {
|
|
129
135
|
if (part.type === "text" && part.text && part.text.trim()) {
|
|
130
|
-
|
|
136
|
+
render.text(part.text.trim());
|
|
131
137
|
} else if (part.type === "tool_use") {
|
|
132
|
-
|
|
138
|
+
render.action(`${part.name}${shortInput(part.name, part.input)}`);
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
} else if (ev.type === "system" && ev.subtype === "post_turn_summary" && ev.status_detail) {
|
|
136
|
-
|
|
142
|
+
render.note(ev.status_detail);
|
|
137
143
|
} else if (ev.type === "result") {
|
|
138
144
|
if (typeof ev.result === "string") resultText = ev.result;
|
|
139
145
|
if (ev.usage && typeof ev.usage === "object") usage = ev.usage;
|
|
140
|
-
if (ev.is_error)
|
|
146
|
+
if (ev.is_error) render.fail();
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
child.on("error", (err) => {
|
|
151
|
+
render.finishLive(); // restore the terminal before exiting
|
|
145
152
|
process.stderr.write(`claude spawn failed: ${err.message}\n`);
|
|
146
153
|
process.exit(1);
|
|
147
154
|
});
|
|
148
155
|
|
|
149
156
|
child.on("close", (code) => {
|
|
157
|
+
render.finishLive(); // stop the spinner + restore the cursor BEFORE any further output
|
|
158
|
+
render.writeTranscript(transcriptPath); // full narration + tool I/O always saved
|
|
150
159
|
if (code !== 0) {
|
|
160
|
+
if (childStderr.trim()) process.stderr.write(`${childStderr.trim()}\n`);
|
|
151
161
|
process.stderr.write(`claude exited ${code === null ? "(timeout/killed)" : code}\n`);
|
|
152
162
|
process.exit(code === null ? 1 : code);
|
|
153
163
|
}
|
|
@@ -158,7 +168,6 @@ child.on("close", (code) => {
|
|
|
158
168
|
}
|
|
159
169
|
// Persist the AGENT's final markdown to the worker's result.md (CW is transport).
|
|
160
170
|
fs.writeFileSync(resultPath, resultText, "utf8");
|
|
161
|
-
trace("● done — result captured");
|
|
162
171
|
// The single JSON CW consumes on STDOUT (data channel): model + usage + result.
|
|
163
172
|
process.stdout.write(JSON.stringify({ model, usage, result: resultText }));
|
|
164
173
|
});
|
|
@@ -20,10 +20,10 @@ const path = require("node:path");
|
|
|
20
20
|
const { spawn } = require("node:child_process");
|
|
21
21
|
const {
|
|
22
22
|
buildPrompt,
|
|
23
|
+
createRenderer,
|
|
23
24
|
emitReport,
|
|
24
25
|
flushJsonLines,
|
|
25
26
|
parseJsonLines,
|
|
26
|
-
trace,
|
|
27
27
|
writeResult
|
|
28
28
|
} = require("./agent-adapter-core");
|
|
29
29
|
|
|
@@ -42,7 +42,9 @@ try {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const prompt = buildPrompt(inputPath);
|
|
45
|
-
const
|
|
45
|
+
const render = createRenderer({ env: process.env, stderr: process.stderr });
|
|
46
|
+
const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
|
|
47
|
+
const state = { provider: "codex", buffer: "", model: undefined, usage: undefined, renderer: render };
|
|
46
48
|
const capturedStdout = [];
|
|
47
49
|
let childStderr = "";
|
|
48
50
|
function recordJsonLine(line) {
|
|
@@ -54,7 +56,7 @@ function recordJsonLine(line) {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
render.action("codex: reading the repo (read-only)…");
|
|
58
60
|
|
|
59
61
|
const args = [
|
|
60
62
|
"exec",
|
|
@@ -83,21 +85,23 @@ child.stdout.on("data", (chunk) => {
|
|
|
83
85
|
parseJsonLines("codex", chunk, state, recordJsonLine);
|
|
84
86
|
});
|
|
85
87
|
|
|
88
|
+
// Capture codex's own stderr (do NOT inherit) so it can never corrupt the live region; it's
|
|
89
|
+
// surfaced only on a non-zero exit.
|
|
86
90
|
child.stderr.setEncoding("utf8");
|
|
87
91
|
child.stderr.on("data", (chunk) => {
|
|
88
|
-
childStderr += chunk;
|
|
89
|
-
if (process.env.CW_AGENT_STREAM !== "0" && process.env.CW_NO_STREAM !== "1" && process.stderr.isTTY) {
|
|
90
|
-
process.stderr.write(chunk);
|
|
91
|
-
}
|
|
92
|
+
if (childStderr.length < 1024 * 1024) childStderr += chunk;
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
child.on("error", (error) => {
|
|
96
|
+
render.finishLive();
|
|
95
97
|
process.stderr.write(`codex spawn failed: ${error.message}\n`);
|
|
96
98
|
process.exit(1);
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
child.on("close", (code) => {
|
|
100
102
|
flushJsonLines("codex", state, recordJsonLine);
|
|
103
|
+
render.finishLive();
|
|
104
|
+
render.writeTranscript(transcriptPath);
|
|
101
105
|
if (code !== 0) {
|
|
102
106
|
const detail = childStderr.trim() || `codex exited ${code === null ? "(timeout/killed)" : code}`;
|
|
103
107
|
process.stderr.write(`${detail}\n`);
|
|
@@ -129,6 +133,5 @@ child.on("close", (code) => {
|
|
|
129
133
|
process.exit(1);
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
trace("* done - result captured");
|
|
133
136
|
emitReport(state.model, state.usage, resultText);
|
|
134
137
|
});
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
// stdout: one JSON object { model, usage, result } for CW provenance.
|
|
15
15
|
// stderr: optional live trace when CW_AGENT_STREAM=1 and attached to a TTY.
|
|
16
16
|
|
|
17
|
+
const path = require("node:path");
|
|
17
18
|
const { spawn } = require("node:child_process");
|
|
18
19
|
const {
|
|
19
20
|
buildPrompt,
|
|
21
|
+
createRenderer,
|
|
20
22
|
emitReport,
|
|
21
23
|
flushJsonLines,
|
|
22
24
|
parseJsonLines,
|
|
23
|
-
trace,
|
|
24
25
|
writeResult
|
|
25
26
|
} = require("./agent-adapter-core");
|
|
26
27
|
|
|
@@ -32,7 +33,9 @@ if (!inputPath || !resultPath) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const prompt = buildPrompt(inputPath);
|
|
35
|
-
const
|
|
36
|
+
const render = createRenderer({ env: process.env, stderr: process.stderr });
|
|
37
|
+
const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
|
|
38
|
+
const state = { provider: "gemini", buffer: "", model: undefined, usage: undefined, textFragments: [], finalResult: undefined, renderer: render };
|
|
36
39
|
let childStderr = "";
|
|
37
40
|
function recordJsonLine(line) {
|
|
38
41
|
let ev;
|
|
@@ -51,7 +54,7 @@ function recordJsonLine(line) {
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
render.action("gemini: reading the repo (read-only)…");
|
|
55
58
|
|
|
56
59
|
const args = [
|
|
57
60
|
"-p",
|
|
@@ -72,21 +75,22 @@ child.stdout.on("data", (chunk) => {
|
|
|
72
75
|
parseJsonLines("gemini", chunk, state, recordJsonLine);
|
|
73
76
|
});
|
|
74
77
|
|
|
78
|
+
// Capture gemini's own stderr (do NOT inherit) so it can never corrupt the live region.
|
|
75
79
|
child.stderr.setEncoding("utf8");
|
|
76
80
|
child.stderr.on("data", (chunk) => {
|
|
77
|
-
childStderr += chunk;
|
|
78
|
-
if (process.env.CW_AGENT_STREAM !== "0" && process.env.CW_NO_STREAM !== "1" && process.stderr.isTTY) {
|
|
79
|
-
process.stderr.write(chunk);
|
|
80
|
-
}
|
|
81
|
+
if (childStderr.length < 1024 * 1024) childStderr += chunk;
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
child.on("error", (error) => {
|
|
85
|
+
render.finishLive();
|
|
84
86
|
process.stderr.write(`gemini spawn failed: ${error.message}\n`);
|
|
85
87
|
process.exit(1);
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
child.on("close", (code) => {
|
|
89
91
|
flushJsonLines("gemini", state, recordJsonLine);
|
|
92
|
+
render.finishLive();
|
|
93
|
+
render.writeTranscript(transcriptPath);
|
|
90
94
|
if (code !== 0) {
|
|
91
95
|
const detail = childStderr.trim() || `gemini exited ${code === null ? "(timeout/killed)" : code}`;
|
|
92
96
|
process.stderr.write(`${detail}\n`);
|
|
@@ -110,6 +114,5 @@ child.on("close", (code) => {
|
|
|
110
114
|
process.exit(1);
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
trace("* done - result captured");
|
|
114
117
|
emitReport(state.model, state.usage, resultText);
|
|
115
118
|
});
|
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
// via execution-backend boundary controls. If OpenCode adds a cleaner read-only
|
|
20
20
|
// flag, prefer that here.
|
|
21
21
|
|
|
22
|
+
const path = require("node:path");
|
|
22
23
|
const { spawn } = require("node:child_process");
|
|
23
24
|
const {
|
|
24
25
|
buildPrompt,
|
|
26
|
+
createRenderer,
|
|
25
27
|
emitReport,
|
|
26
28
|
flushJsonLines,
|
|
27
29
|
parseJsonLines,
|
|
28
|
-
trace,
|
|
29
30
|
writeResult
|
|
30
31
|
} = require("./agent-adapter-core");
|
|
31
32
|
|
|
@@ -37,7 +38,9 @@ if (!inputPath || !resultPath) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const prompt = buildPrompt(inputPath);
|
|
40
|
-
const
|
|
41
|
+
const render = createRenderer({ env: process.env, stderr: process.stderr });
|
|
42
|
+
const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
|
|
43
|
+
const state = { provider: "opencode", buffer: "", model: undefined, usage: undefined, textFragments: [], finalResult: undefined, renderer: render };
|
|
41
44
|
let childStderr = "";
|
|
42
45
|
function recordJsonLine(line) {
|
|
43
46
|
let ev;
|
|
@@ -55,7 +58,7 @@ function recordJsonLine(line) {
|
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
render.action("opencode: reading the repo…");
|
|
59
62
|
|
|
60
63
|
const args = [
|
|
61
64
|
"run",
|
|
@@ -76,21 +79,22 @@ child.stdout.on("data", (chunk) => {
|
|
|
76
79
|
parseJsonLines("opencode", chunk, state, recordJsonLine);
|
|
77
80
|
});
|
|
78
81
|
|
|
82
|
+
// Capture opencode's own stderr (do NOT inherit) so it can never corrupt the live region.
|
|
79
83
|
child.stderr.setEncoding("utf8");
|
|
80
84
|
child.stderr.on("data", (chunk) => {
|
|
81
|
-
childStderr += chunk;
|
|
82
|
-
if (process.env.CW_AGENT_STREAM !== "0" && process.env.CW_NO_STREAM !== "1" && process.stderr.isTTY) {
|
|
83
|
-
process.stderr.write(chunk);
|
|
84
|
-
}
|
|
85
|
+
if (childStderr.length < 1024 * 1024) childStderr += chunk;
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
child.on("error", (error) => {
|
|
89
|
+
render.finishLive();
|
|
88
90
|
process.stderr.write(`opencode spawn failed: ${error.message}\n`);
|
|
89
91
|
process.exit(1);
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
child.on("close", (code) => {
|
|
93
95
|
flushJsonLines("opencode", state, recordJsonLine);
|
|
96
|
+
render.finishLive();
|
|
97
|
+
render.writeTranscript(transcriptPath);
|
|
94
98
|
if (code !== 0) {
|
|
95
99
|
const detail = childStderr.trim() || `opencode exited ${code === null ? "(timeout/killed)" : code}`;
|
|
96
100
|
process.stderr.write(`${detail}\n`);
|
|
@@ -114,6 +118,5 @@ child.on("close", (code) => {
|
|
|
114
118
|
process.exit(1);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
trace("* done - result captured");
|
|
118
121
|
emitReport(state.model, state.usage, resultText);
|
|
119
122
|
});
|
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.1.
|
|
86
|
+
"Cool Workflow v0.1.91",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.1.
|
|
120
|
+
assert.equal(summary.version, "0.1.91");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.1.
|
|
127
|
+
assert.equal(shown.app.version, "0.1.91");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.1.91");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -6,7 +6,7 @@ const fs = require("node:fs");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { CoolWorkflowRunner } = require("../dist/orchestrator.js");
|
|
8
8
|
|
|
9
|
-
const TARGET_VERSION = "0.1.
|
|
9
|
+
const TARGET_VERSION = "0.1.91";
|
|
10
10
|
const PREVIOUS_VERSION = "0.1.31";
|
|
11
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
12
12
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|