@tablation/crew 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
46
|
-
import { resolve, dirname as dirname3, isAbsolute, join as
|
|
47
|
-
import { homedir as
|
|
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
|
|
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
|
|
199
|
-
import { join as
|
|
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
|
|
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
|
|
450
|
-
import { join as
|
|
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
|
|
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
|
|
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:
|
|
643
|
-
config:
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
791
|
-
config:
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
979
|
+
const home = o.home ?? homedir4();
|
|
889
980
|
const platform2 = o.platform ?? process.platform;
|
|
890
|
-
const root = platform2 === "darwin" ?
|
|
891
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
892
|
-
const present = all.filter((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 =
|
|
940
|
-
const instrPath =
|
|
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 =
|
|
969
|
-
const mcpPath =
|
|
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 =
|
|
985
|
-
const hadBlock = removeCopilotInstructions(
|
|
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
|
|
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
|
-
|
|
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
|
|
1288
|
-
import { join as
|
|
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 =
|
|
1362
|
-
if (
|
|
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(
|
|
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(/^~/,
|
|
2006
|
-
out = out.replace(/\$\{?HOME\}?/g,
|
|
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
|
|
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
|
|
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(
|
|
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 ? [
|
|
2049
|
-
|
|
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) =>
|
|
2143
|
+
return candidates.find((c) => existsSync6(c)) ?? candidates[candidates.length - 1];
|
|
2056
2144
|
}
|
|
2057
|
-
var defaultConfigPath = () =>
|
|
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
|
|
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
|
|
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 (!
|
|
2166
|
+
if (!existsSync6(path)) return void 0;
|
|
2079
2167
|
try {
|
|
2080
|
-
return JSON.parse(
|
|
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 && !
|
|
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 ${
|
|
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 ??
|
|
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 ??
|
|
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 &&
|
|
2446
|
-
for (const line of
|
|
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
|
|
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
|
|
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
|
|
2666
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
2799
|
+
return existsSync7(this.path(".crew-paused"));
|
|
2712
2800
|
}
|
|
2713
2801
|
isRolePaused(role) {
|
|
2714
|
-
return
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3160
|
+
return Number.parseInt(readFileSync7(this.path(file), "utf8").trim(), 10) || 0;
|
|
3073
3161
|
} catch {
|
|
3074
3162
|
return 0;
|
|
3075
3163
|
}
|
|
@@ -3103,7 +3191,8 @@ var DEFAULT_CONTRACT = {
|
|
|
3103
3191
|
ship: "ship_id",
|
|
3104
3192
|
heldBy: "held_by_ship_id",
|
|
3105
3193
|
verifiedSha: "verified_sha",
|
|
3106
|
-
prRef: "pr_ref"
|
|
3194
|
+
prRef: "pr_ref",
|
|
3195
|
+
position: "position"
|
|
3107
3196
|
},
|
|
3108
3197
|
comments: {
|
|
3109
3198
|
parent: "ticket_id",
|
|
@@ -3373,9 +3462,9 @@ function describeRelease(o, route) {
|
|
|
3373
3462
|
}
|
|
3374
3463
|
|
|
3375
3464
|
// src/install.ts
|
|
3376
|
-
import { dirname as dirname4, join as
|
|
3377
|
-
import { homedir as
|
|
3378
|
-
import { accessSync, constants, existsSync as
|
|
3465
|
+
import { dirname as dirname4, join as join9, extname } from "path";
|
|
3466
|
+
import { homedir as homedir6 } from "os";
|
|
3467
|
+
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
3468
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3380
3469
|
import { createHash } from "crypto";
|
|
3381
3470
|
|
|
@@ -3387,7 +3476,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
3387
3476
|
// src/install.ts
|
|
3388
3477
|
function crewInvocation(crewHome) {
|
|
3389
3478
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
3390
|
-
return [process.execPath,
|
|
3479
|
+
return [process.execPath, join9(crewHome, "bin", "crew")];
|
|
3391
3480
|
}
|
|
3392
3481
|
var INTERVAL_SECONDS = 120;
|
|
3393
3482
|
function labelFor(crewHome, job) {
|
|
@@ -3425,7 +3514,7 @@ function pathFor(ship) {
|
|
|
3425
3514
|
return parts.join(":");
|
|
3426
3515
|
}
|
|
3427
3516
|
function findOnPath(bin, searchPath) {
|
|
3428
|
-
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
3517
|
+
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join9(dir, bin));
|
|
3429
3518
|
for (const candidate of candidates) {
|
|
3430
3519
|
try {
|
|
3431
3520
|
accessSync(candidate, constants.X_OK);
|
|
@@ -3435,7 +3524,7 @@ function findOnPath(bin, searchPath) {
|
|
|
3435
3524
|
}
|
|
3436
3525
|
return void 0;
|
|
3437
3526
|
}
|
|
3438
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
3527
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join9(homedir6(), ".docker", "bin")].join(":");
|
|
3439
3528
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
3440
3529
|
const schedulerPath = pathFor(ship);
|
|
3441
3530
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -3447,7 +3536,7 @@ function xmlEscape(s) {
|
|
|
3447
3536
|
}
|
|
3448
3537
|
function planLaunchd(ship, crewHome, job) {
|
|
3449
3538
|
const label = labelFor(crewHome, job);
|
|
3450
|
-
const unitPath =
|
|
3539
|
+
const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3451
3540
|
const crewLog = ship.logFile;
|
|
3452
3541
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
3453
3542
|
const persistent = job === "run";
|
|
@@ -3499,8 +3588,8 @@ ${scheduleKeys}
|
|
|
3499
3588
|
}
|
|
3500
3589
|
function planSystemd(ship, crewHome, job) {
|
|
3501
3590
|
const label = labelFor(crewHome, job);
|
|
3502
|
-
const unitDir =
|
|
3503
|
-
const servicePath =
|
|
3591
|
+
const unitDir = join9(homedir6(), ".config", "systemd", "user");
|
|
3592
|
+
const servicePath = join9(unitDir, `${label}.service`);
|
|
3504
3593
|
const crewLog = ship.logFile;
|
|
3505
3594
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
3506
3595
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -3537,7 +3626,7 @@ WantedBy=default.target
|
|
|
3537
3626
|
schedulerLog
|
|
3538
3627
|
};
|
|
3539
3628
|
}
|
|
3540
|
-
const timerPath =
|
|
3629
|
+
const timerPath = join9(unitDir, `${label}.timer`);
|
|
3541
3630
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
3542
3631
|
[Unit]
|
|
3543
3632
|
Description=${description}
|
|
@@ -3604,7 +3693,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3604
3693
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
3605
3694
|
const label = labelFor(crewHome, job);
|
|
3606
3695
|
if (hostShip === "macos") {
|
|
3607
|
-
const unitPath =
|
|
3696
|
+
const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3608
3697
|
return {
|
|
3609
3698
|
mechanism: "launchd",
|
|
3610
3699
|
unitPaths: [unitPath],
|
|
@@ -3614,18 +3703,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3614
3703
|
};
|
|
3615
3704
|
}
|
|
3616
3705
|
if (hostShip === "linux" && hasSystemd) {
|
|
3617
|
-
const unitDir =
|
|
3706
|
+
const unitDir = join9(homedir6(), ".config", "systemd", "user");
|
|
3618
3707
|
if (job === "run") {
|
|
3619
3708
|
return {
|
|
3620
3709
|
mechanism: "systemd",
|
|
3621
|
-
unitPaths: [
|
|
3710
|
+
unitPaths: [join9(unitDir, `${label}.service`)],
|
|
3622
3711
|
crontabLine: null,
|
|
3623
3712
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
|
|
3624
3713
|
};
|
|
3625
3714
|
}
|
|
3626
3715
|
return {
|
|
3627
3716
|
mechanism: "systemd",
|
|
3628
|
-
unitPaths: [
|
|
3717
|
+
unitPaths: [join9(unitDir, `${label}.service`), join9(unitDir, `${label}.timer`)],
|
|
3629
3718
|
crontabLine: null,
|
|
3630
3719
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
|
|
3631
3720
|
};
|
|
@@ -3643,7 +3732,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3643
3732
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
3644
3733
|
const label = labelFor(crewHome, "run");
|
|
3645
3734
|
if (hostShip === "macos") {
|
|
3646
|
-
const unitPath =
|
|
3735
|
+
const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3647
3736
|
return {
|
|
3648
3737
|
mechanism: "launchd",
|
|
3649
3738
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -3738,7 +3827,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
3738
3827
|
}
|
|
3739
3828
|
}
|
|
3740
3829
|
for (const path of plan.unitPaths) {
|
|
3741
|
-
if (!
|
|
3830
|
+
if (!existsSync8(path)) continue;
|
|
3742
3831
|
if (dryRun) {
|
|
3743
3832
|
log.emit(`would remove ${path}`);
|
|
3744
3833
|
continue;
|
|
@@ -3757,7 +3846,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
3757
3846
|
}
|
|
3758
3847
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
3759
3848
|
function launchAgentsDir() {
|
|
3760
|
-
return
|
|
3849
|
+
return join9(homedir6(), "Library", "LaunchAgents");
|
|
3761
3850
|
}
|
|
3762
3851
|
function xmlUnescape(s) {
|
|
3763
3852
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -3774,25 +3863,25 @@ function loadedCrewLabels() {
|
|
|
3774
3863
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
3775
3864
|
}
|
|
3776
3865
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
3777
|
-
const files =
|
|
3866
|
+
const files = existsSync8(dir) ? readdirSync2(dir) : [];
|
|
3778
3867
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
3779
|
-
const plistPath =
|
|
3868
|
+
const plistPath = join9(dir, f);
|
|
3780
3869
|
let program = null;
|
|
3781
3870
|
try {
|
|
3782
|
-
program = programFromPlist(
|
|
3871
|
+
program = programFromPlist(readFileSync8(plistPath, "utf8"));
|
|
3783
3872
|
} catch {
|
|
3784
3873
|
}
|
|
3785
3874
|
return {
|
|
3786
3875
|
label: f.slice(0, -".plist".length),
|
|
3787
3876
|
plistPath,
|
|
3788
3877
|
program,
|
|
3789
|
-
programExists: program !== null &&
|
|
3878
|
+
programExists: program !== null && existsSync8(program)
|
|
3790
3879
|
};
|
|
3791
3880
|
});
|
|
3792
3881
|
const seen = new Set(units.map((u) => u.label));
|
|
3793
3882
|
for (const label of [...loaded].sort()) {
|
|
3794
3883
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
3795
|
-
units.push({ label, plistPath:
|
|
3884
|
+
units.push({ label, plistPath: join9(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
3796
3885
|
}
|
|
3797
3886
|
return units;
|
|
3798
3887
|
}
|
|
@@ -3805,9 +3894,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
3805
3894
|
log.emit(`would unload and remove ${unit.label}`);
|
|
3806
3895
|
return;
|
|
3807
3896
|
}
|
|
3808
|
-
if (
|
|
3897
|
+
if (existsSync8(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
3809
3898
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
3810
|
-
if (
|
|
3899
|
+
if (existsSync8(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
3811
3900
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
3812
3901
|
}
|
|
3813
3902
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -3890,7 +3979,7 @@ import { statfsSync } from "fs";
|
|
|
3890
3979
|
// src/git.ts
|
|
3891
3980
|
import { execFileSync } from "child_process";
|
|
3892
3981
|
import {
|
|
3893
|
-
existsSync as
|
|
3982
|
+
existsSync as existsSync9,
|
|
3894
3983
|
mkdirSync as mkdirSync5,
|
|
3895
3984
|
openSync as openSync2,
|
|
3896
3985
|
closeSync as closeSync2,
|
|
@@ -4262,7 +4351,7 @@ function cloneUrlFor(remote) {
|
|
|
4262
4351
|
return `git@github.com:${remote}.git`;
|
|
4263
4352
|
}
|
|
4264
4353
|
function ensureRepoCheckout(dir, remote) {
|
|
4265
|
-
if (
|
|
4354
|
+
if (existsSync9(`${dir}/.git`)) return false;
|
|
4266
4355
|
if (!remote) {
|
|
4267
4356
|
throw new GitError(
|
|
4268
4357
|
`${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`
|
|
@@ -5391,20 +5480,25 @@ function issueNumber(t) {
|
|
|
5391
5480
|
function epicRank(t) {
|
|
5392
5481
|
return t.epicInProgress ? 0 : 1;
|
|
5393
5482
|
}
|
|
5483
|
+
function positionRank(t) {
|
|
5484
|
+
const p = t.position;
|
|
5485
|
+
return typeof p === "number" && Number.isFinite(p) ? p : Number.POSITIVE_INFINITY;
|
|
5486
|
+
}
|
|
5394
5487
|
function rank(t, c = DEFAULT_CONTRACT) {
|
|
5395
|
-
return [effectivePriority(t, c), severityRank(t, c), epicRank(t), issueNumber(t)];
|
|
5488
|
+
return [effectivePriority(t, c), positionRank(t), severityRank(t, c), epicRank(t), issueNumber(t)];
|
|
5396
5489
|
}
|
|
5397
5490
|
function compareRank(a, b, c = DEFAULT_CONTRACT) {
|
|
5398
5491
|
const ra = rank(a, c);
|
|
5399
5492
|
const rb = rank(b, c);
|
|
5400
5493
|
for (let i = 0; i < ra.length; i++) {
|
|
5401
|
-
const
|
|
5402
|
-
|
|
5494
|
+
const x = ra[i];
|
|
5495
|
+
const y = rb[i];
|
|
5496
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
5403
5497
|
}
|
|
5404
5498
|
return 0;
|
|
5405
5499
|
}
|
|
5406
5500
|
function rankScalar(t, c = DEFAULT_CONTRACT) {
|
|
5407
|
-
const [p, s, e, n] = rank(t, c);
|
|
5501
|
+
const [p, , s, e, n] = rank(t, c);
|
|
5408
5502
|
return p * 1e9 + s * 1e7 + e * 1e6 + n;
|
|
5409
5503
|
}
|
|
5410
5504
|
var NOTHING_ACTIONABLE = 999999999999;
|
|
@@ -5611,7 +5705,7 @@ function claimStatuses(role, c) {
|
|
|
5611
5705
|
}
|
|
5612
5706
|
function rankedCandidates(role, i) {
|
|
5613
5707
|
const c = i.contract ?? DEFAULT_CONTRACT;
|
|
5614
|
-
return [...roleCandidates(role, i)].sort((a, b) =>
|
|
5708
|
+
return [...roleCandidates(role, i)].sort((a, b) => compareRank(a, b, c));
|
|
5615
5709
|
}
|
|
5616
5710
|
function roleTopRank(role, i) {
|
|
5617
5711
|
if (role !== "qa" && role !== "triage" && !i.seats[role]) return NOTHING_ACTIONABLE;
|
|
@@ -5624,7 +5718,7 @@ function actionableSummary(i, pending) {
|
|
|
5624
5718
|
const c = i.contract ?? DEFAULT_CONTRACT;
|
|
5625
5719
|
const all = pending.flatMap((role) => roleCandidates(role, i));
|
|
5626
5720
|
if (all.length === 0) return { count: 0 };
|
|
5627
|
-
const top = all.reduce((best, t) =>
|
|
5721
|
+
const top = all.reduce((best, t) => compareRank(t, best, c) < 0 ? t : best);
|
|
5628
5722
|
return { count: all.length, top };
|
|
5629
5723
|
}
|
|
5630
5724
|
var FIXED_RANK = { qa: -2, triage: -1 };
|
|
@@ -5850,6 +5944,7 @@ function blockers(t, i) {
|
|
|
5850
5944
|
function modeCell(t, i) {
|
|
5851
5945
|
return i.workModes && i.workModes.modes.size > 0 ? modeOf(t, i.workModes) : "\u2014";
|
|
5852
5946
|
}
|
|
5947
|
+
var effCell = (t) => `p${effectivePriority(t)}${positionRank(t) === Number.POSITIVE_INFINITY ? "" : ` #${positionRank(t)}`}`;
|
|
5853
5948
|
var sorted = (ts) => [...ts].sort(compareRank);
|
|
5854
5949
|
var stream = (segments) => `${segments.join("\n")}
|
|
5855
5950
|
`;
|
|
@@ -5868,7 +5963,7 @@ function buildingDigest(i) {
|
|
|
5868
5963
|
const worktree = i.worktreeFor ? i.worktreeFor(t) : "";
|
|
5869
5964
|
const dir = i.dirFor ? i.dirFor(t) : null;
|
|
5870
5965
|
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"} |
|
|
5966
|
+
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
5967
|
};
|
|
5873
5968
|
const blockedRow = (t) => `| ${displayKey2(t)} | ${t.status} | ${who(t, i)} | p${effectivePriority(t)} | ${blockers(t, i)} |`;
|
|
5874
5969
|
const blockedTable = (ts) => ts.length === 0 ? "\n_None._\n" : `
|
|
@@ -5929,7 +6024,7 @@ function qaDigest(i) {
|
|
|
5929
6024
|
const n = newFromOthers(i, t.id);
|
|
5930
6025
|
const dir = i.dirFor ? i.dirFor(t) : null;
|
|
5931
6026
|
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"} |
|
|
6027
|
+
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
6028
|
};
|
|
5934
6029
|
const waiting = i.tickets.filter((t) => (t.status === "qa" || t.status === "fixed") && i.blocked.has(t.id));
|
|
5935
6030
|
const takeable = (status2) => i.tickets.filter((t) => t.status === status2 && !i.blocked.has(t.id));
|
|
@@ -6011,7 +6106,7 @@ function materializeBranchForTicket(dir, cfg, t, role) {
|
|
|
6011
6106
|
|
|
6012
6107
|
// src/poll.ts
|
|
6013
6108
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
6014
|
-
import { join as
|
|
6109
|
+
import { join as join10 } from "path";
|
|
6015
6110
|
|
|
6016
6111
|
// src/stalled.ts
|
|
6017
6112
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -6124,6 +6219,7 @@ async function decideCycle(o) {
|
|
|
6124
6219
|
data: { tickets: tickets.length, comments: comments.length }
|
|
6125
6220
|
});
|
|
6126
6221
|
const epicColumn = tracker.contract.columns.epic;
|
|
6222
|
+
const positionColumn = tracker.contract.columns.position;
|
|
6127
6223
|
const epicBuilding = tracker.contract.epics.statuses.building;
|
|
6128
6224
|
const epicStatusColumn = tracker.contract.epics.statusColumn;
|
|
6129
6225
|
const epicStatusById = new Map(epicRows.map((e) => [e.id, e[epicStatusColumn] ?? null]));
|
|
@@ -6143,6 +6239,9 @@ async function decideCycle(o) {
|
|
|
6143
6239
|
for (const t of tickets) {
|
|
6144
6240
|
const epicId = t[epicColumn];
|
|
6145
6241
|
t.epicInProgress = typeof epicId === "string" && epicStatusById.get(epicId) === epicBuilding;
|
|
6242
|
+
const pos = t[positionColumn];
|
|
6243
|
+
const n = typeof pos === "string" && pos.trim() !== "" ? Number(pos) : pos;
|
|
6244
|
+
t.position = typeof n === "number" && Number.isFinite(n) ? n : null;
|
|
6146
6245
|
}
|
|
6147
6246
|
const extra = await tracker.ticketsByIds(missingBlockerIds(tickets));
|
|
6148
6247
|
const info = blockerInfoMap(tickets, extra);
|
|
@@ -6283,7 +6382,7 @@ async function decideCycle(o) {
|
|
|
6283
6382
|
}
|
|
6284
6383
|
return decision;
|
|
6285
6384
|
}
|
|
6286
|
-
var digestPath = (stateDir, route, role) =>
|
|
6385
|
+
var digestPath = (stateDir, route, role) => join10(stateDir, `digest-${route}-${role}.md`);
|
|
6287
6386
|
function writeDigest(o, d, role, stateDir) {
|
|
6288
6387
|
const me = o.route.resolved?.seats[role];
|
|
6289
6388
|
if (!me) return false;
|
|
@@ -6653,9 +6752,9 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
|
|
|
6653
6752
|
|
|
6654
6753
|
// src/agent.ts
|
|
6655
6754
|
import { spawn as spawn2 } from "child_process";
|
|
6656
|
-
import { readFileSync as
|
|
6755
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
|
|
6657
6756
|
import { finished } from "stream/promises";
|
|
6658
|
-
import { join as
|
|
6757
|
+
import { join as join13, dirname as dirname6 } from "path";
|
|
6659
6758
|
import { createHash as createHash2 } from "crypto";
|
|
6660
6759
|
|
|
6661
6760
|
// src/environment.ts
|
|
@@ -7044,14 +7143,14 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7044
7143
|
}
|
|
7045
7144
|
|
|
7046
7145
|
// src/skills.ts
|
|
7047
|
-
import { readFileSync as
|
|
7048
|
-
import { join as
|
|
7146
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
|
|
7147
|
+
import { join as join12 } from "path";
|
|
7049
7148
|
import { parse as parseYaml } from "yaml";
|
|
7050
7149
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError7, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7051
7150
|
|
|
7052
7151
|
// src/agents.ts
|
|
7053
|
-
import { readFileSync as
|
|
7054
|
-
import { join as
|
|
7152
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
7153
|
+
import { join as join11 } from "path";
|
|
7055
7154
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError6, TablationApiError } from "@tablation/client";
|
|
7056
7155
|
var PERSONA_NAME = {
|
|
7057
7156
|
dev: "Developer",
|
|
@@ -7065,12 +7164,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
7065
7164
|
var AgentsSyncError = class extends Error {
|
|
7066
7165
|
};
|
|
7067
7166
|
function personaDefaultPrompt(promptsDir, role) {
|
|
7068
|
-
const briefPath =
|
|
7069
|
-
if (!
|
|
7070
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
7071
|
-
const commonPath =
|
|
7072
|
-
if (!
|
|
7073
|
-
return `${
|
|
7167
|
+
const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
|
|
7168
|
+
if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
7169
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync9(briefPath, "utf8");
|
|
7170
|
+
const commonPath = join11(promptsDir, "personas", "common.md");
|
|
7171
|
+
if (!existsSync10(commonPath)) return readFileSync9(briefPath, "utf8");
|
|
7172
|
+
return `${readFileSync9(commonPath, "utf8")}${readFileSync9(briefPath, "utf8")}`;
|
|
7074
7173
|
}
|
|
7075
7174
|
async function resolveAgentId(route, role, opts) {
|
|
7076
7175
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7394,7 +7493,7 @@ function describeCrewLink(o) {
|
|
|
7394
7493
|
var SkillSyncError = class extends Error {
|
|
7395
7494
|
};
|
|
7396
7495
|
function parseSkillFile(path) {
|
|
7397
|
-
const text =
|
|
7496
|
+
const text = readFileSync10(path, "utf8");
|
|
7398
7497
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7399
7498
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7400
7499
|
const frontmatter = match[1];
|
|
@@ -7407,9 +7506,9 @@ function parseSkillFile(path) {
|
|
|
7407
7506
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
7408
7507
|
}
|
|
7409
7508
|
function listLocalSkills(promptsDir) {
|
|
7410
|
-
const dir =
|
|
7411
|
-
if (!
|
|
7412
|
-
return
|
|
7509
|
+
const dir = join12(promptsDir, "skills");
|
|
7510
|
+
if (!existsSync11(dir)) return [];
|
|
7511
|
+
return readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
|
|
7413
7512
|
}
|
|
7414
7513
|
async function syncSkills(route, opts) {
|
|
7415
7514
|
if (!route.resolved) {
|
|
@@ -7601,23 +7700,23 @@ var AgentError = class extends Error {
|
|
|
7601
7700
|
var AgentSpawnError = class extends AgentError {
|
|
7602
7701
|
};
|
|
7603
7702
|
function planAgentRun(o) {
|
|
7604
|
-
const briefPath =
|
|
7605
|
-
if (!
|
|
7703
|
+
const briefPath = join13(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
7704
|
+
if (!existsSync12(briefPath)) {
|
|
7606
7705
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
7607
7706
|
}
|
|
7608
|
-
const commonPath =
|
|
7609
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
7707
|
+
const commonPath = join13(o.route.promptsDir, "personas", "common.md");
|
|
7708
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync12(commonPath);
|
|
7610
7709
|
const now = o.now ?? (() => Date.now());
|
|
7611
7710
|
const routeLabel = routeSlug(o.route.route);
|
|
7612
|
-
const digestPath2 =
|
|
7711
|
+
const digestPath2 = join13(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
|
|
7613
7712
|
let digest;
|
|
7614
7713
|
let ageSeconds;
|
|
7615
|
-
if (
|
|
7714
|
+
if (existsSync12(digestPath2)) {
|
|
7616
7715
|
ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
|
|
7617
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
7716
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync11(digestPath2, "utf8");
|
|
7618
7717
|
}
|
|
7619
|
-
const common = usesCommon ?
|
|
7620
|
-
const brief =
|
|
7718
|
+
const common = usesCommon ? readFileSync11(commonPath, "utf8") : "";
|
|
7719
|
+
const brief = readFileSync11(briefPath, "utf8");
|
|
7621
7720
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
7622
7721
|
const effectiveBrief = o.divergedPrompt ?? brief;
|
|
7623
7722
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -7639,7 +7738,7 @@ function planAgentRun(o) {
|
|
|
7639
7738
|
digest
|
|
7640
7739
|
});
|
|
7641
7740
|
const promptSha = createHash2("sha256").update(expanded.text).digest("hex");
|
|
7642
|
-
const streamsDir =
|
|
7741
|
+
const streamsDir = join13(o.stateDir, "streams");
|
|
7643
7742
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
7644
7743
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
7645
7744
|
const { toolchain, model } = seat;
|
|
@@ -7695,8 +7794,8 @@ function planAgentRun(o) {
|
|
|
7695
7794
|
digestAttached: digest !== void 0,
|
|
7696
7795
|
digestAgeSeconds: ageSeconds,
|
|
7697
7796
|
model,
|
|
7698
|
-
streamPath:
|
|
7699
|
-
eventsPath:
|
|
7797
|
+
streamPath: join13(streamsDir, `${base}.jsonl`),
|
|
7798
|
+
eventsPath: join13(streamsDir, `${base}.events.jsonl`),
|
|
7700
7799
|
ticket: o.ticket,
|
|
7701
7800
|
agentLog,
|
|
7702
7801
|
promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
|
|
@@ -7706,7 +7805,7 @@ function planAgentRun(o) {
|
|
|
7706
7805
|
function describePlan(p) {
|
|
7707
7806
|
return [
|
|
7708
7807
|
`role: ${p.role}`,
|
|
7709
|
-
...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}`] : [],
|
|
7808
|
+
...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}${p.provider ? `; provider ${p.provider}` : ""}`] : [],
|
|
7710
7809
|
...(p.warnings ?? []).map((w) => `warning: ${w}`),
|
|
7711
7810
|
// Only for a non-default toolchain, so a plain Claude run's output is unchanged.
|
|
7712
7811
|
...p.toolchain && p.toolchain !== "claude" ? [`toolchain: ${p.toolchain} (${p.harness ?? "claude"})`] : [],
|
|
@@ -7902,13 +8001,13 @@ function spawnAgent(plan, emit) {
|
|
|
7902
8001
|
}
|
|
7903
8002
|
|
|
7904
8003
|
// src/run-worktree.ts
|
|
7905
|
-
import { existsSync as
|
|
7906
|
-
import { dirname as dirname7, join as
|
|
8004
|
+
import { existsSync as existsSync14 } from "fs";
|
|
8005
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
7907
8006
|
|
|
7908
8007
|
// src/ports.ts
|
|
7909
8008
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
7910
|
-
import { existsSync as
|
|
7911
|
-
import { join as
|
|
8009
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8010
|
+
import { join as join14 } from "path";
|
|
7912
8011
|
var run = (cmd, args) => {
|
|
7913
8012
|
try {
|
|
7914
8013
|
return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -7953,7 +8052,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
7953
8052
|
}
|
|
7954
8053
|
function worktreeExists(parent, prefix, n) {
|
|
7955
8054
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
7956
|
-
if (
|
|
8055
|
+
if (existsSync13(join14(parent, name, ".git"))) return true;
|
|
7957
8056
|
}
|
|
7958
8057
|
return false;
|
|
7959
8058
|
}
|
|
@@ -8027,7 +8126,7 @@ function planRunWorktree(dir, cfg, t, role) {
|
|
|
8027
8126
|
const existing = worktreeForNumber(dir, num);
|
|
8028
8127
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8029
8128
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8030
|
-
if (existing && !existing.prunable &&
|
|
8129
|
+
if (existing && !existing.prunable && existsSync14(existing.path)) {
|
|
8031
8130
|
const branch = existing.branch;
|
|
8032
8131
|
if (status(existing.path).length > 0) return { action: "leave-dirty", path: existing.path, branch };
|
|
8033
8132
|
if (!branch) return { action: "leave-unpushed", path: existing.path, branch: "(detached HEAD)" };
|
|
@@ -8041,7 +8140,7 @@ function planRunWorktree(dir, cfg, t, role) {
|
|
|
8041
8140
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8042
8141
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8043
8142
|
}
|
|
8044
|
-
const path =
|
|
8143
|
+
const path = join15(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8045
8144
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8046
8145
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8047
8146
|
}
|
|
@@ -8088,7 +8187,7 @@ function worktreeHoldsUnpushed(path, remote) {
|
|
|
8088
8187
|
}
|
|
8089
8188
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8090
8189
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8091
|
-
if (!wt || !
|
|
8190
|
+
if (!wt || !existsSync14(wt.path)) return { kind: "none" };
|
|
8092
8191
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8093
8192
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8094
8193
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8100,7 +8199,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8100
8199
|
if (pids.length) await killGently(pids);
|
|
8101
8200
|
}
|
|
8102
8201
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8103
|
-
return
|
|
8202
|
+
return existsSync14(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8104
8203
|
}
|
|
8105
8204
|
|
|
8106
8205
|
// src/handoff-guard.ts
|
|
@@ -8249,13 +8348,13 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
|
|
|
8249
8348
|
}
|
|
8250
8349
|
|
|
8251
8350
|
// src/connect-wizard.ts
|
|
8252
|
-
import { delimiter, dirname as dirname8, join as
|
|
8253
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
8351
|
+
import { delimiter, dirname as dirname8, join as join16 } from "path";
|
|
8352
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
8254
8353
|
import { stringify } from "yaml";
|
|
8255
8354
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
8256
8355
|
function shouldRunWizard(opts) {
|
|
8257
8356
|
if (!opts.isTTY) return false;
|
|
8258
|
-
const exists = opts.configExists ??
|
|
8357
|
+
const exists = opts.configExists ?? existsSync15;
|
|
8259
8358
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
8260
8359
|
}
|
|
8261
8360
|
function firstRunConfigPath() {
|
|
@@ -8272,7 +8371,7 @@ function defaultIsExecutable(p) {
|
|
|
8272
8371
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
8273
8372
|
for (const dir of pathEnv.split(delimiter)) {
|
|
8274
8373
|
if (!dir) continue;
|
|
8275
|
-
const candidate =
|
|
8374
|
+
const candidate = join16(dir, name);
|
|
8276
8375
|
if (isExecutable(candidate)) return candidate;
|
|
8277
8376
|
}
|
|
8278
8377
|
return void 0;
|
|
@@ -8367,8 +8466,8 @@ Wrote ${o.configPath}.
|
|
|
8367
8466
|
}
|
|
8368
8467
|
|
|
8369
8468
|
// src/release-run.ts
|
|
8370
|
-
import { readFileSync as
|
|
8371
|
-
import { dirname as dirname9, join as
|
|
8469
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
|
|
8470
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
8372
8471
|
import { tmpdir as tmpdir2 } from "os";
|
|
8373
8472
|
|
|
8374
8473
|
// src/release.ts
|
|
@@ -9026,13 +9125,13 @@ async function bump(o, size) {
|
|
|
9026
9125
|
}
|
|
9027
9126
|
return printed;
|
|
9028
9127
|
}
|
|
9029
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
9128
|
+
const files = o.repo.release.versionFiles.map((f) => join17(o.cwd, f));
|
|
9030
9129
|
const first = files[0];
|
|
9031
|
-
if (!
|
|
9130
|
+
if (!existsSync16(first)) {
|
|
9032
9131
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
9033
9132
|
return null;
|
|
9034
9133
|
}
|
|
9035
|
-
const current = JSON.parse(
|
|
9134
|
+
const current = JSON.parse(readFileSync12(first, "utf8")).version;
|
|
9036
9135
|
if (!current) {
|
|
9037
9136
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
9038
9137
|
return null;
|
|
@@ -9045,7 +9144,7 @@ async function bump(o, size) {
|
|
|
9045
9144
|
return version;
|
|
9046
9145
|
}
|
|
9047
9146
|
for (const f of files) {
|
|
9048
|
-
const src =
|
|
9147
|
+
const src = readFileSync12(f, "utf8");
|
|
9049
9148
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
9050
9149
|
if (out === src) {
|
|
9051
9150
|
o.emit.error(`no version field in ${f}`);
|
|
@@ -9058,13 +9157,13 @@ async function bump(o, size) {
|
|
|
9058
9157
|
function writeChangelog(o, version, entries) {
|
|
9059
9158
|
const file = o.repo.release.changelog;
|
|
9060
9159
|
if (!file || entries.length === 0) return;
|
|
9061
|
-
const path =
|
|
9160
|
+
const path = join17(o.cwd, file);
|
|
9062
9161
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
9063
9162
|
if (o.dryRun) {
|
|
9064
9163
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
9065
9164
|
return;
|
|
9066
9165
|
}
|
|
9067
|
-
const existing =
|
|
9166
|
+
const existing = existsSync16(path) ? readFileSync12(path, "utf8") : "# Changelog\n";
|
|
9068
9167
|
writeFileSync9(path, insertChangelogSection(existing, section));
|
|
9069
9168
|
}
|
|
9070
9169
|
async function confirm(o, expected) {
|
|
@@ -9146,8 +9245,8 @@ async function confirmLastRelease(o, decision) {
|
|
|
9146
9245
|
function openReleaseCheckout(o) {
|
|
9147
9246
|
const { remote, base } = o.repo.branch;
|
|
9148
9247
|
const branch = `crew/release-${Date.now()}`;
|
|
9149
|
-
const root = mkdtempSync(
|
|
9150
|
-
const dir =
|
|
9248
|
+
const root = mkdtempSync(join17(tmpdir2(), "crew-release-"));
|
|
9249
|
+
const dir = join17(root, "checkout");
|
|
9151
9250
|
const close = () => {
|
|
9152
9251
|
gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
|
|
9153
9252
|
rmSync6(root, { recursive: true, force: true });
|
|
@@ -9161,10 +9260,10 @@ function openReleaseCheckout(o) {
|
|
|
9161
9260
|
throw e;
|
|
9162
9261
|
}
|
|
9163
9262
|
for (const f of o.repo.worktrees.copy) {
|
|
9164
|
-
const src =
|
|
9165
|
-
if (!
|
|
9166
|
-
mkdirSync8(dirname9(
|
|
9167
|
-
copyFileSync(src,
|
|
9263
|
+
const src = join17(o.cwd, f);
|
|
9264
|
+
if (!existsSync16(src)) continue;
|
|
9265
|
+
mkdirSync8(dirname9(join17(dir, f)), { recursive: true });
|
|
9266
|
+
copyFileSync(src, join17(dir, f));
|
|
9168
9267
|
}
|
|
9169
9268
|
return { dir, branch, close };
|
|
9170
9269
|
}
|
|
@@ -9633,7 +9732,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
9633
9732
|
}
|
|
9634
9733
|
|
|
9635
9734
|
// src/watch.ts
|
|
9636
|
-
import { watch as watchFile, existsSync as
|
|
9735
|
+
import { watch as watchFile, existsSync as existsSync17, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
|
|
9637
9736
|
var E = "\x1B[";
|
|
9638
9737
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
9639
9738
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -9734,7 +9833,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
9734
9833
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
9735
9834
|
}
|
|
9736
9835
|
function readFrom(file, from) {
|
|
9737
|
-
if (!
|
|
9836
|
+
if (!existsSync17(file)) return { events: [], offset: from };
|
|
9738
9837
|
const size = statSync6(file).size;
|
|
9739
9838
|
if (size < from) return readFrom(file, 0);
|
|
9740
9839
|
if (size === from) return { events: [], offset: from };
|
|
@@ -10445,7 +10544,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
10445
10544
|
}
|
|
10446
10545
|
|
|
10447
10546
|
// src/repos-cmd.ts
|
|
10448
|
-
import { homedir as
|
|
10547
|
+
import { homedir as homedir7 } from "os";
|
|
10449
10548
|
import { basename as basename7, resolve as resolve3 } from "path";
|
|
10450
10549
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
10451
10550
|
var ReposError = class extends Error {
|
|
@@ -10492,7 +10591,7 @@ function parseAddArgs(args, routes, isDir) {
|
|
|
10492
10591
|
return { route, path: "." };
|
|
10493
10592
|
}
|
|
10494
10593
|
function expandDir(p, base) {
|
|
10495
|
-
const home = p === "~" ?
|
|
10594
|
+
const home = p === "~" ? homedir7() : p.startsWith("~/") ? `${homedir7()}/${p.slice(2)}` : p;
|
|
10496
10595
|
return resolve3(base, home);
|
|
10497
10596
|
}
|
|
10498
10597
|
function routeMapOf(doc, route) {
|
|
@@ -10627,7 +10726,7 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
10627
10726
|
}
|
|
10628
10727
|
|
|
10629
10728
|
// src/worktree-sweep.ts
|
|
10630
|
-
import { existsSync as
|
|
10729
|
+
import { existsSync as existsSync18 } from "fs";
|
|
10631
10730
|
import { basename as basename8 } from "path";
|
|
10632
10731
|
function planWorktreeSweep(target, tickets, contract) {
|
|
10633
10732
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
@@ -10655,7 +10754,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
|
|
|
10655
10754
|
let removed = 0;
|
|
10656
10755
|
let keptBranches = 0;
|
|
10657
10756
|
for (const a of actions) {
|
|
10658
|
-
if (!dryRun &&
|
|
10757
|
+
if (!dryRun && existsSync18(a.path)) {
|
|
10659
10758
|
let dirty = false;
|
|
10660
10759
|
try {
|
|
10661
10760
|
dirty = status(a.path).length > 0;
|
|
@@ -10669,7 +10768,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
|
|
|
10669
10768
|
continue;
|
|
10670
10769
|
}
|
|
10671
10770
|
}
|
|
10672
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
10771
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync18(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
10673
10772
|
log.warn(
|
|
10674
10773
|
`not removing worktree ${a.path} for ${a.ticket.issue_id} (${a.ticket.status}): ${a.branch} has commits that are not on ${remote}`,
|
|
10675
10774
|
{ ticket: a.ticket.issue_id, step: "worktree" }
|
|
@@ -10750,14 +10849,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
10750
10849
|
}
|
|
10751
10850
|
|
|
10752
10851
|
// src/stream-sweep.ts
|
|
10753
|
-
import { existsSync as
|
|
10754
|
-
import { join as
|
|
10852
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync7 } from "fs";
|
|
10853
|
+
import { join as join18 } from "path";
|
|
10755
10854
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
10756
10855
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
10757
|
-
const dir =
|
|
10758
|
-
if (!
|
|
10856
|
+
const dir = join18(stateDir, "streams");
|
|
10857
|
+
if (!existsSync19(dir)) return [];
|
|
10759
10858
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
10760
|
-
return
|
|
10859
|
+
return readdirSync4(dir).map((name) => join18(dir, name)).filter((path) => {
|
|
10761
10860
|
try {
|
|
10762
10861
|
return statSync7(path).mtimeMs < cutoff;
|
|
10763
10862
|
} catch {
|
|
@@ -10783,9 +10882,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
10783
10882
|
return removed;
|
|
10784
10883
|
}
|
|
10785
10884
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
10786
|
-
if (!
|
|
10885
|
+
if (!existsSync19(stateDir)) return [];
|
|
10787
10886
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
10788
|
-
return
|
|
10887
|
+
return readdirSync4(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join18(stateDir, name)).filter((path) => {
|
|
10789
10888
|
try {
|
|
10790
10889
|
return statSync7(path).mtimeMs < cutoff;
|
|
10791
10890
|
} catch {
|
|
@@ -10795,23 +10894,23 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
10795
10894
|
}
|
|
10796
10895
|
|
|
10797
10896
|
// src/cli.ts
|
|
10798
|
-
import { readFileSync as
|
|
10897
|
+
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
10898
|
import { createInterface } from "readline/promises";
|
|
10800
10899
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
10801
10900
|
|
|
10802
10901
|
// src/ssh-keys.ts
|
|
10803
10902
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
10804
|
-
import { existsSync as
|
|
10805
|
-
import { dirname as dirname11, join as
|
|
10903
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync13, chmodSync } from "fs";
|
|
10904
|
+
import { dirname as dirname11, join as join19 } from "path";
|
|
10806
10905
|
var SshKeyError = class extends Error {
|
|
10807
10906
|
};
|
|
10808
10907
|
function shipSshKeyPathFor(stateDir) {
|
|
10809
|
-
return
|
|
10908
|
+
return join19(stateDir, "ssh", "id_ed25519");
|
|
10810
10909
|
}
|
|
10811
10910
|
function ensureShipSshKeypair(stateDir) {
|
|
10812
10911
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
10813
10912
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
10814
|
-
if (!
|
|
10913
|
+
if (!existsSync20(privateKeyPath)) {
|
|
10815
10914
|
mkdirSync9(dirname11(privateKeyPath), { recursive: true });
|
|
10816
10915
|
try {
|
|
10817
10916
|
execFileSync3("ssh-keygen", [
|
|
@@ -10832,10 +10931,10 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
10832
10931
|
}
|
|
10833
10932
|
chmodSync(privateKeyPath, 384);
|
|
10834
10933
|
}
|
|
10835
|
-
if (!
|
|
10934
|
+
if (!existsSync20(publicKeyPath)) {
|
|
10836
10935
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
10837
10936
|
}
|
|
10838
|
-
const publicKey =
|
|
10937
|
+
const publicKey = readFileSync13(publicKeyPath, "utf8").trim();
|
|
10839
10938
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
10840
10939
|
}
|
|
10841
10940
|
function sshKeygenAvailable() {
|
|
@@ -11049,9 +11148,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
11049
11148
|
|
|
11050
11149
|
// src/tunnel.ts
|
|
11051
11150
|
import { spawn as spawn3 } from "child_process";
|
|
11052
|
-
import { existsSync as
|
|
11151
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
|
|
11053
11152
|
import { randomInt } from "crypto";
|
|
11054
|
-
import { join as
|
|
11153
|
+
import { join as join20 } from "path";
|
|
11055
11154
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
11056
11155
|
var defaultSpawn = (cmd, args) => spawn3(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
11057
11156
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -11118,20 +11217,20 @@ function startTunnel(opts) {
|
|
|
11118
11217
|
};
|
|
11119
11218
|
}
|
|
11120
11219
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
11121
|
-
return
|
|
11220
|
+
return join20(stateDir, "passengers", `${workspaceId}.json`);
|
|
11122
11221
|
}
|
|
11123
11222
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
11124
11223
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
11125
|
-
if (!
|
|
11224
|
+
if (!existsSync21(path)) return void 0;
|
|
11126
11225
|
try {
|
|
11127
|
-
return JSON.parse(
|
|
11226
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
11128
11227
|
} catch {
|
|
11129
11228
|
return void 0;
|
|
11130
11229
|
}
|
|
11131
11230
|
}
|
|
11132
11231
|
function writePersistedTunnel(stateDir, state) {
|
|
11133
11232
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
11134
|
-
mkdirSync10(
|
|
11233
|
+
mkdirSync10(join20(stateDir, "passengers"), { recursive: true });
|
|
11135
11234
|
writeFileSync10(path, `${JSON.stringify(state, null, 2)}
|
|
11136
11235
|
`);
|
|
11137
11236
|
}
|
|
@@ -11229,9 +11328,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
11229
11328
|
}
|
|
11230
11329
|
}
|
|
11231
11330
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
11232
|
-
const dir =
|
|
11233
|
-
if (!
|
|
11234
|
-
return
|
|
11331
|
+
const dir = join20(stateDir, "passengers");
|
|
11332
|
+
if (!existsSync21(dir)) return [];
|
|
11333
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
11235
11334
|
}
|
|
11236
11335
|
|
|
11237
11336
|
// src/mcp-endpoint-registry.ts
|
|
@@ -11351,8 +11450,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
11351
11450
|
import { createServer as createHttpServer } from "http";
|
|
11352
11451
|
import { spawn as spawn4 } from "child_process";
|
|
11353
11452
|
import { randomBytes } from "crypto";
|
|
11354
|
-
import { existsSync as
|
|
11355
|
-
import { join as
|
|
11453
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
|
|
11454
|
+
import { join as join21 } from "path";
|
|
11356
11455
|
|
|
11357
11456
|
// src/secret-compare.ts
|
|
11358
11457
|
import { timingSafeEqual } from "crypto";
|
|
@@ -11441,20 +11540,20 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
11441
11540
|
return child;
|
|
11442
11541
|
};
|
|
11443
11542
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
11444
|
-
return
|
|
11543
|
+
return join21(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
11445
11544
|
}
|
|
11446
11545
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
11447
11546
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
11448
|
-
if (!
|
|
11547
|
+
if (!existsSync22(path)) return void 0;
|
|
11449
11548
|
try {
|
|
11450
|
-
return JSON.parse(
|
|
11549
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
11451
11550
|
} catch {
|
|
11452
11551
|
return void 0;
|
|
11453
11552
|
}
|
|
11454
11553
|
}
|
|
11455
11554
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
11456
11555
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
11457
|
-
mkdirSync11(
|
|
11556
|
+
mkdirSync11(join21(stateDir, "passengers"), { recursive: true });
|
|
11458
11557
|
writeFileSync11(path, `${JSON.stringify(state, null, 2)}
|
|
11459
11558
|
`);
|
|
11460
11559
|
}
|
|
@@ -11475,10 +11574,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
11475
11574
|
}
|
|
11476
11575
|
}
|
|
11477
11576
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
11478
|
-
const dir =
|
|
11479
|
-
if (!
|
|
11577
|
+
const dir = join21(stateDir, "passengers");
|
|
11578
|
+
if (!existsSync22(dir)) return [];
|
|
11480
11579
|
const suffix = "-sync.json";
|
|
11481
|
-
return
|
|
11580
|
+
return readdirSync6(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
11482
11581
|
}
|
|
11483
11582
|
function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
11484
11583
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
@@ -11533,8 +11632,8 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
11533
11632
|
}
|
|
11534
11633
|
|
|
11535
11634
|
// src/daemon.ts
|
|
11536
|
-
import { existsSync as
|
|
11537
|
-
import { join as
|
|
11635
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
11636
|
+
import { join as join22 } from "path";
|
|
11538
11637
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
11539
11638
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
11540
11639
|
var defaultSleep = (ms2) => new Promise((resolve5) => setTimeout(resolve5, ms2));
|
|
@@ -11572,8 +11671,8 @@ function latestMtime(path) {
|
|
|
11572
11671
|
if (stat.isFile()) return stat.mtimeMs;
|
|
11573
11672
|
if (!stat.isDirectory()) return 0;
|
|
11574
11673
|
let latest = stat.mtimeMs;
|
|
11575
|
-
for (const entry of
|
|
11576
|
-
latest = Math.max(latest, latestMtime(
|
|
11674
|
+
for (const entry of readdirSync7(path, { withFileTypes: true })) {
|
|
11675
|
+
latest = Math.max(latest, latestMtime(join22(path, entry.name)));
|
|
11577
11676
|
}
|
|
11578
11677
|
return latest;
|
|
11579
11678
|
}
|
|
@@ -11582,13 +11681,13 @@ function computeTreeSignature(paths) {
|
|
|
11582
11681
|
}
|
|
11583
11682
|
function makeStaleChecker(paths, opts = {}) {
|
|
11584
11683
|
const initial = computeTreeSignature(paths);
|
|
11585
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
11684
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync23(opts.reloadSentinel);
|
|
11586
11685
|
}
|
|
11587
11686
|
function reloadSentinelFor(stateDir) {
|
|
11588
|
-
return
|
|
11687
|
+
return join22(stateDir, "reload-requested");
|
|
11589
11688
|
}
|
|
11590
11689
|
function daemonStampFor(stateDir) {
|
|
11591
|
-
return
|
|
11690
|
+
return join22(stateDir, "daemon.started");
|
|
11592
11691
|
}
|
|
11593
11692
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
11594
11693
|
mkdirSync12(stateDir, { recursive: true });
|
|
@@ -11598,7 +11697,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
|
11598
11697
|
}
|
|
11599
11698
|
function readDaemonStarted(stateDir) {
|
|
11600
11699
|
try {
|
|
11601
|
-
const v = JSON.parse(
|
|
11700
|
+
const v = JSON.parse(readFileSync16(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
11602
11701
|
return typeof v === "number" ? v : null;
|
|
11603
11702
|
} catch {
|
|
11604
11703
|
return null;
|
|
@@ -12188,8 +12287,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
12188
12287
|
const FLEET_CAPABLE = /* @__PURE__ */ new Set(["poll", "run", "passengers"]);
|
|
12189
12288
|
const releaseFleetWide = ["merge", "deploy", "release"].includes(command) && flag("fleet");
|
|
12190
12289
|
const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
|
|
12191
|
-
const
|
|
12192
|
-
const
|
|
12290
|
+
const worktreeArgs = command === "worktree" ? splitWorktreeArgs(positional, cfg.routes.map((r) => r.route)) : void 0;
|
|
12291
|
+
const named = worktreeArgs ? worktreeArgs.route : command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
|
|
12292
|
+
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
12293
|
let route;
|
|
12194
12294
|
try {
|
|
12195
12295
|
route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
|
|
@@ -12395,8 +12495,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
12395
12495
|
}
|
|
12396
12496
|
if (host === "linux" && hasSystemd) {
|
|
12397
12497
|
const label = labelFor(CREW_HOME, "run");
|
|
12398
|
-
const staleTimer =
|
|
12399
|
-
if (
|
|
12498
|
+
const staleTimer = join23(homedir8(), ".config", "systemd", "user", `${label}.timer`);
|
|
12499
|
+
if (existsSync24(staleTimer)) {
|
|
12400
12500
|
process.stdout.write(
|
|
12401
12501
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
12402
12502
|
`
|
|
@@ -12422,7 +12522,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
12422
12522
|
const tracker = new Tracker(c, cfg.ship);
|
|
12423
12523
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
12424
12524
|
for (const r of await resolvedRepos(c)) {
|
|
12425
|
-
if (!
|
|
12525
|
+
if (!existsSync24(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
12426
12526
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
12427
12527
|
problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
12428
12528
|
}
|
|
@@ -12440,7 +12540,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
12440
12540
|
}
|
|
12441
12541
|
async function doctorFix(o) {
|
|
12442
12542
|
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) =>
|
|
12543
|
+
const unitExists = (p) => existsSync24(p);
|
|
12444
12544
|
const crontab = o.host === "linux" ? spawnSync5("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
12445
12545
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
|
|
12446
12546
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -12457,7 +12557,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
12457
12557
|
for (const a of plan.actions) {
|
|
12458
12558
|
if (a.kind === "enable") {
|
|
12459
12559
|
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(
|
|
12560
|
+
writeFileSync13(cfg.configFile, planEnable(readFileSync17(cfg.configFile, "utf8"), a.route));
|
|
12461
12561
|
enabledSound.add(a.route);
|
|
12462
12562
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
12463
12563
|
} else {
|
|
@@ -13364,7 +13464,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13364
13464
|
process.exit(1);
|
|
13365
13465
|
}
|
|
13366
13466
|
requireArmed("run the daemon loop");
|
|
13367
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
13467
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join23(CREW_HOME, "src"), join23(CREW_HOME, "dist"), join23(CREW_HOME, "bin")];
|
|
13368
13468
|
const configPaths = daemonConfigPaths(cfg);
|
|
13369
13469
|
watchPaths.push(...configPaths);
|
|
13370
13470
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -13523,7 +13623,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13523
13623
|
`);
|
|
13524
13624
|
if (!dryRun) {
|
|
13525
13625
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
13526
|
-
const raw =
|
|
13626
|
+
const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
|
|
13527
13627
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
13528
13628
|
raw.agentSkills = skillResult.agentSkills;
|
|
13529
13629
|
mkdirSync13(dirOf(resolvedPath), { recursive: true });
|
|
@@ -13574,7 +13674,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13574
13674
|
`);
|
|
13575
13675
|
if (!dryRun) {
|
|
13576
13676
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
13577
|
-
const raw =
|
|
13677
|
+
const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
|
|
13578
13678
|
raw.agentSkills = result.agentSkills;
|
|
13579
13679
|
mkdirSync13(dirOf(resolvedPath), { recursive: true });
|
|
13580
13680
|
writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -13590,14 +13690,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13590
13690
|
process.exit(2);
|
|
13591
13691
|
};
|
|
13592
13692
|
if (sub !== "add" && sub !== "list") usage2();
|
|
13593
|
-
const configText =
|
|
13693
|
+
const configText = readFileSync17(cfg.configFile, "utf8");
|
|
13594
13694
|
let routeName = positional[2];
|
|
13595
13695
|
let addPath = ".";
|
|
13596
13696
|
let addName;
|
|
13597
13697
|
if (sub === "list" && !routeName) usage2();
|
|
13598
13698
|
if (sub === "add") {
|
|
13599
13699
|
try {
|
|
13600
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
13700
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync24(resolvePath(p.replace(/^~(?=\/|$)/, homedir8()))));
|
|
13601
13701
|
routeName = a.route;
|
|
13602
13702
|
addPath = a.path;
|
|
13603
13703
|
addName = a.name;
|
|
@@ -13612,9 +13712,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13612
13712
|
}
|
|
13613
13713
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
13614
13714
|
let resolvedRemotes;
|
|
13615
|
-
if (
|
|
13715
|
+
if (existsSync24(resolvedFile)) {
|
|
13616
13716
|
try {
|
|
13617
|
-
resolvedRemotes = JSON.parse(
|
|
13717
|
+
resolvedRemotes = JSON.parse(readFileSync17(resolvedFile, "utf8"));
|
|
13618
13718
|
} catch {
|
|
13619
13719
|
}
|
|
13620
13720
|
}
|
|
@@ -13629,14 +13729,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13629
13729
|
for (const r of repos) {
|
|
13630
13730
|
const origin2 = originOf(r.dir);
|
|
13631
13731
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
13632
|
-
const note = !
|
|
13732
|
+
const note = !existsSync24(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
13633
13733
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
13634
13734
|
`);
|
|
13635
13735
|
}
|
|
13636
13736
|
break;
|
|
13637
13737
|
}
|
|
13638
|
-
const target = resolvePath(addPath.replace(/^~(?=\/|$)/,
|
|
13639
|
-
const top =
|
|
13738
|
+
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir8()));
|
|
13739
|
+
const top = existsSync24(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
13640
13740
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
13641
13741
|
const origin = originOf(top);
|
|
13642
13742
|
const plan = planRepoAdd({
|
|
@@ -13685,8 +13785,8 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13685
13785
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
13686
13786
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
13687
13787
|
`);
|
|
13688
|
-
if (
|
|
13689
|
-
const raw = JSON.parse(
|
|
13788
|
+
if (existsSync24(resolvedFile)) {
|
|
13789
|
+
const raw = JSON.parse(readFileSync17(resolvedFile, "utf8"));
|
|
13690
13790
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
13691
13791
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
13692
13792
|
writeFileSync13(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -13932,10 +14032,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
13932
14032
|
operator = await pickOperator(found.holds);
|
|
13933
14033
|
}
|
|
13934
14034
|
let previous;
|
|
13935
|
-
const hadPreviousFile =
|
|
14035
|
+
const hadPreviousFile = existsSync24(resolvedPath);
|
|
13936
14036
|
if (hadPreviousFile) {
|
|
13937
14037
|
try {
|
|
13938
|
-
previous = JSON.parse(
|
|
14038
|
+
previous = JSON.parse(readFileSync17(resolvedPath, "utf8"));
|
|
13939
14039
|
} catch {
|
|
13940
14040
|
}
|
|
13941
14041
|
}
|
|
@@ -14023,7 +14123,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14023
14123
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
14024
14124
|
const wizard = firstRun;
|
|
14025
14125
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
14026
|
-
const routeDir = value("dir") ?? (writeFirstRun ?
|
|
14126
|
+
const routeDir = value("dir") ?? (writeFirstRun ? join23(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
|
|
14027
14127
|
const routeBlock = renderConnection(
|
|
14028
14128
|
found,
|
|
14029
14129
|
route2,
|
|
@@ -14364,21 +14464,36 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14364
14464
|
}
|
|
14365
14465
|
case "worktree": {
|
|
14366
14466
|
emit.enter("worktree");
|
|
14367
|
-
const rawWt =
|
|
14467
|
+
const rawWt = worktreeArgs?.ticket;
|
|
14368
14468
|
const nWt = rawWt ? parseTicketNumber(rawWt) : null;
|
|
14369
14469
|
if (!nWt) {
|
|
14370
14470
|
process.stderr.write("worktree: need a ticket number (326, ISSUE-326, or TABL-326)\n");
|
|
14371
14471
|
process.exit(2);
|
|
14372
14472
|
}
|
|
14373
|
-
const
|
|
14374
|
-
|
|
14375
|
-
const
|
|
14376
|
-
|
|
14377
|
-
|
|
14473
|
+
const candidatesWt = !named && cfg.routes.length > 1 ? cfg.routes.filter((r) => r.enabled) : [route];
|
|
14474
|
+
let ticketWt;
|
|
14475
|
+
const lookedWt = [];
|
|
14476
|
+
const hitsWt = /* @__PURE__ */ new Map();
|
|
14477
|
+
for (const cand of candidatesWt) {
|
|
14478
|
+
const tr = new Tracker(cand, cfg.ship);
|
|
14479
|
+
const hit = (await tr.openTickets()).find((t) => t[tr.contract.columns.key] === `ISSUE-${nWt}`);
|
|
14480
|
+
if (hit) hitsWt.set(cand.route, hit);
|
|
14481
|
+
lookedWt.push({
|
|
14482
|
+
route: cand.route,
|
|
14483
|
+
hit: hit ? { repoId: hit.repo_id ?? null } : void 0,
|
|
14484
|
+
servedRepoIds: hit ? (await resolvedRepos(cand)).flatMap((r) => r.id ? [r.id] : []) : []
|
|
14485
|
+
});
|
|
14486
|
+
}
|
|
14487
|
+
const pickedWt = pickWorktreeRoute(Number(nWt), lookedWt);
|
|
14488
|
+
if ("error" in pickedWt) {
|
|
14489
|
+
process.stderr.write(`worktree: ${pickedWt.error}
|
|
14378
14490
|
`);
|
|
14379
14491
|
process.exit(2);
|
|
14380
14492
|
}
|
|
14381
|
-
const
|
|
14493
|
+
const routeWt = candidatesWt.find((r) => r.route === pickedWt.route) ?? route;
|
|
14494
|
+
ticketWt = hitsWt.get(pickedWt.route);
|
|
14495
|
+
if (!ticketWt) process.exit(2);
|
|
14496
|
+
const repoWt = (await resolvedRepos(routeWt)).find((r) => r.id === ticketWt.repo_id) ?? (await resolvedRepos(routeWt))[0];
|
|
14382
14497
|
if (!repoWt) {
|
|
14383
14498
|
process.stderr.write("worktree: this route has no repository\n");
|
|
14384
14499
|
process.exit(2);
|
|
@@ -14588,7 +14703,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
14588
14703
|
}
|
|
14589
14704
|
case "log": {
|
|
14590
14705
|
try {
|
|
14591
|
-
const text =
|
|
14706
|
+
const text = readFileSync17(cfg.ship.logFile, "utf8").split("\n");
|
|
14592
14707
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
14593
14708
|
`);
|
|
14594
14709
|
} catch {
|
|
@@ -14798,7 +14913,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
14798
14913
|
);
|
|
14799
14914
|
try {
|
|
14800
14915
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
14801
|
-
const evSize =
|
|
14916
|
+
const evSize = existsSync24(evFile) ? statSync9(evFile).size : 0;
|
|
14802
14917
|
process.stdout.write(
|
|
14803
14918
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
14804
14919
|
` : "events log: ok\n"
|
|
@@ -14932,15 +15047,39 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
|
|
|
14932
15047
|
let allBinsFound = true;
|
|
14933
15048
|
for (const tc of shipToolchains(cfg.ship)) {
|
|
14934
15049
|
const adapter = adapterFor(tc.harness);
|
|
14935
|
-
const
|
|
15050
|
+
const runBin = launcherExecutable(tc) ?? tc.bin;
|
|
15051
|
+
const binPath = findOnPath(runBin, schedulerPath);
|
|
14936
15052
|
if (!binPath) allBinsFound = false;
|
|
14937
15053
|
const label = tc.name === defaultToolchain ? "agent binary: " : `toolchain ${tc.name}:`.padEnd(18);
|
|
14938
15054
|
process.stdout.write(
|
|
14939
|
-
`${label} ${
|
|
15055
|
+
`${label} ${runBin} \u2014 ${binPath ? `OK (${binPath})` : "NOT FOUND on the scheduler PATH"}${tc.name === defaultToolchain ? "" : ` [${adapter.displayName}]`}
|
|
14940
15056
|
`
|
|
14941
15057
|
);
|
|
14942
15058
|
for (const limit of adapter.limitations) process.stdout.write(` cannot enforce: ${limit}
|
|
14943
15059
|
`);
|
|
15060
|
+
if (isOmlxLauncher(tc)) {
|
|
15061
|
+
const s = readOmlxSettings();
|
|
15062
|
+
if (!s) {
|
|
15063
|
+
process.stdout.write(` omlx: ~/.omlx/settings.json not found or has no server.port \u2014 cannot check the server
|
|
15064
|
+
`);
|
|
15065
|
+
} else {
|
|
15066
|
+
let status2;
|
|
15067
|
+
try {
|
|
15068
|
+
status2 = (await fetch(`http://${s.host}:${s.port}/v1/models`, { signal: AbortSignal.timeout(3e3) })).status;
|
|
15069
|
+
} catch {
|
|
15070
|
+
status2 = void 0;
|
|
15071
|
+
}
|
|
15072
|
+
const probe = interpretOmlxProbe(status2);
|
|
15073
|
+
process.stdout.write(` omlx server: ${s.host}:${s.port} \u2014 ${probe.detail}${probe.up ? "" : " (start oMLX)"}
|
|
15074
|
+
`);
|
|
15075
|
+
process.stdout.write(
|
|
15076
|
+
` omlx model: ${tc.model} \u2014 ${omlxModelPresent(tc.model, s.modelDirs) ? "present" : `NOT FOUND in ${s.modelDirs.join(", ") || "any model dir"}`}
|
|
15077
|
+
`
|
|
15078
|
+
);
|
|
15079
|
+
}
|
|
15080
|
+
process.stdout.write(` local model: tool-call reliability and prefill speed vary by model
|
|
15081
|
+
`);
|
|
15082
|
+
}
|
|
14944
15083
|
if (isOriLauncher(tc)) {
|
|
14945
15084
|
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
15085
|
`);
|