@aiden-ade/sandbox-agent 0.1.50 → 0.1.51

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