baro-ai 0.51.3 → 0.51.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +700 -237
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -13750,8 +13750,8 @@ var require_jwa = __commonJS({
|
|
|
13750
13750
|
}
|
|
13751
13751
|
function typeError(template) {
|
|
13752
13752
|
var args = [].slice.call(arguments, 1);
|
|
13753
|
-
var
|
|
13754
|
-
return new TypeError(
|
|
13753
|
+
var errMsg2 = util.format.bind(util, template).apply(null, args);
|
|
13754
|
+
return new TypeError(errMsg2);
|
|
13755
13755
|
}
|
|
13756
13756
|
function bufferOrString(obj) {
|
|
13757
13757
|
return Buffer4.isBuffer(obj) || typeof obj === "string";
|
|
@@ -22243,9 +22243,9 @@ var require_websocket_server = __commonJS({
|
|
|
22243
22243
|
|
|
22244
22244
|
// ../baro-memory/dist/vectra-store.js
|
|
22245
22245
|
import { LocalIndex } from "vectra";
|
|
22246
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as
|
|
22247
|
-
import { join as
|
|
22248
|
-
import { tmpdir, homedir } from "os";
|
|
22246
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, renameSync, rmSync as rmSync2, readdirSync, statSync as statSync2, lstatSync } from "fs";
|
|
22247
|
+
import { join as join3 } from "path";
|
|
22248
|
+
import { tmpdir as tmpdir2, homedir } from "os";
|
|
22249
22249
|
async function createMemoryStore(config) {
|
|
22250
22250
|
const cfg = { ...DEFAULTS, ...config };
|
|
22251
22251
|
if (cfg.defaultMinSimilarity < 0 || cfg.defaultMinSimilarity > 1) {
|
|
@@ -22257,23 +22257,23 @@ async function createMemoryStore(config) {
|
|
|
22257
22257
|
if (cfg.disabled) {
|
|
22258
22258
|
return new NoOpMemoryStore();
|
|
22259
22259
|
}
|
|
22260
|
-
const sessionPath = cfg.sessionPath ||
|
|
22260
|
+
const sessionPath = cfg.sessionPath || join3(tmpdir2(), `baro-memory-${process.pid}-${Date.now()}`);
|
|
22261
22261
|
validateSessionPath(sessionPath);
|
|
22262
22262
|
mkdirSync2(sessionPath, { recursive: true });
|
|
22263
|
-
const indexPath =
|
|
22263
|
+
const indexPath = join3(sessionPath, "index");
|
|
22264
22264
|
mkdirSync2(indexPath, { recursive: true });
|
|
22265
22265
|
const index = new LocalIndex(indexPath);
|
|
22266
22266
|
if (!await index.isIndexCreated()) {
|
|
22267
22267
|
await index.createIndex({ version: 1 });
|
|
22268
22268
|
}
|
|
22269
22269
|
const transformers = await import("@xenova/transformers");
|
|
22270
|
-
transformers.env.cacheDir = process.env.TRANSFORMERS_CACHE ||
|
|
22270
|
+
transformers.env.cacheDir = process.env.TRANSFORMERS_CACHE || join3(homedir(), ".baro", "models");
|
|
22271
22271
|
const { pipeline: pipeline2 } = transformers;
|
|
22272
22272
|
const extractor = await pipeline2("feature-extraction", cfg.embeddingModel);
|
|
22273
22273
|
return new VectraMemoryStore(index, extractor, sessionPath, cfg);
|
|
22274
22274
|
}
|
|
22275
22275
|
function validateSessionPath(sessionPath) {
|
|
22276
|
-
const resolved =
|
|
22276
|
+
const resolved = join3(sessionPath);
|
|
22277
22277
|
if (resolved.includes("..")) {
|
|
22278
22278
|
throw new Error(`Invalid session path (contains ..): ${resolved}`);
|
|
22279
22279
|
}
|
|
@@ -22284,26 +22284,26 @@ function validateSessionPath(sessionPath) {
|
|
|
22284
22284
|
}
|
|
22285
22285
|
}
|
|
22286
22286
|
const normalizedPath = resolved.toLowerCase();
|
|
22287
|
-
const isSafe = ALLOWED_SESSION_PARENTS.some((p) => normalizedPath.includes(p)) || normalizedPath.startsWith(
|
|
22287
|
+
const isSafe = ALLOWED_SESSION_PARENTS.some((p) => normalizedPath.includes(p)) || normalizedPath.startsWith(tmpdir2().toLowerCase());
|
|
22288
22288
|
if (!isSafe) {
|
|
22289
22289
|
throw new Error(`Invalid session path (must be under ~/.baro, tmpdir, or contain 'baro-memory'): ${resolved}`);
|
|
22290
22290
|
}
|
|
22291
22291
|
}
|
|
22292
22292
|
function pruneOldSessions(sessionsDir) {
|
|
22293
22293
|
try {
|
|
22294
|
-
if (!
|
|
22294
|
+
if (!existsSync3(sessionsDir))
|
|
22295
22295
|
return;
|
|
22296
22296
|
const now = Date.now();
|
|
22297
22297
|
for (const entry of readdirSync(sessionsDir)) {
|
|
22298
22298
|
if (!entry.startsWith("run-"))
|
|
22299
22299
|
continue;
|
|
22300
|
-
const entryPath =
|
|
22300
|
+
const entryPath = join3(sessionsDir, entry);
|
|
22301
22301
|
try {
|
|
22302
22302
|
const stat2 = lstatSync(entryPath);
|
|
22303
22303
|
if (stat2.isSymbolicLink())
|
|
22304
22304
|
continue;
|
|
22305
22305
|
if (stat2.isDirectory() && now - stat2.mtimeMs > SESSION_TTL_MS) {
|
|
22306
|
-
|
|
22306
|
+
rmSync2(entryPath, { recursive: true, force: true });
|
|
22307
22307
|
}
|
|
22308
22308
|
} catch {
|
|
22309
22309
|
}
|
|
@@ -22354,10 +22354,10 @@ var init_vectra_store = __esm({
|
|
|
22354
22354
|
this.index = index;
|
|
22355
22355
|
this.extractor = extractor;
|
|
22356
22356
|
this.sessionPath = sessionPath;
|
|
22357
|
-
this.indexPath =
|
|
22358
|
-
this.indexFilePath =
|
|
22359
|
-
this.cachePath =
|
|
22360
|
-
this.lockPath =
|
|
22357
|
+
this.indexPath = join3(sessionPath, "index");
|
|
22358
|
+
this.indexFilePath = join3(this.indexPath, "index.json");
|
|
22359
|
+
this.cachePath = join3(sessionPath, "cache.json");
|
|
22360
|
+
this.lockPath = join3(sessionPath, "cache.lock");
|
|
22361
22361
|
this.config = config;
|
|
22362
22362
|
}
|
|
22363
22363
|
/**
|
|
@@ -22381,7 +22381,7 @@ var init_vectra_store = __esm({
|
|
|
22381
22381
|
*/
|
|
22382
22382
|
refreshIndexIfChanged() {
|
|
22383
22383
|
try {
|
|
22384
|
-
if (!
|
|
22384
|
+
if (!existsSync3(this.indexFilePath))
|
|
22385
22385
|
return;
|
|
22386
22386
|
const mtimeMs = statSync2(this.indexFilePath).mtimeMs;
|
|
22387
22387
|
if (mtimeMs === this.lastIndexMtimeMs)
|
|
@@ -22556,7 +22556,7 @@ ${result.content}
|
|
|
22556
22556
|
}
|
|
22557
22557
|
async close() {
|
|
22558
22558
|
try {
|
|
22559
|
-
|
|
22559
|
+
rmSync2(this.lockPath, { force: true });
|
|
22560
22560
|
} catch {
|
|
22561
22561
|
}
|
|
22562
22562
|
}
|
|
@@ -22605,7 +22605,7 @@ ${result.content}
|
|
|
22605
22605
|
*/
|
|
22606
22606
|
loadCache() {
|
|
22607
22607
|
try {
|
|
22608
|
-
if (
|
|
22608
|
+
if (existsSync3(this.cachePath)) {
|
|
22609
22609
|
const raw = readFileSync3(this.cachePath, "utf-8");
|
|
22610
22610
|
if (raw.trim()) {
|
|
22611
22611
|
return JSON.parse(raw);
|
|
@@ -22633,8 +22633,8 @@ ${result.content}
|
|
|
22633
22633
|
}
|
|
22634
22634
|
} finally {
|
|
22635
22635
|
try {
|
|
22636
|
-
if (
|
|
22637
|
-
|
|
22636
|
+
if (existsSync3(this.lockPath))
|
|
22637
|
+
rmSync2(this.lockPath);
|
|
22638
22638
|
} catch {
|
|
22639
22639
|
}
|
|
22640
22640
|
}
|
|
@@ -22692,12 +22692,12 @@ var init_dist2 = __esm({
|
|
|
22692
22692
|
});
|
|
22693
22693
|
|
|
22694
22694
|
// ../baro-orchestrator/scripts/cli.ts
|
|
22695
|
-
import { existsSync as
|
|
22695
|
+
import { existsSync as existsSync6 } from "fs";
|
|
22696
22696
|
import { resolve as resolve3 } from "path";
|
|
22697
22697
|
|
|
22698
22698
|
// ../baro-orchestrator/src/orchestrate.ts
|
|
22699
22699
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
22700
|
-
import { dirname as dirname3, join as
|
|
22700
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
22701
22701
|
|
|
22702
22702
|
// ../../node_modules/openai/internal/tslib.mjs
|
|
22703
22703
|
function __classPrivateFieldSet(receiver, state, value, kind, f3) {
|
|
@@ -39880,7 +39880,7 @@ async function safePullRebase(cwd, onLog, gate) {
|
|
|
39880
39880
|
} catch {
|
|
39881
39881
|
}
|
|
39882
39882
|
try {
|
|
39883
|
-
await exec("git", ["pull", "--rebase", "origin", branch], { cwd });
|
|
39883
|
+
await exec("git", ["pull", "--rebase=merges", "origin", branch], { cwd });
|
|
39884
39884
|
onLog?.("[git] pull ok");
|
|
39885
39885
|
} catch {
|
|
39886
39886
|
onLog?.("[git] pull conflict, continuing without pull");
|
|
@@ -39923,7 +39923,7 @@ async function gitPushWithRetry(gate, options) {
|
|
|
39923
39923
|
`[git] push rejected (attempt ${attempt}/${max}), pulling and retrying...`
|
|
39924
39924
|
);
|
|
39925
39925
|
try {
|
|
39926
|
-
await exec("git", ["pull", "--rebase", "origin", branch], {
|
|
39926
|
+
await exec("git", ["pull", "--rebase=merges", "origin", branch], {
|
|
39927
39927
|
cwd: options.cwd
|
|
39928
39928
|
});
|
|
39929
39929
|
} catch {
|
|
@@ -39991,6 +39991,316 @@ function extractStderr(e2) {
|
|
|
39991
39991
|
return e2 instanceof Error ? e2.message : String(e2);
|
|
39992
39992
|
}
|
|
39993
39993
|
|
|
39994
|
+
// ../baro-orchestrator/src/worktree.ts
|
|
39995
|
+
import { execFile as execFile2 } from "child_process";
|
|
39996
|
+
import { existsSync, rmSync, symlinkSync } from "fs";
|
|
39997
|
+
import { tmpdir } from "os";
|
|
39998
|
+
import { join } from "path";
|
|
39999
|
+
import { promisify as promisify3 } from "util";
|
|
40000
|
+
var exec2 = promisify3(execFile2);
|
|
40001
|
+
var LINKED_DEP_DIRS = ["node_modules", ".venv", "vendor"];
|
|
40002
|
+
var WorktreeManager = class {
|
|
40003
|
+
constructor(repoRoot, gate, runId, opts = {}) {
|
|
40004
|
+
this.repoRoot = repoRoot;
|
|
40005
|
+
this.gate = gate;
|
|
40006
|
+
this.runId = runId;
|
|
40007
|
+
this.baseDir = join(tmpdir(), "baro-worktrees", runId);
|
|
40008
|
+
this.linkDepDirs = opts.linkDepDirs ?? true;
|
|
40009
|
+
this.log = opts.onLog ?? ((line) => process.stderr.write(`[worktree] ${line}
|
|
40010
|
+
`));
|
|
40011
|
+
}
|
|
40012
|
+
paths = /* @__PURE__ */ new Map();
|
|
40013
|
+
/** Stories whose merge-back failed: their branch is kept for recovery. */
|
|
40014
|
+
preserved = /* @__PURE__ */ new Set();
|
|
40015
|
+
baseDir;
|
|
40016
|
+
linkDepDirs;
|
|
40017
|
+
log;
|
|
40018
|
+
branchOf(storyId) {
|
|
40019
|
+
return `baro-wt/${this.runId}/${sanitize(storyId)}`;
|
|
40020
|
+
}
|
|
40021
|
+
pathOf(storyId) {
|
|
40022
|
+
return join(this.baseDir, sanitize(storyId));
|
|
40023
|
+
}
|
|
40024
|
+
/**
|
|
40025
|
+
* Create an isolated worktree for a story, branched off the current
|
|
40026
|
+
* run-branch HEAD. Returns the worktree path, or null on any failure so
|
|
40027
|
+
* the caller can fall back to the shared repo cwd (preserving the old
|
|
40028
|
+
* behavior rather than failing the story).
|
|
40029
|
+
*/
|
|
40030
|
+
async create(storyId) {
|
|
40031
|
+
const release = await this.gate.acquire();
|
|
40032
|
+
try {
|
|
40033
|
+
const branch = this.branchOf(storyId);
|
|
40034
|
+
const path6 = this.pathOf(storyId);
|
|
40035
|
+
await this.removeWorktreeQuiet(path6);
|
|
40036
|
+
await this.deleteBranchQuiet(branch);
|
|
40037
|
+
await exec2(
|
|
40038
|
+
"git",
|
|
40039
|
+
["worktree", "add", "-b", branch, path6, "HEAD"],
|
|
40040
|
+
{ cwd: this.repoRoot }
|
|
40041
|
+
);
|
|
40042
|
+
this.paths.set(storyId, path6);
|
|
40043
|
+
if (this.linkDepDirs) this.symlinkDepDirs(path6);
|
|
40044
|
+
this.log(`created ${branch} at ${path6}`);
|
|
40045
|
+
return path6;
|
|
40046
|
+
} catch (e2) {
|
|
40047
|
+
this.log(
|
|
40048
|
+
`could not create worktree for ${storyId} (${errMsg(e2)}); falling back to shared tree`
|
|
40049
|
+
);
|
|
40050
|
+
return null;
|
|
40051
|
+
} finally {
|
|
40052
|
+
release();
|
|
40053
|
+
}
|
|
40054
|
+
}
|
|
40055
|
+
/**
|
|
40056
|
+
* Merge a passed story's branch onto the run branch. Returns true if a
|
|
40057
|
+
* worktree merge happened, false if the story had no worktree (create()
|
|
40058
|
+
* fell back to the shared tree). On any merge failure, retries once with
|
|
40059
|
+
* `-X theirs` (the merging story wins). Throws — leaving the run branch
|
|
40060
|
+
* clean (`merge --abort`) — only when even that can't resolve it; the
|
|
40061
|
+
* caller must then preserve the branch rather than discard the work.
|
|
40062
|
+
*/
|
|
40063
|
+
async mergeBack(storyId) {
|
|
40064
|
+
const path6 = this.paths.get(storyId);
|
|
40065
|
+
if (!path6) return false;
|
|
40066
|
+
const branch = this.branchOf(storyId);
|
|
40067
|
+
const release = await this.gate.acquire();
|
|
40068
|
+
try {
|
|
40069
|
+
await this.autoCommitLeftovers(storyId, path6);
|
|
40070
|
+
const msg = `baro: merge story ${storyId}`;
|
|
40071
|
+
try {
|
|
40072
|
+
await exec2("git", ["merge", "--no-ff", "-m", msg, branch], {
|
|
40073
|
+
cwd: this.repoRoot
|
|
40074
|
+
});
|
|
40075
|
+
return true;
|
|
40076
|
+
} catch {
|
|
40077
|
+
const conflicts = await this.conflictedPaths();
|
|
40078
|
+
await this.abortMerge(storyId);
|
|
40079
|
+
this.log(
|
|
40080
|
+
`WARNING: story ${storyId} conflicts with already-merged work` + (conflicts.length ? ` on [${conflicts.join(", ")}]` : "") + `; auto-resolving with -X theirs (this story wins)`
|
|
40081
|
+
);
|
|
40082
|
+
try {
|
|
40083
|
+
await exec2(
|
|
40084
|
+
"git",
|
|
40085
|
+
["merge", "--no-ff", "-X", "theirs", "-m", msg, branch],
|
|
40086
|
+
{ cwd: this.repoRoot }
|
|
40087
|
+
);
|
|
40088
|
+
return true;
|
|
40089
|
+
} catch (e2) {
|
|
40090
|
+
await this.abortMerge(storyId);
|
|
40091
|
+
this.preserved.add(storyId);
|
|
40092
|
+
throw new Error(
|
|
40093
|
+
`could not merge story ${storyId} even with -X theirs: ${errMsg(e2)}`
|
|
40094
|
+
);
|
|
40095
|
+
}
|
|
40096
|
+
}
|
|
40097
|
+
} finally {
|
|
40098
|
+
release();
|
|
40099
|
+
}
|
|
40100
|
+
}
|
|
40101
|
+
/**
|
|
40102
|
+
* Abort an in-progress merge, logging if the abort itself fails. A
|
|
40103
|
+
* lingering MERGE_HEAD (e.g. a held index.lock) would otherwise make the
|
|
40104
|
+
* NEXT story's merge fail and be misdiagnosed against the wrong story.
|
|
40105
|
+
*/
|
|
40106
|
+
async abortMerge(storyId) {
|
|
40107
|
+
try {
|
|
40108
|
+
await exec2("git", ["merge", "--abort"], { cwd: this.repoRoot });
|
|
40109
|
+
} catch (e2) {
|
|
40110
|
+
this.log(
|
|
40111
|
+
`WARNING: 'git merge --abort' failed after story ${storyId} (${errMsg(e2)}); run branch may have a lingering MERGE_HEAD`
|
|
40112
|
+
);
|
|
40113
|
+
}
|
|
40114
|
+
}
|
|
40115
|
+
/** Remove a story's worktree + branch (after merge-back, or on failure). */
|
|
40116
|
+
async cleanup(storyId) {
|
|
40117
|
+
const path6 = this.paths.get(storyId);
|
|
40118
|
+
const branch = this.branchOf(storyId);
|
|
40119
|
+
const release = await this.gate.acquire();
|
|
40120
|
+
try {
|
|
40121
|
+
if (path6) {
|
|
40122
|
+
await this.removeWorktreeQuiet(path6);
|
|
40123
|
+
this.paths.delete(storyId);
|
|
40124
|
+
}
|
|
40125
|
+
await this.deleteBranchQuiet(branch);
|
|
40126
|
+
} finally {
|
|
40127
|
+
release();
|
|
40128
|
+
}
|
|
40129
|
+
}
|
|
40130
|
+
/**
|
|
40131
|
+
* Remove every worktree this manager created, plus its temp dir. Branches
|
|
40132
|
+
* are deleted too — EXCEPT those marked preserved (an unresolvable
|
|
40133
|
+
* merge-back): their worktree dir is freed but the branch ref is kept so
|
|
40134
|
+
* the commits stay recoverable after the run.
|
|
40135
|
+
*/
|
|
40136
|
+
async cleanupAll() {
|
|
40137
|
+
const release = await this.gate.acquire();
|
|
40138
|
+
try {
|
|
40139
|
+
for (const [storyId, path6] of this.paths) {
|
|
40140
|
+
await this.removeWorktreeQuiet(path6);
|
|
40141
|
+
if (this.preserved.has(storyId)) {
|
|
40142
|
+
this.log(
|
|
40143
|
+
`kept branch ${this.branchOf(storyId)} for recovery (merge-back failed); inspect with: git log ${this.branchOf(storyId)}`
|
|
40144
|
+
);
|
|
40145
|
+
} else {
|
|
40146
|
+
await this.deleteBranchQuiet(this.branchOf(storyId));
|
|
40147
|
+
}
|
|
40148
|
+
}
|
|
40149
|
+
this.paths.clear();
|
|
40150
|
+
await execQuiet("git", ["worktree", "prune"], this.repoRoot);
|
|
40151
|
+
rmSyncQuiet(this.baseDir);
|
|
40152
|
+
} finally {
|
|
40153
|
+
release();
|
|
40154
|
+
}
|
|
40155
|
+
}
|
|
40156
|
+
/**
|
|
40157
|
+
* Reclaim worktree admin entries whose dirs are already gone
|
|
40158
|
+
* (`git worktree prune`) and delete any branches under THIS run id
|
|
40159
|
+
* (defensive re-entrancy guard — ids are unique per process, so this
|
|
40160
|
+
* does not touch a concurrent run's worktrees).
|
|
40161
|
+
*/
|
|
40162
|
+
async cleanupStaleOnStart() {
|
|
40163
|
+
const release = await this.gate.acquire();
|
|
40164
|
+
try {
|
|
40165
|
+
await execQuiet("git", ["worktree", "prune"], this.repoRoot);
|
|
40166
|
+
const prefix = `baro-wt/${this.runId}/`;
|
|
40167
|
+
let branches = [];
|
|
40168
|
+
try {
|
|
40169
|
+
const { stdout } = await exec2(
|
|
40170
|
+
"git",
|
|
40171
|
+
["branch", "--list", `${prefix}*`, "--format=%(refname:short)"],
|
|
40172
|
+
{ cwd: this.repoRoot }
|
|
40173
|
+
);
|
|
40174
|
+
branches = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
40175
|
+
} catch {
|
|
40176
|
+
}
|
|
40177
|
+
for (const branch of branches) {
|
|
40178
|
+
await this.deleteBranchQuiet(branch);
|
|
40179
|
+
}
|
|
40180
|
+
} finally {
|
|
40181
|
+
release();
|
|
40182
|
+
}
|
|
40183
|
+
}
|
|
40184
|
+
// ── internals ────────────────────────────────────────────────────
|
|
40185
|
+
symlinkDepDirs(worktreePath) {
|
|
40186
|
+
for (const dir of LINKED_DEP_DIRS) {
|
|
40187
|
+
const src = join(this.repoRoot, dir);
|
|
40188
|
+
const dest = join(worktreePath, dir);
|
|
40189
|
+
if (!existsSync(src) || existsSync(dest)) continue;
|
|
40190
|
+
try {
|
|
40191
|
+
symlinkSync(src, dest, "dir");
|
|
40192
|
+
} catch (e2) {
|
|
40193
|
+
this.log(`could not symlink ${dir} into worktree (${errMsg(e2)})`);
|
|
40194
|
+
}
|
|
40195
|
+
}
|
|
40196
|
+
}
|
|
40197
|
+
/**
|
|
40198
|
+
* Commit work the agent edited but didn't commit, so it isn't lost on
|
|
40199
|
+
* cleanup (passing is signalled by the agent, not by a commit existing).
|
|
40200
|
+
* Never commits the symlinked dep dirs, and surfaces a warning rather than
|
|
40201
|
+
* silently dropping work if any git step fails.
|
|
40202
|
+
*/
|
|
40203
|
+
async autoCommitLeftovers(storyId, worktreePath) {
|
|
40204
|
+
let dirty = false;
|
|
40205
|
+
try {
|
|
40206
|
+
const { stdout } = await exec2("git", ["status", "--porcelain"], {
|
|
40207
|
+
cwd: worktreePath
|
|
40208
|
+
});
|
|
40209
|
+
dirty = stdout.trim().length > 0;
|
|
40210
|
+
} catch {
|
|
40211
|
+
return;
|
|
40212
|
+
}
|
|
40213
|
+
if (!dirty) return;
|
|
40214
|
+
try {
|
|
40215
|
+
await exec2("git", ["add", "-A"], { cwd: worktreePath });
|
|
40216
|
+
} catch (e2) {
|
|
40217
|
+
this.log(
|
|
40218
|
+
`WARNING: failed to stage story ${storyId}'s leftover work (${errMsg(e2)}); it will not be included in the merge`
|
|
40219
|
+
);
|
|
40220
|
+
return;
|
|
40221
|
+
}
|
|
40222
|
+
await execQuiet("git", ["reset", "-q", "--", ...LINKED_DEP_DIRS], worktreePath);
|
|
40223
|
+
let staged = [];
|
|
40224
|
+
let diffFailed = false;
|
|
40225
|
+
try {
|
|
40226
|
+
const { stdout } = await exec2("git", ["diff", "--cached", "--name-only"], {
|
|
40227
|
+
cwd: worktreePath
|
|
40228
|
+
});
|
|
40229
|
+
staged = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
40230
|
+
} catch (e2) {
|
|
40231
|
+
diffFailed = true;
|
|
40232
|
+
this.log(
|
|
40233
|
+
`WARNING: could not inspect staged changes for story ${storyId} (${errMsg(e2)}); committing whatever is staged`
|
|
40234
|
+
);
|
|
40235
|
+
}
|
|
40236
|
+
const depStaged = staged.filter(
|
|
40237
|
+
(p) => LINKED_DEP_DIRS.some((d) => p === d || p.startsWith(`${d}/`))
|
|
40238
|
+
);
|
|
40239
|
+
if (depStaged.length > 0) {
|
|
40240
|
+
this.log(
|
|
40241
|
+
`WARNING: could not keep dep dirs [${depStaged.join(", ")}] out of story ${storyId}'s auto-commit; skipping it to avoid committing symlinks`
|
|
40242
|
+
);
|
|
40243
|
+
await execQuiet("git", ["reset", "-q"], worktreePath);
|
|
40244
|
+
return;
|
|
40245
|
+
}
|
|
40246
|
+
if (!diffFailed && staged.length === 0) return;
|
|
40247
|
+
this.log(
|
|
40248
|
+
`WARNING: story ${storyId} passed with uncommitted changes; auto-committing ${diffFailed ? "" : `${staged.length} path(s) `}before merge`
|
|
40249
|
+
);
|
|
40250
|
+
try {
|
|
40251
|
+
await exec2(
|
|
40252
|
+
"git",
|
|
40253
|
+
["commit", "-m", `baro: auto-commit uncommitted work for story ${storyId}`],
|
|
40254
|
+
{ cwd: worktreePath }
|
|
40255
|
+
);
|
|
40256
|
+
} catch (e2) {
|
|
40257
|
+
this.log(
|
|
40258
|
+
`WARNING: failed to auto-commit story ${storyId}'s leftover work (${errMsg(e2)}); it will not be included in the merge`
|
|
40259
|
+
);
|
|
40260
|
+
}
|
|
40261
|
+
}
|
|
40262
|
+
async conflictedPaths() {
|
|
40263
|
+
try {
|
|
40264
|
+
const { stdout } = await exec2(
|
|
40265
|
+
"git",
|
|
40266
|
+
["diff", "--name-only", "--diff-filter=U"],
|
|
40267
|
+
{ cwd: this.repoRoot }
|
|
40268
|
+
);
|
|
40269
|
+
return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
40270
|
+
} catch {
|
|
40271
|
+
return [];
|
|
40272
|
+
}
|
|
40273
|
+
}
|
|
40274
|
+
async removeWorktreeQuiet(path6) {
|
|
40275
|
+
await execQuiet("git", ["worktree", "remove", "--force", path6], this.repoRoot);
|
|
40276
|
+
if (existsSync(path6)) {
|
|
40277
|
+
rmSyncQuiet(path6);
|
|
40278
|
+
await execQuiet("git", ["worktree", "prune"], this.repoRoot);
|
|
40279
|
+
}
|
|
40280
|
+
}
|
|
40281
|
+
async deleteBranchQuiet(branch) {
|
|
40282
|
+
await execQuiet("git", ["branch", "-D", branch], this.repoRoot);
|
|
40283
|
+
}
|
|
40284
|
+
};
|
|
40285
|
+
function sanitize(storyId) {
|
|
40286
|
+
return storyId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
40287
|
+
}
|
|
40288
|
+
async function execQuiet(cmd, args, cwd) {
|
|
40289
|
+
try {
|
|
40290
|
+
await exec2(cmd, args, { cwd });
|
|
40291
|
+
} catch {
|
|
40292
|
+
}
|
|
40293
|
+
}
|
|
40294
|
+
function rmSyncQuiet(path6) {
|
|
40295
|
+
try {
|
|
40296
|
+
rmSync(path6, { recursive: true, force: true });
|
|
40297
|
+
} catch {
|
|
40298
|
+
}
|
|
40299
|
+
}
|
|
40300
|
+
function errMsg(e2) {
|
|
40301
|
+
return e2?.message ?? String(e2);
|
|
40302
|
+
}
|
|
40303
|
+
|
|
39994
40304
|
// ../baro-orchestrator/src/dag.ts
|
|
39995
40305
|
function buildDag(stories, options = {}) {
|
|
39996
40306
|
const onlyIncomplete = options.onlyIncomplete ?? false;
|
|
@@ -40040,6 +40350,150 @@ function buildDag(stories, options = {}) {
|
|
|
40040
40350
|
return levels;
|
|
40041
40351
|
}
|
|
40042
40352
|
|
|
40353
|
+
// ../baro-orchestrator/src/routing.ts
|
|
40354
|
+
var BACKENDS = ["claude", "openai", "codex", "opencode", "pi"];
|
|
40355
|
+
function isBackend(s2) {
|
|
40356
|
+
return BACKENDS.includes(s2);
|
|
40357
|
+
}
|
|
40358
|
+
var CLAUDE_TIER_RE = /^(opus|sonnet|haiku)\b/i;
|
|
40359
|
+
function isClaudeTierName(s2) {
|
|
40360
|
+
return CLAUDE_TIER_RE.test(s2.trim());
|
|
40361
|
+
}
|
|
40362
|
+
function splitBackendModel(raw) {
|
|
40363
|
+
const trimmed = raw.trim();
|
|
40364
|
+
if (!trimmed) return {};
|
|
40365
|
+
const idx = trimmed.indexOf(":");
|
|
40366
|
+
if (idx > 0) {
|
|
40367
|
+
const prefix = trimmed.slice(0, idx).toLowerCase();
|
|
40368
|
+
if (isBackend(prefix)) {
|
|
40369
|
+
const model = trimmed.slice(idx + 1).trim();
|
|
40370
|
+
return { backend: prefix, model: model.length ? model : void 0 };
|
|
40371
|
+
}
|
|
40372
|
+
}
|
|
40373
|
+
if (isBackend(trimmed.toLowerCase())) {
|
|
40374
|
+
return { backend: trimmed.toLowerCase() };
|
|
40375
|
+
}
|
|
40376
|
+
return { model: trimmed };
|
|
40377
|
+
}
|
|
40378
|
+
function looksLikeUrl(s2) {
|
|
40379
|
+
return /^https?:\/\//i.test(s2.trim());
|
|
40380
|
+
}
|
|
40381
|
+
function splitModelEndpoint(model) {
|
|
40382
|
+
const at = model.indexOf("@");
|
|
40383
|
+
if (at < 0) return { model };
|
|
40384
|
+
const ref = model.slice(at + 1).trim();
|
|
40385
|
+
return { model: model.slice(0, at).trim(), endpointRef: ref || void 0 };
|
|
40386
|
+
}
|
|
40387
|
+
function resolveEndpoint(ref, opts) {
|
|
40388
|
+
if (looksLikeUrl(ref)) {
|
|
40389
|
+
return { baseUrl: ref, apiKey: opts.defaultApiKey };
|
|
40390
|
+
}
|
|
40391
|
+
const ep = opts.endpoints?.[ref.toLowerCase()] ?? opts.endpoints?.[ref];
|
|
40392
|
+
if (!ep) {
|
|
40393
|
+
throw new Error(
|
|
40394
|
+
`unknown OpenAI endpoint "${ref}" \u2014 define it with --openai-endpoint ${ref}=<url> or use an inline https:// URL`
|
|
40395
|
+
);
|
|
40396
|
+
}
|
|
40397
|
+
return { baseUrl: ep.baseUrl, apiKey: ep.apiKey ?? opts.defaultApiKey };
|
|
40398
|
+
}
|
|
40399
|
+
function resolveStoryRoute(rawModel, opts) {
|
|
40400
|
+
const raw = (opts.override ?? rawModel ?? "").trim();
|
|
40401
|
+
if (!raw) return defaultRoute(opts.fallbackBackend, opts.openaiDefaultModel);
|
|
40402
|
+
const direct = splitBackendModel(raw);
|
|
40403
|
+
if (direct.backend === void 0 && opts.tierMap) {
|
|
40404
|
+
const mapped = opts.tierMap[raw.toLowerCase()] ?? opts.tierMap[raw];
|
|
40405
|
+
if (mapped) {
|
|
40406
|
+
return resolveStoryRoute(mapped, {
|
|
40407
|
+
...opts,
|
|
40408
|
+
override: void 0,
|
|
40409
|
+
tierMap: void 0
|
|
40410
|
+
});
|
|
40411
|
+
}
|
|
40412
|
+
}
|
|
40413
|
+
if (direct.backend) {
|
|
40414
|
+
if (direct.model) return buildRoute(direct.backend, direct.model, opts);
|
|
40415
|
+
return defaultRoute(direct.backend, opts.openaiDefaultModel);
|
|
40416
|
+
}
|
|
40417
|
+
const backend = opts.fallbackBackend;
|
|
40418
|
+
if (backend === "claude") {
|
|
40419
|
+
return { backend, model: direct.model };
|
|
40420
|
+
}
|
|
40421
|
+
if (direct.model && !isClaudeTierName(direct.model)) {
|
|
40422
|
+
return buildRoute(backend, direct.model, opts);
|
|
40423
|
+
}
|
|
40424
|
+
return defaultRoute(backend, opts.openaiDefaultModel);
|
|
40425
|
+
}
|
|
40426
|
+
function buildRoute(backend, model, opts) {
|
|
40427
|
+
if (backend !== "openai") return { backend, model };
|
|
40428
|
+
const { model: bareModel, endpointRef } = splitModelEndpoint(model);
|
|
40429
|
+
if (!endpointRef) return { backend, model: bareModel };
|
|
40430
|
+
const ep = resolveEndpoint(endpointRef, opts);
|
|
40431
|
+
return { backend, model: bareModel, baseUrl: ep.baseUrl, apiKey: ep.apiKey };
|
|
40432
|
+
}
|
|
40433
|
+
function defaultRoute(backend, openaiDefaultModel) {
|
|
40434
|
+
if (backend === "openai") return { backend, model: openaiDefaultModel };
|
|
40435
|
+
return { backend };
|
|
40436
|
+
}
|
|
40437
|
+
function parseTierMap(spec) {
|
|
40438
|
+
const map = {};
|
|
40439
|
+
for (const part of spec.split(",")) {
|
|
40440
|
+
const seg = part.trim();
|
|
40441
|
+
if (!seg) continue;
|
|
40442
|
+
const eq = seg.indexOf("=");
|
|
40443
|
+
if (eq <= 0) {
|
|
40444
|
+
throw new Error(
|
|
40445
|
+
`bad --tier-map entry "${seg}" (expected tier=backend:model)`
|
|
40446
|
+
);
|
|
40447
|
+
}
|
|
40448
|
+
const tier = seg.slice(0, eq).trim().toLowerCase();
|
|
40449
|
+
const route = seg.slice(eq + 1).trim();
|
|
40450
|
+
if (!tier || !route) {
|
|
40451
|
+
throw new Error(
|
|
40452
|
+
`bad --tier-map entry "${seg}" (expected tier=backend:model)`
|
|
40453
|
+
);
|
|
40454
|
+
}
|
|
40455
|
+
const { backend } = splitBackendModel(route);
|
|
40456
|
+
if (!backend) {
|
|
40457
|
+
throw new Error(
|
|
40458
|
+
`--tier-map route "${route}" for tier "${tier}" must name a backend (claude: | openai: | codex: | opencode: | pi:)`
|
|
40459
|
+
);
|
|
40460
|
+
}
|
|
40461
|
+
map[tier] = route;
|
|
40462
|
+
}
|
|
40463
|
+
return map;
|
|
40464
|
+
}
|
|
40465
|
+
function parseEndpoints(specs, keyFor) {
|
|
40466
|
+
const map = {};
|
|
40467
|
+
for (const raw of specs) {
|
|
40468
|
+
const spec = raw.trim();
|
|
40469
|
+
if (!spec) continue;
|
|
40470
|
+
const eq = spec.indexOf("=");
|
|
40471
|
+
if (eq <= 0) {
|
|
40472
|
+
throw new Error(
|
|
40473
|
+
`bad --openai-endpoint "${spec}" (expected name=url)`
|
|
40474
|
+
);
|
|
40475
|
+
}
|
|
40476
|
+
const name = spec.slice(0, eq).trim().toLowerCase();
|
|
40477
|
+
const url = spec.slice(eq + 1).trim();
|
|
40478
|
+
if (!name || !url) {
|
|
40479
|
+
throw new Error(
|
|
40480
|
+
`bad --openai-endpoint "${spec}" (expected name=url)`
|
|
40481
|
+
);
|
|
40482
|
+
}
|
|
40483
|
+
if (!looksLikeUrl(url)) {
|
|
40484
|
+
throw new Error(
|
|
40485
|
+
`--openai-endpoint "${name}" url "${url}" must start with http:// or https://`
|
|
40486
|
+
);
|
|
40487
|
+
}
|
|
40488
|
+
map[name] = { baseUrl: url, apiKey: keyFor?.(name) };
|
|
40489
|
+
}
|
|
40490
|
+
return map;
|
|
40491
|
+
}
|
|
40492
|
+
function formatRoute(route) {
|
|
40493
|
+
const base = route.model ? `${route.backend}:${route.model}` : route.backend;
|
|
40494
|
+
return route.baseUrl ? `${base}@${route.baseUrl}` : base;
|
|
40495
|
+
}
|
|
40496
|
+
|
|
40043
40497
|
// ../baro-orchestrator/src/participants/auditor.ts
|
|
40044
40498
|
import { appendFileSync, mkdirSync } from "fs";
|
|
40045
40499
|
import { dirname } from "path";
|
|
@@ -40159,8 +40613,8 @@ var Auditor = class extends BaseObserver {
|
|
|
40159
40613
|
};
|
|
40160
40614
|
|
|
40161
40615
|
// ../baro-orchestrator/src/participants/conductor.ts
|
|
40162
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
40163
|
-
import { join } from "path";
|
|
40616
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
40617
|
+
import { join as join2 } from "path";
|
|
40164
40618
|
|
|
40165
40619
|
// ../baro-orchestrator/src/prd.ts
|
|
40166
40620
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -40345,6 +40799,14 @@ var Conductor = class extends BaseObserver {
|
|
|
40345
40799
|
/** Resolved when the run terminates, exposed for callers that need it. */
|
|
40346
40800
|
done;
|
|
40347
40801
|
resolveDone;
|
|
40802
|
+
/**
|
|
40803
|
+
* Serializes event handling. Mozaik delivers events without awaiting the
|
|
40804
|
+
* handler, so two StoryResults settling together could interleave — one
|
|
40805
|
+
* spawning the next DAG level while another's `await onStoryPassed`
|
|
40806
|
+
* (worktree merge-back, #50) is still in flight, leaving the dependent
|
|
40807
|
+
* level without the merged work. Chaining handlers keeps them sequential.
|
|
40808
|
+
*/
|
|
40809
|
+
handleChain = Promise.resolve();
|
|
40348
40810
|
constructor(opts) {
|
|
40349
40811
|
super();
|
|
40350
40812
|
this.opts = {
|
|
@@ -40381,7 +40843,13 @@ var Conductor = class extends BaseObserver {
|
|
|
40381
40843
|
async onExternalEvent(_source, event) {
|
|
40382
40844
|
await this.handle(event);
|
|
40383
40845
|
}
|
|
40384
|
-
|
|
40846
|
+
handle(event) {
|
|
40847
|
+
const run = this.handleChain.then(() => this.handleEvent(event));
|
|
40848
|
+
this.handleChain = run.catch(() => {
|
|
40849
|
+
});
|
|
40850
|
+
return run;
|
|
40851
|
+
}
|
|
40852
|
+
async handleEvent(event) {
|
|
40385
40853
|
if (RunStartRequest.is(event)) {
|
|
40386
40854
|
await this.handleRunStart();
|
|
40387
40855
|
return;
|
|
@@ -40521,6 +40989,20 @@ var Conductor = class extends BaseObserver {
|
|
|
40521
40989
|
} else {
|
|
40522
40990
|
this.currentLevel.failed.push(item.storyId);
|
|
40523
40991
|
this.addGlobalFailed(item.storyId);
|
|
40992
|
+
if (this.opts.onStoryFailed) {
|
|
40993
|
+
try {
|
|
40994
|
+
await this.opts.onStoryFailed(item.storyId);
|
|
40995
|
+
} catch (e2) {
|
|
40996
|
+
this.emit(
|
|
40997
|
+
ConductorState.create({
|
|
40998
|
+
phase: "running_level",
|
|
40999
|
+
detail: `onStoryFailed hook for ${item.storyId} failed: ${e2?.message ?? String(e2)}`,
|
|
41000
|
+
currentLevel: this.currentLevel.ordinal,
|
|
41001
|
+
totalLevels: this.currentLevel.totalLevelsHint
|
|
41002
|
+
})
|
|
41003
|
+
);
|
|
41004
|
+
}
|
|
41005
|
+
}
|
|
40524
41006
|
}
|
|
40525
41007
|
await this.fillSpawnSlots();
|
|
40526
41008
|
if (this.currentLevel.pending.size === 0) {
|
|
@@ -40790,9 +41272,9 @@ ${prompt}`;
|
|
|
40790
41272
|
return true;
|
|
40791
41273
|
}
|
|
40792
41274
|
resolvePrompt(story) {
|
|
40793
|
-
const candidatePath = this.opts.promptTemplatePath ??
|
|
41275
|
+
const candidatePath = this.opts.promptTemplatePath ?? join2(this.opts.cwd, "prompt.md");
|
|
40794
41276
|
let prompt;
|
|
40795
|
-
if (
|
|
41277
|
+
if (existsSync2(candidatePath)) {
|
|
40796
41278
|
const tpl = readFileSyncSafe(candidatePath);
|
|
40797
41279
|
prompt = tpl ? applyTemplate(tpl, story) : buildDefaultStoryPrompt(story);
|
|
40798
41280
|
} else {
|
|
@@ -40871,9 +41353,9 @@ function applyTemplate(tpl, story) {
|
|
|
40871
41353
|
}
|
|
40872
41354
|
|
|
40873
41355
|
// ../baro-orchestrator/src/participants/critic.ts
|
|
40874
|
-
import { execFile as
|
|
40875
|
-
import { promisify as
|
|
40876
|
-
var execFileAsync =
|
|
41356
|
+
import { execFile as execFile3 } from "child_process";
|
|
41357
|
+
import { promisify as promisify4 } from "util";
|
|
41358
|
+
var execFileAsync = promisify4(execFile3);
|
|
40877
41359
|
var VERDICT_SYSTEM_PROMPT = `You are a strict acceptance-criteria evaluator. You will receive:
|
|
40878
41360
|
1. A list of acceptance criteria that must ALL be satisfied.
|
|
40879
41361
|
2. The output text produced by an agent.
|
|
@@ -42009,9 +42491,9 @@ ${userPrompt}`;
|
|
|
42009
42491
|
};
|
|
42010
42492
|
|
|
42011
42493
|
// ../baro-orchestrator/src/participants/finalizer.ts
|
|
42012
|
-
import { execFile as
|
|
42013
|
-
import { promisify as
|
|
42014
|
-
var execFileAsync2 =
|
|
42494
|
+
import { execFile as execFile4 } from "child_process";
|
|
42495
|
+
import { promisify as promisify5 } from "util";
|
|
42496
|
+
var execFileAsync2 = promisify5(execFile4);
|
|
42015
42497
|
var Finalizer = class extends BaseObserver {
|
|
42016
42498
|
opts;
|
|
42017
42499
|
envRef = null;
|
|
@@ -42576,9 +43058,16 @@ var ProgressForwarder = class extends BaseObserver {
|
|
|
42576
43058
|
};
|
|
42577
43059
|
|
|
42578
43060
|
// ../baro-orchestrator/src/participants/forwarders/story-lifecycle.ts
|
|
43061
|
+
var CREATE_TOOLS = /* @__PURE__ */ new Set(["Write", "write_file"]);
|
|
43062
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "edit_file"]);
|
|
42579
43063
|
var StoryLifecycleForwarder = class extends BaseObserver {
|
|
42580
43064
|
startedStories = /* @__PURE__ */ new Set();
|
|
42581
43065
|
retryCounts = /* @__PURE__ */ new Map();
|
|
43066
|
+
// storyId → (path → first-touch kind). Distinct paths per story, so a
|
|
43067
|
+
// file touched many times counts once, classified by its first write.
|
|
43068
|
+
// Reset on each retry so story_complete reflects only the winning
|
|
43069
|
+
// attempt's touches, not files a failed attempt wrote and abandoned.
|
|
43070
|
+
filesByStory = /* @__PURE__ */ new Map();
|
|
42582
43071
|
async onExternalEvent(_source, event) {
|
|
42583
43072
|
if (AgentState.is(event)) {
|
|
42584
43073
|
this.handleAgentState(event.data);
|
|
@@ -42589,6 +43078,27 @@ var StoryLifecycleForwarder = class extends BaseObserver {
|
|
|
42589
43078
|
return;
|
|
42590
43079
|
}
|
|
42591
43080
|
}
|
|
43081
|
+
// Same signal the Sentry uses: a write/edit tool call from a story
|
|
43082
|
+
// agent. We attribute the touched path to that agent's story and
|
|
43083
|
+
// remember its first-touch kind so story_complete can report real
|
|
43084
|
+
// per-story file counts instead of a hardcoded 0.
|
|
43085
|
+
async onExternalFunctionCall(source, item) {
|
|
43086
|
+
const isCreate = CREATE_TOOLS.has(item.name);
|
|
43087
|
+
const isEdit = EDIT_TOOLS.has(item.name);
|
|
43088
|
+
if (!isCreate && !isEdit) return;
|
|
43089
|
+
const agentId = source.agentId;
|
|
43090
|
+
if (typeof agentId !== "string") return;
|
|
43091
|
+
const path6 = extractPath(item);
|
|
43092
|
+
if (!path6) return;
|
|
43093
|
+
let paths = this.filesByStory.get(agentId);
|
|
43094
|
+
if (!paths) {
|
|
43095
|
+
paths = /* @__PURE__ */ new Map();
|
|
43096
|
+
this.filesByStory.set(agentId, paths);
|
|
43097
|
+
}
|
|
43098
|
+
if (!paths.has(path6)) {
|
|
43099
|
+
paths.set(path6, isCreate ? "created" : "modified");
|
|
43100
|
+
}
|
|
43101
|
+
}
|
|
42592
43102
|
handleAgentState(item) {
|
|
42593
43103
|
if (item.phase === "running" && !this.startedStories.has(item.agentId)) {
|
|
42594
43104
|
this.startedStories.add(item.agentId);
|
|
@@ -42597,19 +43107,31 @@ var StoryLifecycleForwarder = class extends BaseObserver {
|
|
|
42597
43107
|
if (item.phase === "waiting" && item.detail?.includes("retrying")) {
|
|
42598
43108
|
const count = (this.retryCounts.get(item.agentId) ?? 0) + 1;
|
|
42599
43109
|
this.retryCounts.set(item.agentId, count);
|
|
43110
|
+
this.filesByStory.delete(item.agentId);
|
|
42600
43111
|
emit({ type: "story_retry", id: item.agentId, attempt: count });
|
|
42601
43112
|
}
|
|
42602
43113
|
}
|
|
42603
43114
|
handleStoryResult(item) {
|
|
42604
43115
|
if (item.success) {
|
|
43116
|
+
const paths = this.filesByStory.get(item.storyId);
|
|
43117
|
+
let created = 0;
|
|
43118
|
+
let modified = 0;
|
|
43119
|
+
if (paths) {
|
|
43120
|
+
for (const kind of paths.values()) {
|
|
43121
|
+
if (kind === "created") created++;
|
|
43122
|
+
else modified++;
|
|
43123
|
+
}
|
|
43124
|
+
}
|
|
43125
|
+
this.filesByStory.delete(item.storyId);
|
|
42605
43126
|
emit({
|
|
42606
43127
|
type: "story_complete",
|
|
42607
43128
|
id: item.storyId,
|
|
42608
43129
|
duration_secs: item.durationSecs,
|
|
42609
|
-
files_created:
|
|
42610
|
-
files_modified:
|
|
43130
|
+
files_created: created,
|
|
43131
|
+
files_modified: modified
|
|
42611
43132
|
});
|
|
42612
43133
|
} else {
|
|
43134
|
+
this.filesByStory.delete(item.storyId);
|
|
42613
43135
|
emit({
|
|
42614
43136
|
type: "story_error",
|
|
42615
43137
|
id: item.storyId,
|
|
@@ -42620,6 +43142,19 @@ var StoryLifecycleForwarder = class extends BaseObserver {
|
|
|
42620
43142
|
}
|
|
42621
43143
|
}
|
|
42622
43144
|
};
|
|
43145
|
+
function extractPath(item) {
|
|
43146
|
+
let args;
|
|
43147
|
+
try {
|
|
43148
|
+
args = JSON.parse(item.args);
|
|
43149
|
+
} catch {
|
|
43150
|
+
return null;
|
|
43151
|
+
}
|
|
43152
|
+
for (const key of ["file_path", "path", "notebook_path"]) {
|
|
43153
|
+
const v = args[key];
|
|
43154
|
+
if (typeof v === "string") return v;
|
|
43155
|
+
}
|
|
43156
|
+
return null;
|
|
43157
|
+
}
|
|
42623
43158
|
|
|
42624
43159
|
// ../baro-orchestrator/src/participants/forwarders/token-usage.ts
|
|
42625
43160
|
var TokenUsageForwarder = class extends BaseObserver {
|
|
@@ -42923,10 +43458,10 @@ function tokenizeHints(prompt) {
|
|
|
42923
43458
|
|
|
42924
43459
|
// ../baro-orchestrator/src/participants/memory-librarian.ts
|
|
42925
43460
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
42926
|
-
import { join as
|
|
43461
|
+
import { join as join4 } from "path";
|
|
42927
43462
|
var DEBUG = process.env.BARO_DEBUG?.includes("memory") ?? false;
|
|
42928
|
-
var LOG_DIR =
|
|
42929
|
-
var LOG_FILE =
|
|
43463
|
+
var LOG_DIR = join4(process.env.HOME || "/tmp", ".baro", "runs");
|
|
43464
|
+
var LOG_FILE = join4(LOG_DIR, `memory-${Date.now()}.log`);
|
|
42930
43465
|
try {
|
|
42931
43466
|
mkdirSync3(LOG_DIR, { recursive: true });
|
|
42932
43467
|
} catch {
|
|
@@ -43256,7 +43791,7 @@ var Sentry = class extends BaseObserver {
|
|
|
43256
43791
|
if (!WRITE_TOOLS.has(item.name)) return;
|
|
43257
43792
|
const agentId = source.agentId;
|
|
43258
43793
|
if (typeof agentId !== "string") return;
|
|
43259
|
-
const path6 =
|
|
43794
|
+
const path6 = extractPath2(item);
|
|
43260
43795
|
if (!path6) return;
|
|
43261
43796
|
const touch = {
|
|
43262
43797
|
agentId,
|
|
@@ -43291,7 +43826,7 @@ var Sentry = class extends BaseObserver {
|
|
|
43291
43826
|
}
|
|
43292
43827
|
}
|
|
43293
43828
|
};
|
|
43294
|
-
function
|
|
43829
|
+
function extractPath2(item) {
|
|
43295
43830
|
let args;
|
|
43296
43831
|
try {
|
|
43297
43832
|
args = JSON.parse(item.args);
|
|
@@ -46676,150 +47211,6 @@ function raceWithTimeout4(p, ms, label) {
|
|
|
46676
47211
|
]);
|
|
46677
47212
|
}
|
|
46678
47213
|
|
|
46679
|
-
// ../baro-orchestrator/src/routing.ts
|
|
46680
|
-
var BACKENDS = ["claude", "openai", "codex", "opencode", "pi"];
|
|
46681
|
-
function isBackend(s2) {
|
|
46682
|
-
return BACKENDS.includes(s2);
|
|
46683
|
-
}
|
|
46684
|
-
var CLAUDE_TIER_RE = /^(opus|sonnet|haiku)\b/i;
|
|
46685
|
-
function isClaudeTierName(s2) {
|
|
46686
|
-
return CLAUDE_TIER_RE.test(s2.trim());
|
|
46687
|
-
}
|
|
46688
|
-
function splitBackendModel(raw) {
|
|
46689
|
-
const trimmed = raw.trim();
|
|
46690
|
-
if (!trimmed) return {};
|
|
46691
|
-
const idx = trimmed.indexOf(":");
|
|
46692
|
-
if (idx > 0) {
|
|
46693
|
-
const prefix = trimmed.slice(0, idx).toLowerCase();
|
|
46694
|
-
if (isBackend(prefix)) {
|
|
46695
|
-
const model = trimmed.slice(idx + 1).trim();
|
|
46696
|
-
return { backend: prefix, model: model.length ? model : void 0 };
|
|
46697
|
-
}
|
|
46698
|
-
}
|
|
46699
|
-
if (isBackend(trimmed.toLowerCase())) {
|
|
46700
|
-
return { backend: trimmed.toLowerCase() };
|
|
46701
|
-
}
|
|
46702
|
-
return { model: trimmed };
|
|
46703
|
-
}
|
|
46704
|
-
function looksLikeUrl(s2) {
|
|
46705
|
-
return /^https?:\/\//i.test(s2.trim());
|
|
46706
|
-
}
|
|
46707
|
-
function splitModelEndpoint(model) {
|
|
46708
|
-
const at = model.indexOf("@");
|
|
46709
|
-
if (at < 0) return { model };
|
|
46710
|
-
const ref = model.slice(at + 1).trim();
|
|
46711
|
-
return { model: model.slice(0, at).trim(), endpointRef: ref || void 0 };
|
|
46712
|
-
}
|
|
46713
|
-
function resolveEndpoint(ref, opts) {
|
|
46714
|
-
if (looksLikeUrl(ref)) {
|
|
46715
|
-
return { baseUrl: ref, apiKey: opts.defaultApiKey };
|
|
46716
|
-
}
|
|
46717
|
-
const ep = opts.endpoints?.[ref.toLowerCase()] ?? opts.endpoints?.[ref];
|
|
46718
|
-
if (!ep) {
|
|
46719
|
-
throw new Error(
|
|
46720
|
-
`unknown OpenAI endpoint "${ref}" \u2014 define it with --openai-endpoint ${ref}=<url> or use an inline https:// URL`
|
|
46721
|
-
);
|
|
46722
|
-
}
|
|
46723
|
-
return { baseUrl: ep.baseUrl, apiKey: ep.apiKey ?? opts.defaultApiKey };
|
|
46724
|
-
}
|
|
46725
|
-
function resolveStoryRoute(rawModel, opts) {
|
|
46726
|
-
const raw = (opts.override ?? rawModel ?? "").trim();
|
|
46727
|
-
if (!raw) return defaultRoute(opts.fallbackBackend, opts.openaiDefaultModel);
|
|
46728
|
-
const direct = splitBackendModel(raw);
|
|
46729
|
-
if (direct.backend === void 0 && opts.tierMap) {
|
|
46730
|
-
const mapped = opts.tierMap[raw.toLowerCase()] ?? opts.tierMap[raw];
|
|
46731
|
-
if (mapped) {
|
|
46732
|
-
return resolveStoryRoute(mapped, {
|
|
46733
|
-
...opts,
|
|
46734
|
-
override: void 0,
|
|
46735
|
-
tierMap: void 0
|
|
46736
|
-
});
|
|
46737
|
-
}
|
|
46738
|
-
}
|
|
46739
|
-
if (direct.backend) {
|
|
46740
|
-
if (direct.model) return buildRoute(direct.backend, direct.model, opts);
|
|
46741
|
-
return defaultRoute(direct.backend, opts.openaiDefaultModel);
|
|
46742
|
-
}
|
|
46743
|
-
const backend = opts.fallbackBackend;
|
|
46744
|
-
if (backend === "claude") {
|
|
46745
|
-
return { backend, model: direct.model };
|
|
46746
|
-
}
|
|
46747
|
-
if (direct.model && !isClaudeTierName(direct.model)) {
|
|
46748
|
-
return buildRoute(backend, direct.model, opts);
|
|
46749
|
-
}
|
|
46750
|
-
return defaultRoute(backend, opts.openaiDefaultModel);
|
|
46751
|
-
}
|
|
46752
|
-
function buildRoute(backend, model, opts) {
|
|
46753
|
-
if (backend !== "openai") return { backend, model };
|
|
46754
|
-
const { model: bareModel, endpointRef } = splitModelEndpoint(model);
|
|
46755
|
-
if (!endpointRef) return { backend, model: bareModel };
|
|
46756
|
-
const ep = resolveEndpoint(endpointRef, opts);
|
|
46757
|
-
return { backend, model: bareModel, baseUrl: ep.baseUrl, apiKey: ep.apiKey };
|
|
46758
|
-
}
|
|
46759
|
-
function defaultRoute(backend, openaiDefaultModel) {
|
|
46760
|
-
if (backend === "openai") return { backend, model: openaiDefaultModel };
|
|
46761
|
-
return { backend };
|
|
46762
|
-
}
|
|
46763
|
-
function parseTierMap(spec) {
|
|
46764
|
-
const map = {};
|
|
46765
|
-
for (const part of spec.split(",")) {
|
|
46766
|
-
const seg = part.trim();
|
|
46767
|
-
if (!seg) continue;
|
|
46768
|
-
const eq = seg.indexOf("=");
|
|
46769
|
-
if (eq <= 0) {
|
|
46770
|
-
throw new Error(
|
|
46771
|
-
`bad --tier-map entry "${seg}" (expected tier=backend:model)`
|
|
46772
|
-
);
|
|
46773
|
-
}
|
|
46774
|
-
const tier = seg.slice(0, eq).trim().toLowerCase();
|
|
46775
|
-
const route = seg.slice(eq + 1).trim();
|
|
46776
|
-
if (!tier || !route) {
|
|
46777
|
-
throw new Error(
|
|
46778
|
-
`bad --tier-map entry "${seg}" (expected tier=backend:model)`
|
|
46779
|
-
);
|
|
46780
|
-
}
|
|
46781
|
-
const { backend } = splitBackendModel(route);
|
|
46782
|
-
if (!backend) {
|
|
46783
|
-
throw new Error(
|
|
46784
|
-
`--tier-map route "${route}" for tier "${tier}" must name a backend (claude: | openai: | codex: | opencode: | pi:)`
|
|
46785
|
-
);
|
|
46786
|
-
}
|
|
46787
|
-
map[tier] = route;
|
|
46788
|
-
}
|
|
46789
|
-
return map;
|
|
46790
|
-
}
|
|
46791
|
-
function parseEndpoints(specs, keyFor) {
|
|
46792
|
-
const map = {};
|
|
46793
|
-
for (const raw of specs) {
|
|
46794
|
-
const spec = raw.trim();
|
|
46795
|
-
if (!spec) continue;
|
|
46796
|
-
const eq = spec.indexOf("=");
|
|
46797
|
-
if (eq <= 0) {
|
|
46798
|
-
throw new Error(
|
|
46799
|
-
`bad --openai-endpoint "${spec}" (expected name=url)`
|
|
46800
|
-
);
|
|
46801
|
-
}
|
|
46802
|
-
const name = spec.slice(0, eq).trim().toLowerCase();
|
|
46803
|
-
const url = spec.slice(eq + 1).trim();
|
|
46804
|
-
if (!name || !url) {
|
|
46805
|
-
throw new Error(
|
|
46806
|
-
`bad --openai-endpoint "${spec}" (expected name=url)`
|
|
46807
|
-
);
|
|
46808
|
-
}
|
|
46809
|
-
if (!looksLikeUrl(url)) {
|
|
46810
|
-
throw new Error(
|
|
46811
|
-
`--openai-endpoint "${name}" url "${url}" must start with http:// or https://`
|
|
46812
|
-
);
|
|
46813
|
-
}
|
|
46814
|
-
map[name] = { baseUrl: url, apiKey: keyFor?.(name) };
|
|
46815
|
-
}
|
|
46816
|
-
return map;
|
|
46817
|
-
}
|
|
46818
|
-
function formatRoute(route) {
|
|
46819
|
-
const base = route.model ? `${route.backend}:${route.model}` : route.backend;
|
|
46820
|
-
return route.baseUrl ? `${base}@${route.baseUrl}` : base;
|
|
46821
|
-
}
|
|
46822
|
-
|
|
46823
47214
|
// ../baro-orchestrator/src/participants/story-factory.ts
|
|
46824
47215
|
var StoryFactory = class extends BaseObserver {
|
|
46825
47216
|
constructor(opts) {
|
|
@@ -46831,6 +47222,8 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46831
47222
|
// narrows to vanilla AgenticEnvironment.
|
|
46832
47223
|
envRef = null;
|
|
46833
47224
|
active = /* @__PURE__ */ new Map();
|
|
47225
|
+
/** Story ids whose spawn is in progress (closes the await-create window). */
|
|
47226
|
+
spawning = /* @__PURE__ */ new Set();
|
|
46834
47227
|
setEnvironment(env) {
|
|
46835
47228
|
this.envRef = env;
|
|
46836
47229
|
}
|
|
@@ -46849,7 +47242,16 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46849
47242
|
}
|
|
46850
47243
|
async spawn(req) {
|
|
46851
47244
|
if (!this.envRef) return;
|
|
46852
|
-
if (this.active.has(req.storyId)) return;
|
|
47245
|
+
if (this.active.has(req.storyId) || this.spawning.has(req.storyId)) return;
|
|
47246
|
+
this.spawning.add(req.storyId);
|
|
47247
|
+
try {
|
|
47248
|
+
await this.buildAndLaunch(req);
|
|
47249
|
+
} finally {
|
|
47250
|
+
this.spawning.delete(req.storyId);
|
|
47251
|
+
}
|
|
47252
|
+
}
|
|
47253
|
+
async buildAndLaunch(req) {
|
|
47254
|
+
if (!this.envRef) return;
|
|
46853
47255
|
const route = resolveStoryRoute(req.model, {
|
|
46854
47256
|
tierMap: this.opts.tierMap,
|
|
46855
47257
|
fallbackBackend: this.opts.llm ?? "claude",
|
|
@@ -46861,24 +47263,25 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46861
47263
|
process.stderr.write(
|
|
46862
47264
|
`[story-factory] ${req.storyId} \u2192 ${formatRoute(route)}` + (req.model ? ` (model="${req.model}")` : "") + "\n"
|
|
46863
47265
|
);
|
|
47266
|
+
const storyCwd = this.opts.worktrees ? await this.opts.worktrees.create(req.storyId) ?? this.opts.cwd : this.opts.cwd;
|
|
46864
47267
|
const agent = route.backend === "pi" ? new PiStoryAgent({
|
|
46865
47268
|
id: req.storyId,
|
|
46866
47269
|
prompt: req.prompt,
|
|
46867
|
-
cwd:
|
|
47270
|
+
cwd: storyCwd,
|
|
46868
47271
|
model: route.model,
|
|
46869
47272
|
retries: req.retries,
|
|
46870
47273
|
timeoutSecs: req.timeoutSecs
|
|
46871
47274
|
}) : route.backend === "opencode" ? new OpenCodeStoryAgent({
|
|
46872
47275
|
id: req.storyId,
|
|
46873
47276
|
prompt: req.prompt,
|
|
46874
|
-
cwd:
|
|
47277
|
+
cwd: storyCwd,
|
|
46875
47278
|
model: route.model,
|
|
46876
47279
|
retries: req.retries,
|
|
46877
47280
|
timeoutSecs: req.timeoutSecs
|
|
46878
47281
|
}) : route.backend === "codex" ? new CodexStoryAgent({
|
|
46879
47282
|
id: req.storyId,
|
|
46880
47283
|
prompt: req.prompt,
|
|
46881
|
-
cwd:
|
|
47284
|
+
cwd: storyCwd,
|
|
46882
47285
|
// undefined → let Codex pick its account default.
|
|
46883
47286
|
model: route.model,
|
|
46884
47287
|
retries: req.retries,
|
|
@@ -46887,7 +47290,7 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46887
47290
|
{
|
|
46888
47291
|
id: req.storyId,
|
|
46889
47292
|
prompt: req.prompt,
|
|
46890
|
-
cwd:
|
|
47293
|
+
cwd: storyCwd,
|
|
46891
47294
|
model: route.model,
|
|
46892
47295
|
retries: req.retries,
|
|
46893
47296
|
timeoutSecs: req.timeoutSecs
|
|
@@ -46900,7 +47303,7 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46900
47303
|
) : new StoryAgent({
|
|
46901
47304
|
id: req.storyId,
|
|
46902
47305
|
prompt: req.prompt,
|
|
46903
|
-
cwd:
|
|
47306
|
+
cwd: storyCwd,
|
|
46904
47307
|
// undefined → StoryAgent applies its own default.
|
|
46905
47308
|
model: route.model,
|
|
46906
47309
|
effort: this.opts.effort,
|
|
@@ -46918,9 +47321,9 @@ var StoryFactory = class extends BaseObserver {
|
|
|
46918
47321
|
};
|
|
46919
47322
|
|
|
46920
47323
|
// ../baro-orchestrator/src/participants/surgeon.ts
|
|
46921
|
-
import { execFile as
|
|
46922
|
-
import { promisify as
|
|
46923
|
-
var execFileAsync3 =
|
|
47324
|
+
import { execFile as execFile5 } from "child_process";
|
|
47325
|
+
import { promisify as promisify6 } from "util";
|
|
47326
|
+
var execFileAsync3 = promisify6(execFile5);
|
|
46924
47327
|
var SURGEON_SYSTEM_PROMPT = `You are the Surgeon \u2014 an autonomous planner that adapts a software-project
|
|
46925
47328
|
DAG when stories fail. Given:
|
|
46926
47329
|
1. A snapshot of the current PRD (project, story list with dependencies +
|
|
@@ -46997,7 +47400,8 @@ var Surgeon = class extends BaseObserver {
|
|
|
46997
47400
|
maxReplans: opts.maxReplans ?? 10,
|
|
46998
47401
|
claudeBin: opts.claudeBin ?? "claude",
|
|
46999
47402
|
timeoutMs: opts.timeoutMs ?? 9e4,
|
|
47000
|
-
snapshot: opts.snapshot
|
|
47403
|
+
snapshot: opts.snapshot,
|
|
47404
|
+
resolveRoute: opts.resolveRoute
|
|
47001
47405
|
};
|
|
47002
47406
|
}
|
|
47003
47407
|
/** Resolves once every in-flight LLM evaluation has completed. */
|
|
@@ -47036,7 +47440,7 @@ var Surgeon = class extends BaseObserver {
|
|
|
47036
47440
|
*/
|
|
47037
47441
|
async evaluateWithLlm(failure) {
|
|
47038
47442
|
const snap = this.opts.snapshot();
|
|
47039
|
-
const prompt = buildSurgeonPrompt(snap, failure);
|
|
47443
|
+
const prompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
|
|
47040
47444
|
try {
|
|
47041
47445
|
const { stdout } = await execFileAsync3(
|
|
47042
47446
|
this.opts.claudeBin,
|
|
@@ -47086,11 +47490,12 @@ var Surgeon = class extends BaseObserver {
|
|
|
47086
47490
|
}
|
|
47087
47491
|
}
|
|
47088
47492
|
};
|
|
47089
|
-
function buildSurgeonPrompt(snap, failure) {
|
|
47493
|
+
function buildSurgeonPrompt(snap, failure, resolveRoute) {
|
|
47090
47494
|
const storyLines = snap.stories.map(
|
|
47091
47495
|
(s2) => ` - ${s2.id} ${s2.passes ? "[passed]" : "[pending]"} ${s2.model ? `<tier:${s2.model}> ` : ""}"${s2.title}" deps=${JSON.stringify(s2.dependsOn)}`
|
|
47092
47496
|
).join("\n");
|
|
47093
47497
|
const failureStory = snap.stories.find((s2) => s2.id === failure.storyId);
|
|
47498
|
+
const ranOn = resolveRoute ? resolveRoute(failureStory?.model) : null;
|
|
47094
47499
|
return [
|
|
47095
47500
|
`# Project: ${snap.project}`,
|
|
47096
47501
|
`Description: ${snap.description}`,
|
|
@@ -47103,6 +47508,9 @@ function buildSurgeonPrompt(snap, failure) {
|
|
|
47103
47508
|
`Title: ${failureStory?.title ?? "(unknown)"}`,
|
|
47104
47509
|
`Description: ${failureStory?.description ?? "(unknown)"}`,
|
|
47105
47510
|
`Tier that just failed: ${failureStory?.model ?? "(default)"}`,
|
|
47511
|
+
...ranOn ? [
|
|
47512
|
+
`Model that actually ran: ${ranOn} (an override replaced the planner tier above; refer to THIS model in your reason, not the tier)`
|
|
47513
|
+
] : [],
|
|
47106
47514
|
`Attempts: ${failure.attempts}`,
|
|
47107
47515
|
`Error: ${failure.error ?? "(no reason captured)"}`,
|
|
47108
47516
|
"",
|
|
@@ -47153,7 +47561,8 @@ var SurgeonCodex = class extends BaseObserver {
|
|
|
47153
47561
|
maxReplans: opts.maxReplans ?? 10,
|
|
47154
47562
|
codexBin: opts.codexBin ?? "codex",
|
|
47155
47563
|
timeoutMs: opts.timeoutMs ?? 3e5,
|
|
47156
|
-
snapshot: opts.snapshot
|
|
47564
|
+
snapshot: opts.snapshot,
|
|
47565
|
+
resolveRoute: opts.resolveRoute
|
|
47157
47566
|
};
|
|
47158
47567
|
}
|
|
47159
47568
|
async idle() {
|
|
@@ -47177,7 +47586,7 @@ var SurgeonCodex = class extends BaseObserver {
|
|
|
47177
47586
|
}
|
|
47178
47587
|
async evaluateWithLlm(failure) {
|
|
47179
47588
|
const snap = this.opts.snapshot();
|
|
47180
|
-
const userPrompt = buildSurgeonPrompt(snap, failure);
|
|
47589
|
+
const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
|
|
47181
47590
|
const prompt = `${SURGEON_SYSTEM_PROMPT}
|
|
47182
47591
|
|
|
47183
47592
|
${userPrompt}`;
|
|
@@ -47249,7 +47658,8 @@ var SurgeonOpenAI = class extends BaseObserver {
|
|
|
47249
47658
|
this.opts = {
|
|
47250
47659
|
maxReplans: opts.maxReplans ?? 10,
|
|
47251
47660
|
model: opts.model ?? "gpt-5.5",
|
|
47252
|
-
snapshot: opts.snapshot
|
|
47661
|
+
snapshot: opts.snapshot,
|
|
47662
|
+
resolveRoute: opts.resolveRoute
|
|
47253
47663
|
};
|
|
47254
47664
|
this.model = pickModel3(this.opts.model);
|
|
47255
47665
|
}
|
|
@@ -47281,7 +47691,7 @@ var SurgeonOpenAI = class extends BaseObserver {
|
|
|
47281
47691
|
*/
|
|
47282
47692
|
async evaluate(failure) {
|
|
47283
47693
|
const snap = this.opts.snapshot();
|
|
47284
|
-
const userPrompt = buildSurgeonPrompt(snap, failure);
|
|
47694
|
+
const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
|
|
47285
47695
|
const context = ModelContext.create("surgeon").addContextItem(SystemMessageItem.create(SURGEON_SYSTEM_PROMPT)).addContextItem(UserMessageItem.create(userPrompt));
|
|
47286
47696
|
try {
|
|
47287
47697
|
const round = await runInferenceRound(context, this.model);
|
|
@@ -47338,7 +47748,8 @@ var SurgeonOpenCode = class extends BaseObserver {
|
|
|
47338
47748
|
maxReplans: opts.maxReplans ?? 10,
|
|
47339
47749
|
opencodeBin: opts.opencodeBin ?? "opencode",
|
|
47340
47750
|
timeoutMs: opts.timeoutMs ?? 3e5,
|
|
47341
|
-
snapshot: opts.snapshot
|
|
47751
|
+
snapshot: opts.snapshot,
|
|
47752
|
+
resolveRoute: opts.resolveRoute
|
|
47342
47753
|
};
|
|
47343
47754
|
}
|
|
47344
47755
|
async idle() {
|
|
@@ -47362,7 +47773,7 @@ var SurgeonOpenCode = class extends BaseObserver {
|
|
|
47362
47773
|
}
|
|
47363
47774
|
async evaluateWithLlm(failure) {
|
|
47364
47775
|
const snap = this.opts.snapshot();
|
|
47365
|
-
const userPrompt = buildSurgeonPrompt(snap, failure);
|
|
47776
|
+
const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
|
|
47366
47777
|
const prompt = `${SURGEON_SYSTEM_PROMPT}
|
|
47367
47778
|
|
|
47368
47779
|
${userPrompt}`;
|
|
@@ -47417,7 +47828,8 @@ var SurgeonPi = class extends BaseObserver {
|
|
|
47417
47828
|
maxReplans: opts.maxReplans ?? 10,
|
|
47418
47829
|
piBin: opts.piBin ?? "pi",
|
|
47419
47830
|
timeoutMs: opts.timeoutMs ?? 3e5,
|
|
47420
|
-
snapshot: opts.snapshot
|
|
47831
|
+
snapshot: opts.snapshot,
|
|
47832
|
+
resolveRoute: opts.resolveRoute
|
|
47421
47833
|
};
|
|
47422
47834
|
}
|
|
47423
47835
|
async idle() {
|
|
@@ -47441,7 +47853,7 @@ var SurgeonPi = class extends BaseObserver {
|
|
|
47441
47853
|
}
|
|
47442
47854
|
async evaluateWithLlm(failure) {
|
|
47443
47855
|
const snap = this.opts.snapshot();
|
|
47444
|
-
const userPrompt = buildSurgeonPrompt(snap, failure);
|
|
47856
|
+
const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
|
|
47445
47857
|
const prompt = `${SURGEON_SYSTEM_PROMPT}
|
|
47446
47858
|
|
|
47447
47859
|
${userPrompt}`;
|
|
@@ -47574,11 +47986,19 @@ async function orchestrate(config) {
|
|
|
47574
47986
|
const useGit = config.withGit ?? await isInsideGitRepo(config.cwd);
|
|
47575
47987
|
const gitGate = new GitGate();
|
|
47576
47988
|
let baseSha = null;
|
|
47989
|
+
const runId = `run-${Date.now()}-${process.pid}`;
|
|
47990
|
+
const worktreesEnabled = config.withWorktrees ?? !("BARO_NO_WORKTREES" in process.env);
|
|
47991
|
+
const worktrees = useGit && worktreesEnabled ? new WorktreeManager(config.cwd, gitGate, runId, {
|
|
47992
|
+
linkDepDirs: config.worktreeLinkDepDirs ?? true,
|
|
47993
|
+
onLog: (line) => emitTui && emit({ type: "story_log", id: "_git", line })
|
|
47994
|
+
}) : null;
|
|
47995
|
+
const storyPushes = [];
|
|
47996
|
+
let worktreePushNeeded = false;
|
|
47577
47997
|
const useLibrarian = config.withLibrarian ?? true;
|
|
47578
47998
|
const useSentry = config.withSentry ?? true;
|
|
47579
47999
|
const useMemory = config.withMemory ?? true;
|
|
47580
|
-
const sessionsDir =
|
|
47581
|
-
const memorySessionPath = useMemory ?
|
|
48000
|
+
const sessionsDir = join6(process.env.HOME || "/tmp", ".baro", "sessions");
|
|
48001
|
+
const memorySessionPath = useMemory ? join6(sessionsDir, runId, "memory") : void 0;
|
|
47582
48002
|
if (useMemory) {
|
|
47583
48003
|
try {
|
|
47584
48004
|
const { pruneOldSessions: pruneOldSessions2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
@@ -47610,32 +48030,53 @@ async function orchestrate(config) {
|
|
|
47610
48030
|
}))
|
|
47611
48031
|
};
|
|
47612
48032
|
};
|
|
48033
|
+
const storyRouting = {
|
|
48034
|
+
fallbackBackend: storyLlm,
|
|
48035
|
+
openaiDefaultModel: config.storyModel ?? "gpt-5.5",
|
|
48036
|
+
override: config.storyModel,
|
|
48037
|
+
tierMap: config.tierMap,
|
|
48038
|
+
endpoints: config.openaiEndpoints,
|
|
48039
|
+
defaultApiKey: process.env.OPENAI_API_KEY
|
|
48040
|
+
};
|
|
48041
|
+
const routingOverridden = storyLlm !== "claude" || !!config.storyModel || !!config.tierMap;
|
|
48042
|
+
const resolveRoute = routingOverridden ? (model) => {
|
|
48043
|
+
try {
|
|
48044
|
+
return formatRoute(resolveStoryRoute(model, storyRouting));
|
|
48045
|
+
} catch {
|
|
48046
|
+
return null;
|
|
48047
|
+
}
|
|
48048
|
+
} : void 0;
|
|
47613
48049
|
if (surgeonLlm === "openai") {
|
|
47614
48050
|
surgeon = new SurgeonOpenAI({
|
|
47615
48051
|
snapshot,
|
|
48052
|
+
resolveRoute,
|
|
47616
48053
|
model: config.surgeonModel ?? "gpt-5.5"
|
|
47617
48054
|
});
|
|
47618
48055
|
} else if (surgeonLlm === "codex") {
|
|
47619
48056
|
surgeon = new SurgeonCodex({
|
|
47620
48057
|
snapshot,
|
|
48058
|
+
resolveRoute,
|
|
47621
48059
|
useLlm: config.surgeonUseLlm ?? true,
|
|
47622
48060
|
model: config.surgeonModel
|
|
47623
48061
|
});
|
|
47624
48062
|
} else if (surgeonLlm === "opencode") {
|
|
47625
48063
|
surgeon = new SurgeonOpenCode({
|
|
47626
48064
|
snapshot,
|
|
48065
|
+
resolveRoute,
|
|
47627
48066
|
useLlm: config.surgeonUseLlm ?? true,
|
|
47628
48067
|
model: config.surgeonModel
|
|
47629
48068
|
});
|
|
47630
48069
|
} else if (surgeonLlm === "pi") {
|
|
47631
48070
|
surgeon = new SurgeonPi({
|
|
47632
48071
|
snapshot,
|
|
48072
|
+
resolveRoute,
|
|
47633
48073
|
useLlm: config.surgeonUseLlm ?? true,
|
|
47634
48074
|
model: config.surgeonModel
|
|
47635
48075
|
});
|
|
47636
48076
|
} else {
|
|
47637
48077
|
surgeon = new Surgeon({
|
|
47638
48078
|
snapshot,
|
|
48079
|
+
resolveRoute,
|
|
47639
48080
|
useLlm: config.surgeonUseLlm ?? false,
|
|
47640
48081
|
model: config.surgeonModel ?? "opus"
|
|
47641
48082
|
});
|
|
@@ -47702,6 +48143,7 @@ async function orchestrate(config) {
|
|
|
47702
48143
|
(line) => emitTui && emit({ type: "story_log", id: "_git", line })
|
|
47703
48144
|
);
|
|
47704
48145
|
}
|
|
48146
|
+
await worktrees?.cleanupStaleOnStart();
|
|
47705
48147
|
} : void 0,
|
|
47706
48148
|
onBeforeStoryLaunch: librarian ? (storyId, story) => {
|
|
47707
48149
|
const hints = [
|
|
@@ -47711,40 +48153,50 @@ async function orchestrate(config) {
|
|
|
47711
48153
|
return librarian.gatherContext(storyId, hints);
|
|
47712
48154
|
} : void 0,
|
|
47713
48155
|
onStoryPassed: useGit ? async (storyId) => {
|
|
47714
|
-
|
|
47715
|
-
|
|
47716
|
-
|
|
47717
|
-
|
|
47718
|
-
|
|
47719
|
-
|
|
47720
|
-
|
|
47721
|
-
|
|
47722
|
-
|
|
47723
|
-
|
|
47724
|
-
|
|
47725
|
-
emit({
|
|
47726
|
-
type: "push_status",
|
|
47727
|
-
id: storyId,
|
|
47728
|
-
success: true,
|
|
47729
|
-
error: null
|
|
47730
|
-
});
|
|
48156
|
+
const log2 = (line) => emitTui && emit({ type: "story_log", id: storyId, line });
|
|
48157
|
+
if (worktrees) {
|
|
48158
|
+
let merged = false;
|
|
48159
|
+
try {
|
|
48160
|
+
merged = await worktrees.mergeBack(storyId);
|
|
48161
|
+
} catch (e2) {
|
|
48162
|
+
log2(`[git] merge-back failed; worktree preserved for recovery: ${e2?.message ?? String(e2)}`);
|
|
48163
|
+
if (emitTui) {
|
|
48164
|
+
emit({ type: "push_status", id: storyId, success: false, error: e2?.message ?? String(e2) });
|
|
48165
|
+
}
|
|
48166
|
+
return;
|
|
47731
48167
|
}
|
|
47732
|
-
|
|
47733
|
-
|
|
47734
|
-
|
|
47735
|
-
|
|
47736
|
-
id: storyId,
|
|
47737
|
-
|
|
47738
|
-
|
|
47739
|
-
});
|
|
48168
|
+
if (merged) {
|
|
48169
|
+
await worktrees.cleanup(storyId);
|
|
48170
|
+
worktreePushNeeded = true;
|
|
48171
|
+
if (emitTui) {
|
|
48172
|
+
emit({ type: "push_status", id: storyId, success: true, error: null });
|
|
48173
|
+
}
|
|
48174
|
+
return;
|
|
47740
48175
|
}
|
|
47741
48176
|
}
|
|
47742
|
-
|
|
48177
|
+
await safePullRebase(config.cwd, log2, gitGate);
|
|
48178
|
+
storyPushes.push(
|
|
48179
|
+
(async () => {
|
|
48180
|
+
try {
|
|
48181
|
+
await gitPushWithRetry(gitGate, { cwd: config.cwd, onLog: log2 });
|
|
48182
|
+
if (emitTui) {
|
|
48183
|
+
emit({ type: "push_status", id: storyId, success: true, error: null });
|
|
48184
|
+
}
|
|
48185
|
+
} catch (e2) {
|
|
48186
|
+
if (emitTui) {
|
|
48187
|
+
emit({ type: "push_status", id: storyId, success: false, error: e2?.message ?? String(e2) });
|
|
48188
|
+
}
|
|
48189
|
+
}
|
|
48190
|
+
})()
|
|
48191
|
+
);
|
|
48192
|
+
} : void 0,
|
|
48193
|
+
onStoryFailed: worktrees ? (storyId) => worktrees.cleanup(storyId) : void 0
|
|
47743
48194
|
});
|
|
47744
48195
|
conductor.setEnvironment(env);
|
|
47745
48196
|
conductor.join(env);
|
|
47746
48197
|
const storyFactory = new StoryFactory({
|
|
47747
48198
|
cwd: config.cwd,
|
|
48199
|
+
worktrees: worktrees ?? void 0,
|
|
47748
48200
|
llm: storyLlm,
|
|
47749
48201
|
openaiModel: config.storyModel ?? "gpt-5.5",
|
|
47750
48202
|
storyModelOverride: config.storyModel,
|
|
@@ -47776,6 +48228,17 @@ async function orchestrate(config) {
|
|
|
47776
48228
|
RunStartRequest.create({ reason: "orchestrate" })
|
|
47777
48229
|
);
|
|
47778
48230
|
const summary = await conductor.done;
|
|
48231
|
+
await Promise.allSettled(storyPushes);
|
|
48232
|
+
if (worktreePushNeeded) {
|
|
48233
|
+
const log2 = (line) => emitTui && emit({ type: "story_log", id: "_git", line });
|
|
48234
|
+
try {
|
|
48235
|
+
await gitPushWithRetry(gitGate, { cwd: config.cwd, onLog: log2 });
|
|
48236
|
+
if (emitTui) emit({ type: "push_status", id: "_git", success: true, error: null });
|
|
48237
|
+
} catch (e2) {
|
|
48238
|
+
if (emitTui) emit({ type: "push_status", id: "_git", success: false, error: e2?.message ?? String(e2) });
|
|
48239
|
+
}
|
|
48240
|
+
}
|
|
48241
|
+
await worktrees?.cleanupAll();
|
|
47779
48242
|
if (critic) await critic.idle();
|
|
47780
48243
|
if (surgeon) await surgeon.idle();
|
|
47781
48244
|
if (finalizer) await finalizer.complete();
|
|
@@ -48054,7 +48517,7 @@ async function main() {
|
|
|
48054
48517
|
}
|
|
48055
48518
|
const cwd = resolve3(args.cwd);
|
|
48056
48519
|
const prdPath = resolve3(cwd, args.prd);
|
|
48057
|
-
if (!
|
|
48520
|
+
if (!existsSync6(prdPath)) {
|
|
48058
48521
|
process.stderr.write(`[cli] PRD not found: ${prdPath}
|
|
48059
48522
|
`);
|
|
48060
48523
|
process.exit(2);
|