@tablation/crew 0.34.0 → 0.35.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/crew.example.yaml +3 -1
- package/dist/cli.js +35 -11
- package/package.json +1 -1
package/crew.example.yaml
CHANGED
|
@@ -78,7 +78,9 @@ routes:
|
|
|
78
78
|
# and the queue digest marks it NO CHECKOUT rather than leaving it blank.
|
|
79
79
|
repos:
|
|
80
80
|
My Product: "/home/me/src/my-product"
|
|
81
|
-
My Product CLI:
|
|
81
|
+
My Product CLI:
|
|
82
|
+
dir: "/home/me/src/my-product-cli"
|
|
83
|
+
# toolchain: codex # run tickets in this repo on another ship.toolchains entry (CREW-1529)
|
|
82
84
|
|
|
83
85
|
# Leave `repos:` out entirely instead to serve every repo the board's
|
|
84
86
|
# Repos table lists, each one defaulting to
|
package/dist/cli.js
CHANGED
|
@@ -991,8 +991,8 @@ function shipToolchains(ship) {
|
|
|
991
991
|
};
|
|
992
992
|
return [legacy, ...explicit];
|
|
993
993
|
}
|
|
994
|
-
function resolveToolchain(ship, route) {
|
|
995
|
-
const name = route?.toolchain ?? ship.toolchain ?? LEGACY_TOOLCHAIN;
|
|
994
|
+
function resolveToolchain(ship, route, repo) {
|
|
995
|
+
const name = (repo && route?.repoOverrides?.[repo]?.toolchain) ?? route?.toolchain ?? ship.toolchain ?? LEGACY_TOOLCHAIN;
|
|
996
996
|
const found = shipToolchains(ship).find((t) => t.name === name);
|
|
997
997
|
if (!found) throw new Error(`toolchain "${name}" is not defined (have: ${shipToolchains(ship).map((t) => t.name).join(", ")})`);
|
|
998
998
|
return found;
|
|
@@ -1007,11 +1007,12 @@ function isOriLauncher(spec) {
|
|
|
1007
1007
|
function toolchainProvider(spec) {
|
|
1008
1008
|
return isOriLauncher(spec) ? "openrouter" : adapterFor(spec.harness).provider(spec);
|
|
1009
1009
|
}
|
|
1010
|
-
function resolveSeat(ship, route, role, persona = {}) {
|
|
1010
|
+
function resolveSeat(ship, route, role, persona = {}, repo) {
|
|
1011
1011
|
const warnings = [];
|
|
1012
|
-
const base = resolveToolchain(ship, route);
|
|
1012
|
+
const base = resolveToolchain(ship, route, repo);
|
|
1013
1013
|
const baseVendor = toolchainVendor(base);
|
|
1014
|
-
const
|
|
1014
|
+
const repoToolchain = repo ? route?.repoOverrides?.[repo]?.toolchain : void 0;
|
|
1015
|
+
const baseWhy = repoToolchain ? `repo ${repo} toolchain ${base.name}` : route?.toolchain ? `route toolchain ${base.name}` : ship.toolchain ? `ship default ${base.name}` : `default ${base.name}`;
|
|
1015
1016
|
let chosen = base;
|
|
1016
1017
|
let toolchainWhy = baseWhy;
|
|
1017
1018
|
if (persona.vendor && persona.vendor !== baseVendor) {
|
|
@@ -1710,7 +1711,7 @@ function parseWorktreeName(v, where, file) {
|
|
|
1710
1711
|
if (problem) throw new ConfigError(`${file}: ${where}.worktreeName ("${String(v)}") ${problem}`);
|
|
1711
1712
|
return String(v);
|
|
1712
1713
|
}
|
|
1713
|
-
var REPO_ENTRY_KEYS = /* @__PURE__ */ new Set(["dir", "hooks", "labels", "release", "branch", "hostPassengers", "worktreeName"]);
|
|
1714
|
+
var REPO_ENTRY_KEYS = /* @__PURE__ */ new Set(["dir", "hooks", "labels", "release", "branch", "hostPassengers", "worktreeName", "toolchain"]);
|
|
1714
1715
|
var BRANCH_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote"]);
|
|
1715
1716
|
var RELEASE_OVERRIDE_KEYS = /* @__PURE__ */ new Set([
|
|
1716
1717
|
"mode",
|
|
@@ -2140,7 +2141,11 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2140
2141
|
const e = entry;
|
|
2141
2142
|
rejectUnknownKeys(e, REPO_ENTRY_KEYS, `${where}.repos.${name}`, file);
|
|
2142
2143
|
repoDirs[name] = expand(String(missing.req(e.dir, `${where}.repos.${name}.dir`) || ""), base);
|
|
2143
|
-
|
|
2144
|
+
const repoToolchain = e.toolchain === void 0 ? void 0 : String(e.toolchain);
|
|
2145
|
+
if (repoToolchain !== void 0 && !toolchainNames.has(repoToolchain)) {
|
|
2146
|
+
missing.add(`${where}.repos.${name}.toolchain "${repoToolchain}" is not defined in ship.toolchains (have: ${[...toolchainNames].join(", ")})`);
|
|
2147
|
+
}
|
|
2148
|
+
if (e.hooks || e.labels || e.release || e.branch || e.hostPassengers !== void 0 || e.worktreeName !== void 0 || repoToolchain !== void 0) {
|
|
2144
2149
|
repoOverrides[name] = {
|
|
2145
2150
|
hooks: e.hooks,
|
|
2146
2151
|
labels: e.labels,
|
|
@@ -2151,7 +2156,8 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2151
2156
|
// compare this literally.
|
|
2152
2157
|
...e.branch ? { branch: parseBranchOverride(e.branch, `${where}.repos.${name}`, file) } : {},
|
|
2153
2158
|
...e.hostPassengers !== void 0 ? { hostPassengers: e.hostPassengers === true } : {},
|
|
2154
|
-
...e.worktreeName !== void 0 ? { worktreeName: parseWorktreeName(e.worktreeName, `${where}.repos.${name}`, file) } : {}
|
|
2159
|
+
...e.worktreeName !== void 0 ? { worktreeName: parseWorktreeName(e.worktreeName, `${where}.repos.${name}`, file) } : {},
|
|
2160
|
+
...repoToolchain !== void 0 ? { toolchain: repoToolchain } : {}
|
|
2155
2161
|
};
|
|
2156
2162
|
}
|
|
2157
2163
|
} else {
|
|
@@ -6871,7 +6877,7 @@ function planAgentRun(o) {
|
|
|
6871
6877
|
const promptSha = createHash2("sha256").update(`${effectiveCommon}${effectiveBrief}`).digest("hex");
|
|
6872
6878
|
const streamsDir = join9(o.stateDir, "streams");
|
|
6873
6879
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
6874
|
-
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}));
|
|
6880
|
+
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
6875
6881
|
const { toolchain, model } = seat;
|
|
6876
6882
|
const adapter = adapterFor(toolchain.harness);
|
|
6877
6883
|
const invocation = adapter.invocation(toolchain, {
|
|
@@ -6892,8 +6898,9 @@ function planAgentRun(o) {
|
|
|
6892
6898
|
role: o.role,
|
|
6893
6899
|
route: routeLabel,
|
|
6894
6900
|
// The route's directory, which is a STARTING POINT and not the
|
|
6895
|
-
// ticket's repo: the
|
|
6896
|
-
//
|
|
6901
|
+
// ticket's repo: the session picks its own working directory even when
|
|
6902
|
+
// the runner already selected a ticket (`o.repo` is known then, and
|
|
6903
|
+
// only feeds the per-repo toolchain choice). Where an area spans
|
|
6897
6904
|
// several repos this is whichever one `repos` happened to list first
|
|
6898
6905
|
// (config.ts), and the digest's `repo` column is what actually places
|
|
6899
6906
|
// the work. Kept deliberately rather than guessed at (ISSUE-349).
|
|
@@ -11379,6 +11386,7 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
11379
11386
|
const { route, ship, emit } = ctx;
|
|
11380
11387
|
const tracker = new Tracker(route, ship);
|
|
11381
11388
|
let ticketHint = decision.actionable.top?.issue_id;
|
|
11389
|
+
let ticketRepo = repoTargetFor(route, decision.actionable.top?.repo_id)?.name;
|
|
11382
11390
|
let workingId = decision.actionable.top?.id ?? null;
|
|
11383
11391
|
if (role === "dev" || role === "design" || role === "qa") {
|
|
11384
11392
|
const seat = route.resolved?.seats[role];
|
|
@@ -11433,6 +11441,7 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
11433
11441
|
ticketHint = result.ticket.issue_id;
|
|
11434
11442
|
workingId = result.ticket.id;
|
|
11435
11443
|
const target = repoTargetFor(route, result.ticket.repo_id);
|
|
11444
|
+
ticketRepo = target?.name;
|
|
11436
11445
|
try {
|
|
11437
11446
|
if (target && ensureRepoCheckout(target.dir, target.remote)) {
|
|
11438
11447
|
emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role });
|
|
@@ -11455,6 +11464,7 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
11455
11464
|
apiKey: resolveApiKey(route),
|
|
11456
11465
|
cycle: emit.cycle,
|
|
11457
11466
|
ticket: ticketHint,
|
|
11467
|
+
repo: ticketRepo,
|
|
11458
11468
|
divergedPrompt: await fetchDivergedPrompt(route, role, { userAgent: ship.userAgent }),
|
|
11459
11469
|
agentPersona: await fetchSeatPersona(route, role, { userAgent: ship.userAgent }),
|
|
11460
11470
|
resolvedAgentId: await resolveAgentId(route, role, { userAgent: ship.userAgent })
|
|
@@ -12542,6 +12552,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12542
12552
|
apiKey: resolveApiKey(w.route),
|
|
12543
12553
|
cycle: wemit.cycle,
|
|
12544
12554
|
ticket: w.decision.actionable.top?.issue_id,
|
|
12555
|
+
repo: repoTargetFor(w.route, w.decision.actionable.top?.repo_id)?.name,
|
|
12545
12556
|
divergedPrompt: await fetchDivergedPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
12546
12557
|
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
12547
12558
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
@@ -12559,6 +12570,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12559
12570
|
lock = fleetLock;
|
|
12560
12571
|
const fleetTracker = new Tracker(w.route, cfg.ship);
|
|
12561
12572
|
let fleetTicketHint = w.decision.actionable.top?.issue_id;
|
|
12573
|
+
let fleetTicketRepo = repoTargetFor(w.route, w.decision.actionable.top?.repo_id)?.name;
|
|
12562
12574
|
let fleetWorkingId = w.decision.actionable.top?.id ?? null;
|
|
12563
12575
|
if (w.role === "dev" || w.role === "design" || w.role === "qa") {
|
|
12564
12576
|
const seat = w.route.resolved?.seats[w.role];
|
|
@@ -12614,6 +12626,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12614
12626
|
fleetTicketHint = result.ticket.issue_id;
|
|
12615
12627
|
fleetWorkingId = result.ticket.id;
|
|
12616
12628
|
const target = repoTargetFor(w.route, result.ticket.repo_id);
|
|
12629
|
+
fleetTicketRepo = target?.name;
|
|
12617
12630
|
try {
|
|
12618
12631
|
if (target && ensureRepoCheckout(target.dir, target.remote)) {
|
|
12619
12632
|
wemit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: w.role });
|
|
@@ -12635,6 +12648,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12635
12648
|
apiKey: resolveApiKey(w.route),
|
|
12636
12649
|
cycle: wemit.cycle,
|
|
12637
12650
|
ticket: fleetTicketHint,
|
|
12651
|
+
repo: fleetTicketRepo,
|
|
12638
12652
|
divergedPrompt: await fetchDivergedPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
12639
12653
|
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
12640
12654
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
@@ -12752,6 +12766,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12752
12766
|
lock = got;
|
|
12753
12767
|
}
|
|
12754
12768
|
let ticketHint = decision.actionable.top?.issue_id;
|
|
12769
|
+
let ticketRepo = repoTargetFor(route, decision.actionable.top?.repo_id)?.name;
|
|
12755
12770
|
let workingId = decision.actionable.top?.id ?? null;
|
|
12756
12771
|
if (!dryRun && (current === "dev" || current === "design" || current === "qa")) {
|
|
12757
12772
|
const seat = route.resolved?.seats[current];
|
|
@@ -12813,6 +12828,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12813
12828
|
ticketHint = result.ticket.issue_id;
|
|
12814
12829
|
workingId = result.ticket.id;
|
|
12815
12830
|
const target = repoTargetFor(route, result.ticket.repo_id);
|
|
12831
|
+
ticketRepo = target?.name;
|
|
12816
12832
|
try {
|
|
12817
12833
|
if (target && ensureRepoCheckout(target.dir, target.remote)) {
|
|
12818
12834
|
emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: current });
|
|
@@ -12840,6 +12856,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12840
12856
|
apiKey: resolveApiKey(route),
|
|
12841
12857
|
cycle: emit.cycle,
|
|
12842
12858
|
ticket: ticketHint,
|
|
12859
|
+
repo: ticketRepo,
|
|
12843
12860
|
divergedPrompt: await fetchDivergedPrompt(route, current, { userAgent: cfg.ship.userAgent }),
|
|
12844
12861
|
agentPersona: await fetchSeatPersona(route, current, { userAgent: cfg.ship.userAgent }),
|
|
12845
12862
|
resolvedAgentId: await resolveAgentId(route, current, { userAgent: cfg.ship.userAgent })
|
|
@@ -13244,6 +13261,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13244
13261
|
const planned = loadConfig(CREW_HOME, cfg.configFile, { text: plan.text, allowNoRoutes: true });
|
|
13245
13262
|
const planRoute = planned.routes.find((r) => r.route === routeName);
|
|
13246
13263
|
if (!planRoute) throw new ReposError(`route ${routeName} did not load from the planned config; use --no-create`);
|
|
13264
|
+
await hydrateApiKeys([planRoute]);
|
|
13247
13265
|
try {
|
|
13248
13266
|
rowTracker = new Tracker(planRoute, cfg.ship);
|
|
13249
13267
|
} catch (e) {
|
|
@@ -14249,6 +14267,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14249
14267
|
repos: repos.map((r) => ({
|
|
14250
14268
|
name: r.name,
|
|
14251
14269
|
dir: r.dir,
|
|
14270
|
+
// Additive (CREW-1529): the toolchain a ticket in this repo runs on.
|
|
14271
|
+
toolchain: resolveToolchain(cfg.ship, route, r.name).name,
|
|
14252
14272
|
// Additive (CREW-1379): `unsafe` means a ship must not cut a
|
|
14253
14273
|
// worktree or release from this base. `version` stays 1.
|
|
14254
14274
|
baseBranch: baseHealth(r),
|
|
@@ -14489,6 +14509,10 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
|
|
|
14489
14509
|
`);
|
|
14490
14510
|
}
|
|
14491
14511
|
}
|
|
14512
|
+
for (const [repoName, ov] of Object.entries(route.repoOverrides)) {
|
|
14513
|
+
if (ov.toolchain) process.stdout.write(`repo toolchain: ${repoName} \u2192 ${ov.toolchain} (binary and MCP checked above, as for every ship toolchain)
|
|
14514
|
+
`);
|
|
14515
|
+
}
|
|
14492
14516
|
process.stdout.write(`git: ${gitPath ? `OK (${gitPath})` : "NOT FOUND on the scheduler PATH"}
|
|
14493
14517
|
`);
|
|
14494
14518
|
if (!allBinsFound || !gitPath) {
|
package/package.json
CHANGED