@arcships/morula-runtime 0.1.0-alpha.5 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -729,7 +729,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
729
729
  var init_build_env = __esm({
730
730
  "../morula-cli-core/dist/build-env.js"() {
731
731
  "use strict";
732
- BUILD_ID = "20260916084926.nogit";
732
+ BUILD_ID = "20260917073516.nogit";
733
733
  BUILD_CONTEXTS = {
734
734
  "local": "http://localhost:8080/api",
735
735
  "test": "https://morula.basecastle.com/api",
@@ -2042,10 +2042,219 @@ var init_dist2 = __esm({
2042
2042
  }
2043
2043
  });
2044
2044
 
2045
+ // src/daemon/spawn.ts
2046
+ var spawn_exports = {};
2047
+ __export(spawn_exports, {
2048
+ findCliEntry: () => findCliEntry,
2049
+ isSeaMode: () => isSeaMode,
2050
+ resolveDaemonRunCommand: () => resolveDaemonRunCommand,
2051
+ resolvePortalCommand: () => resolvePortalCommand,
2052
+ spawnDaemonProcess: () => spawnDaemonProcess
2053
+ });
2054
+ import { spawn as spawn2 } from "node:child_process";
2055
+ import { closeSync, existsSync as existsSync2, openSync } from "node:fs";
2056
+ import { dirname as dirname2, join as join2, sep } from "node:path";
2057
+ import { fileURLToPath } from "node:url";
2058
+ function findCliEntry() {
2059
+ try {
2060
+ const currentFile = fileURLToPath(import.meta.url);
2061
+ if (currentFile.includes(`${sep}dist${sep}`))
2062
+ return currentFile;
2063
+ const srcDir = dirname2(dirname2(currentFile));
2064
+ const srcEntry = join2(srcDir, "index.ts");
2065
+ if (existsSync2(srcEntry))
2066
+ return srcEntry;
2067
+ } catch {
2068
+ }
2069
+ return null;
2070
+ }
2071
+ function isSeaMode() {
2072
+ const base = process.execPath.split("/").pop() ?? "";
2073
+ const isNode = base === "node" || base === "node.exe";
2074
+ return !isNode || findCliEntry() === null;
2075
+ }
2076
+ function resolveDaemonRunCommand() {
2077
+ const entry = findCliEntry();
2078
+ if (entry) {
2079
+ if (entry.endsWith(".ts"))
2080
+ return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "run"] };
2081
+ return { command: process.execPath, args: [entry, "daemon", "run"] };
2082
+ }
2083
+ return { command: process.execPath, args: ["daemon", "run"] };
2084
+ }
2085
+ function resolvePortalCommand() {
2086
+ const entry = findCliEntry();
2087
+ if (entry) {
2088
+ if (entry.endsWith(".ts"))
2089
+ return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "portal"] };
2090
+ return { command: process.execPath, args: [entry, "daemon", "portal"] };
2091
+ }
2092
+ return { command: process.execPath, args: ["daemon", "portal"] };
2093
+ }
2094
+ function spawnDaemonProcess(opts) {
2095
+ const { command, args } = resolveDaemonRunCommand();
2096
+ let logFd;
2097
+ try {
2098
+ logFd = openSync(opts.logFile, "a", 384);
2099
+ } catch (error) {
2100
+ return { pid: 0, error: `\u65E0\u6CD5\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6 ${opts.logFile}: ${error instanceof Error ? error.message : String(error)}` };
2101
+ }
2102
+ try {
2103
+ const child = spawn2(command, args, {
2104
+ cwd: process.cwd(),
2105
+ detached: true,
2106
+ stdio: ["ignore", logFd, logFd],
2107
+ env: { ...process.env, ...opts.env ?? {} }
2108
+ });
2109
+ child.on("error", () => {
2110
+ try {
2111
+ closeSync(logFd);
2112
+ } catch {
2113
+ }
2114
+ });
2115
+ child.unref();
2116
+ return { pid: child.pid ?? 0 };
2117
+ } catch (error) {
2118
+ try {
2119
+ closeSync(logFd);
2120
+ } catch {
2121
+ }
2122
+ return { pid: 0, error: `\u65E0\u6CD5\u542F\u52A8 daemon: ${error instanceof Error ? error.message : String(error)}` };
2123
+ }
2124
+ }
2125
+ var init_spawn = __esm({
2126
+ "src/daemon/spawn.ts"() {
2127
+ "use strict";
2128
+ }
2129
+ });
2130
+
2131
+ // src/daemon/portal-autostart.ts
2132
+ var portal_autostart_exports = {};
2133
+ __export(portal_autostart_exports, {
2134
+ buildLaunchAgentPlist: () => buildLaunchAgentPlist,
2135
+ launchAgentPath: () => launchAgentPath,
2136
+ reloadLaunchAgent: () => reloadLaunchAgent,
2137
+ removeAutostart: () => removeAutostart,
2138
+ setupAutostart: () => setupAutostart
2139
+ });
2140
+ import { execFileSync } from "node:child_process";
2141
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "node:fs";
2142
+ import { homedir as homedir2 } from "node:os";
2143
+ import { join as join3 } from "node:path";
2144
+ function launchAgentPath() {
2145
+ return join3(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
2146
+ }
2147
+ function legacyLaunchAgentPaths() {
2148
+ return LEGACY_LABELS.map((label) => join3(homedir2(), "Library", "LaunchAgents", `${label}.plist`));
2149
+ }
2150
+ async function removeLegacyAutostart() {
2151
+ const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
2152
+ for (const legacyPath of legacyLaunchAgentPaths()) {
2153
+ if (!existsSync3(legacyPath))
2154
+ continue;
2155
+ try {
2156
+ execFileSync("launchctl", ["bootout", `gui:${uid}`, legacyPath], { stdio: "pipe" });
2157
+ } catch {
2158
+ }
2159
+ rmSync2(legacyPath, { force: true });
2160
+ console.error(`[portal] \u5DF2\u79FB\u9664\u5386\u53F2\u81EA\u542F ${legacyPath}`);
2161
+ }
2162
+ }
2163
+ function escapePlistValue(value) {
2164
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;");
2165
+ }
2166
+ function buildLaunchAgentPlist(spec) {
2167
+ const envEntries = Object.entries(spec.environmentVariables ?? {}).map(([key, value]) => ` <key>${escapePlistValue(key)}</key>
2168
+ <string>${escapePlistValue(value)}</string>`).join("\n");
2169
+ const envSection = envEntries ? `
2170
+ <key>EnvironmentVariables</key>
2171
+ <dict>
2172
+ ${envEntries}
2173
+ </dict>` : "";
2174
+ return `<?xml version="1.0" encoding="UTF-8"?>
2175
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2176
+ <plist version="1.0">
2177
+ <dict>
2178
+ <key>Label</key>
2179
+ <string>${escapePlistValue(spec.label)}</string>
2180
+ <key>ProgramArguments</key>
2181
+ <array>${[spec.command, ...spec.args].map((a) => ` <string>${escapePlistValue(a)}</string>`).join("\n")}
2182
+ </array>
2183
+ <key>RunAtLoad</key>
2184
+ <true/>
2185
+ <key>KeepAlive</key>
2186
+ <dict>
2187
+ <key>SuccessfulExit</key>
2188
+ <false/>
2189
+ </dict>${envSection}
2190
+ <key>StandardOutPath</key>
2191
+ <string>${escapePlistValue(spec.logFile)}</string>
2192
+ <key>StandardErrorPath</key>
2193
+ <string>${escapePlistValue(spec.errFile ?? spec.logFile)}</string>
2194
+ </dict>
2195
+ </plist>
2196
+ `;
2197
+ }
2198
+ async function reloadLaunchAgent(plistPath, log) {
2199
+ const uid = typeof process.getuid === "function" ? process.getuid?.() : void 0;
2200
+ const domain = `gui/${uid ?? process.env.UID ?? ""}`;
2201
+ try {
2202
+ execFileSync("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
2203
+ } catch {
2204
+ }
2205
+ try {
2206
+ execFileSync("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
2207
+ return true;
2208
+ } catch (error) {
2209
+ log(`launchctl bootstrap \u5931\u8D25\uFF08\u53EF\u624B\u52A8 launchctl load -w ${plistPath}\uFF09: ${error instanceof Error ? error.message : String(error)}`);
2210
+ return false;
2211
+ }
2212
+ }
2213
+ async function setupAutostart(context) {
2214
+ if (process.platform !== "darwin")
2215
+ return false;
2216
+ await removeLegacyAutostart();
2217
+ const { command, args } = resolvePortalCommand();
2218
+ const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join3(homedir2(), ".morula-cli");
2219
+ const daemonDir2 = join3(stateDir2, "daemon");
2220
+ mkdirSync2(daemonDir2, { recursive: true, mode: 448 });
2221
+ const logFile = join3(daemonDir2, "portal.log");
2222
+ const plist = buildLaunchAgentPlist({ label: LABEL, command, args, logFile });
2223
+ const plistPath = launchAgentPath();
2224
+ mkdirSync2(join3(plistPath, ".."), { recursive: true });
2225
+ const fs = await import("node:fs");
2226
+ fs.writeFileSync(plistPath, plist, { mode: 420 });
2227
+ await reloadLaunchAgent(plistPath, (line) => context.err(`[portal] ${line}`));
2228
+ return true;
2229
+ }
2230
+ async function removeAutostart(context) {
2231
+ if (process.platform !== "darwin") {
2232
+ context.err("[portal] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u542F\u79FB\u9664");
2233
+ return;
2234
+ }
2235
+ await removeLegacyAutostart();
2236
+ const plistPath = launchAgentPath();
2237
+ const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
2238
+ try {
2239
+ execFileSync("launchctl", ["bootout", `gui:${uid}`, plistPath], { stdio: "pipe" });
2240
+ } catch {
2241
+ }
2242
+ rmSync2(plistPath, { force: true });
2243
+ }
2244
+ var LABEL, LEGACY_LABELS;
2245
+ var init_portal_autostart = __esm({
2246
+ "src/daemon/portal-autostart.ts"() {
2247
+ "use strict";
2248
+ init_spawn();
2249
+ LABEL = "com.morula.runtime.portal";
2250
+ LEGACY_LABELS = ["com.morula-cli.portal", "com.agentctl.portal"];
2251
+ }
2252
+ });
2253
+
2045
2254
  // src/daemon/daemon-config.ts
2046
2255
  import { randomUUID } from "node:crypto";
2047
- import { homedir as homedir2 } from "node:os";
2048
- import { join as join2 } from "node:path";
2256
+ import { homedir as homedir4 } from "node:os";
2257
+ import { join as join5 } from "node:path";
2049
2258
  function loadDaemonConfig(store) {
2050
2259
  const config = store.load();
2051
2260
  const d = config?.[DAEMON_KEY];
@@ -2076,16 +2285,16 @@ var DEFAULT_WORKDIR, DEFAULT_MAX_CONCURRENT, DAEMON_KEY;
2076
2285
  var init_daemon_config = __esm({
2077
2286
  "src/daemon/daemon-config.ts"() {
2078
2287
  "use strict";
2079
- DEFAULT_WORKDIR = join2(homedir2(), ".morula", "daemon");
2288
+ DEFAULT_WORKDIR = join5(homedir4(), ".morula", "daemon");
2080
2289
  DEFAULT_MAX_CONCURRENT = 6;
2081
2290
  DAEMON_KEY = "daemon";
2082
2291
  }
2083
2292
  });
2084
2293
 
2085
2294
  // src/daemon/daemon-lock.ts
2086
- import { execFileSync } from "node:child_process";
2087
- import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2, writeSync } from "node:fs";
2088
- import { dirname as dirname2, join as join3 } from "node:path";
2295
+ import { execFileSync as execFileSync3 } from "node:child_process";
2296
+ import { closeSync as closeSync2, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync3, writeSync } from "node:fs";
2297
+ import { dirname as dirname4, join as join6 } from "node:path";
2089
2298
  function isProcessAlive(pid) {
2090
2299
  if (!pid || pid <= 0)
2091
2300
  return false;
@@ -2098,7 +2307,7 @@ function isProcessAlive(pid) {
2098
2307
  }
2099
2308
  function processCommandLine(pid) {
2100
2309
  try {
2101
- const out = execFileSync("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", stdio: "pipe" }).trim();
2310
+ const out = execFileSync3("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", stdio: "pipe" }).trim();
2102
2311
  return out || null;
2103
2312
  } catch {
2104
2313
  return null;
@@ -2160,7 +2369,7 @@ function probeDaemonLock(lockPath) {
2160
2369
  return { state: "active", record };
2161
2370
  }
2162
2371
  function writeRecord(lockPath, record) {
2163
- writeFileSync2(lockPath, `${JSON.stringify(record, null, 2)}
2372
+ writeFileSync3(lockPath, `${JSON.stringify(record, null, 2)}
2164
2373
  `, { mode: 384 });
2165
2374
  }
2166
2375
  function acquireDaemonLock(lockPath, input) {
@@ -2169,14 +2378,14 @@ function acquireDaemonLock(lockPath, input) {
2169
2378
  startedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
2170
2379
  workdir: input.workdir
2171
2380
  };
2172
- mkdirSync2(dirname2(lockPath), { recursive: true, mode: 448 });
2381
+ mkdirSync4(dirname4(lockPath), { recursive: true, mode: 448 });
2173
2382
  try {
2174
- const fd = openSync(lockPath, "wx", 384);
2383
+ const fd = openSync2(lockPath, "wx", 384);
2175
2384
  try {
2176
2385
  writeSync(fd, `${JSON.stringify(record, null, 2)}
2177
2386
  `);
2178
2387
  } finally {
2179
- closeSync(fd);
2388
+ closeSync2(fd);
2180
2389
  }
2181
2390
  return { ok: true, handle: makeHandle(lockPath, record), tookOver: false, previous: null };
2182
2391
  } catch (error) {
@@ -2204,7 +2413,7 @@ function releaseDaemonLock(lockPath, pid) {
2204
2413
  if (read && "record" in read && read.record.pid !== pid)
2205
2414
  return;
2206
2415
  }
2207
- rmSync2(lockPath, { force: true });
2416
+ rmSync4(lockPath, { force: true });
2208
2417
  }
2209
2418
  async function waitForExit(pid, timeoutMs, pollMs) {
2210
2419
  const deadline = Date.now() + timeoutMs;
@@ -2252,9 +2461,9 @@ __export(portal_instances_exports, {
2252
2461
  parseBaseUrl: () => parseBaseUrl,
2253
2462
  readPidFile: () => readPidFile
2254
2463
  });
2255
- import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
2256
- import { homedir as homedir3 } from "node:os";
2257
- import { dirname as dirname3, join as join4 } from "node:path";
2464
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
2465
+ import { homedir as homedir5 } from "node:os";
2466
+ import { dirname as dirname5, join as join7 } from "node:path";
2258
2467
  function parseBaseUrl(raw) {
2259
2468
  if (!raw)
2260
2469
  return null;
@@ -2279,7 +2488,7 @@ function normalizeBaseUrl2(raw) {
2279
2488
  return "";
2280
2489
  return `${parsed.protocol}//${parsed.host}:${parsed.port}${parsed.path}`;
2281
2490
  }
2282
- function candidateConfigFiles(env = process.env, homeDir = homedir3()) {
2491
+ function candidateConfigFiles(env = process.env, homeDir = homedir5()) {
2283
2492
  const files = [];
2284
2493
  const push = (file) => {
2285
2494
  if (file && !files.includes(file))
@@ -2287,11 +2496,11 @@ function candidateConfigFiles(env = process.env, homeDir = homedir3()) {
2287
2496
  };
2288
2497
  if (env.MORULA_CONFIG)
2289
2498
  push(env.MORULA_CONFIG);
2290
- push(join4(env.MORULA_CONFIG_DIR ?? join4(homeDir, ".morula-cli"), "config.yaml"));
2499
+ push(join7(env.MORULA_CONFIG_DIR ?? join7(homeDir, ".morula-cli"), "config.yaml"));
2291
2500
  try {
2292
2501
  for (const name of readdirSync(homeDir).sort()) {
2293
2502
  if (name.startsWith(".morula-cli-"))
2294
- push(join4(homeDir, name, "config.yaml"));
2503
+ push(join7(homeDir, name, "config.yaml"));
2295
2504
  }
2296
2505
  } catch {
2297
2506
  }
@@ -2299,7 +2508,7 @@ function candidateConfigFiles(env = process.env, homeDir = homedir3()) {
2299
2508
  const item = raw.trim();
2300
2509
  if (!item)
2301
2510
  continue;
2302
- push(item.endsWith(".yaml") || item.endsWith(".yml") ? item : join4(item, "config.yaml"));
2511
+ push(item.endsWith(".yaml") || item.endsWith(".yml") ? item : join7(item, "config.yaml"));
2303
2512
  }
2304
2513
  return files;
2305
2514
  }
@@ -2319,8 +2528,8 @@ function readContexts(file) {
2319
2528
  }
2320
2529
  }
2321
2530
  function readPidFile(configDir) {
2322
- const file = join4(configDir, "daemon", "daemon.pid");
2323
- if (!existsSync2(file))
2531
+ const file = join7(configDir, "daemon", "daemon.pid");
2532
+ if (!existsSync5(file))
2324
2533
  return 0;
2325
2534
  try {
2326
2535
  return Number(readFileSync3(file, "utf8").trim() || 0);
@@ -2332,7 +2541,7 @@ function createPortalEnvironmentResolver(options = {}) {
2332
2541
  const env = options.env ?? process.env;
2333
2542
  const log = options.log ?? (() => {
2334
2543
  });
2335
- const defaultConfigDir = dirname3(env.MORULA_CONFIG ?? join4(env.MORULA_CONFIG_DIR ?? join4(options.homeDir ?? homedir3(), ".morula-cli"), "config.yaml"));
2544
+ const defaultConfigDir = dirname5(env.MORULA_CONFIG ?? join7(env.MORULA_CONFIG_DIR ?? join7(options.homeDir ?? homedir5(), ".morula-cli"), "config.yaml"));
2336
2545
  let index = [];
2337
2546
  let signature = "";
2338
2547
  function buildIndex() {
@@ -2349,7 +2558,7 @@ function createPortalEnvironmentResolver(options = {}) {
2349
2558
  const normalized = normalizeBaseUrl2(entry.url);
2350
2559
  if (!normalized)
2351
2560
  continue;
2352
- const configDir = dirname3(file);
2561
+ const configDir = dirname5(file);
2353
2562
  built.push({
2354
2563
  configDir,
2355
2564
  context,
@@ -2397,7 +2606,7 @@ function createPortalEnvironmentResolver(options = {}) {
2397
2606
  hasToken: !!instance.token,
2398
2607
  runtimeId: () => {
2399
2608
  try {
2400
- return loadDaemonConfig(new FileConfigStore(join4(instance.configDir, "config.yaml")))?.runtimeId;
2609
+ return loadDaemonConfig(new FileConfigStore(join7(instance.configDir, "config.yaml")))?.runtimeId;
2401
2610
  } catch {
2402
2611
  return void 0;
2403
2612
  }
@@ -2456,110 +2665,24 @@ var init_portal_instances = __esm({
2456
2665
  }
2457
2666
  });
2458
2667
 
2459
- // src/daemon/spawn.ts
2460
- var spawn_exports = {};
2461
- __export(spawn_exports, {
2462
- findCliEntry: () => findCliEntry,
2463
- isSeaMode: () => isSeaMode,
2464
- resolveDaemonRunCommand: () => resolveDaemonRunCommand,
2465
- resolvePortalCommand: () => resolvePortalCommand,
2466
- spawnDaemonProcess: () => spawnDaemonProcess
2467
- });
2468
- import { spawn as spawn2 } from "node:child_process";
2469
- import { closeSync as closeSync2, existsSync as existsSync3, openSync as openSync2 } from "node:fs";
2470
- import { dirname as dirname4, join as join5, sep } from "node:path";
2471
- import { fileURLToPath } from "node:url";
2472
- function findCliEntry() {
2473
- try {
2474
- const currentFile = fileURLToPath(import.meta.url);
2475
- if (currentFile.includes(`${sep}dist${sep}`))
2476
- return currentFile;
2477
- const srcDir = dirname4(dirname4(currentFile));
2478
- const srcEntry = join5(srcDir, "index.ts");
2479
- if (existsSync3(srcEntry))
2480
- return srcEntry;
2481
- } catch {
2482
- }
2483
- return null;
2484
- }
2485
- function isSeaMode() {
2486
- const base = process.execPath.split("/").pop() ?? "";
2487
- const isNode = base === "node" || base === "node.exe";
2488
- return !isNode || findCliEntry() === null;
2489
- }
2490
- function resolveDaemonRunCommand() {
2491
- const entry = findCliEntry();
2492
- if (entry) {
2493
- if (entry.endsWith(".ts"))
2494
- return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "run"] };
2495
- return { command: process.execPath, args: [entry, "daemon", "run"] };
2496
- }
2497
- return { command: process.execPath, args: ["daemon", "run"] };
2498
- }
2499
- function resolvePortalCommand() {
2500
- const entry = findCliEntry();
2501
- if (entry) {
2502
- if (entry.endsWith(".ts"))
2503
- return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "portal"] };
2504
- return { command: process.execPath, args: [entry, "daemon", "portal"] };
2505
- }
2506
- return { command: process.execPath, args: ["daemon", "portal"] };
2507
- }
2508
- function spawnDaemonProcess(opts) {
2509
- const { command, args } = resolveDaemonRunCommand();
2510
- let logFd;
2511
- try {
2512
- logFd = openSync2(opts.logFile, "a", 384);
2513
- } catch (error) {
2514
- return { pid: 0, error: `\u65E0\u6CD5\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6 ${opts.logFile}: ${error instanceof Error ? error.message : String(error)}` };
2515
- }
2516
- try {
2517
- const child = spawn2(command, args, {
2518
- cwd: process.cwd(),
2519
- detached: true,
2520
- stdio: ["ignore", logFd, logFd],
2521
- env: { ...process.env, ...opts.env ?? {} }
2522
- });
2523
- child.on("error", () => {
2524
- try {
2525
- closeSync2(logFd);
2526
- } catch {
2527
- }
2528
- });
2529
- child.unref();
2530
- return { pid: child.pid ?? 0 };
2531
- } catch (error) {
2532
- try {
2533
- closeSync2(logFd);
2534
- } catch {
2535
- }
2536
- return { pid: 0, error: `\u65E0\u6CD5\u542F\u52A8 daemon: ${error instanceof Error ? error.message : String(error)}` };
2537
- }
2538
- }
2539
- var init_spawn = __esm({
2540
- "src/daemon/spawn.ts"() {
2541
- "use strict";
2542
- }
2543
- });
2544
-
2545
2668
  // src/daemon/portal-server.ts
2546
2669
  var portal_server_exports = {};
2547
2670
  __export(portal_server_exports, {
2548
2671
  DEFAULT_PORTAL_PORT: () => DEFAULT_PORTAL_PORT,
2549
2672
  PortalServer: () => PortalServer
2550
2673
  });
2551
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2674
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
2552
2675
  import { createServer as createServer2 } from "node:http";
2553
- import { homedir as homedir4 } from "node:os";
2554
- import { join as join6 } from "node:path";
2676
+ import { homedir as homedir6 } from "node:os";
2677
+ import { join as join8 } from "node:path";
2555
2678
  function stateDir() {
2556
- return process.env.MORULA_CONFIG_DIR ?? join6(homedir4(), ".morula-cli");
2679
+ return process.env.MORULA_CONFIG_DIR ?? join8(homedir6(), ".morula-cli");
2557
2680
  }
2558
2681
  function pidFileIn(configDir) {
2559
- return join6(configDir, "daemon", "daemon.pid");
2682
+ return join8(configDir, "daemon", "daemon.pid");
2560
2683
  }
2561
2684
  function daemonLogIn(configDir) {
2562
- return join6(configDir, "daemon", "daemon.log");
2685
+ return join8(configDir, "daemon", "daemon.log");
2563
2686
  }
2564
2687
  function isPidAlive(pid) {
2565
2688
  if (!pid || pid <= 0)
@@ -2572,7 +2695,7 @@ function isPidAlive(pid) {
2572
2695
  }
2573
2696
  }
2574
2697
  function readPidFrom(file) {
2575
- if (!existsSync4(file))
2698
+ if (!existsSync6(file))
2576
2699
  return 0;
2577
2700
  return Number(readFileSync4(file, "utf8").trim() || 0);
2578
2701
  }
@@ -2667,7 +2790,10 @@ var init_portal_server = __esm({
2667
2790
  /**
2668
2791
  * 解析请求声明的目标环境。
2669
2792
  * - 未声明 `server` → `{ target: undefined }`:沿用旧行为(portal 自身配置目录)
2670
- * - 声明了但解析不到 → 直接写出错误响应并返回 `{ failed: true }`
2793
+ * - `server` 解析不到时按 `origin`(portal-token 下发的请求来源地址)兜底:dev 用 ngrok 等
2794
+ * 隧道暴露 webhook 时 `server`(`APP_BASE_URL`)是隧道地址,而本机 daemon 实际注册的是本地地址
2795
+ * —— 两个 URL 指向同一服务进程,`origin` 能命中本机索引(docs/issues/dev-app-base-url-ngrok-portal-stop-404.md)
2796
+ * - 两个键都只作为**本机索引内的查找键**,均未命中 → fail closed 404
2671
2797
  */
2672
2798
  resolveTarget(res, url) {
2673
2799
  const requested = url.searchParams.get("server");
@@ -2689,10 +2815,11 @@ var init_portal_server = __esm({
2689
2815
  });
2690
2816
  return { failed: true };
2691
2817
  }
2692
- const target = this.environments.resolve(requested);
2818
+ const origin = url.searchParams.get("origin");
2819
+ const target = this.environments.resolve(requested) ?? (origin && normalizeBaseUrl2(origin) ? this.environments.resolve(origin) : void 0);
2693
2820
  if (!target) {
2694
2821
  const indexed = this.environments.list();
2695
- this.log(`[portal] \u672A\u7D22\u5F15\u7684\u76EE\u6807\u73AF\u5883: ${normalizeBaseUrl2(requested)}\uFF1B\u5DF2\u7D22\u5F15 ${indexed.map((i) => `${i.context}(${i.baseUrl})`).join(", ") || "\uFF08\u7A7A\uFF09"}`);
2822
+ this.log(`[portal] \u672A\u7D22\u5F15\u7684\u76EE\u6807\u73AF\u5883: ${normalizeBaseUrl2(requested)}${origin ? `\uFF08origin \u515C\u5E95 ${normalizeBaseUrl2(origin)} \u4E5F\u672A\u547D\u4E2D\uFF09` : ""}\uFF1B\u5DF2\u7D22\u5F15 ${indexed.map((i) => `${i.context}(${i.baseUrl})`).join(", ") || "\uFF08\u7A7A\uFF09"}`);
2696
2823
  this.json(res, 404, {
2697
2824
  ok: false,
2698
2825
  error: "UNKNOWN_ENV",
@@ -2758,7 +2885,7 @@ var init_portal_server = __esm({
2758
2885
  return;
2759
2886
  }
2760
2887
  try {
2761
- writeFileSync3(pidFile, String(result.pid), { mode: 384 });
2888
+ writeFileSync4(pidFile, String(result.pid), { mode: 384 });
2762
2889
  } catch {
2763
2890
  try {
2764
2891
  process.kill(result.pid, "SIGTERM");
@@ -2829,8 +2956,8 @@ var init_portal_server = __esm({
2829
2956
 
2830
2957
  // src/daemon/dim-locate.ts
2831
2958
  import { accessSync, constants } from "node:fs";
2832
- import { homedir as homedir5 } from "node:os";
2833
- import { join as join7 } from "node:path";
2959
+ import { homedir as homedir7 } from "node:os";
2960
+ import { join as join9 } from "node:path";
2834
2961
  function isExecutable(p) {
2835
2962
  try {
2836
2963
  accessSync(p, constants.X_OK);
@@ -2840,13 +2967,13 @@ function isExecutable(p) {
2840
2967
  }
2841
2968
  }
2842
2969
  function knownDimLocations() {
2843
- const home = homedir5();
2970
+ const home = homedir7();
2844
2971
  return [
2845
2972
  "/Applications/DimAgent.app/Contents/Resources/runtime/cli/dim",
2846
2973
  "/Applications/Dim.app/Contents/Resources/runtime/cli/dim",
2847
- join7(home, "Applications", "DimAgent.app", "Contents", "Resources", "runtime", "cli", "dim"),
2848
- join7(home, ".dimcode", "bin", "dim"),
2849
- join7(home, ".local", "bin", "dim")
2974
+ join9(home, "Applications", "DimAgent.app", "Contents", "Resources", "runtime", "cli", "dim"),
2975
+ join9(home, ".dimcode", "bin", "dim"),
2976
+ join9(home, ".local", "bin", "dim")
2850
2977
  ];
2851
2978
  }
2852
2979
  function resolveDimCandidates(primary) {
@@ -2859,7 +2986,7 @@ function resolveDimCandidates(primary) {
2859
2986
  for (const c of knownDimLocations())
2860
2987
  push(c);
2861
2988
  for (const dir of (process.env.PATH ?? "").split(":").filter(Boolean))
2862
- push(join7(dir, "dim"));
2989
+ push(join9(dir, "dim"));
2863
2990
  return out;
2864
2991
  }
2865
2992
  function resolveDimBinary() {
@@ -2874,7 +3001,7 @@ function resolveDimBinary() {
2874
3001
  }
2875
3002
  const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
2876
3003
  for (const dir of pathDirs) {
2877
- const candidate = join7(dir, "dim");
3004
+ const candidate = join9(dir, "dim");
2878
3005
  if (isExecutable(candidate)) {
2879
3006
  return candidate;
2880
3007
  }
@@ -2895,7 +3022,7 @@ __export(acp_connection_exports, {
2895
3022
  countDimAcpProcesses: () => countDimAcpProcesses,
2896
3023
  resolveAcpInitTimeoutMs: () => resolveAcpInitTimeoutMs
2897
3024
  });
2898
- import { execFileSync as execFileSync2, spawn as spawn3 } from "node:child_process";
3025
+ import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
2899
3026
  import { randomUUID as randomUUID2 } from "node:crypto";
2900
3027
  function resolveAcpInitTimeoutMs() {
2901
3028
  const raw = Number(process.env.MORULA_ACP_INIT_TIMEOUT_MS);
@@ -2905,7 +3032,7 @@ function resolveDimVersion(dimPath) {
2905
3032
  if (cachedDimVersion !== null)
2906
3033
  return cachedDimVersion;
2907
3034
  try {
2908
- cachedDimVersion = execFileSync2(dimPath, ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim().split("\n")[0] || "unknown";
3035
+ cachedDimVersion = execFileSync4(dimPath, ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim().split("\n")[0] || "unknown";
2909
3036
  } catch {
2910
3037
  cachedDimVersion = "unknown";
2911
3038
  }
@@ -2915,7 +3042,7 @@ function countDimAcpProcesses() {
2915
3042
  if (process.platform === "win32")
2916
3043
  return null;
2917
3044
  try {
2918
- const out = execFileSync2("ps", ["-Ao", "args"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
3045
+ const out = execFileSync4("ps", ["-Ao", "args"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
2919
3046
  return out.split("\n").filter((line) => /(^|\/)dim(\.exe)?\s+acp(\s|$)/.test(line.trim())).length;
2920
3047
  } catch {
2921
3048
  return null;
@@ -2976,9 +3103,9 @@ var init_acp_connection = __esm({
2976
3103
  process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
2977
3104
  `);
2978
3105
  try {
2979
- const { existsSync: existsSync18 } = await import("node:fs");
3106
+ const { existsSync: existsSync19 } = await import("node:fs");
2980
3107
  process.stderr.write(
2981
- `[daemon] diag: fileExists=${existsSync18(dimPath)} cwdExists=${this.options.cwd ? existsSync18(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
3108
+ `[daemon] diag: fileExists=${existsSync19(dimPath)} cwdExists=${this.options.cwd ? existsSync19(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
2982
3109
  `
2983
3110
  );
2984
3111
  } catch {
@@ -3135,7 +3262,7 @@ var init_acp_connection = __esm({
3135
3262
  this.exited = true;
3136
3263
  if (process.platform === "win32") {
3137
3264
  try {
3138
- execFileSync2("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
3265
+ execFileSync4("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
3139
3266
  } catch {
3140
3267
  }
3141
3268
  return;
@@ -3250,12 +3377,12 @@ __export(dim_home_exports, {
3250
3377
  isolatedDimHome: () => isolatedDimHome,
3251
3378
  resolveIsolatedDimEnv: () => resolveIsolatedDimEnv
3252
3379
  });
3253
- import { execFileSync as execFileSync3 } from "node:child_process";
3254
- import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync3 } from "node:fs";
3255
- import { homedir as homedir6 } from "node:os";
3256
- import { join as join8 } from "node:path";
3380
+ import { execFileSync as execFileSync5 } from "node:child_process";
3381
+ import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, rmSync as rmSync5 } from "node:fs";
3382
+ import { homedir as homedir8 } from "node:os";
3383
+ import { join as join10 } from "node:path";
3257
3384
  function isolatedDimHome() {
3258
- return join8(homedir6(), ".morula", "dimcode");
3385
+ return join10(homedir8(), ".morula", "dimcode");
3259
3386
  }
3260
3387
  function resolveIsolatedDimEnv() {
3261
3388
  return {};
@@ -3277,13 +3404,13 @@ __export(dim_model_exports, {
3277
3404
  });
3278
3405
  import { createRequire } from "node:module";
3279
3406
  import { spawnSync } from "node:child_process";
3280
- import { closeSync as closeSync3, existsSync as existsSync8, openSync as openSync3, readFileSync as readFileSync7, unlinkSync } from "node:fs";
3281
- import { homedir as homedir8, tmpdir } from "node:os";
3282
- import { join as join11 } from "node:path";
3407
+ import { closeSync as closeSync3, existsSync as existsSync10, openSync as openSync3, readFileSync as readFileSync7, unlinkSync } from "node:fs";
3408
+ import { homedir as homedir10, tmpdir } from "node:os";
3409
+ import { join as join13 } from "node:path";
3283
3410
  function dimSqlitePath(env) {
3284
3411
  const home = env?.DIMCODE_HOME?.trim();
3285
- const candidates = home && home !== "" ? [join11(home, "v2", "dimcode.sqlite"), join11(home, "dimcode.sqlite")] : [join11(homedir8(), ".dimcode", "v2", "dimcode.sqlite")];
3286
- return candidates.find((p) => existsSync8(p)) ?? null;
3412
+ const candidates = home && home !== "" ? [join13(home, "v2", "dimcode.sqlite"), join13(home, "dimcode.sqlite")] : [join13(homedir10(), ".dimcode", "v2", "dimcode.sqlite")];
3413
+ return candidates.find((p) => existsSync10(p)) ?? null;
3287
3414
  }
3288
3415
  function listDimModels(dimPath = "dim", env) {
3289
3416
  const fromCli = (() => {
@@ -3317,12 +3444,12 @@ function listDimModels(dimPath = "dim", env) {
3317
3444
  function hostDimEnv(env) {
3318
3445
  const home = env?.DIMCODE_HOME?.trim();
3319
3446
  if (home && home !== "" && home.includes(".morula") && home.includes("dimcode")) {
3320
- return { ...process.env, DIMCODE_HOME: join11(homedir8(), ".dimcode", "v2") };
3447
+ return { ...process.env, DIMCODE_HOME: join13(homedir10(), ".dimcode", "v2") };
3321
3448
  }
3322
3449
  return env ? { ...env } : null;
3323
3450
  }
3324
3451
  function listDimModelsFromCli(dimPath, env) {
3325
- const tmpFile = join11(tmpdir(), `${DIM_MODELS_TMP_PREFIX}${process.pid}-${Date.now()}.json`);
3452
+ const tmpFile = join13(tmpdir(), `${DIM_MODELS_TMP_PREFIX}${process.pid}-${Date.now()}.json`);
3326
3453
  const fd = openSync3(tmpFile, "w");
3327
3454
  let collected = "";
3328
3455
  try {
@@ -3359,9 +3486,9 @@ function listDimModelsFromCli(dimPath, env) {
3359
3486
  }
3360
3487
  function dimOAuthTokenPath(env) {
3361
3488
  const home = env?.DIMCODE_HOME?.trim();
3362
- const dir = home && home !== "" ? home : join11(homedir8(), ".dimcode", "v2");
3363
- for (const p of [join11(dir, "auth.json"), join11(dir, "v2", "auth.json")]) {
3364
- if (existsSync8(p))
3489
+ const dir = home && home !== "" ? home : join13(homedir10(), ".dimcode", "v2");
3490
+ for (const p of [join13(dir, "auth.json"), join13(dir, "v2", "auth.json")]) {
3491
+ if (existsSync10(p))
3365
3492
  return p;
3366
3493
  }
3367
3494
  return null;
@@ -3498,138 +3625,92 @@ var init_dim_model = __esm({
3498
3625
  }
3499
3626
  });
3500
3627
 
3501
- // src/daemon/portal-autostart.ts
3502
- var portal_autostart_exports = {};
3503
- __export(portal_autostart_exports, {
3504
- launchAgentPath: () => launchAgentPath,
3505
- removeAutostart: () => removeAutostart,
3506
- setupAutostart: () => setupAutostart
3507
- });
3508
- import { execFileSync as execFileSync9 } from "node:child_process";
3509
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync6 } from "node:fs";
3510
- import { homedir as homedir10 } from "node:os";
3511
- import { join as join18 } from "node:path";
3512
- function launchAgentPath() {
3513
- return join18(homedir10(), "Library", "LaunchAgents", `${LABEL}.plist`);
3628
+ // src/index.ts
3629
+ init_dist2();
3630
+
3631
+ // src/commands/registry-runtime.ts
3632
+ init_dist2();
3633
+
3634
+ // src/build-env.ts
3635
+ var BUILD_VERSION2 = "0.1.0-alpha.7";
3636
+
3637
+ // src/commands/daemon.ts
3638
+ init_dist();
3639
+ init_dist2();
3640
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync11, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
3641
+ import { homedir as homedir12 } from "node:os";
3642
+ import { dirname as dirname9, join as join20 } from "node:path";
3643
+
3644
+ // src/daemon/daemon-autostart.ts
3645
+ init_portal_autostart();
3646
+ init_spawn();
3647
+ import { execFileSync as execFileSync2 } from "node:child_process";
3648
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
3649
+ import { homedir as homedir3 } from "node:os";
3650
+ import { dirname as dirname3, join as join4 } from "node:path";
3651
+ var DAEMON_AUTOSTART_LABEL = "com.morula.runtime.daemon";
3652
+ var DAEMON_SUPERVISED_ENV = "MORULA_DAEMON_SUPERVISED";
3653
+ function daemonAutostartPlistPath() {
3654
+ return join4(homedir3(), "Library", "LaunchAgents", `${DAEMON_AUTOSTART_LABEL}.plist`);
3514
3655
  }
3515
- function legacyLaunchAgentPaths() {
3516
- return LEGACY_LABELS.map((label) => join18(homedir10(), "Library", "LaunchAgents", `${label}.plist`));
3656
+ function daemonAutostartEnabled() {
3657
+ return process.platform === "darwin" && existsSync4(daemonAutostartPlistPath());
3517
3658
  }
3518
- async function removeLegacyAutostart() {
3519
- const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
3520
- for (const legacyPath of legacyLaunchAgentPaths()) {
3521
- if (!existsSync15(legacyPath))
3522
- continue;
3523
- try {
3524
- execFileSync9("launchctl", ["bootout", `gui:${uid}`, legacyPath], { stdio: "pipe" });
3525
- } catch {
3526
- }
3527
- rmSync6(legacyPath, { force: true });
3528
- console.error(`[portal] \u5DF2\u79FB\u9664\u5386\u53F2\u81EA\u542F ${legacyPath}`);
3529
- }
3659
+ function daemonLogFile() {
3660
+ const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join4(homedir3(), ".morula-cli");
3661
+ return join4(stateDir2, "daemon", "daemon.log");
3530
3662
  }
3531
- function buildPlist(command, args, logFile, errFile) {
3532
- return `<?xml version="1.0" encoding="UTF-8"?>
3533
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3534
- <plist version="1.0">
3535
- <dict>
3536
- <key>Label</key>
3537
- <string>${LABEL}</string>
3538
- <key>ProgramArguments</key>
3539
- <array>${[command, ...args].map((a) => ` <string>${a.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</string>`).join("\n")}
3540
- </array>
3541
- <key>RunAtLoad</key>
3542
- <true/>
3543
- <key>KeepAlive</key>
3544
- <dict>
3545
- <key>SuccessfulExit</key>
3546
- <false/>
3547
- </dict>
3548
- <key>StandardOutPath</key>
3549
- <string>${logFile}</string>
3550
- <key>StandardErrorPath</key>
3551
- <string>${errFile}</string>
3552
- </dict>
3553
- </plist>
3554
- `;
3663
+ function planDaemonAutostart() {
3664
+ const { command, args } = resolveDaemonRunCommand();
3665
+ return {
3666
+ label: DAEMON_AUTOSTART_LABEL,
3667
+ plistPath: daemonAutostartPlistPath(),
3668
+ program: [command, ...args].join(" "),
3669
+ logFile: daemonLogFile()
3670
+ };
3555
3671
  }
3556
- async function setupAutostart(context) {
3557
- if (process.platform !== "darwin")
3558
- return false;
3559
- await removeLegacyAutostart();
3560
- const { command, args } = resolvePortalCommand();
3561
- const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join18(homedir10(), ".morula-cli");
3562
- const daemonDir2 = join18(stateDir2, "daemon");
3563
- mkdirSync10(daemonDir2, { recursive: true, mode: 448 });
3564
- const logFile = join18(daemonDir2, "portal.log");
3565
- const plist = buildPlist(command, args, logFile, logFile);
3566
- const plistPath = launchAgentPath();
3567
- mkdirSync10(join18(plistPath, ".."), { recursive: true });
3568
- const fs = await import("node:fs");
3569
- fs.writeFileSync(plistPath, plist, { mode: 420 });
3570
- const uid = typeof process.getuid === "function" ? process.getuid?.() : void 0;
3571
- const domain = `gui/${uid ?? process.env.UID ?? ""}`;
3572
- try {
3573
- execFileSync9("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
3574
- } catch {
3575
- }
3576
- try {
3577
- execFileSync9("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
3578
- } catch (error) {
3579
- context.err(`[portal] launchctl bootstrap \u5931\u8D25\uFF08\u53EF\u624B\u52A8 launchctl load -w ${plistPath}\uFF09: ${error instanceof Error ? error.message : String(error)}`);
3580
- return true;
3581
- }
3582
- return true;
3672
+ function buildDaemonAutostartPlist() {
3673
+ const { command, args } = resolveDaemonRunCommand();
3674
+ return buildLaunchAgentPlist({
3675
+ label: DAEMON_AUTOSTART_LABEL,
3676
+ command,
3677
+ args,
3678
+ logFile: daemonLogFile(),
3679
+ environmentVariables: { [DAEMON_SUPERVISED_ENV]: "1" }
3680
+ });
3583
3681
  }
3584
- async function removeAutostart(context) {
3585
- if (process.platform !== "darwin") {
3586
- context.err("[portal] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u542F\u79FB\u9664");
3682
+ async function setupDaemonAutostart(log) {
3683
+ const plan = planDaemonAutostart();
3684
+ mkdirSync3(dirname3(plan.logFile), { recursive: true, mode: 448 });
3685
+ mkdirSync3(dirname3(plan.plistPath), { recursive: true });
3686
+ writeFileSync2(plan.plistPath, buildDaemonAutostartPlist(), { mode: 420 });
3687
+ return await reloadLaunchAgent(plan.plistPath, (line) => log(`[autostart] ${line}`));
3688
+ }
3689
+ async function removeDaemonAutostart(log) {
3690
+ const plistPath = daemonAutostartPlistPath();
3691
+ if (!existsSync4(plistPath)) {
3692
+ log("[autostart] \u672A\u6CE8\u518C\u8FC7 daemon \u5F00\u673A\u81EA\u542F\uFF08\u5E42\u7B49\uFF0C\u65E0\u64CD\u4F5C\uFF09");
3587
3693
  return;
3588
3694
  }
3589
- await removeLegacyAutostart();
3590
- const plistPath = launchAgentPath();
3591
3695
  const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
3592
3696
  try {
3593
- execFileSync9("launchctl", ["bootout", `gui:${uid}`, plistPath], { stdio: "pipe" });
3697
+ execFileSync2("launchctl", ["bootout", `gui:${uid}`, plistPath], { stdio: "pipe" });
3594
3698
  } catch {
3595
3699
  }
3596
- rmSync6(plistPath, { force: true });
3700
+ rmSync3(plistPath, { force: true });
3597
3701
  }
3598
- var LABEL, LEGACY_LABELS;
3599
- var init_portal_autostart = __esm({
3600
- "src/daemon/portal-autostart.ts"() {
3601
- "use strict";
3602
- init_spawn();
3603
- LABEL = "com.morula.runtime.portal";
3604
- LEGACY_LABELS = ["com.morula-cli.portal", "com.agentctl.portal"];
3605
- }
3606
- });
3607
-
3608
- // src/index.ts
3609
- init_dist2();
3610
-
3611
- // src/commands/registry-runtime.ts
3612
- init_dist2();
3613
-
3614
- // src/build-env.ts
3615
- var BUILD_VERSION2 = "0.1.0-alpha.5";
3616
3702
 
3617
3703
  // src/commands/daemon.ts
3618
- init_dist();
3619
- init_dist2();
3620
3704
  init_daemon_config();
3621
3705
  init_daemon_lock();
3622
3706
  init_portal_server();
3623
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
3624
- import { homedir as homedir11 } from "node:os";
3625
- import { dirname as dirname8, join as join19 } from "node:path";
3626
3707
 
3627
3708
  // src/daemon/runner.ts
3628
3709
  init_dist2();
3629
- import { execFileSync as execFileSync8 } from "node:child_process";
3630
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
3710
+ import { execFileSync as execFileSync10 } from "node:child_process";
3711
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, statSync as statSync3, writeFileSync as writeFileSync9 } from "node:fs";
3631
3712
  import { arch, hostname } from "node:os";
3632
- import { dirname as dirname6, join as join16 } from "node:path";
3713
+ import { dirname as dirname7, join as join18 } from "node:path";
3633
3714
 
3634
3715
  // src/daemon/acp/acp-connection-pool.ts
3635
3716
  var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
@@ -4463,9 +4544,9 @@ function isFlagEnabled(name, defaultValue = true) {
4463
4544
  }
4464
4545
 
4465
4546
  // src/daemon/git-env.ts
4466
- import { execFileSync as execFileSync4 } from "node:child_process";
4467
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "node:fs";
4468
- import { join as join9 } from "node:path";
4547
+ import { execFileSync as execFileSync6 } from "node:child_process";
4548
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "node:fs";
4549
+ import { join as join11 } from "node:path";
4469
4550
  var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
4470
4551
  function prependToPath(dirs) {
4471
4552
  const cur = process.env.PATH ?? "";
@@ -4477,18 +4558,18 @@ function prependToPath(dirs) {
4477
4558
  }
4478
4559
  function ensureGitOnPath() {
4479
4560
  try {
4480
- execFileSync4("git", ["--version"], { stdio: "pipe" });
4561
+ execFileSync6("git", ["--version"], { stdio: "pipe" });
4481
4562
  return;
4482
4563
  } catch {
4483
4564
  }
4484
- const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync6(join9(d, "git"))));
4565
+ const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync8(join11(d, "git"))));
4485
4566
  if (missing.length) {
4486
4567
  console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
4487
4568
  }
4488
4569
  }
4489
4570
  function ensureNodeOnPath() {
4490
- const nodeDir = process.execPath ? join9(process.execPath, "..") : "";
4491
- const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync6(join9(d, "node")));
4571
+ const nodeDir = process.execPath ? join11(process.execPath, "..") : "";
4572
+ const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync8(join11(d, "node")));
4492
4573
  const missing = prependToPath(candidates);
4493
4574
  if (missing.length) {
4494
4575
  console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
@@ -4524,37 +4605,62 @@ function buildGitlabSshRemote(repoFullName, baseUrl) {
4524
4605
  return `ssh://git@${host}:${url.port}/${repoFullName}.git`;
4525
4606
  return `git@${host}:${repoFullName}.git`;
4526
4607
  }
4527
- function ensureRepo(workdir, tenantId, repo, env, remoteUrl) {
4528
- const repoDir = join9(workdir, tenantId, repo);
4529
- if (existsSync6(join9(repoDir, ".git"))) {
4608
+ function buildDaemonRepoUrl(repoFullName, baseUrl) {
4609
+ if (!baseUrl)
4610
+ return `https://github.com/${repoFullName}.git`;
4611
+ const url = new URL(baseUrl);
4612
+ const path = repoFullName.replace(/^\/+/, "");
4613
+ return `${url.protocol}//${url.host}/${path}.git`;
4614
+ }
4615
+ function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
4616
+ const repoDir = join11(workdir, tenantId, repo);
4617
+ const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
4618
+ if (existsSync8(join11(repoDir, ".git"))) {
4530
4619
  if (remoteUrl)
4531
4620
  syncRemoteUrl(repoDir, remoteUrl, env);
4532
4621
  try {
4533
- execFileSync4("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
4534
- } catch {
4622
+ if (daemonRemoteUrl) {
4623
+ execFileSync6(
4624
+ "git",
4625
+ ["-C", repoDir, "fetch", "--prune", fetchUrl, "+refs/heads/*:refs/remotes/origin/*"],
4626
+ { env, stdio: "pipe" }
4627
+ );
4628
+ } else {
4629
+ execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
4630
+ }
4631
+ } catch (err) {
4632
+ throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
4535
4633
  }
4536
4634
  return repoDir;
4537
4635
  }
4538
- mkdirSync4(repoDir, { recursive: true });
4539
- execFileSync4("git", ["clone", "--filter=blob:none", remoteUrl ?? `https://github.com/${repo}`, repoDir], {
4540
- env,
4541
- stdio: "pipe"
4542
- });
4636
+ mkdirSync6(repoDir, { recursive: true });
4637
+ try {
4638
+ execFileSync6("git", ["clone", "--filter=blob:none", fetchUrl, repoDir], { env, stdio: "pipe" });
4639
+ } catch (err) {
4640
+ throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
4641
+ }
4642
+ if (remoteUrl && remoteUrl !== fetchUrl)
4643
+ syncRemoteUrl(repoDir, remoteUrl, env);
4543
4644
  return repoDir;
4544
4645
  }
4646
+ function gitErrorText(err) {
4647
+ const e = err;
4648
+ const stderr = e?.stderr ? e.stderr.toString().trim() : "";
4649
+ return stderr || e?.message || String(err);
4650
+ }
4545
4651
  function syncRemoteUrl(repoDir, remoteUrl, env) {
4546
4652
  try {
4547
- const cur = execFileSync4("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
4653
+ const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
4548
4654
  if (cur === remoteUrl)
4549
4655
  return;
4550
- execFileSync4("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
4656
+ execFileSync6("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
4551
4657
  } catch {
4552
4658
  }
4553
4659
  }
4554
4660
  function isLinkedWorktree(repoDir) {
4555
4661
  try {
4556
- const gitDir = execFileSync4("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
4557
- return !!gitDir && existsSync6(join9(gitDir, "commondir"));
4662
+ const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
4663
+ return !!gitDir && existsSync8(join11(gitDir, "commondir"));
4558
4664
  } catch {
4559
4665
  return false;
4560
4666
  }
@@ -4562,7 +4668,7 @@ function isLinkedWorktree(repoDir) {
4562
4668
  function enableWorktreeConfig(repoDir) {
4563
4669
  const getLocal = () => {
4564
4670
  try {
4565
- return execFileSync4("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
4671
+ return execFileSync6("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
4566
4672
  } catch {
4567
4673
  return null;
4568
4674
  }
@@ -4570,7 +4676,7 @@ function enableWorktreeConfig(repoDir) {
4570
4676
  if (getLocal() === "true")
4571
4677
  return true;
4572
4678
  try {
4573
- execFileSync4("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
4679
+ execFileSync6("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
4574
4680
  return true;
4575
4681
  } catch {
4576
4682
  return getLocal() === "true";
@@ -4581,41 +4687,41 @@ function ensureGitIdentity(repoDir, personaName) {
4581
4687
  const email = `${personaName.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}@morula.local`;
4582
4688
  if (isLinkedWorktree(repoDir)) {
4583
4689
  if (enableWorktreeConfig(repoDir)) {
4584
- execFileSync4("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
4585
- execFileSync4("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
4690
+ execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
4691
+ execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
4586
4692
  return;
4587
4693
  }
4588
4694
  console.warn(`[daemon] worktree \u7EA7 config \u4E0D\u53EF\u7528\uFF08extensions.worktreeConfig \u542F\u7528\u5931\u8D25\uFF09\uFF0C\u9000\u56DE\u5199\u5171\u4EAB config: ${repoDir}`);
4589
4695
  }
4590
- execFileSync4("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
4591
- execFileSync4("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
4696
+ execFileSync6("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
4697
+ execFileSync6("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
4592
4698
  }
4593
4699
 
4594
4700
  // src/daemon/outbox.ts
4595
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
4596
- import { homedir as homedir7 } from "node:os";
4597
- import { join as join10 } from "node:path";
4701
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "node:fs";
4702
+ import { homedir as homedir9 } from "node:os";
4703
+ import { join as join12 } from "node:path";
4598
4704
  var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
4599
4705
  var OUTBOX_MAX_AGE_MS = 24 * 36e5;
4600
4706
  function outboxDir(configDir) {
4601
- const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join10(homedir7(), ".morula-cli");
4602
- return join10(base, "daemon", "outbox");
4707
+ const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join12(homedir9(), ".morula-cli");
4708
+ return join12(base, "daemon", "outbox");
4603
4709
  }
4604
4710
  function writeOutbox(entry, configDir) {
4605
4711
  const dir = outboxDir(configDir);
4606
- mkdirSync5(dir, { recursive: true, mode: 448 });
4607
- writeFileSync4(join10(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
4712
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4713
+ writeFileSync5(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
4608
4714
  }
4609
4715
  function readOutboxEntries(configDir) {
4610
4716
  const dir = outboxDir(configDir);
4611
- if (!existsSync7(dir))
4717
+ if (!existsSync9(dir))
4612
4718
  return [];
4613
4719
  const entries = [];
4614
4720
  for (const file of readdirSync2(dir)) {
4615
4721
  if (!file.endsWith(".json"))
4616
4722
  continue;
4617
4723
  try {
4618
- const raw = readFileSync6(join10(dir, file), "utf8");
4724
+ const raw = readFileSync6(join12(dir, file), "utf8");
4619
4725
  const parsed = JSON.parse(raw);
4620
4726
  if (parsed && typeof parsed.taskId === "string")
4621
4727
  entries.push(parsed);
@@ -4625,17 +4731,17 @@ function readOutboxEntries(configDir) {
4625
4731
  return entries;
4626
4732
  }
4627
4733
  function removeOutbox(taskId, configDir) {
4628
- rmSync4(join10(outboxDir(configDir), `${taskId}.json`), { force: true });
4734
+ rmSync6(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
4629
4735
  }
4630
4736
  function staleDir(configDir) {
4631
- return join10(outboxDir(configDir), "stale");
4737
+ return join12(outboxDir(configDir), "stale");
4632
4738
  }
4633
4739
  function archiveOutbox(entry, reason, configDir) {
4634
4740
  const dir = staleDir(configDir);
4635
- mkdirSync5(dir, { recursive: true, mode: 448 });
4741
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4636
4742
  try {
4637
- writeFileSync4(
4638
- join10(dir, `${entry.taskId}.json`),
4743
+ writeFileSync5(
4744
+ join12(dir, `${entry.taskId}.json`),
4639
4745
  JSON.stringify({ ...entry, archivedReason: reason, archivedAt: (/* @__PURE__ */ new Date()).toISOString() }),
4640
4746
  { mode: 384 }
4641
4747
  );
@@ -5622,8 +5728,8 @@ var DimAgentAdapter = class {
5622
5728
  }
5623
5729
  }
5624
5730
  async probe() {
5625
- const { existsSync: existsSync18 } = await import("node:fs");
5626
- const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
5731
+ const { existsSync: existsSync19 } = await import("node:fs");
5732
+ const installed = existsSync19(this.dimPath) || this.dimPath === "dim";
5627
5733
  let authStatus = installed ? "unknown" : "error";
5628
5734
  let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
5629
5735
  let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
@@ -5914,68 +6020,95 @@ function pickLegacyDimModels(inventory) {
5914
6020
  }
5915
6021
 
5916
6022
  // src/daemon/repo-cache.ts
5917
- import { execFileSync as execFileSync5 } from "node:child_process";
5918
- import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "node:fs";
5919
- import { dirname as dirname5, join as join12 } from "node:path";
6023
+ import { execFileSync as execFileSync7 } from "node:child_process";
6024
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
6025
+ import { dirname as dirname6, join as join14 } from "node:path";
5920
6026
  function ownerOf(repoFullName) {
5921
6027
  const idx = repoFullName.indexOf("/");
5922
6028
  return idx > 0 ? repoFullName.slice(0, idx) : "_default";
5923
6029
  }
5924
6030
  function buildRepoCachePath(workdir, tenantId, repoFullName) {
5925
- return join12(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
6031
+ return join14(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
5926
6032
  }
5927
- function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
6033
+ function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
5928
6034
  const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
5929
- const url = remoteUrl ?? `https://github.com/${repoFullName}`;
6035
+ const currentOrigin = () => {
6036
+ if (!existsSync11(join14(repoCachePath, ".git")))
6037
+ return null;
6038
+ try {
6039
+ return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
6040
+ } catch {
6041
+ return null;
6042
+ }
6043
+ };
6044
+ const fetchUrl = () => daemonRemoteUrl ?? currentOrigin() ?? agentRemoteUrl ?? `https://github.com/${repoFullName}.git`;
6045
+ const originUrl = () => agentRemoteUrl ?? fetchUrl();
5930
6046
  return {
5931
6047
  repoCachePath,
5932
6048
  ensure() {
5933
- if (existsSync9(join12(repoCachePath, ".git"))) {
5934
- if (url)
5935
- syncRemoteUrl(repoCachePath, url, env);
6049
+ if (existsSync11(join14(repoCachePath, ".git"))) {
6050
+ syncRemoteUrl(repoCachePath, originUrl(), env);
5936
6051
  return;
5937
6052
  }
5938
- mkdirSync6(dirname5(repoCachePath), { recursive: true });
5939
- execFileSync5("git", ["clone", "--filter=blob:none", url, repoCachePath], {
5940
- env,
5941
- stdio: "pipe"
5942
- });
6053
+ mkdirSync8(dirname6(repoCachePath), { recursive: true });
6054
+ const cloneUrl = fetchUrl();
6055
+ try {
6056
+ execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
6057
+ } catch (err) {
6058
+ throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
6059
+ }
6060
+ if (originUrl() !== cloneUrl)
6061
+ syncRemoteUrl(repoCachePath, originUrl(), env);
5943
6062
  },
5944
6063
  fetch() {
5945
6064
  try {
5946
- execFileSync5("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
5947
- } catch {
6065
+ if (daemonRemoteUrl) {
6066
+ execFileSync7(
6067
+ "git",
6068
+ ["-C", repoCachePath, "fetch", "--prune", daemonRemoteUrl, "+refs/heads/*:refs/remotes/origin/*"],
6069
+ { env, stdio: "pipe" }
6070
+ );
6071
+ } else {
6072
+ execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
6073
+ }
6074
+ } catch (err) {
6075
+ throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
5948
6076
  }
5949
6077
  }
5950
6078
  };
5951
6079
  }
6080
+ function gitErrorText2(err) {
6081
+ const e = err;
6082
+ const stderr = e?.stderr ? e.stderr.toString().trim() : "";
6083
+ return stderr || e?.message || String(err);
6084
+ }
5952
6085
 
5953
6086
  // src/daemon/repo-checkout-service.ts
5954
6087
  import { createServer as createServer3 } from "node:http";
5955
- import { execFileSync as execFileSync7 } from "node:child_process";
5956
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
5957
- import { basename as basename2, join as join15 } from "node:path";
6088
+ import { execFileSync as execFileSync9 } from "node:child_process";
6089
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
6090
+ import { basename as basename2, join as join17 } from "node:path";
5958
6091
 
5959
6092
  // src/daemon/worktree-manager.ts
5960
- import { execFileSync as execFileSync6 } from "node:child_process";
5961
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
5962
- import { basename, join as join14, sep as sep2 } from "node:path";
6093
+ import { execFileSync as execFileSync8 } from "node:child_process";
6094
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
6095
+ import { basename, join as join16, sep as sep2 } from "node:path";
5963
6096
 
5964
6097
  // src/daemon/workspace-marker.ts
5965
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
5966
- import { join as join13 } from "node:path";
6098
+ import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
6099
+ import { join as join15 } from "node:path";
5967
6100
  var WORKTREE_MARKER_FILE = ".morula-worktree.json";
5968
6101
  var WORKTREE_MARKER_SCHEMA = 1;
5969
6102
  function markerPath(worktreePath) {
5970
- return join13(worktreePath, WORKTREE_MARKER_FILE);
6103
+ return join15(worktreePath, WORKTREE_MARKER_FILE);
5971
6104
  }
5972
6105
  function writeWorktreeMarker(worktreePath, marker) {
5973
- writeFileSync5(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
6106
+ writeFileSync6(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
5974
6107
  `, "utf8");
5975
6108
  }
5976
6109
  function readWorktreeMarker(worktreePath) {
5977
6110
  const p = markerPath(worktreePath);
5978
- if (!existsSync10(p))
6111
+ if (!existsSync12(p))
5979
6112
  return null;
5980
6113
  try {
5981
6114
  const raw = JSON.parse(readFileSync8(p, "utf8"));
@@ -6002,42 +6135,42 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
6002
6135
  throw new Error("WORKTREE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 worktree \u8DEF\u5F84");
6003
6136
  }
6004
6137
  const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
6005
- return join14(workdir, "worktrees", tenantId, runtimeSegment, dirName);
6138
+ return join16(workdir, "worktrees", tenantId, runtimeSegment, dirName);
6006
6139
  }
6007
6140
  function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
6008
- return join14(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
6141
+ return join16(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
6009
6142
  }
6010
6143
  function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
6011
6144
  const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
6012
- return join14(workdir, "worktrees", tenantId, dirName);
6145
+ return join16(workdir, "worktrees", tenantId, dirName);
6013
6146
  }
6014
6147
  var legacyPathWarned = /* @__PURE__ */ new Set();
6015
6148
  function warnLegacyWorktreeResidue(legacyPath) {
6016
- if (legacyPathWarned.has(legacyPath) || !existsSync11(legacyPath))
6149
+ if (legacyPathWarned.has(legacyPath) || !existsSync13(legacyPath))
6017
6150
  return;
6018
6151
  legacyPathWarned.add(legacyPath);
6019
6152
  console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5F62\u6001 worktree \u6B8B\u7559\uFF08\u4E0D\u542B runtimeId \u6BB5\uFF09: ${legacyPath} \u2014\u2014 \u8BE5\u8DEF\u5F84\u5DF2\u5F03\u7528\uFF0C\u65B0\u4EFB\u52A1\u4F7F\u7528 <worktree>/<tenant>/<runtimeId8>/<task>\uFF1B\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
6020
6153
  }
6021
6154
  var legacyLayoutWarned = /* @__PURE__ */ new Set();
6022
6155
  function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
6023
- if (legacyLayoutWarned.has(taskRoot) || existsSync11(worktreePath) || !existsSync11(join14(taskRoot, WORKTREE_MARKER_FILE)))
6156
+ if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join16(taskRoot, WORKTREE_MARKER_FILE)))
6024
6157
  return;
6025
6158
  legacyLayoutWarned.add(taskRoot);
6026
6159
  console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5E03\u5C40\u4EFB\u52A1\u5DE5\u4F5C\u533A\uFF08\u4ED3\u5E93\u76F4\u63A5\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u6839\uFF09: ${taskRoot} \u2014\u2014 \u65B0\u5E03\u5C40\u4F1A\u5728\u5176\u4E0B\u521B\u5EFA\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF0C\u65E7\u76EE\u5F55\u5185\u5BB9\u4E0D\u518D\u88AB\u4F7F\u7528\uFF1B\u5176\u4E2D\u53EF\u80FD\u6709\u672A\u63A8\u9001\u6539\u52A8\uFF0C\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
6027
6160
  }
6028
6161
  function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
6029
- const gitignorePath = join14(worktreePath, ".gitignore");
6030
- const existing = existsSync11(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
6162
+ const gitignorePath = join16(worktreePath, ".gitignore");
6163
+ const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
6031
6164
  const requiredLines = [WORKTREE_MARKER_FILE];
6032
6165
  const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
6033
6166
  if (missing.length > 0) {
6034
6167
  const next = `${existing.trimEnd() ? `${existing.trimEnd()}
6035
6168
  ` : ""}${missing.join("\n")}
6036
6169
  `;
6037
- writeFileSync6(gitignorePath, next, "utf8");
6170
+ writeFileSync7(gitignorePath, next, "utf8");
6038
6171
  try {
6039
- execFileSync6("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
6040
- execFileSync6(
6172
+ execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
6173
+ execFileSync8(
6041
6174
  "git",
6042
6175
  ["-C", worktreePath, "-c", "user.name=morula-daemon", "-c", "user.email=morula-daemon@morula.local", "commit", "-q", "-m", "chore: track morula worktree marker"],
6043
6176
  { env, stdio: "pipe" }
@@ -6048,7 +6181,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
6048
6181
  }
6049
6182
  function currentBranch(repoPath) {
6050
6183
  try {
6051
- const out = execFileSync6("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
6184
+ const out = execFileSync8("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
6052
6185
  encoding: "utf8",
6053
6186
  stdio: "pipe"
6054
6187
  }).trim();
@@ -6059,13 +6192,13 @@ function currentBranch(repoPath) {
6059
6192
  }
6060
6193
  function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
6061
6194
  try {
6062
- const current = execFileSync6("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
6195
+ const current = execFileSync8("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
6063
6196
  if (current)
6064
6197
  return current;
6065
6198
  } catch {
6066
6199
  }
6067
6200
  try {
6068
- const out = execFileSync6("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
6201
+ const out = execFileSync8("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
6069
6202
  const first = out.split("\n").map((l) => l.trim()).find((l) => l && !l.includes("->"));
6070
6203
  if (first)
6071
6204
  return first.replace(/^origin\//, "");
@@ -6076,7 +6209,7 @@ function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
6076
6209
  }
6077
6210
  function headCommitSubject(repoPath, env) {
6078
6211
  try {
6079
- const out = execFileSync6("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
6212
+ const out = execFileSync8("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
6080
6213
  encoding: "utf8",
6081
6214
  stdio: "pipe",
6082
6215
  ...env ? { env } : {}
@@ -6088,7 +6221,7 @@ function headCommitSubject(repoPath, env) {
6088
6221
  }
6089
6222
  function hasLocalChanges(repoPath, env) {
6090
6223
  try {
6091
- const out = execFileSync6("git", ["-C", repoPath, "status", "--porcelain"], {
6224
+ const out = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], {
6092
6225
  encoding: "utf8",
6093
6226
  stdio: "pipe",
6094
6227
  ...env ? { env } : {}
@@ -6114,27 +6247,27 @@ function sleepSync2(ms) {
6114
6247
  function backoffMs(baseMs, random = Math.random) {
6115
6248
  return Math.round(baseMs * (0.5 + random() * 0.5));
6116
6249
  }
6117
- function gitErrorText(err) {
6250
+ function gitErrorText3(err) {
6118
6251
  const e = err;
6119
6252
  const stderr = e?.stderr ? e.stderr.toString() : "";
6120
6253
  return `${stderr}${stderr ? "\n" : ""}${e?.message ?? String(err)}`;
6121
6254
  }
6122
6255
  function isIndexLockConflict(err) {
6123
- const text = gitErrorText(err);
6256
+ const text = gitErrorText3(err);
6124
6257
  return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
6125
6258
  }
6126
6259
  function worktreeIndexLockPath(repoCachePath, worktreePath) {
6127
- return join14(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
6260
+ return join16(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
6128
6261
  }
6129
6262
  function indexLockPathFromError(text) {
6130
6263
  const m = text.match(/[^\s'‘’"]*index\.lock/);
6131
6264
  return m && m[0].length > "index.lock".length ? m[0] : null;
6132
6265
  }
6133
6266
  function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
6134
- if (!existsSync11(lockPath))
6267
+ if (!existsSync13(lockPath))
6135
6268
  return true;
6136
6269
  const deadline = Date.now() + timeoutMs;
6137
- while (existsSync11(lockPath)) {
6270
+ while (existsSync13(lockPath)) {
6138
6271
  const remain = deadline - Date.now();
6139
6272
  if (remain <= 0)
6140
6273
  return false;
@@ -6144,7 +6277,7 @@ function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, p
6144
6277
  }
6145
6278
  function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
6146
6279
  const runAdd = deps.runAdd ?? ((argv2) => {
6147
- execFileSync6("git", argv2, { stdio: "pipe" });
6280
+ execFileSync8("git", argv2, { stdio: "pipe" });
6148
6281
  });
6149
6282
  const argv = ["-C", repoCachePath, "worktree", "add", "--detach", worktreePath, ref];
6150
6283
  const waitMs = deps.indexLockWaitMs ?? INDEX_LOCK_WAIT_MAX_MS;
@@ -6161,7 +6294,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
6161
6294
  throw err;
6162
6295
  if (attempt === WORKTREE_ADD_MAX_ATTEMPTS)
6163
6296
  break;
6164
- const lockPath = indexLockPathFromError(gitErrorText(err)) ?? worktreeIndexLockPath(repoCachePath, worktreePath);
6297
+ const lockPath = indexLockPathFromError(gitErrorText3(err)) ?? worktreeIndexLockPath(repoCachePath, worktreePath);
6165
6298
  const released = waitForIndexLockRelease(lockPath, waitMs);
6166
6299
  const backoff = backoffMs(WORKTREE_ADD_BACKOFF_MS[attempt - 1]);
6167
6300
  console.warn(
@@ -6170,7 +6303,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
6170
6303
  sleepSync2(backoff);
6171
6304
  }
6172
6305
  }
6173
- throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${gitErrorText(lastErr).trim()}\uFF09`);
6306
+ throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${gitErrorText3(lastErr).trim()}\uFF09`);
6174
6307
  }
6175
6308
  var FOREIGN_RESIDUE_MAX_ENTRIES = 64;
6176
6309
  var FOREIGN_RESIDUE_SCAN_LIMIT = 2e3;
@@ -6200,7 +6333,7 @@ function inspectForeignDirectory(dir) {
6200
6333
  if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
6201
6334
  return;
6202
6335
  count++;
6203
- const full = join14(p, e.name);
6336
+ const full = join16(p, e.name);
6204
6337
  try {
6205
6338
  const st = statSync2(full);
6206
6339
  bytes += st.size;
@@ -6232,13 +6365,13 @@ function reportForeignResidue(worktreePath, forensics, cleaned) {
6232
6365
  console.warn(`[daemon] ${detail}`);
6233
6366
  }
6234
6367
  function prepareWorktree(input) {
6235
- const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl);
6368
+ const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
6236
6369
  cache.ensure();
6237
6370
  cache.fetch();
6238
6371
  const requestedBase = input.baseBranch?.trim();
6239
6372
  const hasRef = (ref) => {
6240
6373
  try {
6241
- execFileSync6("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
6374
+ execFileSync8("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
6242
6375
  return true;
6243
6376
  } catch {
6244
6377
  return false;
@@ -6250,14 +6383,14 @@ function prepareWorktree(input) {
6250
6383
  } else {
6251
6384
  const remoteHead = (() => {
6252
6385
  try {
6253
- const out = execFileSync6("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
6386
+ const out = execFileSync8("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
6254
6387
  const name = out.replace(/^refs\/remotes\/origin\//, "");
6255
6388
  if (name && hasRef(name))
6256
6389
  return name;
6257
6390
  } catch {
6258
6391
  }
6259
6392
  try {
6260
- const out = execFileSync6("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
6393
+ const out = execFileSync8("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
6261
6394
  const m = out.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD/m);
6262
6395
  return m?.[1] ?? null;
6263
6396
  } catch {
@@ -6276,10 +6409,10 @@ function prepareWorktree(input) {
6276
6409
  const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
6277
6410
  warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
6278
6411
  warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
6279
- if (existsSync11(worktreePath)) {
6412
+ if (existsSync13(worktreePath)) {
6280
6413
  const isGitRepo = (() => {
6281
6414
  try {
6282
- execFileSync6("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
6415
+ execFileSync8("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
6283
6416
  return true;
6284
6417
  } catch {
6285
6418
  return false;
@@ -6288,7 +6421,7 @@ function prepareWorktree(input) {
6288
6421
  if (!isGitRepo) {
6289
6422
  const forensics = inspectForeignDirectory(worktreePath);
6290
6423
  try {
6291
- rmSync5(worktreePath, { recursive: true, force: true });
6424
+ rmSync7(worktreePath, { recursive: true, force: true });
6292
6425
  } catch {
6293
6426
  reportForeignResidue(worktreePath, forensics, false);
6294
6427
  throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
@@ -6306,11 +6439,11 @@ function prepareWorktree(input) {
6306
6439
  }
6307
6440
  const branch = currentBranch(worktreePath);
6308
6441
  if (!branch) {
6309
- const dirtyOut = execFileSync6("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6442
+ const dirtyOut = execFileSync8("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6310
6443
  const dirty = dirtyOut.split("\n").filter(
6311
6444
  (l) => l.trim() && !l.trim().startsWith("?? .agents") && !l.trim().startsWith("?? .agent_context") && !l.trim().startsWith("?? .morula")
6312
6445
  ).join("\n");
6313
- const extraCommits = Number(execFileSync6(
6446
+ const extraCommits = Number(execFileSync8(
6314
6447
  "git",
6315
6448
  ["-C", worktreePath, "rev-list", "--count", `origin/${input.baseBranch}..HEAD`],
6316
6449
  { encoding: "utf8", stdio: "pipe" }
@@ -6318,7 +6451,7 @@ function prepareWorktree(input) {
6318
6451
  const rescueTag = buildRescueTag(input.workContextId, input.code);
6319
6452
  if (dirty.length > 0) {
6320
6453
  try {
6321
- execFileSync6(
6454
+ execFileSync8(
6322
6455
  "git",
6323
6456
  ["-C", worktreePath, "stash", "push", "-u", "-m", `morula-rescue ${rescueTag}`],
6324
6457
  { stdio: "pipe" }
@@ -6329,9 +6462,9 @@ function prepareWorktree(input) {
6329
6462
  }
6330
6463
  }
6331
6464
  if (extraCommits > 1) {
6332
- const headSha2 = execFileSync6("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
6465
+ const headSha2 = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
6333
6466
  try {
6334
- execFileSync6("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
6467
+ execFileSync8("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
6335
6468
  console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: detached \u672A\u63A8\u9001\u63D0\u4EA4\u5DF2\u5B58 refs/morula-rescue/${rescueTag}\uFF08git -C ${cache.repoCachePath} log -1 refs/morula-rescue/${rescueTag} \u67E5\u770B\uFF09`);
6336
6469
  } catch {
6337
6470
  throw new Error("WORKTREE_BRANCH_DRIFT: worktree \u5904\u4E8E detached \u4E14\u542B\u672A\u63A8\u9001\u63D0\u4EA4\uFF0C\u81EA\u52A8\u5F15\u7528\u4FDD\u5168\u5931\u8D25\uFF0C\u62D2\u7EDD\u81EA\u52A8\u91CD\u5EFA\uFF08\u8BF7\u5148\u4EBA\u5DE5\u5904\u7406\uFF09");
@@ -6343,8 +6476,8 @@ function prepareWorktree(input) {
6343
6476
  }
6344
6477
  }
6345
6478
  }
6346
- if (!existsSync11(worktreePath)) {
6347
- mkdirSync7(join14(worktreePath, ".."), { recursive: true });
6479
+ if (!existsSync13(worktreePath)) {
6480
+ mkdirSync9(join16(worktreePath, ".."), { recursive: true });
6348
6481
  addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
6349
6482
  writeWorktreeMarker(worktreePath, {
6350
6483
  schemaVersion: 1,
@@ -6360,9 +6493,9 @@ function prepareWorktree(input) {
6360
6493
  }
6361
6494
  {
6362
6495
  const dotMorulaRescue = `${worktreePath}.morula-rescue`;
6363
- if (existsSync11(dotMorulaRescue)) {
6364
- const dotMorulaDir = join14(worktreePath, ".morula");
6365
- if (!existsSync11(dotMorulaDir)) {
6496
+ if (existsSync13(dotMorulaRescue)) {
6497
+ const dotMorulaDir = join16(worktreePath, ".morula");
6498
+ if (!existsSync13(dotMorulaDir)) {
6366
6499
  try {
6367
6500
  renameSync2(dotMorulaRescue, dotMorulaDir);
6368
6501
  console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: \u65E7\u7248 .morula/ \u5907\u4EFD\u5DF2\u5F52\u8FD8\uFF08checkout \u51ED\u636E\u4E0E\u6B21\u4ED3\u5E93\u4FDD\u7559\uFF09`);
@@ -6370,11 +6503,11 @@ function prepareWorktree(input) {
6370
6503
  console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
6371
6504
  }
6372
6505
  } else {
6373
- rmSync5(dotMorulaRescue, { recursive: true, force: true });
6506
+ rmSync7(dotMorulaRescue, { recursive: true, force: true });
6374
6507
  }
6375
6508
  }
6376
6509
  }
6377
- const headSha = execFileSync6("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
6510
+ const headSha = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
6378
6511
  encoding: "utf8",
6379
6512
  stdio: "pipe"
6380
6513
  }).trim();
@@ -6390,10 +6523,10 @@ function prepareWorktree(input) {
6390
6523
  };
6391
6524
  }
6392
6525
  function resolveWorkspaceLayout(root, expected) {
6393
- const primaryDir = join14(root, safeDirName(expected.repoFullName));
6394
- if (existsSync11(join14(primaryDir, WORKTREE_MARKER_FILE)))
6526
+ const primaryDir = join16(root, safeDirName(expected.repoFullName));
6527
+ if (existsSync13(join16(primaryDir, WORKTREE_MARKER_FILE)))
6395
6528
  return { primaryDir, legacy: false };
6396
- if (existsSync11(join14(root, WORKTREE_MARKER_FILE)))
6529
+ if (existsSync13(join16(root, WORKTREE_MARKER_FILE)))
6397
6530
  return { primaryDir: root, legacy: true };
6398
6531
  return null;
6399
6532
  }
@@ -6408,7 +6541,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6408
6541
  return "MARKER_MISMATCH";
6409
6542
  const { primaryDir, legacy } = layout;
6410
6543
  try {
6411
- const status3 = execFileSync6("git", ["-C", primaryDir, "status", "--porcelain"], {
6544
+ const status3 = execFileSync8("git", ["-C", primaryDir, "status", "--porcelain"], {
6412
6545
  encoding: "utf8",
6413
6546
  stdio: "pipe"
6414
6547
  }).trim();
@@ -6417,11 +6550,11 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6417
6550
  ).join("\n");
6418
6551
  if (statusRelevant.length > 0)
6419
6552
  return "WORKTREE_DIRTY";
6420
- const additionalActivity = legacy ? findActivityInReposDir(join14(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
6553
+ const additionalActivity = legacy ? findActivityInReposDir(join16(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
6421
6554
  if (additionalActivity)
6422
6555
  return "WORKTREE_DIRTY";
6423
6556
  try {
6424
- const unpushed = execFileSync6("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
6557
+ const unpushed = execFileSync8("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
6425
6558
  encoding: "utf8",
6426
6559
  stdio: "pipe"
6427
6560
  }).trim();
@@ -6437,13 +6570,13 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6437
6570
  }
6438
6571
  function repoHasActivity(repoPath) {
6439
6572
  try {
6440
- const status3 = execFileSync6("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6573
+ const status3 = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6441
6574
  if (status3)
6442
6575
  return true;
6443
- const branches = execFileSync6("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
6576
+ const branches = execFileSync8("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
6444
6577
  for (const b of branches) {
6445
6578
  try {
6446
- const unpushed = execFileSync6("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
6579
+ const unpushed = execFileSync8("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
6447
6580
  if (unpushed)
6448
6581
  return true;
6449
6582
  } catch {
@@ -6456,7 +6589,7 @@ function repoHasActivity(repoPath) {
6456
6589
  return false;
6457
6590
  }
6458
6591
  function findActivityInReposDir(reposDir) {
6459
- if (!existsSync11(reposDir))
6592
+ if (!existsSync13(reposDir))
6460
6593
  return null;
6461
6594
  let names = [];
6462
6595
  try {
@@ -6465,8 +6598,8 @@ function findActivityInReposDir(reposDir) {
6465
6598
  return null;
6466
6599
  }
6467
6600
  for (const name of names) {
6468
- const repoPath = join14(reposDir, name);
6469
- if (!existsSync11(join14(repoPath, ".git")))
6601
+ const repoPath = join16(reposDir, name);
6602
+ if (!existsSync13(join16(repoPath, ".git")))
6470
6603
  continue;
6471
6604
  if (repoHasActivity(repoPath))
6472
6605
  return repoPath;
@@ -6481,16 +6614,16 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
6481
6614
  } catch {
6482
6615
  return null;
6483
6616
  }
6484
- const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join14(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
6485
- const legacyReposDir = join14(taskRoot, ".morula", "repos");
6486
- if (existsSync11(legacyReposDir)) {
6617
+ const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join16(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
6618
+ const legacyReposDir = join16(taskRoot, ".morula", "repos");
6619
+ if (existsSync13(legacyReposDir)) {
6487
6620
  try {
6488
- candidates.push(...readdirSync3(legacyReposDir).map((n) => join14(legacyReposDir, n)));
6621
+ candidates.push(...readdirSync3(legacyReposDir).map((n) => join16(legacyReposDir, n)));
6489
6622
  } catch {
6490
6623
  }
6491
6624
  }
6492
6625
  for (const repoPath of candidates) {
6493
- if (!existsSync11(join14(repoPath, ".git")))
6626
+ if (!existsSync13(join16(repoPath, ".git")))
6494
6627
  continue;
6495
6628
  if (repoHasActivity(repoPath))
6496
6629
  return repoPath;
@@ -6498,18 +6631,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
6498
6631
  return null;
6499
6632
  }
6500
6633
  function cleanupInjectedSkills(dir) {
6501
- const injectedSkillsRoot = join14(dir, ".agents", "skills");
6502
- if (!existsSync11(injectedSkillsRoot))
6634
+ const injectedSkillsRoot = join16(dir, ".agents", "skills");
6635
+ if (!existsSync13(injectedSkillsRoot))
6503
6636
  return;
6504
6637
  try {
6505
6638
  for (const name of readdirSync3(injectedSkillsRoot)) {
6506
6639
  if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
6507
- rmSync5(join14(injectedSkillsRoot, name), { recursive: true, force: true });
6640
+ rmSync7(join16(injectedSkillsRoot, name), { recursive: true, force: true });
6508
6641
  }
6509
6642
  }
6510
6643
  if (readdirSync3(injectedSkillsRoot).length === 0) {
6511
- rmSync5(join14(dir, ".agents", "skills"), { recursive: true, force: true });
6512
- rmSync5(join14(dir, ".agents"), { recursive: true, force: true });
6644
+ rmSync7(join16(dir, ".agents", "skills"), { recursive: true, force: true });
6645
+ rmSync7(join16(dir, ".agents"), { recursive: true, force: true });
6513
6646
  }
6514
6647
  } catch {
6515
6648
  }
@@ -6525,11 +6658,11 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
6525
6658
  for (const e of entries) {
6526
6659
  if (!e.isDirectory() || e.name.startsWith("."))
6527
6660
  continue;
6528
- const repoPath = join14(taskRoot, e.name);
6529
- if (repoPath === excludeDir || !existsSync11(join14(repoPath, ".git")))
6661
+ const repoPath = join16(taskRoot, e.name);
6662
+ if (repoPath === excludeDir || !existsSync13(join16(repoPath, ".git")))
6530
6663
  continue;
6531
6664
  try {
6532
- const commonDir = execFileSync6("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
6665
+ const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
6533
6666
  if (commonDir)
6534
6667
  dirs.add(commonDir);
6535
6668
  } catch {
@@ -6543,37 +6676,37 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
6543
6676
  throw new Error("MARKER_MISMATCH");
6544
6677
  const { primaryDir, legacy } = layout;
6545
6678
  if (!legacy) {
6546
- if (primaryDir !== join14(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
6679
+ if (primaryDir !== join16(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
6547
6680
  throw new Error("GIT_REMOVE_FAILED");
6548
6681
  }
6549
6682
  cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
6550
6683
  const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
6551
6684
  try {
6552
- rmSync5(taskRoot, { recursive: true, force: true });
6685
+ rmSync7(taskRoot, { recursive: true, force: true });
6553
6686
  } catch {
6554
6687
  throw new Error("GIT_REMOVE_FAILED");
6555
6688
  }
6556
6689
  for (const dir of [repoCachePath, ...siblingCommonDirs]) {
6557
6690
  try {
6558
- execFileSync6("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
6691
+ execFileSync8("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
6559
6692
  } catch {
6560
6693
  }
6561
6694
  }
6562
6695
  }
6563
6696
  function removeWorktree(repoCachePath, worktreePath) {
6564
- if (!existsSync11(worktreePath))
6697
+ if (!existsSync13(worktreePath))
6565
6698
  return;
6566
6699
  try {
6567
6700
  cleanupInjectedSkills(worktreePath);
6568
- execFileSync6("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
6569
- execFileSync6("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
6701
+ execFileSync8("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
6702
+ execFileSync8("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
6570
6703
  } catch {
6571
6704
  throw new Error("GIT_REMOVE_FAILED");
6572
6705
  }
6573
6706
  }
6574
6707
 
6575
6708
  // src/daemon/repo-checkout-service.ts
6576
- var CHECKOUT_JSON_FILE = join15(".morula", "checkout.json");
6709
+ var CHECKOUT_JSON_FILE = join17(".morula", "checkout.json");
6577
6710
  var LocalRepoCheckoutService = class {
6578
6711
  constructor(opts) {
6579
6712
  this.opts = opts;
@@ -6661,9 +6794,9 @@ var LocalRepoCheckoutService = class {
6661
6794
  return;
6662
6795
  this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
6663
6796
  try {
6664
- const dir = join15(workdir, ".morula");
6665
- mkdirSync8(dir, { recursive: true, mode: 448 });
6666
- writeFileSync7(join15(dir, "checkout.json"), JSON.stringify({
6797
+ const dir = join17(workdir, ".morula");
6798
+ mkdirSync10(dir, { recursive: true, mode: 448 });
6799
+ writeFileSync8(join17(dir, "checkout.json"), JSON.stringify({
6667
6800
  taskId,
6668
6801
  port: this.boundPort,
6669
6802
  checkToken
@@ -6717,7 +6850,8 @@ var LocalRepoCheckoutService = class {
6717
6850
  this.opts.tenantId,
6718
6851
  repoFullName,
6719
6852
  env,
6720
- baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
6853
+ baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
6854
+ baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
6721
6855
  );
6722
6856
  cache.ensure();
6723
6857
  cache.fetch();
@@ -6725,14 +6859,16 @@ var LocalRepoCheckoutService = class {
6725
6859
  repoCachePath: cache.repoCachePath,
6726
6860
  taskRoot: task.workdir,
6727
6861
  repoFullName,
6862
+ baseBranch: serverRes.baseBranch,
6863
+ ref: req.ref,
6728
6864
  env
6729
6865
  });
6730
- const headSha = execFileSync7("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6866
+ const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6731
6867
  return {
6732
6868
  ok: true,
6733
6869
  path: worktreePath,
6734
6870
  headSha,
6735
- branchName: execFileSync7("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
6871
+ branchName: execFileSync9("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
6736
6872
  };
6737
6873
  } catch (e) {
6738
6874
  return { ok: false, code: "CHECKOUT_FAILED", error: e instanceof Error ? e.message : String(e) };
@@ -6778,7 +6914,8 @@ var LocalRepoCheckoutService = class {
6778
6914
  this.opts.tenantId,
6779
6915
  repoFullName,
6780
6916
  env,
6781
- baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
6917
+ baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
6918
+ baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
6782
6919
  );
6783
6920
  cache.ensure();
6784
6921
  cache.fetch();
@@ -6792,7 +6929,7 @@ var LocalRepoCheckoutService = class {
6792
6929
  env
6793
6930
  });
6794
6931
  syncGitIdentity(task.primaryRepoDir ?? task.workdir, targetPath, env);
6795
- const headSha = execFileSync7("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6932
+ const headSha = execFileSync9("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6796
6933
  this.recordPreparedRepo(req.taskId, repoFullName, {
6797
6934
  workContextId: serverRes.workContextId,
6798
6935
  baseBranch: baseBranch || void 0,
@@ -6823,38 +6960,90 @@ function isReadOnlyRepoDir(dirName) {
6823
6960
  return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
6824
6961
  }
6825
6962
  function resolveRepoTargetDir(taskRoot, base) {
6826
- const direct = join15(taskRoot, base);
6827
- if (!existsSync12(direct) || existsSync12(join15(direct, ".git")))
6963
+ const direct = join17(taskRoot, base);
6964
+ if (!existsSync14(direct) || existsSync14(join17(direct, ".git")))
6828
6965
  return direct;
6829
6966
  for (let i = 2; i < 100; i++) {
6830
- const candidate = join15(taskRoot, `${base}-${i}`);
6831
- if (!existsSync12(candidate))
6967
+ const candidate = join17(taskRoot, `${base}-${i}`);
6968
+ if (!existsSync14(candidate))
6832
6969
  return candidate;
6833
6970
  }
6834
6971
  throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
6835
6972
  }
6836
6973
  function materializeReadWorktree(input) {
6837
6974
  const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
6838
- if (existsSync12(join15(worktreePath, ".git"))) {
6839
- execFileSync7("git", ["-C", worktreePath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
6975
+ const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
6976
+ if (existsSync14(join17(worktreePath, ".git"))) {
6977
+ const current = currentBranchName(worktreePath);
6978
+ if (current)
6979
+ throw new Error(`READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5DF2\u5207\u5230\u5206\u652F ${current}\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09`);
6980
+ if (hasLocalChanges2(worktreePath, input.env))
6981
+ throw new Error("READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5B58\u5728\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09");
6982
+ execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
6840
6983
  } else {
6841
- mkdirSync8(join15(worktreePath, ".."), { recursive: true });
6842
- execFileSync7("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, resolveBaseRef(input.repoCachePath, input.env)], {
6984
+ mkdirSync10(join17(worktreePath, ".."), { recursive: true });
6985
+ execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
6843
6986
  env: input.env,
6844
6987
  stdio: "pipe"
6845
6988
  });
6846
6989
  }
6847
6990
  return worktreePath;
6848
6991
  }
6849
- function resolveBaseRef(repoCachePath, env) {
6850
- for (const ref of ["origin/main", "origin/master", "HEAD"]) {
6851
- try {
6852
- execFileSync7("git", ["-C", repoCachePath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
6992
+ function resolveReadRef(repoCachePath, env, requestedRef, baseBranch) {
6993
+ const requested = requestedRef?.trim();
6994
+ if (requested) {
6995
+ const candidates = requested.startsWith("refs/") ? [requested] : [`origin/${requested}`, requested];
6996
+ for (const ref of candidates) {
6997
+ if (refExists(repoCachePath, ref, env))
6998
+ return ref;
6999
+ }
7000
+ throw new Error(`REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528 ${requested}`);
7001
+ }
7002
+ const explicitBase = baseBranch?.trim();
7003
+ if (explicitBase) {
7004
+ const ref = `origin/${explicitBase}`;
7005
+ if (refExists(repoCachePath, ref, env))
6853
7006
  return ref;
7007
+ throw new Error(`BASE_REF_NOT_FOUND: repo cache \u65E0\u9ED8\u8BA4\u5206\u652F ${explicitBase}`);
7008
+ }
7009
+ const remoteHead = (() => {
7010
+ try {
7011
+ const out = execFileSync9("git", ["-C", repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7012
+ return out.replace(/^refs\/remotes\//, "");
6854
7013
  } catch {
7014
+ return null;
6855
7015
  }
7016
+ })();
7017
+ for (const ref of [remoteHead, "origin/main", "origin/master"]) {
7018
+ if (!ref || !refExists(repoCachePath, ref, env))
7019
+ continue;
7020
+ return ref;
7021
+ }
7022
+ throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/HEAD / origin/main / origin/master\uFF09");
7023
+ }
7024
+ function refExists(repoPath, ref, env) {
7025
+ try {
7026
+ execFileSync9("git", ["-C", repoPath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
7027
+ return true;
7028
+ } catch {
7029
+ return false;
7030
+ }
7031
+ }
7032
+ function currentBranchName(repoPath) {
7033
+ try {
7034
+ const out = execFileSync9("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf8", stdio: "pipe" }).trim();
7035
+ return out || null;
7036
+ } catch {
7037
+ return null;
7038
+ }
7039
+ }
7040
+ function hasLocalChanges2(repoPath, env) {
7041
+ try {
7042
+ const out = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7043
+ return out.length > 0;
7044
+ } catch {
7045
+ return true;
6856
7046
  }
6857
- throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/main / origin/master / HEAD\uFF09");
6858
7047
  }
6859
7048
  function listMaterializedRepoDirs(taskRoot, options = {}) {
6860
7049
  const excluded = new Set(options.excludeDirs ?? []);
@@ -6864,7 +7053,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
6864
7053
  } catch {
6865
7054
  return [];
6866
7055
  }
6867
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join15(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync12(join15(dir, ".git"))).sort();
7056
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join17(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync14(join17(dir, ".git"))).sort();
6868
7057
  }
6869
7058
  function parseRepoFullNameFromRemoteUrl(url) {
6870
7059
  const raw = url?.trim();
@@ -6886,7 +7075,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
6886
7075
  }
6887
7076
  function collectMaterializedRepoDeliveryTargets(input) {
6888
7077
  const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
6889
- const excludedDirs = [...excludedNames].map((name) => join15(input.taskRoot, safeRepoDirName(name)));
7078
+ const excludedDirs = [...excludedNames].map((name) => join17(input.taskRoot, safeRepoDirName(name)));
6890
7079
  const recordsByDir = /* @__PURE__ */ new Map();
6891
7080
  const recordsByDirName = /* @__PURE__ */ new Map();
6892
7081
  for (const [repoFullName, record] of input.prepared ?? []) {
@@ -6895,7 +7084,7 @@ function collectMaterializedRepoDeliveryTargets(input) {
6895
7084
  recordsByDir.set(record.dir, entry);
6896
7085
  recordsByDirName.set(safeRepoDirName(repoFullName), entry);
6897
7086
  }
6898
- const runGit = input.gitRunner ?? ((dir, args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
7087
+ const runGit = input.gitRunner ?? ((dir, args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
6899
7088
  const targets = [];
6900
7089
  for (const dir of listMaterializedRepoDirs(input.taskRoot, { excludeDirs: excludedDirs })) {
6901
7090
  const known = recordsByDir.get(dir) ?? recordsByDirName.get(basename2(dir));
@@ -6932,7 +7121,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
6932
7121
  const read = (key) => {
6933
7122
  for (const scope of ["--worktree", "--local"]) {
6934
7123
  try {
6935
- const out = execFileSync7("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
7124
+ const out = execFileSync9("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
6936
7125
  if (out)
6937
7126
  return out;
6938
7127
  } catch {
@@ -6943,30 +7132,34 @@ function syncGitIdentity(primaryDir, targetDir, env) {
6943
7132
  const name = read("user.name") ?? "morula-daemon[bot]";
6944
7133
  const email = read("user.email") ?? "morula-daemon@morula.local";
6945
7134
  try {
6946
- execFileSync7("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
6947
- execFileSync7("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
7135
+ execFileSync9("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
7136
+ execFileSync9("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
6948
7137
  } catch {
6949
7138
  }
6950
7139
  }
6951
7140
  function materializeAdditionalRepo(input) {
6952
- if (existsSync12(join15(input.targetPath, ".git"))) {
6953
- execFileSync7("git", ["-C", input.targetPath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
7141
+ if (existsSync14(join17(input.targetPath, ".git"))) {
7142
+ const current = currentBranchName(input.targetPath);
7143
+ if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
7144
+ const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
7145
+ execFileSync9("git", ["-C", input.targetPath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
7146
+ }
6954
7147
  return input.targetPath;
6955
7148
  }
6956
- const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master", "HEAD"] : ["origin/main", "origin/master", "HEAD"];
7149
+ const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master"] : ["origin/main", "origin/master"];
6957
7150
  let baseRef = null;
6958
7151
  for (const ref of candidates) {
6959
7152
  try {
6960
- execFileSync7("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
7153
+ execFileSync9("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
6961
7154
  baseRef = ref;
6962
7155
  break;
6963
7156
  } catch {
6964
7157
  }
6965
7158
  }
6966
7159
  if (!baseRef)
6967
- throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master / HEAD\uFF09");
6968
- mkdirSync8(join15(input.targetPath, ".."), { recursive: true });
6969
- execFileSync7("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
7160
+ throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
7161
+ mkdirSync10(join17(input.targetPath, ".."), { recursive: true });
7162
+ execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
6970
7163
  env: input.env,
6971
7164
  stdio: "pipe"
6972
7165
  });
@@ -7435,6 +7628,14 @@ var DaemonRunner = class {
7435
7628
  if (this.running)
7436
7629
  return;
7437
7630
  this.running = true;
7631
+ try {
7632
+ await this.startInner();
7633
+ } catch (error) {
7634
+ this.stop();
7635
+ throw error;
7636
+ }
7637
+ }
7638
+ async startInner() {
7438
7639
  ensureGitOnPath();
7439
7640
  ensureNodeOnPath();
7440
7641
  {
@@ -8116,6 +8317,7 @@ var DaemonRunner = class {
8116
8317
  const wc = detail.workContext;
8117
8318
  repoBaseUrl = tokenRes.baseUrl;
8118
8319
  const remoteUrl = repoBaseUrl ? buildGitlabSshRemote(wc?.repoFullName ?? repo, repoBaseUrl) : void 0;
8320
+ const daemonRemoteUrl = buildDaemonRepoUrl(wc?.repoFullName ?? repo, repoBaseUrl);
8119
8321
  env = { ...env, ...buildRepoEnv(tokenRes.token, repoBaseUrl) };
8120
8322
  if (wc?.baseBranch?.trim())
8121
8323
  env = { ...env, MORULA_BASE_BRANCH: wc.baseBranch.trim() };
@@ -8131,7 +8333,8 @@ var DaemonRunner = class {
8131
8333
  baseBranch: wc.baseBranch || "main",
8132
8334
  code: detail.code ?? null,
8133
8335
  env,
8134
- remoteUrl
8336
+ remoteUrl,
8337
+ daemonRemoteUrl
8135
8338
  });
8136
8339
  repoDir = managed.worktreePath;
8137
8340
  worktreePath = managed.worktreePath;
@@ -8140,7 +8343,7 @@ var DaemonRunner = class {
8140
8343
  });
8141
8344
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5DE5\u4F5C\u533A\u5C31\u7EEA: ${taskRoot}\uFF08\u4ED3\u5E93 ${repoDir}\uFF0Cbranch=${wc.branchName}\uFF09`);
8142
8345
  } else {
8143
- repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl);
8346
+ repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl, daemonRemoteUrl);
8144
8347
  taskRoot = repoDir;
8145
8348
  }
8146
8349
  ensureGitIdentity(repoDir, detail.executor.executorName);
@@ -8157,12 +8360,12 @@ var DaemonRunner = class {
8157
8360
  });
8158
8361
  if (spaceToken?.token)
8159
8362
  env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
8160
- repoDir = join16(cfg.workdir, "scratch", taskId);
8363
+ repoDir = join18(cfg.workdir, "scratch", taskId);
8161
8364
  taskRoot = repoDir;
8162
- mkdirSync9(repoDir, { recursive: true });
8163
- const readme = join16(repoDir, "README.md");
8164
- if (!existsSync13(readme)) {
8165
- writeFileSync8(
8365
+ mkdirSync11(repoDir, { recursive: true });
8366
+ const readme = join18(repoDir, "README.md");
8367
+ if (!existsSync15(readme)) {
8368
+ writeFileSync9(
8166
8369
  readme,
8167
8370
  `# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
8168
8371
 
@@ -8448,7 +8651,7 @@ var DaemonRunner = class {
8448
8651
  };
8449
8652
  const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
8450
8653
  const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
8451
- const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync13(resumeWorkDir);
8654
+ const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
8452
8655
  const input = {
8453
8656
  executionId: `${taskId}:1`,
8454
8657
  // task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
@@ -8655,7 +8858,7 @@ var DaemonRunner = class {
8655
8858
  const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
8656
8859
  try {
8657
8860
  const execEnv = mergeExecEnv(env);
8658
- execFileSync8("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
8861
+ execFileSync10("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
8659
8862
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5206\u652F\u5DF2\u8865\u63A8\u9001: ${branch}`);
8660
8863
  const listRes = await fetch(
8661
8864
  `${baseUrl}/api/v4/projects/${projectPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened`,
@@ -8722,7 +8925,7 @@ var DaemonRunner = class {
8722
8925
  await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
8723
8926
  return;
8724
8927
  }
8725
- const managedRoot = join16(cfg.workdir, "worktrees", this.tenantId);
8928
+ const managedRoot = join18(cfg.workdir, "worktrees", this.tenantId);
8726
8929
  if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
8727
8930
  await this.api.submitCleanupResult(item.id, this.runtimeId, {
8728
8931
  ok: false,
@@ -8770,38 +8973,38 @@ function isDebugLogEnabled() {
8770
8973
  var spaceTokenFailureLogged = false;
8771
8974
  function writeCustomSkills(targetDir, skills, log) {
8772
8975
  const NAME_RE = /^[\w-]{1,64}$/;
8773
- const base = join16(targetDir, ".agents", "skills");
8976
+ const base = join18(targetDir, ".agents", "skills");
8774
8977
  for (const skill of skills) {
8775
8978
  const name = skill.name.trim();
8776
8979
  if (!NAME_RE.test(name)) {
8777
8980
  log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
8778
8981
  continue;
8779
8982
  }
8780
- const dir = join16(base, name);
8781
- if (dirname6(dir) !== base) {
8983
+ const dir = join18(base, name);
8984
+ if (dirname7(dir) !== base) {
8782
8985
  log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
8783
8986
  continue;
8784
8987
  }
8785
- mkdirSync9(dir, { recursive: true });
8988
+ mkdirSync11(dir, { recursive: true });
8786
8989
  const meta = ["---", `name: ${name}`];
8787
8990
  if (skill.description?.trim()) {
8788
8991
  meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
8789
8992
  }
8790
8993
  meta.push("---", "");
8791
- writeFileSync8(join16(dir, "SKILL.md"), `${meta.join("\n")}
8994
+ writeFileSync9(join18(dir, "SKILL.md"), `${meta.join("\n")}
8792
8995
  ${skill.content ?? ""}`, "utf8");
8793
8996
  }
8794
8997
  }
8795
8998
  function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
8796
- const dir = join16(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
8797
- const refsDir = join16(dir, "references");
8798
- mkdirSync9(refsDir, { recursive: true });
8999
+ const dir = join18(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
9000
+ const refsDir = join18(dir, "references");
9001
+ mkdirSync11(refsDir, { recursive: true });
8799
9002
  const isGitlab = provider === "gitlab" && !!repoBaseUrl;
8800
9003
  const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
8801
9004
  const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
8802
- writeFileSync8(join16(dir, "SKILL.md"), `${front}
9005
+ writeFileSync9(join18(dir, "SKILL.md"), `${front}
8803
9006
  ${body}`, "utf8");
8804
- writeFileSync8(join16(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
9007
+ writeFileSync9(join18(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
8805
9008
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u534F\u4F5C\u5951\u7EA6 skill ${COLLABORATION_SKILL_NAME}\uFF08${isGitlab ? "GitLab" : "GitHub"} \u7248\uFF0C\u4ED3\u5E93=${repo ?? "\u65E0"}\uFF09\u5230 .agents/skills/`);
8806
9009
  }
8807
9010
  function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
@@ -8832,8 +9035,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
8832
9035
  };
8833
9036
  }
8834
9037
  function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
8835
- const briefDir = join16(targetDir, ".agent_context");
8836
- mkdirSync9(briefDir, { recursive: true });
9038
+ const briefDir = join18(targetDir, ".agent_context");
9039
+ mkdirSync11(briefDir, { recursive: true });
8837
9040
  const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
8838
9041
 
8839
9042
  - \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
@@ -8842,7 +9045,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
8842
9045
  ---
8843
9046
 
8844
9047
  ${buildTaskPromptWithContract(detail, void 0)}`;
8845
- writeFileSync8(join16(briefDir, "task-context.md"), brief, "utf8");
9048
+ writeFileSync9(join18(briefDir, "task-context.md"), brief, "utf8");
8846
9049
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
8847
9050
  }
8848
9051
  var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
@@ -8856,15 +9059,15 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
8856
9059
  log?.(`[daemon] \u7A7A\u95F4 AGENTS.md \u5185\u5BB9\u8D85\u957F\uFF08${content.length} \u5B57\u7B26 > ${PROJECT_AGENTS_MD_MAX_CHARS}\uFF09\uFF0C\u5DF2\u622A\u65AD\u5199\u5165`);
8857
9060
  content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
8858
9061
  }
8859
- mkdirSync9(targetDir, { recursive: true });
8860
- writeFileSync8(join16(targetDir, "AGENTS.md"), `${content}
9062
+ mkdirSync11(targetDir, { recursive: true });
9063
+ writeFileSync9(join18(targetDir, "AGENTS.md"), `${content}
8861
9064
  `, "utf8");
8862
9065
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\u5230 AGENTS.md\uFF08${content.length} \u5B57\u7B26\uFF0C\u4F5C\u4E3A project.instructions \u81EA\u52A8\u52A0\u8F7D\uFF09`);
8863
9066
  return true;
8864
9067
  }
8865
9068
  function gitHead(repoDir) {
8866
9069
  try {
8867
- return execFileSync8("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
9070
+ return execFileSync10("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
8868
9071
  } catch {
8869
9072
  return null;
8870
9073
  }
@@ -8955,7 +9158,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
8955
9158
  }
8956
9159
  async function reportOneRepoTarget(taskId, target, env, ops) {
8957
9160
  const execEnv = mergeExecEnv(env);
8958
- if (!existsSync13(target.dir) || !existsSync13(join16(target.dir, ".git"))) {
9161
+ if (!existsSync15(target.dir) || !existsSync15(join18(target.dir, ".git"))) {
8959
9162
  ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728\uFF08cleanup/\u5206\u652F\u6539\u540D\u65F6\u5E8F\uFF09`);
8960
9163
  return;
8961
9164
  }
@@ -8970,7 +9173,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
8970
9173
  ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u65E0\u53EF\u4EA4\u4ED8\u5206\u652F\uFF08branch=${branch ?? "null"}\uFF0C\u5360\u4F4D/\u4E3B\u5E72\u5206\u652F\u4E0D\u4EA4\u4ED8\uFF09`);
8971
9174
  return;
8972
9175
  }
8973
- const headSha = execFileSync8("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
9176
+ const headSha = execFileSync10("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
8974
9177
  const headSubject = headCommitSubject(target.dir, execEnv);
8975
9178
  const probePushState = ops.pushState ?? resolveRemotePushState;
8976
9179
  let dirty = hasLocalChanges(target.dir, execEnv);
@@ -9010,7 +9213,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
9010
9213
  }
9011
9214
  }
9012
9215
  function execGit(repoPath, args, env) {
9013
- return execFileSync8("git", ["-C", repoPath, ...args], {
9216
+ return execFileSync10("git", ["-C", repoPath, ...args], {
9014
9217
  encoding: "utf8",
9015
9218
  stdio: "pipe",
9016
9219
  env,
@@ -9051,7 +9254,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9051
9254
  if (!taskCode)
9052
9255
  return;
9053
9256
  const execEnv = mergeExecEnv(env);
9054
- const branch = execFileSync8("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
9257
+ const branch = execFileSync10("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
9055
9258
  if (!branch || branch === "main" || branch === "master")
9056
9259
  return;
9057
9260
  if (env.MORULA_GITLAB_URL && env.MORULA_GITLAB_TOKEN) {
@@ -9060,7 +9263,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9060
9263
  }
9061
9264
  let prList;
9062
9265
  try {
9063
- prList = execFileSync8("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
9266
+ prList = execFileSync10("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
9064
9267
  cwd: repoDir,
9065
9268
  env: execEnv,
9066
9269
  encoding: "utf-8"
@@ -9080,7 +9283,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9080
9283
  const newBody = `${pr.body ?? ""}
9081
9284
 
9082
9285
  Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4E8E PR \u5173\u8054\u4E0E\u5173\u95ED\u8054\u52A8\uFF09`;
9083
- execFileSync8("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
9286
+ execFileSync10("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
9084
9287
  cwd: repoDir,
9085
9288
  env: execEnv,
9086
9289
  encoding: "utf-8"
@@ -9123,12 +9326,12 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
9123
9326
 
9124
9327
  // src/commands/doctor.ts
9125
9328
  init_dist2();
9329
+ import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
9330
+ import { homedir as homedir11 } from "node:os";
9331
+ import { dirname as dirname8, join as join19 } from "node:path";
9126
9332
  init_daemon_config();
9127
9333
  init_daemon_lock();
9128
9334
  init_dim_locate();
9129
- import { existsSync as existsSync14, readFileSync as readFileSync10, realpathSync } from "node:fs";
9130
- import { homedir as homedir9 } from "node:os";
9131
- import { dirname as dirname7, join as join17 } from "node:path";
9132
9335
  async function doctorCommand(context) {
9133
9336
  const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
9134
9337
  const { facts, checks } = await collectDoctor(context, probe);
@@ -9145,16 +9348,16 @@ async function doctorCommand(context) {
9145
9348
  async function collectDoctor(context, probe) {
9146
9349
  const checks = [];
9147
9350
  const env = context.env;
9148
- const configDir = env.MORULA_CONFIG_DIR ?? join17(homedir9(), ".morula-cli");
9149
- const configPath = env.MORULA_CONFIG ?? join17(configDir, "config.yaml");
9150
- const daemonPath = join17(dirname7(configPath), "daemon");
9151
- const logFile = join17(daemonPath, "daemon.log");
9351
+ const configDir = env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli");
9352
+ const configPath = env.MORULA_CONFIG ?? join19(configDir, "config.yaml");
9353
+ const daemonPath = join19(dirname8(configPath), "daemon");
9354
+ const logFile = join19(daemonPath, "daemon.log");
9152
9355
  const contextName = context.runtime.context ?? null;
9153
9356
  const baseUrl = context.runtime.baseUrl ?? null;
9154
9357
  const tokenPresent = Boolean(context.runtime.token);
9155
9358
  const tokenSource = context.runtime.tokenSource ?? null;
9156
9359
  let configReadable = false;
9157
- if (existsSync14(configPath)) {
9360
+ if (existsSync16(configPath)) {
9158
9361
  try {
9159
9362
  readFileSync10(configPath, "utf8");
9160
9363
  configReadable = true;
@@ -9162,10 +9365,10 @@ async function collectDoctor(context, probe) {
9162
9365
  configReadable = false;
9163
9366
  }
9164
9367
  }
9165
- const lockPath = join17(daemonPath, DAEMON_LOCK_FILE);
9368
+ const lockPath = join19(daemonPath, DAEMON_LOCK_FILE);
9166
9369
  const lock = probeDaemonLock(lockPath);
9167
- const pidFile = join17(daemonPath, "daemon.pid");
9168
- const filePid = existsSync14(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
9370
+ const pidFile = join19(daemonPath, "daemon.pid");
9371
+ const filePid = existsSync16(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
9169
9372
  const lockPid = lock.state === "active" ? lock.record.pid : 0;
9170
9373
  const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
9171
9374
  const cfg = loadDaemonConfig(context.store);
@@ -9200,7 +9403,8 @@ async function collectDoctor(context, probe) {
9200
9403
  logFile,
9201
9404
  dimPath,
9202
9405
  dimFound,
9203
- envOverrides
9406
+ envOverrides,
9407
+ daemonAutostart: process.platform === "darwin" ? daemonAutostartEnabled() : null
9204
9408
  };
9205
9409
  checks.push(lock.state === "stale" ? {
9206
9410
  id: "daemon.lock",
@@ -9226,7 +9430,7 @@ async function collectDoctor(context, probe) {
9226
9430
  hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
9227
9431
  });
9228
9432
  }
9229
- checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync14(configPath) ? { id: "config.readable", status: "fail", message: `\u914D\u7F6E\u5B58\u5728\u4F46\u4E0D\u53EF\u8BFB\uFF08\u6743\u9650\uFF1F\uFF09\uFF1A${configPath}`, hint: `ls -l ${configPath}` } : { id: "config.readable", status: "warn", message: `\u914D\u7F6E\u6587\u4EF6\u5C1A\u4E0D\u5B58\u5728\uFF1A${configPath}`, hint: "morula-runtime auth login\uFF08\u767B\u5F55\u540E\u751F\u6210\uFF09" });
9433
+ checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync16(configPath) ? { id: "config.readable", status: "fail", message: `\u914D\u7F6E\u5B58\u5728\u4F46\u4E0D\u53EF\u8BFB\uFF08\u6743\u9650\uFF1F\uFF09\uFF1A${configPath}`, hint: `ls -l ${configPath}` } : { id: "config.readable", status: "warn", message: `\u914D\u7F6E\u6587\u4EF6\u5C1A\u4E0D\u5B58\u5728\uFF1A${configPath}`, hint: "morula-runtime auth login\uFF08\u767B\u5F55\u540E\u751F\u6210\uFF09" });
9230
9434
  if (!dimFound) {
9231
9435
  checks.push({
9232
9436
  id: "dim.present",
@@ -9247,6 +9451,18 @@ async function collectDoctor(context, probe) {
9247
9451
  } else {
9248
9452
  checks.push({ id: "daemon.running", status: "ok", message: `daemon \u8FD0\u884C\u4E2D\uFF08pid=${daemonPid}\uFF09` });
9249
9453
  }
9454
+ if (facts.daemonAutostart !== null) {
9455
+ if (facts.daemonAutostart) {
9456
+ checks.push({
9457
+ id: "daemon.autostart",
9458
+ status: "ok",
9459
+ message: `daemon \u5F00\u673A\u81EA\u542F\uFF1Aon\uFF08${daemonAutostartPlistPath()}\uFF09`,
9460
+ ...facts.daemonRunning ? {} : { hint: `tail -n 50 ${logFile}\uFF08\u81EA\u542F\u5B9E\u4F8B\u53EF\u80FD\u6CE8\u518C\u5931\u8D25\u540E\u5DF2\u9000\u51FA\uFF09` }
9461
+ });
9462
+ } else {
9463
+ checks.push({ id: "daemon.autostart", status: "ok", message: "daemon \u5F00\u673A\u81EA\u542F\uFF1Aoff" });
9464
+ }
9465
+ }
9250
9466
  if (envOverrides.includes("MORULA_CONFIG_DIR") || envOverrides.includes("MORULA_CONFIG")) {
9251
9467
  checks.push({
9252
9468
  id: "config.override",
@@ -9322,10 +9538,10 @@ function detectInstallMode() {
9322
9538
 
9323
9539
  // src/commands/daemon.ts
9324
9540
  function configFilePath(env) {
9325
- return env.MORULA_CONFIG ?? join19(env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli"), "config.yaml");
9541
+ return env.MORULA_CONFIG ?? join20(env.MORULA_CONFIG_DIR ?? join20(homedir12(), ".morula-cli"), "config.yaml");
9326
9542
  }
9327
9543
  function daemonDir(configPath) {
9328
- return join19(dirname8(configPath), "daemon");
9544
+ return join20(dirname9(configPath), "daemon");
9329
9545
  }
9330
9546
  var PID_FILE = "daemon.pid";
9331
9547
  var LOG_FILE = "daemon.log";
@@ -9343,9 +9559,11 @@ async function daemonCommand(context) {
9343
9559
  return models(context);
9344
9560
  if (action === "portal")
9345
9561
  return portal(context);
9562
+ if (action === "autostart")
9563
+ return autostart(context);
9346
9564
  if (action === "doctor")
9347
9565
  return doctorCommand(context);
9348
- throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|doctor>", {
9566
+ throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|autostart|doctor>", {
9349
9567
  hint: "morula-runtime daemon --help"
9350
9568
  });
9351
9569
  }
@@ -9356,29 +9574,29 @@ async function portal(context) {
9356
9574
  const { PortalServer: PortalServer2 } = await Promise.resolve().then(() => (init_portal_server(), portal_server_exports));
9357
9575
  const { createPortalEnvironmentResolver: createPortalEnvironmentResolver2 } = await Promise.resolve().then(() => (init_portal_instances(), portal_instances_exports));
9358
9576
  const port = Number(flagString(context.parsed.flags, "port") ?? process.env.MORULA_PORTAL_PORT ?? String(DEFAULT_PORTAL_PORT));
9359
- const autostart = flagString(context.parsed.flags, "autostart");
9577
+ const autostart2 = flagString(context.parsed.flags, "autostart");
9360
9578
  const dryRun = isDryRun(context);
9361
- if (autostart === "on" || autostart === "off") {
9579
+ if (autostart2 === "on" || autostart2 === "off") {
9362
9580
  const { setupAutostart: setupAutostart2, removeAutostart: removeAutostart2 } = await Promise.resolve().then(() => (init_portal_autostart(), portal_autostart_exports));
9363
9581
  if (dryRun) {
9364
9582
  return {
9365
9583
  ok: true,
9366
9584
  dryRun: true,
9367
- autostart,
9585
+ autostart: autostart2,
9368
9586
  planned: {
9369
- action: autostart === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
9587
+ action: autostart2 === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
9370
9588
  note: "no file was written"
9371
9589
  }
9372
9590
  };
9373
9591
  }
9374
- if (autostart === "on") {
9592
+ if (autostart2 === "on") {
9375
9593
  const installed = await setupAutostart2(context);
9376
9594
  context.err(installed ? "[portal] \u5DF2\u6CE8\u518C\u5F00\u673A\u81EA\u542F" : "[portal] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u542F\u6CE8\u518C\uFF08macOS LaunchAgent \u5DF2\u652F\u6301\uFF09");
9377
9595
  } else {
9378
9596
  await removeAutostart2(context);
9379
9597
  context.err("[portal] \u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F");
9380
9598
  }
9381
- return { ok: true, autostart };
9599
+ return { ok: true, autostart: autostart2 };
9382
9600
  }
9383
9601
  if (dryRun) {
9384
9602
  return {
@@ -9425,6 +9643,75 @@ async function portal(context) {
9425
9643
  });
9426
9644
  return { running: true };
9427
9645
  }
9646
+ async function autostart(context) {
9647
+ rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
9648
+ const state = context.parsed.positionals[0];
9649
+ if (state !== "on" && state !== "off") {
9650
+ throw usage("usage: morula-runtime daemon autostart <on|off>", {
9651
+ hint: "morula-runtime daemon autostart --help"
9652
+ });
9653
+ }
9654
+ const dryRun = isDryRun(context);
9655
+ if (process.platform !== "darwin") {
9656
+ context.err("[autostart] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF08\u4EC5 macOS LaunchAgent\uFF09");
9657
+ return { ok: true, autostart: state, supported: false };
9658
+ }
9659
+ const envOverridden = ["MORULA_ENV", "MORULA_CONFIG", "MORULA_CONFIG_DIR", "MORULA_API_URL", "MORULA_TOKEN", "MORULA_TASK_TOKEN"].filter((name) => Boolean(context.env[name]));
9660
+ const flagOverridden = ["env", "url", "token"].filter((name) => context.parsed.flags[name] !== void 0);
9661
+ if (envOverridden.length || flagOverridden.length) {
9662
+ throw new CliError(
9663
+ "USAGE",
9664
+ EXIT.USAGE,
9665
+ `\u5F00\u673A\u81EA\u542F\u53EA\u652F\u6301\u9ED8\u8BA4\u914D\u7F6E\u76EE\u5F55\uFF08launchd \u4E0D\u7EE7\u627F\u73AF\u5883\u53D8\u91CF/\u65D7\u6807\uFF09\uFF0C\u5F53\u524D\u4F1A\u8BDD\u5B58\u5728\u8986\u76D6\uFF1A${[...envOverridden.map((n) => `${n}`), ...flagOverridden.map((f) => `--${f}`)].join("\u3001")}`,
9666
+ false,
9667
+ { hint: "unset \u8986\u76D6\u53D8\u91CF\u540E\u91CD\u8BD5\uFF1B\u591A\u73AF\u5883\u5171\u5B58\u8BF7\u624B\u52A8\u542F\u505C\uFF08docs/research/runtime-dev-prod-coexist.md\uFF09" }
9668
+ );
9669
+ }
9670
+ let identityError = null;
9671
+ if (dryRun) {
9672
+ try {
9673
+ resolveDaemonIdentity(context, { quiet: true });
9674
+ } catch (error) {
9675
+ identityError = error instanceof Error ? error.message : String(error);
9676
+ }
9677
+ } else {
9678
+ resolveDaemonIdentity(context, { quiet: true });
9679
+ }
9680
+ if (state === "on") {
9681
+ const plan = planDaemonAutostart();
9682
+ if (dryRun) {
9683
+ return {
9684
+ ok: true,
9685
+ dryRun: true,
9686
+ autostart: "on",
9687
+ planned: {
9688
+ action: "register LaunchAgent (daemon boot autostart)",
9689
+ ...plan,
9690
+ note: "no file was written"
9691
+ },
9692
+ ...identityError ? { blockedBy: { code: "NOT_LOGGED_IN", message: identityError, hint: "morula-runtime auth login" } } : {}
9693
+ };
9694
+ }
9695
+ const installed = await setupDaemonAutostart((line) => context.err(line));
9696
+ context.err(installed ? "[autostart] \u5DF2\u6CE8\u518C daemon \u5F00\u673A\u81EA\u542F\uFF08RunAtLoad \u5DF2\u62C9\u8D77\uFF1B\u91CD\u542F\u673A\u5668\u540E\u81EA\u52A8\u8FD0\u884C\uFF09" : "[autostart] plist \u5DF2\u5199\u5165\uFF0C\u4F46 launchctl bootstrap \u5931\u8D25\uFF08\u89C1\u4E0A\u65B9\u63D0\u793A\uFF09");
9697
+ return { ok: true, autostart: "on", installed };
9698
+ }
9699
+ if (dryRun) {
9700
+ return {
9701
+ ok: true,
9702
+ dryRun: true,
9703
+ autostart: "off",
9704
+ planned: {
9705
+ action: "remove LaunchAgent (daemon boot autostart)",
9706
+ plistPath: daemonAutostartPlistPath(),
9707
+ note: "no file was written"
9708
+ }
9709
+ };
9710
+ }
9711
+ await removeDaemonAutostart((line) => context.err(line));
9712
+ context.err("[autostart] \u5DF2\u79FB\u9664 daemon \u5F00\u673A\u81EA\u542F\uFF08\u6B63\u5728\u8FD0\u884C\u7684 daemon \u4E0D\u53D7\u5F71\u54CD\uFF09");
9713
+ return { ok: true, autostart: "off" };
9714
+ }
9428
9715
  async function models(context) {
9429
9716
  rejectUnknownFlags(context.parsed.flags, ["provider"]);
9430
9717
  const { listDimModels: listDimModels2, DIM_DEFAULT_PROVIDER: DIM_DEFAULT_PROVIDER2 } = await Promise.resolve().then(() => (init_dim_model(), dim_model_exports));
@@ -9448,11 +9735,15 @@ async function run2(context) {
9448
9735
  saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
9449
9736
  }
9450
9737
  const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
9451
- mkdirSync11(resolvedWorkdir, { recursive: true });
9452
- const lockPath = join19(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
9738
+ mkdirSync12(resolvedWorkdir, { recursive: true });
9739
+ const lockPath = join20(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
9453
9740
  const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
9454
9741
  if (!acquired.ok) {
9455
9742
  const pid = acquired.existing?.pid;
9743
+ if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
9744
+ context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
9745
+ return { running: false, yieldedTo: pid ?? null };
9746
+ }
9456
9747
  throw new CliError(
9457
9748
  "ALREADY_RUNNING",
9458
9749
  EXIT.CONFLICT,
@@ -9487,11 +9778,21 @@ async function run2(context) {
9487
9778
  context.err(`[daemon] morula-runtime daemon \u524D\u53F0\u8FD0\u884C\u4E2D\uFF08Ctrl+C \u9000\u51FA\uFF09`);
9488
9779
  try {
9489
9780
  await runner.start();
9781
+ } catch (error) {
9782
+ if (isAuthRejected(error)) {
9783
+ context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
9784
+ context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
9785
+ return { running: false, stoppedReason: "auth" };
9786
+ }
9787
+ throw error;
9490
9788
  } finally {
9491
9789
  releaseLock();
9492
9790
  }
9493
9791
  return { running: false };
9494
9792
  }
9793
+ function isAuthRejected(error) {
9794
+ return error instanceof MorulaApiError && (error.code === "UNAUTHENTICATED" || error.status === 401);
9795
+ }
9495
9796
  async function start(context) {
9496
9797
  rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent", "dry-run"]);
9497
9798
  const name = flagString(context.parsed.flags, "name") ?? void 0;
@@ -9509,10 +9810,10 @@ async function start(context) {
9509
9810
  }
9510
9811
  const dir = daemonDir(configFilePath(context.env));
9511
9812
  if (!dryRun)
9512
- mkdirSync11(dir, { recursive: true, mode: 448 });
9513
- const pidFile = join19(dir, PID_FILE);
9514
- const logFile = join19(dir, LOG_FILE);
9515
- const lockPath = join19(dir, DAEMON_LOCK_FILE);
9813
+ mkdirSync12(dir, { recursive: true, mode: 448 });
9814
+ const pidFile = join20(dir, PID_FILE);
9815
+ const logFile = join20(dir, LOG_FILE);
9816
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9516
9817
  const lock = probeDaemonLock(lockPath);
9517
9818
  if (lock.state === "active") {
9518
9819
  throw new CliError(
@@ -9523,7 +9824,7 @@ async function start(context) {
9523
9824
  { hint: "morula-runtime daemon status" }
9524
9825
  );
9525
9826
  }
9526
- if (existsSync16(pidFile)) {
9827
+ if (existsSync17(pidFile)) {
9527
9828
  const pid = Number(readFileSync11(pidFile, "utf8").trim());
9528
9829
  if (isProcessAlive(pid)) {
9529
9830
  throw new CliError("ALREADY_RUNNING", EXIT.CONFLICT, `daemon \u5DF2\u5728\u8FD0\u884C\uFF08pid=${pid}\uFF09\uFF1B\u5982\u9700\u5F3A\u5236\u91CD\u542F\u8BF7\u5148\u6267\u884C kill -9 ${pid}`, false, {
@@ -9531,7 +9832,7 @@ async function start(context) {
9531
9832
  });
9532
9833
  }
9533
9834
  if (!dryRun)
9534
- rmSync7(pidFile, { force: true });
9835
+ rmSync8(pidFile, { force: true });
9535
9836
  }
9536
9837
  const env = {
9537
9838
  ...process.env,
@@ -9569,12 +9870,12 @@ async function start(context) {
9569
9870
  const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
9570
9871
  const result = spawnDaemonProcess2({ logFile, env });
9571
9872
  if (result.error || result.pid <= 0) {
9572
- rmSync7(pidFile, { force: true });
9873
+ rmSync8(pidFile, { force: true });
9573
9874
  throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
9574
9875
  hint: `tail -n 50 ${logFile}`
9575
9876
  });
9576
9877
  }
9577
- writeFileSync9(pidFile, String(result.pid), { mode: 384 });
9878
+ writeFileSync10(pidFile, String(result.pid), { mode: 384 });
9578
9879
  context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
9579
9880
  return { running: true, pid: result.pid, logFile, lockFile: lockPath };
9580
9881
  }
@@ -9582,11 +9883,11 @@ async function stop(context) {
9582
9883
  rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
9583
9884
  const dryRun = isDryRun(context);
9584
9885
  const dir = daemonDir(configFilePath(context.env));
9585
- const pidFile = join19(dir, PID_FILE);
9586
- const lockPath = join19(dir, DAEMON_LOCK_FILE);
9886
+ const pidFile = join20(dir, PID_FILE);
9887
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9587
9888
  const lock = probeDaemonLock(lockPath);
9588
9889
  const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
9589
- const filePid = existsSync16(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
9890
+ const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
9590
9891
  const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
9591
9892
  if (dryRun) {
9592
9893
  return {
@@ -9596,7 +9897,7 @@ async function stop(context) {
9596
9897
  };
9597
9898
  }
9598
9899
  if (!pid) {
9599
- rmSync7(pidFile, { force: true });
9900
+ rmSync8(pidFile, { force: true });
9600
9901
  releaseDaemonLock(lockPath);
9601
9902
  return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
9602
9903
  }
@@ -9605,7 +9906,7 @@ async function stop(context) {
9605
9906
  releaseDaemonLock(lockPath);
9606
9907
  else
9607
9908
  releaseDaemonLock(lockPath, pid);
9608
- rmSync7(pidFile, { force: true });
9909
+ rmSync8(pidFile, { force: true });
9609
9910
  if (result.outcome === "killed")
9610
9911
  context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
9611
9912
  return { running: false, stoppedPid: pid, outcome: result.outcome };
@@ -9614,11 +9915,11 @@ async function status2(context) {
9614
9915
  rejectUnknownFlags(context.parsed.flags, []);
9615
9916
  const cfg = loadDaemonConfig(context.store);
9616
9917
  const dir = daemonDir(configFilePath(context.env));
9617
- const pidFile = join19(dir, PID_FILE);
9618
- const lockPath = join19(dir, DAEMON_LOCK_FILE);
9918
+ const pidFile = join20(dir, PID_FILE);
9919
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9619
9920
  const lock = probeDaemonLock(lockPath);
9620
9921
  const lockPid = lock.state === "active" ? lock.record.pid : null;
9621
- const filePid = existsSync16(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
9922
+ const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
9622
9923
  const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
9623
9924
  return {
9624
9925
  running: pid !== null,
@@ -9628,7 +9929,7 @@ async function status2(context) {
9628
9929
  name: cfg?.name ?? null,
9629
9930
  workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
9630
9931
  maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
9631
- logFile: join19(dir, LOG_FILE),
9932
+ logFile: join20(dir, LOG_FILE),
9632
9933
  lockFile: lockPath
9633
9934
  };
9634
9935
  }
@@ -9658,32 +9959,32 @@ function parseMaxConcurrent(context) {
9658
9959
  init_dist2();
9659
9960
 
9660
9961
  // src/daemon/plugin-register.ts
9661
- import { chmodSync as chmodSync3, cpSync, existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync8, statSync as statSync4, writeFileSync as writeFileSync10 } from "node:fs";
9662
- import { homedir as homedir12 } from "node:os";
9663
- import { dirname as dirname9, join as join20 } from "node:path";
9962
+ import { chmodSync as chmodSync3, cpSync, existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
9963
+ import { homedir as homedir13 } from "node:os";
9964
+ import { dirname as dirname10, join as join21 } from "node:path";
9664
9965
  import { fileURLToPath as fileURLToPath2 } from "node:url";
9665
9966
  var PLUGIN_NAME = "morula-cli";
9666
9967
  var MANIFEST_DIR_NAME = ".codex-plugin";
9667
- function legacyAgentsPluginDir(home = homedir12()) {
9668
- return join20(home, ".agents", "plugins", PLUGIN_NAME);
9968
+ function legacyAgentsPluginDir(home = homedir13()) {
9969
+ return join21(home, ".agents", "plugins", PLUGIN_NAME);
9669
9970
  }
9670
- function resolveDimcodeHome(env = process.env, home = homedir12()) {
9971
+ function resolveDimcodeHome(env = process.env, home = homedir13()) {
9671
9972
  const fromEnv = env.DIMCODE_HOME?.trim();
9672
- return fromEnv || join20(home, ".dimcode", "v2");
9973
+ return fromEnv || join21(home, ".dimcode", "v2");
9673
9974
  }
9674
- function defaultPluginsRoot(env = process.env, home = homedir12()) {
9675
- return join20(resolveDimcodeHome(env, home), "plugins");
9975
+ function defaultPluginsRoot(env = process.env, home = homedir13()) {
9976
+ return join21(resolveDimcodeHome(env, home), "plugins");
9676
9977
  }
9677
9978
  function resolvePackagePaths(entryUrl) {
9678
- const distDir = dirname9(fileURLToPath2(entryUrl));
9979
+ const distDir = dirname10(fileURLToPath2(entryUrl));
9679
9980
  return {
9680
- packageRoot: dirname9(distDir),
9981
+ packageRoot: dirname10(distDir),
9681
9982
  distDir,
9682
- assetsDir: join20(distDir, "plugin")
9983
+ assetsDir: join21(distDir, "plugin")
9683
9984
  };
9684
9985
  }
9685
9986
  function buildLauncherScript(opts) {
9686
- const entry = join20(opts.packageRoot, "dist", "morula.js");
9987
+ const entry = join21(opts.packageRoot, "dist", "morula.js");
9687
9988
  return `#!/bin/sh
9688
9989
  # Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
9689
9990
  # \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
@@ -9697,15 +9998,15 @@ exec "$NODE" ${JSON.stringify(entry)} "$@"
9697
9998
  `;
9698
9999
  }
9699
10000
  function assetsReady(paths) {
9700
- return existsSync17(join20(paths.assetsDir, "plugin.json")) && existsSync17(join20(paths.assetsDir, "skills"));
10001
+ return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
9701
10002
  }
9702
10003
  function listSkillNames(assetsDir) {
9703
- const dir = join20(assetsDir, "skills");
9704
- if (!existsSync17(dir))
10004
+ const dir = join21(assetsDir, "skills");
10005
+ if (!existsSync18(dir))
9705
10006
  return [];
9706
10007
  return readdirSync5(dir).filter((name) => {
9707
10008
  try {
9708
- return statSync4(join20(dir, name)).isDirectory();
10009
+ return statSync4(join21(dir, name)).isDirectory();
9709
10010
  } catch {
9710
10011
  return false;
9711
10012
  }
@@ -9714,24 +10015,24 @@ function listSkillNames(assetsDir) {
9714
10015
  function registerPlugin(opts) {
9715
10016
  const { pluginsRoot, paths, version } = opts;
9716
10017
  const nodePath = opts.nodePath ?? process.execPath;
9717
- const pluginDir = join20(pluginsRoot, PLUGIN_NAME);
10018
+ const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
9718
10019
  if (!assetsReady(paths))
9719
10020
  throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
9720
- const skillsSrc = join20(paths.assetsDir, "skills");
10021
+ const skillsSrc = join21(paths.assetsDir, "skills");
9721
10022
  const skills = listSkillNames(paths.assetsDir);
9722
- const manifestDir = join20(pluginDir, MANIFEST_DIR_NAME);
9723
- mkdirSync12(manifestDir, { recursive: true });
9724
- const manifest = JSON.parse(readFileSync12(join20(paths.assetsDir, "plugin.json"), "utf8"));
9725
- writeFileSync10(join20(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
10023
+ const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
10024
+ mkdirSync13(manifestDir, { recursive: true });
10025
+ const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
10026
+ writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
9726
10027
  `);
9727
- const skillsDest = join20(pluginDir, "skills");
9728
- rmSync8(skillsDest, { recursive: true, force: true });
9729
- mkdirSync12(skillsDest, { recursive: true });
10028
+ const skillsDest = join21(pluginDir, "skills");
10029
+ rmSync9(skillsDest, { recursive: true, force: true });
10030
+ mkdirSync13(skillsDest, { recursive: true });
9730
10031
  cpSync(skillsSrc, skillsDest, { recursive: true });
9731
- const binDir = join20(pluginDir, "bin");
9732
- mkdirSync12(binDir, { recursive: true });
9733
- const binPath = join20(binDir, "morula");
9734
- writeFileSync10(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
10032
+ const binDir = join21(pluginDir, "bin");
10033
+ mkdirSync13(binDir, { recursive: true });
10034
+ const binPath = join21(binDir, "morula");
10035
+ writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
9735
10036
  chmodSync3(binPath, 493);
9736
10037
  const legacy = legacyAgentsPluginDir(opts.home);
9737
10038
  return {
@@ -9740,7 +10041,7 @@ function registerPlugin(opts) {
9740
10041
  skills,
9741
10042
  version,
9742
10043
  node: nodePath,
9743
- legacyDir: existsSync17(join20(legacy, MANIFEST_DIR_NAME)) ? legacy : null
10044
+ legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
9744
10045
  };
9745
10046
  }
9746
10047
 
@@ -9917,6 +10218,21 @@ var SPEC = {
9917
10218
  idempotent: true,
9918
10219
  emits: ["running", "autostart", "ok"]
9919
10220
  },
10221
+ {
10222
+ action: "autostart",
10223
+ summary: "register/remove the macOS LaunchAgent so the daemon starts at login and is relaunched by launchd on abnormal exit (unattended executor machines)",
10224
+ args: [
10225
+ { name: "state", required: true, description: "on = register LaunchAgent (RunAtLoad starts the daemon now and at every login); off = remove it (a running daemon is untouched)" }
10226
+ ],
10227
+ flags: [
10228
+ { name: "dry-run", type: "boolean", description: "print the LaunchAgent plan without writing anything (read-only)" }
10229
+ ],
10230
+ examples: ["morula-runtime daemon autostart on", "morula-runtime daemon autostart off"],
10231
+ writes: "local",
10232
+ interactive: false,
10233
+ idempotent: true,
10234
+ emits: ["ok", "autostart", "installed", "dryRun", "planned", "blockedBy"]
10235
+ },
9920
10236
  {
9921
10237
  action: "doctor",
9922
10238
  summary: "read-only diagnostics: node/cli/config/auth/daemon/dim facts plus checks[] with hints (offline by default; --probe goes online)",