@aiden-ade/sandbox-agent 0.1.50 → 0.1.52
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/index.cjs +2714 -606
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
36
36
|
"use strict";
|
|
37
37
|
var fs = require("fs");
|
|
38
38
|
var Url = require("url");
|
|
39
|
-
var
|
|
39
|
+
var spawn7 = require("child_process").spawn;
|
|
40
40
|
module2.exports = XMLHttpRequest3;
|
|
41
41
|
XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
|
|
42
42
|
function XMLHttpRequest3(opts) {
|
|
@@ -332,7 +332,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
332
332
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
333
333
|
fs.writeFileSync(syncFile, "", "utf8");
|
|
334
334
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
335
|
-
var syncProc =
|
|
335
|
+
var syncProc = spawn7(process.argv[0], ["-e", execString]);
|
|
336
336
|
var statusText;
|
|
337
337
|
while (fs.existsSync(syncFile)) {
|
|
338
338
|
}
|
|
@@ -3247,7 +3247,7 @@ var require_extension = __commonJS({
|
|
|
3247
3247
|
"../../node_modules/engine.io-client/node_modules/ws/lib/extension.js"(exports2, module2) {
|
|
3248
3248
|
"use strict";
|
|
3249
3249
|
var { tokenChars } = require_validation();
|
|
3250
|
-
function
|
|
3250
|
+
function push2(dest, name, elem) {
|
|
3251
3251
|
if (dest[name] === void 0) dest[name] = [elem];
|
|
3252
3252
|
else dest[name].push(elem);
|
|
3253
3253
|
}
|
|
@@ -3277,7 +3277,7 @@ var require_extension = __commonJS({
|
|
|
3277
3277
|
if (end === -1) end = i;
|
|
3278
3278
|
const name = header.slice(start, end);
|
|
3279
3279
|
if (code === 44) {
|
|
3280
|
-
|
|
3280
|
+
push2(offers, name, params);
|
|
3281
3281
|
params = /* @__PURE__ */ Object.create(null);
|
|
3282
3282
|
} else {
|
|
3283
3283
|
extensionName = name;
|
|
@@ -3296,9 +3296,9 @@ var require_extension = __commonJS({
|
|
|
3296
3296
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
3297
3297
|
}
|
|
3298
3298
|
if (end === -1) end = i;
|
|
3299
|
-
|
|
3299
|
+
push2(params, header.slice(start, end), true);
|
|
3300
3300
|
if (code === 44) {
|
|
3301
|
-
|
|
3301
|
+
push2(offers, extensionName, params);
|
|
3302
3302
|
params = /* @__PURE__ */ Object.create(null);
|
|
3303
3303
|
extensionName = void 0;
|
|
3304
3304
|
}
|
|
@@ -3344,9 +3344,9 @@ var require_extension = __commonJS({
|
|
|
3344
3344
|
value2 = value2.replace(/\\/g, "");
|
|
3345
3345
|
mustUnescape = false;
|
|
3346
3346
|
}
|
|
3347
|
-
|
|
3347
|
+
push2(params, paramName, value2);
|
|
3348
3348
|
if (code === 44) {
|
|
3349
|
-
|
|
3349
|
+
push2(offers, extensionName, params);
|
|
3350
3350
|
params = /* @__PURE__ */ Object.create(null);
|
|
3351
3351
|
extensionName = void 0;
|
|
3352
3352
|
}
|
|
@@ -3363,16 +3363,16 @@ var require_extension = __commonJS({
|
|
|
3363
3363
|
if (end === -1) end = i;
|
|
3364
3364
|
const token = header.slice(start, end);
|
|
3365
3365
|
if (extensionName === void 0) {
|
|
3366
|
-
|
|
3366
|
+
push2(offers, token, params);
|
|
3367
3367
|
} else {
|
|
3368
3368
|
if (paramName === void 0) {
|
|
3369
|
-
|
|
3369
|
+
push2(params, token, true);
|
|
3370
3370
|
} else if (mustUnescape) {
|
|
3371
|
-
|
|
3371
|
+
push2(params, paramName, token.replace(/\\/g, ""));
|
|
3372
3372
|
} else {
|
|
3373
|
-
|
|
3373
|
+
push2(params, paramName, token);
|
|
3374
3374
|
}
|
|
3375
|
-
|
|
3375
|
+
push2(offers, extensionName, params);
|
|
3376
3376
|
}
|
|
3377
3377
|
return offers;
|
|
3378
3378
|
}
|
|
@@ -3404,7 +3404,7 @@ var require_websocket = __commonJS({
|
|
|
3404
3404
|
var http2 = require("http");
|
|
3405
3405
|
var net = require("net");
|
|
3406
3406
|
var tls = require("tls");
|
|
3407
|
-
var { randomBytes: randomBytes5, createHash:
|
|
3407
|
+
var { randomBytes: randomBytes5, createHash: createHash8 } = require("crypto");
|
|
3408
3408
|
var { Duplex, Readable } = require("stream");
|
|
3409
3409
|
var { URL: URL2 } = require("url");
|
|
3410
3410
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -4061,7 +4061,7 @@ var require_websocket = __commonJS({
|
|
|
4061
4061
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
4062
4062
|
return;
|
|
4063
4063
|
}
|
|
4064
|
-
const digest =
|
|
4064
|
+
const digest = createHash8("sha1").update(key + GUID).digest("base64");
|
|
4065
4065
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
4066
4066
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
4067
4067
|
return;
|
|
@@ -4428,7 +4428,7 @@ var require_websocket_server = __commonJS({
|
|
|
4428
4428
|
var EventEmitter = require("events");
|
|
4429
4429
|
var http2 = require("http");
|
|
4430
4430
|
var { Duplex } = require("stream");
|
|
4431
|
-
var { createHash:
|
|
4431
|
+
var { createHash: createHash8 } = require("crypto");
|
|
4432
4432
|
var extension = require_extension();
|
|
4433
4433
|
var PerMessageDeflate = require_permessage_deflate();
|
|
4434
4434
|
var subprotocol = require_subprotocol();
|
|
@@ -4725,7 +4725,7 @@ var require_websocket_server = __commonJS({
|
|
|
4725
4725
|
);
|
|
4726
4726
|
}
|
|
4727
4727
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
4728
|
-
const digest =
|
|
4728
|
+
const digest = createHash8("sha1").update(key + GUID).digest("base64");
|
|
4729
4729
|
const headers = [
|
|
4730
4730
|
"HTTP/1.1 101 Switching Protocols",
|
|
4731
4731
|
"Upgrade: websocket",
|
|
@@ -4811,7 +4811,7 @@ var require_websocket_server = __commonJS({
|
|
|
4811
4811
|
});
|
|
4812
4812
|
|
|
4813
4813
|
// src/index.ts
|
|
4814
|
-
var
|
|
4814
|
+
var import_node_child_process10 = require("child_process");
|
|
4815
4815
|
|
|
4816
4816
|
// src/cli-executable.ts
|
|
4817
4817
|
var import_node_child_process = require("child_process");
|
|
@@ -4956,7 +4956,7 @@ function resolveViaWhere(command, env) {
|
|
|
4956
4956
|
return null;
|
|
4957
4957
|
}
|
|
4958
4958
|
function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3, spawnOptions) {
|
|
4959
|
-
return new Promise((
|
|
4959
|
+
return new Promise((resolve7) => {
|
|
4960
4960
|
const isWin = (0, import_node_os.platform)() === "win32";
|
|
4961
4961
|
let command = executable;
|
|
4962
4962
|
let commandArgs = args;
|
|
@@ -4976,7 +4976,7 @@ function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3
|
|
|
4976
4976
|
stdio: closeStdin ? ["pipe", "pipe", "pipe"] : void 0
|
|
4977
4977
|
});
|
|
4978
4978
|
} catch (error) {
|
|
4979
|
-
|
|
4979
|
+
resolve7({ status: null, stdout: "", stderr: "", error });
|
|
4980
4980
|
return;
|
|
4981
4981
|
}
|
|
4982
4982
|
if (closeStdin) {
|
|
@@ -4990,10 +4990,10 @@ function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3
|
|
|
4990
4990
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
4991
4991
|
stderr += chunk;
|
|
4992
4992
|
});
|
|
4993
|
-
child.once("error", (error) =>
|
|
4993
|
+
child.once("error", (error) => resolve7({ status: null, stdout, stderr, error }));
|
|
4994
4994
|
child.once(
|
|
4995
4995
|
"close",
|
|
4996
|
-
(status, signal) =>
|
|
4996
|
+
(status, signal) => resolve7({ status, stdout, stderr, signal: signal ?? null })
|
|
4997
4997
|
);
|
|
4998
4998
|
});
|
|
4999
4999
|
}
|
|
@@ -5204,12 +5204,12 @@ function resolveCommand(args, env) {
|
|
|
5204
5204
|
}
|
|
5205
5205
|
|
|
5206
5206
|
// src/daemon.ts
|
|
5207
|
-
var
|
|
5208
|
-
var
|
|
5209
|
-
var
|
|
5207
|
+
var import_node_child_process8 = require("child_process");
|
|
5208
|
+
var import_node_crypto5 = require("crypto");
|
|
5209
|
+
var import_node_fs14 = require("fs");
|
|
5210
5210
|
var import_node_http = __toESM(require("http"), 1);
|
|
5211
|
-
var
|
|
5212
|
-
var
|
|
5211
|
+
var import_node_os9 = require("os");
|
|
5212
|
+
var import_node_path12 = require("path");
|
|
5213
5213
|
|
|
5214
5214
|
// ../agent-core/dist/index.js
|
|
5215
5215
|
var import_fs = require("fs");
|
|
@@ -5362,7 +5362,7 @@ Every \`start_task_session\` message must include all four of these:
|
|
|
5362
5362
|
3. Write requirements as a team doc (\`create_document\`) before any delegation. Create an artifact only when explicitly requested.
|
|
5363
5363
|
4. Break work into subtasks ordered by dependency. Delegate one at a time unless subtasks are truly independent.
|
|
5364
5364
|
5. Track results and update task status as work completes.`;
|
|
5365
|
-
var GENERAL_PROMPT =
|
|
5365
|
+
var GENERAL_PROMPT = "";
|
|
5366
5366
|
var SETUP_PROMPT = `## You are the Setup Agent
|
|
5367
5367
|
|
|
5368
5368
|
You are the **Setup Agent** for this cloud environment. When identifying yourself, say "I'm the Setup Agent (an Alan agent profile) for this environment."
|
|
@@ -10311,9 +10311,7 @@ Team
|
|
|
10311
10311
|
|
|
10312
10312
|
## Task Logging
|
|
10313
10313
|
|
|
10314
|
-
For non-trivial tasks, log high-signal events as they happen: decisions, changed assumptions, validation results, known-good IDs, blockers, risks, and direction changes. Keep one current task-state summary updated with current state, known-good IDs, open risks, and next action. Do not log routine commands or file reads. Before stopping, ensure the timeline and current state are enough for a human to understand progress and for another agent to resume. If a prior decision is stale, write a newer event that supersedes it
|
|
10315
|
-
|
|
10316
|
-
The specific work you do \u2014 implementation, planning, review, coordination, verification \u2014 is determined by your agent role, described next.`;
|
|
10314
|
+
For non-trivial tasks, log high-signal events as they happen: decisions, changed assumptions, validation results, known-good IDs, blockers, risks, and direction changes. Keep one current task-state summary updated with current state, known-good IDs, open risks, and next action. Do not log routine commands or file reads. Before stopping, ensure the timeline and current state are enough for a human to understand progress and for another agent to resume. If a prior decision is stale, write a newer event that supersedes it.`;
|
|
10317
10315
|
function buildAlanTeamScopePrompt(teamId) {
|
|
10318
10316
|
return [
|
|
10319
10317
|
`You are operating within team \`${teamId}\`.`,
|
|
@@ -10338,6 +10336,7 @@ Examples: "where is X configured", "which file handles Y", "how does Z work", "f
|
|
|
10338
10336
|
- Do NOT start with \`grep\`, \`rg\`, \`find\`, or broad Bash filesystem search for codebase discovery or implementation-answer questions when \`search_code_context\` is available.
|
|
10339
10337
|
- Do NOT repeat or rephrase index searches after the first result identifies useful paths. Continue locally instead.
|
|
10340
10338
|
- Do NOT claim current behavior from an index excerpt without reading the cited local file.
|
|
10339
|
+
- When several repositories are available and repoId is omitted, code context routes automatically using the task workspace, repository-name hints, and indexed evidence. Pass repoId only when the user explicitly names a repository or an existing trusted task selection supplies it. Never invent repo IDs or call list_teams.
|
|
10341
10340
|
|
|
10342
10341
|
### Local follow-ups
|
|
10343
10342
|
- Import/caller structure: use local \`code_graph_query\` or narrow symbol search after reading the cited files.
|
|
@@ -19128,8 +19127,8 @@ async function withTimeout(promise, timeoutMs, fallback) {
|
|
|
19128
19127
|
try {
|
|
19129
19128
|
return await Promise.race([
|
|
19130
19129
|
promise,
|
|
19131
|
-
new Promise((
|
|
19132
|
-
timer = setTimeout(() =>
|
|
19130
|
+
new Promise((resolve7) => {
|
|
19131
|
+
timer = setTimeout(() => resolve7(fallback), timeoutMs);
|
|
19133
19132
|
})
|
|
19134
19133
|
]);
|
|
19135
19134
|
} finally {
|
|
@@ -19142,7 +19141,7 @@ async function collectCodexLimits(env) {
|
|
|
19142
19141
|
const codexCommand = resolveExecutable("codex", runtimeEnv, "ALAN_CODEX_PATH");
|
|
19143
19142
|
if (!codexCommand)
|
|
19144
19143
|
return [];
|
|
19145
|
-
return withTimeout(new Promise((
|
|
19144
|
+
return withTimeout(new Promise((resolve7) => {
|
|
19146
19145
|
const child = (0, import_node_child_process2.spawn)(codexCommand, ["-s", "read-only", "-a", "untrusted", "app-server"], {
|
|
19147
19146
|
env: runtimeEnv,
|
|
19148
19147
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -19154,7 +19153,7 @@ async function collectCodexLimits(env) {
|
|
|
19154
19153
|
pending.clear();
|
|
19155
19154
|
if (!child.killed)
|
|
19156
19155
|
child.kill();
|
|
19157
|
-
|
|
19156
|
+
resolve7(windows);
|
|
19158
19157
|
};
|
|
19159
19158
|
const request = (method, params = {}) => {
|
|
19160
19159
|
const id = nextId++;
|
|
@@ -21987,9 +21986,9 @@ var Socket2 = class extends Emitter {
|
|
|
21987
21986
|
* @return a Promise that will be fulfilled when the server acknowledges the event
|
|
21988
21987
|
*/
|
|
21989
21988
|
emitWithAck(ev, ...args) {
|
|
21990
|
-
return new Promise((
|
|
21989
|
+
return new Promise((resolve7, reject) => {
|
|
21991
21990
|
const fn = (arg1, arg2) => {
|
|
21992
|
-
return arg1 ? reject(arg1) :
|
|
21991
|
+
return arg1 ? reject(arg1) : resolve7(arg2);
|
|
21993
21992
|
};
|
|
21994
21993
|
fn.withError = true;
|
|
21995
21994
|
args.push(fn);
|
|
@@ -22987,231 +22986,1365 @@ Object.assign(lookup, {
|
|
|
22987
22986
|
connect: lookup
|
|
22988
22987
|
});
|
|
22989
22988
|
|
|
22990
|
-
// src/
|
|
22989
|
+
// src/binding-switch-git.ts
|
|
22991
22990
|
var import_node_child_process3 = require("child_process");
|
|
22991
|
+
var import_node_crypto = require("crypto");
|
|
22992
|
+
var import_node_fs4 = require("fs");
|
|
22992
22993
|
var import_node_os4 = require("os");
|
|
22993
|
-
var
|
|
22994
|
-
var
|
|
22995
|
-
|
|
22996
|
-
|
|
22997
|
-
|
|
22998
|
-
|
|
22999
|
-
|
|
23000
|
-
|
|
23001
|
-
|
|
23002
|
-
|
|
23003
|
-
|
|
22994
|
+
var import_node_path4 = require("path");
|
|
22995
|
+
var import_node_url = require("url");
|
|
22996
|
+
var BINDING_SWITCH_GIT_METHODS = [
|
|
22997
|
+
"git.status",
|
|
22998
|
+
"git.checkpoint",
|
|
22999
|
+
"git.push",
|
|
23000
|
+
"git.fetch",
|
|
23001
|
+
"git.prepare_checkout",
|
|
23002
|
+
"git.prepare_worktree",
|
|
23003
|
+
"git.verify_head"
|
|
23004
|
+
];
|
|
23005
|
+
function isBindingSwitchGitMethod(method) {
|
|
23006
|
+
return BINDING_SWITCH_GIT_METHODS.includes(method);
|
|
23004
23007
|
}
|
|
23005
|
-
|
|
23006
|
-
|
|
23007
|
-
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
if (typeof version === "string" && version.trim()) return version.trim();
|
|
23012
|
-
} catch {
|
|
23008
|
+
var BindingSwitchGitError = class extends Error {
|
|
23009
|
+
constructor(code, message, details) {
|
|
23010
|
+
super(message);
|
|
23011
|
+
this.code = code;
|
|
23012
|
+
this.details = details;
|
|
23013
|
+
this.name = "BindingSwitchGitError";
|
|
23013
23014
|
}
|
|
23014
|
-
|
|
23015
|
+
};
|
|
23016
|
+
var LOCAL_GIT_TIMEOUT_MS = 1e4;
|
|
23017
|
+
var NETWORK_GIT_TIMEOUT_MS = 3e4;
|
|
23018
|
+
var MAX_GIT_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
23019
|
+
function runGitResult(cwd, args, options = {}) {
|
|
23020
|
+
const result = (0, import_node_child_process3.spawnSync)("git", args, {
|
|
23021
|
+
cwd,
|
|
23022
|
+
env: { ...getDaemonCliEnvironment(), ...options.env },
|
|
23023
|
+
encoding: "utf8",
|
|
23024
|
+
input: options.input,
|
|
23025
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
23026
|
+
timeout: LOCAL_GIT_TIMEOUT_MS
|
|
23027
|
+
});
|
|
23028
|
+
if (result.error) {
|
|
23029
|
+
return { status: 1, stdout: "", stderr: result.error.message };
|
|
23030
|
+
}
|
|
23031
|
+
return {
|
|
23032
|
+
status: result.status ?? 1,
|
|
23033
|
+
stdout: result.stdout ?? "",
|
|
23034
|
+
stderr: result.stderr ?? ""
|
|
23035
|
+
};
|
|
23015
23036
|
}
|
|
23016
|
-
function
|
|
23017
|
-
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
23037
|
+
function networkGitTimeoutMs() {
|
|
23038
|
+
if (process.env.NODE_ENV !== "test") return NETWORK_GIT_TIMEOUT_MS;
|
|
23039
|
+
const override = Number.parseInt(process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS ?? "", 10);
|
|
23040
|
+
return Number.isFinite(override) && override >= 10 ? Math.min(override, NETWORK_GIT_TIMEOUT_MS) : NETWORK_GIT_TIMEOUT_MS;
|
|
23041
|
+
}
|
|
23042
|
+
async function waitForCheckpointTestBarrier() {
|
|
23043
|
+
const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR : void 0;
|
|
23044
|
+
if (!barrierDir) return;
|
|
23045
|
+
(0, import_node_fs4.mkdirSync)(barrierDir, { recursive: true });
|
|
23046
|
+
const readyPath = (0, import_node_path4.join)(barrierDir, "ready");
|
|
23047
|
+
const releasePath = (0, import_node_path4.join)(barrierDir, "release");
|
|
23048
|
+
(0, import_node_fs4.writeFileSync)(readyPath, "ready\n");
|
|
23049
|
+
const deadline = Date.now() + 5e3;
|
|
23050
|
+
while (!(0, import_node_fs4.existsSync)(releasePath)) {
|
|
23051
|
+
if (Date.now() >= deadline) {
|
|
23052
|
+
throw new BindingSwitchGitError(
|
|
23053
|
+
"checkpoint_prepare_timeout",
|
|
23054
|
+
"Checkpoint preparation test barrier timed out"
|
|
23055
|
+
);
|
|
23056
|
+
}
|
|
23057
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
23058
|
+
}
|
|
23059
|
+
}
|
|
23060
|
+
async function waitForCheckpointFinalizeTestBarrier() {
|
|
23061
|
+
const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR : void 0;
|
|
23062
|
+
if (!barrierDir) return;
|
|
23063
|
+
(0, import_node_fs4.mkdirSync)(barrierDir, { recursive: true });
|
|
23064
|
+
const readyPath = (0, import_node_path4.join)(barrierDir, "ready");
|
|
23065
|
+
const releasePath = (0, import_node_path4.join)(barrierDir, "release");
|
|
23066
|
+
(0, import_node_fs4.writeFileSync)(readyPath, "ready\n");
|
|
23067
|
+
const deadline = Date.now() + 5e3;
|
|
23068
|
+
while (!(0, import_node_fs4.existsSync)(releasePath)) {
|
|
23069
|
+
if (Date.now() >= deadline) {
|
|
23070
|
+
throw new BindingSwitchGitError(
|
|
23071
|
+
"checkpoint_finalize_timeout",
|
|
23072
|
+
"Checkpoint finalization test barrier timed out"
|
|
23073
|
+
);
|
|
23031
23074
|
}
|
|
23075
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
23076
|
+
}
|
|
23077
|
+
}
|
|
23078
|
+
async function runNetworkGit(cwd, args, code) {
|
|
23079
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
23080
|
+
const child = (0, import_node_child_process3.spawn)("git", args, {
|
|
23081
|
+
cwd,
|
|
23082
|
+
env: {
|
|
23083
|
+
...getDaemonCliEnvironment(),
|
|
23084
|
+
GCM_INTERACTIVE: "Never",
|
|
23085
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
23086
|
+
...process.env.NODE_ENV === "test" && process.env.ALAN_TEST_GIT_SSH_COMMAND ? { GIT_SSH_COMMAND: process.env.ALAN_TEST_GIT_SSH_COMMAND } : {}
|
|
23087
|
+
},
|
|
23088
|
+
detached: process.platform !== "win32",
|
|
23089
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
23090
|
+
});
|
|
23032
23091
|
let stdout = "";
|
|
23033
23092
|
let stderr = "";
|
|
23034
|
-
|
|
23035
|
-
|
|
23093
|
+
let outputExceeded = false;
|
|
23094
|
+
let timedOut = false;
|
|
23095
|
+
let settled = false;
|
|
23096
|
+
const killTree = () => {
|
|
23097
|
+
if (child.pid && process.platform !== "win32") {
|
|
23098
|
+
try {
|
|
23099
|
+
process.kill(-child.pid, "SIGKILL");
|
|
23100
|
+
return;
|
|
23101
|
+
} catch {
|
|
23102
|
+
}
|
|
23103
|
+
}
|
|
23104
|
+
child.kill("SIGKILL");
|
|
23105
|
+
};
|
|
23106
|
+
const timer = setTimeout(() => {
|
|
23107
|
+
timedOut = true;
|
|
23108
|
+
killTree();
|
|
23109
|
+
}, networkGitTimeoutMs());
|
|
23110
|
+
timer.unref?.();
|
|
23111
|
+
const append = (current, chunk) => {
|
|
23112
|
+
if (Buffer.byteLength(current) + chunk.byteLength > MAX_GIT_OUTPUT_BYTES) {
|
|
23113
|
+
outputExceeded = true;
|
|
23114
|
+
killTree();
|
|
23115
|
+
return current;
|
|
23116
|
+
}
|
|
23117
|
+
return current + chunk.toString("utf8");
|
|
23118
|
+
};
|
|
23119
|
+
child.stdout.on("data", (chunk) => {
|
|
23120
|
+
stdout = append(stdout, chunk);
|
|
23036
23121
|
});
|
|
23037
|
-
child.stderr
|
|
23038
|
-
stderr
|
|
23122
|
+
child.stderr.on("data", (chunk) => {
|
|
23123
|
+
stderr = append(stderr, chunk);
|
|
23124
|
+
});
|
|
23125
|
+
child.once("error", (error) => {
|
|
23126
|
+
if (settled) return;
|
|
23127
|
+
settled = true;
|
|
23128
|
+
clearTimeout(timer);
|
|
23129
|
+
rejectPromise(new BindingSwitchGitError(code, error.message));
|
|
23130
|
+
});
|
|
23131
|
+
child.once("close", (exitCode) => {
|
|
23132
|
+
if (settled) return;
|
|
23133
|
+
settled = true;
|
|
23134
|
+
clearTimeout(timer);
|
|
23135
|
+
if (timedOut) {
|
|
23136
|
+
rejectPromise(
|
|
23137
|
+
new BindingSwitchGitError(
|
|
23138
|
+
"git_network_timeout",
|
|
23139
|
+
"The Git network operation timed out and was cancelled"
|
|
23140
|
+
)
|
|
23141
|
+
);
|
|
23142
|
+
return;
|
|
23143
|
+
}
|
|
23144
|
+
if (outputExceeded) {
|
|
23145
|
+
rejectPromise(
|
|
23146
|
+
new BindingSwitchGitError(
|
|
23147
|
+
"git_output_limit_exceeded",
|
|
23148
|
+
"The Git network operation produced too much output and was cancelled"
|
|
23149
|
+
)
|
|
23150
|
+
);
|
|
23151
|
+
return;
|
|
23152
|
+
}
|
|
23153
|
+
if (exitCode !== 0) {
|
|
23154
|
+
rejectPromise(
|
|
23155
|
+
new BindingSwitchGitError(code, (stderr || stdout || "Git network command failed").trim())
|
|
23156
|
+
);
|
|
23157
|
+
return;
|
|
23158
|
+
}
|
|
23159
|
+
resolvePromise(stdout.trim());
|
|
23039
23160
|
});
|
|
23040
|
-
child.once("error", (error) => resolve6({ status: null, stdout, stderr, error }));
|
|
23041
|
-
child.once(
|
|
23042
|
-
"close",
|
|
23043
|
-
(status, signal) => resolve6({
|
|
23044
|
-
status,
|
|
23045
|
-
stdout,
|
|
23046
|
-
stderr,
|
|
23047
|
-
// Spawn `timeout` kills by signal without an `error` event; surface it
|
|
23048
|
-
// so a timed-out install is reported as a failure, not status null + ok.
|
|
23049
|
-
...signal ? { error: new Error(`npm install killed by ${signal}`) } : {}
|
|
23050
|
-
})
|
|
23051
|
-
);
|
|
23052
23161
|
});
|
|
23053
23162
|
}
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
const notify = options?.notify ?? ((message) => console.info(`[cli-ensure-install] ${message}`));
|
|
23057
|
-
notify(`Installing ${spec.label} CLI (${installSpec})\u2026`);
|
|
23058
|
-
const runner = options?.runner ?? runNpmInstall;
|
|
23059
|
-
const result = await runner(installSpec, getDaemonCliEnvironment(), INSTALL_TIMEOUT_MS);
|
|
23060
|
-
if (result.error || result.status !== 0) {
|
|
23061
|
-
console.warn(`[cli-ensure-install] npm install -g ${installSpec} failed`, {
|
|
23062
|
-
status: result.status,
|
|
23063
|
-
error: result.error?.message,
|
|
23064
|
-
output: `${result.stderr || result.stdout}`.trim().slice(-500)
|
|
23065
|
-
});
|
|
23066
|
-
notify(`Failed to install ${spec.label} CLI (${installSpec}).`);
|
|
23067
|
-
return false;
|
|
23068
|
-
}
|
|
23069
|
-
invalidateResolvedCli(spec.command);
|
|
23070
|
-
notify(`Installed ${spec.label} CLI (${installSpec}).`);
|
|
23071
|
-
return true;
|
|
23163
|
+
function runGit(cwd, args, code = "git_operation_failed") {
|
|
23164
|
+
return runGitWithOptions(cwd, args, {}, code);
|
|
23072
23165
|
}
|
|
23073
|
-
function
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23082
|
-
});
|
|
23083
|
-
installOutcomes.set(spec.npmPackage, attempt);
|
|
23084
|
-
return attempt;
|
|
23166
|
+
function runGitWithOptions(cwd, args, options, code = "git_operation_failed") {
|
|
23167
|
+
const result = runGitResult(cwd, args, options);
|
|
23168
|
+
if (result.status !== 0) {
|
|
23169
|
+
throw new BindingSwitchGitError(
|
|
23170
|
+
code,
|
|
23171
|
+
(result.stderr || result.stdout || "Git command failed").trim()
|
|
23172
|
+
);
|
|
23173
|
+
}
|
|
23174
|
+
return result.stdout.trim();
|
|
23085
23175
|
}
|
|
23086
|
-
|
|
23087
|
-
|
|
23088
|
-
var import_node_fs5 = require("fs");
|
|
23089
|
-
|
|
23090
|
-
// src/runtime-config.ts
|
|
23091
|
-
var import_node_fs4 = require("fs");
|
|
23092
|
-
var import_node_os5 = require("os");
|
|
23093
|
-
var import_node_path4 = require("path");
|
|
23094
|
-
var DEFAULT_AGENT_DIR = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".alan", "agent");
|
|
23095
|
-
var LEGACY_AGENT_DIR = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".aiden", "agent");
|
|
23096
|
-
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Map([
|
|
23097
|
-
["apiUrl", "string"],
|
|
23098
|
-
["wsUrl", "string"],
|
|
23099
|
-
["endpointProfile", "string"],
|
|
23100
|
-
["teamId", "string"],
|
|
23101
|
-
["runtimeId", "string"],
|
|
23102
|
-
["runtimeToken", "string"],
|
|
23103
|
-
["localApiPort", "number"],
|
|
23104
|
-
["localApiToken", "string"],
|
|
23105
|
-
["displayName", "string"]
|
|
23106
|
-
]);
|
|
23107
|
-
function sanitizeAgentConfig(config) {
|
|
23108
|
-
const sanitized = { ...config };
|
|
23109
|
-
delete sanitized.teamId;
|
|
23110
|
-
delete sanitized.organizationId;
|
|
23111
|
-
delete sanitized.currentTeamId;
|
|
23112
|
-
delete sanitized.currentOrganizationId;
|
|
23113
|
-
return sanitized;
|
|
23176
|
+
function sourceChangedDuringCheckpoint(message) {
|
|
23177
|
+
return new BindingSwitchGitError("source_changed_since_approval", message);
|
|
23114
23178
|
}
|
|
23115
|
-
function
|
|
23179
|
+
function acquireCheckpointHeadLock(locks) {
|
|
23116
23180
|
try {
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(configPath, "utf8"));
|
|
23120
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
23121
|
-
const entries = Object.entries(parsed);
|
|
23122
|
-
return entries.some(([key]) => AGENT_CONFIG_KEYS.has(key)) && entries.every(([key, value2]) => {
|
|
23123
|
-
const expectedType = AGENT_CONFIG_KEYS.get(key);
|
|
23124
|
-
return expectedType === void 0 || typeof value2 === expectedType;
|
|
23125
|
-
});
|
|
23181
|
+
(0, import_node_fs4.writeFileSync)(locks.headLockPath, "", { flag: "wx" });
|
|
23182
|
+
locks.headLockHeld = true;
|
|
23126
23183
|
} catch {
|
|
23127
|
-
|
|
23184
|
+
throw sourceChangedDuringCheckpoint(
|
|
23185
|
+
"Source repository is being changed by another Git operation"
|
|
23186
|
+
);
|
|
23128
23187
|
}
|
|
23129
23188
|
}
|
|
23130
|
-
function
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
const
|
|
23137
|
-
|
|
23138
|
-
|
|
23189
|
+
function releaseCheckpointHeadLock(locks) {
|
|
23190
|
+
if (!locks.headLockHeld) return;
|
|
23191
|
+
(0, import_node_fs4.rmSync)(locks.headLockPath, { force: true });
|
|
23192
|
+
locks.headLockHeld = false;
|
|
23193
|
+
}
|
|
23194
|
+
function acquireCheckpointRepositoryLocks(repoRoot) {
|
|
23195
|
+
const gitDir = runGit(
|
|
23196
|
+
repoRoot,
|
|
23197
|
+
["rev-parse", "--absolute-git-dir"],
|
|
23198
|
+
"checkpoint_lock_unavailable"
|
|
23199
|
+
);
|
|
23200
|
+
const indexPath = runGit(
|
|
23201
|
+
repoRoot,
|
|
23202
|
+
["rev-parse", "--path-format=absolute", "--git-path", "index"],
|
|
23203
|
+
"checkpoint_lock_unavailable"
|
|
23204
|
+
);
|
|
23205
|
+
const locks = {
|
|
23206
|
+
headLockPath: (0, import_node_path4.join)(gitDir, "HEAD.lock"),
|
|
23207
|
+
headLockHeld: false,
|
|
23208
|
+
indexLockPath: `${indexPath}.lock`,
|
|
23209
|
+
indexLockHeld: false,
|
|
23210
|
+
indexPath
|
|
23211
|
+
};
|
|
23139
23212
|
try {
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
|
|
23146
|
-
|
|
23213
|
+
(0, import_node_fs4.writeFileSync)(locks.indexLockPath, "", { flag: "wx" });
|
|
23214
|
+
locks.indexLockHeld = true;
|
|
23215
|
+
acquireCheckpointHeadLock(locks);
|
|
23216
|
+
return locks;
|
|
23217
|
+
} catch {
|
|
23218
|
+
if (locks.indexLockHeld) {
|
|
23219
|
+
(0, import_node_fs4.rmSync)(locks.indexLockPath, { force: true });
|
|
23220
|
+
locks.indexLockHeld = false;
|
|
23147
23221
|
}
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
(0, import_node_fs4.copyFileSync)(legacyPath, alanPath, import_node_fs4.constants.COPYFILE_EXCL);
|
|
23151
|
-
copied = true;
|
|
23152
|
-
const migrated = sanitizeAgentConfig(
|
|
23153
|
-
JSON.parse((0, import_node_fs4.readFileSync)(alanPath, "utf8"))
|
|
23222
|
+
throw sourceChangedDuringCheckpoint(
|
|
23223
|
+
"Source repository is being changed by another Git operation"
|
|
23154
23224
|
);
|
|
23155
|
-
(0, import_node_fs4.writeFileSync)(alanPath, JSON.stringify(migrated, null, 2));
|
|
23156
|
-
(0, import_node_fs4.chmodSync)(alanPath, sourceFileStat.mode & 511);
|
|
23157
|
-
(0, import_node_fs4.utimesSync)(alanPath, sourceFileStat.atime, sourceFileStat.mtime);
|
|
23158
|
-
if (!isValidAgentConfig(alanPath)) {
|
|
23159
|
-
(0, import_node_fs4.unlinkSync)(alanPath);
|
|
23160
|
-
return false;
|
|
23161
|
-
}
|
|
23162
|
-
return true;
|
|
23163
|
-
} catch {
|
|
23164
|
-
if (copied && (0, import_node_fs4.existsSync)(alanPath)) (0, import_node_fs4.unlinkSync)(alanPath);
|
|
23165
|
-
return false;
|
|
23166
23225
|
}
|
|
23167
23226
|
}
|
|
23168
|
-
function
|
|
23169
|
-
|
|
23170
|
-
|
|
23171
|
-
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
if (input?.baseDir === void 0) migrateLegacyAgentConfig({ profile });
|
|
23175
|
-
return (0, import_node_path4.join)(baseDir, resolveAgentConfigFileName(profile));
|
|
23227
|
+
function releaseCheckpointRepositoryLocks(locks) {
|
|
23228
|
+
releaseCheckpointHeadLock(locks);
|
|
23229
|
+
if (locks.indexLockHeld) {
|
|
23230
|
+
(0, import_node_fs4.rmSync)(locks.indexLockPath, { force: true });
|
|
23231
|
+
locks.indexLockHeld = false;
|
|
23232
|
+
}
|
|
23176
23233
|
}
|
|
23177
|
-
function
|
|
23178
|
-
|
|
23234
|
+
function assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha) {
|
|
23235
|
+
const currentRef = runGit(
|
|
23236
|
+
repoRoot,
|
|
23237
|
+
["symbolic-ref", "-q", "HEAD"],
|
|
23238
|
+
"source_changed_since_approval"
|
|
23239
|
+
);
|
|
23240
|
+
const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_changed_since_approval");
|
|
23241
|
+
if (currentRef !== expectedRef || currentHead !== expectedHeadSha) {
|
|
23242
|
+
throw sourceChangedDuringCheckpoint(
|
|
23243
|
+
"Source HEAD or branch changed during checkpoint finalization"
|
|
23244
|
+
);
|
|
23245
|
+
}
|
|
23179
23246
|
}
|
|
23180
|
-
function
|
|
23181
|
-
const
|
|
23182
|
-
const
|
|
23183
|
-
|
|
23247
|
+
function createCheckpointIndexBaseline(repoRoot, checkpointSha) {
|
|
23248
|
+
const tempDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "alan-checkpoint-index-"));
|
|
23249
|
+
const indexPath = (0, import_node_path4.join)(tempDir, "index");
|
|
23250
|
+
try {
|
|
23251
|
+
runGitWithOptions(
|
|
23252
|
+
repoRoot,
|
|
23253
|
+
["read-tree", checkpointSha],
|
|
23254
|
+
{ env: { GIT_INDEX_FILE: indexPath } },
|
|
23255
|
+
"checkpoint_index_sync_failed"
|
|
23256
|
+
);
|
|
23257
|
+
return { indexPath, tempDir };
|
|
23258
|
+
} catch (error) {
|
|
23259
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
23260
|
+
throw error;
|
|
23261
|
+
}
|
|
23184
23262
|
}
|
|
23185
|
-
function
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23263
|
+
function installCheckpointIndexBaseline(locks, baselineIndexPath) {
|
|
23264
|
+
try {
|
|
23265
|
+
(0, import_node_fs4.copyFileSync)(baselineIndexPath, locks.indexLockPath);
|
|
23266
|
+
(0, import_node_fs4.renameSync)(locks.indexLockPath, locks.indexPath);
|
|
23267
|
+
locks.indexLockHeld = false;
|
|
23268
|
+
} catch (error) {
|
|
23269
|
+
throw new BindingSwitchGitError(
|
|
23270
|
+
"checkpoint_index_sync_failed",
|
|
23271
|
+
error instanceof Error ? error.message : "The checkpoint index could not be installed"
|
|
23272
|
+
);
|
|
23273
|
+
}
|
|
23192
23274
|
}
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
this.inspect = inspect;
|
|
23275
|
+
function requiredString(params, key) {
|
|
23276
|
+
const value2 = params[key];
|
|
23277
|
+
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
23278
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is required`);
|
|
23198
23279
|
}
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
if (readiness.ready) {
|
|
23202
|
-
this.issue = null;
|
|
23203
|
-
return;
|
|
23204
|
-
}
|
|
23205
|
-
this.issue = { path, readiness };
|
|
23280
|
+
if (/[\0\r\n]/.test(value2)) {
|
|
23281
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} contains unsafe characters`);
|
|
23206
23282
|
}
|
|
23207
|
-
|
|
23208
|
-
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
|
|
23283
|
+
return value2;
|
|
23284
|
+
}
|
|
23285
|
+
function requiredCommitMessage(params) {
|
|
23286
|
+
const value2 = params.commitMessage;
|
|
23287
|
+
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
23288
|
+
throw new BindingSwitchGitError("invalid_git_request", "commitMessage is required");
|
|
23289
|
+
}
|
|
23290
|
+
if (value2.includes("\0")) {
|
|
23291
|
+
throw new BindingSwitchGitError(
|
|
23292
|
+
"invalid_git_request",
|
|
23293
|
+
"commitMessage contains unsafe characters"
|
|
23294
|
+
);
|
|
23295
|
+
}
|
|
23296
|
+
return value2;
|
|
23297
|
+
}
|
|
23298
|
+
function optionalString(params, key) {
|
|
23299
|
+
const value2 = params[key];
|
|
23300
|
+
if (value2 === void 0 || value2 === null) return void 0;
|
|
23301
|
+
if (typeof value2 !== "string" || value2.trim() === "" || /[\0\r\n]/.test(value2)) {
|
|
23302
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is invalid`);
|
|
23303
|
+
}
|
|
23304
|
+
return value2;
|
|
23305
|
+
}
|
|
23306
|
+
function commonRequest(params) {
|
|
23307
|
+
return {
|
|
23308
|
+
switchId: requiredString(params, "switchId"),
|
|
23309
|
+
repoKey: requiredString(params, "repoKey"),
|
|
23310
|
+
repoPath: (0, import_node_path4.resolve)(requiredString(params, "repoPath"))
|
|
23311
|
+
};
|
|
23312
|
+
}
|
|
23313
|
+
function assertCommitSha(sha, key) {
|
|
23314
|
+
if (!/^[0-9a-f]{40,64}$/i.test(sha)) {
|
|
23315
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is not a commit SHA`);
|
|
23316
|
+
}
|
|
23317
|
+
}
|
|
23318
|
+
function assertValidBranch(branch) {
|
|
23319
|
+
if (branch.startsWith("-")) {
|
|
23320
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch is not a safe Git ref");
|
|
23321
|
+
}
|
|
23322
|
+
const result = (0, import_node_child_process3.spawnSync)("git", ["check-ref-format", "--branch", branch], {
|
|
23323
|
+
env: getDaemonCliEnvironment(),
|
|
23324
|
+
encoding: "utf8"
|
|
23325
|
+
});
|
|
23326
|
+
if (result.status !== 0) {
|
|
23327
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch is not a valid Git ref");
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23330
|
+
function validatedRemote(params) {
|
|
23331
|
+
const remote = optionalString(params, "remote") ?? "origin";
|
|
23332
|
+
if (remote.startsWith("-") || remote.includes("/") || remote.includes("\\")) {
|
|
23333
|
+
throw new BindingSwitchGitError("invalid_git_request", "remote is not a safe remote name");
|
|
23334
|
+
}
|
|
23335
|
+
return remote;
|
|
23336
|
+
}
|
|
23337
|
+
function canonicalRemoteUrl(repoRoot, value2) {
|
|
23338
|
+
const trimmed = value2.trim();
|
|
23339
|
+
if (!trimmed || /[\0\r\n]/.test(trimmed)) {
|
|
23340
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23341
|
+
}
|
|
23342
|
+
const scpStyle = trimmed.match(/^([^/@:\s]+)@([^/:\s]+):(.+)$/);
|
|
23343
|
+
const urlValue = scpStyle ? `ssh://${scpStyle[1]}@${scpStyle[2]}/${scpStyle[3]}` : trimmed;
|
|
23344
|
+
if ((0, import_node_path4.isAbsolute)(urlValue) || urlValue.startsWith("./") || urlValue.startsWith("../")) {
|
|
23345
|
+
const absolute = (0, import_node_path4.isAbsolute)(urlValue) ? urlValue : (0, import_node_path4.resolve)(repoRoot, urlValue);
|
|
23346
|
+
return `file://${canonicalPathWithMissingTail(absolute).replace(/\/+$/, "").replace(/\.git$/, "")}`;
|
|
23347
|
+
}
|
|
23348
|
+
let parsed;
|
|
23349
|
+
try {
|
|
23350
|
+
parsed = new URL(urlValue);
|
|
23351
|
+
} catch {
|
|
23352
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23353
|
+
}
|
|
23354
|
+
if (parsed.protocol === "file:") {
|
|
23355
|
+
return `file://${canonicalPathWithMissingTail((0, import_node_url.fileURLToPath)(parsed)).replace(/\/+$/, "").replace(/\.git$/, "")}`;
|
|
23356
|
+
}
|
|
23357
|
+
if (parsed.search || parsed.hash) {
|
|
23358
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23359
|
+
}
|
|
23360
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(parsed.protocol)) {
|
|
23361
|
+
throw new BindingSwitchGitError(
|
|
23362
|
+
"invalid_git_request",
|
|
23363
|
+
"Remote URL uses an unsupported protocol"
|
|
23364
|
+
);
|
|
23365
|
+
}
|
|
23366
|
+
if (parsed.password || (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username) {
|
|
23367
|
+
throw new BindingSwitchGitError(
|
|
23368
|
+
"remote_url_credentials_rejected",
|
|
23369
|
+
"Credential-bearing remote URLs are not allowed for environment migration"
|
|
23370
|
+
);
|
|
23371
|
+
}
|
|
23372
|
+
const defaultPort = parsed.protocol === "https:" && parsed.port === "443" || parsed.protocol === "http:" && parsed.port === "80" || parsed.protocol === "ssh:" && parsed.port === "22";
|
|
23373
|
+
const transport = parsed.protocol === "https:" || parsed.protocol === "ssh:" ? "secure:" : parsed.protocol;
|
|
23374
|
+
const authority = `${parsed.hostname.toLowerCase()}${parsed.port && !defaultPort ? `:${parsed.port}` : ""}`;
|
|
23375
|
+
const path = parsed.pathname.replace(/\/+/g, "/").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
23376
|
+
return `${transport}//${authority}${path}`;
|
|
23377
|
+
}
|
|
23378
|
+
function verifiedRemote(repoRoot, params, includePushUrls) {
|
|
23379
|
+
const remote = validatedRemote(params);
|
|
23380
|
+
const expectedRemoteUrl = canonicalRemoteUrl(
|
|
23381
|
+
repoRoot,
|
|
23382
|
+
requiredString(params, "expectedRemoteUrl")
|
|
23383
|
+
);
|
|
23384
|
+
const fetchUrls = runGit(
|
|
23385
|
+
repoRoot,
|
|
23386
|
+
["remote", "get-url", "--all", remote],
|
|
23387
|
+
"git_remote_unavailable"
|
|
23388
|
+
).split("\n").filter(Boolean);
|
|
23389
|
+
const pushUrls = includePushUrls ? runGit(repoRoot, ["remote", "get-url", "--push", "--all", remote], "git_remote_unavailable").split("\n").filter(Boolean) : [];
|
|
23390
|
+
const urls = [...fetchUrls, ...pushUrls];
|
|
23391
|
+
if (urls.length === 0 || urls.some((url2) => canonicalRemoteUrl(repoRoot, url2) !== expectedRemoteUrl)) {
|
|
23392
|
+
throw new BindingSwitchGitError(
|
|
23393
|
+
"remote_url_mismatch",
|
|
23394
|
+
"The configured Git remote does not match the repository selected for migration"
|
|
23395
|
+
);
|
|
23396
|
+
}
|
|
23397
|
+
return { remote, expectedRemoteUrl, remoteUrl: fetchUrls[0] };
|
|
23398
|
+
}
|
|
23399
|
+
function inspectedRemoteUrl(repoRoot, params) {
|
|
23400
|
+
if (optionalString(params, "expectedRemoteUrl")) {
|
|
23401
|
+
return verifiedRemote(repoRoot, params, false).remoteUrl;
|
|
23402
|
+
}
|
|
23403
|
+
const remote = validatedRemote(params);
|
|
23404
|
+
const result = runGitResult(repoRoot, ["remote", "get-url", "--all", remote]);
|
|
23405
|
+
const urls = result.status === 0 ? result.stdout.split("\n").filter(Boolean) : [];
|
|
23406
|
+
if (urls.length === 0) return null;
|
|
23407
|
+
for (const url2 of urls) canonicalRemoteUrl(repoRoot, url2);
|
|
23408
|
+
return urls[0];
|
|
23409
|
+
}
|
|
23410
|
+
function isPathContained(childPath, parentPath) {
|
|
23411
|
+
const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parentPath), (0, import_node_path4.resolve)(childPath));
|
|
23412
|
+
return rel === "" || !(0, import_node_path4.isAbsolute)(rel) && rel !== ".." && !rel.startsWith(`..${import_node_path4.sep}`);
|
|
23413
|
+
}
|
|
23414
|
+
function canonicalPathWithMissingTail(path) {
|
|
23415
|
+
const missingSegments = [];
|
|
23416
|
+
let cursor = (0, import_node_path4.resolve)(path);
|
|
23417
|
+
while (!(0, import_node_fs4.existsSync)(cursor)) {
|
|
23418
|
+
const parent = (0, import_node_path4.dirname)(cursor);
|
|
23419
|
+
if (parent === cursor) {
|
|
23420
|
+
throw new BindingSwitchGitError(
|
|
23421
|
+
"path_canonicalization_failed",
|
|
23422
|
+
"The selected path has no existing canonical ancestor"
|
|
23423
|
+
);
|
|
23424
|
+
}
|
|
23425
|
+
missingSegments.unshift((0, import_node_path4.basename)(cursor));
|
|
23426
|
+
cursor = parent;
|
|
23427
|
+
}
|
|
23428
|
+
return (0, import_node_path4.join)(import_node_fs4.realpathSync.native(cursor), ...missingSegments);
|
|
23429
|
+
}
|
|
23430
|
+
function assertCanonicalPathContained(childPath, parentPath, code, message) {
|
|
23431
|
+
if (!isPathContained(
|
|
23432
|
+
canonicalPathWithMissingTail(childPath),
|
|
23433
|
+
canonicalPathWithMissingTail(parentPath)
|
|
23434
|
+
)) {
|
|
23435
|
+
throw new BindingSwitchGitError(code, message);
|
|
23436
|
+
}
|
|
23437
|
+
}
|
|
23438
|
+
function alanWorktreeRoot(projectRoot) {
|
|
23439
|
+
return (0, import_node_path4.join)((0, import_node_path4.dirname)(projectRoot), `${(0, import_node_path4.basename)(projectRoot)}-alan-worktrees`);
|
|
23440
|
+
}
|
|
23441
|
+
function resolveRepositoryRoot(requestedPath) {
|
|
23442
|
+
if (!(0, import_node_fs4.existsSync)(requestedPath)) {
|
|
23443
|
+
throw new BindingSwitchGitError(
|
|
23444
|
+
"repository_not_found",
|
|
23445
|
+
`The selected repository path does not exist: ${requestedPath}`
|
|
23446
|
+
);
|
|
23447
|
+
}
|
|
23448
|
+
const root = runGitResult(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
23449
|
+
if (root.status !== 0 || !root.stdout.trim()) {
|
|
23450
|
+
throw new BindingSwitchGitError(
|
|
23451
|
+
"repository_not_found",
|
|
23452
|
+
"The selected folder is not inside a Git repository"
|
|
23453
|
+
);
|
|
23454
|
+
}
|
|
23455
|
+
return import_node_fs4.realpathSync.native((0, import_node_path4.resolve)(root.stdout.trim()));
|
|
23456
|
+
}
|
|
23457
|
+
function listWorktrees(repoRoot) {
|
|
23458
|
+
return runGit(repoRoot, ["worktree", "list", "--porcelain"]).split("\n\n").flatMap((entry) => {
|
|
23459
|
+
const lines = entry.split("\n");
|
|
23460
|
+
const path = lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
|
|
23461
|
+
if (!path) return [];
|
|
23462
|
+
return [
|
|
23463
|
+
{
|
|
23464
|
+
path: canonicalPathWithMissingTail(path),
|
|
23465
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "") ?? null
|
|
23466
|
+
}
|
|
23467
|
+
];
|
|
23468
|
+
});
|
|
23469
|
+
}
|
|
23470
|
+
function resolveAuthorizedRepository(params, requireTrustedContext = false) {
|
|
23471
|
+
const { repoPath } = commonRequest(params);
|
|
23472
|
+
const repoRoot = resolveRepositoryRoot(repoPath);
|
|
23473
|
+
const expectedRepoRoot = requireTrustedContext ? requiredString(params, "expectedRepoRoot") : optionalString(params, "expectedRepoRoot");
|
|
23474
|
+
if (expectedRepoRoot && resolveRepositoryRoot((0, import_node_path4.resolve)(expectedRepoRoot)) !== repoRoot) {
|
|
23475
|
+
throw new BindingSwitchGitError(
|
|
23476
|
+
"repository_path_changed",
|
|
23477
|
+
"The selected path now resolves to a different Git repository"
|
|
23478
|
+
);
|
|
23479
|
+
}
|
|
23480
|
+
const projectPath = requireTrustedContext ? requiredString(params, "projectPath") : optionalString(params, "projectPath");
|
|
23481
|
+
if (!projectPath) return repoRoot;
|
|
23482
|
+
const projectRoot = resolveRepositoryRoot((0, import_node_path4.resolve)(projectPath));
|
|
23483
|
+
const registrations = listWorktrees(projectRoot);
|
|
23484
|
+
const registered = registrations.find(({ path }) => path === repoRoot);
|
|
23485
|
+
if (!registered) {
|
|
23486
|
+
throw new BindingSwitchGitError(
|
|
23487
|
+
"repository_path_not_authorized",
|
|
23488
|
+
"The selected repository is not the Project checkout or one of its registered worktrees"
|
|
23489
|
+
);
|
|
23490
|
+
}
|
|
23491
|
+
const mainRoot = registrations[0]?.path;
|
|
23492
|
+
if (mainRoot && repoRoot !== mainRoot && !isPathContained(repoRoot, canonicalPathWithMissingTail(alanWorktreeRoot(mainRoot)))) {
|
|
23493
|
+
throw new BindingSwitchGitError(
|
|
23494
|
+
"repository_path_not_authorized",
|
|
23495
|
+
"The selected worktree is outside the Alan-managed worktree directory"
|
|
23496
|
+
);
|
|
23497
|
+
}
|
|
23498
|
+
return repoRoot;
|
|
23499
|
+
}
|
|
23500
|
+
function readRawGit(repoRoot, args, code) {
|
|
23501
|
+
const result = runGitResult(repoRoot, args);
|
|
23502
|
+
if (result.status !== 0) {
|
|
23503
|
+
throw new BindingSwitchGitError(
|
|
23504
|
+
code,
|
|
23505
|
+
(result.stderr || result.stdout || "Git inspection failed").trim()
|
|
23506
|
+
);
|
|
23507
|
+
}
|
|
23508
|
+
return result.stdout;
|
|
23509
|
+
}
|
|
23510
|
+
function updateLengthPrefixed(hash, value2) {
|
|
23511
|
+
const bytes = Buffer.from(value2, "utf8");
|
|
23512
|
+
hash.update(String(bytes.byteLength));
|
|
23513
|
+
hash.update("\0");
|
|
23514
|
+
hash.update(bytes);
|
|
23515
|
+
hash.update("\0");
|
|
23516
|
+
}
|
|
23517
|
+
function proposedTreeSha(repoRoot, headSha) {
|
|
23518
|
+
const tempDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "alan-switch-index-"));
|
|
23519
|
+
const indexPath = (0, import_node_path4.join)(tempDir, "index");
|
|
23520
|
+
const options = { env: { GIT_INDEX_FILE: indexPath } };
|
|
23521
|
+
try {
|
|
23522
|
+
if (headSha) {
|
|
23523
|
+
runGitWithOptions(repoRoot, ["read-tree", headSha], options, "git_status_unreadable");
|
|
23524
|
+
} else {
|
|
23525
|
+
runGitWithOptions(repoRoot, ["read-tree", "--empty"], options, "git_status_unreadable");
|
|
23526
|
+
}
|
|
23527
|
+
runGitWithOptions(repoRoot, ["add", "--all"], options, "git_status_unreadable");
|
|
23528
|
+
const treeSha = runGitWithOptions(repoRoot, ["write-tree"], options, "git_status_unreadable");
|
|
23529
|
+
assertCommitSha(treeSha, "proposedTreeSha");
|
|
23530
|
+
return treeSha;
|
|
23531
|
+
} finally {
|
|
23532
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
23533
|
+
}
|
|
23534
|
+
}
|
|
23535
|
+
function statusFingerprint(repoRoot, porcelain, proposedTree) {
|
|
23536
|
+
const unstagedPatch = readRawGit(
|
|
23537
|
+
repoRoot,
|
|
23538
|
+
["diff", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"],
|
|
23539
|
+
"git_status_unreadable"
|
|
23540
|
+
);
|
|
23541
|
+
const stagedPatch = readRawGit(
|
|
23542
|
+
repoRoot,
|
|
23543
|
+
["diff", "--cached", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"],
|
|
23544
|
+
"git_status_unreadable"
|
|
23545
|
+
);
|
|
23546
|
+
const untrackedRaw = readRawGit(
|
|
23547
|
+
repoRoot,
|
|
23548
|
+
["ls-files", "--others", "--exclude-standard", "-z"],
|
|
23549
|
+
"git_status_unreadable"
|
|
23550
|
+
);
|
|
23551
|
+
const untrackedPaths = untrackedRaw.split("\0").filter(Boolean);
|
|
23552
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
23553
|
+
updateLengthPrefixed(hash, porcelain);
|
|
23554
|
+
updateLengthPrefixed(hash, proposedTree);
|
|
23555
|
+
updateLengthPrefixed(hash, unstagedPatch);
|
|
23556
|
+
updateLengthPrefixed(hash, stagedPatch);
|
|
23557
|
+
for (let index = 0; index < untrackedPaths.length; index += 128) {
|
|
23558
|
+
const paths = untrackedPaths.slice(index, index + 128);
|
|
23559
|
+
const hashes = readRawGit(
|
|
23560
|
+
repoRoot,
|
|
23561
|
+
["hash-object", "--no-filters", "--", ...paths],
|
|
23562
|
+
"git_status_unreadable"
|
|
23563
|
+
);
|
|
23564
|
+
updateLengthPrefixed(hash, paths.join("\0"));
|
|
23565
|
+
updateLengthPrefixed(hash, hashes);
|
|
23566
|
+
}
|
|
23567
|
+
return hash.digest("hex");
|
|
23568
|
+
}
|
|
23569
|
+
function readStatus(repoRoot) {
|
|
23570
|
+
const headResult = runGitResult(repoRoot, ["rev-parse", "HEAD"]);
|
|
23571
|
+
const headSha = headResult.status === 0 ? headResult.stdout.trim() || null : null;
|
|
23572
|
+
const branchResult = runGitResult(repoRoot, ["branch", "--show-current"]);
|
|
23573
|
+
const branch = branchResult.status === 0 ? branchResult.stdout.trim() || null : null;
|
|
23574
|
+
const proposedTree = proposedTreeSha(repoRoot, headSha);
|
|
23575
|
+
const porcelain = runGit(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
23576
|
+
const displayStatus = runGit(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
|
|
23577
|
+
const upstreamResult = runGitResult(repoRoot, [
|
|
23578
|
+
"rev-parse",
|
|
23579
|
+
"--abbrev-ref",
|
|
23580
|
+
"--symbolic-full-name",
|
|
23581
|
+
"@{upstream}"
|
|
23582
|
+
]);
|
|
23583
|
+
const upstream = upstreamResult.status === 0 ? upstreamResult.stdout.trim() || null : null;
|
|
23584
|
+
let ahead = null;
|
|
23585
|
+
let behind = null;
|
|
23586
|
+
if (upstream) {
|
|
23587
|
+
const counts = runGit(repoRoot, [
|
|
23588
|
+
"rev-list",
|
|
23589
|
+
"--left-right",
|
|
23590
|
+
"--count",
|
|
23591
|
+
`${upstream}...HEAD`
|
|
23592
|
+
]).split(/\s+/);
|
|
23593
|
+
const parsedBehind = Number.parseInt(counts[0] ?? "", 10);
|
|
23594
|
+
const parsedAhead = Number.parseInt(counts[1] ?? "", 10);
|
|
23595
|
+
behind = Number.isNaN(parsedBehind) ? null : parsedBehind;
|
|
23596
|
+
ahead = Number.isNaN(parsedAhead) ? null : parsedAhead;
|
|
23597
|
+
}
|
|
23598
|
+
return {
|
|
23599
|
+
repoRoot,
|
|
23600
|
+
remoteUrl: null,
|
|
23601
|
+
branch,
|
|
23602
|
+
headSha,
|
|
23603
|
+
dirty: porcelain.length > 0,
|
|
23604
|
+
filesChanged: displayStatus ? displayStatus.split("\n").length : 0,
|
|
23605
|
+
ahead,
|
|
23606
|
+
behind,
|
|
23607
|
+
hasUpstream: upstream !== null,
|
|
23608
|
+
upstream,
|
|
23609
|
+
statusFingerprint: statusFingerprint(repoRoot, porcelain, proposedTree),
|
|
23610
|
+
proposedTreeSha: proposedTree
|
|
23611
|
+
};
|
|
23612
|
+
}
|
|
23613
|
+
function assertExpectedBranch(status, expectedBranch) {
|
|
23614
|
+
assertValidBranch(expectedBranch);
|
|
23615
|
+
if (status.branch !== expectedBranch) {
|
|
23616
|
+
throw new BindingSwitchGitError(
|
|
23617
|
+
"source_changed_since_approval",
|
|
23618
|
+
`Source branch changed from ${expectedBranch} to ${status.branch ?? "detached HEAD"}`
|
|
23619
|
+
);
|
|
23620
|
+
}
|
|
23621
|
+
}
|
|
23622
|
+
function assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch) {
|
|
23623
|
+
assertCommitSha(expectedHeadSha, "expectedHeadSha");
|
|
23624
|
+
if (expectedBranch !== void 0) assertExpectedBranch(status, expectedBranch);
|
|
23625
|
+
if (status.headSha !== expectedHeadSha) {
|
|
23626
|
+
throw new BindingSwitchGitError(
|
|
23627
|
+
"source_changed_since_approval",
|
|
23628
|
+
`Source HEAD changed from ${expectedHeadSha} to ${status.headSha ?? "unborn"}`
|
|
23629
|
+
);
|
|
23630
|
+
}
|
|
23631
|
+
if (expectedStatusFingerprint !== void 0 && status.statusFingerprint !== expectedStatusFingerprint) {
|
|
23632
|
+
throw new BindingSwitchGitError(
|
|
23633
|
+
"source_changed_since_approval",
|
|
23634
|
+
"Source files changed after the migration inspection"
|
|
23635
|
+
);
|
|
23636
|
+
}
|
|
23637
|
+
}
|
|
23638
|
+
function commitHasSwitchTrailers(repoRoot, switchId, repoKey) {
|
|
23639
|
+
const message = runGitResult(repoRoot, ["show", "-s", "--format=%B", "HEAD"]);
|
|
23640
|
+
if (message.status !== 0) return false;
|
|
23641
|
+
const lines = message.stdout.split(/\r?\n/);
|
|
23642
|
+
return lines.includes(`Alan-Switch-Id: ${switchId}`) && lines.includes(`Alan-Repository-Id: ${repoKey}`);
|
|
23643
|
+
}
|
|
23644
|
+
async function checkpoint(params) {
|
|
23645
|
+
const { switchId, repoKey } = commonRequest(params);
|
|
23646
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23647
|
+
const expectedHeadSha = requiredString(params, "expectedHeadSha");
|
|
23648
|
+
const expectedStatusFingerprint = requiredString(params, "expectedStatusFingerprint");
|
|
23649
|
+
const expectedProposedTreeSha = requiredString(params, "expectedProposedTreeSha");
|
|
23650
|
+
assertCommitSha(expectedProposedTreeSha, "expectedProposedTreeSha");
|
|
23651
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23652
|
+
const status = readStatus(repoRoot);
|
|
23653
|
+
assertExpectedBranch(status, expectedBranch);
|
|
23654
|
+
if (commitHasSwitchTrailers(repoRoot, switchId, repoKey)) {
|
|
23655
|
+
if (!status.headSha) {
|
|
23656
|
+
throw new BindingSwitchGitError("source_head_unreadable", "Checkpoint HEAD is unreadable");
|
|
23657
|
+
}
|
|
23658
|
+
const expectedRef2 = `refs/heads/${expectedBranch}`;
|
|
23659
|
+
const baseline2 = createCheckpointIndexBaseline(repoRoot, status.headSha);
|
|
23660
|
+
let locks2 = null;
|
|
23661
|
+
try {
|
|
23662
|
+
locks2 = acquireCheckpointRepositoryLocks(repoRoot);
|
|
23663
|
+
assertCheckpointHead(repoRoot, expectedRef2, status.headSha);
|
|
23664
|
+
installCheckpointIndexBaseline(locks2, baseline2.indexPath);
|
|
23665
|
+
return { repoRoot, sha: status.headSha, replayed: true };
|
|
23666
|
+
} finally {
|
|
23667
|
+
if (locks2) releaseCheckpointRepositoryLocks(locks2);
|
|
23668
|
+
(0, import_node_fs4.rmSync)(baseline2.tempDir, { recursive: true, force: true });
|
|
23669
|
+
}
|
|
23670
|
+
}
|
|
23671
|
+
assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch);
|
|
23672
|
+
if (status.proposedTreeSha !== expectedProposedTreeSha) {
|
|
23673
|
+
throw new BindingSwitchGitError(
|
|
23674
|
+
"source_changed_since_approval",
|
|
23675
|
+
"The proposed checkpoint tree changed after migration approval"
|
|
23676
|
+
);
|
|
23677
|
+
}
|
|
23678
|
+
if (!status.dirty) {
|
|
23679
|
+
if (!status.headSha) {
|
|
23680
|
+
throw new BindingSwitchGitError("source_head_unreadable", "Source HEAD is unreadable");
|
|
23681
|
+
}
|
|
23682
|
+
return { repoRoot, sha: status.headSha, replayed: true };
|
|
23683
|
+
}
|
|
23684
|
+
const commitMessage = requiredCommitMessage(params).trim();
|
|
23685
|
+
const authorName = requiredString(params, "authorName");
|
|
23686
|
+
const authorEmail = requiredString(params, "authorEmail");
|
|
23687
|
+
const expectedHeadTree = runGit(
|
|
23688
|
+
repoRoot,
|
|
23689
|
+
["rev-parse", `${expectedHeadSha}^{tree}`],
|
|
23690
|
+
"source_head_unreadable"
|
|
23691
|
+
);
|
|
23692
|
+
if (expectedHeadTree === expectedProposedTreeSha) {
|
|
23693
|
+
throw new BindingSwitchGitError(
|
|
23694
|
+
"checkpoint_empty",
|
|
23695
|
+
"The inspected changes did not produce a checkpoint commit"
|
|
23696
|
+
);
|
|
23697
|
+
}
|
|
23698
|
+
const proposedTreeObject = runGitResult(repoRoot, [
|
|
23699
|
+
"cat-file",
|
|
23700
|
+
"-e",
|
|
23701
|
+
`${expectedProposedTreeSha}^{tree}`
|
|
23702
|
+
]);
|
|
23703
|
+
if (proposedTreeObject.status !== 0) {
|
|
23704
|
+
throw new BindingSwitchGitError(
|
|
23705
|
+
"checkpoint_tree_unavailable",
|
|
23706
|
+
"The approved checkpoint tree is no longer available"
|
|
23707
|
+
);
|
|
23708
|
+
}
|
|
23709
|
+
await waitForCheckpointTestBarrier();
|
|
23710
|
+
const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
|
|
23711
|
+
const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_head_unreadable");
|
|
23712
|
+
if (currentBranch !== expectedBranch || currentHead !== expectedHeadSha) {
|
|
23713
|
+
throw new BindingSwitchGitError(
|
|
23714
|
+
"source_changed_since_approval",
|
|
23715
|
+
"Source HEAD or branch changed during checkpoint preparation"
|
|
23716
|
+
);
|
|
23717
|
+
}
|
|
23718
|
+
const message = `${commitMessage}
|
|
23719
|
+
|
|
23720
|
+
Alan-Switch-Id: ${switchId}
|
|
23721
|
+
Alan-Repository-Id: ${repoKey}`;
|
|
23722
|
+
const sha = runGitWithOptions(
|
|
23723
|
+
repoRoot,
|
|
23724
|
+
["commit-tree", expectedProposedTreeSha, "-p", expectedHeadSha],
|
|
23725
|
+
{
|
|
23726
|
+
env: {
|
|
23727
|
+
GIT_AUTHOR_EMAIL: authorEmail,
|
|
23728
|
+
GIT_AUTHOR_NAME: authorName,
|
|
23729
|
+
GIT_COMMITTER_EMAIL: authorEmail,
|
|
23730
|
+
GIT_COMMITTER_NAME: authorName
|
|
23731
|
+
},
|
|
23732
|
+
input: `${message}
|
|
23733
|
+
`
|
|
23734
|
+
},
|
|
23735
|
+
"checkpoint_commit_failed"
|
|
23736
|
+
);
|
|
23737
|
+
assertCommitSha(sha, "checkpointSha");
|
|
23738
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
23739
|
+
const currentRef = runGit(
|
|
23740
|
+
repoRoot,
|
|
23741
|
+
["symbolic-ref", "-q", "HEAD"],
|
|
23742
|
+
"source_changed_since_approval"
|
|
23743
|
+
);
|
|
23744
|
+
if (currentRef !== expectedRef) {
|
|
23745
|
+
throw new BindingSwitchGitError(
|
|
23746
|
+
"source_changed_since_approval",
|
|
23747
|
+
"Source branch changed during checkpoint preparation"
|
|
23748
|
+
);
|
|
23749
|
+
}
|
|
23750
|
+
await waitForCheckpointFinalizeTestBarrier();
|
|
23751
|
+
const baseline = createCheckpointIndexBaseline(repoRoot, sha);
|
|
23752
|
+
let locks = null;
|
|
23753
|
+
let refAdvanced = false;
|
|
23754
|
+
let indexInstalled = false;
|
|
23755
|
+
try {
|
|
23756
|
+
locks = acquireCheckpointRepositoryLocks(repoRoot);
|
|
23757
|
+
assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha);
|
|
23758
|
+
releaseCheckpointHeadLock(locks);
|
|
23759
|
+
runGit(
|
|
23760
|
+
repoRoot,
|
|
23761
|
+
["update-ref", expectedRef, sha, expectedHeadSha],
|
|
23762
|
+
"source_changed_since_approval"
|
|
23763
|
+
);
|
|
23764
|
+
refAdvanced = true;
|
|
23765
|
+
acquireCheckpointHeadLock(locks);
|
|
23766
|
+
assertCheckpointHead(repoRoot, expectedRef, sha);
|
|
23767
|
+
installCheckpointIndexBaseline(locks, baseline.indexPath);
|
|
23768
|
+
indexInstalled = true;
|
|
23769
|
+
return { repoRoot, sha, replayed: false };
|
|
23770
|
+
} finally {
|
|
23771
|
+
if (refAdvanced && !indexInstalled) {
|
|
23772
|
+
if (locks) releaseCheckpointHeadLock(locks);
|
|
23773
|
+
runGitResult(repoRoot, ["update-ref", expectedRef, expectedHeadSha, sha]);
|
|
23774
|
+
}
|
|
23775
|
+
if (locks) releaseCheckpointRepositoryLocks(locks);
|
|
23776
|
+
(0, import_node_fs4.rmSync)(baseline.tempDir, { recursive: true, force: true });
|
|
23777
|
+
}
|
|
23778
|
+
}
|
|
23779
|
+
function resolveRemoteBranch(repoRoot, params) {
|
|
23780
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23781
|
+
const requestedBranch = optionalString(params, "branch");
|
|
23782
|
+
if (requestedBranch && requestedBranch !== expectedBranch) {
|
|
23783
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch must match expectedBranch");
|
|
23784
|
+
}
|
|
23785
|
+
const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
|
|
23786
|
+
const branch = expectedBranch;
|
|
23787
|
+
assertValidBranch(branch);
|
|
23788
|
+
if (currentBranch !== expectedBranch) {
|
|
23789
|
+
throw new BindingSwitchGitError(
|
|
23790
|
+
"source_changed_since_approval",
|
|
23791
|
+
`Source branch changed from ${expectedBranch} to ${currentBranch || "detached HEAD"}`
|
|
23792
|
+
);
|
|
23793
|
+
}
|
|
23794
|
+
const { remote } = verifiedRemote(repoRoot, params, true);
|
|
23795
|
+
return { branch, remote };
|
|
23796
|
+
}
|
|
23797
|
+
async function push(params) {
|
|
23798
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23799
|
+
const expectedHeadSha = requiredString(params, "expectedHeadSha");
|
|
23800
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23801
|
+
const status = readStatus(repoRoot);
|
|
23802
|
+
assertSourcePreconditions(status, expectedHeadSha, void 0, expectedBranch);
|
|
23803
|
+
if (status.dirty) {
|
|
23804
|
+
throw new BindingSwitchGitError(
|
|
23805
|
+
"source_dirty_after_checkpoint",
|
|
23806
|
+
"The source has uncommitted changes and cannot be published"
|
|
23807
|
+
);
|
|
23808
|
+
}
|
|
23809
|
+
const { branch, remote } = resolveRemoteBranch(repoRoot, params);
|
|
23810
|
+
const latest = readStatus(repoRoot);
|
|
23811
|
+
assertSourcePreconditions(latest, expectedHeadSha, void 0, expectedBranch);
|
|
23812
|
+
if (latest.dirty) {
|
|
23813
|
+
throw new BindingSwitchGitError(
|
|
23814
|
+
"source_dirty_after_checkpoint",
|
|
23815
|
+
"The source has uncommitted changes and cannot be published"
|
|
23816
|
+
);
|
|
23817
|
+
}
|
|
23818
|
+
const before = await runNetworkGit(
|
|
23819
|
+
repoRoot,
|
|
23820
|
+
["ls-remote", "--heads", remote, `refs/heads/${branch}`],
|
|
23821
|
+
"git_remote_unavailable"
|
|
23822
|
+
);
|
|
23823
|
+
const remoteBefore = before.trim().split(/\s+/)[0] || null;
|
|
23824
|
+
await runNetworkGit(
|
|
23825
|
+
repoRoot,
|
|
23826
|
+
["push", remote, `${expectedHeadSha}:refs/heads/${expectedBranch}`],
|
|
23827
|
+
"git_push_failed"
|
|
23828
|
+
);
|
|
23829
|
+
const remoteHead = (await runNetworkGit(
|
|
23830
|
+
repoRoot,
|
|
23831
|
+
["ls-remote", "--heads", remote, `refs/heads/${branch}`],
|
|
23832
|
+
"push_verification_failed"
|
|
23833
|
+
)).split(/\s+/)[0]?.trim();
|
|
23834
|
+
if (remoteHead !== expectedHeadSha) {
|
|
23835
|
+
throw new BindingSwitchGitError(
|
|
23836
|
+
"push_verification_failed",
|
|
23837
|
+
`Remote branch ${branch} is at ${remoteHead || "an unreadable SHA"}, expected ${expectedHeadSha}`
|
|
23838
|
+
);
|
|
23839
|
+
}
|
|
23840
|
+
const afterPush = readStatus(repoRoot);
|
|
23841
|
+
if (afterPush.headSha !== expectedHeadSha || afterPush.branch !== expectedBranch || afterPush.dirty || afterPush.statusFingerprint !== latest.statusFingerprint) {
|
|
23842
|
+
throw new BindingSwitchGitError(
|
|
23843
|
+
"source_changed_since_approval",
|
|
23844
|
+
"The source changed while the approved commit was being published",
|
|
23845
|
+
{
|
|
23846
|
+
published: {
|
|
23847
|
+
pushedSha: expectedHeadSha,
|
|
23848
|
+
branch,
|
|
23849
|
+
remote,
|
|
23850
|
+
replayed: remoteBefore === expectedHeadSha
|
|
23851
|
+
}
|
|
23852
|
+
}
|
|
23853
|
+
);
|
|
23854
|
+
}
|
|
23855
|
+
return {
|
|
23856
|
+
repoRoot,
|
|
23857
|
+
pushedSha: expectedHeadSha,
|
|
23858
|
+
branch,
|
|
23859
|
+
remote,
|
|
23860
|
+
replayed: remoteBefore === expectedHeadSha
|
|
23861
|
+
};
|
|
23862
|
+
}
|
|
23863
|
+
async function fetchPublishedBranch(repoRoot, branch, expectedSha, params) {
|
|
23864
|
+
assertValidBranch(branch);
|
|
23865
|
+
assertCommitSha(expectedSha, "expectedSha");
|
|
23866
|
+
const { remote } = verifiedRemote(repoRoot, params, false);
|
|
23867
|
+
await runNetworkGit(
|
|
23868
|
+
repoRoot,
|
|
23869
|
+
["fetch", "--no-tags", remote, `refs/heads/${branch}`],
|
|
23870
|
+
"git_fetch_failed"
|
|
23871
|
+
);
|
|
23872
|
+
const fetchedSha = runGit(repoRoot, ["rev-parse", "FETCH_HEAD"], "target_sha_unavailable");
|
|
23873
|
+
if (fetchedSha !== expectedSha) {
|
|
23874
|
+
throw new BindingSwitchGitError(
|
|
23875
|
+
"target_sha_mismatch",
|
|
23876
|
+
`Fetched ${branch} at ${fetchedSha}, expected the published SHA ${expectedSha}`
|
|
23877
|
+
);
|
|
23878
|
+
}
|
|
23879
|
+
const commit = runGitResult(repoRoot, ["cat-file", "-e", `${expectedSha}^{commit}`]);
|
|
23880
|
+
if (commit.status !== 0) {
|
|
23881
|
+
throw new BindingSwitchGitError(
|
|
23882
|
+
"target_sha_unavailable",
|
|
23883
|
+
`The published SHA ${expectedSha} is not available in the destination repository`
|
|
23884
|
+
);
|
|
23885
|
+
}
|
|
23886
|
+
return { fetchedSha, remote };
|
|
23887
|
+
}
|
|
23888
|
+
async function fetchBranch(params) {
|
|
23889
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23890
|
+
const branch = requiredString(params, "branch");
|
|
23891
|
+
const expectedSha = requiredString(params, "expectedSha");
|
|
23892
|
+
const { fetchedSha, remote } = await fetchPublishedBranch(repoRoot, branch, expectedSha, params);
|
|
23893
|
+
return { repoRoot, fetchedSha, branch, remote };
|
|
23894
|
+
}
|
|
23895
|
+
function isAncestor(repoRoot, ancestor, descendant) {
|
|
23896
|
+
return runGitResult(repoRoot, ["merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
|
|
23897
|
+
}
|
|
23898
|
+
function assertCleanTarget(repoRoot) {
|
|
23899
|
+
if (readStatus(repoRoot).dirty) {
|
|
23900
|
+
throw new BindingSwitchGitError(
|
|
23901
|
+
"target_checkout_dirty",
|
|
23902
|
+
"The destination checkout has uncommitted changes"
|
|
23903
|
+
);
|
|
23904
|
+
}
|
|
23905
|
+
}
|
|
23906
|
+
function assertBranchAvailableAtPath(repoRoot, branch, allowedPath) {
|
|
23907
|
+
const canonicalAllowedPath = canonicalPathWithMissingTail(allowedPath);
|
|
23908
|
+
const conflicting = listWorktrees(repoRoot).find(
|
|
23909
|
+
(worktree) => worktree.branch === branch && worktree.path !== canonicalAllowedPath
|
|
23910
|
+
);
|
|
23911
|
+
if (conflicting) {
|
|
23912
|
+
throw new BindingSwitchGitError(
|
|
23913
|
+
"target_branch_in_use",
|
|
23914
|
+
`Branch ${branch} is already checked out at ${conflicting.path}`
|
|
23915
|
+
);
|
|
23916
|
+
}
|
|
23917
|
+
}
|
|
23918
|
+
function advanceBranchAtCheckout(checkoutRoot, branch, targetSha) {
|
|
23919
|
+
const before = readStatus(checkoutRoot);
|
|
23920
|
+
if (before.branch === branch && before.headSha === targetSha) return true;
|
|
23921
|
+
assertBranchAvailableAtPath(checkoutRoot, branch, checkoutRoot);
|
|
23922
|
+
const branchExists = runGitResult(checkoutRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status === 0;
|
|
23923
|
+
if (branchExists) {
|
|
23924
|
+
const branchHead = runGit(checkoutRoot, ["rev-parse", `refs/heads/${branch}`]);
|
|
23925
|
+
if (!isAncestor(checkoutRoot, branchHead, targetSha)) {
|
|
23926
|
+
throw new BindingSwitchGitError(
|
|
23927
|
+
"target_branch_diverged",
|
|
23928
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
23929
|
+
);
|
|
23930
|
+
}
|
|
23931
|
+
if (before.branch !== branch) {
|
|
23932
|
+
runGit(checkoutRoot, ["switch", branch], "target_checkout_failed");
|
|
23933
|
+
}
|
|
23934
|
+
} else {
|
|
23935
|
+
runGit(checkoutRoot, ["switch", "-c", branch, targetSha], "target_checkout_failed");
|
|
23936
|
+
}
|
|
23937
|
+
const currentHead = runGit(checkoutRoot, ["rev-parse", "HEAD"]);
|
|
23938
|
+
if (currentHead !== targetSha) {
|
|
23939
|
+
if (!isAncestor(checkoutRoot, currentHead, targetSha)) {
|
|
23940
|
+
throw new BindingSwitchGitError(
|
|
23941
|
+
"target_branch_diverged",
|
|
23942
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
23943
|
+
);
|
|
23944
|
+
}
|
|
23945
|
+
runGit(checkoutRoot, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
|
|
23946
|
+
}
|
|
23947
|
+
const verified = readStatus(checkoutRoot);
|
|
23948
|
+
if (verified.branch !== branch || verified.headSha !== targetSha) {
|
|
23949
|
+
throw new BindingSwitchGitError(
|
|
23950
|
+
"target_verification_failed",
|
|
23951
|
+
"The destination checkout did not land on the published branch and SHA"
|
|
23952
|
+
);
|
|
23953
|
+
}
|
|
23954
|
+
return false;
|
|
23955
|
+
}
|
|
23956
|
+
async function prepareCheckout(params) {
|
|
23957
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23958
|
+
const branch = requiredString(params, "branch");
|
|
23959
|
+
const targetSha = requiredString(params, "targetSha");
|
|
23960
|
+
await fetchPublishedBranch(repoRoot, branch, targetSha, params);
|
|
23961
|
+
assertCleanTarget(repoRoot);
|
|
23962
|
+
const replayed = advanceBranchAtCheckout(repoRoot, branch, targetSha);
|
|
23963
|
+
return {
|
|
23964
|
+
repoRoot,
|
|
23965
|
+
path: repoRoot,
|
|
23966
|
+
branch,
|
|
23967
|
+
headSha: targetSha,
|
|
23968
|
+
replayed
|
|
23969
|
+
};
|
|
23970
|
+
}
|
|
23971
|
+
async function prepareWorktree(params) {
|
|
23972
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23973
|
+
const registrations = listWorktrees(repoRoot);
|
|
23974
|
+
const projectRoot = registrations[0]?.path;
|
|
23975
|
+
if (!projectRoot || projectRoot !== repoRoot) {
|
|
23976
|
+
throw new BindingSwitchGitError(
|
|
23977
|
+
"project_checkout_required",
|
|
23978
|
+
"Worktree preparation requires the Project's main checkout"
|
|
23979
|
+
);
|
|
23980
|
+
}
|
|
23981
|
+
const worktreePath = (0, import_node_path4.resolve)(requiredString(params, "worktreePath"));
|
|
23982
|
+
const managedRoot = alanWorktreeRoot(projectRoot);
|
|
23983
|
+
const pathError = "The destination worktree path is outside the Alan-managed worktree directory";
|
|
23984
|
+
const intendedManagedRoot = (0, import_node_path4.join)(
|
|
23985
|
+
import_node_fs4.realpathSync.native((0, import_node_path4.dirname)(managedRoot)),
|
|
23986
|
+
(0, import_node_path4.basename)(managedRoot)
|
|
23987
|
+
);
|
|
23988
|
+
if (canonicalPathWithMissingTail(managedRoot) !== intendedManagedRoot) {
|
|
23989
|
+
throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
|
|
23990
|
+
}
|
|
23991
|
+
assertCanonicalPathContained(
|
|
23992
|
+
worktreePath,
|
|
23993
|
+
managedRoot,
|
|
23994
|
+
"worktree_path_not_authorized",
|
|
23995
|
+
pathError
|
|
23996
|
+
);
|
|
23997
|
+
const canonicalWorktreePath = canonicalPathWithMissingTail(worktreePath);
|
|
23998
|
+
if (canonicalWorktreePath === canonicalPathWithMissingTail(managedRoot)) {
|
|
23999
|
+
throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
|
|
24000
|
+
}
|
|
24001
|
+
const branch = requiredString(params, "branch");
|
|
24002
|
+
const targetSha = requiredString(params, "targetSha");
|
|
24003
|
+
await fetchPublishedBranch(repoRoot, branch, targetSha, params);
|
|
24004
|
+
const refreshed = listWorktrees(repoRoot);
|
|
24005
|
+
const registeredAtPath = refreshed.find(({ path }) => path === canonicalWorktreePath);
|
|
24006
|
+
const registeredForBranch = refreshed.find(({ branch: value2 }) => value2 === branch);
|
|
24007
|
+
if (registeredAtPath) {
|
|
24008
|
+
if (registeredAtPath.branch !== branch) {
|
|
24009
|
+
throw new BindingSwitchGitError(
|
|
24010
|
+
"worktree_path_conflict",
|
|
24011
|
+
"The destination path is registered to a different branch"
|
|
24012
|
+
);
|
|
24013
|
+
}
|
|
24014
|
+
assertCleanTarget(worktreePath);
|
|
24015
|
+
const replayed = advanceBranchAtCheckout(worktreePath, branch, targetSha);
|
|
24016
|
+
return {
|
|
24017
|
+
repoRoot: worktreePath,
|
|
24018
|
+
path: worktreePath,
|
|
24019
|
+
branch,
|
|
24020
|
+
headSha: targetSha,
|
|
24021
|
+
replayed
|
|
24022
|
+
};
|
|
24023
|
+
}
|
|
24024
|
+
if (registeredForBranch) {
|
|
24025
|
+
throw new BindingSwitchGitError(
|
|
24026
|
+
"target_branch_in_use",
|
|
24027
|
+
`Branch ${branch} is already checked out at ${registeredForBranch.path}`
|
|
24028
|
+
);
|
|
24029
|
+
}
|
|
24030
|
+
if ((0, import_node_fs4.existsSync)(worktreePath)) {
|
|
24031
|
+
throw new BindingSwitchGitError(
|
|
24032
|
+
"worktree_path_conflict",
|
|
24033
|
+
"The destination worktree path already exists and is not registered"
|
|
24034
|
+
);
|
|
24035
|
+
}
|
|
24036
|
+
const branchExists = runGitResult(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status === 0;
|
|
24037
|
+
if (branchExists) {
|
|
24038
|
+
const branchHead = runGit(repoRoot, ["rev-parse", `refs/heads/${branch}`]);
|
|
24039
|
+
if (!isAncestor(repoRoot, branchHead, targetSha)) {
|
|
24040
|
+
throw new BindingSwitchGitError(
|
|
24041
|
+
"target_branch_diverged",
|
|
24042
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
24043
|
+
);
|
|
24044
|
+
}
|
|
24045
|
+
}
|
|
24046
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(worktreePath), { recursive: true });
|
|
24047
|
+
if (branchExists) {
|
|
24048
|
+
runGit(repoRoot, ["worktree", "add", worktreePath, branch], "worktree_prepare_failed");
|
|
24049
|
+
const currentHead = runGit(worktreePath, ["rev-parse", "HEAD"]);
|
|
24050
|
+
if (currentHead !== targetSha) {
|
|
24051
|
+
runGit(worktreePath, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
|
|
24052
|
+
}
|
|
24053
|
+
} else {
|
|
24054
|
+
runGit(
|
|
24055
|
+
repoRoot,
|
|
24056
|
+
["worktree", "add", "-b", branch, worktreePath, targetSha],
|
|
24057
|
+
"worktree_prepare_failed"
|
|
24058
|
+
);
|
|
24059
|
+
}
|
|
24060
|
+
const createdRoot = resolveRepositoryRoot(worktreePath);
|
|
24061
|
+
assertCanonicalPathContained(createdRoot, managedRoot, "worktree_path_not_authorized", pathError);
|
|
24062
|
+
if (createdRoot !== import_node_fs4.realpathSync.native(worktreePath)) {
|
|
24063
|
+
throw new BindingSwitchGitError(
|
|
24064
|
+
"worktree_path_changed",
|
|
24065
|
+
"The destination worktree resolved to an unexpected path after creation"
|
|
24066
|
+
);
|
|
24067
|
+
}
|
|
24068
|
+
const verified = readStatus(createdRoot);
|
|
24069
|
+
if (verified.branch !== branch || verified.headSha !== targetSha) {
|
|
24070
|
+
throw new BindingSwitchGitError(
|
|
24071
|
+
"target_verification_failed",
|
|
24072
|
+
"The destination worktree did not land on the published branch and SHA"
|
|
24073
|
+
);
|
|
24074
|
+
}
|
|
24075
|
+
return {
|
|
24076
|
+
repoRoot: worktreePath,
|
|
24077
|
+
path: worktreePath,
|
|
24078
|
+
branch,
|
|
24079
|
+
headSha: targetSha,
|
|
24080
|
+
replayed: false
|
|
24081
|
+
};
|
|
24082
|
+
}
|
|
24083
|
+
function verifyHead(params) {
|
|
24084
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
24085
|
+
const expectedSha = requiredString(params, "expectedSha");
|
|
24086
|
+
assertCommitSha(expectedSha, "expectedSha");
|
|
24087
|
+
assertCleanTarget(repoRoot);
|
|
24088
|
+
const headSha = runGit(repoRoot, ["rev-parse", "HEAD"], "target_head_unreadable");
|
|
24089
|
+
if (headSha !== expectedSha) {
|
|
24090
|
+
throw new BindingSwitchGitError(
|
|
24091
|
+
"verify_head_mismatch",
|
|
24092
|
+
`Destination HEAD is ${headSha}, expected ${expectedSha}`
|
|
24093
|
+
);
|
|
24094
|
+
}
|
|
24095
|
+
return { repoRoot, headSha, verified: true };
|
|
24096
|
+
}
|
|
24097
|
+
function statusBindingSwitchGit(params) {
|
|
24098
|
+
const repoRoot = resolveAuthorizedRepository(params);
|
|
24099
|
+
return {
|
|
24100
|
+
...readStatus(repoRoot),
|
|
24101
|
+
remoteUrl: inspectedRemoteUrl(repoRoot, params)
|
|
24102
|
+
};
|
|
24103
|
+
}
|
|
24104
|
+
async function executeBindingSwitchGitRpc(method, params) {
|
|
24105
|
+
switch (method) {
|
|
24106
|
+
case "git.status":
|
|
24107
|
+
return statusBindingSwitchGit(params);
|
|
24108
|
+
case "git.checkpoint":
|
|
24109
|
+
return checkpoint(params);
|
|
24110
|
+
case "git.push":
|
|
24111
|
+
return push(params);
|
|
24112
|
+
case "git.fetch":
|
|
24113
|
+
return fetchBranch(params);
|
|
24114
|
+
case "git.prepare_checkout":
|
|
24115
|
+
return prepareCheckout(params);
|
|
24116
|
+
case "git.prepare_worktree":
|
|
24117
|
+
return prepareWorktree(params);
|
|
24118
|
+
case "git.verify_head":
|
|
24119
|
+
return verifyHead(params);
|
|
24120
|
+
}
|
|
24121
|
+
}
|
|
24122
|
+
|
|
24123
|
+
// src/cli-ensure-install.ts
|
|
24124
|
+
var import_node_child_process4 = require("child_process");
|
|
24125
|
+
var import_node_os5 = require("os");
|
|
24126
|
+
var CLI_RUNTIME_VERSIONS_ENV = "ALAN_CLI_RUNTIME_VERSIONS";
|
|
24127
|
+
var INSTALLABLE_PROVIDER_PACKAGES = {
|
|
24128
|
+
claude_cli: { npmPackage: "@anthropic-ai/claude-code", command: "claude", label: "Claude Code" },
|
|
24129
|
+
codex_app_server: { npmPackage: "@openai/codex", command: "codex", label: "Codex" },
|
|
24130
|
+
copilot_cli: { npmPackage: "@github/copilot", command: "copilot", label: "Copilot" },
|
|
24131
|
+
opencode_cli: { npmPackage: "opencode-ai", command: "opencode", label: "opencode" }
|
|
24132
|
+
};
|
|
24133
|
+
var INSTALL_TIMEOUT_MS = 12e4;
|
|
24134
|
+
var installOutcomes = /* @__PURE__ */ new Map();
|
|
24135
|
+
function canInstallProviderCli(backendKind) {
|
|
24136
|
+
return Boolean(backendKind && INSTALLABLE_PROVIDER_PACKAGES[backendKind]);
|
|
24137
|
+
}
|
|
24138
|
+
function pinnedVersion(npmPackage) {
|
|
24139
|
+
const raw = process.env[CLI_RUNTIME_VERSIONS_ENV];
|
|
24140
|
+
if (!raw) return "latest";
|
|
24141
|
+
try {
|
|
24142
|
+
const parsed = JSON.parse(raw);
|
|
24143
|
+
const version = parsed[npmPackage];
|
|
24144
|
+
if (typeof version === "string" && version.trim()) return version.trim();
|
|
24145
|
+
} catch {
|
|
24146
|
+
}
|
|
24147
|
+
return "latest";
|
|
24148
|
+
}
|
|
24149
|
+
function runNpmInstall(installSpec, env, timeoutMs) {
|
|
24150
|
+
return new Promise((resolve7) => {
|
|
24151
|
+
const isWin = (0, import_node_os5.platform)() === "win32";
|
|
24152
|
+
let child;
|
|
24153
|
+
try {
|
|
24154
|
+
child = (0, import_node_child_process4.spawn)("npm", ["install", "-g", installSpec], {
|
|
24155
|
+
env,
|
|
24156
|
+
shell: isWin,
|
|
24157
|
+
windowsHide: isWin ? true : void 0,
|
|
24158
|
+
timeout: timeoutMs,
|
|
24159
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
24160
|
+
});
|
|
24161
|
+
} catch (error) {
|
|
24162
|
+
resolve7({ status: null, stdout: "", stderr: "", error });
|
|
24163
|
+
return;
|
|
24164
|
+
}
|
|
24165
|
+
let stdout = "";
|
|
24166
|
+
let stderr = "";
|
|
24167
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
24168
|
+
stdout += chunk;
|
|
24169
|
+
});
|
|
24170
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
24171
|
+
stderr += chunk;
|
|
24172
|
+
});
|
|
24173
|
+
child.once("error", (error) => resolve7({ status: null, stdout, stderr, error }));
|
|
24174
|
+
child.once(
|
|
24175
|
+
"close",
|
|
24176
|
+
(status, signal) => resolve7({
|
|
24177
|
+
status,
|
|
24178
|
+
stdout,
|
|
24179
|
+
stderr,
|
|
24180
|
+
// Spawn `timeout` kills by signal without an `error` event; surface it
|
|
24181
|
+
// so a timed-out install is reported as a failure, not status null + ok.
|
|
24182
|
+
...signal ? { error: new Error(`npm install killed by ${signal}`) } : {}
|
|
24183
|
+
})
|
|
24184
|
+
);
|
|
24185
|
+
});
|
|
24186
|
+
}
|
|
24187
|
+
async function attemptInstall(spec, options) {
|
|
24188
|
+
const installSpec = `${spec.npmPackage}@${pinnedVersion(spec.npmPackage)}`;
|
|
24189
|
+
const notify = options?.notify ?? ((message) => console.info(`[cli-ensure-install] ${message}`));
|
|
24190
|
+
notify(`Installing ${spec.label} CLI (${installSpec})\u2026`);
|
|
24191
|
+
const runner = options?.runner ?? runNpmInstall;
|
|
24192
|
+
const result = await runner(installSpec, getDaemonCliEnvironment(), INSTALL_TIMEOUT_MS);
|
|
24193
|
+
if (result.error || result.status !== 0) {
|
|
24194
|
+
console.warn(`[cli-ensure-install] npm install -g ${installSpec} failed`, {
|
|
24195
|
+
status: result.status,
|
|
24196
|
+
error: result.error?.message,
|
|
24197
|
+
output: `${result.stderr || result.stdout}`.trim().slice(-500)
|
|
24198
|
+
});
|
|
24199
|
+
notify(`Failed to install ${spec.label} CLI (${installSpec}).`);
|
|
24200
|
+
return false;
|
|
24201
|
+
}
|
|
24202
|
+
invalidateResolvedCli(spec.command);
|
|
24203
|
+
notify(`Installed ${spec.label} CLI (${installSpec}).`);
|
|
24204
|
+
return true;
|
|
24205
|
+
}
|
|
24206
|
+
function ensureProviderCliInstalled(backendKind, options) {
|
|
24207
|
+
if (!backendKind) return Promise.resolve(false);
|
|
24208
|
+
const spec = INSTALLABLE_PROVIDER_PACKAGES[backendKind];
|
|
24209
|
+
if (!spec) return Promise.resolve(false);
|
|
24210
|
+
const existing = installOutcomes.get(spec.npmPackage);
|
|
24211
|
+
if (existing) return existing;
|
|
24212
|
+
const attempt = attemptInstall(spec, options).catch((error) => {
|
|
24213
|
+
console.warn(`[cli-ensure-install] install attempt for ${spec.npmPackage} threw`, error);
|
|
24214
|
+
return false;
|
|
24215
|
+
});
|
|
24216
|
+
installOutcomes.set(spec.npmPackage, attempt);
|
|
24217
|
+
return attempt;
|
|
24218
|
+
}
|
|
24219
|
+
|
|
24220
|
+
// src/computer-readiness.ts
|
|
24221
|
+
var import_node_fs6 = require("fs");
|
|
24222
|
+
|
|
24223
|
+
// src/runtime-config.ts
|
|
24224
|
+
var import_node_fs5 = require("fs");
|
|
24225
|
+
var import_node_os6 = require("os");
|
|
24226
|
+
var import_node_path5 = require("path");
|
|
24227
|
+
var DEFAULT_AGENT_DIR = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".alan", "agent");
|
|
24228
|
+
var LEGACY_AGENT_DIR = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".aiden", "agent");
|
|
24229
|
+
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Map([
|
|
24230
|
+
["apiUrl", "string"],
|
|
24231
|
+
["wsUrl", "string"],
|
|
24232
|
+
["endpointProfile", "string"],
|
|
24233
|
+
["teamId", "string"],
|
|
24234
|
+
["runtimeId", "string"],
|
|
24235
|
+
["runtimeToken", "string"],
|
|
24236
|
+
["localApiPort", "number"],
|
|
24237
|
+
["localApiToken", "string"],
|
|
24238
|
+
["displayName", "string"]
|
|
24239
|
+
]);
|
|
24240
|
+
function sanitizeAgentConfig(config) {
|
|
24241
|
+
const sanitized = { ...config };
|
|
24242
|
+
delete sanitized.teamId;
|
|
24243
|
+
delete sanitized.organizationId;
|
|
24244
|
+
delete sanitized.currentTeamId;
|
|
24245
|
+
delete sanitized.currentOrganizationId;
|
|
24246
|
+
return sanitized;
|
|
24247
|
+
}
|
|
24248
|
+
function isValidAgentConfig(configPath) {
|
|
24249
|
+
try {
|
|
24250
|
+
const stat = (0, import_node_fs5.lstatSync)(configPath);
|
|
24251
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
24252
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(configPath, "utf8"));
|
|
24253
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
24254
|
+
const entries = Object.entries(parsed);
|
|
24255
|
+
return entries.some(([key]) => AGENT_CONFIG_KEYS.has(key)) && entries.every(([key, value2]) => {
|
|
24256
|
+
const expectedType = AGENT_CONFIG_KEYS.get(key);
|
|
24257
|
+
return expectedType === void 0 || typeof value2 === expectedType;
|
|
24258
|
+
});
|
|
24259
|
+
} catch {
|
|
24260
|
+
return false;
|
|
24261
|
+
}
|
|
24262
|
+
}
|
|
24263
|
+
function migrateLegacyAgentConfig(input) {
|
|
24264
|
+
const profile = normalizeAgentEndpointProfile(input?.profile ?? null) ?? "production";
|
|
24265
|
+
const alanBaseDir = input?.alanBaseDir ?? DEFAULT_AGENT_DIR;
|
|
24266
|
+
const legacyBaseDir = input?.legacyBaseDir ?? LEGACY_AGENT_DIR;
|
|
24267
|
+
const fileName = resolveAgentConfigFileName(profile);
|
|
24268
|
+
const legacyPath = (0, import_node_path5.join)(legacyBaseDir, fileName);
|
|
24269
|
+
const alanPath = (0, import_node_path5.join)(alanBaseDir, fileName);
|
|
24270
|
+
if ((0, import_node_fs5.existsSync)(alanPath) || !isValidAgentConfig(legacyPath)) return false;
|
|
24271
|
+
let copied = false;
|
|
24272
|
+
try {
|
|
24273
|
+
const sourceDirectoryStat = (0, import_node_fs5.lstatSync)(legacyBaseDir);
|
|
24274
|
+
const sourceFileStat = (0, import_node_fs5.lstatSync)(legacyPath);
|
|
24275
|
+
if (!sourceDirectoryStat.isDirectory() || sourceDirectoryStat.isSymbolicLink()) return false;
|
|
24276
|
+
const alanDirectoryExisted = (0, import_node_fs5.existsSync)(alanBaseDir);
|
|
24277
|
+
if (alanDirectoryExisted) {
|
|
24278
|
+
const alanDirectoryStat = (0, import_node_fs5.lstatSync)(alanBaseDir);
|
|
24279
|
+
if (!alanDirectoryStat.isDirectory() || alanDirectoryStat.isSymbolicLink()) return false;
|
|
24280
|
+
}
|
|
24281
|
+
(0, import_node_fs5.mkdirSync)(alanBaseDir, { recursive: true, mode: sourceDirectoryStat.mode & 511 });
|
|
24282
|
+
if (!alanDirectoryExisted) (0, import_node_fs5.chmodSync)(alanBaseDir, sourceDirectoryStat.mode & 511);
|
|
24283
|
+
(0, import_node_fs5.copyFileSync)(legacyPath, alanPath, import_node_fs5.constants.COPYFILE_EXCL);
|
|
24284
|
+
copied = true;
|
|
24285
|
+
const migrated = sanitizeAgentConfig(
|
|
24286
|
+
JSON.parse((0, import_node_fs5.readFileSync)(alanPath, "utf8"))
|
|
24287
|
+
);
|
|
24288
|
+
(0, import_node_fs5.writeFileSync)(alanPath, JSON.stringify(migrated, null, 2));
|
|
24289
|
+
(0, import_node_fs5.chmodSync)(alanPath, sourceFileStat.mode & 511);
|
|
24290
|
+
(0, import_node_fs5.utimesSync)(alanPath, sourceFileStat.atime, sourceFileStat.mtime);
|
|
24291
|
+
if (!isValidAgentConfig(alanPath)) {
|
|
24292
|
+
(0, import_node_fs5.unlinkSync)(alanPath);
|
|
24293
|
+
return false;
|
|
24294
|
+
}
|
|
24295
|
+
return true;
|
|
24296
|
+
} catch {
|
|
24297
|
+
if (copied && (0, import_node_fs5.existsSync)(alanPath)) (0, import_node_fs5.unlinkSync)(alanPath);
|
|
24298
|
+
return false;
|
|
24299
|
+
}
|
|
24300
|
+
}
|
|
24301
|
+
function resolveAgentConfigPath(input) {
|
|
24302
|
+
if (input?.explicitPath?.trim()) return input.explicitPath.trim();
|
|
24303
|
+
const profile = normalizeAgentEndpointProfile(
|
|
24304
|
+
typeof input?.profile === "string" ? input.profile : input?.profile ?? null
|
|
24305
|
+
) ?? "production";
|
|
24306
|
+
const baseDir = input?.baseDir ?? DEFAULT_AGENT_DIR;
|
|
24307
|
+
if (input?.baseDir === void 0) migrateLegacyAgentConfig({ profile });
|
|
24308
|
+
return (0, import_node_path5.join)(baseDir, resolveAgentConfigFileName(profile));
|
|
24309
|
+
}
|
|
24310
|
+
function configExistsAtPath(configPath) {
|
|
24311
|
+
return (0, import_node_fs5.existsSync)(configPath);
|
|
24312
|
+
}
|
|
24313
|
+
function resolveEndpointProfileFromArgs(args) {
|
|
24314
|
+
const index = args.indexOf("--profile");
|
|
24315
|
+
const raw = index === -1 ? process.env.ALAN_AGENT_PROFILE : args[index + 1];
|
|
24316
|
+
return normalizeAgentEndpointProfile(raw) ?? "production";
|
|
24317
|
+
}
|
|
24318
|
+
function bindAgentConfigPathFromArgs(args) {
|
|
24319
|
+
const explicit = process.env.ALAN_AGENT_CONFIG_PATH?.trim();
|
|
24320
|
+
if (explicit) return explicit;
|
|
24321
|
+
const profile = resolveEndpointProfileFromArgs(args);
|
|
24322
|
+
const configPath = resolveAgentConfigPath({ profile });
|
|
24323
|
+
process.env.ALAN_AGENT_CONFIG_PATH = configPath;
|
|
24324
|
+
return configPath;
|
|
24325
|
+
}
|
|
24326
|
+
|
|
24327
|
+
// src/computer-readiness.ts
|
|
24328
|
+
var WorkspaceAccessTracker = class {
|
|
24329
|
+
constructor(inspect = (path) => inspectWorkspaceReadiness(path)) {
|
|
24330
|
+
this.inspect = inspect;
|
|
24331
|
+
}
|
|
24332
|
+
issue = null;
|
|
24333
|
+
track(path, readiness) {
|
|
24334
|
+
if (readiness.ready) {
|
|
24335
|
+
this.issue = null;
|
|
24336
|
+
return;
|
|
24337
|
+
}
|
|
24338
|
+
this.issue = { path, readiness };
|
|
24339
|
+
}
|
|
24340
|
+
recheck() {
|
|
24341
|
+
if (!this.issue) return null;
|
|
24342
|
+
const readiness = this.inspect(this.issue.path);
|
|
24343
|
+
if (readiness.ready) {
|
|
24344
|
+
this.issue = null;
|
|
24345
|
+
return readiness;
|
|
24346
|
+
}
|
|
24347
|
+
this.issue = { path: this.issue.path, readiness };
|
|
23215
24348
|
return readiness;
|
|
23216
24349
|
}
|
|
23217
24350
|
recoveryPath() {
|
|
@@ -23222,9 +24355,9 @@ var WorkspaceAccessTracker = class {
|
|
|
23222
24355
|
}
|
|
23223
24356
|
};
|
|
23224
24357
|
var DEFAULT_WORKSPACE_PROBE = {
|
|
23225
|
-
stat: (path) => (0,
|
|
23226
|
-
access: (path, mode) => (0,
|
|
23227
|
-
exists: (path) => (0,
|
|
24358
|
+
stat: (path) => (0, import_node_fs6.statSync)(path),
|
|
24359
|
+
access: (path, mode) => (0, import_node_fs6.accessSync)(path, mode),
|
|
24360
|
+
exists: (path) => (0, import_node_fs6.existsSync)(path)
|
|
23228
24361
|
};
|
|
23229
24362
|
function workspaceVolumeRoot(path) {
|
|
23230
24363
|
const windowsDrive = path.match(/^([A-Za-z]):[\\/]/);
|
|
@@ -23288,7 +24421,7 @@ function inspectWorkspaceReadiness(workspacePath, probe = DEFAULT_WORKSPACE_PROB
|
|
|
23288
24421
|
probe
|
|
23289
24422
|
);
|
|
23290
24423
|
}
|
|
23291
|
-
probe.access(workspacePath,
|
|
24424
|
+
probe.access(workspacePath, import_node_fs6.constants.R_OK | import_node_fs6.constants.W_OK | import_node_fs6.constants.X_OK);
|
|
23292
24425
|
return {
|
|
23293
24426
|
ready: true,
|
|
23294
24427
|
message: "Workspace path is configured and accessible.",
|
|
@@ -23400,7 +24533,7 @@ function buildComputerReadiness(input) {
|
|
|
23400
24533
|
}
|
|
23401
24534
|
|
|
23402
24535
|
// src/core-agent.ts
|
|
23403
|
-
var
|
|
24536
|
+
var import_node_os7 = require("os");
|
|
23404
24537
|
var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
23405
24538
|
childProcess = null;
|
|
23406
24539
|
_aborted = false;
|
|
@@ -23550,7 +24683,7 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
23550
24683
|
if (this.runtime.backendKind === "grok_cli" && this.providerApiKey) {
|
|
23551
24684
|
augmented.XAI_API_KEY = this.providerApiKey;
|
|
23552
24685
|
}
|
|
23553
|
-
if ((0,
|
|
24686
|
+
if ((0, import_node_os7.platform)() !== "win32") {
|
|
23554
24687
|
const home = augmented.HOME ?? "/home/user";
|
|
23555
24688
|
const extraPaths = [`${home}/.local/node/bin`, `${home}/.local/bin`];
|
|
23556
24689
|
const separator = ":";
|
|
@@ -23714,9 +24847,9 @@ ${meta.description}`);
|
|
|
23714
24847
|
};
|
|
23715
24848
|
|
|
23716
24849
|
// src/event-outbox.ts
|
|
23717
|
-
var
|
|
23718
|
-
var
|
|
23719
|
-
var
|
|
24850
|
+
var import_node_crypto2 = require("crypto");
|
|
24851
|
+
var import_node_fs7 = require("fs");
|
|
24852
|
+
var import_node_path6 = require("path");
|
|
23720
24853
|
var OUTBOX_VERSION = 1;
|
|
23721
24854
|
var MAX_INTERMEDIATE_EVENTS_PER_RUN = 200;
|
|
23722
24855
|
var MAX_ENCRYPTED_OUTBOX_BYTES = 64 * 1024 * 1024;
|
|
@@ -23749,7 +24882,7 @@ function truncateUtf8(value2, maxBytes) {
|
|
|
23749
24882
|
return `${encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "")}\u2026`;
|
|
23750
24883
|
}
|
|
23751
24884
|
function terminalFingerprint(event) {
|
|
23752
|
-
return (0,
|
|
24885
|
+
return (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(event)).digest("hex").slice(0, 16);
|
|
23753
24886
|
}
|
|
23754
24887
|
function condenseTerminalEvent(event) {
|
|
23755
24888
|
const fingerprint = terminalFingerprint(event);
|
|
@@ -23827,7 +24960,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23827
24960
|
throw new Error("event outbox maxIntermediateEventsPerRun must be a non-negative integer");
|
|
23828
24961
|
}
|
|
23829
24962
|
this.entries = this.read();
|
|
23830
|
-
if (!(0,
|
|
24963
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) {
|
|
23831
24964
|
this.lastPersistedSignature = _EncryptedEventOutbox.EMPTY_SIGNATURE;
|
|
23832
24965
|
} else if (this.entries.length > 0) {
|
|
23833
24966
|
this.lastPersistedSignature = this.serializeForPersist().signature;
|
|
@@ -23857,7 +24990,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23857
24990
|
maxIntermediateEventsPerRun;
|
|
23858
24991
|
enqueue(conversationId, runId, event) {
|
|
23859
24992
|
const entry = {
|
|
23860
|
-
eventId: (0,
|
|
24993
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23861
24994
|
conversationId,
|
|
23862
24995
|
runId,
|
|
23863
24996
|
event,
|
|
@@ -23943,7 +25076,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23943
25076
|
const markerConversationId = runEntry?.conversationId ?? conversationId;
|
|
23944
25077
|
if (!markerConversationId) return;
|
|
23945
25078
|
const marker = {
|
|
23946
|
-
eventId: (0,
|
|
25079
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23947
25080
|
conversationId: markerConversationId,
|
|
23948
25081
|
runId,
|
|
23949
25082
|
event: { type: "text", text: CONDENSATION_NOTICE, ts: Date.now() },
|
|
@@ -24029,14 +25162,14 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24029
25162
|
this.retryTimer = null;
|
|
24030
25163
|
}
|
|
24031
25164
|
read() {
|
|
24032
|
-
if (!(0,
|
|
25165
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) return [];
|
|
24033
25166
|
let envelope;
|
|
24034
25167
|
try {
|
|
24035
|
-
envelope = JSON.parse((0,
|
|
25168
|
+
envelope = JSON.parse((0, import_node_fs7.readFileSync)(this.path, "utf8"));
|
|
24036
25169
|
if (envelope.version !== OUTBOX_VERSION) {
|
|
24037
25170
|
throw new Error(`unsupported event outbox version ${String(envelope.version)}`);
|
|
24038
25171
|
}
|
|
24039
|
-
const decipher = (0,
|
|
25172
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)(
|
|
24040
25173
|
"aes-256-gcm",
|
|
24041
25174
|
this.key,
|
|
24042
25175
|
Buffer.from(envelope.iv, "base64url")
|
|
@@ -24060,21 +25193,21 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24060
25193
|
return { plaintext: "", signature: _EncryptedEventOutbox.EMPTY_SIGNATURE };
|
|
24061
25194
|
}
|
|
24062
25195
|
const plaintext = JSON.stringify({ entries: this.entries });
|
|
24063
|
-
return { plaintext, signature: (0,
|
|
25196
|
+
return { plaintext, signature: (0, import_node_crypto2.createHash)("sha256").update(plaintext).digest("base64url") };
|
|
24064
25197
|
}
|
|
24065
25198
|
persist() {
|
|
24066
25199
|
const { plaintext, signature } = this.serializeForPersist();
|
|
24067
25200
|
if (signature === this.lastPersistedSignature) return;
|
|
24068
25201
|
try {
|
|
24069
25202
|
if (this.entries.length === 0) {
|
|
24070
|
-
(0,
|
|
25203
|
+
(0, import_node_fs7.rmSync)(this.path, { force: true });
|
|
24071
25204
|
this.lastPersistedSignature = signature;
|
|
24072
25205
|
this.persistFailureLogged = false;
|
|
24073
25206
|
return;
|
|
24074
25207
|
}
|
|
24075
|
-
(0,
|
|
24076
|
-
const iv = (0,
|
|
24077
|
-
const cipher = (0,
|
|
25208
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(this.path), { recursive: true, mode: 448 });
|
|
25209
|
+
const iv = (0, import_node_crypto2.randomBytes)(12);
|
|
25210
|
+
const cipher = (0, import_node_crypto2.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
24078
25211
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
24079
25212
|
const envelope = {
|
|
24080
25213
|
version: OUTBOX_VERSION,
|
|
@@ -24082,13 +25215,13 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24082
25215
|
authTag: cipher.getAuthTag().toString("base64url"),
|
|
24083
25216
|
ciphertext: ciphertext.toString("base64url")
|
|
24084
25217
|
};
|
|
24085
|
-
const pendingPath = `${this.path}.pending-${process.pid}-${(0,
|
|
25218
|
+
const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
|
|
24086
25219
|
try {
|
|
24087
|
-
(0,
|
|
24088
|
-
(0,
|
|
24089
|
-
(0,
|
|
25220
|
+
(0, import_node_fs7.writeFileSync)(pendingPath, JSON.stringify(envelope), { mode: 384 });
|
|
25221
|
+
(0, import_node_fs7.chmodSync)(pendingPath, 384);
|
|
25222
|
+
(0, import_node_fs7.renameSync)(pendingPath, this.path);
|
|
24090
25223
|
} finally {
|
|
24091
|
-
(0,
|
|
25224
|
+
(0, import_node_fs7.rmSync)(pendingPath, { force: true });
|
|
24092
25225
|
}
|
|
24093
25226
|
this.lastPersistedSignature = signature;
|
|
24094
25227
|
this.persistFailureLogged = false;
|
|
@@ -24106,38 +25239,38 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24106
25239
|
};
|
|
24107
25240
|
|
|
24108
25241
|
// src/local-skill-scan.ts
|
|
24109
|
-
var
|
|
24110
|
-
var
|
|
25242
|
+
var import_node_fs8 = require("fs");
|
|
25243
|
+
var import_node_path7 = require("path");
|
|
24111
25244
|
var SKILL_READ_LIMIT_BYTES = 8192;
|
|
24112
25245
|
var SKILL_CACHE_TTL_MS = 3e4;
|
|
24113
25246
|
function readSkillHead(path) {
|
|
24114
|
-
const fd = (0,
|
|
25247
|
+
const fd = (0, import_node_fs8.openSync)(path, "r");
|
|
24115
25248
|
try {
|
|
24116
25249
|
const buffer = Buffer.alloc(SKILL_READ_LIMIT_BYTES);
|
|
24117
|
-
const bytesRead = (0,
|
|
25250
|
+
const bytesRead = (0, import_node_fs8.readSync)(fd, buffer, 0, SKILL_READ_LIMIT_BYTES, 0);
|
|
24118
25251
|
return buffer.subarray(0, bytesRead).toString("utf-8");
|
|
24119
25252
|
} finally {
|
|
24120
|
-
(0,
|
|
25253
|
+
(0, import_node_fs8.closeSync)(fd);
|
|
24121
25254
|
}
|
|
24122
25255
|
}
|
|
24123
25256
|
function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
24124
|
-
if (!(0,
|
|
25257
|
+
if (!(0, import_node_fs8.existsSync)(dirPath)) return;
|
|
24125
25258
|
let names;
|
|
24126
25259
|
try {
|
|
24127
|
-
names = (0,
|
|
25260
|
+
names = (0, import_node_fs8.readdirSync)(dirPath);
|
|
24128
25261
|
} catch (error) {
|
|
24129
25262
|
errors.push(`Failed to scan ${dirPath}: ${error instanceof Error ? error.message : error}`);
|
|
24130
25263
|
return;
|
|
24131
25264
|
}
|
|
24132
25265
|
for (const name of names) {
|
|
24133
|
-
const skillPath = (0,
|
|
25266
|
+
const skillPath = (0, import_node_path7.join)(dirPath, name, "SKILL.md");
|
|
24134
25267
|
let isDirectory = false;
|
|
24135
25268
|
try {
|
|
24136
|
-
isDirectory = (0,
|
|
25269
|
+
isDirectory = (0, import_node_fs8.statSync)((0, import_node_path7.join)(dirPath, name)).isDirectory();
|
|
24137
25270
|
} catch {
|
|
24138
25271
|
continue;
|
|
24139
25272
|
}
|
|
24140
|
-
if (!isDirectory || !(0,
|
|
25273
|
+
if (!isDirectory || !(0, import_node_fs8.existsSync)(skillPath)) continue;
|
|
24141
25274
|
try {
|
|
24142
25275
|
entries.push({ content: readSkillHead(skillPath), source, sourcePath: skillPath, repoRoot });
|
|
24143
25276
|
} catch (error) {
|
|
@@ -24148,16 +25281,16 @@ function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
|
24148
25281
|
function discoverChildRepos(parentPath) {
|
|
24149
25282
|
let names;
|
|
24150
25283
|
try {
|
|
24151
|
-
names = (0,
|
|
25284
|
+
names = (0, import_node_fs8.readdirSync)(parentPath);
|
|
24152
25285
|
} catch {
|
|
24153
25286
|
return [];
|
|
24154
25287
|
}
|
|
24155
25288
|
const children = [];
|
|
24156
25289
|
for (const name of names) {
|
|
24157
25290
|
if (name.startsWith(".")) continue;
|
|
24158
|
-
const childPath = (0,
|
|
25291
|
+
const childPath = (0, import_node_path7.join)(parentPath, name);
|
|
24159
25292
|
try {
|
|
24160
|
-
if ((0,
|
|
25293
|
+
if ((0, import_node_fs8.statSync)(childPath).isDirectory()) children.push(childPath);
|
|
24161
25294
|
} catch {
|
|
24162
25295
|
}
|
|
24163
25296
|
}
|
|
@@ -24167,12 +25300,12 @@ function scanLocalSkills(homeDir, projectPath) {
|
|
|
24167
25300
|
const rawEntries = [];
|
|
24168
25301
|
const errors = [];
|
|
24169
25302
|
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
24170
|
-
scanSkillDir((0,
|
|
25303
|
+
scanSkillDir((0, import_node_path7.join)(homeDir, root), "global", void 0, rawEntries, errors);
|
|
24171
25304
|
}
|
|
24172
25305
|
if (projectPath) {
|
|
24173
25306
|
for (const dir of [projectPath, ...discoverChildRepos(projectPath)]) {
|
|
24174
25307
|
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
24175
|
-
scanSkillDir((0,
|
|
25308
|
+
scanSkillDir((0, import_node_path7.join)(dir, root), "team", dir, rawEntries, errors);
|
|
24176
25309
|
}
|
|
24177
25310
|
}
|
|
24178
25311
|
}
|
|
@@ -24191,11 +25324,151 @@ function scanLocalSkillsCached(homeDir, projectPath, options) {
|
|
|
24191
25324
|
return result;
|
|
24192
25325
|
}
|
|
24193
25326
|
|
|
25327
|
+
// src/local-workspace-migration.ts
|
|
25328
|
+
var import_node_crypto3 = require("crypto");
|
|
25329
|
+
var MigrationStashApplyError = class extends Error {
|
|
25330
|
+
stash;
|
|
25331
|
+
stashPreserved = true;
|
|
25332
|
+
constructor(stash, cause) {
|
|
25333
|
+
super(`Could not apply migration stash ${stash.oid}; the stash was preserved`, { cause });
|
|
25334
|
+
this.name = "MigrationStashApplyError";
|
|
25335
|
+
this.stash = stash;
|
|
25336
|
+
}
|
|
25337
|
+
};
|
|
25338
|
+
var BRANCH_LABEL_MAX_LENGTH = 256;
|
|
25339
|
+
function stripControlCharacters(value2) {
|
|
25340
|
+
return Array.from(value2).filter((character) => {
|
|
25341
|
+
const code = character.charCodeAt(0);
|
|
25342
|
+
return code >= 32 && code !== 127;
|
|
25343
|
+
}).join("");
|
|
25344
|
+
}
|
|
25345
|
+
function sanitizeLabel(value2, fallback) {
|
|
25346
|
+
const sanitized = stripControlCharacters(value2).replace(/[^a-zA-Z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, BRANCH_LABEL_MAX_LENGTH);
|
|
25347
|
+
return sanitized || fallback;
|
|
25348
|
+
}
|
|
25349
|
+
function sanitizeBranch(value2) {
|
|
25350
|
+
const sanitized = stripControlCharacters(value2.trim()).slice(0, BRANCH_LABEL_MAX_LENGTH);
|
|
25351
|
+
return sanitized || null;
|
|
25352
|
+
}
|
|
25353
|
+
function countPorcelainEntries(output) {
|
|
25354
|
+
const records = output.split("\0");
|
|
25355
|
+
let count = 0;
|
|
25356
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
25357
|
+
const record = records[index];
|
|
25358
|
+
if (!record) continue;
|
|
25359
|
+
count += 1;
|
|
25360
|
+
const status = record.slice(0, 2);
|
|
25361
|
+
if (status.includes("R") || status.includes("C")) {
|
|
25362
|
+
index += 1;
|
|
25363
|
+
}
|
|
25364
|
+
}
|
|
25365
|
+
return count;
|
|
25366
|
+
}
|
|
25367
|
+
function preflightGitTree(cwd, git2) {
|
|
25368
|
+
const currentBranch = sanitizeBranch(git2(cwd, ["branch", "--show-current"]));
|
|
25369
|
+
const dirtyFileCount = countPorcelainEntries(
|
|
25370
|
+
git2(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"])
|
|
25371
|
+
);
|
|
25372
|
+
return {
|
|
25373
|
+
currentBranch,
|
|
25374
|
+
dirtyFileCount,
|
|
25375
|
+
isDirty: dirtyFileCount > 0
|
|
25376
|
+
};
|
|
25377
|
+
}
|
|
25378
|
+
function preflightWorkspaceMigration(input, git2) {
|
|
25379
|
+
return {
|
|
25380
|
+
direction: input.direction,
|
|
25381
|
+
source: preflightGitTree(input.sourcePath, git2),
|
|
25382
|
+
destination: input.destinationPath ? preflightGitTree(input.destinationPath, git2) : null
|
|
25383
|
+
};
|
|
25384
|
+
}
|
|
25385
|
+
function uniqueOperationLabel(operation, dependencies) {
|
|
25386
|
+
const now = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
25387
|
+
const createdAt = now.toISOString();
|
|
25388
|
+
const id = (dependencies.createId ?? import_node_crypto3.randomUUID)();
|
|
25389
|
+
return {
|
|
25390
|
+
label: [
|
|
25391
|
+
"alan-migration",
|
|
25392
|
+
sanitizeLabel(operation, "workspace"),
|
|
25393
|
+
createdAt.replace(/[:.]/g, "-"),
|
|
25394
|
+
sanitizeLabel(id, "operation")
|
|
25395
|
+
].join("/"),
|
|
25396
|
+
createdAt
|
|
25397
|
+
};
|
|
25398
|
+
}
|
|
25399
|
+
function createMigrationStash(input, dependencies) {
|
|
25400
|
+
const before = preflightGitTree(input.cwd, dependencies.git);
|
|
25401
|
+
if (!before.isDirty) return null;
|
|
25402
|
+
const { label, createdAt } = uniqueOperationLabel(input.operation, dependencies);
|
|
25403
|
+
dependencies.git(input.cwd, ["stash", "push", "--include-untracked", "--message", label]);
|
|
25404
|
+
const oid = dependencies.git(input.cwd, ["rev-parse", "--verify", "refs/stash"]).trim();
|
|
25405
|
+
if (!oid) {
|
|
25406
|
+
throw new Error("Git did not return the created migration stash");
|
|
25407
|
+
}
|
|
25408
|
+
return { oid, label, createdAt };
|
|
25409
|
+
}
|
|
25410
|
+
function findStashRef(cwd, oid, git2) {
|
|
25411
|
+
const matchingLine = git2(cwd, ["stash", "list", "--format=%H %gd"]).split("\n").find((line) => line.startsWith(`${oid} `));
|
|
25412
|
+
return matchingLine?.slice(oid.length + 1).trim() || null;
|
|
25413
|
+
}
|
|
25414
|
+
function dropMigrationStash(cwd, stash, git2) {
|
|
25415
|
+
const stashRef = findStashRef(cwd, stash.oid, git2);
|
|
25416
|
+
if (!stashRef) return false;
|
|
25417
|
+
git2(cwd, ["stash", "drop", stashRef]);
|
|
25418
|
+
return true;
|
|
25419
|
+
}
|
|
25420
|
+
function applyMigrationStash(input, git2) {
|
|
25421
|
+
try {
|
|
25422
|
+
git2(input.cwd, ["stash", "apply", "--index", input.stash.oid]);
|
|
25423
|
+
} catch (cause) {
|
|
25424
|
+
throw new MigrationStashApplyError(input.stash, cause);
|
|
25425
|
+
}
|
|
25426
|
+
if (input.dropOnSuccess !== false) {
|
|
25427
|
+
dropMigrationStash(input.cwd, input.stash, git2);
|
|
25428
|
+
}
|
|
25429
|
+
return preflightGitTree(input.cwd, git2);
|
|
25430
|
+
}
|
|
25431
|
+
function resolveDirtyTree(input, dependencies) {
|
|
25432
|
+
if (input.action === "discard" && input.confirmDiscard !== true) {
|
|
25433
|
+
throw new Error("Discarding workspace changes requires explicit confirmation");
|
|
25434
|
+
}
|
|
25435
|
+
const before = preflightGitTree(input.cwd, dependencies.git);
|
|
25436
|
+
let stash = null;
|
|
25437
|
+
let commitOid = null;
|
|
25438
|
+
if (before.isDirty) {
|
|
25439
|
+
switch (input.action) {
|
|
25440
|
+
case "leave":
|
|
25441
|
+
break;
|
|
25442
|
+
case "move_via_stash":
|
|
25443
|
+
stash = createMigrationStash({ cwd: input.cwd, operation: input.operation }, dependencies);
|
|
25444
|
+
break;
|
|
25445
|
+
case "commit_wip": {
|
|
25446
|
+
const { label } = uniqueOperationLabel(input.operation, dependencies);
|
|
25447
|
+
dependencies.git(input.cwd, ["add", "--all"]);
|
|
25448
|
+
dependencies.git(input.cwd, ["commit", "--message", `WIP: ${label}`]);
|
|
25449
|
+
commitOid = dependencies.git(input.cwd, ["rev-parse", "HEAD"]).trim() || null;
|
|
25450
|
+
break;
|
|
25451
|
+
}
|
|
25452
|
+
case "discard":
|
|
25453
|
+
dependencies.git(input.cwd, ["reset", "--hard", "HEAD"]);
|
|
25454
|
+
dependencies.git(input.cwd, ["clean", "-fd"]);
|
|
25455
|
+
break;
|
|
25456
|
+
}
|
|
25457
|
+
}
|
|
25458
|
+
return {
|
|
25459
|
+
action: input.action,
|
|
25460
|
+
before,
|
|
25461
|
+
after: preflightGitTree(input.cwd, dependencies.git),
|
|
25462
|
+
stash,
|
|
25463
|
+
commitOid
|
|
25464
|
+
};
|
|
25465
|
+
}
|
|
25466
|
+
|
|
24194
25467
|
// src/mcp-registration.ts
|
|
24195
|
-
var
|
|
24196
|
-
var
|
|
24197
|
-
var
|
|
24198
|
-
var
|
|
25468
|
+
var import_node_crypto4 = require("crypto");
|
|
25469
|
+
var import_node_fs9 = require("fs");
|
|
25470
|
+
var import_node_os8 = require("os");
|
|
25471
|
+
var import_node_path8 = require("path");
|
|
24199
25472
|
|
|
24200
25473
|
// ../../node_modules/smol-toml/dist/error.js
|
|
24201
25474
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -24277,7 +25550,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
24277
25550
|
}
|
|
24278
25551
|
return ptr;
|
|
24279
25552
|
}
|
|
24280
|
-
function skipUntil(str, ptr,
|
|
25553
|
+
function skipUntil(str, ptr, sep3, end, banNewLines = false) {
|
|
24281
25554
|
if (!end) {
|
|
24282
25555
|
ptr = indexOfNewline(str, ptr);
|
|
24283
25556
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -24286,7 +25559,7 @@ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
|
24286
25559
|
let c = str[i];
|
|
24287
25560
|
if (c === "#") {
|
|
24288
25561
|
i = indexOfNewline(str, i);
|
|
24289
|
-
} else if (c ===
|
|
25562
|
+
} else if (c === sep3) {
|
|
24290
25563
|
return i + 1;
|
|
24291
25564
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
24292
25565
|
return i;
|
|
@@ -24992,18 +26265,18 @@ var inspectAlanMcpThroughProviderCli = async (backendKind, expectedUrl, home) =>
|
|
|
24992
26265
|
return result;
|
|
24993
26266
|
};
|
|
24994
26267
|
async function waitForConfigLock(lockPath) {
|
|
24995
|
-
(0,
|
|
26268
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(lockPath), { recursive: true });
|
|
24996
26269
|
const startedAt = Date.now();
|
|
24997
26270
|
while (true) {
|
|
24998
26271
|
try {
|
|
24999
|
-
(0,
|
|
25000
|
-
return () => (0,
|
|
26272
|
+
(0, import_node_fs9.mkdirSync)(lockPath);
|
|
26273
|
+
return () => (0, import_node_fs9.rmSync)(lockPath, { recursive: true, force: true });
|
|
25001
26274
|
} catch (error) {
|
|
25002
26275
|
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
25003
26276
|
if (code !== "EEXIST") throw error;
|
|
25004
26277
|
try {
|
|
25005
|
-
if (Date.now() - (0,
|
|
25006
|
-
(0,
|
|
26278
|
+
if (Date.now() - (0, import_node_fs9.statSync)(lockPath).mtimeMs > CONFIG_LOCK_STALE_MS) {
|
|
26279
|
+
(0, import_node_fs9.rmSync)(lockPath, { recursive: true, force: true });
|
|
25007
26280
|
continue;
|
|
25008
26281
|
}
|
|
25009
26282
|
} catch {
|
|
@@ -25012,14 +26285,14 @@ async function waitForConfigLock(lockPath) {
|
|
|
25012
26285
|
if (Date.now() - startedAt >= CONFIG_LOCK_WAIT_MS) {
|
|
25013
26286
|
throw new Error(`Alan could not safely update ${lockPath.slice(0, -".alan-lock".length)}`);
|
|
25014
26287
|
}
|
|
25015
|
-
await new Promise((
|
|
26288
|
+
await new Promise((resolve7) => setTimeout(resolve7, CONFIG_LOCK_POLL_MS));
|
|
25016
26289
|
}
|
|
25017
26290
|
}
|
|
25018
26291
|
}
|
|
25019
26292
|
function saveMalformedBackup(path, content) {
|
|
25020
|
-
const hash = (0,
|
|
26293
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex").slice(0, 12);
|
|
25021
26294
|
const backupPath = `${path}.alan-backup-${hash}`;
|
|
25022
|
-
if (!(0,
|
|
26295
|
+
if (!(0, import_node_fs9.existsSync)(backupPath)) (0, import_node_fs9.copyFileSync)(path, backupPath);
|
|
25023
26296
|
return backupPath;
|
|
25024
26297
|
}
|
|
25025
26298
|
function parseJsonObject2(path, content) {
|
|
@@ -25064,24 +26337,24 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
|
25064
26337
|
return merged;
|
|
25065
26338
|
}
|
|
25066
26339
|
function atomicWriteManagedConfig(path, content) {
|
|
25067
|
-
const pendingPath = `${path}.alan-pending-${process.pid}-${(0,
|
|
26340
|
+
const pendingPath = `${path}.alan-pending-${process.pid}-${(0, import_node_crypto4.randomBytes)(6).toString("hex")}`;
|
|
25068
26341
|
try {
|
|
25069
|
-
(0,
|
|
25070
|
-
(0,
|
|
26342
|
+
(0, import_node_fs9.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
26343
|
+
(0, import_node_fs9.renameSync)(pendingPath, path);
|
|
25071
26344
|
} finally {
|
|
25072
|
-
(0,
|
|
26345
|
+
(0, import_node_fs9.rmSync)(pendingPath, { force: true });
|
|
25073
26346
|
}
|
|
25074
26347
|
}
|
|
25075
|
-
async function ensureAlanMcpRegistered(backendKind, registration, home = (0,
|
|
26348
|
+
async function ensureAlanMcpRegistered(backendKind, registration, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25076
26349
|
try {
|
|
25077
26350
|
const mcpServers = buildAlanMcpServers(registration);
|
|
25078
26351
|
const files = alanMcpConfigFilesForBackend(backendKind, mcpServers, home);
|
|
25079
26352
|
const paths = [];
|
|
25080
26353
|
for (const file of files) {
|
|
25081
|
-
(0,
|
|
26354
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(file.path), { recursive: true });
|
|
25082
26355
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25083
26356
|
try {
|
|
25084
|
-
const current = (0,
|
|
26357
|
+
const current = (0, import_node_fs9.existsSync)(file.path) ? (0, import_node_fs9.readFileSync)(file.path, "utf8") : null;
|
|
25085
26358
|
const content = file.path.endsWith(".toml") ? mergeCodexAlanSection(file.path, current, file.content) : mergeJsonConfig(file.path, current, file.content);
|
|
25086
26359
|
if (current !== content) atomicWriteManagedConfig(file.path, content);
|
|
25087
26360
|
paths.push(file.path);
|
|
@@ -25132,13 +26405,13 @@ async function ensureAlanMcpRegistered(backendKind, registration, home = (0, imp
|
|
|
25132
26405
|
return { ok: false, paths: [], error: err instanceof Error ? err.message : String(err) };
|
|
25133
26406
|
}
|
|
25134
26407
|
}
|
|
25135
|
-
async function verifyAlanMcpRegistered(backendKind, home = (0,
|
|
26408
|
+
async function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25136
26409
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25137
|
-
const missing = files.map((f) => f.path).filter((p) => !(0,
|
|
26410
|
+
const missing = files.map((f) => f.path).filter((p) => !(0, import_node_fs9.existsSync)(p));
|
|
25138
26411
|
const presentEntries = files.map((file) => ({
|
|
25139
26412
|
path: file.path,
|
|
25140
|
-
url: (0,
|
|
25141
|
-
})).filter((entry) => (0,
|
|
26413
|
+
url: (0, import_node_fs9.existsSync)(file.path) ? readUsableAlanMcpUrl(file.path) : null
|
|
26414
|
+
})).filter((entry) => (0, import_node_fs9.existsSync)(entry.path));
|
|
25142
26415
|
let invalid = presentEntries.filter((entry) => !entry.url).map((entry) => entry.path);
|
|
25143
26416
|
const urls = new Set(presentEntries.flatMap((entry) => entry.url ? [entry.url] : []));
|
|
25144
26417
|
if (urls.size > 1) invalid = presentEntries.map((entry) => entry.path);
|
|
@@ -25194,15 +26467,15 @@ function removeAlanFromJsonConfig(path, existingContent) {
|
|
|
25194
26467
|
}
|
|
25195
26468
|
return JSON.stringify(config);
|
|
25196
26469
|
}
|
|
25197
|
-
async function unregisterAlanMcp(backendKind, home = (0,
|
|
26470
|
+
async function unregisterAlanMcp(backendKind, home = (0, import_node_os8.homedir)()) {
|
|
25198
26471
|
const removedPaths = [];
|
|
25199
26472
|
const errors = [];
|
|
25200
26473
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25201
26474
|
for (const file of files) {
|
|
25202
|
-
if (!(0,
|
|
26475
|
+
if (!(0, import_node_fs9.existsSync)(file.path)) continue;
|
|
25203
26476
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25204
26477
|
try {
|
|
25205
|
-
const current = (0,
|
|
26478
|
+
const current = (0, import_node_fs9.readFileSync)(file.path, "utf8");
|
|
25206
26479
|
let next;
|
|
25207
26480
|
if (file.path.endsWith(".toml")) {
|
|
25208
26481
|
const stripped = stripCodexAlanMcpSection(current);
|
|
@@ -25233,7 +26506,7 @@ function isHttpUrl(value2) {
|
|
|
25233
26506
|
}
|
|
25234
26507
|
}
|
|
25235
26508
|
function readUsableAlanMcpUrl(path) {
|
|
25236
|
-
const content = (0,
|
|
26509
|
+
const content = (0, import_node_fs9.readFileSync)(path, "utf8");
|
|
25237
26510
|
if (path.endsWith(".toml")) {
|
|
25238
26511
|
const lines = content.split("\n");
|
|
25239
26512
|
const start = lines.findIndex((line) => line.trim() === "[mcp_servers.alan]");
|
|
@@ -25429,6 +26702,33 @@ function classifyTransportFailure(error) {
|
|
|
25429
26702
|
return "backend_unreachable";
|
|
25430
26703
|
}
|
|
25431
26704
|
|
|
26705
|
+
// src/observable-map.ts
|
|
26706
|
+
var ObservableMap = class extends Map {
|
|
26707
|
+
/**
|
|
26708
|
+
* Invoked after every membership change.
|
|
26709
|
+
*
|
|
26710
|
+
* Assigned after construction rather than passed to the constructor because
|
|
26711
|
+
* `Map`'s constructor may populate entries before subclass fields exist.
|
|
26712
|
+
*/
|
|
26713
|
+
onChange = null;
|
|
26714
|
+
set(key, value2) {
|
|
26715
|
+
const existed = super.has(key);
|
|
26716
|
+
const result = super.set(key, value2);
|
|
26717
|
+
if (!existed) this.onChange?.();
|
|
26718
|
+
return result;
|
|
26719
|
+
}
|
|
26720
|
+
delete(key) {
|
|
26721
|
+
const removed = super.delete(key);
|
|
26722
|
+
if (removed) this.onChange?.();
|
|
26723
|
+
return removed;
|
|
26724
|
+
}
|
|
26725
|
+
clear() {
|
|
26726
|
+
const had = this.size > 0;
|
|
26727
|
+
super.clear();
|
|
26728
|
+
if (had) this.onChange?.();
|
|
26729
|
+
}
|
|
26730
|
+
};
|
|
26731
|
+
|
|
25432
26732
|
// src/provider-auth-preflight.ts
|
|
25433
26733
|
var AUTH_PROBES = {
|
|
25434
26734
|
claude_cli: {
|
|
@@ -25524,7 +26824,7 @@ ${result.stderr}`.trim();
|
|
|
25524
26824
|
}
|
|
25525
26825
|
|
|
25526
26826
|
// src/provider-model-discovery.ts
|
|
25527
|
-
var
|
|
26827
|
+
var import_node_fs10 = require("fs");
|
|
25528
26828
|
var MODEL_DISCOVERY_TIMEOUT_MS = 8e3;
|
|
25529
26829
|
var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
|
|
25530
26830
|
var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
|
|
@@ -25738,7 +27038,7 @@ function filterClaudeDatedModelIds(ids) {
|
|
|
25738
27038
|
}
|
|
25739
27039
|
function resolveExecutableForModelScan(executable) {
|
|
25740
27040
|
try {
|
|
25741
|
-
return (0,
|
|
27041
|
+
return (0, import_node_fs10.realpathSync)(executable);
|
|
25742
27042
|
} catch {
|
|
25743
27043
|
return executable;
|
|
25744
27044
|
}
|
|
@@ -25746,7 +27046,7 @@ function resolveExecutableForModelScan(executable) {
|
|
|
25746
27046
|
function extractClaudeModelIdsFromExecutable(executable) {
|
|
25747
27047
|
try {
|
|
25748
27048
|
const resolved = resolveExecutableForModelScan(executable);
|
|
25749
|
-
const text = (0,
|
|
27049
|
+
const text = (0, import_node_fs10.readFileSync)(resolved).toString("latin1");
|
|
25750
27050
|
const matches = text.match(CLAUDE_MODEL_ID_PATTERN) ?? [];
|
|
25751
27051
|
const unique = [
|
|
25752
27052
|
...new Set(matches.map((id) => id.trim()).filter((id) => isValidClaudeModelId(id))),
|
|
@@ -25873,8 +27173,8 @@ async function discoverProviderModels(provider, executable, env) {
|
|
|
25873
27173
|
}
|
|
25874
27174
|
|
|
25875
27175
|
// src/run-journal.ts
|
|
25876
|
-
var
|
|
25877
|
-
var
|
|
27176
|
+
var import_node_fs11 = require("fs");
|
|
27177
|
+
var import_node_path9 = require("path");
|
|
25878
27178
|
var JOURNAL_VERSION = 1;
|
|
25879
27179
|
var INTERRUPTED_ENTRY_TTL_MS = 24 * 60 * 6e4;
|
|
25880
27180
|
function isEntry(value2) {
|
|
@@ -25893,9 +27193,9 @@ function isEntry(value2) {
|
|
|
25893
27193
|
var RuntimeRunJournal = class {
|
|
25894
27194
|
constructor(path, nowMs = Date.now()) {
|
|
25895
27195
|
this.path = path;
|
|
25896
|
-
if (!(0,
|
|
27196
|
+
if (!(0, import_node_fs11.existsSync)(path)) return;
|
|
25897
27197
|
try {
|
|
25898
|
-
const parsed = JSON.parse((0,
|
|
27198
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
25899
27199
|
if (parsed.version !== JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
|
|
25900
27200
|
throw new Error("unsupported run journal format");
|
|
25901
27201
|
}
|
|
@@ -26002,71 +27302,384 @@ var RuntimeRunJournal = class {
|
|
|
26002
27302
|
providerPid: entry.providerPid
|
|
26003
27303
|
};
|
|
26004
27304
|
}
|
|
26005
|
-
remove(runId) {
|
|
26006
|
-
const previous = this.entries.get(runId);
|
|
26007
|
-
if (!previous) return;
|
|
26008
|
-
const wasRecovered = this.recoveredIds.has(runId);
|
|
26009
|
-
this.entries.delete(runId);
|
|
26010
|
-
this.recoveredIds.delete(runId);
|
|
26011
|
-
try {
|
|
26012
|
-
this.persist();
|
|
26013
|
-
} catch (error) {
|
|
26014
|
-
this.entries.set(runId, previous);
|
|
26015
|
-
if (wasRecovered) this.recoveredIds.add(runId);
|
|
26016
|
-
throw error;
|
|
27305
|
+
remove(runId) {
|
|
27306
|
+
const previous = this.entries.get(runId);
|
|
27307
|
+
if (!previous) return;
|
|
27308
|
+
const wasRecovered = this.recoveredIds.has(runId);
|
|
27309
|
+
this.entries.delete(runId);
|
|
27310
|
+
this.recoveredIds.delete(runId);
|
|
27311
|
+
try {
|
|
27312
|
+
this.persist();
|
|
27313
|
+
} catch (error) {
|
|
27314
|
+
this.entries.set(runId, previous);
|
|
27315
|
+
if (wasRecovered) this.recoveredIds.add(runId);
|
|
27316
|
+
throw error;
|
|
27317
|
+
}
|
|
27318
|
+
}
|
|
27319
|
+
persist() {
|
|
27320
|
+
const pendingPath = `${this.path}.pending`;
|
|
27321
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(this.path), { recursive: true });
|
|
27322
|
+
try {
|
|
27323
|
+
(0, import_node_fs11.writeFileSync)(
|
|
27324
|
+
pendingPath,
|
|
27325
|
+
`${JSON.stringify({ version: JOURNAL_VERSION, entries: [...this.entries.values()] })}
|
|
27326
|
+
`,
|
|
27327
|
+
{ mode: 384 }
|
|
27328
|
+
);
|
|
27329
|
+
(0, import_node_fs11.chmodSync)(pendingPath, 384);
|
|
27330
|
+
(0, import_node_fs11.renameSync)(pendingPath, this.path);
|
|
27331
|
+
} catch (error) {
|
|
27332
|
+
(0, import_node_fs11.rmSync)(pendingPath, { force: true });
|
|
27333
|
+
throw error;
|
|
27334
|
+
}
|
|
27335
|
+
}
|
|
27336
|
+
};
|
|
27337
|
+
|
|
27338
|
+
// src/run-start-gate.ts
|
|
27339
|
+
var RunStartGate = class {
|
|
27340
|
+
tail = Promise.resolve();
|
|
27341
|
+
async acquire() {
|
|
27342
|
+
const previous = this.tail;
|
|
27343
|
+
let releaseCurrent;
|
|
27344
|
+
const current = new Promise((resolve7) => {
|
|
27345
|
+
releaseCurrent = resolve7;
|
|
27346
|
+
});
|
|
27347
|
+
this.tail = previous.then(() => current);
|
|
27348
|
+
await previous;
|
|
27349
|
+
let released = false;
|
|
27350
|
+
return () => {
|
|
27351
|
+
if (released) return;
|
|
27352
|
+
released = true;
|
|
27353
|
+
releaseCurrent();
|
|
27354
|
+
};
|
|
27355
|
+
}
|
|
27356
|
+
};
|
|
27357
|
+
|
|
27358
|
+
// src/sleep-inhibitor.ts
|
|
27359
|
+
var import_node_child_process5 = require("child_process");
|
|
27360
|
+
var SLEEP_PREVENTION_DISABLED_ENV = "ALAN_DISABLE_SLEEP_PREVENTION";
|
|
27361
|
+
var HEALTH_CHECK_INTERVAL_MS = 3e4;
|
|
27362
|
+
var noopLog = () => {
|
|
27363
|
+
};
|
|
27364
|
+
function isSleepPreventionDisabled(env = process.env) {
|
|
27365
|
+
const raw = env[SLEEP_PREVENTION_DISABLED_ENV];
|
|
27366
|
+
if (raw === void 0) return false;
|
|
27367
|
+
const normalized = raw.trim().toLowerCase();
|
|
27368
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
27369
|
+
}
|
|
27370
|
+
var ChildProcessBackend = class {
|
|
27371
|
+
child = null;
|
|
27372
|
+
/**
|
|
27373
|
+
* Set once a candidate command is proven absent (ENOENT). Prevents a spawn
|
|
27374
|
+
* storm from the health watchdog on machines that simply lack the binary.
|
|
27375
|
+
*/
|
|
27376
|
+
unavailable = false;
|
|
27377
|
+
log;
|
|
27378
|
+
constructor(log) {
|
|
27379
|
+
this.log = log;
|
|
27380
|
+
}
|
|
27381
|
+
/**
|
|
27382
|
+
* Let a multi-candidate backend react when its active helper terminates.
|
|
27383
|
+
* This is not called for an intentional release because `release()` clears
|
|
27384
|
+
* the active child before signalling it.
|
|
27385
|
+
*/
|
|
27386
|
+
onUnexpectedTermination() {
|
|
27387
|
+
}
|
|
27388
|
+
isHealthy() {
|
|
27389
|
+
if (this.unavailable) return true;
|
|
27390
|
+
const child = this.child;
|
|
27391
|
+
return child !== null && child.exitCode === null && child.signalCode === null;
|
|
27392
|
+
}
|
|
27393
|
+
acquire() {
|
|
27394
|
+
if (this.unavailable) return;
|
|
27395
|
+
if (this.isHealthy() && this.child !== null) return;
|
|
27396
|
+
this.child = null;
|
|
27397
|
+
const command = this.command();
|
|
27398
|
+
if (command === null) {
|
|
27399
|
+
this.unavailable = true;
|
|
27400
|
+
this.log(`sleep-inhibitor ${this.name} unavailable: no supported command`);
|
|
27401
|
+
return;
|
|
27402
|
+
}
|
|
27403
|
+
let child;
|
|
27404
|
+
try {
|
|
27405
|
+
child = (0, import_node_child_process5.spawn)(command.file, command.args, { stdio: "ignore", detached: false });
|
|
27406
|
+
} catch (error) {
|
|
27407
|
+
this.unavailable = true;
|
|
27408
|
+
this.log(`sleep-inhibitor ${this.name} spawn threw: ${describeError(error)}`);
|
|
27409
|
+
return;
|
|
27410
|
+
}
|
|
27411
|
+
child.on("error", (error) => {
|
|
27412
|
+
const wasActiveChild = this.child === child;
|
|
27413
|
+
if (wasActiveChild) this.child = null;
|
|
27414
|
+
if (error.code === "ENOENT") this.unavailable = true;
|
|
27415
|
+
this.log(`sleep-inhibitor ${this.name} failed: ${describeError(error)}`);
|
|
27416
|
+
if (wasActiveChild) this.onUnexpectedTermination();
|
|
27417
|
+
});
|
|
27418
|
+
child.on("exit", (code, signal) => {
|
|
27419
|
+
const wasActiveChild = this.child === child;
|
|
27420
|
+
if (wasActiveChild) this.child = null;
|
|
27421
|
+
this.log(`sleep-inhibitor ${this.name} exited code=${code} signal=${signal}`);
|
|
27422
|
+
if (wasActiveChild) this.onUnexpectedTermination();
|
|
27423
|
+
});
|
|
27424
|
+
this.child = child;
|
|
27425
|
+
this.log(`sleep-inhibitor ${this.name} acquired pid=${child.pid}`);
|
|
27426
|
+
}
|
|
27427
|
+
release() {
|
|
27428
|
+
const child = this.child;
|
|
27429
|
+
if (child === null) return;
|
|
27430
|
+
this.child = null;
|
|
27431
|
+
try {
|
|
27432
|
+
child.kill("SIGTERM");
|
|
27433
|
+
} catch (error) {
|
|
27434
|
+
this.log(`sleep-inhibitor ${this.name} release failed: ${describeError(error)}`);
|
|
27435
|
+
}
|
|
27436
|
+
}
|
|
27437
|
+
};
|
|
27438
|
+
var MacCaffeinateBackend = class extends ChildProcessBackend {
|
|
27439
|
+
name = "caffeinate";
|
|
27440
|
+
command() {
|
|
27441
|
+
return { file: "/usr/bin/caffeinate", args: ["-i", "-s", "-w", String(process.pid)] };
|
|
27442
|
+
}
|
|
27443
|
+
};
|
|
27444
|
+
var LinuxIdleInhibitBackend = class extends ChildProcessBackend {
|
|
27445
|
+
name = "systemd-inhibit";
|
|
27446
|
+
candidateIndex = 0;
|
|
27447
|
+
candidateCount = 2;
|
|
27448
|
+
command() {
|
|
27449
|
+
const guard = `while kill -0 ${process.pid} 2>/dev/null; do sleep 5; done`;
|
|
27450
|
+
const candidates = [
|
|
27451
|
+
{
|
|
27452
|
+
file: "systemd-inhibit",
|
|
27453
|
+
args: [
|
|
27454
|
+
"--what=idle:sleep",
|
|
27455
|
+
"--mode=block",
|
|
27456
|
+
"--who=alan-agent",
|
|
27457
|
+
"--why=Alan is running an active agent run",
|
|
27458
|
+
"--",
|
|
27459
|
+
"sh",
|
|
27460
|
+
"-c",
|
|
27461
|
+
guard
|
|
27462
|
+
]
|
|
27463
|
+
},
|
|
27464
|
+
{
|
|
27465
|
+
file: "gnome-session-inhibit",
|
|
27466
|
+
args: [
|
|
27467
|
+
"--inhibit",
|
|
27468
|
+
"idle",
|
|
27469
|
+
"--reason",
|
|
27470
|
+
"Alan is running an active agent run",
|
|
27471
|
+
"sh",
|
|
27472
|
+
"-c",
|
|
27473
|
+
guard
|
|
27474
|
+
]
|
|
27475
|
+
}
|
|
27476
|
+
];
|
|
27477
|
+
return candidates[this.candidateIndex] ?? null;
|
|
27478
|
+
}
|
|
27479
|
+
acquire() {
|
|
27480
|
+
const before = this.candidateIndex;
|
|
27481
|
+
super.acquire();
|
|
27482
|
+
if (this.unavailable && this.candidateIndex === before) {
|
|
27483
|
+
this.tryNextCandidate();
|
|
27484
|
+
}
|
|
27485
|
+
}
|
|
27486
|
+
onUnexpectedTermination() {
|
|
27487
|
+
this.tryNextCandidate();
|
|
27488
|
+
}
|
|
27489
|
+
tryNextCandidate() {
|
|
27490
|
+
this.candidateIndex += 1;
|
|
27491
|
+
if (this.candidateIndex >= this.candidateCount) return;
|
|
27492
|
+
this.unavailable = false;
|
|
27493
|
+
super.acquire();
|
|
27494
|
+
}
|
|
27495
|
+
};
|
|
27496
|
+
var WindowsPowerRequestBackend = class extends ChildProcessBackend {
|
|
27497
|
+
name = "windows-power-request";
|
|
27498
|
+
command() {
|
|
27499
|
+
const guard = `while (Get-Process -Id ${process.pid} -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }`;
|
|
27500
|
+
const script = [
|
|
27501
|
+
`Add-Type -Namespace Alan -Name Power -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags);';`,
|
|
27502
|
+
"[Alan.Power]::SetThreadExecutionState(0x80000001) | Out-Null;",
|
|
27503
|
+
guard
|
|
27504
|
+
].join(" ");
|
|
27505
|
+
return {
|
|
27506
|
+
file: "powershell.exe",
|
|
27507
|
+
args: ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", script]
|
|
27508
|
+
};
|
|
27509
|
+
}
|
|
27510
|
+
};
|
|
27511
|
+
var externalBackends = /* @__PURE__ */ new Set();
|
|
27512
|
+
function createPlatformBackends(log, hasExternalBackend) {
|
|
27513
|
+
switch (process.platform) {
|
|
27514
|
+
case "darwin":
|
|
27515
|
+
return [new MacCaffeinateBackend(log)];
|
|
27516
|
+
case "linux":
|
|
27517
|
+
return [new LinuxIdleInhibitBackend(log)];
|
|
27518
|
+
case "win32":
|
|
27519
|
+
return hasExternalBackend ? [] : [new WindowsPowerRequestBackend(log)];
|
|
27520
|
+
default:
|
|
27521
|
+
return [];
|
|
27522
|
+
}
|
|
27523
|
+
}
|
|
27524
|
+
var SleepInhibitor = class {
|
|
27525
|
+
backends;
|
|
27526
|
+
log;
|
|
27527
|
+
enabled;
|
|
27528
|
+
active = false;
|
|
27529
|
+
healthTimer = null;
|
|
27530
|
+
exitHandler = null;
|
|
27531
|
+
disposed = false;
|
|
27532
|
+
constructor(options = {}) {
|
|
27533
|
+
this.log = options.log ?? noopLog;
|
|
27534
|
+
this.enabled = !isSleepPreventionDisabled(options.env ?? process.env);
|
|
27535
|
+
this.backends = this.enabled ? [...createPlatformBackends(this.log, externalBackends.size > 0), ...externalBackends] : [];
|
|
27536
|
+
if (!this.enabled) {
|
|
27537
|
+
this.log(`sleep-inhibitor disabled via ${SLEEP_PREVENTION_DISABLED_ENV}`);
|
|
27538
|
+
}
|
|
27539
|
+
}
|
|
27540
|
+
isActive() {
|
|
27541
|
+
return this.active;
|
|
27542
|
+
}
|
|
27543
|
+
/** Names of the backends this inhibitor will drive. Exposed for diagnostics. */
|
|
27544
|
+
backendNames() {
|
|
27545
|
+
return this.backends.map((backend) => backend.name);
|
|
27546
|
+
}
|
|
27547
|
+
/**
|
|
27548
|
+
* Turn sleep prevention on or off. Safe to call repeatedly with the same
|
|
27549
|
+
* value; only edges do work.
|
|
27550
|
+
*/
|
|
27551
|
+
setActive(active) {
|
|
27552
|
+
if (this.disposed) return;
|
|
27553
|
+
if (this.active === active) {
|
|
27554
|
+
if (active) this.healBackends();
|
|
27555
|
+
return;
|
|
27556
|
+
}
|
|
27557
|
+
this.active = active;
|
|
27558
|
+
if (active) this.start();
|
|
27559
|
+
else this.stop();
|
|
27560
|
+
}
|
|
27561
|
+
/** Release everything and detach timers/handlers. Idempotent. */
|
|
27562
|
+
dispose() {
|
|
27563
|
+
if (this.disposed) return;
|
|
27564
|
+
this.disposed = true;
|
|
27565
|
+
this.active = false;
|
|
27566
|
+
this.stop();
|
|
27567
|
+
}
|
|
27568
|
+
start() {
|
|
27569
|
+
if (this.backends.length === 0) return;
|
|
27570
|
+
this.healBackends();
|
|
27571
|
+
if (this.healthTimer === null) {
|
|
27572
|
+
this.healthTimer = setInterval(() => this.healBackends(), HEALTH_CHECK_INTERVAL_MS);
|
|
27573
|
+
this.healthTimer.unref?.();
|
|
27574
|
+
}
|
|
27575
|
+
if (this.exitHandler === null) {
|
|
27576
|
+
this.exitHandler = () => {
|
|
27577
|
+
for (const backend of this.backends) backend.release();
|
|
27578
|
+
};
|
|
27579
|
+
process.on("exit", this.exitHandler);
|
|
27580
|
+
}
|
|
27581
|
+
}
|
|
27582
|
+
stop() {
|
|
27583
|
+
if (this.healthTimer !== null) {
|
|
27584
|
+
clearInterval(this.healthTimer);
|
|
27585
|
+
this.healthTimer = null;
|
|
27586
|
+
}
|
|
27587
|
+
if (this.exitHandler !== null) {
|
|
27588
|
+
process.removeListener("exit", this.exitHandler);
|
|
27589
|
+
this.exitHandler = null;
|
|
27590
|
+
}
|
|
27591
|
+
for (const backend of this.backends) {
|
|
27592
|
+
try {
|
|
27593
|
+
backend.release();
|
|
27594
|
+
} catch (error) {
|
|
27595
|
+
this.log(`sleep-inhibitor release error: ${describeError(error)}`);
|
|
27596
|
+
}
|
|
26017
27597
|
}
|
|
26018
27598
|
}
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
|
|
26022
|
-
|
|
26023
|
-
|
|
26024
|
-
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
26031
|
-
|
|
26032
|
-
(
|
|
26033
|
-
|
|
27599
|
+
/**
|
|
27600
|
+
* Acquire anything not currently held. This is the self-healing step: a
|
|
27601
|
+
* backend whose helper died is respawned here.
|
|
27602
|
+
*
|
|
27603
|
+
* Healthy backends are skipped rather than re-acquired, so concurrent runs
|
|
27604
|
+
* can never stack assertions even if a backend's own `acquire()` is not
|
|
27605
|
+
* defensively idempotent.
|
|
27606
|
+
*/
|
|
27607
|
+
healBackends() {
|
|
27608
|
+
for (const backend of this.backends) {
|
|
27609
|
+
try {
|
|
27610
|
+
if (backend.isHealthy()) continue;
|
|
27611
|
+
backend.acquire();
|
|
27612
|
+
} catch (error) {
|
|
27613
|
+
this.log(`sleep-inhibitor ${backend.name} acquire error: ${describeError(error)}`);
|
|
27614
|
+
}
|
|
26034
27615
|
}
|
|
26035
27616
|
}
|
|
26036
27617
|
};
|
|
27618
|
+
function describeError(error) {
|
|
27619
|
+
return error instanceof Error ? error.message : String(error);
|
|
27620
|
+
}
|
|
26037
27621
|
|
|
26038
|
-
// src/
|
|
26039
|
-
var
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
|
|
26050
|
-
|
|
26051
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
|
|
27622
|
+
// src/suspension-detector.ts
|
|
27623
|
+
var DEFAULT_CHECK_INTERVAL_MS = 15e3;
|
|
27624
|
+
var DEFAULT_THRESHOLD_MS = 6e4;
|
|
27625
|
+
var SuspensionDetector = class {
|
|
27626
|
+
onSuspensionDetected;
|
|
27627
|
+
checkIntervalMs;
|
|
27628
|
+
thresholdMs;
|
|
27629
|
+
now;
|
|
27630
|
+
setIntervalFn;
|
|
27631
|
+
clearIntervalFn;
|
|
27632
|
+
timer = null;
|
|
27633
|
+
lastTickAtMs = null;
|
|
27634
|
+
constructor(options) {
|
|
27635
|
+
this.onSuspensionDetected = options.onSuspensionDetected;
|
|
27636
|
+
this.checkIntervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
27637
|
+
this.thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
27638
|
+
this.now = options.now ?? Date.now;
|
|
27639
|
+
this.setIntervalFn = options.setIntervalFn ?? setInterval;
|
|
27640
|
+
this.clearIntervalFn = options.clearIntervalFn ?? clearInterval;
|
|
27641
|
+
}
|
|
27642
|
+
start() {
|
|
27643
|
+
if (this.timer !== null) return;
|
|
27644
|
+
this.lastTickAtMs = this.now();
|
|
27645
|
+
this.timer = this.setIntervalFn(() => this.tick(), this.checkIntervalMs);
|
|
27646
|
+
this.timer.unref?.();
|
|
27647
|
+
}
|
|
27648
|
+
stop() {
|
|
27649
|
+
if (this.timer === null) return;
|
|
27650
|
+
this.clearIntervalFn(this.timer);
|
|
27651
|
+
this.timer = null;
|
|
27652
|
+
this.lastTickAtMs = null;
|
|
27653
|
+
}
|
|
27654
|
+
isRunning() {
|
|
27655
|
+
return this.timer !== null;
|
|
27656
|
+
}
|
|
27657
|
+
tick() {
|
|
27658
|
+
const nowMs = this.now();
|
|
27659
|
+
const previousMs = this.lastTickAtMs;
|
|
27660
|
+
this.lastTickAtMs = nowMs;
|
|
27661
|
+
if (previousMs === null) return;
|
|
27662
|
+
const overshootMs = nowMs - previousMs - this.checkIntervalMs;
|
|
27663
|
+
if (overshootMs < this.thresholdMs) return;
|
|
27664
|
+
try {
|
|
27665
|
+
this.onSuspensionDetected(overshootMs);
|
|
27666
|
+
} catch {
|
|
27667
|
+
}
|
|
26055
27668
|
}
|
|
26056
27669
|
};
|
|
26057
27670
|
|
|
26058
27671
|
// src/version.ts
|
|
26059
|
-
var AGENT_VERSION = "0.1.
|
|
27672
|
+
var AGENT_VERSION = "0.1.52";
|
|
26060
27673
|
|
|
26061
27674
|
// src/workspace-relocation.ts
|
|
26062
|
-
var
|
|
26063
|
-
var
|
|
26064
|
-
var
|
|
27675
|
+
var import_node_child_process6 = require("child_process");
|
|
27676
|
+
var import_node_fs12 = require("fs");
|
|
27677
|
+
var import_node_path10 = require("path");
|
|
26065
27678
|
var MAX_RELOCATION_CANDIDATES = 200;
|
|
26066
27679
|
var DEFAULT_PROBE = {
|
|
26067
|
-
childDirectories: (parentPath, limit) => (0,
|
|
27680
|
+
childDirectories: (parentPath, limit) => (0, import_node_fs12.readdirSync)(parentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).slice(0, limit).map((entry) => (0, import_node_path10.join)(parentPath, entry.name)),
|
|
26068
27681
|
gitRemoteUrl: (candidatePath) => {
|
|
26069
|
-
const result = (0,
|
|
27682
|
+
const result = (0, import_node_child_process6.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
|
|
26070
27683
|
encoding: "utf8",
|
|
26071
27684
|
env: getDaemonCliEnvironment(),
|
|
26072
27685
|
timeout: 2e3
|
|
@@ -26095,7 +27708,7 @@ function workspaceMatchesExpectedRepositories(input) {
|
|
|
26095
27708
|
input.expectedRepoUrls.map(normalizeRepositoryIdentity).filter((identity2) => Boolean(identity2))
|
|
26096
27709
|
);
|
|
26097
27710
|
if (identities.size === 0) return false;
|
|
26098
|
-
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0,
|
|
27711
|
+
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0, import_node_path10.resolve)(input.workspacePath));
|
|
26099
27712
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26100
27713
|
return Boolean(identity && identities.has(identity));
|
|
26101
27714
|
}
|
|
@@ -26105,8 +27718,8 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26105
27718
|
);
|
|
26106
27719
|
if (identities.size === 0) return null;
|
|
26107
27720
|
const searchRoots = [
|
|
26108
|
-
(0,
|
|
26109
|
-
...(input.approvedWorkspacePaths ?? []).map((path) => (0,
|
|
27721
|
+
(0, import_node_path10.dirname)((0, import_node_path10.resolve)(input.missingPath)),
|
|
27722
|
+
...(input.approvedWorkspacePaths ?? []).map((path) => (0, import_node_path10.dirname)((0, import_node_path10.resolve)(path)))
|
|
26110
27723
|
].filter((path, index, paths) => paths.indexOf(path) === index);
|
|
26111
27724
|
const candidates = [];
|
|
26112
27725
|
for (const root of searchRoots) {
|
|
@@ -26118,19 +27731,19 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26118
27731
|
}
|
|
26119
27732
|
}
|
|
26120
27733
|
const matches = [];
|
|
26121
|
-
for (const candidate of new Set(candidates.map((path) => (0,
|
|
27734
|
+
for (const candidate of new Set(candidates.map((path) => (0, import_node_path10.resolve)(path)))) {
|
|
26122
27735
|
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl(candidate);
|
|
26123
27736
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26124
|
-
if (identity && identities.has(identity)) matches.push((0,
|
|
27737
|
+
if (identity && identities.has(identity)) matches.push((0, import_node_path10.resolve)(candidate));
|
|
26125
27738
|
if (matches.length > 1) return null;
|
|
26126
27739
|
}
|
|
26127
27740
|
return matches[0] ?? null;
|
|
26128
27741
|
}
|
|
26129
27742
|
|
|
26130
27743
|
// src/workspace-run-lease.ts
|
|
26131
|
-
var
|
|
26132
|
-
var
|
|
26133
|
-
var
|
|
27744
|
+
var import_node_child_process7 = require("child_process");
|
|
27745
|
+
var import_node_fs13 = require("fs");
|
|
27746
|
+
var import_node_path11 = require("path");
|
|
26134
27747
|
var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
|
|
26135
27748
|
function parsePositiveMs(value2, fallback) {
|
|
26136
27749
|
const parsed = value2 ? Number(value2) : Number.NaN;
|
|
@@ -26141,7 +27754,7 @@ var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
|
|
|
26141
27754
|
2e3
|
|
26142
27755
|
);
|
|
26143
27756
|
function gitValue(cwd, args) {
|
|
26144
|
-
const result = (0,
|
|
27757
|
+
const result = (0, import_node_child_process7.spawnSync)("git", ["-C", cwd, ...args], {
|
|
26145
27758
|
encoding: "utf8",
|
|
26146
27759
|
env: getDaemonCliEnvironment(),
|
|
26147
27760
|
timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
|
|
@@ -26150,17 +27763,17 @@ function gitValue(cwd, args) {
|
|
|
26150
27763
|
return result.stdout.trim() || null;
|
|
26151
27764
|
}
|
|
26152
27765
|
function filesystemIdentity(path) {
|
|
26153
|
-
const canonicalPath =
|
|
26154
|
-
const stat = (0,
|
|
27766
|
+
const canonicalPath = import_node_fs13.realpathSync.native(path);
|
|
27767
|
+
const stat = (0, import_node_fs13.statSync)(canonicalPath);
|
|
26155
27768
|
return `${canonicalPath}:${stat.dev}:${stat.ino}`;
|
|
26156
27769
|
}
|
|
26157
27770
|
function resolveGitPath(workspacePath, value2) {
|
|
26158
|
-
return (0,
|
|
27771
|
+
return (0, import_node_path11.isAbsolute)(value2) ? value2 : (0, import_node_path11.resolve)(workspacePath, value2);
|
|
26159
27772
|
}
|
|
26160
27773
|
var DEFAULT_PROBE2 = {
|
|
26161
27774
|
snapshot: (workspacePath) => {
|
|
26162
27775
|
try {
|
|
26163
|
-
const canonicalWorkspacePath =
|
|
27776
|
+
const canonicalWorkspacePath = import_node_fs13.realpathSync.native((0, import_node_path11.resolve)(workspacePath));
|
|
26164
27777
|
const repositoryRoot = gitValue(canonicalWorkspacePath, ["rev-parse", "--show-toplevel"]);
|
|
26165
27778
|
const gitDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-dir"]);
|
|
26166
27779
|
const commonDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-common-dir"]);
|
|
@@ -26169,7 +27782,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26169
27782
|
const remote = gitValue(canonicalWorkspacePath, ["config", "--get", "remote.origin.url"]);
|
|
26170
27783
|
const remoteIdentity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26171
27784
|
return {
|
|
26172
|
-
workspacePath: (0,
|
|
27785
|
+
workspacePath: (0, import_node_path11.resolve)(workspacePath),
|
|
26173
27786
|
workspaceIdentity: filesystemIdentity(canonicalWorkspacePath),
|
|
26174
27787
|
repositoryIdentity: `${filesystemIdentity(
|
|
26175
27788
|
resolveGitPath(canonicalWorkspacePath, commonDirectory)
|
|
@@ -26183,7 +27796,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26183
27796
|
},
|
|
26184
27797
|
pathExists: (workspacePath) => {
|
|
26185
27798
|
try {
|
|
26186
|
-
return (0,
|
|
27799
|
+
return (0, import_node_fs13.existsSync)((0, import_node_path11.resolve)(workspacePath));
|
|
26187
27800
|
} catch {
|
|
26188
27801
|
return true;
|
|
26189
27802
|
}
|
|
@@ -26214,7 +27827,7 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
26214
27827
|
static capture(targets, probe = DEFAULT_PROBE2) {
|
|
26215
27828
|
const captured = [];
|
|
26216
27829
|
for (const target of targets) {
|
|
26217
|
-
const normalizedTarget = { ...target, workspacePath: (0,
|
|
27830
|
+
const normalizedTarget = { ...target, workspacePath: (0, import_node_path11.resolve)(target.workspacePath) };
|
|
26218
27831
|
const identity = probe.snapshot(normalizedTarget.workspacePath);
|
|
26219
27832
|
if (!identity) return { lease: null, failure: unavailableFailure(normalizedTarget) };
|
|
26220
27833
|
if (normalizedTarget.expectedBranch && identity.branch !== normalizedTarget.expectedBranch) {
|
|
@@ -26356,34 +27969,62 @@ var PRODUCTION_API_URL = "https://api.tryalan.ai";
|
|
|
26356
27969
|
var PRODUCTION_WS_URL = "wss://ws.tryalan.ai";
|
|
26357
27970
|
var LOCAL_API_URL = "http://localhost:8400";
|
|
26358
27971
|
var LOCAL_WS_URL = "ws://localhost:8401";
|
|
26359
|
-
function
|
|
27972
|
+
function requiredString2(params, key) {
|
|
26360
27973
|
const value2 = params[key];
|
|
26361
27974
|
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
26362
27975
|
throw new Error(`${key} is required`);
|
|
26363
27976
|
}
|
|
26364
27977
|
return value2;
|
|
26365
27978
|
}
|
|
27979
|
+
var DIRTY_WORKSPACE_ACTIONS = /* @__PURE__ */ new Set([
|
|
27980
|
+
"move_via_stash",
|
|
27981
|
+
"commit_wip",
|
|
27982
|
+
"discard",
|
|
27983
|
+
"leave"
|
|
27984
|
+
]);
|
|
27985
|
+
function parseDirtyWorkspaceResolution(value2) {
|
|
27986
|
+
if (value2 === void 0) return void 0;
|
|
27987
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
|
|
27988
|
+
throw new Error("dirtyResolution must be an object");
|
|
27989
|
+
}
|
|
27990
|
+
const raw = value2;
|
|
27991
|
+
const parseAction = (key) => {
|
|
27992
|
+
const action = raw[key];
|
|
27993
|
+
if (action === void 0) return void 0;
|
|
27994
|
+
if (typeof action !== "string" || !DIRTY_WORKSPACE_ACTIONS.has(action)) {
|
|
27995
|
+
throw new Error(`${key} is not a supported dirty workspace action`);
|
|
27996
|
+
}
|
|
27997
|
+
return action;
|
|
27998
|
+
};
|
|
27999
|
+
return {
|
|
28000
|
+
sourceAction: parseAction("sourceAction"),
|
|
28001
|
+
destinationAction: parseAction("destinationAction"),
|
|
28002
|
+
sourceCommitMessage: typeof raw.sourceCommitMessage === "string" ? raw.sourceCommitMessage : void 0,
|
|
28003
|
+
destinationCommitMessage: typeof raw.destinationCommitMessage === "string" ? raw.destinationCommitMessage : void 0,
|
|
28004
|
+
confirmDiscard: raw.confirmDiscard === true
|
|
28005
|
+
};
|
|
28006
|
+
}
|
|
26366
28007
|
function assertValidGitRef(ref, label) {
|
|
26367
28008
|
if (!ref || ref.startsWith("-") || ref.includes("\0")) {
|
|
26368
28009
|
throw new Error(`${label} is not a safe git ref`);
|
|
26369
28010
|
}
|
|
26370
|
-
const result = (0,
|
|
28011
|
+
const result = (0, import_node_child_process8.spawnSync)("git", ["check-ref-format", "--branch", ref], {
|
|
26371
28012
|
env: getDaemonCliEnvironment(),
|
|
26372
28013
|
encoding: "utf8"
|
|
26373
28014
|
});
|
|
26374
28015
|
if (result.status !== 0) throw new Error(`${label} is not a valid git ref`);
|
|
26375
28016
|
}
|
|
26376
|
-
function
|
|
26377
|
-
const rel = (0,
|
|
26378
|
-
return rel === "" || !(0,
|
|
28017
|
+
function isPathContained2(childPath, parentPath) {
|
|
28018
|
+
const rel = (0, import_node_path12.relative)((0, import_node_path12.resolve)(parentPath), (0, import_node_path12.resolve)(childPath));
|
|
28019
|
+
return rel === "" || !(0, import_node_path12.isAbsolute)(rel) && rel !== ".." && !rel.startsWith(`..${import_node_path12.sep}`);
|
|
26379
28020
|
}
|
|
26380
28021
|
function assertPathContained(childPath, parentPath, label) {
|
|
26381
|
-
if (!
|
|
28022
|
+
if (!isPathContained2(childPath, parentPath)) {
|
|
26382
28023
|
throw new Error(`${label} escapes allowed directory`);
|
|
26383
28024
|
}
|
|
26384
28025
|
}
|
|
26385
28026
|
function git(cwd, args) {
|
|
26386
|
-
const result = (0,
|
|
28027
|
+
const result = (0, import_node_child_process8.spawnSync)("git", args, {
|
|
26387
28028
|
cwd,
|
|
26388
28029
|
env: getDaemonCliEnvironment(),
|
|
26389
28030
|
encoding: "utf8",
|
|
@@ -26402,14 +28043,23 @@ function gitSafe(cwd, args) {
|
|
|
26402
28043
|
}
|
|
26403
28044
|
}
|
|
26404
28045
|
function worktreeDir(projectPath) {
|
|
26405
|
-
const resolved = (0,
|
|
26406
|
-
return (0,
|
|
28046
|
+
const resolved = (0, import_node_path12.resolve)(projectPath);
|
|
28047
|
+
return (0, import_node_path12.join)((0, import_node_path12.dirname)(resolved), `${(0, import_node_path12.basename)(resolved)}-alan-worktrees`);
|
|
28048
|
+
}
|
|
28049
|
+
function hasAlanWorktreeAncestor(targetPath) {
|
|
28050
|
+
let cursor = (0, import_node_path12.resolve)(targetPath);
|
|
28051
|
+
while (true) {
|
|
28052
|
+
if ((0, import_node_path12.basename)(cursor).endsWith("-alan-worktrees")) return true;
|
|
28053
|
+
const parent = (0, import_node_path12.dirname)(cursor);
|
|
28054
|
+
if (parent === cursor) return false;
|
|
28055
|
+
cursor = parent;
|
|
28056
|
+
}
|
|
26407
28057
|
}
|
|
26408
28058
|
function pathSegment(value2) {
|
|
26409
28059
|
return value2.toLowerCase().replace(/^alan\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "task";
|
|
26410
28060
|
}
|
|
26411
28061
|
function repoName(repoUrl, localPath) {
|
|
26412
|
-
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0,
|
|
28062
|
+
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0, import_node_path12.basename)(localPath) || "repo";
|
|
26413
28063
|
}
|
|
26414
28064
|
function findWorktreeByBranch(repoPath, branchName) {
|
|
26415
28065
|
const output = gitSafe(repoPath, ["worktree", "list", "--porcelain"]);
|
|
@@ -26425,21 +28075,21 @@ function findWorktreeByBranch(repoPath, branchName) {
|
|
|
26425
28075
|
function ensureGitWorktree(input) {
|
|
26426
28076
|
assertValidGitRef(input.branchName, "branchName");
|
|
26427
28077
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26428
|
-
const repoPath = (0,
|
|
28078
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26429
28079
|
const root = input.allowedRoot ?? worktreeDir(repoPath);
|
|
26430
28080
|
const existingPath = findWorktreeByBranch(input.repoPath, input.branchName);
|
|
26431
28081
|
if (existingPath) {
|
|
26432
|
-
if ((0,
|
|
28082
|
+
if ((0, import_node_path12.resolve)(existingPath) === repoPath) {
|
|
26433
28083
|
throw new Error(`${input.branchName} is checked out in the shared checkout`);
|
|
26434
28084
|
}
|
|
26435
28085
|
return existingPath;
|
|
26436
28086
|
}
|
|
26437
28087
|
assertPathContained(input.worktreePath, root, "worktreePath");
|
|
26438
|
-
(0,
|
|
28088
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(input.worktreePath), { recursive: true });
|
|
26439
28089
|
if (!gitSafe(input.repoPath, ["branch", "--list", input.branchName])) {
|
|
26440
28090
|
git(input.repoPath, ["branch", input.branchName, input.baseBranch]);
|
|
26441
28091
|
}
|
|
26442
|
-
if ((0,
|
|
28092
|
+
if ((0, import_node_fs14.existsSync)(input.worktreePath)) {
|
|
26443
28093
|
if (gitSafe(input.worktreePath, ["branch", "--show-current"]) === input.branchName) {
|
|
26444
28094
|
return input.worktreePath;
|
|
26445
28095
|
}
|
|
@@ -26449,40 +28099,172 @@ function ensureGitWorktree(input) {
|
|
|
26449
28099
|
return input.worktreePath;
|
|
26450
28100
|
}
|
|
26451
28101
|
function resolveProjectWorktreePaths(projectPath, gitRoot, gitWorktreePath) {
|
|
26452
|
-
const resolvedProjectPath = (0,
|
|
26453
|
-
const resolvedGitRoot = (0,
|
|
26454
|
-
const projectRelativePath = (0,
|
|
26455
|
-
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${
|
|
28102
|
+
const resolvedProjectPath = (0, import_node_path12.resolve)(projectPath);
|
|
28103
|
+
const resolvedGitRoot = (0, import_node_path12.resolve)(gitRoot);
|
|
28104
|
+
const projectRelativePath = (0, import_node_path12.relative)(resolvedGitRoot, resolvedProjectPath);
|
|
28105
|
+
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(projectRelativePath)) {
|
|
26456
28106
|
throw new Error("The selected Project folder is outside its Git repository");
|
|
26457
28107
|
}
|
|
26458
28108
|
return {
|
|
26459
28109
|
repoPath: resolvedGitRoot,
|
|
26460
|
-
projectPath: projectRelativePath ? (0,
|
|
28110
|
+
projectPath: projectRelativePath ? (0, import_node_path12.join)((0, import_node_path12.resolve)(gitWorktreePath), projectRelativePath) : (0, import_node_path12.resolve)(gitWorktreePath)
|
|
28111
|
+
};
|
|
28112
|
+
}
|
|
28113
|
+
function preflightLocalWorkspaceMigration(params) {
|
|
28114
|
+
const direction = params.direction;
|
|
28115
|
+
if (direction !== "to-checkout" && direction !== "to-worktree") {
|
|
28116
|
+
throw new Error("direction must be to-checkout or to-worktree");
|
|
28117
|
+
}
|
|
28118
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
28119
|
+
if (targets.length !== 1) {
|
|
28120
|
+
throw new Error("Dirty workspace migration currently supports exactly one repository");
|
|
28121
|
+
}
|
|
28122
|
+
const rawTarget = targets[0];
|
|
28123
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "projectPath"));
|
|
28124
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
28125
|
+
if (!projectRootOutput) {
|
|
28126
|
+
throw new Error("The selected Project folder is not inside a Git repository");
|
|
28127
|
+
}
|
|
28128
|
+
const projectRoot = (0, import_node_path12.resolve)(projectRootOutput);
|
|
28129
|
+
if (direction === "to-worktree") {
|
|
28130
|
+
const result2 = preflightWorkspaceMigration(
|
|
28131
|
+
{
|
|
28132
|
+
direction: "checkout_to_worktree",
|
|
28133
|
+
sourcePath: projectRoot
|
|
28134
|
+
},
|
|
28135
|
+
git
|
|
28136
|
+
);
|
|
28137
|
+
return {
|
|
28138
|
+
direction,
|
|
28139
|
+
source: result2.source,
|
|
28140
|
+
destination: null,
|
|
28141
|
+
requiresResolution: result2.source.isDirty
|
|
28142
|
+
};
|
|
28143
|
+
}
|
|
28144
|
+
const requestedWorktreePath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
28145
|
+
const worktreeRootOutput = gitSafe(requestedWorktreePath, ["rev-parse", "--show-toplevel"]);
|
|
28146
|
+
if (!worktreeRootOutput) {
|
|
28147
|
+
throw new Error("The task worktree is not a registered Git worktree");
|
|
28148
|
+
}
|
|
28149
|
+
const result = preflightWorkspaceMigration(
|
|
28150
|
+
{
|
|
28151
|
+
direction: "worktree_to_checkout",
|
|
28152
|
+
sourcePath: (0, import_node_path12.resolve)(worktreeRootOutput),
|
|
28153
|
+
destinationPath: projectRoot
|
|
28154
|
+
},
|
|
28155
|
+
git
|
|
28156
|
+
);
|
|
28157
|
+
return {
|
|
28158
|
+
direction,
|
|
28159
|
+
source: result.source,
|
|
28160
|
+
destination: result.destination,
|
|
28161
|
+
requiresResolution: result.source.isDirty || Boolean(result.destination?.isDirty)
|
|
28162
|
+
};
|
|
28163
|
+
}
|
|
28164
|
+
function statusLocalWorkspace(params) {
|
|
28165
|
+
const requestedPath = (0, import_node_path12.resolve)(requiredString2(params, "worktreePath"));
|
|
28166
|
+
const rootOutput = gitSafe(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
28167
|
+
if (!rootOutput) {
|
|
28168
|
+
throw new Error("The selected folder is not inside a Git repository");
|
|
28169
|
+
}
|
|
28170
|
+
const root = (0, import_node_path12.resolve)(rootOutput);
|
|
28171
|
+
const tree = preflightGitTree(root, git);
|
|
28172
|
+
const headSha = gitSafe(root, ["rev-parse", "HEAD"]) || null;
|
|
28173
|
+
const upstream = gitSafe(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
|
28174
|
+
const hasUpstream = upstream !== "";
|
|
28175
|
+
let ahead = null;
|
|
28176
|
+
let behind = null;
|
|
28177
|
+
if (hasUpstream) {
|
|
28178
|
+
const counts = gitSafe(root, ["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]);
|
|
28179
|
+
const [behindRaw, aheadRaw] = counts.split(/\s+/);
|
|
28180
|
+
const parsedBehind = Number.parseInt(behindRaw ?? "", 10);
|
|
28181
|
+
const parsedAhead = Number.parseInt(aheadRaw ?? "", 10);
|
|
28182
|
+
behind = Number.isNaN(parsedBehind) ? null : parsedBehind;
|
|
28183
|
+
ahead = Number.isNaN(parsedAhead) ? null : parsedAhead;
|
|
28184
|
+
}
|
|
28185
|
+
return {
|
|
28186
|
+
branch: tree.currentBranch,
|
|
28187
|
+
headSha,
|
|
28188
|
+
dirty: tree.isDirty,
|
|
28189
|
+
filesChanged: tree.dirtyFileCount,
|
|
28190
|
+
ahead,
|
|
28191
|
+
behind,
|
|
28192
|
+
hasUpstream
|
|
26461
28193
|
};
|
|
26462
28194
|
}
|
|
28195
|
+
function ensureGitWorktreeWithDirtyResolution(input, resolution) {
|
|
28196
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
28197
|
+
const before = preflightGitTree(repoPath, git);
|
|
28198
|
+
let sourceStash = null;
|
|
28199
|
+
if (before.isDirty) {
|
|
28200
|
+
const action = resolution?.sourceAction;
|
|
28201
|
+
if (!action) {
|
|
28202
|
+
throw new Error(
|
|
28203
|
+
`The Project checkout has ${before.dirtyFileCount} uncommitted file${before.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before moving into a worktree.`
|
|
28204
|
+
);
|
|
28205
|
+
}
|
|
28206
|
+
if (action === "commit_wip" && before.currentBranch !== input.baseBranch) {
|
|
28207
|
+
throw new Error(
|
|
28208
|
+
`Commit as WIP would stay on "${before.currentBranch ?? "the current checkout"}", not the selected base "${input.baseBranch}". Choose Move changes with task instead.`
|
|
28209
|
+
);
|
|
28210
|
+
}
|
|
28211
|
+
const resolved = resolveDirtyTree(
|
|
28212
|
+
{
|
|
28213
|
+
cwd: repoPath,
|
|
28214
|
+
action,
|
|
28215
|
+
operation: "checkout-to-worktree",
|
|
28216
|
+
confirmDiscard: resolution?.confirmDiscard
|
|
28217
|
+
},
|
|
28218
|
+
{ git }
|
|
28219
|
+
);
|
|
28220
|
+
sourceStash = resolved.stash;
|
|
28221
|
+
}
|
|
28222
|
+
const existingPath = findWorktreeByBranch(repoPath, input.branchName);
|
|
28223
|
+
const worktreePath = ensureGitWorktree(input);
|
|
28224
|
+
if (!sourceStash) return worktreePath;
|
|
28225
|
+
try {
|
|
28226
|
+
applyMigrationStash({ cwd: worktreePath, stash: sourceStash }, git);
|
|
28227
|
+
return worktreePath;
|
|
28228
|
+
} catch (error) {
|
|
28229
|
+
if (!existingPath) {
|
|
28230
|
+
gitSafe(worktreePath, ["reset", "--hard", "HEAD"]);
|
|
28231
|
+
gitSafe(worktreePath, ["clean", "-fd"]);
|
|
28232
|
+
gitSafe(repoPath, ["worktree", "remove", worktreePath]);
|
|
28233
|
+
gitSafe(repoPath, ["worktree", "prune"]);
|
|
28234
|
+
}
|
|
28235
|
+
throw error;
|
|
28236
|
+
}
|
|
28237
|
+
}
|
|
26463
28238
|
function ensureWorktree(params) {
|
|
26464
28239
|
const input = {
|
|
26465
|
-
projectPath:
|
|
26466
|
-
taskId:
|
|
26467
|
-
branchName:
|
|
28240
|
+
projectPath: requiredString2(params, "projectPath"),
|
|
28241
|
+
taskId: requiredString2(params, "taskId"),
|
|
28242
|
+
branchName: requiredString2(params, "branchName"),
|
|
26468
28243
|
baseBranch: typeof params.baseBranch === "string" ? params.baseBranch : void 0,
|
|
26469
|
-
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : []
|
|
28244
|
+
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : [],
|
|
28245
|
+
dirtyResolution: parseDirtyWorkspaceResolution(params.dirtyResolution)
|
|
26470
28246
|
};
|
|
28247
|
+
if ((input.repoLocalPaths?.length ?? 0) > 1) {
|
|
28248
|
+
throw new Error("A task worktree requires exactly one Git repository.");
|
|
28249
|
+
}
|
|
26471
28250
|
if (input.repoLocalPaths?.length) {
|
|
26472
|
-
const taskRoot2 = (0,
|
|
28251
|
+
const taskRoot2 = (0, import_node_path12.join)(
|
|
26473
28252
|
worktreeDir(input.projectPath),
|
|
26474
28253
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26475
28254
|
);
|
|
26476
28255
|
return {
|
|
26477
28256
|
worktrees: input.repoLocalPaths.map((repo) => {
|
|
26478
28257
|
const baseBranch2 = repo.branch || input.baseBranch || "main";
|
|
26479
|
-
const worktreePath2 =
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
|
|
26483
|
-
|
|
26484
|
-
|
|
26485
|
-
|
|
28258
|
+
const worktreePath2 = ensureGitWorktreeWithDirtyResolution(
|
|
28259
|
+
{
|
|
28260
|
+
repoPath: repo.localPath,
|
|
28261
|
+
worktreePath: (0, import_node_path12.join)(taskRoot2, pathSegment(repoName(repo.repoUrl, repo.localPath))),
|
|
28262
|
+
branchName: input.branchName,
|
|
28263
|
+
baseBranch: baseBranch2,
|
|
28264
|
+
allowedRoot: worktreeDir(input.projectPath)
|
|
28265
|
+
},
|
|
28266
|
+
input.dirtyResolution
|
|
28267
|
+
);
|
|
26486
28268
|
return {
|
|
26487
28269
|
repoId: repo.repoId ?? null,
|
|
26488
28270
|
repoUrl: repo.repoUrl,
|
|
@@ -26490,26 +28272,30 @@ function ensureWorktree(params) {
|
|
|
26490
28272
|
branchName: input.branchName,
|
|
26491
28273
|
baseBranch: baseBranch2
|
|
26492
28274
|
};
|
|
26493
|
-
})
|
|
28275
|
+
}),
|
|
28276
|
+
...input.dirtyResolution ? { dirtyResolution: input.dirtyResolution } : {}
|
|
26494
28277
|
};
|
|
26495
28278
|
}
|
|
26496
|
-
const gitRoot = gitSafe((0,
|
|
28279
|
+
const gitRoot = gitSafe((0, import_node_path12.resolve)(input.projectPath), ["rev-parse", "--show-toplevel"]);
|
|
26497
28280
|
if (!gitRoot) {
|
|
26498
28281
|
throw new Error("The selected Project folder is not inside a Git repository");
|
|
26499
28282
|
}
|
|
26500
|
-
const repoPath = (0,
|
|
28283
|
+
const repoPath = (0, import_node_path12.resolve)(gitRoot);
|
|
26501
28284
|
const baseBranch = input.baseBranch || gitSafe(repoPath, ["branch", "--show-current"]) || "HEAD";
|
|
26502
|
-
const taskRoot = (0,
|
|
28285
|
+
const taskRoot = (0, import_node_path12.join)(
|
|
26503
28286
|
worktreeDir(repoPath),
|
|
26504
28287
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26505
28288
|
);
|
|
26506
28289
|
resolveProjectWorktreePaths(input.projectPath, repoPath, taskRoot);
|
|
26507
|
-
const worktreePath =
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
28290
|
+
const worktreePath = ensureGitWorktreeWithDirtyResolution(
|
|
28291
|
+
{
|
|
28292
|
+
repoPath,
|
|
28293
|
+
worktreePath: taskRoot,
|
|
28294
|
+
branchName: input.branchName,
|
|
28295
|
+
baseBranch
|
|
28296
|
+
},
|
|
28297
|
+
input.dirtyResolution
|
|
28298
|
+
);
|
|
26513
28299
|
const projectWorktreePath = resolveProjectWorktreePaths(
|
|
26514
28300
|
input.projectPath,
|
|
26515
28301
|
repoPath,
|
|
@@ -26522,13 +28308,286 @@ function ensureWorktree(params) {
|
|
|
26522
28308
|
branchName: input.branchName,
|
|
26523
28309
|
baseBranch
|
|
26524
28310
|
}
|
|
26525
|
-
]
|
|
28311
|
+
],
|
|
28312
|
+
...input.dirtyResolution ? { dirtyResolution: input.dirtyResolution } : {}
|
|
28313
|
+
};
|
|
28314
|
+
}
|
|
28315
|
+
function listRegisteredWorktrees(cwd) {
|
|
28316
|
+
return git(cwd, ["worktree", "list", "--porcelain"]).split("\n\n").flatMap((entry) => {
|
|
28317
|
+
const lines = entry.split("\n");
|
|
28318
|
+
const path = lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
|
|
28319
|
+
if (!path) return [];
|
|
28320
|
+
return [
|
|
28321
|
+
{
|
|
28322
|
+
path: (0, import_node_path12.resolve)(path),
|
|
28323
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "")
|
|
28324
|
+
}
|
|
28325
|
+
];
|
|
28326
|
+
});
|
|
28327
|
+
}
|
|
28328
|
+
function removeWorktreesIfClean(params, removeGit = git) {
|
|
28329
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(params, "projectPath"));
|
|
28330
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
28331
|
+
if (targets.length === 0) throw new Error("At least one worktree target is required");
|
|
28332
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
28333
|
+
const projectRoot = projectRootOutput ? (0, import_node_path12.resolve)(projectRootOutput) : null;
|
|
28334
|
+
const projectRegistrations = projectRoot ? listRegisteredWorktrees(projectRoot) : [];
|
|
28335
|
+
const planned = targets.map((target) => {
|
|
28336
|
+
const rawTarget = target;
|
|
28337
|
+
const requestedPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
28338
|
+
const branchName = requiredString2(rawTarget, "branchName");
|
|
28339
|
+
assertValidGitRef(branchName, "branchName");
|
|
28340
|
+
const registrationSets = [];
|
|
28341
|
+
if (projectRegistrations.length > 0) registrationSets.push(projectRegistrations);
|
|
28342
|
+
if ((0, import_node_fs14.existsSync)(requestedPath)) {
|
|
28343
|
+
const requestedRoot = gitSafe(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
28344
|
+
if (!requestedRoot) {
|
|
28345
|
+
throw new Error(
|
|
28346
|
+
`The earlier task worktree is not a registered Git worktree: ${requestedPath}`
|
|
28347
|
+
);
|
|
28348
|
+
}
|
|
28349
|
+
const requestedRegistrations = listRegisteredWorktrees((0, import_node_path12.resolve)(requestedRoot));
|
|
28350
|
+
if (!registrationSets.some(
|
|
28351
|
+
(entries) => entries.length === requestedRegistrations.length && entries.every((entry, index) => entry.path === requestedRegistrations[index]?.path)
|
|
28352
|
+
)) {
|
|
28353
|
+
registrationSets.push(requestedRegistrations);
|
|
28354
|
+
}
|
|
28355
|
+
}
|
|
28356
|
+
for (const registrations of registrationSets) {
|
|
28357
|
+
const mainRoot = registrations[0]?.path;
|
|
28358
|
+
const registeredTarget = registrations.find(
|
|
28359
|
+
(entry) => requestedPath === entry.path || isPathContained2(requestedPath, entry.path)
|
|
28360
|
+
);
|
|
28361
|
+
if (!registeredTarget || !mainRoot) continue;
|
|
28362
|
+
if (registeredTarget.path === mainRoot) {
|
|
28363
|
+
throw new Error("Refusing to remove the Project's main checkout");
|
|
28364
|
+
}
|
|
28365
|
+
if (registeredTarget.branch !== branchName) {
|
|
28366
|
+
throw new Error(
|
|
28367
|
+
`The registered worktree branch changed from "${branchName}" to "${registeredTarget.branch ?? "detached HEAD"}".`
|
|
28368
|
+
);
|
|
28369
|
+
}
|
|
28370
|
+
const allowedRoots = [
|
|
28371
|
+
worktreeDir(mainRoot),
|
|
28372
|
+
worktreeDir(projectPath),
|
|
28373
|
+
...projectRoot ? [worktreeDir(projectRoot)] : []
|
|
28374
|
+
];
|
|
28375
|
+
if (!allowedRoots.some((root) => isPathContained2(registeredTarget.path, root))) {
|
|
28376
|
+
throw new Error("The registered worktree is outside an Alan-managed worktree directory");
|
|
28377
|
+
}
|
|
28378
|
+
if (git(registeredTarget.path, ["status", "--porcelain"]).trim()) {
|
|
28379
|
+
throw new Error(
|
|
28380
|
+
`The earlier task worktree has uncommitted changes at ${requestedPath}. Commit or discard them before changing Project, branch, or worktree mode.`
|
|
28381
|
+
);
|
|
28382
|
+
}
|
|
28383
|
+
return {
|
|
28384
|
+
requestedPath,
|
|
28385
|
+
worktreeRoot: registeredTarget.path,
|
|
28386
|
+
mainRoot,
|
|
28387
|
+
branchName,
|
|
28388
|
+
replayed: false
|
|
28389
|
+
};
|
|
28390
|
+
}
|
|
28391
|
+
if (!(0, import_node_fs14.existsSync)(requestedPath) && hasAlanWorktreeAncestor(requestedPath)) {
|
|
28392
|
+
return {
|
|
28393
|
+
requestedPath,
|
|
28394
|
+
worktreeRoot: null,
|
|
28395
|
+
mainRoot: null,
|
|
28396
|
+
branchName,
|
|
28397
|
+
replayed: true
|
|
28398
|
+
};
|
|
28399
|
+
}
|
|
28400
|
+
throw new Error(
|
|
28401
|
+
`The earlier task worktree is not safely registered and was not changed: ${requestedPath}`
|
|
28402
|
+
);
|
|
28403
|
+
});
|
|
28404
|
+
const removalRoots = planned.flatMap(
|
|
28405
|
+
(target) => target.worktreeRoot && target.mainRoot ? [{ worktreeRoot: target.worktreeRoot, mainRoot: target.mainRoot }] : []
|
|
28406
|
+
);
|
|
28407
|
+
if (new Set(removalRoots.map(({ worktreeRoot }) => worktreeRoot)).size !== removalRoots.length) {
|
|
28408
|
+
throw new Error("Duplicate worktree targets were not changed");
|
|
28409
|
+
}
|
|
28410
|
+
for (const target of removalRoots) {
|
|
28411
|
+
removeGit(target.mainRoot, ["worktree", "remove", target.worktreeRoot]);
|
|
28412
|
+
gitSafe(target.mainRoot, ["worktree", "prune"]);
|
|
28413
|
+
}
|
|
28414
|
+
return {
|
|
28415
|
+
ok: true,
|
|
28416
|
+
removed: planned.map(({ requestedPath, branchName, replayed }) => ({
|
|
28417
|
+
worktreePath: requestedPath,
|
|
28418
|
+
branchName,
|
|
28419
|
+
replayed
|
|
28420
|
+
}))
|
|
28421
|
+
};
|
|
28422
|
+
}
|
|
28423
|
+
function migrateWorktreeToCheckout(params, removeGit = git) {
|
|
28424
|
+
const dirtyResolution = parseDirtyWorkspaceResolution(params.dirtyResolution);
|
|
28425
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
28426
|
+
if (targets.length !== 1) {
|
|
28427
|
+
throw new Error(
|
|
28428
|
+
"Multi-repository worktree migration is not supported; no repositories were changed."
|
|
28429
|
+
);
|
|
28430
|
+
}
|
|
28431
|
+
const target = targets[0];
|
|
28432
|
+
const rawTarget = target;
|
|
28433
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "projectPath"));
|
|
28434
|
+
const requestedWorktreePath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
28435
|
+
const branch = requiredString2(rawTarget, "branch");
|
|
28436
|
+
assertValidGitRef(branch, "branch");
|
|
28437
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
28438
|
+
if (!projectRootOutput) {
|
|
28439
|
+
throw new Error("The selected Project folder is not inside a Git repository");
|
|
28440
|
+
}
|
|
28441
|
+
const projectRoot = (0, import_node_path12.resolve)(projectRootOutput);
|
|
28442
|
+
const projectRelativePath = (0, import_node_path12.relative)(projectRoot, projectPath);
|
|
28443
|
+
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(projectRelativePath)) {
|
|
28444
|
+
throw new Error("The selected Project folder is outside its Git repository");
|
|
28445
|
+
}
|
|
28446
|
+
const registered = git(projectRoot, ["worktree", "list", "--porcelain"]).split("\n\n").map((entry) => {
|
|
28447
|
+
const lines = entry.split("\n");
|
|
28448
|
+
return {
|
|
28449
|
+
path: lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length),
|
|
28450
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "")
|
|
28451
|
+
};
|
|
28452
|
+
});
|
|
28453
|
+
const registeredTarget = registered.find(({ path, branch: registeredBranch }) => {
|
|
28454
|
+
if (!path || registeredBranch !== branch) return false;
|
|
28455
|
+
const root = (0, import_node_path12.resolve)(path);
|
|
28456
|
+
return requestedWorktreePath === root || isPathContained2(requestedWorktreePath, root);
|
|
28457
|
+
});
|
|
28458
|
+
const currentBranch = gitSafe(projectRoot, ["branch", "--show-current"]);
|
|
28459
|
+
if (!registeredTarget?.path) {
|
|
28460
|
+
if (currentBranch === branch) {
|
|
28461
|
+
return {
|
|
28462
|
+
ok: true,
|
|
28463
|
+
replayed: true,
|
|
28464
|
+
migrated: [{ ...target, projectPath, worktreePath: requestedWorktreePath, branch }],
|
|
28465
|
+
...dirtyResolution ? { dirtyResolution } : {}
|
|
28466
|
+
};
|
|
28467
|
+
}
|
|
28468
|
+
throw new Error(
|
|
28469
|
+
`The task worktree is no longer registered and the Project is not on branch "${branch}".`
|
|
28470
|
+
);
|
|
28471
|
+
}
|
|
28472
|
+
const worktreeRoot = (0, import_node_path12.resolve)(registeredTarget.path);
|
|
28473
|
+
const worktreeRelativePath = (0, import_node_path12.relative)(worktreeRoot, requestedWorktreePath);
|
|
28474
|
+
if (worktreeRelativePath !== projectRelativePath) {
|
|
28475
|
+
throw new Error(
|
|
28476
|
+
"The configured Project and task worktree do not point at the same repository subdirectory."
|
|
28477
|
+
);
|
|
28478
|
+
}
|
|
28479
|
+
const allowedRoots = [worktreeDir(projectRoot), worktreeDir(projectPath)];
|
|
28480
|
+
if (!allowedRoots.some((allowedRoot) => isPathContained2(worktreeRoot, allowedRoot))) {
|
|
28481
|
+
throw new Error("The task worktree escapes the Project worktree directory.");
|
|
28482
|
+
}
|
|
28483
|
+
if (!(0, import_node_fs14.existsSync)(worktreeRoot)) {
|
|
28484
|
+
throw new Error(`Worktree directory not found: ${worktreeRoot}`);
|
|
28485
|
+
}
|
|
28486
|
+
const sourceBefore = preflightGitTree(worktreeRoot, git);
|
|
28487
|
+
const destinationBefore = preflightGitTree(projectRoot, git);
|
|
28488
|
+
if (sourceBefore.isDirty && !dirtyResolution?.sourceAction) {
|
|
28489
|
+
throw new Error(
|
|
28490
|
+
`The task worktree has ${sourceBefore.dirtyFileCount} uncommitted file${sourceBefore.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before migrating.`
|
|
28491
|
+
);
|
|
28492
|
+
}
|
|
28493
|
+
if (destinationBefore.isDirty && !dirtyResolution?.destinationAction) {
|
|
28494
|
+
throw new Error(
|
|
28495
|
+
`The Project checkout has ${destinationBefore.dirtyFileCount} uncommitted file${destinationBefore.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before migrating.`
|
|
28496
|
+
);
|
|
28497
|
+
}
|
|
28498
|
+
if (sourceBefore.isDirty && dirtyResolution?.sourceAction === "leave") {
|
|
28499
|
+
throw new Error("Task worktree changes cannot be left behind when releasing the worktree.");
|
|
28500
|
+
}
|
|
28501
|
+
if (destinationBefore.isDirty && dirtyResolution?.destinationAction === "leave") {
|
|
28502
|
+
throw new Error("Project checkout changes cannot stay in place while switching branches.");
|
|
28503
|
+
}
|
|
28504
|
+
const destinationResolved = destinationBefore.isDirty ? resolveDirtyTree(
|
|
28505
|
+
{
|
|
28506
|
+
cwd: projectRoot,
|
|
28507
|
+
action: dirtyResolution?.destinationAction ?? "leave",
|
|
28508
|
+
operation: "worktree-to-checkout-destination",
|
|
28509
|
+
confirmDiscard: dirtyResolution?.confirmDiscard
|
|
28510
|
+
},
|
|
28511
|
+
{ git }
|
|
28512
|
+
) : null;
|
|
28513
|
+
const sourceResolved = sourceBefore.isDirty ? resolveDirtyTree(
|
|
28514
|
+
{
|
|
28515
|
+
cwd: worktreeRoot,
|
|
28516
|
+
action: dirtyResolution?.sourceAction ?? "leave",
|
|
28517
|
+
operation: "worktree-to-checkout-source",
|
|
28518
|
+
confirmDiscard: dirtyResolution?.confirmDiscard
|
|
28519
|
+
},
|
|
28520
|
+
{ git }
|
|
28521
|
+
) : null;
|
|
28522
|
+
try {
|
|
28523
|
+
git(projectRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
28524
|
+
} catch {
|
|
28525
|
+
throw new Error(`The target branch "${branch}" is not available in the Project.`);
|
|
28526
|
+
}
|
|
28527
|
+
removeGit(projectRoot, ["worktree", "remove", worktreeRoot]);
|
|
28528
|
+
gitSafe(projectRoot, ["worktree", "prune"]);
|
|
28529
|
+
try {
|
|
28530
|
+
git(projectRoot, ["checkout", branch]);
|
|
28531
|
+
} catch (checkoutError) {
|
|
28532
|
+
try {
|
|
28533
|
+
git(projectRoot, ["worktree", "add", worktreeRoot, branch]);
|
|
28534
|
+
if (sourceResolved?.stash) {
|
|
28535
|
+
applyMigrationStash({ cwd: worktreeRoot, stash: sourceResolved.stash }, git);
|
|
28536
|
+
}
|
|
28537
|
+
if (destinationResolved?.stash) {
|
|
28538
|
+
applyMigrationStash({ cwd: projectRoot, stash: destinationResolved.stash }, git);
|
|
28539
|
+
}
|
|
28540
|
+
} catch (restoreError) {
|
|
28541
|
+
const checkoutMessage2 = checkoutError instanceof Error ? checkoutError.message : String(checkoutError);
|
|
28542
|
+
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
28543
|
+
throw new Error(
|
|
28544
|
+
`Project checkout failed (${checkoutMessage2}) and the task worktree could not be restored (${restoreMessage}).`
|
|
28545
|
+
);
|
|
28546
|
+
}
|
|
28547
|
+
const checkoutMessage = checkoutError instanceof Error ? checkoutError.message : String(checkoutError);
|
|
28548
|
+
throw new Error(
|
|
28549
|
+
`Project checkout failed (${checkoutMessage}); restored the task worktree at ${worktreeRoot}.`
|
|
28550
|
+
);
|
|
28551
|
+
}
|
|
28552
|
+
if (sourceResolved?.stash) {
|
|
28553
|
+
try {
|
|
28554
|
+
applyMigrationStash({ cwd: projectRoot, stash: sourceResolved.stash }, git);
|
|
28555
|
+
} catch (applyError) {
|
|
28556
|
+
gitSafe(projectRoot, ["reset", "--hard", "HEAD"]);
|
|
28557
|
+
gitSafe(projectRoot, ["clean", "-fd"]);
|
|
28558
|
+
try {
|
|
28559
|
+
if (destinationBefore.currentBranch && destinationBefore.currentBranch !== branch) {
|
|
28560
|
+
git(projectRoot, ["checkout", destinationBefore.currentBranch]);
|
|
28561
|
+
}
|
|
28562
|
+
git(projectRoot, ["worktree", "add", worktreeRoot, branch]);
|
|
28563
|
+
applyMigrationStash({ cwd: worktreeRoot, stash: sourceResolved.stash }, git);
|
|
28564
|
+
if (destinationResolved?.stash) {
|
|
28565
|
+
applyMigrationStash({ cwd: projectRoot, stash: destinationResolved.stash }, git);
|
|
28566
|
+
}
|
|
28567
|
+
} catch (restoreError) {
|
|
28568
|
+
const applyMessage2 = applyError instanceof Error ? applyError.message : String(applyError);
|
|
28569
|
+
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
28570
|
+
throw new Error(
|
|
28571
|
+
`Moving uncommitted changes failed (${applyMessage2}); automatic workspace restoration also failed (${restoreMessage}). The named migration stash was preserved.`
|
|
28572
|
+
);
|
|
28573
|
+
}
|
|
28574
|
+
const applyMessage = applyError instanceof Error ? applyError.message : String(applyError);
|
|
28575
|
+
throw new Error(
|
|
28576
|
+
`Moving uncommitted changes failed (${applyMessage}); the original worktree and checkout were restored.`
|
|
28577
|
+
);
|
|
28578
|
+
}
|
|
28579
|
+
}
|
|
28580
|
+
return {
|
|
28581
|
+
ok: true,
|
|
28582
|
+
replayed: false,
|
|
28583
|
+
migrated: [{ ...target, projectPath, worktreePath: requestedWorktreePath, branch }],
|
|
28584
|
+
...dirtyResolution ? { dirtyResolution } : {}
|
|
26526
28585
|
};
|
|
26527
28586
|
}
|
|
26528
28587
|
function ensureSharedCheckoutBranch(input) {
|
|
26529
28588
|
assertValidGitRef(input.branchName, "branchName");
|
|
26530
28589
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26531
|
-
const repoPath = (0,
|
|
28590
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26532
28591
|
if (gitSafe(repoPath, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
|
|
26533
28592
|
return { checkedOut: false, reason: `${repoPath} is not a git work tree` };
|
|
26534
28593
|
}
|
|
@@ -26573,7 +28632,7 @@ function resolveSharedCheckoutTargets(input) {
|
|
|
26573
28632
|
}
|
|
26574
28633
|
function resolveRuntimeCwd(payload) {
|
|
26575
28634
|
const multiRepoWorktree = payload.worktreeInfo?.allWorktrees?.[0]?.worktreePath;
|
|
26576
|
-
if (multiRepoWorktree) return (0,
|
|
28635
|
+
if (multiRepoWorktree) return (0, import_node_path12.dirname)(multiRepoWorktree);
|
|
26577
28636
|
if (isAlanDefaultWorkspace(payload.projectPath)) return resolveAlanDefaultWorkspacePath();
|
|
26578
28637
|
return payload.worktreeInfo?.worktreePath ?? payload.projectPath ?? process.cwd();
|
|
26579
28638
|
}
|
|
@@ -26686,14 +28745,14 @@ function localServiceOwnerPayload(owner) {
|
|
|
26686
28745
|
].join("\0");
|
|
26687
28746
|
}
|
|
26688
28747
|
function signLocalServiceOwner(secret, owner) {
|
|
26689
|
-
return (0,
|
|
28748
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(localServiceOwnerPayload(owner)).digest("hex");
|
|
26690
28749
|
}
|
|
26691
28750
|
function signLocalServiceAdoption(secret, serviceId, challenge) {
|
|
26692
|
-
return (0,
|
|
28751
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(["adopt", serviceId, challenge].join("\0")).digest("hex");
|
|
26693
28752
|
}
|
|
26694
28753
|
function equalHexDigest(actual, expected) {
|
|
26695
28754
|
if (!/^[a-f0-9]{64}$/i.test(actual) || !/^[a-f0-9]{64}$/i.test(expected)) return false;
|
|
26696
|
-
return (0,
|
|
28755
|
+
return (0, import_node_crypto5.timingSafeEqual)(Buffer.from(actual, "hex"), Buffer.from(expected, "hex"));
|
|
26697
28756
|
}
|
|
26698
28757
|
function parseLocalServiceOwner(value2) {
|
|
26699
28758
|
if (!value2 || typeof value2 !== "object") return null;
|
|
@@ -26795,12 +28854,12 @@ function getConfigPath() {
|
|
|
26795
28854
|
return process.env.ALAN_AGENT_CONFIG_PATH ?? boundAgentConfigPath ?? resolveAgentConfigPath({ profile: "production" });
|
|
26796
28855
|
}
|
|
26797
28856
|
function getEndpointDefaultsPath() {
|
|
26798
|
-
return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0,
|
|
28857
|
+
return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0, import_node_path12.join)((0, import_node_path12.dirname)(getConfigPath()), "endpoints.json");
|
|
26799
28858
|
}
|
|
26800
28859
|
function readConfig() {
|
|
26801
28860
|
const configPath = getConfigPath();
|
|
26802
|
-
if (!(0,
|
|
26803
|
-
const parsed = JSON.parse((0,
|
|
28861
|
+
if (!(0, import_node_fs14.existsSync)(configPath)) return {};
|
|
28862
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(configPath, "utf8"));
|
|
26804
28863
|
const sanitized = sanitizeAgentConfig(parsed);
|
|
26805
28864
|
if (Object.keys(parsed).some((key) => !Object.hasOwn(sanitized, key))) {
|
|
26806
28865
|
writeConfig(sanitized);
|
|
@@ -26816,27 +28875,27 @@ function readConfigForRegistration() {
|
|
|
26816
28875
|
}
|
|
26817
28876
|
function readEndpointDefaults() {
|
|
26818
28877
|
const endpointsPath = getEndpointDefaultsPath();
|
|
26819
|
-
if (!(0,
|
|
28878
|
+
if (!(0, import_node_fs14.existsSync)(endpointsPath)) return {};
|
|
26820
28879
|
try {
|
|
26821
|
-
return JSON.parse((0,
|
|
28880
|
+
return JSON.parse((0, import_node_fs14.readFileSync)(endpointsPath, "utf8"));
|
|
26822
28881
|
} catch {
|
|
26823
28882
|
return {};
|
|
26824
28883
|
}
|
|
26825
28884
|
}
|
|
26826
28885
|
function writeConfig(config) {
|
|
26827
28886
|
const configPath = getConfigPath();
|
|
26828
|
-
(0,
|
|
26829
|
-
const pendingPath = `${configPath}.pending-${process.pid}-${(0,
|
|
28887
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(configPath), { recursive: true, mode: 448 });
|
|
28888
|
+
const pendingPath = `${configPath}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
26830
28889
|
try {
|
|
26831
|
-
(0,
|
|
28890
|
+
(0, import_node_fs14.writeFileSync)(
|
|
26832
28891
|
pendingPath,
|
|
26833
28892
|
JSON.stringify(sanitizeAgentConfig(config), null, 2),
|
|
26834
28893
|
{ mode: 384 }
|
|
26835
28894
|
);
|
|
26836
|
-
(0,
|
|
26837
|
-
(0,
|
|
28895
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28896
|
+
(0, import_node_fs14.renameSync)(pendingPath, configPath);
|
|
26838
28897
|
} finally {
|
|
26839
|
-
(0,
|
|
28898
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26840
28899
|
}
|
|
26841
28900
|
}
|
|
26842
28901
|
function maintenanceLeasePath(configPath) {
|
|
@@ -26844,9 +28903,9 @@ function maintenanceLeasePath(configPath) {
|
|
|
26844
28903
|
}
|
|
26845
28904
|
function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
26846
28905
|
const path = maintenanceLeasePath(configPath);
|
|
26847
|
-
if (!(0,
|
|
28906
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26848
28907
|
try {
|
|
26849
|
-
const lease = JSON.parse((0,
|
|
28908
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26850
28909
|
if (lease.version !== 1 || typeof lease.id !== "string" || lease.reason !== "credential_cleanup" && lease.reason !== "daemon_startup" && lease.reason !== "watchdog" || !(lease.reason === "credential_cleanup" && lease.expiresAtMs === null || typeof lease.expiresAtMs === "number" && Number.isFinite(lease.expiresAtMs))) {
|
|
26851
28910
|
throw new Error("invalid maintenance lease");
|
|
26852
28911
|
}
|
|
@@ -26863,8 +28922,8 @@ function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
|
26863
28922
|
expiresAtMs: nowMs + 5e3
|
|
26864
28923
|
};
|
|
26865
28924
|
try {
|
|
26866
|
-
(0,
|
|
26867
|
-
(0,
|
|
28925
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(fallback), { mode: 384 });
|
|
28926
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26868
28927
|
} catch {
|
|
26869
28928
|
}
|
|
26870
28929
|
return fallback;
|
|
@@ -26875,15 +28934,15 @@ function acquireLocalDaemonMaintenanceLease(configPath, reason, ttlMs = 5e3) {
|
|
|
26875
28934
|
if (existing) return { acquired: false, lease: existing };
|
|
26876
28935
|
const lease = {
|
|
26877
28936
|
version: 1,
|
|
26878
|
-
id: (0,
|
|
28937
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26879
28938
|
reason,
|
|
26880
28939
|
expiresAtMs: reason === "credential_cleanup" ? null : Date.now() + Math.max(1e3, Math.min(ttlMs, 6e4))
|
|
26881
28940
|
};
|
|
26882
28941
|
const path = maintenanceLeasePath(configPath);
|
|
26883
|
-
(0,
|
|
28942
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26884
28943
|
try {
|
|
26885
|
-
(0,
|
|
26886
|
-
(0,
|
|
28944
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28945
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26887
28946
|
return { acquired: true, lease };
|
|
26888
28947
|
} catch (error) {
|
|
26889
28948
|
const raced = readLocalDaemonMaintenanceLease(configPath);
|
|
@@ -26899,9 +28958,9 @@ function daemonOwnerLeasePath(configPath) {
|
|
|
26899
28958
|
}
|
|
26900
28959
|
function readLocalDaemonOwnerLease(configPath) {
|
|
26901
28960
|
const path = daemonOwnerLeasePath(configPath);
|
|
26902
|
-
if (!(0,
|
|
28961
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26903
28962
|
try {
|
|
26904
|
-
const lease = JSON.parse((0,
|
|
28963
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26905
28964
|
if (lease.version !== 1 || typeof lease.id !== "string" || typeof lease.daemonSessionId !== "string" || !Number.isSafeInteger(lease.pid) || lease.pid <= 0 || typeof lease.startedAtMs !== "number" || !Number.isFinite(lease.startedAtMs) || typeof lease.runtimeId !== "string" || typeof lease.localServiceId !== "string" || !Number.isSafeInteger(lease.localApiPort) || lease.localApiPort < 0 || lease.localApiPort > 65535 || typeof lease.localApiToken !== "string" || lease.localApiToken.length === 0) {
|
|
26906
28965
|
throw new Error("invalid daemon owner lease");
|
|
26907
28966
|
}
|
|
@@ -26923,10 +28982,13 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26923
28982
|
}
|
|
26924
28983
|
return { acquired: false, lease: existing };
|
|
26925
28984
|
}
|
|
26926
|
-
if (endpoint.state === "listening_unauthorized"
|
|
28985
|
+
if (endpoint.state === "listening_unauthorized") {
|
|
28986
|
+
return { acquired: false, lease: existing };
|
|
28987
|
+
}
|
|
28988
|
+
const journal = inspectLocalDaemonRunJournal(input.configPath);
|
|
28989
|
+
if (isProviderProcessAlive(existing.pid) && !journal.safeToRestart) {
|
|
26927
28990
|
return { acquired: false, lease: existing };
|
|
26928
28991
|
}
|
|
26929
|
-
inspectLocalDaemonRunJournal(input.configPath);
|
|
26930
28992
|
releaseOwnedJsonFile(daemonOwnerLeasePath(input.configPath), existing.id);
|
|
26931
28993
|
}
|
|
26932
28994
|
if (!existing && input.storedLocalApiPort && input.storedLocalApiToken) {
|
|
@@ -26943,7 +29005,7 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26943
29005
|
}
|
|
26944
29006
|
const lease = {
|
|
26945
29007
|
version: 1,
|
|
26946
|
-
id: (0,
|
|
29008
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26947
29009
|
daemonSessionId: input.daemonSessionId,
|
|
26948
29010
|
pid: process.pid,
|
|
26949
29011
|
startedAtMs: Date.now(),
|
|
@@ -26953,10 +29015,10 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26953
29015
|
localApiToken: input.localApiToken
|
|
26954
29016
|
};
|
|
26955
29017
|
const path = daemonOwnerLeasePath(input.configPath);
|
|
26956
|
-
(0,
|
|
29018
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26957
29019
|
try {
|
|
26958
|
-
(0,
|
|
26959
|
-
(0,
|
|
29020
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
29021
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26960
29022
|
return { acquired: true, lease };
|
|
26961
29023
|
} catch (error) {
|
|
26962
29024
|
const raced = readLocalDaemonOwnerLease(input.configPath);
|
|
@@ -26971,27 +29033,27 @@ function updateLocalDaemonOwnerEndpoint(configPath, lease, localApiPort) {
|
|
|
26971
29033
|
const path = daemonOwnerLeasePath(configPath);
|
|
26972
29034
|
const pendingPath = `${path}.pending-${lease.id}`;
|
|
26973
29035
|
try {
|
|
26974
|
-
(0,
|
|
26975
|
-
(0,
|
|
26976
|
-
(0,
|
|
29036
|
+
(0, import_node_fs14.writeFileSync)(pendingPath, JSON.stringify(updated), { mode: 384 });
|
|
29037
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
29038
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
26977
29039
|
return updated;
|
|
26978
29040
|
} finally {
|
|
26979
|
-
(0,
|
|
29041
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26980
29042
|
}
|
|
26981
29043
|
}
|
|
26982
29044
|
function releaseLocalDaemonOwnerLease(configPath, leaseId) {
|
|
26983
29045
|
releaseOwnedJsonFile(daemonOwnerLeasePath(configPath), leaseId);
|
|
26984
29046
|
}
|
|
26985
29047
|
function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
26986
|
-
const releasePath = `${path}.release-${process.pid}-${(0,
|
|
29048
|
+
const releasePath = `${path}.release-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
26987
29049
|
try {
|
|
26988
|
-
(0,
|
|
29050
|
+
(0, import_node_fs14.renameSync)(path, releasePath);
|
|
26989
29051
|
} catch (error) {
|
|
26990
29052
|
if (error.code === "ENOENT") return;
|
|
26991
29053
|
throw error;
|
|
26992
29054
|
}
|
|
26993
29055
|
try {
|
|
26994
|
-
const content = (0,
|
|
29056
|
+
const content = (0, import_node_fs14.readFileSync)(releasePath, "utf8");
|
|
26995
29057
|
let capturedOwnerId = null;
|
|
26996
29058
|
try {
|
|
26997
29059
|
const captured = JSON.parse(content);
|
|
@@ -27000,13 +29062,13 @@ function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
|
27000
29062
|
}
|
|
27001
29063
|
if (capturedOwnerId === ownerId) return;
|
|
27002
29064
|
try {
|
|
27003
|
-
(0,
|
|
27004
|
-
(0,
|
|
29065
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
29066
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27005
29067
|
} catch (error) {
|
|
27006
29068
|
if (error.code !== "EEXIST") throw error;
|
|
27007
29069
|
}
|
|
27008
29070
|
} finally {
|
|
27009
|
-
(0,
|
|
29071
|
+
(0, import_node_fs14.rmSync)(releasePath, { force: true });
|
|
27010
29072
|
}
|
|
27011
29073
|
}
|
|
27012
29074
|
function inspectLocalDaemonRunJournal(configPath) {
|
|
@@ -27016,8 +29078,8 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
27016
29078
|
try {
|
|
27017
29079
|
let activeRunCount = 0;
|
|
27018
29080
|
for (const artifact of [path, pendingPath]) {
|
|
27019
|
-
if (!(0,
|
|
27020
|
-
const parsed = JSON.parse((0,
|
|
29081
|
+
if (!(0, import_node_fs14.existsSync)(artifact)) continue;
|
|
29082
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(artifact, "utf8"));
|
|
27021
29083
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries))
|
|
27022
29084
|
throw new Error("invalid journal");
|
|
27023
29085
|
if (artifact === path) {
|
|
@@ -27036,7 +29098,7 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
27036
29098
|
}).length;
|
|
27037
29099
|
continue;
|
|
27038
29100
|
}
|
|
27039
|
-
const legacyStartedAtMs = (0,
|
|
29101
|
+
const legacyStartedAtMs = (0, import_node_fs14.statSync)(pendingPath).mtimeMs;
|
|
27040
29102
|
activeRunCount += parsed.entries.filter((entry) => {
|
|
27041
29103
|
if (!entry || typeof entry !== "object") throw new Error("invalid pending run entry");
|
|
27042
29104
|
const startedAtMs = entry.startedAtMs;
|
|
@@ -27058,38 +29120,38 @@ function pendingSetupIntentPath() {
|
|
|
27058
29120
|
return `${getConfigPath()}.setup-pending`;
|
|
27059
29121
|
}
|
|
27060
29122
|
function setupTokenHash(setupToken) {
|
|
27061
|
-
return (0,
|
|
29123
|
+
return (0, import_node_crypto5.createHash)("sha256").update(setupToken).digest("hex");
|
|
27062
29124
|
}
|
|
27063
29125
|
function getOrCreateSetupAttemptId(setupToken) {
|
|
27064
29126
|
const intentPath = pendingSetupIntentPath();
|
|
27065
29127
|
const tokenHash = setupTokenHash(setupToken);
|
|
27066
|
-
if ((0,
|
|
29128
|
+
if ((0, import_node_fs14.existsSync)(intentPath)) {
|
|
27067
29129
|
try {
|
|
27068
|
-
const stored = JSON.parse((0,
|
|
29130
|
+
const stored = JSON.parse((0, import_node_fs14.readFileSync)(intentPath, "utf8"));
|
|
27069
29131
|
if (stored.setupTokenHash === tokenHash && typeof stored.registrationAttemptId === "string" && stored.registrationAttemptId.length > 0) {
|
|
27070
29132
|
return stored.registrationAttemptId;
|
|
27071
29133
|
}
|
|
27072
29134
|
} catch {
|
|
27073
29135
|
}
|
|
27074
29136
|
}
|
|
27075
|
-
const registrationAttemptId = (0,
|
|
27076
|
-
(0,
|
|
27077
|
-
const writePath = `${intentPath}.pending-${process.pid}-${(0,
|
|
29137
|
+
const registrationAttemptId = (0, import_node_crypto5.randomUUID)();
|
|
29138
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(intentPath), { recursive: true, mode: 448 });
|
|
29139
|
+
const writePath = `${intentPath}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27078
29140
|
try {
|
|
27079
|
-
(0,
|
|
29141
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27080
29142
|
writePath,
|
|
27081
29143
|
JSON.stringify({ setupTokenHash: tokenHash, registrationAttemptId }, null, 2),
|
|
27082
29144
|
{ mode: 384 }
|
|
27083
29145
|
);
|
|
27084
|
-
(0,
|
|
27085
|
-
(0,
|
|
29146
|
+
(0, import_node_fs14.chmodSync)(writePath, 384);
|
|
29147
|
+
(0, import_node_fs14.renameSync)(writePath, intentPath);
|
|
27086
29148
|
} finally {
|
|
27087
|
-
(0,
|
|
29149
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27088
29150
|
}
|
|
27089
29151
|
return registrationAttemptId;
|
|
27090
29152
|
}
|
|
27091
29153
|
function clearPendingSetupIntent() {
|
|
27092
|
-
(0,
|
|
29154
|
+
(0, import_node_fs14.rmSync)(pendingSetupIntentPath(), { force: true });
|
|
27093
29155
|
}
|
|
27094
29156
|
function pendingDeviceAuthorizationPath() {
|
|
27095
29157
|
return `${getConfigPath()}.device-auth-pending`;
|
|
@@ -27110,35 +29172,35 @@ function parseDeviceAuthorizationOperationContent(content) {
|
|
|
27110
29172
|
}
|
|
27111
29173
|
function restoreCapturedFileUnlessReplaced(path, content) {
|
|
27112
29174
|
try {
|
|
27113
|
-
(0,
|
|
27114
|
-
(0,
|
|
29175
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
29176
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27115
29177
|
} catch (error) {
|
|
27116
29178
|
if (error.code !== "EEXIST") throw error;
|
|
27117
29179
|
}
|
|
27118
29180
|
}
|
|
27119
29181
|
function quarantineCorruptDeviceAuthorizationOperation(path) {
|
|
27120
|
-
const quarantinePath = `${path}.corrupt-${process.pid}-${(0,
|
|
29182
|
+
const quarantinePath = `${path}.corrupt-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27121
29183
|
try {
|
|
27122
|
-
(0,
|
|
29184
|
+
(0, import_node_fs14.renameSync)(path, quarantinePath);
|
|
27123
29185
|
} catch (error) {
|
|
27124
29186
|
if (error.code === "ENOENT") return null;
|
|
27125
29187
|
throw error;
|
|
27126
29188
|
}
|
|
27127
29189
|
try {
|
|
27128
|
-
const capturedContent = (0,
|
|
29190
|
+
const capturedContent = (0, import_node_fs14.readFileSync)(quarantinePath, "utf8");
|
|
27129
29191
|
if (parseDeviceAuthorizationOperationContent(capturedContent)) {
|
|
27130
29192
|
restoreCapturedFileUnlessReplaced(path, capturedContent);
|
|
27131
29193
|
}
|
|
27132
29194
|
} finally {
|
|
27133
|
-
(0,
|
|
29195
|
+
(0, import_node_fs14.rmSync)(quarantinePath, { force: true });
|
|
27134
29196
|
}
|
|
27135
|
-
if (!(0,
|
|
27136
|
-
return parseDeviceAuthorizationOperationContent((0,
|
|
29197
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
29198
|
+
return parseDeviceAuthorizationOperationContent((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27137
29199
|
}
|
|
27138
29200
|
function readDeviceAuthorizationOperation() {
|
|
27139
29201
|
const path = deviceAuthorizationOperationPath();
|
|
27140
|
-
if (!(0,
|
|
27141
|
-
const parsed = parseDeviceAuthorizationOperationContent((0,
|
|
29202
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
29203
|
+
const parsed = parseDeviceAuthorizationOperationContent((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27142
29204
|
const lease = parsed ?? quarantineCorruptDeviceAuthorizationOperation(path);
|
|
27143
29205
|
if (!lease) return null;
|
|
27144
29206
|
if (lease.expiresAtMs <= Date.now()) {
|
|
@@ -27152,14 +29214,14 @@ function acquireDeviceAuthorizationOperation() {
|
|
|
27152
29214
|
if (existing) return null;
|
|
27153
29215
|
const lease = {
|
|
27154
29216
|
version: 1,
|
|
27155
|
-
id: (0,
|
|
29217
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27156
29218
|
expiresAtMs: Date.now() + 6e4
|
|
27157
29219
|
};
|
|
27158
29220
|
const path = deviceAuthorizationOperationPath();
|
|
27159
|
-
(0,
|
|
29221
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
27160
29222
|
try {
|
|
27161
|
-
(0,
|
|
27162
|
-
(0,
|
|
29223
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
29224
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27163
29225
|
return lease;
|
|
27164
29226
|
} catch (error) {
|
|
27165
29227
|
if (error.code === "EEXIST") return null;
|
|
@@ -27172,17 +29234,17 @@ function extendDeviceAuthorizationOperation(operationId, expiresAtMs) {
|
|
|
27172
29234
|
throw new Error("browser authorization operation ownership was lost");
|
|
27173
29235
|
}
|
|
27174
29236
|
const path = deviceAuthorizationOperationPath();
|
|
27175
|
-
const pendingPath = `${path}.pending-${process.pid}-${(0,
|
|
29237
|
+
const pendingPath = `${path}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27176
29238
|
try {
|
|
27177
|
-
(0,
|
|
29239
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27178
29240
|
pendingPath,
|
|
27179
29241
|
JSON.stringify({ ...current, expiresAtMs: Math.max(expiresAtMs, Date.now() + 6e4) }),
|
|
27180
29242
|
{ mode: 384 }
|
|
27181
29243
|
);
|
|
27182
|
-
(0,
|
|
27183
|
-
(0,
|
|
29244
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
29245
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
27184
29246
|
} finally {
|
|
27185
|
-
(0,
|
|
29247
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27186
29248
|
}
|
|
27187
29249
|
}
|
|
27188
29250
|
function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
@@ -27193,12 +29255,12 @@ function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
|
27193
29255
|
function clearPendingDeviceAuthorization(operationId) {
|
|
27194
29256
|
const path = pendingDeviceAuthorizationPath();
|
|
27195
29257
|
if (!operationId) {
|
|
27196
|
-
(0,
|
|
29258
|
+
(0, import_node_fs14.rmSync)(path, { force: true });
|
|
27197
29259
|
return;
|
|
27198
29260
|
}
|
|
27199
|
-
if (!(0,
|
|
29261
|
+
if (!(0, import_node_fs14.existsSync)(path)) return;
|
|
27200
29262
|
try {
|
|
27201
|
-
const pending = JSON.parse((0,
|
|
29263
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27202
29264
|
if (pending.operationId !== operationId) return;
|
|
27203
29265
|
} catch {
|
|
27204
29266
|
return;
|
|
@@ -27207,9 +29269,9 @@ function clearPendingDeviceAuthorization(operationId) {
|
|
|
27207
29269
|
}
|
|
27208
29270
|
function readPendingDeviceAuthorization(apiUrl) {
|
|
27209
29271
|
const path = pendingDeviceAuthorizationPath();
|
|
27210
|
-
if (!(0,
|
|
29272
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
27211
29273
|
try {
|
|
27212
|
-
const pending = JSON.parse((0,
|
|
29274
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27213
29275
|
const expiresAtMs = Date.parse(pending.expiresAt ?? "");
|
|
27214
29276
|
if (pending.apiUrl !== apiUrl || typeof pending.deviceCode !== "string" || typeof pending.userCode !== "string" || typeof pending.verificationUri !== "string" || typeof pending.installationId !== "string" || pending.operationId !== void 0 && typeof pending.operationId !== "string" || !Number.isFinite(pending.intervalSeconds) || !Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
|
|
27215
29277
|
return null;
|
|
@@ -27221,20 +29283,20 @@ function readPendingDeviceAuthorization(apiUrl) {
|
|
|
27221
29283
|
}
|
|
27222
29284
|
function writePendingDeviceAuthorization(pending) {
|
|
27223
29285
|
const path = pendingDeviceAuthorizationPath();
|
|
27224
|
-
(0,
|
|
27225
|
-
const writePath = `${path}.pending-${process.pid}-${(0,
|
|
29286
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
29287
|
+
const writePath = `${path}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27226
29288
|
try {
|
|
27227
|
-
(0,
|
|
27228
|
-
(0,
|
|
27229
|
-
(0,
|
|
29289
|
+
(0, import_node_fs14.writeFileSync)(writePath, JSON.stringify(pending, null, 2), { mode: 384 });
|
|
29290
|
+
(0, import_node_fs14.chmodSync)(writePath, 384);
|
|
29291
|
+
(0, import_node_fs14.renameSync)(writePath, path);
|
|
27230
29292
|
} finally {
|
|
27231
|
-
(0,
|
|
29293
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27232
29294
|
}
|
|
27233
29295
|
}
|
|
27234
29296
|
function removeAlanOwnedLocalArtifacts() {
|
|
27235
29297
|
const configPath = getConfigPath();
|
|
27236
|
-
const directory = (0,
|
|
27237
|
-
const configName = (0,
|
|
29298
|
+
const directory = (0, import_node_path12.dirname)(configPath);
|
|
29299
|
+
const configName = (0, import_node_path12.basename)(configPath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27238
29300
|
const alanAtomicSidecarPattern = new RegExp(
|
|
27239
29301
|
`^${configName}(?:|\\.setup-pending|\\.device-auth-pending|\\.device-auth-operation\\.json|\\.maintenance-lease\\.json|\\.event-outbox\\.enc)\\.(?:pending|release|corrupt)-\\d+-[a-f0-9]{12}$`
|
|
27240
29302
|
);
|
|
@@ -27251,13 +29313,13 @@ function removeAlanOwnedLocalArtifacts() {
|
|
|
27251
29313
|
`${configPath}.pending-runs.json.pending`,
|
|
27252
29314
|
maintenanceLeasePath(configPath)
|
|
27253
29315
|
]);
|
|
27254
|
-
if ((0,
|
|
27255
|
-
for (const entry of (0,
|
|
27256
|
-
if (alanAtomicSidecarPattern.test(entry)) artifacts.add((0,
|
|
29316
|
+
if ((0, import_node_fs14.existsSync)(directory)) {
|
|
29317
|
+
for (const entry of (0, import_node_fs14.readdirSync)(directory)) {
|
|
29318
|
+
if (alanAtomicSidecarPattern.test(entry)) artifacts.add((0, import_node_path12.join)(directory, entry));
|
|
27257
29319
|
}
|
|
27258
29320
|
}
|
|
27259
|
-
for (const artifact of artifacts) (0,
|
|
27260
|
-
(0,
|
|
29321
|
+
for (const artifact of artifacts) (0, import_node_fs14.rmSync)(artifact, { force: true });
|
|
29322
|
+
(0, import_node_fs14.rmSync)(configPath, { force: true });
|
|
27261
29323
|
}
|
|
27262
29324
|
function argValue(args, name) {
|
|
27263
29325
|
const index = args.indexOf(name);
|
|
@@ -27313,7 +29375,7 @@ function getLocalApiPort(args, stored) {
|
|
|
27313
29375
|
}
|
|
27314
29376
|
function sleep(ms) {
|
|
27315
29377
|
if (ms <= 0) return Promise.resolve();
|
|
27316
|
-
return new Promise((
|
|
29378
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
27317
29379
|
}
|
|
27318
29380
|
function normalizeBackendKind2(backendKind) {
|
|
27319
29381
|
if (backendKind === "codex") return "codex_app_server";
|
|
@@ -27479,13 +29541,13 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27479
29541
|
}
|
|
27480
29542
|
persist() {
|
|
27481
29543
|
if (this.size === 0) {
|
|
27482
|
-
(0,
|
|
29544
|
+
(0, import_node_fs14.rmSync)(this.path, { force: true });
|
|
27483
29545
|
return;
|
|
27484
29546
|
}
|
|
27485
29547
|
const pendingPath = `${this.path}.pending`;
|
|
27486
|
-
(0,
|
|
29548
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(this.path), { recursive: true, mode: 448 });
|
|
27487
29549
|
try {
|
|
27488
|
-
(0,
|
|
29550
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27489
29551
|
pendingPath,
|
|
27490
29552
|
JSON.stringify({
|
|
27491
29553
|
version: 1,
|
|
@@ -27498,10 +29560,10 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27498
29560
|
}),
|
|
27499
29561
|
{ mode: 384 }
|
|
27500
29562
|
);
|
|
27501
|
-
(0,
|
|
27502
|
-
(0,
|
|
29563
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
29564
|
+
(0, import_node_fs14.renameSync)(pendingPath, this.path);
|
|
27503
29565
|
} finally {
|
|
27504
|
-
(0,
|
|
29566
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27505
29567
|
}
|
|
27506
29568
|
}
|
|
27507
29569
|
};
|
|
@@ -27641,7 +29703,7 @@ function buildApplicationRunFailure(input) {
|
|
|
27641
29703
|
const problem = createAlanProblem({
|
|
27642
29704
|
domain: "application",
|
|
27643
29705
|
code: input.code,
|
|
27644
|
-
id: (0,
|
|
29706
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27645
29707
|
title: "Alan could not complete the agent run",
|
|
27646
29708
|
detail: input.detail,
|
|
27647
29709
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27736,21 +29798,21 @@ function reconcileActiveRuns(input) {
|
|
|
27736
29798
|
}
|
|
27737
29799
|
}
|
|
27738
29800
|
function collectRuntimeMetadata() {
|
|
27739
|
-
const cpuList = (0,
|
|
29801
|
+
const cpuList = (0, import_node_os9.cpus)();
|
|
27740
29802
|
const metadata = {
|
|
27741
|
-
platform: (0,
|
|
27742
|
-
arch: (0,
|
|
27743
|
-
osVersion: (0,
|
|
29803
|
+
platform: (0, import_node_os9.platform)(),
|
|
29804
|
+
arch: (0, import_node_os9.arch)(),
|
|
29805
|
+
osVersion: (0, import_node_os9.release)(),
|
|
27744
29806
|
agentVersion: AGENT_VERSION,
|
|
27745
|
-
memoryBytes: (0,
|
|
27746
|
-
memoryFreeBytes: (0,
|
|
29807
|
+
memoryBytes: (0, import_node_os9.totalmem)(),
|
|
29808
|
+
memoryFreeBytes: (0, import_node_os9.freemem)()
|
|
27747
29809
|
};
|
|
27748
29810
|
if (cpuList.length > 0) {
|
|
27749
29811
|
metadata.cpuModel = cpuList[0]?.model;
|
|
27750
29812
|
metadata.cpuCores = cpuList.length;
|
|
27751
29813
|
}
|
|
27752
29814
|
try {
|
|
27753
|
-
const disk = (0,
|
|
29815
|
+
const disk = (0, import_node_fs14.statfsSync)((0, import_node_os9.homedir)());
|
|
27754
29816
|
metadata.diskFreeBytes = disk.bavail * disk.bsize;
|
|
27755
29817
|
metadata.diskTotalBytes = disk.blocks * disk.bsize;
|
|
27756
29818
|
} catch {
|
|
@@ -27968,7 +30030,7 @@ var RuntimePresenter = class {
|
|
|
27968
30030
|
this.applicationProblem = createAlanProblem({
|
|
27969
30031
|
domain: "application",
|
|
27970
30032
|
code: /local path no longer exists/i.test(message) ? "app.workspace.missing" : "app.internal",
|
|
27971
|
-
id: (0,
|
|
30033
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27972
30034
|
title: "Alan could not start the agent run",
|
|
27973
30035
|
detail: message,
|
|
27974
30036
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28006,9 +30068,9 @@ async function setupDaemon(args) {
|
|
|
28006
30068
|
const teamId = argValue(args, "--team") ?? process.env.ALAN_TEAM_ID;
|
|
28007
30069
|
const setupToken = argValue(args, "--setup-token") ?? process.env.ALAN_RUNTIME_SETUP_TOKEN;
|
|
28008
30070
|
const token = argValue(args, "--token") ?? process.env.ALAN_SETUP_TOKEN;
|
|
28009
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
30071
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
28010
30072
|
const scope = argValue(args, "--scope") ?? process.env.ALAN_RUNTIME_SCOPE ?? (setupToken ? "team" : "user");
|
|
28011
|
-
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0,
|
|
30073
|
+
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28012
30074
|
if (scope !== "team" && scope !== "user") {
|
|
28013
30075
|
throw new Error("setup --scope must be either 'team' or 'user'");
|
|
28014
30076
|
}
|
|
@@ -28017,7 +30079,7 @@ async function setupDaemon(args) {
|
|
|
28017
30079
|
"setup requires --setup-token <token> or --token <Alan access token>.\nFor normal setup, copy the setup token from Alan and run: alan-agent setup --setup-token <token>\nFor browser authorization, run: alan-agent login"
|
|
28018
30080
|
);
|
|
28019
30081
|
}
|
|
28020
|
-
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0,
|
|
30082
|
+
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0, import_node_crypto5.randomUUID)();
|
|
28021
30083
|
const registrationUrl = `${apiUrl}/public/runtimes${setupToken ? "/register-with-setup-token" : ""}`;
|
|
28022
30084
|
const registrationInit = {
|
|
28023
30085
|
method: "POST",
|
|
@@ -28029,7 +30091,7 @@ async function setupDaemon(args) {
|
|
|
28029
30091
|
...teamId ? { teamId } : {},
|
|
28030
30092
|
...setupToken ? { setupToken, registrationAttemptId } : {},
|
|
28031
30093
|
displayName,
|
|
28032
|
-
hostname: (0,
|
|
30094
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
28033
30095
|
runtimeKind: "machine",
|
|
28034
30096
|
managementKind: "user_managed",
|
|
28035
30097
|
hostKind: "daemon",
|
|
@@ -28052,7 +30114,7 @@ async function setupDaemon(args) {
|
|
|
28052
30114
|
}
|
|
28053
30115
|
const retriableStatus = response && (response.status === 408 || response.status === 429 || response.status >= 500);
|
|
28054
30116
|
if (response && (!retriableStatus || response.ok) || attempt === 3) break;
|
|
28055
|
-
await new Promise((
|
|
30117
|
+
await new Promise((resolve7) => setTimeout(resolve7, attempt * 100));
|
|
28056
30118
|
}
|
|
28057
30119
|
if (!response) {
|
|
28058
30120
|
const detail = transportError instanceof Error ? transportError.message : String(transportError);
|
|
@@ -28083,10 +30145,10 @@ async function setupDaemon(args) {
|
|
|
28083
30145
|
runtimeToken: body.runtimeToken,
|
|
28084
30146
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28085
30147
|
localApiPort: getLocalApiPort(args, {}),
|
|
28086
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28087
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28088
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28089
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0,
|
|
30148
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex"),
|
|
30149
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto5.randomUUID)(),
|
|
30150
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
30151
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
28090
30152
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28091
30153
|
displayName: body.runtime.displayName ?? displayName,
|
|
28092
30154
|
installationId
|
|
@@ -28113,10 +30175,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28113
30175
|
}
|
|
28114
30176
|
const previousConfig = readConfigForRegistration();
|
|
28115
30177
|
const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
|
|
28116
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
30178
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
28117
30179
|
const maxPolls = Number.parseInt(argValue(args, "--max-polls") ?? "300", 10);
|
|
28118
30180
|
const resumed = readPendingDeviceAuthorization(apiUrl);
|
|
28119
|
-
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0,
|
|
30181
|
+
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28120
30182
|
let authorization = resumed ? { ...resumed, operationId: operation.id } : null;
|
|
28121
30183
|
if (!authorization) {
|
|
28122
30184
|
clearPendingDeviceAuthorization();
|
|
@@ -28125,7 +30187,7 @@ async function loginWithDeviceCode(args) {
|
|
|
28125
30187
|
headers: { "content-type": "application/json" },
|
|
28126
30188
|
body: JSON.stringify({
|
|
28127
30189
|
displayName,
|
|
28128
|
-
hostname: (0,
|
|
30190
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
28129
30191
|
capabilities: discoverCapabilities(),
|
|
28130
30192
|
metadata: {
|
|
28131
30193
|
...await collectRuntimeMetadataWithProviderLimits(),
|
|
@@ -28192,10 +30254,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28192
30254
|
runtimeToken: body.runtimeToken,
|
|
28193
30255
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28194
30256
|
localApiPort: getLocalApiPort(args, {}),
|
|
28195
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28196
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28197
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28198
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0,
|
|
30257
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex"),
|
|
30258
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto5.randomUUID)(),
|
|
30259
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
30260
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
28199
30261
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28200
30262
|
displayName: body.runtime.displayName ?? displayName,
|
|
28201
30263
|
installationId
|
|
@@ -28215,10 +30277,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28215
30277
|
async function startDaemon(args) {
|
|
28216
30278
|
bindConfigPath(args);
|
|
28217
30279
|
const stored = readConfig();
|
|
28218
|
-
const installationId = stored.installationId ?? (0,
|
|
30280
|
+
const installationId = stored.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28219
30281
|
const configPath = getConfigPath();
|
|
28220
30282
|
const endpointProfile = stored.endpointProfile ?? resolveEndpointProfile(args);
|
|
28221
|
-
const daemonSessionId = (0,
|
|
30283
|
+
const daemonSessionId = (0, import_node_crypto5.randomUUID)();
|
|
28222
30284
|
let heartbeatSeq = 0;
|
|
28223
30285
|
const runtimeId = argValue(args, "--runtime-id") ?? process.env.ALAN_RUNTIME_ID ?? stored.runtimeId;
|
|
28224
30286
|
const runtimeTokenOverride = argValue(args, "--token") ?? process.env.ALAN_RUNTIME_TOKEN;
|
|
@@ -28235,11 +30297,11 @@ async function startDaemon(args) {
|
|
|
28235
30297
|
wsUrl = endpointMismatch.expectedWsUrl;
|
|
28236
30298
|
}
|
|
28237
30299
|
let localApiPort = getLocalApiPort(args, stored);
|
|
28238
|
-
const localApiToken = stored.localApiToken ?? (0,
|
|
28239
|
-
const localServiceId = stored.localServiceId ?? (0,
|
|
28240
|
-
const localServiceSecret = stored.localServiceSecret ?? (0,
|
|
30300
|
+
const localApiToken = stored.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex");
|
|
30301
|
+
const localServiceId = stored.localServiceId ?? (0, import_node_crypto5.randomUUID)();
|
|
30302
|
+
const localServiceSecret = stored.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
28241
30303
|
const localServiceStartedAtMs = Date.now();
|
|
28242
|
-
const eventSpoolKey = stored.eventSpoolKey ?? (0,
|
|
30304
|
+
const eventSpoolKey = stored.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
28243
30305
|
if (stored.daemonEpoch !== void 0 && (!Number.isSafeInteger(stored.daemonEpoch) || stored.daemonEpoch < 0 || stored.daemonEpoch >= Number.MAX_SAFE_INTEGER)) {
|
|
28244
30306
|
throw new Error("daemon config contains an invalid durable epoch; repair the runtime config");
|
|
28245
30307
|
}
|
|
@@ -28294,7 +30356,7 @@ async function startDaemon(args) {
|
|
|
28294
30356
|
ownerLeaseHeld = false;
|
|
28295
30357
|
releaseLocalDaemonOwnerLease(configPath, ownerLease.id);
|
|
28296
30358
|
};
|
|
28297
|
-
const activeAgents =
|
|
30359
|
+
const activeAgents = new ObservableMap();
|
|
28298
30360
|
const pendingRunStarts = new DurablePendingRunStarts(
|
|
28299
30361
|
`${configPath}.pending-runs.json`,
|
|
28300
30362
|
daemonSessionId
|
|
@@ -28315,6 +30377,13 @@ async function startDaemon(args) {
|
|
|
28315
30377
|
recentLogs.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
|
|
28316
30378
|
if (recentLogs.length > 200) recentLogs.splice(0, recentLogs.length - 200);
|
|
28317
30379
|
};
|
|
30380
|
+
const sleepInhibitor = new SleepInhibitor({ log: pushLog });
|
|
30381
|
+
activeAgents.onChange = () => {
|
|
30382
|
+
sleepInhibitor.setActive(activeAgents.size > 0);
|
|
30383
|
+
};
|
|
30384
|
+
if (sleepInhibitor.backendNames().length > 0) {
|
|
30385
|
+
pushLog(`sleep-inhibitor backends=${sleepInhibitor.backendNames().join(",")}`);
|
|
30386
|
+
}
|
|
28318
30387
|
const alanMcpRegisteredRuns = /* @__PURE__ */ new Set();
|
|
28319
30388
|
const alanMcpRegisteredBackends = /* @__PURE__ */ new Set();
|
|
28320
30389
|
const finalizeAlanMcpForRun = (runId) => {
|
|
@@ -28359,7 +30428,7 @@ async function startDaemon(args) {
|
|
|
28359
30428
|
const workspaceRelocations = /* @__PURE__ */ new Map();
|
|
28360
30429
|
for (const relocation of stored.workspaceRelocations ?? []) {
|
|
28361
30430
|
if (relocation?.conversationId && relocation.runId && relocation.previousPath && relocation.canonicalPath) {
|
|
28362
|
-
workspaceRelocations.set((0,
|
|
30431
|
+
workspaceRelocations.set((0, import_node_path12.resolve)(relocation.previousPath), relocation);
|
|
28363
30432
|
}
|
|
28364
30433
|
}
|
|
28365
30434
|
if (stored.localApiPort !== localApiPort || stored.localApiToken !== localApiToken || stored.localServiceId !== localServiceId || stored.localServiceSecret !== localServiceSecret || stored.eventSpoolKey !== eventSpoolKey || stored.daemonEpoch !== daemonEpoch || stored.installationId !== installationId) {
|
|
@@ -28543,6 +30612,22 @@ async function startDaemon(args) {
|
|
|
28543
30612
|
emitRunnerPresence();
|
|
28544
30613
|
flushWorkspaceRelocations();
|
|
28545
30614
|
}, 15e3);
|
|
30615
|
+
let pendingWakeGapMs = null;
|
|
30616
|
+
const flushWakeGap = () => {
|
|
30617
|
+
if (pendingWakeGapMs === null || !socket.connected) return;
|
|
30618
|
+
const gapMs = pendingWakeGapMs;
|
|
30619
|
+
pendingWakeGapMs = null;
|
|
30620
|
+
socket.emit("runtime.wake", { gapMs });
|
|
30621
|
+
pushLog(`runtime-wake reported gapMs=${gapMs}`);
|
|
30622
|
+
};
|
|
30623
|
+
const suspensionDetector = new SuspensionDetector({
|
|
30624
|
+
onSuspensionDetected: (gapMs) => {
|
|
30625
|
+
pendingWakeGapMs = Math.max(pendingWakeGapMs ?? 0, gapMs);
|
|
30626
|
+
pushLog(`host-suspension detected gapMs=${gapMs}`);
|
|
30627
|
+
flushWakeGap();
|
|
30628
|
+
}
|
|
30629
|
+
});
|
|
30630
|
+
suspensionDetector.start();
|
|
28546
30631
|
void refreshProviderVersions();
|
|
28547
30632
|
const versionRefresh = setInterval(() => {
|
|
28548
30633
|
void refreshProviderVersions();
|
|
@@ -28634,6 +30719,7 @@ async function startDaemon(args) {
|
|
|
28634
30719
|
runnerStatus: runnerSummary.runnerStatus
|
|
28635
30720
|
}
|
|
28636
30721
|
});
|
|
30722
|
+
flushWakeGap();
|
|
28637
30723
|
scheduleRuntimeModelScan(() => Promise.resolve(metadata));
|
|
28638
30724
|
});
|
|
28639
30725
|
});
|
|
@@ -28675,20 +30761,25 @@ async function startDaemon(args) {
|
|
|
28675
30761
|
pendingDeliveryLost.push({ conversationId: entry.conversationId, runId });
|
|
28676
30762
|
}
|
|
28677
30763
|
});
|
|
28678
|
-
socket.on("desktop:rpc", (message) => {
|
|
30764
|
+
socket.on("desktop:rpc", async (message) => {
|
|
28679
30765
|
if (!message?.id || !message.method) return;
|
|
28680
30766
|
try {
|
|
28681
30767
|
const params = message.params ?? {};
|
|
28682
|
-
|
|
30768
|
+
const result = message.method === "worktree.ensure" ? ensureWorktree(params) : message.method === "worktree.remove_if_clean" ? removeWorktreesIfClean(params) : message.method === "worktree.migrate_to_checkout" ? migrateWorktreeToCheckout(params) : message.method === "worktree.migration_preflight" ? preflightLocalWorkspaceMigration(params) : message.method === "worktree.status" ? statusLocalWorkspace(params) : isBindingSwitchGitMethod(message.method) ? await executeBindingSwitchGitRpc(message.method, params) : (() => {
|
|
28683
30769
|
throw new Error(`Method not found: ${message.method}`);
|
|
28684
|
-
}
|
|
28685
|
-
const result = ensureWorktree(params);
|
|
30770
|
+
})();
|
|
28686
30771
|
socket.emit("desktop:rpc", { jsonrpc: "2.0", id: message.id, result });
|
|
28687
30772
|
} catch (error) {
|
|
30773
|
+
const stableCode = error instanceof BindingSwitchGitError ? error.code : "runtime_operation_failed";
|
|
30774
|
+
const stableDetails = error instanceof BindingSwitchGitError ? error.details : void 0;
|
|
28688
30775
|
socket.emit("desktop:rpc", {
|
|
28689
30776
|
jsonrpc: "2.0",
|
|
28690
30777
|
id: message.id,
|
|
28691
|
-
error: {
|
|
30778
|
+
error: {
|
|
30779
|
+
code: -32e3,
|
|
30780
|
+
message: error.message,
|
|
30781
|
+
data: { ...stableDetails, code: stableCode }
|
|
30782
|
+
}
|
|
28692
30783
|
});
|
|
28693
30784
|
}
|
|
28694
30785
|
});
|
|
@@ -28696,6 +30787,17 @@ async function startDaemon(args) {
|
|
|
28696
30787
|
if (!payload?.runId || !payload.conversationId || typeof payload.content !== "string") {
|
|
28697
30788
|
return;
|
|
28698
30789
|
}
|
|
30790
|
+
const isTaskWorkspacePayload = Boolean(
|
|
30791
|
+
payload.worktreeInfo?.worktreePath || payload.worktreeInfo?.allWorktrees?.length
|
|
30792
|
+
);
|
|
30793
|
+
if ((payload.worktreeInfo?.allWorktrees?.length ?? 0) > 1 || isTaskWorkspacePayload && (payload.repoLocalPaths?.length ?? 0) > 1) {
|
|
30794
|
+
socket.emit("agent.rejected", {
|
|
30795
|
+
runId: payload.runId,
|
|
30796
|
+
message: "A task worktree requires exactly one Git repository.",
|
|
30797
|
+
code: "single_repository_worktree_required"
|
|
30798
|
+
});
|
|
30799
|
+
return;
|
|
30800
|
+
}
|
|
28699
30801
|
const existingRun = activeAgents.get(payload.runId);
|
|
28700
30802
|
if (existingRun) {
|
|
28701
30803
|
if (existingRun.conversationId !== payload.conversationId) {
|
|
@@ -28788,31 +30890,31 @@ async function startDaemon(args) {
|
|
|
28788
30890
|
const workspaceRequired = Boolean(explicitWorkspacePath);
|
|
28789
30891
|
if (isAlanDefaultWorkspace(payload.projectPath)) {
|
|
28790
30892
|
try {
|
|
28791
|
-
(0,
|
|
30893
|
+
(0, import_node_fs14.mkdirSync)(cwd, { recursive: true });
|
|
28792
30894
|
} catch {
|
|
28793
30895
|
}
|
|
28794
30896
|
}
|
|
28795
30897
|
let workspaceReadiness = inspectWorkspaceReadiness(workspaceRequired ? cwd : void 0);
|
|
28796
30898
|
const expectedRepoUrls = [
|
|
28797
30899
|
...(payload.repoLocalPaths ?? []).filter(
|
|
28798
|
-
(repo) => explicitWorkspacePath ? (0,
|
|
30900
|
+
(repo) => explicitWorkspacePath ? (0, import_node_path12.resolve)(repo.localPath) === (0, import_node_path12.resolve)(explicitWorkspacePath) : false
|
|
28799
30901
|
).map((repo) => repo.repoUrl),
|
|
28800
30902
|
...payload.localRepoUrls ?? []
|
|
28801
30903
|
];
|
|
28802
30904
|
if (workspaceReadiness.code === "workspace_missing" && explicitWorkspacePath && !payload.worktreeInfo) {
|
|
28803
|
-
const previousPath = (0,
|
|
30905
|
+
const previousPath = (0, import_node_path12.resolve)(explicitWorkspacePath);
|
|
28804
30906
|
const remembered = workspaceRelocations.get(previousPath);
|
|
28805
30907
|
const rememberedPath = remembered?.canonicalPath;
|
|
28806
30908
|
const relocated = rememberedPath && workspaceMatchesExpectedRepositories({
|
|
28807
30909
|
workspacePath: rememberedPath,
|
|
28808
30910
|
expectedRepoUrls
|
|
28809
|
-
}) ? (0,
|
|
30911
|
+
}) ? (0, import_node_path12.resolve)(rememberedPath) : discoverRelocatedWorkspace({
|
|
28810
30912
|
missingPath: explicitWorkspacePath,
|
|
28811
30913
|
approvedWorkspacePaths: payload.approvedWorkspacePaths,
|
|
28812
30914
|
expectedRepoUrls
|
|
28813
30915
|
});
|
|
28814
30916
|
if (relocated) {
|
|
28815
|
-
if (!remembered || (0,
|
|
30917
|
+
if (!remembered || (0, import_node_path12.resolve)(remembered.canonicalPath) !== relocated) {
|
|
28816
30918
|
workspaceRelocations.set(previousPath, {
|
|
28817
30919
|
conversationId: payload.conversationId,
|
|
28818
30920
|
runId: payload.runId,
|
|
@@ -29280,7 +31382,7 @@ async function startDaemon(args) {
|
|
|
29280
31382
|
const requestId = payload.requestId;
|
|
29281
31383
|
try {
|
|
29282
31384
|
const projectPath = isAlanDefaultWorkspace(payload.projectPath) ? resolveAlanDefaultWorkspacePath() : payload.projectPath;
|
|
29283
|
-
const result = scanLocalSkillsCached((0,
|
|
31385
|
+
const result = scanLocalSkillsCached((0, import_node_os9.homedir)(), projectPath, {
|
|
29284
31386
|
refresh: payload.refresh === true
|
|
29285
31387
|
});
|
|
29286
31388
|
socket.emit("runtime.skills.result", {
|
|
@@ -29308,7 +31410,7 @@ async function startDaemon(args) {
|
|
|
29308
31410
|
if (!oldest) break;
|
|
29309
31411
|
adoptionChallenges.delete(oldest);
|
|
29310
31412
|
}
|
|
29311
|
-
const challenge = (0,
|
|
31413
|
+
const challenge = (0, import_node_crypto5.randomBytes)(24).toString("base64url");
|
|
29312
31414
|
adoptionChallenges.set(challenge, now + LOCAL_SERVICE_CHALLENGE_TTL_MS);
|
|
29313
31415
|
const unsignedOwner = {
|
|
29314
31416
|
version: LOCAL_SERVICE_OWNER_VERSION,
|
|
@@ -29493,6 +31595,7 @@ async function startDaemon(args) {
|
|
|
29493
31595
|
clearInterval(heartbeat);
|
|
29494
31596
|
clearInterval(versionRefresh);
|
|
29495
31597
|
clearInterval(workspaceLeaseMonitor);
|
|
31598
|
+
suspensionDetector.stop();
|
|
29496
31599
|
socket.disconnect();
|
|
29497
31600
|
releaseOwnerLease();
|
|
29498
31601
|
releaseStartupLease();
|
|
@@ -29504,6 +31607,7 @@ async function startDaemon(args) {
|
|
|
29504
31607
|
clearInterval(heartbeat);
|
|
29505
31608
|
clearInterval(versionRefresh);
|
|
29506
31609
|
clearInterval(workspaceLeaseMonitor);
|
|
31610
|
+
suspensionDetector.stop();
|
|
29507
31611
|
socket.disconnect();
|
|
29508
31612
|
releaseOwnerLease();
|
|
29509
31613
|
releaseStartupLease();
|
|
@@ -29511,11 +31615,12 @@ async function startDaemon(args) {
|
|
|
29511
31615
|
`${describeOccupiedLocalDaemon(localApiPort)} The token in ${getConfigPath()} no longer matches the running daemon; quit Alan desktop or run setup again.`
|
|
29512
31616
|
);
|
|
29513
31617
|
}
|
|
29514
|
-
const localApiListening = new Promise((
|
|
31618
|
+
const localApiListening = new Promise((resolve7, reject) => {
|
|
29515
31619
|
const onError = (error) => {
|
|
29516
31620
|
clearInterval(heartbeat);
|
|
29517
31621
|
clearInterval(versionRefresh);
|
|
29518
31622
|
clearInterval(workspaceLeaseMonitor);
|
|
31623
|
+
suspensionDetector.stop();
|
|
29519
31624
|
socket.disconnect();
|
|
29520
31625
|
if (error.code === "EADDRINUSE") {
|
|
29521
31626
|
void probeLocalDaemonEndpoint(localApiPort).then((probe) => {
|
|
@@ -29540,7 +31645,7 @@ async function startDaemon(args) {
|
|
|
29540
31645
|
if (address && typeof address === "object") localApiPort = address.port;
|
|
29541
31646
|
pushLog(`local_api listening port=${localApiPort}`);
|
|
29542
31647
|
console.info("[alan-agent] Local daemon API listening", { port: localApiPort });
|
|
29543
|
-
|
|
31648
|
+
resolve7();
|
|
29544
31649
|
});
|
|
29545
31650
|
});
|
|
29546
31651
|
try {
|
|
@@ -29558,8 +31663,9 @@ async function startDaemon(args) {
|
|
|
29558
31663
|
clearInterval(heartbeat);
|
|
29559
31664
|
clearInterval(versionRefresh);
|
|
29560
31665
|
clearInterval(workspaceLeaseMonitor);
|
|
31666
|
+
suspensionDetector.stop();
|
|
29561
31667
|
socket.disconnect();
|
|
29562
|
-
await new Promise((
|
|
31668
|
+
await new Promise((resolve7) => localServer.close(() => resolve7()));
|
|
29563
31669
|
releaseOwnerLease();
|
|
29564
31670
|
releaseStartupLease();
|
|
29565
31671
|
throw error;
|
|
@@ -29573,15 +31679,17 @@ async function startDaemon(args) {
|
|
|
29573
31679
|
clearInterval(heartbeat);
|
|
29574
31680
|
clearInterval(versionRefresh);
|
|
29575
31681
|
clearInterval(workspaceLeaseMonitor);
|
|
31682
|
+
suspensionDetector.stop();
|
|
29576
31683
|
for (const entry of activeAgents.values()) {
|
|
29577
31684
|
abortActiveAgent(entry, { userInitiated: true });
|
|
29578
31685
|
}
|
|
29579
31686
|
activeAgents.clear();
|
|
29580
31687
|
socket.disconnect();
|
|
29581
|
-
await new Promise((
|
|
29582
|
-
localServer.close(() =>
|
|
31688
|
+
await new Promise((resolve7) => {
|
|
31689
|
+
localServer.close(() => resolve7());
|
|
29583
31690
|
});
|
|
29584
31691
|
} finally {
|
|
31692
|
+
sleepInhibitor.dispose();
|
|
29585
31693
|
releaseOwnerLease();
|
|
29586
31694
|
}
|
|
29587
31695
|
};
|
|
@@ -30016,7 +32124,7 @@ function decodeResumeFallbackContext(encoded) {
|
|
|
30016
32124
|
}
|
|
30017
32125
|
|
|
30018
32126
|
// src/web-presenter.ts
|
|
30019
|
-
var
|
|
32127
|
+
var import_node_crypto6 = require("crypto");
|
|
30020
32128
|
var WebPresenter = class {
|
|
30021
32129
|
constructor(wsClient, conversationId, cwd) {
|
|
30022
32130
|
this.conversationId = conversationId;
|
|
@@ -30193,7 +32301,7 @@ var WebPresenter = class {
|
|
|
30193
32301
|
this.applicationProblem = createAlanProblem({
|
|
30194
32302
|
domain: "application",
|
|
30195
32303
|
code: /required tools|prepare the required tools/i.test(message) ? "app.service.unavailable" : "app.internal",
|
|
30196
|
-
id: (0,
|
|
32304
|
+
id: (0, import_node_crypto6.randomUUID)(),
|
|
30197
32305
|
title: "Alan could not continue the agent run",
|
|
30198
32306
|
detail: message,
|
|
30199
32307
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30260,7 +32368,7 @@ var WebPresenter = class {
|
|
|
30260
32368
|
};
|
|
30261
32369
|
|
|
30262
32370
|
// src/ws-client.ts
|
|
30263
|
-
var
|
|
32371
|
+
var import_node_crypto8 = require("crypto");
|
|
30264
32372
|
|
|
30265
32373
|
// ../shared/dist/agent-liveness.js
|
|
30266
32374
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -30288,8 +32396,8 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
30288
32396
|
});
|
|
30289
32397
|
|
|
30290
32398
|
// src/sandbox-outbox.ts
|
|
30291
|
-
var
|
|
30292
|
-
var
|
|
32399
|
+
var import_node_crypto7 = require("crypto");
|
|
32400
|
+
var import_node_fs15 = require("fs");
|
|
30293
32401
|
var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
|
|
30294
32402
|
function isSandboxEventOutboxEnabled(env = process.env) {
|
|
30295
32403
|
return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
|
|
@@ -30298,7 +32406,7 @@ function deriveSandboxOutboxKey(sessionToken) {
|
|
|
30298
32406
|
if (!sessionToken) {
|
|
30299
32407
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
30300
32408
|
}
|
|
30301
|
-
return (0,
|
|
32409
|
+
return (0, import_node_crypto7.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
30302
32410
|
}
|
|
30303
32411
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
30304
32412
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -30312,7 +32420,7 @@ function createSandboxEventOutbox(input) {
|
|
|
30312
32420
|
input.pushLog?.(
|
|
30313
32421
|
`sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
|
|
30314
32422
|
);
|
|
30315
|
-
(0,
|
|
32423
|
+
(0, import_node_fs15.rmSync)(path, { force: true });
|
|
30316
32424
|
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
30317
32425
|
}
|
|
30318
32426
|
}
|
|
@@ -30435,7 +32543,7 @@ var WSClient = class {
|
|
|
30435
32543
|
}
|
|
30436
32544
|
socket;
|
|
30437
32545
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
30438
|
-
agentSessionId = (0,
|
|
32546
|
+
agentSessionId = (0, import_node_crypto8.randomUUID)();
|
|
30439
32547
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
30440
32548
|
heartbeatSeq = 0;
|
|
30441
32549
|
heartbeatTimer = null;
|
|
@@ -30515,9 +32623,9 @@ var WSClient = class {
|
|
|
30515
32623
|
});
|
|
30516
32624
|
}
|
|
30517
32625
|
waitForConnection(timeoutMs = 15e3) {
|
|
30518
|
-
return new Promise((
|
|
32626
|
+
return new Promise((resolve7, reject) => {
|
|
30519
32627
|
if (this.socket.connected) {
|
|
30520
|
-
|
|
32628
|
+
resolve7();
|
|
30521
32629
|
return;
|
|
30522
32630
|
}
|
|
30523
32631
|
const timeout = setTimeout(
|
|
@@ -30526,7 +32634,7 @@ var WSClient = class {
|
|
|
30526
32634
|
);
|
|
30527
32635
|
this.socket.once("connect", () => {
|
|
30528
32636
|
clearTimeout(timeout);
|
|
30529
|
-
|
|
32637
|
+
resolve7();
|
|
30530
32638
|
});
|
|
30531
32639
|
this.socket.once("connect_error", (err) => {
|
|
30532
32640
|
clearTimeout(timeout);
|
|
@@ -30707,9 +32815,9 @@ async function runSandbox(config) {
|
|
|
30707
32815
|
onUserMessage: (payload) => {
|
|
30708
32816
|
if (payload === null) {
|
|
30709
32817
|
if (pendingMessageResolve) {
|
|
30710
|
-
const
|
|
32818
|
+
const resolve7 = pendingMessageResolve;
|
|
30711
32819
|
pendingMessageResolve = null;
|
|
30712
|
-
|
|
32820
|
+
resolve7(null);
|
|
30713
32821
|
}
|
|
30714
32822
|
return;
|
|
30715
32823
|
}
|
|
@@ -30731,9 +32839,9 @@ async function runSandbox(config) {
|
|
|
30731
32839
|
fileCount: payload.files?.length ?? 0
|
|
30732
32840
|
});
|
|
30733
32841
|
if (pendingMessageResolve) {
|
|
30734
|
-
const
|
|
32842
|
+
const resolve7 = pendingMessageResolve;
|
|
30735
32843
|
pendingMessageResolve = null;
|
|
30736
|
-
|
|
32844
|
+
resolve7(payload);
|
|
30737
32845
|
} else {
|
|
30738
32846
|
queuedMessages.push(payload);
|
|
30739
32847
|
}
|
|
@@ -30926,8 +33034,8 @@ async function runSandbox(config) {
|
|
|
30926
33034
|
});
|
|
30927
33035
|
if (stopped) break;
|
|
30928
33036
|
}
|
|
30929
|
-
const nextPayload = queuedMessages.shift() ?? await new Promise((
|
|
30930
|
-
pendingMessageResolve =
|
|
33037
|
+
const nextPayload = queuedMessages.shift() ?? await new Promise((resolve7) => {
|
|
33038
|
+
pendingMessageResolve = resolve7;
|
|
30931
33039
|
});
|
|
30932
33040
|
if (nextPayload === null || stopped) {
|
|
30933
33041
|
break;
|
|
@@ -31009,10 +33117,10 @@ async function runSandbox(config) {
|
|
|
31009
33117
|
}
|
|
31010
33118
|
|
|
31011
33119
|
// src/service-manager.ts
|
|
31012
|
-
var
|
|
31013
|
-
var
|
|
31014
|
-
var
|
|
31015
|
-
var
|
|
33120
|
+
var import_node_child_process9 = require("child_process");
|
|
33121
|
+
var import_node_fs16 = require("fs");
|
|
33122
|
+
var import_node_os10 = require("os");
|
|
33123
|
+
var import_node_path13 = require("path");
|
|
31016
33124
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
31017
33125
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
31018
33126
|
var WINDOWS_TASK = "Alan Agent";
|
|
@@ -31032,8 +33140,8 @@ function buildDaemonServicePlan(input) {
|
|
|
31032
33140
|
if (userId === void 0) throw new Error("Cannot determine the current macOS user ID");
|
|
31033
33141
|
const domain = `gui/${userId}`;
|
|
31034
33142
|
const serviceTarget = `${domain}/${SERVICE_LABEL}`;
|
|
31035
|
-
const manifestPath = (0,
|
|
31036
|
-
const logDir = (0,
|
|
33143
|
+
const manifestPath = (0, import_node_path13.join)(input.homeDir, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
33144
|
+
const logDir = (0, import_node_path13.join)(input.homeDir, ".alan", "agent", "logs");
|
|
31037
33145
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
31038
33146
|
const manifest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
31039
33147
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -31055,9 +33163,9 @@ ${programArguments}
|
|
|
31055
33163
|
<key>ThrottleInterval</key>
|
|
31056
33164
|
<integer>5</integer>
|
|
31057
33165
|
<key>StandardOutPath</key>
|
|
31058
|
-
<string>${xml((0,
|
|
33166
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon.log"))}</string>
|
|
31059
33167
|
<key>StandardErrorPath</key>
|
|
31060
|
-
<string>${xml((0,
|
|
33168
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon-error.log"))}</string>
|
|
31061
33169
|
</dict>
|
|
31062
33170
|
</plist>
|
|
31063
33171
|
`;
|
|
@@ -31096,7 +33204,7 @@ ${programArguments}
|
|
|
31096
33204
|
};
|
|
31097
33205
|
}
|
|
31098
33206
|
if (input.platform === "linux") {
|
|
31099
|
-
const manifestPath = (0,
|
|
33207
|
+
const manifestPath = (0, import_node_path13.join)(input.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
31100
33208
|
const manifest = `[Unit]
|
|
31101
33209
|
Description=Alan local agent daemon
|
|
31102
33210
|
After=network-online.target
|
|
@@ -31193,7 +33301,7 @@ WantedBy=default.target
|
|
|
31193
33301
|
};
|
|
31194
33302
|
}
|
|
31195
33303
|
function currentServicePlan(args) {
|
|
31196
|
-
const currentPlatform = (0,
|
|
33304
|
+
const currentPlatform = (0, import_node_os10.platform)();
|
|
31197
33305
|
if (currentPlatform !== "darwin" && currentPlatform !== "linux" && currentPlatform !== "win32") {
|
|
31198
33306
|
throw new Error(`Daemon service installation is not supported on ${currentPlatform}`);
|
|
31199
33307
|
}
|
|
@@ -31202,25 +33310,25 @@ function currentServicePlan(args) {
|
|
|
31202
33310
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
31203
33311
|
return buildDaemonServicePlan({
|
|
31204
33312
|
platform: currentPlatform,
|
|
31205
|
-
homeDir: (0,
|
|
33313
|
+
homeDir: (0, import_node_os10.homedir)(),
|
|
31206
33314
|
nodeExecutable: process.execPath,
|
|
31207
|
-
cliEntry: (0,
|
|
33315
|
+
cliEntry: (0, import_node_path13.resolve)(cliEntry),
|
|
31208
33316
|
profile
|
|
31209
33317
|
});
|
|
31210
33318
|
}
|
|
31211
33319
|
function writeManifest(path, contents) {
|
|
31212
|
-
(0,
|
|
33320
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(path), { recursive: true, mode: 448 });
|
|
31213
33321
|
const pendingPath = `${path}.pending-${process.pid}`;
|
|
31214
33322
|
try {
|
|
31215
|
-
(0,
|
|
31216
|
-
(0,
|
|
31217
|
-
(0,
|
|
33323
|
+
(0, import_node_fs16.writeFileSync)(pendingPath, contents, { mode: 384 });
|
|
33324
|
+
(0, import_node_fs16.chmodSync)(pendingPath, 384);
|
|
33325
|
+
(0, import_node_fs16.renameSync)(pendingPath, path);
|
|
31218
33326
|
} finally {
|
|
31219
|
-
(0,
|
|
33327
|
+
(0, import_node_fs16.rmSync)(pendingPath, { force: true });
|
|
31220
33328
|
}
|
|
31221
33329
|
}
|
|
31222
33330
|
function runServiceCommand(command) {
|
|
31223
|
-
const result = (0,
|
|
33331
|
+
const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, { encoding: "utf8" });
|
|
31224
33332
|
const status = result.status ?? 1;
|
|
31225
33333
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
31226
33334
|
if (status !== 0 && !command.tolerateFailure) {
|
|
@@ -31238,8 +33346,8 @@ function installDaemonService(args = []) {
|
|
|
31238
33346
|
}
|
|
31239
33347
|
const plan = currentServicePlan(args);
|
|
31240
33348
|
if (plan.manifestPath && plan.manifest) {
|
|
31241
|
-
if ((0,
|
|
31242
|
-
(0,
|
|
33349
|
+
if ((0, import_node_os10.platform)() === "darwin") {
|
|
33350
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path13.join)((0, import_node_os10.homedir)(), ".alan", "agent", "logs"), { recursive: true, mode: 448 });
|
|
31243
33351
|
}
|
|
31244
33352
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
31245
33353
|
}
|
|
@@ -31249,7 +33357,7 @@ function installDaemonService(args = []) {
|
|
|
31249
33357
|
function uninstallDaemonService(args = []) {
|
|
31250
33358
|
const plan = currentServicePlan(args);
|
|
31251
33359
|
for (const command of plan.uninstallCommands) runServiceCommand(command);
|
|
31252
|
-
if (plan.manifestPath) (0,
|
|
33360
|
+
if (plan.manifestPath) (0, import_node_fs16.rmSync)(plan.manifestPath, { force: true });
|
|
31253
33361
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
31254
33362
|
}
|
|
31255
33363
|
function printDaemonServiceStatus(args = []) {
|
|
@@ -31263,7 +33371,7 @@ function printDaemonServiceStatus(args = []) {
|
|
|
31263
33371
|
function logEffectiveGitIdentity(lifecycle, projectPath) {
|
|
31264
33372
|
const readConfig2 = (key) => {
|
|
31265
33373
|
try {
|
|
31266
|
-
const result = (0,
|
|
33374
|
+
const result = (0, import_node_child_process10.spawnSync)("git", ["config", "--get", key], {
|
|
31267
33375
|
cwd: projectPath,
|
|
31268
33376
|
env: getDaemonCliEnvironment(),
|
|
31269
33377
|
encoding: "utf8",
|