@aiden-ade/sandbox-agent 0.1.50 → 0.1.51
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 +2283 -563
- package/package.json +3 -3
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 spawn6 = 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 = spawn6(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_process9 = 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_process7 = 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");
|
|
@@ -19128,8 +19128,8 @@ async function withTimeout(promise, timeoutMs, fallback) {
|
|
|
19128
19128
|
try {
|
|
19129
19129
|
return await Promise.race([
|
|
19130
19130
|
promise,
|
|
19131
|
-
new Promise((
|
|
19132
|
-
timer = setTimeout(() =>
|
|
19131
|
+
new Promise((resolve7) => {
|
|
19132
|
+
timer = setTimeout(() => resolve7(fallback), timeoutMs);
|
|
19133
19133
|
})
|
|
19134
19134
|
]);
|
|
19135
19135
|
} finally {
|
|
@@ -19142,7 +19142,7 @@ async function collectCodexLimits(env) {
|
|
|
19142
19142
|
const codexCommand = resolveExecutable("codex", runtimeEnv, "ALAN_CODEX_PATH");
|
|
19143
19143
|
if (!codexCommand)
|
|
19144
19144
|
return [];
|
|
19145
|
-
return withTimeout(new Promise((
|
|
19145
|
+
return withTimeout(new Promise((resolve7) => {
|
|
19146
19146
|
const child = (0, import_node_child_process2.spawn)(codexCommand, ["-s", "read-only", "-a", "untrusted", "app-server"], {
|
|
19147
19147
|
env: runtimeEnv,
|
|
19148
19148
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -19154,7 +19154,7 @@ async function collectCodexLimits(env) {
|
|
|
19154
19154
|
pending.clear();
|
|
19155
19155
|
if (!child.killed)
|
|
19156
19156
|
child.kill();
|
|
19157
|
-
|
|
19157
|
+
resolve7(windows);
|
|
19158
19158
|
};
|
|
19159
19159
|
const request = (method, params = {}) => {
|
|
19160
19160
|
const id = nextId++;
|
|
@@ -21987,9 +21987,9 @@ var Socket2 = class extends Emitter {
|
|
|
21987
21987
|
* @return a Promise that will be fulfilled when the server acknowledges the event
|
|
21988
21988
|
*/
|
|
21989
21989
|
emitWithAck(ev, ...args) {
|
|
21990
|
-
return new Promise((
|
|
21990
|
+
return new Promise((resolve7, reject) => {
|
|
21991
21991
|
const fn = (arg1, arg2) => {
|
|
21992
|
-
return arg1 ? reject(arg1) :
|
|
21992
|
+
return arg1 ? reject(arg1) : resolve7(arg2);
|
|
21993
21993
|
};
|
|
21994
21994
|
fn.withError = true;
|
|
21995
21995
|
args.push(fn);
|
|
@@ -22987,232 +22987,1350 @@ Object.assign(lookup, {
|
|
|
22987
22987
|
connect: lookup
|
|
22988
22988
|
});
|
|
22989
22989
|
|
|
22990
|
-
// src/
|
|
22990
|
+
// src/binding-switch-git.ts
|
|
22991
22991
|
var import_node_child_process3 = require("child_process");
|
|
22992
|
+
var import_node_crypto = require("crypto");
|
|
22993
|
+
var import_node_fs4 = require("fs");
|
|
22992
22994
|
var import_node_os4 = require("os");
|
|
22993
|
-
var
|
|
22994
|
-
var
|
|
22995
|
-
|
|
22996
|
-
|
|
22997
|
-
|
|
22998
|
-
|
|
22999
|
-
|
|
23000
|
-
|
|
23001
|
-
|
|
23002
|
-
|
|
23003
|
-
|
|
22995
|
+
var import_node_path4 = require("path");
|
|
22996
|
+
var import_node_url = require("url");
|
|
22997
|
+
var BINDING_SWITCH_GIT_METHODS = [
|
|
22998
|
+
"git.status",
|
|
22999
|
+
"git.checkpoint",
|
|
23000
|
+
"git.push",
|
|
23001
|
+
"git.fetch",
|
|
23002
|
+
"git.prepare_checkout",
|
|
23003
|
+
"git.prepare_worktree",
|
|
23004
|
+
"git.verify_head"
|
|
23005
|
+
];
|
|
23006
|
+
function isBindingSwitchGitMethod(method) {
|
|
23007
|
+
return BINDING_SWITCH_GIT_METHODS.includes(method);
|
|
23004
23008
|
}
|
|
23005
|
-
|
|
23006
|
-
|
|
23007
|
-
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
if (typeof version === "string" && version.trim()) return version.trim();
|
|
23012
|
-
} catch {
|
|
23009
|
+
var BindingSwitchGitError = class extends Error {
|
|
23010
|
+
constructor(code, message, details) {
|
|
23011
|
+
super(message);
|
|
23012
|
+
this.code = code;
|
|
23013
|
+
this.details = details;
|
|
23014
|
+
this.name = "BindingSwitchGitError";
|
|
23013
23015
|
}
|
|
23014
|
-
|
|
23016
|
+
};
|
|
23017
|
+
var LOCAL_GIT_TIMEOUT_MS = 1e4;
|
|
23018
|
+
var NETWORK_GIT_TIMEOUT_MS = 3e4;
|
|
23019
|
+
var MAX_GIT_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
23020
|
+
function runGitResult(cwd, args, options = {}) {
|
|
23021
|
+
const result = (0, import_node_child_process3.spawnSync)("git", args, {
|
|
23022
|
+
cwd,
|
|
23023
|
+
env: { ...getDaemonCliEnvironment(), ...options.env },
|
|
23024
|
+
encoding: "utf8",
|
|
23025
|
+
input: options.input,
|
|
23026
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
23027
|
+
timeout: LOCAL_GIT_TIMEOUT_MS
|
|
23028
|
+
});
|
|
23029
|
+
if (result.error) {
|
|
23030
|
+
return { status: 1, stdout: "", stderr: result.error.message };
|
|
23031
|
+
}
|
|
23032
|
+
return {
|
|
23033
|
+
status: result.status ?? 1,
|
|
23034
|
+
stdout: result.stdout ?? "",
|
|
23035
|
+
stderr: result.stderr ?? ""
|
|
23036
|
+
};
|
|
23015
23037
|
}
|
|
23016
|
-
function
|
|
23017
|
-
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
23038
|
+
function networkGitTimeoutMs() {
|
|
23039
|
+
if (process.env.NODE_ENV !== "test") return NETWORK_GIT_TIMEOUT_MS;
|
|
23040
|
+
const override = Number.parseInt(process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS ?? "", 10);
|
|
23041
|
+
return Number.isFinite(override) && override >= 10 ? Math.min(override, NETWORK_GIT_TIMEOUT_MS) : NETWORK_GIT_TIMEOUT_MS;
|
|
23042
|
+
}
|
|
23043
|
+
async function waitForCheckpointTestBarrier() {
|
|
23044
|
+
const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR : void 0;
|
|
23045
|
+
if (!barrierDir) return;
|
|
23046
|
+
(0, import_node_fs4.mkdirSync)(barrierDir, { recursive: true });
|
|
23047
|
+
const readyPath = (0, import_node_path4.join)(barrierDir, "ready");
|
|
23048
|
+
const releasePath = (0, import_node_path4.join)(barrierDir, "release");
|
|
23049
|
+
(0, import_node_fs4.writeFileSync)(readyPath, "ready\n");
|
|
23050
|
+
const deadline = Date.now() + 5e3;
|
|
23051
|
+
while (!(0, import_node_fs4.existsSync)(releasePath)) {
|
|
23052
|
+
if (Date.now() >= deadline) {
|
|
23053
|
+
throw new BindingSwitchGitError(
|
|
23054
|
+
"checkpoint_prepare_timeout",
|
|
23055
|
+
"Checkpoint preparation test barrier timed out"
|
|
23056
|
+
);
|
|
23031
23057
|
}
|
|
23058
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
23059
|
+
}
|
|
23060
|
+
}
|
|
23061
|
+
async function waitForCheckpointFinalizeTestBarrier() {
|
|
23062
|
+
const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR : void 0;
|
|
23063
|
+
if (!barrierDir) return;
|
|
23064
|
+
(0, import_node_fs4.mkdirSync)(barrierDir, { recursive: true });
|
|
23065
|
+
const readyPath = (0, import_node_path4.join)(barrierDir, "ready");
|
|
23066
|
+
const releasePath = (0, import_node_path4.join)(barrierDir, "release");
|
|
23067
|
+
(0, import_node_fs4.writeFileSync)(readyPath, "ready\n");
|
|
23068
|
+
const deadline = Date.now() + 5e3;
|
|
23069
|
+
while (!(0, import_node_fs4.existsSync)(releasePath)) {
|
|
23070
|
+
if (Date.now() >= deadline) {
|
|
23071
|
+
throw new BindingSwitchGitError(
|
|
23072
|
+
"checkpoint_finalize_timeout",
|
|
23073
|
+
"Checkpoint finalization test barrier timed out"
|
|
23074
|
+
);
|
|
23075
|
+
}
|
|
23076
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
23077
|
+
}
|
|
23078
|
+
}
|
|
23079
|
+
async function runNetworkGit(cwd, args, code) {
|
|
23080
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
23081
|
+
const child = (0, import_node_child_process3.spawn)("git", args, {
|
|
23082
|
+
cwd,
|
|
23083
|
+
env: {
|
|
23084
|
+
...getDaemonCliEnvironment(),
|
|
23085
|
+
GCM_INTERACTIVE: "Never",
|
|
23086
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
23087
|
+
...process.env.NODE_ENV === "test" && process.env.ALAN_TEST_GIT_SSH_COMMAND ? { GIT_SSH_COMMAND: process.env.ALAN_TEST_GIT_SSH_COMMAND } : {}
|
|
23088
|
+
},
|
|
23089
|
+
detached: process.platform !== "win32",
|
|
23090
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
23091
|
+
});
|
|
23032
23092
|
let stdout = "";
|
|
23033
23093
|
let stderr = "";
|
|
23034
|
-
|
|
23035
|
-
|
|
23094
|
+
let outputExceeded = false;
|
|
23095
|
+
let timedOut = false;
|
|
23096
|
+
let settled = false;
|
|
23097
|
+
const killTree = () => {
|
|
23098
|
+
if (child.pid && process.platform !== "win32") {
|
|
23099
|
+
try {
|
|
23100
|
+
process.kill(-child.pid, "SIGKILL");
|
|
23101
|
+
return;
|
|
23102
|
+
} catch {
|
|
23103
|
+
}
|
|
23104
|
+
}
|
|
23105
|
+
child.kill("SIGKILL");
|
|
23106
|
+
};
|
|
23107
|
+
const timer = setTimeout(() => {
|
|
23108
|
+
timedOut = true;
|
|
23109
|
+
killTree();
|
|
23110
|
+
}, networkGitTimeoutMs());
|
|
23111
|
+
timer.unref?.();
|
|
23112
|
+
const append = (current, chunk) => {
|
|
23113
|
+
if (Buffer.byteLength(current) + chunk.byteLength > MAX_GIT_OUTPUT_BYTES) {
|
|
23114
|
+
outputExceeded = true;
|
|
23115
|
+
killTree();
|
|
23116
|
+
return current;
|
|
23117
|
+
}
|
|
23118
|
+
return current + chunk.toString("utf8");
|
|
23119
|
+
};
|
|
23120
|
+
child.stdout.on("data", (chunk) => {
|
|
23121
|
+
stdout = append(stdout, chunk);
|
|
23036
23122
|
});
|
|
23037
|
-
child.stderr
|
|
23038
|
-
stderr
|
|
23123
|
+
child.stderr.on("data", (chunk) => {
|
|
23124
|
+
stderr = append(stderr, chunk);
|
|
23125
|
+
});
|
|
23126
|
+
child.once("error", (error) => {
|
|
23127
|
+
if (settled) return;
|
|
23128
|
+
settled = true;
|
|
23129
|
+
clearTimeout(timer);
|
|
23130
|
+
rejectPromise(new BindingSwitchGitError(code, error.message));
|
|
23131
|
+
});
|
|
23132
|
+
child.once("close", (exitCode) => {
|
|
23133
|
+
if (settled) return;
|
|
23134
|
+
settled = true;
|
|
23135
|
+
clearTimeout(timer);
|
|
23136
|
+
if (timedOut) {
|
|
23137
|
+
rejectPromise(
|
|
23138
|
+
new BindingSwitchGitError(
|
|
23139
|
+
"git_network_timeout",
|
|
23140
|
+
"The Git network operation timed out and was cancelled"
|
|
23141
|
+
)
|
|
23142
|
+
);
|
|
23143
|
+
return;
|
|
23144
|
+
}
|
|
23145
|
+
if (outputExceeded) {
|
|
23146
|
+
rejectPromise(
|
|
23147
|
+
new BindingSwitchGitError(
|
|
23148
|
+
"git_output_limit_exceeded",
|
|
23149
|
+
"The Git network operation produced too much output and was cancelled"
|
|
23150
|
+
)
|
|
23151
|
+
);
|
|
23152
|
+
return;
|
|
23153
|
+
}
|
|
23154
|
+
if (exitCode !== 0) {
|
|
23155
|
+
rejectPromise(
|
|
23156
|
+
new BindingSwitchGitError(code, (stderr || stdout || "Git network command failed").trim())
|
|
23157
|
+
);
|
|
23158
|
+
return;
|
|
23159
|
+
}
|
|
23160
|
+
resolvePromise(stdout.trim());
|
|
23039
23161
|
});
|
|
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
23162
|
});
|
|
23053
23163
|
}
|
|
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;
|
|
23164
|
+
function runGit(cwd, args, code = "git_operation_failed") {
|
|
23165
|
+
return runGitWithOptions(cwd, args, {}, code);
|
|
23072
23166
|
}
|
|
23073
|
-
function
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23082
|
-
});
|
|
23083
|
-
installOutcomes.set(spec.npmPackage, attempt);
|
|
23084
|
-
return attempt;
|
|
23167
|
+
function runGitWithOptions(cwd, args, options, code = "git_operation_failed") {
|
|
23168
|
+
const result = runGitResult(cwd, args, options);
|
|
23169
|
+
if (result.status !== 0) {
|
|
23170
|
+
throw new BindingSwitchGitError(
|
|
23171
|
+
code,
|
|
23172
|
+
(result.stderr || result.stdout || "Git command failed").trim()
|
|
23173
|
+
);
|
|
23174
|
+
}
|
|
23175
|
+
return result.stdout.trim();
|
|
23085
23176
|
}
|
|
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;
|
|
23177
|
+
function sourceChangedDuringCheckpoint(message) {
|
|
23178
|
+
return new BindingSwitchGitError("source_changed_since_approval", message);
|
|
23114
23179
|
}
|
|
23115
|
-
function
|
|
23180
|
+
function acquireCheckpointHeadLock(locks) {
|
|
23116
23181
|
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
|
-
});
|
|
23182
|
+
(0, import_node_fs4.writeFileSync)(locks.headLockPath, "", { flag: "wx" });
|
|
23183
|
+
locks.headLockHeld = true;
|
|
23126
23184
|
} catch {
|
|
23127
|
-
|
|
23185
|
+
throw sourceChangedDuringCheckpoint(
|
|
23186
|
+
"Source repository is being changed by another Git operation"
|
|
23187
|
+
);
|
|
23128
23188
|
}
|
|
23129
23189
|
}
|
|
23130
|
-
function
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
const
|
|
23137
|
-
|
|
23138
|
-
|
|
23190
|
+
function releaseCheckpointHeadLock(locks) {
|
|
23191
|
+
if (!locks.headLockHeld) return;
|
|
23192
|
+
(0, import_node_fs4.rmSync)(locks.headLockPath, { force: true });
|
|
23193
|
+
locks.headLockHeld = false;
|
|
23194
|
+
}
|
|
23195
|
+
function acquireCheckpointRepositoryLocks(repoRoot) {
|
|
23196
|
+
const gitDir = runGit(
|
|
23197
|
+
repoRoot,
|
|
23198
|
+
["rev-parse", "--absolute-git-dir"],
|
|
23199
|
+
"checkpoint_lock_unavailable"
|
|
23200
|
+
);
|
|
23201
|
+
const indexPath = runGit(
|
|
23202
|
+
repoRoot,
|
|
23203
|
+
["rev-parse", "--path-format=absolute", "--git-path", "index"],
|
|
23204
|
+
"checkpoint_lock_unavailable"
|
|
23205
|
+
);
|
|
23206
|
+
const locks = {
|
|
23207
|
+
headLockPath: (0, import_node_path4.join)(gitDir, "HEAD.lock"),
|
|
23208
|
+
headLockHeld: false,
|
|
23209
|
+
indexLockPath: `${indexPath}.lock`,
|
|
23210
|
+
indexLockHeld: false,
|
|
23211
|
+
indexPath
|
|
23212
|
+
};
|
|
23139
23213
|
try {
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
|
|
23146
|
-
|
|
23214
|
+
(0, import_node_fs4.writeFileSync)(locks.indexLockPath, "", { flag: "wx" });
|
|
23215
|
+
locks.indexLockHeld = true;
|
|
23216
|
+
acquireCheckpointHeadLock(locks);
|
|
23217
|
+
return locks;
|
|
23218
|
+
} catch {
|
|
23219
|
+
if (locks.indexLockHeld) {
|
|
23220
|
+
(0, import_node_fs4.rmSync)(locks.indexLockPath, { force: true });
|
|
23221
|
+
locks.indexLockHeld = false;
|
|
23147
23222
|
}
|
|
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"))
|
|
23223
|
+
throw sourceChangedDuringCheckpoint(
|
|
23224
|
+
"Source repository is being changed by another Git operation"
|
|
23154
23225
|
);
|
|
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
23226
|
}
|
|
23167
23227
|
}
|
|
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));
|
|
23228
|
+
function releaseCheckpointRepositoryLocks(locks) {
|
|
23229
|
+
releaseCheckpointHeadLock(locks);
|
|
23230
|
+
if (locks.indexLockHeld) {
|
|
23231
|
+
(0, import_node_fs4.rmSync)(locks.indexLockPath, { force: true });
|
|
23232
|
+
locks.indexLockHeld = false;
|
|
23233
|
+
}
|
|
23176
23234
|
}
|
|
23177
|
-
function
|
|
23178
|
-
|
|
23235
|
+
function assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha) {
|
|
23236
|
+
const currentRef = runGit(
|
|
23237
|
+
repoRoot,
|
|
23238
|
+
["symbolic-ref", "-q", "HEAD"],
|
|
23239
|
+
"source_changed_since_approval"
|
|
23240
|
+
);
|
|
23241
|
+
const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_changed_since_approval");
|
|
23242
|
+
if (currentRef !== expectedRef || currentHead !== expectedHeadSha) {
|
|
23243
|
+
throw sourceChangedDuringCheckpoint(
|
|
23244
|
+
"Source HEAD or branch changed during checkpoint finalization"
|
|
23245
|
+
);
|
|
23246
|
+
}
|
|
23179
23247
|
}
|
|
23180
|
-
function
|
|
23181
|
-
const
|
|
23182
|
-
const
|
|
23183
|
-
|
|
23248
|
+
function createCheckpointIndexBaseline(repoRoot, checkpointSha) {
|
|
23249
|
+
const tempDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "alan-checkpoint-index-"));
|
|
23250
|
+
const indexPath = (0, import_node_path4.join)(tempDir, "index");
|
|
23251
|
+
try {
|
|
23252
|
+
runGitWithOptions(
|
|
23253
|
+
repoRoot,
|
|
23254
|
+
["read-tree", checkpointSha],
|
|
23255
|
+
{ env: { GIT_INDEX_FILE: indexPath } },
|
|
23256
|
+
"checkpoint_index_sync_failed"
|
|
23257
|
+
);
|
|
23258
|
+
return { indexPath, tempDir };
|
|
23259
|
+
} catch (error) {
|
|
23260
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
23261
|
+
throw error;
|
|
23262
|
+
}
|
|
23184
23263
|
}
|
|
23185
|
-
function
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23264
|
+
function installCheckpointIndexBaseline(locks, baselineIndexPath) {
|
|
23265
|
+
try {
|
|
23266
|
+
(0, import_node_fs4.copyFileSync)(baselineIndexPath, locks.indexLockPath);
|
|
23267
|
+
(0, import_node_fs4.renameSync)(locks.indexLockPath, locks.indexPath);
|
|
23268
|
+
locks.indexLockHeld = false;
|
|
23269
|
+
} catch (error) {
|
|
23270
|
+
throw new BindingSwitchGitError(
|
|
23271
|
+
"checkpoint_index_sync_failed",
|
|
23272
|
+
error instanceof Error ? error.message : "The checkpoint index could not be installed"
|
|
23273
|
+
);
|
|
23274
|
+
}
|
|
23192
23275
|
}
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
this.inspect = inspect;
|
|
23276
|
+
function requiredString(params, key) {
|
|
23277
|
+
const value2 = params[key];
|
|
23278
|
+
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
23279
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is required`);
|
|
23198
23280
|
}
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
if (readiness.ready) {
|
|
23202
|
-
this.issue = null;
|
|
23203
|
-
return;
|
|
23204
|
-
}
|
|
23205
|
-
this.issue = { path, readiness };
|
|
23281
|
+
if (/[\0\r\n]/.test(value2)) {
|
|
23282
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} contains unsafe characters`);
|
|
23206
23283
|
}
|
|
23207
|
-
|
|
23208
|
-
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23284
|
+
return value2;
|
|
23285
|
+
}
|
|
23286
|
+
function requiredCommitMessage(params) {
|
|
23287
|
+
const value2 = params.commitMessage;
|
|
23288
|
+
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
23289
|
+
throw new BindingSwitchGitError("invalid_git_request", "commitMessage is required");
|
|
23290
|
+
}
|
|
23291
|
+
if (value2.includes("\0")) {
|
|
23292
|
+
throw new BindingSwitchGitError(
|
|
23293
|
+
"invalid_git_request",
|
|
23294
|
+
"commitMessage contains unsafe characters"
|
|
23295
|
+
);
|
|
23296
|
+
}
|
|
23297
|
+
return value2;
|
|
23298
|
+
}
|
|
23299
|
+
function optionalString(params, key) {
|
|
23300
|
+
const value2 = params[key];
|
|
23301
|
+
if (value2 === void 0 || value2 === null) return void 0;
|
|
23302
|
+
if (typeof value2 !== "string" || value2.trim() === "" || /[\0\r\n]/.test(value2)) {
|
|
23303
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is invalid`);
|
|
23304
|
+
}
|
|
23305
|
+
return value2;
|
|
23306
|
+
}
|
|
23307
|
+
function commonRequest(params) {
|
|
23308
|
+
return {
|
|
23309
|
+
switchId: requiredString(params, "switchId"),
|
|
23310
|
+
repoKey: requiredString(params, "repoKey"),
|
|
23311
|
+
repoPath: (0, import_node_path4.resolve)(requiredString(params, "repoPath"))
|
|
23312
|
+
};
|
|
23313
|
+
}
|
|
23314
|
+
function assertCommitSha(sha, key) {
|
|
23315
|
+
if (!/^[0-9a-f]{40,64}$/i.test(sha)) {
|
|
23316
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} is not a commit SHA`);
|
|
23317
|
+
}
|
|
23318
|
+
}
|
|
23319
|
+
function assertValidBranch(branch) {
|
|
23320
|
+
if (branch.startsWith("-")) {
|
|
23321
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch is not a safe Git ref");
|
|
23322
|
+
}
|
|
23323
|
+
const result = (0, import_node_child_process3.spawnSync)("git", ["check-ref-format", "--branch", branch], {
|
|
23324
|
+
env: getDaemonCliEnvironment(),
|
|
23325
|
+
encoding: "utf8"
|
|
23326
|
+
});
|
|
23327
|
+
if (result.status !== 0) {
|
|
23328
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch is not a valid Git ref");
|
|
23329
|
+
}
|
|
23330
|
+
}
|
|
23331
|
+
function validatedRemote(params) {
|
|
23332
|
+
const remote = optionalString(params, "remote") ?? "origin";
|
|
23333
|
+
if (remote.startsWith("-") || remote.includes("/") || remote.includes("\\")) {
|
|
23334
|
+
throw new BindingSwitchGitError("invalid_git_request", "remote is not a safe remote name");
|
|
23335
|
+
}
|
|
23336
|
+
return remote;
|
|
23337
|
+
}
|
|
23338
|
+
function canonicalRemoteUrl(repoRoot, value2) {
|
|
23339
|
+
const trimmed = value2.trim();
|
|
23340
|
+
if (!trimmed || /[\0\r\n]/.test(trimmed)) {
|
|
23341
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23342
|
+
}
|
|
23343
|
+
const scpStyle = trimmed.match(/^([^/@:\s]+)@([^/:\s]+):(.+)$/);
|
|
23344
|
+
const urlValue = scpStyle ? `ssh://${scpStyle[1]}@${scpStyle[2]}/${scpStyle[3]}` : trimmed;
|
|
23345
|
+
if ((0, import_node_path4.isAbsolute)(urlValue) || urlValue.startsWith("./") || urlValue.startsWith("../")) {
|
|
23346
|
+
const absolute = (0, import_node_path4.isAbsolute)(urlValue) ? urlValue : (0, import_node_path4.resolve)(repoRoot, urlValue);
|
|
23347
|
+
return `file://${canonicalPathWithMissingTail(absolute).replace(/\/+$/, "").replace(/\.git$/, "")}`;
|
|
23348
|
+
}
|
|
23349
|
+
let parsed;
|
|
23350
|
+
try {
|
|
23351
|
+
parsed = new URL(urlValue);
|
|
23352
|
+
} catch {
|
|
23353
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23354
|
+
}
|
|
23355
|
+
if (parsed.protocol === "file:") {
|
|
23356
|
+
return `file://${canonicalPathWithMissingTail((0, import_node_url.fileURLToPath)(parsed)).replace(/\/+$/, "").replace(/\.git$/, "")}`;
|
|
23357
|
+
}
|
|
23358
|
+
if (parsed.search || parsed.hash) {
|
|
23359
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
23360
|
+
}
|
|
23361
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(parsed.protocol)) {
|
|
23362
|
+
throw new BindingSwitchGitError(
|
|
23363
|
+
"invalid_git_request",
|
|
23364
|
+
"Remote URL uses an unsupported protocol"
|
|
23365
|
+
);
|
|
23366
|
+
}
|
|
23367
|
+
if (parsed.password || (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username) {
|
|
23368
|
+
throw new BindingSwitchGitError(
|
|
23369
|
+
"remote_url_credentials_rejected",
|
|
23370
|
+
"Credential-bearing remote URLs are not allowed for environment migration"
|
|
23371
|
+
);
|
|
23372
|
+
}
|
|
23373
|
+
const defaultPort = parsed.protocol === "https:" && parsed.port === "443" || parsed.protocol === "http:" && parsed.port === "80" || parsed.protocol === "ssh:" && parsed.port === "22";
|
|
23374
|
+
const transport = parsed.protocol === "https:" || parsed.protocol === "ssh:" ? "secure:" : parsed.protocol;
|
|
23375
|
+
const authority = `${parsed.hostname.toLowerCase()}${parsed.port && !defaultPort ? `:${parsed.port}` : ""}`;
|
|
23376
|
+
const path = parsed.pathname.replace(/\/+/g, "/").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
23377
|
+
return `${transport}//${authority}${path}`;
|
|
23378
|
+
}
|
|
23379
|
+
function verifiedRemote(repoRoot, params, includePushUrls) {
|
|
23380
|
+
const remote = validatedRemote(params);
|
|
23381
|
+
const expectedRemoteUrl = canonicalRemoteUrl(
|
|
23382
|
+
repoRoot,
|
|
23383
|
+
requiredString(params, "expectedRemoteUrl")
|
|
23384
|
+
);
|
|
23385
|
+
const fetchUrls = runGit(
|
|
23386
|
+
repoRoot,
|
|
23387
|
+
["remote", "get-url", "--all", remote],
|
|
23388
|
+
"git_remote_unavailable"
|
|
23389
|
+
).split("\n").filter(Boolean);
|
|
23390
|
+
const pushUrls = includePushUrls ? runGit(repoRoot, ["remote", "get-url", "--push", "--all", remote], "git_remote_unavailable").split("\n").filter(Boolean) : [];
|
|
23391
|
+
const urls = [...fetchUrls, ...pushUrls];
|
|
23392
|
+
if (urls.length === 0 || urls.some((url2) => canonicalRemoteUrl(repoRoot, url2) !== expectedRemoteUrl)) {
|
|
23393
|
+
throw new BindingSwitchGitError(
|
|
23394
|
+
"remote_url_mismatch",
|
|
23395
|
+
"The configured Git remote does not match the repository selected for migration"
|
|
23396
|
+
);
|
|
23397
|
+
}
|
|
23398
|
+
return { remote, expectedRemoteUrl };
|
|
23399
|
+
}
|
|
23400
|
+
function isPathContained(childPath, parentPath) {
|
|
23401
|
+
const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parentPath), (0, import_node_path4.resolve)(childPath));
|
|
23402
|
+
return rel === "" || !(0, import_node_path4.isAbsolute)(rel) && rel !== ".." && !rel.startsWith(`..${import_node_path4.sep}`);
|
|
23403
|
+
}
|
|
23404
|
+
function canonicalPathWithMissingTail(path) {
|
|
23405
|
+
const missingSegments = [];
|
|
23406
|
+
let cursor = (0, import_node_path4.resolve)(path);
|
|
23407
|
+
while (!(0, import_node_fs4.existsSync)(cursor)) {
|
|
23408
|
+
const parent = (0, import_node_path4.dirname)(cursor);
|
|
23409
|
+
if (parent === cursor) {
|
|
23410
|
+
throw new BindingSwitchGitError(
|
|
23411
|
+
"path_canonicalization_failed",
|
|
23412
|
+
"The selected path has no existing canonical ancestor"
|
|
23413
|
+
);
|
|
23414
|
+
}
|
|
23415
|
+
missingSegments.unshift((0, import_node_path4.basename)(cursor));
|
|
23416
|
+
cursor = parent;
|
|
23417
|
+
}
|
|
23418
|
+
return (0, import_node_path4.join)(import_node_fs4.realpathSync.native(cursor), ...missingSegments);
|
|
23419
|
+
}
|
|
23420
|
+
function assertCanonicalPathContained(childPath, parentPath, code, message) {
|
|
23421
|
+
if (!isPathContained(
|
|
23422
|
+
canonicalPathWithMissingTail(childPath),
|
|
23423
|
+
canonicalPathWithMissingTail(parentPath)
|
|
23424
|
+
)) {
|
|
23425
|
+
throw new BindingSwitchGitError(code, message);
|
|
23426
|
+
}
|
|
23427
|
+
}
|
|
23428
|
+
function alanWorktreeRoot(projectRoot) {
|
|
23429
|
+
return (0, import_node_path4.join)((0, import_node_path4.dirname)(projectRoot), `${(0, import_node_path4.basename)(projectRoot)}-alan-worktrees`);
|
|
23430
|
+
}
|
|
23431
|
+
function resolveRepositoryRoot(requestedPath) {
|
|
23432
|
+
if (!(0, import_node_fs4.existsSync)(requestedPath)) {
|
|
23433
|
+
throw new BindingSwitchGitError(
|
|
23434
|
+
"repository_not_found",
|
|
23435
|
+
`The selected repository path does not exist: ${requestedPath}`
|
|
23436
|
+
);
|
|
23437
|
+
}
|
|
23438
|
+
const root = runGitResult(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
23439
|
+
if (root.status !== 0 || !root.stdout.trim()) {
|
|
23440
|
+
throw new BindingSwitchGitError(
|
|
23441
|
+
"repository_not_found",
|
|
23442
|
+
"The selected folder is not inside a Git repository"
|
|
23443
|
+
);
|
|
23444
|
+
}
|
|
23445
|
+
return import_node_fs4.realpathSync.native((0, import_node_path4.resolve)(root.stdout.trim()));
|
|
23446
|
+
}
|
|
23447
|
+
function listWorktrees(repoRoot) {
|
|
23448
|
+
return runGit(repoRoot, ["worktree", "list", "--porcelain"]).split("\n\n").flatMap((entry) => {
|
|
23449
|
+
const lines = entry.split("\n");
|
|
23450
|
+
const path = lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
|
|
23451
|
+
if (!path) return [];
|
|
23452
|
+
return [
|
|
23453
|
+
{
|
|
23454
|
+
path: canonicalPathWithMissingTail(path),
|
|
23455
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "") ?? null
|
|
23456
|
+
}
|
|
23457
|
+
];
|
|
23458
|
+
});
|
|
23459
|
+
}
|
|
23460
|
+
function resolveAuthorizedRepository(params, requireTrustedContext = false) {
|
|
23461
|
+
const { repoPath } = commonRequest(params);
|
|
23462
|
+
const repoRoot = resolveRepositoryRoot(repoPath);
|
|
23463
|
+
const expectedRepoRoot = requireTrustedContext ? requiredString(params, "expectedRepoRoot") : optionalString(params, "expectedRepoRoot");
|
|
23464
|
+
if (expectedRepoRoot && resolveRepositoryRoot((0, import_node_path4.resolve)(expectedRepoRoot)) !== repoRoot) {
|
|
23465
|
+
throw new BindingSwitchGitError(
|
|
23466
|
+
"repository_path_changed",
|
|
23467
|
+
"The selected path now resolves to a different Git repository"
|
|
23468
|
+
);
|
|
23469
|
+
}
|
|
23470
|
+
const projectPath = requireTrustedContext ? requiredString(params, "projectPath") : optionalString(params, "projectPath");
|
|
23471
|
+
if (!projectPath) return repoRoot;
|
|
23472
|
+
const projectRoot = resolveRepositoryRoot((0, import_node_path4.resolve)(projectPath));
|
|
23473
|
+
const registrations = listWorktrees(projectRoot);
|
|
23474
|
+
const registered = registrations.find(({ path }) => path === repoRoot);
|
|
23475
|
+
if (!registered) {
|
|
23476
|
+
throw new BindingSwitchGitError(
|
|
23477
|
+
"repository_path_not_authorized",
|
|
23478
|
+
"The selected repository is not the Project checkout or one of its registered worktrees"
|
|
23479
|
+
);
|
|
23480
|
+
}
|
|
23481
|
+
const mainRoot = registrations[0]?.path;
|
|
23482
|
+
if (mainRoot && repoRoot !== mainRoot && !isPathContained(repoRoot, canonicalPathWithMissingTail(alanWorktreeRoot(mainRoot)))) {
|
|
23483
|
+
throw new BindingSwitchGitError(
|
|
23484
|
+
"repository_path_not_authorized",
|
|
23485
|
+
"The selected worktree is outside the Alan-managed worktree directory"
|
|
23486
|
+
);
|
|
23487
|
+
}
|
|
23488
|
+
return repoRoot;
|
|
23489
|
+
}
|
|
23490
|
+
function readRawGit(repoRoot, args, code) {
|
|
23491
|
+
const result = runGitResult(repoRoot, args);
|
|
23492
|
+
if (result.status !== 0) {
|
|
23493
|
+
throw new BindingSwitchGitError(
|
|
23494
|
+
code,
|
|
23495
|
+
(result.stderr || result.stdout || "Git inspection failed").trim()
|
|
23496
|
+
);
|
|
23497
|
+
}
|
|
23498
|
+
return result.stdout;
|
|
23499
|
+
}
|
|
23500
|
+
function updateLengthPrefixed(hash, value2) {
|
|
23501
|
+
const bytes = Buffer.from(value2, "utf8");
|
|
23502
|
+
hash.update(String(bytes.byteLength));
|
|
23503
|
+
hash.update("\0");
|
|
23504
|
+
hash.update(bytes);
|
|
23505
|
+
hash.update("\0");
|
|
23506
|
+
}
|
|
23507
|
+
function proposedTreeSha(repoRoot, headSha) {
|
|
23508
|
+
const tempDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "alan-switch-index-"));
|
|
23509
|
+
const indexPath = (0, import_node_path4.join)(tempDir, "index");
|
|
23510
|
+
const options = { env: { GIT_INDEX_FILE: indexPath } };
|
|
23511
|
+
try {
|
|
23512
|
+
if (headSha) {
|
|
23513
|
+
runGitWithOptions(repoRoot, ["read-tree", headSha], options, "git_status_unreadable");
|
|
23514
|
+
} else {
|
|
23515
|
+
runGitWithOptions(repoRoot, ["read-tree", "--empty"], options, "git_status_unreadable");
|
|
23516
|
+
}
|
|
23517
|
+
runGitWithOptions(repoRoot, ["add", "--all"], options, "git_status_unreadable");
|
|
23518
|
+
const treeSha = runGitWithOptions(repoRoot, ["write-tree"], options, "git_status_unreadable");
|
|
23519
|
+
assertCommitSha(treeSha, "proposedTreeSha");
|
|
23520
|
+
return treeSha;
|
|
23521
|
+
} finally {
|
|
23522
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
23523
|
+
}
|
|
23524
|
+
}
|
|
23525
|
+
function statusFingerprint(repoRoot, porcelain, proposedTree) {
|
|
23526
|
+
const unstagedPatch = readRawGit(
|
|
23527
|
+
repoRoot,
|
|
23528
|
+
["diff", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"],
|
|
23529
|
+
"git_status_unreadable"
|
|
23530
|
+
);
|
|
23531
|
+
const stagedPatch = readRawGit(
|
|
23532
|
+
repoRoot,
|
|
23533
|
+
["diff", "--cached", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"],
|
|
23534
|
+
"git_status_unreadable"
|
|
23535
|
+
);
|
|
23536
|
+
const untrackedRaw = readRawGit(
|
|
23537
|
+
repoRoot,
|
|
23538
|
+
["ls-files", "--others", "--exclude-standard", "-z"],
|
|
23539
|
+
"git_status_unreadable"
|
|
23540
|
+
);
|
|
23541
|
+
const untrackedPaths = untrackedRaw.split("\0").filter(Boolean);
|
|
23542
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
23543
|
+
updateLengthPrefixed(hash, porcelain);
|
|
23544
|
+
updateLengthPrefixed(hash, proposedTree);
|
|
23545
|
+
updateLengthPrefixed(hash, unstagedPatch);
|
|
23546
|
+
updateLengthPrefixed(hash, stagedPatch);
|
|
23547
|
+
for (let index = 0; index < untrackedPaths.length; index += 128) {
|
|
23548
|
+
const paths = untrackedPaths.slice(index, index + 128);
|
|
23549
|
+
const hashes = readRawGit(
|
|
23550
|
+
repoRoot,
|
|
23551
|
+
["hash-object", "--no-filters", "--", ...paths],
|
|
23552
|
+
"git_status_unreadable"
|
|
23553
|
+
);
|
|
23554
|
+
updateLengthPrefixed(hash, paths.join("\0"));
|
|
23555
|
+
updateLengthPrefixed(hash, hashes);
|
|
23556
|
+
}
|
|
23557
|
+
return hash.digest("hex");
|
|
23558
|
+
}
|
|
23559
|
+
function readStatus(repoRoot) {
|
|
23560
|
+
const headResult = runGitResult(repoRoot, ["rev-parse", "HEAD"]);
|
|
23561
|
+
const headSha = headResult.status === 0 ? headResult.stdout.trim() || null : null;
|
|
23562
|
+
const branchResult = runGitResult(repoRoot, ["branch", "--show-current"]);
|
|
23563
|
+
const branch = branchResult.status === 0 ? branchResult.stdout.trim() || null : null;
|
|
23564
|
+
const proposedTree = proposedTreeSha(repoRoot, headSha);
|
|
23565
|
+
const porcelain = runGit(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
23566
|
+
const displayStatus = runGit(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
|
|
23567
|
+
const upstreamResult = runGitResult(repoRoot, [
|
|
23568
|
+
"rev-parse",
|
|
23569
|
+
"--abbrev-ref",
|
|
23570
|
+
"--symbolic-full-name",
|
|
23571
|
+
"@{upstream}"
|
|
23572
|
+
]);
|
|
23573
|
+
const upstream = upstreamResult.status === 0 ? upstreamResult.stdout.trim() || null : null;
|
|
23574
|
+
let ahead = null;
|
|
23575
|
+
let behind = null;
|
|
23576
|
+
if (upstream) {
|
|
23577
|
+
const counts = runGit(repoRoot, [
|
|
23578
|
+
"rev-list",
|
|
23579
|
+
"--left-right",
|
|
23580
|
+
"--count",
|
|
23581
|
+
`${upstream}...HEAD`
|
|
23582
|
+
]).split(/\s+/);
|
|
23583
|
+
const parsedBehind = Number.parseInt(counts[0] ?? "", 10);
|
|
23584
|
+
const parsedAhead = Number.parseInt(counts[1] ?? "", 10);
|
|
23585
|
+
behind = Number.isNaN(parsedBehind) ? null : parsedBehind;
|
|
23586
|
+
ahead = Number.isNaN(parsedAhead) ? null : parsedAhead;
|
|
23587
|
+
}
|
|
23588
|
+
return {
|
|
23589
|
+
repoRoot,
|
|
23590
|
+
branch,
|
|
23591
|
+
headSha,
|
|
23592
|
+
dirty: porcelain.length > 0,
|
|
23593
|
+
filesChanged: displayStatus ? displayStatus.split("\n").length : 0,
|
|
23594
|
+
ahead,
|
|
23595
|
+
behind,
|
|
23596
|
+
hasUpstream: upstream !== null,
|
|
23597
|
+
upstream,
|
|
23598
|
+
statusFingerprint: statusFingerprint(repoRoot, porcelain, proposedTree),
|
|
23599
|
+
proposedTreeSha: proposedTree
|
|
23600
|
+
};
|
|
23601
|
+
}
|
|
23602
|
+
function assertExpectedBranch(status, expectedBranch) {
|
|
23603
|
+
assertValidBranch(expectedBranch);
|
|
23604
|
+
if (status.branch !== expectedBranch) {
|
|
23605
|
+
throw new BindingSwitchGitError(
|
|
23606
|
+
"source_changed_since_approval",
|
|
23607
|
+
`Source branch changed from ${expectedBranch} to ${status.branch ?? "detached HEAD"}`
|
|
23608
|
+
);
|
|
23609
|
+
}
|
|
23610
|
+
}
|
|
23611
|
+
function assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch) {
|
|
23612
|
+
assertCommitSha(expectedHeadSha, "expectedHeadSha");
|
|
23613
|
+
if (expectedBranch !== void 0) assertExpectedBranch(status, expectedBranch);
|
|
23614
|
+
if (status.headSha !== expectedHeadSha) {
|
|
23615
|
+
throw new BindingSwitchGitError(
|
|
23616
|
+
"source_changed_since_approval",
|
|
23617
|
+
`Source HEAD changed from ${expectedHeadSha} to ${status.headSha ?? "unborn"}`
|
|
23618
|
+
);
|
|
23619
|
+
}
|
|
23620
|
+
if (expectedStatusFingerprint !== void 0 && status.statusFingerprint !== expectedStatusFingerprint) {
|
|
23621
|
+
throw new BindingSwitchGitError(
|
|
23622
|
+
"source_changed_since_approval",
|
|
23623
|
+
"Source files changed after the migration inspection"
|
|
23624
|
+
);
|
|
23625
|
+
}
|
|
23626
|
+
}
|
|
23627
|
+
function commitHasSwitchTrailers(repoRoot, switchId, repoKey) {
|
|
23628
|
+
const message = runGitResult(repoRoot, ["show", "-s", "--format=%B", "HEAD"]);
|
|
23629
|
+
if (message.status !== 0) return false;
|
|
23630
|
+
const lines = message.stdout.split(/\r?\n/);
|
|
23631
|
+
return lines.includes(`Alan-Switch-Id: ${switchId}`) && lines.includes(`Alan-Repository-Id: ${repoKey}`);
|
|
23632
|
+
}
|
|
23633
|
+
async function checkpoint(params) {
|
|
23634
|
+
const { switchId, repoKey } = commonRequest(params);
|
|
23635
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23636
|
+
const expectedHeadSha = requiredString(params, "expectedHeadSha");
|
|
23637
|
+
const expectedStatusFingerprint = requiredString(params, "expectedStatusFingerprint");
|
|
23638
|
+
const expectedProposedTreeSha = requiredString(params, "expectedProposedTreeSha");
|
|
23639
|
+
assertCommitSha(expectedProposedTreeSha, "expectedProposedTreeSha");
|
|
23640
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23641
|
+
const status = readStatus(repoRoot);
|
|
23642
|
+
assertExpectedBranch(status, expectedBranch);
|
|
23643
|
+
if (commitHasSwitchTrailers(repoRoot, switchId, repoKey)) {
|
|
23644
|
+
if (!status.headSha) {
|
|
23645
|
+
throw new BindingSwitchGitError("source_head_unreadable", "Checkpoint HEAD is unreadable");
|
|
23646
|
+
}
|
|
23647
|
+
const expectedRef2 = `refs/heads/${expectedBranch}`;
|
|
23648
|
+
const baseline2 = createCheckpointIndexBaseline(repoRoot, status.headSha);
|
|
23649
|
+
let locks2 = null;
|
|
23650
|
+
try {
|
|
23651
|
+
locks2 = acquireCheckpointRepositoryLocks(repoRoot);
|
|
23652
|
+
assertCheckpointHead(repoRoot, expectedRef2, status.headSha);
|
|
23653
|
+
installCheckpointIndexBaseline(locks2, baseline2.indexPath);
|
|
23654
|
+
return { repoRoot, sha: status.headSha, replayed: true };
|
|
23655
|
+
} finally {
|
|
23656
|
+
if (locks2) releaseCheckpointRepositoryLocks(locks2);
|
|
23657
|
+
(0, import_node_fs4.rmSync)(baseline2.tempDir, { recursive: true, force: true });
|
|
23658
|
+
}
|
|
23659
|
+
}
|
|
23660
|
+
assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch);
|
|
23661
|
+
if (status.proposedTreeSha !== expectedProposedTreeSha) {
|
|
23662
|
+
throw new BindingSwitchGitError(
|
|
23663
|
+
"source_changed_since_approval",
|
|
23664
|
+
"The proposed checkpoint tree changed after migration approval"
|
|
23665
|
+
);
|
|
23666
|
+
}
|
|
23667
|
+
if (!status.dirty) {
|
|
23668
|
+
if (!status.headSha) {
|
|
23669
|
+
throw new BindingSwitchGitError("source_head_unreadable", "Source HEAD is unreadable");
|
|
23670
|
+
}
|
|
23671
|
+
return { repoRoot, sha: status.headSha, replayed: true };
|
|
23672
|
+
}
|
|
23673
|
+
const commitMessage = requiredCommitMessage(params).trim();
|
|
23674
|
+
const authorName = requiredString(params, "authorName");
|
|
23675
|
+
const authorEmail = requiredString(params, "authorEmail");
|
|
23676
|
+
const expectedHeadTree = runGit(
|
|
23677
|
+
repoRoot,
|
|
23678
|
+
["rev-parse", `${expectedHeadSha}^{tree}`],
|
|
23679
|
+
"source_head_unreadable"
|
|
23680
|
+
);
|
|
23681
|
+
if (expectedHeadTree === expectedProposedTreeSha) {
|
|
23682
|
+
throw new BindingSwitchGitError(
|
|
23683
|
+
"checkpoint_empty",
|
|
23684
|
+
"The inspected changes did not produce a checkpoint commit"
|
|
23685
|
+
);
|
|
23686
|
+
}
|
|
23687
|
+
const proposedTreeObject = runGitResult(repoRoot, [
|
|
23688
|
+
"cat-file",
|
|
23689
|
+
"-e",
|
|
23690
|
+
`${expectedProposedTreeSha}^{tree}`
|
|
23691
|
+
]);
|
|
23692
|
+
if (proposedTreeObject.status !== 0) {
|
|
23693
|
+
throw new BindingSwitchGitError(
|
|
23694
|
+
"checkpoint_tree_unavailable",
|
|
23695
|
+
"The approved checkpoint tree is no longer available"
|
|
23696
|
+
);
|
|
23697
|
+
}
|
|
23698
|
+
await waitForCheckpointTestBarrier();
|
|
23699
|
+
const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
|
|
23700
|
+
const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_head_unreadable");
|
|
23701
|
+
if (currentBranch !== expectedBranch || currentHead !== expectedHeadSha) {
|
|
23702
|
+
throw new BindingSwitchGitError(
|
|
23703
|
+
"source_changed_since_approval",
|
|
23704
|
+
"Source HEAD or branch changed during checkpoint preparation"
|
|
23705
|
+
);
|
|
23706
|
+
}
|
|
23707
|
+
const message = `${commitMessage}
|
|
23708
|
+
|
|
23709
|
+
Alan-Switch-Id: ${switchId}
|
|
23710
|
+
Alan-Repository-Id: ${repoKey}`;
|
|
23711
|
+
const sha = runGitWithOptions(
|
|
23712
|
+
repoRoot,
|
|
23713
|
+
["commit-tree", expectedProposedTreeSha, "-p", expectedHeadSha],
|
|
23714
|
+
{
|
|
23715
|
+
env: {
|
|
23716
|
+
GIT_AUTHOR_EMAIL: authorEmail,
|
|
23717
|
+
GIT_AUTHOR_NAME: authorName,
|
|
23718
|
+
GIT_COMMITTER_EMAIL: authorEmail,
|
|
23719
|
+
GIT_COMMITTER_NAME: authorName
|
|
23720
|
+
},
|
|
23721
|
+
input: `${message}
|
|
23722
|
+
`
|
|
23723
|
+
},
|
|
23724
|
+
"checkpoint_commit_failed"
|
|
23725
|
+
);
|
|
23726
|
+
assertCommitSha(sha, "checkpointSha");
|
|
23727
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
23728
|
+
const currentRef = runGit(
|
|
23729
|
+
repoRoot,
|
|
23730
|
+
["symbolic-ref", "-q", "HEAD"],
|
|
23731
|
+
"source_changed_since_approval"
|
|
23732
|
+
);
|
|
23733
|
+
if (currentRef !== expectedRef) {
|
|
23734
|
+
throw new BindingSwitchGitError(
|
|
23735
|
+
"source_changed_since_approval",
|
|
23736
|
+
"Source branch changed during checkpoint preparation"
|
|
23737
|
+
);
|
|
23738
|
+
}
|
|
23739
|
+
await waitForCheckpointFinalizeTestBarrier();
|
|
23740
|
+
const baseline = createCheckpointIndexBaseline(repoRoot, sha);
|
|
23741
|
+
let locks = null;
|
|
23742
|
+
let refAdvanced = false;
|
|
23743
|
+
let indexInstalled = false;
|
|
23744
|
+
try {
|
|
23745
|
+
locks = acquireCheckpointRepositoryLocks(repoRoot);
|
|
23746
|
+
assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha);
|
|
23747
|
+
releaseCheckpointHeadLock(locks);
|
|
23748
|
+
runGit(
|
|
23749
|
+
repoRoot,
|
|
23750
|
+
["update-ref", expectedRef, sha, expectedHeadSha],
|
|
23751
|
+
"source_changed_since_approval"
|
|
23752
|
+
);
|
|
23753
|
+
refAdvanced = true;
|
|
23754
|
+
acquireCheckpointHeadLock(locks);
|
|
23755
|
+
assertCheckpointHead(repoRoot, expectedRef, sha);
|
|
23756
|
+
installCheckpointIndexBaseline(locks, baseline.indexPath);
|
|
23757
|
+
indexInstalled = true;
|
|
23758
|
+
return { repoRoot, sha, replayed: false };
|
|
23759
|
+
} finally {
|
|
23760
|
+
if (refAdvanced && !indexInstalled) {
|
|
23761
|
+
if (locks) releaseCheckpointHeadLock(locks);
|
|
23762
|
+
runGitResult(repoRoot, ["update-ref", expectedRef, expectedHeadSha, sha]);
|
|
23763
|
+
}
|
|
23764
|
+
if (locks) releaseCheckpointRepositoryLocks(locks);
|
|
23765
|
+
(0, import_node_fs4.rmSync)(baseline.tempDir, { recursive: true, force: true });
|
|
23766
|
+
}
|
|
23767
|
+
}
|
|
23768
|
+
function resolveRemoteBranch(repoRoot, params) {
|
|
23769
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23770
|
+
const requestedBranch = optionalString(params, "branch");
|
|
23771
|
+
if (requestedBranch && requestedBranch !== expectedBranch) {
|
|
23772
|
+
throw new BindingSwitchGitError("invalid_git_request", "branch must match expectedBranch");
|
|
23773
|
+
}
|
|
23774
|
+
const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
|
|
23775
|
+
const branch = expectedBranch;
|
|
23776
|
+
assertValidBranch(branch);
|
|
23777
|
+
if (currentBranch !== expectedBranch) {
|
|
23778
|
+
throw new BindingSwitchGitError(
|
|
23779
|
+
"source_changed_since_approval",
|
|
23780
|
+
`Source branch changed from ${expectedBranch} to ${currentBranch || "detached HEAD"}`
|
|
23781
|
+
);
|
|
23782
|
+
}
|
|
23783
|
+
const { remote } = verifiedRemote(repoRoot, params, true);
|
|
23784
|
+
return { branch, remote };
|
|
23785
|
+
}
|
|
23786
|
+
async function push(params) {
|
|
23787
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23788
|
+
const expectedHeadSha = requiredString(params, "expectedHeadSha");
|
|
23789
|
+
const expectedBranch = requiredString(params, "expectedBranch");
|
|
23790
|
+
const status = readStatus(repoRoot);
|
|
23791
|
+
assertSourcePreconditions(status, expectedHeadSha, void 0, expectedBranch);
|
|
23792
|
+
if (status.dirty) {
|
|
23793
|
+
throw new BindingSwitchGitError(
|
|
23794
|
+
"source_dirty_after_checkpoint",
|
|
23795
|
+
"The source has uncommitted changes and cannot be published"
|
|
23796
|
+
);
|
|
23797
|
+
}
|
|
23798
|
+
const { branch, remote } = resolveRemoteBranch(repoRoot, params);
|
|
23799
|
+
const latest = readStatus(repoRoot);
|
|
23800
|
+
assertSourcePreconditions(latest, expectedHeadSha, void 0, expectedBranch);
|
|
23801
|
+
if (latest.dirty) {
|
|
23802
|
+
throw new BindingSwitchGitError(
|
|
23803
|
+
"source_dirty_after_checkpoint",
|
|
23804
|
+
"The source has uncommitted changes and cannot be published"
|
|
23805
|
+
);
|
|
23806
|
+
}
|
|
23807
|
+
const before = await runNetworkGit(
|
|
23808
|
+
repoRoot,
|
|
23809
|
+
["ls-remote", "--heads", remote, `refs/heads/${branch}`],
|
|
23810
|
+
"git_remote_unavailable"
|
|
23811
|
+
);
|
|
23812
|
+
const remoteBefore = before.trim().split(/\s+/)[0] || null;
|
|
23813
|
+
await runNetworkGit(
|
|
23814
|
+
repoRoot,
|
|
23815
|
+
["push", remote, `${expectedHeadSha}:refs/heads/${expectedBranch}`],
|
|
23816
|
+
"git_push_failed"
|
|
23817
|
+
);
|
|
23818
|
+
const remoteHead = (await runNetworkGit(
|
|
23819
|
+
repoRoot,
|
|
23820
|
+
["ls-remote", "--heads", remote, `refs/heads/${branch}`],
|
|
23821
|
+
"push_verification_failed"
|
|
23822
|
+
)).split(/\s+/)[0]?.trim();
|
|
23823
|
+
if (remoteHead !== expectedHeadSha) {
|
|
23824
|
+
throw new BindingSwitchGitError(
|
|
23825
|
+
"push_verification_failed",
|
|
23826
|
+
`Remote branch ${branch} is at ${remoteHead || "an unreadable SHA"}, expected ${expectedHeadSha}`
|
|
23827
|
+
);
|
|
23828
|
+
}
|
|
23829
|
+
const afterPush = readStatus(repoRoot);
|
|
23830
|
+
if (afterPush.headSha !== expectedHeadSha || afterPush.branch !== expectedBranch || afterPush.dirty || afterPush.statusFingerprint !== latest.statusFingerprint) {
|
|
23831
|
+
throw new BindingSwitchGitError(
|
|
23832
|
+
"source_changed_since_approval",
|
|
23833
|
+
"The source changed while the approved commit was being published",
|
|
23834
|
+
{
|
|
23835
|
+
published: {
|
|
23836
|
+
pushedSha: expectedHeadSha,
|
|
23837
|
+
branch,
|
|
23838
|
+
remote,
|
|
23839
|
+
replayed: remoteBefore === expectedHeadSha
|
|
23840
|
+
}
|
|
23841
|
+
}
|
|
23842
|
+
);
|
|
23843
|
+
}
|
|
23844
|
+
return {
|
|
23845
|
+
repoRoot,
|
|
23846
|
+
pushedSha: expectedHeadSha,
|
|
23847
|
+
branch,
|
|
23848
|
+
remote,
|
|
23849
|
+
replayed: remoteBefore === expectedHeadSha
|
|
23850
|
+
};
|
|
23851
|
+
}
|
|
23852
|
+
async function fetchPublishedBranch(repoRoot, branch, expectedSha, params) {
|
|
23853
|
+
assertValidBranch(branch);
|
|
23854
|
+
assertCommitSha(expectedSha, "expectedSha");
|
|
23855
|
+
const { remote } = verifiedRemote(repoRoot, params, false);
|
|
23856
|
+
await runNetworkGit(
|
|
23857
|
+
repoRoot,
|
|
23858
|
+
["fetch", "--no-tags", remote, `refs/heads/${branch}`],
|
|
23859
|
+
"git_fetch_failed"
|
|
23860
|
+
);
|
|
23861
|
+
const fetchedSha = runGit(repoRoot, ["rev-parse", "FETCH_HEAD"], "target_sha_unavailable");
|
|
23862
|
+
if (fetchedSha !== expectedSha) {
|
|
23863
|
+
throw new BindingSwitchGitError(
|
|
23864
|
+
"target_sha_mismatch",
|
|
23865
|
+
`Fetched ${branch} at ${fetchedSha}, expected the published SHA ${expectedSha}`
|
|
23866
|
+
);
|
|
23867
|
+
}
|
|
23868
|
+
const commit = runGitResult(repoRoot, ["cat-file", "-e", `${expectedSha}^{commit}`]);
|
|
23869
|
+
if (commit.status !== 0) {
|
|
23870
|
+
throw new BindingSwitchGitError(
|
|
23871
|
+
"target_sha_unavailable",
|
|
23872
|
+
`The published SHA ${expectedSha} is not available in the destination repository`
|
|
23873
|
+
);
|
|
23874
|
+
}
|
|
23875
|
+
return { fetchedSha, remote };
|
|
23876
|
+
}
|
|
23877
|
+
async function fetchBranch(params) {
|
|
23878
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23879
|
+
const branch = requiredString(params, "branch");
|
|
23880
|
+
const expectedSha = requiredString(params, "expectedSha");
|
|
23881
|
+
const { fetchedSha, remote } = await fetchPublishedBranch(repoRoot, branch, expectedSha, params);
|
|
23882
|
+
return { repoRoot, fetchedSha, branch, remote };
|
|
23883
|
+
}
|
|
23884
|
+
function isAncestor(repoRoot, ancestor, descendant) {
|
|
23885
|
+
return runGitResult(repoRoot, ["merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
|
|
23886
|
+
}
|
|
23887
|
+
function assertCleanTarget(repoRoot) {
|
|
23888
|
+
if (readStatus(repoRoot).dirty) {
|
|
23889
|
+
throw new BindingSwitchGitError(
|
|
23890
|
+
"target_checkout_dirty",
|
|
23891
|
+
"The destination checkout has uncommitted changes"
|
|
23892
|
+
);
|
|
23893
|
+
}
|
|
23894
|
+
}
|
|
23895
|
+
function assertBranchAvailableAtPath(repoRoot, branch, allowedPath) {
|
|
23896
|
+
const canonicalAllowedPath = canonicalPathWithMissingTail(allowedPath);
|
|
23897
|
+
const conflicting = listWorktrees(repoRoot).find(
|
|
23898
|
+
(worktree) => worktree.branch === branch && worktree.path !== canonicalAllowedPath
|
|
23899
|
+
);
|
|
23900
|
+
if (conflicting) {
|
|
23901
|
+
throw new BindingSwitchGitError(
|
|
23902
|
+
"target_branch_in_use",
|
|
23903
|
+
`Branch ${branch} is already checked out at ${conflicting.path}`
|
|
23904
|
+
);
|
|
23905
|
+
}
|
|
23906
|
+
}
|
|
23907
|
+
function advanceBranchAtCheckout(checkoutRoot, branch, targetSha) {
|
|
23908
|
+
const before = readStatus(checkoutRoot);
|
|
23909
|
+
if (before.branch === branch && before.headSha === targetSha) return true;
|
|
23910
|
+
assertBranchAvailableAtPath(checkoutRoot, branch, checkoutRoot);
|
|
23911
|
+
const branchExists = runGitResult(checkoutRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status === 0;
|
|
23912
|
+
if (branchExists) {
|
|
23913
|
+
const branchHead = runGit(checkoutRoot, ["rev-parse", `refs/heads/${branch}`]);
|
|
23914
|
+
if (!isAncestor(checkoutRoot, branchHead, targetSha)) {
|
|
23915
|
+
throw new BindingSwitchGitError(
|
|
23916
|
+
"target_branch_diverged",
|
|
23917
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
23918
|
+
);
|
|
23919
|
+
}
|
|
23920
|
+
if (before.branch !== branch) {
|
|
23921
|
+
runGit(checkoutRoot, ["switch", branch], "target_checkout_failed");
|
|
23922
|
+
}
|
|
23923
|
+
} else {
|
|
23924
|
+
runGit(checkoutRoot, ["switch", "-c", branch, targetSha], "target_checkout_failed");
|
|
23925
|
+
}
|
|
23926
|
+
const currentHead = runGit(checkoutRoot, ["rev-parse", "HEAD"]);
|
|
23927
|
+
if (currentHead !== targetSha) {
|
|
23928
|
+
if (!isAncestor(checkoutRoot, currentHead, targetSha)) {
|
|
23929
|
+
throw new BindingSwitchGitError(
|
|
23930
|
+
"target_branch_diverged",
|
|
23931
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
23932
|
+
);
|
|
23933
|
+
}
|
|
23934
|
+
runGit(checkoutRoot, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
|
|
23935
|
+
}
|
|
23936
|
+
const verified = readStatus(checkoutRoot);
|
|
23937
|
+
if (verified.branch !== branch || verified.headSha !== targetSha) {
|
|
23938
|
+
throw new BindingSwitchGitError(
|
|
23939
|
+
"target_verification_failed",
|
|
23940
|
+
"The destination checkout did not land on the published branch and SHA"
|
|
23941
|
+
);
|
|
23942
|
+
}
|
|
23943
|
+
return false;
|
|
23944
|
+
}
|
|
23945
|
+
async function prepareCheckout(params) {
|
|
23946
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23947
|
+
const branch = requiredString(params, "branch");
|
|
23948
|
+
const targetSha = requiredString(params, "targetSha");
|
|
23949
|
+
await fetchPublishedBranch(repoRoot, branch, targetSha, params);
|
|
23950
|
+
assertCleanTarget(repoRoot);
|
|
23951
|
+
const replayed = advanceBranchAtCheckout(repoRoot, branch, targetSha);
|
|
23952
|
+
return {
|
|
23953
|
+
repoRoot,
|
|
23954
|
+
path: repoRoot,
|
|
23955
|
+
branch,
|
|
23956
|
+
headSha: targetSha,
|
|
23957
|
+
replayed
|
|
23958
|
+
};
|
|
23959
|
+
}
|
|
23960
|
+
async function prepareWorktree(params) {
|
|
23961
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
23962
|
+
const registrations = listWorktrees(repoRoot);
|
|
23963
|
+
const projectRoot = registrations[0]?.path;
|
|
23964
|
+
if (!projectRoot || projectRoot !== repoRoot) {
|
|
23965
|
+
throw new BindingSwitchGitError(
|
|
23966
|
+
"project_checkout_required",
|
|
23967
|
+
"Worktree preparation requires the Project's main checkout"
|
|
23968
|
+
);
|
|
23969
|
+
}
|
|
23970
|
+
const worktreePath = (0, import_node_path4.resolve)(requiredString(params, "worktreePath"));
|
|
23971
|
+
const managedRoot = alanWorktreeRoot(projectRoot);
|
|
23972
|
+
const pathError = "The destination worktree path is outside the Alan-managed worktree directory";
|
|
23973
|
+
const intendedManagedRoot = (0, import_node_path4.join)(
|
|
23974
|
+
import_node_fs4.realpathSync.native((0, import_node_path4.dirname)(managedRoot)),
|
|
23975
|
+
(0, import_node_path4.basename)(managedRoot)
|
|
23976
|
+
);
|
|
23977
|
+
if (canonicalPathWithMissingTail(managedRoot) !== intendedManagedRoot) {
|
|
23978
|
+
throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
|
|
23979
|
+
}
|
|
23980
|
+
assertCanonicalPathContained(
|
|
23981
|
+
worktreePath,
|
|
23982
|
+
managedRoot,
|
|
23983
|
+
"worktree_path_not_authorized",
|
|
23984
|
+
pathError
|
|
23985
|
+
);
|
|
23986
|
+
const canonicalWorktreePath = canonicalPathWithMissingTail(worktreePath);
|
|
23987
|
+
if (canonicalWorktreePath === canonicalPathWithMissingTail(managedRoot)) {
|
|
23988
|
+
throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
|
|
23989
|
+
}
|
|
23990
|
+
const branch = requiredString(params, "branch");
|
|
23991
|
+
const targetSha = requiredString(params, "targetSha");
|
|
23992
|
+
await fetchPublishedBranch(repoRoot, branch, targetSha, params);
|
|
23993
|
+
const refreshed = listWorktrees(repoRoot);
|
|
23994
|
+
const registeredAtPath = refreshed.find(({ path }) => path === canonicalWorktreePath);
|
|
23995
|
+
const registeredForBranch = refreshed.find(({ branch: value2 }) => value2 === branch);
|
|
23996
|
+
if (registeredAtPath) {
|
|
23997
|
+
if (registeredAtPath.branch !== branch) {
|
|
23998
|
+
throw new BindingSwitchGitError(
|
|
23999
|
+
"worktree_path_conflict",
|
|
24000
|
+
"The destination path is registered to a different branch"
|
|
24001
|
+
);
|
|
24002
|
+
}
|
|
24003
|
+
assertCleanTarget(worktreePath);
|
|
24004
|
+
const replayed = advanceBranchAtCheckout(worktreePath, branch, targetSha);
|
|
24005
|
+
return {
|
|
24006
|
+
repoRoot: worktreePath,
|
|
24007
|
+
path: worktreePath,
|
|
24008
|
+
branch,
|
|
24009
|
+
headSha: targetSha,
|
|
24010
|
+
replayed
|
|
24011
|
+
};
|
|
24012
|
+
}
|
|
24013
|
+
if (registeredForBranch) {
|
|
24014
|
+
throw new BindingSwitchGitError(
|
|
24015
|
+
"target_branch_in_use",
|
|
24016
|
+
`Branch ${branch} is already checked out at ${registeredForBranch.path}`
|
|
24017
|
+
);
|
|
24018
|
+
}
|
|
24019
|
+
if ((0, import_node_fs4.existsSync)(worktreePath)) {
|
|
24020
|
+
throw new BindingSwitchGitError(
|
|
24021
|
+
"worktree_path_conflict",
|
|
24022
|
+
"The destination worktree path already exists and is not registered"
|
|
24023
|
+
);
|
|
24024
|
+
}
|
|
24025
|
+
const branchExists = runGitResult(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status === 0;
|
|
24026
|
+
if (branchExists) {
|
|
24027
|
+
const branchHead = runGit(repoRoot, ["rev-parse", `refs/heads/${branch}`]);
|
|
24028
|
+
if (!isAncestor(repoRoot, branchHead, targetSha)) {
|
|
24029
|
+
throw new BindingSwitchGitError(
|
|
24030
|
+
"target_branch_diverged",
|
|
24031
|
+
`Local branch ${branch} cannot fast-forward to the published SHA`
|
|
24032
|
+
);
|
|
24033
|
+
}
|
|
24034
|
+
}
|
|
24035
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(worktreePath), { recursive: true });
|
|
24036
|
+
if (branchExists) {
|
|
24037
|
+
runGit(repoRoot, ["worktree", "add", worktreePath, branch], "worktree_prepare_failed");
|
|
24038
|
+
const currentHead = runGit(worktreePath, ["rev-parse", "HEAD"]);
|
|
24039
|
+
if (currentHead !== targetSha) {
|
|
24040
|
+
runGit(worktreePath, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
|
|
24041
|
+
}
|
|
24042
|
+
} else {
|
|
24043
|
+
runGit(
|
|
24044
|
+
repoRoot,
|
|
24045
|
+
["worktree", "add", "-b", branch, worktreePath, targetSha],
|
|
24046
|
+
"worktree_prepare_failed"
|
|
24047
|
+
);
|
|
24048
|
+
}
|
|
24049
|
+
const createdRoot = resolveRepositoryRoot(worktreePath);
|
|
24050
|
+
assertCanonicalPathContained(createdRoot, managedRoot, "worktree_path_not_authorized", pathError);
|
|
24051
|
+
if (createdRoot !== import_node_fs4.realpathSync.native(worktreePath)) {
|
|
24052
|
+
throw new BindingSwitchGitError(
|
|
24053
|
+
"worktree_path_changed",
|
|
24054
|
+
"The destination worktree resolved to an unexpected path after creation"
|
|
24055
|
+
);
|
|
24056
|
+
}
|
|
24057
|
+
const verified = readStatus(createdRoot);
|
|
24058
|
+
if (verified.branch !== branch || verified.headSha !== targetSha) {
|
|
24059
|
+
throw new BindingSwitchGitError(
|
|
24060
|
+
"target_verification_failed",
|
|
24061
|
+
"The destination worktree did not land on the published branch and SHA"
|
|
24062
|
+
);
|
|
24063
|
+
}
|
|
24064
|
+
return {
|
|
24065
|
+
repoRoot: worktreePath,
|
|
24066
|
+
path: worktreePath,
|
|
24067
|
+
branch,
|
|
24068
|
+
headSha: targetSha,
|
|
24069
|
+
replayed: false
|
|
24070
|
+
};
|
|
24071
|
+
}
|
|
24072
|
+
function verifyHead(params) {
|
|
24073
|
+
const repoRoot = resolveAuthorizedRepository(params, true);
|
|
24074
|
+
const expectedSha = requiredString(params, "expectedSha");
|
|
24075
|
+
assertCommitSha(expectedSha, "expectedSha");
|
|
24076
|
+
assertCleanTarget(repoRoot);
|
|
24077
|
+
const headSha = runGit(repoRoot, ["rev-parse", "HEAD"], "target_head_unreadable");
|
|
24078
|
+
if (headSha !== expectedSha) {
|
|
24079
|
+
throw new BindingSwitchGitError(
|
|
24080
|
+
"verify_head_mismatch",
|
|
24081
|
+
`Destination HEAD is ${headSha}, expected ${expectedSha}`
|
|
24082
|
+
);
|
|
24083
|
+
}
|
|
24084
|
+
return { repoRoot, headSha, verified: true };
|
|
24085
|
+
}
|
|
24086
|
+
function statusBindingSwitchGit(params) {
|
|
24087
|
+
return readStatus(resolveAuthorizedRepository(params));
|
|
24088
|
+
}
|
|
24089
|
+
async function executeBindingSwitchGitRpc(method, params) {
|
|
24090
|
+
switch (method) {
|
|
24091
|
+
case "git.status":
|
|
24092
|
+
return statusBindingSwitchGit(params);
|
|
24093
|
+
case "git.checkpoint":
|
|
24094
|
+
return checkpoint(params);
|
|
24095
|
+
case "git.push":
|
|
24096
|
+
return push(params);
|
|
24097
|
+
case "git.fetch":
|
|
24098
|
+
return fetchBranch(params);
|
|
24099
|
+
case "git.prepare_checkout":
|
|
24100
|
+
return prepareCheckout(params);
|
|
24101
|
+
case "git.prepare_worktree":
|
|
24102
|
+
return prepareWorktree(params);
|
|
24103
|
+
case "git.verify_head":
|
|
24104
|
+
return verifyHead(params);
|
|
24105
|
+
}
|
|
24106
|
+
}
|
|
24107
|
+
|
|
24108
|
+
// src/cli-ensure-install.ts
|
|
24109
|
+
var import_node_child_process4 = require("child_process");
|
|
24110
|
+
var import_node_os5 = require("os");
|
|
24111
|
+
var CLI_RUNTIME_VERSIONS_ENV = "ALAN_CLI_RUNTIME_VERSIONS";
|
|
24112
|
+
var INSTALLABLE_PROVIDER_PACKAGES = {
|
|
24113
|
+
claude_cli: { npmPackage: "@anthropic-ai/claude-code", command: "claude", label: "Claude Code" },
|
|
24114
|
+
codex_app_server: { npmPackage: "@openai/codex", command: "codex", label: "Codex" },
|
|
24115
|
+
copilot_cli: { npmPackage: "@github/copilot", command: "copilot", label: "Copilot" },
|
|
24116
|
+
opencode_cli: { npmPackage: "opencode-ai", command: "opencode", label: "opencode" }
|
|
24117
|
+
};
|
|
24118
|
+
var INSTALL_TIMEOUT_MS = 12e4;
|
|
24119
|
+
var installOutcomes = /* @__PURE__ */ new Map();
|
|
24120
|
+
function canInstallProviderCli(backendKind) {
|
|
24121
|
+
return Boolean(backendKind && INSTALLABLE_PROVIDER_PACKAGES[backendKind]);
|
|
24122
|
+
}
|
|
24123
|
+
function pinnedVersion(npmPackage) {
|
|
24124
|
+
const raw = process.env[CLI_RUNTIME_VERSIONS_ENV];
|
|
24125
|
+
if (!raw) return "latest";
|
|
24126
|
+
try {
|
|
24127
|
+
const parsed = JSON.parse(raw);
|
|
24128
|
+
const version = parsed[npmPackage];
|
|
24129
|
+
if (typeof version === "string" && version.trim()) return version.trim();
|
|
24130
|
+
} catch {
|
|
24131
|
+
}
|
|
24132
|
+
return "latest";
|
|
24133
|
+
}
|
|
24134
|
+
function runNpmInstall(installSpec, env, timeoutMs) {
|
|
24135
|
+
return new Promise((resolve7) => {
|
|
24136
|
+
const isWin = (0, import_node_os5.platform)() === "win32";
|
|
24137
|
+
let child;
|
|
24138
|
+
try {
|
|
24139
|
+
child = (0, import_node_child_process4.spawn)("npm", ["install", "-g", installSpec], {
|
|
24140
|
+
env,
|
|
24141
|
+
shell: isWin,
|
|
24142
|
+
windowsHide: isWin ? true : void 0,
|
|
24143
|
+
timeout: timeoutMs,
|
|
24144
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
24145
|
+
});
|
|
24146
|
+
} catch (error) {
|
|
24147
|
+
resolve7({ status: null, stdout: "", stderr: "", error });
|
|
24148
|
+
return;
|
|
24149
|
+
}
|
|
24150
|
+
let stdout = "";
|
|
24151
|
+
let stderr = "";
|
|
24152
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
24153
|
+
stdout += chunk;
|
|
24154
|
+
});
|
|
24155
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
24156
|
+
stderr += chunk;
|
|
24157
|
+
});
|
|
24158
|
+
child.once("error", (error) => resolve7({ status: null, stdout, stderr, error }));
|
|
24159
|
+
child.once(
|
|
24160
|
+
"close",
|
|
24161
|
+
(status, signal) => resolve7({
|
|
24162
|
+
status,
|
|
24163
|
+
stdout,
|
|
24164
|
+
stderr,
|
|
24165
|
+
// Spawn `timeout` kills by signal without an `error` event; surface it
|
|
24166
|
+
// so a timed-out install is reported as a failure, not status null + ok.
|
|
24167
|
+
...signal ? { error: new Error(`npm install killed by ${signal}`) } : {}
|
|
24168
|
+
})
|
|
24169
|
+
);
|
|
24170
|
+
});
|
|
24171
|
+
}
|
|
24172
|
+
async function attemptInstall(spec, options) {
|
|
24173
|
+
const installSpec = `${spec.npmPackage}@${pinnedVersion(spec.npmPackage)}`;
|
|
24174
|
+
const notify = options?.notify ?? ((message) => console.info(`[cli-ensure-install] ${message}`));
|
|
24175
|
+
notify(`Installing ${spec.label} CLI (${installSpec})\u2026`);
|
|
24176
|
+
const runner = options?.runner ?? runNpmInstall;
|
|
24177
|
+
const result = await runner(installSpec, getDaemonCliEnvironment(), INSTALL_TIMEOUT_MS);
|
|
24178
|
+
if (result.error || result.status !== 0) {
|
|
24179
|
+
console.warn(`[cli-ensure-install] npm install -g ${installSpec} failed`, {
|
|
24180
|
+
status: result.status,
|
|
24181
|
+
error: result.error?.message,
|
|
24182
|
+
output: `${result.stderr || result.stdout}`.trim().slice(-500)
|
|
24183
|
+
});
|
|
24184
|
+
notify(`Failed to install ${spec.label} CLI (${installSpec}).`);
|
|
24185
|
+
return false;
|
|
24186
|
+
}
|
|
24187
|
+
invalidateResolvedCli(spec.command);
|
|
24188
|
+
notify(`Installed ${spec.label} CLI (${installSpec}).`);
|
|
24189
|
+
return true;
|
|
24190
|
+
}
|
|
24191
|
+
function ensureProviderCliInstalled(backendKind, options) {
|
|
24192
|
+
if (!backendKind) return Promise.resolve(false);
|
|
24193
|
+
const spec = INSTALLABLE_PROVIDER_PACKAGES[backendKind];
|
|
24194
|
+
if (!spec) return Promise.resolve(false);
|
|
24195
|
+
const existing = installOutcomes.get(spec.npmPackage);
|
|
24196
|
+
if (existing) return existing;
|
|
24197
|
+
const attempt = attemptInstall(spec, options).catch((error) => {
|
|
24198
|
+
console.warn(`[cli-ensure-install] install attempt for ${spec.npmPackage} threw`, error);
|
|
24199
|
+
return false;
|
|
24200
|
+
});
|
|
24201
|
+
installOutcomes.set(spec.npmPackage, attempt);
|
|
24202
|
+
return attempt;
|
|
24203
|
+
}
|
|
24204
|
+
|
|
24205
|
+
// src/computer-readiness.ts
|
|
24206
|
+
var import_node_fs6 = require("fs");
|
|
24207
|
+
|
|
24208
|
+
// src/runtime-config.ts
|
|
24209
|
+
var import_node_fs5 = require("fs");
|
|
24210
|
+
var import_node_os6 = require("os");
|
|
24211
|
+
var import_node_path5 = require("path");
|
|
24212
|
+
var DEFAULT_AGENT_DIR = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".alan", "agent");
|
|
24213
|
+
var LEGACY_AGENT_DIR = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".aiden", "agent");
|
|
24214
|
+
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Map([
|
|
24215
|
+
["apiUrl", "string"],
|
|
24216
|
+
["wsUrl", "string"],
|
|
24217
|
+
["endpointProfile", "string"],
|
|
24218
|
+
["teamId", "string"],
|
|
24219
|
+
["runtimeId", "string"],
|
|
24220
|
+
["runtimeToken", "string"],
|
|
24221
|
+
["localApiPort", "number"],
|
|
24222
|
+
["localApiToken", "string"],
|
|
24223
|
+
["displayName", "string"]
|
|
24224
|
+
]);
|
|
24225
|
+
function sanitizeAgentConfig(config) {
|
|
24226
|
+
const sanitized = { ...config };
|
|
24227
|
+
delete sanitized.teamId;
|
|
24228
|
+
delete sanitized.organizationId;
|
|
24229
|
+
delete sanitized.currentTeamId;
|
|
24230
|
+
delete sanitized.currentOrganizationId;
|
|
24231
|
+
return sanitized;
|
|
24232
|
+
}
|
|
24233
|
+
function isValidAgentConfig(configPath) {
|
|
24234
|
+
try {
|
|
24235
|
+
const stat = (0, import_node_fs5.lstatSync)(configPath);
|
|
24236
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
24237
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(configPath, "utf8"));
|
|
24238
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
24239
|
+
const entries = Object.entries(parsed);
|
|
24240
|
+
return entries.some(([key]) => AGENT_CONFIG_KEYS.has(key)) && entries.every(([key, value2]) => {
|
|
24241
|
+
const expectedType = AGENT_CONFIG_KEYS.get(key);
|
|
24242
|
+
return expectedType === void 0 || typeof value2 === expectedType;
|
|
24243
|
+
});
|
|
24244
|
+
} catch {
|
|
24245
|
+
return false;
|
|
24246
|
+
}
|
|
24247
|
+
}
|
|
24248
|
+
function migrateLegacyAgentConfig(input) {
|
|
24249
|
+
const profile = normalizeAgentEndpointProfile(input?.profile ?? null) ?? "production";
|
|
24250
|
+
const alanBaseDir = input?.alanBaseDir ?? DEFAULT_AGENT_DIR;
|
|
24251
|
+
const legacyBaseDir = input?.legacyBaseDir ?? LEGACY_AGENT_DIR;
|
|
24252
|
+
const fileName = resolveAgentConfigFileName(profile);
|
|
24253
|
+
const legacyPath = (0, import_node_path5.join)(legacyBaseDir, fileName);
|
|
24254
|
+
const alanPath = (0, import_node_path5.join)(alanBaseDir, fileName);
|
|
24255
|
+
if ((0, import_node_fs5.existsSync)(alanPath) || !isValidAgentConfig(legacyPath)) return false;
|
|
24256
|
+
let copied = false;
|
|
24257
|
+
try {
|
|
24258
|
+
const sourceDirectoryStat = (0, import_node_fs5.lstatSync)(legacyBaseDir);
|
|
24259
|
+
const sourceFileStat = (0, import_node_fs5.lstatSync)(legacyPath);
|
|
24260
|
+
if (!sourceDirectoryStat.isDirectory() || sourceDirectoryStat.isSymbolicLink()) return false;
|
|
24261
|
+
const alanDirectoryExisted = (0, import_node_fs5.existsSync)(alanBaseDir);
|
|
24262
|
+
if (alanDirectoryExisted) {
|
|
24263
|
+
const alanDirectoryStat = (0, import_node_fs5.lstatSync)(alanBaseDir);
|
|
24264
|
+
if (!alanDirectoryStat.isDirectory() || alanDirectoryStat.isSymbolicLink()) return false;
|
|
24265
|
+
}
|
|
24266
|
+
(0, import_node_fs5.mkdirSync)(alanBaseDir, { recursive: true, mode: sourceDirectoryStat.mode & 511 });
|
|
24267
|
+
if (!alanDirectoryExisted) (0, import_node_fs5.chmodSync)(alanBaseDir, sourceDirectoryStat.mode & 511);
|
|
24268
|
+
(0, import_node_fs5.copyFileSync)(legacyPath, alanPath, import_node_fs5.constants.COPYFILE_EXCL);
|
|
24269
|
+
copied = true;
|
|
24270
|
+
const migrated = sanitizeAgentConfig(
|
|
24271
|
+
JSON.parse((0, import_node_fs5.readFileSync)(alanPath, "utf8"))
|
|
24272
|
+
);
|
|
24273
|
+
(0, import_node_fs5.writeFileSync)(alanPath, JSON.stringify(migrated, null, 2));
|
|
24274
|
+
(0, import_node_fs5.chmodSync)(alanPath, sourceFileStat.mode & 511);
|
|
24275
|
+
(0, import_node_fs5.utimesSync)(alanPath, sourceFileStat.atime, sourceFileStat.mtime);
|
|
24276
|
+
if (!isValidAgentConfig(alanPath)) {
|
|
24277
|
+
(0, import_node_fs5.unlinkSync)(alanPath);
|
|
24278
|
+
return false;
|
|
24279
|
+
}
|
|
24280
|
+
return true;
|
|
24281
|
+
} catch {
|
|
24282
|
+
if (copied && (0, import_node_fs5.existsSync)(alanPath)) (0, import_node_fs5.unlinkSync)(alanPath);
|
|
24283
|
+
return false;
|
|
24284
|
+
}
|
|
24285
|
+
}
|
|
24286
|
+
function resolveAgentConfigPath(input) {
|
|
24287
|
+
if (input?.explicitPath?.trim()) return input.explicitPath.trim();
|
|
24288
|
+
const profile = normalizeAgentEndpointProfile(
|
|
24289
|
+
typeof input?.profile === "string" ? input.profile : input?.profile ?? null
|
|
24290
|
+
) ?? "production";
|
|
24291
|
+
const baseDir = input?.baseDir ?? DEFAULT_AGENT_DIR;
|
|
24292
|
+
if (input?.baseDir === void 0) migrateLegacyAgentConfig({ profile });
|
|
24293
|
+
return (0, import_node_path5.join)(baseDir, resolveAgentConfigFileName(profile));
|
|
24294
|
+
}
|
|
24295
|
+
function configExistsAtPath(configPath) {
|
|
24296
|
+
return (0, import_node_fs5.existsSync)(configPath);
|
|
24297
|
+
}
|
|
24298
|
+
function resolveEndpointProfileFromArgs(args) {
|
|
24299
|
+
const index = args.indexOf("--profile");
|
|
24300
|
+
const raw = index === -1 ? process.env.ALAN_AGENT_PROFILE : args[index + 1];
|
|
24301
|
+
return normalizeAgentEndpointProfile(raw) ?? "production";
|
|
24302
|
+
}
|
|
24303
|
+
function bindAgentConfigPathFromArgs(args) {
|
|
24304
|
+
const explicit = process.env.ALAN_AGENT_CONFIG_PATH?.trim();
|
|
24305
|
+
if (explicit) return explicit;
|
|
24306
|
+
const profile = resolveEndpointProfileFromArgs(args);
|
|
24307
|
+
const configPath = resolveAgentConfigPath({ profile });
|
|
24308
|
+
process.env.ALAN_AGENT_CONFIG_PATH = configPath;
|
|
24309
|
+
return configPath;
|
|
24310
|
+
}
|
|
24311
|
+
|
|
24312
|
+
// src/computer-readiness.ts
|
|
24313
|
+
var WorkspaceAccessTracker = class {
|
|
24314
|
+
constructor(inspect = (path) => inspectWorkspaceReadiness(path)) {
|
|
24315
|
+
this.inspect = inspect;
|
|
24316
|
+
}
|
|
24317
|
+
issue = null;
|
|
24318
|
+
track(path, readiness) {
|
|
24319
|
+
if (readiness.ready) {
|
|
24320
|
+
this.issue = null;
|
|
24321
|
+
return;
|
|
24322
|
+
}
|
|
24323
|
+
this.issue = { path, readiness };
|
|
24324
|
+
}
|
|
24325
|
+
recheck() {
|
|
24326
|
+
if (!this.issue) return null;
|
|
24327
|
+
const readiness = this.inspect(this.issue.path);
|
|
24328
|
+
if (readiness.ready) {
|
|
24329
|
+
this.issue = null;
|
|
24330
|
+
return readiness;
|
|
24331
|
+
}
|
|
24332
|
+
this.issue = { path: this.issue.path, readiness };
|
|
24333
|
+
return readiness;
|
|
23216
24334
|
}
|
|
23217
24335
|
recoveryPath() {
|
|
23218
24336
|
return this.issue?.path ?? null;
|
|
@@ -23222,9 +24340,9 @@ var WorkspaceAccessTracker = class {
|
|
|
23222
24340
|
}
|
|
23223
24341
|
};
|
|
23224
24342
|
var DEFAULT_WORKSPACE_PROBE = {
|
|
23225
|
-
stat: (path) => (0,
|
|
23226
|
-
access: (path, mode) => (0,
|
|
23227
|
-
exists: (path) => (0,
|
|
24343
|
+
stat: (path) => (0, import_node_fs6.statSync)(path),
|
|
24344
|
+
access: (path, mode) => (0, import_node_fs6.accessSync)(path, mode),
|
|
24345
|
+
exists: (path) => (0, import_node_fs6.existsSync)(path)
|
|
23228
24346
|
};
|
|
23229
24347
|
function workspaceVolumeRoot(path) {
|
|
23230
24348
|
const windowsDrive = path.match(/^([A-Za-z]):[\\/]/);
|
|
@@ -23288,7 +24406,7 @@ function inspectWorkspaceReadiness(workspacePath, probe = DEFAULT_WORKSPACE_PROB
|
|
|
23288
24406
|
probe
|
|
23289
24407
|
);
|
|
23290
24408
|
}
|
|
23291
|
-
probe.access(workspacePath,
|
|
24409
|
+
probe.access(workspacePath, import_node_fs6.constants.R_OK | import_node_fs6.constants.W_OK | import_node_fs6.constants.X_OK);
|
|
23292
24410
|
return {
|
|
23293
24411
|
ready: true,
|
|
23294
24412
|
message: "Workspace path is configured and accessible.",
|
|
@@ -23400,7 +24518,7 @@ function buildComputerReadiness(input) {
|
|
|
23400
24518
|
}
|
|
23401
24519
|
|
|
23402
24520
|
// src/core-agent.ts
|
|
23403
|
-
var
|
|
24521
|
+
var import_node_os7 = require("os");
|
|
23404
24522
|
var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
23405
24523
|
childProcess = null;
|
|
23406
24524
|
_aborted = false;
|
|
@@ -23550,7 +24668,7 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
23550
24668
|
if (this.runtime.backendKind === "grok_cli" && this.providerApiKey) {
|
|
23551
24669
|
augmented.XAI_API_KEY = this.providerApiKey;
|
|
23552
24670
|
}
|
|
23553
|
-
if ((0,
|
|
24671
|
+
if ((0, import_node_os7.platform)() !== "win32") {
|
|
23554
24672
|
const home = augmented.HOME ?? "/home/user";
|
|
23555
24673
|
const extraPaths = [`${home}/.local/node/bin`, `${home}/.local/bin`];
|
|
23556
24674
|
const separator = ":";
|
|
@@ -23714,9 +24832,9 @@ ${meta.description}`);
|
|
|
23714
24832
|
};
|
|
23715
24833
|
|
|
23716
24834
|
// src/event-outbox.ts
|
|
23717
|
-
var
|
|
23718
|
-
var
|
|
23719
|
-
var
|
|
24835
|
+
var import_node_crypto2 = require("crypto");
|
|
24836
|
+
var import_node_fs7 = require("fs");
|
|
24837
|
+
var import_node_path6 = require("path");
|
|
23720
24838
|
var OUTBOX_VERSION = 1;
|
|
23721
24839
|
var MAX_INTERMEDIATE_EVENTS_PER_RUN = 200;
|
|
23722
24840
|
var MAX_ENCRYPTED_OUTBOX_BYTES = 64 * 1024 * 1024;
|
|
@@ -23749,7 +24867,7 @@ function truncateUtf8(value2, maxBytes) {
|
|
|
23749
24867
|
return `${encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "")}\u2026`;
|
|
23750
24868
|
}
|
|
23751
24869
|
function terminalFingerprint(event) {
|
|
23752
|
-
return (0,
|
|
24870
|
+
return (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(event)).digest("hex").slice(0, 16);
|
|
23753
24871
|
}
|
|
23754
24872
|
function condenseTerminalEvent(event) {
|
|
23755
24873
|
const fingerprint = terminalFingerprint(event);
|
|
@@ -23827,7 +24945,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23827
24945
|
throw new Error("event outbox maxIntermediateEventsPerRun must be a non-negative integer");
|
|
23828
24946
|
}
|
|
23829
24947
|
this.entries = this.read();
|
|
23830
|
-
if (!(0,
|
|
24948
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) {
|
|
23831
24949
|
this.lastPersistedSignature = _EncryptedEventOutbox.EMPTY_SIGNATURE;
|
|
23832
24950
|
} else if (this.entries.length > 0) {
|
|
23833
24951
|
this.lastPersistedSignature = this.serializeForPersist().signature;
|
|
@@ -23857,7 +24975,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23857
24975
|
maxIntermediateEventsPerRun;
|
|
23858
24976
|
enqueue(conversationId, runId, event) {
|
|
23859
24977
|
const entry = {
|
|
23860
|
-
eventId: (0,
|
|
24978
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23861
24979
|
conversationId,
|
|
23862
24980
|
runId,
|
|
23863
24981
|
event,
|
|
@@ -23943,7 +25061,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23943
25061
|
const markerConversationId = runEntry?.conversationId ?? conversationId;
|
|
23944
25062
|
if (!markerConversationId) return;
|
|
23945
25063
|
const marker = {
|
|
23946
|
-
eventId: (0,
|
|
25064
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23947
25065
|
conversationId: markerConversationId,
|
|
23948
25066
|
runId,
|
|
23949
25067
|
event: { type: "text", text: CONDENSATION_NOTICE, ts: Date.now() },
|
|
@@ -24029,14 +25147,14 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24029
25147
|
this.retryTimer = null;
|
|
24030
25148
|
}
|
|
24031
25149
|
read() {
|
|
24032
|
-
if (!(0,
|
|
25150
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) return [];
|
|
24033
25151
|
let envelope;
|
|
24034
25152
|
try {
|
|
24035
|
-
envelope = JSON.parse((0,
|
|
25153
|
+
envelope = JSON.parse((0, import_node_fs7.readFileSync)(this.path, "utf8"));
|
|
24036
25154
|
if (envelope.version !== OUTBOX_VERSION) {
|
|
24037
25155
|
throw new Error(`unsupported event outbox version ${String(envelope.version)}`);
|
|
24038
25156
|
}
|
|
24039
|
-
const decipher = (0,
|
|
25157
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)(
|
|
24040
25158
|
"aes-256-gcm",
|
|
24041
25159
|
this.key,
|
|
24042
25160
|
Buffer.from(envelope.iv, "base64url")
|
|
@@ -24060,21 +25178,21 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24060
25178
|
return { plaintext: "", signature: _EncryptedEventOutbox.EMPTY_SIGNATURE };
|
|
24061
25179
|
}
|
|
24062
25180
|
const plaintext = JSON.stringify({ entries: this.entries });
|
|
24063
|
-
return { plaintext, signature: (0,
|
|
25181
|
+
return { plaintext, signature: (0, import_node_crypto2.createHash)("sha256").update(plaintext).digest("base64url") };
|
|
24064
25182
|
}
|
|
24065
25183
|
persist() {
|
|
24066
25184
|
const { plaintext, signature } = this.serializeForPersist();
|
|
24067
25185
|
if (signature === this.lastPersistedSignature) return;
|
|
24068
25186
|
try {
|
|
24069
25187
|
if (this.entries.length === 0) {
|
|
24070
|
-
(0,
|
|
25188
|
+
(0, import_node_fs7.rmSync)(this.path, { force: true });
|
|
24071
25189
|
this.lastPersistedSignature = signature;
|
|
24072
25190
|
this.persistFailureLogged = false;
|
|
24073
25191
|
return;
|
|
24074
25192
|
}
|
|
24075
|
-
(0,
|
|
24076
|
-
const iv = (0,
|
|
24077
|
-
const cipher = (0,
|
|
25193
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(this.path), { recursive: true, mode: 448 });
|
|
25194
|
+
const iv = (0, import_node_crypto2.randomBytes)(12);
|
|
25195
|
+
const cipher = (0, import_node_crypto2.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
24078
25196
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
24079
25197
|
const envelope = {
|
|
24080
25198
|
version: OUTBOX_VERSION,
|
|
@@ -24082,13 +25200,13 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24082
25200
|
authTag: cipher.getAuthTag().toString("base64url"),
|
|
24083
25201
|
ciphertext: ciphertext.toString("base64url")
|
|
24084
25202
|
};
|
|
24085
|
-
const pendingPath = `${this.path}.pending-${process.pid}-${(0,
|
|
25203
|
+
const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
|
|
24086
25204
|
try {
|
|
24087
|
-
(0,
|
|
24088
|
-
(0,
|
|
24089
|
-
(0,
|
|
25205
|
+
(0, import_node_fs7.writeFileSync)(pendingPath, JSON.stringify(envelope), { mode: 384 });
|
|
25206
|
+
(0, import_node_fs7.chmodSync)(pendingPath, 384);
|
|
25207
|
+
(0, import_node_fs7.renameSync)(pendingPath, this.path);
|
|
24090
25208
|
} finally {
|
|
24091
|
-
(0,
|
|
25209
|
+
(0, import_node_fs7.rmSync)(pendingPath, { force: true });
|
|
24092
25210
|
}
|
|
24093
25211
|
this.lastPersistedSignature = signature;
|
|
24094
25212
|
this.persistFailureLogged = false;
|
|
@@ -24106,38 +25224,38 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24106
25224
|
};
|
|
24107
25225
|
|
|
24108
25226
|
// src/local-skill-scan.ts
|
|
24109
|
-
var
|
|
24110
|
-
var
|
|
25227
|
+
var import_node_fs8 = require("fs");
|
|
25228
|
+
var import_node_path7 = require("path");
|
|
24111
25229
|
var SKILL_READ_LIMIT_BYTES = 8192;
|
|
24112
25230
|
var SKILL_CACHE_TTL_MS = 3e4;
|
|
24113
25231
|
function readSkillHead(path) {
|
|
24114
|
-
const fd = (0,
|
|
25232
|
+
const fd = (0, import_node_fs8.openSync)(path, "r");
|
|
24115
25233
|
try {
|
|
24116
25234
|
const buffer = Buffer.alloc(SKILL_READ_LIMIT_BYTES);
|
|
24117
|
-
const bytesRead = (0,
|
|
25235
|
+
const bytesRead = (0, import_node_fs8.readSync)(fd, buffer, 0, SKILL_READ_LIMIT_BYTES, 0);
|
|
24118
25236
|
return buffer.subarray(0, bytesRead).toString("utf-8");
|
|
24119
25237
|
} finally {
|
|
24120
|
-
(0,
|
|
25238
|
+
(0, import_node_fs8.closeSync)(fd);
|
|
24121
25239
|
}
|
|
24122
25240
|
}
|
|
24123
25241
|
function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
24124
|
-
if (!(0,
|
|
25242
|
+
if (!(0, import_node_fs8.existsSync)(dirPath)) return;
|
|
24125
25243
|
let names;
|
|
24126
25244
|
try {
|
|
24127
|
-
names = (0,
|
|
25245
|
+
names = (0, import_node_fs8.readdirSync)(dirPath);
|
|
24128
25246
|
} catch (error) {
|
|
24129
25247
|
errors.push(`Failed to scan ${dirPath}: ${error instanceof Error ? error.message : error}`);
|
|
24130
25248
|
return;
|
|
24131
25249
|
}
|
|
24132
25250
|
for (const name of names) {
|
|
24133
|
-
const skillPath = (0,
|
|
25251
|
+
const skillPath = (0, import_node_path7.join)(dirPath, name, "SKILL.md");
|
|
24134
25252
|
let isDirectory = false;
|
|
24135
25253
|
try {
|
|
24136
|
-
isDirectory = (0,
|
|
25254
|
+
isDirectory = (0, import_node_fs8.statSync)((0, import_node_path7.join)(dirPath, name)).isDirectory();
|
|
24137
25255
|
} catch {
|
|
24138
25256
|
continue;
|
|
24139
25257
|
}
|
|
24140
|
-
if (!isDirectory || !(0,
|
|
25258
|
+
if (!isDirectory || !(0, import_node_fs8.existsSync)(skillPath)) continue;
|
|
24141
25259
|
try {
|
|
24142
25260
|
entries.push({ content: readSkillHead(skillPath), source, sourcePath: skillPath, repoRoot });
|
|
24143
25261
|
} catch (error) {
|
|
@@ -24148,16 +25266,16 @@ function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
|
24148
25266
|
function discoverChildRepos(parentPath) {
|
|
24149
25267
|
let names;
|
|
24150
25268
|
try {
|
|
24151
|
-
names = (0,
|
|
25269
|
+
names = (0, import_node_fs8.readdirSync)(parentPath);
|
|
24152
25270
|
} catch {
|
|
24153
25271
|
return [];
|
|
24154
25272
|
}
|
|
24155
25273
|
const children = [];
|
|
24156
25274
|
for (const name of names) {
|
|
24157
25275
|
if (name.startsWith(".")) continue;
|
|
24158
|
-
const childPath = (0,
|
|
25276
|
+
const childPath = (0, import_node_path7.join)(parentPath, name);
|
|
24159
25277
|
try {
|
|
24160
|
-
if ((0,
|
|
25278
|
+
if ((0, import_node_fs8.statSync)(childPath).isDirectory()) children.push(childPath);
|
|
24161
25279
|
} catch {
|
|
24162
25280
|
}
|
|
24163
25281
|
}
|
|
@@ -24167,12 +25285,12 @@ function scanLocalSkills(homeDir, projectPath) {
|
|
|
24167
25285
|
const rawEntries = [];
|
|
24168
25286
|
const errors = [];
|
|
24169
25287
|
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
24170
|
-
scanSkillDir((0,
|
|
25288
|
+
scanSkillDir((0, import_node_path7.join)(homeDir, root), "global", void 0, rawEntries, errors);
|
|
24171
25289
|
}
|
|
24172
25290
|
if (projectPath) {
|
|
24173
25291
|
for (const dir of [projectPath, ...discoverChildRepos(projectPath)]) {
|
|
24174
25292
|
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
24175
|
-
scanSkillDir((0,
|
|
25293
|
+
scanSkillDir((0, import_node_path7.join)(dir, root), "team", dir, rawEntries, errors);
|
|
24176
25294
|
}
|
|
24177
25295
|
}
|
|
24178
25296
|
}
|
|
@@ -24191,11 +25309,151 @@ function scanLocalSkillsCached(homeDir, projectPath, options) {
|
|
|
24191
25309
|
return result;
|
|
24192
25310
|
}
|
|
24193
25311
|
|
|
25312
|
+
// src/local-workspace-migration.ts
|
|
25313
|
+
var import_node_crypto3 = require("crypto");
|
|
25314
|
+
var MigrationStashApplyError = class extends Error {
|
|
25315
|
+
stash;
|
|
25316
|
+
stashPreserved = true;
|
|
25317
|
+
constructor(stash, cause) {
|
|
25318
|
+
super(`Could not apply migration stash ${stash.oid}; the stash was preserved`, { cause });
|
|
25319
|
+
this.name = "MigrationStashApplyError";
|
|
25320
|
+
this.stash = stash;
|
|
25321
|
+
}
|
|
25322
|
+
};
|
|
25323
|
+
var BRANCH_LABEL_MAX_LENGTH = 256;
|
|
25324
|
+
function stripControlCharacters(value2) {
|
|
25325
|
+
return Array.from(value2).filter((character) => {
|
|
25326
|
+
const code = character.charCodeAt(0);
|
|
25327
|
+
return code >= 32 && code !== 127;
|
|
25328
|
+
}).join("");
|
|
25329
|
+
}
|
|
25330
|
+
function sanitizeLabel(value2, fallback) {
|
|
25331
|
+
const sanitized = stripControlCharacters(value2).replace(/[^a-zA-Z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, BRANCH_LABEL_MAX_LENGTH);
|
|
25332
|
+
return sanitized || fallback;
|
|
25333
|
+
}
|
|
25334
|
+
function sanitizeBranch(value2) {
|
|
25335
|
+
const sanitized = stripControlCharacters(value2.trim()).slice(0, BRANCH_LABEL_MAX_LENGTH);
|
|
25336
|
+
return sanitized || null;
|
|
25337
|
+
}
|
|
25338
|
+
function countPorcelainEntries(output) {
|
|
25339
|
+
const records = output.split("\0");
|
|
25340
|
+
let count = 0;
|
|
25341
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
25342
|
+
const record = records[index];
|
|
25343
|
+
if (!record) continue;
|
|
25344
|
+
count += 1;
|
|
25345
|
+
const status = record.slice(0, 2);
|
|
25346
|
+
if (status.includes("R") || status.includes("C")) {
|
|
25347
|
+
index += 1;
|
|
25348
|
+
}
|
|
25349
|
+
}
|
|
25350
|
+
return count;
|
|
25351
|
+
}
|
|
25352
|
+
function preflightGitTree(cwd, git2) {
|
|
25353
|
+
const currentBranch = sanitizeBranch(git2(cwd, ["branch", "--show-current"]));
|
|
25354
|
+
const dirtyFileCount = countPorcelainEntries(
|
|
25355
|
+
git2(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"])
|
|
25356
|
+
);
|
|
25357
|
+
return {
|
|
25358
|
+
currentBranch,
|
|
25359
|
+
dirtyFileCount,
|
|
25360
|
+
isDirty: dirtyFileCount > 0
|
|
25361
|
+
};
|
|
25362
|
+
}
|
|
25363
|
+
function preflightWorkspaceMigration(input, git2) {
|
|
25364
|
+
return {
|
|
25365
|
+
direction: input.direction,
|
|
25366
|
+
source: preflightGitTree(input.sourcePath, git2),
|
|
25367
|
+
destination: input.destinationPath ? preflightGitTree(input.destinationPath, git2) : null
|
|
25368
|
+
};
|
|
25369
|
+
}
|
|
25370
|
+
function uniqueOperationLabel(operation, dependencies) {
|
|
25371
|
+
const now = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
25372
|
+
const createdAt = now.toISOString();
|
|
25373
|
+
const id = (dependencies.createId ?? import_node_crypto3.randomUUID)();
|
|
25374
|
+
return {
|
|
25375
|
+
label: [
|
|
25376
|
+
"alan-migration",
|
|
25377
|
+
sanitizeLabel(operation, "workspace"),
|
|
25378
|
+
createdAt.replace(/[:.]/g, "-"),
|
|
25379
|
+
sanitizeLabel(id, "operation")
|
|
25380
|
+
].join("/"),
|
|
25381
|
+
createdAt
|
|
25382
|
+
};
|
|
25383
|
+
}
|
|
25384
|
+
function createMigrationStash(input, dependencies) {
|
|
25385
|
+
const before = preflightGitTree(input.cwd, dependencies.git);
|
|
25386
|
+
if (!before.isDirty) return null;
|
|
25387
|
+
const { label, createdAt } = uniqueOperationLabel(input.operation, dependencies);
|
|
25388
|
+
dependencies.git(input.cwd, ["stash", "push", "--include-untracked", "--message", label]);
|
|
25389
|
+
const oid = dependencies.git(input.cwd, ["rev-parse", "--verify", "refs/stash"]).trim();
|
|
25390
|
+
if (!oid) {
|
|
25391
|
+
throw new Error("Git did not return the created migration stash");
|
|
25392
|
+
}
|
|
25393
|
+
return { oid, label, createdAt };
|
|
25394
|
+
}
|
|
25395
|
+
function findStashRef(cwd, oid, git2) {
|
|
25396
|
+
const matchingLine = git2(cwd, ["stash", "list", "--format=%H %gd"]).split("\n").find((line) => line.startsWith(`${oid} `));
|
|
25397
|
+
return matchingLine?.slice(oid.length + 1).trim() || null;
|
|
25398
|
+
}
|
|
25399
|
+
function dropMigrationStash(cwd, stash, git2) {
|
|
25400
|
+
const stashRef = findStashRef(cwd, stash.oid, git2);
|
|
25401
|
+
if (!stashRef) return false;
|
|
25402
|
+
git2(cwd, ["stash", "drop", stashRef]);
|
|
25403
|
+
return true;
|
|
25404
|
+
}
|
|
25405
|
+
function applyMigrationStash(input, git2) {
|
|
25406
|
+
try {
|
|
25407
|
+
git2(input.cwd, ["stash", "apply", "--index", input.stash.oid]);
|
|
25408
|
+
} catch (cause) {
|
|
25409
|
+
throw new MigrationStashApplyError(input.stash, cause);
|
|
25410
|
+
}
|
|
25411
|
+
if (input.dropOnSuccess !== false) {
|
|
25412
|
+
dropMigrationStash(input.cwd, input.stash, git2);
|
|
25413
|
+
}
|
|
25414
|
+
return preflightGitTree(input.cwd, git2);
|
|
25415
|
+
}
|
|
25416
|
+
function resolveDirtyTree(input, dependencies) {
|
|
25417
|
+
if (input.action === "discard" && input.confirmDiscard !== true) {
|
|
25418
|
+
throw new Error("Discarding workspace changes requires explicit confirmation");
|
|
25419
|
+
}
|
|
25420
|
+
const before = preflightGitTree(input.cwd, dependencies.git);
|
|
25421
|
+
let stash = null;
|
|
25422
|
+
let commitOid = null;
|
|
25423
|
+
if (before.isDirty) {
|
|
25424
|
+
switch (input.action) {
|
|
25425
|
+
case "leave":
|
|
25426
|
+
break;
|
|
25427
|
+
case "move_via_stash":
|
|
25428
|
+
stash = createMigrationStash({ cwd: input.cwd, operation: input.operation }, dependencies);
|
|
25429
|
+
break;
|
|
25430
|
+
case "commit_wip": {
|
|
25431
|
+
const { label } = uniqueOperationLabel(input.operation, dependencies);
|
|
25432
|
+
dependencies.git(input.cwd, ["add", "--all"]);
|
|
25433
|
+
dependencies.git(input.cwd, ["commit", "--message", `WIP: ${label}`]);
|
|
25434
|
+
commitOid = dependencies.git(input.cwd, ["rev-parse", "HEAD"]).trim() || null;
|
|
25435
|
+
break;
|
|
25436
|
+
}
|
|
25437
|
+
case "discard":
|
|
25438
|
+
dependencies.git(input.cwd, ["reset", "--hard", "HEAD"]);
|
|
25439
|
+
dependencies.git(input.cwd, ["clean", "-fd"]);
|
|
25440
|
+
break;
|
|
25441
|
+
}
|
|
25442
|
+
}
|
|
25443
|
+
return {
|
|
25444
|
+
action: input.action,
|
|
25445
|
+
before,
|
|
25446
|
+
after: preflightGitTree(input.cwd, dependencies.git),
|
|
25447
|
+
stash,
|
|
25448
|
+
commitOid
|
|
25449
|
+
};
|
|
25450
|
+
}
|
|
25451
|
+
|
|
24194
25452
|
// src/mcp-registration.ts
|
|
24195
|
-
var
|
|
24196
|
-
var
|
|
24197
|
-
var
|
|
24198
|
-
var
|
|
25453
|
+
var import_node_crypto4 = require("crypto");
|
|
25454
|
+
var import_node_fs9 = require("fs");
|
|
25455
|
+
var import_node_os8 = require("os");
|
|
25456
|
+
var import_node_path8 = require("path");
|
|
24199
25457
|
|
|
24200
25458
|
// ../../node_modules/smol-toml/dist/error.js
|
|
24201
25459
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -24277,7 +25535,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
24277
25535
|
}
|
|
24278
25536
|
return ptr;
|
|
24279
25537
|
}
|
|
24280
|
-
function skipUntil(str, ptr,
|
|
25538
|
+
function skipUntil(str, ptr, sep3, end, banNewLines = false) {
|
|
24281
25539
|
if (!end) {
|
|
24282
25540
|
ptr = indexOfNewline(str, ptr);
|
|
24283
25541
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -24286,7 +25544,7 @@ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
|
24286
25544
|
let c = str[i];
|
|
24287
25545
|
if (c === "#") {
|
|
24288
25546
|
i = indexOfNewline(str, i);
|
|
24289
|
-
} else if (c ===
|
|
25547
|
+
} else if (c === sep3) {
|
|
24290
25548
|
return i + 1;
|
|
24291
25549
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
24292
25550
|
return i;
|
|
@@ -24992,18 +26250,18 @@ var inspectAlanMcpThroughProviderCli = async (backendKind, expectedUrl, home) =>
|
|
|
24992
26250
|
return result;
|
|
24993
26251
|
};
|
|
24994
26252
|
async function waitForConfigLock(lockPath) {
|
|
24995
|
-
(0,
|
|
26253
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(lockPath), { recursive: true });
|
|
24996
26254
|
const startedAt = Date.now();
|
|
24997
26255
|
while (true) {
|
|
24998
26256
|
try {
|
|
24999
|
-
(0,
|
|
25000
|
-
return () => (0,
|
|
26257
|
+
(0, import_node_fs9.mkdirSync)(lockPath);
|
|
26258
|
+
return () => (0, import_node_fs9.rmSync)(lockPath, { recursive: true, force: true });
|
|
25001
26259
|
} catch (error) {
|
|
25002
26260
|
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
25003
26261
|
if (code !== "EEXIST") throw error;
|
|
25004
26262
|
try {
|
|
25005
|
-
if (Date.now() - (0,
|
|
25006
|
-
(0,
|
|
26263
|
+
if (Date.now() - (0, import_node_fs9.statSync)(lockPath).mtimeMs > CONFIG_LOCK_STALE_MS) {
|
|
26264
|
+
(0, import_node_fs9.rmSync)(lockPath, { recursive: true, force: true });
|
|
25007
26265
|
continue;
|
|
25008
26266
|
}
|
|
25009
26267
|
} catch {
|
|
@@ -25012,14 +26270,14 @@ async function waitForConfigLock(lockPath) {
|
|
|
25012
26270
|
if (Date.now() - startedAt >= CONFIG_LOCK_WAIT_MS) {
|
|
25013
26271
|
throw new Error(`Alan could not safely update ${lockPath.slice(0, -".alan-lock".length)}`);
|
|
25014
26272
|
}
|
|
25015
|
-
await new Promise((
|
|
26273
|
+
await new Promise((resolve7) => setTimeout(resolve7, CONFIG_LOCK_POLL_MS));
|
|
25016
26274
|
}
|
|
25017
26275
|
}
|
|
25018
26276
|
}
|
|
25019
26277
|
function saveMalformedBackup(path, content) {
|
|
25020
|
-
const hash = (0,
|
|
26278
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex").slice(0, 12);
|
|
25021
26279
|
const backupPath = `${path}.alan-backup-${hash}`;
|
|
25022
|
-
if (!(0,
|
|
26280
|
+
if (!(0, import_node_fs9.existsSync)(backupPath)) (0, import_node_fs9.copyFileSync)(path, backupPath);
|
|
25023
26281
|
return backupPath;
|
|
25024
26282
|
}
|
|
25025
26283
|
function parseJsonObject2(path, content) {
|
|
@@ -25064,24 +26322,24 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
|
25064
26322
|
return merged;
|
|
25065
26323
|
}
|
|
25066
26324
|
function atomicWriteManagedConfig(path, content) {
|
|
25067
|
-
const pendingPath = `${path}.alan-pending-${process.pid}-${(0,
|
|
26325
|
+
const pendingPath = `${path}.alan-pending-${process.pid}-${(0, import_node_crypto4.randomBytes)(6).toString("hex")}`;
|
|
25068
26326
|
try {
|
|
25069
|
-
(0,
|
|
25070
|
-
(0,
|
|
26327
|
+
(0, import_node_fs9.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
26328
|
+
(0, import_node_fs9.renameSync)(pendingPath, path);
|
|
25071
26329
|
} finally {
|
|
25072
|
-
(0,
|
|
26330
|
+
(0, import_node_fs9.rmSync)(pendingPath, { force: true });
|
|
25073
26331
|
}
|
|
25074
26332
|
}
|
|
25075
|
-
async function ensureAlanMcpRegistered(backendKind, registration, home = (0,
|
|
26333
|
+
async function ensureAlanMcpRegistered(backendKind, registration, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25076
26334
|
try {
|
|
25077
26335
|
const mcpServers = buildAlanMcpServers(registration);
|
|
25078
26336
|
const files = alanMcpConfigFilesForBackend(backendKind, mcpServers, home);
|
|
25079
26337
|
const paths = [];
|
|
25080
26338
|
for (const file of files) {
|
|
25081
|
-
(0,
|
|
26339
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(file.path), { recursive: true });
|
|
25082
26340
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25083
26341
|
try {
|
|
25084
|
-
const current = (0,
|
|
26342
|
+
const current = (0, import_node_fs9.existsSync)(file.path) ? (0, import_node_fs9.readFileSync)(file.path, "utf8") : null;
|
|
25085
26343
|
const content = file.path.endsWith(".toml") ? mergeCodexAlanSection(file.path, current, file.content) : mergeJsonConfig(file.path, current, file.content);
|
|
25086
26344
|
if (current !== content) atomicWriteManagedConfig(file.path, content);
|
|
25087
26345
|
paths.push(file.path);
|
|
@@ -25132,13 +26390,13 @@ async function ensureAlanMcpRegistered(backendKind, registration, home = (0, imp
|
|
|
25132
26390
|
return { ok: false, paths: [], error: err instanceof Error ? err.message : String(err) };
|
|
25133
26391
|
}
|
|
25134
26392
|
}
|
|
25135
|
-
async function verifyAlanMcpRegistered(backendKind, home = (0,
|
|
26393
|
+
async function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25136
26394
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25137
|
-
const missing = files.map((f) => f.path).filter((p) => !(0,
|
|
26395
|
+
const missing = files.map((f) => f.path).filter((p) => !(0, import_node_fs9.existsSync)(p));
|
|
25138
26396
|
const presentEntries = files.map((file) => ({
|
|
25139
26397
|
path: file.path,
|
|
25140
|
-
url: (0,
|
|
25141
|
-
})).filter((entry) => (0,
|
|
26398
|
+
url: (0, import_node_fs9.existsSync)(file.path) ? readUsableAlanMcpUrl(file.path) : null
|
|
26399
|
+
})).filter((entry) => (0, import_node_fs9.existsSync)(entry.path));
|
|
25142
26400
|
let invalid = presentEntries.filter((entry) => !entry.url).map((entry) => entry.path);
|
|
25143
26401
|
const urls = new Set(presentEntries.flatMap((entry) => entry.url ? [entry.url] : []));
|
|
25144
26402
|
if (urls.size > 1) invalid = presentEntries.map((entry) => entry.path);
|
|
@@ -25194,15 +26452,15 @@ function removeAlanFromJsonConfig(path, existingContent) {
|
|
|
25194
26452
|
}
|
|
25195
26453
|
return JSON.stringify(config);
|
|
25196
26454
|
}
|
|
25197
|
-
async function unregisterAlanMcp(backendKind, home = (0,
|
|
26455
|
+
async function unregisterAlanMcp(backendKind, home = (0, import_node_os8.homedir)()) {
|
|
25198
26456
|
const removedPaths = [];
|
|
25199
26457
|
const errors = [];
|
|
25200
26458
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25201
26459
|
for (const file of files) {
|
|
25202
|
-
if (!(0,
|
|
26460
|
+
if (!(0, import_node_fs9.existsSync)(file.path)) continue;
|
|
25203
26461
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25204
26462
|
try {
|
|
25205
|
-
const current = (0,
|
|
26463
|
+
const current = (0, import_node_fs9.readFileSync)(file.path, "utf8");
|
|
25206
26464
|
let next;
|
|
25207
26465
|
if (file.path.endsWith(".toml")) {
|
|
25208
26466
|
const stripped = stripCodexAlanMcpSection(current);
|
|
@@ -25233,7 +26491,7 @@ function isHttpUrl(value2) {
|
|
|
25233
26491
|
}
|
|
25234
26492
|
}
|
|
25235
26493
|
function readUsableAlanMcpUrl(path) {
|
|
25236
|
-
const content = (0,
|
|
26494
|
+
const content = (0, import_node_fs9.readFileSync)(path, "utf8");
|
|
25237
26495
|
if (path.endsWith(".toml")) {
|
|
25238
26496
|
const lines = content.split("\n");
|
|
25239
26497
|
const start = lines.findIndex((line) => line.trim() === "[mcp_servers.alan]");
|
|
@@ -25524,7 +26782,7 @@ ${result.stderr}`.trim();
|
|
|
25524
26782
|
}
|
|
25525
26783
|
|
|
25526
26784
|
// src/provider-model-discovery.ts
|
|
25527
|
-
var
|
|
26785
|
+
var import_node_fs10 = require("fs");
|
|
25528
26786
|
var MODEL_DISCOVERY_TIMEOUT_MS = 8e3;
|
|
25529
26787
|
var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
|
|
25530
26788
|
var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
|
|
@@ -25738,7 +26996,7 @@ function filterClaudeDatedModelIds(ids) {
|
|
|
25738
26996
|
}
|
|
25739
26997
|
function resolveExecutableForModelScan(executable) {
|
|
25740
26998
|
try {
|
|
25741
|
-
return (0,
|
|
26999
|
+
return (0, import_node_fs10.realpathSync)(executable);
|
|
25742
27000
|
} catch {
|
|
25743
27001
|
return executable;
|
|
25744
27002
|
}
|
|
@@ -25746,7 +27004,7 @@ function resolveExecutableForModelScan(executable) {
|
|
|
25746
27004
|
function extractClaudeModelIdsFromExecutable(executable) {
|
|
25747
27005
|
try {
|
|
25748
27006
|
const resolved = resolveExecutableForModelScan(executable);
|
|
25749
|
-
const text = (0,
|
|
27007
|
+
const text = (0, import_node_fs10.readFileSync)(resolved).toString("latin1");
|
|
25750
27008
|
const matches = text.match(CLAUDE_MODEL_ID_PATTERN) ?? [];
|
|
25751
27009
|
const unique = [
|
|
25752
27010
|
...new Set(matches.map((id) => id.trim()).filter((id) => isValidClaudeModelId(id))),
|
|
@@ -25873,8 +27131,8 @@ async function discoverProviderModels(provider, executable, env) {
|
|
|
25873
27131
|
}
|
|
25874
27132
|
|
|
25875
27133
|
// src/run-journal.ts
|
|
25876
|
-
var
|
|
25877
|
-
var
|
|
27134
|
+
var import_node_fs11 = require("fs");
|
|
27135
|
+
var import_node_path9 = require("path");
|
|
25878
27136
|
var JOURNAL_VERSION = 1;
|
|
25879
27137
|
var INTERRUPTED_ENTRY_TTL_MS = 24 * 60 * 6e4;
|
|
25880
27138
|
function isEntry(value2) {
|
|
@@ -25893,9 +27151,9 @@ function isEntry(value2) {
|
|
|
25893
27151
|
var RuntimeRunJournal = class {
|
|
25894
27152
|
constructor(path, nowMs = Date.now()) {
|
|
25895
27153
|
this.path = path;
|
|
25896
|
-
if (!(0,
|
|
27154
|
+
if (!(0, import_node_fs11.existsSync)(path)) return;
|
|
25897
27155
|
try {
|
|
25898
|
-
const parsed = JSON.parse((0,
|
|
27156
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
25899
27157
|
if (parsed.version !== JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
|
|
25900
27158
|
throw new Error("unsupported run journal format");
|
|
25901
27159
|
}
|
|
@@ -26018,18 +27276,18 @@ var RuntimeRunJournal = class {
|
|
|
26018
27276
|
}
|
|
26019
27277
|
persist() {
|
|
26020
27278
|
const pendingPath = `${this.path}.pending`;
|
|
26021
|
-
(0,
|
|
27279
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(this.path), { recursive: true });
|
|
26022
27280
|
try {
|
|
26023
|
-
(0,
|
|
27281
|
+
(0, import_node_fs11.writeFileSync)(
|
|
26024
27282
|
pendingPath,
|
|
26025
27283
|
`${JSON.stringify({ version: JOURNAL_VERSION, entries: [...this.entries.values()] })}
|
|
26026
27284
|
`,
|
|
26027
27285
|
{ mode: 384 }
|
|
26028
27286
|
);
|
|
26029
|
-
(0,
|
|
26030
|
-
(0,
|
|
27287
|
+
(0, import_node_fs11.chmodSync)(pendingPath, 384);
|
|
27288
|
+
(0, import_node_fs11.renameSync)(pendingPath, this.path);
|
|
26031
27289
|
} catch (error) {
|
|
26032
|
-
(0,
|
|
27290
|
+
(0, import_node_fs11.rmSync)(pendingPath, { force: true });
|
|
26033
27291
|
throw error;
|
|
26034
27292
|
}
|
|
26035
27293
|
}
|
|
@@ -26041,8 +27299,8 @@ var RunStartGate = class {
|
|
|
26041
27299
|
async acquire() {
|
|
26042
27300
|
const previous = this.tail;
|
|
26043
27301
|
let releaseCurrent;
|
|
26044
|
-
const current = new Promise((
|
|
26045
|
-
releaseCurrent =
|
|
27302
|
+
const current = new Promise((resolve7) => {
|
|
27303
|
+
releaseCurrent = resolve7;
|
|
26046
27304
|
});
|
|
26047
27305
|
this.tail = previous.then(() => current);
|
|
26048
27306
|
await previous;
|
|
@@ -26056,17 +27314,17 @@ var RunStartGate = class {
|
|
|
26056
27314
|
};
|
|
26057
27315
|
|
|
26058
27316
|
// src/version.ts
|
|
26059
|
-
var AGENT_VERSION = "0.1.
|
|
27317
|
+
var AGENT_VERSION = "0.1.51";
|
|
26060
27318
|
|
|
26061
27319
|
// src/workspace-relocation.ts
|
|
26062
|
-
var
|
|
26063
|
-
var
|
|
26064
|
-
var
|
|
27320
|
+
var import_node_child_process5 = require("child_process");
|
|
27321
|
+
var import_node_fs12 = require("fs");
|
|
27322
|
+
var import_node_path10 = require("path");
|
|
26065
27323
|
var MAX_RELOCATION_CANDIDATES = 200;
|
|
26066
27324
|
var DEFAULT_PROBE = {
|
|
26067
|
-
childDirectories: (parentPath, limit) => (0,
|
|
27325
|
+
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
27326
|
gitRemoteUrl: (candidatePath) => {
|
|
26069
|
-
const result = (0,
|
|
27327
|
+
const result = (0, import_node_child_process5.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
|
|
26070
27328
|
encoding: "utf8",
|
|
26071
27329
|
env: getDaemonCliEnvironment(),
|
|
26072
27330
|
timeout: 2e3
|
|
@@ -26095,7 +27353,7 @@ function workspaceMatchesExpectedRepositories(input) {
|
|
|
26095
27353
|
input.expectedRepoUrls.map(normalizeRepositoryIdentity).filter((identity2) => Boolean(identity2))
|
|
26096
27354
|
);
|
|
26097
27355
|
if (identities.size === 0) return false;
|
|
26098
|
-
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0,
|
|
27356
|
+
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0, import_node_path10.resolve)(input.workspacePath));
|
|
26099
27357
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26100
27358
|
return Boolean(identity && identities.has(identity));
|
|
26101
27359
|
}
|
|
@@ -26105,8 +27363,8 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26105
27363
|
);
|
|
26106
27364
|
if (identities.size === 0) return null;
|
|
26107
27365
|
const searchRoots = [
|
|
26108
|
-
(0,
|
|
26109
|
-
...(input.approvedWorkspacePaths ?? []).map((path) => (0,
|
|
27366
|
+
(0, import_node_path10.dirname)((0, import_node_path10.resolve)(input.missingPath)),
|
|
27367
|
+
...(input.approvedWorkspacePaths ?? []).map((path) => (0, import_node_path10.dirname)((0, import_node_path10.resolve)(path)))
|
|
26110
27368
|
].filter((path, index, paths) => paths.indexOf(path) === index);
|
|
26111
27369
|
const candidates = [];
|
|
26112
27370
|
for (const root of searchRoots) {
|
|
@@ -26118,19 +27376,19 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26118
27376
|
}
|
|
26119
27377
|
}
|
|
26120
27378
|
const matches = [];
|
|
26121
|
-
for (const candidate of new Set(candidates.map((path) => (0,
|
|
27379
|
+
for (const candidate of new Set(candidates.map((path) => (0, import_node_path10.resolve)(path)))) {
|
|
26122
27380
|
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl(candidate);
|
|
26123
27381
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26124
|
-
if (identity && identities.has(identity)) matches.push((0,
|
|
27382
|
+
if (identity && identities.has(identity)) matches.push((0, import_node_path10.resolve)(candidate));
|
|
26125
27383
|
if (matches.length > 1) return null;
|
|
26126
27384
|
}
|
|
26127
27385
|
return matches[0] ?? null;
|
|
26128
27386
|
}
|
|
26129
27387
|
|
|
26130
27388
|
// src/workspace-run-lease.ts
|
|
26131
|
-
var
|
|
26132
|
-
var
|
|
26133
|
-
var
|
|
27389
|
+
var import_node_child_process6 = require("child_process");
|
|
27390
|
+
var import_node_fs13 = require("fs");
|
|
27391
|
+
var import_node_path11 = require("path");
|
|
26134
27392
|
var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
|
|
26135
27393
|
function parsePositiveMs(value2, fallback) {
|
|
26136
27394
|
const parsed = value2 ? Number(value2) : Number.NaN;
|
|
@@ -26141,7 +27399,7 @@ var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
|
|
|
26141
27399
|
2e3
|
|
26142
27400
|
);
|
|
26143
27401
|
function gitValue(cwd, args) {
|
|
26144
|
-
const result = (0,
|
|
27402
|
+
const result = (0, import_node_child_process6.spawnSync)("git", ["-C", cwd, ...args], {
|
|
26145
27403
|
encoding: "utf8",
|
|
26146
27404
|
env: getDaemonCliEnvironment(),
|
|
26147
27405
|
timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
|
|
@@ -26150,17 +27408,17 @@ function gitValue(cwd, args) {
|
|
|
26150
27408
|
return result.stdout.trim() || null;
|
|
26151
27409
|
}
|
|
26152
27410
|
function filesystemIdentity(path) {
|
|
26153
|
-
const canonicalPath =
|
|
26154
|
-
const stat = (0,
|
|
27411
|
+
const canonicalPath = import_node_fs13.realpathSync.native(path);
|
|
27412
|
+
const stat = (0, import_node_fs13.statSync)(canonicalPath);
|
|
26155
27413
|
return `${canonicalPath}:${stat.dev}:${stat.ino}`;
|
|
26156
27414
|
}
|
|
26157
27415
|
function resolveGitPath(workspacePath, value2) {
|
|
26158
|
-
return (0,
|
|
27416
|
+
return (0, import_node_path11.isAbsolute)(value2) ? value2 : (0, import_node_path11.resolve)(workspacePath, value2);
|
|
26159
27417
|
}
|
|
26160
27418
|
var DEFAULT_PROBE2 = {
|
|
26161
27419
|
snapshot: (workspacePath) => {
|
|
26162
27420
|
try {
|
|
26163
|
-
const canonicalWorkspacePath =
|
|
27421
|
+
const canonicalWorkspacePath = import_node_fs13.realpathSync.native((0, import_node_path11.resolve)(workspacePath));
|
|
26164
27422
|
const repositoryRoot = gitValue(canonicalWorkspacePath, ["rev-parse", "--show-toplevel"]);
|
|
26165
27423
|
const gitDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-dir"]);
|
|
26166
27424
|
const commonDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-common-dir"]);
|
|
@@ -26169,7 +27427,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26169
27427
|
const remote = gitValue(canonicalWorkspacePath, ["config", "--get", "remote.origin.url"]);
|
|
26170
27428
|
const remoteIdentity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26171
27429
|
return {
|
|
26172
|
-
workspacePath: (0,
|
|
27430
|
+
workspacePath: (0, import_node_path11.resolve)(workspacePath),
|
|
26173
27431
|
workspaceIdentity: filesystemIdentity(canonicalWorkspacePath),
|
|
26174
27432
|
repositoryIdentity: `${filesystemIdentity(
|
|
26175
27433
|
resolveGitPath(canonicalWorkspacePath, commonDirectory)
|
|
@@ -26183,7 +27441,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26183
27441
|
},
|
|
26184
27442
|
pathExists: (workspacePath) => {
|
|
26185
27443
|
try {
|
|
26186
|
-
return (0,
|
|
27444
|
+
return (0, import_node_fs13.existsSync)((0, import_node_path11.resolve)(workspacePath));
|
|
26187
27445
|
} catch {
|
|
26188
27446
|
return true;
|
|
26189
27447
|
}
|
|
@@ -26214,7 +27472,7 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
26214
27472
|
static capture(targets, probe = DEFAULT_PROBE2) {
|
|
26215
27473
|
const captured = [];
|
|
26216
27474
|
for (const target of targets) {
|
|
26217
|
-
const normalizedTarget = { ...target, workspacePath: (0,
|
|
27475
|
+
const normalizedTarget = { ...target, workspacePath: (0, import_node_path11.resolve)(target.workspacePath) };
|
|
26218
27476
|
const identity = probe.snapshot(normalizedTarget.workspacePath);
|
|
26219
27477
|
if (!identity) return { lease: null, failure: unavailableFailure(normalizedTarget) };
|
|
26220
27478
|
if (normalizedTarget.expectedBranch && identity.branch !== normalizedTarget.expectedBranch) {
|
|
@@ -26356,34 +27614,62 @@ var PRODUCTION_API_URL = "https://api.tryalan.ai";
|
|
|
26356
27614
|
var PRODUCTION_WS_URL = "wss://ws.tryalan.ai";
|
|
26357
27615
|
var LOCAL_API_URL = "http://localhost:8400";
|
|
26358
27616
|
var LOCAL_WS_URL = "ws://localhost:8401";
|
|
26359
|
-
function
|
|
27617
|
+
function requiredString2(params, key) {
|
|
26360
27618
|
const value2 = params[key];
|
|
26361
27619
|
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
26362
27620
|
throw new Error(`${key} is required`);
|
|
26363
27621
|
}
|
|
26364
27622
|
return value2;
|
|
26365
27623
|
}
|
|
27624
|
+
var DIRTY_WORKSPACE_ACTIONS = /* @__PURE__ */ new Set([
|
|
27625
|
+
"move_via_stash",
|
|
27626
|
+
"commit_wip",
|
|
27627
|
+
"discard",
|
|
27628
|
+
"leave"
|
|
27629
|
+
]);
|
|
27630
|
+
function parseDirtyWorkspaceResolution(value2) {
|
|
27631
|
+
if (value2 === void 0) return void 0;
|
|
27632
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
|
|
27633
|
+
throw new Error("dirtyResolution must be an object");
|
|
27634
|
+
}
|
|
27635
|
+
const raw = value2;
|
|
27636
|
+
const parseAction = (key) => {
|
|
27637
|
+
const action = raw[key];
|
|
27638
|
+
if (action === void 0) return void 0;
|
|
27639
|
+
if (typeof action !== "string" || !DIRTY_WORKSPACE_ACTIONS.has(action)) {
|
|
27640
|
+
throw new Error(`${key} is not a supported dirty workspace action`);
|
|
27641
|
+
}
|
|
27642
|
+
return action;
|
|
27643
|
+
};
|
|
27644
|
+
return {
|
|
27645
|
+
sourceAction: parseAction("sourceAction"),
|
|
27646
|
+
destinationAction: parseAction("destinationAction"),
|
|
27647
|
+
sourceCommitMessage: typeof raw.sourceCommitMessage === "string" ? raw.sourceCommitMessage : void 0,
|
|
27648
|
+
destinationCommitMessage: typeof raw.destinationCommitMessage === "string" ? raw.destinationCommitMessage : void 0,
|
|
27649
|
+
confirmDiscard: raw.confirmDiscard === true
|
|
27650
|
+
};
|
|
27651
|
+
}
|
|
26366
27652
|
function assertValidGitRef(ref, label) {
|
|
26367
27653
|
if (!ref || ref.startsWith("-") || ref.includes("\0")) {
|
|
26368
27654
|
throw new Error(`${label} is not a safe git ref`);
|
|
26369
27655
|
}
|
|
26370
|
-
const result = (0,
|
|
27656
|
+
const result = (0, import_node_child_process7.spawnSync)("git", ["check-ref-format", "--branch", ref], {
|
|
26371
27657
|
env: getDaemonCliEnvironment(),
|
|
26372
27658
|
encoding: "utf8"
|
|
26373
27659
|
});
|
|
26374
27660
|
if (result.status !== 0) throw new Error(`${label} is not a valid git ref`);
|
|
26375
27661
|
}
|
|
26376
|
-
function
|
|
26377
|
-
const rel = (0,
|
|
26378
|
-
return rel === "" || !(0,
|
|
27662
|
+
function isPathContained2(childPath, parentPath) {
|
|
27663
|
+
const rel = (0, import_node_path12.relative)((0, import_node_path12.resolve)(parentPath), (0, import_node_path12.resolve)(childPath));
|
|
27664
|
+
return rel === "" || !(0, import_node_path12.isAbsolute)(rel) && rel !== ".." && !rel.startsWith(`..${import_node_path12.sep}`);
|
|
26379
27665
|
}
|
|
26380
27666
|
function assertPathContained(childPath, parentPath, label) {
|
|
26381
|
-
if (!
|
|
27667
|
+
if (!isPathContained2(childPath, parentPath)) {
|
|
26382
27668
|
throw new Error(`${label} escapes allowed directory`);
|
|
26383
27669
|
}
|
|
26384
27670
|
}
|
|
26385
27671
|
function git(cwd, args) {
|
|
26386
|
-
const result = (0,
|
|
27672
|
+
const result = (0, import_node_child_process7.spawnSync)("git", args, {
|
|
26387
27673
|
cwd,
|
|
26388
27674
|
env: getDaemonCliEnvironment(),
|
|
26389
27675
|
encoding: "utf8",
|
|
@@ -26402,14 +27688,23 @@ function gitSafe(cwd, args) {
|
|
|
26402
27688
|
}
|
|
26403
27689
|
}
|
|
26404
27690
|
function worktreeDir(projectPath) {
|
|
26405
|
-
const resolved = (0,
|
|
26406
|
-
return (0,
|
|
27691
|
+
const resolved = (0, import_node_path12.resolve)(projectPath);
|
|
27692
|
+
return (0, import_node_path12.join)((0, import_node_path12.dirname)(resolved), `${(0, import_node_path12.basename)(resolved)}-alan-worktrees`);
|
|
27693
|
+
}
|
|
27694
|
+
function hasAlanWorktreeAncestor(targetPath) {
|
|
27695
|
+
let cursor = (0, import_node_path12.resolve)(targetPath);
|
|
27696
|
+
while (true) {
|
|
27697
|
+
if ((0, import_node_path12.basename)(cursor).endsWith("-alan-worktrees")) return true;
|
|
27698
|
+
const parent = (0, import_node_path12.dirname)(cursor);
|
|
27699
|
+
if (parent === cursor) return false;
|
|
27700
|
+
cursor = parent;
|
|
27701
|
+
}
|
|
26407
27702
|
}
|
|
26408
27703
|
function pathSegment(value2) {
|
|
26409
27704
|
return value2.toLowerCase().replace(/^alan\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "task";
|
|
26410
27705
|
}
|
|
26411
27706
|
function repoName(repoUrl, localPath) {
|
|
26412
|
-
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0,
|
|
27707
|
+
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0, import_node_path12.basename)(localPath) || "repo";
|
|
26413
27708
|
}
|
|
26414
27709
|
function findWorktreeByBranch(repoPath, branchName) {
|
|
26415
27710
|
const output = gitSafe(repoPath, ["worktree", "list", "--porcelain"]);
|
|
@@ -26425,21 +27720,21 @@ function findWorktreeByBranch(repoPath, branchName) {
|
|
|
26425
27720
|
function ensureGitWorktree(input) {
|
|
26426
27721
|
assertValidGitRef(input.branchName, "branchName");
|
|
26427
27722
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26428
|
-
const repoPath = (0,
|
|
27723
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26429
27724
|
const root = input.allowedRoot ?? worktreeDir(repoPath);
|
|
26430
27725
|
const existingPath = findWorktreeByBranch(input.repoPath, input.branchName);
|
|
26431
27726
|
if (existingPath) {
|
|
26432
|
-
if ((0,
|
|
27727
|
+
if ((0, import_node_path12.resolve)(existingPath) === repoPath) {
|
|
26433
27728
|
throw new Error(`${input.branchName} is checked out in the shared checkout`);
|
|
26434
27729
|
}
|
|
26435
27730
|
return existingPath;
|
|
26436
27731
|
}
|
|
26437
27732
|
assertPathContained(input.worktreePath, root, "worktreePath");
|
|
26438
|
-
(0,
|
|
27733
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(input.worktreePath), { recursive: true });
|
|
26439
27734
|
if (!gitSafe(input.repoPath, ["branch", "--list", input.branchName])) {
|
|
26440
27735
|
git(input.repoPath, ["branch", input.branchName, input.baseBranch]);
|
|
26441
27736
|
}
|
|
26442
|
-
if ((0,
|
|
27737
|
+
if ((0, import_node_fs14.existsSync)(input.worktreePath)) {
|
|
26443
27738
|
if (gitSafe(input.worktreePath, ["branch", "--show-current"]) === input.branchName) {
|
|
26444
27739
|
return input.worktreePath;
|
|
26445
27740
|
}
|
|
@@ -26449,40 +27744,172 @@ function ensureGitWorktree(input) {
|
|
|
26449
27744
|
return input.worktreePath;
|
|
26450
27745
|
}
|
|
26451
27746
|
function resolveProjectWorktreePaths(projectPath, gitRoot, gitWorktreePath) {
|
|
26452
|
-
const resolvedProjectPath = (0,
|
|
26453
|
-
const resolvedGitRoot = (0,
|
|
26454
|
-
const projectRelativePath = (0,
|
|
26455
|
-
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${
|
|
27747
|
+
const resolvedProjectPath = (0, import_node_path12.resolve)(projectPath);
|
|
27748
|
+
const resolvedGitRoot = (0, import_node_path12.resolve)(gitRoot);
|
|
27749
|
+
const projectRelativePath = (0, import_node_path12.relative)(resolvedGitRoot, resolvedProjectPath);
|
|
27750
|
+
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(projectRelativePath)) {
|
|
26456
27751
|
throw new Error("The selected Project folder is outside its Git repository");
|
|
26457
27752
|
}
|
|
26458
27753
|
return {
|
|
26459
27754
|
repoPath: resolvedGitRoot,
|
|
26460
|
-
projectPath: projectRelativePath ? (0,
|
|
27755
|
+
projectPath: projectRelativePath ? (0, import_node_path12.join)((0, import_node_path12.resolve)(gitWorktreePath), projectRelativePath) : (0, import_node_path12.resolve)(gitWorktreePath)
|
|
27756
|
+
};
|
|
27757
|
+
}
|
|
27758
|
+
function preflightLocalWorkspaceMigration(params) {
|
|
27759
|
+
const direction = params.direction;
|
|
27760
|
+
if (direction !== "to-checkout" && direction !== "to-worktree") {
|
|
27761
|
+
throw new Error("direction must be to-checkout or to-worktree");
|
|
27762
|
+
}
|
|
27763
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
27764
|
+
if (targets.length !== 1) {
|
|
27765
|
+
throw new Error("Dirty workspace migration currently supports exactly one repository");
|
|
27766
|
+
}
|
|
27767
|
+
const rawTarget = targets[0];
|
|
27768
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "projectPath"));
|
|
27769
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
27770
|
+
if (!projectRootOutput) {
|
|
27771
|
+
throw new Error("The selected Project folder is not inside a Git repository");
|
|
27772
|
+
}
|
|
27773
|
+
const projectRoot = (0, import_node_path12.resolve)(projectRootOutput);
|
|
27774
|
+
if (direction === "to-worktree") {
|
|
27775
|
+
const result2 = preflightWorkspaceMigration(
|
|
27776
|
+
{
|
|
27777
|
+
direction: "checkout_to_worktree",
|
|
27778
|
+
sourcePath: projectRoot
|
|
27779
|
+
},
|
|
27780
|
+
git
|
|
27781
|
+
);
|
|
27782
|
+
return {
|
|
27783
|
+
direction,
|
|
27784
|
+
source: result2.source,
|
|
27785
|
+
destination: null,
|
|
27786
|
+
requiresResolution: result2.source.isDirty
|
|
27787
|
+
};
|
|
27788
|
+
}
|
|
27789
|
+
const requestedWorktreePath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
27790
|
+
const worktreeRootOutput = gitSafe(requestedWorktreePath, ["rev-parse", "--show-toplevel"]);
|
|
27791
|
+
if (!worktreeRootOutput) {
|
|
27792
|
+
throw new Error("The task worktree is not a registered Git worktree");
|
|
27793
|
+
}
|
|
27794
|
+
const result = preflightWorkspaceMigration(
|
|
27795
|
+
{
|
|
27796
|
+
direction: "worktree_to_checkout",
|
|
27797
|
+
sourcePath: (0, import_node_path12.resolve)(worktreeRootOutput),
|
|
27798
|
+
destinationPath: projectRoot
|
|
27799
|
+
},
|
|
27800
|
+
git
|
|
27801
|
+
);
|
|
27802
|
+
return {
|
|
27803
|
+
direction,
|
|
27804
|
+
source: result.source,
|
|
27805
|
+
destination: result.destination,
|
|
27806
|
+
requiresResolution: result.source.isDirty || Boolean(result.destination?.isDirty)
|
|
27807
|
+
};
|
|
27808
|
+
}
|
|
27809
|
+
function statusLocalWorkspace(params) {
|
|
27810
|
+
const requestedPath = (0, import_node_path12.resolve)(requiredString2(params, "worktreePath"));
|
|
27811
|
+
const rootOutput = gitSafe(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
27812
|
+
if (!rootOutput) {
|
|
27813
|
+
throw new Error("The selected folder is not inside a Git repository");
|
|
27814
|
+
}
|
|
27815
|
+
const root = (0, import_node_path12.resolve)(rootOutput);
|
|
27816
|
+
const tree = preflightGitTree(root, git);
|
|
27817
|
+
const headSha = gitSafe(root, ["rev-parse", "HEAD"]) || null;
|
|
27818
|
+
const upstream = gitSafe(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
|
27819
|
+
const hasUpstream = upstream !== "";
|
|
27820
|
+
let ahead = null;
|
|
27821
|
+
let behind = null;
|
|
27822
|
+
if (hasUpstream) {
|
|
27823
|
+
const counts = gitSafe(root, ["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]);
|
|
27824
|
+
const [behindRaw, aheadRaw] = counts.split(/\s+/);
|
|
27825
|
+
const parsedBehind = Number.parseInt(behindRaw ?? "", 10);
|
|
27826
|
+
const parsedAhead = Number.parseInt(aheadRaw ?? "", 10);
|
|
27827
|
+
behind = Number.isNaN(parsedBehind) ? null : parsedBehind;
|
|
27828
|
+
ahead = Number.isNaN(parsedAhead) ? null : parsedAhead;
|
|
27829
|
+
}
|
|
27830
|
+
return {
|
|
27831
|
+
branch: tree.currentBranch,
|
|
27832
|
+
headSha,
|
|
27833
|
+
dirty: tree.isDirty,
|
|
27834
|
+
filesChanged: tree.dirtyFileCount,
|
|
27835
|
+
ahead,
|
|
27836
|
+
behind,
|
|
27837
|
+
hasUpstream
|
|
26461
27838
|
};
|
|
26462
27839
|
}
|
|
27840
|
+
function ensureGitWorktreeWithDirtyResolution(input, resolution) {
|
|
27841
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
27842
|
+
const before = preflightGitTree(repoPath, git);
|
|
27843
|
+
let sourceStash = null;
|
|
27844
|
+
if (before.isDirty) {
|
|
27845
|
+
const action = resolution?.sourceAction;
|
|
27846
|
+
if (!action) {
|
|
27847
|
+
throw new Error(
|
|
27848
|
+
`The Project checkout has ${before.dirtyFileCount} uncommitted file${before.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before moving into a worktree.`
|
|
27849
|
+
);
|
|
27850
|
+
}
|
|
27851
|
+
if (action === "commit_wip" && before.currentBranch !== input.baseBranch) {
|
|
27852
|
+
throw new Error(
|
|
27853
|
+
`Commit as WIP would stay on "${before.currentBranch ?? "the current checkout"}", not the selected base "${input.baseBranch}". Choose Move changes with task instead.`
|
|
27854
|
+
);
|
|
27855
|
+
}
|
|
27856
|
+
const resolved = resolveDirtyTree(
|
|
27857
|
+
{
|
|
27858
|
+
cwd: repoPath,
|
|
27859
|
+
action,
|
|
27860
|
+
operation: "checkout-to-worktree",
|
|
27861
|
+
confirmDiscard: resolution?.confirmDiscard
|
|
27862
|
+
},
|
|
27863
|
+
{ git }
|
|
27864
|
+
);
|
|
27865
|
+
sourceStash = resolved.stash;
|
|
27866
|
+
}
|
|
27867
|
+
const existingPath = findWorktreeByBranch(repoPath, input.branchName);
|
|
27868
|
+
const worktreePath = ensureGitWorktree(input);
|
|
27869
|
+
if (!sourceStash) return worktreePath;
|
|
27870
|
+
try {
|
|
27871
|
+
applyMigrationStash({ cwd: worktreePath, stash: sourceStash }, git);
|
|
27872
|
+
return worktreePath;
|
|
27873
|
+
} catch (error) {
|
|
27874
|
+
if (!existingPath) {
|
|
27875
|
+
gitSafe(worktreePath, ["reset", "--hard", "HEAD"]);
|
|
27876
|
+
gitSafe(worktreePath, ["clean", "-fd"]);
|
|
27877
|
+
gitSafe(repoPath, ["worktree", "remove", worktreePath]);
|
|
27878
|
+
gitSafe(repoPath, ["worktree", "prune"]);
|
|
27879
|
+
}
|
|
27880
|
+
throw error;
|
|
27881
|
+
}
|
|
27882
|
+
}
|
|
26463
27883
|
function ensureWorktree(params) {
|
|
26464
27884
|
const input = {
|
|
26465
|
-
projectPath:
|
|
26466
|
-
taskId:
|
|
26467
|
-
branchName:
|
|
27885
|
+
projectPath: requiredString2(params, "projectPath"),
|
|
27886
|
+
taskId: requiredString2(params, "taskId"),
|
|
27887
|
+
branchName: requiredString2(params, "branchName"),
|
|
26468
27888
|
baseBranch: typeof params.baseBranch === "string" ? params.baseBranch : void 0,
|
|
26469
|
-
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : []
|
|
27889
|
+
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : [],
|
|
27890
|
+
dirtyResolution: parseDirtyWorkspaceResolution(params.dirtyResolution)
|
|
26470
27891
|
};
|
|
27892
|
+
if ((input.repoLocalPaths?.length ?? 0) > 1) {
|
|
27893
|
+
throw new Error("A task worktree requires exactly one Git repository.");
|
|
27894
|
+
}
|
|
26471
27895
|
if (input.repoLocalPaths?.length) {
|
|
26472
|
-
const taskRoot2 = (0,
|
|
27896
|
+
const taskRoot2 = (0, import_node_path12.join)(
|
|
26473
27897
|
worktreeDir(input.projectPath),
|
|
26474
27898
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26475
27899
|
);
|
|
26476
27900
|
return {
|
|
26477
27901
|
worktrees: input.repoLocalPaths.map((repo) => {
|
|
26478
27902
|
const baseBranch2 = repo.branch || input.baseBranch || "main";
|
|
26479
|
-
const worktreePath2 =
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
|
|
26483
|
-
|
|
26484
|
-
|
|
26485
|
-
|
|
27903
|
+
const worktreePath2 = ensureGitWorktreeWithDirtyResolution(
|
|
27904
|
+
{
|
|
27905
|
+
repoPath: repo.localPath,
|
|
27906
|
+
worktreePath: (0, import_node_path12.join)(taskRoot2, pathSegment(repoName(repo.repoUrl, repo.localPath))),
|
|
27907
|
+
branchName: input.branchName,
|
|
27908
|
+
baseBranch: baseBranch2,
|
|
27909
|
+
allowedRoot: worktreeDir(input.projectPath)
|
|
27910
|
+
},
|
|
27911
|
+
input.dirtyResolution
|
|
27912
|
+
);
|
|
26486
27913
|
return {
|
|
26487
27914
|
repoId: repo.repoId ?? null,
|
|
26488
27915
|
repoUrl: repo.repoUrl,
|
|
@@ -26490,26 +27917,30 @@ function ensureWorktree(params) {
|
|
|
26490
27917
|
branchName: input.branchName,
|
|
26491
27918
|
baseBranch: baseBranch2
|
|
26492
27919
|
};
|
|
26493
|
-
})
|
|
27920
|
+
}),
|
|
27921
|
+
...input.dirtyResolution ? { dirtyResolution: input.dirtyResolution } : {}
|
|
26494
27922
|
};
|
|
26495
27923
|
}
|
|
26496
|
-
const gitRoot = gitSafe((0,
|
|
27924
|
+
const gitRoot = gitSafe((0, import_node_path12.resolve)(input.projectPath), ["rev-parse", "--show-toplevel"]);
|
|
26497
27925
|
if (!gitRoot) {
|
|
26498
27926
|
throw new Error("The selected Project folder is not inside a Git repository");
|
|
26499
27927
|
}
|
|
26500
|
-
const repoPath = (0,
|
|
27928
|
+
const repoPath = (0, import_node_path12.resolve)(gitRoot);
|
|
26501
27929
|
const baseBranch = input.baseBranch || gitSafe(repoPath, ["branch", "--show-current"]) || "HEAD";
|
|
26502
|
-
const taskRoot = (0,
|
|
27930
|
+
const taskRoot = (0, import_node_path12.join)(
|
|
26503
27931
|
worktreeDir(repoPath),
|
|
26504
27932
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26505
27933
|
);
|
|
26506
27934
|
resolveProjectWorktreePaths(input.projectPath, repoPath, taskRoot);
|
|
26507
|
-
const worktreePath =
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
27935
|
+
const worktreePath = ensureGitWorktreeWithDirtyResolution(
|
|
27936
|
+
{
|
|
27937
|
+
repoPath,
|
|
27938
|
+
worktreePath: taskRoot,
|
|
27939
|
+
branchName: input.branchName,
|
|
27940
|
+
baseBranch
|
|
27941
|
+
},
|
|
27942
|
+
input.dirtyResolution
|
|
27943
|
+
);
|
|
26513
27944
|
const projectWorktreePath = resolveProjectWorktreePaths(
|
|
26514
27945
|
input.projectPath,
|
|
26515
27946
|
repoPath,
|
|
@@ -26522,13 +27953,286 @@ function ensureWorktree(params) {
|
|
|
26522
27953
|
branchName: input.branchName,
|
|
26523
27954
|
baseBranch
|
|
26524
27955
|
}
|
|
26525
|
-
]
|
|
27956
|
+
],
|
|
27957
|
+
...input.dirtyResolution ? { dirtyResolution: input.dirtyResolution } : {}
|
|
27958
|
+
};
|
|
27959
|
+
}
|
|
27960
|
+
function listRegisteredWorktrees(cwd) {
|
|
27961
|
+
return git(cwd, ["worktree", "list", "--porcelain"]).split("\n\n").flatMap((entry) => {
|
|
27962
|
+
const lines = entry.split("\n");
|
|
27963
|
+
const path = lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
|
|
27964
|
+
if (!path) return [];
|
|
27965
|
+
return [
|
|
27966
|
+
{
|
|
27967
|
+
path: (0, import_node_path12.resolve)(path),
|
|
27968
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "")
|
|
27969
|
+
}
|
|
27970
|
+
];
|
|
27971
|
+
});
|
|
27972
|
+
}
|
|
27973
|
+
function removeWorktreesIfClean(params, removeGit = git) {
|
|
27974
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(params, "projectPath"));
|
|
27975
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
27976
|
+
if (targets.length === 0) throw new Error("At least one worktree target is required");
|
|
27977
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
27978
|
+
const projectRoot = projectRootOutput ? (0, import_node_path12.resolve)(projectRootOutput) : null;
|
|
27979
|
+
const projectRegistrations = projectRoot ? listRegisteredWorktrees(projectRoot) : [];
|
|
27980
|
+
const planned = targets.map((target) => {
|
|
27981
|
+
const rawTarget = target;
|
|
27982
|
+
const requestedPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
27983
|
+
const branchName = requiredString2(rawTarget, "branchName");
|
|
27984
|
+
assertValidGitRef(branchName, "branchName");
|
|
27985
|
+
const registrationSets = [];
|
|
27986
|
+
if (projectRegistrations.length > 0) registrationSets.push(projectRegistrations);
|
|
27987
|
+
if ((0, import_node_fs14.existsSync)(requestedPath)) {
|
|
27988
|
+
const requestedRoot = gitSafe(requestedPath, ["rev-parse", "--show-toplevel"]);
|
|
27989
|
+
if (!requestedRoot) {
|
|
27990
|
+
throw new Error(
|
|
27991
|
+
`The earlier task worktree is not a registered Git worktree: ${requestedPath}`
|
|
27992
|
+
);
|
|
27993
|
+
}
|
|
27994
|
+
const requestedRegistrations = listRegisteredWorktrees((0, import_node_path12.resolve)(requestedRoot));
|
|
27995
|
+
if (!registrationSets.some(
|
|
27996
|
+
(entries) => entries.length === requestedRegistrations.length && entries.every((entry, index) => entry.path === requestedRegistrations[index]?.path)
|
|
27997
|
+
)) {
|
|
27998
|
+
registrationSets.push(requestedRegistrations);
|
|
27999
|
+
}
|
|
28000
|
+
}
|
|
28001
|
+
for (const registrations of registrationSets) {
|
|
28002
|
+
const mainRoot = registrations[0]?.path;
|
|
28003
|
+
const registeredTarget = registrations.find(
|
|
28004
|
+
(entry) => requestedPath === entry.path || isPathContained2(requestedPath, entry.path)
|
|
28005
|
+
);
|
|
28006
|
+
if (!registeredTarget || !mainRoot) continue;
|
|
28007
|
+
if (registeredTarget.path === mainRoot) {
|
|
28008
|
+
throw new Error("Refusing to remove the Project's main checkout");
|
|
28009
|
+
}
|
|
28010
|
+
if (registeredTarget.branch !== branchName) {
|
|
28011
|
+
throw new Error(
|
|
28012
|
+
`The registered worktree branch changed from "${branchName}" to "${registeredTarget.branch ?? "detached HEAD"}".`
|
|
28013
|
+
);
|
|
28014
|
+
}
|
|
28015
|
+
const allowedRoots = [
|
|
28016
|
+
worktreeDir(mainRoot),
|
|
28017
|
+
worktreeDir(projectPath),
|
|
28018
|
+
...projectRoot ? [worktreeDir(projectRoot)] : []
|
|
28019
|
+
];
|
|
28020
|
+
if (!allowedRoots.some((root) => isPathContained2(registeredTarget.path, root))) {
|
|
28021
|
+
throw new Error("The registered worktree is outside an Alan-managed worktree directory");
|
|
28022
|
+
}
|
|
28023
|
+
if (git(registeredTarget.path, ["status", "--porcelain"]).trim()) {
|
|
28024
|
+
throw new Error(
|
|
28025
|
+
`The earlier task worktree has uncommitted changes at ${requestedPath}. Commit or discard them before changing Project, branch, or worktree mode.`
|
|
28026
|
+
);
|
|
28027
|
+
}
|
|
28028
|
+
return {
|
|
28029
|
+
requestedPath,
|
|
28030
|
+
worktreeRoot: registeredTarget.path,
|
|
28031
|
+
mainRoot,
|
|
28032
|
+
branchName,
|
|
28033
|
+
replayed: false
|
|
28034
|
+
};
|
|
28035
|
+
}
|
|
28036
|
+
if (!(0, import_node_fs14.existsSync)(requestedPath) && hasAlanWorktreeAncestor(requestedPath)) {
|
|
28037
|
+
return {
|
|
28038
|
+
requestedPath,
|
|
28039
|
+
worktreeRoot: null,
|
|
28040
|
+
mainRoot: null,
|
|
28041
|
+
branchName,
|
|
28042
|
+
replayed: true
|
|
28043
|
+
};
|
|
28044
|
+
}
|
|
28045
|
+
throw new Error(
|
|
28046
|
+
`The earlier task worktree is not safely registered and was not changed: ${requestedPath}`
|
|
28047
|
+
);
|
|
28048
|
+
});
|
|
28049
|
+
const removalRoots = planned.flatMap(
|
|
28050
|
+
(target) => target.worktreeRoot && target.mainRoot ? [{ worktreeRoot: target.worktreeRoot, mainRoot: target.mainRoot }] : []
|
|
28051
|
+
);
|
|
28052
|
+
if (new Set(removalRoots.map(({ worktreeRoot }) => worktreeRoot)).size !== removalRoots.length) {
|
|
28053
|
+
throw new Error("Duplicate worktree targets were not changed");
|
|
28054
|
+
}
|
|
28055
|
+
for (const target of removalRoots) {
|
|
28056
|
+
removeGit(target.mainRoot, ["worktree", "remove", target.worktreeRoot]);
|
|
28057
|
+
gitSafe(target.mainRoot, ["worktree", "prune"]);
|
|
28058
|
+
}
|
|
28059
|
+
return {
|
|
28060
|
+
ok: true,
|
|
28061
|
+
removed: planned.map(({ requestedPath, branchName, replayed }) => ({
|
|
28062
|
+
worktreePath: requestedPath,
|
|
28063
|
+
branchName,
|
|
28064
|
+
replayed
|
|
28065
|
+
}))
|
|
28066
|
+
};
|
|
28067
|
+
}
|
|
28068
|
+
function migrateWorktreeToCheckout(params, removeGit = git) {
|
|
28069
|
+
const dirtyResolution = parseDirtyWorkspaceResolution(params.dirtyResolution);
|
|
28070
|
+
const targets = Array.isArray(params.targets) ? params.targets : [];
|
|
28071
|
+
if (targets.length !== 1) {
|
|
28072
|
+
throw new Error(
|
|
28073
|
+
"Multi-repository worktree migration is not supported; no repositories were changed."
|
|
28074
|
+
);
|
|
28075
|
+
}
|
|
28076
|
+
const target = targets[0];
|
|
28077
|
+
const rawTarget = target;
|
|
28078
|
+
const projectPath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "projectPath"));
|
|
28079
|
+
const requestedWorktreePath = (0, import_node_path12.resolve)(requiredString2(rawTarget, "worktreePath"));
|
|
28080
|
+
const branch = requiredString2(rawTarget, "branch");
|
|
28081
|
+
assertValidGitRef(branch, "branch");
|
|
28082
|
+
const projectRootOutput = gitSafe(projectPath, ["rev-parse", "--show-toplevel"]);
|
|
28083
|
+
if (!projectRootOutput) {
|
|
28084
|
+
throw new Error("The selected Project folder is not inside a Git repository");
|
|
28085
|
+
}
|
|
28086
|
+
const projectRoot = (0, import_node_path12.resolve)(projectRootOutput);
|
|
28087
|
+
const projectRelativePath = (0, import_node_path12.relative)(projectRoot, projectPath);
|
|
28088
|
+
if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(projectRelativePath)) {
|
|
28089
|
+
throw new Error("The selected Project folder is outside its Git repository");
|
|
28090
|
+
}
|
|
28091
|
+
const registered = git(projectRoot, ["worktree", "list", "--porcelain"]).split("\n\n").map((entry) => {
|
|
28092
|
+
const lines = entry.split("\n");
|
|
28093
|
+
return {
|
|
28094
|
+
path: lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length),
|
|
28095
|
+
branch: lines.find((line) => line.startsWith("branch "))?.slice("branch ".length).replace("refs/heads/", "")
|
|
28096
|
+
};
|
|
28097
|
+
});
|
|
28098
|
+
const registeredTarget = registered.find(({ path, branch: registeredBranch }) => {
|
|
28099
|
+
if (!path || registeredBranch !== branch) return false;
|
|
28100
|
+
const root = (0, import_node_path12.resolve)(path);
|
|
28101
|
+
return requestedWorktreePath === root || isPathContained2(requestedWorktreePath, root);
|
|
28102
|
+
});
|
|
28103
|
+
const currentBranch = gitSafe(projectRoot, ["branch", "--show-current"]);
|
|
28104
|
+
if (!registeredTarget?.path) {
|
|
28105
|
+
if (currentBranch === branch) {
|
|
28106
|
+
return {
|
|
28107
|
+
ok: true,
|
|
28108
|
+
replayed: true,
|
|
28109
|
+
migrated: [{ ...target, projectPath, worktreePath: requestedWorktreePath, branch }],
|
|
28110
|
+
...dirtyResolution ? { dirtyResolution } : {}
|
|
28111
|
+
};
|
|
28112
|
+
}
|
|
28113
|
+
throw new Error(
|
|
28114
|
+
`The task worktree is no longer registered and the Project is not on branch "${branch}".`
|
|
28115
|
+
);
|
|
28116
|
+
}
|
|
28117
|
+
const worktreeRoot = (0, import_node_path12.resolve)(registeredTarget.path);
|
|
28118
|
+
const worktreeRelativePath = (0, import_node_path12.relative)(worktreeRoot, requestedWorktreePath);
|
|
28119
|
+
if (worktreeRelativePath !== projectRelativePath) {
|
|
28120
|
+
throw new Error(
|
|
28121
|
+
"The configured Project and task worktree do not point at the same repository subdirectory."
|
|
28122
|
+
);
|
|
28123
|
+
}
|
|
28124
|
+
const allowedRoots = [worktreeDir(projectRoot), worktreeDir(projectPath)];
|
|
28125
|
+
if (!allowedRoots.some((allowedRoot) => isPathContained2(worktreeRoot, allowedRoot))) {
|
|
28126
|
+
throw new Error("The task worktree escapes the Project worktree directory.");
|
|
28127
|
+
}
|
|
28128
|
+
if (!(0, import_node_fs14.existsSync)(worktreeRoot)) {
|
|
28129
|
+
throw new Error(`Worktree directory not found: ${worktreeRoot}`);
|
|
28130
|
+
}
|
|
28131
|
+
const sourceBefore = preflightGitTree(worktreeRoot, git);
|
|
28132
|
+
const destinationBefore = preflightGitTree(projectRoot, git);
|
|
28133
|
+
if (sourceBefore.isDirty && !dirtyResolution?.sourceAction) {
|
|
28134
|
+
throw new Error(
|
|
28135
|
+
`The task worktree has ${sourceBefore.dirtyFileCount} uncommitted file${sourceBefore.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before migrating.`
|
|
28136
|
+
);
|
|
28137
|
+
}
|
|
28138
|
+
if (destinationBefore.isDirty && !dirtyResolution?.destinationAction) {
|
|
28139
|
+
throw new Error(
|
|
28140
|
+
`The Project checkout has ${destinationBefore.dirtyFileCount} uncommitted file${destinationBefore.dirtyFileCount === 1 ? "" : "s"}. Choose how to preserve them before migrating.`
|
|
28141
|
+
);
|
|
28142
|
+
}
|
|
28143
|
+
if (sourceBefore.isDirty && dirtyResolution?.sourceAction === "leave") {
|
|
28144
|
+
throw new Error("Task worktree changes cannot be left behind when releasing the worktree.");
|
|
28145
|
+
}
|
|
28146
|
+
if (destinationBefore.isDirty && dirtyResolution?.destinationAction === "leave") {
|
|
28147
|
+
throw new Error("Project checkout changes cannot stay in place while switching branches.");
|
|
28148
|
+
}
|
|
28149
|
+
const destinationResolved = destinationBefore.isDirty ? resolveDirtyTree(
|
|
28150
|
+
{
|
|
28151
|
+
cwd: projectRoot,
|
|
28152
|
+
action: dirtyResolution?.destinationAction ?? "leave",
|
|
28153
|
+
operation: "worktree-to-checkout-destination",
|
|
28154
|
+
confirmDiscard: dirtyResolution?.confirmDiscard
|
|
28155
|
+
},
|
|
28156
|
+
{ git }
|
|
28157
|
+
) : null;
|
|
28158
|
+
const sourceResolved = sourceBefore.isDirty ? resolveDirtyTree(
|
|
28159
|
+
{
|
|
28160
|
+
cwd: worktreeRoot,
|
|
28161
|
+
action: dirtyResolution?.sourceAction ?? "leave",
|
|
28162
|
+
operation: "worktree-to-checkout-source",
|
|
28163
|
+
confirmDiscard: dirtyResolution?.confirmDiscard
|
|
28164
|
+
},
|
|
28165
|
+
{ git }
|
|
28166
|
+
) : null;
|
|
28167
|
+
try {
|
|
28168
|
+
git(projectRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
28169
|
+
} catch {
|
|
28170
|
+
throw new Error(`The target branch "${branch}" is not available in the Project.`);
|
|
28171
|
+
}
|
|
28172
|
+
removeGit(projectRoot, ["worktree", "remove", worktreeRoot]);
|
|
28173
|
+
gitSafe(projectRoot, ["worktree", "prune"]);
|
|
28174
|
+
try {
|
|
28175
|
+
git(projectRoot, ["checkout", branch]);
|
|
28176
|
+
} catch (checkoutError) {
|
|
28177
|
+
try {
|
|
28178
|
+
git(projectRoot, ["worktree", "add", worktreeRoot, branch]);
|
|
28179
|
+
if (sourceResolved?.stash) {
|
|
28180
|
+
applyMigrationStash({ cwd: worktreeRoot, stash: sourceResolved.stash }, git);
|
|
28181
|
+
}
|
|
28182
|
+
if (destinationResolved?.stash) {
|
|
28183
|
+
applyMigrationStash({ cwd: projectRoot, stash: destinationResolved.stash }, git);
|
|
28184
|
+
}
|
|
28185
|
+
} catch (restoreError) {
|
|
28186
|
+
const checkoutMessage2 = checkoutError instanceof Error ? checkoutError.message : String(checkoutError);
|
|
28187
|
+
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
28188
|
+
throw new Error(
|
|
28189
|
+
`Project checkout failed (${checkoutMessage2}) and the task worktree could not be restored (${restoreMessage}).`
|
|
28190
|
+
);
|
|
28191
|
+
}
|
|
28192
|
+
const checkoutMessage = checkoutError instanceof Error ? checkoutError.message : String(checkoutError);
|
|
28193
|
+
throw new Error(
|
|
28194
|
+
`Project checkout failed (${checkoutMessage}); restored the task worktree at ${worktreeRoot}.`
|
|
28195
|
+
);
|
|
28196
|
+
}
|
|
28197
|
+
if (sourceResolved?.stash) {
|
|
28198
|
+
try {
|
|
28199
|
+
applyMigrationStash({ cwd: projectRoot, stash: sourceResolved.stash }, git);
|
|
28200
|
+
} catch (applyError) {
|
|
28201
|
+
gitSafe(projectRoot, ["reset", "--hard", "HEAD"]);
|
|
28202
|
+
gitSafe(projectRoot, ["clean", "-fd"]);
|
|
28203
|
+
try {
|
|
28204
|
+
if (destinationBefore.currentBranch && destinationBefore.currentBranch !== branch) {
|
|
28205
|
+
git(projectRoot, ["checkout", destinationBefore.currentBranch]);
|
|
28206
|
+
}
|
|
28207
|
+
git(projectRoot, ["worktree", "add", worktreeRoot, branch]);
|
|
28208
|
+
applyMigrationStash({ cwd: worktreeRoot, stash: sourceResolved.stash }, git);
|
|
28209
|
+
if (destinationResolved?.stash) {
|
|
28210
|
+
applyMigrationStash({ cwd: projectRoot, stash: destinationResolved.stash }, git);
|
|
28211
|
+
}
|
|
28212
|
+
} catch (restoreError) {
|
|
28213
|
+
const applyMessage2 = applyError instanceof Error ? applyError.message : String(applyError);
|
|
28214
|
+
const restoreMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
28215
|
+
throw new Error(
|
|
28216
|
+
`Moving uncommitted changes failed (${applyMessage2}); automatic workspace restoration also failed (${restoreMessage}). The named migration stash was preserved.`
|
|
28217
|
+
);
|
|
28218
|
+
}
|
|
28219
|
+
const applyMessage = applyError instanceof Error ? applyError.message : String(applyError);
|
|
28220
|
+
throw new Error(
|
|
28221
|
+
`Moving uncommitted changes failed (${applyMessage}); the original worktree and checkout were restored.`
|
|
28222
|
+
);
|
|
28223
|
+
}
|
|
28224
|
+
}
|
|
28225
|
+
return {
|
|
28226
|
+
ok: true,
|
|
28227
|
+
replayed: false,
|
|
28228
|
+
migrated: [{ ...target, projectPath, worktreePath: requestedWorktreePath, branch }],
|
|
28229
|
+
...dirtyResolution ? { dirtyResolution } : {}
|
|
26526
28230
|
};
|
|
26527
28231
|
}
|
|
26528
28232
|
function ensureSharedCheckoutBranch(input) {
|
|
26529
28233
|
assertValidGitRef(input.branchName, "branchName");
|
|
26530
28234
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26531
|
-
const repoPath = (0,
|
|
28235
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26532
28236
|
if (gitSafe(repoPath, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
|
|
26533
28237
|
return { checkedOut: false, reason: `${repoPath} is not a git work tree` };
|
|
26534
28238
|
}
|
|
@@ -26573,7 +28277,7 @@ function resolveSharedCheckoutTargets(input) {
|
|
|
26573
28277
|
}
|
|
26574
28278
|
function resolveRuntimeCwd(payload) {
|
|
26575
28279
|
const multiRepoWorktree = payload.worktreeInfo?.allWorktrees?.[0]?.worktreePath;
|
|
26576
|
-
if (multiRepoWorktree) return (0,
|
|
28280
|
+
if (multiRepoWorktree) return (0, import_node_path12.dirname)(multiRepoWorktree);
|
|
26577
28281
|
if (isAlanDefaultWorkspace(payload.projectPath)) return resolveAlanDefaultWorkspacePath();
|
|
26578
28282
|
return payload.worktreeInfo?.worktreePath ?? payload.projectPath ?? process.cwd();
|
|
26579
28283
|
}
|
|
@@ -26686,14 +28390,14 @@ function localServiceOwnerPayload(owner) {
|
|
|
26686
28390
|
].join("\0");
|
|
26687
28391
|
}
|
|
26688
28392
|
function signLocalServiceOwner(secret, owner) {
|
|
26689
|
-
return (0,
|
|
28393
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(localServiceOwnerPayload(owner)).digest("hex");
|
|
26690
28394
|
}
|
|
26691
28395
|
function signLocalServiceAdoption(secret, serviceId, challenge) {
|
|
26692
|
-
return (0,
|
|
28396
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(["adopt", serviceId, challenge].join("\0")).digest("hex");
|
|
26693
28397
|
}
|
|
26694
28398
|
function equalHexDigest(actual, expected) {
|
|
26695
28399
|
if (!/^[a-f0-9]{64}$/i.test(actual) || !/^[a-f0-9]{64}$/i.test(expected)) return false;
|
|
26696
|
-
return (0,
|
|
28400
|
+
return (0, import_node_crypto5.timingSafeEqual)(Buffer.from(actual, "hex"), Buffer.from(expected, "hex"));
|
|
26697
28401
|
}
|
|
26698
28402
|
function parseLocalServiceOwner(value2) {
|
|
26699
28403
|
if (!value2 || typeof value2 !== "object") return null;
|
|
@@ -26795,12 +28499,12 @@ function getConfigPath() {
|
|
|
26795
28499
|
return process.env.ALAN_AGENT_CONFIG_PATH ?? boundAgentConfigPath ?? resolveAgentConfigPath({ profile: "production" });
|
|
26796
28500
|
}
|
|
26797
28501
|
function getEndpointDefaultsPath() {
|
|
26798
|
-
return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0,
|
|
28502
|
+
return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0, import_node_path12.join)((0, import_node_path12.dirname)(getConfigPath()), "endpoints.json");
|
|
26799
28503
|
}
|
|
26800
28504
|
function readConfig() {
|
|
26801
28505
|
const configPath = getConfigPath();
|
|
26802
|
-
if (!(0,
|
|
26803
|
-
const parsed = JSON.parse((0,
|
|
28506
|
+
if (!(0, import_node_fs14.existsSync)(configPath)) return {};
|
|
28507
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(configPath, "utf8"));
|
|
26804
28508
|
const sanitized = sanitizeAgentConfig(parsed);
|
|
26805
28509
|
if (Object.keys(parsed).some((key) => !Object.hasOwn(sanitized, key))) {
|
|
26806
28510
|
writeConfig(sanitized);
|
|
@@ -26816,27 +28520,27 @@ function readConfigForRegistration() {
|
|
|
26816
28520
|
}
|
|
26817
28521
|
function readEndpointDefaults() {
|
|
26818
28522
|
const endpointsPath = getEndpointDefaultsPath();
|
|
26819
|
-
if (!(0,
|
|
28523
|
+
if (!(0, import_node_fs14.existsSync)(endpointsPath)) return {};
|
|
26820
28524
|
try {
|
|
26821
|
-
return JSON.parse((0,
|
|
28525
|
+
return JSON.parse((0, import_node_fs14.readFileSync)(endpointsPath, "utf8"));
|
|
26822
28526
|
} catch {
|
|
26823
28527
|
return {};
|
|
26824
28528
|
}
|
|
26825
28529
|
}
|
|
26826
28530
|
function writeConfig(config) {
|
|
26827
28531
|
const configPath = getConfigPath();
|
|
26828
|
-
(0,
|
|
26829
|
-
const pendingPath = `${configPath}.pending-${process.pid}-${(0,
|
|
28532
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(configPath), { recursive: true, mode: 448 });
|
|
28533
|
+
const pendingPath = `${configPath}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
26830
28534
|
try {
|
|
26831
|
-
(0,
|
|
28535
|
+
(0, import_node_fs14.writeFileSync)(
|
|
26832
28536
|
pendingPath,
|
|
26833
28537
|
JSON.stringify(sanitizeAgentConfig(config), null, 2),
|
|
26834
28538
|
{ mode: 384 }
|
|
26835
28539
|
);
|
|
26836
|
-
(0,
|
|
26837
|
-
(0,
|
|
28540
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28541
|
+
(0, import_node_fs14.renameSync)(pendingPath, configPath);
|
|
26838
28542
|
} finally {
|
|
26839
|
-
(0,
|
|
28543
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26840
28544
|
}
|
|
26841
28545
|
}
|
|
26842
28546
|
function maintenanceLeasePath(configPath) {
|
|
@@ -26844,9 +28548,9 @@ function maintenanceLeasePath(configPath) {
|
|
|
26844
28548
|
}
|
|
26845
28549
|
function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
26846
28550
|
const path = maintenanceLeasePath(configPath);
|
|
26847
|
-
if (!(0,
|
|
28551
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26848
28552
|
try {
|
|
26849
|
-
const lease = JSON.parse((0,
|
|
28553
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26850
28554
|
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
28555
|
throw new Error("invalid maintenance lease");
|
|
26852
28556
|
}
|
|
@@ -26863,8 +28567,8 @@ function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
|
26863
28567
|
expiresAtMs: nowMs + 5e3
|
|
26864
28568
|
};
|
|
26865
28569
|
try {
|
|
26866
|
-
(0,
|
|
26867
|
-
(0,
|
|
28570
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(fallback), { mode: 384 });
|
|
28571
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26868
28572
|
} catch {
|
|
26869
28573
|
}
|
|
26870
28574
|
return fallback;
|
|
@@ -26875,15 +28579,15 @@ function acquireLocalDaemonMaintenanceLease(configPath, reason, ttlMs = 5e3) {
|
|
|
26875
28579
|
if (existing) return { acquired: false, lease: existing };
|
|
26876
28580
|
const lease = {
|
|
26877
28581
|
version: 1,
|
|
26878
|
-
id: (0,
|
|
28582
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26879
28583
|
reason,
|
|
26880
28584
|
expiresAtMs: reason === "credential_cleanup" ? null : Date.now() + Math.max(1e3, Math.min(ttlMs, 6e4))
|
|
26881
28585
|
};
|
|
26882
28586
|
const path = maintenanceLeasePath(configPath);
|
|
26883
|
-
(0,
|
|
28587
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26884
28588
|
try {
|
|
26885
|
-
(0,
|
|
26886
|
-
(0,
|
|
28589
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28590
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26887
28591
|
return { acquired: true, lease };
|
|
26888
28592
|
} catch (error) {
|
|
26889
28593
|
const raced = readLocalDaemonMaintenanceLease(configPath);
|
|
@@ -26899,9 +28603,9 @@ function daemonOwnerLeasePath(configPath) {
|
|
|
26899
28603
|
}
|
|
26900
28604
|
function readLocalDaemonOwnerLease(configPath) {
|
|
26901
28605
|
const path = daemonOwnerLeasePath(configPath);
|
|
26902
|
-
if (!(0,
|
|
28606
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26903
28607
|
try {
|
|
26904
|
-
const lease = JSON.parse((0,
|
|
28608
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26905
28609
|
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
28610
|
throw new Error("invalid daemon owner lease");
|
|
26907
28611
|
}
|
|
@@ -26943,7 +28647,7 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26943
28647
|
}
|
|
26944
28648
|
const lease = {
|
|
26945
28649
|
version: 1,
|
|
26946
|
-
id: (0,
|
|
28650
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26947
28651
|
daemonSessionId: input.daemonSessionId,
|
|
26948
28652
|
pid: process.pid,
|
|
26949
28653
|
startedAtMs: Date.now(),
|
|
@@ -26953,10 +28657,10 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26953
28657
|
localApiToken: input.localApiToken
|
|
26954
28658
|
};
|
|
26955
28659
|
const path = daemonOwnerLeasePath(input.configPath);
|
|
26956
|
-
(0,
|
|
28660
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26957
28661
|
try {
|
|
26958
|
-
(0,
|
|
26959
|
-
(0,
|
|
28662
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28663
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26960
28664
|
return { acquired: true, lease };
|
|
26961
28665
|
} catch (error) {
|
|
26962
28666
|
const raced = readLocalDaemonOwnerLease(input.configPath);
|
|
@@ -26971,27 +28675,27 @@ function updateLocalDaemonOwnerEndpoint(configPath, lease, localApiPort) {
|
|
|
26971
28675
|
const path = daemonOwnerLeasePath(configPath);
|
|
26972
28676
|
const pendingPath = `${path}.pending-${lease.id}`;
|
|
26973
28677
|
try {
|
|
26974
|
-
(0,
|
|
26975
|
-
(0,
|
|
26976
|
-
(0,
|
|
28678
|
+
(0, import_node_fs14.writeFileSync)(pendingPath, JSON.stringify(updated), { mode: 384 });
|
|
28679
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28680
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
26977
28681
|
return updated;
|
|
26978
28682
|
} finally {
|
|
26979
|
-
(0,
|
|
28683
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26980
28684
|
}
|
|
26981
28685
|
}
|
|
26982
28686
|
function releaseLocalDaemonOwnerLease(configPath, leaseId) {
|
|
26983
28687
|
releaseOwnedJsonFile(daemonOwnerLeasePath(configPath), leaseId);
|
|
26984
28688
|
}
|
|
26985
28689
|
function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
26986
|
-
const releasePath = `${path}.release-${process.pid}-${(0,
|
|
28690
|
+
const releasePath = `${path}.release-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
26987
28691
|
try {
|
|
26988
|
-
(0,
|
|
28692
|
+
(0, import_node_fs14.renameSync)(path, releasePath);
|
|
26989
28693
|
} catch (error) {
|
|
26990
28694
|
if (error.code === "ENOENT") return;
|
|
26991
28695
|
throw error;
|
|
26992
28696
|
}
|
|
26993
28697
|
try {
|
|
26994
|
-
const content = (0,
|
|
28698
|
+
const content = (0, import_node_fs14.readFileSync)(releasePath, "utf8");
|
|
26995
28699
|
let capturedOwnerId = null;
|
|
26996
28700
|
try {
|
|
26997
28701
|
const captured = JSON.parse(content);
|
|
@@ -27000,13 +28704,13 @@ function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
|
27000
28704
|
}
|
|
27001
28705
|
if (capturedOwnerId === ownerId) return;
|
|
27002
28706
|
try {
|
|
27003
|
-
(0,
|
|
27004
|
-
(0,
|
|
28707
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
28708
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27005
28709
|
} catch (error) {
|
|
27006
28710
|
if (error.code !== "EEXIST") throw error;
|
|
27007
28711
|
}
|
|
27008
28712
|
} finally {
|
|
27009
|
-
(0,
|
|
28713
|
+
(0, import_node_fs14.rmSync)(releasePath, { force: true });
|
|
27010
28714
|
}
|
|
27011
28715
|
}
|
|
27012
28716
|
function inspectLocalDaemonRunJournal(configPath) {
|
|
@@ -27016,8 +28720,8 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
27016
28720
|
try {
|
|
27017
28721
|
let activeRunCount = 0;
|
|
27018
28722
|
for (const artifact of [path, pendingPath]) {
|
|
27019
|
-
if (!(0,
|
|
27020
|
-
const parsed = JSON.parse((0,
|
|
28723
|
+
if (!(0, import_node_fs14.existsSync)(artifact)) continue;
|
|
28724
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(artifact, "utf8"));
|
|
27021
28725
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries))
|
|
27022
28726
|
throw new Error("invalid journal");
|
|
27023
28727
|
if (artifact === path) {
|
|
@@ -27036,7 +28740,7 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
27036
28740
|
}).length;
|
|
27037
28741
|
continue;
|
|
27038
28742
|
}
|
|
27039
|
-
const legacyStartedAtMs = (0,
|
|
28743
|
+
const legacyStartedAtMs = (0, import_node_fs14.statSync)(pendingPath).mtimeMs;
|
|
27040
28744
|
activeRunCount += parsed.entries.filter((entry) => {
|
|
27041
28745
|
if (!entry || typeof entry !== "object") throw new Error("invalid pending run entry");
|
|
27042
28746
|
const startedAtMs = entry.startedAtMs;
|
|
@@ -27058,38 +28762,38 @@ function pendingSetupIntentPath() {
|
|
|
27058
28762
|
return `${getConfigPath()}.setup-pending`;
|
|
27059
28763
|
}
|
|
27060
28764
|
function setupTokenHash(setupToken) {
|
|
27061
|
-
return (0,
|
|
28765
|
+
return (0, import_node_crypto5.createHash)("sha256").update(setupToken).digest("hex");
|
|
27062
28766
|
}
|
|
27063
28767
|
function getOrCreateSetupAttemptId(setupToken) {
|
|
27064
28768
|
const intentPath = pendingSetupIntentPath();
|
|
27065
28769
|
const tokenHash = setupTokenHash(setupToken);
|
|
27066
|
-
if ((0,
|
|
28770
|
+
if ((0, import_node_fs14.existsSync)(intentPath)) {
|
|
27067
28771
|
try {
|
|
27068
|
-
const stored = JSON.parse((0,
|
|
28772
|
+
const stored = JSON.parse((0, import_node_fs14.readFileSync)(intentPath, "utf8"));
|
|
27069
28773
|
if (stored.setupTokenHash === tokenHash && typeof stored.registrationAttemptId === "string" && stored.registrationAttemptId.length > 0) {
|
|
27070
28774
|
return stored.registrationAttemptId;
|
|
27071
28775
|
}
|
|
27072
28776
|
} catch {
|
|
27073
28777
|
}
|
|
27074
28778
|
}
|
|
27075
|
-
const registrationAttemptId = (0,
|
|
27076
|
-
(0,
|
|
27077
|
-
const writePath = `${intentPath}.pending-${process.pid}-${(0,
|
|
28779
|
+
const registrationAttemptId = (0, import_node_crypto5.randomUUID)();
|
|
28780
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(intentPath), { recursive: true, mode: 448 });
|
|
28781
|
+
const writePath = `${intentPath}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27078
28782
|
try {
|
|
27079
|
-
(0,
|
|
28783
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27080
28784
|
writePath,
|
|
27081
28785
|
JSON.stringify({ setupTokenHash: tokenHash, registrationAttemptId }, null, 2),
|
|
27082
28786
|
{ mode: 384 }
|
|
27083
28787
|
);
|
|
27084
|
-
(0,
|
|
27085
|
-
(0,
|
|
28788
|
+
(0, import_node_fs14.chmodSync)(writePath, 384);
|
|
28789
|
+
(0, import_node_fs14.renameSync)(writePath, intentPath);
|
|
27086
28790
|
} finally {
|
|
27087
|
-
(0,
|
|
28791
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27088
28792
|
}
|
|
27089
28793
|
return registrationAttemptId;
|
|
27090
28794
|
}
|
|
27091
28795
|
function clearPendingSetupIntent() {
|
|
27092
|
-
(0,
|
|
28796
|
+
(0, import_node_fs14.rmSync)(pendingSetupIntentPath(), { force: true });
|
|
27093
28797
|
}
|
|
27094
28798
|
function pendingDeviceAuthorizationPath() {
|
|
27095
28799
|
return `${getConfigPath()}.device-auth-pending`;
|
|
@@ -27110,35 +28814,35 @@ function parseDeviceAuthorizationOperationContent(content) {
|
|
|
27110
28814
|
}
|
|
27111
28815
|
function restoreCapturedFileUnlessReplaced(path, content) {
|
|
27112
28816
|
try {
|
|
27113
|
-
(0,
|
|
27114
|
-
(0,
|
|
28817
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
28818
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27115
28819
|
} catch (error) {
|
|
27116
28820
|
if (error.code !== "EEXIST") throw error;
|
|
27117
28821
|
}
|
|
27118
28822
|
}
|
|
27119
28823
|
function quarantineCorruptDeviceAuthorizationOperation(path) {
|
|
27120
|
-
const quarantinePath = `${path}.corrupt-${process.pid}-${(0,
|
|
28824
|
+
const quarantinePath = `${path}.corrupt-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27121
28825
|
try {
|
|
27122
|
-
(0,
|
|
28826
|
+
(0, import_node_fs14.renameSync)(path, quarantinePath);
|
|
27123
28827
|
} catch (error) {
|
|
27124
28828
|
if (error.code === "ENOENT") return null;
|
|
27125
28829
|
throw error;
|
|
27126
28830
|
}
|
|
27127
28831
|
try {
|
|
27128
|
-
const capturedContent = (0,
|
|
28832
|
+
const capturedContent = (0, import_node_fs14.readFileSync)(quarantinePath, "utf8");
|
|
27129
28833
|
if (parseDeviceAuthorizationOperationContent(capturedContent)) {
|
|
27130
28834
|
restoreCapturedFileUnlessReplaced(path, capturedContent);
|
|
27131
28835
|
}
|
|
27132
28836
|
} finally {
|
|
27133
|
-
(0,
|
|
28837
|
+
(0, import_node_fs14.rmSync)(quarantinePath, { force: true });
|
|
27134
28838
|
}
|
|
27135
|
-
if (!(0,
|
|
27136
|
-
return parseDeviceAuthorizationOperationContent((0,
|
|
28839
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
28840
|
+
return parseDeviceAuthorizationOperationContent((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27137
28841
|
}
|
|
27138
28842
|
function readDeviceAuthorizationOperation() {
|
|
27139
28843
|
const path = deviceAuthorizationOperationPath();
|
|
27140
|
-
if (!(0,
|
|
27141
|
-
const parsed = parseDeviceAuthorizationOperationContent((0,
|
|
28844
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
28845
|
+
const parsed = parseDeviceAuthorizationOperationContent((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27142
28846
|
const lease = parsed ?? quarantineCorruptDeviceAuthorizationOperation(path);
|
|
27143
28847
|
if (!lease) return null;
|
|
27144
28848
|
if (lease.expiresAtMs <= Date.now()) {
|
|
@@ -27152,14 +28856,14 @@ function acquireDeviceAuthorizationOperation() {
|
|
|
27152
28856
|
if (existing) return null;
|
|
27153
28857
|
const lease = {
|
|
27154
28858
|
version: 1,
|
|
27155
|
-
id: (0,
|
|
28859
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27156
28860
|
expiresAtMs: Date.now() + 6e4
|
|
27157
28861
|
};
|
|
27158
28862
|
const path = deviceAuthorizationOperationPath();
|
|
27159
|
-
(0,
|
|
28863
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
27160
28864
|
try {
|
|
27161
|
-
(0,
|
|
27162
|
-
(0,
|
|
28865
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28866
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27163
28867
|
return lease;
|
|
27164
28868
|
} catch (error) {
|
|
27165
28869
|
if (error.code === "EEXIST") return null;
|
|
@@ -27172,17 +28876,17 @@ function extendDeviceAuthorizationOperation(operationId, expiresAtMs) {
|
|
|
27172
28876
|
throw new Error("browser authorization operation ownership was lost");
|
|
27173
28877
|
}
|
|
27174
28878
|
const path = deviceAuthorizationOperationPath();
|
|
27175
|
-
const pendingPath = `${path}.pending-${process.pid}-${(0,
|
|
28879
|
+
const pendingPath = `${path}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27176
28880
|
try {
|
|
27177
|
-
(0,
|
|
28881
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27178
28882
|
pendingPath,
|
|
27179
28883
|
JSON.stringify({ ...current, expiresAtMs: Math.max(expiresAtMs, Date.now() + 6e4) }),
|
|
27180
28884
|
{ mode: 384 }
|
|
27181
28885
|
);
|
|
27182
|
-
(0,
|
|
27183
|
-
(0,
|
|
28886
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28887
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
27184
28888
|
} finally {
|
|
27185
|
-
(0,
|
|
28889
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27186
28890
|
}
|
|
27187
28891
|
}
|
|
27188
28892
|
function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
@@ -27193,12 +28897,12 @@ function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
|
27193
28897
|
function clearPendingDeviceAuthorization(operationId) {
|
|
27194
28898
|
const path = pendingDeviceAuthorizationPath();
|
|
27195
28899
|
if (!operationId) {
|
|
27196
|
-
(0,
|
|
28900
|
+
(0, import_node_fs14.rmSync)(path, { force: true });
|
|
27197
28901
|
return;
|
|
27198
28902
|
}
|
|
27199
|
-
if (!(0,
|
|
28903
|
+
if (!(0, import_node_fs14.existsSync)(path)) return;
|
|
27200
28904
|
try {
|
|
27201
|
-
const pending = JSON.parse((0,
|
|
28905
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27202
28906
|
if (pending.operationId !== operationId) return;
|
|
27203
28907
|
} catch {
|
|
27204
28908
|
return;
|
|
@@ -27207,9 +28911,9 @@ function clearPendingDeviceAuthorization(operationId) {
|
|
|
27207
28911
|
}
|
|
27208
28912
|
function readPendingDeviceAuthorization(apiUrl) {
|
|
27209
28913
|
const path = pendingDeviceAuthorizationPath();
|
|
27210
|
-
if (!(0,
|
|
28914
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
27211
28915
|
try {
|
|
27212
|
-
const pending = JSON.parse((0,
|
|
28916
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27213
28917
|
const expiresAtMs = Date.parse(pending.expiresAt ?? "");
|
|
27214
28918
|
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
28919
|
return null;
|
|
@@ -27221,20 +28925,20 @@ function readPendingDeviceAuthorization(apiUrl) {
|
|
|
27221
28925
|
}
|
|
27222
28926
|
function writePendingDeviceAuthorization(pending) {
|
|
27223
28927
|
const path = pendingDeviceAuthorizationPath();
|
|
27224
|
-
(0,
|
|
27225
|
-
const writePath = `${path}.pending-${process.pid}-${(0,
|
|
28928
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
28929
|
+
const writePath = `${path}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27226
28930
|
try {
|
|
27227
|
-
(0,
|
|
27228
|
-
(0,
|
|
27229
|
-
(0,
|
|
28931
|
+
(0, import_node_fs14.writeFileSync)(writePath, JSON.stringify(pending, null, 2), { mode: 384 });
|
|
28932
|
+
(0, import_node_fs14.chmodSync)(writePath, 384);
|
|
28933
|
+
(0, import_node_fs14.renameSync)(writePath, path);
|
|
27230
28934
|
} finally {
|
|
27231
|
-
(0,
|
|
28935
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27232
28936
|
}
|
|
27233
28937
|
}
|
|
27234
28938
|
function removeAlanOwnedLocalArtifacts() {
|
|
27235
28939
|
const configPath = getConfigPath();
|
|
27236
|
-
const directory = (0,
|
|
27237
|
-
const configName = (0,
|
|
28940
|
+
const directory = (0, import_node_path12.dirname)(configPath);
|
|
28941
|
+
const configName = (0, import_node_path12.basename)(configPath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27238
28942
|
const alanAtomicSidecarPattern = new RegExp(
|
|
27239
28943
|
`^${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
28944
|
);
|
|
@@ -27251,13 +28955,13 @@ function removeAlanOwnedLocalArtifacts() {
|
|
|
27251
28955
|
`${configPath}.pending-runs.json.pending`,
|
|
27252
28956
|
maintenanceLeasePath(configPath)
|
|
27253
28957
|
]);
|
|
27254
|
-
if ((0,
|
|
27255
|
-
for (const entry of (0,
|
|
27256
|
-
if (alanAtomicSidecarPattern.test(entry)) artifacts.add((0,
|
|
28958
|
+
if ((0, import_node_fs14.existsSync)(directory)) {
|
|
28959
|
+
for (const entry of (0, import_node_fs14.readdirSync)(directory)) {
|
|
28960
|
+
if (alanAtomicSidecarPattern.test(entry)) artifacts.add((0, import_node_path12.join)(directory, entry));
|
|
27257
28961
|
}
|
|
27258
28962
|
}
|
|
27259
|
-
for (const artifact of artifacts) (0,
|
|
27260
|
-
(0,
|
|
28963
|
+
for (const artifact of artifacts) (0, import_node_fs14.rmSync)(artifact, { force: true });
|
|
28964
|
+
(0, import_node_fs14.rmSync)(configPath, { force: true });
|
|
27261
28965
|
}
|
|
27262
28966
|
function argValue(args, name) {
|
|
27263
28967
|
const index = args.indexOf(name);
|
|
@@ -27313,7 +29017,7 @@ function getLocalApiPort(args, stored) {
|
|
|
27313
29017
|
}
|
|
27314
29018
|
function sleep(ms) {
|
|
27315
29019
|
if (ms <= 0) return Promise.resolve();
|
|
27316
|
-
return new Promise((
|
|
29020
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
27317
29021
|
}
|
|
27318
29022
|
function normalizeBackendKind2(backendKind) {
|
|
27319
29023
|
if (backendKind === "codex") return "codex_app_server";
|
|
@@ -27479,13 +29183,13 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27479
29183
|
}
|
|
27480
29184
|
persist() {
|
|
27481
29185
|
if (this.size === 0) {
|
|
27482
|
-
(0,
|
|
29186
|
+
(0, import_node_fs14.rmSync)(this.path, { force: true });
|
|
27483
29187
|
return;
|
|
27484
29188
|
}
|
|
27485
29189
|
const pendingPath = `${this.path}.pending`;
|
|
27486
|
-
(0,
|
|
29190
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(this.path), { recursive: true, mode: 448 });
|
|
27487
29191
|
try {
|
|
27488
|
-
(0,
|
|
29192
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27489
29193
|
pendingPath,
|
|
27490
29194
|
JSON.stringify({
|
|
27491
29195
|
version: 1,
|
|
@@ -27498,10 +29202,10 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27498
29202
|
}),
|
|
27499
29203
|
{ mode: 384 }
|
|
27500
29204
|
);
|
|
27501
|
-
(0,
|
|
27502
|
-
(0,
|
|
29205
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
29206
|
+
(0, import_node_fs14.renameSync)(pendingPath, this.path);
|
|
27503
29207
|
} finally {
|
|
27504
|
-
(0,
|
|
29208
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27505
29209
|
}
|
|
27506
29210
|
}
|
|
27507
29211
|
};
|
|
@@ -27641,7 +29345,7 @@ function buildApplicationRunFailure(input) {
|
|
|
27641
29345
|
const problem = createAlanProblem({
|
|
27642
29346
|
domain: "application",
|
|
27643
29347
|
code: input.code,
|
|
27644
|
-
id: (0,
|
|
29348
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27645
29349
|
title: "Alan could not complete the agent run",
|
|
27646
29350
|
detail: input.detail,
|
|
27647
29351
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27736,21 +29440,21 @@ function reconcileActiveRuns(input) {
|
|
|
27736
29440
|
}
|
|
27737
29441
|
}
|
|
27738
29442
|
function collectRuntimeMetadata() {
|
|
27739
|
-
const cpuList = (0,
|
|
29443
|
+
const cpuList = (0, import_node_os9.cpus)();
|
|
27740
29444
|
const metadata = {
|
|
27741
|
-
platform: (0,
|
|
27742
|
-
arch: (0,
|
|
27743
|
-
osVersion: (0,
|
|
29445
|
+
platform: (0, import_node_os9.platform)(),
|
|
29446
|
+
arch: (0, import_node_os9.arch)(),
|
|
29447
|
+
osVersion: (0, import_node_os9.release)(),
|
|
27744
29448
|
agentVersion: AGENT_VERSION,
|
|
27745
|
-
memoryBytes: (0,
|
|
27746
|
-
memoryFreeBytes: (0,
|
|
29449
|
+
memoryBytes: (0, import_node_os9.totalmem)(),
|
|
29450
|
+
memoryFreeBytes: (0, import_node_os9.freemem)()
|
|
27747
29451
|
};
|
|
27748
29452
|
if (cpuList.length > 0) {
|
|
27749
29453
|
metadata.cpuModel = cpuList[0]?.model;
|
|
27750
29454
|
metadata.cpuCores = cpuList.length;
|
|
27751
29455
|
}
|
|
27752
29456
|
try {
|
|
27753
|
-
const disk = (0,
|
|
29457
|
+
const disk = (0, import_node_fs14.statfsSync)((0, import_node_os9.homedir)());
|
|
27754
29458
|
metadata.diskFreeBytes = disk.bavail * disk.bsize;
|
|
27755
29459
|
metadata.diskTotalBytes = disk.blocks * disk.bsize;
|
|
27756
29460
|
} catch {
|
|
@@ -27968,7 +29672,7 @@ var RuntimePresenter = class {
|
|
|
27968
29672
|
this.applicationProblem = createAlanProblem({
|
|
27969
29673
|
domain: "application",
|
|
27970
29674
|
code: /local path no longer exists/i.test(message) ? "app.workspace.missing" : "app.internal",
|
|
27971
|
-
id: (0,
|
|
29675
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27972
29676
|
title: "Alan could not start the agent run",
|
|
27973
29677
|
detail: message,
|
|
27974
29678
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28006,9 +29710,9 @@ async function setupDaemon(args) {
|
|
|
28006
29710
|
const teamId = argValue(args, "--team") ?? process.env.ALAN_TEAM_ID;
|
|
28007
29711
|
const setupToken = argValue(args, "--setup-token") ?? process.env.ALAN_RUNTIME_SETUP_TOKEN;
|
|
28008
29712
|
const token = argValue(args, "--token") ?? process.env.ALAN_SETUP_TOKEN;
|
|
28009
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
29713
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
28010
29714
|
const scope = argValue(args, "--scope") ?? process.env.ALAN_RUNTIME_SCOPE ?? (setupToken ? "team" : "user");
|
|
28011
|
-
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0,
|
|
29715
|
+
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28012
29716
|
if (scope !== "team" && scope !== "user") {
|
|
28013
29717
|
throw new Error("setup --scope must be either 'team' or 'user'");
|
|
28014
29718
|
}
|
|
@@ -28017,7 +29721,7 @@ async function setupDaemon(args) {
|
|
|
28017
29721
|
"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
29722
|
);
|
|
28019
29723
|
}
|
|
28020
|
-
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0,
|
|
29724
|
+
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0, import_node_crypto5.randomUUID)();
|
|
28021
29725
|
const registrationUrl = `${apiUrl}/public/runtimes${setupToken ? "/register-with-setup-token" : ""}`;
|
|
28022
29726
|
const registrationInit = {
|
|
28023
29727
|
method: "POST",
|
|
@@ -28029,7 +29733,7 @@ async function setupDaemon(args) {
|
|
|
28029
29733
|
...teamId ? { teamId } : {},
|
|
28030
29734
|
...setupToken ? { setupToken, registrationAttemptId } : {},
|
|
28031
29735
|
displayName,
|
|
28032
|
-
hostname: (0,
|
|
29736
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
28033
29737
|
runtimeKind: "machine",
|
|
28034
29738
|
managementKind: "user_managed",
|
|
28035
29739
|
hostKind: "daemon",
|
|
@@ -28052,7 +29756,7 @@ async function setupDaemon(args) {
|
|
|
28052
29756
|
}
|
|
28053
29757
|
const retriableStatus = response && (response.status === 408 || response.status === 429 || response.status >= 500);
|
|
28054
29758
|
if (response && (!retriableStatus || response.ok) || attempt === 3) break;
|
|
28055
|
-
await new Promise((
|
|
29759
|
+
await new Promise((resolve7) => setTimeout(resolve7, attempt * 100));
|
|
28056
29760
|
}
|
|
28057
29761
|
if (!response) {
|
|
28058
29762
|
const detail = transportError instanceof Error ? transportError.message : String(transportError);
|
|
@@ -28083,10 +29787,10 @@ async function setupDaemon(args) {
|
|
|
28083
29787
|
runtimeToken: body.runtimeToken,
|
|
28084
29788
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28085
29789
|
localApiPort: getLocalApiPort(args, {}),
|
|
28086
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28087
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28088
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28089
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0,
|
|
29790
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex"),
|
|
29791
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto5.randomUUID)(),
|
|
29792
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
29793
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
28090
29794
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28091
29795
|
displayName: body.runtime.displayName ?? displayName,
|
|
28092
29796
|
installationId
|
|
@@ -28113,10 +29817,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28113
29817
|
}
|
|
28114
29818
|
const previousConfig = readConfigForRegistration();
|
|
28115
29819
|
const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
|
|
28116
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
29820
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
28117
29821
|
const maxPolls = Number.parseInt(argValue(args, "--max-polls") ?? "300", 10);
|
|
28118
29822
|
const resumed = readPendingDeviceAuthorization(apiUrl);
|
|
28119
|
-
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0,
|
|
29823
|
+
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28120
29824
|
let authorization = resumed ? { ...resumed, operationId: operation.id } : null;
|
|
28121
29825
|
if (!authorization) {
|
|
28122
29826
|
clearPendingDeviceAuthorization();
|
|
@@ -28125,7 +29829,7 @@ async function loginWithDeviceCode(args) {
|
|
|
28125
29829
|
headers: { "content-type": "application/json" },
|
|
28126
29830
|
body: JSON.stringify({
|
|
28127
29831
|
displayName,
|
|
28128
|
-
hostname: (0,
|
|
29832
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
28129
29833
|
capabilities: discoverCapabilities(),
|
|
28130
29834
|
metadata: {
|
|
28131
29835
|
...await collectRuntimeMetadataWithProviderLimits(),
|
|
@@ -28192,10 +29896,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28192
29896
|
runtimeToken: body.runtimeToken,
|
|
28193
29897
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28194
29898
|
localApiPort: getLocalApiPort(args, {}),
|
|
28195
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28196
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28197
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28198
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0,
|
|
29899
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex"),
|
|
29900
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto5.randomUUID)(),
|
|
29901
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
29902
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url"),
|
|
28199
29903
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28200
29904
|
displayName: body.runtime.displayName ?? displayName,
|
|
28201
29905
|
installationId
|
|
@@ -28215,10 +29919,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28215
29919
|
async function startDaemon(args) {
|
|
28216
29920
|
bindConfigPath(args);
|
|
28217
29921
|
const stored = readConfig();
|
|
28218
|
-
const installationId = stored.installationId ?? (0,
|
|
29922
|
+
const installationId = stored.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28219
29923
|
const configPath = getConfigPath();
|
|
28220
29924
|
const endpointProfile = stored.endpointProfile ?? resolveEndpointProfile(args);
|
|
28221
|
-
const daemonSessionId = (0,
|
|
29925
|
+
const daemonSessionId = (0, import_node_crypto5.randomUUID)();
|
|
28222
29926
|
let heartbeatSeq = 0;
|
|
28223
29927
|
const runtimeId = argValue(args, "--runtime-id") ?? process.env.ALAN_RUNTIME_ID ?? stored.runtimeId;
|
|
28224
29928
|
const runtimeTokenOverride = argValue(args, "--token") ?? process.env.ALAN_RUNTIME_TOKEN;
|
|
@@ -28235,11 +29939,11 @@ async function startDaemon(args) {
|
|
|
28235
29939
|
wsUrl = endpointMismatch.expectedWsUrl;
|
|
28236
29940
|
}
|
|
28237
29941
|
let localApiPort = getLocalApiPort(args, stored);
|
|
28238
|
-
const localApiToken = stored.localApiToken ?? (0,
|
|
28239
|
-
const localServiceId = stored.localServiceId ?? (0,
|
|
28240
|
-
const localServiceSecret = stored.localServiceSecret ?? (0,
|
|
29942
|
+
const localApiToken = stored.localApiToken ?? (0, import_node_crypto5.randomBytes)(24).toString("hex");
|
|
29943
|
+
const localServiceId = stored.localServiceId ?? (0, import_node_crypto5.randomUUID)();
|
|
29944
|
+
const localServiceSecret = stored.localServiceSecret ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
28241
29945
|
const localServiceStartedAtMs = Date.now();
|
|
28242
|
-
const eventSpoolKey = stored.eventSpoolKey ?? (0,
|
|
29946
|
+
const eventSpoolKey = stored.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
28243
29947
|
if (stored.daemonEpoch !== void 0 && (!Number.isSafeInteger(stored.daemonEpoch) || stored.daemonEpoch < 0 || stored.daemonEpoch >= Number.MAX_SAFE_INTEGER)) {
|
|
28244
29948
|
throw new Error("daemon config contains an invalid durable epoch; repair the runtime config");
|
|
28245
29949
|
}
|
|
@@ -28359,7 +30063,7 @@ async function startDaemon(args) {
|
|
|
28359
30063
|
const workspaceRelocations = /* @__PURE__ */ new Map();
|
|
28360
30064
|
for (const relocation of stored.workspaceRelocations ?? []) {
|
|
28361
30065
|
if (relocation?.conversationId && relocation.runId && relocation.previousPath && relocation.canonicalPath) {
|
|
28362
|
-
workspaceRelocations.set((0,
|
|
30066
|
+
workspaceRelocations.set((0, import_node_path12.resolve)(relocation.previousPath), relocation);
|
|
28363
30067
|
}
|
|
28364
30068
|
}
|
|
28365
30069
|
if (stored.localApiPort !== localApiPort || stored.localApiToken !== localApiToken || stored.localServiceId !== localServiceId || stored.localServiceSecret !== localServiceSecret || stored.eventSpoolKey !== eventSpoolKey || stored.daemonEpoch !== daemonEpoch || stored.installationId !== installationId) {
|
|
@@ -28675,20 +30379,25 @@ async function startDaemon(args) {
|
|
|
28675
30379
|
pendingDeliveryLost.push({ conversationId: entry.conversationId, runId });
|
|
28676
30380
|
}
|
|
28677
30381
|
});
|
|
28678
|
-
socket.on("desktop:rpc", (message) => {
|
|
30382
|
+
socket.on("desktop:rpc", async (message) => {
|
|
28679
30383
|
if (!message?.id || !message.method) return;
|
|
28680
30384
|
try {
|
|
28681
30385
|
const params = message.params ?? {};
|
|
28682
|
-
|
|
30386
|
+
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
30387
|
throw new Error(`Method not found: ${message.method}`);
|
|
28684
|
-
}
|
|
28685
|
-
const result = ensureWorktree(params);
|
|
30388
|
+
})();
|
|
28686
30389
|
socket.emit("desktop:rpc", { jsonrpc: "2.0", id: message.id, result });
|
|
28687
30390
|
} catch (error) {
|
|
30391
|
+
const stableCode = error instanceof BindingSwitchGitError ? error.code : "runtime_operation_failed";
|
|
30392
|
+
const stableDetails = error instanceof BindingSwitchGitError ? error.details : void 0;
|
|
28688
30393
|
socket.emit("desktop:rpc", {
|
|
28689
30394
|
jsonrpc: "2.0",
|
|
28690
30395
|
id: message.id,
|
|
28691
|
-
error: {
|
|
30396
|
+
error: {
|
|
30397
|
+
code: -32e3,
|
|
30398
|
+
message: error.message,
|
|
30399
|
+
data: { ...stableDetails, code: stableCode }
|
|
30400
|
+
}
|
|
28692
30401
|
});
|
|
28693
30402
|
}
|
|
28694
30403
|
});
|
|
@@ -28696,6 +30405,17 @@ async function startDaemon(args) {
|
|
|
28696
30405
|
if (!payload?.runId || !payload.conversationId || typeof payload.content !== "string") {
|
|
28697
30406
|
return;
|
|
28698
30407
|
}
|
|
30408
|
+
const isTaskWorkspacePayload = Boolean(
|
|
30409
|
+
payload.worktreeInfo?.worktreePath || payload.worktreeInfo?.allWorktrees?.length
|
|
30410
|
+
);
|
|
30411
|
+
if ((payload.worktreeInfo?.allWorktrees?.length ?? 0) > 1 || isTaskWorkspacePayload && (payload.repoLocalPaths?.length ?? 0) > 1) {
|
|
30412
|
+
socket.emit("agent.rejected", {
|
|
30413
|
+
runId: payload.runId,
|
|
30414
|
+
message: "A task worktree requires exactly one Git repository.",
|
|
30415
|
+
code: "single_repository_worktree_required"
|
|
30416
|
+
});
|
|
30417
|
+
return;
|
|
30418
|
+
}
|
|
28699
30419
|
const existingRun = activeAgents.get(payload.runId);
|
|
28700
30420
|
if (existingRun) {
|
|
28701
30421
|
if (existingRun.conversationId !== payload.conversationId) {
|
|
@@ -28788,31 +30508,31 @@ async function startDaemon(args) {
|
|
|
28788
30508
|
const workspaceRequired = Boolean(explicitWorkspacePath);
|
|
28789
30509
|
if (isAlanDefaultWorkspace(payload.projectPath)) {
|
|
28790
30510
|
try {
|
|
28791
|
-
(0,
|
|
30511
|
+
(0, import_node_fs14.mkdirSync)(cwd, { recursive: true });
|
|
28792
30512
|
} catch {
|
|
28793
30513
|
}
|
|
28794
30514
|
}
|
|
28795
30515
|
let workspaceReadiness = inspectWorkspaceReadiness(workspaceRequired ? cwd : void 0);
|
|
28796
30516
|
const expectedRepoUrls = [
|
|
28797
30517
|
...(payload.repoLocalPaths ?? []).filter(
|
|
28798
|
-
(repo) => explicitWorkspacePath ? (0,
|
|
30518
|
+
(repo) => explicitWorkspacePath ? (0, import_node_path12.resolve)(repo.localPath) === (0, import_node_path12.resolve)(explicitWorkspacePath) : false
|
|
28799
30519
|
).map((repo) => repo.repoUrl),
|
|
28800
30520
|
...payload.localRepoUrls ?? []
|
|
28801
30521
|
];
|
|
28802
30522
|
if (workspaceReadiness.code === "workspace_missing" && explicitWorkspacePath && !payload.worktreeInfo) {
|
|
28803
|
-
const previousPath = (0,
|
|
30523
|
+
const previousPath = (0, import_node_path12.resolve)(explicitWorkspacePath);
|
|
28804
30524
|
const remembered = workspaceRelocations.get(previousPath);
|
|
28805
30525
|
const rememberedPath = remembered?.canonicalPath;
|
|
28806
30526
|
const relocated = rememberedPath && workspaceMatchesExpectedRepositories({
|
|
28807
30527
|
workspacePath: rememberedPath,
|
|
28808
30528
|
expectedRepoUrls
|
|
28809
|
-
}) ? (0,
|
|
30529
|
+
}) ? (0, import_node_path12.resolve)(rememberedPath) : discoverRelocatedWorkspace({
|
|
28810
30530
|
missingPath: explicitWorkspacePath,
|
|
28811
30531
|
approvedWorkspacePaths: payload.approvedWorkspacePaths,
|
|
28812
30532
|
expectedRepoUrls
|
|
28813
30533
|
});
|
|
28814
30534
|
if (relocated) {
|
|
28815
|
-
if (!remembered || (0,
|
|
30535
|
+
if (!remembered || (0, import_node_path12.resolve)(remembered.canonicalPath) !== relocated) {
|
|
28816
30536
|
workspaceRelocations.set(previousPath, {
|
|
28817
30537
|
conversationId: payload.conversationId,
|
|
28818
30538
|
runId: payload.runId,
|
|
@@ -29280,7 +31000,7 @@ async function startDaemon(args) {
|
|
|
29280
31000
|
const requestId = payload.requestId;
|
|
29281
31001
|
try {
|
|
29282
31002
|
const projectPath = isAlanDefaultWorkspace(payload.projectPath) ? resolveAlanDefaultWorkspacePath() : payload.projectPath;
|
|
29283
|
-
const result = scanLocalSkillsCached((0,
|
|
31003
|
+
const result = scanLocalSkillsCached((0, import_node_os9.homedir)(), projectPath, {
|
|
29284
31004
|
refresh: payload.refresh === true
|
|
29285
31005
|
});
|
|
29286
31006
|
socket.emit("runtime.skills.result", {
|
|
@@ -29308,7 +31028,7 @@ async function startDaemon(args) {
|
|
|
29308
31028
|
if (!oldest) break;
|
|
29309
31029
|
adoptionChallenges.delete(oldest);
|
|
29310
31030
|
}
|
|
29311
|
-
const challenge = (0,
|
|
31031
|
+
const challenge = (0, import_node_crypto5.randomBytes)(24).toString("base64url");
|
|
29312
31032
|
adoptionChallenges.set(challenge, now + LOCAL_SERVICE_CHALLENGE_TTL_MS);
|
|
29313
31033
|
const unsignedOwner = {
|
|
29314
31034
|
version: LOCAL_SERVICE_OWNER_VERSION,
|
|
@@ -29511,7 +31231,7 @@ async function startDaemon(args) {
|
|
|
29511
31231
|
`${describeOccupiedLocalDaemon(localApiPort)} The token in ${getConfigPath()} no longer matches the running daemon; quit Alan desktop or run setup again.`
|
|
29512
31232
|
);
|
|
29513
31233
|
}
|
|
29514
|
-
const localApiListening = new Promise((
|
|
31234
|
+
const localApiListening = new Promise((resolve7, reject) => {
|
|
29515
31235
|
const onError = (error) => {
|
|
29516
31236
|
clearInterval(heartbeat);
|
|
29517
31237
|
clearInterval(versionRefresh);
|
|
@@ -29540,7 +31260,7 @@ async function startDaemon(args) {
|
|
|
29540
31260
|
if (address && typeof address === "object") localApiPort = address.port;
|
|
29541
31261
|
pushLog(`local_api listening port=${localApiPort}`);
|
|
29542
31262
|
console.info("[alan-agent] Local daemon API listening", { port: localApiPort });
|
|
29543
|
-
|
|
31263
|
+
resolve7();
|
|
29544
31264
|
});
|
|
29545
31265
|
});
|
|
29546
31266
|
try {
|
|
@@ -29559,7 +31279,7 @@ async function startDaemon(args) {
|
|
|
29559
31279
|
clearInterval(versionRefresh);
|
|
29560
31280
|
clearInterval(workspaceLeaseMonitor);
|
|
29561
31281
|
socket.disconnect();
|
|
29562
|
-
await new Promise((
|
|
31282
|
+
await new Promise((resolve7) => localServer.close(() => resolve7()));
|
|
29563
31283
|
releaseOwnerLease();
|
|
29564
31284
|
releaseStartupLease();
|
|
29565
31285
|
throw error;
|
|
@@ -29578,8 +31298,8 @@ async function startDaemon(args) {
|
|
|
29578
31298
|
}
|
|
29579
31299
|
activeAgents.clear();
|
|
29580
31300
|
socket.disconnect();
|
|
29581
|
-
await new Promise((
|
|
29582
|
-
localServer.close(() =>
|
|
31301
|
+
await new Promise((resolve7) => {
|
|
31302
|
+
localServer.close(() => resolve7());
|
|
29583
31303
|
});
|
|
29584
31304
|
} finally {
|
|
29585
31305
|
releaseOwnerLease();
|
|
@@ -30016,7 +31736,7 @@ function decodeResumeFallbackContext(encoded) {
|
|
|
30016
31736
|
}
|
|
30017
31737
|
|
|
30018
31738
|
// src/web-presenter.ts
|
|
30019
|
-
var
|
|
31739
|
+
var import_node_crypto6 = require("crypto");
|
|
30020
31740
|
var WebPresenter = class {
|
|
30021
31741
|
constructor(wsClient, conversationId, cwd) {
|
|
30022
31742
|
this.conversationId = conversationId;
|
|
@@ -30193,7 +31913,7 @@ var WebPresenter = class {
|
|
|
30193
31913
|
this.applicationProblem = createAlanProblem({
|
|
30194
31914
|
domain: "application",
|
|
30195
31915
|
code: /required tools|prepare the required tools/i.test(message) ? "app.service.unavailable" : "app.internal",
|
|
30196
|
-
id: (0,
|
|
31916
|
+
id: (0, import_node_crypto6.randomUUID)(),
|
|
30197
31917
|
title: "Alan could not continue the agent run",
|
|
30198
31918
|
detail: message,
|
|
30199
31919
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30260,7 +31980,7 @@ var WebPresenter = class {
|
|
|
30260
31980
|
};
|
|
30261
31981
|
|
|
30262
31982
|
// src/ws-client.ts
|
|
30263
|
-
var
|
|
31983
|
+
var import_node_crypto8 = require("crypto");
|
|
30264
31984
|
|
|
30265
31985
|
// ../shared/dist/agent-liveness.js
|
|
30266
31986
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -30288,8 +32008,8 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
30288
32008
|
});
|
|
30289
32009
|
|
|
30290
32010
|
// src/sandbox-outbox.ts
|
|
30291
|
-
var
|
|
30292
|
-
var
|
|
32011
|
+
var import_node_crypto7 = require("crypto");
|
|
32012
|
+
var import_node_fs15 = require("fs");
|
|
30293
32013
|
var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
|
|
30294
32014
|
function isSandboxEventOutboxEnabled(env = process.env) {
|
|
30295
32015
|
return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
|
|
@@ -30298,7 +32018,7 @@ function deriveSandboxOutboxKey(sessionToken) {
|
|
|
30298
32018
|
if (!sessionToken) {
|
|
30299
32019
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
30300
32020
|
}
|
|
30301
|
-
return (0,
|
|
32021
|
+
return (0, import_node_crypto7.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
30302
32022
|
}
|
|
30303
32023
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
30304
32024
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -30312,7 +32032,7 @@ function createSandboxEventOutbox(input) {
|
|
|
30312
32032
|
input.pushLog?.(
|
|
30313
32033
|
`sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
|
|
30314
32034
|
);
|
|
30315
|
-
(0,
|
|
32035
|
+
(0, import_node_fs15.rmSync)(path, { force: true });
|
|
30316
32036
|
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
30317
32037
|
}
|
|
30318
32038
|
}
|
|
@@ -30435,7 +32155,7 @@ var WSClient = class {
|
|
|
30435
32155
|
}
|
|
30436
32156
|
socket;
|
|
30437
32157
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
30438
|
-
agentSessionId = (0,
|
|
32158
|
+
agentSessionId = (0, import_node_crypto8.randomUUID)();
|
|
30439
32159
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
30440
32160
|
heartbeatSeq = 0;
|
|
30441
32161
|
heartbeatTimer = null;
|
|
@@ -30515,9 +32235,9 @@ var WSClient = class {
|
|
|
30515
32235
|
});
|
|
30516
32236
|
}
|
|
30517
32237
|
waitForConnection(timeoutMs = 15e3) {
|
|
30518
|
-
return new Promise((
|
|
32238
|
+
return new Promise((resolve7, reject) => {
|
|
30519
32239
|
if (this.socket.connected) {
|
|
30520
|
-
|
|
32240
|
+
resolve7();
|
|
30521
32241
|
return;
|
|
30522
32242
|
}
|
|
30523
32243
|
const timeout = setTimeout(
|
|
@@ -30526,7 +32246,7 @@ var WSClient = class {
|
|
|
30526
32246
|
);
|
|
30527
32247
|
this.socket.once("connect", () => {
|
|
30528
32248
|
clearTimeout(timeout);
|
|
30529
|
-
|
|
32249
|
+
resolve7();
|
|
30530
32250
|
});
|
|
30531
32251
|
this.socket.once("connect_error", (err) => {
|
|
30532
32252
|
clearTimeout(timeout);
|
|
@@ -30707,9 +32427,9 @@ async function runSandbox(config) {
|
|
|
30707
32427
|
onUserMessage: (payload) => {
|
|
30708
32428
|
if (payload === null) {
|
|
30709
32429
|
if (pendingMessageResolve) {
|
|
30710
|
-
const
|
|
32430
|
+
const resolve7 = pendingMessageResolve;
|
|
30711
32431
|
pendingMessageResolve = null;
|
|
30712
|
-
|
|
32432
|
+
resolve7(null);
|
|
30713
32433
|
}
|
|
30714
32434
|
return;
|
|
30715
32435
|
}
|
|
@@ -30731,9 +32451,9 @@ async function runSandbox(config) {
|
|
|
30731
32451
|
fileCount: payload.files?.length ?? 0
|
|
30732
32452
|
});
|
|
30733
32453
|
if (pendingMessageResolve) {
|
|
30734
|
-
const
|
|
32454
|
+
const resolve7 = pendingMessageResolve;
|
|
30735
32455
|
pendingMessageResolve = null;
|
|
30736
|
-
|
|
32456
|
+
resolve7(payload);
|
|
30737
32457
|
} else {
|
|
30738
32458
|
queuedMessages.push(payload);
|
|
30739
32459
|
}
|
|
@@ -30926,8 +32646,8 @@ async function runSandbox(config) {
|
|
|
30926
32646
|
});
|
|
30927
32647
|
if (stopped) break;
|
|
30928
32648
|
}
|
|
30929
|
-
const nextPayload = queuedMessages.shift() ?? await new Promise((
|
|
30930
|
-
pendingMessageResolve =
|
|
32649
|
+
const nextPayload = queuedMessages.shift() ?? await new Promise((resolve7) => {
|
|
32650
|
+
pendingMessageResolve = resolve7;
|
|
30931
32651
|
});
|
|
30932
32652
|
if (nextPayload === null || stopped) {
|
|
30933
32653
|
break;
|
|
@@ -31009,10 +32729,10 @@ async function runSandbox(config) {
|
|
|
31009
32729
|
}
|
|
31010
32730
|
|
|
31011
32731
|
// src/service-manager.ts
|
|
31012
|
-
var
|
|
31013
|
-
var
|
|
31014
|
-
var
|
|
31015
|
-
var
|
|
32732
|
+
var import_node_child_process8 = require("child_process");
|
|
32733
|
+
var import_node_fs16 = require("fs");
|
|
32734
|
+
var import_node_os10 = require("os");
|
|
32735
|
+
var import_node_path13 = require("path");
|
|
31016
32736
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
31017
32737
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
31018
32738
|
var WINDOWS_TASK = "Alan Agent";
|
|
@@ -31032,8 +32752,8 @@ function buildDaemonServicePlan(input) {
|
|
|
31032
32752
|
if (userId === void 0) throw new Error("Cannot determine the current macOS user ID");
|
|
31033
32753
|
const domain = `gui/${userId}`;
|
|
31034
32754
|
const serviceTarget = `${domain}/${SERVICE_LABEL}`;
|
|
31035
|
-
const manifestPath = (0,
|
|
31036
|
-
const logDir = (0,
|
|
32755
|
+
const manifestPath = (0, import_node_path13.join)(input.homeDir, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
32756
|
+
const logDir = (0, import_node_path13.join)(input.homeDir, ".alan", "agent", "logs");
|
|
31037
32757
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
31038
32758
|
const manifest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
31039
32759
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -31055,9 +32775,9 @@ ${programArguments}
|
|
|
31055
32775
|
<key>ThrottleInterval</key>
|
|
31056
32776
|
<integer>5</integer>
|
|
31057
32777
|
<key>StandardOutPath</key>
|
|
31058
|
-
<string>${xml((0,
|
|
32778
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon.log"))}</string>
|
|
31059
32779
|
<key>StandardErrorPath</key>
|
|
31060
|
-
<string>${xml((0,
|
|
32780
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon-error.log"))}</string>
|
|
31061
32781
|
</dict>
|
|
31062
32782
|
</plist>
|
|
31063
32783
|
`;
|
|
@@ -31096,7 +32816,7 @@ ${programArguments}
|
|
|
31096
32816
|
};
|
|
31097
32817
|
}
|
|
31098
32818
|
if (input.platform === "linux") {
|
|
31099
|
-
const manifestPath = (0,
|
|
32819
|
+
const manifestPath = (0, import_node_path13.join)(input.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
31100
32820
|
const manifest = `[Unit]
|
|
31101
32821
|
Description=Alan local agent daemon
|
|
31102
32822
|
After=network-online.target
|
|
@@ -31193,7 +32913,7 @@ WantedBy=default.target
|
|
|
31193
32913
|
};
|
|
31194
32914
|
}
|
|
31195
32915
|
function currentServicePlan(args) {
|
|
31196
|
-
const currentPlatform = (0,
|
|
32916
|
+
const currentPlatform = (0, import_node_os10.platform)();
|
|
31197
32917
|
if (currentPlatform !== "darwin" && currentPlatform !== "linux" && currentPlatform !== "win32") {
|
|
31198
32918
|
throw new Error(`Daemon service installation is not supported on ${currentPlatform}`);
|
|
31199
32919
|
}
|
|
@@ -31202,25 +32922,25 @@ function currentServicePlan(args) {
|
|
|
31202
32922
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
31203
32923
|
return buildDaemonServicePlan({
|
|
31204
32924
|
platform: currentPlatform,
|
|
31205
|
-
homeDir: (0,
|
|
32925
|
+
homeDir: (0, import_node_os10.homedir)(),
|
|
31206
32926
|
nodeExecutable: process.execPath,
|
|
31207
|
-
cliEntry: (0,
|
|
32927
|
+
cliEntry: (0, import_node_path13.resolve)(cliEntry),
|
|
31208
32928
|
profile
|
|
31209
32929
|
});
|
|
31210
32930
|
}
|
|
31211
32931
|
function writeManifest(path, contents) {
|
|
31212
|
-
(0,
|
|
32932
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(path), { recursive: true, mode: 448 });
|
|
31213
32933
|
const pendingPath = `${path}.pending-${process.pid}`;
|
|
31214
32934
|
try {
|
|
31215
|
-
(0,
|
|
31216
|
-
(0,
|
|
31217
|
-
(0,
|
|
32935
|
+
(0, import_node_fs16.writeFileSync)(pendingPath, contents, { mode: 384 });
|
|
32936
|
+
(0, import_node_fs16.chmodSync)(pendingPath, 384);
|
|
32937
|
+
(0, import_node_fs16.renameSync)(pendingPath, path);
|
|
31218
32938
|
} finally {
|
|
31219
|
-
(0,
|
|
32939
|
+
(0, import_node_fs16.rmSync)(pendingPath, { force: true });
|
|
31220
32940
|
}
|
|
31221
32941
|
}
|
|
31222
32942
|
function runServiceCommand(command) {
|
|
31223
|
-
const result = (0,
|
|
32943
|
+
const result = (0, import_node_child_process8.spawnSync)(command.command, command.args, { encoding: "utf8" });
|
|
31224
32944
|
const status = result.status ?? 1;
|
|
31225
32945
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
31226
32946
|
if (status !== 0 && !command.tolerateFailure) {
|
|
@@ -31238,8 +32958,8 @@ function installDaemonService(args = []) {
|
|
|
31238
32958
|
}
|
|
31239
32959
|
const plan = currentServicePlan(args);
|
|
31240
32960
|
if (plan.manifestPath && plan.manifest) {
|
|
31241
|
-
if ((0,
|
|
31242
|
-
(0,
|
|
32961
|
+
if ((0, import_node_os10.platform)() === "darwin") {
|
|
32962
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path13.join)((0, import_node_os10.homedir)(), ".alan", "agent", "logs"), { recursive: true, mode: 448 });
|
|
31243
32963
|
}
|
|
31244
32964
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
31245
32965
|
}
|
|
@@ -31249,7 +32969,7 @@ function installDaemonService(args = []) {
|
|
|
31249
32969
|
function uninstallDaemonService(args = []) {
|
|
31250
32970
|
const plan = currentServicePlan(args);
|
|
31251
32971
|
for (const command of plan.uninstallCommands) runServiceCommand(command);
|
|
31252
|
-
if (plan.manifestPath) (0,
|
|
32972
|
+
if (plan.manifestPath) (0, import_node_fs16.rmSync)(plan.manifestPath, { force: true });
|
|
31253
32973
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
31254
32974
|
}
|
|
31255
32975
|
function printDaemonServiceStatus(args = []) {
|
|
@@ -31263,7 +32983,7 @@ function printDaemonServiceStatus(args = []) {
|
|
|
31263
32983
|
function logEffectiveGitIdentity(lifecycle, projectPath) {
|
|
31264
32984
|
const readConfig2 = (key) => {
|
|
31265
32985
|
try {
|
|
31266
|
-
const result = (0,
|
|
32986
|
+
const result = (0, import_node_child_process9.spawnSync)("git", ["config", "--get", key], {
|
|
31267
32987
|
cwd: projectPath,
|
|
31268
32988
|
env: getDaemonCliEnvironment(),
|
|
31269
32989
|
encoding: "utf8",
|