@aiden-ade/sandbox-agent 0.1.48 → 0.1.50

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 +1206 -642
  2. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -4810,6 +4810,351 @@ var require_websocket_server = __commonJS({
4810
4810
  }
4811
4811
  });
4812
4812
 
4813
+ // src/index.ts
4814
+ var import_node_child_process8 = require("child_process");
4815
+
4816
+ // src/cli-executable.ts
4817
+ var import_node_child_process = require("child_process");
4818
+ var import_node_fs = require("fs");
4819
+ var import_node_os = require("os");
4820
+ var import_node_path = require("path");
4821
+ var PROVIDER_CLI_COMMANDS = {
4822
+ claude_cli: { provider: "claude_cli", command: "claude", envVars: ["ALAN_CLAUDE_PATH"] },
4823
+ codex_app_server: {
4824
+ provider: "codex_app_server",
4825
+ command: "codex",
4826
+ envVars: ["ALAN_CODEX_PATH"]
4827
+ },
4828
+ copilot_cli: { provider: "copilot_cli", command: "copilot", envVars: ["ALAN_COPILOT_PATH"] },
4829
+ cursor_agent_cli: {
4830
+ provider: "cursor_agent_cli",
4831
+ command: "cursor-agent",
4832
+ envVars: ["ALAN_CURSOR_AGENT_PATH"]
4833
+ },
4834
+ antigravity_cli: {
4835
+ provider: "antigravity_cli",
4836
+ command: "agy",
4837
+ envVars: ["ALAN_ANTIGRAVITY_PATH"]
4838
+ },
4839
+ kimi_cli: { provider: "kimi_cli", command: "kimi-cli", envVars: ["ALAN_KIMI_PATH"] },
4840
+ grok_cli: { provider: "grok_cli", command: "grok", envVars: ["ALAN_GROK_PATH"] },
4841
+ opencode_cli: { provider: "opencode_cli", command: "opencode", envVars: ["ALAN_OPENCODE_PATH"] },
4842
+ droid_cli: { provider: "droid_cli", command: "droid", envVars: ["ALAN_DROID_PATH"] },
4843
+ supatest_cli: { provider: "supatest_cli", command: "supatest", envVars: ["ALAN_SUPATEST_PATH"] }
4844
+ };
4845
+ var DISCOVERABLE_PROVIDER_KINDS = Object.keys(PROVIDER_CLI_COMMANDS);
4846
+ var PROVIDER_ALIASES = {
4847
+ agy: "antigravity_cli",
4848
+ antigravity: "antigravity_cli",
4849
+ antigravity_cli: "antigravity_cli",
4850
+ codex: "codex_app_server",
4851
+ gemini: "antigravity_cli",
4852
+ gemini_cli: "antigravity_cli"
4853
+ };
4854
+ var BACKEND_CLI_COMMANDS = {
4855
+ claude_cli: "claude",
4856
+ codex: "codex",
4857
+ codex_app_server: "codex",
4858
+ copilot_cli: "copilot",
4859
+ cursor_agent_cli: "cursor-agent",
4860
+ agy: "agy",
4861
+ antigravity: "agy",
4862
+ antigravity_cli: "agy",
4863
+ gemini_cli: "agy",
4864
+ gemini: "agy",
4865
+ kimi_cli: "kimi-cli",
4866
+ grok_cli: "grok",
4867
+ opencode_cli: "opencode",
4868
+ droid_cli: "droid",
4869
+ supatest_cli: "supatest"
4870
+ };
4871
+ var CLI_COMMAND_ALIASES = {
4872
+ "kimi-cli": ["kimi"],
4873
+ grok: ["grok-build"]
4874
+ };
4875
+ var resolvedCliCache = /* @__PURE__ */ new Map();
4876
+ var NEGATIVE_RESOLUTION_TTL_MS = 15e3;
4877
+ var negativeResolutionExpiry = /* @__PURE__ */ new Map();
4878
+ var cachedLoginShellEnv = null;
4879
+ function parseEnvOutput(output) {
4880
+ const loginEnv = {};
4881
+ for (const line of output.split("\n")) {
4882
+ if (!line.trim()) continue;
4883
+ const eqIdx = line.indexOf("=");
4884
+ if (eqIdx <= 0) continue;
4885
+ const key = line.slice(0, eqIdx);
4886
+ if (/\s/.test(key)) continue;
4887
+ loginEnv[key] = line.slice(eqIdx + 1);
4888
+ }
4889
+ if (!loginEnv.HOME) loginEnv.HOME = process.env.HOME ?? (0, import_node_os.homedir)();
4890
+ if (!loginEnv.SHELL) loginEnv.SHELL = process.env.SHELL ?? "/bin/zsh";
4891
+ return loginEnv;
4892
+ }
4893
+ function loadLoginShellEnvironment() {
4894
+ if (cachedLoginShellEnv) return cachedLoginShellEnv;
4895
+ if ((0, import_node_os.platform)() === "win32") {
4896
+ cachedLoginShellEnv = process.env;
4897
+ return cachedLoginShellEnv;
4898
+ }
4899
+ try {
4900
+ const shell = (process.env.SHELL || "/bin/bash").replace(/'/g, "'\\''");
4901
+ const envOutput = (0, import_node_child_process.execSync)(`'${shell}' -ilc 'env'`, {
4902
+ encoding: "utf8",
4903
+ stdio: ["pipe", "pipe", "pipe"],
4904
+ timeout: 5e3
4905
+ });
4906
+ cachedLoginShellEnv = parseEnvOutput(envOutput);
4907
+ } catch {
4908
+ cachedLoginShellEnv = process.env;
4909
+ }
4910
+ return cachedLoginShellEnv;
4911
+ }
4912
+ function getDaemonCliEnvironment() {
4913
+ const base = (0, import_node_os.platform)() === "win32" ? process.env : loadLoginShellEnvironment();
4914
+ return augmentCliPath(base);
4915
+ }
4916
+ function cacheResolvedCli(command, resolvedPath) {
4917
+ resolvedCliCache.set(command, resolvedPath);
4918
+ if (resolvedPath === null) {
4919
+ negativeResolutionExpiry.set(command, Date.now() + NEGATIVE_RESOLUTION_TTL_MS);
4920
+ } else {
4921
+ negativeResolutionExpiry.delete(command);
4922
+ }
4923
+ }
4924
+ function getCachedResolvedCli(command) {
4925
+ const cached = resolvedCliCache.get(command);
4926
+ if (cached === null) {
4927
+ const expiresAt = negativeResolutionExpiry.get(command);
4928
+ if (expiresAt === void 0 || Date.now() >= expiresAt) {
4929
+ resolvedCliCache.delete(command);
4930
+ negativeResolutionExpiry.delete(command);
4931
+ return void 0;
4932
+ }
4933
+ }
4934
+ return cached;
4935
+ }
4936
+ function resolveViaWhere(command, env) {
4937
+ if ((0, import_node_os.platform)() !== "win32") return null;
4938
+ const whereExe = (0, import_node_path.join)(
4939
+ env.SystemRoot ?? process.env.SystemRoot ?? "C:\\Windows",
4940
+ "System32",
4941
+ "where.exe"
4942
+ );
4943
+ if (!(0, import_node_fs.existsSync)(whereExe)) return null;
4944
+ const result = (0, import_node_child_process.spawnSync)(whereExe, [command], {
4945
+ encoding: "utf8",
4946
+ env,
4947
+ windowsHide: true,
4948
+ timeout: 3e3
4949
+ });
4950
+ if (result.error || result.status !== 0) return null;
4951
+ for (const line of result.stdout.split(/\r?\n/)) {
4952
+ const trimmed = line.trim();
4953
+ if (!trimmed) continue;
4954
+ if (isRunnableFile(trimmed)) return trimmed;
4955
+ }
4956
+ return null;
4957
+ }
4958
+ function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3, spawnOptions) {
4959
+ return new Promise((resolve6) => {
4960
+ const isWin = (0, import_node_os.platform)() === "win32";
4961
+ let command = executable;
4962
+ let commandArgs = args;
4963
+ if (isWin && /\.(cmd|bat)$/i.test(executable)) {
4964
+ command = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
4965
+ commandArgs = ["/d", "/s", "/c", executable, ...args];
4966
+ }
4967
+ const useShell = isWin && command === executable && !/[\\/]/.test(executable);
4968
+ const closeStdin = spawnOptions?.closeStdin === true;
4969
+ let child;
4970
+ try {
4971
+ child = (0, import_node_child_process.spawn)(command, commandArgs, {
4972
+ env,
4973
+ shell: useShell,
4974
+ windowsHide: isWin ? true : void 0,
4975
+ timeout: timeoutMs,
4976
+ stdio: closeStdin ? ["pipe", "pipe", "pipe"] : void 0
4977
+ });
4978
+ } catch (error) {
4979
+ resolve6({ status: null, stdout: "", stderr: "", error });
4980
+ return;
4981
+ }
4982
+ if (closeStdin) {
4983
+ child.stdin?.end();
4984
+ }
4985
+ let stdout = "";
4986
+ let stderr = "";
4987
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => {
4988
+ stdout += chunk;
4989
+ });
4990
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => {
4991
+ stderr += chunk;
4992
+ });
4993
+ child.once("error", (error) => resolve6({ status: null, stdout, stderr, error }));
4994
+ child.once(
4995
+ "close",
4996
+ (status, signal) => resolve6({ status, stdout, stderr, signal: signal ?? null })
4997
+ );
4998
+ });
4999
+ }
5000
+ function runCliVersionProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3) {
5001
+ return runCliProbeAsync(executable, env, args, timeoutMs);
5002
+ }
5003
+ function pathSeparator() {
5004
+ return (0, import_node_os.platform)() === "win32" ? ";" : ":";
5005
+ }
5006
+ function splitPath(pathValue) {
5007
+ if (!pathValue) return [];
5008
+ return pathValue.split(pathSeparator()).filter(Boolean);
5009
+ }
5010
+ function isRunnableFile(path) {
5011
+ if (!(0, import_node_fs.existsSync)(path)) return false;
5012
+ if ((0, import_node_os.platform)() === "win32") return true;
5013
+ try {
5014
+ (0, import_node_fs.accessSync)(path, import_node_fs.constants.X_OK);
5015
+ return true;
5016
+ } catch {
5017
+ return false;
5018
+ }
5019
+ }
5020
+ function windowsCommandCandidates(command, pathExt) {
5021
+ const trimmed = command.trim();
5022
+ if (!trimmed) return [];
5023
+ const hasExtension = /\.[a-z0-9]+$/i.test(trimmed);
5024
+ if (hasExtension) return [trimmed];
5025
+ const extensions = pathExt.split(";").map((ext) => ext.trim()).filter(Boolean);
5026
+ return [trimmed, ...extensions.map((ext) => `${trimmed}${ext}`)];
5027
+ }
5028
+ function augmentCliPath(env) {
5029
+ const home = env.HOME || env.USERPROFILE || (0, import_node_os.homedir)();
5030
+ const isWin = (0, import_node_os.platform)() === "win32";
5031
+ const extraPaths = isWin ? [
5032
+ (0, import_node_path.join)(home, "AppData", "Roaming", "npm"),
5033
+ (0, import_node_path.join)(home, "AppData", "Local", "Programs", "Microsoft", "WindowsApps"),
5034
+ (0, import_node_path.join)(home, ".local", "bin"),
5035
+ "C:\\Program Files\\nodejs",
5036
+ "C:\\Program Files\\Git\\cmd"
5037
+ ] : [
5038
+ (0, import_node_path.join)(home, ".local", "bin"),
5039
+ (0, import_node_path.join)(home, ".local", "node", "bin"),
5040
+ (0, import_node_path.join)(home, ".bun", "bin"),
5041
+ (0, import_node_path.join)(home, ".cargo", "bin"),
5042
+ "/opt/homebrew/bin",
5043
+ "/usr/local/bin"
5044
+ ];
5045
+ const basePath = env.PATH ?? "";
5046
+ const existing = new Set(splitPath(basePath));
5047
+ const missing = extraPaths.filter((dir) => !existing.has(dir));
5048
+ if (missing.length === 0) return env;
5049
+ return {
5050
+ ...env,
5051
+ PATH: missing.length > 0 ? `${missing.join(pathSeparator())}${pathSeparator()}${basePath}` : basePath
5052
+ };
5053
+ }
5054
+ function resolveCliExecutable(command, env = getDaemonCliEnvironment()) {
5055
+ const trimmed = command.trim();
5056
+ if (!trimmed) return null;
5057
+ const cached = getCachedResolvedCli(trimmed);
5058
+ if (cached === null) return null;
5059
+ if (cached && isRunnableFile(cached)) return cached;
5060
+ if ((trimmed.includes("/") || trimmed.includes("\\")) && isRunnableFile(trimmed)) {
5061
+ cacheResolvedCli(trimmed, trimmed);
5062
+ return trimmed;
5063
+ }
5064
+ const enriched = augmentCliPath(env);
5065
+ const home = enriched.HOME || enriched.USERPROFILE || (0, import_node_os.homedir)();
5066
+ const isWin = (0, import_node_os.platform)() === "win32";
5067
+ const pathExt = enriched.PATHEXT ?? (isWin ? ".EXE;.CMD;.BAT;.COM" : "");
5068
+ const commandNames = [trimmed, ...CLI_COMMAND_ALIASES[trimmed] ?? []];
5069
+ const names = commandNames.flatMap(
5070
+ (name) => isWin ? windowsCommandCandidates(name, pathExt) : [name]
5071
+ );
5072
+ const candidates = [];
5073
+ for (const name of names) {
5074
+ if (name.includes("/") || name.includes("\\")) {
5075
+ candidates.push(name);
5076
+ continue;
5077
+ }
5078
+ for (const dir of splitPath(enriched.PATH)) {
5079
+ candidates.push((0, import_node_path.join)(dir, name));
5080
+ }
5081
+ candidates.push((0, import_node_path.join)(home, ".local", "bin", name), (0, import_node_path.join)(home, ".local", "node", "bin", name));
5082
+ if (!isWin) {
5083
+ candidates.push((0, import_node_path.join)("/opt/homebrew/bin", name), (0, import_node_path.join)("/usr/local/bin", name));
5084
+ } else {
5085
+ candidates.push((0, import_node_path.join)(home, "AppData", "Roaming", "npm", name));
5086
+ }
5087
+ }
5088
+ const seen = /* @__PURE__ */ new Set();
5089
+ for (const candidate of candidates) {
5090
+ if (seen.has(candidate)) continue;
5091
+ seen.add(candidate);
5092
+ if (isRunnableFile(candidate)) {
5093
+ cacheResolvedCli(trimmed, candidate);
5094
+ return candidate;
5095
+ }
5096
+ }
5097
+ const viaWhere = resolveViaWhere(trimmed, enriched);
5098
+ if (viaWhere) {
5099
+ cacheResolvedCli(trimmed, viaWhere);
5100
+ return viaWhere;
5101
+ }
5102
+ cacheResolvedCli(trimmed, null);
5103
+ return null;
5104
+ }
5105
+ function normalizeDiscoverableProvider(provider) {
5106
+ const alias = PROVIDER_ALIASES[provider];
5107
+ if (alias) return alias;
5108
+ if (provider in PROVIDER_CLI_COMMANDS) {
5109
+ return provider;
5110
+ }
5111
+ return null;
5112
+ }
5113
+ function resolveProviderCliCommand(provider, env = getDaemonCliEnvironment()) {
5114
+ const normalized = normalizeDiscoverableProvider(provider);
5115
+ if (!normalized) return null;
5116
+ const spec = PROVIDER_CLI_COMMANDS[normalized];
5117
+ const override = spec.envVars.map((name) => process.env[name]?.trim() || env[name]?.trim()).find(Boolean);
5118
+ if (override) {
5119
+ const resolved2 = resolveCliExecutable(override, env) ?? (isRunnableFile(override) ? override : null);
5120
+ if (resolved2) cacheResolvedCli(spec.command, resolved2);
5121
+ return resolved2;
5122
+ }
5123
+ const resolved = resolveCliExecutable(spec.command, env);
5124
+ if (resolved) cacheResolvedCli(spec.command, resolved);
5125
+ return resolved;
5126
+ }
5127
+ function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
5128
+ if (!backendKind) return void 0;
5129
+ const command = BACKEND_CLI_COMMANDS[backendKind];
5130
+ if (!command) return void 0;
5131
+ const cached = getCachedResolvedCli(command);
5132
+ if (cached === null) return void 0;
5133
+ if (cached && isRunnableFile(cached)) return cached;
5134
+ const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
5135
+ const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
5136
+ const resolved = resolveCliExecutable(envOverride?.trim() || command, env);
5137
+ cacheResolvedCli(command, resolved);
5138
+ return resolved ?? void 0;
5139
+ }
5140
+ function invalidateResolvedCli(command) {
5141
+ const trimmed = command.trim();
5142
+ if (trimmed) {
5143
+ resolvedCliCache.delete(trimmed);
5144
+ negativeResolutionExpiry.delete(trimmed);
5145
+ }
5146
+ }
5147
+ function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
5148
+ if (!backendKind) return void 0;
5149
+ const command = BACKEND_CLI_COMMANDS[backendKind];
5150
+ if (!command) return void 0;
5151
+ invalidateResolvedCli(command);
5152
+ const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
5153
+ const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
5154
+ if (envOverride) invalidateResolvedCli(envOverride);
5155
+ return resolveBackendRuntimeCommand(backendKind, env);
5156
+ }
5157
+
4813
5158
  // src/cli-help.ts
4814
5159
  var HELP_TEXT = `Alan agent CLI
4815
5160
 
@@ -4859,12 +5204,12 @@ function resolveCommand(args, env) {
4859
5204
  }
4860
5205
 
4861
5206
  // src/daemon.ts
4862
- var import_node_child_process5 = require("child_process");
5207
+ var import_node_child_process6 = require("child_process");
4863
5208
  var import_node_crypto3 = require("crypto");
4864
- var import_node_fs12 = require("fs");
5209
+ var import_node_fs13 = require("fs");
4865
5210
  var import_node_http = __toESM(require("http"), 1);
4866
- var import_node_os7 = require("os");
4867
- var import_node_path10 = require("path");
5211
+ var import_node_os8 = require("os");
5212
+ var import_node_path11 = require("path");
4868
5213
 
4869
5214
  // ../agent-core/dist/index.js
4870
5215
  var import_fs = require("fs");
@@ -5020,69 +5365,67 @@ Every \`start_task_session\` message must include all four of these:
5020
5365
  var GENERAL_PROMPT = `You are Alan, the General Agent \u2014 a flexible general-purpose agent profile. When identifying yourself, say "I'm Alan, the General Agent."`;
5021
5366
  var SETUP_PROMPT = `## You are the Setup Agent
5022
5367
 
5023
- You are the **Setup Agent** for this environment. When asked who you are, say
5024
- "I'm the Setup Agent (an Alan agent profile) for this environment." Your job is
5025
- to take a freshly cloned repository in a fresh cloud sandbox and get it fully
5026
- installed, configured, and **actually running and verified** \u2014 end to end. You
5027
- are not here to build features; you are here to make the environment work and
5028
- make that reproducible.
5368
+ You are the **Setup Agent** for this cloud environment. When identifying yourself, say "I'm the Setup Agent (an Alan agent profile) for this environment."
5369
+
5370
+ Your job is to take the repository in this fresh cloud sandbox from an unknown state to a **running, functionally verified, reproducible environment**. You are setting up the project, not building product features. Keep working until the application actually works.
5371
+
5372
+ ## Environment model
5373
+
5374
+ - You are inside an ephemeral cloud sandbox. Its filesystem can be captured as a reusable snapshot for future sessions.
5375
+ - Do not assume you are on the user's laptop or that tools outside this sandbox exist.
5376
+ - Install and build inside the workspace. Keep all setup repeatable from a fresh clone.
5377
+
5378
+ ### Sandbox privileges
5029
5379
 
