@tablation/crew 0.28.0 → 0.30.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.js +156 -40
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { resolve as resolve4, dirname as dirname11, join as join20, basename as
|
|
4
|
+
import { resolve as resolve4, dirname as dirname11, join as join20, basename as basename9 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { hostname as hostname2, homedir as homedir5 } from "os";
|
|
7
7
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
10
|
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
11
|
-
import { resolve, dirname as dirname2, isAbsolute, join as join4, basename as
|
|
11
|
+
import { resolve, dirname as dirname2, isAbsolute, join as join4, basename as basename3 } from "path";
|
|
12
12
|
import { homedir as homedir2, tmpdir } from "os";
|
|
13
13
|
import { parse as parse2 } from "yaml";
|
|
14
14
|
|
|
@@ -157,6 +157,9 @@ var Emitter = class _Emitter {
|
|
|
157
157
|
};
|
|
158
158
|
var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
|
|
159
159
|
|
|
160
|
+
// src/toolchain.ts
|
|
161
|
+
import { basename } from "path";
|
|
162
|
+
|
|
160
163
|
// src/stream.ts
|
|
161
164
|
function toolTarget(name, input) {
|
|
162
165
|
if (!input) return void 0;
|
|
@@ -197,11 +200,11 @@ function extractResult(line) {
|
|
|
197
200
|
return {
|
|
198
201
|
sessionId: obj.session_id,
|
|
199
202
|
numTurns: obj.num_turns,
|
|
200
|
-
|
|
203
|
+
costUsd: obj.total_cost_usd,
|
|
201
204
|
inputTokens: obj.usage?.input_tokens,
|
|
202
205
|
outputTokens: obj.usage?.output_tokens,
|
|
203
206
|
cacheReadTokens: obj.usage?.cache_read_input_tokens,
|
|
204
|
-
|
|
207
|
+
cacheWriteTokens: obj.usage?.cache_creation_input_tokens
|
|
205
208
|
};
|
|
206
209
|
}
|
|
207
210
|
|
|
@@ -312,12 +315,22 @@ function allowedTools(role) {
|
|
|
312
315
|
var claudeAdapter = {
|
|
313
316
|
harness: "claude",
|
|
314
317
|
displayName: "Claude Code",
|
|
318
|
+
vendor: "anthropic",
|
|
319
|
+
ownsModel: (model) => model.startsWith("claude-"),
|
|
320
|
+
// The tier's reasoning is the extended-thinking budget; it supersedes the
|
|
321
|
+
// toolchain's `maxThinkingTokens` (0 disables). A non-numeric value is ignored.
|
|
322
|
+
applyReasoning(spec, reasoning) {
|
|
323
|
+
const n = Number(reasoning);
|
|
324
|
+
return Number.isInteger(n) && n >= 0 ? { ...spec, maxThinkingTokens: n } : spec;
|
|
325
|
+
},
|
|
315
326
|
defaultBin: "claude",
|
|
316
327
|
defaultModel: "claude-sonnet-5",
|
|
317
328
|
defaultTiers: {
|
|
318
|
-
|
|
329
|
+
// `standard` carries no reasoning: it defers to the toolchain's own
|
|
330
|
+
// `maxThinkingTokens`, which is what a persona with no tier gets today.
|
|
331
|
+
light: { model: "claude-haiku-4-5-20251001", reasoning: "2048" },
|
|
319
332
|
standard: { model: "claude-sonnet-5-5" },
|
|
320
|
-
deep: { model: "claude-opus-5-5" }
|
|
333
|
+
deep: { model: "claude-opus-5-5", reasoning: "16384" }
|
|
321
334
|
},
|
|
322
335
|
limitations: [
|
|
323
336
|
"Bash is not sandboxed: the deny list refuses known destructive patterns, but a one-liner reaching the same end another way is not caught",
|
|
@@ -363,6 +376,7 @@ var claudeAdapter = {
|
|
|
363
376
|
...spec.env
|
|
364
377
|
};
|
|
365
378
|
},
|
|
379
|
+
provider: () => "anthropic",
|
|
366
380
|
unsetEnv: BILLING_VARS_TO_UNSET,
|
|
367
381
|
mapStreamLine,
|
|
368
382
|
extractResult,
|
|
@@ -411,6 +425,52 @@ function resolveToolchain(ship, route) {
|
|
|
411
425
|
if (!found) throw new Error(`toolchain "${name}" is not defined (have: ${shipToolchains(ship).map((t) => t.name).join(", ")})`);
|
|
412
426
|
return found;
|
|
413
427
|
}
|
|
428
|
+
function toolchainVendor(spec) {
|
|
429
|
+
if (spec.launcher && basename(spec.launcher) === "ori") return "openrouter";
|
|
430
|
+
return adapterFor(spec.harness).vendor;
|
|
431
|
+
}
|
|
432
|
+
function resolveSeat(ship, route, role, persona = {}) {
|
|
433
|
+
const warnings = [];
|
|
434
|
+
const base = resolveToolchain(ship, route);
|
|
435
|
+
const baseVendor = toolchainVendor(base);
|
|
436
|
+
const baseWhy = route?.toolchain ? `route toolchain ${base.name}` : ship.toolchain ? `ship default ${base.name}` : `default ${base.name}`;
|
|
437
|
+
let chosen = base;
|
|
438
|
+
let toolchainWhy = baseWhy;
|
|
439
|
+
if (persona.vendor && persona.vendor !== baseVendor) {
|
|
440
|
+
const match = shipToolchains(ship).find((t) => toolchainVendor(t) === persona.vendor);
|
|
441
|
+
if (match) {
|
|
442
|
+
chosen = match;
|
|
443
|
+
toolchainWhy = `persona vendor ${persona.vendor} \u2192 ${match.name}`;
|
|
444
|
+
} else {
|
|
445
|
+
warnings.push(`seat ${role}: persona vendor "${persona.vendor}" has no toolchain on this ship; running ${base.name} (${baseVendor}) instead`);
|
|
446
|
+
toolchainWhy = `${baseWhy} (persona vendor ${persona.vendor} has no toolchain)`;
|
|
447
|
+
}
|
|
448
|
+
} else if (persona.vendor) {
|
|
449
|
+
toolchainWhy = `${baseWhy} (serves persona vendor ${persona.vendor})`;
|
|
450
|
+
}
|
|
451
|
+
const vendor = toolchainVendor(chosen);
|
|
452
|
+
const adapter = adapterFor(chosen.harness);
|
|
453
|
+
let model = chosen.model;
|
|
454
|
+
let modelWhy = `toolchain ${chosen.name} model`;
|
|
455
|
+
let reasoning = chosen.tiers.standard?.reasoning;
|
|
456
|
+
const explicit = persona.model;
|
|
457
|
+
const explicitOk = explicit !== void 0 && (vendor === "openrouter" || adapter.ownsModel(explicit));
|
|
458
|
+
if (explicit !== void 0 && !explicitOk) {
|
|
459
|
+
warnings.push(`seat ${role}: persona model "${explicit}" does not belong to vendor "${vendor}" (toolchain ${chosen.name}); ignored`);
|
|
460
|
+
}
|
|
461
|
+
if (explicit !== void 0 && explicitOk) {
|
|
462
|
+
model = explicit;
|
|
463
|
+
modelWhy = "persona model";
|
|
464
|
+
reasoning = void 0;
|
|
465
|
+
} else if (persona.tier && persona.tier !== "standard" && chosen.tiers[persona.tier]) {
|
|
466
|
+
const t = chosen.tiers[persona.tier];
|
|
467
|
+
model = t.model;
|
|
468
|
+
reasoning = t.reasoning;
|
|
469
|
+
modelWhy = `persona tier ${persona.tier} via ${chosen.name}`;
|
|
470
|
+
}
|
|
471
|
+
const toolchain = reasoning !== void 0 && adapter.applyReasoning ? adapter.applyReasoning(chosen, reasoning) : chosen;
|
|
472
|
+
return { toolchain, model, toolchainWhy, modelWhy, warnings };
|
|
473
|
+
}
|
|
414
474
|
|
|
415
475
|
// src/config.ts
|
|
416
476
|
import { getSessionStore, hostFromUrl } from "@tablation/client";
|
|
@@ -453,7 +513,7 @@ function explain(ship, required) {
|
|
|
453
513
|
|
|
454
514
|
// src/repo-config.ts
|
|
455
515
|
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
456
|
-
import { join as join3, basename } from "path";
|
|
516
|
+
import { join as join3, basename as basename2 } from "path";
|
|
457
517
|
import { parse } from "yaml";
|
|
458
518
|
var SPEC_VERSION = 1;
|
|
459
519
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
@@ -792,7 +852,7 @@ function renderWorktreeName(template, ctx) {
|
|
|
792
852
|
return renderBranchName(template.replace(/\{dir\}/g, ctx.dir).replace(/\{issue\}/g, issue), ctx);
|
|
793
853
|
}
|
|
794
854
|
function defaultWorktreePrefix(dir) {
|
|
795
|
-
const name =
|
|
855
|
+
const name = basename2(dir.replace(/[/\\]+$/, ""));
|
|
796
856
|
return name ? `${name}-issue-` : "issue-";
|
|
797
857
|
}
|
|
798
858
|
var DEFAULTS = {
|
|
@@ -966,7 +1026,7 @@ function effectiveBranchTemplate(cfg, prefix) {
|
|
|
966
1026
|
function effectiveWorktreeDirName(cfg, dir, branchName, number, renderName) {
|
|
967
1027
|
if (cfg.worktrees.name && renderName) return renderName(cfg.worktrees.name);
|
|
968
1028
|
if (cfg.provenance["worktrees.prefix"] !== "default") return `${cfg.worktrees.prefix}${number}`;
|
|
969
|
-
return `${
|
|
1029
|
+
return `${basename2(dir.replace(/[/\\]+$/, ""))}-${branchName}`;
|
|
970
1030
|
}
|
|
971
1031
|
function validateEffective(cfg) {
|
|
972
1032
|
const problems = [];
|
|
@@ -1571,7 +1631,7 @@ function dirForRepo(r, repoId) {
|
|
|
1571
1631
|
}
|
|
1572
1632
|
function repoTargetFor(r, repoId) {
|
|
1573
1633
|
const names = r.resolved?.repoNames;
|
|
1574
|
-
if (!repoId || !names) return Object.keys(r.repos).length ? null : { name:
|
|
1634
|
+
if (!repoId || !names) return Object.keys(r.repos).length ? null : { name: basename3(r.dir), dir: r.dir };
|
|
1575
1635
|
const name = names[repoId];
|
|
1576
1636
|
if (!name) return null;
|
|
1577
1637
|
return reposOf(r).find((t) => t.name === name) ?? null;
|
|
@@ -1654,7 +1714,7 @@ function reposOf(r) {
|
|
|
1654
1714
|
const named = Object.entries(r.repos);
|
|
1655
1715
|
if (named.length > 0) return named.map(([name, dir]) => ({ name, dir }));
|
|
1656
1716
|
const trackerNames = r.resolved?.repoNames ? Object.values(r.resolved.repoNames) : [];
|
|
1657
|
-
if (trackerNames.length <= 1) return [{ name:
|
|
1717
|
+
if (trackerNames.length <= 1) return [{ name: basename3(r.dir), dir: r.dir }];
|
|
1658
1718
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
1659
1719
|
const remoteByName = {};
|
|
1660
1720
|
for (const [id, name] of Object.entries(r.resolved.repoNames)) {
|
|
@@ -3040,7 +3100,7 @@ import {
|
|
|
3040
3100
|
statSync as statSync4,
|
|
3041
3101
|
unlinkSync as unlinkSync2
|
|
3042
3102
|
} from "fs";
|
|
3043
|
-
import { dirname as dirname4, basename as
|
|
3103
|
+
import { dirname as dirname4, basename as basename4 } from "path";
|
|
3044
3104
|
var GitError = class extends Error {
|
|
3045
3105
|
};
|
|
3046
3106
|
function gitRaw(cwd, args) {
|
|
@@ -3136,7 +3196,7 @@ function deleteRemoteBranch(cwd, remote, branch) {
|
|
|
3136
3196
|
}
|
|
3137
3197
|
function worktreeForNumber(cwd, num) {
|
|
3138
3198
|
const re = new RegExp(`(?:^|-)${num}$`);
|
|
3139
|
-
return worktrees(cwd).find((w) => re.test(
|
|
3199
|
+
return worktrees(cwd).find((w) => re.test(basename4(w.path))) ?? null;
|
|
3140
3200
|
}
|
|
3141
3201
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
3142
3202
|
var resolve2 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
|
|
@@ -5090,7 +5150,7 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
5090
5150
|
}
|
|
5091
5151
|
|
|
5092
5152
|
// src/ticket-branch.ts
|
|
5093
|
-
import { basename as
|
|
5153
|
+
import { basename as basename5 } from "path";
|
|
5094
5154
|
function branchRenderer(t, role) {
|
|
5095
5155
|
const { tag, prefix } = ticketBranchContext(t);
|
|
5096
5156
|
return (template) => renderBranchName(template, { key: t.issue_id, title: t.title ?? void 0, role, tag, prefix });
|
|
@@ -5101,7 +5161,7 @@ function worktreeForTicket(dir, cfg, t, role) {
|
|
|
5101
5161
|
const number = t.issue_id.replace(/^\D+/, "");
|
|
5102
5162
|
const { tag } = ticketBranchContext(t);
|
|
5103
5163
|
return effectiveWorktreeDirName(cfg, dir, branchName, number, (template) => renderWorktreeName(template, {
|
|
5104
|
-
dir:
|
|
5164
|
+
dir: basename5(dir.replace(/[/\\]+$/, "")),
|
|
5105
5165
|
key: t.issue_id,
|
|
5106
5166
|
title: t.title ?? void 0,
|
|
5107
5167
|
role,
|
|
@@ -5790,7 +5850,7 @@ import { join as join8, dirname as dirname5 } from "path";
|
|
|
5790
5850
|
import { createHash as createHash2 } from "crypto";
|
|
5791
5851
|
|
|
5792
5852
|
// src/environment.ts
|
|
5793
|
-
import { basename as
|
|
5853
|
+
import { basename as basename6 } from "path";
|
|
5794
5854
|
var API_KEY_VAR = "CREW_API_KEY";
|
|
5795
5855
|
function authorshipBullets(a, contract) {
|
|
5796
5856
|
if (!a) return [];
|
|
@@ -5841,7 +5901,7 @@ function repoSection(r, key, heading) {
|
|
|
5841
5901
|
const repo = r.config;
|
|
5842
5902
|
const branchTemplate = effectiveBranchTemplate(repo, repoPrefix(repo));
|
|
5843
5903
|
const branchName = branchExample(repo, key);
|
|
5844
|
-
const checkoutName =
|
|
5904
|
+
const checkoutName = basename6(r.dir.replace(/[/\\]+$/, ""));
|
|
5845
5905
|
const worktreeName = effectiveWorktreeDirName(
|
|
5846
5906
|
repo,
|
|
5847
5907
|
r.dir,
|
|
@@ -6072,6 +6132,13 @@ function renderEnvironment(i) {
|
|
|
6072
6132
|
}
|
|
6073
6133
|
|
|
6074
6134
|
// src/agent-log.ts
|
|
6135
|
+
var AgentLogHttpError = class extends Error {
|
|
6136
|
+
status;
|
|
6137
|
+
constructor(message, status2) {
|
|
6138
|
+
super(message);
|
|
6139
|
+
this.status = status2;
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
6075
6142
|
async function post(target, path, body) {
|
|
6076
6143
|
const url = `${target.baseUrl.replace(/\/+$/, "")}/api/workspaces/${target.workspaceId}${path}`;
|
|
6077
6144
|
const res = await fetch(url, {
|
|
@@ -6084,11 +6151,11 @@ async function post(target, path, body) {
|
|
|
6084
6151
|
},
|
|
6085
6152
|
body: JSON.stringify(body)
|
|
6086
6153
|
});
|
|
6087
|
-
if (!res.ok) throw new
|
|
6154
|
+
if (!res.ok) throw new AgentLogHttpError(`${path}: ${res.status} ${res.statusText}`, res.status);
|
|
6088
6155
|
return await res.json();
|
|
6089
6156
|
}
|
|
6090
6157
|
async function reportAgentRun(target, run2) {
|
|
6091
|
-
const
|
|
6158
|
+
const body = {
|
|
6092
6159
|
agentId: target.agentId,
|
|
6093
6160
|
ticketReference: run2.ticketReference,
|
|
6094
6161
|
outcome: run2.outcome,
|
|
@@ -6104,7 +6171,14 @@ async function reportAgentRun(target, run2) {
|
|
|
6104
6171
|
costUsd: run2.costUsd,
|
|
6105
6172
|
promptVersion: run2.promptVersion,
|
|
6106
6173
|
promptSha: run2.promptSha
|
|
6107
|
-
}
|
|
6174
|
+
};
|
|
6175
|
+
let entry;
|
|
6176
|
+
try {
|
|
6177
|
+
entry = await post(target, "/agents/log", { ...body, harness: run2.harness, provider: run2.provider });
|
|
6178
|
+
} catch (e) {
|
|
6179
|
+
if (!(e instanceof AgentLogHttpError) || e.status !== 400 || !run2.harness && !run2.provider) throw e;
|
|
6180
|
+
entry = await post(target, "/agents/log", body);
|
|
6181
|
+
}
|
|
6108
6182
|
for (const cycle of run2.cycles) {
|
|
6109
6183
|
await post(target, `/agents/log/${entry.id}/cycles`, {
|
|
6110
6184
|
cycleIndex: cycle.cycleIndex,
|
|
@@ -6153,6 +6227,7 @@ var TicketAttributionTracker = class {
|
|
|
6153
6227
|
// src/agent.ts
|
|
6154
6228
|
var DIGEST_MAX_AGE_SECONDS = 600;
|
|
6155
6229
|
var LEGACY_TOOLCHAIN_HARNESS = "claude";
|
|
6230
|
+
var LEGACY_TOOLCHAIN_PROVIDER = "anthropic";
|
|
6156
6231
|
function scrubbedEnv(env = process.env, harness = LEGACY_TOOLCHAIN_HARNESS) {
|
|
6157
6232
|
const out = { ...env };
|
|
6158
6233
|
for (const v of adapterFor(harness).unsetEnv) delete out[v];
|
|
@@ -6198,9 +6273,9 @@ function planAgentRun(o) {
|
|
|
6198
6273
|
const promptSha = createHash2("sha256").update(`${effectiveCommon}${effectiveBrief}`).digest("hex");
|
|
6199
6274
|
const streamsDir = join8(o.stateDir, "streams");
|
|
6200
6275
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
6201
|
-
const
|
|
6276
|
+
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}));
|
|
6277
|
+
const { toolchain, model } = seat;
|
|
6202
6278
|
const adapter = adapterFor(toolchain.harness);
|
|
6203
|
-
const model = o.agentModel ?? toolchain.model;
|
|
6204
6279
|
const invocation = adapter.invocation(toolchain, {
|
|
6205
6280
|
role: o.role,
|
|
6206
6281
|
model,
|
|
@@ -6229,6 +6304,9 @@ function planAgentRun(o) {
|
|
|
6229
6304
|
args: invocation.args,
|
|
6230
6305
|
toolchain: toolchain.name,
|
|
6231
6306
|
harness: toolchain.harness,
|
|
6307
|
+
provider: adapter.provider(toolchain),
|
|
6308
|
+
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
6309
|
+
warnings: seat.warnings,
|
|
6232
6310
|
prompt,
|
|
6233
6311
|
promptBytes: Buffer.byteLength(prompt, "utf8"),
|
|
6234
6312
|
unsetEnv: adapter.unsetEnv,
|
|
@@ -6259,6 +6337,8 @@ function planAgentRun(o) {
|
|
|
6259
6337
|
function describePlan(p) {
|
|
6260
6338
|
return [
|
|
6261
6339
|
`role: ${p.role}`,
|
|
6340
|
+
...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}`] : [],
|
|
6341
|
+
...(p.warnings ?? []).map((w) => `warning: ${w}`),
|
|
6262
6342
|
// Only for a non-default toolchain, so a plain Claude run's output is unchanged.
|
|
6263
6343
|
...p.toolchain && p.toolchain !== "claude" ? [`toolchain: ${p.toolchain} (${p.harness ?? "claude"})`] : [],
|
|
6264
6344
|
`cwd: ${p.cwd}`,
|
|
@@ -6289,6 +6369,7 @@ function spawnAgent(plan, emit) {
|
|
|
6289
6369
|
const adapter = adapterFor(plan.harness ?? LEGACY_TOOLCHAIN_HARNESS);
|
|
6290
6370
|
const started = Date.now();
|
|
6291
6371
|
const startedAt = new Date(started).toISOString();
|
|
6372
|
+
for (const w of plan.warnings ?? []) emit.warn(w, { step: "agent", role: plan.role });
|
|
6292
6373
|
const rawSink = openSink(plan.streamPath);
|
|
6293
6374
|
const eventsSink = openSink(plan.eventsPath);
|
|
6294
6375
|
return new Promise((resolve5, reject) => {
|
|
@@ -6376,6 +6457,11 @@ function spawnAgent(plan, emit) {
|
|
|
6376
6457
|
await Promise.all([endSink(rawSink), endSink(eventsSink)]);
|
|
6377
6458
|
if (spawnFailed) return;
|
|
6378
6459
|
const attributedTicket = attribution.winner(plan.ticket);
|
|
6460
|
+
const identity = {
|
|
6461
|
+
toolchain: plan.toolchain ?? LEGACY_TOOLCHAIN_HARNESS,
|
|
6462
|
+
harness: adapter.harness,
|
|
6463
|
+
provider: plan.provider ?? LEGACY_TOOLCHAIN_PROVIDER
|
|
6464
|
+
};
|
|
6379
6465
|
try {
|
|
6380
6466
|
writeFileSync4(`${plan.streamPath}.meta.json`, JSON.stringify({
|
|
6381
6467
|
route: plan.route,
|
|
@@ -6385,9 +6471,10 @@ function spawnAgent(plan, emit) {
|
|
|
6385
6471
|
startedAt,
|
|
6386
6472
|
exitCode: code ?? 1,
|
|
6387
6473
|
model: plan.model,
|
|
6474
|
+
...identity,
|
|
6388
6475
|
sessionId: result?.sessionId ?? null,
|
|
6389
6476
|
numTurns: result?.numTurns ?? null,
|
|
6390
|
-
totalCostUsd: result?.
|
|
6477
|
+
totalCostUsd: result?.costUsd ?? null
|
|
6391
6478
|
}));
|
|
6392
6479
|
} catch {
|
|
6393
6480
|
}
|
|
@@ -6396,9 +6483,10 @@ function spawnAgent(plan, emit) {
|
|
|
6396
6483
|
data: {
|
|
6397
6484
|
ms: ms2,
|
|
6398
6485
|
code: code ?? 1,
|
|
6486
|
+
...identity,
|
|
6399
6487
|
...result?.sessionId ? { sessionId: result.sessionId } : {},
|
|
6400
6488
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
6401
|
-
...result?.
|
|
6489
|
+
...result?.costUsd !== void 0 ? { totalCostUsd: result.costUsd } : {}
|
|
6402
6490
|
}
|
|
6403
6491
|
});
|
|
6404
6492
|
if (plan.agentLog) {
|
|
@@ -6413,8 +6501,10 @@ function spawnAgent(plan, emit) {
|
|
|
6413
6501
|
tokensIn: result?.inputTokens,
|
|
6414
6502
|
tokensOut: result?.outputTokens,
|
|
6415
6503
|
cacheReadTokens: result?.cacheReadTokens,
|
|
6416
|
-
cacheWriteTokens: result?.
|
|
6417
|
-
costUsd: result?.
|
|
6504
|
+
cacheWriteTokens: result?.cacheWriteTokens,
|
|
6505
|
+
costUsd: result?.costUsd,
|
|
6506
|
+
harness: identity.harness,
|
|
6507
|
+
provider: identity.provider,
|
|
6418
6508
|
promptVersion: plan.promptVersion,
|
|
6419
6509
|
promptSha: plan.promptSha
|
|
6420
6510
|
});
|
|
@@ -8577,7 +8667,11 @@ async function fetchLastSyncedPrompt(route, agentsModelId, recordId, historyId,
|
|
|
8577
8667
|
return void 0;
|
|
8578
8668
|
}
|
|
8579
8669
|
}
|
|
8580
|
-
|
|
8670
|
+
function choiceValue(v) {
|
|
8671
|
+
const raw = typeof v === "string" ? v : v && typeof v === "object" ? v.value ?? v.key : void 0;
|
|
8672
|
+
return typeof raw === "string" && raw.trim() ? raw.trim().toLowerCase() : void 0;
|
|
8673
|
+
}
|
|
8674
|
+
async function fetchSeatPersona(route, role, opts) {
|
|
8581
8675
|
const agentId = await resolveAgentId(route, role, opts);
|
|
8582
8676
|
if (!agentId || !route.resolved) return void 0;
|
|
8583
8677
|
const client2 = new TablationClient2({
|
|
@@ -8588,7 +8682,14 @@ async function fetchSeatAgentModel(route, role, opts) {
|
|
|
8588
8682
|
try {
|
|
8589
8683
|
const agentsModel = await client2.dataModels.get("agents", route.resolved.workspaceId);
|
|
8590
8684
|
const row = await client2.records.get(agentsModel.id, agentId);
|
|
8591
|
-
|
|
8685
|
+
const tier = choiceValue(row.tier);
|
|
8686
|
+
const vendor = choiceValue(row.vendor);
|
|
8687
|
+
const persona = {
|
|
8688
|
+
...row.model ? { model: row.model } : {},
|
|
8689
|
+
...tier && TIERS.includes(tier) ? { tier } : {},
|
|
8690
|
+
...vendor ? { vendor } : {}
|
|
8691
|
+
};
|
|
8692
|
+
return Object.keys(persona).length > 0 ? persona : void 0;
|
|
8592
8693
|
} catch {
|
|
8593
8694
|
return void 0;
|
|
8594
8695
|
}
|
|
@@ -9392,7 +9493,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
9392
9493
|
|
|
9393
9494
|
// src/repos-cmd.ts
|
|
9394
9495
|
import { homedir as homedir4 } from "os";
|
|
9395
|
-
import { basename as
|
|
9496
|
+
import { basename as basename7, resolve as resolve3 } from "path";
|
|
9396
9497
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
9397
9498
|
var ReposError = class extends Error {
|
|
9398
9499
|
};
|
|
@@ -9464,7 +9565,7 @@ function configuredRepos(text, route, base) {
|
|
|
9464
9565
|
});
|
|
9465
9566
|
}
|
|
9466
9567
|
const dir = m.get("dir");
|
|
9467
|
-
return dir ? [{ name:
|
|
9568
|
+
return dir ? [{ name: basename7(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
9468
9569
|
}
|
|
9469
9570
|
function planRepoAdd(i) {
|
|
9470
9571
|
const doc = parseDocument(i.text);
|
|
@@ -9476,7 +9577,7 @@ function planRepoAdd(i) {
|
|
|
9476
9577
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
9477
9578
|
}
|
|
9478
9579
|
const matched = matchRepoName(i.origin, i.resolved);
|
|
9479
|
-
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ??
|
|
9580
|
+
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ?? basename7(target);
|
|
9480
9581
|
let warning;
|
|
9481
9582
|
if (!matched) {
|
|
9482
9583
|
const known = Object.values(i.resolved?.repoNames ?? {});
|
|
@@ -9574,14 +9675,14 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
9574
9675
|
|
|
9575
9676
|
// src/worktree-sweep.ts
|
|
9576
9677
|
import { existsSync as existsSync15 } from "fs";
|
|
9577
|
-
import { basename as
|
|
9678
|
+
import { basename as basename8 } from "path";
|
|
9578
9679
|
function planWorktreeSweep(target, tickets, contract) {
|
|
9579
9680
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
9580
9681
|
const verified = contract.statuses.verified;
|
|
9581
9682
|
const byNumber = new Map(tickets.map((t) => [t.issue_id.replace(/^\D+/, ""), t]));
|
|
9582
9683
|
const actions = [];
|
|
9583
9684
|
for (const w of worktrees(target.dir)) {
|
|
9584
|
-
const m = /(?:^|-)(\d+)$/.exec(
|
|
9685
|
+
const m = /(?:^|-)(\d+)$/.exec(basename8(w.path));
|
|
9585
9686
|
if (!m) continue;
|
|
9586
9687
|
const ticket = byNumber.get(m[1]);
|
|
9587
9688
|
if (!ticket) continue;
|
|
@@ -10746,7 +10847,7 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
10746
10847
|
cycle: emit.cycle,
|
|
10747
10848
|
ticket: ticketHint,
|
|
10748
10849
|
divergedPrompt: await fetchDivergedPrompt(route, role, { userAgent: ship.userAgent }),
|
|
10749
|
-
|
|
10850
|
+
agentPersona: await fetchSeatPersona(route, role, { userAgent: ship.userAgent }),
|
|
10750
10851
|
resolvedAgentId: await resolveAgentId(route, role, { userAgent: ship.userAgent })
|
|
10751
10852
|
});
|
|
10752
10853
|
emit.enter("agent", role);
|
|
@@ -11832,7 +11933,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
11832
11933
|
cycle: wemit.cycle,
|
|
11833
11934
|
ticket: w.decision.actionable.top?.issue_id,
|
|
11834
11935
|
divergedPrompt: await fetchDivergedPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
11835
|
-
|
|
11936
|
+
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
11836
11937
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
11837
11938
|
});
|
|
11838
11939
|
process.stdout.write(`${describePlan(fleetPlan2)}
|
|
@@ -11925,7 +12026,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
11925
12026
|
cycle: wemit.cycle,
|
|
11926
12027
|
ticket: fleetTicketHint,
|
|
11927
12028
|
divergedPrompt: await fetchDivergedPrompt(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
11928
|
-
|
|
12029
|
+
agentPersona: await fetchSeatPersona(w.route, w.role, { userAgent: cfg.ship.userAgent }),
|
|
11929
12030
|
resolvedAgentId: await resolveAgentId(w.route, w.role, { userAgent: cfg.ship.userAgent })
|
|
11930
12031
|
});
|
|
11931
12032
|
wemit.enter("agent", w.role);
|
|
@@ -12130,7 +12231,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12130
12231
|
cycle: emit.cycle,
|
|
12131
12232
|
ticket: ticketHint,
|
|
12132
12233
|
divergedPrompt: await fetchDivergedPrompt(route, current, { userAgent: cfg.ship.userAgent }),
|
|
12133
|
-
|
|
12234
|
+
agentPersona: await fetchSeatPersona(route, current, { userAgent: cfg.ship.userAgent }),
|
|
12134
12235
|
resolvedAgentId: await resolveAgentId(route, current, { userAgent: cfg.ship.userAgent })
|
|
12135
12236
|
});
|
|
12136
12237
|
if (dryRun) {
|
|
@@ -12622,7 +12723,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12622
12723
|
}
|
|
12623
12724
|
for (const e of entries) {
|
|
12624
12725
|
process.stdout.write(
|
|
12625
|
-
`${e.id} ${e.started_at ?? "?"} ${e.outcome ?? "?"}${e.ticket_reference ? ` ${e.ticket_reference}` : ""}${e.model ? ` ${e.model}` : ""}
|
|
12726
|
+
`${e.id} ${e.started_at ?? "?"} ${e.outcome ?? "?"}${e.ticket_reference ? ` ${e.ticket_reference}` : ""}${e.model ? ` ${e.model}` : ""}${e.harness || e.provider ? ` (${[e.harness, e.provider].filter(Boolean).join(" \xB7 ")})` : ""}
|
|
12626
12727
|
`
|
|
12627
12728
|
);
|
|
12628
12729
|
}
|
|
@@ -12638,6 +12739,8 @@ finished: ${entry.finished_at ?? "?"}
|
|
|
12638
12739
|
outcome: ${entry.outcome ?? "?"}
|
|
12639
12740
|
ticket: ${entry.ticket_reference ?? "(none)"}
|
|
12640
12741
|
model: ${entry.model ?? "?"}
|
|
12742
|
+
harness: ${entry.harness ?? "?"}
|
|
12743
|
+
provider: ${entry.provider ?? "?"}
|
|
12641
12744
|
`
|
|
12642
12745
|
);
|
|
12643
12746
|
if (flag("prompt")) {
|
|
@@ -13534,7 +13637,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
13534
13637
|
// `synthesis-tabl-123`. `worktreePrefix` above is kept for
|
|
13535
13638
|
// config-provenance display; this is the field that actually
|
|
13536
13639
|
// describes where a worktree lands by default.
|
|
13537
|
-
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${
|
|
13640
|
+
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${basename9(r.dir.replace(/[/\\]+$/, ""))}-<branch>`),
|
|
13538
13641
|
worktreeParent: resolvePath(r.dir, "..")
|
|
13539
13642
|
})),
|
|
13540
13643
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
@@ -13642,7 +13745,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
13642
13745
|
);
|
|
13643
13746
|
for (const r of await resolvedRepos(route)) {
|
|
13644
13747
|
const { config } = r;
|
|
13645
|
-
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${
|
|
13748
|
+
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${basename9(r.dir.replace(/[/\\]+$/, ""))}-${effectiveBranchTemplate(config, void 0)}`);
|
|
13646
13749
|
process.stdout.write(
|
|
13647
13750
|
`${`repo ${r.name}:`.padEnd(19)}${r.dir}
|
|
13648
13751
|
worktrees at ../${worktreePattern} (${config.provenance["worktrees.name"] ?? config.provenance["worktrees.prefix"] ?? "default"}), branch ${config.branch.name}
|
|
@@ -13786,6 +13889,19 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
|
|
|
13786
13889
|
);
|
|
13787
13890
|
}
|
|
13788
13891
|
}
|
|
13892
|
+
const shipVendors = new Set(shipToolchains(cfg.ship).map(toolchainVendor));
|
|
13893
|
+
if (route.apiKey && route.resolved) {
|
|
13894
|
+
let flagged = 0;
|
|
13895
|
+
for (const role of ROLE_NAMES) {
|
|
13896
|
+
const persona = await fetchSeatPersona(route, role, { userAgent: cfg.ship.userAgent });
|
|
13897
|
+
if (persona?.vendor && !shipVendors.has(persona.vendor)) {
|
|
13898
|
+
flagged++;
|
|
13899
|
+
process.stdout.write(`seat vendor: ${role} persona wants "${persona.vendor}" \u2014 no toolchain on this ship serves it (have: ${[...shipVendors].join(", ")}); it will run ${resolveSeat(cfg.ship, route, role).toolchain.name} with a warning
|
|
13900
|
+
`);
|
|
13901
|
+
}
|
|
13902
|
+
}
|
|
13903
|
+
if (flagged === 0) process.stdout.write("seat vendor: OK (every persona vendor has a toolchain on this ship)\n");
|
|
13904
|
+
}
|
|
13789
13905
|
for (const c of cfg.routes.filter((r) => r.enabled)) {
|
|
13790
13906
|
let repos;
|
|
13791
13907
|
try {
|
package/package.json
CHANGED