@tablation/crew 0.121.0 → 0.123.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 +788 -495
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -262,6 +262,9 @@ function intelFlavor(script) {
|
|
|
262
262
|
const w = script?.trim();
|
|
263
263
|
return w && Object.prototype.hasOwnProperty.call(INTEL_FLAVORS, w) ? w : void 0;
|
|
264
264
|
}
|
|
265
|
+
function wantsCodeBridge(script) {
|
|
266
|
+
return !!script?.trim() && intelFlavor(script) !== "typescript-lsp";
|
|
267
|
+
}
|
|
265
268
|
var INTEL_MINIMUM_TOOLS = [
|
|
266
269
|
"find symbols by name",
|
|
267
270
|
"references to a symbol",
|
|
@@ -1309,13 +1312,17 @@ var claudeAdapter = {
|
|
|
1309
1312
|
// so the resolved key is never written to an argv string or a config
|
|
1310
1313
|
// file on disk. Skipped when there's no key to send: a route `crew
|
|
1311
1314
|
// connect` hasn't resolved yet has nothing to authenticate with.
|
|
1312
|
-
...o.mcp ? ["--mcp-config", JSON.stringify({
|
|
1315
|
+
...o.mcp || o.code ? ["--mcp-config", JSON.stringify({
|
|
1313
1316
|
mcpServers: {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1317
|
+
...o.mcp ? {
|
|
1318
|
+
tablation: {
|
|
1319
|
+
type: "http",
|
|
1320
|
+
url: o.mcp.url,
|
|
1321
|
+
headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
|
|
1322
|
+
}
|
|
1323
|
+
} : {},
|
|
1324
|
+
// CREW-2912: the bridge to the worktree's symbol service, attached once `crew worktree` has started it.
|
|
1325
|
+
...o.code ? { [INTEL_SERVER_NAME]: { type: "stdio", command: o.code.command, args: o.code.args } } : {}
|
|
1319
1326
|
}
|
|
1320
1327
|
})] : [],
|
|
1321
1328
|
// A crew-generated language-server plugin (the `typescript-lsp` intel flavor). Not under oMLX,
|
|
@@ -1664,6 +1671,14 @@ var codexAdapter = {
|
|
|
1664
1671
|
...cfg(`mcp_servers.${SERVER}.bearer_token_env_var`, o.mcp.keyVar),
|
|
1665
1672
|
...cfg(`mcp_servers.${SERVER}.default_tools_approval_mode`, "approve")
|
|
1666
1673
|
] : [],
|
|
1674
|
+
// CREW-2912: the bridge to the worktree's symbol service, scoped to this run like the tracker entry above.
|
|
1675
|
+
...o.code ? [
|
|
1676
|
+
"-c",
|
|
1677
|
+
`mcp_servers.${INTEL_SERVER_NAME}.command=${tomlString(o.code.command)}`,
|
|
1678
|
+
"-c",
|
|
1679
|
+
`mcp_servers.${INTEL_SERVER_NAME}.args=[${o.code.args.map(tomlString).join(", ")}]`,
|
|
1680
|
+
...cfg(`mcp_servers.${INTEL_SERVER_NAME}.default_tools_approval_mode`, "approve")
|
|
1681
|
+
] : [],
|
|
1667
1682
|
...spec.args,
|
|
1668
1683
|
// Prompt on stdin.
|
|
1669
1684
|
"-"
|
|
@@ -2045,7 +2060,8 @@ var geminiAdapter = {
|
|
|
2045
2060
|
];
|
|
2046
2061
|
return wrapInvocation(spec, { model: o.model, args });
|
|
2047
2062
|
},
|
|
2048
|
-
|
|
2063
|
+
// CREW-2912: Gemini has no per-run server list, so the bridge is declared in a per-run system settings file the planner wrote.
|
|
2064
|
+
runEnv: (spec, o) => ({ ...spec.env, ...o?.code?.settingsFile ? { GEMINI_CLI_SYSTEM_SETTINGS_PATH: o.code.settingsFile } : {} }),
|
|
2049
2065
|
provider: () => "google",
|
|
2050
2066
|
unsetEnv: BILLING_VARS_TO_UNSET2,
|
|
2051
2067
|
mapStreamLine: mapGeminiLine,
|
|
@@ -5834,6 +5850,78 @@ function cloneFailureMarker(dir) {
|
|
|
5834
5850
|
return `${dir}.clone-failed`;
|
|
5835
5851
|
}
|
|
5836
5852
|
|
|
5853
|
+
// src/repo-map.ts
|
|
5854
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
5855
|
+
import { join as join20 } from "path";
|
|
5856
|
+
var CHARS_PER_TOKEN = 4;
|
|
5857
|
+
var MAP_TIMEOUT_MS = 6e4;
|
|
5858
|
+
function budgetMap(text2, tokenBudget) {
|
|
5859
|
+
const body = text2.replace(/\s+$/, "");
|
|
5860
|
+
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
5861
|
+
if (body.length <= limit) return body;
|
|
5862
|
+
const lines2 = body.split("\n");
|
|
5863
|
+
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
5864
|
+
let kept = lines2.length;
|
|
5865
|
+
let size = body.length;
|
|
5866
|
+
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
5867
|
+
kept--;
|
|
5868
|
+
size -= lines2[kept].length + 1;
|
|
5869
|
+
}
|
|
5870
|
+
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
5871
|
+
}
|
|
5872
|
+
function mapFilePath(value2) {
|
|
5873
|
+
if (!value2) return null;
|
|
5874
|
+
const v = value2.trim();
|
|
5875
|
+
if (!/^[A-Za-z0-9_.+@-]+(?:\/[A-Za-z0-9_.+@-]+)*\.md$/i.test(v)) return null;
|
|
5876
|
+
if (v.split("/").some((seg) => seg === ".." || seg === ".")) return null;
|
|
5877
|
+
return v;
|
|
5878
|
+
}
|
|
5879
|
+
function inspectMapFile(dir, path) {
|
|
5880
|
+
try {
|
|
5881
|
+
const text2 = readFileSync18(join20(dir, path), "utf8").replace(/\s+$/, "");
|
|
5882
|
+
return { path, exists: true, lines: text2 ? text2.split("\n").length : 0, tokens: Math.ceil(text2.length / CHARS_PER_TOKEN) };
|
|
5883
|
+
} catch {
|
|
5884
|
+
return { path, exists: false, lines: 0, tokens: 0 };
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
5888
|
+
const script = repo.config.hooks.map;
|
|
5889
|
+
if (!script) return Promise.resolve({ skipped: "" });
|
|
5890
|
+
const file = mapFilePath(script);
|
|
5891
|
+
if (file) {
|
|
5892
|
+
const m = inspectMapFile(repo.dir, file);
|
|
5893
|
+
if (!m.exists) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is missing or unreadable, so the brief has no repository map` });
|
|
5894
|
+
let text2 = "";
|
|
5895
|
+
try {
|
|
5896
|
+
text2 = readFileSync18(join20(repo.dir, file), "utf8");
|
|
5897
|
+
} catch {
|
|
5898
|
+
}
|
|
5899
|
+
if (!text2.trim()) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is empty, so the brief has no repository map` });
|
|
5900
|
+
return Promise.resolve({ text: budgetMap(text2, tokenBudget) });
|
|
5901
|
+
}
|
|
5902
|
+
return runScript(script, {
|
|
5903
|
+
cwd: repo.dir,
|
|
5904
|
+
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
5905
|
+
env: { CREW_REPO: repo.name },
|
|
5906
|
+
timeoutMs: MAP_TIMEOUT_MS
|
|
5907
|
+
}).then(
|
|
5908
|
+
(r) => {
|
|
5909
|
+
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
5910
|
+
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
5911
|
+
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
5912
|
+
},
|
|
5913
|
+
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
5917
|
+
return Promise.all(repos.map(async (r) => {
|
|
5918
|
+
if (!r.config.hooks.map) return r;
|
|
5919
|
+
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
5920
|
+
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
5921
|
+
return { ...r, map: out.text };
|
|
5922
|
+
}));
|
|
5923
|
+
}
|
|
5924
|
+
|
|
5837
5925
|
// src/ship-attention.ts
|
|
5838
5926
|
var RELEASE_STALE_AFTER_INTERVALS = 3;
|
|
5839
5927
|
function upsert(items, item) {
|
|
@@ -6075,6 +6163,7 @@ function clearShipAttention(state, key) {
|
|
|
6075
6163
|
if (next.length !== previous.length) store.persist(next);
|
|
6076
6164
|
}
|
|
6077
6165
|
function hookCommand(script) {
|
|
6166
|
+
if (mapFilePath(script)) return null;
|
|
6078
6167
|
for (const raw of script.split("\n")) {
|
|
6079
6168
|
const line = raw.trim();
|
|
6080
6169
|
if (!line || line.startsWith("#")) continue;
|
|
@@ -6154,6 +6243,18 @@ function repoFindings(o) {
|
|
|
6154
6243
|
remedy: "add the directory holding it to ship.extraPath in crew.yaml"
|
|
6155
6244
|
});
|
|
6156
6245
|
}
|
|
6246
|
+
if (o.mapFile) {
|
|
6247
|
+
if (!o.mapFile.exists) {
|
|
6248
|
+
problems.push({
|
|
6249
|
+
severity: "attention",
|
|
6250
|
+
kind: "map_file_missing",
|
|
6251
|
+
...at,
|
|
6252
|
+
hook: "map",
|
|
6253
|
+
message: `${o.route}/${o.repo}: hooks.map names ${o.mapFile.path}, which is not in the checkout, so briefs carry no repository map`,
|
|
6254
|
+
remedy: `commit ${o.mapFile.path}, or correct hooks.map in the repository's .crew.yaml`
|
|
6255
|
+
});
|
|
6256
|
+
} else passed++;
|
|
6257
|
+
}
|
|
6157
6258
|
if (o.intelSyntax) {
|
|
6158
6259
|
if (o.intelSyntax.status === "error") {
|
|
6159
6260
|
problems.push({
|
|
@@ -6189,8 +6290,8 @@ function attentionProblems(items, already) {
|
|
|
6189
6290
|
import { createHash as createHash6 } from "crypto";
|
|
6190
6291
|
import { spawn as spawn5 } from "child_process";
|
|
6191
6292
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
6192
|
-
import { mkdirSync as
|
|
6193
|
-
import { dirname as
|
|
6293
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6294
|
+
import { dirname as dirname14, join as join32 } from "path";
|
|
6194
6295
|
import { tmpdir as tmpdir3 } from "os";
|
|
6195
6296
|
|
|
6196
6297
|
// src/passenger-containers.ts
|
|
@@ -6203,23 +6304,23 @@ import { promisify as promisify2 } from "util";
|
|
|
6203
6304
|
|
|
6204
6305
|
// src/deprovision-host.ts
|
|
6205
6306
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6206
|
-
import { existsSync as
|
|
6307
|
+
import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
|
|
6207
6308
|
|
|
6208
6309
|
// src/interrupted.ts
|
|
6209
|
-
import { existsSync as
|
|
6210
|
-
import { join as
|
|
6310
|
+
import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync26 } from "fs";
|
|
6311
|
+
import { join as join30 } from "path";
|
|
6211
6312
|
|
|
6212
6313
|
// src/agent.ts
|
|
6213
6314
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6214
|
-
import { readFileSync as
|
|
6315
|
+
import { readFileSync as readFileSync25, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
|
|
6215
6316
|
import { StringDecoder } from "string_decoder";
|
|
6216
6317
|
import { finished } from "stream/promises";
|
|
6217
|
-
import { join as
|
|
6318
|
+
import { join as join29, dirname as dirname13, basename as basename9 } from "path";
|
|
6218
6319
|
import { createHash as createHash4 } from "crypto";
|
|
6219
6320
|
|
|
6220
6321
|
// src/state.ts
|
|
6221
|
-
import { readFileSync as
|
|
6222
|
-
import { join as
|
|
6322
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
6323
|
+
import { join as join21 } from "path";
|
|
6223
6324
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
6224
6325
|
var State = class _State {
|
|
6225
6326
|
dir;
|
|
@@ -6228,7 +6329,7 @@ var State = class _State {
|
|
|
6228
6329
|
mkdirSync13(dir, { recursive: true });
|
|
6229
6330
|
}
|
|
6230
6331
|
path(...parts) {
|
|
6231
|
-
return
|
|
6332
|
+
return join21(this.dir, ...parts);
|
|
6232
6333
|
}
|
|
6233
6334
|
/**
|
|
6234
6335
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -6239,7 +6340,7 @@ var State = class _State {
|
|
|
6239
6340
|
*/
|
|
6240
6341
|
watermark() {
|
|
6241
6342
|
try {
|
|
6242
|
-
const v =
|
|
6343
|
+
const v = readFileSync19(this.path(".poll-watermark"), "utf8").trim();
|
|
6243
6344
|
return v || EPOCH;
|
|
6244
6345
|
} catch {
|
|
6245
6346
|
return EPOCH;
|
|
@@ -6325,7 +6426,7 @@ var State = class _State {
|
|
|
6325
6426
|
const name = _State.safe(rawName);
|
|
6326
6427
|
const file = this.path(`.${name}.lock`);
|
|
6327
6428
|
try {
|
|
6328
|
-
const existing = Number.parseInt(
|
|
6429
|
+
const existing = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
|
|
6329
6430
|
if (Number.isFinite(existing) && existing > 0) {
|
|
6330
6431
|
try {
|
|
6331
6432
|
process.kill(existing, 0);
|
|
@@ -6363,7 +6464,7 @@ var State = class _State {
|
|
|
6363
6464
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
6364
6465
|
let pid = 0;
|
|
6365
6466
|
try {
|
|
6366
|
-
pid = Number.parseInt(
|
|
6467
|
+
pid = Number.parseInt(readFileSync19(this.path(n), "utf8").trim(), 10);
|
|
6367
6468
|
} catch {
|
|
6368
6469
|
continue;
|
|
6369
6470
|
}
|
|
@@ -6449,7 +6550,7 @@ var State = class _State {
|
|
|
6449
6550
|
ticketInFlight(recordId) {
|
|
6450
6551
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
6451
6552
|
try {
|
|
6452
|
-
const pid = Number.parseInt(
|
|
6553
|
+
const pid = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
|
|
6453
6554
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
6454
6555
|
process.kill(pid, 0);
|
|
6455
6556
|
return true;
|
|
@@ -6483,7 +6584,7 @@ var State = class _State {
|
|
|
6483
6584
|
const heldReported = `.release-held-${suffix}`;
|
|
6484
6585
|
const read = (f) => {
|
|
6485
6586
|
try {
|
|
6486
|
-
return
|
|
6587
|
+
return readFileSync19(this.path(f), "utf8").trim() || null;
|
|
6487
6588
|
} catch {
|
|
6488
6589
|
return null;
|
|
6489
6590
|
}
|
|
@@ -6533,7 +6634,7 @@ var State = class _State {
|
|
|
6533
6634
|
/** Whether the board was already told about this head's red gate. */
|
|
6534
6635
|
heldReported: () => {
|
|
6535
6636
|
try {
|
|
6536
|
-
return
|
|
6637
|
+
return readFileSync19(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
6537
6638
|
} catch {
|
|
6538
6639
|
return null;
|
|
6539
6640
|
}
|
|
@@ -6570,7 +6671,7 @@ var State = class _State {
|
|
|
6570
6671
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
6571
6672
|
const read = (f) => {
|
|
6572
6673
|
try {
|
|
6573
|
-
return
|
|
6674
|
+
return readFileSync19(this.path(f), "utf8").trim() || null;
|
|
6574
6675
|
} catch {
|
|
6575
6676
|
return null;
|
|
6576
6677
|
}
|
|
@@ -6635,7 +6736,7 @@ var State = class _State {
|
|
|
6635
6736
|
return {
|
|
6636
6737
|
previous: () => {
|
|
6637
6738
|
try {
|
|
6638
|
-
const parsed = JSON.parse(
|
|
6739
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6639
6740
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6640
6741
|
} catch {
|
|
6641
6742
|
return {};
|
|
@@ -6658,7 +6759,7 @@ var State = class _State {
|
|
|
6658
6759
|
return {
|
|
6659
6760
|
previous: () => {
|
|
6660
6761
|
try {
|
|
6661
|
-
const parsed = JSON.parse(
|
|
6762
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6662
6763
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6663
6764
|
} catch {
|
|
6664
6765
|
return [];
|
|
@@ -6679,7 +6780,7 @@ var State = class _State {
|
|
|
6679
6780
|
return {
|
|
6680
6781
|
previous: () => {
|
|
6681
6782
|
try {
|
|
6682
|
-
const parsed = JSON.parse(
|
|
6783
|
+
const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
|
|
6683
6784
|
return Array.isArray(parsed) ? parsed : [];
|
|
6684
6785
|
} catch {
|
|
6685
6786
|
return [];
|
|
@@ -6712,7 +6813,7 @@ var State = class _State {
|
|
|
6712
6813
|
}
|
|
6713
6814
|
readInt(file) {
|
|
6714
6815
|
try {
|
|
6715
|
-
return Number.parseInt(
|
|
6816
|
+
return Number.parseInt(readFileSync19(this.path(file), "utf8").trim(), 10) || 0;
|
|
6716
6817
|
} catch {
|
|
6717
6818
|
return 0;
|
|
6718
6819
|
}
|
|
@@ -7100,6 +7201,143 @@ function renderEnvironment(i) {
|
|
|
7100
7201
|
return lines2.join("\n");
|
|
7101
7202
|
}
|
|
7102
7203
|
|
|
7204
|
+
// src/intel-bridge.ts
|
|
7205
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7206
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
7207
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7208
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7209
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7210
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7211
|
+
import {
|
|
7212
|
+
CallToolRequestSchema,
|
|
7213
|
+
ListToolsRequestSchema
|
|
7214
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7215
|
+
var STATUS_TOOL = "code_status";
|
|
7216
|
+
var STATUS_WAITING = "The symbol service is not available yet: the worktree has not been cut. Run `crew worktree` for the ticket; the symbol tools appear in this server afterwards.";
|
|
7217
|
+
var STATUS_LOST = "The symbol service stopped answering, so its tools are unavailable. They return if the service comes back; until then use text search.";
|
|
7218
|
+
function intelDir(stateDir, route2, ticket) {
|
|
7219
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
7220
|
+
return join22(stateDir, "intel", safe(route2), safe(ticket));
|
|
7221
|
+
}
|
|
7222
|
+
var attachFilePath = (stateDir, route2, ticket) => join22(intelDir(stateDir, route2, ticket), "url");
|
|
7223
|
+
function writeAttachFile(file, url) {
|
|
7224
|
+
mkdirSync14(dirname10(file), { recursive: true });
|
|
7225
|
+
writeFileSync15(file, `${url}
|
|
7226
|
+
`);
|
|
7227
|
+
}
|
|
7228
|
+
function readAttachFile(file) {
|
|
7229
|
+
try {
|
|
7230
|
+
const url = readFileSync20(file, "utf8").trim();
|
|
7231
|
+
return /^https?:\/\/\S+$/i.test(url) ? url : void 0;
|
|
7232
|
+
} catch {
|
|
7233
|
+
return void 0;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
var statusTool = {
|
|
7237
|
+
name: STATUS_TOOL,
|
|
7238
|
+
description: "Reports whether this repository's symbol service (find symbols, references, definitions, file outlines) is attached. Before `crew worktree` has been run for the ticket it is not; the symbol tools replace this one once it is.",
|
|
7239
|
+
inputSchema: { type: "object", properties: {} }
|
|
7240
|
+
};
|
|
7241
|
+
var defaultConnect = async (url) => {
|
|
7242
|
+
const client2 = new Client({ name: "crew-intel-bridge", version: "0" }, { capabilities: {} });
|
|
7243
|
+
await client2.connect(new StreamableHTTPClientTransport(new URL(url)));
|
|
7244
|
+
return client2;
|
|
7245
|
+
};
|
|
7246
|
+
function createBridge(o) {
|
|
7247
|
+
const connect2 = o.connectUpstream ?? defaultConnect;
|
|
7248
|
+
const server = new Server(
|
|
7249
|
+
{ name: INTEL_SERVER_NAME, version: o.version ?? "0" },
|
|
7250
|
+
{ capabilities: { tools: { listChanged: true } } }
|
|
7251
|
+
);
|
|
7252
|
+
let upstream;
|
|
7253
|
+
let lost = false;
|
|
7254
|
+
let connecting = false;
|
|
7255
|
+
let timer;
|
|
7256
|
+
const notify2 = () => {
|
|
7257
|
+
void server.sendToolListChanged().catch(() => {
|
|
7258
|
+
});
|
|
7259
|
+
};
|
|
7260
|
+
const drop = () => {
|
|
7261
|
+
const gone = upstream;
|
|
7262
|
+
upstream = void 0;
|
|
7263
|
+
lost = true;
|
|
7264
|
+
if (gone) void gone.close().catch(() => {
|
|
7265
|
+
});
|
|
7266
|
+
notify2();
|
|
7267
|
+
};
|
|
7268
|
+
const status2 = () => ({ content: [{ type: "text", text: lost ? STATUS_LOST : STATUS_WAITING }] });
|
|
7269
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
7270
|
+
if (!upstream) return { tools: [statusTool] };
|
|
7271
|
+
try {
|
|
7272
|
+
return await upstream.listTools();
|
|
7273
|
+
} catch {
|
|
7274
|
+
drop();
|
|
7275
|
+
return { tools: [statusTool] };
|
|
7276
|
+
}
|
|
7277
|
+
});
|
|
7278
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
7279
|
+
if (!upstream) return status2();
|
|
7280
|
+
try {
|
|
7281
|
+
return await upstream.callTool({ name: req.params.name, arguments: req.params.arguments });
|
|
7282
|
+
} catch {
|
|
7283
|
+
drop();
|
|
7284
|
+
return status2();
|
|
7285
|
+
}
|
|
7286
|
+
});
|
|
7287
|
+
const tryAttach = async () => {
|
|
7288
|
+
if (upstream || connecting) return false;
|
|
7289
|
+
const url = readAttachFile(o.attachFile);
|
|
7290
|
+
if (!url) return false;
|
|
7291
|
+
connecting = true;
|
|
7292
|
+
try {
|
|
7293
|
+
const client2 = await connect2(url);
|
|
7294
|
+
client2.onclose = () => {
|
|
7295
|
+
if (upstream === client2) drop();
|
|
7296
|
+
};
|
|
7297
|
+
upstream = client2;
|
|
7298
|
+
lost = false;
|
|
7299
|
+
notify2();
|
|
7300
|
+
return true;
|
|
7301
|
+
} catch {
|
|
7302
|
+
return false;
|
|
7303
|
+
} finally {
|
|
7304
|
+
connecting = false;
|
|
7305
|
+
}
|
|
7306
|
+
};
|
|
7307
|
+
return {
|
|
7308
|
+
server,
|
|
7309
|
+
attached: () => upstream !== void 0,
|
|
7310
|
+
tryAttach,
|
|
7311
|
+
async start(transport) {
|
|
7312
|
+
await server.connect(transport);
|
|
7313
|
+
if (existsSync20(o.attachFile)) await tryAttach();
|
|
7314
|
+
timer = setInterval(() => {
|
|
7315
|
+
void tryAttach();
|
|
7316
|
+
}, o.pollMs ?? 1e3);
|
|
7317
|
+
timer.unref?.();
|
|
7318
|
+
},
|
|
7319
|
+
async stop() {
|
|
7320
|
+
if (timer) clearInterval(timer);
|
|
7321
|
+
const gone = upstream;
|
|
7322
|
+
upstream = void 0;
|
|
7323
|
+
await gone?.close().catch(() => {
|
|
7324
|
+
});
|
|
7325
|
+
await server.close().catch(() => {
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7328
|
+
};
|
|
7329
|
+
}
|
|
7330
|
+
async function runBridge(stateDir, route2, ticket) {
|
|
7331
|
+
const bridge = createBridge({ attachFile: attachFilePath(stateDir, route2, ticket) });
|
|
7332
|
+
const closed = new Promise((resolve8) => {
|
|
7333
|
+
bridge.server.onclose = resolve8;
|
|
7334
|
+
process.stdin.on("end", resolve8);
|
|
7335
|
+
});
|
|
7336
|
+
await bridge.start(new StdioServerTransport());
|
|
7337
|
+
await closed;
|
|
7338
|
+
await bridge.stop();
|
|
7339
|
+
}
|
|
7340
|
+
|
|
7103
7341
|
// src/contract.ts
|
|
7104
7342
|
var DEFAULT_CONTRACT = {
|
|
7105
7343
|
columns: {
|
|
@@ -7329,8 +7567,8 @@ var BeforeFirstEditTracker = class {
|
|
|
7329
7567
|
};
|
|
7330
7568
|
|
|
7331
7569
|
// src/agent-log.ts
|
|
7332
|
-
import { existsSync as
|
|
7333
|
-
import { join as
|
|
7570
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
7571
|
+
import { join as join23 } from "path";
|
|
7334
7572
|
|
|
7335
7573
|
// src/pending-backoff.ts
|
|
7336
7574
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -7533,20 +7771,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
7533
7771
|
};
|
|
7534
7772
|
}
|
|
7535
7773
|
}
|
|
7536
|
-
var queueDir = (stateDir) =>
|
|
7774
|
+
var queueDir = (stateDir) => join23(stateDir, "pending-agent-logs");
|
|
7537
7775
|
function queueAgentRun(stateDir, name, q2) {
|
|
7538
|
-
|
|
7539
|
-
|
|
7776
|
+
mkdirSync15(queueDir(stateDir), { recursive: true });
|
|
7777
|
+
writeFileSync16(join23(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7540
7778
|
}
|
|
7541
7779
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7542
7780
|
const dir = queueDir(stateDir);
|
|
7543
|
-
if (!
|
|
7781
|
+
if (!existsSync21(dir)) return 0;
|
|
7544
7782
|
let sent = 0;
|
|
7545
7783
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7546
|
-
const path =
|
|
7784
|
+
const path = join23(dir, f);
|
|
7547
7785
|
let q2;
|
|
7548
7786
|
try {
|
|
7549
|
-
q2 = JSON.parse(
|
|
7787
|
+
q2 = JSON.parse(readFileSync21(path, "utf8"));
|
|
7550
7788
|
} catch {
|
|
7551
7789
|
continue;
|
|
7552
7790
|
}
|
|
@@ -7565,17 +7803,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7565
7803
|
attempts: 0,
|
|
7566
7804
|
nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7567
7805
|
});
|
|
7568
|
-
|
|
7806
|
+
rmSync12(path, { force: true });
|
|
7569
7807
|
sent += 1;
|
|
7570
7808
|
log.emit(`sent a queued Agent Log report (${f})`);
|
|
7571
7809
|
} catch (e) {
|
|
7572
7810
|
if (!(e instanceof AgentLogUnreachableError)) {
|
|
7573
|
-
|
|
7811
|
+
rmSync12(path, { force: true });
|
|
7574
7812
|
log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
|
|
7575
7813
|
continue;
|
|
7576
7814
|
}
|
|
7577
7815
|
const attempts = q2.attempts + 1;
|
|
7578
|
-
|
|
7816
|
+
writeFileSync16(path, JSON.stringify({
|
|
7579
7817
|
...q2,
|
|
7580
7818
|
attempts,
|
|
7581
7819
|
warned: true,
|
|
@@ -7586,20 +7824,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7586
7824
|
}
|
|
7587
7825
|
return sent;
|
|
7588
7826
|
}
|
|
7589
|
-
var countersDir = (stateDir) =>
|
|
7827
|
+
var countersDir = (stateDir) => join23(stateDir, "pending-ticket-counters");
|
|
7590
7828
|
function queueTicketCounters(stateDir, name, q2) {
|
|
7591
|
-
|
|
7592
|
-
|
|
7829
|
+
mkdirSync15(countersDir(stateDir), { recursive: true });
|
|
7830
|
+
writeFileSync16(join23(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7593
7831
|
}
|
|
7594
7832
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7595
7833
|
const dir = countersDir(stateDir);
|
|
7596
|
-
if (!
|
|
7834
|
+
if (!existsSync21(dir)) return 0;
|
|
7597
7835
|
let sent = 0;
|
|
7598
7836
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7599
|
-
const path =
|
|
7837
|
+
const path = join23(dir, f);
|
|
7600
7838
|
let q2;
|
|
7601
7839
|
try {
|
|
7602
|
-
q2 = JSON.parse(
|
|
7840
|
+
q2 = JSON.parse(readFileSync21(path, "utf8"));
|
|
7603
7841
|
} catch {
|
|
7604
7842
|
continue;
|
|
7605
7843
|
}
|
|
@@ -7608,17 +7846,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
7608
7846
|
const apiKey = await keyFor(q2.role);
|
|
7609
7847
|
if (!apiKey) continue;
|
|
7610
7848
|
await writeCounters({ ...q2.target, apiKey }, q2.write);
|
|
7611
|
-
|
|
7849
|
+
rmSync12(path, { force: true });
|
|
7612
7850
|
sent += 1;
|
|
7613
7851
|
log.emit(`added a queued run's spend to ${q2.write.ticketReference ?? q2.write.recordId} (${f})`);
|
|
7614
7852
|
} catch (e) {
|
|
7615
7853
|
if (!isRetryable(e)) {
|
|
7616
|
-
|
|
7854
|
+
rmSync12(path, { force: true });
|
|
7617
7855
|
log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
|
|
7618
7856
|
continue;
|
|
7619
7857
|
}
|
|
7620
7858
|
const attempts = q2.attempts + 1;
|
|
7621
|
-
|
|
7859
|
+
writeFileSync16(path, JSON.stringify({
|
|
7622
7860
|
...q2,
|
|
7623
7861
|
attempts,
|
|
7624
7862
|
warned: true,
|
|
@@ -7821,8 +8059,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7821
8059
|
};
|
|
7822
8060
|
|
|
7823
8061
|
// src/pending-handoff.ts
|
|
7824
|
-
import { existsSync as
|
|
7825
|
-
import { join as
|
|
8062
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync22, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
8063
|
+
import { join as join25 } from "path";
|
|
7826
8064
|
|
|
7827
8065
|
// src/ticket-branch.ts
|
|
7828
8066
|
import { basename as basename7 } from "path";
|
|
@@ -8988,8 +9226,8 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8988
9226
|
}
|
|
8989
9227
|
|
|
8990
9228
|
// src/run-worktree.ts
|
|
8991
|
-
import { existsSync as
|
|
8992
|
-
import { dirname as
|
|
9229
|
+
import { existsSync as existsSync22 } from "fs";
|
|
9230
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
8993
9231
|
var describeRunWorktreePlan = (p) => {
|
|
8994
9232
|
switch (p.action) {
|
|
8995
9233
|
case "cut-remote":
|
|
@@ -9030,7 +9268,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9030
9268
|
const existing = worktreeForNumber(dir, num);
|
|
9031
9269
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9032
9270
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9033
|
-
if (existing && !existing.prunable &&
|
|
9271
|
+
if (existing && !existing.prunable && existsSync22(existing.path)) {
|
|
9034
9272
|
const branch = existing.branch;
|
|
9035
9273
|
if (status(existing.path).length > 0) {
|
|
9036
9274
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -9046,7 +9284,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9046
9284
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
9047
9285
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
9048
9286
|
}
|
|
9049
|
-
const path =
|
|
9287
|
+
const path = join24(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
|
|
9050
9288
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
9051
9289
|
return { action: "cut-remote", path, branch: located.branch };
|
|
9052
9290
|
}
|
|
@@ -9100,7 +9338,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9100
9338
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
9101
9339
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
9102
9340
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9103
|
-
if (wt &&
|
|
9341
|
+
if (wt && existsSync22(wt.path)) {
|
|
9104
9342
|
if (status(wt.path).length > 0) return false;
|
|
9105
9343
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
9106
9344
|
}
|
|
@@ -9108,7 +9346,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9108
9346
|
}
|
|
9109
9347
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
9110
9348
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9111
|
-
if (!wt || !
|
|
9349
|
+
if (!wt || !existsSync22(wt.path)) return { kind: "none" };
|
|
9112
9350
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
9113
9351
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
9114
9352
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -9120,7 +9358,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
9120
9358
|
if (pids.length) await killGently(pids);
|
|
9121
9359
|
}
|
|
9122
9360
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
9123
|
-
return
|
|
9361
|
+
return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
9124
9362
|
}
|
|
9125
9363
|
|
|
9126
9364
|
// src/handoff-guard.ts
|
|
@@ -9503,20 +9741,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
9503
9741
|
|
|
9504
9742
|
// src/pending-handoff.ts
|
|
9505
9743
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9506
|
-
var pendingDirFor = (stateDir) =>
|
|
9507
|
-
var stateDirOfStream = (streamPath) =>
|
|
9744
|
+
var pendingDirFor = (stateDir) => join25(stateDir, "pending-handoffs");
|
|
9745
|
+
var stateDirOfStream = (streamPath) => join25(streamPath, "..", "..");
|
|
9508
9746
|
function writePendingHandoff(stateDir, rec) {
|
|
9509
9747
|
const dir = pendingDirFor(stateDir);
|
|
9510
|
-
|
|
9511
|
-
|
|
9748
|
+
mkdirSync16(dir, { recursive: true });
|
|
9749
|
+
writeFileSync17(join25(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
9512
9750
|
}
|
|
9513
9751
|
function listPendingHandoffs(stateDir, route2) {
|
|
9514
9752
|
const dir = pendingDirFor(stateDir);
|
|
9515
|
-
if (!
|
|
9753
|
+
if (!existsSync23(dir)) return [];
|
|
9516
9754
|
const out = [];
|
|
9517
9755
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9518
9756
|
try {
|
|
9519
|
-
const rec = JSON.parse(
|
|
9757
|
+
const rec = JSON.parse(readFileSync22(join25(dir, f), "utf8"));
|
|
9520
9758
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
9521
9759
|
} catch {
|
|
9522
9760
|
}
|
|
@@ -9524,7 +9762,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9524
9762
|
return out;
|
|
9525
9763
|
}
|
|
9526
9764
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9527
|
-
|
|
9765
|
+
rmSync13(join25(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9528
9766
|
}
|
|
9529
9767
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9530
9768
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9634,8 +9872,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9634
9872
|
}
|
|
9635
9873
|
|
|
9636
9874
|
// src/skills.ts
|
|
9637
|
-
import { readFileSync as
|
|
9638
|
-
import { join as
|
|
9875
|
+
import { readFileSync as readFileSync24, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
|
|
9876
|
+
import { join as join27 } from "path";
|
|
9639
9877
|
import { parse as parseYaml } from "yaml";
|
|
9640
9878
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9641
9879
|
|
|
@@ -9651,8 +9889,8 @@ ${additions}`;
|
|
|
9651
9889
|
}
|
|
9652
9890
|
|
|
9653
9891
|
// src/agents.ts
|
|
9654
|
-
import { readFileSync as
|
|
9655
|
-
import { join as
|
|
9892
|
+
import { readFileSync as readFileSync23, existsSync as existsSync24 } from "fs";
|
|
9893
|
+
import { join as join26 } from "path";
|
|
9656
9894
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9657
9895
|
var PERSONA_NAME = {
|
|
9658
9896
|
dev: "Developer",
|
|
@@ -9666,12 +9904,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9666
9904
|
var AgentsSyncError = class extends Error {
|
|
9667
9905
|
};
|
|
9668
9906
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9669
|
-
const briefPath =
|
|
9670
|
-
if (!
|
|
9671
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9672
|
-
const commonPath =
|
|
9673
|
-
if (!
|
|
9674
|
-
return `${
|
|
9907
|
+
const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
|
|
9908
|
+
if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9909
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync23(briefPath, "utf8");
|
|
9910
|
+
const commonPath = join26(promptsDir, "personas", "common.md");
|
|
9911
|
+
if (!existsSync24(commonPath)) return readFileSync23(briefPath, "utf8");
|
|
9912
|
+
return `${readFileSync23(commonPath, "utf8")}${readFileSync23(briefPath, "utf8")}`;
|
|
9675
9913
|
}
|
|
9676
9914
|
async function resolveAgentId(route2, role, opts) {
|
|
9677
9915
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -10118,7 +10356,7 @@ function describeCrewLink(o) {
|
|
|
10118
10356
|
var SkillSyncError = class extends Error {
|
|
10119
10357
|
};
|
|
10120
10358
|
function parseSkillFile(path) {
|
|
10121
|
-
const text2 =
|
|
10359
|
+
const text2 = readFileSync24(path, "utf8");
|
|
10122
10360
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
10123
10361
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
10124
10362
|
const frontmatter = match[1];
|
|
@@ -10131,9 +10369,9 @@ function parseSkillFile(path) {
|
|
|
10131
10369
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
10132
10370
|
}
|
|
10133
10371
|
function listLocalSkills(promptsDir) {
|
|
10134
|
-
const dir =
|
|
10135
|
-
if (!
|
|
10136
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
10372
|
+
const dir = join27(promptsDir, "skills");
|
|
10373
|
+
if (!existsSync25(dir)) return [];
|
|
10374
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
|
|
10137
10375
|
}
|
|
10138
10376
|
async function syncSkills(route2, opts) {
|
|
10139
10377
|
if (!route2.resolved) {
|
|
@@ -10318,9 +10556,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
10318
10556
|
|
|
10319
10557
|
// src/runner.ts
|
|
10320
10558
|
import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
|
|
10321
|
-
import { mkdtempSync, mkdirSync as
|
|
10559
|
+
import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
10322
10560
|
import { tmpdir as tmpdir2 } from "os";
|
|
10323
|
-
import { basename as basename8, dirname as
|
|
10561
|
+
import { basename as basename8, dirname as dirname12, join as join28 } from "path";
|
|
10324
10562
|
import { promisify } from "util";
|
|
10325
10563
|
|
|
10326
10564
|
// src/run-limit.ts
|
|
@@ -10449,8 +10687,8 @@ var SandboxRunner = class {
|
|
|
10449
10687
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
10450
10688
|
}
|
|
10451
10689
|
policyFile = policyPathFor(plan.streamPath);
|
|
10452
|
-
|
|
10453
|
-
|
|
10690
|
+
mkdirSync17(dirname12(policyFile), { recursive: true });
|
|
10691
|
+
writeFileSync18(policyFile, generated.yaml, { mode: 384 });
|
|
10454
10692
|
}
|
|
10455
10693
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
10456
10694
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -10493,13 +10731,13 @@ var SandboxRunner = class {
|
|
|
10493
10731
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
10494
10732
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
10495
10733
|
if (viaUpload) {
|
|
10496
|
-
const tmp = mkdtempSync(
|
|
10734
|
+
const tmp = mkdtempSync(join28(tmpdir2(), "crew-prompt-"));
|
|
10497
10735
|
try {
|
|
10498
|
-
const file =
|
|
10499
|
-
|
|
10736
|
+
const file = join28(tmp, "prompt.txt");
|
|
10737
|
+
writeFileSync18(file, plan.prompt, { mode: 384 });
|
|
10500
10738
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
10501
10739
|
} finally {
|
|
10502
|
-
|
|
10740
|
+
rmSync14(tmp, { recursive: true, force: true });
|
|
10503
10741
|
}
|
|
10504
10742
|
}
|
|
10505
10743
|
const withStubs = `PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `;
|
|
@@ -10649,7 +10887,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10649
10887
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10650
10888
|
let raw;
|
|
10651
10889
|
try {
|
|
10652
|
-
raw =
|
|
10890
|
+
raw = readFileSync25(streamPath, "utf8");
|
|
10653
10891
|
} catch {
|
|
10654
10892
|
return false;
|
|
10655
10893
|
}
|
|
@@ -10672,26 +10910,49 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10672
10910
|
}
|
|
10673
10911
|
return false;
|
|
10674
10912
|
}
|
|
10675
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10913
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join29(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10914
|
+
function codeBridgeFor(o, harness) {
|
|
10915
|
+
if (o.ship.runner === "sandbox" || !o.ticket) return void 0;
|
|
10916
|
+
const number = /(\d+)$/.exec(o.ticket)?.[1];
|
|
10917
|
+
if (!number) return void 0;
|
|
10918
|
+
let intel;
|
|
10919
|
+
try {
|
|
10920
|
+
const repos = reposOf(o.route);
|
|
10921
|
+
const target = repos.find((r) => r.name === o.repo) ?? (repos.length === 1 ? repos[0] : void 0);
|
|
10922
|
+
intel = target ? loadRepoConfig(target.dir)?.hooks?.intel : void 0;
|
|
10923
|
+
} catch {
|
|
10924
|
+
}
|
|
10925
|
+
if (!wantsCodeBridge(intel)) return void 0;
|
|
10926
|
+
const args = [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
|
|
10927
|
+
const bridge = { command: process.execPath, args };
|
|
10928
|
+
if (harness === "gemini") {
|
|
10929
|
+
const file = join29(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
|
|
10930
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
10931
|
+
writeFileSync19(file, `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
|
|
10932
|
+
`);
|
|
10933
|
+
bridge.settingsFile = file;
|
|
10934
|
+
}
|
|
10935
|
+
return bridge;
|
|
10936
|
+
}
|
|
10676
10937
|
function planAgentRun(o) {
|
|
10677
|
-
const briefPath =
|
|
10678
|
-
if (!
|
|
10938
|
+
const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10939
|
+
if (!existsSync26(briefPath)) {
|
|
10679
10940
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10680
10941
|
}
|
|
10681
|
-
const commonPath =
|
|
10682
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10942
|
+
const commonPath = join29(o.route.promptsDir, "personas", "common.md");
|
|
10943
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
|
|
10683
10944
|
const now = o.now ?? (() => Date.now());
|
|
10684
10945
|
const routeLabel = routeSlug(o.route.route);
|
|
10685
10946
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10686
|
-
const digestPath2 = ownDigest &&
|
|
10947
|
+
const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10687
10948
|
let digest;
|
|
10688
10949
|
let ageSeconds;
|
|
10689
|
-
if (
|
|
10950
|
+
if (existsSync26(digestPath2)) {
|
|
10690
10951
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10691
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10952
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync25(digestPath2, "utf8");
|
|
10692
10953
|
}
|
|
10693
|
-
const common = usesCommon ?
|
|
10694
|
-
const brief =
|
|
10954
|
+
const common = usesCommon ? readFileSync25(commonPath, "utf8") : "";
|
|
10955
|
+
const brief = readFileSync25(briefPath, "utf8");
|
|
10695
10956
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10696
10957
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10697
10958
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10714,7 +10975,7 @@ function planAgentRun(o) {
|
|
|
10714
10975
|
digest
|
|
10715
10976
|
});
|
|
10716
10977
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10717
|
-
const streamsDir =
|
|
10978
|
+
const streamsDir = join29(o.stateDir, "streams");
|
|
10718
10979
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10719
10980
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10720
10981
|
const { model } = seat;
|
|
@@ -10729,7 +10990,9 @@ function planAgentRun(o) {
|
|
|
10729
10990
|
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
|
|
10730
10991
|
}
|
|
10731
10992
|
const adapter = adapterFor(toolchain.harness);
|
|
10993
|
+
const code = codeBridgeFor(o, adapter.harness);
|
|
10732
10994
|
const invocation = adapter.invocation(toolchain, {
|
|
10995
|
+
code,
|
|
10733
10996
|
role: o.role,
|
|
10734
10997
|
model,
|
|
10735
10998
|
pluginDirs: adapter.harness === "claude" ? lspPluginDirsFor(o.route, o.repo, o.stateDir) : void 0,
|
|
@@ -10788,7 +11051,7 @@ function planAgentRun(o) {
|
|
|
10788
11051
|
unsetEnv: adapter.unsetEnv,
|
|
10789
11052
|
setEnv: {
|
|
10790
11053
|
...o.apiKey ? { [API_KEY_VAR]: o.apiKey } : {},
|
|
10791
|
-
...adapter.runEnv(toolchain),
|
|
11054
|
+
...adapter.runEnv(toolchain, { code }),
|
|
10792
11055
|
// The operator's global SessionStart hook (pair-context-hook.sh) injects
|
|
10793
11056
|
// the interactive Pair persona's prompt for any session it can't tell
|
|
10794
11057
|
// apart from a person's own terminal. Every headless lane run this
|
|
@@ -10805,8 +11068,8 @@ function planAgentRun(o) {
|
|
|
10805
11068
|
digestAttached: digest !== void 0,
|
|
10806
11069
|
digestAgeSeconds: ageSeconds,
|
|
10807
11070
|
model,
|
|
10808
|
-
streamPath:
|
|
10809
|
-
eventsPath:
|
|
11071
|
+
streamPath: join29(streamsDir, `${base}.jsonl`),
|
|
11072
|
+
eventsPath: join29(streamsDir, `${base}.events.jsonl`),
|
|
10810
11073
|
ticket: o.ticket,
|
|
10811
11074
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10812
11075
|
repo: o.repo ?? null,
|
|
@@ -10837,7 +11100,7 @@ function describePlan(p) {
|
|
|
10837
11100
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10838
11101
|
function readRunSidecar(streamPath) {
|
|
10839
11102
|
try {
|
|
10840
|
-
const sc = JSON.parse(
|
|
11103
|
+
const sc = JSON.parse(readFileSync25(pidPathFor(streamPath), "utf8"));
|
|
10841
11104
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10842
11105
|
} catch {
|
|
10843
11106
|
return null;
|
|
@@ -10884,7 +11147,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10884
11147
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10885
11148
|
function openSink(path) {
|
|
10886
11149
|
try {
|
|
10887
|
-
|
|
11150
|
+
mkdirSync18(dirname13(path), { recursive: true });
|
|
10888
11151
|
return createWriteStream(path, { flags: "a" });
|
|
10889
11152
|
} catch {
|
|
10890
11153
|
return void 0;
|
|
@@ -11070,7 +11333,7 @@ var RunObserver = class {
|
|
|
11070
11333
|
};
|
|
11071
11334
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
11072
11335
|
try {
|
|
11073
|
-
|
|
11336
|
+
writeFileSync19(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
11074
11337
|
route: id.route,
|
|
11075
11338
|
role: id.role,
|
|
11076
11339
|
hint: id.ticket,
|
|
@@ -11236,7 +11499,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11236
11499
|
const id = identityOf(plan, adapter.harness);
|
|
11237
11500
|
let streamFd;
|
|
11238
11501
|
try {
|
|
11239
|
-
|
|
11502
|
+
mkdirSync18(dirname13(plan.streamPath), { recursive: true });
|
|
11240
11503
|
streamFd = openSync4(plan.streamPath, "a");
|
|
11241
11504
|
} catch (e) {
|
|
11242
11505
|
void endSink(eventsSink);
|
|
@@ -11294,7 +11557,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11294
11557
|
promptSha: plan.promptSha
|
|
11295
11558
|
};
|
|
11296
11559
|
try {
|
|
11297
|
-
|
|
11560
|
+
writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
11298
11561
|
} catch {
|
|
11299
11562
|
}
|
|
11300
11563
|
}
|
|
@@ -11408,21 +11671,21 @@ var pidAlive = (pid) => {
|
|
|
11408
11671
|
};
|
|
11409
11672
|
function runFinished(streamPath) {
|
|
11410
11673
|
const path = `${streamPath}.meta.json`;
|
|
11411
|
-
if (!
|
|
11674
|
+
if (!existsSync27(path)) return false;
|
|
11412
11675
|
try {
|
|
11413
|
-
return JSON.parse(
|
|
11676
|
+
return JSON.parse(readFileSync26(path, "utf8")).running !== true;
|
|
11414
11677
|
} catch {
|
|
11415
11678
|
return true;
|
|
11416
11679
|
}
|
|
11417
11680
|
}
|
|
11418
11681
|
function orphanedStreams(o) {
|
|
11419
|
-
const dir =
|
|
11420
|
-
if (!
|
|
11682
|
+
const dir = join30(o.stateDir, "streams");
|
|
11683
|
+
if (!existsSync27(dir)) return [];
|
|
11421
11684
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
11422
11685
|
const out = [];
|
|
11423
11686
|
for (const name of readdirSync9(dir)) {
|
|
11424
11687
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11425
|
-
const streamPath =
|
|
11688
|
+
const streamPath = join30(dir, name);
|
|
11426
11689
|
if (runFinished(streamPath)) continue;
|
|
11427
11690
|
const sidecar = readRunSidecar(streamPath);
|
|
11428
11691
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -11475,15 +11738,15 @@ async function reconcileRuns(o) {
|
|
|
11475
11738
|
}
|
|
11476
11739
|
|
|
11477
11740
|
// src/live-runs.ts
|
|
11478
|
-
import { existsSync as
|
|
11479
|
-
import { join as
|
|
11741
|
+
import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11742
|
+
import { join as join31 } from "path";
|
|
11480
11743
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
11481
|
-
const dir =
|
|
11482
|
-
if (!
|
|
11744
|
+
const dir = join31(stateDir, "streams");
|
|
11745
|
+
if (!existsSync28(dir)) return [];
|
|
11483
11746
|
const out = [];
|
|
11484
11747
|
for (const name of readdirSync10(dir)) {
|
|
11485
11748
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11486
|
-
const streamPath =
|
|
11749
|
+
const streamPath = join31(dir, name);
|
|
11487
11750
|
if (runFinished(streamPath)) continue;
|
|
11488
11751
|
const sidecar = readRunSidecar(streamPath);
|
|
11489
11752
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -11493,12 +11756,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
11493
11756
|
}
|
|
11494
11757
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
11495
11758
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
11496
|
-
const dir =
|
|
11759
|
+
const dir = join31(stateDir, "streams");
|
|
11497
11760
|
let newest = 0;
|
|
11498
|
-
if (
|
|
11761
|
+
if (existsSync28(dir)) {
|
|
11499
11762
|
for (const name of readdirSync10(dir)) {
|
|
11500
11763
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11501
|
-
const streamPath =
|
|
11764
|
+
const streamPath = join31(dir, name);
|
|
11502
11765
|
if (!runFinished(streamPath)) continue;
|
|
11503
11766
|
try {
|
|
11504
11767
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -11869,7 +12132,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11869
12132
|
if (host === "windows") return [];
|
|
11870
12133
|
const ctx = installContext();
|
|
11871
12134
|
const systemd = detectSystemd();
|
|
11872
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
12135
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
|
|
11873
12136
|
};
|
|
11874
12137
|
return {
|
|
11875
12138
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11930,7 +12193,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11930
12193
|
},
|
|
11931
12194
|
remaining() {
|
|
11932
12195
|
const left = [];
|
|
11933
|
-
if (
|
|
12196
|
+
if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11934
12197
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11935
12198
|
try {
|
|
11936
12199
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -12538,19 +12801,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
12538
12801
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12539
12802
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12540
12803
|
}
|
|
12541
|
-
var sandboxStatePath = (stateDir) =>
|
|
12804
|
+
var sandboxStatePath = (stateDir) => join32(stateDir, "sandbox-images.json");
|
|
12542
12805
|
function readSandboxState(stateDir) {
|
|
12543
12806
|
try {
|
|
12544
|
-
const v = JSON.parse(
|
|
12807
|
+
const v = JSON.parse(readFileSync27(sandboxStatePath(stateDir), "utf8"));
|
|
12545
12808
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12546
12809
|
} catch {
|
|
12547
12810
|
return {};
|
|
12548
12811
|
}
|
|
12549
12812
|
}
|
|
12550
12813
|
function writeSandboxState(stateDir, state) {
|
|
12551
|
-
|
|
12814
|
+
mkdirSync19(stateDir, { recursive: true });
|
|
12552
12815
|
const file = sandboxStatePath(stateDir);
|
|
12553
|
-
|
|
12816
|
+
writeFileSync20(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
12554
12817
|
`);
|
|
12555
12818
|
renameSync3(`${file}.tmp`, file);
|
|
12556
12819
|
}
|
|
@@ -12570,7 +12833,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
|
|
|
12570
12833
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
12571
12834
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
12572
12835
|
const done = (code, extra = "") => {
|
|
12573
|
-
|
|
12836
|
+
writeFileSync20(logPath, `$ ${engine} ${args.join(" ")}
|
|
12574
12837
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
12575
12838
|
resolve8(code);
|
|
12576
12839
|
};
|
|
@@ -12592,7 +12855,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12592
12855
|
} catch {
|
|
12593
12856
|
}
|
|
12594
12857
|
};
|
|
12595
|
-
var sandboxLogDir = (stateDir) =>
|
|
12858
|
+
var sandboxLogDir = (stateDir) => join32(stateDir, "sandbox-logs");
|
|
12596
12859
|
async function buildSandboxImage(plan, deps) {
|
|
12597
12860
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12598
12861
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12601,20 +12864,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12601
12864
|
return { kind: "reused", tag: plan.tag };
|
|
12602
12865
|
}
|
|
12603
12866
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12604
|
-
|
|
12605
|
-
const logPath =
|
|
12867
|
+
mkdirSync19(logDir, { recursive: true });
|
|
12868
|
+
const logPath = join32(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12606
12869
|
const builtLayers = [];
|
|
12607
12870
|
for (const layer of plan.layers) {
|
|
12608
12871
|
if (deps.hasImage(layer.tag)) continue;
|
|
12609
|
-
const ctx =
|
|
12610
|
-
|
|
12872
|
+
const ctx = join32(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12873
|
+
mkdirSync19(ctx, { recursive: true });
|
|
12611
12874
|
try {
|
|
12612
|
-
|
|
12875
|
+
writeFileSync20(join32(ctx, "Dockerfile"), layer.dockerfile);
|
|
12613
12876
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12614
|
-
|
|
12615
|
-
|
|
12877
|
+
mkdirSync19(dirname14(join32(ctx, name)), { recursive: true });
|
|
12878
|
+
writeFileSync20(join32(ctx, name), content);
|
|
12616
12879
|
}
|
|
12617
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12880
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join32(ctx, "Dockerfile"), ctx], logPath);
|
|
12618
12881
|
if (code !== 0) {
|
|
12619
12882
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12620
12883
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12622,7 +12885,7 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12622
12885
|
}
|
|
12623
12886
|
builtLayers.push(layer.name);
|
|
12624
12887
|
} finally {
|
|
12625
|
-
|
|
12888
|
+
rmSync15(ctx, { recursive: true, force: true });
|
|
12626
12889
|
}
|
|
12627
12890
|
}
|
|
12628
12891
|
const tags = [{ tag: plan.tag, builtAt: now().toISOString(), claude: plan.claude }, ...entry.tags.filter((t) => t.tag !== plan.tag)];
|
|
@@ -12803,9 +13066,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12803
13066
|
|
|
12804
13067
|
// src/selftest.ts
|
|
12805
13068
|
import { spawn as spawn6 } from "child_process";
|
|
12806
|
-
import { existsSync as
|
|
13069
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync28, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
12807
13070
|
import { tmpdir as tmpdir4 } from "os";
|
|
12808
|
-
import { join as
|
|
13071
|
+
import { join as join33 } from "path";
|
|
12809
13072
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12810
13073
|
var KEY_VAR = "CREW_API_KEY";
|
|
12811
13074
|
var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
|
|
@@ -12966,7 +13229,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12966
13229
|
...adapter.runEnv(spec),
|
|
12967
13230
|
[KEY_VAR]: i.apiKey
|
|
12968
13231
|
};
|
|
12969
|
-
const cwd = mkdtempSync2(
|
|
13232
|
+
const cwd = mkdtempSync2(join33(tmpdir4(), "crew-selftest-"));
|
|
12970
13233
|
try {
|
|
12971
13234
|
const run3 = await deps.run({
|
|
12972
13235
|
bin: invocation.bin,
|
|
@@ -12988,7 +13251,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12988
13251
|
return finish({ ok: false, failure, detail, ...usage2 });
|
|
12989
13252
|
} finally {
|
|
12990
13253
|
try {
|
|
12991
|
-
|
|
13254
|
+
rmSync16(cwd, { recursive: true, force: true });
|
|
12992
13255
|
} catch {
|
|
12993
13256
|
}
|
|
12994
13257
|
}
|
|
@@ -12999,11 +13262,11 @@ function formatResult(r) {
|
|
|
12999
13262
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
13000
13263
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
13001
13264
|
}
|
|
13002
|
-
var fileFor2 = (stateDir) =>
|
|
13265
|
+
var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
|
|
13003
13266
|
function readSelftest(stateDir) {
|
|
13004
13267
|
try {
|
|
13005
13268
|
const p = fileFor2(stateDir);
|
|
13006
|
-
return
|
|
13269
|
+
return existsSync30(p) ? JSON.parse(readFileSync28(p, "utf8")) : {};
|
|
13007
13270
|
} catch {
|
|
13008
13271
|
return {};
|
|
13009
13272
|
}
|
|
@@ -13011,8 +13274,8 @@ function readSelftest(stateDir) {
|
|
|
13011
13274
|
function recordSelftest(stateDir, results) {
|
|
13012
13275
|
const all = readSelftest(stateDir);
|
|
13013
13276
|
for (const r of results) all[r.toolchain] = r;
|
|
13014
|
-
|
|
13015
|
-
|
|
13277
|
+
mkdirSync20(stateDir, { recursive: true });
|
|
13278
|
+
writeFileSync21(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
13016
13279
|
`);
|
|
13017
13280
|
}
|
|
13018
13281
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -13024,7 +13287,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13024
13287
|
}
|
|
13025
13288
|
|
|
13026
13289
|
// src/cli.ts
|
|
13027
|
-
import { resolve as resolve7, dirname as
|
|
13290
|
+
import { resolve as resolve7, dirname as dirname33, join as join55, basename as basename16 } from "path";
|
|
13028
13291
|
import { fileURLToPath } from "url";
|
|
13029
13292
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
13030
13293
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
@@ -13032,6 +13295,7 @@ import { parse as parseYaml2 } from "yaml";
|
|
|
13032
13295
|
|
|
13033
13296
|
// src/argv.ts
|
|
13034
13297
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
13298
|
+
"state-dir",
|
|
13035
13299
|
"agent-bin",
|
|
13036
13300
|
"area",
|
|
13037
13301
|
"base-url",
|
|
@@ -13221,14 +13485,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
13221
13485
|
}
|
|
13222
13486
|
|
|
13223
13487
|
// src/connect-resume.ts
|
|
13224
|
-
import { mkdirSync as
|
|
13225
|
-
import { dirname as
|
|
13488
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync29, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
13489
|
+
import { dirname as dirname15, join as join34 } from "path";
|
|
13226
13490
|
function lastSessionFile(stateDir) {
|
|
13227
|
-
return
|
|
13491
|
+
return join34(stateDir, "connect-session.json");
|
|
13228
13492
|
}
|
|
13229
13493
|
function readPointers(stateDir) {
|
|
13230
13494
|
try {
|
|
13231
|
-
const parsed = JSON.parse(
|
|
13495
|
+
const parsed = JSON.parse(readFileSync29(lastSessionFile(stateDir), "utf8"));
|
|
13232
13496
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
13233
13497
|
const out = {};
|
|
13234
13498
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -13242,8 +13506,8 @@ function readPointers(stateDir) {
|
|
|
13242
13506
|
}
|
|
13243
13507
|
function writePointers(stateDir, pointers) {
|
|
13244
13508
|
const file = lastSessionFile(stateDir);
|
|
13245
|
-
|
|
13246
|
-
|
|
13509
|
+
mkdirSync21(dirname15(file), { recursive: true });
|
|
13510
|
+
writeFileSync22(file, `${JSON.stringify(pointers, null, 2)}
|
|
13247
13511
|
`, { mode: 384 });
|
|
13248
13512
|
}
|
|
13249
13513
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -13260,7 +13524,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
|
|
|
13260
13524
|
const { [host]: _old, ...rest } = all;
|
|
13261
13525
|
const next = left.length ? { ...rest, [host]: left } : rest;
|
|
13262
13526
|
if (Object.keys(next).length) writePointers(stateDir, next);
|
|
13263
|
-
else
|
|
13527
|
+
else rmSync17(lastSessionFile(stateDir), { force: true });
|
|
13264
13528
|
} catch {
|
|
13265
13529
|
}
|
|
13266
13530
|
}
|
|
@@ -13300,7 +13564,7 @@ function forgetSession(stateDir, host) {
|
|
|
13300
13564
|
try {
|
|
13301
13565
|
const { [host]: _gone, ...rest } = readPointers(stateDir);
|
|
13302
13566
|
if (Object.keys(rest).length) writePointers(stateDir, rest);
|
|
13303
|
-
else
|
|
13567
|
+
else rmSync17(lastSessionFile(stateDir), { force: true });
|
|
13304
13568
|
} catch {
|
|
13305
13569
|
}
|
|
13306
13570
|
}
|
|
@@ -13328,15 +13592,15 @@ async function signOutSessions(o) {
|
|
|
13328
13592
|
}
|
|
13329
13593
|
|
|
13330
13594
|
// src/reset.ts
|
|
13331
|
-
import { existsSync as
|
|
13332
|
-
import { dirname as
|
|
13595
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync30, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
13596
|
+
import { dirname as dirname16, join as join35 } from "path";
|
|
13333
13597
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
13334
13598
|
function clonedCheckoutsFile(stateDir) {
|
|
13335
|
-
return
|
|
13599
|
+
return join35(stateDir, "cloned-checkouts.json");
|
|
13336
13600
|
}
|
|
13337
13601
|
function readClonedCheckouts(stateDir) {
|
|
13338
13602
|
try {
|
|
13339
|
-
const parsed = JSON.parse(
|
|
13603
|
+
const parsed = JSON.parse(readFileSync30(clonedCheckoutsFile(stateDir), "utf8"));
|
|
13340
13604
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
13341
13605
|
} catch {
|
|
13342
13606
|
return [];
|
|
@@ -13346,8 +13610,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
13346
13610
|
const known = readClonedCheckouts(stateDir);
|
|
13347
13611
|
if (known.includes(dir)) return;
|
|
13348
13612
|
const file = clonedCheckoutsFile(stateDir);
|
|
13349
|
-
|
|
13350
|
-
|
|
13613
|
+
mkdirSync22(dirname16(file), { recursive: true });
|
|
13614
|
+
writeFileSync23(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
13351
13615
|
`);
|
|
13352
13616
|
}
|
|
13353
13617
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -13401,8 +13665,8 @@ async function runReset(o, fx) {
|
|
|
13401
13665
|
}
|
|
13402
13666
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
13403
13667
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
13404
|
-
if (
|
|
13405
|
-
if (!o.dryRun)
|
|
13668
|
+
if (existsSync31(o.configFile)) {
|
|
13669
|
+
if (!o.dryRun) rmSync18(o.configFile, { force: true });
|
|
13406
13670
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
13407
13671
|
} else {
|
|
13408
13672
|
steps.push({ step: "config", target: o.configFile, action: "absent" });
|
|
@@ -13410,7 +13674,7 @@ async function runReset(o, fx) {
|
|
|
13410
13674
|
return steps;
|
|
13411
13675
|
}
|
|
13412
13676
|
function removeCheckout(dir, dryRun, fx) {
|
|
13413
|
-
if (!
|
|
13677
|
+
if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
13414
13678
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
13415
13679
|
if (dirty.length) {
|
|
13416
13680
|
return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
|
|
@@ -13418,21 +13682,21 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
13418
13682
|
if (linkedWorktrees.length) {
|
|
13419
13683
|
return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
|
|
13420
13684
|
}
|
|
13421
|
-
if (!dryRun)
|
|
13685
|
+
if (!dryRun) rmSync18(dir, { recursive: true, force: true });
|
|
13422
13686
|
return { step: "checkout", target: dir, action: mark(dryRun) };
|
|
13423
13687
|
}
|
|
13424
13688
|
function removeState(stateDir, dryRun) {
|
|
13425
13689
|
const out = [];
|
|
13426
|
-
const targets = [
|
|
13690
|
+
const targets = [join35(stateDir, "resolved"), join35(stateDir, "keys"), clonedCheckoutsFile(stateDir), join35(stateDir, "connect-session.json"), join35(stateDir, "reload-requested"), join35(stateDir, "daemon.started")];
|
|
13427
13691
|
try {
|
|
13428
13692
|
for (const name of readdirSync12(stateDir)) {
|
|
13429
|
-
if (name.startsWith(".") && statSync10(
|
|
13693
|
+
if (name.startsWith(".") && statSync10(join35(stateDir, name)).isFile()) targets.push(join35(stateDir, name));
|
|
13430
13694
|
}
|
|
13431
13695
|
} catch {
|
|
13432
13696
|
}
|
|
13433
13697
|
for (const t of targets) {
|
|
13434
|
-
if (!
|
|
13435
|
-
if (!dryRun)
|
|
13698
|
+
if (!existsSync31(t)) continue;
|
|
13699
|
+
if (!dryRun) rmSync18(t, { recursive: true, force: true });
|
|
13436
13700
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
13437
13701
|
}
|
|
13438
13702
|
if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
|
|
@@ -13446,7 +13710,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
13446
13710
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
13447
13711
|
}
|
|
13448
13712
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
13449
|
-
if (!dryRun) for (const n of names)
|
|
13713
|
+
if (!dryRun) for (const n of names) rmSync18(join35(stateDir, n), { recursive: true, force: true });
|
|
13450
13714
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
13451
13715
|
}
|
|
13452
13716
|
function describeResetStep(s) {
|
|
@@ -13527,9 +13791,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
13527
13791
|
}
|
|
13528
13792
|
|
|
13529
13793
|
// src/pool-member.ts
|
|
13530
|
-
import { mkdirSync as
|
|
13794
|
+
import { mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13531
13795
|
import { homedir as homedir8 } from "os";
|
|
13532
|
-
import { dirname as
|
|
13796
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
13533
13797
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
13534
13798
|
function backoffMs(attempt) {
|
|
13535
13799
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13760,22 +14024,22 @@ async function runPoolMember(deps) {
|
|
|
13760
14024
|
}
|
|
13761
14025
|
}
|
|
13762
14026
|
function defaultMemberDir(env = process.env) {
|
|
13763
|
-
return env.CREW_POOL_DIR ??
|
|
14027
|
+
return env.CREW_POOL_DIR ?? join36(env.XDG_STATE_HOME ?? join36(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13764
14028
|
}
|
|
13765
14029
|
function fileMemberStore(dir) {
|
|
13766
|
-
const file =
|
|
14030
|
+
const file = join36(dir, "member.json");
|
|
13767
14031
|
return {
|
|
13768
14032
|
read() {
|
|
13769
14033
|
try {
|
|
13770
|
-
return JSON.parse(
|
|
14034
|
+
return JSON.parse(readFileSync31(file, "utf8"));
|
|
13771
14035
|
} catch {
|
|
13772
14036
|
return {};
|
|
13773
14037
|
}
|
|
13774
14038
|
},
|
|
13775
14039
|
write(f) {
|
|
13776
|
-
|
|
14040
|
+
mkdirSync23(dirname17(file), { recursive: true, mode: 448 });
|
|
13777
14041
|
const tmp = `${file}.tmp`;
|
|
13778
|
-
|
|
14042
|
+
writeFileSync24(tmp, `${JSON.stringify(f)}
|
|
13779
14043
|
`, { mode: 384 });
|
|
13780
14044
|
renameSync4(tmp, file);
|
|
13781
14045
|
}
|
|
@@ -13827,16 +14091,16 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13827
14091
|
|
|
13828
14092
|
// src/pool-member-host.ts
|
|
13829
14093
|
import "child_process";
|
|
13830
|
-
import { existsSync as
|
|
14094
|
+
import { existsSync as existsSync35 } from "fs";
|
|
13831
14095
|
import { hostname as hostname3 } from "os";
|
|
13832
14096
|
|
|
13833
14097
|
// src/machine-id.ts
|
|
13834
14098
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
13835
14099
|
import { createHash as createHash7 } from "crypto";
|
|
13836
|
-
import { existsSync as
|
|
13837
|
-
import { dirname as
|
|
14100
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "fs";
|
|
14101
|
+
import { dirname as dirname18, join as join37 } from "path";
|
|
13838
14102
|
function machineIdPathFor(stateDir) {
|
|
13839
|
-
return
|
|
14103
|
+
return join37(stateDir, "machine-id");
|
|
13840
14104
|
}
|
|
13841
14105
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13842
14106
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -13849,8 +14113,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13849
14113
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13850
14114
|
}
|
|
13851
14115
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13852
|
-
if (
|
|
13853
|
-
const id =
|
|
14116
|
+
if (existsSync32(p)) {
|
|
14117
|
+
const id = readFileSync32(p, "utf8").trim();
|
|
13854
14118
|
if (id) return id;
|
|
13855
14119
|
}
|
|
13856
14120
|
}
|
|
@@ -13861,15 +14125,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13861
14125
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13862
14126
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13863
14127
|
const path = machineIdPathFor(stateDir);
|
|
13864
|
-
if (
|
|
13865
|
-
const cached =
|
|
14128
|
+
if (existsSync32(path)) {
|
|
14129
|
+
const cached = readFileSync32(path, "utf8").trim();
|
|
13866
14130
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13867
14131
|
}
|
|
13868
14132
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13869
14133
|
if (!raw) return void 0;
|
|
13870
14134
|
const hashed = sha256(raw);
|
|
13871
|
-
|
|
13872
|
-
|
|
14135
|
+
mkdirSync24(dirname18(path), { recursive: true });
|
|
14136
|
+
writeFileSync25(path, `${hashed}
|
|
13873
14137
|
`, { mode: 384 });
|
|
13874
14138
|
return hashed;
|
|
13875
14139
|
}
|
|
@@ -13882,7 +14146,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13882
14146
|
|
|
13883
14147
|
// src/provision.ts
|
|
13884
14148
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
13885
|
-
import { existsSync as
|
|
14149
|
+
import { existsSync as existsSync33, readFileSync as readFileSync33, writeFileSync as writeFileSync26 } from "fs";
|
|
13886
14150
|
import { hostname as hostname2 } from "os";
|
|
13887
14151
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13888
14152
|
|
|
@@ -13891,7 +14155,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13891
14155
|
|
|
13892
14156
|
// src/repos-cmd.ts
|
|
13893
14157
|
import { homedir as homedir9 } from "os";
|
|
13894
|
-
import { basename as basename10, join as
|
|
14158
|
+
import { basename as basename10, join as join38, resolve as resolve4 } from "path";
|
|
13895
14159
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13896
14160
|
var ReposError = class extends Error {
|
|
13897
14161
|
};
|
|
@@ -14169,7 +14433,7 @@ function planRepoClone(i) {
|
|
|
14169
14433
|
}
|
|
14170
14434
|
const ship = doc.get("ship", true);
|
|
14171
14435
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14172
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14436
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join38(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14173
14437
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14174
14438
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
14175
14439
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -14476,7 +14740,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14476
14740
|
};
|
|
14477
14741
|
const configFile = () => {
|
|
14478
14742
|
const f = findConfigFile(crewHome);
|
|
14479
|
-
return
|
|
14743
|
+
return existsSync33(f) ? f : null;
|
|
14480
14744
|
};
|
|
14481
14745
|
return {
|
|
14482
14746
|
crew: spawnCrew,
|
|
@@ -14484,13 +14748,13 @@ function realProvisionDeps(crewHome) {
|
|
|
14484
14748
|
enableRoute(route2) {
|
|
14485
14749
|
const file = configFile();
|
|
14486
14750
|
if (!file) return false;
|
|
14487
|
-
const text2 =
|
|
14751
|
+
const text2 = readFileSync33(file, "utf8");
|
|
14488
14752
|
const doc = parseDocument3(text2);
|
|
14489
14753
|
const routes = doc.get("routes");
|
|
14490
14754
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
14491
14755
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
14492
14756
|
if (entry.get("enabled") === true) return false;
|
|
14493
|
-
|
|
14757
|
+
writeFileSync26(file, planEnable(text2, route2));
|
|
14494
14758
|
return true;
|
|
14495
14759
|
},
|
|
14496
14760
|
installState() {
|
|
@@ -14510,14 +14774,14 @@ function realProvisionDeps(crewHome) {
|
|
|
14510
14774
|
continue;
|
|
14511
14775
|
}
|
|
14512
14776
|
for (const p of plan.unitPaths) {
|
|
14513
|
-
if (!
|
|
14777
|
+
if (!existsSync33(p) || readFileSync33(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
14514
14778
|
}
|
|
14515
14779
|
}
|
|
14516
14780
|
const label = labelFor(crewHome, "run");
|
|
14517
14781
|
let running = false;
|
|
14518
14782
|
let lingering = true;
|
|
14519
14783
|
if (mechanism === "systemd") {
|
|
14520
|
-
const system = ctx.root ||
|
|
14784
|
+
const system = ctx.root || existsSync33(systemUnitPathFor(label));
|
|
14521
14785
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
14522
14786
|
running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
14523
14787
|
if (!system) {
|
|
@@ -14534,18 +14798,18 @@ function realProvisionDeps(crewHome) {
|
|
|
14534
14798
|
setShipSetting(key, value2) {
|
|
14535
14799
|
const file = configFile();
|
|
14536
14800
|
if (!file) return false;
|
|
14537
|
-
const doc = parseDocument3(
|
|
14801
|
+
const doc = parseDocument3(readFileSync33(file, "utf8"));
|
|
14538
14802
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
14539
14803
|
doc.setIn(["ship", key], value2);
|
|
14540
|
-
|
|
14804
|
+
writeFileSync26(file, doc.toString());
|
|
14541
14805
|
return true;
|
|
14542
14806
|
},
|
|
14543
14807
|
applyManagedDefaults() {
|
|
14544
14808
|
const file = configFile();
|
|
14545
14809
|
if (!file) return [];
|
|
14546
|
-
const doc = parseDocument3(
|
|
14810
|
+
const doc = parseDocument3(readFileSync33(file, "utf8"));
|
|
14547
14811
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14548
|
-
if (wrote.length)
|
|
14812
|
+
if (wrote.length) writeFileSync26(file, doc.toString());
|
|
14549
14813
|
return wrote;
|
|
14550
14814
|
},
|
|
14551
14815
|
storedToken(host) {
|
|
@@ -14562,9 +14826,9 @@ host=${host}
|
|
|
14562
14826
|
|
|
14563
14827
|
// src/tunnel.ts
|
|
14564
14828
|
import { spawn as spawn7 } from "child_process";
|
|
14565
|
-
import { existsSync as
|
|
14829
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync34, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14566
14830
|
import { randomInt } from "crypto";
|
|
14567
|
-
import { join as
|
|
14831
|
+
import { join as join39 } from "path";
|
|
14568
14832
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14569
14833
|
var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14570
14834
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14636,21 +14900,21 @@ function startTunnel(opts) {
|
|
|
14636
14900
|
};
|
|
14637
14901
|
}
|
|
14638
14902
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14639
|
-
return
|
|
14903
|
+
return join39(stateDir, "passengers", `${workspaceId}.json`);
|
|
14640
14904
|
}
|
|
14641
14905
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14642
14906
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14643
|
-
if (!
|
|
14907
|
+
if (!existsSync34(path)) return void 0;
|
|
14644
14908
|
try {
|
|
14645
|
-
return JSON.parse(
|
|
14909
|
+
return JSON.parse(readFileSync34(path, "utf8"));
|
|
14646
14910
|
} catch {
|
|
14647
14911
|
return void 0;
|
|
14648
14912
|
}
|
|
14649
14913
|
}
|
|
14650
14914
|
function writePersistedTunnel(stateDir, state) {
|
|
14651
14915
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14652
|
-
|
|
14653
|
-
|
|
14916
|
+
mkdirSync25(join39(stateDir, "passengers"), { recursive: true });
|
|
14917
|
+
writeFileSync27(path, `${JSON.stringify(state, null, 2)}
|
|
14654
14918
|
`);
|
|
14655
14919
|
}
|
|
14656
14920
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14752,8 +15016,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14752
15016
|
}
|
|
14753
15017
|
}
|
|
14754
15018
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14755
|
-
const dir =
|
|
14756
|
-
if (!
|
|
15019
|
+
const dir = join39(stateDir, "passengers");
|
|
15020
|
+
if (!existsSync34(dir)) return [];
|
|
14757
15021
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14758
15022
|
}
|
|
14759
15023
|
|
|
@@ -14778,7 +15042,7 @@ function realPoolMemberDeps(o) {
|
|
|
14778
15042
|
crewVersion: crewVersion(o.crewHome),
|
|
14779
15043
|
hostname: hostname3()
|
|
14780
15044
|
},
|
|
14781
|
-
hasShipConfig: () =>
|
|
15045
|
+
hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
|
|
14782
15046
|
async provision(lease, instanceClass, shipKind) {
|
|
14783
15047
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14784
15048
|
const lines2 = [];
|
|
@@ -15654,8 +15918,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15654
15918
|
}
|
|
15655
15919
|
|
|
15656
15920
|
// src/poll.ts
|
|
15657
|
-
import { writeFileSync as
|
|
15658
|
-
import { join as
|
|
15921
|
+
import { writeFileSync as writeFileSync28 } from "fs";
|
|
15922
|
+
import { join as join40 } from "path";
|
|
15659
15923
|
|
|
15660
15924
|
// src/stalled.ts
|
|
15661
15925
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15962,7 +16226,7 @@ async function decideCycle(o) {
|
|
|
15962
16226
|
}
|
|
15963
16227
|
return decision;
|
|
15964
16228
|
}
|
|
15965
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
16229
|
+
var digestPath = (stateDir, route2, role, runId) => join40(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15966
16230
|
function otherRunsRemoved(tickets, own) {
|
|
15967
16231
|
if (!own) return tickets;
|
|
15968
16232
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -16040,7 +16304,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
16040
16304
|
|
|
16041
16305
|
${body}` : body;
|
|
16042
16306
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
16043
|
-
|
|
16307
|
+
writeFileSync28(path, text2);
|
|
16044
16308
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
16045
16309
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
16046
16310
|
});
|
|
@@ -16148,8 +16412,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
16148
16412
|
}
|
|
16149
16413
|
|
|
16150
16414
|
// src/repo-refresh.ts
|
|
16151
|
-
import { existsSync as
|
|
16152
|
-
import { dirname as
|
|
16415
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
|
|
16416
|
+
import { dirname as dirname20 } from "path";
|
|
16153
16417
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
16154
16418
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
16155
16419
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -16178,11 +16442,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
16178
16442
|
}
|
|
16179
16443
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
16180
16444
|
const path = resolvedPathFor(stateDir, routeName);
|
|
16181
|
-
if (!
|
|
16182
|
-
const raw = JSON.parse(
|
|
16445
|
+
if (!existsSync36(path)) return;
|
|
16446
|
+
const raw = JSON.parse(readFileSync35(path, "utf8"));
|
|
16183
16447
|
applyRepoRows(raw, rows);
|
|
16184
|
-
|
|
16185
|
-
|
|
16448
|
+
mkdirSync26(dirname20(path), { recursive: true });
|
|
16449
|
+
writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
|
|
16186
16450
|
`);
|
|
16187
16451
|
}
|
|
16188
16452
|
async function refreshUnknownRepos(o) {
|
|
@@ -16632,13 +16896,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16632
16896
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16633
16897
|
|
|
16634
16898
|
// src/connect-wizard.ts
|
|
16635
|
-
import { delimiter, dirname as
|
|
16636
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16899
|
+
import { delimiter, dirname as dirname21, join as join41 } from "path";
|
|
16900
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
16637
16901
|
import { stringify as stringify3 } from "yaml";
|
|
16638
16902
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16639
16903
|
function shouldRunWizard(opts) {
|
|
16640
16904
|
if (!opts.isTTY) return false;
|
|
16641
|
-
const exists = opts.configExists ??
|
|
16905
|
+
const exists = opts.configExists ?? existsSync37;
|
|
16642
16906
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16643
16907
|
}
|
|
16644
16908
|
function firstRunConfigPath() {
|
|
@@ -16655,7 +16919,7 @@ function defaultIsExecutable(p) {
|
|
|
16655
16919
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16656
16920
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16657
16921
|
if (!dir) continue;
|
|
16658
|
-
const candidate =
|
|
16922
|
+
const candidate = join41(dir, name);
|
|
16659
16923
|
if (isExecutable(candidate)) return candidate;
|
|
16660
16924
|
}
|
|
16661
16925
|
return void 0;
|
|
@@ -16681,9 +16945,9 @@ async function runWizard(p, env) {
|
|
|
16681
16945
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16682
16946
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16683
16947
|
}
|
|
16684
|
-
const nodeDir =
|
|
16948
|
+
const nodeDir = dirname21(env.nodePath);
|
|
16685
16949
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16686
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16950
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname21(pnpmPath) : void 0];
|
|
16687
16951
|
p.say(
|
|
16688
16952
|
`
|
|
16689
16953
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16694,7 +16958,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16694
16958
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16695
16959
|
}
|
|
16696
16960
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16697
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16961
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname21(dockerPath) : void 0]);
|
|
16698
16962
|
if (extraPath) {
|
|
16699
16963
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16700
16964
|
}
|
|
@@ -16716,9 +16980,9 @@ routes:
|
|
|
16716
16980
|
${routeBlock}`;
|
|
16717
16981
|
}
|
|
16718
16982
|
function writeNewConfig(path, text2) {
|
|
16719
|
-
|
|
16983
|
+
mkdirSync27(dirname21(path), { recursive: true });
|
|
16720
16984
|
try {
|
|
16721
|
-
|
|
16985
|
+
writeFileSync30(path, text2, { mode: 384, flag: "wx" });
|
|
16722
16986
|
return true;
|
|
16723
16987
|
} catch (e) {
|
|
16724
16988
|
if (e.code === "EEXIST") return false;
|
|
@@ -16752,7 +17016,7 @@ Wrote ${o.configPath}.
|
|
|
16752
17016
|
// src/connect-json.ts
|
|
16753
17017
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16754
17018
|
import { homedir as homedir10 } from "os";
|
|
16755
|
-
import { dirname as
|
|
17019
|
+
import { dirname as dirname22, join as join42 } from "path";
|
|
16756
17020
|
import { delimiter as delimiter2 } from "path";
|
|
16757
17021
|
var CHOICE_EXIT_CODE = 3;
|
|
16758
17022
|
function createEmitter(json2, write) {
|
|
@@ -16791,12 +17055,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16791
17055
|
const dirs = [
|
|
16792
17056
|
...pathEnv.split(delimiter2),
|
|
16793
17057
|
...(extraPath ?? "").split(delimiter2),
|
|
16794
|
-
|
|
16795
|
-
|
|
17058
|
+
join42(home, ".local/bin"),
|
|
17059
|
+
join42(home, ".claude/local"),
|
|
16796
17060
|
"/opt/homebrew/bin",
|
|
16797
17061
|
"/usr/local/bin",
|
|
16798
|
-
|
|
16799
|
-
|
|
17062
|
+
join42(home, ".npm-global/bin"),
|
|
17063
|
+
join42(home, ".volta/bin")
|
|
16800
17064
|
];
|
|
16801
17065
|
return [...new Set(dirs.filter(Boolean))];
|
|
16802
17066
|
}
|
|
@@ -16818,7 +17082,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16818
17082
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16819
17083
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16820
17084
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16821
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
17085
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname22(env.nodePath), pnpmPath ? dirname22(pnpmPath) : void 0, dockerPath ? dirname22(dockerPath) : void 0]);
|
|
16822
17086
|
return {
|
|
16823
17087
|
answers: {
|
|
16824
17088
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16832,8 +17096,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16832
17096
|
}
|
|
16833
17097
|
|
|
16834
17098
|
// src/release-run.ts
|
|
16835
|
-
import { readFileSync as
|
|
16836
|
-
import { dirname as
|
|
17099
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17100
|
+
import { dirname as dirname23, join as join43 } from "path";
|
|
16837
17101
|
import { tmpdir as tmpdir5 } from "os";
|
|
16838
17102
|
|
|
16839
17103
|
// src/release.ts
|
|
@@ -17574,13 +17838,13 @@ async function bump(o, size) {
|
|
|
17574
17838
|
}
|
|
17575
17839
|
return printed;
|
|
17576
17840
|
}
|
|
17577
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17841
|
+
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
17578
17842
|
const first = files[0];
|
|
17579
|
-
if (!
|
|
17843
|
+
if (!existsSync38(first)) {
|
|
17580
17844
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17581
17845
|
return null;
|
|
17582
17846
|
}
|
|
17583
|
-
const current = JSON.parse(
|
|
17847
|
+
const current = JSON.parse(readFileSync36(first, "utf8")).version;
|
|
17584
17848
|
if (!current) {
|
|
17585
17849
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17586
17850
|
return null;
|
|
@@ -17593,27 +17857,27 @@ async function bump(o, size) {
|
|
|
17593
17857
|
return version;
|
|
17594
17858
|
}
|
|
17595
17859
|
for (const f of files) {
|
|
17596
|
-
const src =
|
|
17860
|
+
const src = readFileSync36(f, "utf8");
|
|
17597
17861
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17598
17862
|
if (out === src) {
|
|
17599
17863
|
o.emit.error(`no version field in ${f}`);
|
|
17600
17864
|
return null;
|
|
17601
17865
|
}
|
|
17602
|
-
|
|
17866
|
+
writeFileSync31(f, out);
|
|
17603
17867
|
}
|
|
17604
17868
|
return version;
|
|
17605
17869
|
}
|
|
17606
17870
|
function writeChangelog(o, version, entries) {
|
|
17607
17871
|
const file = o.repo.release.changelog;
|
|
17608
17872
|
if (!file || entries.length === 0) return;
|
|
17609
|
-
const path =
|
|
17873
|
+
const path = join43(o.cwd, file);
|
|
17610
17874
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17611
17875
|
if (o.dryRun) {
|
|
17612
17876
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17613
17877
|
return;
|
|
17614
17878
|
}
|
|
17615
|
-
const existing =
|
|
17616
|
-
|
|
17879
|
+
const existing = existsSync38(path) ? readFileSync36(path, "utf8") : "# Changelog\n";
|
|
17880
|
+
writeFileSync31(path, insertChangelogSection(existing, section));
|
|
17617
17881
|
}
|
|
17618
17882
|
async function confirm(o, expected) {
|
|
17619
17883
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17696,7 +17960,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17696
17960
|
run3(cwd, ["worktree", "remove", "--force", dir]);
|
|
17697
17961
|
} finally {
|
|
17698
17962
|
try {
|
|
17699
|
-
|
|
17963
|
+
rmSync19(root, { recursive: true, force: true });
|
|
17700
17964
|
} finally {
|
|
17701
17965
|
run3(cwd, ["worktree", "prune"]);
|
|
17702
17966
|
run3(cwd, ["branch", "-D", branch]);
|
|
@@ -17706,8 +17970,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17706
17970
|
function openReleaseCheckout(o) {
|
|
17707
17971
|
const { remote, base } = o.repo.branch;
|
|
17708
17972
|
const branch = `crew/release-${Date.now()}`;
|
|
17709
|
-
const root = mkdtempSync3(
|
|
17710
|
-
const dir =
|
|
17973
|
+
const root = mkdtempSync3(join43(tmpdir5(), "crew-release-"));
|
|
17974
|
+
const dir = join43(root, "checkout");
|
|
17711
17975
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17712
17976
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17713
17977
|
try {
|
|
@@ -17717,10 +17981,10 @@ function openReleaseCheckout(o) {
|
|
|
17717
17981
|
throw e;
|
|
17718
17982
|
}
|
|
17719
17983
|
for (const f of o.repo.worktrees.copy) {
|
|
17720
|
-
const src =
|
|
17721
|
-
if (!
|
|
17722
|
-
|
|
17723
|
-
copyFileSync(src,
|
|
17984
|
+
const src = join43(o.cwd, f);
|
|
17985
|
+
if (!existsSync38(src)) continue;
|
|
17986
|
+
mkdirSync28(dirname23(join43(dir, f)), { recursive: true });
|
|
17987
|
+
copyFileSync(src, join43(dir, f));
|
|
17724
17988
|
}
|
|
17725
17989
|
return { dir, branch, close };
|
|
17726
17990
|
}
|
|
@@ -18310,49 +18574,6 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
18310
18574
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
18311
18575
|
}
|
|
18312
18576
|
|
|
18313
|
-
// src/repo-map.ts
|
|
18314
|
-
var CHARS_PER_TOKEN = 4;
|
|
18315
|
-
var MAP_TIMEOUT_MS = 6e4;
|
|
18316
|
-
function budgetMap(text2, tokenBudget) {
|
|
18317
|
-
const body = text2.replace(/\s+$/, "");
|
|
18318
|
-
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
18319
|
-
if (body.length <= limit) return body;
|
|
18320
|
-
const lines2 = body.split("\n");
|
|
18321
|
-
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
18322
|
-
let kept = lines2.length;
|
|
18323
|
-
let size = body.length;
|
|
18324
|
-
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
18325
|
-
kept--;
|
|
18326
|
-
size -= lines2[kept].length + 1;
|
|
18327
|
-
}
|
|
18328
|
-
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
18329
|
-
}
|
|
18330
|
-
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
18331
|
-
const script = repo.config.hooks.map;
|
|
18332
|
-
if (!script) return Promise.resolve({ skipped: "" });
|
|
18333
|
-
return runScript(script, {
|
|
18334
|
-
cwd: repo.dir,
|
|
18335
|
-
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
18336
|
-
env: { CREW_REPO: repo.name },
|
|
18337
|
-
timeoutMs: MAP_TIMEOUT_MS
|
|
18338
|
-
}).then(
|
|
18339
|
-
(r) => {
|
|
18340
|
-
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
18341
|
-
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
18342
|
-
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
18343
|
-
},
|
|
18344
|
-
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
18345
|
-
);
|
|
18346
|
-
}
|
|
18347
|
-
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
18348
|
-
return Promise.all(repos.map(async (r) => {
|
|
18349
|
-
if (!r.config.hooks.map) return r;
|
|
18350
|
-
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
18351
|
-
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
18352
|
-
return { ...r, map: out.text };
|
|
18353
|
-
}));
|
|
18354
|
-
}
|
|
18355
|
-
|
|
18356
18577
|
// src/failure-alert.ts
|
|
18357
18578
|
var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
|
|
18358
18579
|
function findOpenFailureAlert(tickets, headline) {
|
|
@@ -18742,7 +18963,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18742
18963
|
}
|
|
18743
18964
|
|
|
18744
18965
|
// src/watch.ts
|
|
18745
|
-
import { watch as watchFile, existsSync as
|
|
18966
|
+
import { watch as watchFile, existsSync as existsSync39, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
18746
18967
|
var E = "\x1B[";
|
|
18747
18968
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18748
18969
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18843,7 +19064,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18843
19064
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18844
19065
|
}
|
|
18845
19066
|
function readFrom(file, from) {
|
|
18846
|
-
if (!
|
|
19067
|
+
if (!existsSync39(file)) return { events: [], offset: from };
|
|
18847
19068
|
const size = statSync11(file).size;
|
|
18848
19069
|
if (size < from) return readFrom(file, 0);
|
|
18849
19070
|
if (size === from) return { events: [], offset: from };
|
|
@@ -19086,8 +19307,8 @@ function nextRoles(decision, ran) {
|
|
|
19086
19307
|
}
|
|
19087
19308
|
|
|
19088
19309
|
// src/ship-name.ts
|
|
19089
|
-
import { existsSync as
|
|
19090
|
-
import { dirname as
|
|
19310
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
|
|
19311
|
+
import { dirname as dirname24 } from "path";
|
|
19091
19312
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19092
19313
|
var ShipNameError = class extends Error {
|
|
19093
19314
|
};
|
|
@@ -19148,34 +19369,34 @@ async function syncRouteName(i) {
|
|
|
19148
19369
|
return { status: "ok", ...base, action, name: i.local };
|
|
19149
19370
|
}
|
|
19150
19371
|
function readSyncedName(resolvedFile) {
|
|
19151
|
-
if (!
|
|
19372
|
+
if (!existsSync40(resolvedFile)) return void 0;
|
|
19152
19373
|
try {
|
|
19153
|
-
const v = JSON.parse(
|
|
19374
|
+
const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
|
|
19154
19375
|
return typeof v === "string" ? v : void 0;
|
|
19155
19376
|
} catch {
|
|
19156
19377
|
return void 0;
|
|
19157
19378
|
}
|
|
19158
19379
|
}
|
|
19159
19380
|
function writeSyncedName(resolvedFile, name) {
|
|
19160
|
-
if (!
|
|
19381
|
+
if (!existsSync40(resolvedFile)) return;
|
|
19161
19382
|
let raw;
|
|
19162
19383
|
try {
|
|
19163
|
-
raw = JSON.parse(
|
|
19384
|
+
raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
|
|
19164
19385
|
} catch {
|
|
19165
19386
|
return;
|
|
19166
19387
|
}
|
|
19167
19388
|
if (raw.shipName === name) return;
|
|
19168
19389
|
raw.shipName = name;
|
|
19169
|
-
|
|
19390
|
+
mkdirSync29(dirname24(resolvedFile), { recursive: true });
|
|
19170
19391
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
19171
|
-
|
|
19392
|
+
writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
|
|
19172
19393
|
`);
|
|
19173
19394
|
renameSync5(tmp, resolvedFile);
|
|
19174
19395
|
}
|
|
19175
19396
|
async function reconcileShipName(i) {
|
|
19176
19397
|
let local = i.ship.name;
|
|
19177
19398
|
try {
|
|
19178
|
-
local = shipNameOfYaml(
|
|
19399
|
+
local = shipNameOfYaml(readFileSync37(i.configFile, "utf8")) ?? local;
|
|
19179
19400
|
} catch {
|
|
19180
19401
|
}
|
|
19181
19402
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -19199,8 +19420,8 @@ async function reconcileShipName(i) {
|
|
|
19199
19420
|
}
|
|
19200
19421
|
if (result.status !== "ok") return result;
|
|
19201
19422
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
19202
|
-
const text2 =
|
|
19203
|
-
|
|
19423
|
+
const text2 = readFileSync37(i.configFile, "utf8");
|
|
19424
|
+
writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
|
|
19204
19425
|
i.ship.name = result.name;
|
|
19205
19426
|
if (result.action === "row-wins") {
|
|
19206
19427
|
i.emit?.warn(
|
|
@@ -19859,8 +20080,8 @@ function describeCapability(i) {
|
|
|
19859
20080
|
}
|
|
19860
20081
|
|
|
19861
20082
|
// src/agents-sync-run.ts
|
|
19862
|
-
import { existsSync as
|
|
19863
|
-
import { dirname as
|
|
20083
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
|
|
20084
|
+
import { dirname as dirname25 } from "path";
|
|
19864
20085
|
async function runAgentsSync(target, o) {
|
|
19865
20086
|
const dryRun = o.dryRun ?? false;
|
|
19866
20087
|
const force = o.force ?? false;
|
|
@@ -19893,12 +20114,12 @@ async function runAgentsSync(target, o) {
|
|
|
19893
20114
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19894
20115
|
if (!dryRun) {
|
|
19895
20116
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19896
|
-
const raw =
|
|
20117
|
+
const raw = existsSync41(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
|
|
19897
20118
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19898
20119
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19899
20120
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19900
|
-
|
|
19901
|
-
|
|
20121
|
+
mkdirSync30(dirname25(resolvedPath), { recursive: true });
|
|
20122
|
+
writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19902
20123
|
`);
|
|
19903
20124
|
if (target.resolved) {
|
|
19904
20125
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -20015,7 +20236,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
20015
20236
|
|
|
20016
20237
|
// src/repos-check.ts
|
|
20017
20238
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
20018
|
-
import { readFileSync as
|
|
20239
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
20019
20240
|
import { basename as basename11 } from "path";
|
|
20020
20241
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
20021
20242
|
function checkHookSyntax(script, shell) {
|
|
@@ -20049,14 +20270,14 @@ function renderSamples(cfg, dir) {
|
|
|
20049
20270
|
});
|
|
20050
20271
|
}
|
|
20051
20272
|
function checkRepoSpec(dir, opts = {}) {
|
|
20052
|
-
const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], samples: [] };
|
|
20273
|
+
const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], mapFile: null, samples: [] };
|
|
20053
20274
|
const file = findRepoConfig(dir);
|
|
20054
20275
|
if (!file) {
|
|
20055
20276
|
return { ...empty, file: null, error: `no .crew.yaml, .crew.yml or .crew.json in ${dir}`, exitCode: 1 };
|
|
20056
20277
|
}
|
|
20057
20278
|
let parsed;
|
|
20058
20279
|
try {
|
|
20059
|
-
parsed = parseRepoConfig(
|
|
20280
|
+
parsed = parseRepoConfig(readFileSync39(file, "utf8"), file);
|
|
20060
20281
|
} catch (e) {
|
|
20061
20282
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
20062
20283
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -20067,14 +20288,16 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
20067
20288
|
const engine = effective.hooks.setup ? containerEngineUse(effective.hooks.setup) : null;
|
|
20068
20289
|
if (engine) readiness.push(`hooks.setup runs \`${engine}\`, and a sandbox has no container engine \u2014 start the service as a process instead (REPO_SPEC.md, "Services inside the sandbox")`);
|
|
20069
20290
|
}
|
|
20070
|
-
const
|
|
20291
|
+
const mapPath = mapFilePath(effective.hooks.map);
|
|
20292
|
+
const mapFile = mapPath ? { ...inspectMapFile(dir, mapPath), budget: opts.ship?.mapTokenBudget ?? DEFAULT_MAP_TOKEN_BUDGET } : null;
|
|
20293
|
+
const hooks = Object.keys(effective.hooks).filter((name) => !(name === "map" && mapPath)).map((name) => ({
|
|
20071
20294
|
name,
|
|
20072
20295
|
label: hookLabel(effective, name),
|
|
20073
20296
|
// CREW-2914: a flavor name is not shell, so it is not syntax-checked.
|
|
20074
20297
|
syntax: name === "intel" && intelFlavor(effective.hooks.intel) ? { status: "ok" } : checkHookSyntax(effective.hooks[name], effective.shell),
|
|
20075
20298
|
...name === "intel" && intelFlavor(effective.hooks.intel) ? { flavor: intelFlavor(effective.hooks.intel) } : {}
|
|
20076
20299
|
}));
|
|
20077
|
-
const failed = hooks.some((h) => h.syntax.status === "error");
|
|
20300
|
+
const failed = hooks.some((h) => h.syntax.status === "error") || mapFile !== null && !mapFile.exists;
|
|
20078
20301
|
return {
|
|
20079
20302
|
file,
|
|
20080
20303
|
parsed,
|
|
@@ -20082,6 +20305,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
20082
20305
|
readiness,
|
|
20083
20306
|
hooks,
|
|
20084
20307
|
shadowed: effective.shadowed,
|
|
20308
|
+
mapFile,
|
|
20085
20309
|
samples: renderSamples(effective, dir),
|
|
20086
20310
|
error: null,
|
|
20087
20311
|
exitCode: failed || opts.strict && readiness.length > 0 ? 1 : 0
|
|
@@ -20101,6 +20325,10 @@ function renderRepoCheck(r) {
|
|
|
20101
20325
|
` branch.push ${e.branch.push}`
|
|
20102
20326
|
];
|
|
20103
20327
|
for (const s of r.samples) lines2.push(` ${s.case}: branch ${s.branch}, worktree ../${s.worktree}`);
|
|
20328
|
+
if (r.mapFile) {
|
|
20329
|
+
const m = r.mapFile;
|
|
20330
|
+
lines2.push(m.exists ? ` map: file ${m.path} (${m.lines} lines, about ${m.tokens} tokens; ship budget ${m.budget})` : ` map: file ${m.path} NOT FOUND \u2014 the brief will carry an absence note`);
|
|
20331
|
+
}
|
|
20104
20332
|
lines2.push(r.hooks.length ? " hooks" : " hooks none");
|
|
20105
20333
|
for (const h of r.hooks) {
|
|
20106
20334
|
const named = h.label !== e.hooks[h.name] ? ` (${h.label})` : "";
|
|
@@ -20513,7 +20741,7 @@ ${run3.err}` });
|
|
|
20513
20741
|
}
|
|
20514
20742
|
|
|
20515
20743
|
// src/sample-ticket.ts
|
|
20516
|
-
import { basename as basename12, dirname as
|
|
20744
|
+
import { basename as basename12, dirname as dirname26, join as join44 } from "path";
|
|
20517
20745
|
var SAMPLE_REPO = "crew-sample";
|
|
20518
20746
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
20519
20747
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -20536,8 +20764,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
20536
20764
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
20537
20765
|
`;
|
|
20538
20766
|
function sampleDir(existing, reposBase, workspace) {
|
|
20539
|
-
if (existing.length > 0) return
|
|
20540
|
-
return
|
|
20767
|
+
if (existing.length > 0) return join44(dirname26(existing[0]), SAMPLE_REPO);
|
|
20768
|
+
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
20541
20769
|
}
|
|
20542
20770
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
20543
20771
|
function sampleDescription() {
|
|
@@ -20581,8 +20809,8 @@ function planSampleTicket(i) {
|
|
|
20581
20809
|
}
|
|
20582
20810
|
|
|
20583
20811
|
// src/worktree-sweep.ts
|
|
20584
|
-
import { existsSync as
|
|
20585
|
-
import { basename as basename13, dirname as
|
|
20812
|
+
import { existsSync as existsSync42, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
|
|
20813
|
+
import { basename as basename13, dirname as dirname27, join as join45, resolve as resolve5 } from "path";
|
|
20586
20814
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20587
20815
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
20588
20816
|
const verified = contract.statuses.verified;
|
|
@@ -20646,7 +20874,7 @@ function worktreeTicketNumbers(target) {
|
|
|
20646
20874
|
return [...nums];
|
|
20647
20875
|
}
|
|
20648
20876
|
function siblingWorktreeDirs(target) {
|
|
20649
|
-
const parent =
|
|
20877
|
+
const parent = dirname27(target.dir.replace(/[/\\]+$/, ""));
|
|
20650
20878
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
20651
20879
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
20652
20880
|
let names;
|
|
@@ -20659,7 +20887,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20659
20887
|
for (const name of names) {
|
|
20660
20888
|
if (!name.startsWith(stem)) continue;
|
|
20661
20889
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20662
|
-
const path =
|
|
20890
|
+
const path = join45(parent, name);
|
|
20663
20891
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20664
20892
|
}
|
|
20665
20893
|
return out;
|
|
@@ -20694,7 +20922,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20694
20922
|
let removed = 0;
|
|
20695
20923
|
let keptBranches = 0;
|
|
20696
20924
|
for (const a of actions) {
|
|
20697
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20925
|
+
if (!dryRun && base && !a.keepBranch && existsSync42(a.path) && isHandBackMerge(a.path, base)) {
|
|
20698
20926
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20699
20927
|
log.emit(
|
|
20700
20928
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20702,7 +20930,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20702
20930
|
);
|
|
20703
20931
|
}
|
|
20704
20932
|
}
|
|
20705
|
-
if (!dryRun &&
|
|
20933
|
+
if (!dryRun && existsSync42(a.path)) {
|
|
20706
20934
|
let dirty = false;
|
|
20707
20935
|
try {
|
|
20708
20936
|
dirty = status(a.path).length > 0;
|
|
@@ -20713,7 +20941,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20713
20941
|
continue;
|
|
20714
20942
|
}
|
|
20715
20943
|
}
|
|
20716
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20944
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync42(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20717
20945
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20718
20946
|
continue;
|
|
20719
20947
|
}
|
|
@@ -20762,7 +20990,7 @@ function isHandBackMerge(path, base) {
|
|
|
20762
20990
|
if (!msgPath) return false;
|
|
20763
20991
|
let first;
|
|
20764
20992
|
try {
|
|
20765
|
-
first =
|
|
20993
|
+
first = readFileSync40(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20766
20994
|
} catch {
|
|
20767
20995
|
return false;
|
|
20768
20996
|
}
|
|
@@ -20867,14 +21095,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20867
21095
|
}
|
|
20868
21096
|
|
|
20869
21097
|
// src/stream-sweep.ts
|
|
20870
|
-
import { existsSync as
|
|
20871
|
-
import { join as
|
|
21098
|
+
import { existsSync as existsSync43, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21099
|
+
import { join as join46 } from "path";
|
|
20872
21100
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20873
21101
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20874
|
-
const dir =
|
|
20875
|
-
if (!
|
|
21102
|
+
const dir = join46(stateDir, "streams");
|
|
21103
|
+
if (!existsSync43(dir)) return [];
|
|
20876
21104
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20877
|
-
return readdirSync15(dir).map((name) =>
|
|
21105
|
+
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
20878
21106
|
try {
|
|
20879
21107
|
return statSync12(path).mtimeMs < cutoff;
|
|
20880
21108
|
} catch {
|
|
@@ -20890,7 +21118,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20890
21118
|
continue;
|
|
20891
21119
|
}
|
|
20892
21120
|
try {
|
|
20893
|
-
|
|
21121
|
+
rmSync20(path, { force: true });
|
|
20894
21122
|
removed++;
|
|
20895
21123
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
20896
21124
|
} catch (e) {
|
|
@@ -20900,9 +21128,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20900
21128
|
return removed;
|
|
20901
21129
|
}
|
|
20902
21130
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20903
|
-
if (!
|
|
21131
|
+
if (!existsSync43(stateDir)) return [];
|
|
20904
21132
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20905
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
21133
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
20906
21134
|
try {
|
|
20907
21135
|
return statSync12(path).mtimeMs < cutoff;
|
|
20908
21136
|
} catch {
|
|
@@ -20912,12 +21140,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20912
21140
|
}
|
|
20913
21141
|
|
|
20914
21142
|
// src/cli.ts
|
|
20915
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
21143
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync50, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
20916
21144
|
import { createInterface } from "readline/promises";
|
|
20917
21145
|
|
|
20918
21146
|
// src/periscope-cmd.ts
|
|
20919
|
-
import { existsSync as
|
|
20920
|
-
import { join as
|
|
21147
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
|
|
21148
|
+
import { join as join47 } from "path";
|
|
20921
21149
|
var PeriscopeError = class extends Error {
|
|
20922
21150
|
};
|
|
20923
21151
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20927,22 +21155,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20927
21155
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20928
21156
|
}
|
|
20929
21157
|
};
|
|
20930
|
-
var priorDir = (stateDir) =>
|
|
20931
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
21158
|
+
var priorDir = (stateDir) => join47(stateDir, "periscope-priors");
|
|
21159
|
+
var priorPath = (stateDir, ticketRecordId) => join47(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20932
21160
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20933
|
-
|
|
21161
|
+
rmSync21(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20934
21162
|
}
|
|
20935
21163
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20936
21164
|
try {
|
|
20937
|
-
|
|
20938
|
-
|
|
21165
|
+
mkdirSync31(priorDir(stateDir), { recursive: true });
|
|
21166
|
+
writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20939
21167
|
} catch {
|
|
20940
21168
|
}
|
|
20941
21169
|
}
|
|
20942
21170
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20943
21171
|
try {
|
|
20944
21172
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20945
|
-
return
|
|
21173
|
+
return existsSync44(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
|
|
20946
21174
|
} catch {
|
|
20947
21175
|
return void 0;
|
|
20948
21176
|
}
|
|
@@ -21396,7 +21624,7 @@ ${tail2(preview.output)}`);
|
|
|
21396
21624
|
// src/periscope-idle.ts
|
|
21397
21625
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21398
21626
|
import { statSync as statSync13 } from "fs";
|
|
21399
|
-
import { join as
|
|
21627
|
+
import { join as join48 } from "path";
|
|
21400
21628
|
var MAX_SCANNED_FILES = 2e4;
|
|
21401
21629
|
function newestWorktreeMtime(root) {
|
|
21402
21630
|
const a = newestTrackedMtime(root);
|
|
@@ -21421,7 +21649,7 @@ function newestTrackedMtime(root) {
|
|
|
21421
21649
|
if (!rel) continue;
|
|
21422
21650
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
21423
21651
|
try {
|
|
21424
|
-
const m = statSync13(
|
|
21652
|
+
const m = statSync13(join48(root, rel)).mtimeMs;
|
|
21425
21653
|
if (newest === null || m > newest) newest = m;
|
|
21426
21654
|
} catch {
|
|
21427
21655
|
}
|
|
@@ -21497,18 +21725,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
21497
21725
|
|
|
21498
21726
|
// src/ssh-keys.ts
|
|
21499
21727
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
21500
|
-
import { existsSync as
|
|
21501
|
-
import { dirname as
|
|
21728
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
|
|
21729
|
+
import { dirname as dirname28, join as join49 } from "path";
|
|
21502
21730
|
var SshKeyError = class extends Error {
|
|
21503
21731
|
};
|
|
21504
21732
|
function shipSshKeyPathFor(stateDir) {
|
|
21505
|
-
return
|
|
21733
|
+
return join49(stateDir, "ssh", "id_ed25519");
|
|
21506
21734
|
}
|
|
21507
21735
|
function ensureShipSshKeypair(stateDir) {
|
|
21508
21736
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
21509
21737
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
21510
|
-
if (!
|
|
21511
|
-
|
|
21738
|
+
if (!existsSync45(privateKeyPath)) {
|
|
21739
|
+
mkdirSync32(dirname28(privateKeyPath), { recursive: true });
|
|
21512
21740
|
try {
|
|
21513
21741
|
execFileSync13("ssh-keygen", [
|
|
21514
21742
|
"-t",
|
|
@@ -21528,10 +21756,10 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
21528
21756
|
}
|
|
21529
21757
|
chmodSync3(privateKeyPath, 384);
|
|
21530
21758
|
}
|
|
21531
|
-
if (!
|
|
21759
|
+
if (!existsSync45(publicKeyPath)) {
|
|
21532
21760
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
21533
21761
|
}
|
|
21534
|
-
const publicKey =
|
|
21762
|
+
const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
|
|
21535
21763
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
21536
21764
|
}
|
|
21537
21765
|
function sshKeygenAvailable() {
|
|
@@ -21695,12 +21923,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21695
21923
|
import { createServer as createHttpServer } from "http";
|
|
21696
21924
|
import { spawn as spawn9 } from "child_process";
|
|
21697
21925
|
import { randomBytes } from "crypto";
|
|
21698
|
-
import { existsSync as
|
|
21699
|
-
import { join as
|
|
21926
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21927
|
+
import { join as join50 } from "path";
|
|
21700
21928
|
|
|
21701
21929
|
// src/passenger-mirror.ts
|
|
21702
|
-
import { mkdirSync as
|
|
21703
|
-
import { dirname as
|
|
21930
|
+
import { mkdirSync as mkdirSync33 } from "fs";
|
|
21931
|
+
import { dirname as dirname29 } from "path";
|
|
21704
21932
|
function mirrorRepoOf(spec) {
|
|
21705
21933
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
21706
21934
|
if (fromOrigin) return fromOrigin;
|
|
@@ -21718,9 +21946,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
21718
21946
|
continue;
|
|
21719
21947
|
}
|
|
21720
21948
|
try {
|
|
21721
|
-
const parent =
|
|
21722
|
-
|
|
21723
|
-
|
|
21949
|
+
const parent = dirname29(spec.dir);
|
|
21950
|
+
mkdirSync33(parent, { recursive: true });
|
|
21951
|
+
mkdirSync33(spec.dir, { recursive: true });
|
|
21724
21952
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
21725
21953
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
21726
21954
|
} catch (e) {
|
|
@@ -21826,21 +22054,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21826
22054
|
return child;
|
|
21827
22055
|
};
|
|
21828
22056
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21829
|
-
return
|
|
22057
|
+
return join50(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21830
22058
|
}
|
|
21831
22059
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21832
22060
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21833
|
-
if (!
|
|
22061
|
+
if (!existsSync46(path)) return void 0;
|
|
21834
22062
|
try {
|
|
21835
|
-
return JSON.parse(
|
|
22063
|
+
return JSON.parse(readFileSync43(path, "utf8"));
|
|
21836
22064
|
} catch {
|
|
21837
22065
|
return void 0;
|
|
21838
22066
|
}
|
|
21839
22067
|
}
|
|
21840
22068
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21841
22069
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21842
|
-
|
|
21843
|
-
|
|
22070
|
+
mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
|
|
22071
|
+
writeFileSync35(path, `${JSON.stringify(state, null, 2)}
|
|
21844
22072
|
`);
|
|
21845
22073
|
}
|
|
21846
22074
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21860,8 +22088,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21860
22088
|
}
|
|
21861
22089
|
}
|
|
21862
22090
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21863
|
-
const dir =
|
|
21864
|
-
if (!
|
|
22091
|
+
const dir = join50(stateDir, "passengers");
|
|
22092
|
+
if (!existsSync46(dir)) return [];
|
|
21865
22093
|
const suffix = "-sync.json";
|
|
21866
22094
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21867
22095
|
}
|
|
@@ -21919,12 +22147,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
21919
22147
|
|
|
21920
22148
|
// src/periscope-proxy.ts
|
|
21921
22149
|
import { spawn as spawn10 } from "child_process";
|
|
21922
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
22150
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
|
|
21923
22151
|
import { createServer, request as httpRequest } from "http";
|
|
21924
22152
|
import { request as httpsRequest } from "https";
|
|
21925
22153
|
import { connect as netConnect } from "net";
|
|
21926
22154
|
import { connect as tlsConnect } from "tls";
|
|
21927
|
-
import { dirname as
|
|
22155
|
+
import { dirname as dirname30, isAbsolute as isAbsolute4 } from "path";
|
|
21928
22156
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
21929
22157
|
|
|
21930
22158
|
// src/periscope-inspector.ts
|
|
@@ -22038,7 +22266,7 @@ function parsePointedBody(raw) {
|
|
|
22038
22266
|
return entry;
|
|
22039
22267
|
}
|
|
22040
22268
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
22041
|
-
|
|
22269
|
+
mkdirSync35(dirname30(file), { recursive: true });
|
|
22042
22270
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
22043
22271
|
`);
|
|
22044
22272
|
}
|
|
@@ -22299,6 +22527,33 @@ async function launchProxy(invocation, worktree, previewUrl, number, o) {
|
|
|
22299
22527
|
throw new Error(`the proxy did not start listening on ${o.host}:${port}`);
|
|
22300
22528
|
}
|
|
22301
22529
|
|
|
22530
|
+
// src/intel-worktree.ts
|
|
22531
|
+
async function startWorktreeIntel(d) {
|
|
22532
|
+
const file = attachFilePath(d.stateDir, d.route, d.number);
|
|
22533
|
+
const probe = d.probe ?? probeIntel;
|
|
22534
|
+
let outcome;
|
|
22535
|
+
try {
|
|
22536
|
+
const known = readAttachFile(file);
|
|
22537
|
+
outcome = known ? await probe({ runHook: async () => ({ code: 0, output: known }) }, {}, 3) : { state: "absent" };
|
|
22538
|
+
if (outcome.state !== "ready") {
|
|
22539
|
+
const prepared = await d.prepare(d.worktree);
|
|
22540
|
+
if (prepared && prepared.code !== 0) {
|
|
22541
|
+
outcome = { state: "failed", reason: `setup exited ${prepared.code}` };
|
|
22542
|
+
} else {
|
|
22543
|
+
const env = { CREW_TICKET: d.ticketTag, CREW_BRANCH: d.branch };
|
|
22544
|
+
const ports = await d.runHook("ports", d.worktree, env);
|
|
22545
|
+
if (ports) Object.assign(env, parseEnvLines(ports.output));
|
|
22546
|
+
outcome = await probe({ runHook: (e) => d.runHook("intel", d.worktree, e) }, env, d.startupSeconds);
|
|
22547
|
+
}
|
|
22548
|
+
}
|
|
22549
|
+
if (outcome.state === "ready") writeAttachFile(file, outcome.url);
|
|
22550
|
+
} catch (e) {
|
|
22551
|
+
outcome = { state: "failed", reason: e.message.slice(0, 200) };
|
|
22552
|
+
}
|
|
22553
|
+
const tail3 = outcome.state === "ready" ? " The session's `code` server attaches to it within a second; until it lists these tools it still shows only `code_status`." : "";
|
|
22554
|
+
return `${codeToolsParagraph(outcome)}${tail3}`;
|
|
22555
|
+
}
|
|
22556
|
+
|
|
22302
22557
|
// src/outage.ts
|
|
22303
22558
|
var TRANSIENT_BACKOFF_MS = [15e3, 3e4, 6e4];
|
|
22304
22559
|
var OUTAGE_ESCALATE_AFTER = 5;
|
|
@@ -22331,8 +22586,8 @@ function describeCause(err3) {
|
|
|
22331
22586
|
}
|
|
22332
22587
|
|
|
22333
22588
|
// src/daemon.ts
|
|
22334
|
-
import { existsSync as
|
|
22335
|
-
import { join as
|
|
22589
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22590
|
+
import { join as join51 } from "path";
|
|
22336
22591
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22337
22592
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
22338
22593
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -22420,7 +22675,7 @@ function latestMtime(path) {
|
|
|
22420
22675
|
if (!stat2.isDirectory()) return 0;
|
|
22421
22676
|
let latest = stat2.mtimeMs;
|
|
22422
22677
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
22423
|
-
latest = Math.max(latest, latestMtime(
|
|
22678
|
+
latest = Math.max(latest, latestMtime(join51(path, entry.name)));
|
|
22424
22679
|
}
|
|
22425
22680
|
return latest;
|
|
22426
22681
|
}
|
|
@@ -22429,10 +22684,10 @@ function computeTreeSignature(paths) {
|
|
|
22429
22684
|
}
|
|
22430
22685
|
function makeStaleChecker(paths, opts = {}) {
|
|
22431
22686
|
const initial = computeTreeSignature(paths);
|
|
22432
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
22687
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync47(opts.reloadSentinel);
|
|
22433
22688
|
}
|
|
22434
22689
|
function reloadSentinelFor(stateDir) {
|
|
22435
|
-
return
|
|
22690
|
+
return join51(stateDir, "reload-requested");
|
|
22436
22691
|
}
|
|
22437
22692
|
function readReloadRequestedAt(stateDir) {
|
|
22438
22693
|
try {
|
|
@@ -22442,17 +22697,17 @@ function readReloadRequestedAt(stateDir) {
|
|
|
22442
22697
|
}
|
|
22443
22698
|
}
|
|
22444
22699
|
function daemonStampFor(stateDir) {
|
|
22445
|
-
return
|
|
22700
|
+
return join51(stateDir, "daemon.started");
|
|
22446
22701
|
}
|
|
22447
22702
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22703
|
+
mkdirSync36(stateDir, { recursive: true });
|
|
22704
|
+
rmSync22(reloadSentinelFor(stateDir), { force: true });
|
|
22705
|
+
writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
22451
22706
|
`);
|
|
22452
22707
|
}
|
|
22453
22708
|
function readDaemonStarted(stateDir) {
|
|
22454
22709
|
try {
|
|
22455
|
-
const v = JSON.parse(
|
|
22710
|
+
const v = JSON.parse(readFileSync44(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
22456
22711
|
return typeof v === "number" ? v : null;
|
|
22457
22712
|
} catch {
|
|
22458
22713
|
return null;
|
|
@@ -23018,18 +23273,18 @@ import { createHash as createHash10 } from "crypto";
|
|
|
23018
23273
|
import {
|
|
23019
23274
|
chmodSync as chmodSync4,
|
|
23020
23275
|
closeSync as closeSync6,
|
|
23021
|
-
existsSync as
|
|
23276
|
+
existsSync as existsSync48,
|
|
23022
23277
|
fstatSync,
|
|
23023
23278
|
openSync as openSync6,
|
|
23024
23279
|
readSync as readSync3,
|
|
23025
23280
|
readdirSync as readdirSync18,
|
|
23026
|
-
rmSync as
|
|
23281
|
+
rmSync as rmSync23,
|
|
23027
23282
|
statSync as statSync15,
|
|
23028
23283
|
watch
|
|
23029
23284
|
} from "fs";
|
|
23030
23285
|
import { createConnection } from "net";
|
|
23031
23286
|
import { createServer as createServer2 } from "http";
|
|
23032
|
-
import { basename as basename14, dirname as
|
|
23287
|
+
import { basename as basename14, dirname as dirname31, join as join52 } from "path";
|
|
23033
23288
|
import { execFile as execFile3, spawn as spawn12 } from "child_process";
|
|
23034
23289
|
|
|
23035
23290
|
// src/periscope-control.ts
|
|
@@ -23258,7 +23513,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
23258
23513
|
// src/control-api.ts
|
|
23259
23514
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
23260
23515
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
23261
|
-
var controlSocketFor = (stateDir) =>
|
|
23516
|
+
var controlSocketFor = (stateDir) => join52(stateDir, CONTROL_SOCKET_NAME);
|
|
23262
23517
|
var MAX_SOCKET_PATH = 100;
|
|
23263
23518
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
23264
23519
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -23345,7 +23600,7 @@ function candidates(live, rotated) {
|
|
|
23345
23600
|
const out = [live];
|
|
23346
23601
|
if (!rotated) return out;
|
|
23347
23602
|
try {
|
|
23348
|
-
for (const n of readdirSync18(
|
|
23603
|
+
for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname31(live), n));
|
|
23349
23604
|
} catch {
|
|
23350
23605
|
}
|
|
23351
23606
|
return out;
|
|
@@ -23389,7 +23644,7 @@ function lastLines2(path, n) {
|
|
|
23389
23644
|
}
|
|
23390
23645
|
function readTail(live, opts) {
|
|
23391
23646
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
23392
|
-
if (!
|
|
23647
|
+
if (!existsSync48(live)) {
|
|
23393
23648
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
23394
23649
|
}
|
|
23395
23650
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -23505,7 +23760,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
23505
23760
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
23506
23761
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
23507
23762
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
23508
|
-
await waitForChange(
|
|
23763
|
+
await waitForChange(dirname31(path), deadline - Date.now(), abort);
|
|
23509
23764
|
r = readTail(path, { after: r.cursor, rotated });
|
|
23510
23765
|
}
|
|
23511
23766
|
return reply2(200, r);
|
|
@@ -23545,8 +23800,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
23545
23800
|
if (p.startsWith("/streams/") && get2) {
|
|
23546
23801
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
23547
23802
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
23548
|
-
const file =
|
|
23549
|
-
if (!
|
|
23803
|
+
const file = join52(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
23804
|
+
if (!existsSync48(file)) return err2(404, "no such run");
|
|
23550
23805
|
return tailReply(file, url.searchParams, false, abort);
|
|
23551
23806
|
}
|
|
23552
23807
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -23590,7 +23845,7 @@ function send(res, r) {
|
|
|
23590
23845
|
res.end(body);
|
|
23591
23846
|
}
|
|
23592
23847
|
async function socketIsStale(path) {
|
|
23593
|
-
if (!
|
|
23848
|
+
if (!existsSync48(path)) return false;
|
|
23594
23849
|
return new Promise((resolve8) => {
|
|
23595
23850
|
const c = createConnection(path);
|
|
23596
23851
|
c.once("connect", () => {
|
|
@@ -23608,8 +23863,8 @@ async function startControlServer(deps) {
|
|
|
23608
23863
|
log(`control API not started: socket path is too long for a unix socket (${path})`);
|
|
23609
23864
|
return null;
|
|
23610
23865
|
}
|
|
23611
|
-
if (await socketIsStale(path))
|
|
23612
|
-
else if (
|
|
23866
|
+
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
23867
|
+
else if (existsSync48(path)) {
|
|
23613
23868
|
log(`control API not started: ${path} is in use by another process`);
|
|
23614
23869
|
return null;
|
|
23615
23870
|
}
|
|
@@ -23655,7 +23910,7 @@ async function startControlServer(deps) {
|
|
|
23655
23910
|
path,
|
|
23656
23911
|
close: () => new Promise((done) => {
|
|
23657
23912
|
server.close(() => {
|
|
23658
|
-
|
|
23913
|
+
rmSync23(path, { force: true });
|
|
23659
23914
|
done();
|
|
23660
23915
|
});
|
|
23661
23916
|
})
|
|
@@ -23695,57 +23950,57 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
23695
23950
|
|
|
23696
23951
|
// src/periscope-mcp.ts
|
|
23697
23952
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
23698
|
-
import { basename as basename15, dirname as
|
|
23953
|
+
import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
|
|
23699
23954
|
import { z } from "zod";
|
|
23700
23955
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23701
23956
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23702
23957
|
|
|
23703
23958
|
// src/periscope-endpoints.ts
|
|
23704
23959
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
23705
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
23706
|
-
import { join as
|
|
23707
|
-
var secretDir = (stateDir) =>
|
|
23708
|
-
var secretFile = (stateDir, id) =>
|
|
23709
|
-
var endpointDir = (stateDir) =>
|
|
23710
|
-
var endpointFile = (stateDir, id) =>
|
|
23960
|
+
import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
23961
|
+
import { join as join53 } from "path";
|
|
23962
|
+
var secretDir = (stateDir) => join53(stateDir, "periscope-mcp");
|
|
23963
|
+
var secretFile = (stateDir, id) => join53(secretDir(stateDir), `${id}.json`);
|
|
23964
|
+
var endpointDir = (stateDir) => join53(stateDir, "periscope-endpoints");
|
|
23965
|
+
var endpointFile = (stateDir, id) => join53(endpointDir(stateDir), `${id}.json`);
|
|
23711
23966
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23712
|
-
|
|
23967
|
+
mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
23713
23968
|
const secret = randomBytes3(32).toString("base64url");
|
|
23714
23969
|
const file = secretFile(stateDir, ticketRecordId);
|
|
23715
|
-
|
|
23970
|
+
writeFileSync37(file, `${JSON.stringify({ secret })}
|
|
23716
23971
|
`, { mode: 384 });
|
|
23717
23972
|
chmodSync5(file, 384);
|
|
23718
23973
|
return secret;
|
|
23719
23974
|
}
|
|
23720
23975
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23721
23976
|
try {
|
|
23722
|
-
const v = JSON.parse(
|
|
23977
|
+
const v = JSON.parse(readFileSync45(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
23723
23978
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
23724
23979
|
} catch {
|
|
23725
23980
|
return void 0;
|
|
23726
23981
|
}
|
|
23727
23982
|
}
|
|
23728
23983
|
function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
23729
|
-
|
|
23984
|
+
rmSync24(secretFile(stateDir, ticketRecordId), { force: true });
|
|
23730
23985
|
}
|
|
23731
23986
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
23732
23987
|
try {
|
|
23733
|
-
return JSON.parse(
|
|
23988
|
+
return JSON.parse(readFileSync45(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
23734
23989
|
} catch {
|
|
23735
23990
|
return void 0;
|
|
23736
23991
|
}
|
|
23737
23992
|
}
|
|
23738
23993
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
23739
|
-
|
|
23740
|
-
|
|
23994
|
+
mkdirSync37(endpointDir(stateDir), { recursive: true });
|
|
23995
|
+
writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
23741
23996
|
`);
|
|
23742
23997
|
}
|
|
23743
23998
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
23744
|
-
|
|
23999
|
+
rmSync24(endpointFile(stateDir, ticketRecordId), { force: true });
|
|
23745
24000
|
}
|
|
23746
24001
|
function listPeriscopeEndpoints(stateDir) {
|
|
23747
24002
|
const dir = endpointDir(stateDir);
|
|
23748
|
-
if (!
|
|
24003
|
+
if (!existsSync49(dir)) return [];
|
|
23749
24004
|
const out = [];
|
|
23750
24005
|
for (const name of readdirSync19(dir)) {
|
|
23751
24006
|
if (!name.endsWith(".json")) continue;
|
|
@@ -23833,10 +24088,10 @@ async function realAncestor(p) {
|
|
|
23833
24088
|
}
|
|
23834
24089
|
if (isLink) {
|
|
23835
24090
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
23836
|
-
cur = resolve6(
|
|
24091
|
+
cur = resolve6(dirname32(cur), await readlink(cur));
|
|
23837
24092
|
continue;
|
|
23838
24093
|
}
|
|
23839
|
-
const parent =
|
|
24094
|
+
const parent = dirname32(cur);
|
|
23840
24095
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
23841
24096
|
tail3.push(basename15(cur));
|
|
23842
24097
|
cur = parent;
|
|
@@ -23848,13 +24103,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
23848
24103
|
let abs;
|
|
23849
24104
|
if (isAbsolute5(requested)) {
|
|
23850
24105
|
abs = resolve6(requested);
|
|
23851
|
-
if (isInside(root, abs)) abs =
|
|
24106
|
+
if (isInside(root, abs)) abs = join54(realRoot, relative2(root, abs));
|
|
23852
24107
|
} else {
|
|
23853
24108
|
abs = resolve6(realRoot, requested);
|
|
23854
24109
|
}
|
|
23855
24110
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23856
24111
|
const { real, rest } = await realAncestor(abs);
|
|
23857
|
-
const final = rest ?
|
|
24112
|
+
const final = rest ? join54(real, rest) : real;
|
|
23858
24113
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23859
24114
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
23860
24115
|
return final;
|
|
@@ -23922,7 +24177,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23922
24177
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
23923
24178
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
23924
24179
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
23925
|
-
const size = (await lstat(
|
|
24180
|
+
const size = (await lstat(join54(abs, e.name))).size;
|
|
23926
24181
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
23927
24182
|
}));
|
|
23928
24183
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -23947,7 +24202,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23947
24202
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
23948
24203
|
const abs = await inTree(path);
|
|
23949
24204
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
23950
|
-
await mkdir(
|
|
24205
|
+
await mkdir(dirname32(abs), { recursive: true });
|
|
23951
24206
|
await inTree(path);
|
|
23952
24207
|
await writeFile(abs, content);
|
|
23953
24208
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -23987,8 +24242,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23987
24242
|
inputSchema: { path: z.string() },
|
|
23988
24243
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
23989
24244
|
}, async ({ path }) => {
|
|
23990
|
-
const parent = await inTree(
|
|
23991
|
-
const abs =
|
|
24245
|
+
const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
|
|
24246
|
+
const abs = join54(parent, basename15(path));
|
|
23992
24247
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
23993
24248
|
const st = await lstat(abs).catch(() => void 0);
|
|
23994
24249
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -24109,7 +24364,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
24109
24364
|
}
|
|
24110
24365
|
|
|
24111
24366
|
// src/cli.ts
|
|
24112
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
24367
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath(import.meta.url)), "..");
|
|
24113
24368
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
24114
24369
|
var CYCLE_SECONDS2 = 120;
|
|
24115
24370
|
var STALE_CYCLES = 3;
|
|
@@ -24367,6 +24622,7 @@ if (!command) usage(argv.length ? `unknown option "${argv[0]}"` : void 0);
|
|
|
24367
24622
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
24368
24623
|
"passenger-sync-daemon",
|
|
24369
24624
|
"periscope-proxy",
|
|
24625
|
+
"intel",
|
|
24370
24626
|
"agents",
|
|
24371
24627
|
"connect",
|
|
24372
24628
|
"credential",
|
|
@@ -24478,7 +24734,7 @@ if (command === "provision") {
|
|
|
24478
24734
|
process.exit(outcome.code);
|
|
24479
24735
|
}
|
|
24480
24736
|
if (command === "pool-member") {
|
|
24481
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
24737
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync46(0, "utf8"));
|
|
24482
24738
|
if (typeof pool === "string") {
|
|
24483
24739
|
process.stderr.write(`crew pool-member: ${pool}
|
|
24484
24740
|
`);
|
|
@@ -24512,6 +24768,14 @@ if (command === "passenger-sync-daemon") {
|
|
|
24512
24768
|
process.exit(2);
|
|
24513
24769
|
}
|
|
24514
24770
|
runProxyFromEnv(dropFile);
|
|
24771
|
+
} else if (command === "intel") {
|
|
24772
|
+
const [, sub, bridgeRoute, bridgeTicket] = positional;
|
|
24773
|
+
const stateDir = value("state-dir");
|
|
24774
|
+
if (sub !== "bridge" || !bridgeRoute || !bridgeTicket || !stateDir) {
|
|
24775
|
+
process.stderr.write("usage: crew intel bridge <route> <ticket number> --state-dir DIR (registered by a lane run, not run by hand)\n");
|
|
24776
|
+
process.exit(2);
|
|
24777
|
+
}
|
|
24778
|
+
await runBridge(stateDir, bridgeRoute, bridgeTicket);
|
|
24515
24779
|
} else {
|
|
24516
24780
|
let requireArmed = function(what) {
|
|
24517
24781
|
if (dryRun || route2.enabled) return;
|
|
@@ -24713,7 +24977,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24713
24977
|
} else {
|
|
24714
24978
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
24715
24979
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
24716
|
-
if (configless && !
|
|
24980
|
+
if (configless && !existsSync50(findConfigFile(CREW_HOME))) {
|
|
24717
24981
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
24718
24982
|
} else {
|
|
24719
24983
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -24728,7 +24992,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24728
24992
|
throw e;
|
|
24729
24993
|
}
|
|
24730
24994
|
if (command === "credential") {
|
|
24731
|
-
const r = runCredential(positional[1],
|
|
24995
|
+
const r = runCredential(positional[1], readFileSync46(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
24732
24996
|
process.stdout.write(r.out);
|
|
24733
24997
|
process.stderr.write(r.err);
|
|
24734
24998
|
process.exit(r.code);
|
|
@@ -24740,9 +25004,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
24740
25004
|
forges: cfg.ship.forges,
|
|
24741
25005
|
stateDir: cfg.ship.stateDir,
|
|
24742
25006
|
configFile: cfg.configFile,
|
|
24743
|
-
readFile: (p) =>
|
|
24744
|
-
writeFile: (p, t) =>
|
|
24745
|
-
stdin: () =>
|
|
25007
|
+
readFile: (p) => readFileSync46(p, "utf8"),
|
|
25008
|
+
writeFile: (p, t) => writeFileSync38(p, t),
|
|
25009
|
+
stdin: () => readFileSync46(0, "utf8"),
|
|
24746
25010
|
env: process.env
|
|
24747
25011
|
}
|
|
24748
25012
|
);
|
|
@@ -25035,8 +25299,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
25035
25299
|
}
|
|
25036
25300
|
if (host === "linux" && hasSystemd) {
|
|
25037
25301
|
const label = labelFor(CREW_HOME, "run");
|
|
25038
|
-
const staleTimer =
|
|
25039
|
-
if (
|
|
25302
|
+
const staleTimer = join55(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25303
|
+
if (existsSync50(staleTimer)) {
|
|
25040
25304
|
process.stdout.write(
|
|
25041
25305
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
25042
25306
|
`
|
|
@@ -25063,7 +25327,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25063
25327
|
const tracker = new Tracker(c, cfg.ship);
|
|
25064
25328
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
25065
25329
|
for (const r of await resolvedRepos(c)) {
|
|
25066
|
-
if (!
|
|
25330
|
+
if (!existsSync50(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
25067
25331
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
25068
25332
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
25069
25333
|
}
|
|
@@ -25090,7 +25354,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25090
25354
|
}
|
|
25091
25355
|
async function doctorFix(o) {
|
|
25092
25356
|
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))));
|
|
25093
|
-
const unitExists = (p) =>
|
|
25357
|
+
const unitExists = (p) => existsSync50(p);
|
|
25094
25358
|
const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
25095
25359
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
25096
25360
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -25107,7 +25371,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25107
25371
|
for (const a of plan.actions) {
|
|
25108
25372
|
if (a.kind === "enable") {
|
|
25109
25373
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
25110
|
-
|
|
25374
|
+
writeFileSync38(cfg.configFile, planEnable(readFileSync46(cfg.configFile, "utf8"), a.route));
|
|
25111
25375
|
enabledSound.add(a.route);
|
|
25112
25376
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
25113
25377
|
} else {
|
|
@@ -26246,7 +26510,7 @@ ${cu.tail.trimEnd()}
|
|
|
26246
26510
|
process.exit(1);
|
|
26247
26511
|
}
|
|
26248
26512
|
requireArmed("run the daemon loop");
|
|
26249
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
26513
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join55(CREW_HOME, "src"), join55(CREW_HOME, "dist"), join55(CREW_HOME, "bin")];
|
|
26250
26514
|
const configPaths = daemonConfigPaths(cfg);
|
|
26251
26515
|
watchPaths.push(...configPaths);
|
|
26252
26516
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -26551,10 +26815,10 @@ ${cu.tail.trimEnd()}
|
|
|
26551
26815
|
`);
|
|
26552
26816
|
if (!dryRun) {
|
|
26553
26817
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
26554
|
-
const raw =
|
|
26818
|
+
const raw = existsSync50(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
|
|
26555
26819
|
raw.agentSkills = result.agentSkills;
|
|
26556
|
-
|
|
26557
|
-
|
|
26820
|
+
mkdirSync38(dirOf2(resolvedPath), { recursive: true });
|
|
26821
|
+
writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
26558
26822
|
`);
|
|
26559
26823
|
}
|
|
26560
26824
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -26577,7 +26841,7 @@ ${cu.tail.trimEnd()}
|
|
|
26577
26841
|
throw e;
|
|
26578
26842
|
}
|
|
26579
26843
|
const oldName = cfg.ship.name;
|
|
26580
|
-
|
|
26844
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), newName));
|
|
26581
26845
|
cfg.ship.name = newName;
|
|
26582
26846
|
let renameMachineId;
|
|
26583
26847
|
try {
|
|
@@ -26624,14 +26888,14 @@ ${cu.tail.trimEnd()}
|
|
|
26624
26888
|
process.exit(2);
|
|
26625
26889
|
};
|
|
26626
26890
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
26627
|
-
const configText =
|
|
26891
|
+
const configText = readFileSync46(cfg.configFile, "utf8");
|
|
26628
26892
|
if (sub === "check") {
|
|
26629
26893
|
try {
|
|
26630
26894
|
const routeArg = value("route") ?? positional[2];
|
|
26631
26895
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
26632
26896
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
26633
26897
|
const named2 = value("route") ? void 0 : positional[3];
|
|
26634
|
-
const dir = named2 ? repoDirOf(configText,
|
|
26898
|
+
const dir = named2 ? repoDirOf(configText, dirname33(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
26635
26899
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
26636
26900
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
26637
26901
|
runReposCheck(dir, {
|
|
@@ -26639,7 +26903,8 @@ ${cu.tail.trimEnd()}
|
|
|
26639
26903
|
labels: { ...found.labels, ...o?.labels },
|
|
26640
26904
|
release: mergeRouteRelease(found.release, o?.release),
|
|
26641
26905
|
branch: { ...found.branch, ...o?.branch },
|
|
26642
|
-
worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName }
|
|
26906
|
+
worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName },
|
|
26907
|
+
mapTokenBudget: cfg.ship.mapTokenBudget
|
|
26643
26908
|
});
|
|
26644
26909
|
} catch (e) {
|
|
26645
26910
|
if (e instanceof ReposError) {
|
|
@@ -26654,9 +26919,9 @@ ${cu.tail.trimEnd()}
|
|
|
26654
26919
|
if (sub === "hooks") {
|
|
26655
26920
|
try {
|
|
26656
26921
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
26657
|
-
const dir = repoDirOf(configText,
|
|
26658
|
-
if (!
|
|
26659
|
-
const files = { exists: (r) =>
|
|
26922
|
+
const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
|
|
26923
|
+
if (!existsSync50(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
26924
|
+
const files = { exists: (r) => existsSync50(resolvePath(dir, r)), list: (r) => {
|
|
26660
26925
|
try {
|
|
26661
26926
|
return readdirSync20(resolvePath(dir, r));
|
|
26662
26927
|
} catch {
|
|
@@ -26664,7 +26929,7 @@ ${cu.tail.trimEnd()}
|
|
|
26664
26929
|
}
|
|
26665
26930
|
}, read: (r) => {
|
|
26666
26931
|
try {
|
|
26667
|
-
return
|
|
26932
|
+
return readFileSync46(resolvePath(dir, r), "utf8");
|
|
26668
26933
|
} catch {
|
|
26669
26934
|
return void 0;
|
|
26670
26935
|
}
|
|
@@ -26706,19 +26971,19 @@ ${cu.tail.trimEnd()}
|
|
|
26706
26971
|
let cleared;
|
|
26707
26972
|
if (clear2) {
|
|
26708
26973
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
26709
|
-
if (plan.removed.length > 0)
|
|
26974
|
+
if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
|
|
26710
26975
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
26711
26976
|
}
|
|
26712
26977
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
26713
26978
|
if (write === "route") {
|
|
26714
26979
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
26715
|
-
|
|
26980
|
+
writeFileSync38(cfg.configFile, plan.text);
|
|
26716
26981
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
26717
26982
|
} else if (write === "repo") {
|
|
26718
26983
|
const have = existingRepoConfig(files);
|
|
26719
26984
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
26720
26985
|
const file = resolvePath(dir, ".crew.yaml");
|
|
26721
|
-
|
|
26986
|
+
writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
26722
26987
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
26723
26988
|
}
|
|
26724
26989
|
if (flag("json")) {
|
|
@@ -26774,7 +27039,7 @@ ${cu.tail.trimEnd()}
|
|
|
26774
27039
|
if (sub === "list" && !routeName) usage2();
|
|
26775
27040
|
if (sub === "add") {
|
|
26776
27041
|
try {
|
|
26777
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
27042
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync50(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
26778
27043
|
routeName = a.route;
|
|
26779
27044
|
addPath = a.path;
|
|
26780
27045
|
addName = a.name;
|
|
@@ -26804,7 +27069,7 @@ ${cu.tail.trimEnd()}
|
|
|
26804
27069
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
26805
27070
|
if (sub === "list" || sub === "add") {
|
|
26806
27071
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
26807
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
27072
|
+
if (liveRoute?.resolved?.reposModelId && existsSync50(resolvedFile) && !dryRun) {
|
|
26808
27073
|
try {
|
|
26809
27074
|
await hydrateApiKeys([liveRoute]);
|
|
26810
27075
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -26813,16 +27078,16 @@ ${cu.tail.trimEnd()}
|
|
|
26813
27078
|
}
|
|
26814
27079
|
}
|
|
26815
27080
|
let resolvedRemotes;
|
|
26816
|
-
if (
|
|
27081
|
+
if (existsSync50(resolvedFile)) {
|
|
26817
27082
|
try {
|
|
26818
|
-
resolvedRemotes = JSON.parse(
|
|
27083
|
+
resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
26819
27084
|
} catch {
|
|
26820
27085
|
}
|
|
26821
27086
|
}
|
|
26822
27087
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
26823
27088
|
const text2 = configText;
|
|
26824
|
-
const base =
|
|
26825
|
-
const isCheckout = (d) =>
|
|
27089
|
+
const base = dirname33(cfg.configFile);
|
|
27090
|
+
const isCheckout = (d) => existsSync50(`${d}/.git`);
|
|
26826
27091
|
try {
|
|
26827
27092
|
if (sub === "list") {
|
|
26828
27093
|
if (flag("json")) {
|
|
@@ -26832,11 +27097,11 @@ ${cu.tail.trimEnd()}
|
|
|
26832
27097
|
base,
|
|
26833
27098
|
resolved: resolvedRemotes,
|
|
26834
27099
|
originOf,
|
|
26835
|
-
exists: (d) =>
|
|
27100
|
+
exists: (d) => existsSync50(d),
|
|
26836
27101
|
isCheckout,
|
|
26837
27102
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
26838
27103
|
try {
|
|
26839
|
-
return Object.keys(parseRepoConfig(
|
|
27104
|
+
return Object.keys(parseRepoConfig(readFileSync46(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
26840
27105
|
} catch {
|
|
26841
27106
|
return false;
|
|
26842
27107
|
}
|
|
@@ -26853,7 +27118,7 @@ ${cu.tail.trimEnd()}
|
|
|
26853
27118
|
for (const r of repos) {
|
|
26854
27119
|
const origin2 = originOf(r.dir);
|
|
26855
27120
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
26856
|
-
const note = !
|
|
27121
|
+
const note = !existsSync50(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
26857
27122
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
26858
27123
|
`);
|
|
26859
27124
|
}
|
|
@@ -26865,9 +27130,9 @@ ${cu.tail.trimEnd()}
|
|
|
26865
27130
|
` : `${human}
|
|
26866
27131
|
`);
|
|
26867
27132
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
26868
|
-
if (
|
|
27133
|
+
if (existsSync50(plan2.target) && (existsSync50(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
26869
27134
|
throw new ReposError(
|
|
26870
|
-
|
|
27135
|
+
existsSync50(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
26871
27136
|
);
|
|
26872
27137
|
}
|
|
26873
27138
|
if (dryRun) {
|
|
@@ -26885,14 +27150,14 @@ ${cu.tail.trimEnd()}
|
|
|
26885
27150
|
throw new ReposError(`${e.message}${authHint}
|
|
26886
27151
|
crew.yaml was not changed`);
|
|
26887
27152
|
}
|
|
26888
|
-
|
|
27153
|
+
writeFileSync38(cfg.configFile, plan2.text);
|
|
26889
27154
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
26890
27155
|
`);
|
|
26891
27156
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
26892
27157
|
break;
|
|
26893
27158
|
}
|
|
26894
27159
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
26895
|
-
const top =
|
|
27160
|
+
const top = existsSync50(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
26896
27161
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
26897
27162
|
const origin = originOf(top);
|
|
26898
27163
|
const plan = planRepoAdd({
|
|
@@ -26944,15 +27209,15 @@ ${cu.tail.trimEnd()}
|
|
|
26944
27209
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
26945
27210
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
26946
27211
|
`);
|
|
26947
|
-
if (
|
|
26948
|
-
const raw = JSON.parse(
|
|
27212
|
+
if (existsSync50(resolvedFile)) {
|
|
27213
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
26949
27214
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
26950
27215
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
26951
|
-
|
|
27216
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26952
27217
|
`);
|
|
26953
27218
|
}
|
|
26954
27219
|
}
|
|
26955
|
-
|
|
27220
|
+
writeFileSync38(cfg.configFile, plan.text);
|
|
26956
27221
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
26957
27222
|
`);
|
|
26958
27223
|
} catch (e) {
|
|
@@ -27323,7 +27588,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27323
27588
|
process.exit(2);
|
|
27324
27589
|
}
|
|
27325
27590
|
if (found.shipNameAdopted && !dryRun) {
|
|
27326
|
-
|
|
27591
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
27327
27592
|
cfg.ship.name = found.shipNameAdopted;
|
|
27328
27593
|
}
|
|
27329
27594
|
if (found.shipRow) {
|
|
@@ -27366,10 +27631,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27366
27631
|
}
|
|
27367
27632
|
}
|
|
27368
27633
|
let previous;
|
|
27369
|
-
const hadPreviousFile =
|
|
27634
|
+
const hadPreviousFile = existsSync50(resolvedPath);
|
|
27370
27635
|
if (hadPreviousFile) {
|
|
27371
27636
|
try {
|
|
27372
|
-
previous = JSON.parse(
|
|
27637
|
+
previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
|
|
27373
27638
|
} catch {
|
|
27374
27639
|
}
|
|
27375
27640
|
}
|
|
@@ -27499,10 +27764,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27499
27764
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
27500
27765
|
`);
|
|
27501
27766
|
} else {
|
|
27502
|
-
|
|
27767
|
+
mkdirSync38(dirname33(resolvedPath), { recursive: true });
|
|
27503
27768
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
27504
27769
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
27505
|
-
|
|
27770
|
+
writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
27506
27771
|
`);
|
|
27507
27772
|
renameSync6(tmpPath, resolvedPath);
|
|
27508
27773
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -27548,8 +27813,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27548
27813
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
27549
27814
|
`);
|
|
27550
27815
|
} else {
|
|
27551
|
-
|
|
27552
|
-
|
|
27816
|
+
mkdirSync38(dirname33(apiKeyFile), { recursive: true });
|
|
27817
|
+
writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
27553
27818
|
`, { mode: 384 });
|
|
27554
27819
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
27555
27820
|
`);
|
|
@@ -28231,6 +28496,33 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28231
28496
|
}
|
|
28232
28497
|
const pathWt = applyRunWorktree(repoWt.dir, repoWt.config, planWt);
|
|
28233
28498
|
emit.emit(describeRunWorktreePlan(planWt));
|
|
28499
|
+
if (pathWt && planIsUsable(planWt) && repoWt.config.hooks.intel) {
|
|
28500
|
+
const shellWt = resolveShell(repoWt.config.shell ?? cfg.ship.shell);
|
|
28501
|
+
const paragraph = await startWorktreeIntel({
|
|
28502
|
+
worktree: pathWt,
|
|
28503
|
+
route: routeWt.route,
|
|
28504
|
+
number: String(nWt),
|
|
28505
|
+
stateDir: cfg.ship.stateDir,
|
|
28506
|
+
ticketTag: String(ticketWt.issue_tag ?? ticketWt.issue_id ?? `ISSUE-${nWt}`),
|
|
28507
|
+
branch: planWt.branch ?? "",
|
|
28508
|
+
startupSeconds: cfg.ship.intelStartupSeconds,
|
|
28509
|
+
prepare: async (path) => {
|
|
28510
|
+
for (const f of repoWt.config.worktrees.copy) {
|
|
28511
|
+
const src = join55(repoWt.dir, f);
|
|
28512
|
+
if (!existsSync50(src) || existsSync50(join55(path, f))) continue;
|
|
28513
|
+
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28514
|
+
copyFileSync2(src, join55(path, f));
|
|
28515
|
+
}
|
|
28516
|
+
const setup = repoWt.config.hooks.setup;
|
|
28517
|
+
return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
|
|
28518
|
+
},
|
|
28519
|
+
runHook: async (name, cwd, env) => {
|
|
28520
|
+
const script = repoWt.config.hooks[name];
|
|
28521
|
+
return script ? runScript(script, { cwd, env, shell: shellWt, timeoutMs: name === "intel" ? 6e4 : void 0 }) : null;
|
|
28522
|
+
}
|
|
28523
|
+
});
|
|
28524
|
+
emit.emit(paragraph);
|
|
28525
|
+
}
|
|
28234
28526
|
if (pathWt) process.stdout.write(`${pathWt}
|
|
28235
28527
|
`);
|
|
28236
28528
|
if (!planIsUsable(planWt)) process.exitCode = 1;
|
|
@@ -28430,7 +28722,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28430
28722
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
28431
28723
|
const resumed = await runPeriscopeResume({
|
|
28432
28724
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
28433
|
-
worktreeExists: (p) =>
|
|
28725
|
+
worktreeExists: (p) => existsSync50(p),
|
|
28434
28726
|
prepareDropFile,
|
|
28435
28727
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
28436
28728
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -28526,10 +28818,10 @@ ${where.join("\n")}`);
|
|
|
28526
28818
|
},
|
|
28527
28819
|
prepare: async (path) => {
|
|
28528
28820
|
for (const f of repoP.config.worktrees.copy) {
|
|
28529
|
-
const src =
|
|
28530
|
-
if (!
|
|
28531
|
-
|
|
28532
|
-
copyFileSync2(src,
|
|
28821
|
+
const src = join55(repoP.dir, f);
|
|
28822
|
+
if (!existsSync50(src)) continue;
|
|
28823
|
+
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28824
|
+
copyFileSync2(src, join55(path, f));
|
|
28533
28825
|
}
|
|
28534
28826
|
const setup = repoP.config.hooks.setup;
|
|
28535
28827
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -28839,7 +29131,7 @@ ${where.join("\n")}`);
|
|
|
28839
29131
|
};
|
|
28840
29132
|
const droppedRoutes = [];
|
|
28841
29133
|
try {
|
|
28842
|
-
const rawCfg = parseYaml2(
|
|
29134
|
+
const rawCfg = parseYaml2(readFileSync46(cfg.configFile, "utf8"));
|
|
28843
29135
|
for (const r of rawCfg?.routes ?? []) {
|
|
28844
29136
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
28845
29137
|
try {
|
|
@@ -28892,8 +29184,8 @@ ${where.join("\n")}`);
|
|
|
28892
29184
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
28893
29185
|
break;
|
|
28894
29186
|
}
|
|
28895
|
-
|
|
28896
|
-
|
|
29187
|
+
mkdirSync38(cfg.ship.stateDir, { recursive: true });
|
|
29188
|
+
writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
28897
29189
|
`);
|
|
28898
29190
|
process.stdout.write(
|
|
28899
29191
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -28958,7 +29250,7 @@ ${where.join("\n")}`);
|
|
|
28958
29250
|
}
|
|
28959
29251
|
case "log": {
|
|
28960
29252
|
try {
|
|
28961
|
-
const text2 =
|
|
29253
|
+
const text2 = readFileSync46(cfg.ship.logFile, "utf8").split("\n");
|
|
28962
29254
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
28963
29255
|
`);
|
|
28964
29256
|
} catch {
|
|
@@ -28981,8 +29273,8 @@ ${where.join("\n")}`);
|
|
|
28981
29273
|
};
|
|
28982
29274
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
28983
29275
|
const tracker = new Tracker(route2, cfg.ship);
|
|
28984
|
-
const configText =
|
|
28985
|
-
const cfgBase =
|
|
29276
|
+
const configText = readFileSync46(cfg.configFile, "utf8");
|
|
29277
|
+
const cfgBase = dirname33(cfg.configFile);
|
|
28986
29278
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
28987
29279
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
28988
29280
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -29010,7 +29302,7 @@ ${where.join("\n")}`);
|
|
|
29010
29302
|
}
|
|
29011
29303
|
if (configured2.some((r) => r.dir === dir)) {
|
|
29012
29304
|
try {
|
|
29013
|
-
|
|
29305
|
+
writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
29014
29306
|
} catch (e) {
|
|
29015
29307
|
if (e instanceof ReposError) fail2(e.message);
|
|
29016
29308
|
throw e;
|
|
@@ -29018,29 +29310,29 @@ ${where.join("\n")}`);
|
|
|
29018
29310
|
}
|
|
29019
29311
|
if (row) {
|
|
29020
29312
|
await tracker.deleteRepoRow(row.id);
|
|
29021
|
-
if (
|
|
29022
|
-
const raw = JSON.parse(
|
|
29313
|
+
if (existsSync50(resolvedFile)) {
|
|
29314
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29023
29315
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
29024
29316
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
29025
|
-
|
|
29317
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
29026
29318
|
`);
|
|
29027
29319
|
}
|
|
29028
29320
|
}
|
|
29029
|
-
if (
|
|
29321
|
+
if (existsSync50(join55(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29030
29322
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29031
29323
|
break;
|
|
29032
29324
|
}
|
|
29033
29325
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29034
|
-
const fresh = !
|
|
29035
|
-
if (
|
|
29326
|
+
const fresh = !existsSync50(join55(dir, ".git"));
|
|
29327
|
+
if (existsSync50(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29036
29328
|
if (dryRun) {
|
|
29037
29329
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
29038
29330
|
break;
|
|
29039
29331
|
}
|
|
29040
29332
|
if (fresh) {
|
|
29041
|
-
|
|
29042
|
-
|
|
29043
|
-
|
|
29333
|
+
mkdirSync38(dir, { recursive: true });
|
|
29334
|
+
writeFileSync38(join55(dir, "README.md"), SAMPLE_README);
|
|
29335
|
+
writeFileSync38(join55(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
29044
29336
|
const g = (...a) => {
|
|
29045
29337
|
const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
|
|
29046
29338
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -29058,17 +29350,17 @@ ${where.join("\n")}`);
|
|
|
29058
29350
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
29059
29351
|
});
|
|
29060
29352
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
29061
|
-
if (
|
|
29062
|
-
const raw = JSON.parse(
|
|
29353
|
+
if (existsSync50(resolvedFile)) {
|
|
29354
|
+
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29063
29355
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
29064
29356
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
29065
|
-
|
|
29357
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
29066
29358
|
`);
|
|
29067
29359
|
}
|
|
29068
29360
|
}
|
|
29069
29361
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
29070
29362
|
try {
|
|
29071
|
-
|
|
29363
|
+
writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
29072
29364
|
} catch (e) {
|
|
29073
29365
|
if (e instanceof ReposError) fail2(e.message);
|
|
29074
29366
|
throw e;
|
|
@@ -29414,7 +29706,7 @@ watermark: ${state.watermark()}
|
|
|
29414
29706
|
}
|
|
29415
29707
|
for (const r of repos) {
|
|
29416
29708
|
const remote = r.config.branch.remote;
|
|
29417
|
-
if (
|
|
29709
|
+
if (existsSync50(r.dir)) {
|
|
29418
29710
|
pushAuth.push({
|
|
29419
29711
|
route: c.route,
|
|
29420
29712
|
repo: r.name,
|
|
@@ -29453,6 +29745,7 @@ watermark: ${state.watermark()}
|
|
|
29453
29745
|
base: doctorBaseReport(r, r.config.branch.base, baseHealth),
|
|
29454
29746
|
hooks: r.config.hooks,
|
|
29455
29747
|
onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
|
|
29748
|
+
mapFile: mapFilePath(r.config.hooks.map) ? inspectMapFile(r.dir, mapFilePath(r.config.hooks.map)) : void 0,
|
|
29456
29749
|
intelSyntax: r.config.hooks.intel && !intelFlavor(r.config.hooks.intel) ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0,
|
|
29457
29750
|
intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: [
|
|
29458
29751
|
...missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)),
|
|
@@ -29509,7 +29802,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29509
29802
|
}
|
|
29510
29803
|
try {
|
|
29511
29804
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
29512
|
-
const evSize =
|
|
29805
|
+
const evSize = existsSync50(evFile) ? statSync16(evFile).size : 0;
|
|
29513
29806
|
process.stdout.write(
|
|
29514
29807
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
29515
29808
|
` : "events log: ok\n"
|
|
@@ -29520,7 +29813,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29520
29813
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
29521
29814
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
29522
29815
|
try {
|
|
29523
|
-
return
|
|
29816
|
+
return readFileSync46(p, "utf8");
|
|
29524
29817
|
} catch {
|
|
29525
29818
|
return null;
|
|
29526
29819
|
}
|