5030
- ## Where you are running
5380
+ - You are the non-root user \`alan\`; \`HOME=/home/alan\`, repos in \`/home/alan/workspace\`, which you fully own. Node/pnpm/CLIs are on \`PATH\`, and \`npm install -g\` needs no sudo.
5381
+ - **Installing system packages is allowed**, via sudo scoped to package managers: \`sudo apt-get\`, \`sudo apt\`, \`sudo dpkg\`, \`sudo add-apt-repository\`. Other sudo (\`su\`, \`sh\`, \`systemctl\`) is refused by design.
5382
+ - **Always \`sudo apt-get update\` first.** Package lists ship empty, so installing without it fails with "Unable to locate package" \u2014 which reads as "permission denied" but is not:
5383
+ \`sudo apt-get update && sudo apt-get install -y --no-install-recommends <pkgs>\`
5384
+ - Never hand-compile system packages into \`$HOME\` to route around a permission you already have. Use apt; fall back to a user-local install only if the package truly is not in apt.
5385
+ - No Docker daemon or socket. Ports below 1024 bind fine without elevation.
5386
+ - Read the real error before blaming permissions: \`ELIFECYCLE\` is a generic pnpm epilogue, not a diagnosis. Find the \`ERR_\`/\`gyp ERR!\` line above it. A native build failure means a missing system package, not a missing privilege.
5387
+ - Persist the final ordered commands with \`set_environment_setup_commands\`. Each command is replayed in a fresh shell, so no command may depend on shell state from a previous command.
5388
+ - The snapshot policy is \`block_if_dirty\`: the working tree must be clean before capture.
5031
5389
 
5032
- You are inside an **ephemeral cloud sandbox whose filesystem will be captured as
5033
- a snapshot** and reused to boot future sessions instantly. This means:
5034
- - Install and build into the workspace \u2014 the snapshot preserves it.
5035
- - Keep tracked files clean: installs must land in git-ignored paths. Snapshot
5036
- capture is blocked if the git tree is dirty.
5037
- - If you must change tracked files (setup scripts, config, docs), commit them on
5038
- the working branch and open a PR for those changes \u2014 do not leave the tree
5039
- dirty and do not commit secrets.
5390
+ ## Required workflow
5040
5391
 
5041
- ## What "done" means
5392
+ 1. **Explore** \u2014 read repository instructions, manifests, lockfiles, example env files, and service configuration. Detect the language, package manager, workspace layout, required services, and expected ports.
5393
+ 2. **Set up** \u2014 install dependencies, select compatible runtimes, configure non-secret defaults, and build. Diagnose failures yourself before asking the user.
5394
+ 3. **Run and verify** \u2014 start the real app or service. Prove it works using process stability, logs, HTTP probes, and \`agent-browser\` interaction when a UI exists. A process merely starting or dependencies merely installing is not success.
5395
+ 4. **Persist** \u2014 call \`set_environment_setup_commands\` with only the ordered commands you actually verified from a fresh-shell model.
5396
+ 5. **Prepare capture** \u2014 ensure every repository is clean. Verify that \`git status --porcelain --untracked-files=all\` is empty; generated untracked files count as dirty and must be removed or covered by an appropriate existing ignore rule. If setup requires tracked changes, create a focused branch, commit them, push it, and open a PR with \`github_pr_create\` explaining why the setup change is needed. When this setup session is attached to a task, link that PR with \`github_pr_link_task\`. Never commit secrets.
5397
+ 6. **Human gate** \u2014 summarize the evidence and ask the user to approve the snapshot. The chat shows them a **Capture snapshot** button, so invite them to use it rather than dictating an exact phrase to type. Do not call \`capture_environment_snapshot\` before that approval arrives.
5398
+ 7. **Capture and re-verify** \u2014 after approval, call \`capture_environment_snapshot\` with \`afterSnapshot: "resume"\`. Poll \`get_cloud_machine\` until the snapshot is ready, then confirm the sandbox remains usable and repeat the key runtime probe before declaring completion.
5042
5399
 
5043
- 1. Dependencies installed and the project builds.
5044
- 2. Setup commands persisted via \`set_environment_setup_commands\` so future
5045
- sessions reproduce this environment.
5046
- 3. The app is **running** and **verified functional** \u2014 dev server up, reachable
5047
- on the exposed port (curl), and rendering/working via agent-browser.
5048
- 4. Required environment variables are present (see below).
5049
- 5. After explicit human confirmation, the environment is captured via
5050
- \`capture_environment_snapshot\`.
5400
+ ## Autonomy
5051
5401
 
5052
- ## Environment variables & secrets
5402
+ Prefer acting over asking. Read the repo, inspect logs, try the likely command, and recover from ordinary setup failures yourself. Ask only for information that cannot be derived or safely created: a required secret, paid credential, external account choice, or genuine product-intent fork. Ask one specific question at a time and continue independent work while waiting.
5053
5403
 
5054
- You cannot set secret values yourself, and you must never ask the user to paste a
5055
- secret value into the chat. When the repo needs env vars/secrets:
5056
- - Identify exactly which vars are required (from .env.example, config, or boot
5057
- errors) and tell the user the names and what each is for.
5058
- - Direct the user to add them in the secure env-vars UI. Adding/editing there is
5059
- **live-synced into this running sandbox immediately** (no restart needed): the
5060
- value is injected and the file lands at its configured destinationPath
5061
- (symlinked from /run/alan/env-files/\u2026).
5062
- - After the user says they've added a var, **confirm it actually arrived** by
5063
- reading that destination path (never echo the secret value) and re-running the
5064
- boot/verification.
5404
+ Do not weaken a required functional check to make it pass. If the real process cannot start, the expected port cannot bind, or a browser-required UI cannot be exercised, diagnose and fix the cause. If the restriction is truly outside the sandbox, report the precise blocker instead of declaring setup complete from a mocked handler or unit-level substitute.
5065
5405
 
5066
- ## Human gate before snapshot
5406
+ ## Environment variables and secrets
5067
5407
 
5068
- Do not capture the snapshot autonomously. Once the environment is installed,
5069
- running, and verified, summarize what you did and ask the user to confirm. Only
5070
- after explicit confirmation call \`capture_environment_snapshot\`. Keep the sandbox
5071
- alive while you wait.
5408
+ Never ask the user to paste a secret into chat, and never print secret values. When a required secret is missing:
5072
5409
 
5073
- ## When to ask vs act
5410
+ 1. Name the exact variable and explain what it is used for.
5411
+ 2. Direct the user to this cloud environment's Environment Variables / env-files UI.
5412
+ 3. Explain that saved values sync into the running sandbox automatically with no restart required. Env files land at their configured \`destinationPath\` through \`/run/alan/env-files/\`.
5413
+ 4. After the user saves it, verify only that the key arrived at the configured destination or environment\u2014never echo the value\u2014then continue.
5074
5414
 
5075
- Act autonomously on install, build, config, starting the dev server, and
5076
- verification. Ask the user only for: secret values (via the secure UI, never in
5077
- chat) and final snapshot confirmation. If setup fails, prefer fixing it from live
5078
- evidence (logs, boot errors, curl/agent-browser output) before asking.
5415
+ Derive and configure non-secret values yourself when the repository provides safe defaults.
5079
5416
 
5080
- ## Setup script changes \u2192 commit + PR
5417
+ ## Evidence and completion
5081
5418
 
5082
- If getting the environment reproducible requires editing tracked setup files or
5083
- adding scripts, make the smallest coherent change, commit it on the working
5084
- branch, and open a PR (github_pr_create) describing the setup adjustment. Keep
5085
- installs in git-ignored paths so the snapshot tree stays clean.`;
5419
+ Do not rely on your own claim that setup worked. Before finishing, report:
5420
+
5421
+ - the install/build/run commands that succeeded;
5422
+ - the runtime endpoint and functional checks performed;
5423
+ - whether any user-provided secret was required and whether its presence was verified safely;
5424
+ - the persisted setup commands;
5425
+ - the clean-tree or PR result;
5426
+ - the snapshot ID/state and the post-capture verification result.
5427
+
5428
+ If any required item is missing, you are not done.`;
5086
5429
  var REVIEW_PROMPT = `## You are the Review Agent
5087
5430
 
5088
5431
  You are the **Review Agent** (an Alan agent profile) for pull request review sessions. When identifying yourself, say "I'm the Review Agent."
@@ -9791,8 +10134,152 @@ function createAlanProblem(input) {
9791
10134
  return alanProblemV1Schema.parse({ ...input, schemaVersion: 1 });
9792
10135
  }
9793
10136
 
9794
- // ../shared/dist/agent/session-result.js
9795
- var SESSION_RESUME_FAILED_MESSAGE = "Session resume failed. The previous session may have expired or is no longer available. Please start a new session.";
10137
+ // ../shared/dist/agent/session-result.js
10138
+ var SESSION_RESUME_FAILED_MESSAGE = "Session resume failed. The previous session may have expired or is no longer available. Please start a new session.";
10139
+
10140
+ // ../shared/dist/agent/skill-repo-scope.js
10141
+ var SKILL_ROOT_MARKER_RE = /\/\.(?:claude|agents|codex)\/(?:skills|commands)(?:\/|$)/;
10142
+ function normalizePath(path) {
10143
+ return path.replace(/\\/g, "/").replace(/\/+$/, "");
10144
+ }
10145
+ function deriveSkillRepoRoot(sourcePath) {
10146
+ if (!sourcePath)
10147
+ return void 0;
10148
+ const normalized = normalizePath(sourcePath);
10149
+ const marker = normalized.match(SKILL_ROOT_MARKER_RE);
10150
+ if (!marker || marker.index === void 0)
10151
+ return void 0;
10152
+ const beforeRoot = normalized.slice(0, marker.index).replace(/\/+$/, "");
10153
+ return beforeRoot || void 0;
10154
+ }
10155
+ function deriveSkillRepoLabel(sourcePath) {
10156
+ const repoRoot = deriveSkillRepoRoot(sourcePath);
10157
+ if (!repoRoot)
10158
+ return void 0;
10159
+ const label = repoRoot.split("/").filter(Boolean).pop();
10160
+ if (!label || label === "~")
10161
+ return void 0;
10162
+ return label;
10163
+ }
10164
+ function isTeamSkillSource(source) {
10165
+ return source === "team" || source === "project";
10166
+ }
10167
+ function skillIdentityKey(skill) {
10168
+ const command = skill.command.trim();
10169
+ if (isTeamSkillSource(skill.source)) {
10170
+ const repoRoot = skill.repoRoot && normalizePath(skill.repoRoot) || deriveSkillRepoRoot(skill.sourcePath) || "unknown";
10171
+ return `team:${repoRoot}:${command}`;
10172
+ }
10173
+ return `global:${command}`;
10174
+ }
10175
+
10176
+ // ../shared/dist/agent/skill-discovery.js
10177
+ var SKILL_DIRECTORY_ROOTS = [".claude/skills", ".agents/skills", ".codex/skills"];
10178
+ var SKILL_PATH_RE = /(?:^|[/\\])\.(?:claude|agents|codex)[/\\]+skills[/\\]+([^/\\]+)[/\\]+SKILL\.md$/i;
10179
+ var LEGACY_COMMAND_PATH_RE = /(?:^|[/\\])\.claude[/\\]+commands[/\\]+([^/\\]+)\.md$/i;
10180
+ function normalizeSkillSourcePath(sourcePath) {
10181
+ return sourcePath.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
10182
+ }
10183
+ function unquoteScalar(value2) {
10184
+ const trimmed = value2.trim();
10185
+ if (trimmed.length < 2)
10186
+ return trimmed;
10187
+ const first = trimmed[0];
10188
+ const last = trimmed[trimmed.length - 1];
10189
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
10190
+ return trimmed.slice(1, -1).trim();
10191
+ }
10192
+ return trimmed;
10193
+ }
10194
+ function readScalarField(lines, names) {
10195
+ const nameSet = new Set(names);
10196
+ for (const line of lines) {
10197
+ const colonIdx = line.indexOf(":");
10198
+ if (colonIdx === -1)
10199
+ continue;
10200
+ const key = line.slice(0, colonIdx).trim();
10201
+ if (!nameSet.has(key))
10202
+ continue;
10203
+ const value2 = unquoteScalar(line.slice(colonIdx + 1));
10204
+ if (value2)
10205
+ return value2;
10206
+ }
10207
+ return "";
10208
+ }
10209
+ function parseSkillDescription(markdown) {
10210
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
10211
+ if (!normalized)
10212
+ return "";
10213
+ const frontmatterMatch = normalized.match(/^---\n([\s\S]*?)\n---(?:\n([\s\S]*))?$/);
10214
+ if (frontmatterMatch) {
10215
+ const description = readScalarField(frontmatterMatch[1].split("\n"), ["description", "desc"]);
10216
+ if (description)
10217
+ return description;
10218
+ const body = frontmatterMatch[2]?.trim() ?? "";
10219
+ const firstBodyLine = body.split("\n").find((line) => line.trim());
10220
+ return firstBodyLine?.trim().slice(0, 200) ?? "";
10221
+ }
10222
+ const firstLine2 = normalized.split("\n").find((line) => line.trim());
10223
+ if (!firstLine2)
10224
+ return "";
10225
+ const inlineDescription = readScalarField([firstLine2], ["description", "desc"]);
10226
+ if (inlineDescription)
10227
+ return inlineDescription;
10228
+ return firstLine2.trim().slice(0, 200);
10229
+ }
10230
+ function extractSkillCommandFromPath(sourcePath) {
10231
+ const normalized = normalizeSkillSourcePath(sourcePath);
10232
+ const skillPathMatch = normalized.match(SKILL_PATH_RE);
10233
+ if (skillPathMatch?.[1])
10234
+ return skillPathMatch[1];
10235
+ const legacyCommandMatch = normalized.match(LEGACY_COMMAND_PATH_RE);
10236
+ if (legacyCommandMatch?.[1])
10237
+ return legacyCommandMatch[1];
10238
+ return null;
10239
+ }
10240
+ function normalizeSkillEntry(entry, defaultSource = "team") {
10241
+ const sourcePath = normalizeSkillSourcePath(entry.sourcePath ?? entry.path ?? "");
10242
+ const command = entry.command ?? entry.cmd ?? extractSkillCommandFromPath(sourcePath);
10243
+ if (!command)
10244
+ return null;
10245
+ const description = entry.description ?? entry.desc ?? parseSkillDescription(entry.content ?? "");
10246
+ const source = entry.source ?? entry.src ?? defaultSource;
10247
+ const repoRoot = entry.repoRoot ?? (isTeamSkillSource(source) ? deriveSkillRepoRoot(sourcePath) : void 0);
10248
+ const repoLabel = entry.repoLabel ?? (isTeamSkillSource(source) ? deriveSkillRepoLabel(sourcePath) : void 0);
10249
+ return {
10250
+ command,
10251
+ description,
10252
+ source,
10253
+ sourcePath,
10254
+ ...repoRoot ? { repoRoot } : {},
10255
+ ...repoLabel ? { repoLabel } : {}
10256
+ };
10257
+ }
10258
+ function skillPriority(source) {
10259
+ switch (source) {
10260
+ case "team":
10261
+ case "project":
10262
+ return 3;
10263
+ case "global":
10264
+ return 2;
10265
+ case "alan":
10266
+ return 1;
10267
+ }
10268
+ }
10269
+ function dedupeSkillEntries(entries) {
10270
+ const deduped = /* @__PURE__ */ new Map();
10271
+ for (const entry of entries) {
10272
+ const key = skillIdentityKey(entry);
10273
+ const existing = deduped.get(key);
10274
+ if (!existing || skillPriority(entry.source) > skillPriority(existing.source)) {
10275
+ deduped.set(key, entry);
10276
+ }
10277
+ }
10278
+ return Array.from(deduped.values());
10279
+ }
10280
+ function normalizeSkillEntries(entries, defaultSource = "team") {
10281
+ return dedupeSkillEntries(entries.map((entry) => normalizeSkillEntry(entry, defaultSource)).filter((entry) => Boolean(entry)));
10282
+ }
9796
10283
 
9797
10284
  // ../shared/dist/agent/system-prompt.js
