@tablation/crew 0.123.1 → 0.125.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 +261 -122
- package/dist/sandbox-intel-bridge.cjs +25189 -0
- package/package.json +1 -1
- package/prompts/default/skills/sandbox-setup.md +3 -0
package/dist/cli.js
CHANGED
|
@@ -234,14 +234,8 @@ function ensureLspPlugin(stateDir, flavor, mainCheckout) {
|
|
|
234
234
|
const def = LSP_SERVERS[flavor];
|
|
235
235
|
const dir = join2(stateDir, "intel", "plugins", flavor);
|
|
236
236
|
const command2 = existsSync3(lspLocalBin(mainCheckout, flavor)) ? `\${CLAUDE_PROJECT_DIR}/node_modules/.bin/${def.bin}` : def.bin;
|
|
237
|
-
const
|
|
238
|
-
[join2(".claude-plugin", "plugin.json"), { name: `crew-${flavor}`, version: "1.0.0", description: `Language server for the ${flavor} intel flavor, generated by crew` }],
|
|
239
|
-
[".lsp.json", { [def.bin]: { command: command2, args: [...def.args], extensionToLanguage: def.extensionToLanguage } }]
|
|
240
|
-
];
|
|
241
|
-
for (const [rel, doc] of files) {
|
|
237
|
+
for (const [rel, text2] of lspPluginFiles(flavor, command2)) {
|
|
242
238
|
const path = join2(dir, rel);
|
|
243
|
-
const text2 = `${JSON.stringify(doc, null, 2)}
|
|
244
|
-
`;
|
|
245
239
|
let old;
|
|
246
240
|
try {
|
|
247
241
|
old = readFileSync2(path, "utf8");
|
|
@@ -253,6 +247,21 @@ function ensureLspPlugin(stateDir, flavor, mainCheckout) {
|
|
|
253
247
|
}
|
|
254
248
|
return dir;
|
|
255
249
|
}
|
|
250
|
+
function lspPluginFiles(flavor, command2) {
|
|
251
|
+
const def = LSP_SERVERS[flavor];
|
|
252
|
+
const files = [
|
|
253
|
+
[join2(".claude-plugin", "plugin.json"), { name: `crew-${flavor}`, version: "1.0.0", description: `Language server for the ${flavor} intel flavor, generated by crew` }],
|
|
254
|
+
[".lsp.json", { [def.bin]: { command: command2, args: [...def.args], extensionToLanguage: def.extensionToLanguage } }]
|
|
255
|
+
];
|
|
256
|
+
return files.map(([rel, doc]) => [rel, `${JSON.stringify(doc, null, 2)}
|
|
257
|
+
`]);
|
|
258
|
+
}
|
|
259
|
+
var SANDBOX_INTEL_DIR = "/sandbox/.crew/intel";
|
|
260
|
+
function sandboxLspPlugin(flavor) {
|
|
261
|
+
const dir = `${SANDBOX_INTEL_DIR}/plugins/${flavor}`;
|
|
262
|
+
const command2 = `\${CLAUDE_PROJECT_DIR}/node_modules/.bin/${LSP_SERVERS[flavor].bin}`;
|
|
263
|
+
return { dir, files: lspPluginFiles(flavor, command2).map(([rel, content]) => ({ path: `${dir}/${rel}`, content })) };
|
|
264
|
+
}
|
|
256
265
|
function lspToolsParagraph(harness, omlx = false) {
|
|
257
266
|
if (harness !== "claude") return "No symbol service is available for this harness: this repository's `intel` flavor serves only the Claude Code harness's own LSP tool.";
|
|
258
267
|
if (omlx) return "The harness's LSP tool is off for this launcher, so no symbol service is available for this run.";
|
|
@@ -406,7 +415,7 @@ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
|
|
|
406
415
|
|
|
407
416
|
// src/intel-serena.ts
|
|
408
417
|
import { spawn, spawnSync } from "child_process";
|
|
409
|
-
import { closeSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
418
|
+
import { closeSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, rmSync, symlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
410
419
|
import { dirname as dirname3, join as join5 } from "path";
|
|
411
420
|
|
|
412
421
|
// src/ports.ts
|
|
@@ -575,11 +584,15 @@ function startSerena(d, s) {
|
|
|
575
584
|
const p = serenaPaths(s.stateDir, s.route, String(s.ticket));
|
|
576
585
|
try {
|
|
577
586
|
prepareFolder(d, p.home, p.project, s.worktree);
|
|
587
|
+
if (s.languageServers && d.exists(s.languageServers) && d.link) {
|
|
588
|
+
const dest = join5(p.home, "language_servers");
|
|
589
|
+
if (!d.exists(dest)) d.link(s.languageServers, dest);
|
|
590
|
+
}
|
|
578
591
|
const mainCache = join5(p.mainProject, "cache");
|
|
579
592
|
const cache = join5(p.project, "cache");
|
|
580
593
|
if (!d.exists(cache) && d.exists(mainCache)) d.copyDir(mainCache, cache);
|
|
581
594
|
try {
|
|
582
|
-
ensureMainIndex(d, s);
|
|
595
|
+
if (!s.noMainIndex) ensureMainIndex(d, s);
|
|
583
596
|
} catch {
|
|
584
597
|
}
|
|
585
598
|
const port = intelPortFor(s.ticket);
|
|
@@ -625,6 +638,7 @@ function realSerenaDeps(onPath2, extraEnv = process.env) {
|
|
|
625
638
|
spawnDetached: detached,
|
|
626
639
|
// `&&` so the stamp exists only for an index that finished; a killed one is retried next start.
|
|
627
640
|
spawnIndex: (args, env, log, stamp, stampText) => detached("sh", ["-c", 'serena "$@" && printf %s "$STAMP_TEXT" > "$STAMP_FILE"', "serena", ...args], { ...env, STAMP_FILE: stamp, STAMP_TEXT: stampText }, log),
|
|
641
|
+
link: (target, path) => symlinkSync(target, path),
|
|
628
642
|
alive: (pid) => {
|
|
629
643
|
try {
|
|
630
644
|
process.kill(pid, 0);
|
|
@@ -6290,8 +6304,9 @@ function attentionProblems(items, already) {
|
|
|
6290
6304
|
import { createHash as createHash6 } from "crypto";
|
|
6291
6305
|
import { spawn as spawn5 } from "child_process";
|
|
6292
6306
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
6293
|
-
import { mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6307
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6294
6308
|
import { dirname as dirname14, join as join32 } from "path";
|
|
6309
|
+
import { fileURLToPath } from "url";
|
|
6295
6310
|
import { tmpdir as tmpdir3 } from "os";
|
|
6296
6311
|
|
|
6297
6312
|
// src/passenger-containers.ts
|
|
@@ -6845,6 +6860,12 @@ exit 125
|
|
|
6845
6860
|
...CONTAINER_ENGINES.map((e) => `printf '%b' ${shSingle(body)} > ${shSingle(`${dir}/${e}`)} && chmod 755 ${shSingle(`${dir}/${e}`)}`)
|
|
6846
6861
|
].join(" && ");
|
|
6847
6862
|
}
|
|
6863
|
+
function writeFilesScript(files) {
|
|
6864
|
+
return files.map((f) => {
|
|
6865
|
+
const dir = f.path.slice(0, f.path.lastIndexOf("/")) || "/";
|
|
6866
|
+
return `mkdir -p ${shSingle(dir)} && printf %s ${shSingle(Buffer.from(f.content, "utf8").toString("base64"))} | base64 -d > ${shSingle(f.path)}`;
|
|
6867
|
+
}).join(" && ");
|
|
6868
|
+
}
|
|
6848
6869
|
function shSingle(s) {
|
|
6849
6870
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
6850
6871
|
}
|
|
@@ -6871,6 +6892,17 @@ function sandboxCheckoutSection(workDir, heading) {
|
|
|
6871
6892
|
"skill for setup, ports and the hand-off push."
|
|
6872
6893
|
];
|
|
6873
6894
|
}
|
|
6895
|
+
function sandboxCodeToolsSection(intel) {
|
|
6896
|
+
if (lspFlavor(intel)) return ["", `**Code tools.** On the Claude Code harness: ${lspToolsParagraph("claude").replace(/^The harness's/, "the harness's")}`];
|
|
6897
|
+
if (!wantsCodeBridge(intel)) return [];
|
|
6898
|
+
return [
|
|
6899
|
+
"",
|
|
6900
|
+
`**Code tools.** A \`${INTEL_SERVER_NAME}\` MCP server serves this repository's symbols from its language server. It offers at least: ${INTEL_MINIMUM_TOOLS.join("; ")}.`,
|
|
6901
|
+
"It lists only `code_status` until the symbol service is up. The service starts inside this sandbox",
|
|
6902
|
+
"the first time the session asks the server for tools, and it expects `setup` to have run, so run `setup`",
|
|
6903
|
+
"before your first lookup. `code_status` says whether it is starting, attached, or failed to start (and why)."
|
|
6904
|
+
];
|
|
6905
|
+
}
|
|
6874
6906
|
function sandboxEnv(ship) {
|
|
6875
6907
|
return ship.runner === "sandbox" ? { sandbox: { workDir: ship.sandbox?.workDir ?? DEFAULT_SANDBOX_WORKDIR } } : {};
|
|
6876
6908
|
}
|
|
@@ -7170,7 +7202,7 @@ function renderEnvironment(i) {
|
|
|
7170
7202
|
);
|
|
7171
7203
|
}
|
|
7172
7204
|
if (only && i.sandbox) {
|
|
7173
|
-
lines2.push(...sandboxCheckoutSection(i.sandbox.workDir, "## Your checkout"));
|
|
7205
|
+
lines2.push(...sandboxCheckoutSection(i.sandbox.workDir, "## Your checkout"), ...sandboxCodeToolsSection(only.config.hooks.intel));
|
|
7174
7206
|
} else if (only) {
|
|
7175
7207
|
lines2.push(...repoSection(only, key, "## Your worktree"));
|
|
7176
7208
|
} else {
|
|
@@ -7186,7 +7218,7 @@ function renderEnvironment(i) {
|
|
|
7186
7218
|
);
|
|
7187
7219
|
if (i.sandbox) lines2.push(...sandboxCheckoutSection(i.sandbox.workDir, "### Your checkout"));
|
|
7188
7220
|
for (const r of repos) {
|
|
7189
|
-
lines2.push(...i.sandbox ? ["", `### ${r.name}
|
|
7221
|
+
lines2.push(...i.sandbox ? ["", `### ${r.name}`, ...sandboxCodeToolsSection(r.config.hooks.intel)] : repoSection(r, key, `### ${r.name} \u2014 \`${r.dir}\``));
|
|
7190
7222
|
lines2.push("", `Hooks for ${r.name} \u2014 the only commands you should run here:`, "", hookTable(r.config));
|
|
7191
7223
|
lines2.push(...mapSection(r, `#### Repository map \u2014 ${r.name}`));
|
|
7192
7224
|
}
|
|
@@ -7214,6 +7246,8 @@ import {
|
|
|
7214
7246
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
7215
7247
|
var STATUS_TOOL = "code_status";
|
|
7216
7248
|
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.";
|
|
7249
|
+
var STATUS_STARTING = "The symbol service is starting inside this sandbox. Call `code_status` again in a few seconds; the symbol tools replace it once the service answers.";
|
|
7250
|
+
var statusFailed = (reason) => `The symbol service did not start (${reason}). Run the repository's \`setup\` hook first if you have not; this call has started a retry, so call \`code_status\` again in a few seconds; until it starts use text search.`;
|
|
7217
7251
|
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
7252
|
function intelDir(stateDir, route2, ticket) {
|
|
7219
7253
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
@@ -7253,6 +7287,8 @@ function createBridge(o) {
|
|
|
7253
7287
|
let lost = false;
|
|
7254
7288
|
let connecting = false;
|
|
7255
7289
|
let timer;
|
|
7290
|
+
let starting;
|
|
7291
|
+
let startError;
|
|
7256
7292
|
const notify2 = () => {
|
|
7257
7293
|
void server.sendToolListChanged().catch(() => {
|
|
7258
7294
|
});
|
|
@@ -7265,8 +7301,40 @@ function createBridge(o) {
|
|
|
7265
7301
|
});
|
|
7266
7302
|
notify2();
|
|
7267
7303
|
};
|
|
7268
|
-
const
|
|
7304
|
+
const statusText = () => lost ? STATUS_LOST : starting ? STATUS_STARTING : startError !== void 0 ? statusFailed(startError) : STATUS_WAITING;
|
|
7305
|
+
const status2 = () => ({ content: [{ type: "text", text: statusText() }] });
|
|
7306
|
+
const ensureStarted = () => {
|
|
7307
|
+
if (!o.start || upstream || starting) return;
|
|
7308
|
+
startError = void 0;
|
|
7309
|
+
starting = (async () => {
|
|
7310
|
+
try {
|
|
7311
|
+
const r = await o.start();
|
|
7312
|
+
if ("error" in r) {
|
|
7313
|
+
startError = r.error;
|
|
7314
|
+
return;
|
|
7315
|
+
}
|
|
7316
|
+
writeAttachFile(o.attachFile, r.url);
|
|
7317
|
+
await tryAttach();
|
|
7318
|
+
if (!upstream) startError = `${r.url} did not accept a connection`;
|
|
7319
|
+
} catch (e) {
|
|
7320
|
+
startError = e.message.replace(/\s+/g, " ").slice(0, 200);
|
|
7321
|
+
} finally {
|
|
7322
|
+
starting = void 0;
|
|
7323
|
+
notify2();
|
|
7324
|
+
}
|
|
7325
|
+
})();
|
|
7326
|
+
};
|
|
7327
|
+
const waitForStart = async () => {
|
|
7328
|
+
const running = starting;
|
|
7329
|
+
if (!running) return;
|
|
7330
|
+
let t;
|
|
7331
|
+
await Promise.race([running, new Promise((r) => {
|
|
7332
|
+
t = setTimeout(r, o.startWaitMs ?? 12e4);
|
|
7333
|
+
})]);
|
|
7334
|
+
if (t) clearTimeout(t);
|
|
7335
|
+
};
|
|
7269
7336
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
7337
|
+
ensureStarted();
|
|
7270
7338
|
if (!upstream) return { tools: [statusTool] };
|
|
7271
7339
|
try {
|
|
7272
7340
|
return await upstream.listTools();
|
|
@@ -7276,6 +7344,11 @@ function createBridge(o) {
|
|
|
7276
7344
|
}
|
|
7277
7345
|
});
|
|
7278
7346
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
7347
|
+
const failedNow = req.params.name === STATUS_TOOL && !upstream && !starting && startError !== void 0;
|
|
7348
|
+
const failedText = failedNow ? statusText() : void 0;
|
|
7349
|
+
ensureStarted();
|
|
7350
|
+
if (failedText !== void 0) return { content: [{ type: "text", text: failedText }] };
|
|
7351
|
+
if (req.params.name !== STATUS_TOOL) await waitForStart();
|
|
7279
7352
|
if (!upstream) return status2();
|
|
7280
7353
|
try {
|
|
7281
7354
|
return await upstream.callTool({ name: req.params.name, arguments: req.params.arguments });
|
|
@@ -10728,6 +10801,9 @@ var SandboxRunner = class {
|
|
|
10728
10801
|
lockfile: cfg.depsLockfile ?? DEFAULT_CACHE_SETTINGS.depsLockfile
|
|
10729
10802
|
})} && ${containerEngineStubScript()}`;
|
|
10730
10803
|
await this.os(["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone]);
|
|
10804
|
+
if (plan.sandboxFiles?.length) {
|
|
10805
|
+
await this.os(["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", writeFilesScript(plan.sandboxFiles)]);
|
|
10806
|
+
}
|
|
10731
10807
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
10732
10808
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
10733
10809
|
if (viaUpload) {
|
|
@@ -10877,6 +10953,15 @@ function lspPluginDirsFor(route2, repo, stateDir) {
|
|
|
10877
10953
|
return void 0;
|
|
10878
10954
|
}
|
|
10879
10955
|
}
|
|
10956
|
+
function sandboxLspPluginFor(route2, repo) {
|
|
10957
|
+
try {
|
|
10958
|
+
const dir = reposOf(route2).find((r) => r.name === repo)?.dir ?? route2.dir;
|
|
10959
|
+
const flavor = lspFlavor(loadRepoConfig(dir)?.hooks.intel);
|
|
10960
|
+
return flavor ? sandboxLspPlugin(flavor) : void 0;
|
|
10961
|
+
} catch {
|
|
10962
|
+
return void 0;
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10880
10965
|
var AgentSpawnError = class extends AgentError {
|
|
10881
10966
|
};
|
|
10882
10967
|
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
@@ -10912,7 +10997,7 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10912
10997
|
}
|
|
10913
10998
|
var digestPathFor = (stateDir, routeLabel, role, runId) => join29(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10914
10999
|
function codeBridgeFor(o, harness) {
|
|
10915
|
-
if (
|
|
11000
|
+
if (!o.ticket) return void 0;
|
|
10916
11001
|
const number = /(\d+)$/.exec(o.ticket)?.[1];
|
|
10917
11002
|
if (!number) return void 0;
|
|
10918
11003
|
let intel;
|
|
@@ -10923,14 +11008,33 @@ function codeBridgeFor(o, harness) {
|
|
|
10923
11008
|
} catch {
|
|
10924
11009
|
}
|
|
10925
11010
|
if (!wantsCodeBridge(intel)) return void 0;
|
|
10926
|
-
const
|
|
10927
|
-
const
|
|
11011
|
+
const sandbox = o.ship.runner === "sandbox";
|
|
11012
|
+
const args = sandbox ? [
|
|
11013
|
+
SANDBOX_BRIDGE_PATH,
|
|
11014
|
+
"--attach-file",
|
|
11015
|
+
`${SANDBOX_INTEL_DIR}/url`,
|
|
11016
|
+
"--start",
|
|
11017
|
+
intel.trim(),
|
|
11018
|
+
"--clone",
|
|
11019
|
+
o.ship.sandbox?.workDir ?? DEFAULT_SANDBOX_WORKDIR,
|
|
11020
|
+
"--state-dir",
|
|
11021
|
+
dirname13(SANDBOX_INTEL_DIR),
|
|
11022
|
+
"--ticket",
|
|
11023
|
+
number
|
|
11024
|
+
] : [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
|
|
11025
|
+
const bridge = { command: sandbox ? "node" : process.execPath, args };
|
|
10928
11026
|
if (harness === "gemini") {
|
|
10929
|
-
const
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
11027
|
+
const text2 = `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
|
|
11028
|
+
`;
|
|
11029
|
+
if (sandbox) {
|
|
11030
|
+
bridge.settingsFile = `${SANDBOX_INTEL_DIR}/gemini-settings.json`;
|
|
11031
|
+
bridge.files = [{ path: bridge.settingsFile, content: text2 }];
|
|
11032
|
+
} else {
|
|
11033
|
+
const file = join29(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
|
|
11034
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
11035
|
+
writeFileSync19(file, text2);
|
|
11036
|
+
bridge.settingsFile = file;
|
|
11037
|
+
}
|
|
10934
11038
|
}
|
|
10935
11039
|
return bridge;
|
|
10936
11040
|
}
|
|
@@ -10991,11 +11095,13 @@ function planAgentRun(o) {
|
|
|
10991
11095
|
}
|
|
10992
11096
|
const adapter = adapterFor(toolchain.harness);
|
|
10993
11097
|
const code = codeBridgeFor(o, adapter.harness);
|
|
11098
|
+
const sandboxLsp = o.ship.runner === "sandbox" && adapter.harness === "claude" ? sandboxLspPluginFor(o.route, o.repo) : void 0;
|
|
11099
|
+
const sandboxFiles = [...code?.files ?? [], ...sandboxLsp?.files ?? []];
|
|
10994
11100
|
const invocation = adapter.invocation(toolchain, {
|
|
10995
11101
|
code,
|
|
10996
11102
|
role: o.role,
|
|
10997
11103
|
model,
|
|
10998
|
-
pluginDirs: adapter.harness
|
|
11104
|
+
pluginDirs: adapter.harness !== "claude" ? void 0 : sandboxLsp ? [sandboxLsp.dir] : lspPluginDirsFor(o.route, o.repo, o.stateDir),
|
|
10999
11105
|
// Skipped when there's no key to send: a route `crew connect` hasn't
|
|
11000
11106
|
// resolved yet has nothing to authenticate the MCP connection with either.
|
|
11001
11107
|
mcp: o.apiKey ? { url: `${o.route.baseUrl}/api/mcp`, keyVar: API_KEY_VAR } : void 0
|
|
@@ -11042,7 +11148,8 @@ function planAgentRun(o) {
|
|
|
11042
11148
|
...o.ship.runner === "sandbox" ? {
|
|
11043
11149
|
runner: "sandbox",
|
|
11044
11150
|
...o.ship.sandbox ? { sandboxConfig: o.ship.sandbox } : {},
|
|
11045
|
-
sandboxPolicy: sandboxPolicyInputFor(o.route, toolchain, { harnessBin: invocation.bin, repo: o.repo, sandbox: o.ship.sandbox })
|
|
11151
|
+
sandboxPolicy: sandboxPolicyInputFor(o.route, toolchain, { harnessBin: invocation.bin, repo: o.repo, sandbox: o.ship.sandbox }),
|
|
11152
|
+
...sandboxFiles.length ? { sandboxFiles } : {}
|
|
11046
11153
|
} : {},
|
|
11047
11154
|
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
11048
11155
|
warnings: [...seat.warnings ?? [], ...expanded.warnings],
|
|
@@ -12681,6 +12788,18 @@ var SANDBOX_CLAUDE_SHA256 = {
|
|
|
12681
12788
|
};
|
|
12682
12789
|
var SANDBOX_UID = 1e3;
|
|
12683
12790
|
var CREW_PREFIX = "/opt/crew";
|
|
12791
|
+
var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
|
|
12792
|
+
function readBridgeBundle() {
|
|
12793
|
+
const here = dirname14(fileURLToPath(import.meta.url));
|
|
12794
|
+
for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
|
|
12795
|
+
if (existsSync30(p)) return readFileSync27(p, "utf8");
|
|
12796
|
+
}
|
|
12797
|
+
return void 0;
|
|
12798
|
+
}
|
|
12799
|
+
var SANDBOX_UV_VERSION = "0.13.6";
|
|
12800
|
+
var SANDBOX_SERENA_VERSION = "2.1.0";
|
|
12801
|
+
var SERENA_IMAGE_HOME = `${CREW_PREFIX}/serena`;
|
|
12802
|
+
var SERENA_LANGUAGE_SERVERS_ENV = "CREW_SERENA_LANGUAGE_SERVERS";
|
|
12684
12803
|
var SANDBOX_RETRY_AFTER_MS = 60 * 60 * 1e3;
|
|
12685
12804
|
var KEEP_TAGS = 2;
|
|
12686
12805
|
var CREDENTIAL_HELPER = `#!/bin/sh
|
|
@@ -12717,6 +12836,21 @@ function claudeInstallCommand() {
|
|
|
12717
12836
|
`${CREW_PREFIX}/bin/claude --version | grep -F "${SANDBOX_CLAUDE_VERSION}"`
|
|
12718
12837
|
].join("; ");
|
|
12719
12838
|
}
|
|
12839
|
+
function serenaInstallCommand() {
|
|
12840
|
+
return [
|
|
12841
|
+
`python3 -m venv ${CREW_PREFIX}/uv-venv`,
|
|
12842
|
+
`${CREW_PREFIX}/uv-venv/bin/pip install --no-cache-dir uv==${SANDBOX_UV_VERSION}`,
|
|
12843
|
+
`ln -sf ${CREW_PREFIX}/uv-venv/bin/uv ${CREW_PREFIX}/bin/uv`,
|
|
12844
|
+
`UV_TOOL_DIR=${CREW_PREFIX}/uv/tools UV_TOOL_BIN_DIR=${CREW_PREFIX}/bin UV_PYTHON_INSTALL_DIR=${CREW_PREFIX}/uv/python UV_CACHE_DIR=${CREW_PREFIX}/cache/uv ${CREW_PREFIX}/bin/uv tool install -p 3.13 serena-agent==${SANDBOX_SERENA_VERSION}`,
|
|
12845
|
+
`${CREW_PREFIX}/bin/serena --help >/dev/null`
|
|
12846
|
+
].join("; ");
|
|
12847
|
+
}
|
|
12848
|
+
function serenaWarmCommand() {
|
|
12849
|
+
return [
|
|
12850
|
+
`mkdir -p ${SERENA_IMAGE_HOME}`,
|
|
12851
|
+
`SERENA_HOME=${SERENA_IMAGE_HOME} serena project index ${WARM_DIR}`
|
|
12852
|
+
].join("; ");
|
|
12853
|
+
}
|
|
12720
12854
|
function baseLayer() {
|
|
12721
12855
|
const dockerfile = [
|
|
12722
12856
|
`FROM ${SANDBOX_BASE_IMAGE}`,
|
|
@@ -12725,18 +12859,21 @@ function baseLayer() {
|
|
|
12725
12859
|
].join("\n");
|
|
12726
12860
|
return { name: "base", tag: `crew-sandbox-base:${hash({ SANDBOX_BASE_IMAGE, SANDBOX_OPENSHELL_VERSION })}`, dockerfile, files: {} };
|
|
12727
12861
|
}
|
|
12728
|
-
function crewLayer(base) {
|
|
12729
|
-
const files = { "credential-helper": CREDENTIAL_HELPER, "mcp.json": MCP_CONFIG };
|
|
12862
|
+
function crewLayer(base, intel, bridge) {
|
|
12863
|
+
const files = { "credential-helper": CREDENTIAL_HELPER, "mcp.json": MCP_CONFIG, ...bridge ? { "intel-bridge": bridge } : {} };
|
|
12730
12864
|
const env = Object.entries(CREW_ENV).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
12731
12865
|
const dockerfile = [
|
|
12732
12866
|
`FROM ${base.tag}`,
|
|
12733
12867
|
"USER root",
|
|
12734
|
-
run2(
|
|
12868
|
+
run2(`apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git${intel ? " python3 python3-venv nodejs" : ""} && rm -rf /var/lib/apt/lists/*`),
|
|
12735
12869
|
`COPY credential-helper ${CREW_PREFIX}/bin/credential-helper`,
|
|
12736
12870
|
`COPY mcp.json ${CREW_PREFIX}/mcp/mcp.json`,
|
|
12871
|
+
...bridge ? [`COPY intel-bridge ${SANDBOX_BRIDGE_PATH}`] : [],
|
|
12737
12872
|
run2(claudeInstallCommand()),
|
|
12873
|
+
...intel ? [run2(`mkdir -p ${CREW_PREFIX}/cache && ${serenaInstallCommand()}`)] : [],
|
|
12738
12874
|
run2(`chmod 0755 ${CREW_PREFIX}/bin/credential-helper && git config --system credential.helper ${CREW_PREFIX}/bin/credential-helper`),
|
|
12739
12875
|
run2(`mkdir -p ${CREW_PREFIX}/cache ${CREW_PREFIX}/pnpm-store ${CREW_PREFIX}/pnpm /sandbox && chown -R ${SANDBOX_UID}:${SANDBOX_UID} ${CREW_PREFIX} /sandbox`),
|
|
12876
|
+
...intel ? [`ENV ${SERENA_LANGUAGE_SERVERS_ENV}=${SERENA_IMAGE_HOME}/language_servers UV_PYTHON_INSTALL_DIR=${CREW_PREFIX}/uv/python`] : [],
|
|
12740
12877
|
`ENV ${env} PATH=${CREW_PREFIX}/pnpm:${CREW_PREFIX}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
|
|
12741
12878
|
`LABEL io.crew.sandbox.layer="crew" io.crew.sandbox.claude="${SANDBOX_CLAUDE_VERSION}"`,
|
|
12742
12879
|
""
|
|
@@ -12790,14 +12927,15 @@ function repoLayer(repo, decl, crew, warm) {
|
|
|
12790
12927
|
lines2.push(`COPY --chown=${SANDBOX_UID}:${SANDBOX_UID} warm/ ${WARM_DIR}/`, `WORKDIR ${WARM_DIR}`);
|
|
12791
12928
|
}
|
|
12792
12929
|
for (const step of decl.setup) lines2.push(run2(step));
|
|
12930
|
+
if (warming && decl.intel) lines2.push(run2(serenaWarmCommand()));
|
|
12793
12931
|
if (warming) lines2.push("WORKDIR /sandbox", run2(`rm -rf ${WARM_DIR}`));
|
|
12794
12932
|
lines2.push(`LABEL io.crew.sandbox.layer="repo" io.crew.sandbox.repo=${JSON.stringify(repo)}`, "");
|
|
12795
12933
|
const dockerfile = lines2.join("\n");
|
|
12796
12934
|
return { name: "repo", tag: `crew-sandbox/${slug(repo)}:${hash({ crew: crew.tag, dockerfile, files })}`, dockerfile, files };
|
|
12797
12935
|
}
|
|
12798
|
-
function planSandbox(repo, decl, warm = {}) {
|
|
12936
|
+
function planSandbox(repo, decl, warm = {}, bridge) {
|
|
12799
12937
|
const base = baseLayer();
|
|
12800
|
-
const crew = crewLayer(base);
|
|
12938
|
+
const crew = crewLayer(base, decl.intel, bridge);
|
|
12801
12939
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12802
12940
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12803
12941
|
}
|
|
@@ -12907,7 +13045,7 @@ function sandboxFailedItem(repo, tag, layer, logPath, nowIso = (/* @__PURE__ */
|
|
|
12907
13045
|
async function sandboxBuildTick(repos, deps, force = false) {
|
|
12908
13046
|
const nowMs = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
12909
13047
|
for (const r of repos) {
|
|
12910
|
-
const plan = planSandbox(r.name, r.decl, r.warm);
|
|
13048
|
+
const plan = planSandbox(r.name, r.decl, r.warm, r.bridge);
|
|
12911
13049
|
const state = readSandboxState(deps.stateDir)[r.name];
|
|
12912
13050
|
const upToDate = state?.tags[0]?.tag === plan.tag && deps.hasImage(plan.tag);
|
|
12913
13051
|
if (upToDate) {
|
|
@@ -13066,7 +13204,7 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
13066
13204
|
|
|
13067
13205
|
// src/selftest.ts
|
|
13068
13206
|
import { spawn as spawn6 } from "child_process";
|
|
13069
|
-
import { existsSync as
|
|
13207
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync28, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
13070
13208
|
import { tmpdir as tmpdir4 } from "os";
|
|
13071
13209
|
import { join as join33 } from "path";
|
|
13072
13210
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
@@ -13266,7 +13404,7 @@ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
|
|
|
13266
13404
|
function readSelftest(stateDir) {
|
|
13267
13405
|
try {
|
|
13268
13406
|
const p = fileFor2(stateDir);
|
|
13269
|
-
return
|
|
13407
|
+
return existsSync31(p) ? JSON.parse(readFileSync28(p, "utf8")) : {};
|
|
13270
13408
|
} catch {
|
|
13271
13409
|
return {};
|
|
13272
13410
|
}
|
|
@@ -13288,7 +13426,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13288
13426
|
|
|
13289
13427
|
// src/cli.ts
|
|
13290
13428
|
import { resolve as resolve7, dirname as dirname33, join as join55, basename as basename16 } from "path";
|
|
13291
|
-
import { fileURLToPath } from "url";
|
|
13429
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13292
13430
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
13293
13431
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
13294
13432
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -13592,7 +13730,7 @@ async function signOutSessions(o) {
|
|
|
13592
13730
|
}
|
|
13593
13731
|
|
|
13594
13732
|
// src/reset.ts
|
|
13595
|
-
import { existsSync as
|
|
13733
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
13596
13734
|
import { dirname as dirname16, join as join35 } from "path";
|
|
13597
13735
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
13598
13736
|
function clonedCheckoutsFile(stateDir) {
|
|
@@ -13665,7 +13803,7 @@ async function runReset(o, fx) {
|
|
|
13665
13803
|
}
|
|
13666
13804
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
13667
13805
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
13668
|
-
if (
|
|
13806
|
+
if (existsSync32(o.configFile)) {
|
|
13669
13807
|
if (!o.dryRun) rmSync18(o.configFile, { force: true });
|
|
13670
13808
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
13671
13809
|
} else {
|
|
@@ -13674,7 +13812,7 @@ async function runReset(o, fx) {
|
|
|
13674
13812
|
return steps;
|
|
13675
13813
|
}
|
|
13676
13814
|
function removeCheckout(dir, dryRun, fx) {
|
|
13677
|
-
if (!
|
|
13815
|
+
if (!existsSync32(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
13678
13816
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
13679
13817
|
if (dirty.length) {
|
|
13680
13818
|
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" : ""})` };
|
|
@@ -13695,7 +13833,7 @@ function removeState(stateDir, dryRun) {
|
|
|
13695
13833
|
} catch {
|
|
13696
13834
|
}
|
|
13697
13835
|
for (const t of targets) {
|
|
13698
|
-
if (!
|
|
13836
|
+
if (!existsSync32(t)) continue;
|
|
13699
13837
|
if (!dryRun) rmSync18(t, { recursive: true, force: true });
|
|
13700
13838
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
13701
13839
|
}
|
|
@@ -14091,13 +14229,13 @@ function hostArchitecture(arch = process.arch) {
|
|
|
14091
14229
|
|
|
14092
14230
|
// src/pool-member-host.ts
|
|
14093
14231
|
import "child_process";
|
|
14094
|
-
import { existsSync as
|
|
14232
|
+
import { existsSync as existsSync36 } from "fs";
|
|
14095
14233
|
import { hostname as hostname3 } from "os";
|
|
14096
14234
|
|
|
14097
14235
|
// src/machine-id.ts
|
|
14098
14236
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
14099
14237
|
import { createHash as createHash7 } from "crypto";
|
|
14100
|
-
import { existsSync as
|
|
14238
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "fs";
|
|
14101
14239
|
import { dirname as dirname18, join as join37 } from "path";
|
|
14102
14240
|
function machineIdPathFor(stateDir) {
|
|
14103
14241
|
return join37(stateDir, "machine-id");
|
|
@@ -14113,7 +14251,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14113
14251
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
14114
14252
|
}
|
|
14115
14253
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14116
|
-
if (
|
|
14254
|
+
if (existsSync33(p)) {
|
|
14117
14255
|
const id = readFileSync32(p, "utf8").trim();
|
|
14118
14256
|
if (id) return id;
|
|
14119
14257
|
}
|
|
@@ -14125,7 +14263,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14125
14263
|
function ensureMachineId(stateDir, opts = {}) {
|
|
14126
14264
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14127
14265
|
const path = machineIdPathFor(stateDir);
|
|
14128
|
-
if (
|
|
14266
|
+
if (existsSync33(path)) {
|
|
14129
14267
|
const cached = readFileSync32(path, "utf8").trim();
|
|
14130
14268
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14131
14269
|
}
|
|
@@ -14146,7 +14284,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
14146
14284
|
|
|
14147
14285
|
// src/provision.ts
|
|
14148
14286
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
14149
|
-
import { existsSync as
|
|
14287
|
+
import { existsSync as existsSync34, readFileSync as readFileSync33, writeFileSync as writeFileSync26 } from "fs";
|
|
14150
14288
|
import { hostname as hostname2 } from "os";
|
|
14151
14289
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
14152
14290
|
|
|
@@ -14740,7 +14878,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14740
14878
|
};
|
|
14741
14879
|
const configFile = () => {
|
|
14742
14880
|
const f = findConfigFile(crewHome);
|
|
14743
|
-
return
|
|
14881
|
+
return existsSync34(f) ? f : null;
|
|
14744
14882
|
};
|
|
14745
14883
|
return {
|
|
14746
14884
|
crew: spawnCrew,
|
|
@@ -14774,14 +14912,14 @@ function realProvisionDeps(crewHome) {
|
|
|
14774
14912
|
continue;
|
|
14775
14913
|
}
|
|
14776
14914
|
for (const p of plan.unitPaths) {
|
|
14777
|
-
if (!
|
|
14915
|
+
if (!existsSync34(p) || readFileSync33(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
14778
14916
|
}
|
|
14779
14917
|
}
|
|
14780
14918
|
const label = labelFor(crewHome, "run");
|
|
14781
14919
|
let running = false;
|
|
14782
14920
|
let lingering = true;
|
|
14783
14921
|
if (mechanism === "systemd") {
|
|
14784
|
-
const system = ctx.root ||
|
|
14922
|
+
const system = ctx.root || existsSync34(systemUnitPathFor(label));
|
|
14785
14923
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
14786
14924
|
running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
14787
14925
|
if (!system) {
|
|
@@ -14826,7 +14964,7 @@ host=${host}
|
|
|
14826
14964
|
|
|
14827
14965
|
// src/tunnel.ts
|
|
14828
14966
|
import { spawn as spawn7 } from "child_process";
|
|
14829
|
-
import { existsSync as
|
|
14967
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync34, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14830
14968
|
import { randomInt } from "crypto";
|
|
14831
14969
|
import { join as join39 } from "path";
|
|
14832
14970
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -14904,7 +15042,7 @@ function tunnelStatePath(stateDir, workspaceId) {
|
|
|
14904
15042
|
}
|
|
14905
15043
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14906
15044
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14907
|
-
if (!
|
|
15045
|
+
if (!existsSync35(path)) return void 0;
|
|
14908
15046
|
try {
|
|
14909
15047
|
return JSON.parse(readFileSync34(path, "utf8"));
|
|
14910
15048
|
} catch {
|
|
@@ -15017,7 +15155,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15017
15155
|
}
|
|
15018
15156
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15019
15157
|
const dir = join39(stateDir, "passengers");
|
|
15020
|
-
if (!
|
|
15158
|
+
if (!existsSync35(dir)) return [];
|
|
15021
15159
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15022
15160
|
}
|
|
15023
15161
|
|
|
@@ -15042,7 +15180,7 @@ function realPoolMemberDeps(o) {
|
|
|
15042
15180
|
crewVersion: crewVersion(o.crewHome),
|
|
15043
15181
|
hostname: hostname3()
|
|
15044
15182
|
},
|
|
15045
|
-
hasShipConfig: () =>
|
|
15183
|
+
hasShipConfig: () => existsSync36(findConfigFile(o.crewHome)),
|
|
15046
15184
|
async provision(lease, instanceClass, shipKind) {
|
|
15047
15185
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
15048
15186
|
const lines2 = [];
|
|
@@ -16412,7 +16550,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
16412
16550
|
}
|
|
16413
16551
|
|
|
16414
16552
|
// src/repo-refresh.ts
|
|
16415
|
-
import { existsSync as
|
|
16553
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
|
|
16416
16554
|
import { dirname as dirname20 } from "path";
|
|
16417
16555
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
16418
16556
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -16442,7 +16580,7 @@ function applyRepoRows(resolved, rows) {
|
|
|
16442
16580
|
}
|
|
16443
16581
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
16444
16582
|
const path = resolvedPathFor(stateDir, routeName);
|
|
16445
|
-
if (!
|
|
16583
|
+
if (!existsSync37(path)) return;
|
|
16446
16584
|
const raw = JSON.parse(readFileSync35(path, "utf8"));
|
|
16447
16585
|
applyRepoRows(raw, rows);
|
|
16448
16586
|
mkdirSync26(dirname20(path), { recursive: true });
|
|
@@ -16897,12 +17035,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
|
|
|
16897
17035
|
|
|
16898
17036
|
// src/connect-wizard.ts
|
|
16899
17037
|
import { delimiter, dirname as dirname21, join as join41 } from "path";
|
|
16900
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
17038
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync38, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
16901
17039
|
import { stringify as stringify3 } from "yaml";
|
|
16902
17040
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16903
17041
|
function shouldRunWizard(opts) {
|
|
16904
17042
|
if (!opts.isTTY) return false;
|
|
16905
|
-
const exists = opts.configExists ??
|
|
17043
|
+
const exists = opts.configExists ?? existsSync38;
|
|
16906
17044
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16907
17045
|
}
|
|
16908
17046
|
function firstRunConfigPath() {
|
|
@@ -17096,7 +17234,7 @@ function flagWizardAnswers(f, env) {
|
|
|
17096
17234
|
}
|
|
17097
17235
|
|
|
17098
17236
|
// src/release-run.ts
|
|
17099
|
-
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as
|
|
17237
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync39, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17100
17238
|
import { dirname as dirname23, join as join43 } from "path";
|
|
17101
17239
|
import { tmpdir as tmpdir5 } from "os";
|
|
17102
17240
|
|
|
@@ -17840,7 +17978,7 @@ async function bump(o, size) {
|
|
|
17840
17978
|
}
|
|
17841
17979
|
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
17842
17980
|
const first = files[0];
|
|
17843
|
-
if (!
|
|
17981
|
+
if (!existsSync39(first)) {
|
|
17844
17982
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17845
17983
|
return null;
|
|
17846
17984
|
}
|
|
@@ -17876,7 +18014,7 @@ function writeChangelog(o, version, entries) {
|
|
|
17876
18014
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17877
18015
|
return;
|
|
17878
18016
|
}
|
|
17879
|
-
const existing =
|
|
18017
|
+
const existing = existsSync39(path) ? readFileSync36(path, "utf8") : "# Changelog\n";
|
|
17880
18018
|
writeFileSync31(path, insertChangelogSection(existing, section));
|
|
17881
18019
|
}
|
|
17882
18020
|
async function confirm(o, expected) {
|
|
@@ -17982,7 +18120,7 @@ function openReleaseCheckout(o) {
|
|
|
17982
18120
|
}
|
|
17983
18121
|
for (const f of o.repo.worktrees.copy) {
|
|
17984
18122
|
const src = join43(o.cwd, f);
|
|
17985
|
-
if (!
|
|
18123
|
+
if (!existsSync39(src)) continue;
|
|
17986
18124
|
mkdirSync28(dirname23(join43(dir, f)), { recursive: true });
|
|
17987
18125
|
copyFileSync(src, join43(dir, f));
|
|
17988
18126
|
}
|
|
@@ -18963,7 +19101,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18963
19101
|
}
|
|
18964
19102
|
|
|
18965
19103
|
// src/watch.ts
|
|
18966
|
-
import { watch as watchFile, existsSync as
|
|
19104
|
+
import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
18967
19105
|
var E = "\x1B[";
|
|
18968
19106
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18969
19107
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -19064,7 +19202,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
19064
19202
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
19065
19203
|
}
|
|
19066
19204
|
function readFrom(file, from) {
|
|
19067
|
-
if (!
|
|
19205
|
+
if (!existsSync40(file)) return { events: [], offset: from };
|
|
19068
19206
|
const size = statSync11(file).size;
|
|
19069
19207
|
if (size < from) return readFrom(file, 0);
|
|
19070
19208
|
if (size === from) return { events: [], offset: from };
|
|
@@ -19307,7 +19445,7 @@ function nextRoles(decision, ran) {
|
|
|
19307
19445
|
}
|
|
19308
19446
|
|
|
19309
19447
|
// src/ship-name.ts
|
|
19310
|
-
import { existsSync as
|
|
19448
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
|
|
19311
19449
|
import { dirname as dirname24 } from "path";
|
|
19312
19450
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19313
19451
|
var ShipNameError = class extends Error {
|
|
@@ -19369,7 +19507,7 @@ async function syncRouteName(i) {
|
|
|
19369
19507
|
return { status: "ok", ...base, action, name: i.local };
|
|
19370
19508
|
}
|
|
19371
19509
|
function readSyncedName(resolvedFile) {
|
|
19372
|
-
if (!
|
|
19510
|
+
if (!existsSync41(resolvedFile)) return void 0;
|
|
19373
19511
|
try {
|
|
19374
19512
|
const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
|
|
19375
19513
|
return typeof v === "string" ? v : void 0;
|
|
@@ -19378,7 +19516,7 @@ function readSyncedName(resolvedFile) {
|
|
|
19378
19516
|
}
|
|
19379
19517
|
}
|
|
19380
19518
|
function writeSyncedName(resolvedFile, name) {
|
|
19381
|
-
if (!
|
|
19519
|
+
if (!existsSync41(resolvedFile)) return;
|
|
19382
19520
|
let raw;
|
|
19383
19521
|
try {
|
|
19384
19522
|
raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
|
|
@@ -20080,7 +20218,7 @@ function describeCapability(i) {
|
|
|
20080
20218
|
}
|
|
20081
20219
|
|
|
20082
20220
|
// src/agents-sync-run.ts
|
|
20083
|
-
import { existsSync as
|
|
20221
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
|
|
20084
20222
|
import { dirname as dirname25 } from "path";
|
|
20085
20223
|
async function runAgentsSync(target, o) {
|
|
20086
20224
|
const dryRun = o.dryRun ?? false;
|
|
@@ -20114,7 +20252,7 @@ async function runAgentsSync(target, o) {
|
|
|
20114
20252
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
20115
20253
|
if (!dryRun) {
|
|
20116
20254
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
20117
|
-
const raw =
|
|
20255
|
+
const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
|
|
20118
20256
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
20119
20257
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
20120
20258
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
@@ -20809,7 +20947,7 @@ function planSampleTicket(i) {
|
|
|
20809
20947
|
}
|
|
20810
20948
|
|
|
20811
20949
|
// src/worktree-sweep.ts
|
|
20812
|
-
import { existsSync as
|
|
20950
|
+
import { existsSync as existsSync43, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
|
|
20813
20951
|
import { basename as basename13, dirname as dirname27, join as join45, resolve as resolve5 } from "path";
|
|
20814
20952
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20815
20953
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
@@ -20922,7 +21060,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20922
21060
|
let removed = 0;
|
|
20923
21061
|
let keptBranches = 0;
|
|
20924
21062
|
for (const a of actions) {
|
|
20925
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
21063
|
+
if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
|
|
20926
21064
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20927
21065
|
log.emit(
|
|
20928
21066
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20930,7 +21068,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20930
21068
|
);
|
|
20931
21069
|
}
|
|
20932
21070
|
}
|
|
20933
|
-
if (!dryRun &&
|
|
21071
|
+
if (!dryRun && existsSync43(a.path)) {
|
|
20934
21072
|
let dirty = false;
|
|
20935
21073
|
try {
|
|
20936
21074
|
dirty = status(a.path).length > 0;
|
|
@@ -20941,7 +21079,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20941
21079
|
continue;
|
|
20942
21080
|
}
|
|
20943
21081
|
}
|
|
20944
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
21082
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20945
21083
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20946
21084
|
continue;
|
|
20947
21085
|
}
|
|
@@ -21095,12 +21233,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21095
21233
|
}
|
|
21096
21234
|
|
|
21097
21235
|
// src/stream-sweep.ts
|
|
21098
|
-
import { existsSync as
|
|
21236
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21099
21237
|
import { join as join46 } from "path";
|
|
21100
21238
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21101
21239
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21102
21240
|
const dir = join46(stateDir, "streams");
|
|
21103
|
-
if (!
|
|
21241
|
+
if (!existsSync44(dir)) return [];
|
|
21104
21242
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21105
21243
|
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21106
21244
|
try {
|
|
@@ -21128,7 +21266,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
21128
21266
|
return removed;
|
|
21129
21267
|
}
|
|
21130
21268
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21131
|
-
if (!
|
|
21269
|
+
if (!existsSync44(stateDir)) return [];
|
|
21132
21270
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21133
21271
|
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
21134
21272
|
try {
|
|
@@ -21140,11 +21278,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
21140
21278
|
}
|
|
21141
21279
|
|
|
21142
21280
|
// src/cli.ts
|
|
21143
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as
|
|
21281
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync51, 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";
|
|
21144
21282
|
import { createInterface } from "readline/promises";
|
|
21145
21283
|
|
|
21146
21284
|
// src/periscope-cmd.ts
|
|
21147
|
-
import { existsSync as
|
|
21285
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
|
|
21148
21286
|
import { join as join47 } from "path";
|
|
21149
21287
|
var PeriscopeError = class extends Error {
|
|
21150
21288
|
};
|
|
@@ -21170,7 +21308,7 @@ function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
|
21170
21308
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
21171
21309
|
try {
|
|
21172
21310
|
const p = priorPath(stateDir, ticketRecordId);
|
|
21173
|
-
return
|
|
21311
|
+
return existsSync45(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
|
|
21174
21312
|
} catch {
|
|
21175
21313
|
return void 0;
|
|
21176
21314
|
}
|
|
@@ -21725,7 +21863,7 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
21725
21863
|
|
|
21726
21864
|
// src/ssh-keys.ts
|
|
21727
21865
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
21728
|
-
import { existsSync as
|
|
21866
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
|
|
21729
21867
|
import { dirname as dirname28, join as join49 } from "path";
|
|
21730
21868
|
var SshKeyError = class extends Error {
|
|
21731
21869
|
};
|
|
@@ -21735,7 +21873,7 @@ function shipSshKeyPathFor(stateDir) {
|
|
|
21735
21873
|
function ensureShipSshKeypair(stateDir) {
|
|
21736
21874
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
21737
21875
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
21738
|
-
if (!
|
|
21876
|
+
if (!existsSync46(privateKeyPath)) {
|
|
21739
21877
|
mkdirSync32(dirname28(privateKeyPath), { recursive: true });
|
|
21740
21878
|
try {
|
|
21741
21879
|
execFileSync13("ssh-keygen", [
|
|
@@ -21756,7 +21894,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
21756
21894
|
}
|
|
21757
21895
|
chmodSync3(privateKeyPath, 384);
|
|
21758
21896
|
}
|
|
21759
|
-
if (!
|
|
21897
|
+
if (!existsSync46(publicKeyPath)) {
|
|
21760
21898
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
21761
21899
|
}
|
|
21762
21900
|
const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
|
|
@@ -21923,7 +22061,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21923
22061
|
import { createServer as createHttpServer } from "http";
|
|
21924
22062
|
import { spawn as spawn9 } from "child_process";
|
|
21925
22063
|
import { randomBytes } from "crypto";
|
|
21926
|
-
import { existsSync as
|
|
22064
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21927
22065
|
import { join as join50 } from "path";
|
|
21928
22066
|
|
|
21929
22067
|
// src/passenger-mirror.ts
|
|
@@ -22058,7 +22196,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
22058
22196
|
}
|
|
22059
22197
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22060
22198
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22061
|
-
if (!
|
|
22199
|
+
if (!existsSync47(path)) return void 0;
|
|
22062
22200
|
try {
|
|
22063
22201
|
return JSON.parse(readFileSync43(path, "utf8"));
|
|
22064
22202
|
} catch {
|
|
@@ -22089,7 +22227,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22089
22227
|
}
|
|
22090
22228
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22091
22229
|
const dir = join50(stateDir, "passengers");
|
|
22092
|
-
if (!
|
|
22230
|
+
if (!existsSync47(dir)) return [];
|
|
22093
22231
|
const suffix = "-sync.json";
|
|
22094
22232
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
22095
22233
|
}
|
|
@@ -22586,7 +22724,7 @@ function describeCause(err3) {
|
|
|
22586
22724
|
}
|
|
22587
22725
|
|
|
22588
22726
|
// src/daemon.ts
|
|
22589
|
-
import { existsSync as
|
|
22727
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22590
22728
|
import { join as join51 } from "path";
|
|
22591
22729
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22592
22730
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -22684,7 +22822,7 @@ function computeTreeSignature(paths) {
|
|
|
22684
22822
|
}
|
|
22685
22823
|
function makeStaleChecker(paths, opts = {}) {
|
|
22686
22824
|
const initial = computeTreeSignature(paths);
|
|
22687
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
22825
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
|
|
22688
22826
|
}
|
|
22689
22827
|
function reloadSentinelFor(stateDir) {
|
|
22690
22828
|
return join51(stateDir, "reload-requested");
|
|
@@ -23273,7 +23411,7 @@ import { createHash as createHash10 } from "crypto";
|
|
|
23273
23411
|
import {
|
|
23274
23412
|
chmodSync as chmodSync4,
|
|
23275
23413
|
closeSync as closeSync6,
|
|
23276
|
-
existsSync as
|
|
23414
|
+
existsSync as existsSync49,
|
|
23277
23415
|
fstatSync,
|
|
23278
23416
|
openSync as openSync6,
|
|
23279
23417
|
readSync as readSync3,
|
|
@@ -23644,7 +23782,7 @@ function lastLines2(path, n) {
|
|
|
23644
23782
|
}
|
|
23645
23783
|
function readTail(live, opts) {
|
|
23646
23784
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
23647
|
-
if (!
|
|
23785
|
+
if (!existsSync49(live)) {
|
|
23648
23786
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
23649
23787
|
}
|
|
23650
23788
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -23801,7 +23939,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
23801
23939
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
23802
23940
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
23803
23941
|
const file = join52(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
23804
|
-
if (!
|
|
23942
|
+
if (!existsSync49(file)) return err2(404, "no such run");
|
|
23805
23943
|
return tailReply(file, url.searchParams, false, abort);
|
|
23806
23944
|
}
|
|
23807
23945
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -23845,7 +23983,7 @@ function send(res, r) {
|
|
|
23845
23983
|
res.end(body);
|
|
23846
23984
|
}
|
|
23847
23985
|
async function socketIsStale(path) {
|
|
23848
|
-
if (!
|
|
23986
|
+
if (!existsSync49(path)) return false;
|
|
23849
23987
|
return new Promise((resolve8) => {
|
|
23850
23988
|
const c = createConnection(path);
|
|
23851
23989
|
c.once("connect", () => {
|
|
@@ -23864,7 +24002,7 @@ async function startControlServer(deps) {
|
|
|
23864
24002
|
return null;
|
|
23865
24003
|
}
|
|
23866
24004
|
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
23867
|
-
else if (
|
|
24005
|
+
else if (existsSync49(path)) {
|
|
23868
24006
|
log(`control API not started: ${path} is in use by another process`);
|
|
23869
24007
|
return null;
|
|
23870
24008
|
}
|
|
@@ -23957,7 +24095,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
23957
24095
|
|
|
23958
24096
|
// src/periscope-endpoints.ts
|
|
23959
24097
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
23960
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
24098
|
+
import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
23961
24099
|
import { join as join53 } from "path";
|
|
23962
24100
|
var secretDir = (stateDir) => join53(stateDir, "periscope-mcp");
|
|
23963
24101
|
var secretFile = (stateDir, id) => join53(secretDir(stateDir), `${id}.json`);
|
|
@@ -24000,7 +24138,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
24000
24138
|
}
|
|
24001
24139
|
function listPeriscopeEndpoints(stateDir) {
|
|
24002
24140
|
const dir = endpointDir(stateDir);
|
|
24003
|
-
if (!
|
|
24141
|
+
if (!existsSync50(dir)) return [];
|
|
24004
24142
|
const out = [];
|
|
24005
24143
|
for (const name of readdirSync19(dir)) {
|
|
24006
24144
|
if (!name.endsWith(".json")) continue;
|
|
@@ -24364,7 +24502,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
24364
24502
|
}
|
|
24365
24503
|
|
|
24366
24504
|
// src/cli.ts
|
|
24367
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(
|
|
24505
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath2(import.meta.url)), "..");
|
|
24368
24506
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
24369
24507
|
var CYCLE_SECONDS2 = 120;
|
|
24370
24508
|
var STALE_CYCLES = 3;
|
|
@@ -24977,7 +25115,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24977
25115
|
} else {
|
|
24978
25116
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
24979
25117
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
24980
|
-
if (configless && !
|
|
25118
|
+
if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
|
|
24981
25119
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
24982
25120
|
} else {
|
|
24983
25121
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -25300,7 +25438,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25300
25438
|
if (host === "linux" && hasSystemd) {
|
|
25301
25439
|
const label = labelFor(CREW_HOME, "run");
|
|
25302
25440
|
const staleTimer = join55(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25303
|
-
if (
|
|
25441
|
+
if (existsSync51(staleTimer)) {
|
|
25304
25442
|
process.stdout.write(
|
|
25305
25443
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
25306
25444
|
`
|
|
@@ -25327,7 +25465,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25327
25465
|
const tracker = new Tracker(c, cfg.ship);
|
|
25328
25466
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
25329
25467
|
for (const r of await resolvedRepos(c)) {
|
|
25330
|
-
if (!
|
|
25468
|
+
if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
25331
25469
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
25332
25470
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
25333
25471
|
}
|
|
@@ -25354,7 +25492,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25354
25492
|
}
|
|
25355
25493
|
async function doctorFix(o) {
|
|
25356
25494
|
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))));
|
|
25357
|
-
const unitExists = (p) =>
|
|
25495
|
+
const unitExists = (p) => existsSync51(p);
|
|
25358
25496
|
const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
25359
25497
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
25360
25498
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -25400,7 +25538,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25400
25538
|
warm = readWarmFiles(r.dir, `${r.config.branch.remote}/${r.config.branch.base}`, r.config.sandbox.warm ?? []);
|
|
25401
25539
|
} catch {
|
|
25402
25540
|
}
|
|
25403
|
-
out.set(r.name, { name: r.name, decl: r.config.sandbox, warm });
|
|
25541
|
+
out.set(r.name, { name: r.name, decl: intelFlavor(r.config.hooks.intel) === "serena" ? { ...r.config.sandbox, intel: "serena" } : r.config.sandbox, warm, ...wantsCodeBridge(r.config.hooks.intel) ? { bridge: readBridgeBundle() } : {} });
|
|
25404
25542
|
}
|
|
25405
25543
|
}
|
|
25406
25544
|
return [...out.values()];
|
|
@@ -26815,7 +26953,7 @@ ${cu.tail.trimEnd()}
|
|
|
26815
26953
|
`);
|
|
26816
26954
|
if (!dryRun) {
|
|
26817
26955
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
26818
|
-
const raw =
|
|
26956
|
+
const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
|
|
26819
26957
|
raw.agentSkills = result.agentSkills;
|
|
26820
26958
|
mkdirSync38(dirOf2(resolvedPath), { recursive: true });
|
|
26821
26959
|
writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -26920,8 +27058,8 @@ ${cu.tail.trimEnd()}
|
|
|
26920
27058
|
try {
|
|
26921
27059
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
26922
27060
|
const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
|
|
26923
|
-
if (!
|
|
26924
|
-
const files = { exists: (r) =>
|
|
27061
|
+
if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
27062
|
+
const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
|
|
26925
27063
|
try {
|
|
26926
27064
|
return readdirSync20(resolvePath(dir, r));
|
|
26927
27065
|
} catch {
|
|
@@ -27039,7 +27177,7 @@ ${cu.tail.trimEnd()}
|
|
|
27039
27177
|
if (sub === "list" && !routeName) usage2();
|
|
27040
27178
|
if (sub === "add") {
|
|
27041
27179
|
try {
|
|
27042
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
27180
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
27043
27181
|
routeName = a.route;
|
|
27044
27182
|
addPath = a.path;
|
|
27045
27183
|
addName = a.name;
|
|
@@ -27069,7 +27207,7 @@ ${cu.tail.trimEnd()}
|
|
|
27069
27207
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
27070
27208
|
if (sub === "list" || sub === "add") {
|
|
27071
27209
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
27072
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
27210
|
+
if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
|
|
27073
27211
|
try {
|
|
27074
27212
|
await hydrateApiKeys([liveRoute]);
|
|
27075
27213
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -27078,7 +27216,7 @@ ${cu.tail.trimEnd()}
|
|
|
27078
27216
|
}
|
|
27079
27217
|
}
|
|
27080
27218
|
let resolvedRemotes;
|
|
27081
|
-
if (
|
|
27219
|
+
if (existsSync51(resolvedFile)) {
|
|
27082
27220
|
try {
|
|
27083
27221
|
resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27084
27222
|
} catch {
|
|
@@ -27087,7 +27225,7 @@ ${cu.tail.trimEnd()}
|
|
|
27087
27225
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
27088
27226
|
const text2 = configText;
|
|
27089
27227
|
const base = dirname33(cfg.configFile);
|
|
27090
|
-
const isCheckout = (d) =>
|
|
27228
|
+
const isCheckout = (d) => existsSync51(`${d}/.git`);
|
|
27091
27229
|
try {
|
|
27092
27230
|
if (sub === "list") {
|
|
27093
27231
|
if (flag("json")) {
|
|
@@ -27097,7 +27235,7 @@ ${cu.tail.trimEnd()}
|
|
|
27097
27235
|
base,
|
|
27098
27236
|
resolved: resolvedRemotes,
|
|
27099
27237
|
originOf,
|
|
27100
|
-
exists: (d) =>
|
|
27238
|
+
exists: (d) => existsSync51(d),
|
|
27101
27239
|
isCheckout,
|
|
27102
27240
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
27103
27241
|
try {
|
|
@@ -27118,7 +27256,7 @@ ${cu.tail.trimEnd()}
|
|
|
27118
27256
|
for (const r of repos) {
|
|
27119
27257
|
const origin2 = originOf(r.dir);
|
|
27120
27258
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
27121
|
-
const note = !
|
|
27259
|
+
const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
27122
27260
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
27123
27261
|
`);
|
|
27124
27262
|
}
|
|
@@ -27130,9 +27268,9 @@ ${cu.tail.trimEnd()}
|
|
|
27130
27268
|
` : `${human}
|
|
27131
27269
|
`);
|
|
27132
27270
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
27133
|
-
if (
|
|
27271
|
+
if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
27134
27272
|
throw new ReposError(
|
|
27135
|
-
|
|
27273
|
+
existsSync51(`${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`
|
|
27136
27274
|
);
|
|
27137
27275
|
}
|
|
27138
27276
|
if (dryRun) {
|
|
@@ -27157,7 +27295,7 @@ ${cu.tail.trimEnd()}
|
|
|
27157
27295
|
break;
|
|
27158
27296
|
}
|
|
27159
27297
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
27160
|
-
const top =
|
|
27298
|
+
const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
27161
27299
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
27162
27300
|
const origin = originOf(top);
|
|
27163
27301
|
const plan = planRepoAdd({
|
|
@@ -27209,7 +27347,7 @@ ${cu.tail.trimEnd()}
|
|
|
27209
27347
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
27210
27348
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
27211
27349
|
`);
|
|
27212
|
-
if (
|
|
27350
|
+
if (existsSync51(resolvedFile)) {
|
|
27213
27351
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
27214
27352
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
27215
27353
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
@@ -27631,7 +27769,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27631
27769
|
}
|
|
27632
27770
|
}
|
|
27633
27771
|
let previous;
|
|
27634
|
-
const hadPreviousFile =
|
|
27772
|
+
const hadPreviousFile = existsSync51(resolvedPath);
|
|
27635
27773
|
if (hadPreviousFile) {
|
|
27636
27774
|
try {
|
|
27637
27775
|
previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
|
|
@@ -28163,7 +28301,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28163
28301
|
if (subS === "images") {
|
|
28164
28302
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
28165
28303
|
const rows = declared.map((r) => {
|
|
28166
|
-
const plan = planSandbox(r.name, r.decl, r.warm);
|
|
28304
|
+
const plan = planSandbox(r.name, r.decl, r.warm, r.bridge);
|
|
28167
28305
|
return { repo: r.name, wants: plan.tag, tags: stateS[r.name]?.tags ?? [], ...sandboxImageStatus(stateS, plan) };
|
|
28168
28306
|
});
|
|
28169
28307
|
if (jsonS) {
|
|
@@ -28197,7 +28335,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28197
28335
|
for (const t of targets) {
|
|
28198
28336
|
await sandboxBuildTick([t], depsS, true);
|
|
28199
28337
|
const after = readSandboxState(cfg.ship.stateDir);
|
|
28200
|
-
const st = sandboxImageStatus(after, planSandbox(t.name, t.decl, t.warm));
|
|
28338
|
+
const st = sandboxImageStatus(after, planSandbox(t.name, t.decl, t.warm, t.bridge));
|
|
28201
28339
|
if (st.status === "failed") failed = true;
|
|
28202
28340
|
process.stdout.write(`${t.name}: ${st.status} \u2014 ${st.detail}
|
|
28203
28341
|
`);
|
|
@@ -28509,7 +28647,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28509
28647
|
prepare: async (path) => {
|
|
28510
28648
|
for (const f of repoWt.config.worktrees.copy) {
|
|
28511
28649
|
const src = join55(repoWt.dir, f);
|
|
28512
|
-
if (!
|
|
28650
|
+
if (!existsSync51(src) || existsSync51(join55(path, f))) continue;
|
|
28513
28651
|
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28514
28652
|
copyFileSync2(src, join55(path, f));
|
|
28515
28653
|
}
|
|
@@ -28731,7 +28869,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28731
28869
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
28732
28870
|
const resumed = await runPeriscopeResume({
|
|
28733
28871
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
28734
|
-
worktreeExists: (p) =>
|
|
28872
|
+
worktreeExists: (p) => existsSync51(p),
|
|
28735
28873
|
prepareDropFile,
|
|
28736
28874
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
28737
28875
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -28828,7 +28966,7 @@ ${where.join("\n")}`);
|
|
|
28828
28966
|
prepare: async (path) => {
|
|
28829
28967
|
for (const f of repoP.config.worktrees.copy) {
|
|
28830
28968
|
const src = join55(repoP.dir, f);
|
|
28831
|
-
if (!
|
|
28969
|
+
if (!existsSync51(src)) continue;
|
|
28832
28970
|
mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
|
|
28833
28971
|
copyFileSync2(src, join55(path, f));
|
|
28834
28972
|
}
|
|
@@ -28845,7 +28983,8 @@ ${where.join("\n")}`);
|
|
|
28845
28983
|
route: routeP.route,
|
|
28846
28984
|
ticket: Number(/(\d+)$/.exec(env.CREW_TICKET ?? "")?.[1] ?? 0),
|
|
28847
28985
|
worktree: cwd,
|
|
28848
|
-
mainCheckout: repoP.dir
|
|
28986
|
+
mainCheckout: repoP.dir,
|
|
28987
|
+
languageServers: process.env.CREW_SERENA_LANGUAGE_SERVERS
|
|
28849
28988
|
});
|
|
28850
28989
|
}
|
|
28851
28990
|
if (name === "intel" && lspFlavor(script)) return null;
|
|
@@ -29319,7 +29458,7 @@ ${where.join("\n")}`);
|
|
|
29319
29458
|
}
|
|
29320
29459
|
if (row) {
|
|
29321
29460
|
await tracker.deleteRepoRow(row.id);
|
|
29322
|
-
if (
|
|
29461
|
+
if (existsSync51(resolvedFile)) {
|
|
29323
29462
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29324
29463
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
29325
29464
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
@@ -29327,13 +29466,13 @@ ${where.join("\n")}`);
|
|
|
29327
29466
|
`);
|
|
29328
29467
|
}
|
|
29329
29468
|
}
|
|
29330
|
-
if (
|
|
29469
|
+
if (existsSync51(join55(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29331
29470
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29332
29471
|
break;
|
|
29333
29472
|
}
|
|
29334
29473
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29335
|
-
const fresh = !
|
|
29336
|
-
if (
|
|
29474
|
+
const fresh = !existsSync51(join55(dir, ".git"));
|
|
29475
|
+
if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29337
29476
|
if (dryRun) {
|
|
29338
29477
|
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`);
|
|
29339
29478
|
break;
|
|
@@ -29359,7 +29498,7 @@ ${where.join("\n")}`);
|
|
|
29359
29498
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
29360
29499
|
});
|
|
29361
29500
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
29362
|
-
if (
|
|
29501
|
+
if (existsSync51(resolvedFile)) {
|
|
29363
29502
|
const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
|
|
29364
29503
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
29365
29504
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
@@ -29715,7 +29854,7 @@ watermark: ${state.watermark()}
|
|
|
29715
29854
|
}
|
|
29716
29855
|
for (const r of repos) {
|
|
29717
29856
|
const remote = r.config.branch.remote;
|
|
29718
|
-
if (
|
|
29857
|
+
if (existsSync51(r.dir)) {
|
|
29719
29858
|
pushAuth.push({
|
|
29720
29859
|
route: c.route,
|
|
29721
29860
|
repo: r.name,
|
|
@@ -29811,7 +29950,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29811
29950
|
}
|
|
29812
29951
|
try {
|
|
29813
29952
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
29814
|
-
const evSize =
|
|
29953
|
+
const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
|
|
29815
29954
|
process.stdout.write(
|
|
29816
29955
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
29817
29956
|
` : "events log: ok\n"
|
|
@@ -30275,7 +30414,7 @@ ${foreign.join("\n")}
|
|
|
30275
30414
|
const declaredS = await sandboxReposOf(cfg.routes.filter((r) => r.enabled));
|
|
30276
30415
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
30277
30416
|
for (const r of declaredS) {
|
|
30278
|
-
const st = sandboxImageStatus(stateS, planSandbox(r.name, r.decl, r.warm));
|
|
30417
|
+
const st = sandboxImageStatus(stateS, planSandbox(r.name, r.decl, r.warm, r.bridge));
|
|
30279
30418
|
process.stdout.write(`sandbox image: ${r.name}: ${st.status.toUpperCase()} \u2014 ${st.detail}
|
|
30280
30419
|
`);
|
|
30281
30420
|
}
|