@tablation/crew 0.129.1 → 0.130.1
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 +192 -62
- 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";
|
|
@@ -7302,6 +7314,27 @@ async function revokeSeatKeys(route2, o) {
|
|
|
7302
7314
|
}
|
|
7303
7315
|
const host = hostFromUrl2(route2.baseUrl);
|
|
7304
7316
|
const base = route2.baseUrl.replace(/\/+$/, "");
|
|
7317
|
+
const headers = { Authorization: `Bearer ${resolveApiKey(route2)}`, "User-Agent": o.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" };
|
|
7318
|
+
const sweepNamedKeys = async (role, agentId) => {
|
|
7319
|
+
const prefix = `crew ${o.shipName} `;
|
|
7320
|
+
try {
|
|
7321
|
+
const res = await doFetch(`${base}/api/workspaces/${resolved.workspaceId}/agents/${agentId}/api-keys`, { headers });
|
|
7322
|
+
if (!res.ok) return;
|
|
7323
|
+
const keys = await res.json();
|
|
7324
|
+
for (const k of Array.isArray(keys) ? keys : []) {
|
|
7325
|
+
if (!k.id || k.revokedAt || !k.name?.startsWith(prefix)) continue;
|
|
7326
|
+
if (o.dryRun) {
|
|
7327
|
+
out.push({ role, action: "revoked", detail: `dry run; key "${k.name}" found on the server` });
|
|
7328
|
+
continue;
|
|
7329
|
+
}
|
|
7330
|
+
const del2 = await doFetch(`${base}/api/workspaces/${resolved.workspaceId}/agents/${agentId}/api-keys/${k.id}`, { method: "DELETE", headers });
|
|
7331
|
+
if (del2.ok || del2.status === 404) out.push({ role, action: "revoked", detail: `key "${k.name}" found on the server, not in the keychain` });
|
|
7332
|
+
else out.push({ role, action: "failed", detail: `key "${k.name}": ${del2.status} ${del2.statusText}` });
|
|
7333
|
+
}
|
|
7334
|
+
} catch (e) {
|
|
7335
|
+
out.push({ role, action: "failed", detail: `could not sweep keys named for this ship (${e.message})` });
|
|
7336
|
+
}
|
|
7337
|
+
};
|
|
7305
7338
|
for (const { role, agentId } of targets) {
|
|
7306
7339
|
if (!agentId) {
|
|
7307
7340
|
out.push({ role, action: "no_agent" });
|
|
@@ -7312,6 +7345,7 @@ async function revokeSeatKeys(route2, o) {
|
|
|
7312
7345
|
const session = await store.get(host, slug2);
|
|
7313
7346
|
if (!session?.apiKey) {
|
|
7314
7347
|
out.push({ role, action: "not_stored" });
|
|
7348
|
+
if (o.shipName) await sweepNamedKeys(role, agentId);
|
|
7315
7349
|
continue;
|
|
7316
7350
|
}
|
|
7317
7351
|
if (!session.keyId) {
|
|
@@ -7332,6 +7366,7 @@ async function revokeSeatKeys(route2, o) {
|
|
|
7332
7366
|
}
|
|
7333
7367
|
await store.clear(host, slug2);
|
|
7334
7368
|
out.push({ role, action: "revoked", detail: del2.ok ? void 0 : "server already had no such key" });
|
|
7369
|
+
if (o.shipName) await sweepNamedKeys(role, agentId);
|
|
7335
7370
|
} catch (e) {
|
|
7336
7371
|
out.push({ role, action: "failed", detail: /Command failed|security|sk_|-w /i.test(e.message) ? describeStoreError(e) : e.message });
|
|
7337
7372
|
}
|
|
@@ -9606,8 +9641,8 @@ async function noteTakeover(tracker, result, seat, ships, myShipName, continuing
|
|
|
9606
9641
|
}
|
|
9607
9642
|
}
|
|
9608
9643
|
function originBranchSha(dir, t, rev) {
|
|
9609
|
-
const
|
|
9610
|
-
const names = [...new Set([(t.issue_tag ?? "").toLowerCase(), `issue-${
|
|
9644
|
+
const num2 = (t.issue_tag ?? t.issue_id).replace(/^\D+/, "");
|
|
9645
|
+
const names = [...new Set([(t.issue_tag ?? "").toLowerCase(), `issue-${num2}`].filter(Boolean))];
|
|
9611
9646
|
for (const n of names) {
|
|
9612
9647
|
const sha = rev(dir, `origin/${n}`);
|
|
9613
9648
|
if (sha) return sha.slice(0, 7);
|
|
@@ -9669,8 +9704,8 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9669
9704
|
unlock();
|
|
9670
9705
|
}
|
|
9671
9706
|
}
|
|
9672
|
-
const
|
|
9673
|
-
const existing = worktreeForNumber(dir,
|
|
9707
|
+
const num2 = t.issue_id.replace(/^\D+/, "");
|
|
9708
|
+
const existing = worktreeForNumber(dir, num2);
|
|
9674
9709
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9675
9710
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9676
9711
|
if (existing && !existing.prunable && existsSync21(existing.path)) {
|
|
@@ -10167,6 +10202,49 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
10167
10202
|
}
|
|
10168
10203
|
|
|
10169
10204
|
// src/agent-log.ts
|
|
10205
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : Number(v) || 0;
|
|
10206
|
+
function sumInferenceRows(rows) {
|
|
10207
|
+
const t = { calls: 0, tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: 0 };
|
|
10208
|
+
for (const r of rows) {
|
|
10209
|
+
t.calls++;
|
|
10210
|
+
t.tokensIn += num(r.input_tokens);
|
|
10211
|
+
t.tokensOut += num(r.output_tokens);
|
|
10212
|
+
t.cacheReadTokens += num(r.cache_read_tokens);
|
|
10213
|
+
t.cacheWriteTokens += num(r.cache_write_tokens);
|
|
10214
|
+
t.costUsd += num(r.cost_usd);
|
|
10215
|
+
}
|
|
10216
|
+
return t;
|
|
10217
|
+
}
|
|
10218
|
+
var INFERENCE_PAGE = 1e3;
|
|
10219
|
+
async function listInferenceRows(target, modelId, runId) {
|
|
10220
|
+
const filters = encodeURIComponent(JSON.stringify({
|
|
10221
|
+
combinator: "AND",
|
|
10222
|
+
conditions: [
|
|
10223
|
+
{ columnName: "run_id", operator: "EQ", value: runId },
|
|
10224
|
+
{ columnName: "client", operator: "EQ", value: "inference" }
|
|
10225
|
+
]
|
|
10226
|
+
}));
|
|
10227
|
+
const out = [];
|
|
10228
|
+
for (let offset = 0; ; offset += INFERENCE_PAGE) {
|
|
10229
|
+
const page = await request(
|
|
10230
|
+
target,
|
|
10231
|
+
"GET",
|
|
10232
|
+
`/data-models/${modelId}/records?limit=${INFERENCE_PAGE}&offset=${offset}&filters=${filters}`
|
|
10233
|
+
);
|
|
10234
|
+
out.push(...page);
|
|
10235
|
+
if (page.length < INFERENCE_PAGE) return out;
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
async function inferenceTotalsFor(target, runId) {
|
|
10239
|
+
const model = await request(
|
|
10240
|
+
target,
|
|
10241
|
+
"GET",
|
|
10242
|
+
`/data-models/agent_log?workspaceId=${encodeURIComponent(target.workspaceId)}`
|
|
10243
|
+
);
|
|
10244
|
+
if (!(await columnsOf(target, model.id)).has("run_id")) return void 0;
|
|
10245
|
+
const rows = await listInferenceRows(target, model.id, runId);
|
|
10246
|
+
return rows.length > 0 ? sumInferenceRows(rows) : void 0;
|
|
10247
|
+
}
|
|
10170
10248
|
var AgentLogUnreachableError = class extends Error {
|
|
10171
10249
|
};
|
|
10172
10250
|
var AgentLogHttpError = class extends Error {
|
|
@@ -10331,19 +10409,20 @@ async function reportAgentRun(target, run3) {
|
|
|
10331
10409
|
promptVersion: run3.promptVersion,
|
|
10332
10410
|
promptSha: run3.promptSha
|
|
10333
10411
|
};
|
|
10412
|
+
const { runId, ...legacyBody } = { ...body, runId: run3.runId };
|
|
10334
10413
|
const workItem = target.tickets && ticketRecordId ? { workItemTable: target.tickets.issuesModelId, workItemRecordId: ticketRecordId } : {};
|
|
10335
10414
|
let entry;
|
|
10336
10415
|
try {
|
|
10337
|
-
entry = await post(target, "/agents/log", { ...body, ...workItem, harness: run3.harness, provider: run3.provider });
|
|
10416
|
+
entry = await post(target, "/agents/log", { ...body, ...runId ? { runId } : {}, ...workItem, harness: run3.harness, provider: run3.provider });
|
|
10338
10417
|
} catch (e) {
|
|
10339
10418
|
if (!(e instanceof AgentLogHttpError)) throw new AgentLogUnreachableError(e.message);
|
|
10340
10419
|
if (e.status >= 500) throw new AgentLogUnreachableError(e.message);
|
|
10341
|
-
if (e.status !== 400 || !run3.harness && !run3.provider && !ticketRecordId) {
|
|
10420
|
+
if (e.status !== 400 || !run3.harness && !run3.provider && !ticketRecordId && !runId) {
|
|
10342
10421
|
e.row = true;
|
|
10343
10422
|
throw e;
|
|
10344
10423
|
}
|
|
10345
10424
|
try {
|
|
10346
|
-
entry = await post(target, "/agents/log",
|
|
10425
|
+
entry = await post(target, "/agents/log", legacyBody);
|
|
10347
10426
|
} catch (e2) {
|
|
10348
10427
|
if (e2 instanceof AgentLogHttpError && e2.status < 500) e2.row = true;
|
|
10349
10428
|
throw e2;
|
|
@@ -10362,10 +10441,21 @@ async function reportAgentRun(target, run3) {
|
|
|
10362
10441
|
const bounces = await countAcceptanceBounces(target, target.tickets.issuesModelId, ticketRecordId);
|
|
10363
10442
|
if (bounces !== null) await writeHandoffBounces(target, entry.id, bounces);
|
|
10364
10443
|
}
|
|
10365
|
-
|
|
10444
|
+
let counterWarning;
|
|
10445
|
+
let counted = run3;
|
|
10446
|
+
if (runId && target.tickets && ticketRecordId) {
|
|
10447
|
+
try {
|
|
10448
|
+
const totals = await inferenceTotalsFor(target, runId);
|
|
10449
|
+
if (totals) counted = { ...run3, tokensIn: totals.tokensIn, tokensOut: totals.tokensOut, cacheReadTokens: totals.cacheReadTokens, cacheWriteTokens: totals.cacheWriteTokens, costUsd: totals.costUsd };
|
|
10450
|
+
else counted = { ...run3, ...run3.harnessTotals };
|
|
10451
|
+
} catch (e) {
|
|
10452
|
+
counterWarning = `could not sum run ${runId}'s inference rows for ${run3.ticketReference}: ${e.message}`;
|
|
10453
|
+
}
|
|
10454
|
+
}
|
|
10455
|
+
const counters = target.tickets && ticketRecordId ? await addRunToTicket(target, target.tickets, ticketRecordId, counted) : void 0;
|
|
10366
10456
|
return {
|
|
10367
10457
|
agentLogId: entry.id,
|
|
10368
|
-
...counters?.warning ? { counterWarning: counters.
|
|
10458
|
+
...counters?.warning || counterWarning ? { counterWarning: [counterWarning, counters?.warning].filter(Boolean).join("; ") } : {},
|
|
10369
10459
|
...counters?.retry ? { counterRetry: counters.retry } : {}
|
|
10370
10460
|
};
|
|
10371
10461
|
}
|
|
@@ -11941,6 +12031,7 @@ function planAgentRun(o) {
|
|
|
11941
12031
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
11942
12032
|
const { model } = seat;
|
|
11943
12033
|
let toolchain = seat.toolchain;
|
|
12034
|
+
let workspaceRunId;
|
|
11944
12035
|
if (toolchain.auth === "workspace") {
|
|
11945
12036
|
if (!o.apiKey || o.seatKind === "ship") {
|
|
11946
12037
|
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 +12039,8 @@ function planAgentRun(o) {
|
|
|
11948
12039
|
if (!o.route.resolved) {
|
|
11949
12040
|
throw new WorkspaceAuthRefused(`${o.role}: toolchain ${toolchain.name} has auth: workspace, but route ${o.route.route} is not connected (run \`crew connect\`)`);
|
|
11950
12041
|
}
|
|
11951
|
-
|
|
12042
|
+
workspaceRunId = o.runId ?? randomUUID();
|
|
12043
|
+
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey, ticket: o.ticket, runId: workspaceRunId } };
|
|
11952
12044
|
}
|
|
11953
12045
|
const adapter = adapterFor(toolchain.harness);
|
|
11954
12046
|
const code = codeBridgeFor(o, adapter.harness);
|
|
@@ -12045,7 +12137,8 @@ function planAgentRun(o) {
|
|
|
12045
12137
|
routeName: o.route.route,
|
|
12046
12138
|
agentLog,
|
|
12047
12139
|
promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
|
|
12048
|
-
promptSha
|
|
12140
|
+
promptSha,
|
|
12141
|
+
...workspaceRunId ? { workspaceRunId } : {}
|
|
12049
12142
|
};
|
|
12050
12143
|
}
|
|
12051
12144
|
function describePlan(p) {
|
|
@@ -12099,7 +12192,8 @@ function identityOf(plan, adapterHarness) {
|
|
|
12099
12192
|
streamPath: plan.streamPath,
|
|
12100
12193
|
agentLog: plan.agentLog ? withoutKey(plan.agentLog) : void 0,
|
|
12101
12194
|
promptVersion: plan.promptVersion,
|
|
12102
|
-
promptSha: plan.promptSha
|
|
12195
|
+
promptSha: plan.promptSha,
|
|
12196
|
+
...plan.workspaceRunId ? { workspaceRunId: plan.workspaceRunId } : {}
|
|
12103
12197
|
};
|
|
12104
12198
|
}
|
|
12105
12199
|
async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMinutes) {
|
|
@@ -12406,11 +12500,22 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
12406
12500
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12407
12501
|
cycles: obs.cycles,
|
|
12408
12502
|
model: id.model,
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12503
|
+
// CREW-2980: under `auth: workspace` the endpoint's rows are the accounting; the run row
|
|
12504
|
+
// keeps turns only, and `reportAgentRun` sums those rows into the ticket counters.
|
|
12505
|
+
...id.workspaceRunId ? { runId: id.workspaceRunId } : {
|
|
12506
|
+
tokensIn: result?.inputTokens,
|
|
12507
|
+
tokensOut: result?.outputTokens,
|
|
12508
|
+
cacheReadTokens: result?.cacheReadTokens,
|
|
12509
|
+
cacheWriteTokens: result?.cacheWriteTokens,
|
|
12510
|
+
costUsd: result?.costUsd
|
|
12511
|
+
},
|
|
12512
|
+
...id.workspaceRunId ? { harnessTotals: {
|
|
12513
|
+
tokensIn: result?.inputTokens,
|
|
12514
|
+
tokensOut: result?.outputTokens,
|
|
12515
|
+
cacheReadTokens: result?.cacheReadTokens,
|
|
12516
|
+
cacheWriteTokens: result?.cacheWriteTokens,
|
|
12517
|
+
costUsd: result?.costUsd
|
|
12518
|
+
} } : {},
|
|
12414
12519
|
// Both come from a finished run: without the result line (interrupted) neither is trustworthy.
|
|
12415
12520
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
12416
12521
|
...result?.numTurns !== void 0 && obs.prefixTokens !== void 0 ? { prefixTokens: obs.prefixTokens } : {},
|
|
@@ -12537,7 +12642,8 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
12537
12642
|
eventsPath: plan.eventsPath,
|
|
12538
12643
|
agentLog: id.agentLog,
|
|
12539
12644
|
promptVersion: plan.promptVersion,
|
|
12540
|
-
promptSha: plan.promptSha
|
|
12645
|
+
promptSha: plan.promptSha,
|
|
12646
|
+
...plan.workspaceRunId ? { workspaceRunId: plan.workspaceRunId } : {}
|
|
12541
12647
|
};
|
|
12542
12648
|
try {
|
|
12543
12649
|
writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
@@ -12792,14 +12898,14 @@ function describeRun(run3, route2, seatIds, worktreeOf = (d, n) => {
|
|
|
12792
12898
|
}) {
|
|
12793
12899
|
const ownRoute = run3.route === routeSlug(route2.route);
|
|
12794
12900
|
const repoDir = run3.repo ? reposOf(route2).find((t) => t.name === run3.repo)?.dir : void 0;
|
|
12795
|
-
const
|
|
12901
|
+
const num2 = run3.ticket?.replace(/^\D+/, "") ?? "";
|
|
12796
12902
|
return {
|
|
12797
12903
|
role: run3.role,
|
|
12798
12904
|
seat: ownRoute ? seatIds[run3.role] ?? null : null,
|
|
12799
12905
|
ticket: run3.ticket,
|
|
12800
12906
|
ticketRecordId: run3.ticketRecordId,
|
|
12801
12907
|
repo: run3.repo ?? null,
|
|
12802
|
-
worktree: repoDir &&
|
|
12908
|
+
worktree: repoDir && num2 ? worktreeOf(repoDir, num2) : null,
|
|
12803
12909
|
stream: run3.streamPath,
|
|
12804
12910
|
pid: run3.pid,
|
|
12805
12911
|
startedAt: run3.spawnedAt,
|
|
@@ -12821,7 +12927,12 @@ function labeledDocker() {
|
|
|
12821
12927
|
if (!dockerAvailable()) return { volumes: [], images: [] };
|
|
12822
12928
|
return {
|
|
12823
12929
|
volumes: lines(docker(["volume", "ls", "-q", "--filter", `label=${DOCKER_LABEL_OWNER}`, "--filter", `label=${DOCKER_LABEL_CACHE}`])),
|
|
12824
|
-
|
|
12930
|
+
// CREW-2984: the repository and ship layers are tagged `crew-sandbox/<name>:<hash>` but carry only an `io.crew.sandbox.*` label,
|
|
12931
|
+
// so the label filter alone missed them. The tag namespace is crew's own; the base and crew layers are `crew-sandbox-base`/`-crew`.
|
|
12932
|
+
images: [.../* @__PURE__ */ new Set([
|
|
12933
|
+
...lines(docker(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", "--filter", "reference=crew-sandbox/*"])),
|
|
12934
|
+
...lines(docker(["image", "ls", "-q", "--filter", `label=${DOCKER_LABEL_OWNER}`, "--filter", `label=${DOCKER_LABEL_REPO_LAYER}`]))
|
|
12935
|
+
])]
|
|
12825
12936
|
};
|
|
12826
12937
|
}
|
|
12827
12938
|
function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
@@ -12855,8 +12966,8 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12855
12966
|
const tracker = new Tracker(route2, cfg.ship, { apiKey: identity.apiKey });
|
|
12856
12967
|
const t = await tracker.ticket(run3.ticketRecordId);
|
|
12857
12968
|
const target = repoTargetFor(route2, t.repo_id);
|
|
12858
|
-
const
|
|
12859
|
-
const wt = target &&
|
|
12969
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
12970
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
12860
12971
|
let dirty = null;
|
|
12861
12972
|
try {
|
|
12862
12973
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -18132,10 +18243,10 @@ function dedupeChangelogEntries(entries, ticket) {
|
|
|
18132
18243
|
let key = line;
|
|
18133
18244
|
if (ref) {
|
|
18134
18245
|
const prefix = ref[1];
|
|
18135
|
-
const
|
|
18136
|
-
const canonical = own && own[2] ===
|
|
18137
|
-
line = `${canonical}-${
|
|
18138
|
-
key = `${canonical}-${
|
|
18246
|
+
const num2 = ref[2];
|
|
18247
|
+
const canonical = own && own[2] === num2 ? own[1] : prefix;
|
|
18248
|
+
line = `${canonical}-${num2}${line.slice(ref[0].length)}`;
|
|
18249
|
+
key = `${canonical}-${num2}`;
|
|
18139
18250
|
}
|
|
18140
18251
|
if (seen.has(key)) continue;
|
|
18141
18252
|
seen.add(key);
|
|
@@ -21343,10 +21454,24 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21343
21454
|
} catch (e) {
|
|
21344
21455
|
throw new LogbookError(`no Agent Log entry ${entryId} in this workspace (${e.message})`);
|
|
21345
21456
|
}
|
|
21346
|
-
|
|
21457
|
+
let inference;
|
|
21458
|
+
if (entry.run_id && entry.client !== "inference") {
|
|
21459
|
+
try {
|
|
21460
|
+
const rows = await listInferenceRows(
|
|
21461
|
+
{ baseUrl: route2.baseUrl, apiKey: resolveApiKey(route2), userAgent: opts.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" },
|
|
21462
|
+
agentLogModel.id,
|
|
21463
|
+
entry.run_id
|
|
21464
|
+
);
|
|
21465
|
+
if (rows.length > 0) inference = sumInferenceRows(rows);
|
|
21466
|
+
} catch {
|
|
21467
|
+
}
|
|
21468
|
+
}
|
|
21469
|
+
const withInference = inference ? { inference } : {};
|
|
21470
|
+
if (!opts.withPrompt) return { entry, ...withInference, reconstruction: { status: "not_recorded", reason: "not requested" } };
|
|
21347
21471
|
if (!entry.agent_id || !entry.prompt_version) {
|
|
21348
21472
|
return {
|
|
21349
21473
|
entry,
|
|
21474
|
+
...withInference,
|
|
21350
21475
|
reconstruction: {
|
|
21351
21476
|
status: "not_recorded",
|
|
21352
21477
|
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 +21483,13 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21358
21483
|
try {
|
|
21359
21484
|
historyEntries = await fetchHistoryEntries(route2, agentsModel.id, entry.agent_id, opts.userAgent, 200);
|
|
21360
21485
|
} catch (e) {
|
|
21361
|
-
return { entry, reconstruction: { status: "approximate", reason: `history lookup failed: ${e.message}` } };
|
|
21486
|
+
return { entry, ...withInference, reconstruction: { status: "approximate", reason: `history lookup failed: ${e.message}` } };
|
|
21362
21487
|
}
|
|
21363
21488
|
const match = historyEntries.find((h) => h.history_id === entry.prompt_version);
|
|
21364
21489
|
if (!match) {
|
|
21365
21490
|
return {
|
|
21366
21491
|
entry,
|
|
21492
|
+
...withInference,
|
|
21367
21493
|
reconstruction: {
|
|
21368
21494
|
status: "approximate",
|
|
21369
21495
|
reason: `history entry ${entry.prompt_version} no longer exists on the Agents row (purged, or the row itself was recreated)`
|
|
@@ -21375,6 +21501,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
21375
21501
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
21376
21502
|
return {
|
|
21377
21503
|
entry,
|
|
21504
|
+
...withInference,
|
|
21378
21505
|
reconstruction: {
|
|
21379
21506
|
status: exact ? "exact" : "approximate",
|
|
21380
21507
|
historyId: match.history_id,
|
|
@@ -21998,8 +22125,8 @@ var BRANCH_STEM = /^(?:.*\/)?([^/]+?-\d+)(?:-.*)?$/;
|
|
|
21998
22125
|
function ticketForWorktree(w, byNumber, contract) {
|
|
21999
22126
|
if (w.branch) {
|
|
22000
22127
|
const stem = BRANCH_STEM.exec(w.branch)?.[1];
|
|
22001
|
-
const
|
|
22002
|
-
const t =
|
|
22128
|
+
const num2 = stem && /-(\d+)$/.exec(stem)?.[1];
|
|
22129
|
+
const t = num2 ? byNumber.get(num2) : void 0;
|
|
22003
22130
|
if (t && ticketStems(t).has(stem)) return t;
|
|
22004
22131
|
}
|
|
22005
22132
|
const dir = basename13(w.path);
|
|
@@ -22179,11 +22306,11 @@ function deployedBranchStems(tickets, contract) {
|
|
|
22179
22306
|
}
|
|
22180
22307
|
function ticketStems(t) {
|
|
22181
22308
|
const stems = /* @__PURE__ */ new Set();
|
|
22182
|
-
const
|
|
22183
|
-
const unpadded = String(Number.parseInt(
|
|
22309
|
+
const num2 = t.issue_id.replace(/^\D+/, "");
|
|
22310
|
+
const unpadded = String(Number.parseInt(num2, 10));
|
|
22184
22311
|
const prefixes = ["issue", t.project_issue_prefix?.toLowerCase()].filter((p) => !!p);
|
|
22185
22312
|
for (const p of prefixes) {
|
|
22186
|
-
stems.add(`${p}-${
|
|
22313
|
+
stems.add(`${p}-${num2}`);
|
|
22187
22314
|
stems.add(`${p}-${unpadded}`);
|
|
22188
22315
|
}
|
|
22189
22316
|
return stems;
|
|
@@ -24161,8 +24288,8 @@ async function reconcileForRoute(ctx, o) {
|
|
|
24161
24288
|
if (!memberId) return;
|
|
24162
24289
|
const t = await tracker.ticket(sc.ticketRecordId);
|
|
24163
24290
|
const target = repoTargetFor(route2, t.repo_id);
|
|
24164
|
-
const
|
|
24165
|
-
const wt = target &&
|
|
24291
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
24292
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
24166
24293
|
let dirty = null;
|
|
24167
24294
|
try {
|
|
24168
24295
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -24196,8 +24323,8 @@ async function postRunKilled(route2, tracker, ticketId, memberId, limitMinutes,
|
|
|
24196
24323
|
try {
|
|
24197
24324
|
const t = await tracker.ticket(ticketId);
|
|
24198
24325
|
const target = repoTargetFor(route2, t.repo_id);
|
|
24199
|
-
const
|
|
24200
|
-
const wt = target &&
|
|
24326
|
+
const num2 = String(t[tracker.contract.columns.key] ?? "").replace(/^\D+/, "");
|
|
24327
|
+
const wt = target && num2 ? worktreeForNumber(target.dir, num2) : null;
|
|
24201
24328
|
let dirty = null;
|
|
24202
24329
|
try {
|
|
24203
24330
|
if (wt) dirty = status(wt.path).length > 0;
|
|
@@ -25545,10 +25672,10 @@ async function handlePeriscopeMcp(d, req, res, segment) {
|
|
|
25545
25672
|
function findPeriscope(sidecars, segment) {
|
|
25546
25673
|
const m = /^(?:([A-Za-z]+)-)?(\d+)$/.exec(segment);
|
|
25547
25674
|
if (!m) return void 0;
|
|
25548
|
-
const
|
|
25675
|
+
const num2 = String(Number(m[2]));
|
|
25549
25676
|
return sidecars.find((sc) => {
|
|
25550
25677
|
const t = /^(?:([A-Za-z]+)-)?0*(\d+)$/.exec(sc.ticket);
|
|
25551
|
-
if (!t || t[2] !==
|
|
25678
|
+
if (!t || t[2] !== num2) return false;
|
|
25552
25679
|
return !m[1] || !t[1] || m[1].toLowerCase() === t[1].toLowerCase();
|
|
25553
25680
|
});
|
|
25554
25681
|
}
|
|
@@ -28113,7 +28240,7 @@ ${cu.tail.trimEnd()}
|
|
|
28113
28240
|
}
|
|
28114
28241
|
const { personaResult, skillResult } = run3;
|
|
28115
28242
|
if (flag("revoke-seat-keys")) {
|
|
28116
|
-
for (const o of await revokeSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun })) {
|
|
28243
|
+
for (const o of await revokeSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun, shipName: cfg.ship.name })) {
|
|
28117
28244
|
process.stdout.write(`${describeRevokeOutcome(o)}
|
|
28118
28245
|
`);
|
|
28119
28246
|
}
|
|
@@ -28636,7 +28763,7 @@ ${cu.tail.trimEnd()}
|
|
|
28636
28763
|
);
|
|
28637
28764
|
}
|
|
28638
28765
|
} else {
|
|
28639
|
-
const { entry, reconstruction } = await showLogEntry(target, entryId, {
|
|
28766
|
+
const { entry, inference, reconstruction } = await showLogEntry(target, entryId, {
|
|
28640
28767
|
userAgent: cfg.ship.userAgent,
|
|
28641
28768
|
withPrompt: flag("prompt")
|
|
28642
28769
|
});
|
|
@@ -28650,6 +28777,9 @@ model: ${entry.model ?? "?"}
|
|
|
28650
28777
|
harness: ${entry.harness ?? "?"}
|
|
28651
28778
|
provider: ${entry.provider ?? "?"}
|
|
28652
28779
|
` + (entry.num_turns != null ? `turns: ${entry.num_turns}
|
|
28780
|
+
` : "") + (inference ? `calls: ${inference.calls}
|
|
28781
|
+
tokens: in ${inference.tokensIn}, out ${inference.tokensOut}, cache read ${inference.cacheReadTokens}, cache write ${inference.cacheWriteTokens}
|
|
28782
|
+
cost: $${inference.costUsd.toFixed(4)}
|
|
28653
28783
|
` : "") + (entry.prefix_tokens != null ? `prefix: ${entry.prefix_tokens} tokens
|
|
28654
28784
|
` : "") + (formatBeforeEdit(entry) ? `before edit: ${formatBeforeEdit(entry)}
|
|
28655
28785
|
` : "")
|
|
@@ -29692,7 +29822,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29692
29822
|
process.stderr.write("drop: need a ticket number (326, ISSUE-326, or TABL-326)\n");
|
|
29693
29823
|
process.exit(2);
|
|
29694
29824
|
}
|
|
29695
|
-
const
|
|
29825
|
+
const num2 = Number(n);
|
|
29696
29826
|
const repos = await resolvedRepos(route2);
|
|
29697
29827
|
const candidates2 = repos.map((r) => ({ repo: r, found: worktreeForNumber(r.dir, n) })).filter((c) => c.found !== null);
|
|
29698
29828
|
if (candidates2.length === 0) {
|
|
@@ -29713,9 +29843,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29713
29843
|
const wt = found.path;
|
|
29714
29844
|
try {
|
|
29715
29845
|
const trD = new Tracker(route2, cfg.ship);
|
|
29716
|
-
const tD = (await trD.ticketsByNumbers([n])).find((t) => t.issue_id.replace(/^\D+/, "") === String(
|
|
29846
|
+
const tD = (await trD.ticketsByNumbers([n])).find((t) => t.issue_id.replace(/^\D+/, "") === String(num2));
|
|
29717
29847
|
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 ${
|
|
29848
|
+
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
29849
|
`);
|
|
29720
29850
|
process.exit(2);
|
|
29721
29851
|
}
|
|
@@ -29725,7 +29855,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29725
29855
|
name: target.config.branch.name,
|
|
29726
29856
|
push: target.config.branch.push
|
|
29727
29857
|
}, (t) => renderBranchName(t, { key: `ISSUE-${n}`, lowercase: target.config.branch.lowercase }));
|
|
29728
|
-
const pids = pidsInWorktree(wt,
|
|
29858
|
+
const pids = pidsInWorktree(wt, num2);
|
|
29729
29859
|
if (pids.length) {
|
|
29730
29860
|
emit.emit(`${dryRun ? "would kill" : "killing"} processes still inside ${wt}: ${pids.join(" ")}`);
|
|
29731
29861
|
if (!dryRun) await killGently(pids);
|
|
@@ -30483,7 +30613,7 @@ ${where.join("\n")}`);
|
|
|
30483
30613
|
},
|
|
30484
30614
|
removePair: removePairEntries,
|
|
30485
30615
|
async revokeSeatKeys(r, dry) {
|
|
30486
|
-
const outcomes = await revokeSeatKeys(r, { userAgent: cfg.ship.userAgent, dryRun: dry });
|
|
30616
|
+
const outcomes = await revokeSeatKeys(r, { userAgent: cfg.ship.userAgent, dryRun: dry, shipName: cfg.ship.name });
|
|
30487
30617
|
return outcomes.map((o) => ({ line: describeRevokeOutcome(o), ok: o.action !== "failed" }));
|
|
30488
30618
|
},
|
|
30489
30619
|
async credential(r) {
|
package/package.json
CHANGED