@tablation/crew 0.50.0 → 0.50.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 +95 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -240,7 +240,7 @@ function mapStreamLine(line) {
|
|
|
240
240
|
if (block.type === "thinking") out.push({ kind: "thought", text: block.thinking });
|
|
241
241
|
else if (block.type === "text") out.push({ kind: "text", text: block.text });
|
|
242
242
|
else if (block.type === "tool_use") {
|
|
243
|
-
out.push({ kind: "tool", tool: block.name, target: toolTarget(block.name, block.input) });
|
|
243
|
+
out.push({ kind: "tool", tool: block.name, target: toolTarget(block.name, block.input), input: block.input });
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
return out;
|
|
@@ -8161,6 +8161,15 @@ function ticketMentions(text) {
|
|
|
8161
8161
|
var TicketAttributionTracker = class {
|
|
8162
8162
|
#tokensByTicket = /* @__PURE__ */ new Map();
|
|
8163
8163
|
#current;
|
|
8164
|
+
/** The ticket key most recently named in the transcript, or undefined before any. */
|
|
8165
|
+
get current() {
|
|
8166
|
+
return this.#current;
|
|
8167
|
+
}
|
|
8168
|
+
/** Record the mentions in `text` without crediting tokens (a claim needs "which ticket" before the turn's usage line arrives). */
|
|
8169
|
+
note(text) {
|
|
8170
|
+
const mentions = ticketMentions(text);
|
|
8171
|
+
if (mentions.length > 0) this.#current = mentions[mentions.length - 1];
|
|
8172
|
+
}
|
|
8164
8173
|
/** One assistant turn: its combined visible text (thinking + text blocks) and its own token cost. */
|
|
8165
8174
|
add(text, tokens) {
|
|
8166
8175
|
const mentions = ticketMentions(text);
|
|
@@ -8186,6 +8195,11 @@ var TicketAttributionTracker = class {
|
|
|
8186
8195
|
return best;
|
|
8187
8196
|
}
|
|
8188
8197
|
};
|
|
8198
|
+
function isClaimWrite(tool, input) {
|
|
8199
|
+
if (!tool || !/records_controller_update$/.test(tool)) return false;
|
|
8200
|
+
const body = input?.body;
|
|
8201
|
+
return typeof body === "object" && body !== null && body.status === "in_progress";
|
|
8202
|
+
}
|
|
8189
8203
|
|
|
8190
8204
|
// src/agent.ts
|
|
8191
8205
|
var DIGEST_MAX_AGE_SECONDS = 600;
|
|
@@ -8415,6 +8429,13 @@ var RunObserver = class {
|
|
|
8415
8429
|
/** Set once the stream carries the harness's final result (turn count or cost). */
|
|
8416
8430
|
sawResult = false;
|
|
8417
8431
|
muted = false;
|
|
8432
|
+
/**
|
|
8433
|
+
* The label stamped on events. Starts as the poll hint and switches to the
|
|
8434
|
+
* ticket the run actually claims (CREW-1776); `id.ticket` stays the hint.
|
|
8435
|
+
*/
|
|
8436
|
+
liveTicket;
|
|
8437
|
+
/** Called after `liveTicket` changes, so the sidecar can be rewritten. */
|
|
8438
|
+
onClaim;
|
|
8418
8439
|
buf = "";
|
|
8419
8440
|
mapper;
|
|
8420
8441
|
id;
|
|
@@ -8426,6 +8447,7 @@ var RunObserver = class {
|
|
|
8426
8447
|
this.adapter = adapter;
|
|
8427
8448
|
this.eventsSink = eventsSink;
|
|
8428
8449
|
this.emitCycle = emitCycle;
|
|
8450
|
+
this.liveTicket = id.ticket ?? void 0;
|
|
8429
8451
|
this.mapper = adapter.createStreamMapper?.() ?? { map: (p) => adapter.mapStreamLine(p), flush: () => [] };
|
|
8430
8452
|
}
|
|
8431
8453
|
feed(text) {
|
|
@@ -8466,7 +8488,29 @@ var RunObserver = class {
|
|
|
8466
8488
|
consume(parsed, events) {
|
|
8467
8489
|
const plan = this.id;
|
|
8468
8490
|
let turnText = "";
|
|
8491
|
+
for (const ev of events) if ((ev.kind === "thought" || ev.kind === "text") && ev.text) this.attribution.note(ev.text);
|
|
8469
8492
|
for (const ev of events) {
|
|
8493
|
+
if (ev.kind === "tool" && isClaimWrite(ev.tool, ev.input)) {
|
|
8494
|
+
const claimed = this.attribution.current;
|
|
8495
|
+
if (claimed && claimed !== this.liveTicket) {
|
|
8496
|
+
if (!this.muted) {
|
|
8497
|
+
this.eventsSink?.write(`${JSON.stringify({
|
|
8498
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8499
|
+
cycle: this.emitCycle,
|
|
8500
|
+
route: plan.route,
|
|
8501
|
+
step: "agent",
|
|
8502
|
+
level: "info",
|
|
8503
|
+
message: `ticket: ${this.liveTicket ?? "none"} \u2192 ${claimed}`,
|
|
8504
|
+
role: plan.role,
|
|
8505
|
+
ticket: claimed,
|
|
8506
|
+
data: { kind: "ticket", from: this.liveTicket ?? null, to: claimed }
|
|
8507
|
+
})}
|
|
8508
|
+
`);
|
|
8509
|
+
}
|
|
8510
|
+
this.liveTicket = claimed;
|
|
8511
|
+
if (!this.muted) this.onClaim?.();
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8470
8514
|
const e = {
|
|
8471
8515
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8472
8516
|
cycle: this.emitCycle,
|
|
@@ -8475,7 +8519,7 @@ var RunObserver = class {
|
|
|
8475
8519
|
level: "info",
|
|
8476
8520
|
message: ev.kind === "tool" ? `${ev.tool ?? "tool"}${ev.target ? ` ${ev.target}` : ""}` : ev.kind,
|
|
8477
8521
|
role: plan.role,
|
|
8478
|
-
...
|
|
8522
|
+
...this.liveTicket ? { ticket: this.liveTicket } : {},
|
|
8479
8523
|
data: { kind: ev.kind, ...ev.text ? { text: ev.text } : {}, ...ev.tool ? { tool: ev.tool } : {}, ...ev.target ? { target: ev.target } : {} }
|
|
8480
8524
|
};
|
|
8481
8525
|
if (!this.muted) this.eventsSink?.write(`${JSON.stringify(e)}
|
|
@@ -8510,25 +8554,39 @@ var RunObserver = class {
|
|
|
8510
8554
|
if (turnTokens !== void 0) this.attribution.add(turnText, turnTokens);
|
|
8511
8555
|
}
|
|
8512
8556
|
};
|
|
8513
|
-
|
|
8514
|
-
const attributedTicket = obs.attribution.winner(id.ticket ?? void 0);
|
|
8515
|
-
const identity = { toolchain: id.toolchain, harness: id.harness, provider: id.provider };
|
|
8516
|
-
const { result } = obs;
|
|
8557
|
+
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
8517
8558
|
try {
|
|
8518
8559
|
writeFileSync8(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
8519
8560
|
route: id.route,
|
|
8520
8561
|
role: id.role,
|
|
8521
|
-
|
|
8522
|
-
|
|
8562
|
+
hint: id.ticket,
|
|
8563
|
+
ticket: liveTicket ?? id.ticket,
|
|
8523
8564
|
startedAt,
|
|
8524
|
-
exitCode: end.code,
|
|
8525
8565
|
model: id.model,
|
|
8566
|
+
toolchain: id.toolchain,
|
|
8567
|
+
harness: id.harness,
|
|
8568
|
+
provider: id.provider,
|
|
8569
|
+
exitCode: null,
|
|
8570
|
+
running: true,
|
|
8571
|
+
...extra
|
|
8572
|
+
}));
|
|
8573
|
+
} catch {
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
8577
|
+
const attributedTicket = obs.attribution.winner(obs.liveTicket);
|
|
8578
|
+
const identity = { toolchain: id.toolchain, harness: id.harness, provider: id.provider };
|
|
8579
|
+
const { result } = obs;
|
|
8580
|
+
try {
|
|
8581
|
+
writeRunMeta(id, startedAt, obs.liveTicket, {
|
|
8582
|
+
attributedTicket: attributedTicket ?? null,
|
|
8583
|
+
running: void 0,
|
|
8584
|
+
exitCode: end.code,
|
|
8526
8585
|
...end.interrupted ? { interrupted: end.interrupted } : {},
|
|
8527
|
-
...identity,
|
|
8528
8586
|
sessionId: result?.sessionId ?? null,
|
|
8529
8587
|
numTurns: result?.numTurns ?? null,
|
|
8530
8588
|
totalCostUsd: result?.costUsd ?? null
|
|
8531
|
-
})
|
|
8589
|
+
});
|
|
8532
8590
|
} catch {
|
|
8533
8591
|
}
|
|
8534
8592
|
const ms2 = Date.now() - Date.parse(startedAt);
|
|
@@ -8616,6 +8674,9 @@ function spawnAgent(plan, emit) {
|
|
|
8616
8674
|
}
|
|
8617
8675
|
}
|
|
8618
8676
|
const obs = new RunObserver(id, adapter, eventsSink, emit.cycle);
|
|
8677
|
+
const meta = { pid: child.pid ?? null };
|
|
8678
|
+
obs.onClaim = () => writeRunMeta(id, startedAt, obs.liveTicket, meta);
|
|
8679
|
+
if (child.pid !== void 0) writeRunMeta(id, startedAt, obs.liveTicket, meta);
|
|
8619
8680
|
const tail = new FileTail(plan.streamPath);
|
|
8620
8681
|
const timer = setInterval(() => obs.feed(tail.poll()), TAIL_POLL_MS);
|
|
8621
8682
|
let spawnFailed = false;
|
|
@@ -11996,7 +12057,7 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
11996
12057
|
}
|
|
11997
12058
|
|
|
11998
12059
|
// src/cli.ts
|
|
11999
|
-
import { readFileSync as
|
|
12060
|
+
import { readFileSync as readFileSync19, existsSync as existsSync25, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
|
|
12000
12061
|
import { createInterface } from "readline/promises";
|
|
12001
12062
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
12002
12063
|
|
|
@@ -12736,11 +12797,11 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
12736
12797
|
}
|
|
12737
12798
|
|
|
12738
12799
|
// src/daemon.ts
|
|
12739
|
-
import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as
|
|
12800
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync18, readdirSync as readdirSync9, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
12740
12801
|
import { join as join24 } from "path";
|
|
12741
12802
|
|
|
12742
12803
|
// src/interrupted.ts
|
|
12743
|
-
import { existsSync as existsSync23, readdirSync as readdirSync8 } from "fs";
|
|
12804
|
+
import { existsSync as existsSync23, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
12744
12805
|
import { join as join23 } from "path";
|
|
12745
12806
|
var INTERRUPTED_MARKER = "<!-- crew:interrupted -->";
|
|
12746
12807
|
function interruptedComment(b) {
|
|
@@ -12757,6 +12818,15 @@ var pidAlive = (pid) => {
|
|
|
12757
12818
|
return e.code === "EPERM";
|
|
12758
12819
|
}
|
|
12759
12820
|
};
|
|
12821
|
+
function runFinished(streamPath) {
|
|
12822
|
+
const path = `${streamPath}.meta.json`;
|
|
12823
|
+
if (!existsSync23(path)) return false;
|
|
12824
|
+
try {
|
|
12825
|
+
return JSON.parse(readFileSync17(path, "utf8")).running !== true;
|
|
12826
|
+
} catch {
|
|
12827
|
+
return true;
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12760
12830
|
function orphanedStreams(o) {
|
|
12761
12831
|
const dir = join23(o.stateDir, "streams");
|
|
12762
12832
|
if (!existsSync23(dir)) return [];
|
|
@@ -12765,7 +12835,7 @@ function orphanedStreams(o) {
|
|
|
12765
12835
|
for (const name of readdirSync8(dir)) {
|
|
12766
12836
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
12767
12837
|
const streamPath = join23(dir, name);
|
|
12768
|
-
if (
|
|
12838
|
+
if (runFinished(streamPath)) continue;
|
|
12769
12839
|
const sidecar = readRunSidecar(streamPath);
|
|
12770
12840
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
12771
12841
|
if (ownerAlive(sidecar.ownerPid)) continue;
|
|
@@ -12889,7 +12959,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
|
12889
12959
|
}
|
|
12890
12960
|
function readDaemonStarted(stateDir) {
|
|
12891
12961
|
try {
|
|
12892
|
-
const v = JSON.parse(
|
|
12962
|
+
const v = JSON.parse(readFileSync18(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
12893
12963
|
return typeof v === "number" ? v : null;
|
|
12894
12964
|
} catch {
|
|
12895
12965
|
return null;
|
|
@@ -13820,7 +13890,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
13820
13890
|
for (const a of plan.actions) {
|
|
13821
13891
|
if (a.kind === "enable") {
|
|
13822
13892
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
13823
|
-
writeFileSync14(cfg.configFile, planEnable(
|
|
13893
|
+
writeFileSync14(cfg.configFile, planEnable(readFileSync19(cfg.configFile, "utf8"), a.route));
|
|
13824
13894
|
enabledSound.add(a.route);
|
|
13825
13895
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
13826
13896
|
} else {
|
|
@@ -14958,7 +15028,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
14958
15028
|
`);
|
|
14959
15029
|
if (!dryRun) {
|
|
14960
15030
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
14961
|
-
const raw = existsSync25(resolvedPath) ? JSON.parse(
|
|
15031
|
+
const raw = existsSync25(resolvedPath) ? JSON.parse(readFileSync19(resolvedPath, "utf8")) : {};
|
|
14962
15032
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
14963
15033
|
raw.agentSkills = skillResult.agentSkills;
|
|
14964
15034
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
@@ -15018,7 +15088,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15018
15088
|
`);
|
|
15019
15089
|
if (!dryRun) {
|
|
15020
15090
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
15021
|
-
const raw = existsSync25(resolvedPath) ? JSON.parse(
|
|
15091
|
+
const raw = existsSync25(resolvedPath) ? JSON.parse(readFileSync19(resolvedPath, "utf8")) : {};
|
|
15022
15092
|
raw.agentSkills = result.agentSkills;
|
|
15023
15093
|
mkdirSync13(dirOf(resolvedPath), { recursive: true });
|
|
15024
15094
|
writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -15034,7 +15104,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15034
15104
|
process.exit(2);
|
|
15035
15105
|
};
|
|
15036
15106
|
if (sub !== "add" && sub !== "list") usage2();
|
|
15037
|
-
const configText =
|
|
15107
|
+
const configText = readFileSync19(cfg.configFile, "utf8");
|
|
15038
15108
|
let routeName = positional[2];
|
|
15039
15109
|
let addPath = ".";
|
|
15040
15110
|
let addName;
|
|
@@ -15058,7 +15128,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15058
15128
|
let resolvedRemotes;
|
|
15059
15129
|
if (existsSync25(resolvedFile)) {
|
|
15060
15130
|
try {
|
|
15061
|
-
resolvedRemotes = JSON.parse(
|
|
15131
|
+
resolvedRemotes = JSON.parse(readFileSync19(resolvedFile, "utf8"));
|
|
15062
15132
|
} catch {
|
|
15063
15133
|
}
|
|
15064
15134
|
}
|
|
@@ -15130,7 +15200,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15130
15200
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
15131
15201
|
`);
|
|
15132
15202
|
if (existsSync25(resolvedFile)) {
|
|
15133
|
-
const raw = JSON.parse(
|
|
15203
|
+
const raw = JSON.parse(readFileSync19(resolvedFile, "utf8"));
|
|
15134
15204
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
15135
15205
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
15136
15206
|
writeFileSync14(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -15379,7 +15449,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
15379
15449
|
const hadPreviousFile = existsSync25(resolvedPath);
|
|
15380
15450
|
if (hadPreviousFile) {
|
|
15381
15451
|
try {
|
|
15382
|
-
previous = JSON.parse(
|
|
15452
|
+
previous = JSON.parse(readFileSync19(resolvedPath, "utf8"));
|
|
15383
15453
|
} catch {
|
|
15384
15454
|
}
|
|
15385
15455
|
}
|
|
@@ -16071,7 +16141,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
16071
16141
|
}
|
|
16072
16142
|
case "log": {
|
|
16073
16143
|
try {
|
|
16074
|
-
const text =
|
|
16144
|
+
const text = readFileSync19(cfg.ship.logFile, "utf8").split("\n");
|
|
16075
16145
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
16076
16146
|
`);
|
|
16077
16147
|
} catch {
|
|
@@ -16292,7 +16362,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
16292
16362
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
16293
16363
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
16294
16364
|
try {
|
|
16295
|
-
return
|
|
16365
|
+
return readFileSync19(p, "utf8");
|
|
16296
16366
|
} catch {
|
|
16297
16367
|
return null;
|
|
16298
16368
|
}
|
package/package.json
CHANGED