@tablation/crew 0.41.0 → 0.43.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 CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve as resolve4, dirname as dirname12, join as join22, basename as basename9 } from "path";
4
+ import { resolve as resolve4, dirname as dirname12, join as join23, basename as basename9 } from "path";
5
5
  import { fileURLToPath } from "url";
6
- import { hostname as hostname2, homedir as homedir7 } from "os";
6
+ import { hostname as hostname2, homedir as homedir8 } from "os";
7
7
  import { spawnSync as spawnSync5 } from "child_process";
8
8
 
9
9
  // src/argv.ts
@@ -40,11 +40,31 @@ function positionalArgs(argv2) {
40
40
  }
41
41
  return out;
42
42
  }
43
+ var WORKTREE_ROLES = ["dev", "design", "qa"];
44
+ function splitWorktreeArgs(positional2, routeNames) {
45
+ const args = positional2.slice(1);
46
+ if (args.length > 1 && WORKTREE_ROLES.includes(args[args.length - 1] ?? "")) args.pop();
47
+ const [a, b] = [args[0], args[1]];
48
+ const isTicket = (s) => !!s && /^(?:[A-Za-z]+-)?\d+$/.test(s.trim());
49
+ if (b !== void 0) return { route: a, ticket: b };
50
+ if (a !== void 0 && isTicket(a) && !routeNames.includes(a)) return { ticket: a };
51
+ return { route: a, ticket: void 0 };
52
+ }
53
+ function pickWorktreeRoute(ticketNumber, candidates) {
54
+ for (const c of candidates) {
55
+ if (c.hit && (candidates.length === 1 || c.servedRepoIds.includes(c.hit.repoId ?? ""))) return { route: c.route };
56
+ }
57
+ const looked = candidates.map((c) => c.route).join(", ");
58
+ const found = candidates.filter((c) => c.hit).map((c) => `${c.route} (repo ${c.hit?.repoId ?? "none"})`);
59
+ return {
60
+ error: `no open ticket ISSUE-${ticketNumber} served by this ship \u2014 looked on ${looked}` + (found.length ? `; found it only on ${found.join(", ")}, which has no checkout for its repo` : "")
61
+ };
62
+ }
43
63
 
44
64
  // src/config.ts
45
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
46
- import { resolve, dirname as dirname3, isAbsolute, join as join6, basename as basename3 } from "path";
47
- import { homedir as homedir4, tmpdir } from "os";
65
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
66
+ import { resolve, dirname as dirname3, isAbsolute, join as join7, basename as basename3 } from "path";
67
+ import { homedir as homedir5, tmpdir } from "os";
48
68
  import { parse as parse2 } from "yaml";
49
69
 
50
70
  // src/events.ts
