@yagni-app/code 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -11
- package/dist/claudeCompat.d.ts +109 -0
- package/dist/claudeCompat.js +260 -0
- package/dist/claudePlugins.d.ts +109 -0
- package/dist/claudePlugins.js +336 -0
- package/dist/cli.d.ts +1 -5
- package/dist/cli.js +79 -20
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/crashReport.d.ts +135 -0
- package/dist/crashReport.js +291 -0
- package/dist/credentials.js +2 -1
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.js +45 -0
- package/dist/doctor.d.ts +27 -0
- package/dist/doctor.js +81 -3
- package/dist/extension/advisor.d.ts +84 -0
- package/dist/extension/advisor.js +101 -0
- package/dist/extension/askAdvisorTool.d.ts +83 -0
- package/dist/extension/askAdvisorTool.js +194 -0
- package/dist/extension/askYagniTool.d.ts +39 -0
- package/dist/extension/askYagniTool.js +63 -0
- package/dist/extension/bless.d.ts +45 -0
- package/dist/extension/bless.js +87 -0
- package/dist/extension/boostCommand.d.ts +144 -0
- package/dist/extension/boostCommand.js +263 -0
- package/dist/extension/branding.d.ts +79 -0
- package/dist/extension/branding.js +156 -0
- package/dist/extension/chipEditor.d.ts +117 -0
- package/dist/extension/chipEditor.js +373 -0
- package/dist/extension/claudeRules.d.ts +54 -0
- package/dist/extension/claudeRules.js +180 -0
- package/dist/extension/config.d.ts +173 -0
- package/dist/extension/config.js +194 -0
- package/dist/extension/costHud.d.ts +186 -0
- package/dist/extension/costHud.js +293 -0
- package/dist/extension/crashReport.d.ts +89 -0
- package/dist/extension/crashReport.js +241 -0
- package/dist/extension/decisionCapture.d.ts +52 -0
- package/dist/extension/decisionCapture.js +66 -0
- package/dist/extension/decisions.d.ts +83 -0
- package/dist/extension/decisions.js +200 -0
- package/dist/extension/diagnostics.d.ts +41 -0
- package/dist/extension/diagnostics.js +110 -0
- package/dist/extension/index.d.ts +155 -0
- package/dist/extension/index.js +597 -0
- package/dist/extension/initDone.d.ts +28 -0
- package/dist/extension/initDone.js +66 -0
- package/dist/extension/initPass.d.ts +170 -0
- package/dist/extension/initPass.js +394 -0
- package/dist/extension/mcpTools.d.ts +57 -0
- package/dist/extension/mcpTools.js +132 -0
- package/dist/extension/nextWorkTool.d.ts +51 -0
- package/dist/extension/nextWorkTool.js +80 -0
- package/dist/extension/permission.d.ts +91 -0
- package/dist/extension/permission.js +236 -0
- package/dist/extension/pipeline/activity.d.ts +37 -0
- package/dist/extension/pipeline/activity.js +151 -0
- package/dist/extension/pipeline/activityFeed.d.ts +59 -0
- package/dist/extension/pipeline/activityFeed.js +175 -0
- package/dist/extension/pipeline/budget.d.ts +48 -0
- package/dist/extension/pipeline/budget.js +68 -0
- package/dist/extension/pipeline/checkpoint.d.ts +31 -0
- package/dist/extension/pipeline/checkpoint.js +176 -0
- package/dist/extension/pipeline/eval.d.ts +205 -0
- package/dist/extension/pipeline/eval.js +226 -0
- package/dist/extension/pipeline/events.d.ts +56 -0
- package/dist/extension/pipeline/events.js +147 -0
- package/dist/extension/pipeline/findings.d.ts +42 -0
- package/dist/extension/pipeline/findings.js +144 -0
- package/dist/extension/pipeline/finish.d.ts +128 -0
- package/dist/extension/pipeline/finish.js +307 -0
- package/dist/extension/pipeline/goCommand.d.ts +146 -0
- package/dist/extension/pipeline/goCommand.js +1085 -0
- package/dist/extension/pipeline/goCompareCommand.d.ts +58 -0
- package/dist/extension/pipeline/goCompareCommand.js +203 -0
- package/dist/extension/pipeline/goFlags.d.ts +40 -0
- package/dist/extension/pipeline/goFlags.js +46 -0
- package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
- package/dist/extension/pipeline/goStatusCommands.js +188 -0
- package/dist/extension/pipeline/invocation.d.ts +45 -0
- package/dist/extension/pipeline/invocation.js +64 -0
- package/dist/extension/pipeline/orchestrator.d.ts +131 -0
- package/dist/extension/pipeline/orchestrator.js +645 -0
- package/dist/extension/pipeline/personas.d.ts +44 -0
- package/dist/extension/pipeline/personas.js +248 -0
- package/dist/extension/pipeline/resilience.d.ts +85 -0
- package/dist/extension/pipeline/resilience.js +166 -0
- package/dist/extension/pipeline/resume.d.ts +18 -0
- package/dist/extension/pipeline/resume.js +106 -0
- package/dist/extension/pipeline/runCostTable.d.ts +37 -0
- package/dist/extension/pipeline/runCostTable.js +165 -0
- package/dist/extension/pipeline/runRegistry.d.ts +112 -0
- package/dist/extension/pipeline/runRegistry.js +202 -0
- package/dist/extension/pipeline/runSession.d.ts +152 -0
- package/dist/extension/pipeline/runSession.js +167 -0
- package/dist/extension/pipeline/runState.d.ts +177 -0
- package/dist/extension/pipeline/runState.js +275 -0
- package/dist/extension/pipeline/runner.d.ts +98 -0
- package/dist/extension/pipeline/runner.js +310 -0
- package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
- package/dist/extension/pipeline/scrubSecrets.js +32 -0
- package/dist/extension/pipeline/stages.d.ts +53 -0
- package/dist/extension/pipeline/stages.js +115 -0
- package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
- package/dist/extension/pipeline/ticketResolution.js +75 -0
- package/dist/extension/pipeline/types.d.ts +439 -0
- package/dist/extension/pipeline/types.js +48 -0
- package/dist/extension/pipeline/verify.d.ts +277 -0
- package/dist/extension/pipeline/verify.js +763 -0
- package/dist/extension/pipeline/workspace.d.ts +32 -0
- package/dist/extension/pipeline/workspace.js +73 -0
- package/dist/extension/pipeline/worktree.d.ts +107 -0
- package/dist/extension/pipeline/worktree.js +202 -0
- package/dist/extension/provider.d.ts +17 -0
- package/dist/extension/provider.js +33 -0
- package/dist/extension/recall.d.ts +93 -0
- package/dist/extension/recall.js +190 -0
- package/dist/extension/recordContextTool.d.ts +38 -0
- package/dist/extension/recordContextTool.js +85 -0
- package/dist/extension/recordDecisionTool.d.ts +52 -0
- package/dist/extension/recordDecisionTool.js +102 -0
- package/dist/extension/repoDocs.d.ts +81 -0
- package/dist/extension/repoDocs.js +260 -0
- package/dist/extension/rerouteNotice.d.ts +42 -0
- package/dist/extension/rerouteNotice.js +67 -0
- package/dist/extension/resilientFetch.d.ts +60 -0
- package/dist/extension/resilientFetch.js +133 -0
- package/dist/extension/reviewTool.d.ts +34 -0
- package/dist/extension/reviewTool.js +81 -0
- package/dist/extension/sessionRuns.d.ts +45 -0
- package/dist/extension/sessionRuns.js +77 -0
- package/dist/extension/spool.d.ts +92 -0
- package/dist/extension/spool.js +266 -0
- package/dist/extension/stateHome.d.ts +2 -0
- package/dist/extension/stateHome.js +6 -0
- package/dist/extension/subagents.d.ts +145 -0
- package/dist/extension/subagents.js +326 -0
- package/dist/extension/surface.d.ts +10 -0
- package/dist/extension/surface.js +12 -0
- package/dist/extension/todos.d.ts +110 -0
- package/dist/extension/todos.js +217 -0
- package/dist/extension/tokenProvider.d.ts +93 -0
- package/dist/extension/tokenProvider.js +234 -0
- package/dist/launch.d.ts +25 -3
- package/dist/launch.js +27 -9
- package/dist/login.d.ts +7 -0
- package/dist/login.js +3 -1
- package/dist/paths.d.ts +13 -4
- package/dist/paths.js +17 -5
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +5 -2
- package/dist/upgrade.d.ts +97 -0
- package/dist/upgrade.js +284 -0
- package/package.json +11 -15
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headless model behind every `/go` progress display.
|
|
3
|
+
*
|
|
4
|
+
* `RunState` folds the structured `PipelineProgress` signals, curated
|
|
5
|
+
* `ActivityLine`s and raw assistant `JsonEvent` usage into one small model that
|
|
6
|
+
* both surfaces render from: the terminal's boxed ANSI panel (via
|
|
7
|
+
* `ActivityFeed`, which is now a pure renderer over this) and the desktop's
|
|
8
|
+
* Agents tab (via `toJSON()`, pushed as one line over `setWidget`).
|
|
9
|
+
*
|
|
10
|
+
* It owns no I/O and no clock — the caller injects `at` — so it is fully
|
|
11
|
+
* deterministic in tests, exactly like the feed it replaces.
|
|
12
|
+
*
|
|
13
|
+
* Two properties are load-bearing:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Lens events never touch their parent stage.** A `stage_start` /
|
|
16
|
+
* `stage_done` carrying a `lens` mutates only that lens's agent. Without
|
|
17
|
+
* this the per-lens signals would mark the terminal's `review` row done
|
|
18
|
+
* while two lenses were still running.
|
|
19
|
+
*
|
|
20
|
+
* 2. **Per-agent action lists only.** The terminal's flat, arrival-ordered ring
|
|
21
|
+
* buffer stays in `ActivityFeed`: it exists to fit pi's widget cap, which is
|
|
22
|
+
* a property of that renderer rather than of the run. Keeping it there means
|
|
23
|
+
* the panel's cross-lens interleaving is preserved by code that never moved.
|
|
24
|
+
*/
|
|
25
|
+
/** The fixed stage order shown by both surfaces (v1 stages + the post-loop FINISH row). */
|
|
26
|
+
export const STAGE_ORDER = ["map", "plan", "implement", "review", "fix", "finish"];
|
|
27
|
+
/**
|
|
28
|
+
* Per-agent action cap. Generous compared to the terminal's `MAX_VISIBLE_ACTIONS`
|
|
29
|
+
* because the desktop pane scrolls: the TUI's 4-row ceiling is a widget-height
|
|
30
|
+
* constraint, not a truth about how much happened.
|
|
31
|
+
*/
|
|
32
|
+
export const AGENT_ACTION_WINDOW = 40;
|
|
33
|
+
function emptyUsage() {
|
|
34
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Stable agent id: a lens child is keyed by its lens AND round, and fix — the
|
|
38
|
+
* only lens-less stage that re-executes per round — by its round, so each
|
|
39
|
+
* round's fix keeps its own clock and usage instead of accumulating tokens on a
|
|
40
|
+
* timer that resets (the "181k tok in 59s" artifact). Review stage-level events
|
|
41
|
+
* stay on the bare `review` node: they mark the round boundary, and the lens
|
|
42
|
+
* children carry the round's real work.
|
|
43
|
+
*/
|
|
44
|
+
export function agentId(stageId, lens, round) {
|
|
45
|
+
if (lens)
|
|
46
|
+
return `${stageId}:${lens}:r${round ?? 1}`;
|
|
47
|
+
if (stageId === "fix" && round !== undefined)
|
|
48
|
+
return `fix:r${round}`;
|
|
49
|
+
return stageId;
|
|
50
|
+
}
|
|
51
|
+
export class RunState {
|
|
52
|
+
runId;
|
|
53
|
+
ticket;
|
|
54
|
+
startedAt;
|
|
55
|
+
endedAt;
|
|
56
|
+
stopReason;
|
|
57
|
+
serverRunId;
|
|
58
|
+
round = 0;
|
|
59
|
+
findingsTotal = 0;
|
|
60
|
+
findingsBlocking = 0;
|
|
61
|
+
rounds = [];
|
|
62
|
+
agents;
|
|
63
|
+
constructor(init) {
|
|
64
|
+
this.runId = init.runId;
|
|
65
|
+
this.ticket = init.ticket;
|
|
66
|
+
this.startedAt = init.startedAt;
|
|
67
|
+
this.agents = STAGE_ORDER.map((id) => ({
|
|
68
|
+
id,
|
|
69
|
+
stageId: id,
|
|
70
|
+
status: "pending",
|
|
71
|
+
summary: "",
|
|
72
|
+
actions: [],
|
|
73
|
+
usage: emptyUsage(),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
/** The lens-less stage agent, or undefined for an unknown stage. */
|
|
77
|
+
stageAgent(stageId) {
|
|
78
|
+
return this.agents.find((a) => a.id === stageId);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The agent for a tag, created on demand. Lens children are inserted directly
|
|
82
|
+
* after their parent stage and any existing siblings, so the array order still
|
|
83
|
+
* reads top-to-bottom for the desktop and the lens-less filter still yields
|
|
84
|
+
* STAGE_ORDER for the terminal.
|
|
85
|
+
*/
|
|
86
|
+
ensureAgent(stageId, lens, round, at) {
|
|
87
|
+
const id = agentId(stageId, lens, round);
|
|
88
|
+
const found = this.agents.find((a) => a.id === id);
|
|
89
|
+
if (found)
|
|
90
|
+
return found;
|
|
91
|
+
const node = {
|
|
92
|
+
id,
|
|
93
|
+
stageId,
|
|
94
|
+
...(lens ? { lens } : {}),
|
|
95
|
+
...(round !== undefined ? { round } : {}),
|
|
96
|
+
status: "active",
|
|
97
|
+
summary: "",
|
|
98
|
+
startedAt: at,
|
|
99
|
+
actions: [],
|
|
100
|
+
usage: emptyUsage(),
|
|
101
|
+
};
|
|
102
|
+
const parentIdx = this.agents.findIndex((a) => a.id === stageId);
|
|
103
|
+
if (parentIdx < 0) {
|
|
104
|
+
this.agents.push(node);
|
|
105
|
+
return node;
|
|
106
|
+
}
|
|
107
|
+
let insertAt = parentIdx + 1;
|
|
108
|
+
// Skip existing children (lens or per-round nodes: any id that is not its own
|
|
109
|
+
// stage row) so siblings append in arrival order after their parent.
|
|
110
|
+
while (insertAt < this.agents.length && this.agents[insertAt]?.id !== this.agents[insertAt]?.stageId)
|
|
111
|
+
insertAt += 1;
|
|
112
|
+
this.agents.splice(insertAt, 0, node);
|
|
113
|
+
return node;
|
|
114
|
+
}
|
|
115
|
+
/** Fold a structured progress signal. `at` is only read by the desktop surface. */
|
|
116
|
+
applyProgress(p, at = 0) {
|
|
117
|
+
switch (p.kind) {
|
|
118
|
+
case "stage_start": {
|
|
119
|
+
if (typeof p.round === "number")
|
|
120
|
+
this.round = p.round;
|
|
121
|
+
// `ensureAgent` routes by id: a lens or per-round fix signal creates its
|
|
122
|
+
// own child; a bare stage signal (including review's round boundary,
|
|
123
|
+
// which must return to active even though a prior round left it done)
|
|
124
|
+
// lands on the pre-seeded stage node.
|
|
125
|
+
const target = this.ensureAgent(p.stageId, p.lens, p.round, at);
|
|
126
|
+
target.status = "active";
|
|
127
|
+
target.startedAt = at;
|
|
128
|
+
// `delete`, not `= undefined`: an explicitly-undefined property still
|
|
129
|
+
// shows up in Object.keys, and the wire shape is asserted by key set.
|
|
130
|
+
delete target.endedAt;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "stage_done": {
|
|
134
|
+
if (typeof p.round === "number")
|
|
135
|
+
this.round = p.round;
|
|
136
|
+
const target = this.ensureAgent(p.stageId, p.lens, p.round, at);
|
|
137
|
+
if (p.degraded === "failed") {
|
|
138
|
+
target.status = "failed";
|
|
139
|
+
}
|
|
140
|
+
else if (target.status !== "failed") {
|
|
141
|
+
target.status = "done";
|
|
142
|
+
}
|
|
143
|
+
target.endedAt = at;
|
|
144
|
+
if (typeof p.findings === "number")
|
|
145
|
+
target.findings = p.findings;
|
|
146
|
+
if (typeof p.blocking === "number")
|
|
147
|
+
target.blocking = p.blocking;
|
|
148
|
+
if (p.degraded)
|
|
149
|
+
target.degraded = p.degraded;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "findings": {
|
|
153
|
+
this.round = p.round;
|
|
154
|
+
this.findingsTotal = p.total;
|
|
155
|
+
this.findingsBlocking = p.blocking;
|
|
156
|
+
// Keep the per-round history too: the header tally is the latest round,
|
|
157
|
+
// but the trajectory across rounds is what shows the loop converging.
|
|
158
|
+
const tally = { round: p.round, findings: p.total, blocking: p.blocking };
|
|
159
|
+
const existing = this.rounds.findIndex((r) => r.round === p.round);
|
|
160
|
+
if (existing >= 0)
|
|
161
|
+
this.rounds[existing] = tally;
|
|
162
|
+
else
|
|
163
|
+
this.rounds.push(tally);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "done": {
|
|
167
|
+
this.stopReason = p.stopReason;
|
|
168
|
+
const failed = p.stopReason === "failed" || p.stopReason === "aborted";
|
|
169
|
+
for (const a of this.agents) {
|
|
170
|
+
if (a.status === "active") {
|
|
171
|
+
a.status = failed ? "failed" : "done";
|
|
172
|
+
a.endedAt = at;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Fold one curated line. Narration becomes the emitting agent's summary;
|
|
181
|
+
* actions ride BOTH the agent's own list and the terminal's flat ring buffer.
|
|
182
|
+
*/
|
|
183
|
+
applyLine(line, tag, at = 0) {
|
|
184
|
+
const target = this.ensureAgent(tag.stageId, tag.lens, tag.round, at);
|
|
185
|
+
if (line.kind === "narration") {
|
|
186
|
+
target.summary = line.text;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (line.state === "running") {
|
|
190
|
+
target.actions.push({
|
|
191
|
+
...(line.toolCallId !== undefined ? { toolCallId: line.toolCallId } : {}),
|
|
192
|
+
text: line.text,
|
|
193
|
+
state: "running",
|
|
194
|
+
});
|
|
195
|
+
if (target.actions.length > AGENT_ACTION_WINDOW)
|
|
196
|
+
target.actions.shift();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// A resolving end: update the matching running entry in place.
|
|
200
|
+
if (line.toolCallId === undefined)
|
|
201
|
+
return;
|
|
202
|
+
const mine = target.actions.find((a) => a.toolCallId === line.toolCallId);
|
|
203
|
+
if (mine)
|
|
204
|
+
mine.state = line.state;
|
|
205
|
+
}
|
|
206
|
+
/** Fold an assistant turn's usage into the emitting agent. Ignores everything else. */
|
|
207
|
+
applyUsage(ev, tag, at = 0) {
|
|
208
|
+
if (ev.type !== "message_end" || ev.message?.role !== "assistant")
|
|
209
|
+
return;
|
|
210
|
+
const target = this.ensureAgent(tag.stageId, tag.lens, tag.round, at);
|
|
211
|
+
target.usage.turns += 1;
|
|
212
|
+
const u = ev.message.usage;
|
|
213
|
+
if (!u)
|
|
214
|
+
return;
|
|
215
|
+
target.usage.input += u.input ?? 0;
|
|
216
|
+
target.usage.output += u.output ?? 0;
|
|
217
|
+
target.usage.cacheRead += u.cacheRead ?? 0;
|
|
218
|
+
target.usage.cacheWrite += u.cacheWrite ?? 0;
|
|
219
|
+
target.usage.cost += u.cost?.total ?? 0;
|
|
220
|
+
}
|
|
221
|
+
/** Stamp the run's end. Separate from `done` because FINISH runs after the pipeline returns. */
|
|
222
|
+
markEnded(at) {
|
|
223
|
+
this.endedAt = at;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Record the backend's run UUID once `openFreshRun` resolves (goCommand.ts,
|
|
227
|
+
* a beat after the run starts — never known at construction). The caller is
|
|
228
|
+
* responsible for repainting immediately after this so the desktop learns
|
|
229
|
+
* it as soon as possible rather than on the next debounced paint.
|
|
230
|
+
*/
|
|
231
|
+
setServerRunId(id) {
|
|
232
|
+
this.serverRunId = id;
|
|
233
|
+
}
|
|
234
|
+
// ---- Terminal renderer accessors (never serialized) ----------------------
|
|
235
|
+
/**
|
|
236
|
+
* Stage rows for the panel: one row per stage, in order. Lens children are the
|
|
237
|
+
* desktop's business alone, and a looped stage that re-executes per round
|
|
238
|
+
* (fix) paints its LATEST round's node so the row shows the live execution,
|
|
239
|
+
* not a stale bare placeholder.
|
|
240
|
+
*/
|
|
241
|
+
stageAgents() {
|
|
242
|
+
return STAGE_ORDER.flatMap((id) => {
|
|
243
|
+
const perRound = this.agents.filter((a) => a.stageId === id && a.lens === undefined && a.id !== id);
|
|
244
|
+
const row = perRound[perRound.length - 1] ?? this.stageAgent(id);
|
|
245
|
+
return row ? [row] : [];
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
header() {
|
|
249
|
+
return {
|
|
250
|
+
ticket: this.ticket,
|
|
251
|
+
round: this.round,
|
|
252
|
+
findingsTotal: this.findingsTotal,
|
|
253
|
+
findingsBlocking: this.findingsBlocking,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
// ---- Wire record ---------------------------------------------------------
|
|
257
|
+
/** Named `toJSON` so `JSON.stringify(run)` yields the wire shape directly. */
|
|
258
|
+
toJSON() {
|
|
259
|
+
return {
|
|
260
|
+
v: 1,
|
|
261
|
+
runId: this.runId,
|
|
262
|
+
ticket: this.ticket,
|
|
263
|
+
round: this.round,
|
|
264
|
+
findingsTotal: this.findingsTotal,
|
|
265
|
+
findingsBlocking: this.findingsBlocking,
|
|
266
|
+
rounds: this.rounds,
|
|
267
|
+
startedAt: this.startedAt,
|
|
268
|
+
...(this.endedAt !== undefined ? { endedAt: this.endedAt } : {}),
|
|
269
|
+
...(this.stopReason ? { stopReason: this.stopReason } : {}),
|
|
270
|
+
agents: this.agents,
|
|
271
|
+
...(this.serverRunId !== undefined ? { serverRunId: this.serverRunId } : {}),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=runState.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THIN, IMPURE per-stage spawn-runner — lifted almost verbatim from the pi
|
|
3
|
+
* subagent example's `runSingleAgent` (index.ts L267-429).
|
|
4
|
+
*
|
|
5
|
+
* It writes the grounded persona body to a 0600 temp file, builds the grounded
|
|
6
|
+
* child argv, spawns ONE pi child, line-buffers the stdout NDJSON, delegates all
|
|
7
|
+
* parsing to `events.ts`, applies `PER_TASK_OUTPUT_CAP`, wires abort
|
|
8
|
+
* (SIGTERM then SIGKILL after 5s if the child has not really exited), and cleans
|
|
9
|
+
* up the temp file.
|
|
10
|
+
*
|
|
11
|
+
* v1 spawns `pi` directly (NOT the `yagni-code` launcher) and re-injects
|
|
12
|
+
* `-e <self> --provider yagni`, inheriting the parent's env (token / base URL /
|
|
13
|
+
* agent dir). This avoids a yagni-code-cli ↔ pi-extension-yagni circular
|
|
14
|
+
* dependency — see the blueprint risks.
|
|
15
|
+
*
|
|
16
|
+
* ALL impurity sits behind injectable seams (`spawn`, `writePrompt`,
|
|
17
|
+
* `resolveChild`) so the runner is unit-tested with a fake spawn emitting canned
|
|
18
|
+
* NDJSON — no real process, no model, no network. The live end-to-end run is
|
|
19
|
+
* Jack dogfood, not CI.
|
|
20
|
+
*/
|
|
21
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
22
|
+
import { type JsonEvent, type PipelineStage, type ReviewLens, type StageResult } from "./types.js";
|
|
23
|
+
/** Injectable seams so the runner is unit-testable without fs / process / net. */
|
|
24
|
+
export interface RunStageDeps {
|
|
25
|
+
cwd: string;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
spawn?: typeof nodeSpawn;
|
|
28
|
+
writePrompt?: (body: string) => Promise<{
|
|
29
|
+
filePath: string;
|
|
30
|
+
cleanup: () => void;
|
|
31
|
+
}>;
|
|
32
|
+
resolveChild?: () => {
|
|
33
|
+
piCli: string;
|
|
34
|
+
extensionPath: string;
|
|
35
|
+
};
|
|
36
|
+
/** Optional environment for spawned child stages. Defaults to Node's inherited env. */
|
|
37
|
+
env?: NodeJS.ProcessEnv;
|
|
38
|
+
/**
|
|
39
|
+
* YAG-471 attribution: the tracked run's server-assigned id (a UUID), sent to
|
|
40
|
+
* the child as `YAGNI_RUN_ID` so the model proxy's `x-yagni-run-id` header
|
|
41
|
+
* wins over the session id for `llm_usage.session_id`. Absent when the run is
|
|
42
|
+
* untracked (recording disabled/failed) — the child still gets a caller
|
|
43
|
+
* label, just not a run id; attribution degrades gracefully rather than
|
|
44
|
+
* blocking anything.
|
|
45
|
+
*/
|
|
46
|
+
attribution?: {
|
|
47
|
+
runId?: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* YAG-471 attribution: the child's caller label, sent as `YAGNI_CALLER` (the
|
|
51
|
+
* model proxy's `x-yagni-caller` header). Defaults to `go:<stage.id>` (or
|
|
52
|
+
* `go:<stage.id>:<lens>` for a fanned-out review lens) when absent, so the
|
|
53
|
+
* /go pipeline's own stage loop never has to set this explicitly. The
|
|
54
|
+
* subagent tool and the advisor pass their own label through this seam.
|
|
55
|
+
*/
|
|
56
|
+
callerLabel?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Additive live tap: invoked once per parsed NDJSON event, in stream order,
|
|
59
|
+
* right after it is buffered for the reducers. PURE side-channel for the
|
|
60
|
+
* activity feed — it changes nothing about buffering, capping, abort, or the
|
|
61
|
+
* StageResult reduction, and a headless run simply omits it.
|
|
62
|
+
*/
|
|
63
|
+
onEvent?: (ev: JsonEvent) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Max bytes for a single stdout (NDJSON) line before it is discarded. Guards
|
|
66
|
+
* against a pathological runaway line growing `buffer` without bound and
|
|
67
|
+
* OOM-crashing the harness. Defaults to {@link DEFAULT_MAX_STDOUT_LINE_BYTES};
|
|
68
|
+
* tests inject a small value.
|
|
69
|
+
*/
|
|
70
|
+
maxLineBytes?: number;
|
|
71
|
+
/**
|
|
72
|
+
* System-prompt body builder override. The general subagent tool injects
|
|
73
|
+
* custom agent bodies (from .claude/agents files) through this seam; /go's
|
|
74
|
+
* stages keep the default grounded personas.
|
|
75
|
+
*/
|
|
76
|
+
personaBody?: (stage: PipelineStage, opts: {
|
|
77
|
+
grounded?: boolean;
|
|
78
|
+
}) => string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Default per-line stdout ceiling (16 MiB). A legitimate NDJSON event — even one
|
|
82
|
+
* carrying a large tool payload — is far below this; a line beyond it is a
|
|
83
|
+
* runaway/garbage line, dropped rather than accumulated. Keeps the stdout reader
|
|
84
|
+
* O(n) and memory-bounded (a single ~GB line previously OOM-crashed `runStage`).
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_MAX_STDOUT_LINE_BYTES: number;
|
|
87
|
+
/**
|
|
88
|
+
* Run one grounded child stage and reduce its NDJSON stream into a StageResult.
|
|
89
|
+
* `ctx.lens` (when set, for a fanned-out review) selects the lens clause in the
|
|
90
|
+
* persona body and is threaded through the invocation builder.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runStage(stage: PipelineStage, ctx: {
|
|
93
|
+
ticket: string;
|
|
94
|
+
previous?: string;
|
|
95
|
+
lens?: ReviewLens;
|
|
96
|
+
grounded?: boolean;
|
|
97
|
+
}, deps: RunStageDeps): Promise<StageResult>;
|
|
98
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THIN, IMPURE per-stage spawn-runner — lifted almost verbatim from the pi
|
|
3
|
+
* subagent example's `runSingleAgent` (index.ts L267-429).
|
|
4
|
+
*
|
|
5
|
+
* It writes the grounded persona body to a 0600 temp file, builds the grounded
|
|
6
|
+
* child argv, spawns ONE pi child, line-buffers the stdout NDJSON, delegates all
|
|
7
|
+
* parsing to `events.ts`, applies `PER_TASK_OUTPUT_CAP`, wires abort
|
|
8
|
+
* (SIGTERM then SIGKILL after 5s if the child has not really exited), and cleans
|
|
9
|
+
* up the temp file.
|
|
10
|
+
*
|
|
11
|
+
* v1 spawns `pi` directly (NOT the `yagni-code` launcher) and re-injects
|
|
12
|
+
* `-e <self> --provider yagni`, inheriting the parent's env (token / base URL /
|
|
13
|
+
* agent dir). This avoids a yagni-code-cli ↔ pi-extension-yagni circular
|
|
14
|
+
* dependency — see the blueprint risks.
|
|
15
|
+
*
|
|
16
|
+
* ALL impurity sits behind injectable seams (`spawn`, `writePrompt`,
|
|
17
|
+
* `resolveChild`) so the runner is unit-tested with a fake spawn emitting canned
|
|
18
|
+
* NDJSON — no real process, no model, no network. The live end-to-end run is
|
|
19
|
+
* Jack dogfood, not CI.
|
|
20
|
+
*/
|
|
21
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { finalOutputFrom, foldEvent, newEventAccumulator } from "./events.js";
|
|
27
|
+
import { buildStageInvocation, groundedChildArgv } from "./invocation.js";
|
|
28
|
+
import { personaBody } from "./personas.js";
|
|
29
|
+
import { PER_TASK_OUTPUT_CAP, } from "./types.js";
|
|
30
|
+
/**
|
|
31
|
+
* Default per-line stdout ceiling (16 MiB). A legitimate NDJSON event — even one
|
|
32
|
+
* carrying a large tool payload — is far below this; a line beyond it is a
|
|
33
|
+
* runaway/garbage line, dropped rather than accumulated. Keeps the stdout reader
|
|
34
|
+
* O(n) and memory-bounded (a single ~GB line previously OOM-crashed `runStage`).
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_MAX_STDOUT_LINE_BYTES = 16 * 1024 * 1024;
|
|
37
|
+
/** Default: write the persona body to a private (0600) temp file. */
|
|
38
|
+
async function defaultWritePrompt(body) {
|
|
39
|
+
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "yagni-pipeline-"));
|
|
40
|
+
const filePath = path.join(dir, "persona.md");
|
|
41
|
+
await fs.promises.writeFile(filePath, body, { encoding: "utf-8", mode: 0o600 });
|
|
42
|
+
const cleanup = () => {
|
|
43
|
+
try {
|
|
44
|
+
fs.unlinkSync(filePath);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* ignore */
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
fs.rmdirSync(dir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* ignore */
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return { filePath, cleanup };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Default child resolution: the parent process IS pi (so `process.argv[1]` is
|
|
60
|
+
* pi's cli), and our compiled extension entry sits one dir up from this module.
|
|
61
|
+
*/
|
|
62
|
+
function defaultResolveChild() {
|
|
63
|
+
const piCli = process.argv[1] ?? "pi";
|
|
64
|
+
const extensionPath = fileURLToPath(new URL("../index.js", import.meta.url));
|
|
65
|
+
return { piCli, extensionPath };
|
|
66
|
+
}
|
|
67
|
+
const EMPTY_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
68
|
+
/** Truncate to PER_TASK_OUTPUT_CAP bytes, never exceeding the cap. */
|
|
69
|
+
function capOutput(output) {
|
|
70
|
+
if (Buffer.byteLength(output, "utf8") <= PER_TASK_OUTPUT_CAP)
|
|
71
|
+
return output;
|
|
72
|
+
let truncated = output.slice(0, PER_TASK_OUTPUT_CAP);
|
|
73
|
+
while (Buffer.byteLength(truncated, "utf8") > PER_TASK_OUTPUT_CAP) {
|
|
74
|
+
truncated = truncated.slice(0, -1);
|
|
75
|
+
}
|
|
76
|
+
return truncated;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run one grounded child stage and reduce its NDJSON stream into a StageResult.
|
|
80
|
+
* `ctx.lens` (when set, for a fanned-out review) selects the lens clause in the
|
|
81
|
+
* persona body and is threaded through the invocation builder.
|
|
82
|
+
*/
|
|
83
|
+
export async function runStage(stage, ctx, deps) {
|
|
84
|
+
const spawnFn = deps.spawn ?? nodeSpawn;
|
|
85
|
+
const writePrompt = deps.writePrompt ?? defaultWritePrompt;
|
|
86
|
+
const resolveChild = deps.resolveChild ?? defaultResolveChild;
|
|
87
|
+
const personaStage = ctx.lens ? { ...stage, lens: ctx.lens } : stage;
|
|
88
|
+
// grounded defaults true; only the M6 blind eval lane passes grounded: false.
|
|
89
|
+
const body = (deps.personaBody ?? personaBody)(personaStage, { grounded: ctx.grounded !== false });
|
|
90
|
+
const { filePath, cleanup } = await writePrompt(body);
|
|
91
|
+
const result = {
|
|
92
|
+
stageId: stage.id,
|
|
93
|
+
agent: stage.agent,
|
|
94
|
+
exitCode: 0,
|
|
95
|
+
finalOutput: "",
|
|
96
|
+
usage: { ...EMPTY_USAGE },
|
|
97
|
+
stderr: "",
|
|
98
|
+
toolCalls: 0,
|
|
99
|
+
toolErrors: 0,
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
const passthrough = buildStageInvocation(stage, {
|
|
103
|
+
ticket: ctx.ticket,
|
|
104
|
+
previous: ctx.previous,
|
|
105
|
+
promptFilePath: filePath,
|
|
106
|
+
lens: ctx.lens,
|
|
107
|
+
});
|
|
108
|
+
const { piCli, extensionPath } = resolveChild();
|
|
109
|
+
const argv = groundedChildArgv(passthrough, { piCli, extensionPath });
|
|
110
|
+
// Fold events into a fixed-size accumulator as they stream — never retain
|
|
111
|
+
// the full array (that OOMs on a verbose stage; YAG-317 follow-up).
|
|
112
|
+
const acc = newEventAccumulator();
|
|
113
|
+
let eventCount = 0;
|
|
114
|
+
let stderr = "";
|
|
115
|
+
let wasAborted = false;
|
|
116
|
+
const maxLineBytes = deps.maxLineBytes ?? DEFAULT_MAX_STDOUT_LINE_BYTES;
|
|
117
|
+
let overlongLinesDropped = 0;
|
|
118
|
+
// Env-gated diagnostic (no-op unless YAGNI_PIPELINE_DEBUG_DIR set). Captures
|
|
119
|
+
// per-stage stream metrics — total bytes, max single NDJSON line, event count,
|
|
120
|
+
// interim heap — plus the full final output. Used to characterise the plan
|
|
121
|
+
// `length` truncation and the implement-stage OOM. Safe to leave in.
|
|
122
|
+
const DEBUG_DIR = process.env.YAGNI_PIPELINE_DEBUG_DIR;
|
|
123
|
+
const dbgLabel = ctx.lens ? `${stage.id}-${ctx.lens}` : stage.id;
|
|
124
|
+
let dbgTotalBytes = 0, dbgMaxLineBytes = 0, dbgLineCount = 0, dbgLastLogBytes = 0;
|
|
125
|
+
const dbgLog = (note) => {
|
|
126
|
+
if (!DEBUG_DIR)
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
fs.mkdirSync(DEBUG_DIR, { recursive: true });
|
|
130
|
+
const m = process.memoryUsage();
|
|
131
|
+
fs.appendFileSync(path.join(DEBUG_DIR, `${dbgLabel}.stream.log`), `${new Date().toISOString()} ${note} bytes=${dbgTotalBytes} maxLineBytes=${dbgMaxLineBytes} lines=${dbgLineCount} events=${eventCount} heapUsedMB=${Math.round(m.heapUsed / 1048576)} rssMB=${Math.round(m.rss / 1048576)}\n`);
|
|
132
|
+
}
|
|
133
|
+
catch { /* ignore */ }
|
|
134
|
+
};
|
|
135
|
+
// YAG-471 attribution: every child builds its OWN provider config at init
|
|
136
|
+
// (reading its own env), so a per-child env var becomes a per-child header.
|
|
137
|
+
// The caller label defaults from the stage/lens being run; a run id is
|
|
138
|
+
// included only when the caller (the orchestrator, once its run is tracked)
|
|
139
|
+
// supplied one — its absence just means this child's completions attribute
|
|
140
|
+
// to the session id alone.
|
|
141
|
+
const callerLabel = deps.callerLabel ?? (ctx.lens ? `go:${stage.id}:${ctx.lens}` : `go:${stage.id}`);
|
|
142
|
+
const childEnv = {
|
|
143
|
+
...(deps.env ?? process.env),
|
|
144
|
+
YAGNI_CALLER: callerLabel,
|
|
145
|
+
...(deps.attribution?.runId ? { YAGNI_RUN_ID: deps.attribution.runId } : {}),
|
|
146
|
+
};
|
|
147
|
+
const exitCode = await new Promise((resolve) => {
|
|
148
|
+
const proc = spawnFn(process.execPath, argv, {
|
|
149
|
+
cwd: deps.cwd,
|
|
150
|
+
env: childEnv,
|
|
151
|
+
shell: false,
|
|
152
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
153
|
+
});
|
|
154
|
+
let buffer = "";
|
|
155
|
+
// `overlong` = we are mid-way through a line that already blew the cap; we
|
|
156
|
+
// discard incoming bytes until its terminating newline, then drop the line.
|
|
157
|
+
let overlong = false;
|
|
158
|
+
const processLine = (line) => {
|
|
159
|
+
if (DEBUG_DIR) {
|
|
160
|
+
const b = Buffer.byteLength(line, "utf8");
|
|
161
|
+
if (b > dbgMaxLineBytes)
|
|
162
|
+
dbgMaxLineBytes = b;
|
|
163
|
+
dbgLineCount++;
|
|
164
|
+
}
|
|
165
|
+
if (!line.trim())
|
|
166
|
+
return;
|
|
167
|
+
let event;
|
|
168
|
+
try {
|
|
169
|
+
event = JSON.parse(line);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
foldEvent(acc, event);
|
|
175
|
+
eventCount += 1;
|
|
176
|
+
deps.onEvent?.(event);
|
|
177
|
+
};
|
|
178
|
+
// Track real process exit explicitly. `ChildProcess.killed` is set the
|
|
179
|
+
// instant a signal is *sent*, not when the process exits — so it cannot
|
|
180
|
+
// gate the SIGKILL escalation (it is already true after SIGTERM). This
|
|
181
|
+
// flag flips only on the real `close`/`error`, so a child that ignores
|
|
182
|
+
// SIGTERM still gets force-killed after the grace window.
|
|
183
|
+
const signal = deps.signal;
|
|
184
|
+
let closed = false;
|
|
185
|
+
let killTimer;
|
|
186
|
+
const onAbort = () => {
|
|
187
|
+
wasAborted = true;
|
|
188
|
+
// pid guard: kill() on a child whose spawn FAILED resolves to pid 0 —
|
|
189
|
+
// the whole process group — and would take the parent down with it.
|
|
190
|
+
if (proc.pid)
|
|
191
|
+
proc.kill("SIGTERM");
|
|
192
|
+
killTimer = setTimeout(() => {
|
|
193
|
+
if (!closed && proc.pid)
|
|
194
|
+
proc.kill("SIGKILL");
|
|
195
|
+
}, 5000);
|
|
196
|
+
killTimer.unref?.();
|
|
197
|
+
};
|
|
198
|
+
const finish = (code) => {
|
|
199
|
+
closed = true;
|
|
200
|
+
if (killTimer)
|
|
201
|
+
clearTimeout(killTimer);
|
|
202
|
+
// Detach the abort listener on normal completion so it does not
|
|
203
|
+
// accumulate on the shared, long-lived pipeline signal across stages.
|
|
204
|
+
if (signal)
|
|
205
|
+
signal.removeEventListener("abort", onAbort);
|
|
206
|
+
resolve(code);
|
|
207
|
+
};
|
|
208
|
+
proc.stdout?.on("data", (data) => {
|
|
209
|
+
const s = data.toString();
|
|
210
|
+
if (DEBUG_DIR) {
|
|
211
|
+
dbgTotalBytes += Buffer.byteLength(s, "utf8");
|
|
212
|
+
if (dbgTotalBytes - dbgLastLogBytes > 20_000_000) {
|
|
213
|
+
dbgLastLogBytes = dbgTotalBytes;
|
|
214
|
+
dbgLog("stream");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Incremental newline scan — never re-split a growing buffer (that was
|
|
218
|
+
// O(n²) and OOM-crashed on a runaway single line). `buffer` only ever
|
|
219
|
+
// holds the current *partial* line, and is capped at maxLineBytes: an
|
|
220
|
+
// over-cap line is dropped, not accumulated.
|
|
221
|
+
let start = 0;
|
|
222
|
+
for (;;) {
|
|
223
|
+
const nl = s.indexOf("\n", start);
|
|
224
|
+
if (nl === -1) {
|
|
225
|
+
if (!overlong) {
|
|
226
|
+
buffer += s.slice(start);
|
|
227
|
+
if (buffer.length > maxLineBytes) {
|
|
228
|
+
overlong = true;
|
|
229
|
+
buffer = "";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
const seg = s.slice(start, nl);
|
|
235
|
+
start = nl + 1;
|
|
236
|
+
if (overlong) {
|
|
237
|
+
overlong = false;
|
|
238
|
+
overlongLinesDropped += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const line = buffer + seg;
|
|
242
|
+
buffer = "";
|
|
243
|
+
if (line.length > maxLineBytes) {
|
|
244
|
+
overlongLinesDropped += 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
processLine(line);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
proc.stderr?.on("data", (data) => {
|
|
251
|
+
stderr += data.toString();
|
|
252
|
+
});
|
|
253
|
+
proc.on("close", (code) => {
|
|
254
|
+
if (overlong) {
|
|
255
|
+
overlongLinesDropped += 1;
|
|
256
|
+
overlong = false;
|
|
257
|
+
buffer = "";
|
|
258
|
+
}
|
|
259
|
+
else if (buffer.trim())
|
|
260
|
+
processLine(buffer);
|
|
261
|
+
finish(code ?? 0);
|
|
262
|
+
});
|
|
263
|
+
proc.on("error", () => finish(1));
|
|
264
|
+
if (signal) {
|
|
265
|
+
if (signal.aborted)
|
|
266
|
+
onAbort();
|
|
267
|
+
else
|
|
268
|
+
signal.addEventListener("abort", onAbort);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
result.exitCode = exitCode;
|
|
272
|
+
result.stderr = stderr;
|
|
273
|
+
result.finalOutput = capOutput(finalOutputFrom(acc));
|
|
274
|
+
result.usage = acc.usage;
|
|
275
|
+
// R3-a: tap tool-result counts into stage health. The orchestrator turns
|
|
276
|
+
// this ratio into a write-stage failure when most edits errored, so a
|
|
277
|
+
// clean-looking summary can't mask a silent partial implement.
|
|
278
|
+
result.toolCalls = acc.toolCalls;
|
|
279
|
+
result.toolErrors = acc.toolErrors;
|
|
280
|
+
if (acc.stopReason)
|
|
281
|
+
result.stopReason = acc.stopReason;
|
|
282
|
+
if (acc.errorMessage)
|
|
283
|
+
result.errorMessage = acc.errorMessage;
|
|
284
|
+
if (wasAborted)
|
|
285
|
+
result.stopReason = "aborted";
|
|
286
|
+
if (overlongLinesDropped > 0)
|
|
287
|
+
result.overlongLinesDropped = overlongLinesDropped;
|
|
288
|
+
if (DEBUG_DIR) {
|
|
289
|
+
try {
|
|
290
|
+
fs.mkdirSync(DEBUG_DIR, { recursive: true });
|
|
291
|
+
const finalOut = finalOutputFrom(acc);
|
|
292
|
+
fs.writeFileSync(path.join(DEBUG_DIR, `${dbgLabel}.summary.json`), JSON.stringify({
|
|
293
|
+
stageId: stage.id, lens: ctx.lens, exitCode,
|
|
294
|
+
stopReason: result.stopReason, usage: result.usage,
|
|
295
|
+
events_count: eventCount, total_stream_bytes: dbgTotalBytes,
|
|
296
|
+
max_line_bytes: dbgMaxLineBytes, line_count: dbgLineCount,
|
|
297
|
+
final_output_bytes: Buffer.byteLength(finalOut, "utf8"),
|
|
298
|
+
final_output: finalOut,
|
|
299
|
+
}, null, 2));
|
|
300
|
+
dbgLog("stage_end");
|
|
301
|
+
}
|
|
302
|
+
catch { /* ignore */ }
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
cleanup();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=runner.js.map
|