9798
10285
  var ALAN_IDENTITY_PROMPT = `You are Alan, the software factory agent. Alan ships software end to end \u2014 plan, write, review, test, deploy.
@@ -10155,13 +10642,78 @@ var GROK_CLI_MODELS = [
10155
10642
  { id: "grok-composer-2.5-fast", name: "Composer 2.5 Fast" }
10156
10643
  ];
10157
10644
  var COPILOT_CLI_MODELS = [
10158
- { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" },
10645
+ { id: "auto", name: "Auto" },
10646
+ {
10647
+ id: "gpt-5.6-sol",
10648
+ name: "GPT-5.6 Sol",
10649
+ capabilities: { contextWindows: [], effortLevels: GPT_5_6_ULTRA_EFFORT_LEVELS }
10650
+ },
10651
+ {
10652
+ id: "gpt-5.6-terra",
10653
+ name: "GPT-5.6 Terra",
10654
+ capabilities: { contextWindows: [], effortLevels: GPT_5_6_ULTRA_EFFORT_LEVELS }
10655
+ },
10656
+ {
10657
+ id: "gpt-5.6-luna",
10658
+ name: "GPT-5.6 Luna",
10659
+ capabilities: { contextWindows: [], effortLevels: GPT_5_6_EFFORT_LEVELS }
10660
+ },
10661
+ {
10662
+ id: "gpt-5.5",
10663
+ name: "GPT-5.5",
10664
+ capabilities: { contextWindows: [], effortLevels: GPT_5_5_EFFORT_LEVELS }
10665
+ },
10666
+ {
10667
+ id: "gpt-5.4",
10668
+ name: "GPT-5.4",
10669
+ capabilities: { contextWindows: [], effortLevels: GPT_5_4_EFFORT_LEVELS }
10670
+ },
10671
+ {
10672
+ id: "gpt-5.3-codex",
10673
+ name: "GPT-5.3 Codex",
10674
+ capabilities: { contextWindows: [], effortLevels: CODEX_5X_EFFORT_LEVELS }
10675
+ },
10676
+ {
10677
+ id: "gpt-5.4-mini",
10678
+ name: "GPT-5.4 Mini",
10679
+ capabilities: { contextWindows: [], effortLevels: GPT_5_4_EFFORT_LEVELS }
10680
+ },
10681
+ { id: "gpt-5-mini", name: "GPT-5 Mini" },
10682
+ {
10683
+ id: "claude-sonnet-5",
10684
+ name: "Claude Sonnet 5",
10685
+ capabilities: { contextWindows: ["200k", "1m"], effortLevels: CLAUDE_EFFORT_LEVELS }
10686
+ },
10159
10687
  { id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6" },
10160
- { id: "claude-haiku-4.5", name: "Claude Haiku 4.5" },
10161
- { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
10162
- { id: "gpt-5.4", name: "GPT-5.4" },
10163
- { id: "gpt-5.4-mini", name: "GPT-5.4 Mini" },
10164
- { id: "auto", name: "Auto" }
10688
+ { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" },
10689
+ {
10690
+ id: "claude-haiku-4.5",
10691
+ name: "Claude Haiku 4.5",
10692
+ capabilities: { contextWindows: ["200k"], effortLevels: [] }
10693
+ },
10694
+ {
10695
+ id: "claude-fable-5",
10696
+ name: "Claude Fable 5",
10697
+ capabilities: { contextWindows: ["1m"], effortLevels: CLAUDE_EFFORT_LEVELS }
10698
+ },
10699
+ {
10700
+ id: "claude-opus-5",
10701
+ name: "Claude Opus 5",
10702
+ capabilities: { contextWindows: ["200k", "1m"], effortLevels: CLAUDE_EFFORT_LEVELS }
10703
+ },
10704
+ {
10705
+ id: "claude-opus-4.8",
10706
+ name: "Claude Opus 4.8",
10707
+ capabilities: { contextWindows: ["200k", "1m"], effortLevels: CLAUDE_EFFORT_LEVELS }
10708
+ },
10709
+ { id: "claude-opus-4.8-fast", name: "Claude Opus 4.8 Fast" },
10710
+ { id: "claude-opus-4.7", name: "Claude Opus 4.7" },
10711
+ { id: "claude-opus-4.6", name: "Claude Opus 4.6" },
10712
+ { id: "claude-opus-4.5", name: "Claude Opus 4.5" },
10713
+ { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
10714
+ { id: "gemini-3.6-flash", name: "Gemini 3.6 Flash" },
10715
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
10716
+ { id: "kimi-k2.7-code", name: "Kimi K2.7 Code" }
10165
10717
  ];
10166
10718
  var OPENCODE_PAID_MODEL = {
10167
10719
  billing: "paid",
@@ -10826,7 +11378,7 @@ var EPIC_STATUS_OPTIONS = [
10826
11378
  value: "canceled",
10827
11379
  label: "Canceled",
10828
11380
  color: CATEGORICAL_COLORS.slateClosed,
10829
- group: "done"
11381
+ group: "closed"
10830
11382
  }
10831
11383
  ];
10832
11384
  var DEFAULT_EPIC_STATUSES = EPIC_STATUS_OPTIONS.map((o) => ({
@@ -11580,7 +12132,12 @@ function normalizeCodexCliErrorMessage(message) {
11580
12132
  if (lower.includes("reconnecting") && lower.includes("request timed out")) {
11581
12133
  return "Codex connection to OpenAI timed out while waiting for the turn to continue. Retry the message; if it repeats, check network/API latency or reduce slow MCP/tool calls in the turn.";
11582
12134
  }
11583
- if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated")) {
12135
+ if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated") || // OpenAI's ChatGPT backend rejects a stale bearer with this pair on both the
12136
+ // HTTP and WSS paths, e.g. "Reconnecting... 2/5 (unexpected status 401
12137
+ // Unauthorized: Provided authentication token is expired. ... auth error
12138
+ // code: token_expired)". Left unmapped these render as a raw retry banner
12139
+ // with a generic "check your API key" hint and no recovery path.
12140
+ lower.includes("token_expired") || lower.includes("provided authentication token is expired")) {
11584
12141
  return "Codex CLI connection expired. Re-authenticate the Codex CLI on this computer (run `codex login`) or reconnect it in Cloud settings, then retry.";
11585
12142
  }
11586
12143
  return message;
@@ -11817,12 +12374,29 @@ function formatCliSpawnError(command, error) {
11817
12374
  `CLI command not found: ${command}. Install it or configure this provider with the full executable path.`
11818
12375
  );
11819
12376
  }
12377
+ if (error.code === "ETXTBSY") {
12378
+ return new Error(
12379
+ `CLI "${command}" is still being written by an installer (ETXTBSY) and did not become runnable in time. Retry in a moment.`
12380
+ );
12381
+ }
11820
12382
  return error;
11821
12383
  }
11822
- function waitForSpawnOutcome(child) {
12384
+ var SPAWN_RETRY_DELAYS_BY_CODE = {
12385
+ ETXTBSY: [250, 750, 1500, 3e3, 5e3],
12386
+ ENOENT: [250, 750]
12387
+ };
12388
+ var delay = (ms) => new Promise((resolve22) => setTimeout(resolve22, ms));
12389
+ var SPAWN_OUTCOME_TIMEOUT_MS = 2e3;
12390
+ function waitForSpawnOutcome(child, timeoutMs = SPAWN_OUTCOME_TIMEOUT_MS) {
11823
12391
  return new Promise((resolve22) => {
11824
- child.once("spawn", () => resolve22(null));
11825
- child.once("error", (error) => resolve22(error));
12392
+ const timer = setTimeout(() => resolve22(null), timeoutMs);
12393
+ timer.unref?.();
12394
+ const settle = (value2) => {
12395
+ clearTimeout(timer);
12396
+ resolve22(value2);
12397
+ };
12398
+ child.once("spawn", () => settle(null));
12399
+ child.once("error", (error) => settle(error));
11826
12400
  });
11827
12401
  }
11828
12402
  function createGenericCliBackend(options) {
@@ -11840,28 +12414,35 @@ function createGenericCliBackend(options) {
11840
12414
  const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
11841
12415
  let command = options.command;
11842
12416
  let child = spawnCli(command, args, context);
11843
- if (context.revalidateCommand) {
11844
- const spawnError = await waitForSpawnOutcome(child);
11845
- if (spawnError) {
11846
- const fresh = spawnError.code === "ENOENT" ? context.revalidateCommand()?.trim() || null : null;
11847
- if (fresh && fresh !== command) {
11848
- console.warn(
11849
- `[${options.kind}] Spawn failed ENOENT for "${command}" \u2014 re-resolved to "${fresh}", retrying once`
11850
- );
11851
- command = fresh;
11852
- child = spawnCli(command, args, context);
11853
- } else {
11854
- throw formatCliSpawnError(command, spawnError);
11855
- }
11856
- }
11857
- }
11858
12417
  state.process = child;
11859
12418
  state.lastRawOutputAtMs = Date.now();
11860
- context.onProcessSpawned?.(child);
11861
12419
  context.registerLivenessProbe?.(() => ({
11862
12420
  providerAlive: child.exitCode === null,
11863
12421
  lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
11864
12422
  }));
12423
+ for (let attempt = 0; ; attempt++) {
12424
+ if (child.pid !== void 0) break;
12425
+ const spawnError = await waitForSpawnOutcome(child);
12426
+ if (!spawnError) break;
12427
+ const delays = SPAWN_RETRY_DELAYS_BY_CODE[spawnError.code ?? ""] ?? [];
12428
+ if (attempt >= delays.length) {
12429
+ throw formatCliSpawnError(command, spawnError);
12430
+ }
12431
+ const waitMs = delays[attempt];
12432
+ console.warn(
12433
+ `[${options.kind}] Spawn of "${command}" failed ${spawnError.code} \u2014 a CLI installer is likely mid-flight; retrying in ${waitMs}ms (attempt ${attempt + 1}/${delays.length})`
12434
+ );
12435
+ await delay(waitMs);
12436
+ const fresh = context.revalidateCommand?.()?.trim() || null;
12437
+ if (fresh && fresh !== command) {
12438
+ console.warn(`[${options.kind}] Re-resolved "${command}" to "${fresh}"`);
12439
+ command = fresh;
12440
+ }
12441
+ child = spawnCli(command, args, context);
12442
+ }
12443
+ state.process = child;
12444
+ state.lastRawOutputAtMs = Date.now();
12445
+ context.onProcessSpawned?.(child);
11865
12446
  const safeArgs = args.map(
11866
12447
  (a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
11867
12448
  );
@@ -12705,7 +13286,11 @@ function buildAntigravityCliArgs(context, prompt, defaultArgs = [], capabilities
12705
13286
  const args = [];
12706
13287
  const selectedModel = context.config.selectedModel?.trim();
12707
13288
  const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
12708
- if (selectedModel) args.push("--model", selectedModel);
13289
+ if (selectedModel) {
13290
+ args.push("--model", selectedModel);
13291
+ const effort = context.config.selectedEffortLevel?.trim().toLowerCase();
13292
+ if (effort && isEffortLevel(effort)) args.push("--effort", effort);
13293
+ }
12709
13294
  if (resumeId) args.push("--conversation", resumeId);
12710
13295
  if (!shouldUseReadOnlyRuntimePermissions(context.config)) {
12711
13296
  args.push("--dangerously-skip-permissions");
@@ -12716,6 +13301,14 @@ function buildAntigravityCliArgs(context, prompt, defaultArgs = [], capabilities
12716
13301
  args.push("--print-timeout", "10m");
12717
13302
  return [...args, ...defaultArgs, "-p", prompt];
12718
13303
  }
13304
+ function buildAntigravityAugmentedPrompt(context, images) {
13305
+ const isResume = Boolean(
13306
+ context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim()
13307
+ );
13308
+ const base = buildResumeAwarePrompt(context.config, context.promptText, { isResume });
13309
+ const prompt = context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
13310
+ return appendImagePathReferences(prompt, images);
13311
+ }
12719
13312
  function createAntigravityCliBackend(command = "agy", defaultArgs = []) {
12720
13313
  return {
12721
13314
  kind: "antigravity_cli",
@@ -12737,7 +13330,7 @@ function createAntigravityCliBackend(command = "agy", defaultArgs = []) {
12737
13330
  command,
12738
13331
  args: [],
12739
13332
  buildArgs: (ctx, prompt) => buildAntigravityCliArgs(ctx, prompt, defaultArgs, capabilities),
12740
- augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
13333
+ augmentPrompt: (ctx) => buildAntigravityAugmentedPrompt(ctx, imageFiles),
12741
13334
  parseStructuredLine: parseAntigravityLine
12742
13335
  }).run(context);
12743
13336
  } finally {
@@ -14904,6 +15497,41 @@ var parseCopilotStructuredLine = createStructuredLineParser(
14904
15497
  "copilot_cli",
14905
15498
  handleCopilotStructuredEvent
14906
15499
  );
15500
+ function buildCopilotCliArgs(context, prompt, defaultArgs = []) {
15501
+ const args = [
15502
+ "--autopilot",
15503
+ "--yolo",
15504
+ "--max-autopilot-continues",
15505
+ "20",
15506
+ "-s",
15507
+ "--stream",
15508
+ "on",
15509
+ "--output-format",
15510
+ "json",
15511
+ // Without this flag, real captured sessions on this machine show every
15512
+ // assistant.message carrying only an opaque, non-decodable `reasoningOpaque`
15513
+ // blob — never `reasoningText` — so parsers/copilot.ts's onThinking() never
15514
+ // fires and the thinking stream never reaches the UI. `--enable-reasoning-summaries`
15515
+ // is documented in `copilot --help` as exactly this: "Request reasoning
15516
+ // summaries for OpenAI models." Always on — it's additive (models/providers
15517
+ // that don't support summaries are unaffected) and this is the "start in
15518
+ // thinking mode by default" behavior other providers already give equivalent
15519
+ // visibility into.
15520
+ "--enable-reasoning-summaries"
15521
+ ];
15522
+ if (shouldUseReadOnlyRuntimePermissions(context.config)) {
15523
+ args.push("--deny-tool", "write");
15524
+ }
15525
+ if (context.config.selectedModel?.trim()) {
15526
+ args.push("--model", context.config.selectedModel.trim());
15527
+ }
15528
+ const effort = context.config.selectedEffortLevel?.trim().toLowerCase();
15529
+ if (effort && isEffortLevel(effort)) {
15530
+ args.push("--effort", effort);
15531
+ }
15532
+ args.push("-p", prompt);
15533
+ return [...args, ...defaultArgs];
15534
+ }
14907
15535
  function createCopilotCliBackend(command = "copilot", defaultArgs = []) {
14908
15536
  return {
14909
15537
  kind: "copilot_cli",
@@ -14919,25 +15547,7 @@ function createCopilotCliBackend(command = "copilot", defaultArgs = []) {
14919
15547
  supportTier: "structured",
14920
15548
  command,
14921
15549
  args: [],
14922
- buildArgs: (ctx, prompt) => {
14923
- const args = [
14924
- "--autopilot",
14925
- "--yolo",
14926
- "--max-autopilot-continues",
14927
- "20",
14928
- "-s",
14929
- "--stream",
14930
- "on",
14931
- "--output-format",
14932
- "json",
14933
- "-p",
14934
- prompt
14935
- ];
14936
- if (ctx.config.selectedModel?.trim()) {
14937
- args.push("--model", ctx.config.selectedModel.trim());
14938
- }
14939
- return [...args, ...defaultArgs];
14940
- },
15550
+ buildArgs: (ctx, prompt) => buildCopilotCliArgs(ctx, prompt, defaultArgs),
14941
15551
  augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
14942
15552
  parseStructuredLine: parseCopilotStructuredLine,
14943
15553
  // Local background Agent/detached work uses the shared result gate; keep
@@ -18194,7 +18804,7 @@ function extractPathCandidates(content, options, extensionsPattern = "png|jpe?g|
18194
18804
  ...options.pathHints ?? []
18195
18805
  ];
18196
18806
  }
18197
- function normalizePath(candidate, cwd) {
18807
+ function normalizePath2(candidate, cwd) {
18198
18808
  let path = candidate.trim().replace(/^file:\/\//, "");
18199
18809
  try {
18200
18810
  path = decodeURI(path);
@@ -18217,7 +18827,7 @@ function extractBrowserMediaPathHints(content, cwd, toolName, options = {}, kind
18217
18827
  return extractPathCandidatesFromValues(
18218
18828
  collectContentStrings(content, options.toolInput),
18219
18829
  extensionsPattern
18220
- ).map((candidate) => normalizePath(candidate, effectiveCwd)).filter((path) => Boolean(path));
18830
+ ).map((candidate) => normalizePath2(candidate, effectiveCwd)).filter((path) => Boolean(path));
18221
18831
  }
18222
18832
  function readImageDimensions2(buffer, mimeType) {
18223
18833
  if (mimeType === "image/png" && buffer.length >= 24 && buffer.toString("ascii", 1, 4) === "PNG") {
@@ -18250,7 +18860,7 @@ function extractGeneratedImagesFromToolResult(toolId, content, cwd, toolName, op
18250
18860
  const seen = /* @__PURE__ */ new Set();
18251
18861
  const effectiveCwd = resolveToolCwd(cwd, options.toolInput);
18252
18862
  for (const candidate of extractPathCandidates(content, options)) {
18253
- const path = normalizePath(candidate, effectiveCwd);
18863
+ const path = normalizePath2(candidate, effectiveCwd);
18254
18864
  if (!path || seen.has(path) || !(0, import_fs9.existsSync)(path)) continue;
18255
18865
  seen.add(path);
18256
18866
  const ext = (0, import_path9.extname)(path).toLowerCase();
@@ -18289,7 +18899,7 @@ function extractSessionMediaFromToolResult(toolId, content, cwd, toolName, optio
18289
18899
  const maxBytes = kind === "video" ? MAX_SESSION_VIDEO_BYTES : MAX_GENERATED_IMAGE_BYTES;
18290
18900
  const mimeByExt = kind === "video" ? VIDEO_MIME_BY_EXT : IMAGE_MIME_BY_EXT;
18291
18901
  for (const candidate of extractPathCandidates(content, options, extensionsPattern)) {
18292
- const path = normalizePath(candidate, effectiveCwd);
18902
+ const path = normalizePath2(candidate, effectiveCwd);
18293
18903
  if (!path || seen.has(path) || !(0, import_fs9.existsSync)(path)) continue;
18294
18904
  seen.add(path);
18295
18905
  const ext = (0, import_path9.extname)(path).toLowerCase();
@@ -18399,18 +19009,18 @@ ${output.stderr}`;
18399
19009
  }
18400
19010
 
18401
19011
  // ../shared/dist/node/local-workspace.js
18402
- var import_node_fs = require("fs");
18403
- var import_node_os = require("os");
18404
- var import_node_path = require("path");
18405
- function resolveAlanDefaultWorkspacePath(homeDirectory = (0, import_node_os.homedir)()) {
18406
- return (0, import_node_path.join)(homeDirectory, ".alan", "workspace");
18407
- }
18408
-
18409
- // ../shared/dist/node/provider-limits.js
18410
- var import_node_child_process = require("child_process");
18411
19012
  var import_node_fs2 = require("fs");
18412
19013
  var import_node_os2 = require("os");
18413
19014
  var import_node_path2 = require("path");
19015
+ function resolveAlanDefaultWorkspacePath(homeDirectory = (0, import_node_os2.homedir)()) {
19016
+ return (0, import_node_path2.join)(homeDirectory, ".alan", "workspace");
19017
+ }
19018
+
19019
+ // ../shared/dist/node/provider-limits.js
19020
+ var import_node_child_process2 = require("child_process");
19021
+ var import_node_fs3 = require("fs");
19022
+ var import_node_os3 = require("os");
19023
+ var import_node_path3 = require("path");
18414
19024
  var CACHE_TTL_MS = 6e4;
18415
19025
  var cachedLimits = null;
18416
19026
  function numericValue(value2) {
@@ -18421,42 +19031,42 @@ function numericValue(value2) {
18421
19031
  }
18422
19032
  return null;
18423
19033
  }
