@tablation/crew 0.117.0 → 0.118.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
CHANGED
|
@@ -6932,6 +6932,69 @@ var PERISCOPE_COLUMNS = [
|
|
|
6932
6932
|
"expires_at"
|
|
6933
6933
|
];
|
|
6934
6934
|
|
|
6935
|
+
// src/tool-classify.ts
|
|
6936
|
+
var INTEL_SERVER_NAME2 = "code";
|
|
6937
|
+
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
6938
|
+
"Grep",
|
|
6939
|
+
"Glob",
|
|
6940
|
+
"LS",
|
|
6941
|
+
// Claude
|
|
6942
|
+
"search_file_content",
|
|
6943
|
+
"glob",
|
|
6944
|
+
"list_directory",
|
|
6945
|
+
// Gemini
|
|
6946
|
+
"grep_search",
|
|
6947
|
+
"file_search"
|
|
6948
|
+
]);
|
|
6949
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set([
|
|
6950
|
+
"Edit",
|
|
6951
|
+
"Write",
|
|
6952
|
+
"MultiEdit",
|
|
6953
|
+
"NotebookEdit",
|
|
6954
|
+
// Claude
|
|
6955
|
+
"file_change",
|
|
6956
|
+
// Codex
|
|
6957
|
+
"replace",
|
|
6958
|
+
"write_file"
|
|
6959
|
+
// Gemini
|
|
6960
|
+
]);
|
|
6961
|
+
var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "command", "run_shell_command"]);
|
|
6962
|
+
var SEARCH_COMMANDS = /* @__PURE__ */ new Set(["grep", "egrep", "fgrep", "rg", "find", "ls", "fd", "ag"]);
|
|
6963
|
+
function classifyTool(tool, target) {
|
|
6964
|
+
if (!tool) return "other";
|
|
6965
|
+
if (tool.startsWith(`mcp__${INTEL_SERVER_NAME2}__`)) return "symbol";
|
|
6966
|
+
if (EDIT_TOOLS.has(tool)) return "edit";
|
|
6967
|
+
if (SEARCH_TOOLS.has(tool)) return "search";
|
|
6968
|
+
if (SHELL_TOOLS.has(tool)) {
|
|
6969
|
+
const first = (target ?? "").split("/").pop();
|
|
6970
|
+
return first && SEARCH_COMMANDS.has(first) ? "search" : "other";
|
|
6971
|
+
}
|
|
6972
|
+
return "other";
|
|
6973
|
+
}
|
|
6974
|
+
var BeforeFirstEditTracker = class {
|
|
6975
|
+
calls = { search: 0, symbol: 0, other: 0 };
|
|
6976
|
+
edited = false;
|
|
6977
|
+
seconds;
|
|
6978
|
+
/** `elapsedMs` is the time since session start, when known (not on a replayed stream). */
|
|
6979
|
+
note(tool, target, elapsedMs) {
|
|
6980
|
+
if (this.edited) return;
|
|
6981
|
+
const kind = classifyTool(tool, target);
|
|
6982
|
+
if (kind === "edit") {
|
|
6983
|
+
this.edited = true;
|
|
6984
|
+
if (elapsedMs !== void 0) this.seconds = Math.max(0, Math.round(elapsedMs / 1e3));
|
|
6985
|
+
} else {
|
|
6986
|
+
this.calls[kind]++;
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
result() {
|
|
6990
|
+
return {
|
|
6991
|
+
calls: { ...this.calls },
|
|
6992
|
+
edited: this.edited,
|
|
6993
|
+
...this.seconds !== void 0 ? { secondsToFirstEdit: this.seconds } : {}
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6996
|
+
};
|
|
6997
|
+
|
|
6935
6998
|
// src/agent-log.ts
|
|
6936
6999
|
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync6, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
6937
7000
|
import { join as join18 } from "path";
|
|
@@ -7006,6 +7069,36 @@ function counterIncrements(run3, cols, have) {
|
|
|
7006
7069
|
}
|
|
7007
7070
|
return out;
|
|
7008
7071
|
}
|
|
7072
|
+
var BEFORE_EDIT_COLUMNS = {
|
|
7073
|
+
search: "calls_before_edit_search",
|
|
7074
|
+
symbol: "calls_before_edit_symbol",
|
|
7075
|
+
other: "calls_before_edit_other",
|
|
7076
|
+
seconds: "seconds_to_first_edit",
|
|
7077
|
+
edited: "edited"
|
|
7078
|
+
};
|
|
7079
|
+
function beforeFirstEditBody(b) {
|
|
7080
|
+
return {
|
|
7081
|
+
[BEFORE_EDIT_COLUMNS.search]: b.calls.search,
|
|
7082
|
+
[BEFORE_EDIT_COLUMNS.symbol]: b.calls.symbol,
|
|
7083
|
+
[BEFORE_EDIT_COLUMNS.other]: b.calls.other,
|
|
7084
|
+
[BEFORE_EDIT_COLUMNS.edited]: b.edited,
|
|
7085
|
+
...b.secondsToFirstEdit !== void 0 ? { [BEFORE_EDIT_COLUMNS.seconds]: b.secondsToFirstEdit } : {}
|
|
7086
|
+
};
|
|
7087
|
+
}
|
|
7088
|
+
async function writeBeforeFirstEdit(target, rowId, b) {
|
|
7089
|
+
try {
|
|
7090
|
+
const model = await request(
|
|
7091
|
+
target,
|
|
7092
|
+
"GET",
|
|
7093
|
+
`/data-models/agent_log?workspaceId=${encodeURIComponent(target.workspaceId)}`
|
|
7094
|
+
);
|
|
7095
|
+
const have = await columnsOf(target, model.id);
|
|
7096
|
+
const body = Object.fromEntries(Object.entries(beforeFirstEditBody(b)).filter(([column]) => have.has(column)));
|
|
7097
|
+
if (Object.keys(body).length === 0) return;
|
|
7098
|
+
await request(target, "PATCH", `/data-models/${model.id}/records/${rowId}`, body);
|
|
7099
|
+
} catch {
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7009
7102
|
async function post(target, path, body) {
|
|
7010
7103
|
const url = `${target.baseUrl.replace(/\/+$/, "")}/api/workspaces/${target.workspaceId}${path}`;
|
|
7011
7104
|
const res = await fetch(url, {
|
|
@@ -7074,6 +7167,7 @@ async function reportAgentRun(target, run3) {
|
|
|
7074
7167
|
...cycle.role ? { role: cycle.role } : {}
|
|
7075
7168
|
});
|
|
7076
7169
|
}
|
|
7170
|
+
if (run3.beforeFirstEdit) await writeBeforeFirstEdit(target, entry.id, run3.beforeFirstEdit);
|
|
7077
7171
|
const counters = target.tickets && ticketRecordId ? await addRunToTicket(target, target.tickets, ticketRecordId, run3) : void 0;
|
|
7078
7172
|
return {
|
|
7079
7173
|
agentLogId: entry.id,
|
|
@@ -10588,6 +10682,8 @@ var RunObserver = class {
|
|
|
10588
10682
|
result;
|
|
10589
10683
|
/** Prefix tokens of the first assistant call that carried usage; kept once. */
|
|
10590
10684
|
prefixTokens;
|
|
10685
|
+
/** Tool calls before the run's first edit (CREW-2879). */
|
|
10686
|
+
beforeFirstEdit = new BeforeFirstEditTracker();
|
|
10591
10687
|
/** Set once the stream carries the harness's final result (turn count or cost). */
|
|
10592
10688
|
sawResult = false;
|
|
10593
10689
|
muted = false;
|
|
@@ -10599,6 +10695,7 @@ var RunObserver = class {
|
|
|
10599
10695
|
/** Called after `liveTicket` changes, so the sidecar can be rewritten. */
|
|
10600
10696
|
onClaim;
|
|
10601
10697
|
buf = "";
|
|
10698
|
+
startMs = Date.now();
|
|
10602
10699
|
mapper;
|
|
10603
10700
|
id;
|
|
10604
10701
|
adapter;
|
|
@@ -10654,6 +10751,7 @@ var RunObserver = class {
|
|
|
10654
10751
|
for (const ev of events) if ((ev.kind === "thought" || ev.kind === "text") && ev.text) this.attribution.note(ev.text);
|
|
10655
10752
|
for (const ev of events) {
|
|
10656
10753
|
if (ev.kind === "tool") this.attribution.noteWrite(ev.tool, ev.input);
|
|
10754
|
+
if (ev.kind === "tool") this.beforeFirstEdit.note(ev.tool, ev.target, this.muted ? void 0 : Date.now() - this.startMs);
|
|
10657
10755
|
if (ev.kind === "tool" && isClaimWrite(ev.tool, ev.input)) {
|
|
10658
10756
|
const claimed = this.attribution.current;
|
|
10659
10757
|
if (claimed && claimed !== this.liveTicket) {
|
|
@@ -10768,6 +10866,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10768
10866
|
emit.warn(`could not save the pending hand-off: ${e.message}`, { step: "agent" });
|
|
10769
10867
|
}
|
|
10770
10868
|
}
|
|
10869
|
+
const before = obs.beforeFirstEdit.result();
|
|
10771
10870
|
try {
|
|
10772
10871
|
writeRunMeta(id, startedAt, obs.liveTicket, {
|
|
10773
10872
|
attributedTicket: attributedTicket ?? null,
|
|
@@ -10779,6 +10878,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10779
10878
|
sessionId: result?.sessionId ?? null,
|
|
10780
10879
|
numTurns: result?.numTurns ?? null,
|
|
10781
10880
|
prefixTokens: obs.prefixTokens ?? null,
|
|
10881
|
+
beforeFirstEdit: before,
|
|
10782
10882
|
totalCostUsd: result?.costUsd ?? null
|
|
10783
10883
|
});
|
|
10784
10884
|
} catch {
|
|
@@ -10794,6 +10894,9 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10794
10894
|
attributedTicket: attributedTicket ?? null,
|
|
10795
10895
|
attributedBy,
|
|
10796
10896
|
...result?.sessionId ? { sessionId: result.sessionId } : {},
|
|
10897
|
+
callsBeforeFirstEdit: before.calls,
|
|
10898
|
+
edited: before.edited,
|
|
10899
|
+
...before.secondsToFirstEdit !== void 0 ? { secondsToFirstEdit: before.secondsToFirstEdit } : {},
|
|
10797
10900
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
10798
10901
|
...result?.costUsd !== void 0 ? { totalCostUsd: result.costUsd } : {}
|
|
10799
10902
|
}
|
|
@@ -10814,6 +10917,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10814
10917
|
// Both come from a finished run: without the result line (interrupted) neither is trustworthy.
|
|
10815
10918
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
10816
10919
|
...result?.numTurns !== void 0 && obs.prefixTokens !== void 0 ? { prefixTokens: obs.prefixTokens } : {},
|
|
10920
|
+
beforeFirstEdit: before,
|
|
10817
10921
|
harness: identity.harness,
|
|
10818
10922
|
provider: identity.provider,
|
|
10819
10923
|
promptVersion: id.promptVersion,
|
|
@@ -19591,7 +19695,20 @@ async function listLogEntries(route2, opts = {}) {
|
|
|
19591
19695
|
limit: opts.limit ?? 20,
|
|
19592
19696
|
...filters.length > 0 ? { filters: JSON.stringify(filters) } : {}
|
|
19593
19697
|
});
|
|
19594
|
-
|
|
19698
|
+
const newestFirst = [...entries].sort((a, b) => String(b.started_at ?? "").localeCompare(String(a.started_at ?? "")));
|
|
19699
|
+
if (!opts.beforeEdit) return newestFirst;
|
|
19700
|
+
const searches = (e) => e.calls_before_edit_search ?? -1;
|
|
19701
|
+
return newestFirst.sort((a, b) => searches(b) - searches(a));
|
|
19702
|
+
}
|
|
19703
|
+
function formatBeforeEdit(e) {
|
|
19704
|
+
if (e.calls_before_edit_search == null && e.calls_before_edit_symbol == null && e.calls_before_edit_other == null) return void 0;
|
|
19705
|
+
const parts = [
|
|
19706
|
+
`search ${e.calls_before_edit_search ?? 0}`,
|
|
19707
|
+
`symbol ${e.calls_before_edit_symbol ?? 0}`,
|
|
19708
|
+
`other ${e.calls_before_edit_other ?? 0}`
|
|
19709
|
+
];
|
|
19710
|
+
const when = e.edited === false ? "no edit" : e.seconds_to_first_edit != null ? `${e.seconds_to_first_edit}s to first edit` : void 0;
|
|
19711
|
+
return `${parts.join(", ")}${when ? ` (${when})` : ""}`;
|
|
19595
19712
|
}
|
|
19596
19713
|
async function showLogEntry(route2, entryId, opts = {}) {
|
|
19597
19714
|
requireWorkspace(route2);
|
|
@@ -23526,7 +23643,7 @@ function usage(problem, requested = false) {
|
|
|
23526
23643
|
crew repos clone [ROUTE] NAME clone a Repos row's remote into the route directory and attach it (--dir PATH, --dry-run, --json)
|
|
23527
23644
|
crew repos hooks [ROUTE] NAME suggest test/build hooks for a checkout (--agent, --write route|repo, --recipes, --json)
|
|
23528
23645
|
crew repos list ROUTE the checkouts a route has, with the tracker's Repos row each matches (--json: every Repos row too)
|
|
23529
|
-
crew logbook list [route] recent Agent Log entries, filterable by --role/--ticket
|
|
23646
|
+
crew logbook list [route] recent Agent Log entries, filterable by --role/--ticket; --before-edit sorts by searches before the first edit
|
|
23530
23647
|
crew logbook show [route] ID [--prompt] one entry; --prompt reconstructs and verifies its prompt
|
|
23531
23648
|
crew passengers [route] sync Host Passengers containers/tunnels once; what the passengers unit invokes
|
|
23532
23649
|
crew install write and load this platform's scheduler unit
|
|
@@ -26179,7 +26296,7 @@ ${cu.tail.trimEnd()}
|
|
|
26179
26296
|
const sub = positional[1];
|
|
26180
26297
|
if (sub !== "list" && sub !== "show") {
|
|
26181
26298
|
process.stderr.write(
|
|
26182
|
-
"crew logbook list [route] [--role R] [--ticket T] [--limit N]\ncrew logbook show [route] <entryId> [--prompt]\n"
|
|
26299
|
+
"crew logbook list [route] [--role R] [--ticket T] [--limit N] [--before-edit]\ncrew logbook show [route] <entryId> [--prompt]\n"
|
|
26183
26300
|
);
|
|
26184
26301
|
process.exit(2);
|
|
26185
26302
|
}
|
|
@@ -26206,7 +26323,8 @@ ${cu.tail.trimEnd()}
|
|
|
26206
26323
|
userAgent: cfg.ship.userAgent,
|
|
26207
26324
|
role: value("role"),
|
|
26208
26325
|
ticket: value("ticket"),
|
|
26209
|
-
limit: value("limit") ? Number(value("limit")) : void 0
|
|
26326
|
+
limit: value("limit") ? Number(value("limit")) : void 0,
|
|
26327
|
+
beforeEdit: flag("before-edit")
|
|
26210
26328
|
});
|
|
26211
26329
|
if (entries.length === 0) {
|
|
26212
26330
|
process.stdout.write("(no Agent Log entries match)\n");
|
|
@@ -26214,7 +26332,7 @@ ${cu.tail.trimEnd()}
|
|
|
26214
26332
|
}
|
|
26215
26333
|
for (const e of entries) {
|
|
26216
26334
|
process.stdout.write(
|
|
26217
|
-
`${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}` : ""}
|
|
26335
|
+
`${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}` : ""}${formatBeforeEdit(e) ? ` before edit: ${formatBeforeEdit(e)}` : ""}
|
|
26218
26336
|
`
|
|
26219
26337
|
);
|
|
26220
26338
|
}
|
|
@@ -26234,6 +26352,7 @@ harness: ${entry.harness ?? "?"}
|
|
|
26234
26352
|
provider: ${entry.provider ?? "?"}
|
|
26235
26353
|
` + (entry.num_turns != null ? `turns: ${entry.num_turns}
|
|
26236
26354
|
` : "") + (entry.prefix_tokens != null ? `prefix: ${entry.prefix_tokens} tokens
|
|
26355
|
+
` : "") + (formatBeforeEdit(entry) ? `before edit: ${formatBeforeEdit(entry)}
|
|
26237
26356
|
` : "")
|
|
26238
26357
|
);
|
|
26239
26358
|
if (flag("prompt")) {
|
package/package.json
CHANGED
|
@@ -93,6 +93,11 @@ available only in a repository whose hooks table declares `preview`.
|
|
|
93
93
|
the URL. Tell them where it is once, early: the URL always, and, when you
|
|
94
94
|
are the operator's own session on this ship (not a Passenger's chat), the
|
|
95
95
|
worktree path as well, so they can open the files directly.
|
|
96
|
+
- **When the person refers to something on the page** ("this", "here",
|
|
97
|
+
"that heading"), read the newest line of `.crew/pointed-at.jsonl` in the
|
|
98
|
+
periscope's worktree before searching; it names the file and line they
|
|
99
|
+
clicked. If the file is empty, ask them to point at it with the inspector
|
|
100
|
+
or paste the location.
|
|
96
101
|
- **When a dev lane is mid-flight on the same files** (visible from the
|
|
97
102
|
roster and the board), say so, so the person knows a merge is coming.
|
|
98
103
|
- **Before keep,** read back the ticket title and a one-paragraph summary of
|
|
@@ -424,6 +424,26 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
424
424
|
sets the ticket back to `in_progress` instead of passing it to QA. A ticket
|
|
425
425
|
with no Acceptance section is unaffected.
|
|
426
426
|
|
|
427
|
+
### Finding where something lives
|
|
428
|
+
|
|
429
|
+
Before you read or edit, locate the code once, in this order:
|
|
430
|
+
|
|
431
|
+
1. **The map.** Read the "Repository map" section of this brief (when the
|
|
432
|
+
repository has one) and the repository's own instructions file, which may
|
|
433
|
+
also say where things live.
|
|
434
|
+
2. **The symbol tools.** When the Environment section lists code tools, use
|
|
435
|
+
them for code questions before searching text: find a symbol by name, list
|
|
436
|
+
its references, read its definition, outline a file. Text search is for
|
|
437
|
+
strings that are not symbols.
|
|
438
|
+
3. **One broad search.** To find a visible string, search for it once across
|
|
439
|
+
the whole relevant tree, excluding tests, and prefer registries over
|
|
440
|
+
components. A search that found only a test file is widened, not
|
|
441
|
+
narrowed.
|
|
442
|
+
|
|
443
|
+
Count the calls: if more than three searches have not found the location, say
|
|
444
|
+
so in your progress comment and ask for the map or the person's pointer
|
|
445
|
+
rather than continuing to search.
|
|
446
|
+
|
|
427
447
|
## Step 4 — you never merge, and you never deploy
|
|
428
448
|
|
|
429
449
|
**Nothing you do touches the base branch.** The release phase takes every
|
|
@@ -93,6 +93,11 @@ available only in a repository whose hooks table declares `preview`.
|
|
|
93
93
|
the URL. Tell them where it is once, early: the URL always, and, when you
|
|
94
94
|
are the operator's own session on this ship (not a Passenger's chat), the
|
|
95
95
|
worktree path as well, so they can open the files directly.
|
|
96
|
+
- **When the person refers to something on the page** ("this", "here",
|
|
97
|
+
"that heading"), read the newest line of `.crew/pointed-at.jsonl` in the
|
|
98
|
+
periscope's worktree before searching; it names the file and line they
|
|
99
|
+
clicked. If the file is empty, ask them to point at it with the inspector
|
|
100
|
+
or paste the location.
|
|
96
101
|
- **When a dev lane is mid-flight on the same files** (visible from the
|
|
97
102
|
roster and the board), say so, so the person knows a merge is coming.
|
|
98
103
|
- **Before keep,** read back the ticket title and a one-paragraph summary of
|
|
@@ -419,6 +419,26 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
419
419
|
sets the ticket back to `in_progress` instead of passing it to QA. A ticket
|
|
420
420
|
with no Acceptance section is unaffected.
|
|
421
421
|
|
|
422
|
+
### Finding where something lives
|
|
423
|
+
|
|
424
|
+
Before you read or edit, locate the code once, in this order:
|
|
425
|
+
|
|
426
|
+
1. **The map.** Read the "Repository map" section of this brief (when the
|
|
427
|
+
repository has one) and the repository's own instructions file, which may
|
|
428
|
+
also say where things live.
|
|
429
|
+
2. **The symbol tools.** When the Environment section lists code tools, use
|
|
430
|
+
them for code questions before searching text: find a symbol by name, list
|
|
431
|
+
its references, read its definition, outline a file. Text search is for
|
|
432
|
+
strings that are not symbols.
|
|
433
|
+
3. **One broad search.** To find a visible string, search for it once across
|
|
434
|
+
the whole relevant tree, excluding tests, and prefer registries over
|
|
435
|
+
components. A search that found only a test file is widened, not
|
|
436
|
+
narrowed.
|
|
437
|
+
|
|
438
|
+
Count the calls: if more than three searches have not found the location, say
|
|
439
|
+
so in your progress comment and ask for the map or the person's pointer
|
|
440
|
+
rather than continuing to search.
|
|
441
|
+
|
|
422
442
|
## Step 4 — you never merge, and you never deploy
|
|
423
443
|
|
|
424
444
|
**Nothing you do touches the base branch.** The release phase takes every
|