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

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 = "20260917063948.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.6";
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(", ")}`);
@@ -4525,18 +4606,18 @@ function buildGitlabSshRemote(repoFullName, baseUrl) {
4525
4606
  return `git@${host}:${repoFullName}.git`;
4526
4607
  }
4527
4608
  function ensureRepo(workdir, tenantId, repo, env, remoteUrl) {
4528
- const repoDir = join9(workdir, tenantId, repo);
4529
- if (existsSync6(join9(repoDir, ".git"))) {
4609
+ const repoDir = join11(workdir, tenantId, repo);
4610
+ if (existsSync8(join11(repoDir, ".git"))) {
4530
4611
  if (remoteUrl)
4531
4612
  syncRemoteUrl(repoDir, remoteUrl, env);
4532
4613
  try {
4533
- execFileSync4("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
4614
+ execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
4534
4615
  } catch {
4535
4616
  }
4536
4617
  return repoDir;
4537
4618
  }
4538
- mkdirSync4(repoDir, { recursive: true });
4539
- execFileSync4("git", ["clone", "--filter=blob:none", remoteUrl ?? `https://github.com/${repo}`, repoDir], {
4619
+ mkdirSync6(repoDir, { recursive: true });
4620
+ execFileSync6("git", ["clone", "--filter=blob:none", remoteUrl ?? `https://github.com/${repo}`, repoDir], {
4540
4621
  env,
4541
4622
  stdio: "pipe"
4542
4623
  });
@@ -4544,17 +4625,17 @@ function ensureRepo(workdir, tenantId, repo, env, remoteUrl) {
4544
4625
  }
