baro-ai 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +853 -77
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../baro-orchestrator/scripts/cli.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { resolve } from "path";
|
|
4
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5
|
+
import { resolve as resolve3 } from "path";
|
|
6
6
|
|
|
7
7
|
// ../baro-orchestrator/src/orchestrate.ts
|
|
8
|
-
import { mkdirSync as
|
|
9
|
-
import { dirname as
|
|
8
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
9
|
+
import { dirname as dirname3 } from "path";
|
|
10
10
|
|
|
11
11
|
// ../../node_modules/openai/internal/tslib.mjs
|
|
12
12
|
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
@@ -247,7 +247,7 @@ var safeJSON = (text) => {
|
|
|
247
247
|
};
|
|
248
248
|
|
|
249
249
|
// ../../node_modules/openai/internal/utils/sleep.mjs
|
|
250
|
-
var sleep = (ms) => new Promise((
|
|
250
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
251
251
|
|
|
252
252
|
// ../../node_modules/openai/version.mjs
|
|
253
253
|
var VERSION = "6.35.0";
|
|
@@ -1326,8 +1326,8 @@ function addRequestID(value, response) {
|
|
|
1326
1326
|
var _APIPromise_client;
|
|
1327
1327
|
var APIPromise = class _APIPromise extends Promise {
|
|
1328
1328
|
constructor(client, responsePromise, parseResponse2 = defaultParseResponse) {
|
|
1329
|
-
super((
|
|
1330
|
-
|
|
1329
|
+
super((resolve4) => {
|
|
1330
|
+
resolve4(null);
|
|
1331
1331
|
});
|
|
1332
1332
|
this.responsePromise = responsePromise;
|
|
1333
1333
|
this.parseResponse = parseResponse2;
|
|
@@ -1756,12 +1756,12 @@ function encodeURIPath(str2) {
|
|
|
1756
1756
|
return str2.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
1757
1757
|
}
|
|
1758
1758
|
var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
1759
|
-
var createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
1759
|
+
var createPathTagFunction = (pathEncoder = encodeURIPath) => function path4(statics, ...params) {
|
|
1760
1760
|
if (statics.length === 1)
|
|
1761
1761
|
return statics[0];
|
|
1762
1762
|
let postPath = false;
|
|
1763
1763
|
const invalidSegments = [];
|
|
1764
|
-
const
|
|
1764
|
+
const path5 = statics.reduce((previousValue, currentValue, index) => {
|
|
1765
1765
|
if (/[?#]/.test(currentValue)) {
|
|
1766
1766
|
postPath = true;
|
|
1767
1767
|
}
|
|
@@ -1778,7 +1778,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path2(stat
|
|
|
1778
1778
|
}
|
|
1779
1779
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
1780
1780
|
}, "");
|
|
1781
|
-
const pathOnly =
|
|
1781
|
+
const pathOnly = path5.split(/[?#]/, 1)[0];
|
|
1782
1782
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
1783
1783
|
let match;
|
|
1784
1784
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -1799,10 +1799,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path2(stat
|
|
|
1799
1799
|
}, "");
|
|
1800
1800
|
throw new OpenAIError(`Path parameters result in path with invalid segments:
|
|
1801
1801
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
1802
|
-
${
|
|
1802
|
+
${path5}
|
|
1803
1803
|
${underline}`);
|
|
1804
1804
|
}
|
|
1805
|
-
return
|
|
1805
|
+
return path5;
|
|
1806
1806
|
};
|
|
1807
1807
|
var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
1808
1808
|
|
|
@@ -1975,12 +1975,12 @@ var EventStream = class {
|
|
|
1975
1975
|
_EventStream_errored.set(this, false);
|
|
1976
1976
|
_EventStream_aborted.set(this, false);
|
|
1977
1977
|
_EventStream_catchingPromiseCreated.set(this, false);
|
|
1978
|
-
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((
|
|
1979
|
-
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise,
|
|
1978
|
+
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((resolve4, reject) => {
|
|
1979
|
+
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve4, "f");
|
|
1980
1980
|
__classPrivateFieldSet(this, _EventStream_rejectConnectedPromise, reject, "f");
|
|
1981
1981
|
}), "f");
|
|
1982
|
-
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((
|
|
1983
|
-
__classPrivateFieldSet(this, _EventStream_resolveEndPromise,
|
|
1982
|
+
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((resolve4, reject) => {
|
|
1983
|
+
__classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve4, "f");
|
|
1984
1984
|
__classPrivateFieldSet(this, _EventStream_rejectEndPromise, reject, "f");
|
|
1985
1985
|
}), "f");
|
|
1986
1986
|
__classPrivateFieldGet(this, _EventStream_connectedPromise, "f").catch(() => {
|
|
@@ -2064,11 +2064,11 @@ var EventStream = class {
|
|
|
2064
2064
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
2065
2065
|
*/
|
|
2066
2066
|
emitted(event) {
|
|
2067
|
-
return new Promise((
|
|
2067
|
+
return new Promise((resolve4, reject) => {
|
|
2068
2068
|
__classPrivateFieldSet(this, _EventStream_catchingPromiseCreated, true, "f");
|
|
2069
2069
|
if (event !== "error")
|
|
2070
2070
|
this.once("error", reject);
|
|
2071
|
-
this.once(event,
|
|
2071
|
+
this.once(event, resolve4);
|
|
2072
2072
|
});
|
|
2073
2073
|
}
|
|
2074
2074
|
async done() {
|
|
@@ -3007,7 +3007,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
|
|
|
3007
3007
|
if (done) {
|
|
3008
3008
|
return { value: void 0, done: true };
|
|
3009
3009
|
}
|
|
3010
|
-
return new Promise((
|
|
3010
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
3011
3011
|
}
|
|
3012
3012
|
const chunk = pushQueue.shift();
|
|
3013
3013
|
return { value: chunk, done: false };
|
|
@@ -3839,7 +3839,7 @@ var AssistantStream = class extends EventStream {
|
|
|
3839
3839
|
if (done) {
|
|
3840
3840
|
return { value: void 0, done: true };
|
|
3841
3841
|
}
|
|
3842
|
-
return new Promise((
|
|
3842
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
3843
3843
|
}
|
|
3844
3844
|
const chunk = pushQueue.shift();
|
|
3845
3845
|
return { value: chunk, done: false };
|
|
@@ -5788,7 +5788,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
|
|
|
5788
5788
|
if (done) {
|
|
5789
5789
|
return { value: void 0, done: true };
|
|
5790
5790
|
}
|
|
5791
|
-
return new Promise((
|
|
5791
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
|
|
5792
5792
|
}
|
|
5793
5793
|
const event = pushQueue.shift();
|
|
5794
5794
|
return { value: event, done: false };
|
|
@@ -6775,9 +6775,9 @@ var OpenAI = class {
|
|
|
6775
6775
|
this.apiKey = token;
|
|
6776
6776
|
return true;
|
|
6777
6777
|
}
|
|
6778
|
-
buildURL(
|
|
6778
|
+
buildURL(path4, query, defaultBaseURL) {
|
|
6779
6779
|
const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
6780
|
-
const url = isAbsoluteURL(
|
|
6780
|
+
const url = isAbsoluteURL(path4) ? new URL(path4) : new URL(baseURL + (baseURL.endsWith("/") && path4.startsWith("/") ? path4.slice(1) : path4));
|
|
6781
6781
|
const defaultQuery = this.defaultQuery();
|
|
6782
6782
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
6783
6783
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -6802,24 +6802,24 @@ var OpenAI = class {
|
|
|
6802
6802
|
*/
|
|
6803
6803
|
async prepareRequest(request, { url, options }) {
|
|
6804
6804
|
}
|
|
6805
|
-
get(
|
|
6806
|
-
return this.methodRequest("get",
|
|
6805
|
+
get(path4, opts) {
|
|
6806
|
+
return this.methodRequest("get", path4, opts);
|
|
6807
6807
|
}
|
|
6808
|
-
post(
|
|
6809
|
-
return this.methodRequest("post",
|
|
6808
|
+
post(path4, opts) {
|
|
6809
|
+
return this.methodRequest("post", path4, opts);
|
|
6810
6810
|
}
|
|
6811
|
-
patch(
|
|
6812
|
-
return this.methodRequest("patch",
|
|
6811
|
+
patch(path4, opts) {
|
|
6812
|
+
return this.methodRequest("patch", path4, opts);
|
|
6813
6813
|
}
|
|
6814
|
-
put(
|
|
6815
|
-
return this.methodRequest("put",
|
|
6814
|
+
put(path4, opts) {
|
|
6815
|
+
return this.methodRequest("put", path4, opts);
|
|
6816
6816
|
}
|
|
6817
|
-
delete(
|
|
6818
|
-
return this.methodRequest("delete",
|
|
6817
|
+
delete(path4, opts) {
|
|
6818
|
+
return this.methodRequest("delete", path4, opts);
|
|
6819
6819
|
}
|
|
6820
|
-
methodRequest(method,
|
|
6820
|
+
methodRequest(method, path4, opts) {
|
|
6821
6821
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
6822
|
-
return { method, path:
|
|
6822
|
+
return { method, path: path4, ...opts2 };
|
|
6823
6823
|
}));
|
|
6824
6824
|
}
|
|
6825
6825
|
request(options, remainingRetries = null) {
|
|
@@ -6937,8 +6937,8 @@ var OpenAI = class {
|
|
|
6937
6937
|
}));
|
|
6938
6938
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
6939
6939
|
}
|
|
6940
|
-
getAPIList(
|
|
6941
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
6940
|
+
getAPIList(path4, Page2, opts) {
|
|
6941
|
+
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path4, ...opts2 })) : { method: "get", path: path4, ...opts });
|
|
6942
6942
|
}
|
|
6943
6943
|
requestAPIList(Page2, options) {
|
|
6944
6944
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -7029,8 +7029,8 @@ var OpenAI = class {
|
|
|
7029
7029
|
}
|
|
7030
7030
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
7031
7031
|
const options = { ...inputOptions };
|
|
7032
|
-
const { method, path:
|
|
7033
|
-
const url = this.buildURL(
|
|
7032
|
+
const { method, path: path4, query, defaultBaseURL } = options;
|
|
7033
|
+
const url = this.buildURL(path4, query, defaultBaseURL);
|
|
7034
7034
|
if ("timeout" in options)
|
|
7035
7035
|
validatePositiveInteger("timeout", options.timeout);
|
|
7036
7036
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -7665,7 +7665,7 @@ var AgenticEnvironment = class {
|
|
|
7665
7665
|
async start() {
|
|
7666
7666
|
this.isActive = true;
|
|
7667
7667
|
while (this.isActive) {
|
|
7668
|
-
await new Promise((
|
|
7668
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
7669
7669
|
}
|
|
7670
7670
|
}
|
|
7671
7671
|
stop() {
|
|
@@ -7783,8 +7783,8 @@ var GitGate = class {
|
|
|
7783
7783
|
chain = Promise.resolve();
|
|
7784
7784
|
async acquire() {
|
|
7785
7785
|
let release;
|
|
7786
|
-
const next = new Promise((
|
|
7787
|
-
release =
|
|
7786
|
+
const next = new Promise((resolve4) => {
|
|
7787
|
+
release = resolve4;
|
|
7788
7788
|
});
|
|
7789
7789
|
const wait = this.chain;
|
|
7790
7790
|
this.chain = this.chain.then(() => next);
|
|
@@ -8491,13 +8491,13 @@ import { join } from "path";
|
|
|
8491
8491
|
// ../baro-orchestrator/src/prd.ts
|
|
8492
8492
|
import { readFileSync, writeFileSync } from "fs";
|
|
8493
8493
|
var STORY_DEFAULTS = { retries: 2 };
|
|
8494
|
-
function loadPrd(
|
|
8495
|
-
const raw = readFileSync(
|
|
8494
|
+
function loadPrd(path4) {
|
|
8495
|
+
const raw = readFileSync(path4, "utf8");
|
|
8496
8496
|
const json = JSON.parse(raw);
|
|
8497
|
-
return normalizePrd(json,
|
|
8497
|
+
return normalizePrd(json, path4);
|
|
8498
8498
|
}
|
|
8499
|
-
function savePrd(
|
|
8500
|
-
writeFileSync(
|
|
8499
|
+
function savePrd(path4, prd) {
|
|
8500
|
+
writeFileSync(path4, JSON.stringify(prd, null, 2) + "\n");
|
|
8501
8501
|
}
|
|
8502
8502
|
function normalizePrd(input, source) {
|
|
8503
8503
|
if (!input || typeof input !== "object") {
|
|
@@ -9306,8 +9306,8 @@ var Conductor = class extends BaroParticipant {
|
|
|
9306
9306
|
if (this.opts.intraLevelDelaySecs == null) {
|
|
9307
9307
|
this.opts.intraLevelDelaySecs = 10;
|
|
9308
9308
|
}
|
|
9309
|
-
this.done = new Promise((
|
|
9310
|
-
this.resolveDone =
|
|
9309
|
+
this.done = new Promise((resolve4) => {
|
|
9310
|
+
this.resolveDone = resolve4;
|
|
9311
9311
|
});
|
|
9312
9312
|
}
|
|
9313
9313
|
setEnvironment(env) {
|
|
@@ -9674,9 +9674,9 @@ function applyReplan(prd, replan) {
|
|
|
9674
9674
|
}
|
|
9675
9675
|
return { ...prd, userStories: stories };
|
|
9676
9676
|
}
|
|
9677
|
-
function readFileSyncSafe(
|
|
9677
|
+
function readFileSyncSafe(path4) {
|
|
9678
9678
|
try {
|
|
9679
|
-
return readFileSync2(
|
|
9679
|
+
return readFileSync2(path4, "utf8");
|
|
9680
9680
|
} catch {
|
|
9681
9681
|
return null;
|
|
9682
9682
|
}
|
|
@@ -10626,19 +10626,19 @@ function describeCall(tool, args) {
|
|
|
10626
10626
|
const tags = [tool.toLowerCase()];
|
|
10627
10627
|
let summary = `${tool} call`;
|
|
10628
10628
|
if (tool === "Read") {
|
|
10629
|
-
const
|
|
10630
|
-
if (
|
|
10631
|
-
summary = `Read ${
|
|
10632
|
-
tags.push(
|
|
10633
|
-
const base =
|
|
10629
|
+
const path4 = stringArg(args, "file_path") ?? stringArg(args, "path");
|
|
10630
|
+
if (path4) {
|
|
10631
|
+
summary = `Read ${path4}`;
|
|
10632
|
+
tags.push(path4);
|
|
10633
|
+
const base = path4.split("/").pop();
|
|
10634
10634
|
if (base) tags.push(base);
|
|
10635
10635
|
}
|
|
10636
10636
|
} else if (tool === "Grep") {
|
|
10637
10637
|
const pattern = stringArg(args, "pattern");
|
|
10638
|
-
const
|
|
10639
|
-
summary = `Grep '${pattern ?? "?"}'${
|
|
10638
|
+
const path4 = stringArg(args, "path");
|
|
10639
|
+
summary = `Grep '${pattern ?? "?"}'${path4 ? ` in ${path4}` : ""}`;
|
|
10640
10640
|
if (pattern) tags.push(pattern);
|
|
10641
|
-
if (
|
|
10641
|
+
if (path4) tags.push(path4);
|
|
10642
10642
|
} else if (tool === "Glob") {
|
|
10643
10643
|
const pattern = stringArg(args, "pattern");
|
|
10644
10644
|
summary = `Glob '${pattern ?? "?"}'`;
|
|
@@ -10752,27 +10752,27 @@ var Sentry = class extends BaroParticipant {
|
|
|
10752
10752
|
if (!WRITE_TOOLS.has(item.name)) return;
|
|
10753
10753
|
const agentId = source.agentId;
|
|
10754
10754
|
if (typeof agentId !== "string") return;
|
|
10755
|
-
const
|
|
10756
|
-
if (!
|
|
10755
|
+
const path4 = extractPath(item);
|
|
10756
|
+
if (!path4) return;
|
|
10757
10757
|
const touch = {
|
|
10758
10758
|
agentId,
|
|
10759
|
-
path:
|
|
10759
|
+
path: path4,
|
|
10760
10760
|
tool: item.name,
|
|
10761
10761
|
at: Date.now()
|
|
10762
10762
|
};
|
|
10763
10763
|
this.touches.push(touch);
|
|
10764
|
-
const set = this.touchedBy.get(
|
|
10764
|
+
const set = this.touchedBy.get(path4) ?? /* @__PURE__ */ new Set();
|
|
10765
10765
|
set.add(agentId);
|
|
10766
|
-
this.touchedBy.set(
|
|
10766
|
+
this.touchedBy.set(path4, set);
|
|
10767
10767
|
const otherAgents = [...set].filter((a) => a !== agentId);
|
|
10768
10768
|
if (otherAgents.length === 0) return;
|
|
10769
10769
|
this.opts.onOverlap?.({
|
|
10770
|
-
path:
|
|
10770
|
+
path: path4,
|
|
10771
10771
|
agents: [agentId, ...otherAgents]
|
|
10772
10772
|
});
|
|
10773
|
-
if (!this.opts.emitNotice || this.noticedPaths.has(
|
|
10774
|
-
this.noticedPaths.add(
|
|
10775
|
-
const reason = `agents [${[agentId, ...otherAgents].join(", ")}] both touched ${
|
|
10773
|
+
if (!this.opts.emitNotice || this.noticedPaths.has(path4)) return;
|
|
10774
|
+
this.noticedPaths.add(path4);
|
|
10775
|
+
const reason = `agents [${[agentId, ...otherAgents].join(", ")}] both touched ${path4}`;
|
|
10776
10776
|
for (const env of this.getEnvironments()) {
|
|
10777
10777
|
;
|
|
10778
10778
|
env.deliverBusEvent(
|
|
@@ -10782,7 +10782,7 @@ var Sentry = class extends BaroParticipant {
|
|
|
10782
10782
|
otherAgents[0],
|
|
10783
10783
|
"notice",
|
|
10784
10784
|
reason,
|
|
10785
|
-
{ path:
|
|
10785
|
+
{ path: path4, agents: [agentId, ...otherAgents] }
|
|
10786
10786
|
)
|
|
10787
10787
|
);
|
|
10788
10788
|
}
|
|
@@ -10802,6 +10802,767 @@ function extractPath(item) {
|
|
|
10802
10802
|
return null;
|
|
10803
10803
|
}
|
|
10804
10804
|
|
|
10805
|
+
// ../baro-orchestrator/src/planning/story-tools.ts
|
|
10806
|
+
import * as fs2 from "fs";
|
|
10807
|
+
import * as path3 from "path";
|
|
10808
|
+
|
|
10809
|
+
// ../baro-orchestrator/src/planning/codebase-tools.ts
|
|
10810
|
+
import { execSync } from "child_process";
|
|
10811
|
+
import * as fs from "fs";
|
|
10812
|
+
import * as path2 from "path";
|
|
10813
|
+
var IGNORE = /* @__PURE__ */ new Set([
|
|
10814
|
+
"node_modules",
|
|
10815
|
+
".git",
|
|
10816
|
+
"dist",
|
|
10817
|
+
"build",
|
|
10818
|
+
".next",
|
|
10819
|
+
".nuxt",
|
|
10820
|
+
"coverage",
|
|
10821
|
+
".cache",
|
|
10822
|
+
"__pycache__",
|
|
10823
|
+
"target",
|
|
10824
|
+
".output",
|
|
10825
|
+
".vercel"
|
|
10826
|
+
]);
|
|
10827
|
+
var MAX_FILE_BYTES = 15e3;
|
|
10828
|
+
var MAX_GREP_LINES = 80;
|
|
10829
|
+
var MAX_BASH_OUTPUT_BYTES = 8e3;
|
|
10830
|
+
function createCodebaseTools(cwd) {
|
|
10831
|
+
return [
|
|
10832
|
+
readFileTool(cwd),
|
|
10833
|
+
listFilesTool(cwd),
|
|
10834
|
+
fileTreeTool(cwd),
|
|
10835
|
+
grepTool(cwd),
|
|
10836
|
+
globTool(cwd),
|
|
10837
|
+
bashTool(cwd)
|
|
10838
|
+
];
|
|
10839
|
+
}
|
|
10840
|
+
function readFileTool(cwd) {
|
|
10841
|
+
return {
|
|
10842
|
+
type: "function",
|
|
10843
|
+
name: "read_file",
|
|
10844
|
+
description: "Read the full contents of a file by path relative to the project root. Returns up to 15000 characters (truncates past that). Use to inspect package.json, source files, config files, READMEs, etc.",
|
|
10845
|
+
strict: true,
|
|
10846
|
+
parameters: {
|
|
10847
|
+
type: "object",
|
|
10848
|
+
properties: {
|
|
10849
|
+
path: {
|
|
10850
|
+
type: "string",
|
|
10851
|
+
description: "Path relative to the project root (e.g. 'src/index.ts')."
|
|
10852
|
+
}
|
|
10853
|
+
},
|
|
10854
|
+
required: ["path"],
|
|
10855
|
+
additionalProperties: false
|
|
10856
|
+
},
|
|
10857
|
+
async invoke(args) {
|
|
10858
|
+
const target = safePath(cwd, args.path);
|
|
10859
|
+
if (!target) return `Error: path '${args.path}' escapes the project root.`;
|
|
10860
|
+
if (!fs.existsSync(target)) return `File not found: ${args.path}`;
|
|
10861
|
+
const stat = fs.statSync(target);
|
|
10862
|
+
if (stat.isDirectory()) {
|
|
10863
|
+
return `${args.path} is a directory \u2014 use list_files or file_tree.`;
|
|
10864
|
+
}
|
|
10865
|
+
if (stat.size > 5e5) {
|
|
10866
|
+
return `File too large (${(stat.size / 1024).toFixed(0)} KB) \u2014 skip or grep instead.`;
|
|
10867
|
+
}
|
|
10868
|
+
let content = fs.readFileSync(target, "utf-8");
|
|
10869
|
+
if (content.length > MAX_FILE_BYTES) {
|
|
10870
|
+
content = content.slice(0, MAX_FILE_BYTES) + "\n... (truncated)";
|
|
10871
|
+
}
|
|
10872
|
+
return content;
|
|
10873
|
+
}
|
|
10874
|
+
};
|
|
10875
|
+
}
|
|
10876
|
+
function listFilesTool(cwd) {
|
|
10877
|
+
return {
|
|
10878
|
+
type: "function",
|
|
10879
|
+
name: "list_files",
|
|
10880
|
+
description: "List files and directories at a path. Use path='' for the project root. Skips node_modules, .git, dist, build, target, and other dependency dirs.",
|
|
10881
|
+
strict: true,
|
|
10882
|
+
parameters: {
|
|
10883
|
+
type: "object",
|
|
10884
|
+
properties: {
|
|
10885
|
+
path: {
|
|
10886
|
+
type: "string",
|
|
10887
|
+
description: "Directory path relative to the project root. Empty for root."
|
|
10888
|
+
},
|
|
10889
|
+
recursive: {
|
|
10890
|
+
type: "boolean",
|
|
10891
|
+
description: "Walk subdirectories up to 4 deep, capped at 200 entries."
|
|
10892
|
+
}
|
|
10893
|
+
},
|
|
10894
|
+
required: ["path", "recursive"],
|
|
10895
|
+
additionalProperties: false
|
|
10896
|
+
},
|
|
10897
|
+
async invoke(args) {
|
|
10898
|
+
const target = safePath(cwd, args.path || ".");
|
|
10899
|
+
if (!target) return `Error: path '${args.path}' escapes the project root.`;
|
|
10900
|
+
if (!fs.existsSync(target)) return `Directory not found: ${args.path}`;
|
|
10901
|
+
const stat = fs.statSync(target);
|
|
10902
|
+
if (!stat.isDirectory()) return `${args.path} is not a directory \u2014 use read_file.`;
|
|
10903
|
+
const results = [];
|
|
10904
|
+
function walk(dir, prefix, depth) {
|
|
10905
|
+
if (results.length >= 200 || depth > 4) return;
|
|
10906
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
10907
|
+
if (IGNORE.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10908
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
10909
|
+
if (entry.isDirectory()) {
|
|
10910
|
+
results.push(rel + "/");
|
|
10911
|
+
if (args.recursive) walk(path2.join(dir, entry.name), rel, depth + 1);
|
|
10912
|
+
} else {
|
|
10913
|
+
results.push(rel);
|
|
10914
|
+
}
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
walk(target, "", 0);
|
|
10918
|
+
return results.join("\n") || "(empty directory)";
|
|
10919
|
+
}
|
|
10920
|
+
};
|
|
10921
|
+
}
|
|
10922
|
+
function fileTreeTool(cwd) {
|
|
10923
|
+
return {
|
|
10924
|
+
type: "function",
|
|
10925
|
+
name: "file_tree",
|
|
10926
|
+
description: "Show a condensed ASCII tree of the project structure up to 3 levels deep. Cheapest way to get a first overview before deciding which files to read.",
|
|
10927
|
+
strict: true,
|
|
10928
|
+
parameters: {
|
|
10929
|
+
type: "object",
|
|
10930
|
+
properties: {},
|
|
10931
|
+
required: [],
|
|
10932
|
+
additionalProperties: false
|
|
10933
|
+
},
|
|
10934
|
+
async invoke() {
|
|
10935
|
+
const lines = [path2.basename(cwd) + "/"];
|
|
10936
|
+
function walk(dir, prefix, depth) {
|
|
10937
|
+
if (lines.length >= 150 || depth > 3) return;
|
|
10938
|
+
let entries;
|
|
10939
|
+
try {
|
|
10940
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10941
|
+
} catch {
|
|
10942
|
+
return;
|
|
10943
|
+
}
|
|
10944
|
+
entries.sort((a, b) => {
|
|
10945
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
10946
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
10947
|
+
return a.name.localeCompare(b.name);
|
|
10948
|
+
});
|
|
10949
|
+
for (let i = 0; i < entries.length; i++) {
|
|
10950
|
+
const entry = entries[i];
|
|
10951
|
+
if (IGNORE.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10952
|
+
const isLast = i === entries.length - 1;
|
|
10953
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
10954
|
+
const childPrefix = isLast ? " " : "\u2502 ";
|
|
10955
|
+
if (entry.isDirectory()) {
|
|
10956
|
+
lines.push(`${prefix}${connector}${entry.name}/`);
|
|
10957
|
+
walk(path2.join(dir, entry.name), prefix + childPrefix, depth + 1);
|
|
10958
|
+
} else {
|
|
10959
|
+
lines.push(`${prefix}${connector}${entry.name}`);
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
}
|
|
10963
|
+
walk(cwd, "", 0);
|
|
10964
|
+
return lines.join("\n");
|
|
10965
|
+
}
|
|
10966
|
+
};
|
|
10967
|
+
}
|
|
10968
|
+
function grepTool(cwd) {
|
|
10969
|
+
return {
|
|
10970
|
+
type: "function",
|
|
10971
|
+
name: "grep",
|
|
10972
|
+
description: "Search for a text pattern across project files. Returns matching lines with their file paths. Case-insensitive. Skips dependency directories. Caps results at 80 lines.",
|
|
10973
|
+
strict: true,
|
|
10974
|
+
parameters: {
|
|
10975
|
+
type: "object",
|
|
10976
|
+
properties: {
|
|
10977
|
+
pattern: {
|
|
10978
|
+
type: "string",
|
|
10979
|
+
description: "Text or regex (POSIX) to search for."
|
|
10980
|
+
},
|
|
10981
|
+
path: {
|
|
10982
|
+
type: "string",
|
|
10983
|
+
description: "Directory to search in. Default: entire project."
|
|
10984
|
+
},
|
|
10985
|
+
file_pattern: {
|
|
10986
|
+
type: "string",
|
|
10987
|
+
description: "File glob filter (e.g. '*.ts', '*.tsx'). Default: all files."
|
|
10988
|
+
}
|
|
10989
|
+
},
|
|
10990
|
+
required: ["pattern", "path", "file_pattern"],
|
|
10991
|
+
additionalProperties: false
|
|
10992
|
+
},
|
|
10993
|
+
async invoke(args) {
|
|
10994
|
+
const searchDir = safePath(cwd, args.path || ".");
|
|
10995
|
+
if (!searchDir) return `Error: path '${args.path}' escapes the project root.`;
|
|
10996
|
+
if (!fs.existsSync(searchDir)) return `Directory not found: ${args.path}`;
|
|
10997
|
+
try {
|
|
10998
|
+
const excludes = Array.from(IGNORE).map((d) => `--exclude-dir=${d}`).join(" ");
|
|
10999
|
+
const include = args.file_pattern ? `--include='${args.file_pattern}'` : "";
|
|
11000
|
+
const cmd = `grep -rn -i ${excludes} ${include} --max-count=50 -- ${JSON.stringify(
|
|
11001
|
+
args.pattern
|
|
11002
|
+
)} ${JSON.stringify(searchDir)} 2>/dev/null || true`;
|
|
11003
|
+
const output = execSync(cmd, { encoding: "utf-8", maxBuffer: 2 * 1024 * 1024 });
|
|
11004
|
+
const lines = output.split("\n").filter(Boolean).map((line) => line.startsWith(cwd) ? line.slice(cwd.length + 1) : line);
|
|
11005
|
+
return lines.slice(0, MAX_GREP_LINES).join("\n") || "No matches found.";
|
|
11006
|
+
} catch {
|
|
11007
|
+
return "No matches found.";
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
};
|
|
11011
|
+
}
|
|
11012
|
+
function globTool(cwd) {
|
|
11013
|
+
return {
|
|
11014
|
+
type: "function",
|
|
11015
|
+
name: "glob",
|
|
11016
|
+
description: "List files matching a glob pattern (e.g. 'src/**/*.ts'). Useful when you want every file of a type without scanning the whole tree.",
|
|
11017
|
+
strict: true,
|
|
11018
|
+
parameters: {
|
|
11019
|
+
type: "object",
|
|
11020
|
+
properties: {
|
|
11021
|
+
pattern: {
|
|
11022
|
+
type: "string",
|
|
11023
|
+
description: "Glob pattern relative to the project root."
|
|
11024
|
+
}
|
|
11025
|
+
},
|
|
11026
|
+
required: ["pattern"],
|
|
11027
|
+
additionalProperties: false
|
|
11028
|
+
},
|
|
11029
|
+
async invoke(args) {
|
|
11030
|
+
try {
|
|
11031
|
+
const cmd = `cd ${JSON.stringify(cwd)} && find . -path './node_modules' -prune -o -path './.git' -prune -o -path './target' -prune -o -path './dist' -prune -o -path './build' -prune -o -type f -print | grep -E ${JSON.stringify(
|
|
11032
|
+
globToRegex(args.pattern)
|
|
11033
|
+
)} 2>/dev/null | head -200 || true`;
|
|
11034
|
+
const output = execSync(cmd, { encoding: "utf-8", maxBuffer: 1024 * 1024 });
|
|
11035
|
+
const lines = output.split("\n").filter(Boolean).map((l) => l.replace(/^\.\//, ""));
|
|
11036
|
+
return lines.join("\n") || "(no matches)";
|
|
11037
|
+
} catch {
|
|
11038
|
+
return "(glob failed)";
|
|
11039
|
+
}
|
|
11040
|
+
}
|
|
11041
|
+
};
|
|
11042
|
+
}
|
|
11043
|
+
function bashTool(cwd) {
|
|
11044
|
+
return {
|
|
11045
|
+
type: "function",
|
|
11046
|
+
name: "bash",
|
|
11047
|
+
description: "Run a non-destructive read-only shell command in the project root. Use for inspections like 'cat package.json | head', 'git log --oneline | head', 'wc -l src/**/*.ts'. Caps output at 8 KB. Do NOT use for writes, edits, or installs.",
|
|
11048
|
+
strict: true,
|
|
11049
|
+
parameters: {
|
|
11050
|
+
type: "object",
|
|
11051
|
+
properties: {
|
|
11052
|
+
command: {
|
|
11053
|
+
type: "string",
|
|
11054
|
+
description: "The shell command to execute. Read-only operations only."
|
|
11055
|
+
}
|
|
11056
|
+
},
|
|
11057
|
+
required: ["command"],
|
|
11058
|
+
additionalProperties: false
|
|
11059
|
+
},
|
|
11060
|
+
async invoke(args) {
|
|
11061
|
+
try {
|
|
11062
|
+
const output = execSync(args.command, {
|
|
11063
|
+
cwd,
|
|
11064
|
+
encoding: "utf-8",
|
|
11065
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
11066
|
+
timeout: 3e4,
|
|
11067
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
11068
|
+
});
|
|
11069
|
+
if (output.length > MAX_BASH_OUTPUT_BYTES) {
|
|
11070
|
+
return output.slice(0, MAX_BASH_OUTPUT_BYTES) + `
|
|
11071
|
+
... (truncated, ${output.length - MAX_BASH_OUTPUT_BYTES} bytes elided)`;
|
|
11072
|
+
}
|
|
11073
|
+
return output || "(empty output)";
|
|
11074
|
+
} catch (e) {
|
|
11075
|
+
const err = e;
|
|
11076
|
+
const stderr = err.stderr instanceof Buffer ? err.stderr.toString() : err.stderr ?? "";
|
|
11077
|
+
return `bash exited with status ${err.status ?? "?"}: ${stderr || err.message || "unknown error"}`;
|
|
11078
|
+
}
|
|
11079
|
+
}
|
|
11080
|
+
};
|
|
11081
|
+
}
|
|
11082
|
+
function safePath(cwd, filePath) {
|
|
11083
|
+
const resolved = path2.resolve(cwd, filePath);
|
|
11084
|
+
if (!resolved.startsWith(path2.resolve(cwd))) return null;
|
|
11085
|
+
return resolved;
|
|
11086
|
+
}
|
|
11087
|
+
function globToRegex(glob) {
|
|
11088
|
+
let regex = "^";
|
|
11089
|
+
let i = 0;
|
|
11090
|
+
while (i < glob.length) {
|
|
11091
|
+
const ch = glob[i];
|
|
11092
|
+
if (ch === "*") {
|
|
11093
|
+
if (glob[i + 1] === "*") {
|
|
11094
|
+
regex += ".*";
|
|
11095
|
+
i += 2;
|
|
11096
|
+
if (glob[i] === "/") i++;
|
|
11097
|
+
continue;
|
|
11098
|
+
}
|
|
11099
|
+
regex += "[^/]*";
|
|
11100
|
+
i++;
|
|
11101
|
+
continue;
|
|
11102
|
+
}
|
|
11103
|
+
if (ch === "?") {
|
|
11104
|
+
regex += "[^/]";
|
|
11105
|
+
i++;
|
|
11106
|
+
continue;
|
|
11107
|
+
}
|
|
11108
|
+
if (ch === ".") {
|
|
11109
|
+
regex += "\\.";
|
|
11110
|
+
i++;
|
|
11111
|
+
continue;
|
|
11112
|
+
}
|
|
11113
|
+
if ("()[]{}+|^$\\".includes(ch)) {
|
|
11114
|
+
regex += "\\" + ch;
|
|
11115
|
+
i++;
|
|
11116
|
+
continue;
|
|
11117
|
+
}
|
|
11118
|
+
regex += ch;
|
|
11119
|
+
i++;
|
|
11120
|
+
}
|
|
11121
|
+
regex += "$";
|
|
11122
|
+
return regex;
|
|
11123
|
+
}
|
|
11124
|
+
|
|
11125
|
+
// ../baro-orchestrator/src/planning/story-tools.ts
|
|
11126
|
+
var MAX_WRITE_BYTES = 5e5;
|
|
11127
|
+
function createStoryTools(cwd) {
|
|
11128
|
+
return [...createCodebaseTools(cwd), writeFileTool(cwd), editFileTool(cwd)];
|
|
11129
|
+
}
|
|
11130
|
+
function writeFileTool(cwd) {
|
|
11131
|
+
return {
|
|
11132
|
+
type: "function",
|
|
11133
|
+
name: "write_file",
|
|
11134
|
+
description: "Create a file or overwrite its full contents. Parent directories are created if needed. Use this for new files or when replacing the entire body of an existing file is simpler than a series of edits. Caps file size at 500 KB.",
|
|
11135
|
+
strict: true,
|
|
11136
|
+
parameters: {
|
|
11137
|
+
type: "object",
|
|
11138
|
+
properties: {
|
|
11139
|
+
path: {
|
|
11140
|
+
type: "string",
|
|
11141
|
+
description: "Path relative to the project root."
|
|
11142
|
+
},
|
|
11143
|
+
content: {
|
|
11144
|
+
type: "string",
|
|
11145
|
+
description: "Full file contents to write. UTF-8."
|
|
11146
|
+
}
|
|
11147
|
+
},
|
|
11148
|
+
required: ["path", "content"],
|
|
11149
|
+
additionalProperties: false
|
|
11150
|
+
},
|
|
11151
|
+
async invoke(args) {
|
|
11152
|
+
const target = safePath2(cwd, args.path);
|
|
11153
|
+
if (!target) return `Error: path '${args.path}' escapes the project root.`;
|
|
11154
|
+
if (args.content.length > MAX_WRITE_BYTES) {
|
|
11155
|
+
return `Error: write_file refused \u2014 content is ${args.content.length} bytes, max is ${MAX_WRITE_BYTES}. Split the file or use edit_file for partial updates.`;
|
|
11156
|
+
}
|
|
11157
|
+
try {
|
|
11158
|
+
const dir = path3.dirname(target);
|
|
11159
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
11160
|
+
fs2.writeFileSync(target, args.content, "utf-8");
|
|
11161
|
+
return `Wrote ${args.path} (${args.content.length} bytes).`;
|
|
11162
|
+
} catch (e) {
|
|
11163
|
+
return `Error writing ${args.path}: ${e?.message ?? String(e)}`;
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
};
|
|
11167
|
+
}
|
|
11168
|
+
function editFileTool(cwd) {
|
|
11169
|
+
return {
|
|
11170
|
+
type: "function",
|
|
11171
|
+
name: "edit_file",
|
|
11172
|
+
description: "Find-and-replace a single occurrence of a string in an existing file. `old` must appear in the file exactly once (case-sensitive, whitespace and newlines included); pass enough surrounding context to make it unique. Returns an error if `old` is not found or appears multiple times \u2014 at that point read_file again and pick a larger snippet.",
|
|
11173
|
+
strict: true,
|
|
11174
|
+
parameters: {
|
|
11175
|
+
type: "object",
|
|
11176
|
+
properties: {
|
|
11177
|
+
path: {
|
|
11178
|
+
type: "string",
|
|
11179
|
+
description: "Path relative to the project root."
|
|
11180
|
+
},
|
|
11181
|
+
old: {
|
|
11182
|
+
type: "string",
|
|
11183
|
+
description: "Exact text to find. Must be unique in the file. Include surrounding lines if needed to make it unique."
|
|
11184
|
+
},
|
|
11185
|
+
new: {
|
|
11186
|
+
type: "string",
|
|
11187
|
+
description: "Replacement text."
|
|
11188
|
+
}
|
|
11189
|
+
},
|
|
11190
|
+
required: ["path", "old", "new"],
|
|
11191
|
+
additionalProperties: false
|
|
11192
|
+
},
|
|
11193
|
+
async invoke(args) {
|
|
11194
|
+
const target = safePath2(cwd, args.path);
|
|
11195
|
+
if (!target) return `Error: path '${args.path}' escapes the project root.`;
|
|
11196
|
+
if (!fs2.existsSync(target)) {
|
|
11197
|
+
return `Error: ${args.path} does not exist. Use write_file to create it.`;
|
|
11198
|
+
}
|
|
11199
|
+
if (fs2.statSync(target).isDirectory()) {
|
|
11200
|
+
return `Error: ${args.path} is a directory.`;
|
|
11201
|
+
}
|
|
11202
|
+
let original;
|
|
11203
|
+
try {
|
|
11204
|
+
original = fs2.readFileSync(target, "utf-8");
|
|
11205
|
+
} catch (e) {
|
|
11206
|
+
return `Error reading ${args.path}: ${e?.message ?? String(e)}`;
|
|
11207
|
+
}
|
|
11208
|
+
if (!args.old) return "Error: `old` is empty \u2014 refusing to edit.";
|
|
11209
|
+
const firstIdx = original.indexOf(args.old);
|
|
11210
|
+
if (firstIdx === -1) {
|
|
11211
|
+
return `Error: \`old\` not found in ${args.path}. Re-read the file and pass an exact-matching snippet (including whitespace).`;
|
|
11212
|
+
}
|
|
11213
|
+
const secondIdx = original.indexOf(args.old, firstIdx + 1);
|
|
11214
|
+
if (secondIdx !== -1) {
|
|
11215
|
+
return `Error: \`old\` appears multiple times in ${args.path} (first at offset ${firstIdx}, again at ${secondIdx}). Include more surrounding context so the match is unique.`;
|
|
11216
|
+
}
|
|
11217
|
+
const updated = original.slice(0, firstIdx) + args.new + original.slice(firstIdx + args.old.length);
|
|
11218
|
+
try {
|
|
11219
|
+
fs2.writeFileSync(target, updated, "utf-8");
|
|
11220
|
+
const delta = updated.length - original.length;
|
|
11221
|
+
const sign = delta >= 0 ? "+" : "";
|
|
11222
|
+
return `Edited ${args.path} (${sign}${delta} bytes).`;
|
|
11223
|
+
} catch (e) {
|
|
11224
|
+
return `Error writing ${args.path}: ${e?.message ?? String(e)}`;
|
|
11225
|
+
}
|
|
11226
|
+
}
|
|
11227
|
+
};
|
|
11228
|
+
}
|
|
11229
|
+
function safePath2(cwd, filePath) {
|
|
11230
|
+
const resolved = path3.resolve(cwd, filePath);
|
|
11231
|
+
if (!resolved.startsWith(path3.resolve(cwd))) return null;
|
|
11232
|
+
return resolved;
|
|
11233
|
+
}
|
|
11234
|
+
|
|
11235
|
+
// ../baro-orchestrator/src/participants/openai-story-agent.ts
|
|
11236
|
+
var STORY_SYSTEM_PROMPT = `You are an autonomous coding agent. The user will hand you exactly one
|
|
11237
|
+
focused story: a goal plus acceptance criteria and (optionally) test
|
|
11238
|
+
commands. Your job is to read the relevant code, make the changes that
|
|
11239
|
+
satisfy every acceptance criterion, run the tests, and commit.
|
|
11240
|
+
|
|
11241
|
+
Tools available (call them as function calls; arguments are JSON):
|
|
11242
|
+
read_file, list_files, file_tree, grep, glob \u2014 explore the repo
|
|
11243
|
+
write_file \u2014 create or overwrite
|
|
11244
|
+
edit_file \u2014 find-and-replace
|
|
11245
|
+
bash \u2014 run any shell cmd
|
|
11246
|
+
(build, test, git\u2026)
|
|
11247
|
+
|
|
11248
|
+
Rules:
|
|
11249
|
+
- Stay tightly inside the story scope. Do NOT refactor unrelated code.
|
|
11250
|
+
If you notice an unrelated issue, note it in your final commit body
|
|
11251
|
+
under "Noted (out of scope)" \u2014 don't fix it inline.
|
|
11252
|
+
- Before you write code, READ the files you'll touch. Confirm exact
|
|
11253
|
+
function names, exact paths.
|
|
11254
|
+
- When you edit, use edit_file with enough surrounding context to make
|
|
11255
|
+
the match unique. write_file is for new files or full rewrites only.
|
|
11256
|
+
- After your edits, run the test commands the story specified. Use the
|
|
11257
|
+
bash tool. Fix any failures and re-run.
|
|
11258
|
+
- When you're done, run \`git add -A && git commit -m "..."\` via bash
|
|
11259
|
+
with a concise message. Then respond with a brief summary message
|
|
11260
|
+
(no more tool calls) \u2014 that signals the turn is over.
|
|
11261
|
+
|
|
11262
|
+
You may be sent corrective feedback after your turn. If you receive a
|
|
11263
|
+
follow-up user message, treat it as additional acceptance criteria
|
|
11264
|
+
and revise.`;
|
|
11265
|
+
var OpenAIStoryAgent = class extends BaroParticipant {
|
|
11266
|
+
spec;
|
|
11267
|
+
opts;
|
|
11268
|
+
model;
|
|
11269
|
+
runner = new OpenAIInferenceRunner();
|
|
11270
|
+
tools;
|
|
11271
|
+
envRef = null;
|
|
11272
|
+
currentPhase = "idle";
|
|
11273
|
+
startedAt = null;
|
|
11274
|
+
resolveDone;
|
|
11275
|
+
done;
|
|
11276
|
+
/** Resolved by `onExternalBusEvent` when a targeted message arrives. */
|
|
11277
|
+
notifyMessage = null;
|
|
11278
|
+
/** Most recent pending message text (set alongside notifyMessage). */
|
|
11279
|
+
pendingMessage = null;
|
|
11280
|
+
constructor(spec, opts = {}) {
|
|
11281
|
+
super();
|
|
11282
|
+
this.spec = {
|
|
11283
|
+
retries: 2,
|
|
11284
|
+
timeoutSecs: 600,
|
|
11285
|
+
retryDelayMs: 1500,
|
|
11286
|
+
quietTimeoutMs: 2e3,
|
|
11287
|
+
maxTurns: 4,
|
|
11288
|
+
hardTimeoutSecs: 0,
|
|
11289
|
+
...spec
|
|
11290
|
+
};
|
|
11291
|
+
this.opts = {
|
|
11292
|
+
model: opts.model ?? "gpt-5.5",
|
|
11293
|
+
maxRoundsPerTurn: opts.maxRoundsPerTurn ?? 30,
|
|
11294
|
+
perRoundTimeoutSecs: opts.perRoundTimeoutSecs ?? 180
|
|
11295
|
+
};
|
|
11296
|
+
this.model = pickModel2(this.opts.model);
|
|
11297
|
+
this.tools = createStoryTools(spec.cwd);
|
|
11298
|
+
setModelTools(this.model, this.tools);
|
|
11299
|
+
this.done = new Promise((res) => {
|
|
11300
|
+
this.resolveDone = res;
|
|
11301
|
+
});
|
|
11302
|
+
}
|
|
11303
|
+
get id() {
|
|
11304
|
+
return this.spec.id;
|
|
11305
|
+
}
|
|
11306
|
+
get agentId() {
|
|
11307
|
+
return this.spec.id;
|
|
11308
|
+
}
|
|
11309
|
+
getPhase() {
|
|
11310
|
+
return this.currentPhase;
|
|
11311
|
+
}
|
|
11312
|
+
run(env) {
|
|
11313
|
+
if (this.startedAt != null) return this.done;
|
|
11314
|
+
this.envRef = env;
|
|
11315
|
+
this.startedAt = Date.now();
|
|
11316
|
+
this.transition("starting", "story queued");
|
|
11317
|
+
void this.executeAllAttempts();
|
|
11318
|
+
return this.done;
|
|
11319
|
+
}
|
|
11320
|
+
abort() {
|
|
11321
|
+
this.transition("aborted", "external abort");
|
|
11322
|
+
}
|
|
11323
|
+
async onExternalBusEvent(_source, event) {
|
|
11324
|
+
if (event instanceof AgentTargetedMessageItem && event.recipientId === this.spec.id) {
|
|
11325
|
+
this.pendingMessage = event.text;
|
|
11326
|
+
this.notifyMessage?.();
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
// ─── Attempt orchestration ──────────────────────────────────────
|
|
11330
|
+
async executeAllAttempts() {
|
|
11331
|
+
const maxAttempts = this.spec.retries + 1;
|
|
11332
|
+
let lastError = null;
|
|
11333
|
+
let attempts = 0;
|
|
11334
|
+
const hardTimer = this.spec.hardTimeoutSecs > 0 ? setTimeout(() => {
|
|
11335
|
+
lastError = `hard timeout (${this.spec.hardTimeoutSecs}s) hit`;
|
|
11336
|
+
this.transition("aborted", lastError);
|
|
11337
|
+
}, this.spec.hardTimeoutSecs * 1e3) : null;
|
|
11338
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
11339
|
+
attempts = i + 1;
|
|
11340
|
+
if (this.currentPhase === "aborted") break;
|
|
11341
|
+
if (i > 0) {
|
|
11342
|
+
this.transition("starting", `retry ${attempts}/${maxAttempts}`);
|
|
11343
|
+
await sleep2(this.spec.retryDelayMs);
|
|
11344
|
+
}
|
|
11345
|
+
try {
|
|
11346
|
+
await this.runOneAttempt();
|
|
11347
|
+
if (this.currentPhase === "done") {
|
|
11348
|
+
lastError = null;
|
|
11349
|
+
break;
|
|
11350
|
+
}
|
|
11351
|
+
lastError = "attempt did not reach a terminal state";
|
|
11352
|
+
} catch (e) {
|
|
11353
|
+
lastError = e?.message ?? String(e);
|
|
11354
|
+
this.transition("failed", lastError);
|
|
11355
|
+
}
|
|
11356
|
+
}
|
|
11357
|
+
if (hardTimer) clearTimeout(hardTimer);
|
|
11358
|
+
const durationSecs = this.startedAt ? Math.round((Date.now() - this.startedAt) / 1e3) : 0;
|
|
11359
|
+
const success = this.currentPhase === "done";
|
|
11360
|
+
this.envRef?.deliverBusEvent(
|
|
11361
|
+
this,
|
|
11362
|
+
new StoryResultItem(this.spec.id, success, attempts, durationSecs, lastError)
|
|
11363
|
+
);
|
|
11364
|
+
this.resolveDone({
|
|
11365
|
+
storyId: this.spec.id,
|
|
11366
|
+
success,
|
|
11367
|
+
attempts,
|
|
11368
|
+
durationSecs,
|
|
11369
|
+
finalSummary: null,
|
|
11370
|
+
error: lastError
|
|
11371
|
+
});
|
|
11372
|
+
}
|
|
11373
|
+
async runOneAttempt() {
|
|
11374
|
+
const userMessageText = this.spec.prompt;
|
|
11375
|
+
this.envRef?.deliverBusEvent(
|
|
11376
|
+
this,
|
|
11377
|
+
new AgentUserMessageItem(this.spec.id, userMessageText)
|
|
11378
|
+
);
|
|
11379
|
+
let context = ModelContext.create(this.spec.id).addContextItem(SystemMessageItem.create(STORY_SYSTEM_PROMPT)).addContextItem(UserMessageItem.create(userMessageText));
|
|
11380
|
+
this.transition("running", "first turn");
|
|
11381
|
+
for (let turn = 1; turn <= this.spec.maxTurns; turn++) {
|
|
11382
|
+
const turnResult = await this.runOneTurn(context);
|
|
11383
|
+
context = turnResult.context;
|
|
11384
|
+
this.envRef?.deliverBusEvent(
|
|
11385
|
+
this,
|
|
11386
|
+
new ClaudeResultItem(
|
|
11387
|
+
this.spec.id,
|
|
11388
|
+
turnResult.success ? "success" : "error",
|
|
11389
|
+
null,
|
|
11390
|
+
// session id — not applicable for OpenAI
|
|
11391
|
+
!turnResult.success,
|
|
11392
|
+
turnResult.assistantText,
|
|
11393
|
+
null,
|
|
11394
|
+
// usage info — not surfaced this phase
|
|
11395
|
+
null,
|
|
11396
|
+
null,
|
|
11397
|
+
null,
|
|
11398
|
+
{}
|
|
11399
|
+
)
|
|
11400
|
+
);
|
|
11401
|
+
if (!turnResult.success) {
|
|
11402
|
+
this.transition("failed", turnResult.error ?? "turn failed");
|
|
11403
|
+
return;
|
|
11404
|
+
}
|
|
11405
|
+
const gotMessage = await this.waitForMessageOrQuiet();
|
|
11406
|
+
if (!gotMessage) {
|
|
11407
|
+
this.transition("done", `${turn} turn(s)`);
|
|
11408
|
+
return;
|
|
11409
|
+
}
|
|
11410
|
+
context = context.addContextItem(
|
|
11411
|
+
UserMessageItem.create(this.pendingMessage ?? "")
|
|
11412
|
+
);
|
|
11413
|
+
this.envRef?.deliverBusEvent(
|
|
11414
|
+
this,
|
|
11415
|
+
new AgentUserMessageItem(this.spec.id, this.pendingMessage ?? "")
|
|
11416
|
+
);
|
|
11417
|
+
this.pendingMessage = null;
|
|
11418
|
+
}
|
|
11419
|
+
this.transition("failed", `maxTurns (${this.spec.maxTurns}) exhausted`);
|
|
11420
|
+
}
|
|
11421
|
+
/**
|
|
11422
|
+
* Drive a single TURN — repeated inference rounds with tool
|
|
11423
|
+
* execution in between — until the model returns a final
|
|
11424
|
+
* assistant message without tool calls. That terminal message
|
|
11425
|
+
* IS the end of the turn.
|
|
11426
|
+
*/
|
|
11427
|
+
async runOneTurn(initialContext) {
|
|
11428
|
+
let context = initialContext;
|
|
11429
|
+
let assistantText = null;
|
|
11430
|
+
const perRoundMs = this.opts.perRoundTimeoutSecs * 1e3;
|
|
11431
|
+
for (let round = 1; round <= this.opts.maxRoundsPerTurn; round++) {
|
|
11432
|
+
const ac = new AbortController();
|
|
11433
|
+
const timer = setTimeout(() => ac.abort(), perRoundMs);
|
|
11434
|
+
const calls = [];
|
|
11435
|
+
let sawMessage = false;
|
|
11436
|
+
let lastMessageText = null;
|
|
11437
|
+
try {
|
|
11438
|
+
for await (const item of this.runner.run(context, this.model, ac.signal)) {
|
|
11439
|
+
if (item.type === "function_call") {
|
|
11440
|
+
await this.envRef?.deliverFunctionCall(this, item);
|
|
11441
|
+
context = context.addContextItem(item);
|
|
11442
|
+
calls.push(item);
|
|
11443
|
+
} else if (item.type === "message" && item.role === "assistant") {
|
|
11444
|
+
await this.envRef?.deliverModelMessage(this, item);
|
|
11445
|
+
context = context.addContextItem(item);
|
|
11446
|
+
const json = item.toJSON();
|
|
11447
|
+
const text = json.content?.[0]?.text ?? "";
|
|
11448
|
+
lastMessageText = text;
|
|
11449
|
+
sawMessage = true;
|
|
11450
|
+
} else if (item.type === "reasoning") {
|
|
11451
|
+
context = context.addContextItem(item);
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
} catch (e) {
|
|
11455
|
+
clearTimeout(timer);
|
|
11456
|
+
return {
|
|
11457
|
+
context,
|
|
11458
|
+
success: false,
|
|
11459
|
+
assistantText,
|
|
11460
|
+
error: `inference round ${round} failed: ${e?.message ?? String(e)}`
|
|
11461
|
+
};
|
|
11462
|
+
} finally {
|
|
11463
|
+
clearTimeout(timer);
|
|
11464
|
+
}
|
|
11465
|
+
for (const call of calls) {
|
|
11466
|
+
const tool = this.tools.find((t) => t.name === call.name);
|
|
11467
|
+
const output = tool ? await runToolSafely(tool, call.args) : `Error: tool '${call.name}' not registered`;
|
|
11468
|
+
const outItem = FunctionCallOutputItem.create(call.callId, output);
|
|
11469
|
+
await this.envRef?.deliverFunctionCallOutput(this, outItem);
|
|
11470
|
+
context = context.addContextItem(outItem);
|
|
11471
|
+
}
|
|
11472
|
+
if (sawMessage && calls.length === 0) {
|
|
11473
|
+
return {
|
|
11474
|
+
context,
|
|
11475
|
+
success: true,
|
|
11476
|
+
assistantText: lastMessageText
|
|
11477
|
+
};
|
|
11478
|
+
}
|
|
11479
|
+
if (!sawMessage && calls.length === 0) {
|
|
11480
|
+
return {
|
|
11481
|
+
context,
|
|
11482
|
+
success: false,
|
|
11483
|
+
assistantText,
|
|
11484
|
+
error: `round ${round} returned no items`
|
|
11485
|
+
};
|
|
11486
|
+
}
|
|
11487
|
+
assistantText = lastMessageText ?? assistantText;
|
|
11488
|
+
}
|
|
11489
|
+
return {
|
|
11490
|
+
context,
|
|
11491
|
+
success: false,
|
|
11492
|
+
assistantText,
|
|
11493
|
+
error: `exceeded maxRoundsPerTurn=${this.opts.maxRoundsPerTurn}`
|
|
11494
|
+
};
|
|
11495
|
+
}
|
|
11496
|
+
/**
|
|
11497
|
+
* Resolves `true` if an `AgentTargetedMessageItem` arrives within
|
|
11498
|
+
* `quietTimeoutMs`, `false` if the timer fires first. Mirrors
|
|
11499
|
+
* Claude side's quiet timer that closes stdin after silence.
|
|
11500
|
+
*/
|
|
11501
|
+
async waitForMessageOrQuiet() {
|
|
11502
|
+
return new Promise((resolve4) => {
|
|
11503
|
+
const timer = setTimeout(() => {
|
|
11504
|
+
this.notifyMessage = null;
|
|
11505
|
+
resolve4(false);
|
|
11506
|
+
}, this.spec.quietTimeoutMs);
|
|
11507
|
+
this.notifyMessage = () => {
|
|
11508
|
+
clearTimeout(timer);
|
|
11509
|
+
this.notifyMessage = null;
|
|
11510
|
+
resolve4(true);
|
|
11511
|
+
};
|
|
11512
|
+
});
|
|
11513
|
+
}
|
|
11514
|
+
transition(next, detail) {
|
|
11515
|
+
if (next === this.currentPhase) return;
|
|
11516
|
+
this.currentPhase = next;
|
|
11517
|
+
this.envRef?.deliverBusEvent(
|
|
11518
|
+
this,
|
|
11519
|
+
new AgentStateItem(this.spec.id, next, detail)
|
|
11520
|
+
);
|
|
11521
|
+
}
|
|
11522
|
+
};
|
|
11523
|
+
function pickModel2(name) {
|
|
11524
|
+
switch (name) {
|
|
11525
|
+
case "gpt-5.5":
|
|
11526
|
+
return new Gpt55();
|
|
11527
|
+
case "gpt-5.4":
|
|
11528
|
+
return new Gpt54();
|
|
11529
|
+
case "gpt-5.4-mini":
|
|
11530
|
+
return new Gpt54Mini();
|
|
11531
|
+
case "gpt-5.4-nano":
|
|
11532
|
+
return new Gpt54Nano();
|
|
11533
|
+
default:
|
|
11534
|
+
throw new Error(
|
|
11535
|
+
`OpenAIStoryAgent: unknown model "${name}" \u2014 Mozaik 3.9 ships gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano`
|
|
11536
|
+
);
|
|
11537
|
+
}
|
|
11538
|
+
}
|
|
11539
|
+
function setModelTools(model, tools) {
|
|
11540
|
+
const m = model;
|
|
11541
|
+
if (typeof m.setTools !== "function") {
|
|
11542
|
+
throw new Error(
|
|
11543
|
+
`OpenAIStoryAgent: model ${model.specification.name} does not implement ToolCallingCapability`
|
|
11544
|
+
);
|
|
11545
|
+
}
|
|
11546
|
+
m.setTools(tools);
|
|
11547
|
+
}
|
|
11548
|
+
async function runToolSafely(tool, argsJson) {
|
|
11549
|
+
let parsed;
|
|
11550
|
+
try {
|
|
11551
|
+
parsed = JSON.parse(argsJson);
|
|
11552
|
+
} catch (e) {
|
|
11553
|
+
return `Error: tool args were not valid JSON: ${e?.message ?? String(e)}`;
|
|
11554
|
+
}
|
|
11555
|
+
try {
|
|
11556
|
+
const result = await tool.invoke(parsed);
|
|
11557
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
11558
|
+
} catch (e) {
|
|
11559
|
+
return `Error running ${tool.name}: ${e?.message ?? String(e)}`;
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11562
|
+
function sleep2(ms) {
|
|
11563
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
11564
|
+
}
|
|
11565
|
+
|
|
10805
11566
|
// ../baro-orchestrator/src/participants/story-factory.ts
|
|
10806
11567
|
var StoryFactory = class extends BaroParticipant {
|
|
10807
11568
|
constructor(opts) {
|
|
@@ -10829,7 +11590,18 @@ var StoryFactory = class extends BaroParticipant {
|
|
|
10829
11590
|
async spawn(req) {
|
|
10830
11591
|
if (!this.envRef) return;
|
|
10831
11592
|
if (this.active.has(req.storyId)) return;
|
|
10832
|
-
const
|
|
11593
|
+
const llm = this.opts.llm ?? "claude";
|
|
11594
|
+
const agent = llm === "openai" ? new OpenAIStoryAgent(
|
|
11595
|
+
{
|
|
11596
|
+
id: req.storyId,
|
|
11597
|
+
prompt: req.prompt,
|
|
11598
|
+
cwd: this.opts.cwd,
|
|
11599
|
+
model: req.model,
|
|
11600
|
+
retries: req.retries,
|
|
11601
|
+
timeoutSecs: req.timeoutSecs
|
|
11602
|
+
},
|
|
11603
|
+
{ model: this.opts.openaiModel ?? "gpt-5.5" }
|
|
11604
|
+
) : new StoryAgent({
|
|
10833
11605
|
id: req.storyId,
|
|
10834
11606
|
prompt: req.prompt,
|
|
10835
11607
|
cwd: this.opts.cwd,
|
|
@@ -11059,7 +11831,7 @@ function surgeonDeterministicReplan(failure) {
|
|
|
11059
11831
|
}
|
|
11060
11832
|
|
|
11061
11833
|
// ../baro-orchestrator/src/participants/surgeon-openai.ts
|
|
11062
|
-
function
|
|
11834
|
+
function pickModel3(name) {
|
|
11063
11835
|
switch (name) {
|
|
11064
11836
|
case "gpt-5.5":
|
|
11065
11837
|
return new Gpt55();
|
|
@@ -11088,7 +11860,7 @@ var SurgeonOpenAI = class extends BaroParticipant {
|
|
|
11088
11860
|
model: opts.model ?? "gpt-5.4",
|
|
11089
11861
|
snapshot: opts.snapshot
|
|
11090
11862
|
};
|
|
11091
|
-
this.model =
|
|
11863
|
+
this.model = pickModel3(this.opts.model);
|
|
11092
11864
|
}
|
|
11093
11865
|
async idle() {
|
|
11094
11866
|
await Promise.allSettled([...this.pending]);
|
|
@@ -11178,7 +11950,7 @@ async function orchestrate(config) {
|
|
|
11178
11950
|
);
|
|
11179
11951
|
}
|
|
11180
11952
|
if (config.auditLogPath) {
|
|
11181
|
-
|
|
11953
|
+
mkdirSync3(dirname3(config.auditLogPath), { recursive: true });
|
|
11182
11954
|
new Auditor({ path: config.auditLogPath }).join(env);
|
|
11183
11955
|
}
|
|
11184
11956
|
if (config.extraParticipants) {
|
|
@@ -11307,7 +12079,11 @@ async function orchestrate(config) {
|
|
|
11307
12079
|
});
|
|
11308
12080
|
conductor.setEnvironment(env);
|
|
11309
12081
|
conductor.join(env);
|
|
11310
|
-
const storyFactory = new StoryFactory({
|
|
12082
|
+
const storyFactory = new StoryFactory({
|
|
12083
|
+
cwd: config.cwd,
|
|
12084
|
+
llm,
|
|
12085
|
+
openaiModel: "gpt-5.5"
|
|
12086
|
+
});
|
|
11311
12087
|
storyFactory.setEnvironment(env);
|
|
11312
12088
|
storyFactory.join(env);
|
|
11313
12089
|
if (emitTui) {
|
|
@@ -11662,9 +12438,9 @@ async function main() {
|
|
|
11662
12438
|
printHelp();
|
|
11663
12439
|
return;
|
|
11664
12440
|
}
|
|
11665
|
-
const cwd =
|
|
11666
|
-
const prdPath =
|
|
11667
|
-
if (!
|
|
12441
|
+
const cwd = resolve3(args.cwd);
|
|
12442
|
+
const prdPath = resolve3(cwd, args.prd);
|
|
12443
|
+
if (!existsSync4(prdPath)) {
|
|
11668
12444
|
process.stderr.write(`[cli] PRD not found: ${prdPath}
|
|
11669
12445
|
`);
|
|
11670
12446
|
process.exit(2);
|