@@ -193,10 +213,10 @@ var Emitter = class _Emitter {
193
213
  var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
194
214
 
195
215
  // src/pair-install.ts
196
- import { existsSync as existsSync3, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
216
+ import { existsSync as existsSync4, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
197
217
  import { spawnSync as spawnSync3 } from "child_process";
198
- import { homedir as homedir3 } from "os";
199
- import { join as join4 } from "path";
218
+ import { homedir as homedir4 } from "os";
219
+ import { join as join5 } from "path";
200
220
 
201
221
  // src/stream.ts
202
222
  function toolTarget(name, input) {
@@ -320,6 +340,74 @@ function removeClaudeMcp(o) {
320
340
  return rm.status === 0 ? `mcp: removed the ${MCP_SERVER_NAME} MCP server from Claude Code (user scope)` : `mcp: \`claude mcp remove\` failed (${rm.stderr.trim() || `exit ${rm.status}`})`;
321
341
  }
322
342
 
343
+ // src/launcher.ts
344
+ import { basename, join as join3 } from "path";
345
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "fs";
346
+ import { homedir as homedir2 } from "os";
347
+ function isOriLauncher(spec) {
348
+ return !!spec.launcher && basename(spec.launcher) === "ori";
349
+ }
350
+ function isOmlxLauncher(spec) {
351
+ return !!spec.launcher && basename(spec.launcher) === "omlx";
352
+ }
353
+ function defaultOmlxBin(home = homedir2()) {
354
+ return join3(home, ".omlx", "bin", "omlx");
355
+ }
356
+ function launcherExecutable(spec, home) {
357
+ if (!spec.launcher) return void 0;
358
+ if (spec.launcherBin) return spec.launcherBin;
359
+ return isOmlxLauncher(spec) ? defaultOmlxBin(home) : spec.launcher;
360
+ }
361
+ function wrapInvocation(spec, o) {
362
+ const exe = launcherExecutable(spec);
363
+ if (!exe) return { bin: spec.bin, args: o.args };
364
+ if (isOmlxLauncher(spec)) return { bin: exe, args: ["launch", spec.harness, "--model", o.model, "--", ...o.args] };
365
+ return { bin: exe, args: [spec.bin, ...o.args] };
366
+ }
367
+ function selectsCustomModelProvider(args) {
368
+ for (let i = 0; i < args.length; i++) {
369
+ const a = args[i];
370
+ const v = a === "-c" || a === "--config" ? args[i + 1] : a.startsWith("--config=") ? a.slice("--config=".length) : void 0;
371
+ const m = v && /^model_provider\s*=\s*"?([^"\s]+)"?$/.exec(v.trim());
372
+ if (m && m[1] !== "openai") return true;
373
+ }
374
+ return false;
375
+ }
376
+ function parseOmlxSettings(text) {
377
+ let raw;
378
+ try {
379
+ raw = JSON.parse(text);
380
+ } catch {
381
+ return void 0;
382
+ }
383
+ if (!raw || typeof raw !== "object") return void 0;
384
+ const { server, model } = raw;
385
+ const port = Number(server?.port);
386
+ if (!Number.isInteger(port) || port <= 0) return void 0;
387
+ const host = typeof server?.host === "string" && server.host ? server.host : "127.0.0.1";
388
+ const dirs = Array.isArray(model?.model_dirs) ? model.model_dirs.filter((d) => typeof d === "string") : [];
389
+ return { host: host === "0.0.0.0" ? "127.0.0.1" : host, port, modelDirs: dirs };
390
+ }
391
+ function interpretOmlxProbe(status2) {
392
+ if (status2 === void 0) return { up: false, detail: "not reachable" };
393
+ if (status2 === 401) return { up: true, detail: "up (key required)" };
394
+ if (status2 >= 200 && status2 < 300) return { up: true, detail: "up (no key required)" };
395
+ return { up: false, detail: `answered HTTP ${status2}` };
396
+ }
397
+ function omlxModelPresent(model, dirs) {
398
+ return dirs.some((d) => {
399
+ try {
400
+ return readdirSync(d).includes(model);
401
+ } catch {
402
+ return false;
403
+ }
404
+ });
405
+ }
406
+ function readOmlxSettings(home = homedir2()) {
407
+ const p = join3(home, ".omlx", "settings.json");
408
+ return existsSync2(p) ? parseOmlxSettings(readFileSync3(p, "utf8")) : void 0;
409
+ }
410
+
323
411
  // src/capability.ts
324
412
  function profileFor(role) {
325
413
  if (role === "triage") return { level: "read", designTools: false };
@@ -400,6 +488,9 @@ var claudeAdapter = {
400
488
  ...allowedTools(o.role),
401
489
  "--disallowedTools",
402
490
  ...DISALLOWED_TOOLS,
491
+ // oMLX denies LSP itself for prefix-cache stability, but only when the
492
+ // caller passes no --disallowedTools of its own — and crew always does.
493
+ ...isOmlxLauncher(spec) ? ["LSP"] : [],
403
494
  "--model",
404
495
  o.model,
405
496
  "--output-format",
@@ -421,7 +512,7 @@ var claudeAdapter = {
421
512
  })] : [],
422
513
  ...spec.args
423
514
  ];
424
- return spec.launcher ? { bin: spec.launcherBin ?? spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
515
+ return wrapInvocation(spec, { model: o.model, args });
425
516
  },
426
517
  runEnv(spec) {
427
518
  return {
@@ -446,12 +537,12 @@ var claudeAdapter = {
446
537
 
447
538
  // src/adapters/codex.ts
448
539
  import { spawnSync as spawnSync2 } from "child_process";
449
- import { homedir as homedir2 } from "os";
450
- import { join as join3 } from "path";
540
+ import { homedir as homedir3 } from "os";
541
+ import { join as join4 } from "path";
451
542
  import { rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
452
543
 
453
544
  // src/pair-files.ts
454
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync, rmSync as rmSync2 } from "fs";
545
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync, rmSync as rmSync2 } from "fs";
455
546
  import { dirname as dirname2 } from "path";
456
547
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
457
548
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
@@ -492,7 +583,7 @@ function removePairBlock(text) {
492
583
  return text.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
493
584
  }
494
585
  function readIfExists(path) {
495
- return existsSync2(path) ? readFileSync3(path, "utf8") : void 0;
586
+ return existsSync3(path) ? readFileSync4(path, "utf8") : void 0;
496
587
  }
497
588
  function writeFileMkdir(path, text) {
498
589
  mkdirSync2(dirname2(path), { recursive: true });
@@ -639,8 +730,8 @@ function codexTable(url) {
639
730
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
640
731
  }
641
732
  var codexPaths = (home) => ({
642
- instructions: join3(home, ".codex", "AGENTS.md"),
643
- config: join3(home, ".codex", "config.toml")
733
+ instructions: join4(home, ".codex", "AGENTS.md"),
734
+ config: join4(home, ".codex", "config.toml")
644
735
  });
645
736
  function codexMcpState(config, url) {
646
737
  const lines = config.split("\n");
@@ -656,7 +747,7 @@ var codexPair = {
656
747
  harness: "codex",
657
748
  displayName: "Codex",
658
749
  installPair(o) {
659
- const p = codexPaths(o.home ?? homedir2());
750
+ const p = codexPaths(o.home ?? homedir3());
660
751
  const config = readIfExists(p.config) ?? "";
661
752
  const st = codexMcpState(config, o.url);
662
753
  if (st.state === "different" && !o.replace) {
@@ -673,7 +764,7 @@ var codexPair = {
673
764
  return `pair(codex): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
674
765
  },
675
766
  verifyPair(o) {
676
- const p = codexPaths(o.home ?? homedir2());
767
+ const p = codexPaths(o.home ?? homedir3());
677
768
  const b = blockVerdict(p.instructions, "codex");
678
769
  if (!b.ok) return b;
679
770
  const st = codexMcpState(readIfExists(p.config) ?? "", o.url);
@@ -682,7 +773,7 @@ var codexPair = {
682
773
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
683
774
  },
684
775
  removePair(o) {
685
- const p = codexPaths(o.home ?? homedir2());
776
+ const p = codexPaths(o.home ?? homedir3());
686
777
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
687
778
  const config = readIfExists(p.config);
688
779
  const st = codexMcpState(config ?? "", o.url);
@@ -758,21 +849,21 @@ var codexAdapter = {
758
849
  // Prompt on stdin.
759
850
  "-"
760
851
  ];
761
- return spec.launcher ? { bin: spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
852
+ return wrapInvocation(spec, { model: o.model, args });
762
853
  },
763
854
  runEnv: (spec) => ({ ...spec.env }),
764
- provider: () => "openai",
855
+ provider: (spec) => selectsCustomModelProvider(spec.args) ? "local" : "openai",
765
856
  unsetEnv: CODEX_BILLING_VARS_TO_UNSET,
766
857
  mapStreamLine: mapCodexLine,
767
858
  extractResult: extractCodexResult,
768
859
  extractTurnTokens: extractCodexTurnTokens,
769
860
  inspectMcp(o) {
770
861
  if (binMissing(o.bin)) return { state: "no-binary" };
771
- const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir2()).config) ?? "", o.url);
862
+ const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir3()).config) ?? "", o.url);
772
863
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
773
864
  },
774
865
  ensureMcp(o) {
775
- const p = codexPaths(o.home ?? homedir2());
866
+ const p = codexPaths(o.home ?? homedir3());
776
867
  const config = readIfExists(p.config) ?? "";
777
868
  const st = codexMcpState(config, o.url);
778
869
  if (st.state === "registered") return `mcp: ${SERVER} already registered with Codex \u2192 ${o.url}`;
@@ -787,8 +878,8 @@ var codexAdapter = {
787
878
 
788
879
  // src/pair-install.ts
789
880
  var geminiPaths = (home) => ({
790
- instructions: join4(home, ".gemini", "GEMINI.md"),
791
- config: join4(home, ".gemini", "settings.json")
881
+ instructions: join5(home, ".gemini", "GEMINI.md"),
882
+ config: join5(home, ".gemini", "settings.json")
792
883
  });
793
884
  function readGeminiSettings(path) {
794
885
  const text = readIfExists(path);
@@ -809,14 +900,14 @@ function geminiMcpState(s, url) {
809
900
  return { state: "registered" };
810
901
  }
811
902
  function inspectGeminiMcp(o) {
812
- const p = geminiPaths(o.home ?? homedir3());
903
+ const p = geminiPaths(o.home ?? homedir4());
813
904
  const settings = readGeminiSettings(p.config);
814
905
  if (!settings) return { state: "different", detail: `${p.config} is not a JSON object` };
815
906
  const st = geminiMcpState(settings, o.url);
816
907
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
817
908
  }
818
909
  function ensureGeminiMcp(o) {
819
- const p = geminiPaths(o.home ?? homedir3());
910
+ const p = geminiPaths(o.home ?? homedir4());
820
911
  const settings = readGeminiSettings(p.config);
821
912
  if (!settings) return `gemini: ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
822
913
  const st = geminiMcpState(settings, o.url);
@@ -834,7 +925,7 @@ var geminiPair = {
834
925
  harness: "gemini",
835
926
  displayName: "Gemini CLI",
836
927
  installPair(o) {
837
- const p = geminiPaths(o.home ?? homedir3());
928
+ const p = geminiPaths(o.home ?? homedir4());
838
929
  const settings = readGeminiSettings(p.config);
839
930
  if (!settings) return `pair(gemini): ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
840
931
  const st = geminiMcpState(settings, o.url);
@@ -853,7 +944,7 @@ var geminiPair = {
853
944
  return `pair(gemini): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
854
945
  },
855
946
  verifyPair(o) {
856
- const p = geminiPaths(o.home ?? homedir3());
947
+ const p = geminiPaths(o.home ?? homedir4());
857
948
  const b = blockVerdict(p.instructions, "gemini");
858
949
  if (!b.ok) return b;
859
950
  const settings = readGeminiSettings(p.config);
@@ -864,7 +955,7 @@ var geminiPair = {
864
955
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
865
956
  },
866
957
  removePair(o) {
867
- const p = geminiPaths(o.home ?? homedir3());
958
+ const p = geminiPaths(o.home ?? homedir4());
868
959
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
869
960
  const settings = readGeminiSettings(p.config);
870
961
  const st = settings ? geminiMcpState(settings, o.url) : { state: "missing" };
@@ -885,11 +976,11 @@ var geminiPair = {
885
976
  };
886
977
  var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
887
978
  function vscodeUserDirs(o) {
888
- const home = o.home ?? homedir3();
979
+ const home = o.home ?? homedir4();
889
980
  const platform2 = o.platform ?? process.platform;
890
- const root = platform2 === "darwin" ? join4(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join4(home, "AppData", "Roaming") : join4(home, ".config");
891
- const all = VSCODE_FLAVOURS.map((f) => join4(root, f, "User"));
892
- const present = all.filter((d) => existsSync3(d));
981
+ const root = platform2 === "darwin" ? join5(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join5(home, "AppData", "Roaming") : join5(home, ".config");
982
+ const all = VSCODE_FLAVOURS.map((f) => join5(root, f, "User"));
983
+ const present = all.filter((d) => existsSync4(d));
893
984
  return present.length > 0 ? present : [all[0]];
894
985
  }
895
986
  var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
@@ -936,8 +1027,8 @@ var copilotPair = {
936
1027
  installPair(o) {
937
1028
  const lines = [];
938
1029
  for (const dir of vscodeUserDirs(o)) {
939
- const mcpPath = join4(dir, "mcp.json");
940
- const instrPath = join4(dir, "prompts", "crew-pair.instructions.md");
1030
+ const mcpPath = join5(dir, "mcp.json");
1031
+ const instrPath = join5(dir, "prompts", "crew-pair.instructions.md");
941
1032
  const settings = readVscodeMcp(mcpPath);
942
1033
  if (!settings) {
943
1034
  lines.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -965,8 +1056,8 @@ var copilotPair = {
965
1056
  },
966
1057
  verifyPair(o) {
967
1058
  const verdicts = vscodeUserDirs(o).map((dir) => {
968
- const instrPath = join4(dir, "prompts", "crew-pair.instructions.md");
969
- const mcpPath = join4(dir, "mcp.json");
1059
+ const instrPath = join5(dir, "prompts", "crew-pair.instructions.md");
1060
+ const mcpPath = join5(dir, "mcp.json");
970
1061
  const b = blockVerdict(instrPath, "copilot");
971
1062
  if (!b.ok) return b;
972
1063
  const settings = readVscodeMcp(mcpPath);
@@ -981,8 +1072,8 @@ var copilotPair = {
981
1072
  removePair(o) {
982
1073
  const lines = [];
983
1074
  for (const dir of vscodeUserDirs(o)) {
984
- const mcpPath = join4(dir, "mcp.json");
985
- const hadBlock = removeCopilotInstructions(join4(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1075
+ const mcpPath = join5(dir, "mcp.json");
1076
+ const hadBlock = removeCopilotInstructions(join5(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
986
1077
  const settings = readVscodeMcp(mcpPath);
987
1078
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
988
1079
  const hadMcp = st.state === "registered";
@@ -1040,9 +1131,6 @@ function harnessPromptNote(harness) {
1040
1131
  `;
1041
1132
  }
1042
1133
 
1043
- // src/toolchain.ts
1044
- import { basename } from "path";
1045
-
1046
1134
  // src/adapters/gemini.ts
1047
1135
  var BILLING_VARS_TO_UNSET2 = [
1048
1136
  "GEMINI_API_KEY",
@@ -1136,7 +1224,7 @@ var geminiAdapter = {
1136
1224
  ...geminiGrantArgs(o.role),
1137
1225
  ...spec.args
1138
1226
  ];
1139
- return spec.launcher ? { bin: spec.launcherBin ?? spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
1227
+ return wrapInvocation(spec, { model: o.model, args });
1140
1228
  },
1141
1229
  runEnv: (spec) => ({ ...spec.env }),
1142
1230
  provider: () => "google",
@@ -1192,13 +1280,13 @@ function resolveToolchain(ship, route, repo) {
1192
1280
  }
1193
1281
  function toolchainVendor(spec) {
1194
1282
  if (isOriLauncher(spec)) return "openrouter";
1283
+ if (isOmlxLauncher(spec)) return "local";
1195
1284
  return adapterFor(spec.harness).vendor;
1196
1285
  }
1197
- function isOriLauncher(spec) {
1198
- return !!spec.launcher && basename(spec.launcher) === "ori";
1199
- }
1200
1286
  function toolchainProvider(spec) {
1201
- return isOriLauncher(spec) ? "openrouter" : adapterFor(spec.harness).provider(spec);
1287
+ if (isOriLauncher(spec)) return "openrouter";
1288
+ if (isOmlxLauncher(spec)) return "local";
1289
+ return adapterFor(spec.harness).provider(spec);
1202
1290
  }
1203
1291
  function resolveSeat(ship, route, role, persona = {}, repo) {
1204
1292
  const warnings = [];
@@ -1226,7 +1314,7 @@ function resolveSeat(ship, route, role, persona = {}, repo) {
1226
1314
  let modelWhy = `toolchain ${chosen.name} model`;
1227
1315
  let reasoning = chosen.tiers.standard?.reasoning;
1228
1316
  const explicit = persona.model;
1229
- const explicitOk = explicit !== void 0 && (vendor === "openrouter" || adapter.ownsModel(explicit));
1317
+ const explicitOk = explicit !== void 0 && (vendor === "openrouter" || vendor === "local" || adapter.ownsModel(explicit));
1230
1318
  if (explicit !== void 0 && !explicitOk) {
1231
1319
  warnings.push(`seat ${role}: persona model "${explicit}" does not belong to vendor "${vendor}" (toolchain ${chosen.name}); ignored`);
1232
1320
  }
@@ -1284,8 +1372,8 @@ function explain(ship, required) {
1284
1372
  }
1285
1373
 
1286
1374
  // src/repo-config.ts
1287
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1288
- import { join as join5, basename as basename2 } from "path";
1375
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
1376
+ import { join as join6, basename as basename2 } from "path";
1289
1377
  import { parse } from "yaml";
1290
1378
  var SPEC_VERSION = 1;
1291
1379
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
@@ -1358,8 +1446,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
1358
1446
  var PROVIDERS = ["github", "buildkite", "other", "none"];
1359
1447
  function findRepoConfig(dir) {
1360
1448
  for (const name of REPO_CONFIG_FILENAMES) {
1361
- const p = join5(dir, name);
1362
- if (existsSync4(p)) return p;
1449
+ const p = join6(dir, name);
1450
+ if (existsSync5(p)) return p;
1363
1451
  }
1364
1452
  return null;
1365
1453
  }
@@ -1589,7 +1677,7 @@ function parseRepoConfig(text, file) {
1589
1677
  function loadRepoConfig(dir) {
1590
1678
  const file = findRepoConfig(dir);
1591
1679
  if (!file) return null;
1592
- return parseRepoConfig(readFileSync4(file, "utf8"), file);
1680
+ return parseRepoConfig(readFileSync5(file, "utf8"), file);
1593
1681
  }
1594
1682
  function hookLabel(cfg, hook2) {
1595
1683
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -2002,16 +2090,16 @@ function mergeRouteRelease(route, override) {
2002
2090
  };
2003
2091
  }
2004
2092
  function expand(p, base) {
2005
- let out = p.startsWith("~") ? p.replace(/^~/, homedir4()) : p;
2006
- out = out.replace(/\$\{?HOME\}?/g, homedir4());
2093
+ let out = p.startsWith("~") ? p.replace(/^~/, homedir5()) : p;
2094
+ out = out.replace(/\$\{?HOME\}?/g, homedir5());
2007
2095
  return isAbsolute(out) ? out : resolve(base, out);
2008
2096
  }
2009
2097
  function resolvePromptsDir(promptSet, crewHome, base) {
2010
- if (!promptSet) return join6(crewHome, "prompts", "default");
2098
+ if (!promptSet) return join7(crewHome, "prompts", "default");
2011
2099
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
2012
2100
  return expand(promptSet, base);
2013
2101
  }
2014
- return join6(crewHome, "prompts", promptSet);
2102
+ return join7(crewHome, "prompts", promptSet);
2015
2103
  }
2016
2104
  var Missing = class {
2017
2105
  paths = [];
@@ -2035,7 +2123,7 @@ var Missing = class {
2035
2123
  };
2036
2124
  function crewVersion(crewHome) {
2037
2125
  try {
2038
- const pkg = JSON.parse(readFileSync5(join6(crewHome, "package.json"), "utf8"));
2126
+ const pkg = JSON.parse(readFileSync6(join7(crewHome, "package.json"), "utf8"));
2039
2127
  return pkg.version ?? "0.0.0";
2040
2128
  } catch {
2041
2129
  return "0.0.0";
@@ -2045,26 +2133,26 @@ function configSearchPath(crewHome) {
2045
2133
  if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
2046
2134
  const xdg = process.env.XDG_CONFIG_HOME;
2047
2135
  return [
2048
- ...xdg ? [join6(xdg, "crew", "crew.yaml")] : [],
2049
- join6(homedir4(), ".config", "crew", "crew.yaml"),
2136
+ ...xdg ? [join7(xdg, "crew", "crew.yaml")] : [],
2137
+ join7(homedir5(), ".config", "crew", "crew.yaml"),
2050
2138
  resolve(crewHome, "crew.yaml")
2051
2139
  ];
2052
2140
  }
2053
2141
  function findConfigFile(crewHome) {
2054
2142
  const candidates = configSearchPath(crewHome);
2055
- return candidates.find((c) => existsSync5(c)) ?? candidates[candidates.length - 1];
2143
+ return candidates.find((c) => existsSync6(c)) ?? candidates[candidates.length - 1];
2056
2144
  }
2057
- var defaultConfigPath = () => join6(process.env.XDG_CONFIG_HOME ?? join6(homedir4(), ".config"), "crew", "crew.yaml");
2145
+ var defaultConfigPath = () => join7(process.env.XDG_CONFIG_HOME ?? join7(homedir5(), ".config"), "crew", "crew.yaml");
2058
2146
  function routeSlug(route) {
2059
2147
  return route.replace(/\//g, "-");
2060
2148
  }
2061
2149
  function resolvedPathFor(stateDir, route) {
2062
2150
  const [workspace, project] = splitRoute(route);
2063
- return join6(stateDir, "resolved", workspace, `${project}.json`);
2151
+ return join7(stateDir, "resolved", workspace, `${project}.json`);
2064
2152
  }
2065
2153
  function apiKeyPathFor(stateDir, route) {
2066
2154
  const [workspace, project] = splitRoute(route);
2067
- return join6(stateDir, "keys", workspace, `${project}.env`);
2155
+ return join7(stateDir, "keys", workspace, `${project}.env`);
2068
2156
  }
2069
2157
  function splitRoute(route) {
2070
2158
  const parts = route.split("/");
@@ -2075,9 +2163,9 @@ function splitRoute(route) {
2075
2163
  }
2076
2164
  function readResolvedFile(stateDir, route) {
2077
2165
  const path = resolvedPathFor(stateDir, route);
2078
- if (!existsSync5(path)) return void 0;
2166
+ if (!existsSync6(path)) return void 0;
2079
2167
  try {
2080
- return JSON.parse(readFileSync5(path, "utf8"));
2168
+ return JSON.parse(readFileSync6(path, "utf8"));
2081
2169
  } catch (e) {
2082
2170
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
2083
2171
  }
@@ -2133,18 +2221,18 @@ function parseResolved(raw, m, where) {
2133
2221
  }
2134
2222
  function loadConfig(crewHome, configFile, opts = {}) {
2135
2223
  const file = configFile ?? findConfigFile(crewHome);
2136
- if (opts.text === void 0 && !existsSync5(file)) {
2224
+ if (opts.text === void 0 && !existsSync6(file)) {
2137
2225
  throw new ConfigError(
2138
2226
  `no config found. Looked in:
2139
2227
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
2140
2228
 
2141
2229
  Create the first of those:
2142
- mkdir -p ${dirname3(defaultConfigPath())} && cp ${join6(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2230
+ mkdir -p ${dirname3(defaultConfigPath())} && cp ${join7(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2143
2231
  );
2144
2232
  }
2145
2233
  let raw;
2146
2234
  try {
2147
- raw = parse2(opts.text ?? readFileSync5(file, "utf8"));
2235
+ raw = parse2(opts.text ?? readFileSync6(file, "utf8"));
2148
2236
  } catch (e) {
2149
2237
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
2150
2238
  }
@@ -2222,7 +2310,7 @@ Create the first of those:
2222
2310
  useNvm: shipRaw.useNvm !== false,
2223
2311
  nvmSh: shipRaw.nvmSh,
2224
2312
  stateDir,
2225
- logFile: expand(shipRaw.logFile ?? join6(tmpdir(), "crew.log"), base),
2313
+ logFile: expand(shipRaw.logFile ?? join7(tmpdir(), "crew.log"), base),
2226
2314
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
2227
2315
  maxConcurrentAgents,
2228
2316
  streamRetentionDays,
@@ -2442,8 +2530,8 @@ function repoIdForName(r, name) {
2442
2530
  }
2443
2531
  function resolveApiKey(r) {
2444
2532
  if (r.apiKey) return r.apiKey;
2445
- if (r.apiKeyFile && r.apiKeyVar && existsSync5(r.apiKeyFile)) {
2446
- for (const line of readFileSync5(r.apiKeyFile, "utf8").split("\n")) {
2533
+ if (r.apiKeyFile && r.apiKeyVar && existsSync6(r.apiKeyFile)) {
2534
+ for (const line of readFileSync6(r.apiKeyFile, "utf8").split("\n")) {
2447
2535
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
2448
2536
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
2449
2537
  }
@@ -2506,7 +2594,7 @@ function configuredMembers(rt) {
2506
2594
  return out;
2507
2595
  }
2508
2596
  function defaultRepoDir(basePath, workspace, repoName) {
2509
- return join6(basePath, workspace, repoName);
2597
+ return join7(basePath, workspace, repoName);
2510
2598
  }
2511
2599
  function reposOf(r) {
2512
2600
  const named = Object.entries(r.repos);
@@ -2530,7 +2618,7 @@ function passengerRepoTargets(r) {
2530
2618
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
2531
2619
  }
2532
2620
  function passengerCheckoutDir(basePath, workspace, repoName) {
2533
- return join6(basePath, workspace, ".passenger-checkouts", repoName);
2621
+ return join7(basePath, workspace, ".passenger-checkouts", repoName);
2534
2622
  }
2535
2623
  function passengerCheckoutTargets(r) {
2536
2624
  if (!r.hostPassengers) return [];
@@ -2662,8 +2750,8 @@ function rosterMarkdown(roster, meId) {
2662
2750
  }
2663
2751
 
2664
2752
  // src/state.ts
2665
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "fs";
2666
- import { join as join7 } from "path";
2753
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "fs";
2754
+ import { join as join8 } from "path";
2667
2755
  var EPOCH = "1970-01-01T00:00:00.000Z";
2668
2756
  var State = class _State {
2669
2757
  dir;
@@ -2672,7 +2760,7 @@ var State = class _State {
2672
2760
  mkdirSync3(dir, { recursive: true });
2673
2761
  }
2674
2762
  path(...parts) {
2675
- return join7(this.dir, ...parts);
2763
+ return join8(this.dir, ...parts);
2676
2764
  }
2677
2765
  /**
2678
2766
  * The timestamp every "new since last poll" check is measured against.
@@ -2683,7 +2771,7 @@ var State = class _State {
2683
2771
  */
2684
2772
  watermark() {
2685
2773
  try {
2686
- const v = readFileSync6(this.path(".poll-watermark"), "utf8").trim();
2774
+ const v = readFileSync7(this.path(".poll-watermark"), "utf8").trim();
2687
2775
  return v || EPOCH;
2688
2776
  } catch {
2689
2777
  return EPOCH;
@@ -2708,10 +2796,10 @@ var State = class _State {
2708
2796
  }
2709
2797
  /** Whole-crew pause, and the per-role sentinels one level down. */
2710
2798
  isPaused() {
2711
- return existsSync6(this.path(".crew-paused"));
2799
+ return existsSync7(this.path(".crew-paused"));
2712
2800
  }
2713
2801
  isRolePaused(role) {
2714
- return existsSync6(this.path(`.role-paused-${role}`));
2802
+ return existsSync7(this.path(`.role-paused-${role}`));
2715
2803
  }
2716
2804
  pausedRoles(roles) {
2717
2805
  return new Set(roles.filter((r) => this.isRolePaused(r)));
@@ -2728,7 +2816,7 @@ var State = class _State {
2728
2816
  * while agents keep working.
2729
2817
  */
2730
2818
  isReleasePaused() {
2731
- return existsSync6(this.path(".release-paused"));
2819
+ return existsSync7(this.path(".release-paused"));
2732
2820
  }
2733
2821
  setReleasePaused(paused) {
2734
2822
  if (paused) writeFileSync4(this.path(".release-paused"), "");
@@ -2769,7 +2857,7 @@ var State = class _State {
2769
2857
  const name = _State.safe(rawName);
2770
2858
  const file = this.path(`.${name}.lock`);
2771
2859
  try {
2772
- const existing = Number.parseInt(readFileSync6(file, "utf8").trim(), 10);
2860
+ const existing = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2773
2861
  if (Number.isFinite(existing) && existing > 0) {
2774
2862
  try {
2775
2863
  process.kill(existing, 0);
@@ -2863,7 +2951,7 @@ var State = class _State {
2863
2951
  const heldReported = `.release-held-${suffix}`;
2864
2952
  const read = (f) => {
2865
2953
  try {
2866
- return readFileSync6(this.path(f), "utf8").trim() || null;
2954
+ return readFileSync7(this.path(f), "utf8").trim() || null;
2867
2955
  } catch {
2868
2956
  return null;
2869
2957
  }
@@ -2913,7 +3001,7 @@ var State = class _State {
2913
3001
  /** Whether the board was already told about this head's red gate. */
2914
3002
  heldReported: () => {
2915
3003
  try {
2916
- return readFileSync6(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
3004
+ return readFileSync7(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
2917
3005
  } catch {
2918
3006
  return null;
2919
3007
  }
@@ -2950,7 +3038,7 @@ var State = class _State {
2950
3038
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
2951
3039
  const read = (f) => {
2952
3040
  try {
2953
- return readFileSync6(this.path(f), "utf8").trim() || null;
3041
+ return readFileSync7(this.path(f), "utf8").trim() || null;
2954
3042
  } catch {
2955
3043
  return null;
2956
3044
  }
@@ -3015,7 +3103,7 @@ var State = class _State {
3015
3103
  return {
3016
3104
  previous: () => {
3017
3105
  try {
3018
- const parsed = JSON.parse(readFileSync6(this.path(file), "utf8"));
3106
+ const parsed = JSON.parse(readFileSync7(this.path(file), "utf8"));
3019
3107
  return parsed && typeof parsed === "object" ? parsed : {};
3020
3108
  } catch {
3021
3109
  return {};
@@ -3036,7 +3124,7 @@ var State = class _State {
3036
3124
  return {
3037
3125
  previous: () => {
3038
3126
  try {
3039
- const parsed = JSON.parse(readFileSync6(this.path(file), "utf8"));
3127
+ const parsed = JSON.parse(readFileSync7(this.path(file), "utf8"));
3040
3128
  return Array.isArray(parsed) ? parsed : [];
3041
3129
  } catch {
3042
3130
  return [];
@@ -3069,7 +3157,7 @@ var State = class _State {
3069
3157
  }
3070
3158
  readInt(file) {
3071
3159
  try {
3072
- return Number.parseInt(readFileSync6(this.path(file), "utf8").trim(), 10) || 0;
3160
+ return Number.parseInt(readFileSync7(this.path(file), "utf8").trim(), 10) || 0;
3073
3161
  } catch {
3074
3162
  return 0;
3075
3163
  }
@@ -3096,6 +3184,7 @@ var DEFAULT_CONTRACT = {
3096
3184
  blockedBy: "blocked_by",
3097
3185
  updatedAt: "updated_at",
3098
3186
  needsDesign: "needs_design",
3187
+ attachments: "attachments",
3099
3188
  workMode: "work_mode",
3100
3189
  reportType: "report_type",
3101
3190
  epic: "epic_id",
@@ -3103,7 +3192,8 @@ var DEFAULT_CONTRACT = {
3103
3192
  ship: "ship_id",
3104
3193
  heldBy: "held_by_ship_id",
3105
3194
  verifiedSha: "verified_sha",
3106
- prRef: "pr_ref"
3195
+ prRef: "pr_ref",
3196
+ position: "position"
3107
3197
  },
3108
3198
  comments: {
3109
3199
  parent: "ticket_id",
@@ -3373,9 +3463,9 @@ function describeRelease(o, route) {
3373
3463
  }
3374
3464
 
3375
3465
  // src/install.ts
3376
- import { dirname as dirname4, join as join8, extname } from "path";
3377
- import { homedir as homedir5 } from "os";
3378
- import { accessSync, constants, existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync5, unlinkSync, realpathSync } from "fs";
3466
+ import { dirname as dirname4, join as join9, extname } from "path";
3467
+ import { homedir as homedir6 } from "os";
3468
+ import { accessSync, constants, existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync5, unlinkSync, realpathSync } from "fs";
3379
3469
  import { spawnSync as spawnSync4 } from "child_process";
3380
3470
  import { createHash } from "crypto";
3381
3471
 
@@ -3387,7 +3477,7 @@ function isCompiledBinary(moduleUrl) {
3387
3477
  // src/install.ts
3388
3478
  function crewInvocation(crewHome) {
3389
3479
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
3390
- return [process.execPath, join8(crewHome, "bin", "crew")];
3480
+ return [process.execPath, join9(crewHome, "bin", "crew")];
3391
3481
  }
3392
3482
  var INTERVAL_SECONDS = 120;
3393
3483
  function labelFor(crewHome, job) {
@@ -3425,7 +3515,7 @@ function pathFor(ship) {
3425
3515
  return parts.join(":");
3426
3516
  }
3427
3517
  function findOnPath(bin, searchPath) {
3428
- const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join8(dir, bin));
3518
+ const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join9(dir, bin));
3429
3519
  for (const candidate of candidates) {
3430
3520
  try {
3431
3521
  accessSync(candidate, constants.X_OK);
@@ -3435,7 +3525,7 @@ function findOnPath(bin, searchPath) {
3435
3525
  }
3436
3526
  return void 0;
3437
3527
  }
3438
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join8(homedir5(), ".docker", "bin")].join(":");
3528
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join9(homedir6(), ".docker", "bin")].join(":");
3439
3529
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
3440
3530
  const schedulerPath = pathFor(ship);
3441
3531
  if (findOnPath("docker", schedulerPath)) return void 0;
@@ -3447,7 +3537,7 @@ function xmlEscape(s) {
3447
3537
  }
3448
3538
  function planLaunchd(ship, crewHome, job) {
3449
3539
  const label = labelFor(crewHome, job);
3450
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3540
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3451
3541
  const crewLog = ship.logFile;
3452
3542
  const schedulerLog = schedulerLogFor(crewLog, job);
3453
3543
  const persistent = job === "run";
@@ -3499,8 +3589,8 @@ ${scheduleKeys}
3499
3589
  }
3500
3590
  function planSystemd(ship, crewHome, job) {
3501
3591
  const label = labelFor(crewHome, job);
3502
- const unitDir = join8(homedir5(), ".config", "systemd", "user");
3503
- const servicePath = join8(unitDir, `${label}.service`);
3592
+ const unitDir = join9(homedir6(), ".config", "systemd", "user");
3593
+ const servicePath = join9(unitDir, `${label}.service`);
3504
3594
  const crewLog = ship.logFile;
3505
3595
  const schedulerLog = schedulerLogFor(crewLog, job);
3506
3596
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -3537,7 +3627,7 @@ WantedBy=default.target
3537
3627
  schedulerLog
3538
3628
  };
3539
3629
  }
3540
- const timerPath = join8(unitDir, `${label}.timer`);
3630
+ const timerPath = join9(unitDir, `${label}.timer`);
3541
3631
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
3542
3632
  [Unit]
3543
3633
  Description=${description}
@@ -3604,7 +3694,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3604
3694
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3605
3695
  const label = labelFor(crewHome, job);
3606
3696
  if (hostShip === "macos") {
3607
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3697
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3608
3698
  return {
3609
3699
  mechanism: "launchd",
3610
3700
  unitPaths: [unitPath],
@@ -3614,18 +3704,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3614
3704
  };
3615
3705
  }
3616
3706
  if (hostShip === "linux" && hasSystemd) {
3617
- const unitDir = join8(homedir5(), ".config", "systemd", "user");
3707
+ const unitDir = join9(homedir6(), ".config", "systemd", "user");
3618
3708
  if (job === "run") {
3619
3709
  return {
3620
3710
  mechanism: "systemd",
3621
- unitPaths: [join8(unitDir, `${label}.service`)],
3711
+ unitPaths: [join9(unitDir, `${label}.service`)],
3622
3712
  crontabLine: null,
3623
3713
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
3624
3714
  };
3625
3715
  }
3626
3716
  return {
3627
3717
  mechanism: "systemd",
3628
- unitPaths: [join8(unitDir, `${label}.service`), join8(unitDir, `${label}.timer`)],
3718
+ unitPaths: [join9(unitDir, `${label}.service`), join9(unitDir, `${label}.timer`)],
3629
3719
  crontabLine: null,
3630
3720
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
3631
3721
  };
@@ -3643,7 +3733,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3643
3733
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
3644
3734
  const label = labelFor(crewHome, "run");
3645
3735
  if (hostShip === "macos") {
3646
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3736
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3647
3737
  return {
3648
3738
  mechanism: "launchd",
3649
3739
  startCommand: ["launchctl", "load", unitPath],
@@ -3738,7 +3828,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3738
3828
  }
3739
3829
  }
3740
3830
  for (const path of plan.unitPaths) {
3741
- if (!existsSync7(path)) continue;
3831
+ if (!existsSync8(path)) continue;
3742
3832
  if (dryRun) {
3743
3833
  log.emit(`would remove ${path}`);
3744
3834
  continue;
@@ -3757,7 +3847,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3757
3847
  }
3758
3848
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
3759
3849
  function launchAgentsDir() {
3760
- return join8(homedir5(), "Library", "LaunchAgents");
3850
+ return join9(homedir6(), "Library", "LaunchAgents");
3761
3851
  }
3762
3852
  function xmlUnescape(s) {
3763
3853
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -3774,25 +3864,25 @@ function loadedCrewLabels() {
3774
3864
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
3775
3865
  }
3776
3866
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
3777
- const files = existsSync7(dir) ? readdirSync(dir) : [];
3867
+ const files = existsSync8(dir) ? readdirSync2(dir) : [];
3778
3868
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
3779
- const plistPath = join8(dir, f);
3869
+ const plistPath = join9(dir, f);
3780
3870
  let program = null;
3781
3871
  try {
3782
- program = programFromPlist(readFileSync7(plistPath, "utf8"));
3872
+ program = programFromPlist(readFileSync8(plistPath, "utf8"));
3783
3873
  } catch {
3784
3874
  }
3785
3875
  return {
3786
3876
  label: f.slice(0, -".plist".length),
3787
3877
  plistPath,
3788
3878
  program,
3789
- programExists: program !== null && existsSync7(program)
3879
+ programExists: program !== null && existsSync8(program)
3790
3880
  };
3791
3881
  });
3792
3882
  const seen = new Set(units.map((u) => u.label));
3793
3883
  for (const label of [...loaded].sort()) {
3794
3884
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
3795
- units.push({ label, plistPath: join8(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
3885
+ units.push({ label, plistPath: join9(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
3796
3886
  }
3797
3887
  return units;
3798
3888
  }
@@ -3805,9 +3895,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
3805
3895
  log.emit(`would unload and remove ${unit.label}`);
3806
3896
  return;
3807
3897
  }
3808
- if (existsSync7(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
3898
+ if (existsSync8(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
3809
3899
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
3810
- if (existsSync7(unit.plistPath)) unlinkSync(unit.plistPath);
3900
+ if (existsSync8(unit.plistPath)) unlinkSync(unit.plistPath);
3811
3901
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
3812
3902
  }
3813
3903
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -3890,7 +3980,7 @@ import { statfsSync } from "fs";
3890
3980
  // src/git.ts
3891
3981
  import { execFileSync } from "child_process";
3892
3982
  import {
3893
- existsSync as existsSync8,
3983
+ existsSync as existsSync9,
3894
3984
  mkdirSync as mkdirSync5,
3895
3985
  openSync as openSync2,
3896
3986
  closeSync as closeSync2,
@@ -4262,7 +4352,7 @@ function cloneUrlFor(remote) {
4262
4352
  return `git@github.com:${remote}.git`;
4263
4353
  }
4264
4354
  function ensureRepoCheckout(dir, remote) {
4265
- if (existsSync8(`${dir}/.git`)) return false;
4355
+ if (existsSync9(`${dir}/.git`)) return false;
4266
4356
  if (!remote) {
4267
4357
  throw new GitError(
4268
4358
  `${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
@@ -4697,6 +4787,7 @@ var Tracker = class {
4697
4787
  * so concurrent first writes share one round of field-metadata reads.
4698
4788
  */
4699
4789
  authorship;
4790
+ attachments;
4700
4791
  stateDir;
4701
4792
  constructor(route, ship) {
4702
4793
  this.route = route;
@@ -5149,6 +5240,28 @@ var Tracker = class {
5149
5240
  shipId: shipRow?.id ?? null
5150
5241
  };
5151
5242
  }
5243
+ /**
5244
+ * The attachments probe (CREW-1558): which table the Issues `attachments`
5245
+ * column references, read once per process like the authorship probe. On
5246
+ * TABL-1557 two runs reported a ticket's screenshot as a 404 because, with
5247
+ * no table id in hand, they guessed `files/<attachment id>` paths; naming
5248
+ * the table in the Environment section is what makes the lookup mechanical.
5249
+ */
5250
+ attachmentsProbe() {
5251
+ this.attachments ??= this.probeAttachments();
5252
+ return this.attachments;
5253
+ }
5254
+ async probeAttachments() {
5255
+ try {
5256
+ const model = await this.client.dataModels.get(this.models.issues);
5257
+ const field = model.fields.find((f) => f.columnName === this.contract.columns.attachments);
5258
+ const fieldType = field?.fieldType;
5259
+ const target = fieldType?.targetModelId;
5260
+ return { mediaLibraryModelId: typeof target === "string" && target ? target : null };
5261
+ } catch {
5262
+ return { mediaLibraryModelId: null };
5263
+ }
5264
+ }
5152
5265
  async columnNames(modelId) {
5153
5266
  try {
5154
5267
  const model = await this.client.dataModels.get(modelId);
@@ -5391,20 +5504,25 @@ function issueNumber(t) {
5391
5504
  function epicRank(t) {
5392
5505
  return t.epicInProgress ? 0 : 1;
5393
5506
  }
5507
+ function positionRank(t) {
5508
+ const p = t.position;
5509
+ return typeof p === "number" && Number.isFinite(p) ? p : Number.POSITIVE_INFINITY;
5510
+ }
5394
5511
  function rank(t, c = DEFAULT_CONTRACT) {
5395
- return [effectivePriority(t, c), severityRank(t, c), epicRank(t), issueNumber(t)];
5512
+ return [effectivePriority(t, c), positionRank(t), severityRank(t, c), epicRank(t), issueNumber(t)];
5396
5513
  }
5397
5514
  function compareRank(a, b, c = DEFAULT_CONTRACT) {
5398
5515
  const ra = rank(a, c);
5399
5516
  const rb = rank(b, c);
5400
5517
  for (let i = 0; i < ra.length; i++) {
5401
- const d = ra[i] - rb[i];
5402
- if (d !== 0) return d;
5518
+ const x = ra[i];
5519
+ const y = rb[i];
5520
+ if (x !== y) return x < y ? -1 : 1;
5403
5521
  }
5404
5522
  return 0;
5405
5523
  }
5406
5524
  function rankScalar(t, c = DEFAULT_CONTRACT) {
5407
- const [p, s, e, n] = rank(t, c);
5525
+ const [p, , s, e, n] = rank(t, c);
5408
5526
  return p * 1e9 + s * 1e7 + e * 1e6 + n;
5409
5527
  }
5410
5528
  var NOTHING_ACTIONABLE = 999999999999;
@@ -5611,7 +5729,7 @@ function claimStatuses(role, c) {
5611
5729
  }
5612
5730
  function rankedCandidates(role, i) {
5613
5731
  const c = i.contract ?? DEFAULT_CONTRACT;
5614
- return [...roleCandidates(role, i)].sort((a, b) => rankScalar(a, c) - rankScalar(b, c));
5732
+ return [...roleCandidates(role, i)].sort((a, b) => compareRank(a, b, c));
5615
5733
  }
5616
5734
  function roleTopRank(role, i) {
5617
5735
  if (role !== "qa" && role !== "triage" && !i.seats[role]) return NOTHING_ACTIONABLE;
@@ -5624,7 +5742,7 @@ function actionableSummary(i, pending) {
5624
5742
  const c = i.contract ?? DEFAULT_CONTRACT;
5625
5743
  const all = pending.flatMap((role) => roleCandidates(role, i));
5626
5744
  if (all.length === 0) return { count: 0 };
5627
- const top = all.reduce((best, t) => rankScalar(t, c) < rankScalar(best, c) ? t : best);
5745
+ const top = all.reduce((best, t) => compareRank(t, best, c) < 0 ? t : best);
5628
5746
  return { count: all.length, top };
5629
5747
  }
5630
5748
  var FIXED_RANK = { qa: -2, triage: -1 };
@@ -5850,6 +5968,7 @@ function blockers(t, i) {
5850
5968
  function modeCell(t, i) {
5851
5969
  return i.workModes && i.workModes.modes.size > 0 ? modeOf(t, i.workModes) : "\u2014";
5852
5970
  }
5971
+ var effCell = (t) => `p${effectivePriority(t)}${positionRank(t) === Number.POSITIVE_INFINITY ? "" : ` #${positionRank(t)}`}`;
5853
5972
  var sorted = (ts) => [...ts].sort(compareRank);
5854
5973
  var stream = (segments) => `${segments.join("\n")}
5855
5974
  `;
@@ -5868,7 +5987,7 @@ function buildingDigest(i) {
5868
5987
  const worktree = i.worktreeFor ? i.worktreeFor(t) : "";
5869
5988
  const dir = i.dirFor ? i.dirFor(t) : null;
5870
5989
  const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
5871
- return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5990
+ return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5872
5991
  };
5873
5992
  const blockedRow = (t) => `| ${displayKey2(t)} | ${t.status} | ${who(t, i)} | p${effectivePriority(t)} | ${blockers(t, i)} |`;
5874
5993
  const blockedTable = (ts) => ts.length === 0 ? "\n_None._\n" : `
@@ -5929,7 +6048,7 @@ function qaDigest(i) {
5929
6048
  const n = newFromOthers(i, t.id);
5930
6049
  const dir = i.dirFor ? i.dirFor(t) : null;
5931
6050
  const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
5932
- return `| ${displayKey2(t)} | ${repo} | ${modeCell(t, i)} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
6051
+ return `| ${displayKey2(t)} | ${repo} | ${modeCell(t, i)} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5933
6052
  };
5934
6053
  const waiting = i.tickets.filter((t) => (t.status === "qa" || t.status === "fixed") && i.blocked.has(t.id));
5935
6054
  const takeable = (status2) => i.tickets.filter((t) => t.status === status2 && !i.blocked.has(t.id));
@@ -6011,7 +6130,7 @@ function materializeBranchForTicket(dir, cfg, t, role) {
6011
6130
 
6012
6131
  // src/poll.ts
6013
6132
  import { writeFileSync as writeFileSync6 } from "fs";
6014
- import { join as join9 } from "path";
6133
+ import { join as join10 } from "path";
6015
6134
 
6016
6135
  // src/stalled.ts
6017
6136
  var STALLED_AFTER_CYCLES = 30;
@@ -6124,6 +6243,7 @@ async function decideCycle(o) {
6124
6243
  data: { tickets: tickets.length, comments: comments.length }
6125
6244
  });
6126
6245
  const epicColumn = tracker.contract.columns.epic;
6246
+ const positionColumn = tracker.contract.columns.position;
6127
6247
  const epicBuilding = tracker.contract.epics.statuses.building;
6128
6248
  const epicStatusColumn = tracker.contract.epics.statusColumn;
6129
6249
  const epicStatusById = new Map(epicRows.map((e) => [e.id, e[epicStatusColumn] ?? null]));
@@ -6143,6 +6263,9 @@ async function decideCycle(o) {
6143
6263
  for (const t of tickets) {
6144
6264
  const epicId = t[epicColumn];
6145
6265
  t.epicInProgress = typeof epicId === "string" && epicStatusById.get(epicId) === epicBuilding;
6266
+ const pos = t[positionColumn];
6267
+ const n = typeof pos === "string" && pos.trim() !== "" ? Number(pos) : pos;
6268
+ t.position = typeof n === "number" && Number.isFinite(n) ? n : null;
6146
6269
  }
6147
6270
  const extra = await tracker.ticketsByIds(missingBlockerIds(tickets));
6148
6271
  const info = blockerInfoMap(tickets, extra);
@@ -6283,7 +6406,7 @@ async function decideCycle(o) {
6283
6406
  }
6284
6407
  return decision;
6285
6408
  }
6286
- var digestPath = (stateDir, route, role) => join9(stateDir, `digest-${route}-${role}.md`);
6409
+ var digestPath = (stateDir, route, role) => join10(stateDir, `digest-${route}-${role}.md`);
6287
6410
  function writeDigest(o, d, role, stateDir) {
6288
6411
  const me = o.route.resolved?.seats[role];
6289
6412
  if (!me) return false;
@@ -6653,9 +6776,9 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
6653
6776
 
6654
6777
  // src/agent.ts
6655
6778
  import { spawn as spawn2 } from "child_process";
6656
- import { readFileSync as readFileSync10, existsSync as existsSync11, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
6779
+ import { readFileSync as readFileSync11, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
6657
6780
  import { finished } from "stream/promises";
6658
- import { join as join12, dirname as dirname6 } from "path";
6781
+ import { join as join13, dirname as dirname6 } from "path";
6659
6782
  import { createHash as createHash2 } from "crypto";
6660
6783
 
6661
6784
  // src/environment.ts
@@ -6775,6 +6898,7 @@ function renderEnvironment(i) {
6775
6898
  const only = repos.length === 1 ? repos[0] : void 0;
6776
6899
  const models = route.resolved?.models;
6777
6900
  const epicsModelId = route.resolved?.epicsModelId;
6901
+ const mediaLibraryModelId = i.attachments?.mediaLibraryModelId ?? null;
6778
6902
  const key = "ISSUE-000";
6779
6903
  const lines = [
6780
6904
  "# Your environment",
@@ -6799,7 +6923,8 @@ function renderEnvironment(i) {
6799
6923
  `- Issues: \`${models.issues}\``,
6800
6924
  `- Comments: \`${models.comments}\``,
6801
6925
  `- Crew: \`${models.crew}\``,
6802
- ...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : []
6926
+ ...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : [],
6927
+ ...mediaLibraryModelId ? [`- Media Library: \`${mediaLibraryModelId}\` (what a ticket's \`${contract.columns.attachments}\` ids point into)`] : []
6803
6928
  );
6804
6929
  }
6805
6930
  const workspaceId = route.resolved?.workspaceId;
@@ -6826,6 +6951,30 @@ function renderEnvironment(i) {
6826
6951
  "status and response body in your comment \u2014 do not write that uploads are",
6827
6952
  "impossible."
6828
6953
  );
6954
+ if (mediaLibraryModelId) {
6955
+ lines.push(
6956
+ "",
6957
+ "## Opening a ticket's attachments",
6958
+ "",
6959
+ `A ticket's or a comment's \`${contract.columns.attachments}\` value is a list of Media Library`,
6960
+ "**record ids** \u2014 not file ids and not URLs. `files/<id>`, `files/<id>/download` and",
6961
+ "`media-library/<id>/...` do not exist for such an id and return 404. For each id,",
6962
+ "read the record, then fetch the file by its storage key:",
6963
+ "",
6964
+ "```sh",
6965
+ `curl -s -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
6966
+ ` "${base}/api/workspaces/${workspaceId}/data-models/${mediaLibraryModelId}/records/<attachment id>"`,
6967
+ '# \u2192 JSON with `storage_key` ("<uuid>/<filename>") and `mime_type`',
6968
+ `curl -s -o <scratch dir>/<filename> -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
6969
+ ` "${base}/api/workspaces/${workspaceId}/files/<storage_key>"`,
6970
+ "```",
6971
+ "",
6972
+ "The MCP forms are `records_controller_find_one` (`dataModelId` = the Media Library",
6973
+ "id above) and `files_controller_download` (`uuid` and `filename` = the two halves",
6974
+ "of the storage key). Save the file outside the repository and open it; where it",
6975
+ "disagrees with the description, the picture is the reproduction."
6976
+ );
6977
+ }
6829
6978
  }
6830
6979
  if (i.shipName) {
6831
6980
  lines.push(
@@ -7044,14 +7193,14 @@ function expandSkillMarkers(text, lookup, opts = {}) {
7044
7193
  }
7045
7194
 
7046
7195
  // src/skills.ts
7047
- import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync10 } from "fs";
7048
- import { join as join11 } from "path";
7196
+ import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
7197
+ import { join as join12 } from "path";
7049
7198
  import { parse as parseYaml } from "yaml";
7050
7199
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError7, TablationApiError as TablationApiError2 } from "@tablation/client";
7051
7200
 
7052
7201
  // src/agents.ts
7053
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
7054
- import { join as join10 } from "path";
7202
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7203
+ import { join as join11 } from "path";
7055
7204
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError6, TablationApiError } from "@tablation/client";
7056
7205
  var PERSONA_NAME = {
7057
7206
  dev: "Developer",
@@ -7065,12 +7214,12 @@ var CREW_AGENT_COLUMN = "agent_id";
7065
7214
  var AgentsSyncError = class extends Error {
7066
7215
  };
7067
7216
  function personaDefaultPrompt(promptsDir, role) {
7068
- const briefPath = join10(promptsDir, "personas", `lane-${role}.md`);
7069
- if (!existsSync9(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7070
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync8(briefPath, "utf8");
7071
- const commonPath = join10(promptsDir, "personas", "common.md");
7072
- if (!existsSync9(commonPath)) return readFileSync8(briefPath, "utf8");
7073
- return `${readFileSync8(commonPath, "utf8")}${readFileSync8(briefPath, "utf8")}`;
7217
+ const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
7218
+ if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7219
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync9(briefPath, "utf8");
7220
+ const commonPath = join11(promptsDir, "personas", "common.md");
7221
+ if (!existsSync10(commonPath)) return readFileSync9(briefPath, "utf8");
7222
+ return `${readFileSync9(commonPath, "utf8")}${readFileSync9(briefPath, "utf8")}`;
7074
7223
  }
7075
7224
  async function resolveAgentId(route, role, opts) {
7076
7225
  const cached = route.resolved?.agentPersonas?.[role]?.agentId;
@@ -7394,7 +7543,7 @@ function describeCrewLink(o) {
7394
7543
  var SkillSyncError = class extends Error {
7395
7544
  };
7396
7545
  function parseSkillFile(path) {
7397
- const text = readFileSync9(path, "utf8");
7546
+ const text = readFileSync10(path, "utf8");
7398
7547
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
7399
7548
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
7400
7549
  const frontmatter = match[1];
@@ -7407,9 +7556,9 @@ function parseSkillFile(path) {
7407
7556
  return { name: meta.name, description: meta.description, prompt: body, file: path };
7408
7557
  }
7409
7558
  function listLocalSkills(promptsDir) {
7410
- const dir = join11(promptsDir, "skills");
7411
- if (!existsSync10(dir)) return [];
7412
- return readdirSync2(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join11(dir, f)));
7559
+ const dir = join12(promptsDir, "skills");
7560
+ if (!existsSync11(dir)) return [];
7561
+ return readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
7413
7562
  }
7414
7563
  async function syncSkills(route, opts) {
7415
7564
  if (!route.resolved) {
@@ -7601,23 +7750,23 @@ var AgentError = class extends Error {
7601
7750
  var AgentSpawnError = class extends AgentError {
7602
7751
  };
7603
7752
  function planAgentRun(o) {
7604
- const briefPath = join12(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7605
- if (!existsSync11(briefPath)) {
7753
+ const briefPath = join13(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7754
+ if (!existsSync12(briefPath)) {
7606
7755
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
7607
7756
  }
7608
- const commonPath = join12(o.route.promptsDir, "personas", "common.md");
7609
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync11(commonPath);
7757
+ const commonPath = join13(o.route.promptsDir, "personas", "common.md");
7758
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync12(commonPath);
7610
7759
  const now = o.now ?? (() => Date.now());
7611
7760
  const routeLabel = routeSlug(o.route.route);
7612
- const digestPath2 = join12(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7761
+ const digestPath2 = join13(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7613
7762
  let digest;
7614
7763
  let ageSeconds;
7615
- if (existsSync11(digestPath2)) {
7764
+ if (existsSync12(digestPath2)) {
7616
7765
  ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
7617
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync10(digestPath2, "utf8");
7766
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync11(digestPath2, "utf8");
7618
7767
  }
7619
- const common = usesCommon ? readFileSync10(commonPath, "utf8") : "";
7620
- const brief = readFileSync10(briefPath, "utf8");
7768
+ const common = usesCommon ? readFileSync11(commonPath, "utf8") : "";
7769
+ const brief = readFileSync11(briefPath, "utf8");
7621
7770
  const effectiveCommon = o.divergedPrompt ? "" : common;
7622
7771
  const effectiveBrief = o.divergedPrompt ?? brief;
7623
7772
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -7639,7 +7788,7 @@ function planAgentRun(o) {
7639
7788
  digest
7640
7789
  });
7641
7790
  const promptSha = createHash2("sha256").update(expanded.text).digest("hex");
7642
- const streamsDir = join12(o.stateDir, "streams");
7791
+ const streamsDir = join13(o.stateDir, "streams");
7643
7792
  const base = `${routeLabel}-${o.role}-${o.cycle}`;
7644
7793
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
7645
7794
  const { toolchain, model } = seat;
@@ -7695,8 +7844,8 @@ function planAgentRun(o) {
7695
7844
  digestAttached: digest !== void 0,
7696
7845
  digestAgeSeconds: ageSeconds,
7697
7846
  model,
7698
- streamPath: join12(streamsDir, `${base}.jsonl`),
7699
- eventsPath: join12(streamsDir, `${base}.events.jsonl`),
7847
+ streamPath: join13(streamsDir, `${base}.jsonl`),
7848
+ eventsPath: join13(streamsDir, `${base}.events.jsonl`),
7700
7849
  ticket: o.ticket,
7701
7850
  agentLog,
7702
7851
  promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
@@ -7706,7 +7855,7 @@ function planAgentRun(o) {
7706
7855
  function describePlan(p) {
7707
7856
  return [
7708
7857
  `role: ${p.role}`,
7709
- ...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}`] : [],
7858
+ ...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}${p.provider ? `; provider ${p.provider}` : ""}`] : [],
7710
7859
  ...(p.warnings ?? []).map((w) => `warning: ${w}`),
7711
7860
  // Only for a non-default toolchain, so a plain Claude run's output is unchanged.
7712
7861
  ...p.toolchain && p.toolchain !== "claude" ? [`toolchain: ${p.toolchain} (${p.harness ?? "claude"})`] : [],
@@ -7902,13 +8051,13 @@ function spawnAgent(plan, emit) {
7902
8051
  }
7903
8052
 
7904
8053
  // src/run-worktree.ts
7905
- import { existsSync as existsSync13 } from "fs";
7906
- import { dirname as dirname7, join as join14 } from "path";
8054
+ import { existsSync as existsSync14 } from "fs";
8055
+ import { dirname as dirname7, join as join15 } from "path";
7907
8056
 
7908
8057
  // src/ports.ts
7909
8058
  import { execFileSync as execFileSync2 } from "child_process";
7910
- import { existsSync as existsSync12 } from "fs";
7911
- import { join as join13 } from "path";
8059
+ import { existsSync as existsSync13 } from "fs";
8060
+ import { join as join14 } from "path";
7912
8061
  var run = (cmd, args) => {
7913
8062
  try {
7914
8063
  return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -7953,7 +8102,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
7953
8102
  }
7954
8103
  function worktreeExists(parent, prefix, n) {
7955
8104
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
7956
- if (existsSync12(join13(parent, name, ".git"))) return true;
8105
+ if (existsSync13(join14(parent, name, ".git"))) return true;
7957
8106
  }
7958
8107
  return false;
7959
8108
  }
@@ -8027,7 +8176,7 @@ function planRunWorktree(dir, cfg, t, role) {
8027
8176
  const existing = worktreeForNumber(dir, num);
8028
8177
  const located = locateBranchForTicket(dir, cfg, t, role);
8029
8178
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8030
- if (existing && !existing.prunable && existsSync13(existing.path)) {
8179
+ if (existing && !existing.prunable && existsSync14(existing.path)) {
8031
8180
  const branch = existing.branch;
8032
8181
  if (status(existing.path).length > 0) return { action: "leave-dirty", path: existing.path, branch };
8033
8182
  if (!branch) return { action: "leave-unpushed", path: existing.path, branch: "(detached HEAD)" };
@@ -8041,7 +8190,7 @@ function planRunWorktree(dir, cfg, t, role) {
8041
8190
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8042
8191
  return { action: "recut", path: existing.path, branch, base: baseRef };
8043
8192
  }
8044
- const path = join14(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8193
+ const path = join15(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8045
8194
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8046
8195
  return { action: "cut-remote", path, branch: located.branch };
8047
8196
  }
@@ -8088,7 +8237,7 @@ function worktreeHoldsUnpushed(path, remote) {
8088
8237
  }
8089
8238
  async function finishRunWorktree(dir, cfg, branch) {
8090
8239
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8091
- if (!wt || !existsSync13(wt.path)) return { kind: "none" };
8240
+ if (!wt || !existsSync14(wt.path)) return { kind: "none" };
8092
8241
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
8093
8242
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
8094
8243
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -8100,7 +8249,7 @@ async function finishRunWorktree(dir, cfg, branch) {
8100
8249
  if (pids.length) await killGently(pids);
8101
8250
  }
8102
8251
  gitOk(dir, ["worktree", "remove", wt.path]);
8103
- return existsSync13(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8252
+ return existsSync14(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8104
8253
  }
8105
8254
 
8106
8255
  // src/handoff-guard.ts
@@ -8249,13 +8398,13 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
8249
8398
  }
8250
8399
 
8251
8400
  // src/connect-wizard.ts
8252
- import { delimiter, dirname as dirname8, join as join15 } from "path";
8253
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
8401
+ import { delimiter, dirname as dirname8, join as join16 } from "path";
8402
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
8254
8403
  import { stringify } from "yaml";
8255
8404
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
8256
8405
  function shouldRunWizard(opts) {
8257
8406
  if (!opts.isTTY) return false;
8258
- const exists = opts.configExists ?? existsSync14;
8407
+ const exists = opts.configExists ?? existsSync15;
8259
8408
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
8260
8409
  }
8261
8410
  function firstRunConfigPath() {
@@ -8272,7 +8421,7 @@ function defaultIsExecutable(p) {
8272
8421
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
8273
8422
  for (const dir of pathEnv.split(delimiter)) {
8274
8423
  if (!dir) continue;
8275
- const candidate = join15(dir, name);
8424
+ const candidate = join16(dir, name);
8276
8425
  if (isExecutable(candidate)) return candidate;
8277
8426
  }
8278
8427
  return void 0;
@@ -8367,8 +8516,8 @@ Wrote ${o.configPath}.
8367
8516
  }
8368
8517
 
8369
8518
  // src/release-run.ts
8370
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8371
- import { dirname as dirname9, join as join16 } from "path";
8519
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8520
+ import { dirname as dirname9, join as join17 } from "path";
8372
8521
  import { tmpdir as tmpdir2 } from "os";
8373
8522
 
8374
8523
  // src/release.ts
@@ -9026,13 +9175,13 @@ async function bump(o, size) {
9026
9175
  }
9027
9176
  return printed;
9028
9177
  }
9029
- const files = o.repo.release.versionFiles.map((f) => join16(o.cwd, f));
9178
+ const files = o.repo.release.versionFiles.map((f) => join17(o.cwd, f));
9030
9179
  const first = files[0];
9031
- if (!existsSync15(first)) {
9180
+ if (!existsSync16(first)) {
9032
9181
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
9033
9182
  return null;
9034
9183
  }
9035
- const current = JSON.parse(readFileSync11(first, "utf8")).version;
9184
+ const current = JSON.parse(readFileSync12(first, "utf8")).version;
9036
9185
  if (!current) {
9037
9186
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
9038
9187
  return null;
@@ -9045,7 +9194,7 @@ async function bump(o, size) {
9045
9194
  return version;
9046
9195
  }
9047
9196
  for (const f of files) {
9048
- const src = readFileSync11(f, "utf8");
9197
+ const src = readFileSync12(f, "utf8");
9049
9198
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
9050
9199
  if (out === src) {
9051
9200
  o.emit.error(`no version field in ${f}`);
@@ -9058,13 +9207,13 @@ async function bump(o, size) {
9058
9207
  function writeChangelog(o, version, entries) {
9059
9208
  const file = o.repo.release.changelog;
9060
9209
  if (!file || entries.length === 0) return;
9061
- const path = join16(o.cwd, file);
9210
+ const path = join17(o.cwd, file);
9062
9211
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
9063
9212
  if (o.dryRun) {
9064
9213
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
9065
9214
  return;
9066
9215
  }
9067
- const existing = existsSync15(path) ? readFileSync11(path, "utf8") : "# Changelog\n";
9216
+ const existing = existsSync16(path) ? readFileSync12(path, "utf8") : "# Changelog\n";
9068
9217
  writeFileSync9(path, insertChangelogSection(existing, section));
9069
9218
  }
9070
9219
  async function confirm(o, expected) {
@@ -9146,8 +9295,8 @@ async function confirmLastRelease(o, decision) {
9146
9295
  function openReleaseCheckout(o) {
9147
9296
  const { remote, base } = o.repo.branch;
9148
9297
  const branch = `crew/release-${Date.now()}`;
9149
- const root = mkdtempSync(join16(tmpdir2(), "crew-release-"));
9150
- const dir = join16(root, "checkout");
9298
+ const root = mkdtempSync(join17(tmpdir2(), "crew-release-"));
9299
+ const dir = join17(root, "checkout");
9151
9300
  const close = () => {
9152
9301
  gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
9153
9302
  rmSync6(root, { recursive: true, force: true });
@@ -9161,10 +9310,10 @@ function openReleaseCheckout(o) {
9161
9310
  throw e;
9162
9311
  }
9163
9312
  for (const f of o.repo.worktrees.copy) {
9164
- const src = join16(o.cwd, f);
9165
- if (!existsSync15(src)) continue;
9166
- mkdirSync8(dirname9(join16(dir, f)), { recursive: true });
9167
- copyFileSync(src, join16(dir, f));
9313
+ const src = join17(o.cwd, f);
9314
+ if (!existsSync16(src)) continue;
9315
+ mkdirSync8(dirname9(join17(dir, f)), { recursive: true });
9316
+ copyFileSync(src, join17(dir, f));
9168
9317
  }
9169
9318
  return { dir, branch, close };
9170
9319
  }
@@ -9633,7 +9782,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
9633
9782
  }
9634
9783
 
9635
9784
  // src/watch.ts
9636
- import { watch as watchFile, existsSync as existsSync16, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
9785
+ import { watch as watchFile, existsSync as existsSync17, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
9637
9786
  var E = "\x1B[";
9638
9787
  var dim = (s) => `${E}2m${s}${E}0m`;
9639
9788
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -9734,7 +9883,7 @@ function renderHeaders(states, now = Date.now()) {
9734
9883
  return states.map((s) => renderHeader(s, now)).join("");
9735
9884
  }
9736
9885
  function readFrom(file, from) {
9737
- if (!existsSync16(file)) return { events: [], offset: from };
9886
+ if (!existsSync17(file)) return { events: [], offset: from };
9738
9887
  const size = statSync6(file).size;
9739
9888
  if (size < from) return readFrom(file, 0);
9740
9889
  if (size === from) return { events: [], offset: from };
@@ -10445,7 +10594,7 @@ async function showLogEntry(route, entryId, opts = {}) {
10445
10594
  }
10446
10595
 
10447
10596
  // src/repos-cmd.ts
10448
- import { homedir as homedir6 } from "os";
10597
+ import { homedir as homedir7 } from "os";
10449
10598
  import { basename as basename7, resolve as resolve3 } from "path";
10450
10599
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
10451
10600
  var ReposError = class extends Error {
@@ -10492,7 +10641,7 @@ function parseAddArgs(args, routes, isDir) {
10492
10641
  return { route, path: "." };
10493
10642
  }
10494
10643
  function expandDir(p, base) {
10495
- const home = p === "~" ? homedir6() : p.startsWith("~/") ? `${homedir6()}/${p.slice(2)}` : p;
10644
+ const home = p === "~" ? homedir7() : p.startsWith("~/") ? `${homedir7()}/${p.slice(2)}` : p;
10496
10645
  return resolve3(base, home);
10497
10646
  }
10498
10647
  function routeMapOf(doc, route) {
@@ -10627,7 +10776,7 @@ function schedulerInstalled(plan, exists, crontab) {
10627
10776
  }
10628
10777
 
10629
10778
  // src/worktree-sweep.ts
10630
- import { existsSync as existsSync17 } from "fs";
10779
+ import { existsSync as existsSync18 } from "fs";
10631
10780
  import { basename as basename8 } from "path";
10632
10781
  function planWorktreeSweep(target, tickets, contract) {
10633
10782
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
@@ -10655,7 +10804,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
10655
10804
  let removed = 0;
10656
10805
  let keptBranches = 0;
10657
10806
  for (const a of actions) {
10658
- if (!dryRun && existsSync17(a.path)) {
10807
+ if (!dryRun && existsSync18(a.path)) {
10659
10808
  let dirty = false;
10660
10809
  try {
10661
10810
  dirty = status(a.path).length > 0;
@@ -10669,7 +10818,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
10669
10818
  continue;
10670
10819
  }
10671
10820
  }
10672
- if (!dryRun && a.requirePushed && a.branch && existsSync17(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
10821
+ if (!dryRun && a.requirePushed && a.branch && existsSync18(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
10673
10822
  log.warn(
10674
10823
  `not removing worktree ${a.path} for ${a.ticket.issue_id} (${a.ticket.status}): ${a.branch} has commits that are not on ${remote}`,
10675
10824
  { ticket: a.ticket.issue_id, step: "worktree" }
@@ -10750,14 +10899,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
10750
10899
  }
10751
10900
 
10752
10901
  // src/stream-sweep.ts
10753
- import { existsSync as existsSync18, readdirSync as readdirSync3, rmSync as rmSync7, statSync as statSync7 } from "fs";
10754
- import { join as join17 } from "path";
10902
+ import { existsSync as existsSync19, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync7 } from "fs";
10903
+ import { join as join18 } from "path";
10755
10904
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
10756
10905
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
10757
- const dir = join17(stateDir, "streams");
10758
- if (!existsSync18(dir)) return [];
10906
+ const dir = join18(stateDir, "streams");
10907
+ if (!existsSync19(dir)) return [];
10759
10908
  const cutoff = now - retentionDays * DAY_MS2;
10760
- return readdirSync3(dir).map((name) => join17(dir, name)).filter((path) => {
10909
+ return readdirSync4(dir).map((name) => join18(dir, name)).filter((path) => {
10761
10910
  try {
10762
10911
  return statSync7(path).mtimeMs < cutoff;
10763
10912
  } catch {
@@ -10783,9 +10932,9 @@ function applyStreamSweep(paths, dryRun, log) {
10783
10932
  return removed;
10784
10933
  }
10785
10934
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
10786
- if (!existsSync18(stateDir)) return [];
10935
+ if (!existsSync19(stateDir)) return [];
10787
10936
  const cutoff = now - retentionDays * DAY_MS2;
10788
- return readdirSync3(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join17(stateDir, name)).filter((path) => {
10937
+ return readdirSync4(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join18(stateDir, name)).filter((path) => {
10789
10938
  try {
10790
10939
  return statSync7(path).mtimeMs < cutoff;
10791
10940
  } catch {
@@ -10795,23 +10944,23 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
10795
10944
  }
10796
10945
 
10797
10946
  // src/cli.ts
10798
- import { readFileSync as readFileSync16, existsSync as existsSync23, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
10947
+ import { readFileSync as readFileSync17, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
10799
10948
  import { createInterface } from "readline/promises";
10800
10949
  import { dirname as dirOf, resolve as resolvePath } from "path";
10801
10950
 
10802
10951
  // src/ssh-keys.ts
10803
10952
  import { execFileSync as execFileSync3 } from "child_process";
10804
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync12, chmodSync } from "fs";
10805
- import { dirname as dirname11, join as join18 } from "path";
10953
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync13, chmodSync } from "fs";
10954
+ import { dirname as dirname11, join as join19 } from "path";
10806
10955
  var SshKeyError = class extends Error {
10807
10956
  };
10808
10957
  function shipSshKeyPathFor(stateDir) {
10809
- return join18(stateDir, "ssh", "id_ed25519");
10958
+ return join19(stateDir, "ssh", "id_ed25519");
10810
10959
  }
10811
10960
  function ensureShipSshKeypair(stateDir) {
10812
10961
  const privateKeyPath = shipSshKeyPathFor(stateDir);
10813
10962
  const publicKeyPath = `${privateKeyPath}.pub`;
10814
- if (!existsSync19(privateKeyPath)) {
10963
+ if (!existsSync20(privateKeyPath)) {
10815
10964
  mkdirSync9(dirname11(privateKeyPath), { recursive: true });
10816
10965
  try {
10817
10966
  execFileSync3("ssh-keygen", [
@@ -10832,10 +10981,10 @@ function ensureShipSshKeypair(stateDir) {
10832
10981
  }
10833
10982
  chmodSync(privateKeyPath, 384);
10834
10983
  }
10835
- if (!existsSync19(publicKeyPath)) {
10984
+ if (!existsSync20(publicKeyPath)) {
10836
10985
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
10837
10986
  }
10838
- const publicKey = readFileSync12(publicKeyPath, "utf8").trim();
10987
+ const publicKey = readFileSync13(publicKeyPath, "utf8").trim();
10839
10988
  return { privateKeyPath, publicKeyPath, publicKey };
10840
10989
  }
10841
10990
  function sshKeygenAvailable() {
@@ -11049,9 +11198,9 @@ function syncAllPassengerCheckouts(cfg) {
11049
11198
 
11050
11199
  // src/tunnel.ts
11051
11200
  import { spawn as spawn3 } from "child_process";
11052
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
11201
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
11053
11202
  import { randomInt } from "crypto";
11054
- import { join as join19 } from "path";
11203
+ import { join as join20 } from "path";
11055
11204
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
11056
11205
  var defaultSpawn = (cmd, args) => spawn3(cmd, args, DEFAULT_SPAWN_OPTIONS);
11057
11206
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -11118,20 +11267,20 @@ function startTunnel(opts) {
11118
11267
  };
11119
11268
  }
11120
11269
  function tunnelStatePath(stateDir, workspaceId) {
11121
- return join19(stateDir, "passengers", `${workspaceId}.json`);
11270
+ return join20(stateDir, "passengers", `${workspaceId}.json`);
11122
11271
  }
11123
11272
  function readPersistedTunnel(stateDir, workspaceId) {
11124
11273
  const path = tunnelStatePath(stateDir, workspaceId);
11125
- if (!existsSync20(path)) return void 0;
11274
+ if (!existsSync21(path)) return void 0;
11126
11275
  try {
11127
- return JSON.parse(readFileSync13(path, "utf8"));
11276
+ return JSON.parse(readFileSync14(path, "utf8"));
11128
11277
  } catch {
11129
11278
  return void 0;
11130
11279
  }
11131
11280
  }
11132
11281
  function writePersistedTunnel(stateDir, state) {
11133
11282
  const path = tunnelStatePath(stateDir, state.workspaceId);
11134
- mkdirSync10(join19(stateDir, "passengers"), { recursive: true });
11283
+ mkdirSync10(join20(stateDir, "passengers"), { recursive: true });
11135
11284
  writeFileSync10(path, `${JSON.stringify(state, null, 2)}
11136
11285
  `);
11137
11286
  }
@@ -11229,9 +11378,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
11229
11378
  }
11230
11379
  }
11231
11380
  function readAllPersistedWorkspaceIds(stateDir) {
11232
- const dir = join19(stateDir, "passengers");
11233
- if (!existsSync20(dir)) return [];
11234
- return readdirSync4(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11381
+ const dir = join20(stateDir, "passengers");
11382
+ if (!existsSync21(dir)) return [];
11383
+ return readdirSync5(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11235
11384
  }
11236
11385
 
11237
11386
  // src/mcp-endpoint-registry.ts
@@ -11351,8 +11500,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
11351
11500
  import { createServer as createHttpServer } from "http";
11352
11501
  import { spawn as spawn4 } from "child_process";
11353
11502
  import { randomBytes } from "crypto";
11354
- import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync5 } from "fs";
11355
- import { join as join20 } from "path";
11503
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
11504
+ import { join as join21 } from "path";
11356
11505
 
11357
11506
  // src/secret-compare.ts
11358
11507
  import { timingSafeEqual } from "crypto";
@@ -11441,20 +11590,20 @@ var defaultSpawn2 = (cmd, args, env) => {
11441
11590
  return child;
11442
11591
  };
11443
11592
  function syncDaemonStatePath(stateDir, workspaceId) {
11444
- return join20(stateDir, "passengers", `${workspaceId}-sync.json`);
11593
+ return join21(stateDir, "passengers", `${workspaceId}-sync.json`);
11445
11594
  }
11446
11595
  function readPersistedSyncDaemon(stateDir, workspaceId) {
11447
11596
  const path = syncDaemonStatePath(stateDir, workspaceId);
11448
- if (!existsSync21(path)) return void 0;
11597
+ if (!existsSync22(path)) return void 0;
11449
11598
  try {
11450
- return JSON.parse(readFileSync14(path, "utf8"));
11599
+ return JSON.parse(readFileSync15(path, "utf8"));
11451
11600
  } catch {
11452
11601
  return void 0;
11453
11602
  }
11454
11603
  }
11455
11604
  function writePersistedSyncDaemon(stateDir, state) {
11456
11605
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
11457
- mkdirSync11(join20(stateDir, "passengers"), { recursive: true });
11606
+ mkdirSync11(join21(stateDir, "passengers"), { recursive: true });
11458
11607
  writeFileSync11(path, `${JSON.stringify(state, null, 2)}
11459
11608
  `);
11460
11609
  }
@@ -11475,10 +11624,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
11475
11624
  }
11476
11625
  }
11477
11626
  function readAllPersistedSyncWorkspaceIds(stateDir) {
11478
- const dir = join20(stateDir, "passengers");
11479
- if (!existsSync21(dir)) return [];
11627
+ const dir = join21(stateDir, "passengers");
11628
+ if (!existsSync22(dir)) return [];
11480
11629
  const suffix = "-sync.json";
11481
- return readdirSync5(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
11630
+ return readdirSync6(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
11482
11631
  }
11483
11632
  function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
11484
11633
  const alive = deps.isPidAlive ?? isPidAlive;
@@ -11533,8 +11682,8 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
11533
11682
  }
11534
11683
 
11535
11684
  // src/daemon.ts
11536
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync15, readdirSync as readdirSync6, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
11537
- import { join as join21 } from "path";
11685
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
11686
+ import { join as join22 } from "path";
11538
11687
  var DEFAULT_FLOOR_MS = 5e3;
11539
11688
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
11540
11689
  var defaultSleep = (ms2) => new Promise((resolve5) => setTimeout(resolve5, ms2));
@@ -11572,8 +11721,8 @@ function latestMtime(path) {
11572
11721
  if (stat.isFile()) return stat.mtimeMs;
11573
11722
  if (!stat.isDirectory()) return 0;
11574
11723
  let latest = stat.mtimeMs;
11575
- for (const entry of readdirSync6(path, { withFileTypes: true })) {
11576
- latest = Math.max(latest, latestMtime(join21(path, entry.name)));
11724
+ for (const entry of readdirSync7(path, { withFileTypes: true })) {
11725
+ latest = Math.max(latest, latestMtime(join22(path, entry.name)));
11577
11726
  }
11578
11727
  return latest;
11579
11728
  }
@@ -11582,13 +11731,13 @@ function computeTreeSignature(paths) {
11582
11731
  }
11583
11732
  function makeStaleChecker(paths, opts = {}) {
11584
11733
  const initial = computeTreeSignature(paths);
11585
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync22(opts.reloadSentinel);
11734
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync23(opts.reloadSentinel);
11586
11735
  }
11587
11736
  function reloadSentinelFor(stateDir) {
11588
- return join21(stateDir, "reload-requested");
11737
+ return join22(stateDir, "reload-requested");
11589
11738
  }
11590
11739
  function daemonStampFor(stateDir) {
11591
- return join21(stateDir, "daemon.started");
11740
+ return join22(stateDir, "daemon.started");
11592
11741
  }
11593
11742
  function noteDaemonStarted(stateDir, now = Date.now()) {
11594
11743
  mkdirSync12(stateDir, { recursive: true });
@@ -11598,7 +11747,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
11598
11747
  }
11599
11748
  function readDaemonStarted(stateDir) {
11600
11749
  try {
11601
- const v = JSON.parse(readFileSync15(daemonStampFor(stateDir), "utf8")).startedAt;
11750
+ const v = JSON.parse(readFileSync16(daemonStampFor(stateDir), "utf8")).startedAt;
11602
11751
  return typeof v === "number" ? v : null;
11603
11752
  } catch {
11604
11753
  return null;
@@ -11888,7 +12037,8 @@ async function buildEnvironment(route, ship, ticket) {
11888
12037
  };
11889
12038
  });
11890
12039
  const authorship = await tracker.authorshipProbe().catch(() => null);
11891
- return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, shipName: ship.name });
12040
+ const attachments = await tracker.attachmentsProbe().catch(() => null);
12041
+ return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name });
11892
12042
  }
11893
12043
 
11894
12044
  // src/cli.ts
@@ -12188,8 +12338,9 @@ if (command === "passenger-sync-daemon") {
12188
12338
  const FLEET_CAPABLE = /* @__PURE__ */ new Set(["poll", "run", "passengers"]);
12189
12339
  const releaseFleetWide = ["merge", "deploy", "release"].includes(command) && flag("fleet");
12190
12340
  const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
12191
- const named = command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
12192
- const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1;
12341
+ const worktreeArgs = command === "worktree" ? splitWorktreeArgs(positional, cfg.routes.map((r) => r.route)) : void 0;
12342
+ const named = worktreeArgs ? worktreeArgs.route : command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
12343
+ const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
12193
12344
  let route;
12194
12345
  try {
12195
12346
  route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
@@ -12395,8 +12546,8 @@ if (command === "passenger-sync-daemon") {
12395
12546
  }
12396
12547
  if (host === "linux" && hasSystemd) {
12397
12548
  const label = labelFor(CREW_HOME, "run");
12398
- const staleTimer = join22(homedir7(), ".config", "systemd", "user", `${label}.timer`);
12399
- if (existsSync23(staleTimer)) {
12549
+ const staleTimer = join23(homedir8(), ".config", "systemd", "user", `${label}.timer`);
12550
+ if (existsSync24(staleTimer)) {
12400
12551
  process.stdout.write(
12401
12552
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
12402
12553
  `
@@ -12422,7 +12573,7 @@ if (command === "passenger-sync-daemon") {
12422
12573
  const tracker = new Tracker(c, cfg.ship);
12423
12574
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
12424
12575
  for (const r of await resolvedRepos(c)) {
12425
- if (!existsSync23(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
12576
+ if (!existsSync24(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
12426
12577
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
12427
12578
  problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
12428
12579
  }
@@ -12440,7 +12591,7 @@ if (command === "passenger-sync-daemon") {
12440
12591
  }
12441
12592
  async function doctorFix(o) {
12442
12593
  const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
12443
- const unitExists = (p) => existsSync23(p);
12594
+ const unitExists = (p) => existsSync24(p);
12444
12595
  const crontab = o.host === "linux" ? spawnSync5("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
12445
12596
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
12446
12597
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -12457,7 +12608,7 @@ if (command === "passenger-sync-daemon") {
12457
12608
  for (const a of plan.actions) {
12458
12609
  if (a.kind === "enable") {
12459
12610
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
12460
- writeFileSync13(cfg.configFile, planEnable(readFileSync16(cfg.configFile, "utf8"), a.route));
12611
+ writeFileSync13(cfg.configFile, planEnable(readFileSync17(cfg.configFile, "utf8"), a.route));
12461
12612
  enabledSound.add(a.route);
12462
12613
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
12463
12614
  } else {
@@ -12520,6 +12671,7 @@ ${changed2.map((c) => ` - ${c}
12520
12671
  contract,
12521
12672
  sourceTicket: ticket,
12522
12673
  authorship: await tracker.authorshipProbe().catch(() => null),
12674
+ attachments: await tracker.attachmentsProbe().catch(() => null),
12523
12675
  shipName: cfg.ship.name
12524
12676
  });
12525
12677
  }
@@ -13364,7 +13516,7 @@ ${changed2.map((c) => ` - ${c}
13364
13516
  process.exit(1);
13365
13517
  }
13366
13518
  requireArmed("run the daemon loop");
13367
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join22(CREW_HOME, "src"), join22(CREW_HOME, "dist"), join22(CREW_HOME, "bin")];
13519
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join23(CREW_HOME, "src"), join23(CREW_HOME, "dist"), join23(CREW_HOME, "bin")];
13368
13520
  const configPaths = daemonConfigPaths(cfg);
13369
13521
  watchPaths.push(...configPaths);
13370
13522
  noteDaemonStarted(cfg.ship.stateDir);
@@ -13523,7 +13675,7 @@ ${changed2.map((c) => ` - ${c}
13523
13675
  `);
13524
13676
  if (!dryRun) {
13525
13677
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
13526
- const raw = existsSync23(resolvedPath) ? JSON.parse(readFileSync16(resolvedPath, "utf8")) : {};
13678
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
13527
13679
  raw.agentPersonas = personaResult.agentPersonas;
13528
13680
  raw.agentSkills = skillResult.agentSkills;
13529
13681
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
@@ -13574,7 +13726,7 @@ ${changed2.map((c) => ` - ${c}
13574
13726
  `);
13575
13727
  if (!dryRun) {
13576
13728
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
13577
- const raw = existsSync23(resolvedPath) ? JSON.parse(readFileSync16(resolvedPath, "utf8")) : {};
13729
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
13578
13730
  raw.agentSkills = result.agentSkills;
13579
13731
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
13580
13732
  writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
@@ -13590,14 +13742,14 @@ ${changed2.map((c) => ` - ${c}
13590
13742
  process.exit(2);
13591
13743
  };
13592
13744
  if (sub !== "add" && sub !== "list") usage2();
13593
- const configText = readFileSync16(cfg.configFile, "utf8");
13745
+ const configText = readFileSync17(cfg.configFile, "utf8");
13594
13746
  let routeName = positional[2];
13595
13747
  let addPath = ".";
13596
13748
  let addName;
13597
13749
  if (sub === "list" && !routeName) usage2();
13598
13750
  if (sub === "add") {
13599
13751
  try {
13600
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync23(resolvePath(p.replace(/^~(?=\/|$)/, homedir7()))));
13752
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync24(resolvePath(p.replace(/^~(?=\/|$)/, homedir8()))));
13601
13753
  routeName = a.route;
13602
13754
  addPath = a.path;
13603
13755
  addName = a.name;
@@ -13612,9 +13764,9 @@ ${changed2.map((c) => ` - ${c}
13612
13764
  }
13613
13765
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
13614
13766
  let resolvedRemotes;
13615
- if (existsSync23(resolvedFile)) {
13767
+ if (existsSync24(resolvedFile)) {
13616
13768
  try {
13617
- resolvedRemotes = JSON.parse(readFileSync16(resolvedFile, "utf8"));
13769
+ resolvedRemotes = JSON.parse(readFileSync17(resolvedFile, "utf8"));
13618
13770
  } catch {
13619
13771
  }
13620
13772
  }
@@ -13629,14 +13781,14 @@ ${changed2.map((c) => ` - ${c}
13629
13781
  for (const r of repos) {
13630
13782
  const origin2 = originOf(r.dir);
13631
13783
  const row = matchRepoName(origin2, resolvedRemotes);
13632
- const note = !existsSync23(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
13784
+ const note = !existsSync24(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
13633
13785
  process.stdout.write(`${r.name} ${r.dir} ${note}
13634
13786
  `);
13635
13787
  }
13636
13788
  break;
13637
13789
  }
13638
- const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir7()));
13639
- const top = existsSync23(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
13790
+ const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir8()));
13791
+ const top = existsSync24(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
13640
13792
  if (!top) throw new ReposError(`${target} is not a git checkout`);
13641
13793
  const origin = originOf(top);
13642
13794
  const plan = planRepoAdd({
@@ -13685,8 +13837,8 @@ ${changed2.map((c) => ` - ${c}
13685
13837
  const created = await rowTracker.createRepoRow(rowPayload);
13686
13838
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
13687
13839
  `);
13688
- if (existsSync23(resolvedFile)) {
13689
- const raw = JSON.parse(readFileSync16(resolvedFile, "utf8"));
13840
+ if (existsSync24(resolvedFile)) {
13841
+ const raw = JSON.parse(readFileSync17(resolvedFile, "utf8"));
13690
13842
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
13691
13843
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
13692
13844
  writeFileSync13(resolvedFile, `${JSON.stringify(raw, null, 2)}
@@ -13932,10 +14084,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
13932
14084
  operator = await pickOperator(found.holds);
13933
14085
  }
13934
14086
  let previous;
13935
- const hadPreviousFile = existsSync23(resolvedPath);
14087
+ const hadPreviousFile = existsSync24(resolvedPath);
13936
14088
  if (hadPreviousFile) {
13937
14089
  try {
13938
- previous = JSON.parse(readFileSync16(resolvedPath, "utf8"));
14090
+ previous = JSON.parse(readFileSync17(resolvedPath, "utf8"));
13939
14091
  } catch {
13940
14092
  }
13941
14093
  }
@@ -14023,7 +14175,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14023
14175
  const keyPersisted = keychainBacked || apiKeyFile !== void 0;
14024
14176
  const wizard = firstRun;
14025
14177
  const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
14026
- const routeDir = value("dir") ?? (writeFirstRun ? join22(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14178
+ const routeDir = value("dir") ?? (writeFirstRun ? join23(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14027
14179
  const routeBlock = renderConnection(
14028
14180
  found,
14029
14181
  route2,
@@ -14364,21 +14516,36 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14364
14516
  }
14365
14517
  case "worktree": {
14366
14518
  emit.enter("worktree");
14367
- const rawWt = positional[2] ?? positional[1];
14519
+ const rawWt = worktreeArgs?.ticket;
14368
14520
  const nWt = rawWt ? parseTicketNumber(rawWt) : null;
14369
14521
  if (!nWt) {
14370
14522
  process.stderr.write("worktree: need a ticket number (326, ISSUE-326, or TABL-326)\n");
14371
14523
  process.exit(2);
14372
14524
  }
14373
- const trackerWt = new Tracker(route, cfg.ship);
14374
- const cWt = trackerWt.contract;
14375
- const ticketWt = (await trackerWt.openTickets()).find((t) => t[cWt.columns.key] === `ISSUE-${nWt}`);
14376
- if (!ticketWt) {
14377
- process.stderr.write(`worktree: no open ticket ISSUE-${nWt} on ${route.route}
14525
+ const candidatesWt = !named && cfg.routes.length > 1 ? cfg.routes.filter((r) => r.enabled) : [route];
14526
+ let ticketWt;
14527
+ const lookedWt = [];
14528
+ const hitsWt = /* @__PURE__ */ new Map();
14529
+ for (const cand of candidatesWt) {
14530
+ const tr = new Tracker(cand, cfg.ship);
14531
+ const hit = (await tr.openTickets()).find((t) => t[tr.contract.columns.key] === `ISSUE-${nWt}`);
14532
+ if (hit) hitsWt.set(cand.route, hit);
14533
+ lookedWt.push({
14534
+ route: cand.route,
14535
+ hit: hit ? { repoId: hit.repo_id ?? null } : void 0,
14536
+ servedRepoIds: hit ? (await resolvedRepos(cand)).flatMap((r) => r.id ? [r.id] : []) : []
14537
+ });
14538
+ }
14539
+ const pickedWt = pickWorktreeRoute(Number(nWt), lookedWt);
14540
+ if ("error" in pickedWt) {
14541
+ process.stderr.write(`worktree: ${pickedWt.error}
14378
14542
  `);
14379
14543
  process.exit(2);
14380
14544
  }
14381
- const repoWt = (await resolvedRepos(route)).find((r) => r.id === ticketWt.repo_id) ?? (await resolvedRepos(route))[0];
14545
+ const routeWt = candidatesWt.find((r) => r.route === pickedWt.route) ?? route;
14546
+ ticketWt = hitsWt.get(pickedWt.route);
14547
+ if (!ticketWt) process.exit(2);
14548
+ const repoWt = (await resolvedRepos(routeWt)).find((r) => r.id === ticketWt.repo_id) ?? (await resolvedRepos(routeWt))[0];
14382
14549
  if (!repoWt) {
14383
14550
  process.stderr.write("worktree: this route has no repository\n");
14384
14551
  process.exit(2);
@@ -14588,7 +14755,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14588
14755
  }
14589
14756
  case "log": {
14590
14757
  try {
14591
- const text = readFileSync16(cfg.ship.logFile, "utf8").split("\n");
14758
+ const text = readFileSync17(cfg.ship.logFile, "utf8").split("\n");
14592
14759
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
14593
14760
  `);
14594
14761
  } catch {
@@ -14798,7 +14965,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
14798
14965
  );
14799
14966
  try {
14800
14967
  const evFile = eventFileFor(cfg.ship.stateDir);
14801
- const evSize = existsSync23(evFile) ? statSync9(evFile).size : 0;
14968
+ const evSize = existsSync24(evFile) ? statSync9(evFile).size : 0;
14802
14969
  process.stdout.write(
14803
14970
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
14804
14971
  ` : "events log: ok\n"
@@ -14932,15 +15099,39 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
14932
15099
  let allBinsFound = true;
14933
15100
  for (const tc of shipToolchains(cfg.ship)) {
14934
15101
  const adapter = adapterFor(tc.harness);
14935
- const binPath = findOnPath(tc.launcherBin ?? tc.launcher ?? tc.bin, schedulerPath);
15102
+ const runBin = launcherExecutable(tc) ?? tc.bin;
15103
+ const binPath = findOnPath(runBin, schedulerPath);
14936
15104
  if (!binPath) allBinsFound = false;
14937
15105
  const label = tc.name === defaultToolchain ? "agent binary: " : `toolchain ${tc.name}:`.padEnd(18);
14938
15106
  process.stdout.write(
14939
- `${label} ${tc.launcherBin ?? tc.launcher ?? tc.bin} \u2014 ${binPath ? `OK (${binPath})` : "NOT FOUND on the scheduler PATH"}${tc.name === defaultToolchain ? "" : ` [${adapter.displayName}]`}
15107
+ `${label} ${runBin} \u2014 ${binPath ? `OK (${binPath})` : "NOT FOUND on the scheduler PATH"}${tc.name === defaultToolchain ? "" : ` [${adapter.displayName}]`}
14940
15108
  `
14941
15109
  );
14942
15110
  for (const limit of adapter.limitations) process.stdout.write(` cannot enforce: ${limit}
14943
15111
  `);
15112
+ if (isOmlxLauncher(tc)) {
15113
+ const s = readOmlxSettings();
15114
+ if (!s) {
15115
+ process.stdout.write(` omlx: ~/.omlx/settings.json not found or has no server.port \u2014 cannot check the server
15116
+ `);
15117
+ } else {
15118
+ let status2;
15119
+ try {
15120
+ status2 = (await fetch(`http://${s.host}:${s.port}/v1/models`, { signal: AbortSignal.timeout(3e3) })).status;
15121
+ } catch {
15122
+ status2 = void 0;
15123
+ }
15124
+ const probe = interpretOmlxProbe(status2);
15125
+ process.stdout.write(` omlx server: ${s.host}:${s.port} \u2014 ${probe.detail}${probe.up ? "" : " (start oMLX)"}
15126
+ `);
15127
+ process.stdout.write(
15128
+ ` omlx model: ${tc.model} \u2014 ${omlxModelPresent(tc.model, s.modelDirs) ? "present" : `NOT FOUND in ${s.modelDirs.join(", ") || "any model dir"}`}
15129
+ `
15130
+ );
15131
+ }
15132
+ process.stdout.write(` local model: tool-call reliability and prefill speed vary by model
15133
+ `);
15134
+ }
14944
15135
  if (isOriLauncher(tc)) {
14945
15136
  process.stdout.write(` ori: headless auth under the scheduler is unverified \u2014 run \`ori login\` as the scheduler user, then confirm a run records provider openrouter (see docs/toolchains.md)
14946
15137
  `);