4545
4626
  function syncRemoteUrl(repoDir, remoteUrl, env) {
4546
4627
  try {
4547
- const cur = execFileSync4("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
4628
+ const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
4548
4629
  if (cur === remoteUrl)
4549
4630
  return;
4550
- execFileSync4("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
4631
+ execFileSync6("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
4551
4632
  } catch {
4552
4633
  }
4553
4634
  }
4554
4635
  function isLinkedWorktree(repoDir) {
4555
4636
  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"));
4637
+ const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
4638
+ return !!gitDir && existsSync8(join11(gitDir, "commondir"));
4558
4639
  } catch {
4559
4640
  return false;
4560
4641
  }
@@ -4562,7 +4643,7 @@ function isLinkedWorktree(repoDir) {
4562
4643
  function enableWorktreeConfig(repoDir) {
4563
4644
  const getLocal = () => {
4564
4645
  try {
4565
- return execFileSync4("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
4646
+ return execFileSync6("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
4566
4647
  } catch {
4567
4648
  return null;
4568
4649
  }
@@ -4570,7 +4651,7 @@ function enableWorktreeConfig(repoDir) {
4570
4651
  if (getLocal() === "true")
4571
4652
  return true;
4572
4653
  try {
4573
- execFileSync4("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
4654
+ execFileSync6("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
4574
4655
  return true;
4575
4656
  } catch {
4576
4657
  return getLocal() === "true";
@@ -4581,41 +4662,41 @@ function ensureGitIdentity(repoDir, personaName) {
4581
4662
  const email = `${personaName.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}@morula.local`;
4582
4663
  if (isLinkedWorktree(repoDir)) {
4583
4664
  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" });
4665
+ execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
4666
+ execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
4586
4667
  return;
4587
4668
  }
4588
4669
  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
4670
  }
4590
- execFileSync4("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
4591
- execFileSync4("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
4671
+ execFileSync6("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
4672
+ execFileSync6("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
4592
4673
  }
4593
4674
 
4594
4675
  // 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";
4676
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "node:fs";
4677
+ import { homedir as homedir9 } from "node:os";
4678
+ import { join as join12 } from "node:path";
4598
4679
  var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
4599
4680
  var OUTBOX_MAX_AGE_MS = 24 * 36e5;
4600
4681
  function outboxDir(configDir) {
4601
- const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join10(homedir7(), ".morula-cli");
4602
- return join10(base, "daemon", "outbox");
4682
+ const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join12(homedir9(), ".morula-cli");
4683
+ return join12(base, "daemon", "outbox");
4603
4684
  }
4604
4685
  function writeOutbox(entry, configDir) {
4605
4686
  const dir = outboxDir(configDir);
4606
- mkdirSync5(dir, { recursive: true, mode: 448 });
4607
- writeFileSync4(join10(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
4687
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4688
+ writeFileSync5(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
4608
4689
  }
4609
4690
  function readOutboxEntries(configDir) {
4610
4691
  const dir = outboxDir(configDir);
4611
- if (!existsSync7(dir))
4692
+ if (!existsSync9(dir))
4612
4693
  return [];
4613
4694
  const entries = [];
4614
4695
  for (const file of readdirSync2(dir)) {
4615
4696
  if (!file.endsWith(".json"))
4616
4697
  continue;
4617
4698
  try {
4618
- const raw = readFileSync6(join10(dir, file), "utf8");
4699
+ const raw = readFileSync6(join12(dir, file), "utf8");
4619
4700
  const parsed = JSON.parse(raw);
4620
4701
  if (parsed && typeof parsed.taskId === "string")
4621
4702
  entries.push(parsed);
@@ -4625,17 +4706,17 @@ function readOutboxEntries(configDir) {
4625
4706
  return entries;
4626
4707
  }
4627
4708
  function removeOutbox(taskId, configDir) {
4628
- rmSync4(join10(outboxDir(configDir), `${taskId}.json`), { force: true });
4709
+ rmSync6(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
4629
4710
  }
4630
4711
  function staleDir(configDir) {
4631
- return join10(outboxDir(configDir), "stale");
4712
+ return join12(outboxDir(configDir), "stale");
4632
4713
  }
4633
4714
  function archiveOutbox(entry, reason, configDir) {
4634
4715
  const dir = staleDir(configDir);
4635
- mkdirSync5(dir, { recursive: true, mode: 448 });
4716
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4636
4717
  try {
4637
- writeFileSync4(
4638
- join10(dir, `${entry.taskId}.json`),
4718
+ writeFileSync5(
4719
+ join12(dir, `${entry.taskId}.json`),
4639
4720
  JSON.stringify({ ...entry, archivedReason: reason, archivedAt: (/* @__PURE__ */ new Date()).toISOString() }),
4640
4721
  { mode: 384 }
4641
4722
  );
@@ -5622,8 +5703,8 @@ var DimAgentAdapter = class {
5622
5703
  }
5623
5704
  }
5624
5705
  async probe() {
5625
- const { existsSync: existsSync18 } = await import("node:fs");
5626
- const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
5706
+ const { existsSync: existsSync19 } = await import("node:fs");
5707
+ const installed = existsSync19(this.dimPath) || this.dimPath === "dim";
5627
5708
  let authStatus = installed ? "unknown" : "error";
5628
5709
  let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
5629
5710
  let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
@@ -5914,15 +5995,15 @@ function pickLegacyDimModels(inventory) {
5914
5995
  }
5915
5996
 
5916
5997
  // 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";
5998
+ import { execFileSync as execFileSync7 } from "node:child_process";
5999
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
6000
+ import { dirname as dirname6, join as join14 } from "node:path";
5920
6001
  function ownerOf(repoFullName) {
5921
6002
  const idx = repoFullName.indexOf("/");
5922
6003
  return idx > 0 ? repoFullName.slice(0, idx) : "_default";
5923
6004
  }
5924
6005
  function buildRepoCachePath(workdir, tenantId, repoFullName) {
5925
- return join12(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
6006
+ return join14(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
5926
6007
  }
5927
6008
  function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
5928
6009
  const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
@@ -5930,20 +6011,20 @@ function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
5930
6011
  return {
5931
6012
  repoCachePath,
5932
6013
  ensure() {
5933
- if (existsSync9(join12(repoCachePath, ".git"))) {
6014
+ if (existsSync11(join14(repoCachePath, ".git"))) {
5934
6015
  if (url)
5935
6016
  syncRemoteUrl(repoCachePath, url, env);
5936
6017
  return;
5937
6018
  }
5938
- mkdirSync6(dirname5(repoCachePath), { recursive: true });
5939
- execFileSync5("git", ["clone", "--filter=blob:none", url, repoCachePath], {
6019
+ mkdirSync8(dirname6(repoCachePath), { recursive: true });
6020
+ execFileSync7("git", ["clone", "--filter=blob:none", url, repoCachePath], {
5940
6021
  env,
5941
6022
  stdio: "pipe"
5942
6023
  });
5943
6024
  },
5944
6025
  fetch() {
5945
6026
  try {
5946
- execFileSync5("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
6027
+ execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
5947
6028
  } catch {
5948
6029
  }
5949
6030
  }
@@ -5952,30 +6033,30 @@ function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
5952
6033
 
5953
6034
  // src/daemon/repo-checkout-service.ts
5954
6035
  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";
6036
+ import { execFileSync as execFileSync9 } from "node:child_process";
6037
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
6038
+ import { basename as basename2, join as join17 } from "node:path";
5958
6039
 
5959
6040
  // 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";
6041
+ import { execFileSync as execFileSync8 } from "node:child_process";
6042
+ 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";
6043
+ import { basename, join as join16, sep as sep2 } from "node:path";
5963
6044
 
5964
6045
  // 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";
6046
+ import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
6047
+ import { join as join15 } from "node:path";
5967
6048
  var WORKTREE_MARKER_FILE = ".morula-worktree.json";
5968
6049
  var WORKTREE_MARKER_SCHEMA = 1;
5969
6050
  function markerPath(worktreePath) {
5970
- return join13(worktreePath, WORKTREE_MARKER_FILE);
6051
+ return join15(worktreePath, WORKTREE_MARKER_FILE);
5971
6052
  }
5972
6053
  function writeWorktreeMarker(worktreePath, marker) {
5973
- writeFileSync5(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
6054
+ writeFileSync6(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
5974
6055
  `, "utf8");
5975
6056
  }
5976
6057
  function readWorktreeMarker(worktreePath) {
5977
6058
  const p = markerPath(worktreePath);
5978
- if (!existsSync10(p))
6059
+ if (!existsSync12(p))
5979
6060
  return null;
5980
6061
  try {
5981
6062
  const raw = JSON.parse(readFileSync8(p, "utf8"));
@@ -6002,42 +6083,42 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
6002
6083
  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
6084
  }
6004
6085
  const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
6005
- return join14(workdir, "worktrees", tenantId, runtimeSegment, dirName);
6086
+ return join16(workdir, "worktrees", tenantId, runtimeSegment, dirName);
6006
6087
  }
6007
6088
  function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
6008
- return join14(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
6089
+ return join16(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
6009
6090
  }
6010
6091
  function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
6011
6092
  const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
6012
- return join14(workdir, "worktrees", tenantId, dirName);
6093
+ return join16(workdir, "worktrees", tenantId, dirName);
6013
6094
  }
6014
6095
  var legacyPathWarned = /* @__PURE__ */ new Set();
6015
6096
  function warnLegacyWorktreeResidue(legacyPath) {
6016
- if (legacyPathWarned.has(legacyPath) || !existsSync11(legacyPath))
6097
+ if (legacyPathWarned.has(legacyPath) || !existsSync13(legacyPath))
6017
6098
  return;
6018
6099
  legacyPathWarned.add(legacyPath);
6019
6100
  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
6101
  }
6021
6102
  var legacyLayoutWarned = /* @__PURE__ */ new Set();
6022
6103
  function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
6023
- if (legacyLayoutWarned.has(taskRoot) || existsSync11(worktreePath) || !existsSync11(join14(taskRoot, WORKTREE_MARKER_FILE)))
6104
+ if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join16(taskRoot, WORKTREE_MARKER_FILE)))
6024
6105
  return;
6025
6106
  legacyLayoutWarned.add(taskRoot);
6026
6107
  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
6108
  }
6028
6109
  function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
6029
- const gitignorePath = join14(worktreePath, ".gitignore");
6030
- const existing = existsSync11(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
6110
+ const gitignorePath = join16(worktreePath, ".gitignore");
6111
+ const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
6031
6112
  const requiredLines = [WORKTREE_MARKER_FILE];
6032
6113
  const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
6033
6114
  if (missing.length > 0) {
6034
6115
  const next = `${existing.trimEnd() ? `${existing.trimEnd()}
6035
6116
  ` : ""}${missing.join("\n")}
6036
6117
  `;
6037
- writeFileSync6(gitignorePath, next, "utf8");
6118
+ writeFileSync7(gitignorePath, next, "utf8");
6038
6119
  try {
6039
- execFileSync6("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
6040
- execFileSync6(
6120
+ execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
6121
+ execFileSync8(
6041
6122
  "git",
6042
6123
  ["-C", worktreePath, "-c", "user.name=morula-daemon", "-c", "user.email=morula-daemon@morula.local", "commit", "-q", "-m", "chore: track morula worktree marker"],
6043
6124
  { env, stdio: "pipe" }
@@ -6048,7 +6129,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
6048
6129
  }
6049
6130
  function currentBranch(repoPath) {
6050
6131
  try {
6051
- const out = execFileSync6("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
6132
+ const out = execFileSync8("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
6052
6133
  encoding: "utf8",
6053
6134
  stdio: "pipe"
6054
6135
  }).trim();
@@ -6059,13 +6140,13 @@ function currentBranch(repoPath) {
6059
6140
  }
6060
6141
  function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
6061
6142
  try {
6062
- const current = execFileSync6("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
6143
+ const current = execFileSync8("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
6063
6144
  if (current)
6064
6145
  return current;
6065
6146
  } catch {
6066
6147
  }
6067
6148
  try {
6068
- const out = execFileSync6("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
6149
+ const out = execFileSync8("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
6069
6150
  const first = out.split("\n").map((l) => l.trim()).find((l) => l && !l.includes("->"));
6070
6151
  if (first)
6071
6152
  return first.replace(/^origin\//, "");
@@ -6076,7 +6157,7 @@ function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
6076
6157
  }
6077
6158
  function headCommitSubject(repoPath, env) {
6078
6159
  try {
6079
- const out = execFileSync6("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
6160
+ const out = execFileSync8("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
6080
6161
  encoding: "utf8",
6081
6162
  stdio: "pipe",
6082
6163
  ...env ? { env } : {}
@@ -6088,7 +6169,7 @@ function headCommitSubject(repoPath, env) {
6088
6169
  }
6089
6170
  function hasLocalChanges(repoPath, env) {
6090
6171
  try {
6091
- const out = execFileSync6("git", ["-C", repoPath, "status", "--porcelain"], {
6172
+ const out = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], {
6092
6173
  encoding: "utf8",
6093
6174
  stdio: "pipe",
6094
6175
  ...env ? { env } : {}
@@ -6124,17 +6205,17 @@ function isIndexLockConflict(err) {
6124
6205
  return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
6125
6206
  }
6126
6207
  function worktreeIndexLockPath(repoCachePath, worktreePath) {
6127
- return join14(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
6208
+ return join16(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
6128
6209
  }
6129
6210
  function indexLockPathFromError(text) {
6130
6211
  const m = text.match(/[^\s'‘’"]*index\.lock/);
6131
6212
  return m && m[0].length > "index.lock".length ? m[0] : null;
6132
6213
  }
6133
6214
  function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
6134
- if (!existsSync11(lockPath))
6215
+ if (!existsSync13(lockPath))
6135
6216
  return true;
6136
6217
  const deadline = Date.now() + timeoutMs;
6137
- while (existsSync11(lockPath)) {
6218
+ while (existsSync13(lockPath)) {
6138
6219
  const remain = deadline - Date.now();
6139
6220
  if (remain <= 0)
6140
6221
  return false;
@@ -6144,7 +6225,7 @@ function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, p
6144
6225
  }
6145
6226
  function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
6146
6227
  const runAdd = deps.runAdd ?? ((argv2) => {
6147
- execFileSync6("git", argv2, { stdio: "pipe" });
6228
+ execFileSync8("git", argv2, { stdio: "pipe" });
6148
6229
  });
6149
6230
  const argv = ["-C", repoCachePath, "worktree", "add", "--detach", worktreePath, ref];
6150
6231
  const waitMs = deps.indexLockWaitMs ?? INDEX_LOCK_WAIT_MAX_MS;
@@ -6200,7 +6281,7 @@ function inspectForeignDirectory(dir) {
6200
6281
  if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
6201
6282
  return;
6202
6283
  count++;
6203
- const full = join14(p, e.name);
6284
+ const full = join16(p, e.name);
6204
6285
  try {
6205
6286
  const st = statSync2(full);
6206
6287
  bytes += st.size;
@@ -6238,7 +6319,7 @@ function prepareWorktree(input) {
6238
6319
  const requestedBase = input.baseBranch?.trim();
6239
6320
  const hasRef = (ref) => {
6240
6321
  try {
6241
- execFileSync6("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
6322
+ execFileSync8("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
6242
6323
  return true;
6243
6324
  } catch {
6244
6325
  return false;
@@ -6250,14 +6331,14 @@ function prepareWorktree(input) {
6250
6331
  } else {
6251
6332
  const remoteHead = (() => {
6252
6333
  try {
6253
- const out = execFileSync6("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
6334
+ const out = execFileSync8("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
6254
6335
  const name = out.replace(/^refs\/remotes\/origin\//, "");
6255
6336
  if (name && hasRef(name))
6256
6337
  return name;
6257
6338
  } catch {
6258
6339
  }
6259
6340
  try {
6260
- const out = execFileSync6("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
6341
+ const out = execFileSync8("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
6261
6342
  const m = out.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD/m);
6262
6343
  return m?.[1] ?? null;
6263
6344
  } catch {
@@ -6276,10 +6357,10 @@ function prepareWorktree(input) {
6276
6357
  const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
6277
6358
  warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
6278
6359
  warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
6279
- if (existsSync11(worktreePath)) {
6360
+ if (existsSync13(worktreePath)) {
6280
6361
  const isGitRepo = (() => {
6281
6362
  try {
6282
- execFileSync6("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
6363
+ execFileSync8("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
6283
6364
  return true;
6284
6365
  } catch {
6285
6366
  return false;
@@ -6288,7 +6369,7 @@ function prepareWorktree(input) {
6288
6369
  if (!isGitRepo) {
6289
6370
  const forensics = inspectForeignDirectory(worktreePath);
6290
6371
  try {
6291
- rmSync5(worktreePath, { recursive: true, force: true });
6372
+ rmSync7(worktreePath, { recursive: true, force: true });
6292
6373
  } catch {
6293
6374
  reportForeignResidue(worktreePath, forensics, false);
6294
6375
  throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
@@ -6306,11 +6387,11 @@ function prepareWorktree(input) {
6306
6387
  }
6307
6388
  const branch = currentBranch(worktreePath);
6308
6389
  if (!branch) {
6309
- const dirtyOut = execFileSync6("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6390
+ const dirtyOut = execFileSync8("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6310
6391
  const dirty = dirtyOut.split("\n").filter(
6311
6392
  (l) => l.trim() && !l.trim().startsWith("?? .agents") && !l.trim().startsWith("?? .agent_context") && !l.trim().startsWith("?? .morula")
6312
6393
  ).join("\n");
6313
- const extraCommits = Number(execFileSync6(
6394
+ const extraCommits = Number(execFileSync8(
6314
6395
  "git",
6315
6396
  ["-C", worktreePath, "rev-list", "--count", `origin/${input.baseBranch}..HEAD`],
6316
6397
  { encoding: "utf8", stdio: "pipe" }
@@ -6318,7 +6399,7 @@ function prepareWorktree(input) {
6318
6399
  const rescueTag = buildRescueTag(input.workContextId, input.code);
6319
6400
  if (dirty.length > 0) {
6320
6401
  try {
6321
- execFileSync6(
6402
+ execFileSync8(
6322
6403
  "git",
6323
6404
  ["-C", worktreePath, "stash", "push", "-u", "-m", `morula-rescue ${rescueTag}`],
6324
6405
  { stdio: "pipe" }
@@ -6329,9 +6410,9 @@ function prepareWorktree(input) {
6329
6410
  }
6330
6411
  }
6331
6412
  if (extraCommits > 1) {
6332
- const headSha2 = execFileSync6("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
6413
+ const headSha2 = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
6333
6414
  try {
6334
- execFileSync6("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
6415
+ execFileSync8("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
6335
6416
  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
6417
  } catch {
6337
6418
  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 +6424,8 @@ function prepareWorktree(input) {
6343
6424
  }
6344
6425
  }
6345
6426
  }
6346
- if (!existsSync11(worktreePath)) {
6347
- mkdirSync7(join14(worktreePath, ".."), { recursive: true });
6427
+ if (!existsSync13(worktreePath)) {
6428
+ mkdirSync9(join16(worktreePath, ".."), { recursive: true });
6348
6429
  addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
6349
6430
  writeWorktreeMarker(worktreePath, {
6350
6431
  schemaVersion: 1,
@@ -6360,9 +6441,9 @@ function prepareWorktree(input) {
6360
6441
  }
6361
6442
  {
6362
6443
  const dotMorulaRescue = `${worktreePath}.morula-rescue`;
6363
- if (existsSync11(dotMorulaRescue)) {
6364
- const dotMorulaDir = join14(worktreePath, ".morula");
6365
- if (!existsSync11(dotMorulaDir)) {
6444
+ if (existsSync13(dotMorulaRescue)) {
6445
+ const dotMorulaDir = join16(worktreePath, ".morula");
6446
+ if (!existsSync13(dotMorulaDir)) {
6366
6447
  try {
6367
6448
  renameSync2(dotMorulaRescue, dotMorulaDir);
6368
6449
  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 +6451,11 @@ function prepareWorktree(input) {
6370
6451
  console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
6371
6452
  }
6372
6453
  } else {
6373
- rmSync5(dotMorulaRescue, { recursive: true, force: true });
6454
+ rmSync7(dotMorulaRescue, { recursive: true, force: true });
6374
6455
  }
6375
6456
  }
6376
6457
  }
6377
- const headSha = execFileSync6("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
6458
+ const headSha = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
6378
6459
  encoding: "utf8",
6379
6460
  stdio: "pipe"
6380
6461
  }).trim();
@@ -6390,10 +6471,10 @@ function prepareWorktree(input) {
6390
6471
  };
6391
6472
  }
6392
6473
  function resolveWorkspaceLayout(root, expected) {
6393
- const primaryDir = join14(root, safeDirName(expected.repoFullName));
6394
- if (existsSync11(join14(primaryDir, WORKTREE_MARKER_FILE)))
6474
+ const primaryDir = join16(root, safeDirName(expected.repoFullName));
6475
+ if (existsSync13(join16(primaryDir, WORKTREE_MARKER_FILE)))
6395
6476
  return { primaryDir, legacy: false };
6396
- if (existsSync11(join14(root, WORKTREE_MARKER_FILE)))
6477
+ if (existsSync13(join16(root, WORKTREE_MARKER_FILE)))
6397
6478
  return { primaryDir: root, legacy: true };
6398
6479
  return null;
6399
6480
  }
@@ -6408,7 +6489,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6408
6489
  return "MARKER_MISMATCH";
6409
6490
  const { primaryDir, legacy } = layout;
6410
6491
  try {
6411
- const status3 = execFileSync6("git", ["-C", primaryDir, "status", "--porcelain"], {
6492
+ const status3 = execFileSync8("git", ["-C", primaryDir, "status", "--porcelain"], {
6412
6493
  encoding: "utf8",
6413
6494
  stdio: "pipe"
6414
6495
  }).trim();
@@ -6417,11 +6498,11 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6417
6498
  ).join("\n");
6418
6499
  if (statusRelevant.length > 0)
6419
6500
  return "WORKTREE_DIRTY";
6420
- const additionalActivity = legacy ? findActivityInReposDir(join14(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
6501
+ const additionalActivity = legacy ? findActivityInReposDir(join16(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
6421
6502
  if (additionalActivity)
6422
6503
  return "WORKTREE_DIRTY";
6423
6504
  try {
6424
- const unpushed = execFileSync6("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
6505
+ const unpushed = execFileSync8("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
6425
6506
  encoding: "utf8",
6426
6507
  stdio: "pipe"
6427
6508
  }).trim();
@@ -6437,13 +6518,13 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
6437
6518
  }
6438
6519
  function repoHasActivity(repoPath) {
6439
6520
  try {
6440
- const status3 = execFileSync6("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6521
+ const status3 = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
6441
6522
  if (status3)
6442
6523
  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);
6524
+ const branches = execFileSync8("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
6444
6525
  for (const b of branches) {
6445
6526
  try {
6446
- const unpushed = execFileSync6("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
6527
+ const unpushed = execFileSync8("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
6447
6528
  if (unpushed)
6448
6529
  return true;
6449
6530
  } catch {
@@ -6456,7 +6537,7 @@ function repoHasActivity(repoPath) {
6456
6537
  return false;
6457
6538
  }
6458
6539
  function findActivityInReposDir(reposDir) {
6459
- if (!existsSync11(reposDir))
6540
+ if (!existsSync13(reposDir))
6460
6541
  return null;
6461
6542
  let names = [];
6462
6543
  try {
@@ -6465,8 +6546,8 @@ function findActivityInReposDir(reposDir) {
6465
6546
  return null;
6466
6547
  }
6467
6548
  for (const name of names) {
6468
- const repoPath = join14(reposDir, name);
6469
- if (!existsSync11(join14(repoPath, ".git")))
6549
+ const repoPath = join16(reposDir, name);
6550
+ if (!existsSync13(join16(repoPath, ".git")))
6470
6551
  continue;
6471
6552
  if (repoHasActivity(repoPath))
6472
6553
  return repoPath;
@@ -6481,16 +6562,16 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
6481
6562
  } catch {
6482
6563
  return null;
6483
6564
  }
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)) {
6565
+ const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join16(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
6566
+ const legacyReposDir = join16(taskRoot, ".morula", "repos");
6567
+ if (existsSync13(legacyReposDir)) {
6487
6568
  try {
6488
- candidates.push(...readdirSync3(legacyReposDir).map((n) => join14(legacyReposDir, n)));
6569
+ candidates.push(...readdirSync3(legacyReposDir).map((n) => join16(legacyReposDir, n)));
6489
6570
  } catch {
6490
6571
  }
6491
6572
  }
6492
6573
  for (const repoPath of candidates) {
6493
- if (!existsSync11(join14(repoPath, ".git")))
6574
+ if (!existsSync13(join16(repoPath, ".git")))
6494
6575
  continue;
6495
6576
  if (repoHasActivity(repoPath))
6496
6577
  return repoPath;
@@ -6498,18 +6579,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
6498
6579
  return null;
6499
6580
  }
6500
6581
  function cleanupInjectedSkills(dir) {
6501
- const injectedSkillsRoot = join14(dir, ".agents", "skills");
6502
- if (!existsSync11(injectedSkillsRoot))
6582
+ const injectedSkillsRoot = join16(dir, ".agents", "skills");
6583
+ if (!existsSync13(injectedSkillsRoot))
6503
6584
  return;
6504
6585
  try {
6505
6586
  for (const name of readdirSync3(injectedSkillsRoot)) {
6506
6587
  if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
6507
- rmSync5(join14(injectedSkillsRoot, name), { recursive: true, force: true });
6588
+ rmSync7(join16(injectedSkillsRoot, name), { recursive: true, force: true });
6508
6589
  }
6509
6590
  }
6510
6591
  if (readdirSync3(injectedSkillsRoot).length === 0) {
6511
- rmSync5(join14(dir, ".agents", "skills"), { recursive: true, force: true });
6512
- rmSync5(join14(dir, ".agents"), { recursive: true, force: true });
6592
+ rmSync7(join16(dir, ".agents", "skills"), { recursive: true, force: true });
6593
+ rmSync7(join16(dir, ".agents"), { recursive: true, force: true });
6513
6594
  }
6514
6595
  } catch {
6515
6596
  }
@@ -6525,11 +6606,11 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
6525
6606
  for (const e of entries) {
6526
6607
  if (!e.isDirectory() || e.name.startsWith("."))
6527
6608
  continue;
6528
- const repoPath = join14(taskRoot, e.name);
6529
- if (repoPath === excludeDir || !existsSync11(join14(repoPath, ".git")))
6609
+ const repoPath = join16(taskRoot, e.name);
6610
+ if (repoPath === excludeDir || !existsSync13(join16(repoPath, ".git")))
6530
6611
  continue;
6531
6612
  try {
6532
- const commonDir = execFileSync6("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
6613
+ const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
6533
6614
  if (commonDir)
6534
6615
  dirs.add(commonDir);
6535
6616
  } catch {
@@ -6543,37 +6624,37 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
6543
6624
  throw new Error("MARKER_MISMATCH");
6544
6625
  const { primaryDir, legacy } = layout;
6545
6626
  if (!legacy) {
6546
- if (primaryDir !== join14(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
6627
+ if (primaryDir !== join16(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
6547
6628
  throw new Error("GIT_REMOVE_FAILED");
6548
6629
  }
6549
6630
  cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
6550
6631
  const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
6551
6632
  try {
6552
- rmSync5(taskRoot, { recursive: true, force: true });
6633
+ rmSync7(taskRoot, { recursive: true, force: true });
6553
6634
  } catch {
6554
6635
  throw new Error("GIT_REMOVE_FAILED");
6555
6636
  }
6556
6637
  for (const dir of [repoCachePath, ...siblingCommonDirs]) {
6557
6638
  try {
6558
- execFileSync6("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
6639
+ execFileSync8("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
6559
6640
  } catch {
6560
6641
  }
6561
6642
  }
6562
6643
  }
6563
6644
  function removeWorktree(repoCachePath, worktreePath) {
6564
- if (!existsSync11(worktreePath))
6645
+ if (!existsSync13(worktreePath))
6565
6646
  return;
6566
6647
  try {
6567
6648
  cleanupInjectedSkills(worktreePath);
6568
- execFileSync6("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
6569
- execFileSync6("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
6649
+ execFileSync8("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
6650
+ execFileSync8("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
6570
6651
  } catch {
6571
6652
  throw new Error("GIT_REMOVE_FAILED");
6572
6653
  }
6573
6654
  }
6574
6655
 
6575
6656
  // src/daemon/repo-checkout-service.ts
6576
- var CHECKOUT_JSON_FILE = join15(".morula", "checkout.json");
6657
+ var CHECKOUT_JSON_FILE = join17(".morula", "checkout.json");
6577
6658
  var LocalRepoCheckoutService = class {
6578
6659
  constructor(opts) {
6579
6660
  this.opts = opts;
@@ -6661,9 +6742,9 @@ var LocalRepoCheckoutService = class {
6661
6742
  return;
6662
6743
  this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
6663
6744
  try {
6664
- const dir = join15(workdir, ".morula");
6665
- mkdirSync8(dir, { recursive: true, mode: 448 });
6666
- writeFileSync7(join15(dir, "checkout.json"), JSON.stringify({
6745
+ const dir = join17(workdir, ".morula");
6746
+ mkdirSync10(dir, { recursive: true, mode: 448 });
6747
+ writeFileSync8(join17(dir, "checkout.json"), JSON.stringify({
6667
6748
  taskId,
6668
6749
  port: this.boundPort,
6669
6750
  checkToken
@@ -6727,12 +6808,12 @@ var LocalRepoCheckoutService = class {
6727
6808
  repoFullName,
6728
6809
  env
6729
6810
  });
6730
- const headSha = execFileSync7("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6811
+ const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6731
6812
  return {
6732
6813
  ok: true,
6733
6814
  path: worktreePath,
6734
6815
  headSha,
6735
- branchName: execFileSync7("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
6816
+ branchName: execFileSync9("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
6736
6817
  };
6737
6818
  } catch (e) {
6738
6819
  return { ok: false, code: "CHECKOUT_FAILED", error: e instanceof Error ? e.message : String(e) };
@@ -6792,7 +6873,7 @@ var LocalRepoCheckoutService = class {
6792
6873
  env
6793
6874
  });
6794
6875
  syncGitIdentity(task.primaryRepoDir ?? task.workdir, targetPath, env);
6795
- const headSha = execFileSync7("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6876
+ const headSha = execFileSync9("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
6796
6877
  this.recordPreparedRepo(req.taskId, repoFullName, {
6797
6878
  workContextId: serverRes.workContextId,
6798
6879
  baseBranch: baseBranch || void 0,
@@ -6823,23 +6904,23 @@ function isReadOnlyRepoDir(dirName) {
6823
6904
  return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
6824
6905
  }
6825
6906
  function resolveRepoTargetDir(taskRoot, base) {
6826
- const direct = join15(taskRoot, base);
6827
- if (!existsSync12(direct) || existsSync12(join15(direct, ".git")))
6907
+ const direct = join17(taskRoot, base);
6908
+ if (!existsSync14(direct) || existsSync14(join17(direct, ".git")))
6828
6909
  return direct;
6829
6910
  for (let i = 2; i < 100; i++) {
6830
- const candidate = join15(taskRoot, `${base}-${i}`);
6831
- if (!existsSync12(candidate))
6911
+ const candidate = join17(taskRoot, `${base}-${i}`);
6912
+ if (!existsSync14(candidate))
6832
6913
  return candidate;
6833
6914
  }
6834
6915
  throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
6835
6916
  }
6836
6917
  function materializeReadWorktree(input) {
6837
6918
  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" });
6919
+ if (existsSync14(join17(worktreePath, ".git"))) {
6920
+ execFileSync9("git", ["-C", worktreePath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
6840
6921
  } else {
6841
- mkdirSync8(join15(worktreePath, ".."), { recursive: true });
6842
- execFileSync7("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, resolveBaseRef(input.repoCachePath, input.env)], {
6922
+ mkdirSync10(join17(worktreePath, ".."), { recursive: true });
6923
+ execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, resolveBaseRef(input.repoCachePath, input.env)], {
6843
6924
  env: input.env,
6844
6925
  stdio: "pipe"
6845
6926
  });
@@ -6849,7 +6930,7 @@ function materializeReadWorktree(input) {
6849
6930
  function resolveBaseRef(repoCachePath, env) {
6850
6931
  for (const ref of ["origin/main", "origin/master", "HEAD"]) {
6851
6932
  try {
6852
- execFileSync7("git", ["-C", repoCachePath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
6933
+ execFileSync9("git", ["-C", repoCachePath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
6853
6934
  return ref;
6854
6935
  } catch {
6855
6936
  }
@@ -6864,7 +6945,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
6864
6945
  } catch {
6865
6946
  return [];
6866
6947
  }
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();
6948
+ 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
6949
  }
6869
6950
  function parseRepoFullNameFromRemoteUrl(url) {
6870
6951
  const raw = url?.trim();
@@ -6886,7 +6967,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
6886
6967
  }
6887
6968
  function collectMaterializedRepoDeliveryTargets(input) {
6888
6969
  const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
6889
- const excludedDirs = [...excludedNames].map((name) => join15(input.taskRoot, safeRepoDirName(name)));
6970
+ const excludedDirs = [...excludedNames].map((name) => join17(input.taskRoot, safeRepoDirName(name)));
6890
6971
  const recordsByDir = /* @__PURE__ */ new Map();
6891
6972
  const recordsByDirName = /* @__PURE__ */ new Map();
6892
6973
  for (const [repoFullName, record] of input.prepared ?? []) {
@@ -6895,7 +6976,7 @@ function collectMaterializedRepoDeliveryTargets(input) {
6895
6976
  recordsByDir.set(record.dir, entry);
6896
6977
  recordsByDirName.set(safeRepoDirName(repoFullName), entry);
6897
6978
  }
6898
- const runGit = input.gitRunner ?? ((dir, args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
6979
+ const runGit = input.gitRunner ?? ((dir, args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
6899
6980
  const targets = [];
6900
6981
  for (const dir of listMaterializedRepoDirs(input.taskRoot, { excludeDirs: excludedDirs })) {
6901
6982
  const known = recordsByDir.get(dir) ?? recordsByDirName.get(basename2(dir));
@@ -6932,7 +7013,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
6932
7013
  const read = (key) => {
6933
7014
  for (const scope of ["--worktree", "--local"]) {
6934
7015
  try {
6935
- const out = execFileSync7("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
7016
+ const out = execFileSync9("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
6936
7017
  if (out)
6937
7018
  return out;
6938
7019
  } catch {
@@ -6943,21 +7024,21 @@ function syncGitIdentity(primaryDir, targetDir, env) {
6943
7024
  const name = read("user.name") ?? "morula-daemon[bot]";
6944
7025
  const email = read("user.email") ?? "morula-daemon@morula.local";
6945
7026
  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" });
7027
+ execFileSync9("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
7028
+ execFileSync9("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
6948
7029
  } catch {
6949
7030
  }
6950
7031
  }
6951
7032
  function materializeAdditionalRepo(input) {
6952
- if (existsSync12(join15(input.targetPath, ".git"))) {
6953
- execFileSync7("git", ["-C", input.targetPath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
7033
+ if (existsSync14(join17(input.targetPath, ".git"))) {
7034
+ execFileSync9("git", ["-C", input.targetPath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
6954
7035
  return input.targetPath;
6955
7036
  }
6956
7037
  const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master", "HEAD"] : ["origin/main", "origin/master", "HEAD"];
6957
7038
  let baseRef = null;
6958
7039
  for (const ref of candidates) {
6959
7040
  try {
6960
- execFileSync7("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
7041
+ execFileSync9("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
6961
7042
  baseRef = ref;
6962
7043
  break;
6963
7044
  } catch {
@@ -6965,8 +7046,8 @@ function materializeAdditionalRepo(input) {
6965
7046
  }
6966
7047
  if (!baseRef)
6967
7048
  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], {
7049
+ mkdirSync10(join17(input.targetPath, ".."), { recursive: true });
7050
+ execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
6970
7051
  env: input.env,
6971
7052
  stdio: "pipe"
6972
7053
  });
@@ -7435,6 +7516,14 @@ var DaemonRunner = class {
7435
7516
  if (this.running)
7436
7517
  return;
7437
7518
  this.running = true;
7519
+ try {
7520
+ await this.startInner();
7521
+ } catch (error) {
7522
+ this.stop();
7523
+ throw error;
7524
+ }
7525
+ }
7526
+ async startInner() {
7438
7527
  ensureGitOnPath();
7439
7528
  ensureNodeOnPath();
7440
7529
  {
@@ -8157,12 +8246,12 @@ var DaemonRunner = class {
8157
8246
  });
8158
8247
  if (spaceToken?.token)
8159
8248
  env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
8160
- repoDir = join16(cfg.workdir, "scratch", taskId);
8249
+ repoDir = join18(cfg.workdir, "scratch", taskId);
8161
8250
  taskRoot = repoDir;
8162
- mkdirSync9(repoDir, { recursive: true });
8163
- const readme = join16(repoDir, "README.md");
8164
- if (!existsSync13(readme)) {
8165
- writeFileSync8(
8251
+ mkdirSync11(repoDir, { recursive: true });
8252
+ const readme = join18(repoDir, "README.md");
8253
+ if (!existsSync15(readme)) {
8254
+ writeFileSync9(
8166
8255
  readme,
8167
8256
  `# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
8168
8257
 
@@ -8448,7 +8537,7 @@ var DaemonRunner = class {
8448
8537
  };
8449
8538
  const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
8450
8539
  const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
8451
- const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync13(resumeWorkDir);
8540
+ const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
8452
8541
  const input = {
8453
8542
  executionId: `${taskId}:1`,
8454
8543
  // task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
@@ -8655,7 +8744,7 @@ var DaemonRunner = class {
8655
8744
  const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
8656
8745
  try {
8657
8746
  const execEnv = mergeExecEnv(env);
8658
- execFileSync8("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
8747
+ execFileSync10("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
8659
8748
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5206\u652F\u5DF2\u8865\u63A8\u9001: ${branch}`);
8660
8749
  const listRes = await fetch(
8661
8750
  `${baseUrl}/api/v4/projects/${projectPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened`,
@@ -8722,7 +8811,7 @@ var DaemonRunner = class {
8722
8811
  await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
8723
8812
  return;
8724
8813
  }
8725
- const managedRoot = join16(cfg.workdir, "worktrees", this.tenantId);
8814
+ const managedRoot = join18(cfg.workdir, "worktrees", this.tenantId);
8726
8815
  if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
8727
8816
  await this.api.submitCleanupResult(item.id, this.runtimeId, {
8728
8817
  ok: false,
@@ -8770,38 +8859,38 @@ function isDebugLogEnabled() {
8770
8859
  var spaceTokenFailureLogged = false;
8771
8860
  function writeCustomSkills(targetDir, skills, log) {
8772
8861
  const NAME_RE = /^[\w-]{1,64}$/;
8773
- const base = join16(targetDir, ".agents", "skills");
8862
+ const base = join18(targetDir, ".agents", "skills");
8774
8863
  for (const skill of skills) {
8775
8864
  const name = skill.name.trim();
8776
8865
  if (!NAME_RE.test(name)) {
8777
8866
  log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
8778
8867
  continue;
8779
8868
  }
8780
- const dir = join16(base, name);
8781
- if (dirname6(dir) !== base) {
8869
+ const dir = join18(base, name);
8870
+ if (dirname7(dir) !== base) {
8782
8871
  log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
8783
8872
  continue;
8784
8873
  }
8785
- mkdirSync9(dir, { recursive: true });
8874
+ mkdirSync11(dir, { recursive: true });
8786
8875
  const meta = ["---", `name: ${name}`];
8787
8876
  if (skill.description?.trim()) {
8788
8877
  meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
8789
8878
  }
8790
8879
  meta.push("---", "");
8791
- writeFileSync8(join16(dir, "SKILL.md"), `${meta.join("\n")}
8880
+ writeFileSync9(join18(dir, "SKILL.md"), `${meta.join("\n")}
8792
8881
  ${skill.content ?? ""}`, "utf8");
8793
8882
  }
8794
8883
  }
8795
8884
  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 });
8885
+ const dir = join18(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
8886
+ const refsDir = join18(dir, "references");
8887
+ mkdirSync11(refsDir, { recursive: true });
8799
8888
  const isGitlab = provider === "gitlab" && !!repoBaseUrl;
8800
8889
  const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
8801
8890
  const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
8802
- writeFileSync8(join16(dir, "SKILL.md"), `${front}
8891
+ writeFileSync9(join18(dir, "SKILL.md"), `${front}
8803
8892
  ${body}`, "utf8");
8804
- writeFileSync8(join16(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
8893
+ writeFileSync9(join18(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
8805
8894
  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
8895
  }
8807
8896
  function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
@@ -8832,8 +8921,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
8832
8921
  };
8833
8922
  }
8834
8923
  function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
8835
- const briefDir = join16(targetDir, ".agent_context");
8836
- mkdirSync9(briefDir, { recursive: true });
8924
+ const briefDir = join18(targetDir, ".agent_context");
8925
+ mkdirSync11(briefDir, { recursive: true });
8837
8926
  const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
8838
8927
 
8839
8928
  - \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
@@ -8842,7 +8931,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
8842
8931
  ---
8843
8932
 
8844
8933
  ${buildTaskPromptWithContract(detail, void 0)}`;
8845
- writeFileSync8(join16(briefDir, "task-context.md"), brief, "utf8");
8934
+ writeFileSync9(join18(briefDir, "task-context.md"), brief, "utf8");
8846
8935
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
8847
8936
  }
8848
8937
  var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
@@ -8856,15 +8945,15 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
8856
8945
  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
8946
  content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
8858
8947
  }
8859
- mkdirSync9(targetDir, { recursive: true });
8860
- writeFileSync8(join16(targetDir, "AGENTS.md"), `${content}
8948
+ mkdirSync11(targetDir, { recursive: true });
8949
+ writeFileSync9(join18(targetDir, "AGENTS.md"), `${content}
8861
8950
  `, "utf8");
8862
8951
  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
8952
  return true;
8864
8953
  }
8865
8954
  function gitHead(repoDir) {
8866
8955
  try {
8867
- return execFileSync8("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
8956
+ return execFileSync10("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
8868
8957
  } catch {
8869
8958
  return null;
8870
8959
  }
@@ -8955,7 +9044,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
8955
9044
  }
8956
9045
  async function reportOneRepoTarget(taskId, target, env, ops) {
8957
9046
  const execEnv = mergeExecEnv(env);
8958
- if (!existsSync13(target.dir) || !existsSync13(join16(target.dir, ".git"))) {
9047
+ if (!existsSync15(target.dir) || !existsSync15(join18(target.dir, ".git"))) {
8959
9048
  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
9049
  return;
8961
9050
  }
@@ -8970,7 +9059,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
8970
9059
  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
9060
  return;
8972
9061
  }
8973
- const headSha = execFileSync8("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
9062
+ const headSha = execFileSync10("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
8974
9063
  const headSubject = headCommitSubject(target.dir, execEnv);
8975
9064
  const probePushState = ops.pushState ?? resolveRemotePushState;
8976
9065
  let dirty = hasLocalChanges(target.dir, execEnv);
@@ -9010,7 +9099,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
9010
9099
  }
9011
9100
  }
9012
9101
  function execGit(repoPath, args, env) {
9013
- return execFileSync8("git", ["-C", repoPath, ...args], {
9102
+ return execFileSync10("git", ["-C", repoPath, ...args], {
9014
9103
  encoding: "utf8",
9015
9104
  stdio: "pipe",
9016
9105
  env,
@@ -9051,7 +9140,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9051
9140
  if (!taskCode)
9052
9141
  return;
9053
9142
  const execEnv = mergeExecEnv(env);
9054
- const branch = execFileSync8("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
9143
+ const branch = execFileSync10("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
9055
9144
  if (!branch || branch === "main" || branch === "master")
9056
9145
  return;
9057
9146
  if (env.MORULA_GITLAB_URL && env.MORULA_GITLAB_TOKEN) {
@@ -9060,7 +9149,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9060
9149
  }
9061
9150
  let prList;
9062
9151
  try {
9063
- prList = execFileSync8("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
9152
+ prList = execFileSync10("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
9064
9153
  cwd: repoDir,
9065
9154
  env: execEnv,
9066
9155
  encoding: "utf-8"
@@ -9080,7 +9169,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
9080
9169
  const newBody = `${pr.body ?? ""}
9081
9170
 
9082
9171
  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}`], {
9172
+ execFileSync10("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
9084
9173
  cwd: repoDir,
9085
9174
  env: execEnv,
9086
9175
  encoding: "utf-8"
@@ -9123,12 +9212,12 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
9123
9212
 
9124
9213
  // src/commands/doctor.ts
9125
9214
  init_dist2();
9215
+ import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
9216
+ import { homedir as homedir11 } from "node:os";
9217
+ import { dirname as dirname8, join as join19 } from "node:path";
9126
9218
  init_daemon_config();
9127
9219
  init_daemon_lock();
9128
9220
  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
9221
  async function doctorCommand(context) {
9133
9222
  const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
9134
9223
  const { facts, checks } = await collectDoctor(context, probe);
@@ -9145,16 +9234,16 @@ async function doctorCommand(context) {
9145
9234
  async function collectDoctor(context, probe) {
9146
9235
  const checks = [];
9147
9236
  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");
9237
+ const configDir = env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli");
9238
+ const configPath = env.MORULA_CONFIG ?? join19(configDir, "config.yaml");
9239
+ const daemonPath = join19(dirname8(configPath), "daemon");
9240
+ const logFile = join19(daemonPath, "daemon.log");
9152
9241
  const contextName = context.runtime.context ?? null;
9153
9242
  const baseUrl = context.runtime.baseUrl ?? null;
9154
9243
  const tokenPresent = Boolean(context.runtime.token);
9155
9244
  const tokenSource = context.runtime.tokenSource ?? null;
9156
9245
  let configReadable = false;
9157
- if (existsSync14(configPath)) {
9246
+ if (existsSync16(configPath)) {
9158
9247
  try {
9159
9248
  readFileSync10(configPath, "utf8");
9160
9249
  configReadable = true;
@@ -9162,10 +9251,10 @@ async function collectDoctor(context, probe) {
9162
9251
  configReadable = false;
9163
9252
  }
9164
9253
  }
9165
- const lockPath = join17(daemonPath, DAEMON_LOCK_FILE);
9254
+ const lockPath = join19(daemonPath, DAEMON_LOCK_FILE);
9166
9255
  const lock = probeDaemonLock(lockPath);
9167
- const pidFile = join17(daemonPath, "daemon.pid");
9168
- const filePid = existsSync14(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
9256
+ const pidFile = join19(daemonPath, "daemon.pid");
9257
+ const filePid = existsSync16(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
9169
9258
  const lockPid = lock.state === "active" ? lock.record.pid : 0;
9170
9259
  const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
9171
9260
  const cfg = loadDaemonConfig(context.store);
@@ -9200,7 +9289,8 @@ async function collectDoctor(context, probe) {
9200
9289
  logFile,
9201
9290
  dimPath,
9202
9291
  dimFound,
9203
- envOverrides
9292
+ envOverrides,
9293
+ daemonAutostart: process.platform === "darwin" ? daemonAutostartEnabled() : null
9204
9294
  };
9205
9295
  checks.push(lock.state === "stale" ? {
9206
9296
  id: "daemon.lock",
@@ -9226,7 +9316,7 @@ async function collectDoctor(context, probe) {
9226
9316
  hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
9227
9317
  });
9228
9318
  }
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" });
9319
+ 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
9320
  if (!dimFound) {
9231
9321
  checks.push({
9232
9322
  id: "dim.present",
@@ -9247,6 +9337,18 @@ async function collectDoctor(context, probe) {
9247
9337
  } else {
9248
9338
  checks.push({ id: "daemon.running", status: "ok", message: `daemon \u8FD0\u884C\u4E2D\uFF08pid=${daemonPid}\uFF09` });
9249
9339
  }
9340
+ if (facts.daemonAutostart !== null) {
9341
+ if (facts.daemonAutostart) {
9342
+ checks.push({
9343
+ id: "daemon.autostart",
9344
+ status: "ok",
9345
+ message: `daemon \u5F00\u673A\u81EA\u542F\uFF1Aon\uFF08${daemonAutostartPlistPath()}\uFF09`,
9346
+ ...facts.daemonRunning ? {} : { hint: `tail -n 50 ${logFile}\uFF08\u81EA\u542F\u5B9E\u4F8B\u53EF\u80FD\u6CE8\u518C\u5931\u8D25\u540E\u5DF2\u9000\u51FA\uFF09` }
9347
+ });
9348
+ } else {
9349
+ checks.push({ id: "daemon.autostart", status: "ok", message: "daemon \u5F00\u673A\u81EA\u542F\uFF1Aoff" });
9350
+ }
9351
+ }
9250
9352
  if (envOverrides.includes("MORULA_CONFIG_DIR") || envOverrides.includes("MORULA_CONFIG")) {
9251
9353
  checks.push({
9252
9354
  id: "config.override",
@@ -9322,10 +9424,10 @@ function detectInstallMode() {
9322
9424
 
9323
9425
  // src/commands/daemon.ts
9324
9426
  function configFilePath(env) {
9325
- return env.MORULA_CONFIG ?? join19(env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli"), "config.yaml");
9427
+ return env.MORULA_CONFIG ?? join20(env.MORULA_CONFIG_DIR ?? join20(homedir12(), ".morula-cli"), "config.yaml");
9326
9428
  }
9327
9429
  function daemonDir(configPath) {
9328
- return join19(dirname8(configPath), "daemon");
9430
+ return join20(dirname9(configPath), "daemon");
9329
9431
  }
9330
9432
  var PID_FILE = "daemon.pid";
9331
9433
  var LOG_FILE = "daemon.log";
@@ -9343,9 +9445,11 @@ async function daemonCommand(context) {
9343
9445
  return models(context);
9344
9446
  if (action === "portal")
9345
9447
  return portal(context);
9448
+ if (action === "autostart")
9449
+ return autostart(context);
9346
9450
  if (action === "doctor")
9347
9451
  return doctorCommand(context);
9348
- throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|doctor>", {
9452
+ throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|autostart|doctor>", {
9349
9453
  hint: "morula-runtime daemon --help"
9350
9454
  });
9351
9455
  }
@@ -9356,29 +9460,29 @@ async function portal(context) {
9356
9460
  const { PortalServer: PortalServer2 } = await Promise.resolve().then(() => (init_portal_server(), portal_server_exports));
9357
9461
  const { createPortalEnvironmentResolver: createPortalEnvironmentResolver2 } = await Promise.resolve().then(() => (init_portal_instances(), portal_instances_exports));
9358
9462
  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");
9463
+ const autostart2 = flagString(context.parsed.flags, "autostart");
9360
9464
  const dryRun = isDryRun(context);
9361
- if (autostart === "on" || autostart === "off") {
9465
+ if (autostart2 === "on" || autostart2 === "off") {
9362
9466
  const { setupAutostart: setupAutostart2, removeAutostart: removeAutostart2 } = await Promise.resolve().then(() => (init_portal_autostart(), portal_autostart_exports));
9363
9467
  if (dryRun) {
9364
9468
  return {
9365
9469
  ok: true,
9366
9470
  dryRun: true,
9367
- autostart,
9471
+ autostart: autostart2,
9368
9472
  planned: {
9369
- action: autostart === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
9473
+ action: autostart2 === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
9370
9474
  note: "no file was written"
9371
9475
  }
9372
9476
  };
9373
9477
  }
9374
- if (autostart === "on") {
9478
+ if (autostart2 === "on") {
9375
9479
  const installed = await setupAutostart2(context);
9376
9480
  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
9481
  } else {
9378
9482
  await removeAutostart2(context);
9379
9483
  context.err("[portal] \u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F");
9380
9484
  }
9381
- return { ok: true, autostart };
9485
+ return { ok: true, autostart: autostart2 };
9382
9486
  }
9383
9487
  if (dryRun) {
9384
9488
  return {
@@ -9425,6 +9529,75 @@ async function portal(context) {
9425
9529
  });
9426
9530
  return { running: true };
9427
9531
  }
9532
+ async function autostart(context) {
9533
+ rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
9534
+ const state = context.parsed.positionals[0];
9535
+ if (state !== "on" && state !== "off") {
9536
+ throw usage("usage: morula-runtime daemon autostart <on|off>", {
9537
+ hint: "morula-runtime daemon autostart --help"
9538
+ });
9539
+ }
9540
+ const dryRun = isDryRun(context);
9541
+ if (process.platform !== "darwin") {
9542
+ context.err("[autostart] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF08\u4EC5 macOS LaunchAgent\uFF09");
9543
+ return { ok: true, autostart: state, supported: false };
9544
+ }
9545
+ const envOverridden = ["MORULA_ENV", "MORULA_CONFIG", "MORULA_CONFIG_DIR", "MORULA_API_URL", "MORULA_TOKEN", "MORULA_TASK_TOKEN"].filter((name) => Boolean(context.env[name]));
9546
+ const flagOverridden = ["env", "url", "token"].filter((name) => context.parsed.flags[name] !== void 0);
9547
+ if (envOverridden.length || flagOverridden.length) {
9548
+ throw new CliError(
9549
+ "USAGE",
9550
+ EXIT.USAGE,
9551
+ `\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")}`,
9552
+ false,
9553
+ { 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" }
9554
+ );
9555
+ }
9556
+ let identityError = null;
9557
+ if (dryRun) {
9558
+ try {
9559
+ resolveDaemonIdentity(context, { quiet: true });
9560
+ } catch (error) {
9561
+ identityError = error instanceof Error ? error.message : String(error);
9562
+ }
9563
+ } else {
9564
+ resolveDaemonIdentity(context, { quiet: true });
9565
+ }
9566
+ if (state === "on") {
9567
+ const plan = planDaemonAutostart();
9568
+ if (dryRun) {
9569
+ return {
9570
+ ok: true,
9571
+ dryRun: true,
9572
+ autostart: "on",
9573
+ planned: {
9574
+ action: "register LaunchAgent (daemon boot autostart)",
9575
+ ...plan,
9576
+ note: "no file was written"
9577
+ },
9578
+ ...identityError ? { blockedBy: { code: "NOT_LOGGED_IN", message: identityError, hint: "morula-runtime auth login" } } : {}
9579
+ };
9580
+ }
9581
+ const installed = await setupDaemonAutostart((line) => context.err(line));
9582
+ 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");
9583
+ return { ok: true, autostart: "on", installed };
9584
+ }
9585
+ if (dryRun) {
9586
+ return {
9587
+ ok: true,
9588
+ dryRun: true,
9589
+ autostart: "off",
9590
+ planned: {
9591
+ action: "remove LaunchAgent (daemon boot autostart)",
9592
+ plistPath: daemonAutostartPlistPath(),
9593
+ note: "no file was written"
9594
+ }
9595
+ };
9596
+ }
9597
+ await removeDaemonAutostart((line) => context.err(line));
9598
+ context.err("[autostart] \u5DF2\u79FB\u9664 daemon \u5F00\u673A\u81EA\u542F\uFF08\u6B63\u5728\u8FD0\u884C\u7684 daemon \u4E0D\u53D7\u5F71\u54CD\uFF09");
9599
+ return { ok: true, autostart: "off" };
9600
+ }
9428
9601
  async function models(context) {
9429
9602
  rejectUnknownFlags(context.parsed.flags, ["provider"]);
9430
9603
  const { listDimModels: listDimModels2, DIM_DEFAULT_PROVIDER: DIM_DEFAULT_PROVIDER2 } = await Promise.resolve().then(() => (init_dim_model(), dim_model_exports));
@@ -9448,11 +9621,15 @@ async function run2(context) {
9448
9621
  saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
9449
9622
  }
9450
9623
  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);
9624
+ mkdirSync12(resolvedWorkdir, { recursive: true });
9625
+ const lockPath = join20(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
9453
9626
  const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
9454
9627
  if (!acquired.ok) {
9455
9628
  const pid = acquired.existing?.pid;
9629
+ if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
9630
+ context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
9631
+ return { running: false, yieldedTo: pid ?? null };
9632
+ }
9456
9633
  throw new CliError(
9457
9634
  "ALREADY_RUNNING",
9458
9635
  EXIT.CONFLICT,
@@ -9487,11 +9664,21 @@ async function run2(context) {
9487
9664
  context.err(`[daemon] morula-runtime daemon \u524D\u53F0\u8FD0\u884C\u4E2D\uFF08Ctrl+C \u9000\u51FA\uFF09`);
9488
9665
  try {
9489
9666
  await runner.start();
9667
+ } catch (error) {
9668
+ if (isAuthRejected(error)) {
9669
+ context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
9670
+ context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
9671
+ return { running: false, stoppedReason: "auth" };
9672
+ }
9673
+ throw error;
9490
9674
  } finally {
9491
9675
  releaseLock();
9492
9676
  }
9493
9677
  return { running: false };
9494
9678
  }
9679
+ function isAuthRejected(error) {
9680
+ return error instanceof MorulaApiError && (error.code === "UNAUTHENTICATED" || error.status === 401);
9681
+ }
9495
9682
  async function start(context) {
9496
9683
  rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent", "dry-run"]);
9497
9684
  const name = flagString(context.parsed.flags, "name") ?? void 0;
@@ -9509,10 +9696,10 @@ async function start(context) {
9509
9696
  }
9510
9697
  const dir = daemonDir(configFilePath(context.env));
9511
9698
  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);
9699
+ mkdirSync12(dir, { recursive: true, mode: 448 });
9700
+ const pidFile = join20(dir, PID_FILE);
9701
+ const logFile = join20(dir, LOG_FILE);
9702
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9516
9703
  const lock = probeDaemonLock(lockPath);
9517
9704
  if (lock.state === "active") {
9518
9705
  throw new CliError(
@@ -9523,7 +9710,7 @@ async function start(context) {
9523
9710
  { hint: "morula-runtime daemon status" }
9524
9711
  );
9525
9712
  }
9526
- if (existsSync16(pidFile)) {
9713
+ if (existsSync17(pidFile)) {
9527
9714
  const pid = Number(readFileSync11(pidFile, "utf8").trim());
9528
9715
  if (isProcessAlive(pid)) {
9529
9716
  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 +9718,7 @@ async function start(context) {
9531
9718
  });
9532
9719
  }
9533
9720
  if (!dryRun)
9534
- rmSync7(pidFile, { force: true });
9721
+ rmSync8(pidFile, { force: true });
9535
9722
  }
9536
9723
  const env = {
9537
9724
  ...process.env,
@@ -9569,12 +9756,12 @@ async function start(context) {
9569
9756
  const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
9570
9757
  const result = spawnDaemonProcess2({ logFile, env });
9571
9758
  if (result.error || result.pid <= 0) {
9572
- rmSync7(pidFile, { force: true });
9759
+ rmSync8(pidFile, { force: true });
9573
9760
  throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
9574
9761
  hint: `tail -n 50 ${logFile}`
9575
9762
  });
9576
9763
  }
9577
- writeFileSync9(pidFile, String(result.pid), { mode: 384 });
9764
+ writeFileSync10(pidFile, String(result.pid), { mode: 384 });
9578
9765
  context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
9579
9766
  return { running: true, pid: result.pid, logFile, lockFile: lockPath };
9580
9767
  }
@@ -9582,11 +9769,11 @@ async function stop(context) {
9582
9769
  rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
9583
9770
  const dryRun = isDryRun(context);
9584
9771
  const dir = daemonDir(configFilePath(context.env));
9585
- const pidFile = join19(dir, PID_FILE);
9586
- const lockPath = join19(dir, DAEMON_LOCK_FILE);
9772
+ const pidFile = join20(dir, PID_FILE);
9773
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9587
9774
  const lock = probeDaemonLock(lockPath);
9588
9775
  const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
9589
- const filePid = existsSync16(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
9776
+ const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
9590
9777
  const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
9591
9778
  if (dryRun) {
9592
9779
  return {
@@ -9596,7 +9783,7 @@ async function stop(context) {
9596
9783
  };
9597
9784
  }
9598
9785
  if (!pid) {
9599
- rmSync7(pidFile, { force: true });
9786
+ rmSync8(pidFile, { force: true });
9600
9787
  releaseDaemonLock(lockPath);
9601
9788
  return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
9602
9789
  }
@@ -9605,7 +9792,7 @@ async function stop(context) {
9605
9792
  releaseDaemonLock(lockPath);
9606
9793
  else
9607
9794
  releaseDaemonLock(lockPath, pid);
9608
- rmSync7(pidFile, { force: true });
9795
+ rmSync8(pidFile, { force: true });
9609
9796
  if (result.outcome === "killed")
9610
9797
  context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
9611
9798
  return { running: false, stoppedPid: pid, outcome: result.outcome };
@@ -9614,11 +9801,11 @@ async function status2(context) {
9614
9801
  rejectUnknownFlags(context.parsed.flags, []);
9615
9802
  const cfg = loadDaemonConfig(context.store);
9616
9803
  const dir = daemonDir(configFilePath(context.env));
9617
- const pidFile = join19(dir, PID_FILE);
9618
- const lockPath = join19(dir, DAEMON_LOCK_FILE);
9804
+ const pidFile = join20(dir, PID_FILE);
9805
+ const lockPath = join20(dir, DAEMON_LOCK_FILE);
9619
9806
  const lock = probeDaemonLock(lockPath);
9620
9807
  const lockPid = lock.state === "active" ? lock.record.pid : null;
9621
- const filePid = existsSync16(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
9808
+ const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
9622
9809
  const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
9623
9810
  return {
9624
9811
  running: pid !== null,
@@ -9628,7 +9815,7 @@ async function status2(context) {
9628
9815
  name: cfg?.name ?? null,
9629
9816
  workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
9630
9817
  maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
9631
- logFile: join19(dir, LOG_FILE),
9818
+ logFile: join20(dir, LOG_FILE),
9632
9819
  lockFile: lockPath
9633
9820
  };
9634
9821
  }
@@ -9658,32 +9845,32 @@ function parseMaxConcurrent(context) {
9658
9845
  init_dist2();
9659
9846
 
9660
9847
  // 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";
9848
+ 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";
9849
+ import { homedir as homedir13 } from "node:os";
9850
+ import { dirname as dirname10, join as join21 } from "node:path";
9664
9851
  import { fileURLToPath as fileURLToPath2 } from "node:url";
9665
9852
  var PLUGIN_NAME = "morula-cli";
9666
9853
  var MANIFEST_DIR_NAME = ".codex-plugin";
9667
- function legacyAgentsPluginDir(home = homedir12()) {
9668
- return join20(home, ".agents", "plugins", PLUGIN_NAME);
9854
+ function legacyAgentsPluginDir(home = homedir13()) {
9855
+ return join21(home, ".agents", "plugins", PLUGIN_NAME);
9669
9856
  }
9670
- function resolveDimcodeHome(env = process.env, home = homedir12()) {
9857
+ function resolveDimcodeHome(env = process.env, home = homedir13()) {
9671
9858
  const fromEnv = env.DIMCODE_HOME?.trim();
9672
- return fromEnv || join20(home, ".dimcode", "v2");
9859
+ return fromEnv || join21(home, ".dimcode", "v2");
9673
9860
  }
9674
- function defaultPluginsRoot(env = process.env, home = homedir12()) {
9675
- return join20(resolveDimcodeHome(env, home), "plugins");
9861
+ function defaultPluginsRoot(env = process.env, home = homedir13()) {
9862
+ return join21(resolveDimcodeHome(env, home), "plugins");
9676
9863
  }
9677
9864
  function resolvePackagePaths(entryUrl) {
9678
- const distDir = dirname9(fileURLToPath2(entryUrl));
9865
+ const distDir = dirname10(fileURLToPath2(entryUrl));
9679
9866
  return {
9680
- packageRoot: dirname9(distDir),
9867
+ packageRoot: dirname10(distDir),
9681
9868
  distDir,
9682
- assetsDir: join20(distDir, "plugin")
9869
+ assetsDir: join21(distDir, "plugin")
9683
9870
  };
9684
9871
  }
9685
9872
  function buildLauncherScript(opts) {
9686
- const entry = join20(opts.packageRoot, "dist", "morula.js");
9873
+ const entry = join21(opts.packageRoot, "dist", "morula.js");
9687
9874
  return `#!/bin/sh
9688
9875
  # 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
9876
  # \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 +9884,15 @@ exec "$NODE" ${JSON.stringify(entry)} "$@"
9697
9884
  `;
9698
9885
  }
9699
9886
  function assetsReady(paths) {
9700
- return existsSync17(join20(paths.assetsDir, "plugin.json")) && existsSync17(join20(paths.assetsDir, "skills"));
9887
+ return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
9701
9888
  }
9702
9889
  function listSkillNames(assetsDir) {
9703
- const dir = join20(assetsDir, "skills");
9704
- if (!existsSync17(dir))
9890
+ const dir = join21(assetsDir, "skills");
9891
+ if (!existsSync18(dir))
9705
9892
  return [];
9706
9893
  return readdirSync5(dir).filter((name) => {
9707
9894
  try {
9708
- return statSync4(join20(dir, name)).isDirectory();
9895
+ return statSync4(join21(dir, name)).isDirectory();
9709
9896
  } catch {
9710
9897
  return false;
9711
9898
  }
@@ -9714,24 +9901,24 @@ function listSkillNames(assetsDir) {
9714
9901
  function registerPlugin(opts) {
9715
9902
  const { pluginsRoot, paths, version } = opts;
9716
9903
  const nodePath = opts.nodePath ?? process.execPath;
9717
- const pluginDir = join20(pluginsRoot, PLUGIN_NAME);
9904
+ const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
9718
9905
  if (!assetsReady(paths))
9719
9906
  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");
9907
+ const skillsSrc = join21(paths.assetsDir, "skills");
9721
9908
  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)}
9909
+ const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
9910
+ mkdirSync13(manifestDir, { recursive: true });
9911
+ const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
9912
+ writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
9726
9913
  `);
9727
- const skillsDest = join20(pluginDir, "skills");
9728
- rmSync8(skillsDest, { recursive: true, force: true });
9729
- mkdirSync12(skillsDest, { recursive: true });
9914
+ const skillsDest = join21(pluginDir, "skills");
9915
+ rmSync9(skillsDest, { recursive: true, force: true });
9916
+ mkdirSync13(skillsDest, { recursive: true });
9730
9917
  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 }));
9918
+ const binDir = join21(pluginDir, "bin");
9919
+ mkdirSync13(binDir, { recursive: true });
9920
+ const binPath = join21(binDir, "morula");
9921
+ writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
9735
9922
  chmodSync3(binPath, 493);
9736
9923
  const legacy = legacyAgentsPluginDir(opts.home);
9737
9924
  return {
@@ -9740,7 +9927,7 @@ function registerPlugin(opts) {
9740
9927
  skills,
9741
9928
  version,
9742
9929
  node: nodePath,
9743
- legacyDir: existsSync17(join20(legacy, MANIFEST_DIR_NAME)) ? legacy : null
9930
+ legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
9744
9931
  };
9745
9932
  }
9746
9933
 
@@ -9917,6 +10104,21 @@ var SPEC = {
9917
10104
  idempotent: true,
9918
10105
  emits: ["running", "autostart", "ok"]
9919
10106
  },
10107
+ {
10108
+ action: "autostart",
10109
+ summary: "register/remove the macOS LaunchAgent so the daemon starts at login and is relaunched by launchd on abnormal exit (unattended executor machines)",
10110
+ args: [
10111
+ { 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)" }
10112
+ ],
10113
+ flags: [
10114
+ { name: "dry-run", type: "boolean", description: "print the LaunchAgent plan without writing anything (read-only)" }
10115
+ ],
10116
+ examples: ["morula-runtime daemon autostart on", "morula-runtime daemon autostart off"],
10117
+ writes: "local",
10118
+ interactive: false,
10119
+ idempotent: true,
10120
+ emits: ["ok", "autostart", "installed", "dryRun", "planned", "blockedBy"]
10121
+ },
9920
10122
  {
9921
10123
  action: "doctor",
9922
10124
  summary: "read-only diagnostics: node/cli/config/auth/daemon/dim facts plus checks[] with hints (offline by default; --probe goes online)",