@tablation/crew 0.129.1 → 0.130.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 +161 -59
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1259,6 +1259,14 @@ var claudePair = {
|
|
|
1259
1259
|
removePair: (o) => removeClaudeMcp({ bin: o.bin ?? "claude", dryRun: o.dryRun })
|
|
1260
1260
|
};
|
|
1261
1261
|
var inferenceBaseUrl = (baseUrl, workspaceId) => `${baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/inference`;
|
|
1262
|
+
function inferenceHeaders(inf) {
|
|
1263
|
+
const clean = (v) => v.replace(/[\r\n]+/g, " ").trim();
|
|
1264
|
+
const lines2 = [
|
|
1265
|
+
...inf.ticket ? [`X-Crew-Ticket: ${clean(inf.ticket)}`] : [],
|
|
1266
|
+
...inf.runId ? [`X-Crew-Run: ${clean(inf.runId)}`] : []
|
|
1267
|
+
];
|
|
1268
|
+
return lines2.length > 0 ? lines2.join("\n") : void 0;
|
|
1269
|
+
}
|
|
1262
1270
|
function workspaceSettings(baseUrl) {
|
|
1263
1271
|
const blank = BILLING_VARS_TO_UNSET.filter((v) => v !== "ANTHROPIC_AUTH_TOKEN");
|
|
1264
1272
|
return JSON.stringify({ env: { ...Object.fromEntries(blank.map((v) => [v, ""])), ANTHROPIC_BASE_URL: baseUrl } });
|
|
@@ -1362,7 +1370,11 @@ var claudeAdapter = {
|
|
|
1362
1370
|
// variables are already unset at exec (`unsetEnv`), and a spec-level `env:` entry must not
|
|
1363
1371
|
// smuggle one back in.
|
|
1364
1372
|
...spec.auth === "workspace" ? withoutProviderVars(spec.env) : spec.env,
|
|
1365
|
-
...spec.auth === "workspace" && spec.inference ? {
|
|
1373
|
+
...spec.auth === "workspace" && spec.inference ? {
|
|
1374
|
+
ANTHROPIC_BASE_URL: spec.inference.baseUrl,
|
|
1375
|
+
ANTHROPIC_AUTH_TOKEN: spec.inference.token,
|
|
1376
|
+
...inferenceHeaders(spec.inference) ? { ANTHROPIC_CUSTOM_HEADERS: inferenceHeaders(spec.inference) } : {}
|
|
1377
|
+
} : {}
|
|
1366
1378
|
};
|
|
1367
1379
|
},
|
|
1368
1380
|
provider: () => "anthropic",
|
|
@@ -3210,7 +3222,7 @@ function parseRepoConfig(text2, file) {
|
|
|
3210
3222
|
if (!MATCHES.includes(vmatch)) {
|
|
3211
3223
|
throw new RepoConfigError(`${file}: release.verify.match must be commit|version (got "${vmatch}")`);
|
|
3212
3224
|
}
|
|
3213
|
-
const
|
|
3225
|
+
const num2 = (v, key, dflt) => {
|
|
3214
3226
|
if (v === void 0 || v === null) return dflt;
|
|
3215
3227
|
const n = Number(v);
|
|
3216
3228
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -3220,8 +3232,8 @@ function parseRepoConfig(text2, file) {
|
|
|
3220
3232
|
};
|
|
3221
3233
|
const verify = {
|
|
3222
3234
|
match: vmatch,
|
|
3223
|
-
timeoutSeconds:
|
|
3224
|
-
intervalSeconds:
|
|
3235
|
+
timeoutSeconds: num2(raw.release?.verify?.timeoutSeconds, "timeoutSeconds", 600),
|
|
3236
|
+
intervalSeconds: num2(raw.release?.verify?.intervalSeconds, "intervalSeconds", 15)
|
|
3225
3237
|
};
|
|
3226
3238
|
if (verify.intervalSeconds > verify.timeoutSeconds) {
|
|
3227
3239
|
throw new RepoConfigError(
|
|
@@ -3794,7 +3806,7 @@ function parseReleaseOverride(raw, where, file) {
|
|
|
3794
3806
|
if (versioning !== void 0 && !VERSIONINGS.includes(versioning)) {
|
|
3795
3807
|
throw new ConfigError(`${file}: ${where}.release.versioning must be auto|none (got "${raw.versioning}")`);
|
|
3796
3808
|
}
|
|
3797
|
-
const
|
|
3809
|
+
const num2 = (v, key) => {
|
|
3798
3810
|
if (v === void 0 || v === null) return void 0;
|
|
3799
3811
|
const n = Number(v);
|
|
3800
3812
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -3802,8 +3814,8 @@ function parseReleaseOverride(raw, where, file) {
|
|
|
3802
3814
|
}
|
|
3803
3815
|
return n;
|
|
3804
3816
|
};
|
|
3805
|
-
const timeoutSeconds =
|
|
3806
|
-
const intervalSeconds =
|
|
3817
|
+
const timeoutSeconds = num2(raw.verify?.timeoutSeconds, "timeoutSeconds");
|
|
3818
|
+
const intervalSeconds = num2(raw.verify?.intervalSeconds, "intervalSeconds");
|
|
3807
3819
|
const isolateMax = parseIsolateMax(
|
|
3808
3820
|
raw.isolateFailures?.maxCandidates,
|
|
3809
3821
|
`${where}.release.isolateFailures`,
|
|
@@ -5496,20 +5508,20 @@ function branchForIssue(cwd, issueKey, templates = { name: "issue-{number}" }, r
|
|
|
5496
5508
|
return null;
|
|
5497
5509
|
}
|
|
5498
5510
|
function branchCandidates(issueKey, templates, render2) {
|
|
5499
|
-
const
|
|
5500
|
-
const unpadded = String(Number.parseInt(
|
|
5511
|
+
const num2 = issueKey.replace(/^\D+/, "");
|
|
5512
|
+
const unpadded = String(Number.parseInt(num2, 10));
|
|
5501
5513
|
const candidates2 = [];
|
|
5502
5514
|
for (const template of [templates.name, templates.push].filter(Boolean)) {
|
|
5503
5515
|
const exact = render2(template);
|
|
5504
5516
|
candidates2.push(exact);
|
|
5505
|
-
if (
|
|
5517
|
+
if (num2 !== unpadded) candidates2.push(exact.replace(num2, unpadded));
|
|
5506
5518
|
if (template.includes("{slug}")) {
|
|
5507
5519
|
const prefix = render2(template.slice(0, template.indexOf("{slug}")));
|
|
5508
5520
|
if (prefix) candidates2.push(`${prefix}*`);
|
|
5509
5521
|
}
|
|
5510
5522
|
}
|
|
5511
|
-
candidates2.push(`issue-${
|
|
5512
|
-
if (
|
|
5523
|
+
candidates2.push(`issue-${num2}`);
|
|
5524
|
+
if (num2 !== unpadded) candidates2.push(`issue-${unpadded}`);
|
|
5513
5525
|
return [...new Set(candidates2)];
|
|
5514
5526
|
}
|
|
5515
5527
|
function remoteBranchForIssue(cwd, remote, issueKey, templates = { name: "issue-{number}" }, render2 = (t) => t.replace(/\{number\}/g, issueKey.replace(/^\D+/, ""))) {
|
|
@@ -5580,8 +5592,8 @@ function deleteRemoteBranch(cwd, remote, branch) {
|
|
|
5580
5592
|
if (!remoteBranchExists(cwd, remote, branch)) return true;
|
|
5581
5593
|
return gitOk(cwd, ["push", remote, "--delete", branch]) !== null;
|
|
5582
5594
|
}
|
|
5583
|
-
function worktreeForNumber(cwd,
|
|
5584
|
-
const re = new RegExp(`(?:^|-)${
|
|
5595
|
+
function worktreeForNumber(cwd, num2) {
|
|
5596
|
+
const re = new RegExp(`(?:^|-)${num2}$`);
|
|
5585
5597
|
return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
|
|
5586
5598
|
}
|
|
5587
5599
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
@@ -6475,7 +6487,7 @@ import { readFileSync as readFileSync26, existsSync as existsSync25, statSync as
|
|
|
6475
6487
|
import { StringDecoder } from "string_decoder";
|
|
6476
6488
|
import { finished } from "stream/promises";
|
|
6477
6489
|
import { join as join29, dirname as dirname14, basename as basename9 } from "path";
|
|
6478
|
-
import { createHash as createHash5 } from "crypto";
|
|
6490
|
+
import { createHash as createHash5, randomUUID } from "crypto";
|
|
6479
6491
|
|
|
6480
6492
|
// src/state.ts
|
|
6481
6493
|
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync18, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
@@ -9606,8 +9618,8 @@ async function noteTakeover(tracker, result, seat, ships, myShipName, continuing
|
|
|
9606
9618
|
}
|
|
9607
9619
|
}
|
|
9608
9620
|
function originBranchSha(dir, t, rev) {
|
|
9609
|
-
const
|
|
9610
|
-
const names = [...new Set([(t.issue_tag ?? "").toLowerCase(), `issue-${
|
|
9621
|
+
const num2 = (t.issue_tag ?? t.issue_id).replace(/^\D+/, "");
|
|
9622
|
+
const names = [...new Set([(t.issue_tag ?? "").toLowerCase(), `issue-${num2}`].filter(Boolean))];
|
|
9611
9623
|
for (const n of names) {
|
|
9612
9624
|
const sha = rev(dir, `origin/${n}`);
|
|
9613
9625
|
if (sha) return sha.slice(0, 7);
|
|
@@ -9669,8 +9681,8 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9669
9681
|
unlock();
|
|
9670
9682
|
}
|
|
9671
9683
|
}
|
|
9672
|
-
const
|
|
9673
|
-
const existing = worktreeForNumber(dir,
|
|
9684
|
+
const num2 = t.issue_id.replace(/^\D+/, "");
|
|
9685
|
+
const existing = worktreeForNumber(dir, num2);
|
|
9674
9686
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9675
9687
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9676
9688
|
if (existing && !existing.prunable && existsSync21(existing.path)) {
|
|
@@ -10167,6 +10179,49 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
10167
10179
|
}
|
|
10168
10180
|
|
|
10169
10181
|
// src/agent-log.ts
|
|
10182
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : Number(v) || 0;
|
|
10183
|
+
function sumInferenceRows(rows) {
|
|
10184
|
+
const t = { calls: 0, tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: 0 };
|
|
10185
|
+
for (const r of rows) {
|
|
10186
|
+
t.calls++;
|
|
10187
|
+
t.tokensIn += num(r.input_tokens);
|
|
10188
|
+
t.tokensOut += num(r.output_tokens);
|
|
10189
|
+
t.cacheReadTokens += num(r.cache_read_tokens);
|
|
10190
|
+
t.cacheWriteTokens += num(r.cache_write_tokens);
|
|
10191
|
+
t.costUsd += num(r.cost_usd);
|
|
10192
|
+
}
|
|
10193
|
+
return t;
|
|
10194
|
+
}
|
|
10195
|
+
var INFERENCE_PAGE = 1e3;
|
|
10196
|
+
async function listInferenceRows(target, modelId, runId) {
|
|
10197
|
+
const filters = encodeURIComponent(JSON.stringify({
|
|
10198
|
+
combinator: "AND",
|
|
10199
|
+
conditions: [
|
|
10200
|
+
{ columnName: "run_id", operator: "EQ", value: runId },
|
|
10201
|
+
{ columnName: "client", operator: "EQ", value: "inference" }
|
|
10202
|
+
]
|
|
10203
|
+
}));
|
|
10204
|
+
const out = [];
|
|
10205
|
+
for (let offset = 0; ; offset += INFERENCE_PAGE) {
|
|
10206
|
+
const page = await request(
|
|
10207
|
+
target,
|
|
10208
|
+
"GET",
|
|
10209
|
+
`/data-models/${modelId}/records?limit=${INFERENCE_PAGE}&offset=${offset}&filters=${filters}`
|
|
10210
|
+
);
|
|
10211
|
+
out.push(...page);
|
|
10212
|
+
if (page.length < INFERENCE_PAGE) return out;
|
|
10213
|
+
}
|
|
10214
|
+
}
|
|
10215
|
+
async function inferenceTotalsFor(target, runId) {
|
|
10216
|
+
const model = await request(
|
|
10217
|
+
target,
|
|
10218
|
+
"GET",
|
|
10219
|
+
`/data-models/agent_log?workspaceId=${encodeURIComponent(target.workspaceId)}`
|
|
10220
|
+
);
|
|
10221
|
+
if (!(await columnsOf(target, model.id)).has("run_id")) return void 0;
|
|
10222
|
+
const rows = await listInferenceRows(target, model.id, runId);
|
|
10223
|
+
return rows.length > 0 ? sumInferenceRows(rows) : void 0;
|
|
10224
|
+
}
|
|
10170
10225
|
var AgentLogUnreachableError = class extends Error {
|
|
10171
10226
|
};
|
|
10172
10227
|
var AgentLogHttpError = class extends Error {
|
|
@@ -10331,19 +10386,20 @@ async function reportAgentRun(target, run3) {
|
|
|
10331
10386
|
promptVersion: run3.promptVersion,
|
|
10332
10387
|
promptSha: run3.promptSha
|
|
10333
10388
|
};
|
|
10389
|
+
const { runId, ...legacyBody } = { ...body, runId: run3.runId };
|
|
10334
10390
|
const workItem = target.tickets && ticketRecordId ? { workItemTable: target.tickets.issuesModelId, workItemRecordId: ticketRecordId } : {};
|
|
10335
10391
|
let entry;
|
|
10336
10392
|
try {
|
|
10337
|
-
entry = await post(target, "/agents/log", { ...body, ...workItem, harness: run3.harness, provider: run3.provider });
|
|
10393
|
+
entry = await post(target, "/agents/log", { ...body, ...runId ? { runId } : {}, ...workItem, harness: run3.harness, provider: run3.provider });
|
|
10338
10394
|
} catch (e) {
|
|
10339
10395
|
if (!(e instanceof AgentLogHttpError)) throw new AgentLogUnreachableError(e.message);
|
|
10340
10396
|
if (e.status >= 500) throw new AgentLogUnreachableError(e.message);
|
|
10341
|
-
if (e.status !== 400 || !run3.harness && !run3.provider && !ticketRecordId) {
|
|
10397
|
+
if (e.status !== 400 || !run3.harness && !run3.provider && !ticketRecordId && !runId) {
|
|
10342
10398
|
e.row = true;
|
|
10343
10399
|
throw e;
|
|
10344
10400
|
}
|
|
10345
10401
|
try {
|
|
10346
|
-
entry = await post(target, "/agents/log",
|
|
10402
|
+
entry = await post(target, "/agents/log", legacyBody);
|
|
10347
10403
|
} catch (e2) {
|
|
10348
10404
|
if (e2 instanceof AgentLogHttpError && e2.status < 500) e2.row = true;
|
|
10349
10405
|
throw e2;
|
|
@@ -10362,10 +10418,21 @@ async function reportAgentRun(target, run3) {
|
|
|
10362
10418
|
const bounces = await countAcceptanceBounces(target, target.tickets.issuesModelId, ticketRecordId);
|
|
10363
10419
|
if (bounces !== null) await writeHandoffBounces(target, entry.id, bounces);
|
|
10364
10420
|
}
|
|
10365
|
-
|
|
10421
|
+
let counterWarning;
|
|
10422
|
+
let counted = run3;
|
|
10423
|
+
if (runId && target.tickets && ticketRecordId) {
|
|
10424
|
+
try {
|
|
10425
|
+
const totals = await inferenceTotalsFor(target, runId);
|
|
10426
|
+
if (totals) counted = { ...run3, tokensIn: totals.tokensIn, tokensOut: totals.tokensOut, cacheReadTokens: totals.cacheReadTokens, cacheWriteTokens: totals.cacheWriteTokens, costUsd: totals.costUsd };
|
|
10427
|
+
else counted = { ...run3, ...run3.harnessTotals };
|
|
10428
|
+
} catch (e) {
|
|
10429
|
+
counterWarning = `could not sum run ${runId}'s inference rows for ${run3.ticketReference}: ${e.message}`;
|
|
10430
|
+
}
|
|
10431
|
+
}
|
|
10432
|
+
const counters = target.tickets && ticketRecordId ? await addRunToTicket(target, target.tickets, ticketRecordId, counted) : void 0;
|
|
10366
10433
|
return {
|
|
10367
10434
|
agentLogId: entry.id,
|
|
10368
|
-
...counters?.warning ? { counterWarning: counters.
|
|
10435
|
+
...counters?.warning || counterWarning ? { counterWarning: [counterWarning, counters?.warning].filter(Boolean).join("; ") } : {},
|
|
10369
10436
|
...counters?.retry ? { counterRetry: counters.retry } : {}
|
|
10370
10437
|
};
|
|
10371
10438
|
}
|
|
@@ -11941,6 +12008,7 @@ function planAgentRun(o) {
|
|
|
11941
12008
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
11942
12009
|
const { model } = seat;
|
|
11943
12010
|
let toolchain = seat.toolchain;
|
|
12011
|
+
let workspaceRunId;
|
|
11944
12012
|
if (toolchain.auth === "workspace") {
|
|
11945
12013
|
if (!o.apiKey || o.seatKind === "ship") {
|
|
11946
12014
|
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, which needs the seat's own agent key \u2014 this seat would fall back to the ship key (run \`crew agents sync\`)`);
|
|
@@ -11948,7 +12016,8 @@ function planAgentRun(o) {
|
|
|
11948
12016
|
if (!o.route.resolved) {
|
|
11949
12017
|
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, but route ${o.route.route} is not connected (run \`crew connect\`)`);
|
|
11950
12018
|
}
|
|
11951
|
-
|
|
12019
|
+
workspaceRunId = o.runId ?? randomUUID();
|
|
12020
|
+
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey, ticket: o.ticket, runId: workspaceRunId } };
|
|
11952
12021
|
}
|
|
11953
12022
|
const adapter = adapterFor(toolchain.harness);
|
|
11954
12023
|
const code = codeBridgeFor(o, adapter.harness);
|
|
@@ -12045,7 +12114,8 @@ function planAgentRun(o) {
|
|
|
12045
12114
|
routeName: o.route.route,
|
|
12046
12115
|
agentLog,
|
|
12047
12116
|
promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
|
|
12048
|
-
promptSha
|
|
12117
|
+
promptSha,
|
|
12118
|
+
...workspaceRunId ? { workspaceRunId } : {}
|
|
12049
12119
|
};
|
|
12050
12120
|
}
|
|
12051
12121
|
function describePlan(p) {
|
|
@@ -12099,7 +12169,8 @@ function identityOf(plan, adapterHarness) {
|
|
|
12099
12169
|
streamPath: plan.streamPath,
|
|
12100
12170
|
agentLog: plan.agentLog ? withoutKey(plan.agentLog) : void 0,
|
|
12101
12171
|
promptVersion: plan.promptVersion,
|
|
12102
|
-
promptSha: plan.promptSha
|
|
12172
|
+
promptSha: plan.promptSha,
|
|
12173
|
+
...plan.workspaceRunId ? { workspaceRunId: plan.workspaceRunId } : {}
|
|
12103
12174
|
};
|
|
12104
12175
|
}
|
|
12105
12176
|
async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMinutes) {
|
|
@@ -12406,11 +12477,22 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
12406
12477
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12407
12478
|
cycles: obs.cycles,
|
|
12408
12479
|
model: id.model,
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12480
|
+
// CREW-2980: under `auth: workspace` the endpoint's rows are the accounting; the run row
|
|
12481
|
+
// keeps turns only, and `reportAgentRun` sums those rows into the ticket counters.
|
|
12482
|
+
...id.workspaceRunId ? { runId: id.workspaceRunId } : {
|
|
12483
|
+
tokensIn: result?.inputTokens,
|
|
12484
|
+
tokensOut: result?.outputTokens,
|
|
12485
|
+
cacheReadTokens: result?.cacheReadTokens,
|
|
12486
|
+
cacheWriteTokens: result?.cacheWriteTokens,
|
|
12487
|
+
costUsd: result?.costUsd
|
|
12488
|
+
},
|
|
12489
|
+
...id.workspaceRunId ? { harnessTotals: {
|
|
12490
|
+
tokensIn: result?.inputTokens,
|
|
12491
|
+
tokensOut: result?.outputTokens,
|
|
12492
|
+
cacheReadTokens: result?.cacheReadTokens,
|
|
12493
|
+
cacheWriteTokens: result?.cacheWriteTokens,
|
|
12494
|
+
costUsd: result?.costUsd
|
|
12495
|
+
} } : {},
|
|
12414
12496
|
// Both come from a finished run: without the result line (interrupted) neither is trustworthy.
|
|
12415
12497
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
12416
12498
|
...result?.numTurns !== void 0 && obs.prefixTokens !== void 0 ? { prefixTokens: obs.prefixTokens } : {},
|
|
@@ -12537,7 +12619,8 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
12537
12619
|
eventsPath: plan.eventsPath,
|
|
12538
12620
|
agentLog: id.agentLog,
|
|
12539
12621
|
promptVersion: plan.promptVersion,
|
|
12540
|
-
promptSha: plan.promptSha
|
|
12622
|
+
promptSha: plan.promptSha,
|
|
12623
|
+
...plan.workspaceRunId ? { workspaceRunId: plan.workspaceRunId } : {}
|
|
12541
12624
|
};
|
|
12542
12625
|
try {
|
|
12543
12626
|
writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
@@ -12792,14 +12875,14 @@ function describeRun(run3, route2, seatIds, worktreeOf = (d, n) => {
|
|
|
12792
12875
|
}) {
|
|
12793
12876
|
const ownRoute = run3.route === routeSlug(route2.route);
|
|
12794
12877
|
const repoDir = run3.repo ? reposOf(route2).find((t) => t.name === run3.repo)?.dir : void 0;
|
|
12795
|
-
const
|
|
12878
|
+
const num2 = run3.ticket?.replace(/^\D+/, "") ?? "";
|
|
12796
12879
|
return {
|
|
12797
12880
|
role: run3.role,
|
|
12798
12881
|
seat: ownRoute ? seatIds[run3.role] ?? null : null,
|
|
12799
12882
|
ticket: run3.ticket,
|
|
12800
12883
|
ticketRecordId: run3.ticketRecordId,
|
|
12801
12884
|
repo: run3.repo ?? null,
|
|
12802
|
-
worktree: repoDir &&
|
|
12885
|
+
worktree: repoDir && num2 ? worktreeOf(repoDir, num2) : null,
|
|
12803
12886
|
stream: run3.streamPath,
|
|
12804
12887
|
pid: run3.pid,
|
|
12805
12888
|
startedAt: run3.spawnedAt,
|
|
@@ -12855,8 +12938,8 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12855
12938
|
const tracker = new Tracker(route2, cfg.ship, { apiKey: identity.apiKey });
|
|
12856
12939
|
const t = await tracker.ticket(run3.ticketRecordId);
|
|
12857
12940
|
const target = repoTargetFor(route2, t.repo_id);
|
|
12858
|
-
const
|
|
12859
|
-
const wt = target &&
|
|
12941
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
12942
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
12860
12943
|
let dirty = null;
|
|
12861
12944
|
try {
|
|
12862
12945
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -18132,10 +18215,10 @@ function dedupeChangelogEntries(entries, ticket) {
|
|
|
18132
18215
|
let key = line;
|
|
18133
18216
|
if (ref) {
|
|
18134
18217
|
const prefix = ref[1];
|
|
18135
|
-
const
|
|
18136
|
-
const canonical = own && own[2] ===
|
|
18137
|
-
line = `${canonical}-${
|
|
18138
|
-
key = `${canonical}-${
|
|
18218
|
+
const num2 = ref[2];
|
|
18219
|
+
const canonical = own && own[2] === num2 ? own[1] : prefix;
|
|
18220
|
+
line = `${canonical}-${num2}${line.slice(ref[0].length)}`;
|
|
18221
|
+
key = `${canonical}-${num2}`;
|
|
18139
18222
|
}
|
|
18140
18223
|
if (seen.has(key)) continue;
|
|
18141
18224
|
seen.add(key);
|
|
@@ -21343,10 +21426,24 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21343
21426
|
} catch (e) {
|
|
21344
21427
|
throw new LogbookError(`no Agent Log entry ${entryId} in this workspace (${e.message})`);
|
|
21345
21428
|
}
|
|
21346
|
-
|
|
21429
|
+
let inference;
|
|
21430
|
+
if (entry.run_id && entry.client !== "inference") {
|
|
21431
|
+
try {
|
|
21432
|
+
const rows = await listInferenceRows(
|
|
21433
|
+
{ baseUrl: route2.baseUrl, apiKey: resolveApiKey(route2), userAgent: opts.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" },
|
|
21434
|
+
agentLogModel.id,
|
|
21435
|
+
entry.run_id
|
|
21436
|
+
);
|
|
21437
|
+
if (rows.length > 0) inference = sumInferenceRows(rows);
|
|
21438
|
+
} catch {
|
|
21439
|
+
}
|
|
21440
|
+
}
|
|
21441
|
+
const withInference = inference ? { inference } : {};
|
|
21442
|
+
if (!opts.withPrompt) return { entry, ...withInference, reconstruction: { status: "not_recorded", reason: "not requested" } };
|
|
21347
21443
|
if (!entry.agent_id || !entry.prompt_version) {
|
|
21348
21444
|
return {
|
|
21349
21445
|
entry,
|
|
21446
|
+
...withInference,
|
|
21350
21447
|
reconstruction: {
|
|
21351
21448
|
status: "not_recorded",
|
|
21352
21449
|
reason: !entry.agent_id ? "this entry has no linked Agents row (predates crew agents sync being run for its role)" : "this entry predates ISSUE-377/529 \u2014 no prompt_version was recorded"
|
|
@@ -21358,12 +21455,13 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21358
21455
|
try {
|
|
21359
21456
|
historyEntries = await fetchHistoryEntries(route2, agentsModel.id, entry.agent_id, opts.userAgent, 200);
|
|
21360
21457
|
} catch (e) {
|
|
21361
|
-
return { entry, reconstruction: { status: "approximate", reason: `history lookup failed: ${e.message}` } };
|
|
21458
|
+
return { entry, ...withInference, reconstruction: { status: "approximate", reason: `history lookup failed: ${e.message}` } };
|
|
21362
21459
|
}
|
|
21363
21460
|
const match = historyEntries.find((h) => h.history_id === entry.prompt_version);
|
|
21364
21461
|
if (!match) {
|
|
21365
21462
|
return {
|
|
21366
21463
|
entry,
|
|
21464
|
+
...withInference,
|
|
21367
21465
|
reconstruction: {
|
|
21368
21466
|
status: "approximate",
|
|
21369
21467
|
reason: `history entry ${entry.prompt_version} no longer exists on the Agents row (purged, or the row itself was recreated)`
|
|
@@ -21375,6 +21473,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21375
21473
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
21376
21474
|
return {
|
|
21377
21475
|
entry,
|
|
21476
|
+
...withInference,
|
|
21378
21477
|
reconstruction: {
|
|
21379
21478
|
status: exact ? "exact" : "approximate",
|
|
21380
21479
|
historyId: match.history_id,
|
|
@@ -21998,8 +22097,8 @@ var BRANCH_STEM = /^(?:.*\/)?([^/]+?-\d+)(?:-.*)?$/;
|
|
|
21998
22097
|
function ticketForWorktree(w, byNumber, contract) {
|
|
21999
22098
|
if (w.branch) {
|
|
22000
22099
|
const stem = BRANCH_STEM.exec(w.branch)?.[1];
|
|
22001
|
-
const
|
|
22002
|
-
const t =
|
|
22100
|
+
const num2 = stem && /-(\d+)$/.exec(stem)?.[1];
|
|
22101
|
+
const t = num2 ? byNumber.get(num2) : void 0;
|
|
22003
22102
|
if (t && ticketStems(t).has(stem)) return t;
|
|
22004
22103
|
}
|
|
22005
22104
|
const dir = basename13(w.path);
|
|
@@ -22179,11 +22278,11 @@ function deployedBranchStems(tickets, contract) {
|
|
|
22179
22278
|
}
|
|
22180
22279
|
function ticketStems(t) {
|
|
22181
22280
|
const stems = /* @__PURE__ */ new Set();
|
|
22182
|
-
const
|
|
22183
|
-
const unpadded = String(Number.parseInt(
|
|
22281
|
+
const num2 = t.issue_id.replace(/^\D+/, "");
|
|
22282
|
+
const unpadded = String(Number.parseInt(num2, 10));
|
|
22184
22283
|
const prefixes = ["issue", t.project_issue_prefix?.toLowerCase()].filter((p) => !!p);
|
|
22185
22284
|
for (const p of prefixes) {
|
|
22186
|
-
stems.add(`${p}-${
|
|
22285
|
+
stems.add(`${p}-${num2}`);
|
|
22187
22286
|
stems.add(`${p}-${unpadded}`);
|
|
22188
22287
|
}
|
|
22189
22288
|
return stems;
|
|
@@ -24161,8 +24260,8 @@ async function reconcileForRoute(ctx, o) {
|
|
|
24161
24260
|
if (!memberId) return;
|
|
24162
24261
|
const t = await tracker.ticket(sc.ticketRecordId);
|
|
24163
24262
|
const target = repoTargetFor(route2, t.repo_id);
|
|
24164
|
-
const
|
|
24165
|
-
const wt = target &&
|
|
24263
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
24264
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
24166
24265
|
let dirty = null;
|
|
24167
24266
|
try {
|
|
24168
24267
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -24196,8 +24295,8 @@ async function postRunKilled(route2, tracker, ticketId, memberId, limitMinutes,
|
|
|
24196
24295
|
try {
|
|
24197
24296
|
const t = await tracker.ticket(ticketId);
|
|
24198
24297
|
const target = repoTargetFor(route2, t.repo_id);
|
|
24199
|
-
const
|
|
24200
|
-
const wt = target &&
|
|
24298
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
24299
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
24201
24300
|
let dirty = null;
|
|
24202
24301
|
try {
|
|
24203
24302
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -25545,10 +25644,10 @@ async function handlePeriscopeMcp(d, req, res, segment) {
|
|
|
25545
25644
|
function findPeriscope(sidecars, segment) {
|
|
25546
25645
|
const m = /^(?:([A-Za-z]+)-)?(\d+)$/.exec(segment);
|
|
25547
25646
|
if (!m) return void 0;
|
|
25548
|
-
const
|
|
25647
|
+
const num2 = String(Number(m[2]));
|
|
25549
25648
|
return sidecars.find((sc) => {
|
|
25550
25649
|
const t = /^(?:([A-Za-z]+)-)?0*(\d+)$/.exec(sc.ticket);
|
|
25551
|
-
if (!t || t[2] !==
|
|
25650
|
+
if (!t || t[2] !== num2) return false;
|
|
25552
25651
|
return !m[1] || !t[1] || m[1].toLowerCase() === t[1].toLowerCase();
|
|
25553
25652
|
});
|
|
25554
25653
|
}
|
|
@@ -28636,7 +28735,7 @@ ${cu.tail.trimEnd()}
|
|
|
28636
28735
|
);
|
|
28637
28736
|
}
|
|
28638
28737
|
} else {
|
|
28639
|
-
const { entry, reconstruction } = await showLogEntry(target, entryId, {
|
|
28738
|
+
const { entry, inference, reconstruction } = await showLogEntry(target, entryId, {
|
|
28640
28739
|
userAgent: cfg.ship.userAgent,
|
|
28641
28740
|
withPrompt: flag("prompt")
|
|
28642
28741
|
});
|
|
@@ -28650,6 +28749,9 @@ model: ${entry.model ?? "?"}
|
|
|
28650
28749
|
harness: ${entry.harness ?? "?"}
|
|
28651
28750
|
provider: ${entry.provider ?? "?"}
|
|
28652
28751
|
` + (entry.num_turns != null ? `turns: ${entry.num_turns}
|
|
28752
|
+
` : "") + (inference ? `calls: ${inference.calls}
|
|
28753
|
+
tokens: in ${inference.tokensIn}, out ${inference.tokensOut}, cache read ${inference.cacheReadTokens}, cache write ${inference.cacheWriteTokens}
|
|
28754
|
+
cost: $${inference.costUsd.toFixed(4)}
|
|
28653
28755
|
` : "") + (entry.prefix_tokens != null ? `prefix: ${entry.prefix_tokens} tokens
|
|
28654
28756
|
` : "") + (formatBeforeEdit(entry) ? `before edit: ${formatBeforeEdit(entry)}
|
|
28655
28757
|
` : "")
|
|
@@ -29692,7 +29794,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29692
29794
|
process.stderr.write("drop: need a ticket number (326, ISSUE-326, or TABL-326)\n");
|
|
29693
29795
|
process.exit(2);
|
|
29694
29796
|
}
|
|
29695
|
-
const
|
|
29797
|
+
const num2 = Number(n);
|
|
29696
29798
|
const repos = await resolvedRepos(route2);
|
|
29697
29799
|
const candidates2 = repos.map((r) => ({ repo: r, found: worktreeForNumber(r.dir, n) })).filter((c) => c.found !== null);
|
|
29698
29800
|
if (candidates2.length === 0) {
|
|
@@ -29713,9 +29815,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29713
29815
|
const wt = found.path;
|
|
29714
29816
|
try {
|
|
29715
29817
|
const trD = new Tracker(route2, cfg.ship);
|
|
29716
|
-
const tD = (await trD.ticketsByNumbers([n])).find((t) => t.issue_id.replace(/^\D+/, "") === String(
|
|
29818
|
+
const tD = (await trD.ticketsByNumbers([n])).find((t) => t.issue_id.replace(/^\D+/, "") === String(num2));
|
|
29717
29819
|
if (tD && tD[trD.contract.columns.status] === trD.contract.statuses.draft) {
|
|
29718
|
-
process.stderr.write(`drop: ${tD.issue_tag ?? tD.issue_id} is a draft ticket \u2014 its worktree belongs to a periscope; end it with \`crew periscope down ${
|
|
29820
|
+
process.stderr.write(`drop: ${tD.issue_tag ?? tD.issue_id} is a draft ticket \u2014 its worktree belongs to a periscope; end it with \`crew periscope down ${num2} --keep\` or \`--discard\`
|
|
29719
29821
|
`);
|
|
29720
29822
|
process.exit(2);
|
|
29721
29823
|
}
|
|
@@ -29725,7 +29827,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29725
29827
|
name: target.config.branch.name,
|
|
29726
29828
|
push: target.config.branch.push
|
|
29727
29829
|
}, (t) => renderBranchName(t, { key: `ISSUE-${n}`, lowercase: target.config.branch.lowercase }));
|
|
29728
|
-
const pids = pidsInWorktree(wt,
|
|
29830
|
+
const pids = pidsInWorktree(wt, num2);
|
|
29729
29831
|
if (pids.length) {
|
|
29730
29832
|
emit.emit(`${dryRun ? "would kill" : "killing"} processes still inside ${wt}: ${pids.join(" ")}`);
|
|
29731
29833
|
if (!dryRun) await killGently(pids);
|
package/package.json
CHANGED