@glrs-dev/cli 2.4.0 → 2.4.1
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/CHANGELOG.md +6 -0
- package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.d.ts +261 -0
- package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.js +488 -0
- package/dist/node_modules/@glrs-dev/adapter-opencode/package.json +8 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-LCT6LIH7.js +7 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/changeset-generator-DG3MVWVV.js +15 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-7OSEI5TF.js +249 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-E7PWTRFO.js +91 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-M2ZVBPWL.js +101 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-Q4ULU6ER.js +68 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-VITL2Z45.js +2772 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-ZNJWARTM.js +449 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/index.d.ts +1765 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/index.js +688 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/logger-UITJGIZE.js +8 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-XKL3NHUA.js +8 -0
- package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-D3RPJR2J.js +14 -0
- package/dist/node_modules/@glrs-dev/autopilot/package.json +8 -0
- package/dist/vendor/harness-opencode/dist/index.js +1 -1
- package/dist/vendor/harness-opencode/package.json +1 -1
- package/package.json +9 -7
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import {
|
|
2
|
+
enrichPlanForFastModel
|
|
3
|
+
} from "./chunk-ZNJWARTM.js";
|
|
4
|
+
import {
|
|
5
|
+
generateChangeset
|
|
6
|
+
} from "./chunk-E7PWTRFO.js";
|
|
7
|
+
import {
|
|
8
|
+
autoShip
|
|
9
|
+
} from "./chunk-M2ZVBPWL.js";
|
|
10
|
+
import {
|
|
11
|
+
MAX_ITERATIONS,
|
|
12
|
+
MAX_ITERATIONS_PER_PHASE_BY_TIER,
|
|
13
|
+
STALL_MS,
|
|
14
|
+
STALL_MS_BY_TIER,
|
|
15
|
+
STATUS_INTERVAL_MS,
|
|
16
|
+
STRUGGLE_THRESHOLD,
|
|
17
|
+
StruggleDetector,
|
|
18
|
+
TIMEOUT_MS,
|
|
19
|
+
buildConflictGraph,
|
|
20
|
+
checkKillSwitch,
|
|
21
|
+
createStatusHeartbeat,
|
|
22
|
+
createWorktree,
|
|
23
|
+
deleteCheckpoint,
|
|
24
|
+
detectSentinel,
|
|
25
|
+
formatCost,
|
|
26
|
+
formatElapsed,
|
|
27
|
+
getChangedFiles,
|
|
28
|
+
hasParallelism,
|
|
29
|
+
markPhaseCompleted,
|
|
30
|
+
mergeWorktree,
|
|
31
|
+
parseItems,
|
|
32
|
+
parsePlanState,
|
|
33
|
+
readCheckpoint,
|
|
34
|
+
recordHead,
|
|
35
|
+
resetSoft,
|
|
36
|
+
runLanes,
|
|
37
|
+
runLoopSession,
|
|
38
|
+
runRalphLoop,
|
|
39
|
+
runVerifyCommands,
|
|
40
|
+
validatePlan,
|
|
41
|
+
validateScope,
|
|
42
|
+
writeCheckpoint
|
|
43
|
+
} from "./chunk-VITL2Z45.js";
|
|
44
|
+
import {
|
|
45
|
+
childLogger,
|
|
46
|
+
createAutopilotLogger
|
|
47
|
+
} from "./chunk-Q4ULU6ER.js";
|
|
48
|
+
import {
|
|
49
|
+
detectSpecPhases,
|
|
50
|
+
filterUncheckedSpecPhases,
|
|
51
|
+
hasSpec,
|
|
52
|
+
parseSpecItems,
|
|
53
|
+
readSpecConstraints,
|
|
54
|
+
readSpecGoal,
|
|
55
|
+
validateMainSpec,
|
|
56
|
+
validatePhaseSpec
|
|
57
|
+
} from "./chunk-7OSEI5TF.js";
|
|
58
|
+
|
|
59
|
+
// src/event-stream.ts
|
|
60
|
+
import * as fs from "fs";
|
|
61
|
+
import * as path from "path";
|
|
62
|
+
var EventStreamWriter = class {
|
|
63
|
+
fd;
|
|
64
|
+
constructor(filePath) {
|
|
65
|
+
const dir = path.dirname(filePath);
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
this.fd = fs.openSync(filePath, "a");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Serialize `event` as a JSON line and write it synchronously.
|
|
71
|
+
* Sync write ensures the line is on disk even if the process crashes
|
|
72
|
+
* immediately after.
|
|
73
|
+
*/
|
|
74
|
+
emit(event) {
|
|
75
|
+
const line = JSON.stringify(event) + "\n";
|
|
76
|
+
const buf = Buffer.from(line, "utf8");
|
|
77
|
+
fs.writeSync(this.fd, buf);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Close the underlying file descriptor. Safe to call multiple times
|
|
81
|
+
* (subsequent calls are no-ops).
|
|
82
|
+
*/
|
|
83
|
+
close() {
|
|
84
|
+
try {
|
|
85
|
+
fs.closeSync(this.fd);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var EventStreamReader = class {
|
|
91
|
+
constructor(filePath) {
|
|
92
|
+
this.filePath = filePath;
|
|
93
|
+
}
|
|
94
|
+
filePath;
|
|
95
|
+
/**
|
|
96
|
+
* Read all events from the file. Returns an empty array if the file
|
|
97
|
+
* does not exist or is empty.
|
|
98
|
+
*/
|
|
99
|
+
readAll() {
|
|
100
|
+
let raw;
|
|
101
|
+
try {
|
|
102
|
+
raw = fs.readFileSync(this.filePath, "utf8");
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
return parseLines(raw);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Read events starting at `byteOffset`. Returns the parsed events and
|
|
110
|
+
* the new byte offset (= file size after reading). Useful for polling
|
|
111
|
+
* (tail mode): pass the returned `newOffset` on the next call to read
|
|
112
|
+
* only new events.
|
|
113
|
+
*
|
|
114
|
+
* Truncated or malformed last lines are skipped.
|
|
115
|
+
*/
|
|
116
|
+
readFrom(byteOffset) {
|
|
117
|
+
let stat;
|
|
118
|
+
try {
|
|
119
|
+
stat = fs.statSync(this.filePath);
|
|
120
|
+
} catch {
|
|
121
|
+
return { events: [], newOffset: byteOffset };
|
|
122
|
+
}
|
|
123
|
+
const fileSize = stat.size;
|
|
124
|
+
if (fileSize <= byteOffset) {
|
|
125
|
+
return { events: [], newOffset: byteOffset };
|
|
126
|
+
}
|
|
127
|
+
const length = fileSize - byteOffset;
|
|
128
|
+
const buf = Buffer.alloc(length);
|
|
129
|
+
const fd = fs.openSync(this.filePath, "r");
|
|
130
|
+
try {
|
|
131
|
+
fs.readSync(fd, buf, 0, length, byteOffset);
|
|
132
|
+
} finally {
|
|
133
|
+
fs.closeSync(fd);
|
|
134
|
+
}
|
|
135
|
+
const raw = buf.toString("utf8");
|
|
136
|
+
const events = parseLines(raw);
|
|
137
|
+
return { events, newOffset: fileSize };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function parseLines(raw) {
|
|
141
|
+
const events = [];
|
|
142
|
+
const lines = raw.split("\n");
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const trimmed = line.trim();
|
|
145
|
+
if (!trimmed) continue;
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(trimmed);
|
|
148
|
+
events.push(parsed);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return events;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/session-runner.ts
|
|
156
|
+
import { EventEmitter } from "events";
|
|
157
|
+
import * as fs2 from "fs";
|
|
158
|
+
import * as path2 from "path";
|
|
159
|
+
var SessionEventEmitter = class extends EventEmitter {
|
|
160
|
+
emitEvent(event) {
|
|
161
|
+
const eventName = event.type === "error" ? "session:error" : event.type;
|
|
162
|
+
this.emit(eventName, event);
|
|
163
|
+
this.emit("event", event);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var STATUS_WRITE_INTERVAL_MS = 5e3;
|
|
167
|
+
var LegacyStatusBridge = class {
|
|
168
|
+
statusFilePath;
|
|
169
|
+
lastWriteMs = 0;
|
|
170
|
+
startedAt = Date.now();
|
|
171
|
+
iterationsCompleted = 0;
|
|
172
|
+
cumulativeCostUsd = 0;
|
|
173
|
+
phasesCompleted = 0;
|
|
174
|
+
phaseCount = 0;
|
|
175
|
+
lastIterationProgress = false;
|
|
176
|
+
lastIterationErrored = false;
|
|
177
|
+
constructor(cwd) {
|
|
178
|
+
this.statusFilePath = path2.join(cwd, ".agent", "autopilot-status.json");
|
|
179
|
+
}
|
|
180
|
+
/** Update internal state from an event and conditionally write the file. */
|
|
181
|
+
onEvent(event) {
|
|
182
|
+
switch (event.type) {
|
|
183
|
+
case "session:start":
|
|
184
|
+
this.startedAt = Date.now();
|
|
185
|
+
this._writeNow();
|
|
186
|
+
return;
|
|
187
|
+
case "iteration:done":
|
|
188
|
+
this.iterationsCompleted = event.iteration;
|
|
189
|
+
this.lastIterationProgress = event.madeProgress ?? false;
|
|
190
|
+
this.lastIterationErrored = false;
|
|
191
|
+
break;
|
|
192
|
+
case "cost:update":
|
|
193
|
+
this.cumulativeCostUsd = event.cumulativeCostUsd;
|
|
194
|
+
break;
|
|
195
|
+
case "phase:start":
|
|
196
|
+
this.phaseCount = event.total;
|
|
197
|
+
break;
|
|
198
|
+
case "phase:done":
|
|
199
|
+
this.phasesCompleted += 1;
|
|
200
|
+
break;
|
|
201
|
+
case "error":
|
|
202
|
+
this.lastIterationErrored = true;
|
|
203
|
+
break;
|
|
204
|
+
case "session:done":
|
|
205
|
+
if (event.cumulativeCostUsd !== void 0) {
|
|
206
|
+
this.cumulativeCostUsd = event.cumulativeCostUsd;
|
|
207
|
+
}
|
|
208
|
+
this._writeNow();
|
|
209
|
+
return;
|
|
210
|
+
default:
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
if (now - this.lastWriteMs >= STATUS_WRITE_INTERVAL_MS) {
|
|
215
|
+
this._writeNow();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
_writeNow() {
|
|
219
|
+
const now = Date.now();
|
|
220
|
+
this.lastWriteMs = now;
|
|
221
|
+
const state = {
|
|
222
|
+
startedAt: this.startedAt,
|
|
223
|
+
iterationsCompleted: this.iterationsCompleted,
|
|
224
|
+
cumulativeCostUsd: this.cumulativeCostUsd,
|
|
225
|
+
lastIterationProgress: this.lastIterationProgress,
|
|
226
|
+
lastIterationErrored: this.lastIterationErrored,
|
|
227
|
+
phaseCount: this.phaseCount > 0 ? this.phaseCount : void 0,
|
|
228
|
+
phasesCompleted: this.phasesCompleted > 0 ? this.phasesCompleted : void 0
|
|
229
|
+
};
|
|
230
|
+
const snapshot = {
|
|
231
|
+
...state,
|
|
232
|
+
elapsedMs: now - this.startedAt,
|
|
233
|
+
writtenAt: new Date(now).toISOString()
|
|
234
|
+
};
|
|
235
|
+
try {
|
|
236
|
+
const dir = path2.dirname(this.statusFilePath);
|
|
237
|
+
if (!fs2.existsSync(dir)) {
|
|
238
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
239
|
+
}
|
|
240
|
+
const tmp = `${this.statusFilePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
|
|
241
|
+
fs2.writeFileSync(tmp, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
242
|
+
fs2.renameSync(tmp, this.statusFilePath);
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
var SessionRunner = class {
|
|
248
|
+
/** Channel 1: in-process event emitter. Subscribe to event types or "event" for all. */
|
|
249
|
+
events;
|
|
250
|
+
opts;
|
|
251
|
+
_abortController = null;
|
|
252
|
+
_abortCount = 0;
|
|
253
|
+
constructor(opts) {
|
|
254
|
+
this.opts = opts;
|
|
255
|
+
this.events = new SessionEventEmitter();
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Request graceful shutdown. First call signals the running loop session
|
|
259
|
+
* to stop at the next phase boundary (checkpoint is written). Second call
|
|
260
|
+
* force-exits via process.exit(1).
|
|
261
|
+
*/
|
|
262
|
+
abort() {
|
|
263
|
+
this._abortCount++;
|
|
264
|
+
if (this._abortCount === 1) {
|
|
265
|
+
if (this._abortController) {
|
|
266
|
+
this._abortController.abort();
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Run the autopilot session:
|
|
274
|
+
* 1. Emit session:start
|
|
275
|
+
* 2. If --fast, run enrichment (emitting enrich:* events)
|
|
276
|
+
* 3. Run the loop session (emitting phase:* events via loop-session.ts)
|
|
277
|
+
* 4. Emit session:done
|
|
278
|
+
*
|
|
279
|
+
* Returns a SessionResult with the final LoopResult.
|
|
280
|
+
*/
|
|
281
|
+
async run() {
|
|
282
|
+
this._abortController = new AbortController();
|
|
283
|
+
this._abortCount = 0;
|
|
284
|
+
const { planPath, cwd, fast, resume, parallel, ship, maxIterationsPerPhase } = this.opts;
|
|
285
|
+
const eventStreamPath = this.opts.eventStreamPath ?? path2.join(cwd, ".agent", "autopilot-events.jsonl");
|
|
286
|
+
const _createWriter = this.opts._deps?.createWriter ?? ((fp) => new EventStreamWriter(fp));
|
|
287
|
+
const _createLogger = this.opts._deps?.createLogger;
|
|
288
|
+
const _enrichPlan = this.opts._deps?.enrichPlan;
|
|
289
|
+
const _runLoopSession = this.opts._deps?.runLoopSession;
|
|
290
|
+
const writer = _createWriter(eventStreamPath);
|
|
291
|
+
const statusBridge = new LegacyStatusBridge(cwd);
|
|
292
|
+
const emitEvent = (event) => {
|
|
293
|
+
this.events.emitEvent(event);
|
|
294
|
+
writer.emit(event);
|
|
295
|
+
statusBridge.onEvent(event);
|
|
296
|
+
};
|
|
297
|
+
let logger;
|
|
298
|
+
if (_createLogger) {
|
|
299
|
+
logger = _createLogger({ cwd });
|
|
300
|
+
} else {
|
|
301
|
+
const { createAutopilotLogger: createAutopilotLogger2 } = await import("./logger-UITJGIZE.js");
|
|
302
|
+
logger = createAutopilotLogger2({ cwd });
|
|
303
|
+
}
|
|
304
|
+
let enrichModel = "unknown";
|
|
305
|
+
let executeModel = "unknown";
|
|
306
|
+
try {
|
|
307
|
+
const { join: join3 } = await import("path");
|
|
308
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
309
|
+
const configHome = process.env["XDG_CONFIG_HOME"] ?? join3(process.env["HOME"] ?? "", ".config");
|
|
310
|
+
const configPath = join3(configHome, "opencode", "opencode.json");
|
|
311
|
+
const raw = readFileSync3(configPath, "utf8");
|
|
312
|
+
const config = JSON.parse(raw);
|
|
313
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
314
|
+
for (const entry of plugins) {
|
|
315
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
316
|
+
const opts2 = entry[1];
|
|
317
|
+
const models = opts2?.models;
|
|
318
|
+
if (models) {
|
|
319
|
+
const deepArr = models["deep"] ?? models["prime"];
|
|
320
|
+
if (Array.isArray(deepArr) && deepArr[0]) enrichModel = deepArr[0];
|
|
321
|
+
const execArr = models["autopilot-execute"] ?? models["mid-execute"] ?? models["mid"];
|
|
322
|
+
if (Array.isArray(execArr) && execArr[0]) executeModel = execArr[0];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (typeof config.model === "string" && enrichModel === "unknown") {
|
|
327
|
+
enrichModel = config.model;
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
emitEvent({
|
|
332
|
+
type: "session:start",
|
|
333
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
|
+
planPath,
|
|
335
|
+
cwd,
|
|
336
|
+
fast: fast ?? false,
|
|
337
|
+
resume: resume ?? false,
|
|
338
|
+
enrichModel,
|
|
339
|
+
executeModel
|
|
340
|
+
});
|
|
341
|
+
let loopResult;
|
|
342
|
+
try {
|
|
343
|
+
if (fast && planPath) {
|
|
344
|
+
emitEvent({
|
|
345
|
+
type: "enrich:start",
|
|
346
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
347
|
+
planPath,
|
|
348
|
+
fileCount: 0
|
|
349
|
+
// file count is determined inside enrichPlanForFastModel
|
|
350
|
+
});
|
|
351
|
+
try {
|
|
352
|
+
if (_enrichPlan) {
|
|
353
|
+
await _enrichPlan(cwd, planPath, logger);
|
|
354
|
+
emitEvent({
|
|
355
|
+
type: "enrich:done",
|
|
356
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
|
+
filesProcessed: 0
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
const { enrichPlanForFastModel: enrichPlanForFastModel2 } = await import("./plan-enrichment-D3RPJR2J.js");
|
|
361
|
+
await enrichPlanForFastModel2(cwd, planPath, logger, this.events, this.opts.adapter);
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
365
|
+
emitEvent({
|
|
366
|
+
type: "error",
|
|
367
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
368
|
+
message: `Enrichment failed: ${message}`
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const loopOpts = {
|
|
373
|
+
planPath,
|
|
374
|
+
cwd,
|
|
375
|
+
fast,
|
|
376
|
+
resume,
|
|
377
|
+
parallel,
|
|
378
|
+
ship,
|
|
379
|
+
maxIterationsPerPhase,
|
|
380
|
+
logger,
|
|
381
|
+
emitter: this.events,
|
|
382
|
+
adapter: this.opts.adapter,
|
|
383
|
+
signal: this._abortController?.signal
|
|
384
|
+
};
|
|
385
|
+
if (_runLoopSession) {
|
|
386
|
+
loopResult = await _runLoopSession(loopOpts);
|
|
387
|
+
} else {
|
|
388
|
+
const { runLoopSession: runLoopSession2 } = await import("./loop-session-XKL3NHUA.js");
|
|
389
|
+
loopResult = await runLoopSession2(loopOpts);
|
|
390
|
+
}
|
|
391
|
+
} catch (err) {
|
|
392
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
393
|
+
emitEvent({
|
|
394
|
+
type: "error",
|
|
395
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
396
|
+
message
|
|
397
|
+
});
|
|
398
|
+
loopResult = {
|
|
399
|
+
exitReason: "error",
|
|
400
|
+
iterations: 0,
|
|
401
|
+
message
|
|
402
|
+
};
|
|
403
|
+
} finally {
|
|
404
|
+
if (logger) {
|
|
405
|
+
await logger.flush().catch(() => {
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
emitEvent({
|
|
410
|
+
type: "session:done",
|
|
411
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
412
|
+
exitReason: loopResult.exitReason,
|
|
413
|
+
iterations: loopResult.iterations,
|
|
414
|
+
cumulativeCostUsd: loopResult.cumulativeCostUsd,
|
|
415
|
+
message: loopResult.message
|
|
416
|
+
});
|
|
417
|
+
writer.close();
|
|
418
|
+
return { planPath, loopResult };
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/session-state.ts
|
|
423
|
+
function deriveId(planPath, startedAt) {
|
|
424
|
+
const raw = `${planPath}|${startedAt}`;
|
|
425
|
+
return Buffer.from(raw).toString("base64url").slice(0, 24);
|
|
426
|
+
}
|
|
427
|
+
function deriveState(events) {
|
|
428
|
+
const startEvent = events.find((e) => e.type === "session:start");
|
|
429
|
+
if (!startEvent || startEvent.type !== "session:start") {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const id = deriveId(startEvent.planPath, startEvent.timestamp);
|
|
433
|
+
let status = "running";
|
|
434
|
+
let currentPhase = void 0;
|
|
435
|
+
let currentIteration = void 0;
|
|
436
|
+
let totalIterations = 0;
|
|
437
|
+
let cost = 0;
|
|
438
|
+
let lastEventAt = startEvent.timestamp;
|
|
439
|
+
let error = void 0;
|
|
440
|
+
let exitReason = void 0;
|
|
441
|
+
let enrichProgress = void 0;
|
|
442
|
+
let verifyProgress = void 0;
|
|
443
|
+
for (const event of events) {
|
|
444
|
+
lastEventAt = event.timestamp;
|
|
445
|
+
switch (event.type) {
|
|
446
|
+
case "session:start":
|
|
447
|
+
status = "running";
|
|
448
|
+
break;
|
|
449
|
+
case "session:done":
|
|
450
|
+
status = "complete";
|
|
451
|
+
exitReason = event.exitReason;
|
|
452
|
+
totalIterations = event.iterations;
|
|
453
|
+
if (event.cumulativeCostUsd !== void 0) {
|
|
454
|
+
cost = event.cumulativeCostUsd;
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case "enrich:start":
|
|
458
|
+
status = "enriching";
|
|
459
|
+
enrichProgress = { done: 0, total: event.fileCount };
|
|
460
|
+
break;
|
|
461
|
+
case "enrich:file:done":
|
|
462
|
+
case "enrich:file:skip":
|
|
463
|
+
case "enrich:file:error":
|
|
464
|
+
if (enrichProgress) {
|
|
465
|
+
enrichProgress = { done: enrichProgress.done + 1, total: enrichProgress.total };
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
case "enrich:file:start":
|
|
469
|
+
break;
|
|
470
|
+
case "enrich:done":
|
|
471
|
+
status = "running";
|
|
472
|
+
enrichProgress = enrichProgress ? { done: enrichProgress.total, total: enrichProgress.total } : void 0;
|
|
473
|
+
break;
|
|
474
|
+
case "phase:start":
|
|
475
|
+
status = "running";
|
|
476
|
+
currentPhase = {
|
|
477
|
+
phase: event.phase,
|
|
478
|
+
current: event.current,
|
|
479
|
+
total: event.total
|
|
480
|
+
};
|
|
481
|
+
break;
|
|
482
|
+
case "phase:done":
|
|
483
|
+
currentPhase = void 0;
|
|
484
|
+
currentIteration = void 0;
|
|
485
|
+
break;
|
|
486
|
+
case "iteration:start":
|
|
487
|
+
status = "running";
|
|
488
|
+
currentIteration = {
|
|
489
|
+
iteration: event.iteration,
|
|
490
|
+
max: event.maxIterations
|
|
491
|
+
};
|
|
492
|
+
break;
|
|
493
|
+
case "iteration:done":
|
|
494
|
+
totalIterations = event.iteration;
|
|
495
|
+
currentIteration = void 0;
|
|
496
|
+
if (event.costUsd !== void 0) {
|
|
497
|
+
cost = event.costUsd;
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
case "cost:update":
|
|
501
|
+
cost = event.cumulativeCostUsd;
|
|
502
|
+
break;
|
|
503
|
+
case "error":
|
|
504
|
+
status = "error";
|
|
505
|
+
error = event.message;
|
|
506
|
+
break;
|
|
507
|
+
case "credential:expired":
|
|
508
|
+
status = "error";
|
|
509
|
+
error = `Credential expired: ${event.provider} \u2014 ${event.message}`;
|
|
510
|
+
break;
|
|
511
|
+
case "verify:start":
|
|
512
|
+
status = "verifying";
|
|
513
|
+
verifyProgress = { passed: 0, total: event.itemCount };
|
|
514
|
+
break;
|
|
515
|
+
case "verify:result":
|
|
516
|
+
if (verifyProgress) {
|
|
517
|
+
verifyProgress = {
|
|
518
|
+
passed: verifyProgress.passed + (event.passed ? 1 : 0),
|
|
519
|
+
total: verifyProgress.total
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
case "verify:done":
|
|
524
|
+
status = "running";
|
|
525
|
+
verifyProgress = verifyProgress ? { passed: verifyProgress.passed, total: event.passed + event.failed } : void 0;
|
|
526
|
+
break;
|
|
527
|
+
case "tool:call":
|
|
528
|
+
case "thinking":
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
id,
|
|
534
|
+
planPath: startEvent.planPath,
|
|
535
|
+
cwd: startEvent.cwd,
|
|
536
|
+
fast: startEvent.fast,
|
|
537
|
+
resume: startEvent.resume,
|
|
538
|
+
status,
|
|
539
|
+
currentPhase,
|
|
540
|
+
currentIteration,
|
|
541
|
+
totalIterations,
|
|
542
|
+
cost,
|
|
543
|
+
startedAt: startEvent.timestamp,
|
|
544
|
+
lastEventAt,
|
|
545
|
+
error,
|
|
546
|
+
exitReason,
|
|
547
|
+
enrichProgress,
|
|
548
|
+
verifyProgress
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/plan-session.ts
|
|
553
|
+
import * as fs3 from "fs";
|
|
554
|
+
import * as path3 from "path";
|
|
555
|
+
var DEFAULT_PLAN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
556
|
+
async function runPlanSession(opts) {
|
|
557
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_PLAN_TIMEOUT_MS;
|
|
558
|
+
const adapter = opts.adapter;
|
|
559
|
+
const _existsSync = opts._deps?.existsSync ?? fs3.existsSync;
|
|
560
|
+
const handle = await adapter.start({ cwd: opts.planDir });
|
|
561
|
+
try {
|
|
562
|
+
const sessionId = await adapter.createSession(handle, {
|
|
563
|
+
agentName: "plan"
|
|
564
|
+
});
|
|
565
|
+
const multiFileDir = path3.join(opts.planDir, opts.slug);
|
|
566
|
+
const multiFileMain = path3.join(multiFileDir, "main.md");
|
|
567
|
+
const singleFilePlan = path3.join(opts.planDir, `${opts.slug}.md`);
|
|
568
|
+
const prompt = `Read the scope at ${opts.scopePath} and produce a plan. Use slug ${opts.slug} for the plan file(s). If the scope warrants multiple phases, produce a multi-file plan at ${multiFileDir}/main.md + phase_N.md files. Otherwise produce a single-file plan at ${singleFilePlan}.`;
|
|
569
|
+
const result = await adapter.sendAndWait(handle, {
|
|
570
|
+
sessionId,
|
|
571
|
+
message: prompt,
|
|
572
|
+
stallMs: timeoutMs
|
|
573
|
+
});
|
|
574
|
+
if (result.kind === "abort") {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`Plan session aborted (timeout after ${timeoutMs}ms).`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
if (result.kind === "stall") {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`Plan session stalled for ${result.stallMs}ms with no idle signal.`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (result.kind === "error") {
|
|
585
|
+
throw new Error(`Plan session error: ${result.message}`);
|
|
586
|
+
}
|
|
587
|
+
if (result.kind === "question_rejected") {
|
|
588
|
+
}
|
|
589
|
+
if (_existsSync(multiFileMain)) {
|
|
590
|
+
return { planPath: multiFileDir };
|
|
591
|
+
}
|
|
592
|
+
if (_existsSync(singleFilePlan)) {
|
|
593
|
+
return { planPath: singleFilePlan };
|
|
594
|
+
}
|
|
595
|
+
const retryPrompt = `You did not write a plan file. Write the plan NOW. Read the scope at ${opts.scopePath}. Write the plan to ${singleFilePlan} (single-file) or ${multiFileDir}/main.md (multi-file). Do NOT ask questions. Just write the plan.`;
|
|
596
|
+
const retryResult = await adapter.sendAndWait(handle, {
|
|
597
|
+
sessionId,
|
|
598
|
+
message: retryPrompt,
|
|
599
|
+
stallMs: timeoutMs
|
|
600
|
+
});
|
|
601
|
+
if (retryResult.kind !== "idle" && retryResult.kind !== "question_rejected") {
|
|
602
|
+
throw new Error(`@plan retry failed: ${retryResult.kind}`);
|
|
603
|
+
}
|
|
604
|
+
if (_existsSync(multiFileMain)) {
|
|
605
|
+
return { planPath: multiFileDir };
|
|
606
|
+
}
|
|
607
|
+
if (_existsSync(singleFilePlan)) {
|
|
608
|
+
return { planPath: singleFilePlan };
|
|
609
|
+
}
|
|
610
|
+
const scopeContent = fs3.existsSync(opts.scopePath) ? fs3.readFileSync(opts.scopePath, "utf-8") : `# Plan
|
|
611
|
+
|
|
612
|
+
Scope file not found at ${opts.scopePath}.`;
|
|
613
|
+
const minimalPlan = [
|
|
614
|
+
`# Plan (auto-generated from scope)`,
|
|
615
|
+
"",
|
|
616
|
+
"This plan was auto-generated because @plan did not produce a plan file.",
|
|
617
|
+
"Review and refine before executing.",
|
|
618
|
+
"",
|
|
619
|
+
scopeContent,
|
|
620
|
+
"",
|
|
621
|
+
"## Acceptance criteria",
|
|
622
|
+
"",
|
|
623
|
+
"- [ ] Review and refine this auto-generated plan",
|
|
624
|
+
"",
|
|
625
|
+
"## File-level changes",
|
|
626
|
+
"",
|
|
627
|
+
"- To be determined after plan review."
|
|
628
|
+
].join("\n");
|
|
629
|
+
fs3.mkdirSync(path3.dirname(singleFilePlan), { recursive: true });
|
|
630
|
+
fs3.writeFileSync(singleFilePlan, minimalPlan);
|
|
631
|
+
return { planPath: singleFilePlan };
|
|
632
|
+
} finally {
|
|
633
|
+
await adapter.shutdown(handle);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
export {
|
|
637
|
+
EventStreamReader,
|
|
638
|
+
EventStreamWriter,
|
|
639
|
+
MAX_ITERATIONS,
|
|
640
|
+
MAX_ITERATIONS_PER_PHASE_BY_TIER,
|
|
641
|
+
STALL_MS,
|
|
642
|
+
STALL_MS_BY_TIER,
|
|
643
|
+
STATUS_INTERVAL_MS,
|
|
644
|
+
STRUGGLE_THRESHOLD,
|
|
645
|
+
SessionEventEmitter,
|
|
646
|
+
SessionRunner,
|
|
647
|
+
StruggleDetector,
|
|
648
|
+
TIMEOUT_MS,
|
|
649
|
+
autoShip,
|
|
650
|
+
buildConflictGraph,
|
|
651
|
+
checkKillSwitch,
|
|
652
|
+
childLogger,
|
|
653
|
+
createAutopilotLogger,
|
|
654
|
+
createStatusHeartbeat,
|
|
655
|
+
createWorktree,
|
|
656
|
+
deleteCheckpoint,
|
|
657
|
+
deriveState,
|
|
658
|
+
detectSentinel,
|
|
659
|
+
detectSpecPhases,
|
|
660
|
+
enrichPlanForFastModel,
|
|
661
|
+
filterUncheckedSpecPhases,
|
|
662
|
+
formatCost,
|
|
663
|
+
formatElapsed,
|
|
664
|
+
generateChangeset,
|
|
665
|
+
getChangedFiles,
|
|
666
|
+
hasParallelism,
|
|
667
|
+
hasSpec,
|
|
668
|
+
markPhaseCompleted,
|
|
669
|
+
mergeWorktree,
|
|
670
|
+
parseItems,
|
|
671
|
+
parsePlanState,
|
|
672
|
+
parseSpecItems,
|
|
673
|
+
readCheckpoint,
|
|
674
|
+
readSpecConstraints,
|
|
675
|
+
readSpecGoal,
|
|
676
|
+
recordHead,
|
|
677
|
+
resetSoft,
|
|
678
|
+
runLanes,
|
|
679
|
+
runLoopSession,
|
|
680
|
+
runPlanSession,
|
|
681
|
+
runRalphLoop,
|
|
682
|
+
runVerifyCommands,
|
|
683
|
+
validateMainSpec,
|
|
684
|
+
validatePhaseSpec,
|
|
685
|
+
validatePlan,
|
|
686
|
+
validateScope,
|
|
687
|
+
writeCheckpoint
|
|
688
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ENRICHMENT_RATIO_THRESHOLD,
|
|
3
|
+
computeEnrichmentRatio,
|
|
4
|
+
computeSpecEnrichmentRatio,
|
|
5
|
+
enrichPlanForFastModel
|
|
6
|
+
} from "./chunk-ZNJWARTM.js";
|
|
7
|
+
import "./chunk-Q4ULU6ER.js";
|
|
8
|
+
import "./chunk-7OSEI5TF.js";
|
|
9
|
+
export {
|
|
10
|
+
ENRICHMENT_RATIO_THRESHOLD,
|
|
11
|
+
computeEnrichmentRatio,
|
|
12
|
+
computeSpecEnrichmentRatio,
|
|
13
|
+
enrichPlanForFastModel
|
|
14
|
+
};
|