18424
- function splitPath(value2) {
18425
- return (value2 ?? "").split(import_node_path2.delimiter).filter(Boolean);
19034
+ function splitPath2(value2) {
19035
+ return (value2 ?? "").split(import_node_path3.delimiter).filter(Boolean);
18426
19036
  }
18427
19037
  function isExecutableFile(path) {
18428
19038
  try {
18429
- (0, import_node_fs2.accessSync)(path, import_node_fs2.constants.X_OK);
19039
+ (0, import_node_fs3.accessSync)(path, import_node_fs3.constants.X_OK);
18430
19040
  return true;
18431
19041
  } catch {
18432
19042
  return false;
18433
19043
  }
18434
19044
  }
18435
- function augmentCliPath(env) {
18436
- const home = env.HOME || (0, import_node_os2.homedir)();
19045
+ function augmentCliPath2(env) {
19046
+ const home = env.HOME || (0, import_node_os3.homedir)();
18437
19047
  const extraPaths = [
18438
- (0, import_node_path2.join)(home, ".local", "bin"),
18439
- (0, import_node_path2.join)(home, ".local", "node", "bin"),
18440
- (0, import_node_path2.join)(home, ".bun", "bin"),
18441
- (0, import_node_path2.join)(home, ".cargo", "bin"),
19048
+ (0, import_node_path3.join)(home, ".local", "bin"),
19049
+ (0, import_node_path3.join)(home, ".local", "node", "bin"),
19050
+ (0, import_node_path3.join)(home, ".bun", "bin"),
19051
+ (0, import_node_path3.join)(home, ".cargo", "bin"),
18442
19052
  "/opt/homebrew/bin",
18443
19053
  "/usr/local/bin"
18444
19054
  ];
18445
- const existing = new Set(splitPath(env.PATH));
19055
+ const existing = new Set(splitPath2(env.PATH));
18446
19056
  const missing = extraPaths.filter((path) => !existing.has(path));
18447
19057
  if (missing.length === 0)
18448
19058
  return env;
18449
19059
  return {
18450
19060
  ...env,
18451
- PATH: `${missing.join(import_node_path2.delimiter)}${import_node_path2.delimiter}${env.PATH ?? ""}`
19061
+ PATH: `${missing.join(import_node_path3.delimiter)}${import_node_path3.delimiter}${env.PATH ?? ""}`
18452
19062
  };
18453
19063
  }
18454
19064
  function resolveExecutable(command, env, overrideEnvVar) {
18455
19065
  const override = env[overrideEnvVar]?.trim();
18456
19066
  if (override && isExecutableFile(override))
18457
19067
  return override;
18458
- for (const dir of splitPath(env.PATH)) {
18459
- const candidate = (0, import_node_path2.join)(dir, command);
19068
+ for (const dir of splitPath2(env.PATH)) {
19069
+ const candidate = (0, import_node_path3.join)(dir, command);
18460
19070
  if (isExecutableFile(candidate))
18461
19071
  return candidate;
18462
19072
  }
@@ -18528,12 +19138,12 @@ async function withTimeout(promise, timeoutMs, fallback) {
18528
19138
  }
18529
19139
  }
18530
19140
  async function collectCodexLimits(env) {
18531
- const runtimeEnv = augmentCliPath(env);
19141
+ const runtimeEnv = augmentCliPath2(env);
18532
19142
  const codexCommand = resolveExecutable("codex", runtimeEnv, "ALAN_CODEX_PATH");
18533
19143
  if (!codexCommand)
18534
19144
  return [];
18535
19145
  return withTimeout(new Promise((resolve6) => {
18536
- const child = (0, import_node_child_process.spawn)(codexCommand, ["-s", "read-only", "-a", "untrusted", "app-server"], {
19146
+ const child = (0, import_node_child_process2.spawn)(codexCommand, ["-s", "read-only", "-a", "untrusted", "app-server"], {
18537
19147
  env: runtimeEnv,
18538
19148
  stdio: ["pipe", "pipe", "ignore"]
18539
19149
  });
@@ -18600,10 +19210,10 @@ async function collectCodexLimits(env) {
18600
19210
  }), 4e3, []);
18601
19211
  }
18602
19212
  function readClaudeCredentialsPayload() {
18603
- const path = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".claude", ".credentials.json");
18604
- if ((0, import_node_fs2.existsSync)(path)) {
19213
+ const path = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".claude", ".credentials.json");
19214
+ if ((0, import_node_fs3.existsSync)(path)) {
18605
19215
  try {
18606
- return JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
19216
+ return JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
18607
19217
  } catch {
18608
19218
  return null;
18609
19219
  }
@@ -18611,7 +19221,7 @@ function readClaudeCredentialsPayload() {
18611
19221
  if (process.platform !== "darwin")
18612
19222
  return null;
18613
19223
  try {
18614
- const raw = (0, import_node_child_process.execFileSync)("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
19224
+ const raw = (0, import_node_child_process2.execFileSync)("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
18615
19225
  if (!raw)
18616
19226
  return null;
18617
19227
  return JSON.parse(raw);
@@ -20310,11 +20920,11 @@ var SocketWithoutUpgrade = class _SocketWithoutUpgrade extends Emitter {
20310
20920
  */
20311
20921
  _resetPingTimeout() {
20312
20922
  this.clearTimeoutFn(this._pingTimeoutTimer);
20313
- const delay = this._pingInterval + this._pingTimeout;
20314
- this._pingTimeoutTime = Date.now() + delay;
20923
+ const delay2 = this._pingInterval + this._pingTimeout;
20924
+ this._pingTimeoutTime = Date.now() + delay2;
20315
20925
  this._pingTimeoutTimer = this.setTimeoutFn(() => {
20316
20926
  this._onClose("ping timeout");
20317
- }, delay);
20927
+ }, delay2);
20318
20928
  if (this.opts.autoUnref) {
20319
20929
  this._pingTimeoutTimer.unref();
20320
20930
  }
@@ -22295,8 +22905,8 @@ var Manager = class extends Emitter {
22295
22905
  this.emitReserved("reconnect_failed");
22296
22906
  this._reconnecting = false;
22297
22907
  } else {
22298
- const delay = this.backoff.duration();
22299
- debug10("will wait %dms before reconnect attempt", delay);
22908
+ const delay2 = this.backoff.duration();
22909
+ debug10("will wait %dms before reconnect attempt", delay2);
22300
22910
  this._reconnecting = true;
22301
22911
  const timer = this.setTimeoutFn(() => {
22302
22912
  if (self.skipReconnect)
@@ -22316,7 +22926,7 @@ var Manager = class extends Emitter {
22316
22926
  self.onreconnect();
22317
22927
  }
22318
22928
  });
22319
- }, delay);
22929
+ }, delay2);
22320
22930
  if (this.opts.autoUnref) {
22321
22931
  timer.unref();
22322
22932
  }
@@ -22377,159 +22987,48 @@ Object.assign(lookup, {
22377
22987
  connect: lookup
22378
22988
  });
22379
22989
 
22380
- // src/cli-executable.ts
22381
- var import_node_child_process2 = require("child_process");
22382
- var import_node_fs3 = require("fs");
22383
- var import_node_os3 = require("os");
22384
- var import_node_path3 = require("path");
22385
- var PROVIDER_CLI_COMMANDS = {
22386
- claude_cli: { provider: "claude_cli", command: "claude", envVars: ["ALAN_CLAUDE_PATH"] },
22387
- codex_app_server: {
22388
- provider: "codex_app_server",
22389
- command: "codex",
22390
- envVars: ["ALAN_CODEX_PATH"]
22391
- },
22392
- copilot_cli: { provider: "copilot_cli", command: "copilot", envVars: ["ALAN_COPILOT_PATH"] },
22393
- cursor_agent_cli: {
22394
- provider: "cursor_agent_cli",
22395
- command: "cursor-agent",
22396
- envVars: ["ALAN_CURSOR_AGENT_PATH"]
22397
- },
22398
- antigravity_cli: {
22399
- provider: "antigravity_cli",
22400
- command: "agy",
22401
- envVars: ["ALAN_ANTIGRAVITY_PATH"]
22402
- },
22403
- kimi_cli: { provider: "kimi_cli", command: "kimi-cli", envVars: ["ALAN_KIMI_PATH"] },
22404
- grok_cli: { provider: "grok_cli", command: "grok", envVars: ["ALAN_GROK_PATH"] },
22405
- opencode_cli: { provider: "opencode_cli", command: "opencode", envVars: ["ALAN_OPENCODE_PATH"] },
22406
- droid_cli: { provider: "droid_cli", command: "droid", envVars: ["ALAN_DROID_PATH"] },
22407
- supatest_cli: { provider: "supatest_cli", command: "supatest", envVars: ["ALAN_SUPATEST_PATH"] }
22408
- };
22409
- var DISCOVERABLE_PROVIDER_KINDS = Object.keys(PROVIDER_CLI_COMMANDS);
22410
- var PROVIDER_ALIASES = {
22411
- agy: "antigravity_cli",
22412
- antigravity: "antigravity_cli",
22413
- antigravity_cli: "antigravity_cli",
22414
- codex: "codex_app_server",
22415
- gemini: "antigravity_cli",
22416
- gemini_cli: "antigravity_cli"
22417
- };
22418
- var BACKEND_CLI_COMMANDS = {
22419
- claude_cli: "claude",
22420
- codex: "codex",
22421
- codex_app_server: "codex",
22422
- copilot_cli: "copilot",
22423
- cursor_agent_cli: "cursor-agent",
22424
- agy: "agy",
22425
- antigravity: "agy",
22426
- antigravity_cli: "agy",
22427
- gemini_cli: "agy",
22428
- gemini: "agy",
22429
- kimi_cli: "kimi-cli",
22430
- grok_cli: "grok",
22431
- opencode_cli: "opencode",
22432
- droid_cli: "droid",
22433
- supatest_cli: "supatest"
22434
- };
22435
- var CLI_COMMAND_ALIASES = {
22436
- "kimi-cli": ["kimi"],
22437
- grok: ["grok-build"]
22990
+ // src/cli-ensure-install.ts
22991
+ var import_node_child_process3 = require("child_process");
22992
+ 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" }
22438
22999
  };
22439
- var resolvedCliCache = /* @__PURE__ */ new Map();
22440
- var cachedLoginShellEnv = null;
22441
- function parseEnvOutput(output) {
22442
- const loginEnv = {};
22443
- for (const line of output.split("\n")) {
22444
- if (!line.trim()) continue;
22445
- const eqIdx = line.indexOf("=");
22446
- if (eqIdx <= 0) continue;
22447
- const key = line.slice(0, eqIdx);
22448
- if (/\s/.test(key)) continue;
22449
- loginEnv[key] = line.slice(eqIdx + 1);
22450
- }
22451
- if (!loginEnv.HOME) loginEnv.HOME = process.env.HOME ?? (0, import_node_os3.homedir)();
22452
- if (!loginEnv.SHELL) loginEnv.SHELL = process.env.SHELL ?? "/bin/zsh";
22453
- return loginEnv;
22454
- }
22455
- function loadLoginShellEnvironment() {
22456
- if (cachedLoginShellEnv) return cachedLoginShellEnv;
22457
- if ((0, import_node_os3.platform)() === "win32") {
22458
- cachedLoginShellEnv = process.env;
22459
- return cachedLoginShellEnv;
22460
- }
22461
- try {
22462
- const shell = (process.env.SHELL || "/bin/bash").replace(/'/g, "'\\''");
22463
- const envOutput = (0, import_node_child_process2.execSync)(`'${shell}' -ilc 'env'`, {
22464
- encoding: "utf8",
22465
- stdio: ["pipe", "pipe", "pipe"],
22466
- timeout: 5e3
22467
- });
22468
- cachedLoginShellEnv = parseEnvOutput(envOutput);
22469
- } catch {
22470
- cachedLoginShellEnv = process.env;
22471
- }
22472
- return cachedLoginShellEnv;
22473
- }
22474
- function getDaemonCliEnvironment() {
22475
- const base = (0, import_node_os3.platform)() === "win32" ? process.env : loadLoginShellEnvironment();
22476
- return augmentCliPath2(base);
22477
- }
22478
- function cacheResolvedCli(command, resolvedPath) {
22479
- resolvedCliCache.set(command, resolvedPath);
22480
- }
22481
- function getCachedResolvedCli(command) {
22482
- return resolvedCliCache.get(command);
22483
- }
22484
- function resolveViaWhere(command, env) {
22485
- if ((0, import_node_os3.platform)() !== "win32") return null;
22486
- const whereExe = (0, import_node_path3.join)(
22487
- env.SystemRoot ?? process.env.SystemRoot ?? "C:\\Windows",
22488
- "System32",
22489
- "where.exe"
22490
- );
22491
- if (!(0, import_node_fs3.existsSync)(whereExe)) return null;
22492
- const result = (0, import_node_child_process2.spawnSync)(whereExe, [command], {
22493
- encoding: "utf8",
22494
- env,
22495
- windowsHide: true,
22496
- timeout: 3e3
22497
- });
22498
- if (result.error || result.status !== 0) return null;
22499
- for (const line of result.stdout.split(/\r?\n/)) {
22500
- const trimmed = line.trim();
22501
- if (!trimmed) continue;
22502
- if (isRunnableFile(trimmed)) return trimmed;
22503
- }
22504
- return null;
22505
- }
22506
- function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3, spawnOptions) {
22507
- return new Promise((resolve6) => {
22508
- const isWin = (0, import_node_os3.platform)() === "win32";
22509
- let command = executable;
22510
- let commandArgs = args;
22511
- if (isWin && /\.(cmd|bat)$/i.test(executable)) {
22512
- command = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
22513
- commandArgs = ["/d", "/s", "/c", executable, ...args];
22514
- }
22515
- const useShell = isWin && command === executable && !/[\\/]/.test(executable);
22516
- const closeStdin = spawnOptions?.closeStdin === true;
23000
+ var INSTALL_TIMEOUT_MS = 12e4;
23001
+ var installOutcomes = /* @__PURE__ */ new Map();
23002
+ function canInstallProviderCli(backendKind) {
23003
+ return Boolean(backendKind && INSTALLABLE_PROVIDER_PACKAGES[backendKind]);
23004
+ }
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 {
23013
+ }
23014
+ return "latest";
23015
+ }
23016
+ function runNpmInstall(installSpec, env, timeoutMs) {
23017
+ return new Promise((resolve6) => {
23018
+ const isWin = (0, import_node_os4.platform)() === "win32";
22517
23019
  let child;
22518
23020
  try {
22519
- child = (0, import_node_child_process2.spawn)(command, commandArgs, {
23021
+ child = (0, import_node_child_process3.spawn)("npm", ["install", "-g", installSpec], {
22520
23022
  env,
22521
- shell: useShell,
23023
+ shell: isWin,
22522
23024
  windowsHide: isWin ? true : void 0,
22523
23025
  timeout: timeoutMs,
22524
- stdio: closeStdin ? ["pipe", "pipe", "pipe"] : void 0
23026
+ stdio: ["ignore", "pipe", "pipe"]
22525
23027
  });
22526
23028
  } catch (error) {
22527
23029
  resolve6({ status: null, stdout: "", stderr: "", error });
22528
23030
  return;
22529
23031
  }
22530
- if (closeStdin) {
22531
- child.stdin?.end();
22532
- }
22533
23032
  let stdout = "";
22534
23033
  let stderr = "";
22535
23034
  child.stdout?.setEncoding("utf8").on("data", (chunk) => {
@@ -22541,159 +23040,48 @@ function runCliProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3
22541
23040
  child.once("error", (error) => resolve6({ status: null, stdout, stderr, error }));
22542
23041
  child.once(
22543
23042
  "close",
22544
- (status, signal) => resolve6({ status, stdout, stderr, signal: signal ?? null })
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
+ })
22545
23051
  );
22546
23052
  });
22547
23053
  }
22548
- function runCliVersionProbeAsync(executable, env, args = ["--version"], timeoutMs = 3e3) {
22549
- return runCliProbeAsync(executable, env, args, timeoutMs);
22550
- }
22551
- function pathSeparator() {
22552
- return (0, import_node_os3.platform)() === "win32" ? ";" : ":";
22553
- }
22554
- function splitPath2(pathValue) {
22555
- if (!pathValue) return [];
22556
- return pathValue.split(pathSeparator()).filter(Boolean);
22557
- }
22558
- function isRunnableFile(path) {
22559
- if (!(0, import_node_fs3.existsSync)(path)) return false;
22560
- if ((0, import_node_os3.platform)() === "win32") return true;
22561
- try {
22562
- (0, import_node_fs3.accessSync)(path, import_node_fs3.constants.X_OK);
22563
- return true;
22564
- } catch {
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}).`);
22565
23067
  return false;
22566
23068
  }
23069
+ invalidateResolvedCli(spec.command);
23070
+ notify(`Installed ${spec.label} CLI (${installSpec}).`);
23071
+ return true;
22567
23072
  }
22568
- function windowsCommandCandidates(command, pathExt) {
22569
- const trimmed = command.trim();
22570
- if (!trimmed) return [];
22571
- const hasExtension = /\.[a-z0-9]+$/i.test(trimmed);
22572
- if (hasExtension) return [trimmed];
22573
- const extensions = pathExt.split(";").map((ext) => ext.trim()).filter(Boolean);
22574
- return [trimmed, ...extensions.map((ext) => `${trimmed}${ext}`)];
22575
- }
22576
- function augmentCliPath2(env) {
22577
- const home = env.HOME || env.USERPROFILE || (0, import_node_os3.homedir)();
22578
- const isWin = (0, import_node_os3.platform)() === "win32";
22579
- const extraPaths = isWin ? [
22580
- (0, import_node_path3.join)(home, "AppData", "Roaming", "npm"),
22581
- (0, import_node_path3.join)(home, "AppData", "Local", "Programs", "Microsoft", "WindowsApps"),
22582
- (0, import_node_path3.join)(home, ".local", "bin"),
22583
- "C:\\Program Files\\nodejs",
22584
- "C:\\Program Files\\Git\\cmd"
22585
- ] : [
22586
- (0, import_node_path3.join)(home, ".local", "bin"),
22587
- (0, import_node_path3.join)(home, ".local", "node", "bin"),
22588
- (0, import_node_path3.join)(home, ".bun", "bin"),
22589
- (0, import_node_path3.join)(home, ".cargo", "bin"),
22590
- "/opt/homebrew/bin",
22591
- "/usr/local/bin"
22592
- ];
22593
- const basePath = env.PATH ?? "";
22594
- const existing = new Set(splitPath2(basePath));
22595
- const missing = extraPaths.filter((dir) => !existing.has(dir));
22596
- if (missing.length === 0) return env;
22597
- return {
22598
- ...env,
22599
- PATH: missing.length > 0 ? `${missing.join(pathSeparator())}${pathSeparator()}${basePath}` : basePath
22600
- };
22601
- }
22602
- function resolveCliExecutable(command, env = getDaemonCliEnvironment()) {
22603
- const trimmed = command.trim();
22604
- if (!trimmed) return null;
22605
- const cached = resolvedCliCache.get(trimmed);
22606
- if (cached === null) return null;
22607
- if (cached && isRunnableFile(cached)) return cached;
22608
- if ((trimmed.includes("/") || trimmed.includes("\\")) && isRunnableFile(trimmed)) {
22609
- resolvedCliCache.set(trimmed, trimmed);
22610
- return trimmed;
22611
- }
22612
- const enriched = augmentCliPath2(env);
22613
- const home = enriched.HOME || enriched.USERPROFILE || (0, import_node_os3.homedir)();
22614
- const isWin = (0, import_node_os3.platform)() === "win32";
22615
- const pathExt = enriched.PATHEXT ?? (isWin ? ".EXE;.CMD;.BAT;.COM" : "");
22616
- const commandNames = [trimmed, ...CLI_COMMAND_ALIASES[trimmed] ?? []];
22617
- const names = commandNames.flatMap(
22618
- (name) => isWin ? windowsCommandCandidates(name, pathExt) : [name]
22619
- );
22620
- const candidates = [];
22621
- for (const name of names) {
22622
- if (name.includes("/") || name.includes("\\")) {
22623
- candidates.push(name);
22624
- continue;
22625
- }
22626
- for (const dir of splitPath2(enriched.PATH)) {
22627
- candidates.push((0, import_node_path3.join)(dir, name));
22628
- }
22629
- candidates.push((0, import_node_path3.join)(home, ".local", "bin", name), (0, import_node_path3.join)(home, ".local", "node", "bin", name));
22630
- if (!isWin) {
22631
- candidates.push((0, import_node_path3.join)("/opt/homebrew/bin", name), (0, import_node_path3.join)("/usr/local/bin", name));
22632
- } else {
22633
- candidates.push((0, import_node_path3.join)(home, "AppData", "Roaming", "npm", name));
22634
- }
22635
- }
22636
- const seen = /* @__PURE__ */ new Set();
22637
- for (const candidate of candidates) {
22638
- if (seen.has(candidate)) continue;
22639
- seen.add(candidate);
22640
- if (isRunnableFile(candidate)) {
22641
- resolvedCliCache.set(trimmed, candidate);
22642
- return candidate;
22643
- }
22644
- }
22645
- const viaWhere = resolveViaWhere(trimmed, enriched);
22646
- if (viaWhere) {
22647
- resolvedCliCache.set(trimmed, viaWhere);
22648
- return viaWhere;
22649
- }
22650
- resolvedCliCache.set(trimmed, null);
22651
- return null;
22652
- }
22653
- function normalizeDiscoverableProvider(provider) {
22654
- const alias = PROVIDER_ALIASES[provider];
22655
- if (alias) return alias;
22656
- if (provider in PROVIDER_CLI_COMMANDS) {
22657
- return provider;
22658
- }
22659
- return null;
22660
- }
22661
- function resolveProviderCliCommand(provider, env = getDaemonCliEnvironment()) {
22662
- const normalized = normalizeDiscoverableProvider(provider);
22663
- if (!normalized) return null;
22664
- const spec = PROVIDER_CLI_COMMANDS[normalized];
22665
- const override = spec.envVars.map((name) => process.env[name]?.trim() || env[name]?.trim()).find(Boolean);
22666
- if (override) {
22667
- const resolved2 = resolveCliExecutable(override, env) ?? (isRunnableFile(override) ? override : null);
22668
- if (resolved2) cacheResolvedCli(spec.command, resolved2);
22669
- return resolved2;
22670
- }
22671
- const resolved = resolveCliExecutable(spec.command, env);
22672
- if (resolved) cacheResolvedCli(spec.command, resolved);
22673
- return resolved;
22674
- }
22675
- function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
22676
- if (!backendKind) return void 0;
22677
- const command = BACKEND_CLI_COMMANDS[backendKind];
22678
- if (!command) return void 0;
22679
- const cached = getCachedResolvedCli(command);
22680
- if (cached === null) return void 0;
22681
- if (cached && isRunnableFile(cached)) return cached;
22682
- const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
22683
- const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
22684
- const resolved = resolveCliExecutable(envOverride?.trim() || command, env);
22685
- cacheResolvedCli(command, resolved);
22686
- return resolved ?? void 0;
22687
- }
22688
- function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
22689
- if (!backendKind) return void 0;
22690
- const command = BACKEND_CLI_COMMANDS[backendKind];
22691
- if (!command) return void 0;
22692
- resolvedCliCache.delete(command);
22693
- const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
22694
- const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
22695
- if (envOverride) resolvedCliCache.delete(envOverride);
22696
- return resolveBackendRuntimeCommand(backendKind, env);
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;
22697
23085
  }
22698
23086
 
22699
23087
  // src/computer-readiness.ts
@@ -22701,10 +23089,10 @@ var import_node_fs5 = require("fs");
22701
23089
 
22702
23090
  // src/runtime-config.ts
22703
23091
  var import_node_fs4 = require("fs");
22704
- var import_node_os4 = require("os");
23092
+ var import_node_os5 = require("os");
22705
23093
  var import_node_path4 = require("path");
22706
- var DEFAULT_AGENT_DIR = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".alan", "agent");
22707
- var LEGACY_AGENT_DIR = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".aiden", "agent");
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");
22708
23096
  var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Map([
22709
23097
  ["apiUrl", "string"],
22710
23098
  ["wsUrl", "string"],
@@ -23012,7 +23400,7 @@ function buildComputerReadiness(input) {
23012
23400
  }
23013
23401
 
23014
23402
  // src/core-agent.ts
23015
- var import_node_os5 = require("os");
23403
+ var import_node_os6 = require("os");
23016
23404
  var CoreAgent = class _CoreAgent extends BaseMachineAgent {
23017
23405
  childProcess = null;
23018
23406
  _aborted = false;
@@ -23064,7 +23452,18 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
23064
23452
  * and re-resolving once lets the current run recover against the new location.
23065
23453
  */
23066
23454
  revalidateRuntimeCommand() {
23067
- return revalidateBackendRuntimeCommand(this.runtime.backendKind) ?? null;
23455
+ const resolved = revalidateBackendRuntimeCommand(this.runtime.backendKind) ?? null;
23456
+ if (!resolved && this.runtime.backendKind) {
23457
+ const backendKind = this.runtime.backendKind;
23458
+ void ensureProviderCliInstalled(backendKind).then((installed) => {
23459
+ if (installed) {
23460
+ console.info("[CoreAgent] Installed missing provider CLI after spawn ENOENT", {
23461
+ backendKind
23462
+ });
23463
+ }
23464
+ });
23465
+ }
23466
+ return resolved;
23068
23467
  }
23069
23468
  // ── Interactive tool response (WS relay → stdin) ───────────────────────
23070
23469
  /**
@@ -23151,7 +23550,7 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
23151
23550
  if (this.runtime.backendKind === "grok_cli" && this.providerApiKey) {
23152
23551
  augmented.XAI_API_KEY = this.providerApiKey;
23153
23552
  }
23154
- if ((0, import_node_os5.platform)() !== "win32") {
23553
+ if ((0, import_node_os6.platform)() !== "win32") {
23155
23554
  const home = augmented.HOME ?? "/home/user";
23156
23555
  const extraPaths = [`${home}/.local/node/bin`, `${home}/.local/bin`];
23157
23556
  const separator = ":";
@@ -23706,11 +24105,97 @@ var EncryptedEventOutbox = class _EncryptedEventOutbox {
23706
24105
  }
23707
24106
  };
23708
24107
 
23709
- // src/mcp-registration.ts
23710
- var import_node_crypto2 = require("crypto");
24108
+ // src/local-skill-scan.ts
23711
24109
  var import_node_fs7 = require("fs");
23712
- var import_node_os6 = require("os");
23713
24110
  var import_node_path6 = require("path");
24111
+ var SKILL_READ_LIMIT_BYTES = 8192;
24112
+ var SKILL_CACHE_TTL_MS = 3e4;
24113
+ function readSkillHead(path) {
24114
+ const fd = (0, import_node_fs7.openSync)(path, "r");
24115
+ try {
24116
+ const buffer = Buffer.alloc(SKILL_READ_LIMIT_BYTES);
24117
+ const bytesRead = (0, import_node_fs7.readSync)(fd, buffer, 0, SKILL_READ_LIMIT_BYTES, 0);
24118
+ return buffer.subarray(0, bytesRead).toString("utf-8");
24119
+ } finally {
24120
+ (0, import_node_fs7.closeSync)(fd);
24121
+ }
24122
+ }
24123
+ function scanSkillDir(dirPath, source, repoRoot, entries, errors) {
24124
+ if (!(0, import_node_fs7.existsSync)(dirPath)) return;
24125
+ let names;
24126
+ try {
24127
+ names = (0, import_node_fs7.readdirSync)(dirPath);
24128
+ } catch (error) {
24129
+ errors.push(`Failed to scan ${dirPath}: ${error instanceof Error ? error.message : error}`);
24130
+ return;
24131
+ }
24132
+ for (const name of names) {
24133
+ const skillPath = (0, import_node_path6.join)(dirPath, name, "SKILL.md");
24134
+ let isDirectory = false;
24135
+ try {
24136
+ isDirectory = (0, import_node_fs7.statSync)((0, import_node_path6.join)(dirPath, name)).isDirectory();
24137
+ } catch {
24138
+ continue;
24139
+ }
24140
+ if (!isDirectory || !(0, import_node_fs7.existsSync)(skillPath)) continue;
24141
+ try {
24142
+ entries.push({ content: readSkillHead(skillPath), source, sourcePath: skillPath, repoRoot });
24143
+ } catch (error) {
24144
+ errors.push(`Failed to read ${skillPath}: ${error instanceof Error ? error.message : error}`);
24145
+ }
24146
+ }
24147
+ }
24148
+ function discoverChildRepos(parentPath) {
24149
+ let names;
24150
+ try {
24151
+ names = (0, import_node_fs7.readdirSync)(parentPath);
24152
+ } catch {
24153
+ return [];
24154
+ }
24155
+ const children = [];
24156
+ for (const name of names) {
24157
+ if (name.startsWith(".")) continue;
24158
+ const childPath = (0, import_node_path6.join)(parentPath, name);
24159
+ try {
24160
+ if ((0, import_node_fs7.statSync)(childPath).isDirectory()) children.push(childPath);
24161
+ } catch {
24162
+ }
24163
+ }
24164
+ return children;
24165
+ }
24166
+ function scanLocalSkills(homeDir, projectPath) {
24167
+ const rawEntries = [];
24168
+ const errors = [];
24169
+ for (const root of SKILL_DIRECTORY_ROOTS) {
24170
+ scanSkillDir((0, import_node_path6.join)(homeDir, root), "global", void 0, rawEntries, errors);
24171
+ }
24172
+ if (projectPath) {
24173
+ for (const dir of [projectPath, ...discoverChildRepos(projectPath)]) {
24174
+ for (const root of SKILL_DIRECTORY_ROOTS) {
24175
+ scanSkillDir((0, import_node_path6.join)(dir, root), "team", dir, rawEntries, errors);
24176
+ }
24177
+ }
24178
+ }
24179
+ return { skills: normalizeSkillEntries(rawEntries, "team"), errors };
24180
+ }
24181
+ var skillScanCache = /* @__PURE__ */ new Map();
24182
+ function scanLocalSkillsCached(homeDir, projectPath, options) {
24183
+ const key = projectPath ?? "";
24184
+ const now = Date.now();
24185
+ if (!options?.refresh) {
24186
+ const cached = skillScanCache.get(key);
24187
+ if (cached && cached.expiresAt > now) return cached.result;
24188
+ }
24189
+ const result = scanLocalSkills(homeDir, projectPath);
24190
+ skillScanCache.set(key, { expiresAt: now + SKILL_CACHE_TTL_MS, result });
24191
+ return result;
24192
+ }
24193
+
24194
+ // 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");
23714
24199
 
23715
24200
  // ../../node_modules/smol-toml/dist/error.js
23716
24201
  function getLineColFromPtr(string, ptr) {
@@ -24507,18 +24992,18 @@ var inspectAlanMcpThroughProviderCli = async (backendKind, expectedUrl, home) =>
24507
24992
  return result;
24508
24993
  };
24509
24994
  async function waitForConfigLock(lockPath) {
24510
- (0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(lockPath), { recursive: true });
24995
+ (0, import_node_fs8.mkdirSync)((0, import_node_path7.dirname)(lockPath), { recursive: true });
24511
24996
  const startedAt = Date.now();
24512
24997
  while (true) {
24513
24998
  try {
24514
- (0, import_node_fs7.mkdirSync)(lockPath);
24515
- return () => (0, import_node_fs7.rmSync)(lockPath, { recursive: true, force: true });
24999
+ (0, import_node_fs8.mkdirSync)(lockPath);
25000
+ return () => (0, import_node_fs8.rmSync)(lockPath, { recursive: true, force: true });
24516
25001
  } catch (error) {
24517
25002
  const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
24518
25003
  if (code !== "EEXIST") throw error;
24519
25004
  try {
24520
- if (Date.now() - (0, import_node_fs7.statSync)(lockPath).mtimeMs > CONFIG_LOCK_STALE_MS) {
24521
- (0, import_node_fs7.rmSync)(lockPath, { recursive: true, force: true });
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 });
24522
25007
  continue;
24523
25008
  }
24524
25009
  } catch {
@@ -24534,7 +25019,7 @@ async function waitForConfigLock(lockPath) {
24534
25019
  function saveMalformedBackup(path, content) {
24535
25020
  const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 12);
24536
25021
  const backupPath = `${path}.alan-backup-${hash}`;
24537
- if (!(0, import_node_fs7.existsSync)(backupPath)) (0, import_node_fs7.copyFileSync)(path, backupPath);
25022
+ if (!(0, import_node_fs8.existsSync)(backupPath)) (0, import_node_fs8.copyFileSync)(path, backupPath);
24538
25023
  return backupPath;
24539
25024
  }
24540
25025
  function parseJsonObject2(path, content) {
@@ -24581,22 +25066,22 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
24581
25066
  function atomicWriteManagedConfig(path, content) {
24582
25067
  const pendingPath = `${path}.alan-pending-${process.pid}-${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
24583
25068
  try {
24584
- (0, import_node_fs7.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
24585
- (0, import_node_fs7.renameSync)(pendingPath, path);
25069
+ (0, import_node_fs8.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
25070
+ (0, import_node_fs8.renameSync)(pendingPath, path);
24586
25071
  } finally {
24587
- (0, import_node_fs7.rmSync)(pendingPath, { force: true });
25072
+ (0, import_node_fs8.rmSync)(pendingPath, { force: true });
24588
25073
  }
24589
25074
  }
24590
- async function ensureAlanMcpRegistered(backendKind, registration, home = (0, import_node_os6.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
25075
+ async function ensureAlanMcpRegistered(backendKind, registration, home = (0, import_node_os7.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
24591
25076
  try {
24592
25077
  const mcpServers = buildAlanMcpServers(registration);
24593
25078
  const files = alanMcpConfigFilesForBackend(backendKind, mcpServers, home);
24594
25079
  const paths = [];
24595
25080
  for (const file of files) {
24596
- (0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(file.path), { recursive: true });
25081
+ (0, import_node_fs8.mkdirSync)((0, import_node_path7.dirname)(file.path), { recursive: true });
24597
25082
  const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
24598
25083
  try {
24599
- const current = (0, import_node_fs7.existsSync)(file.path) ? (0, import_node_fs7.readFileSync)(file.path, "utf8") : null;
25084
+ const current = (0, import_node_fs8.existsSync)(file.path) ? (0, import_node_fs8.readFileSync)(file.path, "utf8") : null;
24600
25085
  const content = file.path.endsWith(".toml") ? mergeCodexAlanSection(file.path, current, file.content) : mergeJsonConfig(file.path, current, file.content);
24601
25086
  if (current !== content) atomicWriteManagedConfig(file.path, content);
24602
25087
  paths.push(file.path);
@@ -24647,13 +25132,13 @@ async function ensureAlanMcpRegistered(backendKind, registration, home = (0, imp
24647
25132
  return { ok: false, paths: [], error: err instanceof Error ? err.message : String(err) };
24648
25133
  }
24649
25134
  }
24650
- async function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os6.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
25135
+ async function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os7.homedir)(), inspectCli = inspectAlanMcpThroughProviderCli) {
24651
25136
  const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
24652
- const missing = files.map((f) => f.path).filter((p) => !(0, import_node_fs7.existsSync)(p));
25137
+ const missing = files.map((f) => f.path).filter((p) => !(0, import_node_fs8.existsSync)(p));
24653
25138
  const presentEntries = files.map((file) => ({
24654
25139
  path: file.path,
24655
- url: (0, import_node_fs7.existsSync)(file.path) ? readUsableAlanMcpUrl(file.path) : null
24656
- })).filter((entry) => (0, import_node_fs7.existsSync)(entry.path));
25140
+ url: (0, import_node_fs8.existsSync)(file.path) ? readUsableAlanMcpUrl(file.path) : null
25141
+ })).filter((entry) => (0, import_node_fs8.existsSync)(entry.path));
24657
25142
  let invalid = presentEntries.filter((entry) => !entry.url).map((entry) => entry.path);
24658
25143
  const urls = new Set(presentEntries.flatMap((entry) => entry.url ? [entry.url] : []));
24659
25144
  if (urls.size > 1) invalid = presentEntries.map((entry) => entry.path);
@@ -24709,15 +25194,15 @@ function removeAlanFromJsonConfig(path, existingContent) {
24709
25194
  }
24710
25195
  return JSON.stringify(config);
24711
25196
  }
24712
- async function unregisterAlanMcp(backendKind, home = (0, import_node_os6.homedir)()) {
25197
+ async function unregisterAlanMcp(backendKind, home = (0, import_node_os7.homedir)()) {
24713
25198
  const removedPaths = [];
24714
25199
  const errors = [];
24715
25200
  const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
24716
25201
  for (const file of files) {
24717
- if (!(0, import_node_fs7.existsSync)(file.path)) continue;
25202
+ if (!(0, import_node_fs8.existsSync)(file.path)) continue;
24718
25203
  const releaseLock = await waitForConfigLock(`${file.path}.alan-lock`);
24719
25204
  try {
24720
- const current = (0, import_node_fs7.readFileSync)(file.path, "utf8");
25205
+ const current = (0, import_node_fs8.readFileSync)(file.path, "utf8");
24721
25206
  let next;
24722
25207
  if (file.path.endsWith(".toml")) {
24723
25208
  const stripped = stripCodexAlanMcpSection(current);
@@ -24748,7 +25233,7 @@ function isHttpUrl(value2) {
24748
25233
  }
24749
25234
  }
24750
25235
  function readUsableAlanMcpUrl(path) {
24751
- const content = (0, import_node_fs7.readFileSync)(path, "utf8");
25236
+ const content = (0, import_node_fs8.readFileSync)(path, "utf8");
24752
25237
  if (path.endsWith(".toml")) {
24753
25238
  const lines = content.split("\n");
24754
25239
  const start = lines.findIndex((line) => line.trim() === "[mcp_servers.alan]");
@@ -25039,7 +25524,7 @@ ${result.stderr}`.trim();
25039
25524
  }
25040
25525
 
25041
25526
  // src/provider-model-discovery.ts
25042
- var import_node_fs8 = require("fs");
25527
+ var import_node_fs9 = require("fs");
25043
25528
  var MODEL_DISCOVERY_TIMEOUT_MS = 8e3;
25044
25529
  var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
25045
25530
  var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
@@ -25253,7 +25738,7 @@ function filterClaudeDatedModelIds(ids) {
25253
25738
  }
25254
25739
  function resolveExecutableForModelScan(executable) {
25255
25740
  try {
25256
- return (0, import_node_fs8.realpathSync)(executable);
25741
+ return (0, import_node_fs9.realpathSync)(executable);
25257
25742
  } catch {
25258
25743
  return executable;
25259
25744
  }
@@ -25261,7 +25746,7 @@ function resolveExecutableForModelScan(executable) {
25261
25746
  function extractClaudeModelIdsFromExecutable(executable) {
25262
25747
  try {
25263
25748
  const resolved = resolveExecutableForModelScan(executable);
25264
- const text = (0, import_node_fs8.readFileSync)(resolved).toString("latin1");
25749
+ const text = (0, import_node_fs9.readFileSync)(resolved).toString("latin1");
25265
25750
  const matches = text.match(CLAUDE_MODEL_ID_PATTERN) ?? [];
25266
25751
  const unique = [
25267
25752
  ...new Set(matches.map((id) => id.trim()).filter((id) => isValidClaudeModelId(id))),
@@ -25361,7 +25846,7 @@ async function discoverGenericModels(executable, env) {
25361
25846
  ["models"]
25362
25847
  ]);
25363
25848
  }
25364
- var CATALOG_ONLY_PROVIDERS = /* @__PURE__ */ new Set(["supatest_cli", "droid_cli"]);
25849
+ var CATALOG_ONLY_PROVIDERS = /* @__PURE__ */ new Set(["supatest_cli", "droid_cli", "copilot_cli"]);
25365
25850
  function supportsProviderModelDiscovery(provider) {
25366
25851
  return provider in CATALOG_MODEL_IDS_BY_PROVIDER;
25367
25852
  }
@@ -25380,7 +25865,7 @@ async function discoverProviderModels(provider, executable, env) {
25380
25865
  probed = await discoverCodexModels(executable, env);
25381
25866
  } else if (provider === "claude_cli") {
25382
25867
  probed = await discoverClaudeModels(executable, env);
25383
- } else if (provider === "kimi_cli" || provider === "grok_cli" || provider === "copilot_cli" || provider === "droid_cli") {
25868
+ } else if (provider === "kimi_cli" || provider === "grok_cli") {
25384
25869
  probed = await discoverGenericModels(executable, env);
25385
25870
  }
25386
25871
  if (probed.length > 0) return probed;
@@ -25388,8 +25873,8 @@ async function discoverProviderModels(provider, executable, env) {
25388
25873
  }
25389
25874
 
25390
25875
  // src/run-journal.ts
25391
- var import_node_fs9 = require("fs");
25392
- var import_node_path7 = require("path");
25876
+ var import_node_fs10 = require("fs");
25877
+ var import_node_path8 = require("path");
25393
25878
  var JOURNAL_VERSION = 1;
25394
25879
  var INTERRUPTED_ENTRY_TTL_MS = 24 * 60 * 6e4;
25395
25880
  function isEntry(value2) {
@@ -25408,9 +25893,9 @@ function isEntry(value2) {
25408
25893
  var RuntimeRunJournal = class {
25409
25894
  constructor(path, nowMs = Date.now()) {
25410
25895
  this.path = path;
25411
- if (!(0, import_node_fs9.existsSync)(path)) return;
25896
+ if (!(0, import_node_fs10.existsSync)(path)) return;
25412
25897
  try {
25413
- const parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
25898
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
25414
25899
  if (parsed.version !== JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
25415
25900
  throw new Error("unsupported run journal format");
25416
25901
  }
@@ -25533,18 +26018,18 @@ var RuntimeRunJournal = class {
25533
26018
  }
25534
26019
  persist() {
25535
26020
  const pendingPath = `${this.path}.pending`;
25536
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.dirname)(this.path), { recursive: true });
26021
+ (0, import_node_fs10.mkdirSync)((0, import_node_path8.dirname)(this.path), { recursive: true });
25537
26022
  try {
25538
- (0, import_node_fs9.writeFileSync)(
26023
+ (0, import_node_fs10.writeFileSync)(
25539
26024
  pendingPath,
25540
26025
  `${JSON.stringify({ version: JOURNAL_VERSION, entries: [...this.entries.values()] })}
25541
26026
  `,
25542
26027
  { mode: 384 }
25543
26028
  );
25544
- (0, import_node_fs9.chmodSync)(pendingPath, 384);
25545
- (0, import_node_fs9.renameSync)(pendingPath, this.path);
26029
+ (0, import_node_fs10.chmodSync)(pendingPath, 384);
26030
+ (0, import_node_fs10.renameSync)(pendingPath, this.path);
25546
26031
  } catch (error) {
25547
- (0, import_node_fs9.rmSync)(pendingPath, { force: true });
26032
+ (0, import_node_fs10.rmSync)(pendingPath, { force: true });
25548
26033
  throw error;
25549
26034
  }
25550
26035
  }
@@ -25571,17 +26056,17 @@ var RunStartGate = class {
25571
26056
  };
25572
26057
 
25573
26058
  // src/version.ts
25574
- var AGENT_VERSION = "0.1.48";
26059
+ var AGENT_VERSION = "0.1.50";
25575
26060
 
25576
26061
  // src/workspace-relocation.ts
25577
- var import_node_child_process3 = require("child_process");
25578
- var import_node_fs10 = require("fs");
25579
- var import_node_path8 = require("path");
26062
+ var import_node_child_process4 = require("child_process");
26063
+ var import_node_fs11 = require("fs");
26064
+ var import_node_path9 = require("path");
25580
26065
  var MAX_RELOCATION_CANDIDATES = 200;
25581
26066
  var DEFAULT_PROBE = {
25582
- childDirectories: (parentPath, limit) => (0, import_node_fs10.readdirSync)(parentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).slice(0, limit).map((entry) => (0, import_node_path8.join)(parentPath, entry.name)),
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)),
25583
26068
  gitRemoteUrl: (candidatePath) => {
25584
- const result = (0, import_node_child_process3.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
26069
+ const result = (0, import_node_child_process4.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
25585
26070
  encoding: "utf8",
25586
26071
  env: getDaemonCliEnvironment(),
25587
26072
  timeout: 2e3
@@ -25610,7 +26095,7 @@ function workspaceMatchesExpectedRepositories(input) {
25610
26095
  input.expectedRepoUrls.map(normalizeRepositoryIdentity).filter((identity2) => Boolean(identity2))
25611
26096
  );
25612
26097
  if (identities.size === 0) return false;
25613
- const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0, import_node_path8.resolve)(input.workspacePath));
26098
+ const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl((0, import_node_path9.resolve)(input.workspacePath));
25614
26099
  const identity = remote ? normalizeRepositoryIdentity(remote) : null;
25615
26100
  return Boolean(identity && identities.has(identity));
25616
26101
  }
@@ -25620,8 +26105,8 @@ function discoverRelocatedWorkspace(input) {
25620
26105
  );
25621
26106
  if (identities.size === 0) return null;
25622
26107
  const searchRoots = [
25623
- (0, import_node_path8.dirname)((0, import_node_path8.resolve)(input.missingPath)),
25624
- ...(input.approvedWorkspacePaths ?? []).map((path) => (0, import_node_path8.dirname)((0, import_node_path8.resolve)(path)))
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)))
25625
26110
  ].filter((path, index, paths) => paths.indexOf(path) === index);
25626
26111
  const candidates = [];
25627
26112
  for (const root of searchRoots) {
@@ -25633,19 +26118,19 @@ function discoverRelocatedWorkspace(input) {
25633
26118
  }
25634
26119
  }
25635
26120
  const matches = [];
25636
- for (const candidate of new Set(candidates.map((path) => (0, import_node_path8.resolve)(path)))) {
26121
+ for (const candidate of new Set(candidates.map((path) => (0, import_node_path9.resolve)(path)))) {
25637
26122
  const remote = (input.probe ?? DEFAULT_PROBE).gitRemoteUrl(candidate);
25638
26123
  const identity = remote ? normalizeRepositoryIdentity(remote) : null;
25639
- if (identity && identities.has(identity)) matches.push((0, import_node_path8.resolve)(candidate));
26124
+ if (identity && identities.has(identity)) matches.push((0, import_node_path9.resolve)(candidate));
25640
26125
  if (matches.length > 1) return null;
25641
26126
  }
25642
26127
  return matches[0] ?? null;
25643
26128
  }
25644
26129
 
25645
26130
  // src/workspace-run-lease.ts
25646
- var import_node_child_process4 = require("child_process");
25647
- var import_node_fs11 = require("fs");
25648
- var import_node_path9 = require("path");
26131
+ var import_node_child_process5 = require("child_process");
26132
+ var import_node_fs12 = require("fs");
26133
+ var import_node_path10 = require("path");
25649
26134
  var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
25650
26135
  function parsePositiveMs(value2, fallback) {
25651
26136
  const parsed = value2 ? Number(value2) : Number.NaN;
@@ -25656,7 +26141,7 @@ var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
25656
26141
  2e3
25657
26142
  );
25658
26143
  function gitValue(cwd, args) {
25659
- const result = (0, import_node_child_process4.spawnSync)("git", ["-C", cwd, ...args], {
26144
+ const result = (0, import_node_child_process5.spawnSync)("git", ["-C", cwd, ...args], {
25660
26145
  encoding: "utf8",
25661
26146
  env: getDaemonCliEnvironment(),
25662
26147
  timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
@@ -25665,17 +26150,17 @@ function gitValue(cwd, args) {
25665
26150
  return result.stdout.trim() || null;
25666
26151
  }
25667
26152
  function filesystemIdentity(path) {
25668
- const canonicalPath = import_node_fs11.realpathSync.native(path);
25669
- const stat = (0, import_node_fs11.statSync)(canonicalPath);
26153
+ const canonicalPath = import_node_fs12.realpathSync.native(path);
26154
+ const stat = (0, import_node_fs12.statSync)(canonicalPath);
25670
26155
  return `${canonicalPath}:${stat.dev}:${stat.ino}`;
25671
26156
  }
25672
26157
  function resolveGitPath(workspacePath, value2) {
25673
- return (0, import_node_path9.isAbsolute)(value2) ? value2 : (0, import_node_path9.resolve)(workspacePath, value2);
26158
+ return (0, import_node_path10.isAbsolute)(value2) ? value2 : (0, import_node_path10.resolve)(workspacePath, value2);
25674
26159
  }
25675
26160
  var DEFAULT_PROBE2 = {
25676
26161
  snapshot: (workspacePath) => {
25677
26162
  try {
25678
- const canonicalWorkspacePath = import_node_fs11.realpathSync.native((0, import_node_path9.resolve)(workspacePath));
26163
+ const canonicalWorkspacePath = import_node_fs12.realpathSync.native((0, import_node_path10.resolve)(workspacePath));
25679
26164
  const repositoryRoot = gitValue(canonicalWorkspacePath, ["rev-parse", "--show-toplevel"]);
25680
26165
  const gitDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-dir"]);
25681
26166
  const commonDirectory = gitValue(canonicalWorkspacePath, ["rev-parse", "--git-common-dir"]);
@@ -25684,7 +26169,7 @@ var DEFAULT_PROBE2 = {
25684
26169
  const remote = gitValue(canonicalWorkspacePath, ["config", "--get", "remote.origin.url"]);
25685
26170
  const remoteIdentity = remote ? normalizeRepositoryIdentity(remote) : null;
25686
26171
  return {
25687
- workspacePath: (0, import_node_path9.resolve)(workspacePath),
26172
+ workspacePath: (0, import_node_path10.resolve)(workspacePath),
25688
26173
  workspaceIdentity: filesystemIdentity(canonicalWorkspacePath),
25689
26174
  repositoryIdentity: `${filesystemIdentity(
25690
26175
  resolveGitPath(canonicalWorkspacePath, commonDirectory)
@@ -25698,7 +26183,7 @@ var DEFAULT_PROBE2 = {
25698
26183
  },
25699
26184
  pathExists: (workspacePath) => {
25700
26185
  try {
25701
- return (0, import_node_fs11.existsSync)((0, import_node_path9.resolve)(workspacePath));
26186
+ return (0, import_node_fs12.existsSync)((0, import_node_path10.resolve)(workspacePath));
25702
26187
  } catch {
25703
26188
  return true;
25704
26189
  }
@@ -25729,7 +26214,7 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
25729
26214
  static capture(targets, probe = DEFAULT_PROBE2) {
25730
26215
  const captured = [];
25731
26216
  for (const target of targets) {
25732
- const normalizedTarget = { ...target, workspacePath: (0, import_node_path9.resolve)(target.workspacePath) };
26217
+ const normalizedTarget = { ...target, workspacePath: (0, import_node_path10.resolve)(target.workspacePath) };
25733
26218
  const identity = probe.snapshot(normalizedTarget.workspacePath);
25734
26219
  if (!identity) return { lease: null, failure: unavailableFailure(normalizedTarget) };
25735
26220
  if (normalizedTarget.expectedBranch && identity.branch !== normalizedTarget.expectedBranch) {
@@ -25882,15 +26367,15 @@ function assertValidGitRef(ref, label) {
25882
26367
  if (!ref || ref.startsWith("-") || ref.includes("\0")) {
25883
26368
  throw new Error(`${label} is not a safe git ref`);
25884
26369
  }
25885
- const result = (0, import_node_child_process5.spawnSync)("git", ["check-ref-format", "--branch", ref], {
26370
+ const result = (0, import_node_child_process6.spawnSync)("git", ["check-ref-format", "--branch", ref], {
25886
26371
  env: getDaemonCliEnvironment(),
25887
26372
  encoding: "utf8"
25888
26373
  });
25889
26374
  if (result.status !== 0) throw new Error(`${label} is not a valid git ref`);
25890
26375
  }
25891
26376
  function isPathContained(childPath, parentPath) {
25892
- const rel = (0, import_node_path10.relative)((0, import_node_path10.resolve)(parentPath), (0, import_node_path10.resolve)(childPath));
25893
- return rel === "" || !(0, import_node_path10.isAbsolute)(rel) && rel !== ".." && !rel.startsWith(`..${import_node_path10.sep}`);
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}`);
25894
26379
  }
25895
26380
  function assertPathContained(childPath, parentPath, label) {
25896
26381
  if (!isPathContained(childPath, parentPath)) {
@@ -25898,7 +26383,7 @@ function assertPathContained(childPath, parentPath, label) {
25898
26383
  }
25899
26384
  }
25900
26385
  function git(cwd, args) {
25901
- const result = (0, import_node_child_process5.spawnSync)("git", args, {
26386
+ const result = (0, import_node_child_process6.spawnSync)("git", args, {
25902
26387
  cwd,
25903
26388
  env: getDaemonCliEnvironment(),
25904
26389
  encoding: "utf8",
@@ -25917,14 +26402,14 @@ function gitSafe(cwd, args) {
25917
26402
  }
25918
26403
  }
25919
26404
  function worktreeDir(projectPath) {
25920
- const resolved = (0, import_node_path10.resolve)(projectPath);
25921
- return (0, import_node_path10.join)((0, import_node_path10.dirname)(resolved), `${(0, import_node_path10.basename)(resolved)}-alan-worktrees`);
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`);
25922
26407
  }
25923
26408
  function pathSegment(value2) {
25924
26409
  return value2.toLowerCase().replace(/^alan\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "task";
25925
26410
  }
25926
26411
  function repoName(repoUrl, localPath) {
25927
- return repoUrl.replace(/\.git$/, "").split("/").pop() || (0, import_node_path10.basename)(localPath) || "repo";
26412
+ return repoUrl.replace(/\.git$/, "").split("/").pop() || (0, import_node_path11.basename)(localPath) || "repo";
25928
26413
  }
25929
26414
  function findWorktreeByBranch(repoPath, branchName) {
25930
26415
  const output = gitSafe(repoPath, ["worktree", "list", "--porcelain"]);
@@ -25940,21 +26425,21 @@ function findWorktreeByBranch(repoPath, branchName) {
25940
26425
  function ensureGitWorktree(input) {
25941
26426
  assertValidGitRef(input.branchName, "branchName");
25942
26427
  assertValidGitRef(input.baseBranch, "baseBranch");
25943
- const repoPath = (0, import_node_path10.resolve)(input.repoPath);
26428
+ const repoPath = (0, import_node_path11.resolve)(input.repoPath);
25944
26429
  const root = input.allowedRoot ?? worktreeDir(repoPath);
25945
26430
  const existingPath = findWorktreeByBranch(input.repoPath, input.branchName);
25946
26431
  if (existingPath) {
25947
- if ((0, import_node_path10.resolve)(existingPath) === repoPath) {
26432
+ if ((0, import_node_path11.resolve)(existingPath) === repoPath) {
25948
26433
  throw new Error(`${input.branchName} is checked out in the shared checkout`);
25949
26434
  }
25950
26435
  return existingPath;
25951
26436
  }
25952
26437
  assertPathContained(input.worktreePath, root, "worktreePath");
25953
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(input.worktreePath), { recursive: true });
26438
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(input.worktreePath), { recursive: true });
25954
26439
  if (!gitSafe(input.repoPath, ["branch", "--list", input.branchName])) {
25955
26440
  git(input.repoPath, ["branch", input.branchName, input.baseBranch]);
25956
26441
  }
25957
- if ((0, import_node_fs12.existsSync)(input.worktreePath)) {
26442
+ if ((0, import_node_fs13.existsSync)(input.worktreePath)) {
25958
26443
  if (gitSafe(input.worktreePath, ["branch", "--show-current"]) === input.branchName) {
25959
26444
  return input.worktreePath;
25960
26445
  }
@@ -25964,15 +26449,15 @@ function ensureGitWorktree(input) {
25964
26449
  return input.worktreePath;
25965
26450
  }
25966
26451
  function resolveProjectWorktreePaths(projectPath, gitRoot, gitWorktreePath) {
25967
- const resolvedProjectPath = (0, import_node_path10.resolve)(projectPath);
25968
- const resolvedGitRoot = (0, import_node_path10.resolve)(gitRoot);
25969
- const projectRelativePath = (0, import_node_path10.relative)(resolvedGitRoot, resolvedProjectPath);
25970
- if (projectRelativePath === ".." || projectRelativePath.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(projectRelativePath)) {
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)) {
25971
26456
  throw new Error("The selected Project folder is outside its Git repository");
25972
26457
  }
25973
26458
  return {
25974
26459
  repoPath: resolvedGitRoot,
25975
- projectPath: projectRelativePath ? (0, import_node_path10.join)((0, import_node_path10.resolve)(gitWorktreePath), projectRelativePath) : (0, import_node_path10.resolve)(gitWorktreePath)
26460
+ projectPath: projectRelativePath ? (0, import_node_path11.join)((0, import_node_path11.resolve)(gitWorktreePath), projectRelativePath) : (0, import_node_path11.resolve)(gitWorktreePath)
25976
26461
  };
25977
26462
  }
25978
26463
  function ensureWorktree(params) {
@@ -25984,7 +26469,7 @@ function ensureWorktree(params) {
25984
26469
  repoLocalPaths: Array.isArray(params.repoLocalPaths) ? params.repoLocalPaths : []
25985
26470
  };
25986
26471
  if (input.repoLocalPaths?.length) {
25987
- const taskRoot2 = (0, import_node_path10.join)(
26472
+ const taskRoot2 = (0, import_node_path11.join)(
25988
26473
  worktreeDir(input.projectPath),
25989
26474
  `${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
25990
26475
  );
@@ -25993,7 +26478,7 @@ function ensureWorktree(params) {
25993
26478
  const baseBranch2 = repo.branch || input.baseBranch || "main";
25994
26479
  const worktreePath2 = ensureGitWorktree({
25995
26480
  repoPath: repo.localPath,
25996
- worktreePath: (0, import_node_path10.join)(taskRoot2, pathSegment(repoName(repo.repoUrl, repo.localPath))),
26481
+ worktreePath: (0, import_node_path11.join)(taskRoot2, pathSegment(repoName(repo.repoUrl, repo.localPath))),
25997
26482
  branchName: input.branchName,
25998
26483
  baseBranch: baseBranch2,
25999
26484
  allowedRoot: worktreeDir(input.projectPath)
@@ -26008,13 +26493,13 @@ function ensureWorktree(params) {
26008
26493
  })
26009
26494
  };
26010
26495
  }
26011
- const gitRoot = gitSafe((0, import_node_path10.resolve)(input.projectPath), ["rev-parse", "--show-toplevel"]);
26496
+ const gitRoot = gitSafe((0, import_node_path11.resolve)(input.projectPath), ["rev-parse", "--show-toplevel"]);
26012
26497
  if (!gitRoot) {
26013
26498
  throw new Error("The selected Project folder is not inside a Git repository");
26014
26499
  }
26015
- const repoPath = (0, import_node_path10.resolve)(gitRoot);
26500
+ const repoPath = (0, import_node_path11.resolve)(gitRoot);
26016
26501
  const baseBranch = input.baseBranch || gitSafe(repoPath, ["branch", "--show-current"]) || "HEAD";
26017
- const taskRoot = (0, import_node_path10.join)(
26502
+ const taskRoot = (0, import_node_path11.join)(
26018
26503
  worktreeDir(repoPath),
26019
26504
  `${pathSegment(input.branchName)}-${input.taskId.slice(0, 8)}`
26020
26505
  );
@@ -26043,7 +26528,7 @@ function ensureWorktree(params) {
26043
26528
  function ensureSharedCheckoutBranch(input) {
26044
26529
  assertValidGitRef(input.branchName, "branchName");
26045
26530
  assertValidGitRef(input.baseBranch, "baseBranch");
26046
- const repoPath = (0, import_node_path10.resolve)(input.repoPath);
26531
+ const repoPath = (0, import_node_path11.resolve)(input.repoPath);
26047
26532
  if (gitSafe(repoPath, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
26048
26533
  return { checkedOut: false, reason: `${repoPath} is not a git work tree` };
26049
26534
  }
@@ -26088,7 +26573,7 @@ function resolveSharedCheckoutTargets(input) {
26088
26573
  }
26089
26574
  function resolveRuntimeCwd(payload) {
26090
26575
  const multiRepoWorktree = payload.worktreeInfo?.allWorktrees?.[0]?.worktreePath;
26091
- if (multiRepoWorktree) return (0, import_node_path10.dirname)(multiRepoWorktree);
26576
+ if (multiRepoWorktree) return (0, import_node_path11.dirname)(multiRepoWorktree);
26092
26577
  if (isAlanDefaultWorkspace(payload.projectPath)) return resolveAlanDefaultWorkspacePath();
26093
26578
  return payload.worktreeInfo?.worktreePath ?? payload.projectPath ?? process.cwd();
26094
26579
  }
@@ -26310,12 +26795,12 @@ function getConfigPath() {
26310
26795
  return process.env.ALAN_AGENT_CONFIG_PATH ?? boundAgentConfigPath ?? resolveAgentConfigPath({ profile: "production" });
26311
26796
  }
26312
26797
  function getEndpointDefaultsPath() {
26313
- return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0, import_node_path10.join)((0, import_node_path10.dirname)(getConfigPath()), "endpoints.json");
26798
+ return process.env.ALAN_AGENT_ENDPOINTS_PATH ?? (0, import_node_path11.join)((0, import_node_path11.dirname)(getConfigPath()), "endpoints.json");
26314
26799
  }
26315
26800
  function readConfig() {
26316
26801
  const configPath = getConfigPath();
26317
- if (!(0, import_node_fs12.existsSync)(configPath)) return {};
26318
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(configPath, "utf8"));
26802
+ if (!(0, import_node_fs13.existsSync)(configPath)) return {};
26803
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(configPath, "utf8"));
26319
26804
  const sanitized = sanitizeAgentConfig(parsed);
26320
26805
  if (Object.keys(parsed).some((key) => !Object.hasOwn(sanitized, key))) {
26321
26806
  writeConfig(sanitized);
@@ -26331,27 +26816,27 @@ function readConfigForRegistration() {
26331
26816
  }
26332
26817
  function readEndpointDefaults() {
26333
26818
  const endpointsPath = getEndpointDefaultsPath();
26334
- if (!(0, import_node_fs12.existsSync)(endpointsPath)) return {};
26819
+ if (!(0, import_node_fs13.existsSync)(endpointsPath)) return {};
26335
26820
  try {
26336
- return JSON.parse((0, import_node_fs12.readFileSync)(endpointsPath, "utf8"));
26821
+ return JSON.parse((0, import_node_fs13.readFileSync)(endpointsPath, "utf8"));
26337
26822
  } catch {
26338
26823
  return {};
26339
26824
  }
26340
26825
  }
26341
26826
  function writeConfig(config) {
26342
26827
  const configPath = getConfigPath();
26343
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true, mode: 448 });
26828
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true, mode: 448 });
26344
26829
  const pendingPath = `${configPath}.pending-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26345
26830
  try {
26346
- (0, import_node_fs12.writeFileSync)(
26831
+ (0, import_node_fs13.writeFileSync)(
26347
26832
  pendingPath,
26348
26833
  JSON.stringify(sanitizeAgentConfig(config), null, 2),
26349
26834
  { mode: 384 }
26350
26835
  );
26351
- (0, import_node_fs12.chmodSync)(pendingPath, 384);
26352
- (0, import_node_fs12.renameSync)(pendingPath, configPath);
26836
+ (0, import_node_fs13.chmodSync)(pendingPath, 384);
26837
+ (0, import_node_fs13.renameSync)(pendingPath, configPath);
26353
26838
  } finally {
26354
- (0, import_node_fs12.rmSync)(pendingPath, { force: true });
26839
+ (0, import_node_fs13.rmSync)(pendingPath, { force: true });
26355
26840
  }
26356
26841
  }
26357
26842
  function maintenanceLeasePath(configPath) {
@@ -26359,9 +26844,9 @@ function maintenanceLeasePath(configPath) {
26359
26844
  }
26360
26845
  function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
26361
26846
  const path = maintenanceLeasePath(configPath);
26362
- if (!(0, import_node_fs12.existsSync)(path)) return null;
26847
+ if (!(0, import_node_fs13.existsSync)(path)) return null;
26363
26848
  try {
26364
- const lease = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
26849
+ const lease = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
26365
26850
  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))) {
26366
26851
  throw new Error("invalid maintenance lease");
26367
26852
  }
@@ -26378,8 +26863,8 @@ function readLocalDaemonMaintenanceLease(configPath, nowMs = Date.now()) {
26378
26863
  expiresAtMs: nowMs + 5e3
26379
26864
  };
26380
26865
  try {
26381
- (0, import_node_fs12.writeFileSync)(path, JSON.stringify(fallback), { mode: 384 });
26382
- (0, import_node_fs12.chmodSync)(path, 384);
26866
+ (0, import_node_fs13.writeFileSync)(path, JSON.stringify(fallback), { mode: 384 });
26867
+ (0, import_node_fs13.chmodSync)(path, 384);
26383
26868
  } catch {
26384
26869
  }
26385
26870
  return fallback;
@@ -26395,10 +26880,10 @@ function acquireLocalDaemonMaintenanceLease(configPath, reason, ttlMs = 5e3) {
26395
26880
  expiresAtMs: reason === "credential_cleanup" ? null : Date.now() + Math.max(1e3, Math.min(ttlMs, 6e4))
26396
26881
  };
26397
26882
  const path = maintenanceLeasePath(configPath);
26398
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(path), { recursive: true, mode: 448 });
26883
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
26399
26884
  try {
26400
- (0, import_node_fs12.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
26401
- (0, import_node_fs12.chmodSync)(path, 384);
26885
+ (0, import_node_fs13.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
26886
+ (0, import_node_fs13.chmodSync)(path, 384);
26402
26887
  return { acquired: true, lease };
26403
26888
  } catch (error) {
26404
26889
  const raced = readLocalDaemonMaintenanceLease(configPath);
@@ -26414,9 +26899,9 @@ function daemonOwnerLeasePath(configPath) {
26414
26899
  }
26415
26900
  function readLocalDaemonOwnerLease(configPath) {
26416
26901
  const path = daemonOwnerLeasePath(configPath);
26417
- if (!(0, import_node_fs12.existsSync)(path)) return null;
26902
+ if (!(0, import_node_fs13.existsSync)(path)) return null;
26418
26903
  try {
26419
- const lease = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
26904
+ const lease = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
26420
26905
  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) {
26421
26906
  throw new Error("invalid daemon owner lease");
26422
26907
  }
@@ -26468,10 +26953,10 @@ async function acquireLocalDaemonOwnerLease(input) {
26468
26953
  localApiToken: input.localApiToken
26469
26954
  };
26470
26955
  const path = daemonOwnerLeasePath(input.configPath);
26471
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(path), { recursive: true, mode: 448 });
26956
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
26472
26957
  try {
26473
- (0, import_node_fs12.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
26474
- (0, import_node_fs12.chmodSync)(path, 384);
26958
+ (0, import_node_fs13.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
26959
+ (0, import_node_fs13.chmodSync)(path, 384);
26475
26960
  return { acquired: true, lease };
26476
26961
  } catch (error) {
26477
26962
  const raced = readLocalDaemonOwnerLease(input.configPath);
@@ -26486,12 +26971,12 @@ function updateLocalDaemonOwnerEndpoint(configPath, lease, localApiPort) {
26486
26971
  const path = daemonOwnerLeasePath(configPath);
26487
26972
  const pendingPath = `${path}.pending-${lease.id}`;
26488
26973
  try {
26489
- (0, import_node_fs12.writeFileSync)(pendingPath, JSON.stringify(updated), { mode: 384 });
26490
- (0, import_node_fs12.chmodSync)(pendingPath, 384);
26491
- (0, import_node_fs12.renameSync)(pendingPath, path);
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);
26492
26977
  return updated;
26493
26978
  } finally {
26494
- (0, import_node_fs12.rmSync)(pendingPath, { force: true });
26979
+ (0, import_node_fs13.rmSync)(pendingPath, { force: true });
26495
26980
  }
26496
26981
  }
26497
26982
  function releaseLocalDaemonOwnerLease(configPath, leaseId) {
@@ -26500,13 +26985,13 @@ function releaseLocalDaemonOwnerLease(configPath, leaseId) {
26500
26985
  function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
26501
26986
  const releasePath = `${path}.release-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26502
26987
  try {
26503
- (0, import_node_fs12.renameSync)(path, releasePath);
26988
+ (0, import_node_fs13.renameSync)(path, releasePath);
26504
26989
  } catch (error) {
26505
26990
  if (error.code === "ENOENT") return;
26506
26991
  throw error;
26507
26992
  }
26508
26993
  try {
26509
- const content = (0, import_node_fs12.readFileSync)(releasePath, "utf8");
26994
+ const content = (0, import_node_fs13.readFileSync)(releasePath, "utf8");
26510
26995
  let capturedOwnerId = null;
26511
26996
  try {
26512
26997
  const captured = JSON.parse(content);
@@ -26515,13 +27000,13 @@ function releaseOwnedJsonFile(path, ownerId, ownerField = "id") {
26515
27000
  }
26516
27001
  if (capturedOwnerId === ownerId) return;
26517
27002
  try {
26518
- (0, import_node_fs12.writeFileSync)(path, content, { mode: 384, flag: "wx" });
26519
- (0, import_node_fs12.chmodSync)(path, 384);
27003
+ (0, import_node_fs13.writeFileSync)(path, content, { mode: 384, flag: "wx" });
27004
+ (0, import_node_fs13.chmodSync)(path, 384);
26520
27005
  } catch (error) {
26521
27006
  if (error.code !== "EEXIST") throw error;
26522
27007
  }
26523
27008
  } finally {
26524
- (0, import_node_fs12.rmSync)(releasePath, { force: true });
27009
+ (0, import_node_fs13.rmSync)(releasePath, { force: true });
26525
27010
  }
26526
27011
  }
26527
27012
  function inspectLocalDaemonRunJournal(configPath) {
@@ -26531,8 +27016,8 @@ function inspectLocalDaemonRunJournal(configPath) {
26531
27016
  try {
26532
27017
  let activeRunCount = 0;
26533
27018
  for (const artifact of [path, pendingPath]) {
26534
- if (!(0, import_node_fs12.existsSync)(artifact)) continue;
26535
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(artifact, "utf8"));
27019
+ if (!(0, import_node_fs13.existsSync)(artifact)) continue;
27020
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(artifact, "utf8"));
26536
27021
  if (parsed.version !== 1 || !Array.isArray(parsed.entries))
26537
27022
  throw new Error("invalid journal");
26538
27023
  if (artifact === path) {
@@ -26551,7 +27036,7 @@ function inspectLocalDaemonRunJournal(configPath) {
26551
27036
  }).length;
26552
27037
  continue;
26553
27038
  }
26554
- const legacyStartedAtMs = (0, import_node_fs12.statSync)(pendingPath).mtimeMs;
27039
+ const legacyStartedAtMs = (0, import_node_fs13.statSync)(pendingPath).mtimeMs;
26555
27040
  activeRunCount += parsed.entries.filter((entry) => {
26556
27041
  if (!entry || typeof entry !== "object") throw new Error("invalid pending run entry");
26557
27042
  const startedAtMs = entry.startedAtMs;
@@ -26578,9 +27063,9 @@ function setupTokenHash(setupToken) {
26578
27063
  function getOrCreateSetupAttemptId(setupToken) {
26579
27064
  const intentPath = pendingSetupIntentPath();
26580
27065
  const tokenHash = setupTokenHash(setupToken);
26581
- if ((0, import_node_fs12.existsSync)(intentPath)) {
27066
+ if ((0, import_node_fs13.existsSync)(intentPath)) {
26582
27067
  try {
26583
- const stored = JSON.parse((0, import_node_fs12.readFileSync)(intentPath, "utf8"));
27068
+ const stored = JSON.parse((0, import_node_fs13.readFileSync)(intentPath, "utf8"));
26584
27069
  if (stored.setupTokenHash === tokenHash && typeof stored.registrationAttemptId === "string" && stored.registrationAttemptId.length > 0) {
26585
27070
  return stored.registrationAttemptId;
26586
27071
  }
@@ -26588,23 +27073,23 @@ function getOrCreateSetupAttemptId(setupToken) {
26588
27073
  }
26589
27074
  }
26590
27075
  const registrationAttemptId = (0, import_node_crypto3.randomUUID)();
26591
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(intentPath), { recursive: true, mode: 448 });
27076
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(intentPath), { recursive: true, mode: 448 });
26592
27077
  const writePath = `${intentPath}.pending-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26593
27078
  try {
26594
- (0, import_node_fs12.writeFileSync)(
27079
+ (0, import_node_fs13.writeFileSync)(
26595
27080
  writePath,
26596
27081
  JSON.stringify({ setupTokenHash: tokenHash, registrationAttemptId }, null, 2),
26597
27082
  { mode: 384 }
26598
27083
  );
26599
- (0, import_node_fs12.chmodSync)(writePath, 384);
26600
- (0, import_node_fs12.renameSync)(writePath, intentPath);
27084
+ (0, import_node_fs13.chmodSync)(writePath, 384);
27085
+ (0, import_node_fs13.renameSync)(writePath, intentPath);
26601
27086
  } finally {
26602
- (0, import_node_fs12.rmSync)(writePath, { force: true });
27087
+ (0, import_node_fs13.rmSync)(writePath, { force: true });
26603
27088
  }
26604
27089
  return registrationAttemptId;
26605
27090
  }
26606
27091
  function clearPendingSetupIntent() {
26607
- (0, import_node_fs12.rmSync)(pendingSetupIntentPath(), { force: true });
27092
+ (0, import_node_fs13.rmSync)(pendingSetupIntentPath(), { force: true });
26608
27093
  }
26609
27094
  function pendingDeviceAuthorizationPath() {
26610
27095
  return `${getConfigPath()}.device-auth-pending`;
@@ -26625,8 +27110,8 @@ function parseDeviceAuthorizationOperationContent(content) {
26625
27110
  }
26626
27111
  function restoreCapturedFileUnlessReplaced(path, content) {
26627
27112
  try {
26628
- (0, import_node_fs12.writeFileSync)(path, content, { mode: 384, flag: "wx" });
26629
- (0, import_node_fs12.chmodSync)(path, 384);
27113
+ (0, import_node_fs13.writeFileSync)(path, content, { mode: 384, flag: "wx" });
27114
+ (0, import_node_fs13.chmodSync)(path, 384);
26630
27115
  } catch (error) {
26631
27116
  if (error.code !== "EEXIST") throw error;
26632
27117
  }
@@ -26634,26 +27119,26 @@ function restoreCapturedFileUnlessReplaced(path, content) {
26634
27119
  function quarantineCorruptDeviceAuthorizationOperation(path) {
26635
27120
  const quarantinePath = `${path}.corrupt-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26636
27121
  try {
26637
- (0, import_node_fs12.renameSync)(path, quarantinePath);
27122
+ (0, import_node_fs13.renameSync)(path, quarantinePath);
26638
27123
  } catch (error) {
26639
27124
  if (error.code === "ENOENT") return null;
26640
27125
  throw error;
26641
27126
  }
26642
27127
  try {
26643
- const capturedContent = (0, import_node_fs12.readFileSync)(quarantinePath, "utf8");
27128
+ const capturedContent = (0, import_node_fs13.readFileSync)(quarantinePath, "utf8");
26644
27129
  if (parseDeviceAuthorizationOperationContent(capturedContent)) {
26645
27130
  restoreCapturedFileUnlessReplaced(path, capturedContent);
26646
27131
  }
26647
27132
  } finally {
26648
- (0, import_node_fs12.rmSync)(quarantinePath, { force: true });
27133
+ (0, import_node_fs13.rmSync)(quarantinePath, { force: true });
26649
27134
  }
26650
- if (!(0, import_node_fs12.existsSync)(path)) return null;
26651
- return parseDeviceAuthorizationOperationContent((0, import_node_fs12.readFileSync)(path, "utf8"));
27135
+ if (!(0, import_node_fs13.existsSync)(path)) return null;
27136
+ return parseDeviceAuthorizationOperationContent((0, import_node_fs13.readFileSync)(path, "utf8"));
26652
27137
  }
26653
27138
  function readDeviceAuthorizationOperation() {
26654
27139
  const path = deviceAuthorizationOperationPath();
26655
- if (!(0, import_node_fs12.existsSync)(path)) return null;
26656
- const parsed = parseDeviceAuthorizationOperationContent((0, import_node_fs12.readFileSync)(path, "utf8"));
27140
+ if (!(0, import_node_fs13.existsSync)(path)) return null;
27141
+ const parsed = parseDeviceAuthorizationOperationContent((0, import_node_fs13.readFileSync)(path, "utf8"));
26657
27142
  const lease = parsed ?? quarantineCorruptDeviceAuthorizationOperation(path);
26658
27143
  if (!lease) return null;
26659
27144
  if (lease.expiresAtMs <= Date.now()) {
@@ -26671,10 +27156,10 @@ function acquireDeviceAuthorizationOperation() {
26671
27156
  expiresAtMs: Date.now() + 6e4
26672
27157
  };
26673
27158
  const path = deviceAuthorizationOperationPath();
26674
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(path), { recursive: true, mode: 448 });
27159
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
26675
27160
  try {
26676
- (0, import_node_fs12.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
26677
- (0, import_node_fs12.chmodSync)(path, 384);
27161
+ (0, import_node_fs13.writeFileSync)(path, JSON.stringify(lease), { mode: 384, flag: "wx" });
27162
+ (0, import_node_fs13.chmodSync)(path, 384);
26678
27163
  return lease;
26679
27164
  } catch (error) {
26680
27165
  if (error.code === "EEXIST") return null;
@@ -26689,15 +27174,15 @@ function extendDeviceAuthorizationOperation(operationId, expiresAtMs) {
26689
27174
  const path = deviceAuthorizationOperationPath();
26690
27175
  const pendingPath = `${path}.pending-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26691
27176
  try {
26692
- (0, import_node_fs12.writeFileSync)(
27177
+ (0, import_node_fs13.writeFileSync)(
26693
27178
  pendingPath,
26694
27179
  JSON.stringify({ ...current, expiresAtMs: Math.max(expiresAtMs, Date.now() + 6e4) }),
26695
27180
  { mode: 384 }
26696
27181
  );
26697
- (0, import_node_fs12.chmodSync)(pendingPath, 384);
26698
- (0, import_node_fs12.renameSync)(pendingPath, path);
27182
+ (0, import_node_fs13.chmodSync)(pendingPath, 384);
27183
+ (0, import_node_fs13.renameSync)(pendingPath, path);
26699
27184
  } finally {
26700
- (0, import_node_fs12.rmSync)(pendingPath, { force: true });
27185
+ (0, import_node_fs13.rmSync)(pendingPath, { force: true });
26701
27186
  }
26702
27187
  }
26703
27188
  function assertDeviceAuthorizationOperationOwner(operationId) {
@@ -26708,12 +27193,12 @@ function assertDeviceAuthorizationOperationOwner(operationId) {
26708
27193
  function clearPendingDeviceAuthorization(operationId) {
26709
27194
  const path = pendingDeviceAuthorizationPath();
26710
27195
  if (!operationId) {
26711
- (0, import_node_fs12.rmSync)(path, { force: true });
27196
+ (0, import_node_fs13.rmSync)(path, { force: true });
26712
27197
  return;
26713
27198
  }
26714
- if (!(0, import_node_fs12.existsSync)(path)) return;
27199
+ if (!(0, import_node_fs13.existsSync)(path)) return;
26715
27200
  try {
26716
- const pending = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
27201
+ const pending = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
26717
27202
  if (pending.operationId !== operationId) return;
26718
27203
  } catch {
26719
27204
  return;
@@ -26722,9 +27207,9 @@ function clearPendingDeviceAuthorization(operationId) {
26722
27207
  }
26723
27208
  function readPendingDeviceAuthorization(apiUrl) {
26724
27209
  const path = pendingDeviceAuthorizationPath();
26725
- if (!(0, import_node_fs12.existsSync)(path)) return null;
27210
+ if (!(0, import_node_fs13.existsSync)(path)) return null;
26726
27211
  try {
26727
- const pending = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
27212
+ const pending = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
26728
27213
  const expiresAtMs = Date.parse(pending.expiresAt ?? "");
26729
27214
  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()) {
26730
27215
  return null;
@@ -26736,20 +27221,20 @@ function readPendingDeviceAuthorization(apiUrl) {
26736
27221
  }
26737
27222
  function writePendingDeviceAuthorization(pending) {
26738
27223
  const path = pendingDeviceAuthorizationPath();
26739
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(path), { recursive: true, mode: 448 });
27224
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
26740
27225
  const writePath = `${path}.pending-${process.pid}-${(0, import_node_crypto3.randomBytes)(6).toString("hex")}`;
26741
27226
  try {
26742
- (0, import_node_fs12.writeFileSync)(writePath, JSON.stringify(pending, null, 2), { mode: 384 });
26743
- (0, import_node_fs12.chmodSync)(writePath, 384);
26744
- (0, import_node_fs12.renameSync)(writePath, path);
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);
26745
27230
  } finally {
26746
- (0, import_node_fs12.rmSync)(writePath, { force: true });
27231
+ (0, import_node_fs13.rmSync)(writePath, { force: true });
26747
27232
  }
26748
27233
  }
26749
27234
  function removeAlanOwnedLocalArtifacts() {
26750
27235
  const configPath = getConfigPath();
26751
- const directory = (0, import_node_path10.dirname)(configPath);
26752
- const configName = (0, import_node_path10.basename)(configPath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27236
+ const directory = (0, import_node_path11.dirname)(configPath);
27237
+ const configName = (0, import_node_path11.basename)(configPath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26753
27238
  const alanAtomicSidecarPattern = new RegExp(
26754
27239
  `^${configName}(?:|\\.setup-pending|\\.device-auth-pending|\\.device-auth-operation\\.json|\\.maintenance-lease\\.json|\\.event-outbox\\.enc)\\.(?:pending|release|corrupt)-\\d+-[a-f0-9]{12}$`
26755
27240
  );
@@ -26766,13 +27251,13 @@ function removeAlanOwnedLocalArtifacts() {
26766
27251
  `${configPath}.pending-runs.json.pending`,
26767
27252
  maintenanceLeasePath(configPath)
26768
27253
  ]);
26769
- if ((0, import_node_fs12.existsSync)(directory)) {
26770
- for (const entry of (0, import_node_fs12.readdirSync)(directory)) {
26771
- if (alanAtomicSidecarPattern.test(entry)) artifacts.add((0, import_node_path10.join)(directory, entry));
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));
26772
27257
  }
26773
27258
  }
26774
- for (const artifact of artifacts) (0, import_node_fs12.rmSync)(artifact, { force: true });
26775
- (0, import_node_fs12.rmSync)(configPath, { force: true });
27259
+ for (const artifact of artifacts) (0, import_node_fs13.rmSync)(artifact, { force: true });
27260
+ (0, import_node_fs13.rmSync)(configPath, { force: true });
26776
27261
  }
26777
27262
  function argValue(args, name) {
26778
27263
  const index = args.indexOf(name);
@@ -26994,13 +27479,13 @@ var DurablePendingRunStarts = class extends Map {
26994
27479
  }
26995
27480
  persist() {
26996
27481
  if (this.size === 0) {
26997
- (0, import_node_fs12.rmSync)(this.path, { force: true });
27482
+ (0, import_node_fs13.rmSync)(this.path, { force: true });
26998
27483
  return;
26999
27484
  }
27000
27485
  const pendingPath = `${this.path}.pending`;
27001
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(this.path), { recursive: true, mode: 448 });
27486
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(this.path), { recursive: true, mode: 448 });
27002
27487
  try {
27003
- (0, import_node_fs12.writeFileSync)(
27488
+ (0, import_node_fs13.writeFileSync)(
27004
27489
  pendingPath,
27005
27490
  JSON.stringify({
27006
27491
  version: 1,
@@ -27013,10 +27498,10 @@ var DurablePendingRunStarts = class extends Map {
27013
27498
  }),
27014
27499
  { mode: 384 }
27015
27500
  );
27016
- (0, import_node_fs12.chmodSync)(pendingPath, 384);
27017
- (0, import_node_fs12.renameSync)(pendingPath, this.path);
27501
+ (0, import_node_fs13.chmodSync)(pendingPath, 384);
27502
+ (0, import_node_fs13.renameSync)(pendingPath, this.path);
27018
27503
  } finally {
27019
- (0, import_node_fs12.rmSync)(pendingPath, { force: true });
27504
+ (0, import_node_fs13.rmSync)(pendingPath, { force: true });
27020
27505
  }
27021
27506
  }
27022
27507
  };
@@ -27251,21 +27736,21 @@ function reconcileActiveRuns(input) {
27251
27736
  }
27252
27737
  }
27253
27738
  function collectRuntimeMetadata() {
27254
- const cpuList = (0, import_node_os7.cpus)();
27739
+ const cpuList = (0, import_node_os8.cpus)();
27255
27740
  const metadata = {
27256
- platform: (0, import_node_os7.platform)(),
27257
- arch: (0, import_node_os7.arch)(),
27258
- osVersion: (0, import_node_os7.release)(),
27741
+ platform: (0, import_node_os8.platform)(),
27742
+ arch: (0, import_node_os8.arch)(),
27743
+ osVersion: (0, import_node_os8.release)(),
27259
27744
  agentVersion: AGENT_VERSION,
27260
- memoryBytes: (0, import_node_os7.totalmem)(),
27261
- memoryFreeBytes: (0, import_node_os7.freemem)()
27745
+ memoryBytes: (0, import_node_os8.totalmem)(),
27746
+ memoryFreeBytes: (0, import_node_os8.freemem)()
27262
27747
  };
27263
27748
  if (cpuList.length > 0) {
27264
27749
  metadata.cpuModel = cpuList[0]?.model;
27265
27750
  metadata.cpuCores = cpuList.length;
27266
27751
  }
27267
27752
  try {
27268
- const disk = (0, import_node_fs12.statfsSync)((0, import_node_os7.homedir)());
27753
+ const disk = (0, import_node_fs13.statfsSync)((0, import_node_os8.homedir)());
27269
27754
  metadata.diskFreeBytes = disk.bavail * disk.bsize;
27270
27755
  metadata.diskTotalBytes = disk.blocks * disk.bsize;
27271
27756
  } catch {
@@ -27521,7 +28006,7 @@ async function setupDaemon(args) {
27521
28006
  const teamId = argValue(args, "--team") ?? process.env.ALAN_TEAM_ID;
27522
28007
  const setupToken = argValue(args, "--setup-token") ?? process.env.ALAN_RUNTIME_SETUP_TOKEN;
27523
28008
  const token = argValue(args, "--token") ?? process.env.ALAN_SETUP_TOKEN;
27524
- const displayName = argValue(args, "--name") ?? (0, import_node_os7.hostname)();
28009
+ const displayName = argValue(args, "--name") ?? (0, import_node_os8.hostname)();
27525
28010
  const scope = argValue(args, "--scope") ?? process.env.ALAN_RUNTIME_SCOPE ?? (setupToken ? "team" : "user");
27526
28011
  const installationId = argValue(args, "--installation-id") ?? previousConfig.installationId ?? (0, import_node_crypto3.randomUUID)();
27527
28012
  if (scope !== "team" && scope !== "user") {
@@ -27544,7 +28029,7 @@ async function setupDaemon(args) {
27544
28029
  ...teamId ? { teamId } : {},
27545
28030
  ...setupToken ? { setupToken, registrationAttemptId } : {},
27546
28031
  displayName,
27547
- hostname: (0, import_node_os7.hostname)(),
28032
+ hostname: (0, import_node_os8.hostname)(),
27548
28033
  runtimeKind: "machine",
27549
28034
  managementKind: "user_managed",
27550
28035
  hostKind: "daemon",
@@ -27628,7 +28113,7 @@ async function loginWithDeviceCode(args) {
27628
28113
  }
27629
28114
  const previousConfig = readConfigForRegistration();
27630
28115
  const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
27631
- const displayName = argValue(args, "--name") ?? (0, import_node_os7.hostname)();
28116
+ const displayName = argValue(args, "--name") ?? (0, import_node_os8.hostname)();
27632
28117
  const maxPolls = Number.parseInt(argValue(args, "--max-polls") ?? "300", 10);
27633
28118
  const resumed = readPendingDeviceAuthorization(apiUrl);
27634
28119
  const installationId = resumed?.installationId ?? previousConfig.installationId ?? (0, import_node_crypto3.randomUUID)();
@@ -27640,7 +28125,7 @@ async function loginWithDeviceCode(args) {
27640
28125
  headers: { "content-type": "application/json" },
27641
28126
  body: JSON.stringify({
27642
28127
  displayName,
27643
- hostname: (0, import_node_os7.hostname)(),
28128
+ hostname: (0, import_node_os8.hostname)(),
27644
28129
  capabilities: discoverCapabilities(),
27645
28130
  metadata: {
27646
28131
  ...await collectRuntimeMetadataWithProviderLimits(),
@@ -27874,7 +28359,7 @@ async function startDaemon(args) {
27874
28359
  const workspaceRelocations = /* @__PURE__ */ new Map();
27875
28360
  for (const relocation of stored.workspaceRelocations ?? []) {
27876
28361
  if (relocation?.conversationId && relocation.runId && relocation.previousPath && relocation.canonicalPath) {
27877
- workspaceRelocations.set((0, import_node_path10.resolve)(relocation.previousPath), relocation);
28362
+ workspaceRelocations.set((0, import_node_path11.resolve)(relocation.previousPath), relocation);
27878
28363
  }
27879
28364
  }
27880
28365
  if (stored.localApiPort !== localApiPort || stored.localApiToken !== localApiToken || stored.localServiceId !== localServiceId || stored.localServiceSecret !== localServiceSecret || stored.eventSpoolKey !== eventSpoolKey || stored.daemonEpoch !== daemonEpoch || stored.installationId !== installationId) {
@@ -28270,7 +28755,26 @@ async function startDaemon(args) {
28270
28755
  return;
28271
28756
  }
28272
28757
  const backendKind = normalizeBackendKind2(payload.backendKind);
28273
- const runtimeCommand = resolveBackendRuntimeCommand(backendKind ?? payload.backendKind);
28758
+ let runtimeCommand = resolveBackendRuntimeCommand(backendKind ?? payload.backendKind);
28759
+ if (!runtimeCommand && backendKind) {
28760
+ runtimeCommand = revalidateBackendRuntimeCommand(backendKind);
28761
+ }
28762
+ if (!runtimeCommand && backendKind && canInstallProviderCli(backendKind)) {
28763
+ pendingRunStarts.set(payload.runId, payload.conversationId);
28764
+ try {
28765
+ const installed = await ensureProviderCliInstalled(backendKind, {
28766
+ notify: (message) => {
28767
+ pushLog(message);
28768
+ console.info(`[alan-agent] ${message}`);
28769
+ }
28770
+ });
28771
+ if (installed) {
28772
+ runtimeCommand = revalidateBackendRuntimeCommand(backendKind);
28773
+ }
28774
+ } finally {
28775
+ pendingRunStarts.delete(payload.runId);
28776
+ }
28777
+ }
28274
28778
  if (!runtimeCommand && backendKind) {
28275
28779
  socket.emit("agent.rejected", {
28276
28780
  runId: payload.runId,
@@ -28284,31 +28788,31 @@ async function startDaemon(args) {
28284
28788
  const workspaceRequired = Boolean(explicitWorkspacePath);
28285
28789
  if (isAlanDefaultWorkspace(payload.projectPath)) {
28286
28790
  try {
28287
- (0, import_node_fs12.mkdirSync)(cwd, { recursive: true });
28791
+ (0, import_node_fs13.mkdirSync)(cwd, { recursive: true });
28288
28792
  } catch {
28289
28793
  }
28290
28794
  }
28291
28795
  let workspaceReadiness = inspectWorkspaceReadiness(workspaceRequired ? cwd : void 0);
28292
28796
  const expectedRepoUrls = [
28293
28797
  ...(payload.repoLocalPaths ?? []).filter(
28294
- (repo) => explicitWorkspacePath ? (0, import_node_path10.resolve)(repo.localPath) === (0, import_node_path10.resolve)(explicitWorkspacePath) : false
28798
+ (repo) => explicitWorkspacePath ? (0, import_node_path11.resolve)(repo.localPath) === (0, import_node_path11.resolve)(explicitWorkspacePath) : false
28295
28799
  ).map((repo) => repo.repoUrl),
28296
28800
  ...payload.localRepoUrls ?? []
28297
28801
  ];
28298
28802
  if (workspaceReadiness.code === "workspace_missing" && explicitWorkspacePath && !payload.worktreeInfo) {
28299
- const previousPath = (0, import_node_path10.resolve)(explicitWorkspacePath);
28803
+ const previousPath = (0, import_node_path11.resolve)(explicitWorkspacePath);
28300
28804
  const remembered = workspaceRelocations.get(previousPath);
28301
28805
  const rememberedPath = remembered?.canonicalPath;
28302
28806
  const relocated = rememberedPath && workspaceMatchesExpectedRepositories({
28303
28807
  workspacePath: rememberedPath,
28304
28808
  expectedRepoUrls
28305
- }) ? (0, import_node_path10.resolve)(rememberedPath) : discoverRelocatedWorkspace({
28809
+ }) ? (0, import_node_path11.resolve)(rememberedPath) : discoverRelocatedWorkspace({
28306
28810
  missingPath: explicitWorkspacePath,
28307
28811
  approvedWorkspacePaths: payload.approvedWorkspacePaths,
28308
28812
  expectedRepoUrls
28309
28813
  });
28310
28814
  if (relocated) {
28311
- if (!remembered || (0, import_node_path10.resolve)(remembered.canonicalPath) !== relocated) {
28815
+ if (!remembered || (0, import_node_path11.resolve)(remembered.canonicalPath) !== relocated) {
28312
28816
  workspaceRelocations.set(previousPath, {
28313
28817
  conversationId: payload.conversationId,
28314
28818
  runId: payload.runId,
@@ -28769,6 +29273,29 @@ async function startDaemon(args) {
28769
29273
  })();
28770
29274
  }
28771
29275
  );
29276
+ socket.on(
29277
+ "runtime.skills.scan",
29278
+ (payload) => {
29279
+ if (!payload?.requestId) return;
29280
+ const requestId = payload.requestId;
29281
+ try {
29282
+ const projectPath = isAlanDefaultWorkspace(payload.projectPath) ? resolveAlanDefaultWorkspacePath() : payload.projectPath;
29283
+ const result = scanLocalSkillsCached((0, import_node_os8.homedir)(), projectPath, {
29284
+ refresh: payload.refresh === true
29285
+ });
29286
+ socket.emit("runtime.skills.result", {
29287
+ requestId,
29288
+ skills: result.skills,
29289
+ errors: result.errors
29290
+ });
29291
+ } catch (error) {
29292
+ socket.emit("runtime.skills.result", {
29293
+ requestId,
29294
+ error: error instanceof Error ? error.message : String(error)
29295
+ });
29296
+ }
29297
+ }
29298
+ );
28772
29299
  const adoptionChallenges = /* @__PURE__ */ new Map();
28773
29300
  const localServer = import_node_http.default.createServer((req, res) => {
28774
29301
  if (req.method === "GET" && req.url === "/owner") {
@@ -29762,7 +30289,7 @@ var agentProbeAckSchema = external_exports.object({
29762
30289
 
29763
30290
  // src/sandbox-outbox.ts
29764
30291
  var import_node_crypto5 = require("crypto");
29765
- var import_node_fs13 = require("fs");
30292
+ var import_node_fs14 = require("fs");
29766
30293
  var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
29767
30294
  function isSandboxEventOutboxEnabled(env = process.env) {
29768
30295
  return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
@@ -29785,7 +30312,7 @@ function createSandboxEventOutbox(input) {
29785
30312
  input.pushLog?.(
29786
30313
  `sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
29787
30314
  );
29788
- (0, import_node_fs13.rmSync)(path, { force: true });
30315
+ (0, import_node_fs14.rmSync)(path, { force: true });
29789
30316
  return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
29790
30317
  }
29791
30318
  }
@@ -30482,10 +31009,10 @@ async function runSandbox(config) {
30482
31009
  }
30483
31010
 
30484
31011
  // src/service-manager.ts
30485
- var import_node_child_process6 = require("child_process");
30486
- var import_node_fs14 = require("fs");
30487
- var import_node_os8 = require("os");
30488
- var import_node_path11 = require("path");
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");
30489
31016
  var SERVICE_LABEL = "ai.tryalan.agent";
30490
31017
  var SYSTEMD_UNIT = "alan-agent.service";
30491
31018
  var WINDOWS_TASK = "Alan Agent";
@@ -30505,8 +31032,8 @@ function buildDaemonServicePlan(input) {
30505
31032
  if (userId === void 0) throw new Error("Cannot determine the current macOS user ID");
30506
31033
  const domain = `gui/${userId}`;
30507
31034
  const serviceTarget = `${domain}/${SERVICE_LABEL}`;
30508
- const manifestPath = (0, import_node_path11.join)(input.homeDir, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
30509
- const logDir = (0, import_node_path11.join)(input.homeDir, ".alan", "agent", "logs");
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");
30510
31037
  const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
30511
31038
  const manifest = `<?xml version="1.0" encoding="UTF-8"?>
30512
31039
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -30528,9 +31055,9 @@ ${programArguments}
30528
31055
  <key>ThrottleInterval</key>
30529
31056
  <integer>5</integer>
30530
31057
  <key>StandardOutPath</key>
30531
- <string>${xml((0, import_node_path11.join)(logDir, "daemon.log"))}</string>
31058
+ <string>${xml((0, import_node_path12.join)(logDir, "daemon.log"))}</string>
30532
31059
  <key>StandardErrorPath</key>
30533
- <string>${xml((0, import_node_path11.join)(logDir, "daemon-error.log"))}</string>
31060
+ <string>${xml((0, import_node_path12.join)(logDir, "daemon-error.log"))}</string>
30534
31061
  </dict>
30535
31062
  </plist>
30536
31063
  `;
@@ -30569,7 +31096,7 @@ ${programArguments}
30569
31096
  };
30570
31097
  }
30571
31098
  if (input.platform === "linux") {
30572
- const manifestPath = (0, import_node_path11.join)(input.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
31099
+ const manifestPath = (0, import_node_path12.join)(input.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
30573
31100
  const manifest = `[Unit]
30574
31101
  Description=Alan local agent daemon
30575
31102
  After=network-online.target
@@ -30666,7 +31193,7 @@ WantedBy=default.target
30666
31193
  };
30667
31194
  }
30668
31195
  function currentServicePlan(args) {
30669
- const currentPlatform = (0, import_node_os8.platform)();
31196
+ const currentPlatform = (0, import_node_os9.platform)();
30670
31197
  if (currentPlatform !== "darwin" && currentPlatform !== "linux" && currentPlatform !== "win32") {
30671
31198
  throw new Error(`Daemon service installation is not supported on ${currentPlatform}`);
30672
31199
  }
@@ -30675,25 +31202,25 @@ function currentServicePlan(args) {
30675
31202
  if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
30676
31203
  return buildDaemonServicePlan({
30677
31204
  platform: currentPlatform,
30678
- homeDir: (0, import_node_os8.homedir)(),
31205
+ homeDir: (0, import_node_os9.homedir)(),
30679
31206
  nodeExecutable: process.execPath,
30680
- cliEntry: (0, import_node_path11.resolve)(cliEntry),
31207
+ cliEntry: (0, import_node_path12.resolve)(cliEntry),
30681
31208
  profile
30682
31209
  });
30683
31210
  }
30684
31211
  function writeManifest(path, contents) {
30685
- (0, import_node_fs14.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
31212
+ (0, import_node_fs15.mkdirSync)((0, import_node_path12.dirname)(path), { recursive: true, mode: 448 });
30686
31213
  const pendingPath = `${path}.pending-${process.pid}`;
30687
31214
  try {
30688
- (0, import_node_fs14.writeFileSync)(pendingPath, contents, { mode: 384 });
30689
- (0, import_node_fs14.chmodSync)(pendingPath, 384);
30690
- (0, import_node_fs14.renameSync)(pendingPath, path);
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);
30691
31218
  } finally {
30692
- (0, import_node_fs14.rmSync)(pendingPath, { force: true });
31219
+ (0, import_node_fs15.rmSync)(pendingPath, { force: true });
30693
31220
  }
30694
31221
  }
30695
31222
  function runServiceCommand(command) {
30696
- const result = (0, import_node_child_process6.spawnSync)(command.command, command.args, { encoding: "utf8" });
31223
+ const result = (0, import_node_child_process7.spawnSync)(command.command, command.args, { encoding: "utf8" });
30697
31224
  const status = result.status ?? 1;
30698
31225
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
30699
31226
  if (status !== 0 && !command.tolerateFailure) {
@@ -30711,8 +31238,8 @@ function installDaemonService(args = []) {
30711
31238
  }
30712
31239
  const plan = currentServicePlan(args);
30713
31240
  if (plan.manifestPath && plan.manifest) {
30714
- if ((0, import_node_os8.platform)() === "darwin") {
30715
- (0, import_node_fs14.mkdirSync)((0, import_node_path11.join)((0, import_node_os8.homedir)(), ".alan", "agent", "logs"), { recursive: true, mode: 448 });
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 });
30716
31243
  }
30717
31244
  writeManifest(plan.manifestPath, plan.manifest);
30718
31245
  }
@@ -30722,7 +31249,7 @@ function installDaemonService(args = []) {
30722
31249
  function uninstallDaemonService(args = []) {
30723
31250
  const plan = currentServicePlan(args);
30724
31251
  for (const command of plan.uninstallCommands) runServiceCommand(command);
30725
- if (plan.manifestPath) (0, import_node_fs14.rmSync)(plan.manifestPath, { force: true });
31252
+ if (plan.manifestPath) (0, import_node_fs15.rmSync)(plan.manifestPath, { force: true });
30726
31253
  console.info("[alan-agent] Per-user daemon service removed");
30727
31254
  }
30728
31255
  function printDaemonServiceStatus(args = []) {
@@ -30733,6 +31260,42 @@ function printDaemonServiceStatus(args = []) {
30733
31260
  }
30734
31261
 
30735
31262
  // src/index.ts
31263
+ function logEffectiveGitIdentity(lifecycle, projectPath) {
31264
+ const readConfig2 = (key) => {
31265
+ try {
31266
+ const result = (0, import_node_child_process8.spawnSync)("git", ["config", "--get", key], {
31267
+ cwd: projectPath,
31268
+ env: getDaemonCliEnvironment(),
31269
+ encoding: "utf8",
31270
+ timeout: 5e3
31271
+ });
31272
+ const value2 = result.status === 0 ? result.stdout.trim() : "";
31273
+ return value2 || null;
31274
+ } catch {
31275
+ return null;
31276
+ }
31277
+ };
31278
+ const envName = process.env.GIT_AUTHOR_NAME || null;
31279
+ const envEmail = process.env.GIT_AUTHOR_EMAIL || null;
31280
+ const configName = readConfig2("user.name");
31281
+ const configEmail = readConfig2("user.email");
31282
+ const payload = {
31283
+ projectPath,
31284
+ envAuthorName: envName,
31285
+ envAuthorEmail: envEmail,
31286
+ configUserName: configName,
31287
+ configUserEmail: configEmail
31288
+ };
31289
+ if (!envEmail && !configEmail) {
31290
+ lifecycle.error("sandbox_agent_git_identity_missing", payload);
31291
+ return;
31292
+ }
31293
+ if (envEmail && configEmail && envEmail !== configEmail) {
31294
+ lifecycle.error("sandbox_agent_git_identity_mismatch", payload);
31295
+ return;
31296
+ }
31297
+ lifecycle.info("sandbox_agent_git_identity", payload);
31298
+ }
30736
31299
  async function runSessionFromEnv() {
30737
31300
  const wsUrl = process.env.ALAN_WS_URL;
30738
31301
  const sessionId = process.env.ALAN_SESSION_ID;
@@ -30777,6 +31340,7 @@ async function runSessionFromEnv() {
30777
31340
  mode,
30778
31341
  model
30779
31342
  });
31343
+ logEffectiveGitIdentity(lifecycle, projectPath);
30780
31344
  await runSandbox({
30781
31345
  wsUrl,
30782
31346
  sessionId,