@aiden-ade/sandbox-agent 0.1.49 → 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 +2584 -787
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -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",
|
|
@@ -4810,6 +4810,351 @@ var require_websocket_server = __commonJS({
|
|
|
4810
4810
|
}
|
|
4811
4811
|
});
|
|
4812
4812
|
|
|
4813
|
+
// src/index.ts
|
|
4814
|
+
var import_node_child_process9 = require("child_process");
|
|
4815
|
+
|
|
4816
|
+
// src/cli-executable.ts
|
|
4817
|
+
var import_node_child_process = require("child_process");
|
|
4818
|
+
var import_node_fs = require("fs");
|
|
4819
|
+
var import_node_os = require("os");
|
|
4820
|
+
var import_node_path = require("path");
|
|
4821
|
+
var PROVIDER_CLI_COMMANDS = {
|
|
4822
|
+
claude_cli: { provider: "claude_cli", command: "claude", envVars: ["ALAN_CLAUDE_PATH"] },
|
|
4823
|
+
codex_app_server: {
|
|
4824
|
+
provider: "codex_app_server",
|
|
4825
|
+
command: "codex",
|
|
4826
|
+
envVars: ["ALAN_CODEX_PATH"]
|
|
4827
|
+
},
|
|
4828
|
+
copilot_cli: { provider: "copilot_cli", command: "copilot", envVars: ["ALAN_COPILOT_PATH"] },
|
|
4829
|
+
cursor_agent_cli: {
|
|
4830
|
+
provider: "cursor_agent_cli",
|
|
4831
|
+
command: "cursor-agent",
|
|
4832
|
+
envVars: ["ALAN_CURSOR_AGENT_PATH"]
|
|
4833
|
+
},
|
|
4834
|
+
antigravity_cli: {
|
|
4835
|
+
provider: "antigravity_cli",
|
|
4836
|
+
command: "agy",
|
|
4837
|
+
envVars: ["ALAN_ANTIGRAVITY_PATH"]
|
|
4838
|
+
},
|
|
4839
|
+
kimi_cli: { provider: "kimi_cli", command: "kimi-cli", envVars: ["ALAN_KIMI_PATH"] },
|
|
4840
|
+
grok_cli: { provider: "grok_cli", command: "grok", envVars: ["ALAN_GROK_PATH"] },
|
|
4841
|
+
opencode_cli: { provider: "opencode_cli", command: "opencode", envVars: ["ALAN_OPENCODE_PATH"] },
|
|
4842
|
+
droid_cli: { provider: "droid_cli", command: "droid", envVars: ["ALAN_DROID_PATH"] },
|
|
4843
|
+
supatest_cli: { provider: "supatest_cli", command: "supatest", envVars: ["ALAN_SUPATEST_PATH"] }
|
|
4844
|
+
};
|
|
4845
|
+
var DISCOVERABLE_PROVIDER_KINDS = Object.keys(PROVIDER_CLI_COMMANDS);
|
|
4846
|
+
var PROVIDER_ALIASES = {
|
|
4847
|
+
agy: "antigravity_cli",
|
|
4848
|
+
antigravity: "antigravity_cli",
|
|
4849
|
+
antigravity_cli: "antigravity_cli",
|
|
4850
|
+
codex: "codex_app_server",
|
|
4851
|
+
gemini: "antigravity_cli",
|
|
4852
|
+
gemini_cli: "antigravity_cli"
|
|
4853
|
+
};
|
|
4854
|
+
var BACKEND_CLI_COMMANDS = {
|
|
4855
|
+
claude_cli: "claude",
|
|
4856
|
+
codex: "codex",
|
|
4857
|
+
codex_app_server: "codex",
|
|
4858
|
+
copilot_cli: "copilot",
|
|
4859
|
+
cursor_agent_cli: "cursor-agent",
|
|
4860
|
+
agy: "agy",
|
|
4861
|
+
antigravity: "agy",
|
|
4862
|
+
antigravity_cli: "agy",
|
|
4863
|
+
gemini_cli: "agy",
|
|
4864
|
+
gemini: "agy",
|
|
4865
|
+
kimi_cli: "kimi-cli",
|
|
4866
|
+
grok_cli: "grok",
|
|
4867
|
+
opencode_cli: "opencode",
|
|
4868
|
+
droid_cli: "droid",
|
|
4869
|
+
supatest_cli: "supatest"
|
|
4870
|
+
};
|
|
4871
|
+
var CLI_COMMAND_ALIASES = {
|
|
4872
|
+
"kimi-cli": ["kimi"],
|
|
4873
|
+
grok: ["grok-build"]
|
|
4874
|
+
};
|
|
4875
|
+
var resolvedCliCache = /* @__PURE__ */ new Map();
|
|
4876
|
+
var NEGATIVE_RESOLUTION_TTL_MS = 15e3;
|
|
4877
|
+
var negativeResolutionExpiry = /* @__PURE__ */ new Map();
|
|
4878
|
+
var cachedLoginShellEnv = null;
|
|
4879
|
+
function parseEnvOutput(output) {
|
|
4880
|
+
const loginEnv = {};
|
|
4881
|
+
for (const line of output.split("\n")) {
|
|
4882
|
+
if (!line.trim()) continue;
|
|
4883
|
+
const eqIdx = line.indexOf("=");
|
|
4884
|
+
if (eqIdx <= 0) continue;
|
|
4885
|
+
const key = line.slice(0, eqIdx);
|
|
4886
|
+
if (/\s/.test(key)) continue;
|
|
4887
|
+
loginEnv[key] = line.slice(eqIdx + 1);
|
|
4888
|
+
}
|
|
4889
|
+
if (!loginEnv.HOME) loginEnv.HOME = process.env.HOME ?? (0, import_node_os.homedir)();
|
|
4890
|
+
if (!loginEnv.SHELL) loginEnv.SHELL = process.env.SHELL ?? "/bin/zsh";
|
|
4891
|
+
return loginEnv;
|
|
4892
|
+
}
|
|
4893
|
+
function loadLoginShellEnvironment() {
|
|
4894
|
+
if (cachedLoginShellEnv) return cachedLoginShellEnv;
|
|
4895
|
+
if ((0, import_node_os.platform)() === "win32") {
|
|
4896
|
+
cachedLoginShellEnv = process.env;
|
|
4897
|
+
return cachedLoginShellEnv;
|
|
4898
|
+
}
|
|
4899
|
+
try {
|
|
4900
|
+
const shell = (process.env.SHELL || "/bin/bash").replace(/'/g, "'\\''");
|
|
4901
|
+
const envOutput = (0, import_node_child_process.execSync)(`'${shell}' -ilc 'env'`, {
|
|
4902
|
+
encoding: "utf8",
|
|
4903
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4904
|
+
timeout: 5e3
|
|
4905
|
+
});
|
|
4906
|
+
cachedLoginShellEnv = parseEnvOutput(envOutput);
|
|
4907
|
+
} catch {
|
|
4908
|
+
cachedLoginShellEnv = process.env;
|
|
4909
|
+
}
|
|
4910
|
+
return cachedLoginShellEnv;
|
|
4911
|
+
}
|
|
4912
|
+
function getDaemonCliEnvironment() {
|
|
4913
|
+
const base = (0, import_node_os.platform)() === "win32" ? process.env : loadLoginShellEnvironment();
|
|
4914
|
+
return augmentCliPath(base);
|
|
4915
|
+
}
|
|
4916
|
+
function cacheResolvedCli(command, resolvedPath) {
|
|
4917
|
+
resolvedCliCache.set(command, resolvedPath);
|
|
4918
|
+
if (resolvedPath === null) {
|
|
4919
|
+
negativeResolutionExpiry.set(command, Date.now() + NEGATIVE_RESOLUTION_TTL_MS);
|
|
4920
|
+
} else {
|
|
4921
|
+
negativeResolutionExpiry.delete(command);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function getCachedResolvedCli(command) {
|
|
4925
|
+
const cached = resolvedCliCache.get(command);
|
|
4926
|
+
if (cached === null) {
|
|
4927
|
+
const expiresAt = negativeResolutionExpiry.get(command);
|
|
4928
|
+
if (expiresAt === void 0 || Date.now() >= expiresAt) {
|
|
4929
|
+
resolvedCliCache.delete(command);
|
|
4930
|
+
negativeResolutionExpiry.delete(command);
|
|
4931
|
+
return void 0;
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
return cached;
|
|
4935
|
+
}
|
|
4936
|
+
function resolveViaWhere(command, env) {
|
|
4937
|
+
if ((0, import_node_os.platform)() !== "win32") return null;
|
|
4938
|
+
const whereExe = (0, import_node_path.join)(
|
|
4939
|
+
env.SystemRoot ?? process.env.SystemRoot ?? "C:\\Windows",
|
|
4940
|
+
"System32",
|
|
4941
|
+
"where.exe"
|
|
4942
|
+
);
|
|
4943
|
+
if (!(0, import_node_fs.existsSync)(whereExe)) return null;
|
|
4944
|
+
const result = (0, import_node_child_process.spawnSync)(whereExe, [command], {
|
|
4945
|
+
encoding: "utf8",
|
|
4946
|
+
env,
|
|
4947
|
+
windowsHide: true,
|
|
4948
|
+
timeout: 3e3
|
|
4949
|
+
});
|
|
4950
|
+
if (result.error || result.status !== 0) return null;
|
|
4951
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
4952
|
+
const trimmed = line.trim();
|
|
4953
|
+
if (!trimmed) continue;
|
|
4954
|
+
if (isRunnableFile(trimmed)) return trimmed;
|
|
4955
|
+
}
|
|
4956
|
+
return null;
|
|
4957
|
+
}
|
|
4958
|
+
function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3, spawnOptions) {
|
|
4959
|
+
return new Promise((resolve7) => {
|
|
4960
|
+
const isWin = (0, import_node_os.platform)() === "win32";
|
|
4961
|
+
let command = executable;
|
|
4962
|
+
let commandArgs = args;
|
|
4963
|
+
if (isWin && /\.(cmd|bat)$/i.test(executable)) {
|
|
4964
|
+
command = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
|
|
4965
|
+
commandArgs = ["/d", "/s", "/c", executable, ...args];
|
|
4966
|
+
}
|
|
4967
|
+
const useShell = isWin && command === executable && !/[\\/]/.test(executable);
|
|
4968
|
+
const closeStdin = spawnOptions?.closeStdin === true;
|
|
4969
|
+
let child;
|
|
4970
|
+
try {
|
|
4971
|
+
child = (0, import_node_child_process.spawn)(command, commandArgs, {
|
|
4972
|
+
env,
|
|
4973
|
+
shell: useShell,
|
|
4974
|
+
windowsHide: isWin ? true : void 0,
|
|
4975
|
+
timeout: timeoutMs,
|
|
4976
|
+
stdio: closeStdin ? ["pipe", "pipe", "pipe"] : void 0
|
|
4977
|
+
});
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
resolve7({ status: null, stdout: "", stderr: "", error });
|
|
4980
|
+
return;
|
|
4981
|
+
}
|
|
4982
|
+
if (closeStdin) {
|
|
4983
|
+
child.stdin?.end();
|
|
4984
|
+
}
|
|
4985
|
+
let stdout = "";
|
|
4986
|
+
let stderr = "";
|
|
4987
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
4988
|
+
stdout += chunk;
|
|
4989
|
+
});
|
|
4990
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
4991
|
+
stderr += chunk;
|
|
4992
|
+
});
|
|
4993
|
+
child.once("error", (error) => resolve7({ status: null, stdout, stderr, error }));
|
|
4994
|
+
child.once(
|
|
4995
|
+
"close",
|
|
4996
|
+
(status, signal) => resolve7({ status, stdout, stderr, signal: signal ?? null })
|
|
4997
|
+
);
|
|
4998
|
+
});
|
|
4999
|
+
}
|
|
5000
|
+
function runCliVersionProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3) {
|
|
5001
|
+
return runCliProbeAsync(executable, env, args, timeoutMs);
|
|
5002
|
+
}
|
|
5003
|
+
function pathSeparator() {
|
|
5004
|
+
return (0, import_node_os.platform)() === "win32" ? ";" : ":";
|
|
5005
|
+
}
|
|
5006
|
+
function splitPath(pathValue) {
|
|
5007
|
+
if (!pathValue) return [];
|
|
5008
|
+
return pathValue.split(pathSeparator()).filter(Boolean);
|
|
5009
|
+
}
|
|
5010
|
+
function isRunnableFile(path) {
|
|
5011
|
+
if (!(0, import_node_fs.existsSync)(path)) return false;
|
|
5012
|
+
if ((0, import_node_os.platform)() === "win32") return true;
|
|
5013
|
+
try {
|
|
5014
|
+
(0, import_node_fs.accessSync)(path, import_node_fs.constants.X_OK);
|
|
5015
|
+
return true;
|
|
5016
|
+
} catch {
|
|
5017
|
+
return false;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
function windowsCommandCandidates(command, pathExt) {
|
|
5021
|
+
const trimmed = command.trim();
|
|
5022
|
+
if (!trimmed) return [];
|
|
5023
|
+
const hasExtension = /\.[a-z0-9]+$/i.test(trimmed);
|
|
5024
|
+
if (hasExtension) return [trimmed];
|
|
5025
|
+
const extensions = pathExt.split(";").map((ext) => ext.trim()).filter(Boolean);
|
|
5026
|
+
return [trimmed, ...extensions.map((ext) => `${trimmed}${ext}`)];
|
|
5027
|
+
}
|
|
5028
|
+
function augmentCliPath(env) {
|
|
5029
|
+
const home = env.HOME || env.USERPROFILE || (0, import_node_os.homedir)();
|
|
5030
|
+
const isWin = (0, import_node_os.platform)() === "win32";
|
|
5031
|
+
const extraPaths = isWin ? [
|
|
5032
|
+
(0, import_node_path.join)(home, "AppData", "Roaming", "npm"),
|
|
5033
|
+
(0, import_node_path.join)(home, "AppData", "Local", "Programs", "Microsoft", "WindowsApps"),
|
|
5034
|
+
(0, import_node_path.join)(home, ".local", "bin"),
|
|
5035
|
+
"C:\\Program Files\\nodejs",
|
|
5036
|
+
"C:\\Program Files\\Git\\cmd"
|
|
5037
|
+
] : [
|
|
5038
|
+
(0, import_node_path.join)(home, ".local", "bin"),
|
|
5039
|
+
(0, import_node_path.join)(home, ".local", "node", "bin"),
|
|
5040
|
+
(0, import_node_path.join)(home, ".bun", "bin"),
|
|
5041
|
+
(0, import_node_path.join)(home, ".cargo", "bin"),
|
|
5042
|
+
"/opt/homebrew/bin",
|
|
5043
|
+
"/usr/local/bin"
|
|
5044
|
+
];
|
|
5045
|
+
const basePath = env.PATH ?? "";
|
|
5046
|
+
const existing = new Set(splitPath(basePath));
|
|
5047
|
+
const missing = extraPaths.filter((dir) => !existing.has(dir));
|
|
5048
|
+
if (missing.length === 0) return env;
|
|
5049
|
+
return {
|
|
5050
|
+
...env,
|
|
5051
|
+
PATH: missing.length > 0 ? `${missing.join(pathSeparator())}${pathSeparator()}${basePath}` : basePath
|
|
5052
|
+
};
|
|
5053
|
+
}
|
|
5054
|
+
function resolveCliExecutable(command, env = getDaemonCliEnvironment()) {
|
|
5055
|
+
const trimmed = command.trim();
|
|
5056
|
+
if (!trimmed) return null;
|
|
5057
|
+
const cached = getCachedResolvedCli(trimmed);
|
|
5058
|
+
if (cached === null) return null;
|
|
5059
|
+
if (cached && isRunnableFile(cached)) return cached;
|
|
5060
|
+
if ((trimmed.includes("/") || trimmed.includes("\\")) && isRunnableFile(trimmed)) {
|
|
5061
|
+
cacheResolvedCli(trimmed, trimmed);
|
|
5062
|
+
return trimmed;
|
|
5063
|
+
}
|
|
5064
|
+
const enriched = augmentCliPath(env);
|
|
5065
|
+
const home = enriched.HOME || enriched.USERPROFILE || (0, import_node_os.homedir)();
|
|
5066
|
+
const isWin = (0, import_node_os.platform)() === "win32";
|
|
5067
|
+
const pathExt = enriched.PATHEXT ?? (isWin ? ".EXE;.CMD;.BAT;.COM" : "");
|
|
5068
|
+
const commandNames = [trimmed, ...CLI_COMMAND_ALIASES[trimmed] ?? []];
|
|
5069
|
+
const names = commandNames.flatMap(
|
|
5070
|
+
(name) => isWin ? windowsCommandCandidates(name, pathExt) : [name]
|
|
5071
|
+
);
|
|
5072
|
+
const candidates = [];
|
|
5073
|
+
for (const name of names) {
|
|
5074
|
+
if (name.includes("/") || name.includes("\\")) {
|
|
5075
|
+
candidates.push(name);
|
|
5076
|
+
continue;
|
|
5077
|
+
}
|
|
5078
|
+
for (const dir of splitPath(enriched.PATH)) {
|
|
5079
|
+
candidates.push((0, import_node_path.join)(dir, name));
|
|
5080
|
+
}
|
|
5081
|
+
candidates.push((0, import_node_path.join)(home, ".local", "bin", name), (0, import_node_path.join)(home, ".local", "node", "bin", name));
|
|
5082
|
+
if (!isWin) {
|
|
5083
|
+
candidates.push((0, import_node_path.join)("/opt/homebrew/bin", name), (0, import_node_path.join)("/usr/local/bin", name));
|
|
5084
|
+
} else {
|
|
5085
|
+
candidates.push((0, import_node_path.join)(home, "AppData", "Roaming", "npm", name));
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5089
|
+
for (const candidate of candidates) {
|
|
5090
|
+
if (seen.has(candidate)) continue;
|
|
5091
|
+
seen.add(candidate);
|
|
5092
|
+
if (isRunnableFile(candidate)) {
|
|
5093
|
+
cacheResolvedCli(trimmed, candidate);
|
|
5094
|
+
return candidate;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
const viaWhere = resolveViaWhere(trimmed, enriched);
|
|
5098
|
+
if (viaWhere) {
|
|
5099
|
+
cacheResolvedCli(trimmed, viaWhere);
|
|
5100
|
+
return viaWhere;
|
|
5101
|
+
}
|
|
5102
|
+
cacheResolvedCli(trimmed, null);
|
|
5103
|
+
return null;
|
|
5104
|
+
}
|
|
5105
|
+
function normalizeDiscoverableProvider(provider) {
|
|
5106
|
+
const alias = PROVIDER_ALIASES[provider];
|
|
5107
|
+
if (alias) return alias;
|
|
5108
|
+
if (provider in PROVIDER_CLI_COMMANDS) {
|
|
5109
|
+
return provider;
|
|
5110
|
+
}
|
|
5111
|
+
return null;
|
|
5112
|
+
}
|
|
5113
|
+
function resolveProviderCliCommand(provider, env = getDaemonCliEnvironment()) {
|
|
5114
|
+
const normalized = normalizeDiscoverableProvider(provider);
|
|
5115
|
+
if (!normalized) return null;
|
|
5116
|
+
const spec = PROVIDER_CLI_COMMANDS[normalized];
|
|
5117
|
+
const override = spec.envVars.map((name) => process.env[name]?.trim() || env[name]?.trim()).find(Boolean);
|
|
5118
|
+
if (override) {
|
|
5119
|
+
const resolved2 = resolveCliExecutable(override, env) ?? (isRunnableFile(override) ? override : null);
|
|
5120
|
+
if (resolved2) cacheResolvedCli(spec.command, resolved2);
|
|
5121
|
+
return resolved2;
|
|
5122
|
+
}
|
|
5123
|
+
const resolved = resolveCliExecutable(spec.command, env);
|
|
5124
|
+
if (resolved) cacheResolvedCli(spec.command, resolved);
|
|
5125
|
+
return resolved;
|
|
5126
|
+
}
|
|
5127
|
+
function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
|
|
5128
|
+
if (!backendKind) return void 0;
|
|
5129
|
+
const command = BACKEND_CLI_COMMANDS[backendKind];
|
|
5130
|
+
if (!command) return void 0;
|
|
5131
|
+
const cached = getCachedResolvedCli(command);
|
|
5132
|
+
if (cached === null) return void 0;
|
|
5133
|
+
if (cached && isRunnableFile(cached)) return cached;
|
|
5134
|
+
const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
|
|
5135
|
+
const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
|
|
5136
|
+
const resolved = resolveCliExecutable(envOverride?.trim() || command, env);
|
|
5137
|
+
cacheResolvedCli(command, resolved);
|
|
5138
|
+
return resolved ?? void 0;
|
|
5139
|
+
}
|
|
5140
|
+
function invalidateResolvedCli(command) {
|
|
5141
|
+
const trimmed = command.trim();
|
|
5142
|
+
if (trimmed) {
|
|
5143
|
+
resolvedCliCache.delete(trimmed);
|
|
5144
|
+
negativeResolutionExpiry.delete(trimmed);
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
|
|
5148
|
+
if (!backendKind) return void 0;
|
|
5149
|
+
const command = BACKEND_CLI_COMMANDS[backendKind];
|
|
5150
|
+
if (!command) return void 0;
|
|
5151
|
+
invalidateResolvedCli(command);
|
|
5152
|
+
const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
|
|
5153
|
+
const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
|
|
5154
|
+
if (envOverride) invalidateResolvedCli(envOverride);
|
|
5155
|
+
return resolveBackendRuntimeCommand(backendKind, env);
|
|
5156
|
+
}
|
|
5157
|
+
|
|
4813
5158
|
// src/cli-help.ts
|
|
4814
5159
|
var HELP_TEXT = `Alan agent CLI
|
|
4815
5160
|
|
|
@@ -4859,12 +5204,12 @@ function resolveCommand(args, env) {
|
|
|
4859
5204
|
}
|
|
4860
5205
|
|
|
4861
5206
|
// src/daemon.ts
|
|
4862
|
-
var
|
|
4863
|
-
var
|
|
4864
|
-
var
|
|
5207
|
+
var import_node_child_process7 = require("child_process");
|
|
5208
|
+
var import_node_crypto5 = require("crypto");
|
|
5209
|
+
var import_node_fs14 = require("fs");
|
|
4865
5210
|
var import_node_http = __toESM(require("http"), 1);
|
|
4866
|
-
var
|
|
4867
|
-
var
|
|
5211
|
+
var import_node_os9 = require("os");
|
|
5212
|
+
var import_node_path12 = require("path");
|
|
4868
5213
|
|
|
4869
5214
|
// ../agent-core/dist/index.js
|
|
4870
5215
|
var import_fs = require("fs");
|
|
@@ -5029,6 +5374,16 @@ Your job is to take the repository in this fresh cloud sandbox from an unknown s
|
|
|
5029
5374
|
- You are inside an ephemeral cloud sandbox. Its filesystem can be captured as a reusable snapshot for future sessions.
|
|
5030
5375
|
- Do not assume you are on the user's laptop or that tools outside this sandbox exist.
|
|
5031
5376
|
- Install and build inside the workspace. Keep all setup repeatable from a fresh clone.
|
|
5377
|
+
|
|
5378
|
+
### Sandbox privileges
|
|
5379
|
+
|
|
5380
|
+
- You are the non-root user \`alan\`; \`HOME=/home/alan\`, repos in \`/home/alan/workspace\`, which you fully own. Node/pnpm/CLIs are on \`PATH\`, and \`npm install -g\` needs no sudo.
|
|
5381
|
+
- **Installing system packages is allowed**, via sudo scoped to package managers: \`sudo apt-get\`, \`sudo apt\`, \`sudo dpkg\`, \`sudo add-apt-repository\`. Other sudo (\`su\`, \`sh\`, \`systemctl\`) is refused by design.
|
|
5382
|
+
- **Always \`sudo apt-get update\` first.** Package lists ship empty, so installing without it fails with "Unable to locate package" \u2014 which reads as "permission denied" but is not:
|
|
5383
|
+
\`sudo apt-get update && sudo apt-get install -y --no-install-recommends <pkgs>\`
|
|
5384
|
+
- Never hand-compile system packages into \`$HOME\` to route around a permission you already have. Use apt; fall back to a user-local install only if the package truly is not in apt.
|
|
5385
|
+
- No Docker daemon or socket. Ports below 1024 bind fine without elevation.
|
|
5386
|
+
- Read the real error before blaming permissions: \`ELIFECYCLE\` is a generic pnpm epilogue, not a diagnosis. Find the \`ERR_\`/\`gyp ERR!\` line above it. A native build failure means a missing system package, not a missing privilege.
|
|
5032
5387
|
- Persist the final ordered commands with \`set_environment_setup_commands\`. Each command is replayed in a fresh shell, so no command may depend on shell state from a previous command.
|
|
5033
5388
|
- The snapshot policy is \`block_if_dirty\`: the working tree must be clean before capture.
|
|
5034
5389
|
|
|
@@ -5038,8 +5393,8 @@ Your job is to take the repository in this fresh cloud sandbox from an unknown s
|
|
|
5038
5393
|
2. **Set up** \u2014 install dependencies, select compatible runtimes, configure non-secret defaults, and build. Diagnose failures yourself before asking the user.
|
|
5039
5394
|
3. **Run and verify** \u2014 start the real app or service. Prove it works using process stability, logs, HTTP probes, and \`agent-browser\` interaction when a UI exists. A process merely starting or dependencies merely installing is not success.
|
|
5040
5395
|
4. **Persist** \u2014 call \`set_environment_setup_commands\` with only the ordered commands you actually verified from a fresh-shell model.
|
|
5041
|
-
5. **Prepare capture** \u2014 ensure every repository is clean. Verify that \`git status --porcelain --untracked-files=all\` is empty; generated untracked files count as dirty and must be removed or covered by an appropriate existing ignore rule. If setup requires tracked changes, create a focused branch, commit them, push it, and open a PR with \`github_pr_create\` explaining why the setup change is needed. Never commit secrets.
|
|
5042
|
-
6. **Human gate** \u2014 summarize the evidence and ask the user to
|
|
5396
|
+
5. **Prepare capture** \u2014 ensure every repository is clean. Verify that \`git status --porcelain --untracked-files=all\` is empty; generated untracked files count as dirty and must be removed or covered by an appropriate existing ignore rule. If setup requires tracked changes, create a focused branch, commit them, push it, and open a PR with \`github_pr_create\` explaining why the setup change is needed. When this setup session is attached to a task, link that PR with \`github_pr_link_task\`. Never commit secrets.
|
|
5397
|
+
6. **Human gate** \u2014 summarize the evidence and ask the user to approve the snapshot. The chat shows them a **Capture snapshot** button, so invite them to use it rather than dictating an exact phrase to type. Do not call \`capture_environment_snapshot\` before that approval arrives.
|
|
5043
5398
|
7. **Capture and re-verify** \u2014 after approval, call \`capture_environment_snapshot\` with \`afterSnapshot: "resume"\`. Poll \`get_cloud_machine\` until the snapshot is ready, then confirm the sandbox remains usable and repeat the key runtime probe before declaring completion.
|
|
5044
5399
|
|
|
5045
5400
|
## Autonomy
|
|
@@ -11023,7 +11378,7 @@ var EPIC_STATUS_OPTIONS = [
|
|
|
11023
11378
|
value: "canceled",
|
|
11024
11379
|
label: "Canceled",
|
|
11025
11380
|
color: CATEGORICAL_COLORS.slateClosed,
|
|
11026
|
-
group: "
|
|
11381
|
+
group: "closed"
|
|
11027
11382
|
}
|
|
11028
11383
|
];
|
|
11029
11384
|
var DEFAULT_EPIC_STATUSES = EPIC_STATUS_OPTIONS.map((o) => ({
|
|
@@ -11777,7 +12132,12 @@ function normalizeCodexCliErrorMessage(message) {
|
|
|
11777
12132
|
if (lower.includes("reconnecting") && lower.includes("request timed out")) {
|
|
11778
12133
|
return "Codex connection to OpenAI timed out while waiting for the turn to continue. Retry the message; if it repeats, check network/API latency or reduce slow MCP/tool calls in the turn.";
|
|
11779
12134
|
}
|
|
11780
|
-
if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated")
|
|
12135
|
+
if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated") || // OpenAI's ChatGPT backend rejects a stale bearer with this pair on both the
|
|
12136
|
+
// HTTP and WSS paths, e.g. "Reconnecting... 2/5 (unexpected status 401
|
|
12137
|
+
// Unauthorized: Provided authentication token is expired. ... auth error
|
|
12138
|
+
// code: token_expired)". Left unmapped these render as a raw retry banner
|
|
12139
|
+
// with a generic "check your API key" hint and no recovery path.
|
|
12140
|
+
lower.includes("token_expired") || lower.includes("provided authentication token is expired")) {
|
|
11781
12141
|
return "Codex CLI connection expired. Re-authenticate the Codex CLI on this computer (run `codex login`) or reconnect it in Cloud settings, then retry.";
|
|
11782
12142
|
}
|
|
11783
12143
|
return message;
|
|
@@ -12014,12 +12374,29 @@ function formatCliSpawnError(command, error) {
|
|
|
12014
12374
|
`CLI command not found: ${command}. Install it or configure this provider with the full executable path.`
|
|
12015
12375
|
);
|
|
12016
12376
|
}
|
|
12377
|
+
if (error.code === "ETXTBSY") {
|
|
12378
|
+
return new Error(
|
|
12379
|
+
`CLI "${command}" is still being written by an installer (ETXTBSY) and did not become runnable in time. Retry in a moment.`
|
|
12380
|
+
);
|
|
12381
|
+
}
|
|
12017
12382
|
return error;
|
|
12018
12383
|
}
|
|
12019
|
-
|
|
12384
|
+
var SPAWN_RETRY_DELAYS_BY_CODE = {
|
|
12385
|
+
ETXTBSY: [250, 750, 1500, 3e3, 5e3],
|
|
12386
|
+
ENOENT: [250, 750]
|
|
12387
|
+
};
|
|
12388
|
+
var delay = (ms) => new Promise((resolve22) => setTimeout(resolve22, ms));
|
|
12389
|
+
var SPAWN_OUTCOME_TIMEOUT_MS = 2e3;
|
|
12390
|
+
function waitForSpawnOutcome(child, timeoutMs = SPAWN_OUTCOME_TIMEOUT_MS) {
|
|
12020
12391
|
return new Promise((resolve22) => {
|
|
12021
|
-
|
|
12022
|
-
|
|
12392
|
+
const timer = setTimeout(() => resolve22(null), timeoutMs);
|
|
12393
|
+
timer.unref?.();
|
|
12394
|
+
const settle = (value2) => {
|
|
12395
|
+
clearTimeout(timer);
|
|
12396
|
+
resolve22(value2);
|
|
12397
|
+
};
|
|
12398
|
+
child.once("spawn", () => settle(null));
|
|
12399
|
+
child.once("error", (error) => settle(error));
|
|
12023
12400
|
});
|
|
12024
12401
|
}
|
|
12025
12402
|
function createGenericCliBackend(options) {
|
|
@@ -12037,28 +12414,35 @@ function createGenericCliBackend(options) {
|
|
|
12037
12414
|
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
12038
12415
|
let command = options.command;
|
|
12039
12416
|
let child = spawnCli(command, args, context);
|
|
12040
|
-
if (context.revalidateCommand) {
|
|
12041
|
-
const spawnError = await waitForSpawnOutcome(child);
|
|
12042
|
-
if (spawnError) {
|
|
12043
|
-
const fresh = spawnError.code === "ENOENT" ? context.revalidateCommand()?.trim() || null : null;
|
|
12044
|
-
if (fresh && fresh !== command) {
|
|
12045
|
-
console.warn(
|
|
12046
|
-
`[${options.kind}] Spawn failed ENOENT for "${command}" \u2014 re-resolved to "${fresh}", retrying once`
|
|
12047
|
-
);
|
|
12048
|
-
command = fresh;
|
|
12049
|
-
child = spawnCli(command, args, context);
|
|
12050
|
-
} else {
|
|
12051
|
-
throw formatCliSpawnError(command, spawnError);
|
|
12052
|
-
}
|
|
12053
|
-
}
|
|
12054
|
-
}
|
|
12055
12417
|
state.process = child;
|
|
12056
12418
|
state.lastRawOutputAtMs = Date.now();
|
|
12057
|
-
context.onProcessSpawned?.(child);
|
|
12058
12419
|
context.registerLivenessProbe?.(() => ({
|
|
12059
12420
|
providerAlive: child.exitCode === null,
|
|
12060
12421
|
lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
|
|
12061
12422
|
}));
|
|
12423
|
+
for (let attempt = 0; ; attempt++) {
|
|
12424
|
+
if (child.pid !== void 0) break;
|
|
12425
|
+
const spawnError = await waitForSpawnOutcome(child);
|
|
12426
|
+
if (!spawnError) break;
|
|
12427
|
+
const delays = SPAWN_RETRY_DELAYS_BY_CODE[spawnError.code ?? ""] ?? [];
|
|
12428
|
+
if (attempt >= delays.length) {
|
|
12429
|
+
throw formatCliSpawnError(command, spawnError);
|
|
12430
|
+
}
|
|
12431
|
+
const waitMs = delays[attempt];
|
|
12432
|
+
console.warn(
|
|
12433
|
+
`[${options.kind}] Spawn of "${command}" failed ${spawnError.code} \u2014 a CLI installer is likely mid-flight; retrying in ${waitMs}ms (attempt ${attempt + 1}/${delays.length})`
|
|
12434
|
+
);
|
|
12435
|
+
await delay(waitMs);
|
|
12436
|
+
const fresh = context.revalidateCommand?.()?.trim() || null;
|
|
12437
|
+
if (fresh && fresh !== command) {
|
|
12438
|
+
console.warn(`[${options.kind}] Re-resolved "${command}" to "${fresh}"`);
|
|
12439
|
+
command = fresh;
|
|
12440
|
+
}
|
|
12441
|
+
child = spawnCli(command, args, context);
|
|
12442
|
+
}
|
|
12443
|
+
state.process = child;
|
|
12444
|
+
state.lastRawOutputAtMs = Date.now();
|
|
12445
|
+
context.onProcessSpawned?.(child);
|
|
12062
12446
|
const safeArgs = args.map(
|
|
12063
12447
|
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
12064
12448
|
);
|
|
@@ -18625,18 +19009,18 @@ ${output.stderr}`;
|
|
|
18625
19009
|
}
|
|
18626
19010
|
|
|
18627
19011
|
// ../shared/dist/node/local-workspace.js
|
|
18628
|
-
var import_node_fs = require("fs");
|
|
18629
|
-
var import_node_os = require("os");
|
|
18630
|
-
var import_node_path = require("path");
|
|
18631
|
-
function resolveAlanDefaultWorkspacePath(homeDirectory = (0, import_node_os.homedir)()) {
|
|
18632
|
-
return (0, import_node_path.join)(homeDirectory, ".alan", "workspace");
|
|
18633
|
-
}
|
|
18634
|
-
|
|
18635
|
-
// ../shared/dist/node/provider-limits.js
|
|
18636
|
-
var import_node_child_process = require("child_process");
|
|
18637
19012
|
var import_node_fs2 = require("fs");
|
|
18638
19013
|
var import_node_os2 = require("os");
|
|
18639
19014
|
var import_node_path2 = require("path");
|
|
19015
|
+
function resolveAlanDefaultWorkspacePath(homeDirectory = (0, import_node_os2.homedir)()) {
|
|
19016
|
+
return (0, import_node_path2.join)(homeDirectory, ".alan", "workspace");
|
|
19017
|
+
}
|
|
19018
|
+
|
|
19019
|
+
// ../shared/dist/node/provider-limits.js
|
|
19020
|
+
var import_node_child_process2 = require("child_process");
|
|
19021
|
+
var import_node_fs3 = require("fs");
|
|
19022
|
+
var import_node_os3 = require("os");
|
|
19023
|
+
var import_node_path3 = require("path");
|
|
18640
19024
|
var CACHE_TTL_MS = 6e4;
|
|
18641
19025
|
var cachedLimits = null;
|
|
18642
19026
|
function numericValue(value2) {
|
|
@@ -18647,42 +19031,42 @@ function numericValue(value2) {
|
|
|
18647
19031
|
}
|
|
18648
19032
|
return null;
|
|
18649
19033
|
}
|
|
18650
|
-
function
|
|
18651
|
-
return (value2 ?? "").split(
|
|
19034
|
+
function splitPath2(value2) {
|
|
19035
|
+
return (value2 ?? "").split(import_node_path3.delimiter).filter(Boolean);
|
|
18652
19036
|
}
|
|
18653
19037
|
function isExecutableFile(path) {
|
|
18654
19038
|
try {
|
|
18655
|
-
(0,
|
|
19039
|
+
(0, import_node_fs3.accessSync)(path, import_node_fs3.constants.X_OK);
|
|
18656
19040
|
return true;
|
|
18657
19041
|
} catch {
|
|
18658
19042
|
return false;
|
|
18659
19043
|
}
|
|
18660
19044
|
}
|
|
18661
|
-
function
|
|
18662
|
-
const home = env.HOME || (0,
|
|
19045
|
+
function augmentCliPath2(env) {
|
|
19046
|
+
const home = env.HOME || (0, import_node_os3.homedir)();
|
|
18663
19047
|
const extraPaths = [
|
|
18664
|
-
(0,
|
|
18665
|
-
(0,
|
|
18666
|
-
(0,
|
|
18667
|
-
(0,
|
|
19048
|
+
(0, import_node_path3.join)(home, ".local", "bin"),
|
|
19049
|
+
(0, import_node_path3.join)(home, ".local", "node", "bin"),
|
|
19050
|
+
(0, import_node_path3.join)(home, ".bun", "bin"),
|
|
19051
|
+
(0, import_node_path3.join)(home, ".cargo", "bin"),
|
|
18668
19052
|
"/opt/homebrew/bin",
|
|
18669
19053
|
"/usr/local/bin"
|
|
18670
19054
|
];
|
|
18671
|
-
const existing = new Set(
|
|
19055
|
+
const existing = new Set(splitPath2(env.PATH));
|
|
18672
19056
|
const missing = extraPaths.filter((path) => !existing.has(path));
|
|
18673
19057
|
if (missing.length === 0)
|
|
18674
19058
|
return env;
|
|
18675
19059
|
return {
|
|
18676
19060
|
...env,
|
|
18677
|
-
PATH: `${missing.join(
|
|
19061
|
+
PATH: `${missing.join(import_node_path3.delimiter)}${import_node_path3.delimiter}${env.PATH ?? ""}`
|
|
18678
19062
|
};
|
|
18679
19063
|
}
|
|
18680
19064
|
function resolveExecutable(command, env, overrideEnvVar) {
|
|
18681
19065
|
const override = env[overrideEnvVar]?.trim();
|
|
18682
19066
|
if (override && isExecutableFile(override))
|
|
18683
19067
|
return override;
|
|
18684
|
-
for (const dir of
|
|
18685
|
-
const candidate = (0,
|
|
19068
|
+
for (const dir of splitPath2(env.PATH)) {
|
|
19069
|
+
const candidate = (0, import_node_path3.join)(dir, command);
|
|
18686
19070
|
if (isExecutableFile(candidate))
|
|
18687
19071
|
return candidate;
|
|
18688
19072
|
}
|
|
@@ -18744,8 +19128,8 @@ async function withTimeout(promise, timeoutMs, fallback) {
|
|
|
18744
19128
|
try {
|
|
18745
19129
|
return await Promise.race([
|
|
18746
19130
|
promise,
|
|
18747
|
-
new Promise((
|
|
18748
|
-
timer = setTimeout(() =>
|
|
19131
|
+
new Promise((resolve7) => {
|
|
19132
|
+
timer = setTimeout(() => resolve7(fallback), timeoutMs);
|
|
18749
19133
|
})
|
|
18750
19134
|
]);
|
|
18751
19135
|
} finally {
|
|
@@ -18754,12 +19138,12 @@ async function withTimeout(promise, timeoutMs, fallback) {
|
|
|
18754
19138
|
}
|
|
18755
19139
|
}
|
|
18756
19140
|
async function collectCodexLimits(env) {
|
|
18757
|
-
const runtimeEnv =
|
|
19141
|
+
const runtimeEnv = augmentCliPath2(env);
|
|
18758
19142
|
const codexCommand = resolveExecutable("codex", runtimeEnv, "ALAN_CODEX_PATH");
|
|
18759
19143
|
if (!codexCommand)
|
|
18760
19144
|
return [];
|
|
18761
|
-
return withTimeout(new Promise((
|
|
18762
|
-
const child = (0,
|
|
19145
|
+
return withTimeout(new Promise((resolve7) => {
|
|
19146
|
+
const child = (0, import_node_child_process2.spawn)(codexCommand, ["-s", "read-only", "-a", "untrusted", "app-server"], {
|
|
18763
19147
|
env: runtimeEnv,
|
|
18764
19148
|
stdio: ["pipe", "pipe", "ignore"]
|
|
18765
19149
|
});
|
|
@@ -18770,7 +19154,7 @@ async function collectCodexLimits(env) {
|
|
|
18770
19154
|
pending.clear();
|
|
18771
19155
|
if (!child.killed)
|
|
18772
19156
|
child.kill();
|
|
18773
|
-
|
|
19157
|
+
resolve7(windows);
|
|
18774
19158
|
};
|
|
18775
19159
|
const request = (method, params = {}) => {
|
|
18776
19160
|
const id = nextId++;
|
|
@@ -18826,10 +19210,10 @@ async function collectCodexLimits(env) {
|
|
|
18826
19210
|
}), 4e3, []);
|
|
18827
19211
|
}
|
|
18828
19212
|
function readClaudeCredentialsPayload() {
|
|
18829
|
-
const path = (0,
|
|
18830
|
-
if ((0,
|
|
19213
|
+
const path = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".claude", ".credentials.json");
|
|
19214
|
+
if ((0, import_node_fs3.existsSync)(path)) {
|
|
18831
19215
|
try {
|
|
18832
|
-
return JSON.parse((0,
|
|
19216
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
|
|
18833
19217
|
} catch {
|
|
18834
19218
|
return null;
|
|
18835
19219
|
}
|
|
@@ -18837,7 +19221,7 @@ function readClaudeCredentialsPayload() {
|
|
|
18837
19221
|
if (process.platform !== "darwin")
|
|
18838
19222
|
return null;
|
|
18839
19223
|
try {
|
|
18840
|
-
const raw = (0,
|
|
19224
|
+
const raw = (0, import_node_child_process2.execFileSync)("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
18841
19225
|
if (!raw)
|
|
18842
19226
|
return null;
|
|
18843
19227
|
return JSON.parse(raw);
|
|
@@ -20536,11 +20920,11 @@ var SocketWithoutUpgrade = class _SocketWithoutUpgrade extends Emitter {
|
|
|
20536
20920
|
*/
|
|
20537
20921
|
_resetPingTimeout() {
|
|
20538
20922
|
this.clearTimeoutFn(this._pingTimeoutTimer);
|
|
20539
|
-
const
|
|
20540
|
-
this._pingTimeoutTime = Date.now() +
|
|
20923
|
+
const delay2 = this._pingInterval + this._pingTimeout;
|
|
20924
|
+
this._pingTimeoutTime = Date.now() + delay2;
|
|
20541
20925
|
this._pingTimeoutTimer = this.setTimeoutFn(() => {
|
|
20542
20926
|
this._onClose("ping timeout");
|
|
20543
|
-
},
|
|
20927
|
+
}, delay2);
|
|
20544
20928
|
if (this.opts.autoUnref) {
|
|
20545
20929
|
this._pingTimeoutTimer.unref();
|
|
20546
20930
|
}
|
|
@@ -21603,9 +21987,9 @@ var Socket2 = class extends Emitter {
|
|
|
21603
21987
|
* @return a Promise that will be fulfilled when the server acknowledges the event
|
|
21604
21988
|
*/
|
|
21605
21989
|
emitWithAck(ev, ...args) {
|
|
21606
|
-
return new Promise((
|
|
21990
|
+
return new Promise((resolve7, reject) => {
|
|
21607
21991
|
const fn = (arg1, arg2) => {
|
|
21608
|
-
return arg1 ? reject(arg1) :
|
|
21992
|
+
return arg1 ? reject(arg1) : resolve7(arg2);
|
|
21609
21993
|
};
|
|
21610
21994
|
fn.withError = true;
|
|
21611
21995
|
args.push(fn);
|
|
@@ -22521,8 +22905,8 @@ var Manager = class extends Emitter {
|
|
|
22521
22905
|
this.emitReserved("reconnect_failed");
|
|
22522
22906
|
this._reconnecting = false;
|
|
22523
22907
|
} else {
|
|
22524
|
-
const
|
|
22525
|
-
debug10("will wait %dms before reconnect attempt",
|
|
22908
|
+
const delay2 = this.backoff.duration();
|
|
22909
|
+
debug10("will wait %dms before reconnect attempt", delay2);
|
|
22526
22910
|
this._reconnecting = true;
|
|
22527
22911
|
const timer = this.setTimeoutFn(() => {
|
|
22528
22912
|
if (self.skipReconnect)
|
|
@@ -22542,7 +22926,7 @@ var Manager = class extends Emitter {
|
|
|
22542
22926
|
self.onreconnect();
|
|
22543
22927
|
}
|
|
22544
22928
|
});
|
|
22545
|
-
},
|
|
22929
|
+
}, delay2);
|
|
22546
22930
|
if (this.opts.autoUnref) {
|
|
22547
22931
|
timer.unref();
|
|
22548
22932
|
}
|
|
@@ -22591,365 +22975,1139 @@ function lookup(uri, opts) {
|
|
|
22591
22975
|
}
|
|
22592
22976
|
io = cache[id];
|
|
22593
22977
|
}
|
|
22594
|
-
if (parsed.query && !opts.query) {
|
|
22595
|
-
opts.query = parsed.queryKey;
|
|
22978
|
+
if (parsed.query && !opts.query) {
|
|
22979
|
+
opts.query = parsed.queryKey;
|
|
22980
|
+
}
|
|
22981
|
+
return io.socket(parsed.path, opts);
|
|
22982
|
+
}
|
|
22983
|
+
Object.assign(lookup, {
|
|
22984
|
+
Manager,
|
|
22985
|
+
Socket: Socket2,
|
|
22986
|
+
io: lookup,
|
|
22987
|
+
connect: lookup
|
|
22988
|
+
});
|
|
22989
|
+
|
|
22990
|
+
// src/binding-switch-git.ts
|
|
22991
|
+
var import_node_child_process3 = require("child_process");
|
|
22992
|
+
var import_node_crypto = require("crypto");
|
|
22993
|
+
var import_node_fs4 = require("fs");
|
|
22994
|
+
var import_node_os4 = require("os");
|
|
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);
|
|
23008
|
+
}
|
|
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";
|
|
23015
|
+
}
|
|
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
|
+
};
|
|
23037
|
+
}
|
|
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
|
+
);
|
|
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
|
+
});
|
|
23092
|
+
let stdout = "";
|
|
23093
|
+
let stderr = "";
|
|
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);
|
|
23122
|
+
});
|
|
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());
|
|
23161
|
+
});
|
|
23162
|
+
});
|
|
23163
|
+
}
|
|
23164
|
+
function runGit(cwd, args, code = "git_operation_failed") {
|
|
23165
|
+
return runGitWithOptions(cwd, args, {}, code);
|
|
23166
|
+
}
|
|
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();
|
|
23176
|
+
}
|
|
23177
|
+
function sourceChangedDuringCheckpoint(message) {
|
|
23178
|
+
return new BindingSwitchGitError("source_changed_since_approval", message);
|
|
23179
|
+
}
|
|
23180
|
+
function acquireCheckpointHeadLock(locks) {
|
|
23181
|
+
try {
|
|
23182
|
+
(0, import_node_fs4.writeFileSync)(locks.headLockPath, "", { flag: "wx" });
|
|
23183
|
+
locks.headLockHeld = true;
|
|
23184
|
+
} catch {
|
|
23185
|
+
throw sourceChangedDuringCheckpoint(
|
|
23186
|
+
"Source repository is being changed by another Git operation"
|
|
23187
|
+
);
|
|
23188
|
+
}
|
|
23189
|
+
}
|
|
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
|
+
};
|
|
23213
|
+
try {
|
|
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;
|
|
23222
|
+
}
|
|
23223
|
+
throw sourceChangedDuringCheckpoint(
|
|
23224
|
+
"Source repository is being changed by another Git operation"
|
|
23225
|
+
);
|
|
23226
|
+
}
|
|
23227
|
+
}
|
|
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
|
+
}
|
|
23234
|
+
}
|
|
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
|
+
}
|
|
23247
|
+
}
|
|
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
|
+
}
|
|
23263
|
+
}
|
|
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
|
+
}
|
|
23275
|
+
}
|
|
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`);
|
|
23280
|
+
}
|
|
23281
|
+
if (/[\0\r\n]/.test(value2)) {
|
|
23282
|
+
throw new BindingSwitchGitError("invalid_git_request", `${key} contains unsafe characters`);
|
|
23283
|
+
}
|
|
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`);
|
|
22596
23317
|
}
|
|
22597
|
-
return io.socket(parsed.path, opts);
|
|
22598
23318
|
}
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
// src/cli-executable.ts
|
|
22611
|
-
var import_node_child_process2 = require("child_process");
|
|
22612
|
-
var import_node_fs3 = require("fs");
|
|
22613
|
-
var import_node_os3 = require("os");
|
|
22614
|
-
var import_node_path3 = require("path");
|
|
22615
|
-
var PROVIDER_CLI_COMMANDS = {
|
|
22616
|
-
claude_cli: { provider: "claude_cli", command: "claude", envVars: ["ALAN_CLAUDE_PATH"] },
|
|
22617
|
-
codex_app_server: {
|
|
22618
|
-
provider: "codex_app_server",
|
|
22619
|
-
command: "codex",
|
|
22620
|
-
envVars: ["ALAN_CODEX_PATH"]
|
|
22621
|
-
},
|
|
22622
|
-
copilot_cli: { provider: "copilot_cli", command: "copilot", envVars: ["ALAN_COPILOT_PATH"] },
|
|
22623
|
-
cursor_agent_cli: {
|
|
22624
|
-
provider: "cursor_agent_cli",
|
|
22625
|
-
command: "cursor-agent",
|
|
22626
|
-
envVars: ["ALAN_CURSOR_AGENT_PATH"]
|
|
22627
|
-
},
|
|
22628
|
-
antigravity_cli: {
|
|
22629
|
-
provider: "antigravity_cli",
|
|
22630
|
-
command: "agy",
|
|
22631
|
-
envVars: ["ALAN_ANTIGRAVITY_PATH"]
|
|
22632
|
-
},
|
|
22633
|
-
kimi_cli: { provider: "kimi_cli", command: "kimi-cli", envVars: ["ALAN_KIMI_PATH"] },
|
|
22634
|
-
grok_cli: { provider: "grok_cli", command: "grok", envVars: ["ALAN_GROK_PATH"] },
|
|
22635
|
-
opencode_cli: { provider: "opencode_cli", command: "opencode", envVars: ["ALAN_OPENCODE_PATH"] },
|
|
22636
|
-
droid_cli: { provider: "droid_cli", command: "droid", envVars: ["ALAN_DROID_PATH"] },
|
|
22637
|
-
supatest_cli: { provider: "supatest_cli", command: "supatest", envVars: ["ALAN_SUPATEST_PATH"] }
|
|
22638
|
-
};
|
|
22639
|
-
var DISCOVERABLE_PROVIDER_KINDS = Object.keys(PROVIDER_CLI_COMMANDS);
|
|
22640
|
-
var PROVIDER_ALIASES = {
|
|
22641
|
-
agy: "antigravity_cli",
|
|
22642
|
-
antigravity: "antigravity_cli",
|
|
22643
|
-
antigravity_cli: "antigravity_cli",
|
|
22644
|
-
codex: "codex_app_server",
|
|
22645
|
-
gemini: "antigravity_cli",
|
|
22646
|
-
gemini_cli: "antigravity_cli"
|
|
22647
|
-
};
|
|
22648
|
-
var BACKEND_CLI_COMMANDS = {
|
|
22649
|
-
claude_cli: "claude",
|
|
22650
|
-
codex: "codex",
|
|
22651
|
-
codex_app_server: "codex",
|
|
22652
|
-
copilot_cli: "copilot",
|
|
22653
|
-
cursor_agent_cli: "cursor-agent",
|
|
22654
|
-
agy: "agy",
|
|
22655
|
-
antigravity: "agy",
|
|
22656
|
-
antigravity_cli: "agy",
|
|
22657
|
-
gemini_cli: "agy",
|
|
22658
|
-
gemini: "agy",
|
|
22659
|
-
kimi_cli: "kimi-cli",
|
|
22660
|
-
grok_cli: "grok",
|
|
22661
|
-
opencode_cli: "opencode",
|
|
22662
|
-
droid_cli: "droid",
|
|
22663
|
-
supatest_cli: "supatest"
|
|
22664
|
-
};
|
|
22665
|
-
var CLI_COMMAND_ALIASES = {
|
|
22666
|
-
"kimi-cli": ["kimi"],
|
|
22667
|
-
grok: ["grok-build"]
|
|
22668
|
-
};
|
|
22669
|
-
var resolvedCliCache = /* @__PURE__ */ new Map();
|
|
22670
|
-
var NEGATIVE_RESOLUTION_TTL_MS = 15e3;
|
|
22671
|
-
var negativeResolutionExpiry = /* @__PURE__ */ new Map();
|
|
22672
|
-
var cachedLoginShellEnv = null;
|
|
22673
|
-
function parseEnvOutput(output) {
|
|
22674
|
-
const loginEnv = {};
|
|
22675
|
-
for (const line of output.split("\n")) {
|
|
22676
|
-
if (!line.trim()) continue;
|
|
22677
|
-
const eqIdx = line.indexOf("=");
|
|
22678
|
-
if (eqIdx <= 0) continue;
|
|
22679
|
-
const key = line.slice(0, eqIdx);
|
|
22680
|
-
if (/\s/.test(key)) continue;
|
|
22681
|
-
loginEnv[key] = line.slice(eqIdx + 1);
|
|
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");
|
|
22682
23329
|
}
|
|
22683
|
-
if (!loginEnv.HOME) loginEnv.HOME = process.env.HOME ?? (0, import_node_os3.homedir)();
|
|
22684
|
-
if (!loginEnv.SHELL) loginEnv.SHELL = process.env.SHELL ?? "/bin/zsh";
|
|
22685
|
-
return loginEnv;
|
|
22686
23330
|
}
|
|
22687
|
-
function
|
|
22688
|
-
|
|
22689
|
-
if ((
|
|
22690
|
-
|
|
22691
|
-
|
|
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$/, "")}`;
|
|
22692
23348
|
}
|
|
23349
|
+
let parsed;
|
|
22693
23350
|
try {
|
|
22694
|
-
|
|
22695
|
-
const envOutput = (0, import_node_child_process2.execSync)(`'${shell}' -ilc 'env'`, {
|
|
22696
|
-
encoding: "utf8",
|
|
22697
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
22698
|
-
timeout: 5e3
|
|
22699
|
-
});
|
|
22700
|
-
cachedLoginShellEnv = parseEnvOutput(envOutput);
|
|
23351
|
+
parsed = new URL(urlValue);
|
|
22701
23352
|
} catch {
|
|
22702
|
-
|
|
23353
|
+
throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
|
|
22703
23354
|
}
|
|
22704
|
-
|
|
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 };
|
|
22705
23399
|
}
|
|
22706
|
-
function
|
|
22707
|
-
const
|
|
22708
|
-
return
|
|
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);
|
|
22709
23419
|
}
|
|
22710
|
-
function
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
23420
|
+
function assertCanonicalPathContained(childPath, parentPath, code, message) {
|
|
23421
|
+
if (!isPathContained(
|
|
23422
|
+
canonicalPathWithMissingTail(childPath),
|
|
23423
|
+
canonicalPathWithMissingTail(parentPath)
|
|
23424
|
+
)) {
|
|
23425
|
+
throw new BindingSwitchGitError(code, message);
|
|
22716
23426
|
}
|
|
22717
23427
|
}
|
|
22718
|
-
function
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
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
|
+
);
|
|
22727
23437
|
}
|
|
22728
|
-
|
|
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()));
|
|
22729
23446
|
}
|
|
22730
|
-
function
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
windowsHide: true,
|
|
22742
|
-
timeout: 3e3
|
|
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
|
+
];
|
|
22743
23458
|
});
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
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
|
+
);
|
|
22749
23469
|
}
|
|
22750
|
-
|
|
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;
|
|
22751
23489
|
}
|
|
22752
|
-
function
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
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");
|
|
22760
23516
|
}
|
|
22761
|
-
|
|
22762
|
-
const
|
|
22763
|
-
|
|
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;
|
|
22764
23650
|
try {
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
});
|
|
22772
|
-
} catch (error) {
|
|
22773
|
-
resolve6({ status: null, stdout: "", stderr: "", error });
|
|
22774
|
-
return;
|
|
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 });
|
|
22775
23658
|
}
|
|
22776
|
-
|
|
22777
|
-
|
|
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");
|
|
22778
23670
|
}
|
|
22779
|
-
|
|
22780
|
-
|
|
22781
|
-
|
|
22782
|
-
|
|
22783
|
-
|
|
22784
|
-
|
|
22785
|
-
|
|
22786
|
-
}
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
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"
|
|
22791
23752
|
);
|
|
22792
|
-
|
|
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
|
+
};
|
|
22793
23851
|
}
|
|
22794
|
-
function
|
|
22795
|
-
|
|
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 };
|
|
22796
23876
|
}
|
|
22797
|
-
function
|
|
22798
|
-
|
|
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 };
|
|
22799
23883
|
}
|
|
22800
|
-
function
|
|
22801
|
-
|
|
22802
|
-
return pathValue.split(pathSeparator()).filter(Boolean);
|
|
23884
|
+
function isAncestor(repoRoot, ancestor, descendant) {
|
|
23885
|
+
return runGitResult(repoRoot, ["merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
|
|
22803
23886
|
}
|
|
22804
|
-
function
|
|
22805
|
-
if (
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
} catch {
|
|
22811
|
-
return false;
|
|
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
|
+
);
|
|
22812
23893
|
}
|
|
22813
23894
|
}
|
|
22814
|
-
function
|
|
22815
|
-
const
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22820
|
-
|
|
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
|
+
}
|
|
22821
23906
|
}
|
|
22822
|
-
function
|
|
22823
|
-
const
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22841
|
-
const
|
|
22842
|
-
if (
|
|
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);
|
|
22843
23952
|
return {
|
|
22844
|
-
|
|
22845
|
-
|
|
23953
|
+
repoRoot,
|
|
23954
|
+
path: repoRoot,
|
|
23955
|
+
branch,
|
|
23956
|
+
headSha: targetSha,
|
|
23957
|
+
replayed
|
|
22846
23958
|
};
|
|
22847
23959
|
}
|
|
22848
|
-
function
|
|
22849
|
-
const
|
|
22850
|
-
|
|
22851
|
-
const
|
|
22852
|
-
if (
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
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
|
+
);
|
|
22857
23969
|
}
|
|
22858
|
-
const
|
|
22859
|
-
const
|
|
22860
|
-
const
|
|
22861
|
-
const
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
(name) => isWin ? windowsCommandCandidates(name, pathExt) : [name]
|
|
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)
|
|
22865
23976
|
);
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
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
|
+
);
|
|
22874
24002
|
}
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
|
|
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
|
+
);
|
|
22880
24033
|
}
|
|
22881
24034
|
}
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
if (
|
|
22887
|
-
|
|
22888
|
-
return candidate;
|
|
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");
|
|
22889
24041
|
}
|
|
24042
|
+
} else {
|
|
24043
|
+
runGit(
|
|
24044
|
+
repoRoot,
|
|
24045
|
+
["worktree", "add", "-b", branch, worktreePath, targetSha],
|
|
24046
|
+
"worktree_prepare_failed"
|
|
24047
|
+
);
|
|
22890
24048
|
}
|
|
22891
|
-
const
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
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
|
+
);
|
|
22895
24056
|
}
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
if (provider in PROVIDER_CLI_COMMANDS) {
|
|
22903
|
-
return provider;
|
|
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
|
+
);
|
|
22904
24063
|
}
|
|
22905
|
-
return
|
|
24064
|
+
return {
|
|
24065
|
+
repoRoot: worktreePath,
|
|
24066
|
+
path: worktreePath,
|
|
24067
|
+
branch,
|
|
24068
|
+
headSha: targetSha,
|
|
24069
|
+
replayed: false
|
|
24070
|
+
};
|
|
22906
24071
|
}
|
|
22907
|
-
function
|
|
22908
|
-
const
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
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
|
+
);
|
|
22916
24083
|
}
|
|
22917
|
-
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
22932
|
-
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
|
|
22938
|
-
negativeResolutionExpiry.delete(trimmed);
|
|
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);
|
|
22939
24105
|
}
|
|
22940
24106
|
}
|
|
22941
|
-
function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
|
|
22942
|
-
if (!backendKind) return void 0;
|
|
22943
|
-
const command = BACKEND_CLI_COMMANDS[backendKind];
|
|
22944
|
-
if (!command) return void 0;
|
|
22945
|
-
invalidateResolvedCli(command);
|
|
22946
|
-
const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
|
|
22947
|
-
const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
|
|
22948
|
-
if (envOverride) invalidateResolvedCli(envOverride);
|
|
22949
|
-
return resolveBackendRuntimeCommand(backendKind, env);
|
|
22950
|
-
}
|
|
22951
24107
|
|
|
22952
24108
|
// src/cli-ensure-install.ts
|
|
24109
|
+
var import_node_child_process4 = require("child_process");
|
|
24110
|
+
var import_node_os5 = require("os");
|
|
22953
24111
|
var CLI_RUNTIME_VERSIONS_ENV = "ALAN_CLI_RUNTIME_VERSIONS";
|
|
22954
24112
|
var INSTALLABLE_PROVIDER_PACKAGES = {
|
|
22955
24113
|
claude_cli: { npmPackage: "@anthropic-ai/claude-code", command: "claude", label: "Claude Code" },
|
|
@@ -22974,11 +24132,11 @@ function pinnedVersion(npmPackage) {
|
|
|
22974
24132
|
return "latest";
|
|
22975
24133
|
}
|
|
22976
24134
|
function runNpmInstall(installSpec, env, timeoutMs) {
|
|
22977
|
-
return new Promise((
|
|
22978
|
-
const isWin = (0,
|
|
24135
|
+
return new Promise((resolve7) => {
|
|
24136
|
+
const isWin = (0, import_node_os5.platform)() === "win32";
|
|
22979
24137
|
let child;
|
|
22980
24138
|
try {
|
|
22981
|
-
child = (0,
|
|
24139
|
+
child = (0, import_node_child_process4.spawn)("npm", ["install", "-g", installSpec], {
|
|
22982
24140
|
env,
|
|
22983
24141
|
shell: isWin,
|
|
22984
24142
|
windowsHide: isWin ? true : void 0,
|
|
@@ -22986,7 +24144,7 @@ function runNpmInstall(installSpec, env, timeoutMs) {
|
|
|
22986
24144
|
stdio: ["ignore", "pipe", "pipe"]
|
|
22987
24145
|
});
|
|
22988
24146
|
} catch (error) {
|
|
22989
|
-
|
|
24147
|
+
resolve7({ status: null, stdout: "", stderr: "", error });
|
|
22990
24148
|
return;
|
|
22991
24149
|
}
|
|
22992
24150
|
let stdout = "";
|
|
@@ -22997,10 +24155,10 @@ function runNpmInstall(installSpec, env, timeoutMs) {
|
|
|
22997
24155
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
22998
24156
|
stderr += chunk;
|
|
22999
24157
|
});
|
|
23000
|
-
child.once("error", (error) =>
|
|
24158
|
+
child.once("error", (error) => resolve7({ status: null, stdout, stderr, error }));
|
|
23001
24159
|
child.once(
|
|
23002
24160
|
"close",
|
|
23003
|
-
(status, signal) =>
|
|
24161
|
+
(status, signal) => resolve7({
|
|
23004
24162
|
status,
|
|
23005
24163
|
stdout,
|
|
23006
24164
|
stderr,
|
|
@@ -23045,14 +24203,14 @@ function ensureProviderCliInstalled(backendKind, options) {
|
|
|
23045
24203
|
}
|
|
23046
24204
|
|
|
23047
24205
|
// src/computer-readiness.ts
|
|
23048
|
-
var
|
|
24206
|
+
var import_node_fs6 = require("fs");
|
|
23049
24207
|
|
|
23050
24208
|
// src/runtime-config.ts
|
|
23051
|
-
var
|
|
23052
|
-
var
|
|
23053
|
-
var
|
|
23054
|
-
var DEFAULT_AGENT_DIR = (0,
|
|
23055
|
-
var LEGACY_AGENT_DIR = (0,
|
|
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");
|
|
23056
24214
|
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Map([
|
|
23057
24215
|
["apiUrl", "string"],
|
|
23058
24216
|
["wsUrl", "string"],
|
|
@@ -23074,9 +24232,9 @@ function sanitizeAgentConfig(config) {
|
|
|
23074
24232
|
}
|
|
23075
24233
|
function isValidAgentConfig(configPath) {
|
|
23076
24234
|
try {
|
|
23077
|
-
const stat = (0,
|
|
24235
|
+
const stat = (0, import_node_fs5.lstatSync)(configPath);
|
|
23078
24236
|
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
23079
|
-
const parsed = JSON.parse((0,
|
|
24237
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(configPath, "utf8"));
|
|
23080
24238
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
23081
24239
|
const entries = Object.entries(parsed);
|
|
23082
24240
|
return entries.some(([key]) => AGENT_CONFIG_KEYS.has(key)) && entries.every(([key, value2]) => {
|
|
@@ -23092,36 +24250,36 @@ function migrateLegacyAgentConfig(input) {
|
|
|
23092
24250
|
const alanBaseDir = input?.alanBaseDir ?? DEFAULT_AGENT_DIR;
|
|
23093
24251
|
const legacyBaseDir = input?.legacyBaseDir ?? LEGACY_AGENT_DIR;
|
|
23094
24252
|
const fileName = resolveAgentConfigFileName(profile);
|
|
23095
|
-
const legacyPath = (0,
|
|
23096
|
-
const alanPath = (0,
|
|
23097
|
-
if ((0,
|
|
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;
|
|
23098
24256
|
let copied = false;
|
|
23099
24257
|
try {
|
|
23100
|
-
const sourceDirectoryStat = (0,
|
|
23101
|
-
const sourceFileStat = (0,
|
|
24258
|
+
const sourceDirectoryStat = (0, import_node_fs5.lstatSync)(legacyBaseDir);
|
|
24259
|
+
const sourceFileStat = (0, import_node_fs5.lstatSync)(legacyPath);
|
|
23102
24260
|
if (!sourceDirectoryStat.isDirectory() || sourceDirectoryStat.isSymbolicLink()) return false;
|
|
23103
|
-
const alanDirectoryExisted = (0,
|
|
24261
|
+
const alanDirectoryExisted = (0, import_node_fs5.existsSync)(alanBaseDir);
|
|
23104
24262
|
if (alanDirectoryExisted) {
|
|
23105
|
-
const alanDirectoryStat = (0,
|
|
24263
|
+
const alanDirectoryStat = (0, import_node_fs5.lstatSync)(alanBaseDir);
|
|
23106
24264
|
if (!alanDirectoryStat.isDirectory() || alanDirectoryStat.isSymbolicLink()) return false;
|
|
23107
24265
|
}
|
|
23108
|
-
(0,
|
|
23109
|
-
if (!alanDirectoryExisted) (0,
|
|
23110
|
-
(0,
|
|
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);
|
|
23111
24269
|
copied = true;
|
|
23112
24270
|
const migrated = sanitizeAgentConfig(
|
|
23113
|
-
JSON.parse((0,
|
|
24271
|
+
JSON.parse((0, import_node_fs5.readFileSync)(alanPath, "utf8"))
|
|
23114
24272
|
);
|
|
23115
|
-
(0,
|
|
23116
|
-
(0,
|
|
23117
|
-
(0,
|
|
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);
|
|
23118
24276
|
if (!isValidAgentConfig(alanPath)) {
|
|
23119
|
-
(0,
|
|
24277
|
+
(0, import_node_fs5.unlinkSync)(alanPath);
|
|
23120
24278
|
return false;
|
|
23121
24279
|
}
|
|
23122
24280
|
return true;
|
|
23123
24281
|
} catch {
|
|
23124
|
-
if (copied && (0,
|
|
24282
|
+
if (copied && (0, import_node_fs5.existsSync)(alanPath)) (0, import_node_fs5.unlinkSync)(alanPath);
|
|
23125
24283
|
return false;
|
|
23126
24284
|
}
|
|
23127
24285
|
}
|
|
@@ -23132,10 +24290,10 @@ function resolveAgentConfigPath(input) {
|
|
|
23132
24290
|
) ?? "production";
|
|
23133
24291
|
const baseDir = input?.baseDir ?? DEFAULT_AGENT_DIR;
|
|
23134
24292
|
if (input?.baseDir === void 0) migrateLegacyAgentConfig({ profile });
|
|
23135
|
-
return (0,
|
|
24293
|
+
return (0, import_node_path5.join)(baseDir, resolveAgentConfigFileName(profile));
|
|
23136
24294
|
}
|
|
23137
24295
|
function configExistsAtPath(configPath) {
|
|
23138
|
-
return (0,
|
|
24296
|
+
return (0, import_node_fs5.existsSync)(configPath);
|
|
23139
24297
|
}
|
|
23140
24298
|
function resolveEndpointProfileFromArgs(args) {
|
|
23141
24299
|
const index = args.indexOf("--profile");
|
|
@@ -23182,9 +24340,9 @@ var WorkspaceAccessTracker = class {
|
|
|
23182
24340
|
}
|
|
23183
24341
|
};
|
|
23184
24342
|
var DEFAULT_WORKSPACE_PROBE = {
|
|
23185
|
-
stat: (path) => (0,
|
|
23186
|
-
access: (path, mode) => (0,
|
|
23187
|
-
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)
|
|
23188
24346
|
};
|
|
23189
24347
|
function workspaceVolumeRoot(path) {
|
|
23190
24348
|
const windowsDrive = path.match(/^([A-Za-z]):[\\/]/);
|
|
@@ -23248,7 +24406,7 @@ function inspectWorkspaceReadiness(workspacePath, probe = DEFAULT_WORKSPACE_PROB
|
|
|
23248
24406
|
probe
|
|
23249
24407
|
);
|
|
23250
24408
|
}
|
|
23251
|
-
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);
|
|
23252
24410
|
return {
|
|
23253
24411
|
ready: true,
|
|
23254
24412
|
message: "Workspace path is configured and accessible.",
|
|
@@ -23360,7 +24518,7 @@ function buildComputerReadiness(input) {
|
|
|
23360
24518
|
}
|
|
23361
24519
|
|
|
23362
24520
|
// src/core-agent.ts
|
|
23363
|
-
var
|
|
24521
|
+
var import_node_os7 = require("os");
|
|
23364
24522
|
var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
23365
24523
|
childProcess = null;
|
|
23366
24524
|
_aborted = false;
|
|
@@ -23510,7 +24668,7 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
23510
24668
|
if (this.runtime.backendKind === "grok_cli" && this.providerApiKey) {
|
|
23511
24669
|
augmented.XAI_API_KEY = this.providerApiKey;
|
|
23512
24670
|
}
|
|
23513
|
-
if ((0,
|
|
24671
|
+
if ((0, import_node_os7.platform)() !== "win32") {
|
|
23514
24672
|
const home = augmented.HOME ?? "/home/user";
|
|
23515
24673
|
const extraPaths = [`${home}/.local/node/bin`, `${home}/.local/bin`];
|
|
23516
24674
|
const separator = ":";
|
|
@@ -23674,9 +24832,9 @@ ${meta.description}`);
|
|
|
23674
24832
|
};
|
|
23675
24833
|
|
|
23676
24834
|
// src/event-outbox.ts
|
|
23677
|
-
var
|
|
23678
|
-
var
|
|
23679
|
-
var
|
|
24835
|
+
var import_node_crypto2 = require("crypto");
|
|
24836
|
+
var import_node_fs7 = require("fs");
|
|
24837
|
+
var import_node_path6 = require("path");
|
|
23680
24838
|
var OUTBOX_VERSION = 1;
|
|
23681
24839
|
var MAX_INTERMEDIATE_EVENTS_PER_RUN = 200;
|
|
23682
24840
|
var MAX_ENCRYPTED_OUTBOX_BYTES = 64 * 1024 * 1024;
|
|
@@ -23709,7 +24867,7 @@ function truncateUtf8(value2, maxBytes) {
|
|
|
23709
24867
|
return `${encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "")}\u2026`;
|
|
23710
24868
|
}
|
|
23711
24869
|
function terminalFingerprint(event) {
|
|
23712
|
-
return (0,
|
|
24870
|
+
return (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(event)).digest("hex").slice(0, 16);
|
|
23713
24871
|
}
|
|
23714
24872
|
function condenseTerminalEvent(event) {
|
|
23715
24873
|
const fingerprint = terminalFingerprint(event);
|
|
@@ -23787,7 +24945,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23787
24945
|
throw new Error("event outbox maxIntermediateEventsPerRun must be a non-negative integer");
|
|
23788
24946
|
}
|
|
23789
24947
|
this.entries = this.read();
|
|
23790
|
-
if (!(0,
|
|
24948
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) {
|
|
23791
24949
|
this.lastPersistedSignature = _EncryptedEventOutbox.EMPTY_SIGNATURE;
|
|
23792
24950
|
} else if (this.entries.length > 0) {
|
|
23793
24951
|
this.lastPersistedSignature = this.serializeForPersist().signature;
|
|
@@ -23817,7 +24975,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23817
24975
|
maxIntermediateEventsPerRun;
|
|
23818
24976
|
enqueue(conversationId, runId, event) {
|
|
23819
24977
|
const entry = {
|
|
23820
|
-
eventId: (0,
|
|
24978
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23821
24979
|
conversationId,
|
|
23822
24980
|
runId,
|
|
23823
24981
|
event,
|
|
@@ -23903,7 +25061,7 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23903
25061
|
const markerConversationId = runEntry?.conversationId ?? conversationId;
|
|
23904
25062
|
if (!markerConversationId) return;
|
|
23905
25063
|
const marker = {
|
|
23906
|
-
eventId: (0,
|
|
25064
|
+
eventId: (0, import_node_crypto2.randomUUID)(),
|
|
23907
25065
|
conversationId: markerConversationId,
|
|
23908
25066
|
runId,
|
|
23909
25067
|
event: { type: "text", text: CONDENSATION_NOTICE, ts: Date.now() },
|
|
@@ -23989,14 +25147,14 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
23989
25147
|
this.retryTimer = null;
|
|
23990
25148
|
}
|
|
23991
25149
|
read() {
|
|
23992
|
-
if (!(0,
|
|
25150
|
+
if (!(0, import_node_fs7.existsSync)(this.path)) return [];
|
|
23993
25151
|
let envelope;
|
|
23994
25152
|
try {
|
|
23995
|
-
envelope = JSON.parse((0,
|
|
25153
|
+
envelope = JSON.parse((0, import_node_fs7.readFileSync)(this.path, "utf8"));
|
|
23996
25154
|
if (envelope.version !== OUTBOX_VERSION) {
|
|
23997
25155
|
throw new Error(`unsupported event outbox version ${String(envelope.version)}`);
|
|
23998
25156
|
}
|
|
23999
|
-
const decipher = (0,
|
|
25157
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)(
|
|
24000
25158
|
"aes-256-gcm",
|
|
24001
25159
|
this.key,
|
|
24002
25160
|
Buffer.from(envelope.iv, "base64url")
|
|
@@ -24020,21 +25178,21 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24020
25178
|
return { plaintext: "", signature: _EncryptedEventOutbox.EMPTY_SIGNATURE };
|
|
24021
25179
|
}
|
|
24022
25180
|
const plaintext = JSON.stringify({ entries: this.entries });
|
|
24023
|
-
return { plaintext, signature: (0,
|
|
25181
|
+
return { plaintext, signature: (0, import_node_crypto2.createHash)("sha256").update(plaintext).digest("base64url") };
|
|
24024
25182
|
}
|
|
24025
25183
|
persist() {
|
|
24026
25184
|
const { plaintext, signature } = this.serializeForPersist();
|
|
24027
25185
|
if (signature === this.lastPersistedSignature) return;
|
|
24028
25186
|
try {
|
|
24029
25187
|
if (this.entries.length === 0) {
|
|
24030
|
-
(0,
|
|
25188
|
+
(0, import_node_fs7.rmSync)(this.path, { force: true });
|
|
24031
25189
|
this.lastPersistedSignature = signature;
|
|
24032
25190
|
this.persistFailureLogged = false;
|
|
24033
25191
|
return;
|
|
24034
25192
|
}
|
|
24035
|
-
(0,
|
|
24036
|
-
const iv = (0,
|
|
24037
|
-
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);
|
|
24038
25196
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
24039
25197
|
const envelope = {
|
|
24040
25198
|
version: OUTBOX_VERSION,
|
|
@@ -24042,13 +25200,13 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24042
25200
|
authTag: cipher.getAuthTag().toString("base64url"),
|
|
24043
25201
|
ciphertext: ciphertext.toString("base64url")
|
|
24044
25202
|
};
|
|
24045
|
-
const pendingPath = `${this.path}.pending-${process.pid}-${(0,
|
|
25203
|
+
const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
|
|
24046
25204
|
try {
|
|
24047
|
-
(0,
|
|
24048
|
-
(0,
|
|
24049
|
-
(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);
|
|
24050
25208
|
} finally {
|
|
24051
|
-
(0,
|
|
25209
|
+
(0, import_node_fs7.rmSync)(pendingPath, { force: true });
|
|
24052
25210
|
}
|
|
24053
25211
|
this.lastPersistedSignature = signature;
|
|
24054
25212
|
this.persistFailureLogged = false;
|
|
@@ -24066,38 +25224,38 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
|
24066
25224
|
};
|
|
24067
25225
|
|
|
24068
25226
|
// src/local-skill-scan.ts
|
|
24069
|
-
var
|
|
24070
|
-
var
|
|
25227
|
+
var import_node_fs8 = require("fs");
|
|
25228
|
+
var import_node_path7 = require("path");
|
|
24071
25229
|
var SKILL_READ_LIMIT_BYTES = 8192;
|
|
24072
25230
|
var SKILL_CACHE_TTL_MS = 3e4;
|
|
24073
25231
|
function readSkillHead(path) {
|
|
24074
|
-
const fd = (0,
|
|
25232
|
+
const fd = (0, import_node_fs8.openSync)(path, "r");
|
|
24075
25233
|
try {
|
|
24076
25234
|
const buffer = Buffer.alloc(SKILL_READ_LIMIT_BYTES);
|
|
24077
|
-
const bytesRead = (0,
|
|
25235
|
+
const bytesRead = (0, import_node_fs8.readSync)(fd, buffer, 0, SKILL_READ_LIMIT_BYTES, 0);
|
|
24078
25236
|
return buffer.subarray(0, bytesRead).toString("utf-8");
|
|
24079
25237
|
} finally {
|
|
24080
|
-
(0,
|
|
25238
|
+
(0, import_node_fs8.closeSync)(fd);
|
|
24081
25239
|
}
|
|
24082
25240
|
}
|
|
24083
25241
|
function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
24084
|
-
if (!(0,
|
|
25242
|
+
if (!(0, import_node_fs8.existsSync)(dirPath)) return;
|
|
24085
25243
|
let names;
|
|
24086
25244
|
try {
|
|
24087
|
-
names = (0,
|
|
25245
|
+
names = (0, import_node_fs8.readdirSync)(dirPath);
|
|
24088
25246
|
} catch (error) {
|
|
24089
25247
|
errors.push(`Failed to scan ${dirPath}: ${error instanceof Error ? error.message : error}`);
|
|
24090
25248
|
return;
|
|
24091
25249
|
}
|
|
24092
25250
|
for (const name of names) {
|
|
24093
|
-
const skillPath = (0,
|
|
25251
|
+
const skillPath = (0, import_node_path7.join)(dirPath, name, "SKILL.md");
|
|
24094
25252
|
let isDirectory = false;
|
|
24095
25253
|
try {
|
|
24096
|
-
isDirectory = (0,
|
|
25254
|
+
isDirectory = (0, import_node_fs8.statSync)((0, import_node_path7.join)(dirPath, name)).isDirectory();
|
|
24097
25255
|
} catch {
|
|
24098
25256
|
continue;
|
|
24099
25257
|
}
|
|
24100
|
-
if (!isDirectory || !(0,
|
|
25258
|
+
if (!isDirectory || !(0, import_node_fs8.existsSync)(skillPath)) continue;
|
|
24101
25259
|
try {
|
|
24102
25260
|
entries.push({ content: readSkillHead(skillPath), source, sourcePath: skillPath, repoRoot });
|
|
24103
25261
|
} catch (error) {
|
|
@@ -24108,54 +25266,194 @@ function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
|
|
|
24108
25266
|
function discoverChildRepos(parentPath) {
|
|
24109
25267
|
let names;
|
|
24110
25268
|
try {
|
|
24111
|
-
names = (0,
|
|
25269
|
+
names = (0, import_node_fs8.readdirSync)(parentPath);
|
|
24112
25270
|
} catch {
|
|
24113
25271
|
return [];
|
|
24114
25272
|
}
|
|
24115
|
-
const children = [];
|
|
24116
|
-
for (const name of names) {
|
|
24117
|
-
if (name.startsWith(".")) continue;
|
|
24118
|
-
const childPath = (0,
|
|
24119
|
-
try {
|
|
24120
|
-
if ((0,
|
|
24121
|
-
} catch {
|
|
24122
|
-
}
|
|
25273
|
+
const children = [];
|
|
25274
|
+
for (const name of names) {
|
|
25275
|
+
if (name.startsWith(".")) continue;
|
|
25276
|
+
const childPath = (0, import_node_path7.join)(parentPath, name);
|
|
25277
|
+
try {
|
|
25278
|
+
if ((0, import_node_fs8.statSync)(childPath).isDirectory()) children.push(childPath);
|
|
25279
|
+
} catch {
|
|
25280
|
+
}
|
|
25281
|
+
}
|
|
25282
|
+
return children;
|
|
25283
|
+
}
|
|
25284
|
+
function scanLocalSkills(homeDir, projectPath) {
|
|
25285
|
+
const rawEntries = [];
|
|
25286
|
+
const errors = [];
|
|
25287
|
+
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
25288
|
+
scanSkillDir((0, import_node_path7.join)(homeDir, root), "global", void 0, rawEntries, errors);
|
|
25289
|
+
}
|
|
25290
|
+
if (projectPath) {
|
|
25291
|
+
for (const dir of [projectPath, ...discoverChildRepos(projectPath)]) {
|
|
25292
|
+
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
25293
|
+
scanSkillDir((0, import_node_path7.join)(dir, root), "team", dir, rawEntries, errors);
|
|
25294
|
+
}
|
|
25295
|
+
}
|
|
25296
|
+
}
|
|
25297
|
+
return { skills: normalizeSkillEntries(rawEntries, "team"), errors };
|
|
25298
|
+
}
|
|
25299
|
+
var skillScanCache = /* @__PURE__ */ new Map();
|
|
25300
|
+
function scanLocalSkillsCached(homeDir, projectPath, options) {
|
|
25301
|
+
const key = projectPath ?? "";
|
|
25302
|
+
const now = Date.now();
|
|
25303
|
+
if (!options?.refresh) {
|
|
25304
|
+
const cached = skillScanCache.get(key);
|
|
25305
|
+
if (cached && cached.expiresAt > now) return cached.result;
|
|
25306
|
+
}
|
|
25307
|
+
const result = scanLocalSkills(homeDir, projectPath);
|
|
25308
|
+
skillScanCache.set(key, { expiresAt: now + SKILL_CACHE_TTL_MS, result });
|
|
25309
|
+
return result;
|
|
25310
|
+
}
|
|
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);
|
|
24123
25413
|
}
|
|
24124
|
-
return
|
|
25414
|
+
return preflightGitTree(input.cwd, git2);
|
|
24125
25415
|
}
|
|
24126
|
-
function
|
|
24127
|
-
|
|
24128
|
-
|
|
24129
|
-
for (const root of SKILL_DIRECTORY_ROOTS) {
|
|
24130
|
-
scanSkillDir((0, import_node_path6.join)(homeDir, root), "global", void 0, rawEntries, errors);
|
|
25416
|
+
function resolveDirtyTree(input, dependencies) {
|
|
25417
|
+
if (input.action === "discard" && input.confirmDiscard !== true) {
|
|
25418
|
+
throw new Error("Discarding workspace changes requires explicit confirmation");
|
|
24131
25419
|
}
|
|
24132
|
-
|
|
24133
|
-
|
|
24134
|
-
|
|
24135
|
-
|
|
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;
|
|
24136
25436
|
}
|
|
25437
|
+
case "discard":
|
|
25438
|
+
dependencies.git(input.cwd, ["reset", "--hard", "HEAD"]);
|
|
25439
|
+
dependencies.git(input.cwd, ["clean", "-fd"]);
|
|
25440
|
+
break;
|
|
24137
25441
|
}
|
|
24138
25442
|
}
|
|
24139
|
-
return {
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
|
|
24143
|
-
|
|
24144
|
-
|
|
24145
|
-
|
|
24146
|
-
const cached = skillScanCache.get(key);
|
|
24147
|
-
if (cached && cached.expiresAt > now) return cached.result;
|
|
24148
|
-
}
|
|
24149
|
-
const result = scanLocalSkills(homeDir, projectPath);
|
|
24150
|
-
skillScanCache.set(key, { expiresAt: now + SKILL_CACHE_TTL_MS, result });
|
|
24151
|
-
return result;
|
|
25443
|
+
return {
|
|
25444
|
+
action: input.action,
|
|
25445
|
+
before,
|
|
25446
|
+
after: preflightGitTree(input.cwd, dependencies.git),
|
|
25447
|
+
stash,
|
|
25448
|
+
commitOid
|
|
25449
|
+
};
|
|
24152
25450
|
}
|
|
24153
25451
|
|
|
24154
25452
|
// src/mcp-registration.ts
|
|
24155
|
-
var
|
|
24156
|
-
var
|
|
24157
|
-
var
|
|
24158
|
-
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");
|
|
24159
25457
|
|
|
24160
25458
|
// ../../node_modules/smol-toml/dist/error.js
|
|
24161
25459
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -24237,7 +25535,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
24237
25535
|
}
|
|
24238
25536
|
return ptr;
|
|
24239
25537
|
}
|
|
24240
|
-
function skipUntil(str, ptr,
|
|
25538
|
+
function skipUntil(str, ptr, sep3, end, banNewLines = false) {
|
|
24241
25539
|
if (!end) {
|
|
24242
25540
|
ptr = indexOfNewline(str, ptr);
|
|
24243
25541
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -24246,7 +25544,7 @@ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
|
24246
25544
|
let c = str[i];
|
|
24247
25545
|
if (c === "#") {
|
|
24248
25546
|
i = indexOfNewline(str, i);
|
|
24249
|
-
} else if (c ===
|
|
25547
|
+
} else if (c === sep3) {
|
|
24250
25548
|
return i + 1;
|
|
24251
25549
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
24252
25550
|
return i;
|
|
@@ -24952,18 +26250,18 @@ var inspectAlanMcpThroughProviderCli = async (backendKind, expectedUrl, home) =>
|
|
|
24952
26250
|
return result;
|
|
24953
26251
|
};
|
|
24954
26252
|
async function waitForConfigLock(lockPath) {
|
|
24955
|
-
(0,
|
|
26253
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(lockPath), { recursive: true });
|
|
24956
26254
|
const startedAt = Date.now();
|
|
24957
26255
|
while (true) {
|
|
24958
26256
|
try {
|
|
24959
|
-
(0,
|
|
24960
|
-
return () => (0,
|
|
26257
|
+
(0, import_node_fs9.mkdirSync)(lockPath);
|
|
26258
|
+
return () => (0, import_node_fs9.rmSync)(lockPath, { recursive: true, force: true });
|
|
24961
26259
|
} catch (error) {
|
|
24962
26260
|
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
24963
26261
|
if (code !== "EEXIST") throw error;
|
|
24964
26262
|
try {
|
|
24965
|
-
if (Date.now() - (0,
|
|
24966
|
-
(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 });
|
|
24967
26265
|
continue;
|
|
24968
26266
|
}
|
|
24969
26267
|
} catch {
|
|
@@ -24972,14 +26270,14 @@ async function waitForConfigLock(lockPath) {
|
|
|
24972
26270
|
if (Date.now() - startedAt >= CONFIG_LOCK_WAIT_MS) {
|
|
24973
26271
|
throw new Error(`Alan could not safely update ${lockPath.slice(0, -".alan-lock".length)}`);
|
|
24974
26272
|
}
|
|
24975
|
-
await new Promise((
|
|
26273
|
+
await new Promise((resolve7) => setTimeout(resolve7, CONFIG_LOCK_POLL_MS));
|
|
24976
26274
|
}
|
|
24977
26275
|
}
|
|
24978
26276
|
}
|
|
24979
26277
|
function saveMalformedBackup(path, content) {
|
|
24980
|
-
const hash = (0,
|
|
26278
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex").slice(0, 12);
|
|
24981
26279
|
const backupPath = `${path}.alan-backup-${hash}`;
|
|
24982
|
-
if (!(0,
|
|
26280
|
+
if (!(0, import_node_fs9.existsSync)(backupPath)) (0, import_node_fs9.copyFileSync)(path, backupPath);
|
|
24983
26281
|
return backupPath;
|
|
24984
26282
|
}
|
|
24985
26283
|
function parseJsonObject2(path, content) {
|
|
@@ -25024,24 +26322,24 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
|
25024
26322
|
return merged;
|
|
25025
26323
|
}
|
|
25026
26324
|
function atomicWriteManagedConfig(path, content) {
|
|
25027
|
-
const pendingPath = `${path}.alan-pending-${process.pid}-${(0,
|
|
26325
|
+
const pendingPath = `${path}.alan-pending-${process.pid}-${(0, import_node_crypto4.randomBytes)(6).toString("hex")}`;
|
|
25028
26326
|
try {
|
|
25029
|
-
(0,
|
|
25030
|
-
(0,
|
|
26327
|
+
(0, import_node_fs9.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
26328
|
+
(0, import_node_fs9.renameSync)(pendingPath, path);
|
|
25031
26329
|
} finally {
|
|
25032
|
-
(0,
|
|
26330
|
+
(0, import_node_fs9.rmSync)(pendingPath, { force: true });
|
|
25033
26331
|
}
|
|
25034
26332
|
}
|
|
25035
|
-
async function ensureAlanMcpRegistered(backendKind, registration, home = (0,
|
|
26333
|
+
async function ensureAlanMcpRegistered(backendKind, registration, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25036
26334
|
try {
|
|
25037
26335
|
const mcpServers = buildAlanMcpServers(registration);
|
|
25038
26336
|
const files = alanMcpConfigFilesForBackend(backendKind, mcpServers, home);
|
|
25039
26337
|
const paths = [];
|
|
25040
26338
|
for (const file of files) {
|
|
25041
|
-
(0,
|
|
26339
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path8.dirname)(file.path), { recursive: true });
|
|
25042
26340
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25043
26341
|
try {
|
|
25044
|
-
const current = (0,
|
|
26342
|
+
const current = (0, import_node_fs9.existsSync)(file.path) ? (0, import_node_fs9.readFileSync)(file.path, "utf8") : null;
|
|
25045
26343
|
const content = file.path.endsWith(".toml") ? mergeCodexAlanSection(file.path, current, file.content) : mergeJsonConfig(file.path, current, file.content);
|
|
25046
26344
|
if (current !== content) atomicWriteManagedConfig(file.path, content);
|
|
25047
26345
|
paths.push(file.path);
|
|
@@ -25092,13 +26390,13 @@ async function ensureAlanMcpRegistered(backendKind, registration, home = (0, imp
|
|
|
25092
26390
|
return { ok: false, paths: [], error: err instanceof Error ? err.message : String(err) };
|
|
25093
26391
|
}
|
|
25094
26392
|
}
|
|
25095
|
-
async function verifyAlanMcpRegistered(backendKind, home = (0,
|
|
26393
|
+
async function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os8.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
|
|
25096
26394
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25097
|
-
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));
|
|
25098
26396
|
const presentEntries = files.map((file) => ({
|
|
25099
26397
|
path: file.path,
|
|
25100
|
-
url: (0,
|
|
25101
|
-
})).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));
|
|
25102
26400
|
let invalid = presentEntries.filter((entry) => !entry.url).map((entry) => entry.path);
|
|
25103
26401
|
const urls = new Set(presentEntries.flatMap((entry) => entry.url ? [entry.url] : []));
|
|
25104
26402
|
if (urls.size > 1) invalid = presentEntries.map((entry) => entry.path);
|
|
@@ -25154,15 +26452,15 @@ function removeAlanFromJsonConfig(path, existingContent) {
|
|
|
25154
26452
|
}
|
|
25155
26453
|
return JSON.stringify(config);
|
|
25156
26454
|
}
|
|
25157
|
-
async function unregisterAlanMcp(backendKind, home = (0,
|
|
26455
|
+
async function unregisterAlanMcp(backendKind, home = (0, import_node_os8.homedir)()) {
|
|
25158
26456
|
const removedPaths = [];
|
|
25159
26457
|
const errors = [];
|
|
25160
26458
|
const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
|
|
25161
26459
|
for (const file of files) {
|
|
25162
|
-
if (!(0,
|
|
26460
|
+
if (!(0, import_node_fs9.existsSync)(file.path)) continue;
|
|
25163
26461
|
const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
|
|
25164
26462
|
try {
|
|
25165
|
-
const current = (0,
|
|
26463
|
+
const current = (0, import_node_fs9.readFileSync)(file.path, "utf8");
|
|
25166
26464
|
let next;
|
|
25167
26465
|
if (file.path.endsWith(".toml")) {
|
|
25168
26466
|
const stripped = stripCodexAlanMcpSection(current);
|
|
@@ -25193,7 +26491,7 @@ function isHttpUrl(value2) {
|
|
|
25193
26491
|
}
|
|
25194
26492
|
}
|
|
25195
26493
|
function readUsableAlanMcpUrl(path) {
|
|
25196
|
-
const content = (0,
|
|
26494
|
+
const content = (0, import_node_fs9.readFileSync)(path, "utf8");
|
|
25197
26495
|
if (path.endsWith(".toml")) {
|
|
25198
26496
|
const lines = content.split("\n");
|
|
25199
26497
|
const start = lines.findIndex((line) => line.trim() === "[mcp_servers.alan]");
|
|
@@ -25484,7 +26782,7 @@ ${result.stderr}`.trim();
|
|
|
25484
26782
|
}
|
|
25485
26783
|
|
|
25486
26784
|
// src/provider-model-discovery.ts
|
|
25487
|
-
var
|
|
26785
|
+
var import_node_fs10 = require("fs");
|
|
25488
26786
|
var MODEL_DISCOVERY_TIMEOUT_MS = 8e3;
|
|
25489
26787
|
var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
|
|
25490
26788
|
var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
|
|
@@ -25698,7 +26996,7 @@ function filterClaudeDatedModelIds(ids) {
|
|
|
25698
26996
|
}
|
|
25699
26997
|
function resolveExecutableForModelScan(executable) {
|
|
25700
26998
|
try {
|
|
25701
|
-
return (0,
|
|
26999
|
+
return (0, import_node_fs10.realpathSync)(executable);
|
|
25702
27000
|
} catch {
|
|
25703
27001
|
return executable;
|
|
25704
27002
|
}
|
|
@@ -25706,7 +27004,7 @@ function resolveExecutableForModelScan(executable) {
|
|
|
25706
27004
|
function extractClaudeModelIdsFromExecutable(executable) {
|
|
25707
27005
|
try {
|
|
25708
27006
|
const resolved = resolveExecutableForModelScan(executable);
|
|
25709
|
-
const text = (0,
|
|
27007
|
+
const text = (0, import_node_fs10.readFileSync)(resolved).toString("latin1");
|
|
25710
27008
|
const matches = text.match(CLAUDE_MODEL_ID_PATTERN) ?? [];
|
|
25711
27009
|
const unique = [
|
|
25712
27010
|
...new Set(matches.map((id) => id.trim()).filter((id) => isValidClaudeModelId(id))),
|
|
@@ -25833,8 +27131,8 @@ async function discoverProviderModels(provider, executable, env) {
|
|
|
25833
27131
|
}
|
|
25834
27132
|
|
|
25835
27133
|
// src/run-journal.ts
|
|
25836
|
-
var
|
|
25837
|
-
var
|
|
27134
|
+
var import_node_fs11 = require("fs");
|
|
27135
|
+
var import_node_path9 = require("path");
|
|
25838
27136
|
var JOURNAL_VERSION = 1;
|
|
25839
27137
|
var INTERRUPTED_ENTRY_TTL_MS = 24 * 60 * 6e4;
|
|
25840
27138
|
function isEntry(value2) {
|
|
@@ -25853,9 +27151,9 @@ function isEntry(value2) {
|
|
|
25853
27151
|
var RuntimeRunJournal = class {
|
|
25854
27152
|
constructor(path, nowMs = Date.now()) {
|
|
25855
27153
|
this.path = path;
|
|
25856
|
-
if (!(0,
|
|
27154
|
+
if (!(0, import_node_fs11.existsSync)(path)) return;
|
|
25857
27155
|
try {
|
|
25858
|
-
const parsed = JSON.parse((0,
|
|
27156
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
25859
27157
|
if (parsed.version !== JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
|
|
25860
27158
|
throw new Error("unsupported run journal format");
|
|
25861
27159
|
}
|
|
@@ -25978,18 +27276,18 @@ var RuntimeRunJournal = class {
|
|
|
25978
27276
|
}
|
|
25979
27277
|
persist() {
|
|
25980
27278
|
const pendingPath = `${this.path}.pending`;
|
|
25981
|
-
(0,
|
|
27279
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(this.path), { recursive: true });
|
|
25982
27280
|
try {
|
|
25983
|
-
(0,
|
|
27281
|
+
(0, import_node_fs11.writeFileSync)(
|
|
25984
27282
|
pendingPath,
|
|
25985
27283
|
`${JSON.stringify({ version: JOURNAL_VERSION, entries: [...this.entries.values()] })}
|
|
25986
27284
|
`,
|
|
25987
27285
|
{ mode: 384 }
|
|
25988
27286
|
);
|
|
25989
|
-
(0,
|
|
25990
|
-
(0,
|
|
27287
|
+
(0, import_node_fs11.chmodSync)(pendingPath, 384);
|
|
27288
|
+
(0, import_node_fs11.renameSync)(pendingPath, this.path);
|
|
25991
27289
|
} catch (error) {
|
|
25992
|
-
(0,
|
|
27290
|
+
(0, import_node_fs11.rmSync)(pendingPath, { force: true });
|
|
25993
27291
|
throw error;
|
|
25994
27292
|
}
|
|
25995
27293
|
}
|
|
@@ -26001,8 +27299,8 @@ var RunStartGate = class {
|
|
|
26001
27299
|
async acquire() {
|
|
26002
27300
|
const previous = this.tail;
|
|
26003
27301
|
let releaseCurrent;
|
|
26004
|
-
const current = new Promise((
|
|
26005
|
-
releaseCurrent =
|
|
27302
|
+
const current = new Promise((resolve7) => {
|
|
27303
|
+
releaseCurrent = resolve7;
|
|
26006
27304
|
});
|
|
26007
27305
|
this.tail = previous.then(() => current);
|
|
26008
27306
|
await previous;
|
|
@@ -26016,17 +27314,17 @@ var RunStartGate = class {
|
|
|
26016
27314
|
};
|
|
26017
27315
|
|
|
26018
27316
|
// src/version.ts
|
|
26019
|
-
var AGENT_VERSION = "0.1.
|
|
27317
|
+
var AGENT_VERSION = "0.1.51";
|
|
26020
27318
|
|
|
26021
27319
|
// src/workspace-relocation.ts
|
|
26022
|
-
var
|
|
26023
|
-
var
|
|
26024
|
-
var
|
|
27320
|
+
var import_node_child_process5 = require("child_process");
|
|
27321
|
+
var import_node_fs12 = require("fs");
|
|
27322
|
+
var import_node_path10 = require("path");
|
|
26025
27323
|
var MAX_RELOCATION_CANDIDATES = 200;
|
|
26026
27324
|
var DEFAULT_PROBE = {
|
|
26027
|
-
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)),
|
|
26028
27326
|
gitRemoteUrl: (candidatePath) => {
|
|
26029
|
-
const result = (0,
|
|
27327
|
+
const result = (0, import_node_child_process5.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
|
|
26030
27328
|
encoding: "utf8",
|
|
26031
27329
|
env: getDaemonCliEnvironment(),
|
|
26032
27330
|
timeout: 2e3
|
|
@@ -26055,7 +27353,7 @@ function workspaceMatchesExpectedRepositories(input) {
|
|
|
26055
27353
|
input.expectedRepoUrls.map(normalizeRepositoryIdentity).filter((identity2) => Boolean(identity2))
|
|
26056
27354
|
);
|
|
26057
27355
|
if (identities.size === 0) return false;
|
|
26058
|
-
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0,
|
|
27356
|
+
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0, import_node_path10.resolve)(input.workspacePath));
|
|
26059
27357
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26060
27358
|
return Boolean(identity && identities.has(identity));
|
|
26061
27359
|
}
|
|
@@ -26065,8 +27363,8 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26065
27363
|
);
|
|
26066
27364
|
if (identities.size === 0) return null;
|
|
26067
27365
|
const searchRoots = [
|
|
26068
|
-
(0,
|
|
26069
|
-
...(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)))
|
|
26070
27368
|
].filter((path, index, paths) => paths.indexOf(path) === index);
|
|
26071
27369
|
const candidates = [];
|
|
26072
27370
|
for (const root of searchRoots) {
|
|
@@ -26078,19 +27376,19 @@ function discoverRelocatedWorkspace(input) {
|
|
|
26078
27376
|
}
|
|
26079
27377
|
}
|
|
26080
27378
|
const matches = [];
|
|
26081
|
-
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)))) {
|
|
26082
27380
|
const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl(candidate);
|
|
26083
27381
|
const identity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26084
|
-
if (identity && identities.has(identity)) matches.push((0,
|
|
27382
|
+
if (identity && identities.has(identity)) matches.push((0, import_node_path10.resolve)(candidate));
|
|
26085
27383
|
if (matches.length > 1) return null;
|
|
26086
27384
|
}
|
|
26087
27385
|
return matches[0] ?? null;
|
|
26088
27386
|
}
|
|
26089
27387
|
|
|
26090
27388
|
// src/workspace-run-lease.ts
|
|
26091
|
-
var
|
|
26092
|
-
var
|
|
26093
|
-
var
|
|
27389
|
+
var import_node_child_process6 = require("child_process");
|
|
27390
|
+
var import_node_fs13 = require("fs");
|
|
27391
|
+
var import_node_path11 = require("path");
|
|
26094
27392
|
var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
|
|
26095
27393
|
function parsePositiveMs(value2, fallback) {
|
|
26096
27394
|
const parsed = value2 ? Number(value2) : Number.NaN;
|
|
@@ -26101,7 +27399,7 @@ var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
|
|
|
26101
27399
|
2e3
|
|
26102
27400
|
);
|
|
26103
27401
|
function gitValue(cwd, args) {
|
|
26104
|
-
const result = (0,
|
|
27402
|
+
const result = (0, import_node_child_process6.spawnSync)("git", ["-C", cwd, ...args], {
|
|
26105
27403
|
encoding: "utf8",
|
|
26106
27404
|
env: getDaemonCliEnvironment(),
|
|
26107
27405
|
timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
|
|
@@ -26110,17 +27408,17 @@ function gitValue(cwd, args) {
|
|
|
26110
27408
|
return result.stdout.trim() || null;
|
|
26111
27409
|
}
|
|
26112
27410
|
function filesystemIdentity(path) {
|
|
26113
|
-
const canonicalPath =
|
|
26114
|
-
const stat = (0,
|
|
27411
|
+
const canonicalPath = import_node_fs13.realpathSync.native(path);
|
|
27412
|
+
const stat = (0, import_node_fs13.statSync)(canonicalPath);
|
|
26115
27413
|
return `${canonicalPath}:${stat.dev}:${stat.ino}`;
|
|
26116
27414
|
}
|
|
26117
27415
|
function resolveGitPath(workspacePath, value2) {
|
|
26118
|
-
return (0,
|
|
27416
|
+
return (0, import_node_path11.isAbsolute)(value2) ? value2 : (0, import_node_path11.resolve)(workspacePath, value2);
|
|
26119
27417
|
}
|
|
26120
27418
|
var DEFAULT_PROBE2 = {
|
|
26121
27419
|
snapshot: (workspacePath) => {
|
|
26122
27420
|
try {
|
|
26123
|
-
const canonicalWorkspacePath =
|
|
27421
|
+
const canonicalWorkspacePath = import_node_fs13.realpathSync.native((0, import_node_path11.resolve)(workspacePath));
|
|
26124
27422
|
const repositoryRoot = gitValue(canonicalWorkspacePath, ["rev-parse", "--show-toplevel"]);
|
|
26125
27423
|
const gitDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-dir"]);
|
|
26126
27424
|
const commonDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-common-dir"]);
|
|
@@ -26129,7 +27427,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26129
27427
|
const remote = gitValue(canonicalWorkspacePath, ["config", "--get", "remote.origin.url"]);
|
|
26130
27428
|
const remoteIdentity = remote ? normalizeRepositoryIdentity(remote) : null;
|
|
26131
27429
|
return {
|
|
26132
|
-
workspacePath: (0,
|
|
27430
|
+
workspacePath: (0, import_node_path11.resolve)(workspacePath),
|
|
26133
27431
|
workspaceIdentity: filesystemIdentity(canonicalWorkspacePath),
|
|
26134
27432
|
repositoryIdentity: `${filesystemIdentity(
|
|
26135
27433
|
resolveGitPath(canonicalWorkspacePath, commonDirectory)
|
|
@@ -26143,7 +27441,7 @@ var DEFAULT_PROBE2 = {
|
|
|
26143
27441
|
},
|
|
26144
27442
|
pathExists: (workspacePath) => {
|
|
26145
27443
|
try {
|
|
26146
|
-
return (0,
|
|
27444
|
+
return (0, import_node_fs13.existsSync)((0, import_node_path11.resolve)(workspacePath));
|
|
26147
27445
|
} catch {
|
|
26148
27446
|
return true;
|
|
26149
27447
|
}
|
|
@@ -26174,7 +27472,7 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
26174
27472
|
static capture(targets, probe = DEFAULT_PROBE2) {
|
|
26175
27473
|
const captured = [];
|
|
26176
27474
|
for (const target of targets) {
|
|
26177
|
-
const normalizedTarget = { ...target, workspacePath: (0,
|
|
27475
|
+
const normalizedTarget = { ...target, workspacePath: (0, import_node_path11.resolve)(target.workspacePath) };
|
|
26178
27476
|
const identity = probe.snapshot(normalizedTarget.workspacePath);
|
|
26179
27477
|
if (!identity) return { lease: null, failure: unavailableFailure(normalizedTarget) };
|
|
26180
27478
|
if (normalizedTarget.expectedBranch && identity.branch !== normalizedTarget.expectedBranch) {
|
|
@@ -26316,34 +27614,62 @@ var PRODUCTION_API_URL = "https://api.tryalan.ai";
|
|
|
26316
27614
|
var PRODUCTION_WS_URL = "wss://ws.tryalan.ai";
|
|
26317
27615
|
var LOCAL_API_URL = "http://localhost:8400";
|
|
26318
27616
|
var LOCAL_WS_URL = "ws://localhost:8401";
|
|
26319
|
-
function
|
|
27617
|
+
function requiredString2(params, key) {
|
|
26320
27618
|
const value2 = params[key];
|
|
26321
27619
|
if (typeof value2 !== "string" || value2.trim() === "") {
|
|
26322
27620
|
throw new Error(`${key} is required`);
|
|
26323
27621
|
}
|
|
26324
27622
|
return value2;
|
|
26325
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
|
+
}
|
|
26326
27652
|
function assertValidGitRef(ref, label) {
|
|
26327
27653
|
if (!ref || ref.startsWith("-") || ref.includes("\0")) {
|
|
26328
27654
|
throw new Error(`${label} is not a safe git ref`);
|
|
26329
27655
|
}
|
|
26330
|
-
const result = (0,
|
|
27656
|
+
const result = (0, import_node_child_process7.spawnSync)("git", ["check-ref-format", "--branch", ref], {
|
|
26331
27657
|
env: getDaemonCliEnvironment(),
|
|
26332
27658
|
encoding: "utf8"
|
|
26333
27659
|
});
|
|
26334
27660
|
if (result.status !== 0) throw new Error(`${label} is not a valid git ref`);
|
|
26335
27661
|
}
|
|
26336
|
-
function
|
|
26337
|
-
const rel = (0,
|
|
26338
|
-
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}`);
|
|
26339
27665
|
}
|
|
26340
27666
|
function assertPathContained(childPath, parentPath, label) {
|
|
26341
|
-
if (!
|
|
27667
|
+
if (!isPathContained2(childPath, parentPath)) {
|
|
26342
27668
|
throw new Error(`${label} escapes allowed directory`);
|
|
26343
27669
|
}
|
|
26344
27670
|
}
|
|
26345
27671
|
function git(cwd, args) {
|
|
26346
|
-
const result = (0,
|
|
27672
|
+
const result = (0, import_node_child_process7.spawnSync)("git", args, {
|
|
26347
27673
|
cwd,
|
|
26348
27674
|
env: getDaemonCliEnvironment(),
|
|
26349
27675
|
encoding: "utf8",
|
|
@@ -26362,14 +27688,23 @@ function gitSafe(cwd, args) {
|
|
|
26362
27688
|
}
|
|
26363
27689
|
}
|
|
26364
27690
|
function worktreeDir(projectPath) {
|
|
26365
|
-
const resolved = (0,
|
|
26366
|
-
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
|
+
}
|
|
26367
27702
|
}
|
|
26368
27703
|
function pathSegment(value2) {
|
|
26369
27704
|
return value2.toLowerCase().replace(/^alan\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "task";
|
|
26370
27705
|
}
|
|
26371
27706
|
function repoName(repoUrl, localPath) {
|
|
26372
|
-
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0,
|
|
27707
|
+
return repoUrl.replace(/\.git$/, "").split("/").pop() || (0, import_node_path12.basename)(localPath) || "repo";
|
|
26373
27708
|
}
|
|
26374
27709
|
function findWorktreeByBranch(repoPath, branchName) {
|
|
26375
27710
|
const output = gitSafe(repoPath, ["worktree", "list", "--porcelain"]);
|
|
@@ -26385,21 +27720,21 @@ function findWorktreeByBranch(repoPath, branchName) {
|
|
|
26385
27720
|
function ensureGitWorktree(input) {
|
|
26386
27721
|
assertValidGitRef(input.branchName, "branchName");
|
|
26387
27722
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26388
|
-
const repoPath = (0,
|
|
27723
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26389
27724
|
const root = input.allowedRoot ?? worktreeDir(repoPath);
|
|
26390
27725
|
const existingPath = findWorktreeByBranch(input.repoPath, input.branchName);
|
|
26391
27726
|
if (existingPath) {
|
|
26392
|
-
if ((0,
|
|
27727
|
+
if ((0, import_node_path12.resolve)(existingPath) === repoPath) {
|
|
26393
27728
|
throw new Error(`${input.branchName} is checked out in the shared checkout`);
|
|
26394
27729
|
}
|
|
26395
27730
|
return existingPath;
|
|
26396
27731
|
}
|
|
26397
27732
|
assertPathContained(input.worktreePath, root, "worktreePath");
|
|
26398
|
-
(0,
|
|
27733
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(input.worktreePath), { recursive: true });
|
|
26399
27734
|
if (!gitSafe(input.repoPath, ["branch", "--list", input.branchName])) {
|
|
26400
27735
|
git(input.repoPath, ["branch", input.branchName, input.baseBranch]);
|
|
26401
27736
|
}
|
|
26402
|
-
if ((0,
|
|
27737
|
+
if ((0, import_node_fs14.existsSync)(input.worktreePath)) {
|
|
26403
27738
|
if (gitSafe(input.worktreePath, ["branch", "--show-current"]) === input.branchName) {
|
|
26404
27739
|
return input.worktreePath;
|
|
26405
27740
|
}
|
|
@@ -26409,40 +27744,172 @@ function ensureGitWorktree(input) {
|
|
|
26409
27744
|
return input.worktreePath;
|
|
26410
27745
|
}
|
|
26411
27746
|
function resolveProjectWorktreePaths(projectPath, gitRoot, gitWorktreePath) {
|
|
26412
|
-
const resolvedProjectPath = (0,
|
|
26413
|
-
const resolvedGitRoot = (0,
|
|
26414
|
-
const projectRelativePath = (0,
|
|
26415
|
-
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)) {
|
|
26416
27751
|
throw new Error("The selected Project folder is outside its Git repository");
|
|
26417
27752
|
}
|
|
26418
27753
|
return {
|
|
26419
27754
|
repoPath: resolvedGitRoot,
|
|
26420
|
-
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
|
|
26421
27838
|
};
|
|
26422
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
|
+
}
|
|
26423
27883
|
function ensureWorktree(params) {
|
|
26424
27884
|
const input = {
|
|
26425
|
-
projectPath:
|
|
26426
|
-
taskId:
|
|
26427
|
-
branchName:
|
|
27885
|
+
projectPath: requiredString2(params, "projectPath"),
|
|
27886
|
+
taskId: requiredString2(params, "taskId"),
|
|
27887
|
+
branchName: requiredString2(params, "branchName"),
|
|
26428
27888
|
baseBranch: typeof params.baseBranch === "string" ? params.baseBranch : void 0,
|
|
26429
|
-
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : []
|
|
27889
|
+
repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : [],
|
|
27890
|
+
dirtyResolution: parseDirtyWorkspaceResolution(params.dirtyResolution)
|
|
26430
27891
|
};
|
|
27892
|
+
if ((input.repoLocalPaths?.length ?? 0) > 1) {
|
|
27893
|
+
throw new Error("A task worktree requires exactly one Git repository.");
|
|
27894
|
+
}
|
|
26431
27895
|
if (input.repoLocalPaths?.length) {
|
|
26432
|
-
const taskRoot2 = (0,
|
|
27896
|
+
const taskRoot2 = (0, import_node_path12.join)(
|
|
26433
27897
|
worktreeDir(input.projectPath),
|
|
26434
27898
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26435
27899
|
);
|
|
26436
27900
|
return {
|
|
26437
27901
|
worktrees: input.repoLocalPaths.map((repo) => {
|
|
26438
27902
|
const baseBranch2 = repo.branch || input.baseBranch || "main";
|
|
26439
|
-
const worktreePath2 =
|
|
26440
|
-
|
|
26441
|
-
|
|
26442
|
-
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
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
|
+
);
|
|
26446
27913
|
return {
|
|
26447
27914
|
repoId: repo.repoId ?? null,
|
|
26448
27915
|
repoUrl: repo.repoUrl,
|
|
@@ -26450,26 +27917,30 @@ function ensureWorktree(params) {
|
|
|
26450
27917
|
branchName: input.branchName,
|
|
26451
27918
|
baseBranch: baseBranch2
|
|
26452
27919
|
};
|
|
26453
|
-
})
|
|
27920
|
+
}),
|
|
27921
|
+
...input.dirtyResolution ? { dirtyResolution: input.dirtyResolution } : {}
|
|
26454
27922
|
};
|
|
26455
27923
|
}
|
|
26456
|
-
const gitRoot = gitSafe((0,
|
|
27924
|
+
const gitRoot = gitSafe((0, import_node_path12.resolve)(input.projectPath), ["rev-parse", "--show-toplevel"]);
|
|
26457
27925
|
if (!gitRoot) {
|
|
26458
27926
|
throw new Error("The selected Project folder is not inside a Git repository");
|
|
26459
27927
|
}
|
|
26460
|
-
const repoPath = (0,
|
|
27928
|
+
const repoPath = (0, import_node_path12.resolve)(gitRoot);
|
|
26461
27929
|
const baseBranch = input.baseBranch || gitSafe(repoPath, ["branch", "--show-current"]) || "HEAD";
|
|
26462
|
-
const taskRoot = (0,
|
|
27930
|
+
const taskRoot = (0, import_node_path12.join)(
|
|
26463
27931
|
worktreeDir(repoPath),
|
|
26464
27932
|
`${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
|
|
26465
27933
|
);
|
|
26466
27934
|
resolveProjectWorktreePaths(input.projectPath, repoPath, taskRoot);
|
|
26467
|
-
const worktreePath =
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
|
|
26471
|
-
|
|
26472
|
-
|
|
27935
|
+
const worktreePath = ensureGitWorktreeWithDirtyResolution(
|
|
27936
|
+
{
|
|
27937
|
+
repoPath,
|
|
27938
|
+
worktreePath: taskRoot,
|
|
27939
|
+
branchName: input.branchName,
|
|
27940
|
+
baseBranch
|
|
27941
|
+
},
|
|
27942
|
+
input.dirtyResolution
|
|
27943
|
+
);
|
|
26473
27944
|
const projectWorktreePath = resolveProjectWorktreePaths(
|
|
26474
27945
|
input.projectPath,
|
|
26475
27946
|
repoPath,
|
|
@@ -26482,13 +27953,286 @@ function ensureWorktree(params) {
|
|
|
26482
27953
|
branchName: input.branchName,
|
|
26483
27954
|
baseBranch
|
|
26484
27955
|
}
|
|
26485
|
-
]
|
|
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 } : {}
|
|
26486
28230
|
};
|
|
26487
28231
|
}
|
|
26488
28232
|
function ensureSharedCheckoutBranch(input) {
|
|
26489
28233
|
assertValidGitRef(input.branchName, "branchName");
|
|
26490
28234
|
assertValidGitRef(input.baseBranch, "baseBranch");
|
|
26491
|
-
const repoPath = (0,
|
|
28235
|
+
const repoPath = (0, import_node_path12.resolve)(input.repoPath);
|
|
26492
28236
|
if (gitSafe(repoPath, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
|
|
26493
28237
|
return { checkedOut: false, reason: `${repoPath} is not a git work tree` };
|
|
26494
28238
|
}
|
|
@@ -26533,7 +28277,7 @@ function resolveSharedCheckoutTargets(input) {
|
|
|
26533
28277
|
}
|
|
26534
28278
|
function resolveRuntimeCwd(payload) {
|
|
26535
28279
|
const multiRepoWorktree = payload.worktreeInfo?.allWorktrees?.[0]?.worktreePath;
|
|
26536
|
-
if (multiRepoWorktree) return (0,
|
|
28280
|
+
if (multiRepoWorktree) return (0, import_node_path12.dirname)(multiRepoWorktree);
|
|
26537
28281
|
if (isAlanDefaultWorkspace(payload.projectPath)) return resolveAlanDefaultWorkspacePath();
|
|
26538
28282
|
return payload.worktreeInfo?.worktreePath ?? payload.projectPath ?? process.cwd();
|
|
26539
28283
|
}
|
|
@@ -26646,14 +28390,14 @@ function localServiceOwnerPayload(owner) {
|
|
|
26646
28390
|
].join("\0");
|
|
26647
28391
|
}
|
|
26648
28392
|
function signLocalServiceOwner(secret, owner) {
|
|
26649
|
-
return (0,
|
|
28393
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(localServiceOwnerPayload(owner)).digest("hex");
|
|
26650
28394
|
}
|
|
26651
28395
|
function signLocalServiceAdoption(secret, serviceId, challenge) {
|
|
26652
|
-
return (0,
|
|
28396
|
+
return (0, import_node_crypto5.createHmac)("sha256", secret).update(["adopt", serviceId, challenge].join("\0")).digest("hex");
|
|
26653
28397
|
}
|
|
26654
28398
|
function equalHexDigest(actual, expected) {
|
|
26655
28399
|
if (!/^[a-f0-9]{64}$/i.test(actual) || !/^[a-f0-9]{64}$/i.test(expected)) return false;
|
|
26656
|
-
return (0,
|
|
28400
|
+
return (0, import_node_crypto5.timingSafeEqual)(Buffer.from(actual, "hex"), Buffer.from(expected, "hex"));
|
|
26657
28401
|
}
|
|
26658
28402
|
function parseLocalServiceOwner(value2) {
|
|
26659
28403
|
if (!value2 || typeof value2 !== "object") return null;
|
|
@@ -26755,12 +28499,12 @@ function getConfigPath() {
|
|
|
26755
28499
|
return process.env.ALAN_AGENT_CONFIG_PATH ?? boundAgentConfigPath ?? resolveAgentConfigPath({ profile: "production" });
|
|
26756
28500
|
}
|
|
26757
28501
|
function getEndpointDefaultsPath() {
|
|
26758
|
-
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");
|
|
26759
28503
|
}
|
|
26760
28504
|
function readConfig() {
|
|
26761
28505
|
const configPath = getConfigPath();
|
|
26762
|
-
if (!(0,
|
|
26763
|
-
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"));
|
|
26764
28508
|
const sanitized = sanitizeAgentConfig(parsed);
|
|
26765
28509
|
if (Object.keys(parsed).some((key) => !Object.hasOwn(sanitized, key))) {
|
|
26766
28510
|
writeConfig(sanitized);
|
|
@@ -26776,27 +28520,27 @@ function readConfigForRegistration() {
|
|
|
26776
28520
|
}
|
|
26777
28521
|
function readEndpointDefaults() {
|
|
26778
28522
|
const endpointsPath = getEndpointDefaultsPath();
|
|
26779
|
-
if (!(0,
|
|
28523
|
+
if (!(0, import_node_fs14.existsSync)(endpointsPath)) return {};
|
|
26780
28524
|
try {
|
|
26781
|
-
return JSON.parse((0,
|
|
28525
|
+
return JSON.parse((0, import_node_fs14.readFileSync)(endpointsPath, "utf8"));
|
|
26782
28526
|
} catch {
|
|
26783
28527
|
return {};
|
|
26784
28528
|
}
|
|
26785
28529
|
}
|
|
26786
28530
|
function writeConfig(config) {
|
|
26787
28531
|
const configPath = getConfigPath();
|
|
26788
|
-
(0,
|
|
26789
|
-
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")}`;
|
|
26790
28534
|
try {
|
|
26791
|
-
(0,
|
|
28535
|
+
(0, import_node_fs14.writeFileSync)(
|
|
26792
28536
|
pendingPath,
|
|
26793
28537
|
JSON.stringify(sanitizeAgentConfig(config), null, 2),
|
|
26794
28538
|
{ mode: 384 }
|
|
26795
28539
|
);
|
|
26796
|
-
(0,
|
|
26797
|
-
(0,
|
|
28540
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28541
|
+
(0, import_node_fs14.renameSync)(pendingPath, configPath);
|
|
26798
28542
|
} finally {
|
|
26799
|
-
(0,
|
|
28543
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26800
28544
|
}
|
|
26801
28545
|
}
|
|
26802
28546
|
function maintenanceLeasePath(configPath) {
|
|
@@ -26804,9 +28548,9 @@ function maintenanceLeasePath(configPath) {
|
|
|
26804
28548
|
}
|
|
26805
28549
|
function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
26806
28550
|
const path = maintenanceLeasePath(configPath);
|
|
26807
|
-
if (!(0,
|
|
28551
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26808
28552
|
try {
|
|
26809
|
-
const lease = JSON.parse((0,
|
|
28553
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26810
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))) {
|
|
26811
28555
|
throw new Error("invalid maintenance lease");
|
|
26812
28556
|
}
|
|
@@ -26823,8 +28567,8 @@ function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
|
|
|
26823
28567
|
expiresAtMs: nowMs + 5e3
|
|
26824
28568
|
};
|
|
26825
28569
|
try {
|
|
26826
|
-
(0,
|
|
26827
|
-
(0,
|
|
28570
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(fallback), { mode: 384 });
|
|
28571
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26828
28572
|
} catch {
|
|
26829
28573
|
}
|
|
26830
28574
|
return fallback;
|
|
@@ -26835,15 +28579,15 @@ function acquireLocalDaemonMaintenanceLease(configPath, reason, ttlMs = 5e3) {
|
|
|
26835
28579
|
if (existing) return { acquired: false, lease: existing };
|
|
26836
28580
|
const lease = {
|
|
26837
28581
|
version: 1,
|
|
26838
|
-
id: (0,
|
|
28582
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26839
28583
|
reason,
|
|
26840
28584
|
expiresAtMs: reason === "credential_cleanup" ? null : Date.now() + Math.max(1e3, Math.min(ttlMs, 6e4))
|
|
26841
28585
|
};
|
|
26842
28586
|
const path = maintenanceLeasePath(configPath);
|
|
26843
|
-
(0,
|
|
28587
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26844
28588
|
try {
|
|
26845
|
-
(0,
|
|
26846
|
-
(0,
|
|
28589
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28590
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26847
28591
|
return { acquired: true, lease };
|
|
26848
28592
|
} catch (error) {
|
|
26849
28593
|
const raced = readLocalDaemonMaintenanceLease(configPath);
|
|
@@ -26859,9 +28603,9 @@ function daemonOwnerLeasePath(configPath) {
|
|
|
26859
28603
|
}
|
|
26860
28604
|
function readLocalDaemonOwnerLease(configPath) {
|
|
26861
28605
|
const path = daemonOwnerLeasePath(configPath);
|
|
26862
|
-
if (!(0,
|
|
28606
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
26863
28607
|
try {
|
|
26864
|
-
const lease = JSON.parse((0,
|
|
28608
|
+
const lease = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
26865
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) {
|
|
26866
28610
|
throw new Error("invalid daemon owner lease");
|
|
26867
28611
|
}
|
|
@@ -26903,7 +28647,7 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26903
28647
|
}
|
|
26904
28648
|
const lease = {
|
|
26905
28649
|
version: 1,
|
|
26906
|
-
id: (0,
|
|
28650
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
26907
28651
|
daemonSessionId: input.daemonSessionId,
|
|
26908
28652
|
pid: process.pid,
|
|
26909
28653
|
startedAtMs: Date.now(),
|
|
@@ -26913,10 +28657,10 @@ async function acquireLocalDaemonOwnerLease(input) {
|
|
|
26913
28657
|
localApiToken: input.localApiToken
|
|
26914
28658
|
};
|
|
26915
28659
|
const path = daemonOwnerLeasePath(input.configPath);
|
|
26916
|
-
(0,
|
|
28660
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
26917
28661
|
try {
|
|
26918
|
-
(0,
|
|
26919
|
-
(0,
|
|
28662
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28663
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26920
28664
|
return { acquired: true, lease };
|
|
26921
28665
|
} catch (error) {
|
|
26922
28666
|
const raced = readLocalDaemonOwnerLease(input.configPath);
|
|
@@ -26931,27 +28675,27 @@ function updateLocalDaemonOwnerEndpoint(configPath, lease, localApiPort) {
|
|
|
26931
28675
|
const path = daemonOwnerLeasePath(configPath);
|
|
26932
28676
|
const pendingPath = `${path}.pending-${lease.id}`;
|
|
26933
28677
|
try {
|
|
26934
|
-
(0,
|
|
26935
|
-
(0,
|
|
26936
|
-
(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);
|
|
26937
28681
|
return updated;
|
|
26938
28682
|
} finally {
|
|
26939
|
-
(0,
|
|
28683
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26940
28684
|
}
|
|
26941
28685
|
}
|
|
26942
28686
|
function releaseLocalDaemonOwnerLease(configPath, leaseId) {
|
|
26943
28687
|
releaseOwnedJsonFile(daemonOwnerLeasePath(configPath), leaseId);
|
|
26944
28688
|
}
|
|
26945
28689
|
function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
26946
|
-
const releasePath = `${path}.release-${process.pid}-${(0,
|
|
28690
|
+
const releasePath = `${path}.release-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
26947
28691
|
try {
|
|
26948
|
-
(0,
|
|
28692
|
+
(0, import_node_fs14.renameSync)(path, releasePath);
|
|
26949
28693
|
} catch (error) {
|
|
26950
28694
|
if (error.code === "ENOENT") return;
|
|
26951
28695
|
throw error;
|
|
26952
28696
|
}
|
|
26953
28697
|
try {
|
|
26954
|
-
const content = (0,
|
|
28698
|
+
const content = (0, import_node_fs14.readFileSync)(releasePath, "utf8");
|
|
26955
28699
|
let capturedOwnerId = null;
|
|
26956
28700
|
try {
|
|
26957
28701
|
const captured = JSON.parse(content);
|
|
@@ -26960,13 +28704,13 @@ function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
|
|
|
26960
28704
|
}
|
|
26961
28705
|
if (capturedOwnerId === ownerId) return;
|
|
26962
28706
|
try {
|
|
26963
|
-
(0,
|
|
26964
|
-
(0,
|
|
28707
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
28708
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
26965
28709
|
} catch (error) {
|
|
26966
28710
|
if (error.code !== "EEXIST") throw error;
|
|
26967
28711
|
}
|
|
26968
28712
|
} finally {
|
|
26969
|
-
(0,
|
|
28713
|
+
(0, import_node_fs14.rmSync)(releasePath, { force: true });
|
|
26970
28714
|
}
|
|
26971
28715
|
}
|
|
26972
28716
|
function inspectLocalDaemonRunJournal(configPath) {
|
|
@@ -26976,8 +28720,8 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
26976
28720
|
try {
|
|
26977
28721
|
let activeRunCount = 0;
|
|
26978
28722
|
for (const artifact of [path, pendingPath]) {
|
|
26979
|
-
if (!(0,
|
|
26980
|
-
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"));
|
|
26981
28725
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries))
|
|
26982
28726
|
throw new Error("invalid journal");
|
|
26983
28727
|
if (artifact === path) {
|
|
@@ -26996,7 +28740,7 @@ function inspectLocalDaemonRunJournal(configPath) {
|
|
|
26996
28740
|
}).length;
|
|
26997
28741
|
continue;
|
|
26998
28742
|
}
|
|
26999
|
-
const legacyStartedAtMs = (0,
|
|
28743
|
+
const legacyStartedAtMs = (0, import_node_fs14.statSync)(pendingPath).mtimeMs;
|
|
27000
28744
|
activeRunCount += parsed.entries.filter((entry) => {
|
|
27001
28745
|
if (!entry || typeof entry !== "object") throw new Error("invalid pending run entry");
|
|
27002
28746
|
const startedAtMs = entry.startedAtMs;
|
|
@@ -27018,38 +28762,38 @@ function pendingSetupIntentPath() {
|
|
|
27018
28762
|
return `${getConfigPath()}.setup-pending`;
|
|
27019
28763
|
}
|
|
27020
28764
|
function setupTokenHash(setupToken) {
|
|
27021
|
-
return (0,
|
|
28765
|
+
return (0, import_node_crypto5.createHash)("sha256").update(setupToken).digest("hex");
|
|
27022
28766
|
}
|
|
27023
28767
|
function getOrCreateSetupAttemptId(setupToken) {
|
|
27024
28768
|
const intentPath = pendingSetupIntentPath();
|
|
27025
28769
|
const tokenHash = setupTokenHash(setupToken);
|
|
27026
|
-
if ((0,
|
|
28770
|
+
if ((0, import_node_fs14.existsSync)(intentPath)) {
|
|
27027
28771
|
try {
|
|
27028
|
-
const stored = JSON.parse((0,
|
|
28772
|
+
const stored = JSON.parse((0, import_node_fs14.readFileSync)(intentPath, "utf8"));
|
|
27029
28773
|
if (stored.setupTokenHash === tokenHash && typeof stored.registrationAttemptId === "string" && stored.registrationAttemptId.length > 0) {
|
|
27030
28774
|
return stored.registrationAttemptId;
|
|
27031
28775
|
}
|
|
27032
28776
|
} catch {
|
|
27033
28777
|
}
|
|
27034
28778
|
}
|
|
27035
|
-
const registrationAttemptId = (0,
|
|
27036
|
-
(0,
|
|
27037
|
-
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")}`;
|
|
27038
28782
|
try {
|
|
27039
|
-
(0,
|
|
28783
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27040
28784
|
writePath,
|
|
27041
28785
|
JSON.stringify({ setupTokenHash: tokenHash, registrationAttemptId }, null, 2),
|
|
27042
28786
|
{ mode: 384 }
|
|
27043
28787
|
);
|
|
27044
|
-
(0,
|
|
27045
|
-
(0,
|
|
28788
|
+
(0, import_node_fs14.chmodSync)(writePath, 384);
|
|
28789
|
+
(0, import_node_fs14.renameSync)(writePath, intentPath);
|
|
27046
28790
|
} finally {
|
|
27047
|
-
(0,
|
|
28791
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27048
28792
|
}
|
|
27049
28793
|
return registrationAttemptId;
|
|
27050
28794
|
}
|
|
27051
28795
|
function clearPendingSetupIntent() {
|
|
27052
|
-
(0,
|
|
28796
|
+
(0, import_node_fs14.rmSync)(pendingSetupIntentPath(), { force: true });
|
|
27053
28797
|
}
|
|
27054
28798
|
function pendingDeviceAuthorizationPath() {
|
|
27055
28799
|
return `${getConfigPath()}.device-auth-pending`;
|
|
@@ -27070,35 +28814,35 @@ function parseDeviceAuthorizationOperationContent(content) {
|
|
|
27070
28814
|
}
|
|
27071
28815
|
function restoreCapturedFileUnlessReplaced(path, content) {
|
|
27072
28816
|
try {
|
|
27073
|
-
(0,
|
|
27074
|
-
(0,
|
|
28817
|
+
(0, import_node_fs14.writeFileSync)(path, content, { mode: 384, flag: "wx" });
|
|
28818
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27075
28819
|
} catch (error) {
|
|
27076
28820
|
if (error.code !== "EEXIST") throw error;
|
|
27077
28821
|
}
|
|
27078
28822
|
}
|
|
27079
28823
|
function quarantineCorruptDeviceAuthorizationOperation(path) {
|
|
27080
|
-
const quarantinePath = `${path}.corrupt-${process.pid}-${(0,
|
|
28824
|
+
const quarantinePath = `${path}.corrupt-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27081
28825
|
try {
|
|
27082
|
-
(0,
|
|
28826
|
+
(0, import_node_fs14.renameSync)(path, quarantinePath);
|
|
27083
28827
|
} catch (error) {
|
|
27084
28828
|
if (error.code === "ENOENT") return null;
|
|
27085
28829
|
throw error;
|
|
27086
28830
|
}
|
|
27087
28831
|
try {
|
|
27088
|
-
const capturedContent = (0,
|
|
28832
|
+
const capturedContent = (0, import_node_fs14.readFileSync)(quarantinePath, "utf8");
|
|
27089
28833
|
if (parseDeviceAuthorizationOperationContent(capturedContent)) {
|
|
27090
28834
|
restoreCapturedFileUnlessReplaced(path, capturedContent);
|
|
27091
28835
|
}
|
|
27092
28836
|
} finally {
|
|
27093
|
-
(0,
|
|
28837
|
+
(0, import_node_fs14.rmSync)(quarantinePath, { force: true });
|
|
27094
28838
|
}
|
|
27095
|
-
if (!(0,
|
|
27096
|
-
return parseDeviceAuthorizationOperationContent((0,
|
|
28839
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
28840
|
+
return parseDeviceAuthorizationOperationContent((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27097
28841
|
}
|
|
27098
28842
|
function readDeviceAuthorizationOperation() {
|
|
27099
28843
|
const path = deviceAuthorizationOperationPath();
|
|
27100
|
-
if (!(0,
|
|
27101
|
-
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"));
|
|
27102
28846
|
const lease = parsed ?? quarantineCorruptDeviceAuthorizationOperation(path);
|
|
27103
28847
|
if (!lease) return null;
|
|
27104
28848
|
if (lease.expiresAtMs <= Date.now()) {
|
|
@@ -27112,14 +28856,14 @@ function acquireDeviceAuthorizationOperation() {
|
|
|
27112
28856
|
if (existing) return null;
|
|
27113
28857
|
const lease = {
|
|
27114
28858
|
version: 1,
|
|
27115
|
-
id: (0,
|
|
28859
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27116
28860
|
expiresAtMs: Date.now() + 6e4
|
|
27117
28861
|
};
|
|
27118
28862
|
const path = deviceAuthorizationOperationPath();
|
|
27119
|
-
(0,
|
|
28863
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
|
|
27120
28864
|
try {
|
|
27121
|
-
(0,
|
|
27122
|
-
(0,
|
|
28865
|
+
(0, import_node_fs14.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
|
|
28866
|
+
(0, import_node_fs14.chmodSync)(path, 384);
|
|
27123
28867
|
return lease;
|
|
27124
28868
|
} catch (error) {
|
|
27125
28869
|
if (error.code === "EEXIST") return null;
|
|
@@ -27132,17 +28876,17 @@ function extendDeviceAuthorizationOperation(operationId, expiresAtMs) {
|
|
|
27132
28876
|
throw new Error("browser authorization operation ownership was lost");
|
|
27133
28877
|
}
|
|
27134
28878
|
const path = deviceAuthorizationOperationPath();
|
|
27135
|
-
const pendingPath = `${path}.pending-${process.pid}-${(0,
|
|
28879
|
+
const pendingPath = `${path}.pending-${process.pid}-${(0, import_node_crypto5.randomBytes)(6).toString("hex")}`;
|
|
27136
28880
|
try {
|
|
27137
|
-
(0,
|
|
28881
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27138
28882
|
pendingPath,
|
|
27139
28883
|
JSON.stringify({ ...current, expiresAtMs: Math.max(expiresAtMs, Date.now() + 6e4) }),
|
|
27140
28884
|
{ mode: 384 }
|
|
27141
28885
|
);
|
|
27142
|
-
(0,
|
|
27143
|
-
(0,
|
|
28886
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
28887
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
27144
28888
|
} finally {
|
|
27145
|
-
(0,
|
|
28889
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27146
28890
|
}
|
|
27147
28891
|
}
|
|
27148
28892
|
function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
@@ -27153,12 +28897,12 @@ function assertDeviceAuthorizationOperationOwner(operationId) {
|
|
|
27153
28897
|
function clearPendingDeviceAuthorization(operationId) {
|
|
27154
28898
|
const path = pendingDeviceAuthorizationPath();
|
|
27155
28899
|
if (!operationId) {
|
|
27156
|
-
(0,
|
|
28900
|
+
(0, import_node_fs14.rmSync)(path, { force: true });
|
|
27157
28901
|
return;
|
|
27158
28902
|
}
|
|
27159
|
-
if (!(0,
|
|
28903
|
+
if (!(0, import_node_fs14.existsSync)(path)) return;
|
|
27160
28904
|
try {
|
|
27161
|
-
const pending = JSON.parse((0,
|
|
28905
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27162
28906
|
if (pending.operationId !== operationId) return;
|
|
27163
28907
|
} catch {
|
|
27164
28908
|
return;
|
|
@@ -27167,9 +28911,9 @@ function clearPendingDeviceAuthorization(operationId) {
|
|
|
27167
28911
|
}
|
|
27168
28912
|
function readPendingDeviceAuthorization(apiUrl) {
|
|
27169
28913
|
const path = pendingDeviceAuthorizationPath();
|
|
27170
|
-
if (!(0,
|
|
28914
|
+
if (!(0, import_node_fs14.existsSync)(path)) return null;
|
|
27171
28915
|
try {
|
|
27172
|
-
const pending = JSON.parse((0,
|
|
28916
|
+
const pending = JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
|
|
27173
28917
|
const expiresAtMs = Date.parse(pending.expiresAt ?? "");
|
|
27174
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()) {
|
|
27175
28919
|
return null;
|
|
@@ -27181,20 +28925,20 @@ function readPendingDeviceAuthorization(apiUrl) {
|
|
|
27181
28925
|
}
|
|
27182
28926
|
function writePendingDeviceAuthorization(pending) {
|
|
27183
28927
|
const path = pendingDeviceAuthorizationPath();
|
|
27184
|
-
(0,
|
|
27185
|
-
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")}`;
|
|
27186
28930
|
try {
|
|
27187
|
-
(0,
|
|
27188
|
-
(0,
|
|
27189
|
-
(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);
|
|
27190
28934
|
} finally {
|
|
27191
|
-
(0,
|
|
28935
|
+
(0, import_node_fs14.rmSync)(writePath, { force: true });
|
|
27192
28936
|
}
|
|
27193
28937
|
}
|
|
27194
28938
|
function removeAlanOwnedLocalArtifacts() {
|
|
27195
28939
|
const configPath = getConfigPath();
|
|
27196
|
-
const directory = (0,
|
|
27197
|
-
const configName = (0,
|
|
28940
|
+
const directory = (0, import_node_path12.dirname)(configPath);
|
|
28941
|
+
const configName = (0, import_node_path12.basename)(configPath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27198
28942
|
const alanAtomicSidecarPattern = new RegExp(
|
|
27199
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}$`
|
|
27200
28944
|
);
|
|
@@ -27211,13 +28955,13 @@ function removeAlanOwnedLocalArtifacts() {
|
|
|
27211
28955
|
`${configPath}.pending-runs.json.pending`,
|
|
27212
28956
|
maintenanceLeasePath(configPath)
|
|
27213
28957
|
]);
|
|
27214
|
-
if ((0,
|
|
27215
|
-
for (const entry of (0,
|
|
27216
|
-
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));
|
|
27217
28961
|
}
|
|
27218
28962
|
}
|
|
27219
|
-
for (const artifact of artifacts) (0,
|
|
27220
|
-
(0,
|
|
28963
|
+
for (const artifact of artifacts) (0, import_node_fs14.rmSync)(artifact, { force: true });
|
|
28964
|
+
(0, import_node_fs14.rmSync)(configPath, { force: true });
|
|
27221
28965
|
}
|
|
27222
28966
|
function argValue(args, name) {
|
|
27223
28967
|
const index = args.indexOf(name);
|
|
@@ -27273,7 +29017,7 @@ function getLocalApiPort(args, stored) {
|
|
|
27273
29017
|
}
|
|
27274
29018
|
function sleep(ms) {
|
|
27275
29019
|
if (ms <= 0) return Promise.resolve();
|
|
27276
|
-
return new Promise((
|
|
29020
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
27277
29021
|
}
|
|
27278
29022
|
function normalizeBackendKind2(backendKind) {
|
|
27279
29023
|
if (backendKind === "codex") return "codex_app_server";
|
|
@@ -27439,13 +29183,13 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27439
29183
|
}
|
|
27440
29184
|
persist() {
|
|
27441
29185
|
if (this.size === 0) {
|
|
27442
|
-
(0,
|
|
29186
|
+
(0, import_node_fs14.rmSync)(this.path, { force: true });
|
|
27443
29187
|
return;
|
|
27444
29188
|
}
|
|
27445
29189
|
const pendingPath = `${this.path}.pending`;
|
|
27446
|
-
(0,
|
|
29190
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(this.path), { recursive: true, mode: 448 });
|
|
27447
29191
|
try {
|
|
27448
|
-
(0,
|
|
29192
|
+
(0, import_node_fs14.writeFileSync)(
|
|
27449
29193
|
pendingPath,
|
|
27450
29194
|
JSON.stringify({
|
|
27451
29195
|
version: 1,
|
|
@@ -27458,10 +29202,10 @@ var DurablePendingRunStarts = class extends Map {
|
|
|
27458
29202
|
}),
|
|
27459
29203
|
{ mode: 384 }
|
|
27460
29204
|
);
|
|
27461
|
-
(0,
|
|
27462
|
-
(0,
|
|
29205
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
29206
|
+
(0, import_node_fs14.renameSync)(pendingPath, this.path);
|
|
27463
29207
|
} finally {
|
|
27464
|
-
(0,
|
|
29208
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
27465
29209
|
}
|
|
27466
29210
|
}
|
|
27467
29211
|
};
|
|
@@ -27601,7 +29345,7 @@ function buildApplicationRunFailure(input) {
|
|
|
27601
29345
|
const problem = createAlanProblem({
|
|
27602
29346
|
domain: "application",
|
|
27603
29347
|
code: input.code,
|
|
27604
|
-
id: (0,
|
|
29348
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27605
29349
|
title: "Alan could not complete the agent run",
|
|
27606
29350
|
detail: input.detail,
|
|
27607
29351
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27696,21 +29440,21 @@ function reconcileActiveRuns(input) {
|
|
|
27696
29440
|
}
|
|
27697
29441
|
}
|
|
27698
29442
|
function collectRuntimeMetadata() {
|
|
27699
|
-
const cpuList = (0,
|
|
29443
|
+
const cpuList = (0, import_node_os9.cpus)();
|
|
27700
29444
|
const metadata = {
|
|
27701
|
-
platform: (0,
|
|
27702
|
-
arch: (0,
|
|
27703
|
-
osVersion: (0,
|
|
29445
|
+
platform: (0, import_node_os9.platform)(),
|
|
29446
|
+
arch: (0, import_node_os9.arch)(),
|
|
29447
|
+
osVersion: (0, import_node_os9.release)(),
|
|
27704
29448
|
agentVersion: AGENT_VERSION,
|
|
27705
|
-
memoryBytes: (0,
|
|
27706
|
-
memoryFreeBytes: (0,
|
|
29449
|
+
memoryBytes: (0, import_node_os9.totalmem)(),
|
|
29450
|
+
memoryFreeBytes: (0, import_node_os9.freemem)()
|
|
27707
29451
|
};
|
|
27708
29452
|
if (cpuList.length > 0) {
|
|
27709
29453
|
metadata.cpuModel = cpuList[0]?.model;
|
|
27710
29454
|
metadata.cpuCores = cpuList.length;
|
|
27711
29455
|
}
|
|
27712
29456
|
try {
|
|
27713
|
-
const disk = (0,
|
|
29457
|
+
const disk = (0, import_node_fs14.statfsSync)((0, import_node_os9.homedir)());
|
|
27714
29458
|
metadata.diskFreeBytes = disk.bavail * disk.bsize;
|
|
27715
29459
|
metadata.diskTotalBytes = disk.blocks * disk.bsize;
|
|
27716
29460
|
} catch {
|
|
@@ -27928,7 +29672,7 @@ var RuntimePresenter = class {
|
|
|
27928
29672
|
this.applicationProblem = createAlanProblem({
|
|
27929
29673
|
domain: "application",
|
|
27930
29674
|
code: /local path no longer exists/i.test(message) ? "app.workspace.missing" : "app.internal",
|
|
27931
|
-
id: (0,
|
|
29675
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
27932
29676
|
title: "Alan could not start the agent run",
|
|
27933
29677
|
detail: message,
|
|
27934
29678
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27966,9 +29710,9 @@ async function setupDaemon(args) {
|
|
|
27966
29710
|
const teamId = argValue(args, "--team") ?? process.env.ALAN_TEAM_ID;
|
|
27967
29711
|
const setupToken = argValue(args, "--setup-token") ?? process.env.ALAN_RUNTIME_SETUP_TOKEN;
|
|
27968
29712
|
const token = argValue(args, "--token") ?? process.env.ALAN_SETUP_TOKEN;
|
|
27969
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
29713
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
27970
29714
|
const scope = argValue(args, "--scope") ?? process.env.ALAN_RUNTIME_SCOPE ?? (setupToken ? "team" : "user");
|
|
27971
|
-
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0,
|
|
29715
|
+
const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
27972
29716
|
if (scope !== "team" && scope !== "user") {
|
|
27973
29717
|
throw new Error("setup --scope must be either 'team' or 'user'");
|
|
27974
29718
|
}
|
|
@@ -27977,7 +29721,7 @@ async function setupDaemon(args) {
|
|
|
27977
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"
|
|
27978
29722
|
);
|
|
27979
29723
|
}
|
|
27980
|
-
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0,
|
|
29724
|
+
const registrationAttemptId = setupToken ? getOrCreateSetupAttemptId(setupToken) : (0, import_node_crypto5.randomUUID)();
|
|
27981
29725
|
const registrationUrl = `${apiUrl}/public/runtimes${setupToken ? "/register-with-setup-token" : ""}`;
|
|
27982
29726
|
const registrationInit = {
|
|
27983
29727
|
method: "POST",
|
|
@@ -27989,7 +29733,7 @@ async function setupDaemon(args) {
|
|
|
27989
29733
|
...teamId ? { teamId } : {},
|
|
27990
29734
|
...setupToken ? { setupToken, registrationAttemptId } : {},
|
|
27991
29735
|
displayName,
|
|
27992
|
-
hostname: (0,
|
|
29736
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
27993
29737
|
runtimeKind: "machine",
|
|
27994
29738
|
managementKind: "user_managed",
|
|
27995
29739
|
hostKind: "daemon",
|
|
@@ -28012,7 +29756,7 @@ async function setupDaemon(args) {
|
|
|
28012
29756
|
}
|
|
28013
29757
|
const retriableStatus = response && (response.status === 408 || response.status === 429 || response.status >= 500);
|
|
28014
29758
|
if (response && (!retriableStatus || response.ok) || attempt === 3) break;
|
|
28015
|
-
await new Promise((
|
|
29759
|
+
await new Promise((resolve7) => setTimeout(resolve7, attempt * 100));
|
|
28016
29760
|
}
|
|
28017
29761
|
if (!response) {
|
|
28018
29762
|
const detail = transportError instanceof Error ? transportError.message : String(transportError);
|
|
@@ -28043,10 +29787,10 @@ async function setupDaemon(args) {
|
|
|
28043
29787
|
runtimeToken: body.runtimeToken,
|
|
28044
29788
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28045
29789
|
localApiPort: getLocalApiPort(args, {}),
|
|
28046
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28047
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28048
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28049
|
-
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"),
|
|
28050
29794
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28051
29795
|
displayName: body.runtime.displayName ?? displayName,
|
|
28052
29796
|
installationId
|
|
@@ -28073,10 +29817,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28073
29817
|
}
|
|
28074
29818
|
const previousConfig = readConfigForRegistration();
|
|
28075
29819
|
const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
|
|
28076
|
-
const displayName = argValue(args, "--name") ?? (0,
|
|
29820
|
+
const displayName = argValue(args, "--name") ?? (0, import_node_os9.hostname)();
|
|
28077
29821
|
const maxPolls = Number.parseInt(argValue(args, "--max-polls") ?? "300", 10);
|
|
28078
29822
|
const resumed = readPendingDeviceAuthorization(apiUrl);
|
|
28079
|
-
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0,
|
|
29823
|
+
const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28080
29824
|
let authorization = resumed ? { ...resumed, operationId: operation.id } : null;
|
|
28081
29825
|
if (!authorization) {
|
|
28082
29826
|
clearPendingDeviceAuthorization();
|
|
@@ -28085,7 +29829,7 @@ async function loginWithDeviceCode(args) {
|
|
|
28085
29829
|
headers: { "content-type": "application/json" },
|
|
28086
29830
|
body: JSON.stringify({
|
|
28087
29831
|
displayName,
|
|
28088
|
-
hostname: (0,
|
|
29832
|
+
hostname: (0, import_node_os9.hostname)(),
|
|
28089
29833
|
capabilities: discoverCapabilities(),
|
|
28090
29834
|
metadata: {
|
|
28091
29835
|
...await collectRuntimeMetadataWithProviderLimits(),
|
|
@@ -28152,10 +29896,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28152
29896
|
runtimeToken: body.runtimeToken,
|
|
28153
29897
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
28154
29898
|
localApiPort: getLocalApiPort(args, {}),
|
|
28155
|
-
localApiToken: previousConfig.localApiToken ?? (0,
|
|
28156
|
-
localServiceId: previousConfig.localServiceId ?? (0,
|
|
28157
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0,
|
|
28158
|
-
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"),
|
|
28159
29903
|
daemonEpoch: previousConfig.daemonEpoch,
|
|
28160
29904
|
displayName: body.runtime.displayName ?? displayName,
|
|
28161
29905
|
installationId
|
|
@@ -28175,10 +29919,10 @@ async function loginWithDeviceCode(args) {
|
|
|
28175
29919
|
async function startDaemon(args) {
|
|
28176
29920
|
bindConfigPath(args);
|
|
28177
29921
|
const stored = readConfig();
|
|
28178
|
-
const installationId = stored.installationId ?? (0,
|
|
29922
|
+
const installationId = stored.installationId ?? (0, import_node_crypto5.randomUUID)();
|
|
28179
29923
|
const configPath = getConfigPath();
|
|
28180
29924
|
const endpointProfile = stored.endpointProfile ?? resolveEndpointProfile(args);
|
|
28181
|
-
const daemonSessionId = (0,
|
|
29925
|
+
const daemonSessionId = (0, import_node_crypto5.randomUUID)();
|
|
28182
29926
|
let heartbeatSeq = 0;
|
|
28183
29927
|
const runtimeId = argValue(args, "--runtime-id") ?? process.env.ALAN_RUNTIME_ID ?? stored.runtimeId;
|
|
28184
29928
|
const runtimeTokenOverride = argValue(args, "--token") ?? process.env.ALAN_RUNTIME_TOKEN;
|
|
@@ -28195,11 +29939,11 @@ async function startDaemon(args) {
|
|
|
28195
29939
|
wsUrl = endpointMismatch.expectedWsUrl;
|
|
28196
29940
|
}
|
|
28197
29941
|
let localApiPort = getLocalApiPort(args, stored);
|
|
28198
|
-
const localApiToken = stored.localApiToken ?? (0,
|
|
28199
|
-
const localServiceId = stored.localServiceId ?? (0,
|
|
28200
|
-
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");
|
|
28201
29945
|
const localServiceStartedAtMs = Date.now();
|
|
28202
|
-
const eventSpoolKey = stored.eventSpoolKey ?? (0,
|
|
29946
|
+
const eventSpoolKey = stored.eventSpoolKey ?? (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
28203
29947
|
if (stored.daemonEpoch !== void 0 && (!Number.isSafeInteger(stored.daemonEpoch) || stored.daemonEpoch < 0 || stored.daemonEpoch >= Number.MAX_SAFE_INTEGER)) {
|
|
28204
29948
|
throw new Error("daemon config contains an invalid durable epoch; repair the runtime config");
|
|
28205
29949
|
}
|
|
@@ -28319,7 +30063,7 @@ async function startDaemon(args) {
|
|
|
28319
30063
|
const workspaceRelocations = /* @__PURE__ */ new Map();
|
|
28320
30064
|
for (const relocation of stored.workspaceRelocations ?? []) {
|
|
28321
30065
|
if (relocation?.conversationId && relocation.runId && relocation.previousPath && relocation.canonicalPath) {
|
|
28322
|
-
workspaceRelocations.set((0,
|
|
30066
|
+
workspaceRelocations.set((0, import_node_path12.resolve)(relocation.previousPath), relocation);
|
|
28323
30067
|
}
|
|
28324
30068
|
}
|
|
28325
30069
|
if (stored.localApiPort !== localApiPort || stored.localApiToken !== localApiToken || stored.localServiceId !== localServiceId || stored.localServiceSecret !== localServiceSecret || stored.eventSpoolKey !== eventSpoolKey || stored.daemonEpoch !== daemonEpoch || stored.installationId !== installationId) {
|
|
@@ -28635,20 +30379,25 @@ async function startDaemon(args) {
|
|
|
28635
30379
|
pendingDeliveryLost.push({ conversationId: entry.conversationId, runId });
|
|
28636
30380
|
}
|
|
28637
30381
|
});
|
|
28638
|
-
socket.on("desktop:rpc", (message) => {
|
|
30382
|
+
socket.on("desktop:rpc", async (message) => {
|
|
28639
30383
|
if (!message?.id || !message.method) return;
|
|
28640
30384
|
try {
|
|
28641
30385
|
const params = message.params ?? {};
|
|
28642
|
-
|
|
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) : (() => {
|
|
28643
30387
|
throw new Error(`Method not found: ${message.method}`);
|
|
28644
|
-
}
|
|
28645
|
-
const result = ensureWorktree(params);
|
|
30388
|
+
})();
|
|
28646
30389
|
socket.emit("desktop:rpc", { jsonrpc: "2.0", id: message.id, result });
|
|
28647
30390
|
} catch (error) {
|
|
30391
|
+
const stableCode = error instanceof BindingSwitchGitError ? error.code : "runtime_operation_failed";
|
|
30392
|
+
const stableDetails = error instanceof BindingSwitchGitError ? error.details : void 0;
|
|
28648
30393
|
socket.emit("desktop:rpc", {
|
|
28649
30394
|
jsonrpc: "2.0",
|
|
28650
30395
|
id: message.id,
|
|
28651
|
-
error: {
|
|
30396
|
+
error: {
|
|
30397
|
+
code: -32e3,
|
|
30398
|
+
message: error.message,
|
|
30399
|
+
data: { ...stableDetails, code: stableCode }
|
|
30400
|
+
}
|
|
28652
30401
|
});
|
|
28653
30402
|
}
|
|
28654
30403
|
});
|
|
@@ -28656,6 +30405,17 @@ async function startDaemon(args) {
|
|
|
28656
30405
|
if (!payload?.runId || !payload.conversationId || typeof payload.content !== "string") {
|
|
28657
30406
|
return;
|
|
28658
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
|
+
}
|
|
28659
30419
|
const existingRun = activeAgents.get(payload.runId);
|
|
28660
30420
|
if (existingRun) {
|
|
28661
30421
|
if (existingRun.conversationId !== payload.conversationId) {
|
|
@@ -28748,31 +30508,31 @@ async function startDaemon(args) {
|
|
|
28748
30508
|
const workspaceRequired = Boolean(explicitWorkspacePath);
|
|
28749
30509
|
if (isAlanDefaultWorkspace(payload.projectPath)) {
|
|
28750
30510
|
try {
|
|
28751
|
-
(0,
|
|
30511
|
+
(0, import_node_fs14.mkdirSync)(cwd, { recursive: true });
|
|
28752
30512
|
} catch {
|
|
28753
30513
|
}
|
|
28754
30514
|
}
|
|
28755
30515
|
let workspaceReadiness = inspectWorkspaceReadiness(workspaceRequired ? cwd : void 0);
|
|
28756
30516
|
const expectedRepoUrls = [
|
|
28757
30517
|
...(payload.repoLocalPaths ?? []).filter(
|
|
28758
|
-
(repo) => explicitWorkspacePath ? (0,
|
|
30518
|
+
(repo) => explicitWorkspacePath ? (0, import_node_path12.resolve)(repo.localPath) === (0, import_node_path12.resolve)(explicitWorkspacePath) : false
|
|
28759
30519
|
).map((repo) => repo.repoUrl),
|
|
28760
30520
|
...payload.localRepoUrls ?? []
|
|
28761
30521
|
];
|
|
28762
30522
|
if (workspaceReadiness.code === "workspace_missing" && explicitWorkspacePath && !payload.worktreeInfo) {
|
|
28763
|
-
const previousPath = (0,
|
|
30523
|
+
const previousPath = (0, import_node_path12.resolve)(explicitWorkspacePath);
|
|
28764
30524
|
const remembered = workspaceRelocations.get(previousPath);
|
|
28765
30525
|
const rememberedPath = remembered?.canonicalPath;
|
|
28766
30526
|
const relocated = rememberedPath && workspaceMatchesExpectedRepositories({
|
|
28767
30527
|
workspacePath: rememberedPath,
|
|
28768
30528
|
expectedRepoUrls
|
|
28769
|
-
}) ? (0,
|
|
30529
|
+
}) ? (0, import_node_path12.resolve)(rememberedPath) : discoverRelocatedWorkspace({
|
|
28770
30530
|
missingPath: explicitWorkspacePath,
|
|
28771
30531
|
approvedWorkspacePaths: payload.approvedWorkspacePaths,
|
|
28772
30532
|
expectedRepoUrls
|
|
28773
30533
|
});
|
|
28774
30534
|
if (relocated) {
|
|
28775
|
-
if (!remembered || (0,
|
|
30535
|
+
if (!remembered || (0, import_node_path12.resolve)(remembered.canonicalPath) !== relocated) {
|
|
28776
30536
|
workspaceRelocations.set(previousPath, {
|
|
28777
30537
|
conversationId: payload.conversationId,
|
|
28778
30538
|
runId: payload.runId,
|
|
@@ -29240,7 +31000,7 @@ async function startDaemon(args) {
|
|
|
29240
31000
|
const requestId = payload.requestId;
|
|
29241
31001
|
try {
|
|
29242
31002
|
const projectPath = isAlanDefaultWorkspace(payload.projectPath) ? resolveAlanDefaultWorkspacePath() : payload.projectPath;
|
|
29243
|
-
const result = scanLocalSkillsCached((0,
|
|
31003
|
+
const result = scanLocalSkillsCached((0, import_node_os9.homedir)(), projectPath, {
|
|
29244
31004
|
refresh: payload.refresh === true
|
|
29245
31005
|
});
|
|
29246
31006
|
socket.emit("runtime.skills.result", {
|
|
@@ -29268,7 +31028,7 @@ async function startDaemon(args) {
|
|
|
29268
31028
|
if (!oldest) break;
|
|
29269
31029
|
adoptionChallenges.delete(oldest);
|
|
29270
31030
|
}
|
|
29271
|
-
const challenge = (0,
|
|
31031
|
+
const challenge = (0, import_node_crypto5.randomBytes)(24).toString("base64url");
|
|
29272
31032
|
adoptionChallenges.set(challenge, now + LOCAL_SERVICE_CHALLENGE_TTL_MS);
|
|
29273
31033
|
const unsignedOwner = {
|
|
29274
31034
|
version: LOCAL_SERVICE_OWNER_VERSION,
|
|
@@ -29471,7 +31231,7 @@ async function startDaemon(args) {
|
|
|
29471
31231
|
`${describeOccupiedLocalDaemon(localApiPort)} The token in ${getConfigPath()} no longer matches the running daemon; quit Alan desktop or run setup again.`
|
|
29472
31232
|
);
|
|
29473
31233
|
}
|
|
29474
|
-
const localApiListening = new Promise((
|
|
31234
|
+
const localApiListening = new Promise((resolve7, reject) => {
|
|
29475
31235
|
const onError = (error) => {
|
|
29476
31236
|
clearInterval(heartbeat);
|
|
29477
31237
|
clearInterval(versionRefresh);
|
|
@@ -29500,7 +31260,7 @@ async function startDaemon(args) {
|
|
|
29500
31260
|
if (address && typeof address === "object") localApiPort = address.port;
|
|
29501
31261
|
pushLog(`local_api listening port=${localApiPort}`);
|
|
29502
31262
|
console.info("[alan-agent] Local daemon API listening", { port: localApiPort });
|
|
29503
|
-
|
|
31263
|
+
resolve7();
|
|
29504
31264
|
});
|
|
29505
31265
|
});
|
|
29506
31266
|
try {
|
|
@@ -29519,7 +31279,7 @@ async function startDaemon(args) {
|
|
|
29519
31279
|
clearInterval(versionRefresh);
|
|
29520
31280
|
clearInterval(workspaceLeaseMonitor);
|
|
29521
31281
|
socket.disconnect();
|
|
29522
|
-
await new Promise((
|
|
31282
|
+
await new Promise((resolve7) => localServer.close(() => resolve7()));
|
|
29523
31283
|
releaseOwnerLease();
|
|
29524
31284
|
releaseStartupLease();
|
|
29525
31285
|
throw error;
|
|
@@ -29538,8 +31298,8 @@ async function startDaemon(args) {
|
|
|
29538
31298
|
}
|
|
29539
31299
|
activeAgents.clear();
|
|
29540
31300
|
socket.disconnect();
|
|
29541
|
-
await new Promise((
|
|
29542
|
-
localServer.close(() =>
|
|
31301
|
+
await new Promise((resolve7) => {
|
|
31302
|
+
localServer.close(() => resolve7());
|
|
29543
31303
|
});
|
|
29544
31304
|
} finally {
|
|
29545
31305
|
releaseOwnerLease();
|
|
@@ -29976,7 +31736,7 @@ function decodeResumeFallbackContext(encoded) {
|
|
|
29976
31736
|
}
|
|
29977
31737
|
|
|
29978
31738
|
// src/web-presenter.ts
|
|
29979
|
-
var
|
|
31739
|
+
var import_node_crypto6 = require("crypto");
|
|
29980
31740
|
var WebPresenter = class {
|
|
29981
31741
|
constructor(wsClient, conversationId, cwd) {
|
|
29982
31742
|
this.conversationId = conversationId;
|
|
@@ -30153,7 +31913,7 @@ var WebPresenter = class {
|
|
|
30153
31913
|
this.applicationProblem = createAlanProblem({
|
|
30154
31914
|
domain: "application",
|
|
30155
31915
|
code: /required tools|prepare the required tools/i.test(message) ? "app.service.unavailable" : "app.internal",
|
|
30156
|
-
id: (0,
|
|
31916
|
+
id: (0, import_node_crypto6.randomUUID)(),
|
|
30157
31917
|
title: "Alan could not continue the agent run",
|
|
30158
31918
|
detail: message,
|
|
30159
31919
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30220,7 +31980,7 @@ var WebPresenter = class {
|
|
|
30220
31980
|
};
|
|
30221
31981
|
|
|
30222
31982
|
// src/ws-client.ts
|
|
30223
|
-
var
|
|
31983
|
+
var import_node_crypto8 = require("crypto");
|
|
30224
31984
|
|
|
30225
31985
|
// ../shared/dist/agent-liveness.js
|
|
30226
31986
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -30248,8 +32008,8 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
30248
32008
|
});
|
|
30249
32009
|
|
|
30250
32010
|
// src/sandbox-outbox.ts
|
|
30251
|
-
var
|
|
30252
|
-
var
|
|
32011
|
+
var import_node_crypto7 = require("crypto");
|
|
32012
|
+
var import_node_fs15 = require("fs");
|
|
30253
32013
|
var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
|
|
30254
32014
|
function isSandboxEventOutboxEnabled(env = process.env) {
|
|
30255
32015
|
return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
|
|
@@ -30258,7 +32018,7 @@ function deriveSandboxOutboxKey(sessionToken) {
|
|
|
30258
32018
|
if (!sessionToken) {
|
|
30259
32019
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
30260
32020
|
}
|
|
30261
|
-
return (0,
|
|
32021
|
+
return (0, import_node_crypto7.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
30262
32022
|
}
|
|
30263
32023
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
30264
32024
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -30272,7 +32032,7 @@ function createSandboxEventOutbox(input) {
|
|
|
30272
32032
|
input.pushLog?.(
|
|
30273
32033
|
`sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
|
|
30274
32034
|
);
|
|
30275
|
-
(0,
|
|
32035
|
+
(0, import_node_fs15.rmSync)(path, { force: true });
|
|
30276
32036
|
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
30277
32037
|
}
|
|
30278
32038
|
}
|
|
@@ -30395,7 +32155,7 @@ var WSClient = class {
|
|
|
30395
32155
|
}
|
|
30396
32156
|
socket;
|
|
30397
32157
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
30398
|
-
agentSessionId = (0,
|
|
32158
|
+
agentSessionId = (0, import_node_crypto8.randomUUID)();
|
|
30399
32159
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
30400
32160
|
heartbeatSeq = 0;
|
|
30401
32161
|
heartbeatTimer = null;
|
|
@@ -30475,9 +32235,9 @@ var WSClient = class {
|
|
|
30475
32235
|
});
|
|
30476
32236
|
}
|
|
30477
32237
|
waitForConnection(timeoutMs = 15e3) {
|
|
30478
|
-
return new Promise((
|
|
32238
|
+
return new Promise((resolve7, reject) => {
|
|
30479
32239
|
if (this.socket.connected) {
|
|
30480
|
-
|
|
32240
|
+
resolve7();
|
|
30481
32241
|
return;
|
|
30482
32242
|
}
|
|
30483
32243
|
const timeout = setTimeout(
|
|
@@ -30486,7 +32246,7 @@ var WSClient = class {
|
|
|
30486
32246
|
);
|
|
30487
32247
|
this.socket.once("connect", () => {
|
|
30488
32248
|
clearTimeout(timeout);
|
|
30489
|
-
|
|
32249
|
+
resolve7();
|
|
30490
32250
|
});
|
|
30491
32251
|
this.socket.once("connect_error", (err) => {
|
|
30492
32252
|
clearTimeout(timeout);
|
|
@@ -30667,9 +32427,9 @@ async function runSandbox(config) {
|
|
|
30667
32427
|
onUserMessage: (payload) => {
|
|
30668
32428
|
if (payload === null) {
|
|
30669
32429
|
if (pendingMessageResolve) {
|
|
30670
|
-
const
|
|
32430
|
+
const resolve7 = pendingMessageResolve;
|
|
30671
32431
|
pendingMessageResolve = null;
|
|
30672
|
-
|
|
32432
|
+
resolve7(null);
|
|
30673
32433
|
}
|
|
30674
32434
|
return;
|
|
30675
32435
|
}
|
|
@@ -30691,9 +32451,9 @@ async function runSandbox(config) {
|
|
|
30691
32451
|
fileCount: payload.files?.length ?? 0
|
|
30692
32452
|
});
|
|
30693
32453
|
if (pendingMessageResolve) {
|
|
30694
|
-
const
|
|
32454
|
+
const resolve7 = pendingMessageResolve;
|
|
30695
32455
|
pendingMessageResolve = null;
|
|
30696
|
-
|
|
32456
|
+
resolve7(payload);
|
|
30697
32457
|
} else {
|
|
30698
32458
|
queuedMessages.push(payload);
|
|
30699
32459
|
}
|
|
@@ -30886,8 +32646,8 @@ async function runSandbox(config) {
|
|
|
30886
32646
|
});
|
|
30887
32647
|
if (stopped) break;
|
|
30888
32648
|
}
|
|
30889
|
-
const nextPayload = queuedMessages.shift() ?? await new Promise((
|
|
30890
|
-
pendingMessageResolve =
|
|
32649
|
+
const nextPayload = queuedMessages.shift() ?? await new Promise((resolve7) => {
|
|
32650
|
+
pendingMessageResolve = resolve7;
|
|
30891
32651
|
});
|
|
30892
32652
|
if (nextPayload === null || stopped) {
|
|
30893
32653
|
break;
|
|
@@ -30969,10 +32729,10 @@ async function runSandbox(config) {
|
|
|
30969
32729
|
}
|
|
30970
32730
|
|
|
30971
32731
|
// src/service-manager.ts
|
|
30972
|
-
var
|
|
30973
|
-
var
|
|
30974
|
-
var
|
|
30975
|
-
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");
|
|
30976
32736
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
30977
32737
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
30978
32738
|
var WINDOWS_TASK = "Alan Agent";
|
|
@@ -30992,8 +32752,8 @@ function buildDaemonServicePlan(input) {
|
|
|
30992
32752
|
if (userId === void 0) throw new Error("Cannot determine the current macOS user ID");
|
|
30993
32753
|
const domain = `gui/${userId}`;
|
|
30994
32754
|
const serviceTarget = `${domain}/${SERVICE_LABEL}`;
|
|
30995
|
-
const manifestPath = (0,
|
|
30996
|
-
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");
|
|
30997
32757
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
30998
32758
|
const manifest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
30999
32759
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -31015,9 +32775,9 @@ ${programArguments}
|
|
|
31015
32775
|
<key>ThrottleInterval</key>
|
|
31016
32776
|
<integer>5</integer>
|
|
31017
32777
|
<key>StandardOutPath</key>
|
|
31018
|
-
<string>${xml((0,
|
|
32778
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon.log"))}</string>
|
|
31019
32779
|
<key>StandardErrorPath</key>
|
|
31020
|
-
<string>${xml((0,
|
|
32780
|
+
<string>${xml((0, import_node_path13.join)(logDir, "daemon-error.log"))}</string>
|
|
31021
32781
|
</dict>
|
|
31022
32782
|
</plist>
|
|
31023
32783
|
`;
|
|
@@ -31056,7 +32816,7 @@ ${programArguments}
|
|
|
31056
32816
|
};
|
|
31057
32817
|
}
|
|
31058
32818
|
if (input.platform === "linux") {
|
|
31059
|
-
const manifestPath = (0,
|
|
32819
|
+
const manifestPath = (0, import_node_path13.join)(input.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
31060
32820
|
const manifest = `[Unit]
|
|
31061
32821
|
Description=Alan local agent daemon
|
|
31062
32822
|
After=network-online.target
|
|
@@ -31153,7 +32913,7 @@ WantedBy=default.target
|
|
|
31153
32913
|
};
|
|
31154
32914
|
}
|
|
31155
32915
|
function currentServicePlan(args) {
|
|
31156
|
-
const currentPlatform = (0,
|
|
32916
|
+
const currentPlatform = (0, import_node_os10.platform)();
|
|
31157
32917
|
if (currentPlatform !== "darwin" && currentPlatform !== "linux" && currentPlatform !== "win32") {
|
|
31158
32918
|
throw new Error(`Daemon service installation is not supported on ${currentPlatform}`);
|
|
31159
32919
|
}
|
|
@@ -31162,25 +32922,25 @@ function currentServicePlan(args) {
|
|
|
31162
32922
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
31163
32923
|
return buildDaemonServicePlan({
|
|
31164
32924
|
platform: currentPlatform,
|
|
31165
|
-
homeDir: (0,
|
|
32925
|
+
homeDir: (0, import_node_os10.homedir)(),
|
|
31166
32926
|
nodeExecutable: process.execPath,
|
|
31167
|
-
cliEntry: (0,
|
|
32927
|
+
cliEntry: (0, import_node_path13.resolve)(cliEntry),
|
|
31168
32928
|
profile
|
|
31169
32929
|
});
|
|
31170
32930
|
}
|
|
31171
32931
|
function writeManifest(path, contents) {
|
|
31172
|
-
(0,
|
|
32932
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(path), { recursive: true, mode: 448 });
|
|
31173
32933
|
const pendingPath = `${path}.pending-${process.pid}`;
|
|
31174
32934
|
try {
|
|
31175
|
-
(0,
|
|
31176
|
-
(0,
|
|
31177
|
-
(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);
|
|
31178
32938
|
} finally {
|
|
31179
|
-
(0,
|
|
32939
|
+
(0, import_node_fs16.rmSync)(pendingPath, { force: true });
|
|
31180
32940
|
}
|
|
31181
32941
|
}
|
|
31182
32942
|
function runServiceCommand(command) {
|
|
31183
|
-
const result = (0,
|
|
32943
|
+
const result = (0, import_node_child_process8.spawnSync)(command.command, command.args, { encoding: "utf8" });
|
|
31184
32944
|
const status = result.status ?? 1;
|
|
31185
32945
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
31186
32946
|
if (status !== 0 && !command.tolerateFailure) {
|
|
@@ -31198,8 +32958,8 @@ function installDaemonService(args = []) {
|
|
|
31198
32958
|
}
|
|
31199
32959
|
const plan = currentServicePlan(args);
|
|
31200
32960
|
if (plan.manifestPath && plan.manifest) {
|
|
31201
|
-
if ((0,
|
|
31202
|
-
(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 });
|
|
31203
32963
|
}
|
|
31204
32964
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
31205
32965
|
}
|
|
@@ -31209,7 +32969,7 @@ function installDaemonService(args = []) {
|
|
|
31209
32969
|
function uninstallDaemonService(args = []) {
|
|
31210
32970
|
const plan = currentServicePlan(args);
|
|
31211
32971
|
for (const command of plan.uninstallCommands) runServiceCommand(command);
|
|
31212
|
-
if (plan.manifestPath) (0,
|
|
32972
|
+
if (plan.manifestPath) (0, import_node_fs16.rmSync)(plan.manifestPath, { force: true });
|
|
31213
32973
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
31214
32974
|
}
|
|
31215
32975
|
function printDaemonServiceStatus(args = []) {
|
|
@@ -31220,6 +32980,42 @@ function printDaemonServiceStatus(args = []) {
|
|
|
31220
32980
|
}
|
|
31221
32981
|
|
|
31222
32982
|
// src/index.ts
|
|
32983
|
+
function logEffectiveGitIdentity(lifecycle, projectPath) {
|
|
32984
|
+
const readConfig2 = (key) => {
|
|
32985
|
+
try {
|
|
32986
|
+
const result = (0, import_node_child_process9.spawnSync)("git", ["config", "--get", key], {
|
|
32987
|
+
cwd: projectPath,
|
|
32988
|
+
env: getDaemonCliEnvironment(),
|
|
32989
|
+
encoding: "utf8",
|
|
32990
|
+
timeout: 5e3
|
|
32991
|
+
});
|
|
32992
|
+
const value2 = result.status === 0 ? result.stdout.trim() : "";
|
|
32993
|
+
return value2 || null;
|
|
32994
|
+
} catch {
|
|
32995
|
+
return null;
|
|
32996
|
+
}
|
|
32997
|
+
};
|
|
32998
|
+
const envName = process.env.GIT_AUTHOR_NAME || null;
|
|
32999
|
+
const envEmail = process.env.GIT_AUTHOR_EMAIL || null;
|
|
33000
|
+
const configName = readConfig2("user.name");
|
|
33001
|
+
const configEmail = readConfig2("user.email");
|
|
33002
|
+
const payload = {
|
|
33003
|
+
projectPath,
|
|
33004
|
+
envAuthorName: envName,
|
|
33005
|
+
envAuthorEmail: envEmail,
|
|
33006
|
+
configUserName: configName,
|
|
33007
|
+
configUserEmail: configEmail
|
|
33008
|
+
};
|
|
33009
|
+
if (!envEmail && !configEmail) {
|
|
33010
|
+
lifecycle.error("sandbox_agent_git_identity_missing", payload);
|
|
33011
|
+
return;
|
|
33012
|
+
}
|
|
33013
|
+
if (envEmail && configEmail && envEmail !== configEmail) {
|
|
33014
|
+
lifecycle.error("sandbox_agent_git_identity_mismatch", payload);
|
|
33015
|
+
return;
|
|
33016
|
+
}
|
|
33017
|
+
lifecycle.info("sandbox_agent_git_identity", payload);
|
|
33018
|
+
}
|
|
31223
33019
|
async function runSessionFromEnv() {
|
|
31224
33020
|
const wsUrl = process.env.ALAN_WS_URL;
|
|
31225
33021
|
const sessionId = process.env.ALAN_SESSION_ID;
|
|
@@ -31264,6 +33060,7 @@ async function runSessionFromEnv() {
|
|
|
31264
33060
|
mode,
|
|
31265
33061
|
model
|
|
31266
33062
|
});
|
|
33063
|
+
logEffectiveGitIdentity(lifecycle, projectPath);
|
|
31267
33064
|
await runSandbox({
|
|
31268
33065
|
wsUrl,
|
|
31269
33066
|
sessionId,
|