@tablation/crew 0.78.0 → 0.78.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 +77 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -230,6 +230,13 @@ function extractTurnTokens(line) {
|
|
|
230
230
|
if (!usage2) return void 0;
|
|
231
231
|
return (usage2.input_tokens ?? 0) + (usage2.output_tokens ?? 0) + (usage2.cache_read_input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
|
|
232
232
|
}
|
|
233
|
+
function extractPrefixTokens(line) {
|
|
234
|
+
const obj = line;
|
|
235
|
+
if (!obj || obj.type !== "assistant") return void 0;
|
|
236
|
+
const usage2 = obj.message?.usage;
|
|
237
|
+
if (!usage2) return void 0;
|
|
238
|
+
return (usage2.input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
|
|
239
|
+
}
|
|
233
240
|
function extractResult(line) {
|
|
234
241
|
const obj = line;
|
|
235
242
|
if (!obj || obj.type !== "result") return void 0;
|
|
@@ -580,6 +587,7 @@ var claudeAdapter = {
|
|
|
580
587
|
mapStreamLine,
|
|
581
588
|
extractResult,
|
|
582
589
|
extractTurnTokens,
|
|
590
|
+
extractPrefixTokens,
|
|
583
591
|
inspectMcp(o) {
|
|
584
592
|
const s = inspectClaudeMcp(o);
|
|
585
593
|
return s.state === "no-claude" ? { state: "no-binary" } : s;
|
|
@@ -5114,6 +5122,8 @@ async function reportAgentRun(target, run2) {
|
|
|
5114
5122
|
cacheReadTokens: run2.cacheReadTokens,
|
|
5115
5123
|
cacheWriteTokens: run2.cacheWriteTokens,
|
|
5116
5124
|
costUsd: run2.costUsd,
|
|
5125
|
+
numTurns: run2.numTurns,
|
|
5126
|
+
prefixTokens: run2.prefixTokens,
|
|
5117
5127
|
promptVersion: run2.promptVersion,
|
|
5118
5128
|
promptSha: run2.promptSha
|
|
5119
5129
|
};
|
|
@@ -5271,24 +5281,31 @@ function ticketMentions(text) {
|
|
|
5271
5281
|
}
|
|
5272
5282
|
var TicketAttributionTracker = class {
|
|
5273
5283
|
#tokensByTicket = /* @__PURE__ */ new Map();
|
|
5284
|
+
#turns = 0;
|
|
5274
5285
|
#current;
|
|
5275
5286
|
/** Tracker record uuid → ticket key, learned when a write names both (a claim resolves the key from the last mention). */
|
|
5276
5287
|
#keyByRecord = /* @__PURE__ */ new Map();
|
|
5277
5288
|
#lastBy = {};
|
|
5278
|
-
/**
|
|
5289
|
+
/**
|
|
5290
|
+
* The ticket key most recently named in the transcript, or undefined before any.
|
|
5291
|
+
* Only the FIRST key in a turn counts: a closing summary names its own
|
|
5292
|
+
* ticket first and then lists others ("Other tickets", "Blocked by", "See
|
|
5293
|
+
* also"), and those trailing keys are not what the run worked (CREW-2598).
|
|
5294
|
+
*/
|
|
5279
5295
|
get current() {
|
|
5280
5296
|
return this.#current;
|
|
5281
5297
|
}
|
|
5282
5298
|
/** Record the mentions in `text` without crediting tokens (a claim needs "which ticket" before the turn's usage line arrives). */
|
|
5283
5299
|
note(text) {
|
|
5284
5300
|
const mentions = ticketMentions(text);
|
|
5285
|
-
if (mentions.length > 0) this.#current = mentions[
|
|
5301
|
+
if (mentions.length > 0) this.#current = mentions[0];
|
|
5286
5302
|
}
|
|
5287
5303
|
/** One assistant turn: its combined visible text (thinking + text blocks) and its own token cost. */
|
|
5288
5304
|
add(text, tokens) {
|
|
5289
5305
|
const mentions = ticketMentions(text);
|
|
5290
|
-
if (mentions.length > 0) this.#current = mentions[
|
|
5306
|
+
if (mentions.length > 0) this.#current = mentions[0];
|
|
5291
5307
|
if (this.#current === void 0 || tokens <= 0) return;
|
|
5308
|
+
this.#turns++;
|
|
5292
5309
|
this.#tokensByTicket.set(this.#current, (this.#tokensByTicket.get(this.#current) ?? 0) + tokens);
|
|
5293
5310
|
}
|
|
5294
5311
|
/**
|
|
@@ -5310,16 +5327,31 @@ var TicketAttributionTracker = class {
|
|
|
5310
5327
|
/**
|
|
5311
5328
|
* How the attribution is decided: a hand-off write beats a claim, a claim
|
|
5312
5329
|
* beats a comment (so a follow-up ticket filed or commented on while
|
|
5313
|
-
* working never displaces the claimed one), and any write beats
|
|
5314
|
-
*
|
|
5315
|
-
*
|
|
5330
|
+
* working never displaces the claimed one), and any write beats everything
|
|
5331
|
+
* below. Next, `hintMoved` — the post-run read-back showed the hinted
|
|
5332
|
+
* ticket changed status or assignee during the run — is a hand-off signal
|
|
5333
|
+
* for a run that wrote through MCP tools crew cannot see. Then the
|
|
5334
|
+
* mention-token heuristic, which with a hint may override it only when a
|
|
5335
|
+
* different ticket holds a strict majority of the credited tokens (a single
|
|
5336
|
+
* credited turn, like a lone end-of-run summary naming another ticket,
|
|
5337
|
+
* must not). A run with no hint
|
|
5338
|
+
* keeps the plain winner. The pre-run hint is last.
|
|
5316
5339
|
*/
|
|
5317
|
-
decide(hint) {
|
|
5340
|
+
decide(hint, opts = {}) {
|
|
5318
5341
|
for (const by of ["handoff", "claim", "comment"]) {
|
|
5319
5342
|
const ticket = this.#lastBy[by];
|
|
5320
5343
|
if (ticket) return { ticket, by };
|
|
5321
5344
|
}
|
|
5322
|
-
if (
|
|
5345
|
+
if (hint && opts.hintMoved) return { ticket: hint, by: "readback" };
|
|
5346
|
+
if (this.#tokensByTicket.size > 0) {
|
|
5347
|
+
if (!hint) return { ticket: this.winner(), by: "mentions" };
|
|
5348
|
+
const winner = this.winner(hint);
|
|
5349
|
+
if (winner === hint) return { ticket: hint, by: "mentions" };
|
|
5350
|
+
let total = 0;
|
|
5351
|
+
for (const n of this.#tokensByTicket.values()) total += n;
|
|
5352
|
+
const share = (this.#tokensByTicket.get(winner) ?? 0) / total;
|
|
5353
|
+
return share > 0.5 && this.#turns > 1 ? { ticket: winner, by: "mentions" } : { ticket: hint, by: "hint" };
|
|
5354
|
+
}
|
|
5323
5355
|
return { ticket: hint, by: "hint" };
|
|
5324
5356
|
}
|
|
5325
5357
|
/**
|
|
@@ -8467,6 +8499,8 @@ var RunObserver = class {
|
|
|
8467
8499
|
/** Hand-off writes and their outcomes, to tell a run whose hand-off never landed (CREW-2363). */
|
|
8468
8500
|
handoff = new HandoffTranscriptTracker();
|
|
8469
8501
|
result;
|
|
8502
|
+
/** Prefix tokens of the first assistant call that carried usage; kept once. */
|
|
8503
|
+
prefixTokens;
|
|
8470
8504
|
/** Set once the stream carries the harness's final result (turn count or cost). */
|
|
8471
8505
|
sawResult = false;
|
|
8472
8506
|
muted = false;
|
|
@@ -8593,6 +8627,7 @@ var RunObserver = class {
|
|
|
8593
8627
|
}
|
|
8594
8628
|
}
|
|
8595
8629
|
}
|
|
8630
|
+
if (this.prefixTokens === void 0 && parsed !== void 0) this.prefixTokens = this.adapter.extractPrefixTokens?.(parsed);
|
|
8596
8631
|
const turnTokens = parsed === void 0 ? void 0 : this.adapter.extractTurnTokens(parsed);
|
|
8597
8632
|
if (turnTokens !== void 0) this.attribution.add(turnText, turnTokens);
|
|
8598
8633
|
}
|
|
@@ -8616,8 +8651,15 @@ function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
|
8616
8651
|
} catch {
|
|
8617
8652
|
}
|
|
8618
8653
|
}
|
|
8619
|
-
async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
8620
|
-
|
|
8654
|
+
async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved) {
|
|
8655
|
+
let moved = false;
|
|
8656
|
+
if (hintMoved) {
|
|
8657
|
+
try {
|
|
8658
|
+
moved = await hintMoved();
|
|
8659
|
+
} catch {
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
const { ticket: attributedTicket, by: attributedBy } = obs.attribution.decide(obs.liveTicket, { hintMoved: moved });
|
|
8621
8663
|
const identity = { toolchain: id.toolchain, harness: id.harness, provider: id.provider };
|
|
8622
8664
|
const { result } = obs;
|
|
8623
8665
|
const pending = end.interrupted ? null : obs.handoff.pending();
|
|
@@ -8649,6 +8691,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8649
8691
|
...pending ? { handoff: "pending" } : {},
|
|
8650
8692
|
sessionId: result?.sessionId ?? null,
|
|
8651
8693
|
numTurns: result?.numTurns ?? null,
|
|
8694
|
+
prefixTokens: obs.prefixTokens ?? null,
|
|
8652
8695
|
totalCostUsd: result?.costUsd ?? null
|
|
8653
8696
|
});
|
|
8654
8697
|
} catch {
|
|
@@ -8661,6 +8704,8 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8661
8704
|
...pending ? { handoff: "pending" } : {},
|
|
8662
8705
|
...end.interrupted ? { interrupted: end.interrupted } : {},
|
|
8663
8706
|
...identity,
|
|
8707
|
+
attributedTicket: attributedTicket ?? null,
|
|
8708
|
+
attributedBy,
|
|
8664
8709
|
...result?.sessionId ? { sessionId: result.sessionId } : {},
|
|
8665
8710
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
8666
8711
|
...result?.costUsd !== void 0 ? { totalCostUsd: result.costUsd } : {}
|
|
@@ -8679,6 +8724,9 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8679
8724
|
cacheReadTokens: result?.cacheReadTokens,
|
|
8680
8725
|
cacheWriteTokens: result?.cacheWriteTokens,
|
|
8681
8726
|
costUsd: result?.costUsd,
|
|
8727
|
+
// Both come from a finished run: without the result line (interrupted) neither is trustworthy.
|
|
8728
|
+
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
8729
|
+
...result?.numTurns !== void 0 && obs.prefixTokens !== void 0 ? { prefixTokens: obs.prefixTokens } : {},
|
|
8682
8730
|
harness: identity.harness,
|
|
8683
8731
|
provider: identity.provider,
|
|
8684
8732
|
promptVersion: id.promptVersion,
|
|
@@ -8723,7 +8771,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8723
8771
|
}
|
|
8724
8772
|
return { ms: ms2, ticket: attributedTicket, handoffPending: !!pending };
|
|
8725
8773
|
}
|
|
8726
|
-
function spawnAgent(plan, emit) {
|
|
8774
|
+
function spawnAgent(plan, emit, hintMoved) {
|
|
8727
8775
|
const adapter = adapterFor(plan.harness ?? LEGACY_TOOLCHAIN_HARNESS);
|
|
8728
8776
|
const started = Date.now();
|
|
8729
8777
|
const startedAt = new Date(started).toISOString();
|
|
@@ -8788,7 +8836,7 @@ function spawnAgent(plan, emit) {
|
|
|
8788
8836
|
obs.end();
|
|
8789
8837
|
await endSink(eventsSink);
|
|
8790
8838
|
if (spawnFailed) return;
|
|
8791
|
-
const { ms: ms2, handoffPending } = await finishRun(id, obs, emit, startedAt, { code: code ?? 1 }, plan.agentLog?.apiKey);
|
|
8839
|
+
const { ms: ms2, handoffPending } = await finishRun(id, obs, emit, startedAt, { code: code ?? 1 }, plan.agentLog?.apiKey, hintMoved);
|
|
8792
8840
|
resolve6({ code: code ?? 1, ms: ms2, ...handoffPending ? { handoffPending } : {} });
|
|
8793
8841
|
});
|
|
8794
8842
|
child.stdin?.on("error", (err) => {
|
|
@@ -16914,7 +16962,19 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
16914
16962
|
}
|
|
16915
16963
|
try {
|
|
16916
16964
|
const runStart = (/* @__PURE__ */ new Date()).toISOString();
|
|
16917
|
-
|
|
16965
|
+
let hintMoved;
|
|
16966
|
+
if (workingId) {
|
|
16967
|
+
try {
|
|
16968
|
+
const before = await tracker.ticket(workingId);
|
|
16969
|
+
const col = tracker.contract.columns;
|
|
16970
|
+
hintMoved = async () => {
|
|
16971
|
+
const after = await tracker.ticket(workingId);
|
|
16972
|
+
return after[col.status] !== before[col.status] || after[col.assignee] !== before[col.assignee];
|
|
16973
|
+
};
|
|
16974
|
+
} catch {
|
|
16975
|
+
}
|
|
16976
|
+
}
|
|
16977
|
+
await spawnAgent(plan, emit, hintMoved);
|
|
16918
16978
|
await guardRunHandoff(route, ship, tracker, role, workingId, emit, runStart);
|
|
16919
16979
|
} finally {
|
|
16920
16980
|
if (memberId) {
|
|
@@ -19383,7 +19443,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
19383
19443
|
}
|
|
19384
19444
|
for (const e of entries) {
|
|
19385
19445
|
process.stdout.write(
|
|
19386
|
-
`${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 ")})` : ""}
|
|
19446
|
+
`${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 ")})` : ""}${e.num_turns != null ? ` turns ${e.num_turns}` : ""}${e.prefix_tokens != null ? ` prefix ${e.prefix_tokens}` : ""}
|
|
19387
19447
|
`
|
|
19388
19448
|
);
|
|
19389
19449
|
}
|
|
@@ -19401,7 +19461,9 @@ ticket: ${entry.ticket_reference ?? "(none)"}
|
|
|
19401
19461
|
model: ${entry.model ?? "?"}
|
|
19402
19462
|
harness: ${entry.harness ?? "?"}
|
|
19403
19463
|
provider: ${entry.provider ?? "?"}
|
|
19404
|
-
`
|
|
19464
|
+
` + (entry.num_turns != null ? `turns: ${entry.num_turns}
|
|
19465
|
+
` : "") + (entry.prefix_tokens != null ? `prefix: ${entry.prefix_tokens} tokens
|
|
19466
|
+
` : "")
|
|
19405
19467
|
);
|
|
19406
19468
|
if (flag("prompt")) {
|
|
19407
19469
|
process.stdout.write(`
|
package/package.json
CHANGED