@arcships/morula-runtime 0.1.0-alpha.10 → 0.1.0-alpha.11

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
@@ -740,7 +740,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
740
740
  var init_build_env = __esm({
741
741
  "../morula-cli-core/dist/build-env.js"() {
742
742
  "use strict";
743
- BUILD_ID = "20260922054915.d5e2cc87";
743
+ BUILD_ID = "20260922130415.e49a4856";
744
744
  BUILD_CONTEXTS = {
745
745
  "local": "http://localhost:8080/api",
746
746
  "test": "https://morula.basecastle.com/api",
@@ -951,24 +951,6 @@ var init_ci = __esm({
951
951
  testReport(runId) {
952
952
  return this.client.get(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/test-report`);
953
953
  }
954
- /**
955
- * 重跑整条流水线的失败 job(Phase 3;开放面 = 人 + 管家)。
956
- *
957
- * 只允许 failure / cancelled 的运行,其余状态服务端回 `CI_RETRY_NOT_ALLOWED`(不是 4xx)。
958
- * **非幂等**:每次调用都会在平台上产生一次新的重跑,重复调用不会去重。
959
- */
960
- retryRun(runId) {
961
- return this.client.post(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/retry`);
962
- }
963
- /**
964
- * 重跑单个 job(Phase 3;开放面 = 人 + 管家)。
965
- *
966
- * 服务端按 jobId 反查所属运行(`CI_JOB_NOT_FOUND` 覆盖「job 不存在」与「反查不到运行」两种情况);
967
- * 状态门禁与 `retryRun` 相同。非幂等。
968
- */
969
- retryJob(jobId) {
970
- return this.client.post(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/retry`);
971
- }
972
954
  };
973
955
  }
974
956
  });
@@ -3481,6 +3463,256 @@ var init_dim_locate = __esm({
3481
3463
  }
3482
3464
  });
3483
3465
 
3466
+ // src/daemon/acp/acp-process-registry.ts
3467
+ import { execFileSync as execFileSync4 } from "node:child_process";
3468
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
3469
+ import { dirname as dirname6 } from "node:path";
3470
+ function parseLstartToMs(tokens) {
3471
+ if (tokens.length < 5)
3472
+ return null;
3473
+ const [, month, day, time, year] = tokens;
3474
+ const ms = Date.parse(`${month} ${day} ${year} ${time}`);
3475
+ return Number.isFinite(ms) ? ms : null;
3476
+ }
3477
+ function parseDimAcpPsLine(line) {
3478
+ const tokens = line.trim().split(/\s+/);
3479
+ if (tokens.length < 8)
3480
+ return null;
3481
+ const pid = Number(tokens[0]);
3482
+ const ppid = Number(tokens[1]);
3483
+ if (!Number.isInteger(pid) || pid <= 0 || !Number.isInteger(ppid))
3484
+ return null;
3485
+ const lstartTokens = tokens.slice(2, 7);
3486
+ const args = tokens.slice(7).join(" ");
3487
+ if (!DIM_ACP_ARGS_RE.test(args.trim()))
3488
+ return null;
3489
+ return { pid, ppid, startedAtMs: parseLstartToMs(lstartTokens), args };
3490
+ }
3491
+ function listDimAcpProcesses() {
3492
+ if (process.platform === "win32")
3493
+ return [];
3494
+ try {
3495
+ const out = execFileSync4("ps", ["-Ao", "pid=,ppid=,lstart=,args="], {
3496
+ encoding: "utf8",
3497
+ stdio: "pipe",
3498
+ timeout: 5e3
3499
+ });
3500
+ const result = [];
3501
+ for (const line of out.split("\n")) {
3502
+ const info = parseDimAcpPsLine(line);
3503
+ if (info)
3504
+ result.push(info);
3505
+ }
3506
+ return result;
3507
+ } catch {
3508
+ return [];
3509
+ }
3510
+ }
3511
+ function isPidAlive(pid) {
3512
+ try {
3513
+ process.kill(pid, 0);
3514
+ return true;
3515
+ } catch (error) {
3516
+ return error.code === "EPERM";
3517
+ }
3518
+ }
3519
+ async function waitForPidExit(pid, timeoutMs, wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), isAlive = isPidAlive) {
3520
+ const deadline = Date.now() + Math.max(0, timeoutMs);
3521
+ while (isAlive(pid)) {
3522
+ if (Date.now() >= deadline)
3523
+ return false;
3524
+ await wait(Math.min(EXIT_POLL_MS, deadline - Date.now()));
3525
+ }
3526
+ return true;
3527
+ }
3528
+ function killDimAcpProcessTree(pid) {
3529
+ if (process.platform === "win32") {
3530
+ try {
3531
+ execFileSync4("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
3532
+ return { ok: true };
3533
+ } catch (error) {
3534
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
3535
+ }
3536
+ }
3537
+ try {
3538
+ process.kill(-pid, "SIGKILL");
3539
+ return { ok: true };
3540
+ } catch (error) {
3541
+ const code = error.code;
3542
+ if (code === "EPERM")
3543
+ return { ok: false, error: "EPERM\uFF08\u65E0\u6743\u9650\uFF09" };
3544
+ return { ok: true };
3545
+ }
3546
+ }
3547
+ async function ensureDimAcpTerminated(pid, options = {}) {
3548
+ const log = options.log ?? (() => {
3549
+ });
3550
+ const tag = options.tag ?? "";
3551
+ const deps = options.deps ?? {};
3552
+ const isAlive = deps.isAlive ?? isPidAlive;
3553
+ const killGroup = deps.killGroup ?? killDimAcpProcessTree;
3554
+ const wait = deps.wait ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
3555
+ const suffix = tag ? `\uFF08${tag}\uFF09` : "";
3556
+ if (!isAlive(pid)) {
3557
+ log(`[acp] dim acp \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF08pid=${pid}\uFF09${suffix}`);
3558
+ return true;
3559
+ }
3560
+ if ((options.graceMs ?? TERMINATE_GRACE_MS) > 0) {
3561
+ if (await waitForPidExit(pid, options.graceMs ?? TERMINATE_GRACE_MS, wait, isAlive)) {
3562
+ log(`[acp] dim acp \u8FDB\u7A0B\u5DF2\u56DE\u6536\uFF08pid=${pid}\uFF0CSIGTERM \u4F18\u96C5\u9000\u51FA\uFF09${suffix}`);
3563
+ return true;
3564
+ }
3565
+ }
3566
+ const killed = killGroup(pid);
3567
+ if (!killed.ok) {
3568
+ log(`[acp] dim acp \u8FDB\u7A0B\u56DE\u6536\u5931\u8D25\uFF08pid=${pid}\uFF0CSIGKILL \u8FDB\u7A0B\u7EC4\u5931\u8D25\uFF1A${killed.error ?? "unknown"}\uFF09${suffix}`);
3569
+ return false;
3570
+ }
3571
+ if (await waitForPidExit(pid, options.killWaitMs ?? KILL_VERIFY_WAIT_MS, wait, isAlive)) {
3572
+ log(`[acp] dim acp \u8FDB\u7A0B\u5DF2\u56DE\u6536\uFF08pid=${pid}\uFF0CSIGTERM \u672A\u9000\u51FA\uFF0C\u5DF2 SIGKILL \u8FDB\u7A0B\u7EC4\uFF09${suffix}`);
3573
+ return true;
3574
+ }
3575
+ log(`[acp] dim acp \u8FDB\u7A0B\u56DE\u6536\u5931\u8D25\uFF08pid=${pid}\uFF0CSIGKILL \u8FDB\u7A0B\u7EC4\u540E ${options.killWaitMs ?? KILL_VERIFY_WAIT_MS}ms \u5185\u4ECD\u672A\u9000\u51FA\uFF0C\u9700\u4EBA\u5DE5\u68C0\u67E5\uFF09${suffix}`);
3576
+ return false;
3577
+ }
3578
+ function persistRecords() {
3579
+ if (!recordFile)
3580
+ return;
3581
+ try {
3582
+ mkdirSync5(dirname6(recordFile), { recursive: true });
3583
+ writeFileSync5(recordFile, JSON.stringify({
3584
+ v: RECORD_FILE_VERSION,
3585
+ children: [...ownedChildren.values()]
3586
+ }), "utf8");
3587
+ } catch {
3588
+ }
3589
+ }
3590
+ function enableDimAcpChildRecordFile(file) {
3591
+ recordFile = file;
3592
+ }
3593
+ function registerDimAcpChild(pid, startedAt) {
3594
+ ownedChildren.set(pid, { pid, startedAt });
3595
+ persistRecords();
3596
+ }
3597
+ function unregisterDimAcpChild(pid) {
3598
+ if (ownedChildren.delete(pid))
3599
+ persistRecords();
3600
+ }
3601
+ function ownedDimAcpChildPids() {
3602
+ return new Set(ownedChildren.keys());
3603
+ }
3604
+ function readDimAcpChildRecords(file) {
3605
+ try {
3606
+ if (!existsSync6(file))
3607
+ return [];
3608
+ const raw = JSON.parse(readFileSync4(file, "utf8"));
3609
+ if (raw?.v !== RECORD_FILE_VERSION || !Array.isArray(raw.children))
3610
+ return [];
3611
+ return raw.children.filter((c) => typeof c?.pid === "number" && typeof c?.startedAt === "number");
3612
+ } catch {
3613
+ return [];
3614
+ }
3615
+ }
3616
+ async function reapOrphanDimAcpChildren(options = {}) {
3617
+ const log = options.log ?? (() => {
3618
+ });
3619
+ const deps = options.deps ?? {};
3620
+ const list = deps.list ?? listDimAcpProcesses;
3621
+ const now = deps.now ?? Date.now;
3622
+ const minAgeMs = options.minAgeMs ?? SWEEP_MIN_AGE_MS;
3623
+ const owned = ownedDimAcpChildPids();
3624
+ let reaped = 0;
3625
+ let kept = 0;
3626
+ for (const info of list()) {
3627
+ if (info.ppid !== process.pid)
3628
+ continue;
3629
+ if (owned.has(info.pid)) {
3630
+ kept += 1;
3631
+ continue;
3632
+ }
3633
+ if (info.startedAtMs !== null && now() - info.startedAtMs < minAgeMs) {
3634
+ kept += 1;
3635
+ continue;
3636
+ }
3637
+ const terminated = await ensureDimAcpTerminated(info.pid, {
3638
+ graceMs: 0,
3639
+ log,
3640
+ tag: `\u5B64\u513F\u5DE1\u68C0\uFF0C\u542F\u52A8\u4E8E ${new Date(info.startedAtMs ?? 0).toISOString()}`,
3641
+ deps
3642
+ });
3643
+ if (terminated)
3644
+ reaped += 1;
3645
+ else
3646
+ kept += 1;
3647
+ }
3648
+ if (reaped > 0)
3649
+ log(`[acp] \u5B64\u513F\u5DE1\u68C0\uFF1A\u56DE\u6536 ${reaped} \u4E2A\u65E0\u5F52\u5C5E dim acp \u5B50\u8FDB\u7A0B\uFF08\u4FDD\u7559 ${kept} \u4E2A\u6709\u5F52\u5C5E/\u4FDD\u62A4\u7A97\u5185\uFF09`);
3650
+ return { reaped, kept };
3651
+ }
3652
+ async function reapRecordedOrphanDimAcpChildren(options) {
3653
+ const log = options.log ?? (() => {
3654
+ });
3655
+ const deps = options.deps ?? {};
3656
+ const list = deps.list ?? listDimAcpProcesses;
3657
+ const tolerance = options.startToleranceMs ?? START_MATCH_TOLERANCE_MS;
3658
+ const records = readDimAcpChildRecords(options.file);
3659
+ let reaped = 0;
3660
+ let kept = 0;
3661
+ if (records.length > 0) {
3662
+ const processes = list();
3663
+ for (const record of records) {
3664
+ const info = processes.find((p) => p.pid === record.pid);
3665
+ if (!info)
3666
+ continue;
3667
+ if (info.startedAtMs === null || Math.abs(info.startedAtMs - record.startedAt) > tolerance) {
3668
+ log(`[acp] \u542F\u52A8\u81EA\u6108\uFF1Apid=${record.pid} \u542F\u52A8\u65F6\u95F4\u4E0E\u8BB0\u5F55\u4E0D\u7B26\uFF08\u7591 pid \u590D\u7528\uFF09\uFF0C\u8DF3\u8FC7\u4E0D\u56DE\u6536`);
3669
+ kept += 1;
3670
+ continue;
3671
+ }
3672
+ if (info.ppid !== 1 && info.ppid !== process.pid) {
3673
+ log(`[acp] \u542F\u52A8\u81EA\u6108\uFF1Apid=${record.pid} \u4ECD\u6302\u5728\u5176\u5B83\u8FDB\u7A0B\uFF08ppid=${info.ppid}\uFF09\u540D\u4E0B\uFF0C\u4E0D\u5C5E\u4E8E\u672C daemon\uFF0C\u8DF3\u8FC7`);
3674
+ kept += 1;
3675
+ continue;
3676
+ }
3677
+ const terminated = await ensureDimAcpTerminated(record.pid, {
3678
+ graceMs: 0,
3679
+ log,
3680
+ tag: `\u542F\u52A8\u81EA\u6108\uFF0C\u4E0A\u4E00\u4EE3 daemon \u9057\u7559\uFF0C\u542F\u52A8\u4E8E ${new Date(record.startedAt).toISOString()}`,
3681
+ deps
3682
+ });
3683
+ if (terminated) {
3684
+ reaped += 1;
3685
+ log(`[acp] \u542F\u52A8\u81EA\u6108\uFF1A\u5DF2\u56DE\u6536\u9057\u7559 dim acp \u5B64\u513F\uFF08pid=${record.pid}\uFF09`);
3686
+ } else {
3687
+ kept += 1;
3688
+ }
3689
+ }
3690
+ }
3691
+ if (recordFile === options.file)
3692
+ persistRecords();
3693
+ return { reaped, kept };
3694
+ }
3695
+ function registerSpawnedChild(child) {
3696
+ if (typeof child.pid === "number" && child.pid > 0)
3697
+ registerDimAcpChild(child.pid, Date.now());
3698
+ }
3699
+ var TERMINATE_GRACE_MS, KILL_VERIFY_WAIT_MS, ACP_SWEEP_INTERVAL_MS, SWEEP_MIN_AGE_MS, START_MATCH_TOLERANCE_MS, EXIT_POLL_MS, DIM_ACP_ARGS_RE, ownedChildren, recordFile, RECORD_FILE_VERSION;
3700
+ var init_acp_process_registry = __esm({
3701
+ "src/daemon/acp/acp-process-registry.ts"() {
3702
+ "use strict";
3703
+ TERMINATE_GRACE_MS = 5e3;
3704
+ KILL_VERIFY_WAIT_MS = 3e3;
3705
+ ACP_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
3706
+ SWEEP_MIN_AGE_MS = 6e4;
3707
+ START_MATCH_TOLERANCE_MS = 1e4;
3708
+ EXIT_POLL_MS = 100;
3709
+ DIM_ACP_ARGS_RE = /(^|\/)dim(\.exe)?\s+acp(\s|$)/;
3710
+ ownedChildren = /* @__PURE__ */ new Map();
3711
+ recordFile = null;
3712
+ RECORD_FILE_VERSION = 1;
3713
+ }
3714
+ });
3715
+
3484
3716
  // src/daemon/acp/acp-connection.ts
3485
3717
  var acp_connection_exports = {};
3486
3718
  __export(acp_connection_exports, {
@@ -3490,7 +3722,7 @@ __export(acp_connection_exports, {
3490
3722
  normalizeAcpConfigOptions: () => normalizeAcpConfigOptions,
3491
3723
  resolveAcpInitTimeoutMs: () => resolveAcpInitTimeoutMs
3492
3724
  });
3493
- import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
3725
+ import { execFileSync as execFileSync5, spawn as spawn3 } from "node:child_process";
3494
3726
  import { randomUUID as randomUUID2 } from "node:crypto";
3495
3727
  function resolveAcpInitTimeoutMs() {
3496
3728
  const raw = Number(process.env.MORULA_ACP_INIT_TIMEOUT_MS);
@@ -3500,7 +3732,7 @@ function resolveDimVersion(dimPath) {
3500
3732
  if (cachedDimVersion !== null)
3501
3733
  return cachedDimVersion;
3502
3734
  try {
3503
- cachedDimVersion = execFileSync4(dimPath, ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim().split("\n")[0] || "unknown";
3735
+ cachedDimVersion = execFileSync5(dimPath, ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim().split("\n")[0] || "unknown";
3504
3736
  } catch {
3505
3737
  cachedDimVersion = "unknown";
3506
3738
  }
@@ -3510,7 +3742,7 @@ function countDimAcpProcesses() {
3510
3742
  if (process.platform === "win32")
3511
3743
  return null;
3512
3744
  try {
3513
- const out = execFileSync4("ps", ["-Ao", "args"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
3745
+ const out = execFileSync5("ps", ["-Ao", "args"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
3514
3746
  return out.split("\n").filter((line) => /(^|\/)dim(\.exe)?\s+acp(\s|$)/.test(line.trim())).length;
3515
3747
  } catch {
3516
3748
  return null;
@@ -3531,6 +3763,7 @@ var init_acp_connection = __esm({
3531
3763
  "src/daemon/acp/acp-connection.ts"() {
3532
3764
  "use strict";
3533
3765
  init_dim_locate();
3766
+ init_acp_process_registry();
3534
3767
  DEFAULT_ACP_INIT_TIMEOUT_MS = 45e3;
3535
3768
  cachedDimVersion = null;
3536
3769
  requestSeq = 0;
@@ -3542,6 +3775,9 @@ var init_acp_connection = __esm({
3542
3775
  }));
3543
3776
  this.onExit = options.onExit;
3544
3777
  this.elicitationHandler = options.onElicitation;
3778
+ this.log = options.log ?? ((line) => process.stderr.write(`${line}
3779
+ `));
3780
+ this.killGraceMs = options.killGraceMs ?? TERMINATE_GRACE_MS;
3545
3781
  }
3546
3782
  options;
3547
3783
  child = null;
@@ -3551,6 +3787,8 @@ var init_acp_connection = __esm({
3551
3787
  permissionHandler;
3552
3788
  elicitationHandler;
3553
3789
  onExit;
3790
+ log;
3791
+ killGraceMs;
3554
3792
  /** sessionId → 当前 update 处理器(同一 session 单活跃 prompt) */
3555
3793
  updateHandlers = /* @__PURE__ */ new Map();
3556
3794
  /** 运行时切换权限处理器(每次 prompt 前按审批模式设置) */
@@ -3576,6 +3814,7 @@ var init_acp_connection = __esm({
3576
3814
  child.once("error", reject);
3577
3815
  child.once("spawn", resolve2);
3578
3816
  });
3817
+ registerSpawnedChild(child);
3579
3818
  return child;
3580
3819
  } catch (error) {
3581
3820
  lastError = error;
@@ -3583,9 +3822,9 @@ var init_acp_connection = __esm({
3583
3822
  process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
3584
3823
  `);
3585
3824
  try {
3586
- const { existsSync: existsSync18 } = await import("node:fs");
3825
+ const { existsSync: existsSync19 } = await import("node:fs");
3587
3826
  process.stderr.write(
3588
- `[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 ?? "-"}
3827
+ `[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 ?? "-"}
3589
3828
  `
3590
3829
  );
3591
3830
  } catch {
@@ -3619,6 +3858,8 @@ var init_acp_connection = __esm({
3619
3858
  });
3620
3859
  child.on("exit", (code) => {
3621
3860
  this.exited = true;
3861
+ if (child.pid)
3862
+ unregisterDimAcpChild(child.pid);
3622
3863
  const err = new Error(`dim acp \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code}\uFF09`);
3623
3864
  this.failAllPending(err);
3624
3865
  this.updateHandlers.clear();
@@ -3627,6 +3868,8 @@ var init_acp_connection = __esm({
3627
3868
  });
3628
3869
  child.on("error", (error) => {
3629
3870
  this.exited = true;
3871
+ if (child.pid)
3872
+ unregisterDimAcpChild(child.pid);
3630
3873
  const err = new Error(`dim acp \u8FDB\u7A0B\u9519\u8BEF: ${error.message}`);
3631
3874
  this.failAllPending(err);
3632
3875
  this.onExit?.(null);
@@ -3735,11 +3978,26 @@ var init_acp_connection = __esm({
3735
3978
  this.notify("session/cancel", { sessionId });
3736
3979
  }
3737
3980
  // ── 生命周期 ──
3738
- /** 优雅关闭:close 全部会话 + 结束进程(宽限后强杀由调用方 killTree 兜底) */
3981
+ /**
3982
+ * 优雅关闭:close 全部会话 → SIGTERM → **回收必达**(issues/dim-acp-process-leak-shared-space-contention.md)。
3983
+ *
3984
+ * 真机根因:卡死的 dim acp(持有共享 dim sqlite 跨进程状态)**无视 SIGTERM**(实测 3 个泄漏
3985
+ * 进程全部存活),旧实现发完 SIGTERM 即返回 → 进程「名义回收、实际长存」(最久 50h)。
3986
+ * 现在宽限后未退出即 SIGKILL 进程组,并校验进程确实退出(失败 warn 留 pid;成功留痕可见)。
3987
+ */
3739
3988
  async close() {
3740
3989
  const handlers = [...this.updateHandlers.keys()];
3741
3990
  await Promise.allSettled(handlers.map((sessionId) => this.closeSession(sessionId)));
3991
+ const pid = this.child?.pid;
3742
3992
  this.kill();
3993
+ if (pid === void 0)
3994
+ return;
3995
+ await ensureDimAcpTerminated(pid, {
3996
+ graceMs: this.killGraceMs,
3997
+ log: this.log,
3998
+ tag: "close"
3999
+ });
4000
+ unregisterDimAcpChild(pid);
3743
4001
  }
3744
4002
  kill() {
3745
4003
  const child = this.child;
@@ -3751,23 +4009,26 @@ var init_acp_connection = __esm({
3751
4009
  } catch {
3752
4010
  }
3753
4011
  }
3754
- /** 强杀进程组(超时兜底) */
4012
+ /**
4013
+ * 强杀进程组(超时兜底)+ 退出校验留痕。
4014
+ * 旧实现发完 SIGKILL 即返回,进程死活不可见;现在校验后 warn(失败,带 pid)或留成功日志。
4015
+ * 保持 void 签名(既有调用方为 fire-and-forget);校验以浮动 promise 进行。
4016
+ */
3755
4017
  killTree() {
3756
4018
  const child = this.child;
3757
4019
  if (!child || this.exited)
3758
4020
  return;
3759
4021
  this.exited = true;
3760
- if (process.platform === "win32") {
3761
- try {
3762
- execFileSync4("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
3763
- } catch {
3764
- }
4022
+ const pid = child.pid;
4023
+ if (pid === void 0)
3765
4024
  return;
3766
- }
3767
- try {
3768
- process.kill(-child.pid, "SIGKILL");
3769
- } catch {
3770
- }
4025
+ void ensureDimAcpTerminated(pid, {
4026
+ graceMs: 0,
4027
+ log: this.log,
4028
+ tag: "killTree"
4029
+ }).then(() => {
4030
+ unregisterDimAcpChild(pid);
4031
+ });
3771
4032
  }
3772
4033
  // ── 内部:请求/响应路由 ──
3773
4034
  request(method, params, timeoutMs = 3e5) {
@@ -3892,8 +4153,8 @@ __export(dim_home_exports, {
3892
4153
  isolatedDimHome: () => isolatedDimHome,
3893
4154
  resolveIsolatedDimEnv: () => resolveIsolatedDimEnv
3894
4155
  });
3895
- import { execFileSync as execFileSync5 } from "node:child_process";
3896
- import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync6 } from "node:fs";
4156
+ import { execFileSync as execFileSync6 } from "node:child_process";
4157
+ import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, rmSync as rmSync6 } from "node:fs";
3897
4158
  import { homedir as homedir8 } from "node:os";
3898
4159
  import { join as join10 } from "node:path";
3899
4160
  function isolatedDimHome() {
@@ -3919,13 +4180,13 @@ __export(dim_model_exports, {
3919
4180
  });
3920
4181
  import { createRequire } from "node:module";
3921
4182
  import { spawnSync } from "node:child_process";
3922
- import { closeSync as closeSync3, existsSync as existsSync10, openSync as openSync3, readFileSync as readFileSync7, unlinkSync } from "node:fs";
4183
+ import { closeSync as closeSync3, existsSync as existsSync11, openSync as openSync3, readFileSync as readFileSync8, unlinkSync } from "node:fs";
3923
4184
  import { homedir as homedir11, tmpdir } from "node:os";
3924
4185
  import { join as join14 } from "node:path";
3925
4186
  function dimSqlitePath(env) {
3926
4187
  const home = env?.DIMCODE_HOME?.trim();
3927
4188
  const candidates = home && home !== "" ? [join14(home, "v2", "dimcode.sqlite"), join14(home, "dimcode.sqlite")] : [join14(homedir11(), ".dimcode", "v2", "dimcode.sqlite")];
3928
- return candidates.find((p) => existsSync10(p)) ?? null;
4189
+ return candidates.find((p) => existsSync11(p)) ?? null;
3929
4190
  }
3930
4191
  function listDimModels(dimPath = "dim", env) {
3931
4192
  const fromCli = (() => {
@@ -3975,7 +4236,7 @@ function listDimModelsFromCli(dimPath, env) {
3975
4236
  encoding: "buffer"
3976
4237
  });
3977
4238
  closeSync3(fd);
3978
- collected = readFileSync7(tmpFile, "utf8");
4239
+ collected = readFileSync8(tmpFile, "utf8");
3979
4240
  if (result.status !== 0)
3980
4241
  throw new Error(`dim model list \u9000\u51FA\u7801 ${result.status}`);
3981
4242
  } finally {
@@ -4003,7 +4264,7 @@ function dimOAuthTokenPath(env) {
4003
4264
  const home = env?.DIMCODE_HOME?.trim();
4004
4265
  const dir = home && home !== "" ? home : join14(homedir11(), ".dimcode", "v2");
4005
4266
  for (const p of [join14(dir, "auth.json"), join14(dir, "v2", "auth.json")]) {
4006
- if (existsSync10(p))
4267
+ if (existsSync11(p))
4007
4268
  return p;
4008
4269
  }
4009
4270
  return null;
@@ -4147,14 +4408,14 @@ init_dist2();
4147
4408
  init_dist2();
4148
4409
 
4149
4410
  // src/build-env.ts
4150
- var BUILD_VERSION2 = "0.1.0-alpha.10";
4411
+ var BUILD_VERSION2 = "0.1.0-alpha.11";
4151
4412
 
4152
4413
  // src/commands/daemon.ts
4153
4414
  init_dist();
4154
4415
  init_dist2();
4155
- import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync11, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "node:fs";
4416
+ import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as readFileSync12, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
4156
4417
  import { homedir as homedir13 } from "node:os";
4157
- import { dirname as dirname10, join as join22 } from "node:path";
4418
+ import { dirname as dirname11, join as join22 } from "node:path";
4158
4419
 
4159
4420
  // src/daemon/daemon-autostart.ts
4160
4421
  init_portal_autostart();
@@ -4222,10 +4483,10 @@ init_portal_server();
4222
4483
 
4223
4484
  // src/daemon/runner.ts
4224
4485
  init_dist2();
4225
- import { execFileSync as execFileSync10 } from "node:child_process";
4226
- import { existsSync as existsSync15, mkdirSync as mkdirSync13, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
4486
+ import { execFileSync as execFileSync11 } from "node:child_process";
4487
+ import { existsSync as existsSync16, mkdirSync as mkdirSync14, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
4227
4488
  import { arch, hostname } from "node:os";
4228
- import { dirname as dirname8, join as join20 } from "node:path";
4489
+ import { dirname as dirname9, join as join20 } from "node:path";
4229
4490
 
4230
4491
  // src/daemon/acp/acp-connection-pool.ts
4231
4492
  var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
@@ -4271,6 +4532,7 @@ var AcpConnectionPool = class {
4271
4532
  dimPath: this.options.dimPath,
4272
4533
  cwd: this.options.cwd,
4273
4534
  env: this.options.env,
4535
+ log: this.options.log,
4274
4536
  onExit: () => {
4275
4537
  if (this.connection === conn) {
4276
4538
  this.connection = null;
@@ -4328,6 +4590,9 @@ var AcpConnectionPool = class {
4328
4590
  }
4329
4591
  };
4330
4592
 
4593
+ // src/daemon/runner.ts
4594
+ init_acp_process_registry();
4595
+
4331
4596
  // src/daemon/api.ts
4332
4597
  var HEARTBEAT_TIMEOUT_MS = 1e4;
4333
4598
  var DaemonApi = class {
@@ -4857,33 +5122,32 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
4857
5122
  ## 0.3 \u5206\u652F\u547D\u540D\uFF1A\u7531\u4F60\uFF08Agent\uFF09\u53D6\u540D\u5E76\u4E0A\u62A5
4858
5123
 
4859
5124
  - **\u4EFB\u52A1 ID \u83B7\u53D6**\uFF1A\u672C\u4EFB\u52A1\u7684 AgentTask ID\uFF08UUID\uFF09\u5728\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF08\u4E5F\u662F \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u8FD4\u56DE\u7684 \`id\`\uFF0C\`.agent_context/task-context.md\` \u5F00\u5934\u4EA6\u6709\uFF09\u3002\u9700\u8981\u8C03\u7528 \`morula task branch\` \u65F6\u7528\u5B83\u4F5C\u4E3A taskId\u3002
4860
- - **\u91CD\u8981\u533A\u5206**\uFF1A\`morula task branch <taskId>\` \u7684 taskId \u7528**\u672C\u4EFB\u52A1 UUID**\uFF08\u673A\u5668\u534F\u8BAE\uFF09\uFF1B\u4F46 **MR / \u5206\u652F / PR \u91CC\u7684\u4EFB\u52A1\u7F16\u53F7\u7528\u5BF9\u5916\u7F16\u53F7 code**\uFF08\u5982 \`AGENT-958201\`\uFF0C\u7528\u4E8E\u670D\u52A1\u7AEF\u5173\u8054/\u5173\u5355\uFF09\u2014\u2014**\u4E0D\u8981\u7528 UUID \u6807\u8BB0 MR**\uFF08UUID \u5173\u5355\u53EA\u8BA4\u4E1A\u52A1\u5BF9\u8C61\uFF0CAgentTask \u4E0D\u4F1A\u88AB\u8BC6\u522B\uFF0C\u4F1A\u5BFC\u81F4 MR \u5173\u8054\u5931\u8D25\uFF09\u3002
5125
+ - **\u91CD\u8981\u533A\u5206**\uFF1A\`morula task branch <taskId>\` \u7684 taskId \u7528**\u672C\u4EFB\u52A1 UUID**\uFF08\u673A\u5668\u534F\u8BAE\uFF09\uFF1B\u4F46 **MR / PR \u91CC\u7684\u4EFB\u52A1\u7F16\u53F7\u7528\u5BF9\u5916\u7F16\u53F7 code**\uFF08\u5982 \`AGENT-958201\`\uFF0C\u7528\u4E8E\u670D\u52A1\u7AEF\u5173\u8054/\u5173\u5355\uFF09\u2014\u2014**\u4E0D\u8981\u7528 UUID \u6807\u8BB0 MR**\uFF08UUID \u5173\u5355\u53EA\u8BA4\u4E1A\u52A1\u5BF9\u8C61\uFF0CAgentTask \u4E0D\u4F1A\u88AB\u8BC6\u522B\uFF0C\u4F1A\u5BFC\u81F4 MR \u5173\u8054\u5931\u8D25\uFF09\u3002
4861
5126
  - **\u53EA\u6709\u9700\u8981\u6539\u4EE3\u7801\u5E76\u63A8\u5206\u652F\u7684\u4EFB\u52A1\u624D\u53D6\u540D**\uFF1B\u7EAF\u95EE\u7B54/\u53EA\u8BFB\u8C03\u7814/\u672C\u5730\u8D77\u670D\u52A1\u4E0D\u5EFA\u5206\u652F\uFF08\u89C1\u7B2C 3 \u6761\u51B3\u7B56\u6811\uFF09\u3002
4862
5127
  - **\u5148\u68C0\u67E5\u5F53\u524D\u5206\u652F**\uFF1A\u82E5\u4ED3\u5E93\u5DF2\u6709 feature \u5206\u652F\uFF08\u540C\u4EFB\u52A1/\u540C\u4E00\u6539\u52A8\u95ED\u73AF\u7684\u540E\u7EED\u8F6E\u6B21\u590D\u7528\uFF09\uFF0C**\u76F4\u63A5\u7EE7\u7EED\u7528\u5F53\u524D\u5206\u652F\uFF0C\u4E0D\u8981\u65B0\u5EFA**\uFF08\u5728\u4ED3\u5E93\u76EE\u5F55\u5185\u6267\u884C\uFF09\uFF1A
4863
5128
  \`\`\`bash
4864
5129
  cd <\u5DE5\u4F5C\u533A\u6839>/<\u4ED3\u5E93\u76EE\u5F55> && git branch --show-current # \u82E5\u975E\u7A7A\u4E14\u975E main/master \u2192 \u7528\u8FD9\u4E2A\u5206\u652F\u7EE7\u7EED\u6539
4865
5130
  \`\`\`
4866
5131
  - \u82E5\u5F53\u524D\u4E3A detached HEAD\uFF08\u9996\u6B21\u6267\u884C\uFF09\uFF0C\u4E14\u4EFB\u52A1\u786E\u5B9E\u8981\u6539\u4EE3\u7801\uFF1A
4867
- 1. \u521B\u5EFA\u5206\u652F\u3002\u5206\u652F\u540D**\u4E25\u683C\u7531\u4EE5\u4E0B\u90E8\u5206\u4F9D\u6B21\u62FC\u6210\uFF0C\u4E0D\u591A\u4E0D\u5C11**\uFF1A\`<type>\` + \`/\` + \`<\u672C\u4EFB\u52A1\u5BF9\u5916\u7F16\u53F7>\` + \`-\` + \`<slug>\`
5132
+ 1. \u521B\u5EFA\u5206\u652F\u3002\u5206\u652F\u540D**\u4E25\u683C\u7531\u4EE5\u4E0B\u90E8\u5206\u4F9D\u6B21\u62FC\u6210\uFF0C\u4E0D\u591A\u4E0D\u5C11**\uFF1A\`<type>\` + \`/\` + \`<slug>\`
4868
5133
  - \`<type>\`\uFF1A\u53D6\u4E0B\u65B9\u300C\u547D\u540D\u89C4\u8303\u300D\u6E05\u5355\u91CC\u7684\u4E00\u4E2A\uFF08\u6309\u4EFB\u52A1\u6027\u8D28\u9009\uFF0C\u5982\u6587\u6863\u6539\u52A8\u7528 docs\uFF09\uFF1B
4869
- - \`<\u672C\u4EFB\u52A1\u5BF9\u5916\u7F16\u53F7>\`\uFF1A\u89C1 0.2\uFF0C**\u539F\u6837\u7167\u6284**\u8BE5\u7F16\u53F7\u5230\u5206\u652F\u540D\u91CC\uFF08\u5927\u5C0F\u5199\u4E0E\u5185\u5BB9\u90FD\u4E0D\u6539\u52A8\uFF0C\u4E5F\u4E0D\u7701\u7565\uFF09\uFF1B
4870
5134
  - \`<slug>\`\uFF1A\u5177\u4F53\u5DE5\u4F5C\u4E8B\u60C5\uFF0C\u5C0F\u5199\u3001\u7528 \`-\` \u8FDE\u63A5\u3002
4871
5135
  \`\`\`bash
4872
- git switch -c <type>/<\u672C\u4EFB\u52A1\u5BF9\u5916\u7F16\u53F7>-<slug>
5136
+ git switch -c <type>/<slug>
4873
5137
  \`\`\`
4874
- \u4F8B\uFF1A\u4EFB\u52A1\u7F16\u53F7\u4E3A SPW299-30\u3001\u8981\u505A meta build-info \u2192 \u5206\u652F\u540D \`feature/SPW299-30-meta-build-info\`
4875
- \u5206\u652F\u540D**\u5230 \`<slug>\` \u4E3A\u6B62\u5C31\u7ED3\u675F** \u2014\u2014 \u7F16\u53F7\u4E0E slug \u4E4B\u540E\u4E0D\u518D\u63A5\u4EFB\u4F55\u5185\u5BB9\u3002
5138
+ \u4F8B\uFF1A\u505A mention \u76F8\u5173\u7684\u6D4B\u8BD5\u8865\u5168 \u2192 \u5206\u652F\u540D \`test/mention-tests\`\uFF1B\u4FEE\u590D\u767B\u5F55 401 \u2192 \`fix/login-401\`
5139
+ \u5206\u652F\u540D**\u5230 \`<slug>\` \u4E3A\u6B62\u5C31\u7ED3\u675F** \u2014\u2014 slug \u4E4B\u540E\u4E0D\u518D\u63A5\u4EFB\u4F55\u5185\u5BB9\u3002
4876
5140
  2. **\u901A\u8FC7 CLI \u628A\u5206\u652F\u540D\u4E0A\u62A5\u670D\u52A1\u7AEF**\uFF08\u5FC5\u987B\uFF0C\u670D\u52A1\u7AEF\u636E\u6B64\u505A PR \u5173\u8054/\u6E05\u7406\uFF09\uFF1A
4877
5141
  \`\`\`bash
4878
- morula task branch <taskId> --name <type>/<\u4EFB\u52A1\u7F16\u53F7>-<slug>
5142
+ morula task branch <taskId> --name <type>/<slug>
4879
5143
  \`\`\`
4880
5144
  - **\`morula task branch\` \u9700\u8981 \`MORULA_RUNTIME_ID\`**\uFF08daemon \u6CE8\u5165\u7684 env\uFF0C\u7528\u4E8E\u6821\u9A8C\u4F60\u7684\u5F52\u5C5E\uFF09\u3002\u82E5\u4F60\u7684 exec \u73AF\u5883\u6CA1\u6709\u8FD9\u4E2A env\uFF0C**\u4ECE\u4E3B\u4ED3\u5E93\u76EE\u5F55\u5185\u7684 \`.morula-worktree.json\` \u8BFB\u53D6 \`runtimeId\` \u5B57\u6BB5**\uFF08\u5B8C\u6574\u8DEF\u5F84 \`<\u5DE5\u4F5C\u533A\u6839>/<\u4ED3\u5E93\u76EE\u5F55>/.morula-worktree.json\`\uFF1B\u5DE5\u4F5C\u533A\u6839\u4E0B\u6CA1\u6709\u8FD9\u4E2A\u6587\u4EF6\uFF09\uFF0C\u7528 \`env MORULA_RUNTIME_ID=<\u8BE5\u503C> morula task branch ...\` \u6216\u5BFC\u51FA\u540E\u518D\u8C03\u7528\uFF08\u4E0D\u8981\u4ECE\u522B\u5904\u731C\uFF09\u3002
4881
5145
  - **\u547D\u540D\u89C4\u8303**\uFF08\u5206\u652F\u540D\u4F1A\u51FA\u73B0\u5728 PR\u3001CI\u3001\u5386\u53F2\u91CC\uFF0C\u5FC5\u987B\u89C4\u8303\uFF09\uFF1A
4882
5146
  - \u524D\u7F00 type \u9009\u4E00\uFF1A\`feature\`\uFF08\u65B0\u529F\u80FD\uFF09\u3001\`fix\`\uFF08\u7F3A\u9677\u4FEE\u590D\uFF09\u3001\`hotfix\`\uFF08\u7D27\u6025/\u7EBF\u4E0A\uFF09\u3001\`docs\`\uFF08\u6587\u6863\uFF09\u3001\`refactor\`\uFF08\u91CD\u6784\uFF09\u3001\`chore\`\uFF08\u6742\u52A1\uFF09\u3001\`test\`\uFF08\u6D4B\u8BD5\uFF09\u3001\`perf\`\uFF08\u6027\u80FD\uFF09\u3001\`style\`\uFF08\u6837\u5F0F\uFF09\u3001\`build\`\uFF08\u6784\u5EFA/\u4F9D\u8D56\uFF09\u3001\`ci\`\uFF08CI\uFF09\u3001\`revert\`\uFF08\u56DE\u6EDA\uFF09\uFF1B
4883
- - \u4EFB\u52A1\u7F16\u53F7 = \u672C\u4EFB\u52A1\u7684**\u5BF9\u5916\u7F16\u53F7**\uFF08\u89C1 0.2\uFF0C\u5982 \`SPW299-30\`\uFF09\uFF0C**\u539F\u6837\u5927\u5199**\uFF1A\u670D\u52A1\u7AEF\u6309 title/branch/body \u626B\u63CF\u4EFB\u52A1\u7F16\u53F7\u5173\u8054 MR\uFF0C**\u5339\u914D\u5927\u5C0F\u5199\u654F\u611F**\uFF08\`[A-Z][A-Z0-9]{1,9}-\u6570\u5B57\`\uFF09\uFF0C\u5199\u6210\u5C0F\u5199\u4F1A\u5173\u8054\u4E0D\u5230\uFF1B
4884
5147
  - slug = \u5177\u4F53\u5DE5\u4F5C\u4E8B\u60C5\uFF0C\u5C0F\u5199\u3001\u7528 \`-\` \u8FDE\u63A5\uFF08\u53EF\u7528\u82F1\u6587\uFF1B**\u4E0D\u8981\u7528\u4E2D\u6587**\uFF0C\u4E2D\u6587\u5206\u652F\u540D\u4E0D\u88AB\u591A\u6570 CI/\u5DE5\u5177\u94FE\u652F\u6301\uFF09\uFF1B
4885
- - **\u53EA\u6309\u672C\u8282\u89C4\u5219\u62FC\uFF0C\u4E0D\u8981\u53C2\u8003/\u6A21\u4EFF\u4ED3\u5E93\u91CC\u5DF2\u6709\u5206\u652F\u7684\u547D\u540D**\uFF1A\u5386\u53F2\u5206\u652F\u662F\u8FC7\u5F80\u4EA7\u7269\uFF08\u53EF\u80FD\u542B\u65E5\u671F\u540E\u7F00\u3001\u7F16\u53F7\u5927\u5C0F\u5199\u4E0D\u4E00\u3001\u7F3A\u7F16\u53F7\u7B49\uFF09\uFF0C\u7167\u7740\u5B83\u4EEC\u53D6\u540D\u4F1A\u6CBF\u7528\u65E7\u4E60\u60EF\uFF1B\u5206\u652F\u540D\u5B8C\u5168\u7531\u4E0A\u9762\u4E09\u6BB5\u51B3\u5B9A\uFF0C\u4E0E\u4ED3\u5E93\u73B0\u6709\u5206\u652F\u65E0\u5173\uFF1B
4886
- - **\u5206\u652F\u540D\u5230 slug \u5C31\u7ED3\u675F**\uFF1A\u7F16\u53F7\u4E0E slug \u4E4B\u540E**\u4E0D\u518D\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9**\u2014\u2014\u65E5\u671F\u3001\u65F6\u95F4\u6233\u3001\u8F6E\u6B21\u53F7\u3001\u968F\u673A\u4E32\u90FD\u4E0D\u8981\uFF1B\u65E5\u671F\u4E0D\u8868\u8FBE\u8BED\u4E49\uFF0C\u4E14\u4F1A\u8BA9\u300C\u540C\u4E00\u4EF6\u4E8B\u7684\u540E\u7EED\u8F6E\u6B21\u590D\u7528\u540C\u5206\u652F\u300D\u770B\u8D77\u6765\u50CF\u8981\u65B0\u5EFA\u5206\u652F\uFF1B
5148
+ - **\u53EA\u6309\u672C\u8282\u89C4\u5219\u62FC\uFF0C\u4E0D\u8981\u53C2\u8003/\u6A21\u4EFF\u4ED3\u5E93\u91CC\u5DF2\u6709\u5206\u652F\u7684\u547D\u540D**\uFF1A\u5386\u53F2\u5206\u652F\u662F\u8FC7\u5F80\u4EA7\u7269\uFF08\u53EF\u80FD\u542B\u65E5\u671F\u540E\u7F00\u3001\u4EFB\u52A1\u7F16\u53F7\u7B49\u65E7\u5F62\u6001\uFF09\uFF0C\u7167\u7740\u5B83\u4EEC\u53D6\u540D\u4F1A\u6CBF\u7528\u65E7\u4E60\u60EF\uFF1B\u5206\u652F\u540D\u5B8C\u5168\u7531\u4E0A\u9762\u4E24\u6BB5\u51B3\u5B9A\uFF0C\u4E0E\u4ED3\u5E93\u73B0\u6709\u5206\u652F\u65E0\u5173\uFF1B
5149
+ - **\u5206\u652F\u540D\u5230 slug \u5C31\u7ED3\u675F**\uFF1Aslug \u4E4B\u540E**\u4E0D\u518D\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9**\u2014\u2014\u65E5\u671F\u3001\u65F6\u95F4\u6233\u3001\u8F6E\u6B21\u53F7\u3001\u968F\u673A\u4E32\u90FD\u4E0D\u8981\uFF1B\u65E5\u671F\u4E0D\u8868\u8FBE\u8BED\u4E49\uFF0C\u4E14\u4F1A\u8BA9\u300C\u540C\u4E00\u4EF6\u4E8B\u7684\u540E\u7EED\u8F6E\u6B21\u590D\u7528\u540C\u5206\u652F\u300D\u770B\u8D77\u6765\u50CF\u8981\u65B0\u5EFA\u5206\u652F\uFF1B
5150
+ - **\u5206\u652F\u540D\u662F\u5168\u4ED3\u5E93\u5171\u4EAB\u7684\u547D\u540D\u7A7A\u95F4\uFF0C\u649E\u540D\u4F1A\u88AB\u62D2\u7EDD**\uFF1A\u82E5 \`morula task branch\` / \`morula repo mr create\` \u8FD4\u56DE \`BRANCH_OWNED_BY_OTHER_TASK\`\uFF08\u5206\u652F\u540D\u5DF2\u88AB\u5176\u4ED6\u4EFB\u52A1\u5360\u7528\uFF09\uFF0C\u6362\u4E00\u4E2A\u66F4\u5177\u4F53\u7684 slug \u91CD\u65B0\u53D6\u540D\u3001\u4E0A\u62A5\u540E\u518D\u7EE7\u7EED\uFF1B
4887
5151
  - **\u7981\u6B62**\u7528 \`main\`/\`master\`\uFF0C\u7981\u6B62\u4EE5 \`-\` \u5F00\u5934/\u7ED3\u5C3E\uFF0C\u957F\u5EA6 \u2264 255\u3002
4888
5152
  - \u5982\u679C\u4F60\u4E0D\u6539\u4EE3\u7801\uFF0C**\u4E0D\u8981**\u521B\u5EFA\u5206\u652F\uFF0C\u4E5F\u4E0D\u8981\u8C03\u7528 \`morula task branch\`\u3002
4889
5153
 
@@ -4898,6 +5162,10 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
4898
5162
  \`\`\`
4899
5163
  - \u82E5 \`git status\` \u663E\u793A\u5DE5\u4F5C\u76EE\u5F55\u6709**\u4E0D\u5C5E\u4E8E\u672C\u4EFB\u52A1**\u7684\u6539\u52A8\uFF08\u5982\u8BEF\u5165\u5176\u4ED6\u4EFB\u52A1 worktree\uFF09\uFF0C**\u7ACB\u5373\u505C\u6B62\u5E76\u62A5\u544A**\uFF0C\u4E0D\u8981\u7EE7\u7EED\u7F16\u8F91\u3002
4900
5164
  - **\u4E0D\u8981\u5728\u5DE5\u4F5C\u533A\u6839\u76F4\u63A5\u8DD1 git/gh**\uFF1A\u5DE5\u4F5C\u533A\u6839\u4E0D\u662F git \u4ED3\u5E93\uFF08\u9664\u4ED3\u5E93\u5B50\u76EE\u5F55\u5916\u8FD8\u6709 \`AGENTS.md\`/\`.agents/\`/\`.agent_context/\`/\`.morula/\`\uFF09\uFF0C\u5728\u6839\u76EE\u5F55\u8DD1 git \u53EA\u4F1A\u62A5 \`not a git repository\`\u3002\`morula\` CLI \u53CD\u4E4B\u53EF\u4ECE\u5DE5\u4F5C\u533A\u6839**\u6216\u5176\u4EFB\u610F\u5B50\u76EE\u5F55**\u6267\u884C\uFF08\u5B83\u4ECE cwd \u5411\u4E0A\u67E5\u627E \`.morula/checkout.json\` \u51ED\u636E\uFF09\u3002
5165
+ - **\u5DE5\u4F5C\u533A\u51C6\u5907\uFF08\u5168\u65B0\u68C0\u51FA\uFF0C\u5148\u8865\u73AF\u5883\u524D\u7F6E\u518D\u8DD1\u6784\u5EFA/\u9A8C\u6536\u547D\u4EE4\uFF09**\uFF1A
5166
+ - \u672C\u5DE5\u4F5C\u533A\u662F\u5168\u65B0\u68C0\u51FA\uFF0C\`node_modules\` \u4E0D\u5B58\u5728\uFF1A\u5148\u6309\u4ED3\u5E93\u58F0\u660E\u7684\u5305\u7BA1\u7406\u5668\u5B89\u88C5\u4F9D\u8D56\u2014\u2014\u6709 \`pnpm-lock.yaml\` \u7528 \`pnpm install\`\u3001\`package-lock.json\` \u7528 \`npm ci\`\u3001\`yarn.lock\` \u7528 \`yarn\`\uFF1B
5167
+ - \u4ED3\u5E93\u4F7F\u7528 Prisma \u65F6\uFF08\u5B58\u5728 \`prisma/schema.prisma\`\uFF09\u5148 \`npx prisma generate\`\uFF0C\u5426\u5219 \`tsc\` \u4F1A\u62A5\u5927\u91CF\u4E0E\u672C\u6B21\u6539\u52A8\u65E0\u5173\u7684\u9519\u8BEF\uFF1B
5168
+ - \u9A8C\u6536/\u6784\u5EFA\u547D\u4EE4\u5931\u8D25\u65F6\uFF0C\u5148\u533A\u5206\u300C\u73AF\u5883\u524D\u7F6E\uFF08\u4F9D\u8D56\u3001\u751F\u6210\u7269\u7F3A\u5931\uFF09\u300D\u4E0E\u300C\u81EA\u8EAB\u6539\u52A8\u300D\uFF1A\u73AF\u5883\u524D\u7F6E\u5148\u8865\u9F50\u518D\u590D\u8DD1\uFF0C\u4E0D\u8981\u628A\u73AF\u5883\u62A5\u9519\u5F53\u6210\u672C\u6B21\u6539\u52A8\u7684\u95EE\u9898\u53BB\u4FEE\u3002
4901
5169
 
4902
5170
  ## 0.5 \u4ED3\u5E93\u4E0E\u5DE5\u4F5C\u76EE\u5F55
4903
5171
 
@@ -5038,8 +5306,8 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
5038
5306
 
5039
5307
  ## 2. PR \u94FE\u63A5\u4E0E\u5173\u5355\u610F\u56FE\u662F\u4E24\u5957\u4E0D\u540C\u7684\u5951\u7EA6
5040
5308
 
5041
- - **\u94FE\u63A5\u901A\u9053**\uFF1APR \u7684 title / branch / body \u4E2D\u51FA\u73B0\u4EFB\u52A1\u7F16\u53F7 \u2192 \u670D\u52A1\u7AEF\u81EA\u52A8\u5173\u8054 PR \u5230\u4EFB\u52A1\u3002
5042
- \u63A8\u8350\u5199\u6CD5\uFF1Atitle \u4EE5\u300CSTR-101: \u63CF\u8FF0\u300D\u5F00\u5934\uFF0C\u6216 branch \u7528 \`str-101-xxx\`\uFF0C\u6216 body \u5199\u300CCloses STR-101\u300D\u3002
5309
+ - **\u94FE\u63A5\u901A\u9053**\uFF1APR \u7684 title / body \u4E2D\u51FA\u73B0\u4EFB\u52A1\u7F16\u53F7 \u2192 \u670D\u52A1\u7AEF\u81EA\u52A8\u5173\u8054 PR \u5230\u4EFB\u52A1\u3002
5310
+ \u63A8\u8350\u5199\u6CD5\uFF1Atitle \u4EE5\u300CSTR-101: \u63CF\u8FF0\u300D\u5F00\u5934\uFF0C\u6216 body \u5199\u300CCloses STR-101\u300D\uFF08\u5206\u652F\u540D\u4E0D\u542B\u4EFB\u52A1\u7F16\u53F7\uFF0C\u89C1 0.3\uFF1B\u5E73\u53F0\u901A\u9053\u5EFA\u7684 MR \u4F1A\u81EA\u52A8\u628A\u7F16\u53F7\u5199\u8FDB title/body\uFF09\u3002
5043
5311
  - **\u5173\u5355\u901A\u9053**\uFF1A\u53EA\u6709 \`Closes\` / \`Fixes\` / \`Resolves\` \u540E**\u7D27\u90BB**\u4EFB\u52A1\u7F16\u53F7\uFF08\u5982\u300CCloses STR-101\u300D\uFF09\u2192 \u8BE5 PR
5044
5312
  merge \u65F6\u670D\u52A1\u7AEF\u81EA\u52A8\u628A\u4EFB\u52A1\u6D41\u8F6C\u5230\u5B8C\u6210\u72B6\u6001\u3002title \u524D\u7F00\u6216 branch \u5F15\u7528\u53EA\u4F1A\u5173\u8054 PR\uFF0C\u4E0D\u4F1A\u5728 merge \u65F6\u5173\u5355\u3002
5045
5313
  - body \u4E2D\u677E\u6563\u7684\u63D0\u53CA\uFF08\u5982\u300CRelated to STR-101\u300D\uFF09\u4F1A\u88AB\u6807\u8BB0\u4E3A\u7EAF\u5F15\u7528\uFF0C\u4E0D\u5C55\u793A\u5728\u4EFB\u52A1\u7684 PR \u5217\u8868\u4E2D\u3002
@@ -5185,7 +5453,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
5185
5453
 
5186
5454
  | \u5951\u7EA6 | \u670D\u52A1\u7AEF\u5B9E\u73B0 | \u4F4D\u7F6E |
5187
5455
  |------|-----------|------|
5188
- | \u4EFB\u52A1 key \u5339\u914D\uFF08\`SPW299-30\`\uFF09 | \`VcsLinkService.TASK_KEY_RE\`\uFF08\u626B\u63CF title/**branch**/body\uFF1B\u6B63\u5219 \`[A-Z][A-Z0-9]{1,9}-\u6570\u5B57\`\uFF0C**\u5927\u5C0F\u5199\u654F\u611F** \u2192 \u5206\u652F\u540D\u91CC\u7684\u7F16\u53F7\u5FC5\u987B\u539F\u6837\u5927\u5199\uFF09 | \`src/modules/vcs-integration/link/vcs-link.service.ts:30,435\` |
5456
+ | \u4EFB\u52A1 key \u5339\u914D\uFF08\`SPW299-30\`\uFF09 | \`VcsLinkService.TASK_KEY_RE\`\uFF08\u626B\u63CF title/branch/body\uFF1B\u6B63\u5219 \`[A-Z][A-Z0-9]{1,9}-\u6570\u5B57\`\uFF0C\u5927\u5C0F\u5199\u654F\u611F \u2192 **MR title/body \u4E2D\u7684\u7F16\u53F7\u987B\u539F\u6837\u5927\u5199**\uFF1B\u5206\u652F\u540D\u5DF2\u4E0D\u542B\u7F16\u53F7\uFF08\u5951\u7EA6 0.3 \u5B9A\u6848\uFF09\uFF0C\u7F16\u53F7\u7531\u5E73\u53F0\u901A\u9053\u81EA\u52A8\u5199\u5165 title/body\uFF09 | \`src/modules/vcs-integration/link/vcs-link.service.ts:30,435\` |
5189
5457
  | Close intent \u5355\u72EC\u626B\u63CF\uFF08title/body\uFF0C\`Closes|Fixes|Resolves\` \u7D27\u8DDF key\uFF09 | \`CLOSE_INTENT_RE\` | \`vcs-link.service.ts:29\` |
5190
5458
  | reference-only \u9690\u85CF\uFF08\u677E\u6563 body \u63D0\u53CA\u4E0D\u5165 PR \u5217\u8868\uFF09 | \`resolveTargets\` \u4E0E closeIntent \u5206\u652F | \`vcs-link.service.ts:60-80, 184-186\` |
5191
5459
  | merge \u5173\u5355\u5E42\u7B49 | merged \u2192 closeIntent \u94FE\u63A5\u5BF9\u5E94\u4EFB\u52A1 | \`vcs-link.service.ts:19-20\`\uFF08\u6587\u4EF6\u5934\u6CE8\u91CA\uFF09 |
@@ -5207,7 +5475,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
5207
5475
 
5208
5476
  | \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
5209
5477
  |------|------|------|
5210
- | \u5206\u652F\u540D\u7531 Agent \u53D6\u540D\u5E76\u4E0A\u62A5\uFF08\u683C\u5F0F \`<type>/<\u4EFB\u52A1\u7F16\u53F7>-<slug>\`\uFF0C\u89C1\u5951\u7EA6 0.3\uFF09 | workContext \u521D\u59CB\u5360\u4F4D\u7B26 \`morula/tmp-<random6>\`\uFF1B\`morula task branch <id> --name <name> [--repo <owner/name>]\` \u66F4\u65B0\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u7684\u6B21\u4ED3\u5E93\u5E26 \`--repo\`\uFF09 | \`buildPlaceholderBranchName\`\uFF08\`collaboration-logic.ts\`\uFF09\u3001\`POST /agent-tasks/:id/branch\`\uFF08\`agent-task.controller.ts\`\uFF09\u3001\`commands/task.ts\`\u3002**\u670D\u52A1\u7AEF\u4E0D\u751F\u6210\u5206\u652F\u540D**\uFF08\u66FE\u6709\u7684 \`buildBranchName\` \u7F16\u7801\u65E7\u89C4\u8303\uFF0C\u5DF2\u5220\u9664\uFF09 |
5478
+ | \u5206\u652F\u540D\u7531 Agent \u53D6\u540D\u5E76\u4E0A\u62A5\uFF08\u683C\u5F0F \`<type>/<slug>\`\uFF0C\u89C1\u5951\u7EA6 0.3\uFF09 | workContext \u521D\u59CB\u5360\u4F4D\u7B26 \`morula/tmp-<random6>\`\uFF1B\`morula task branch <id> --name <name> [--repo <owner/name>]\` \u66F4\u65B0\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u7684\u6B21\u4ED3\u5E93\u5E26 \`--repo\`\uFF09 | \`buildPlaceholderBranchName\`\uFF08\`collaboration-logic.ts\`\uFF09\u3001\`POST /agent-tasks/:id/branch\`\uFF08\`agent-task.controller.ts\`\uFF09\u3001\`commands/task.ts\`\u3002**\u670D\u52A1\u7AEF\u4E0D\u751F\u6210\u5206\u652F\u540D**\uFF08\u66FE\u6709\u7684 \`buildBranchName\` \u7F16\u7801\u65E7\u89C4\u8303\uFF0C\u5DF2\u5220\u9664\uFF09 |
5211
5479
  | \u72EC\u7ACB worktree \u521B\u5EFA\uFF08detached\uFF0C\u4E0D\u5EFA\u5360\u4F4D\u5206\u652F\uFF09 | \`prepareWorktree\`\uFF08\`git worktree add --detach\`\uFF09 | \`packages/runtime/src/daemon/worktree-manager.ts\` |
5212
5480
  | \u590D\u7528\u68C0\u6D4B\uFF08\u5141\u8BB8 agent \u6539\u540D\uFF0C\u7981\u4E3B\u5E72\uFF09 | marker \u8EAB\u4EFD\u6821\u9A8C\u4E0D\u542B branchName\uFF1Bdetached \u6B8B\u7559\u81EA\u52A8\u4FDD\u5168\u91CD\u5EFA\uFF08\u672A\u63D0\u4EA4\u6539\u52A8 \u2192 stash \`morula-rescue <tag>\`\u3001\u672A\u63A8\u9001\u63D0\u4EA4 \u2192 \`refs/morula-rescue/<tag>\`\uFF09\uFF0C\u4FDD\u5168\u5931\u8D25/\u4E3B\u5E72 \u2192 \`WORKTREE_BRANCH_DRIFT\` | \`worktree-manager.ts\`\u3001\`workspace-marker.ts\` |
5213
5481
  | \u4EFB\u52A1\u7F16\u53F7\u8865\u9F50\u515C\u5E95 | \`ensurePullRequestTaskCode\` | \`packages/runtime/src/daemon/runner.ts\` |
@@ -5274,8 +5542,8 @@ function isFlagEnabled(name, defaultValue = true) {
5274
5542
  }
5275
5543
 
5276
5544
  // src/daemon/git-env.ts
5277
- import { execFileSync as execFileSync6 } from "node:child_process";
5278
- import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
5545
+ import { execFileSync as execFileSync7 } from "node:child_process";
5546
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7 } from "node:fs";
5279
5547
  import { join as join11 } from "node:path";
5280
5548
  var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
5281
5549
  function prependToPath(dirs) {
@@ -5288,18 +5556,18 @@ function prependToPath(dirs) {
5288
5556
  }
5289
5557
  function ensureGitOnPath() {
5290
5558
  try {
5291
- execFileSync6("git", ["--version"], { stdio: "pipe" });
5559
+ execFileSync7("git", ["--version"], { stdio: "pipe" });
5292
5560
  return;
5293
5561
  } catch {
5294
5562
  }
5295
- const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync7(join11(d, "git"))));
5563
+ const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync8(join11(d, "git"))));
5296
5564
  if (missing.length) {
5297
5565
  console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
5298
5566
  }
5299
5567
  }
5300
5568
  function ensureNodeOnPath() {
5301
5569
  const nodeDir = process.execPath ? join11(process.execPath, "..") : "";
5302
- const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync7(join11(d, "node")));
5570
+ const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync8(join11(d, "node")));
5303
5571
  const missing = prependToPath(candidates);
5304
5572
  if (missing.length) {
5305
5573
  console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
@@ -5345,27 +5613,27 @@ function buildDaemonRepoUrl(repoFullName, baseUrl) {
5345
5613
  function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
5346
5614
  const repoDir = join11(workdir, tenantId, repo);
5347
5615
  const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
5348
- if (existsSync7(join11(repoDir, ".git"))) {
5616
+ if (existsSync8(join11(repoDir, ".git"))) {
5349
5617
  if (remoteUrl)
5350
5618
  syncRemoteUrl(repoDir, remoteUrl, env);
5351
5619
  try {
5352
5620
  if (daemonRemoteUrl) {
5353
- execFileSync6(
5621
+ execFileSync7(
5354
5622
  "git",
5355
5623
  ["-C", repoDir, "fetch", "--prune", fetchUrl, "+refs/heads/*:refs/remotes/origin/*"],
5356
5624
  { env, stdio: "pipe" }
5357
5625
  );
5358
5626
  } else {
5359
- execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
5627
+ execFileSync7("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
5360
5628
  }
5361
5629
  } catch (err) {
5362
5630
  throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
5363
5631
  }
5364
5632
  return repoDir;
5365
5633
  }
5366
- mkdirSync6(repoDir, { recursive: true });
5634
+ mkdirSync7(repoDir, { recursive: true });
5367
5635
  try {
5368
- execFileSync6("git", ["clone", "--filter=blob:none", fetchUrl, repoDir], { env, stdio: "pipe" });
5636
+ execFileSync7("git", ["clone", "--filter=blob:none", fetchUrl, repoDir], { env, stdio: "pipe" });
5369
5637
  } catch (err) {
5370
5638
  throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
5371
5639
  }
@@ -5380,17 +5648,17 @@ function gitErrorText(err) {
5380
5648
  }
5381
5649
  function syncRemoteUrl(repoDir, remoteUrl, env) {
5382
5650
  try {
5383
- const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
5651
+ const cur = execFileSync7("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
5384
5652
  if (cur === remoteUrl)
5385
5653
  return;
5386
- execFileSync6("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
5654
+ execFileSync7("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
5387
5655
  } catch {
5388
5656
  }
5389
5657
  }
5390
5658
  function isLinkedWorktree(repoDir) {
5391
5659
  try {
5392
- const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
5393
- return !!gitDir && existsSync7(join11(gitDir, "commondir"));
5660
+ const gitDir = execFileSync7("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
5661
+ return !!gitDir && existsSync8(join11(gitDir, "commondir"));
5394
5662
  } catch {
5395
5663
  return false;
5396
5664
  }
@@ -5398,7 +5666,7 @@ function isLinkedWorktree(repoDir) {
5398
5666
  function enableWorktreeConfig(repoDir) {
5399
5667
  const getLocal = () => {
5400
5668
  try {
5401
- return execFileSync6("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
5669
+ return execFileSync7("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
5402
5670
  } catch {
5403
5671
  return null;
5404
5672
  }
@@ -5406,7 +5674,7 @@ function enableWorktreeConfig(repoDir) {
5406
5674
  if (getLocal() === "true")
5407
5675
  return true;
5408
5676
  try {
5409
- execFileSync6("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
5677
+ execFileSync7("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
5410
5678
  return true;
5411
5679
  } catch {
5412
5680
  return getLocal() === "true";
@@ -5417,18 +5685,18 @@ function ensureGitIdentity(repoDir, personaName) {
5417
5685
  const email = `${personaName.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}@morula.local`;
5418
5686
  if (isLinkedWorktree(repoDir)) {
5419
5687
  if (enableWorktreeConfig(repoDir)) {
5420
- execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
5421
- execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
5688
+ execFileSync7("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
5689
+ execFileSync7("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
5422
5690
  return;
5423
5691
  }
5424
5692
  console.warn(`[daemon] worktree \u7EA7 config \u4E0D\u53EF\u7528\uFF08extensions.worktreeConfig \u542F\u7528\u5931\u8D25\uFF09\uFF0C\u9000\u56DE\u5199\u5171\u4EAB config: ${repoDir}`);
5425
5693
  }
5426
- execFileSync6("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
5427
- execFileSync6("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
5694
+ execFileSync7("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
5695
+ execFileSync7("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
5428
5696
  }
5429
5697
 
5430
5698
  // src/daemon/outbox.ts
5431
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync7, writeFileSync as writeFileSync5 } from "node:fs";
5699
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "node:fs";
5432
5700
  import { homedir as homedir9 } from "node:os";
5433
5701
  import { join as join12 } from "node:path";
5434
5702
  var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
@@ -5439,19 +5707,19 @@ function outboxDir(configDir) {
5439
5707
  }
5440
5708
  function writeOutbox(entry, configDir) {
5441
5709
  const dir = outboxDir(configDir);
5442
- mkdirSync7(dir, { recursive: true, mode: 448 });
5443
- writeFileSync5(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
5710
+ mkdirSync8(dir, { recursive: true, mode: 448 });
5711
+ writeFileSync6(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
5444
5712
  }
5445
5713
  function readOutboxEntries(configDir) {
5446
5714
  const dir = outboxDir(configDir);
5447
- if (!existsSync8(dir))
5715
+ if (!existsSync9(dir))
5448
5716
  return [];
5449
5717
  const entries = [];
5450
5718
  for (const file of readdirSync2(dir)) {
5451
5719
  if (!file.endsWith(".json"))
5452
5720
  continue;
5453
5721
  try {
5454
- const raw = readFileSync5(join12(dir, file), "utf8");
5722
+ const raw = readFileSync6(join12(dir, file), "utf8");
5455
5723
  const parsed = JSON.parse(raw);
5456
5724
  if (parsed && typeof parsed.taskId === "string")
5457
5725
  entries.push(parsed);
@@ -5468,9 +5736,9 @@ function staleDir(configDir) {
5468
5736
  }
5469
5737
  function archiveOutbox(entry, reason, configDir) {
5470
5738
  const dir = staleDir(configDir);
5471
- mkdirSync7(dir, { recursive: true, mode: 448 });
5739
+ mkdirSync8(dir, { recursive: true, mode: 448 });
5472
5740
  try {
5473
- writeFileSync5(
5741
+ writeFileSync6(
5474
5742
  join12(dir, `${entry.taskId}.json`),
5475
5743
  JSON.stringify({ ...entry, archivedReason: reason, archivedAt: (/* @__PURE__ */ new Date()).toISOString() }),
5476
5744
  { mode: 384 }
@@ -5485,9 +5753,9 @@ function outboxAge(entry) {
5485
5753
  }
5486
5754
 
5487
5755
  // src/daemon/plugin-register.ts
5488
- import { chmodSync as chmodSync3, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, rmSync as rmSync8, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
5756
+ import { chmodSync as chmodSync3, cpSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync8, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
5489
5757
  import { homedir as homedir10 } from "node:os";
5490
- import { dirname as dirname6, join as join13 } from "node:path";
5758
+ import { dirname as dirname7, join as join13 } from "node:path";
5491
5759
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5492
5760
  var PLUGIN_NAME = "morula-cli";
5493
5761
  var MANIFEST_DIR_NAME = ".codex-plugin";
@@ -5502,9 +5770,9 @@ function defaultPluginsRoot(env = process.env, home = homedir10()) {
5502
5770
  return join13(resolveDimcodeHome(env, home), "plugins");
5503
5771
  }
5504
5772
  function resolvePackagePaths(entryUrl) {
5505
- const distDir = dirname6(fileURLToPath2(entryUrl));
5773
+ const distDir = dirname7(fileURLToPath2(entryUrl));
5506
5774
  return {
5507
- packageRoot: dirname6(distDir),
5775
+ packageRoot: dirname7(distDir),
5508
5776
  distDir,
5509
5777
  assetsDir: join13(distDir, "plugin")
5510
5778
  };
@@ -5524,11 +5792,11 @@ exec "$NODE" ${JSON.stringify(entry)} "$@"
5524
5792
  `;
5525
5793
  }
5526
5794
  function assetsReady(paths) {
5527
- return existsSync9(join13(paths.assetsDir, "plugin.json")) && existsSync9(join13(paths.assetsDir, "skills"));
5795
+ return existsSync10(join13(paths.assetsDir, "plugin.json")) && existsSync10(join13(paths.assetsDir, "skills"));
5528
5796
  }
5529
5797
  function listSkillNames(assetsDir) {
5530
5798
  const dir = join13(assetsDir, "skills");
5531
- if (!existsSync9(dir))
5799
+ if (!existsSync10(dir))
5532
5800
  return [];
5533
5801
  return readdirSync3(dir).filter((name) => {
5534
5802
  try {
@@ -5546,7 +5814,7 @@ function pluginBinPath(env = process.env, home = homedir10()) {
5546
5814
  }
5547
5815
  function ownCliEntryPath(moduleUrl = import.meta.url) {
5548
5816
  const entry = join13(resolvePackagePaths(moduleUrl).packageRoot, "dist", "morula.js");
5549
- return existsSync9(entry) ? entry : null;
5817
+ return existsSync10(entry) ? entry : null;
5550
5818
  }
5551
5819
  function resolveAgentCliPath(moduleUrl = import.meta.url, env = process.env, home = homedir10()) {
5552
5820
  return ownCliEntryPath(moduleUrl) ?? pluginBinPath(env, home);
@@ -5567,18 +5835,18 @@ function registerPlugin(opts) {
5567
5835
  const skillsSrc = join13(paths.assetsDir, "skills");
5568
5836
  const skills = listSkillNames(paths.assetsDir);
5569
5837
  const manifestDir = join13(pluginDir, MANIFEST_DIR_NAME);
5570
- mkdirSync8(manifestDir, { recursive: true });
5571
- const manifest = JSON.parse(readFileSync6(join13(paths.assetsDir, "plugin.json"), "utf8"));
5572
- writeFileSync6(join13(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
5838
+ mkdirSync9(manifestDir, { recursive: true });
5839
+ const manifest = JSON.parse(readFileSync7(join13(paths.assetsDir, "plugin.json"), "utf8"));
5840
+ writeFileSync7(join13(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
5573
5841
  `);
5574
5842
  const skillsDest = join13(pluginDir, "skills");
5575
5843
  rmSync8(skillsDest, { recursive: true, force: true });
5576
- mkdirSync8(skillsDest, { recursive: true });
5844
+ mkdirSync9(skillsDest, { recursive: true });
5577
5845
  cpSync(skillsSrc, skillsDest, { recursive: true });
5578
5846
  const binDir = join13(pluginDir, "bin");
5579
- mkdirSync8(binDir, { recursive: true });
5847
+ mkdirSync9(binDir, { recursive: true });
5580
5848
  const binPath = join13(binDir, "morula");
5581
- writeFileSync6(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
5849
+ writeFileSync7(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
5582
5850
  chmodSync3(binPath, 493);
5583
5851
  const legacy = legacyAgentsPluginDir(opts.home);
5584
5852
  return {
@@ -5587,7 +5855,7 @@ function registerPlugin(opts) {
5587
5855
  skills,
5588
5856
  version,
5589
5857
  node: nodePath,
5590
- legacyDir: existsSync9(join13(legacy, MANIFEST_DIR_NAME)) ? legacy : null
5858
+ legacyDir: existsSync10(join13(legacy, MANIFEST_DIR_NAME)) ? legacy : null
5591
5859
  };
5592
5860
  }
5593
5861
 
@@ -6544,8 +6812,8 @@ var DimAgentAdapter = class {
6544
6812
  }
6545
6813
  }
6546
6814
  async probe() {
6547
- const { existsSync: existsSync18 } = await import("node:fs");
6548
- const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
6815
+ const { existsSync: existsSync19 } = await import("node:fs");
6816
+ const installed = existsSync19(this.dimPath) || this.dimPath === "dim";
6549
6817
  let authStatus = installed ? "unknown" : "error";
6550
6818
  let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
6551
6819
  let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
@@ -6836,9 +7104,9 @@ function pickLegacyDimModels(inventory) {
6836
7104
  }
6837
7105
 
6838
7106
  // src/daemon/repo-cache.ts
6839
- import { execFileSync as execFileSync7 } from "node:child_process";
6840
- import { existsSync as existsSync11, mkdirSync as mkdirSync9 } from "node:fs";
6841
- import { dirname as dirname7, join as join15 } from "node:path";
7107
+ import { execFileSync as execFileSync8 } from "node:child_process";
7108
+ import { existsSync as existsSync12, mkdirSync as mkdirSync10 } from "node:fs";
7109
+ import { dirname as dirname8, join as join15 } from "node:path";
6842
7110
  function ownerOf(repoFullName) {
6843
7111
  const idx = repoFullName.indexOf("/");
6844
7112
  return idx > 0 ? repoFullName.slice(0, idx) : "_default";
@@ -6856,10 +7124,10 @@ function buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName) {
6856
7124
  function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
6857
7125
  const repoCachePath = buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName);
6858
7126
  const currentOrigin = () => {
6859
- if (!existsSync11(join15(repoCachePath, ".git")))
7127
+ if (!existsSync12(join15(repoCachePath, ".git")))
6860
7128
  return null;
6861
7129
  try {
6862
- return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
7130
+ return execFileSync8("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
6863
7131
  } catch {
6864
7132
  return null;
6865
7133
  }
@@ -6869,14 +7137,14 @@ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRem
6869
7137
  return {
6870
7138
  repoCachePath,
6871
7139
  ensure() {
6872
- if (existsSync11(join15(repoCachePath, ".git"))) {
7140
+ if (existsSync12(join15(repoCachePath, ".git"))) {
6873
7141
  syncRemoteUrl(repoCachePath, originUrl(), env);
6874
7142
  return;
6875
7143
  }
6876
- mkdirSync9(dirname7(repoCachePath), { recursive: true });
7144
+ mkdirSync10(dirname8(repoCachePath), { recursive: true });
6877
7145
  const cloneUrl = fetchUrl();
6878
7146
  try {
6879
- execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
7147
+ execFileSync8("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
6880
7148
  } catch (err) {
6881
7149
  throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
6882
7150
  }
@@ -6886,13 +7154,13 @@ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRem
6886
7154
  fetch() {
6887
7155
  try {
6888
7156
  if (daemonRemoteUrl) {
6889
- execFileSync7(
7157
+ execFileSync8(
6890
7158
  "git",
6891
7159
  ["-C", repoCachePath, "fetch", "--prune", daemonRemoteUrl, "+refs/heads/*:refs/remotes/origin/*"],
6892
7160
  { env, stdio: "pipe" }
6893
7161
  );
6894
7162
  } else {
6895
- execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
7163
+ execFileSync8("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
6896
7164
  }
6897
7165
  } catch (err) {
6898
7166
  throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
@@ -6908,17 +7176,17 @@ function gitErrorText2(err) {
6908
7176
 
6909
7177
  // src/daemon/repo-checkout-service.ts
6910
7178
  import { createServer as createServer3 } from "node:http";
6911
- import { execFileSync as execFileSync9 } from "node:child_process";
6912
- import { existsSync as existsSync14, mkdirSync as mkdirSync11, readdirSync as readdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
7179
+ import { execFileSync as execFileSync10 } from "node:child_process";
7180
+ import { existsSync as existsSync15, mkdirSync as mkdirSync12, readdirSync as readdirSync5, writeFileSync as writeFileSync10 } from "node:fs";
6913
7181
  import { basename as basename2, join as join18 } from "node:path";
6914
7182
 
6915
7183
  // src/daemon/worktree-manager.ts
6916
- import { execFileSync as execFileSync8 } from "node:child_process";
6917
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
7184
+ import { execFileSync as execFileSync9 } from "node:child_process";
7185
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readdirSync as readdirSync4, readFileSync as readFileSync10, renameSync as renameSync2, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync9 } from "node:fs";
6918
7186
  import { basename, join as join17, sep as sep2 } from "node:path";
6919
7187
 
6920
7188
  // src/daemon/workspace-marker.ts
6921
- import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
7189
+ import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
6922
7190
  import { join as join16 } from "node:path";
6923
7191
  var WORKTREE_MARKER_FILE = ".morula-worktree.json";
6924
7192
  var WORKTREE_MARKER_SCHEMA = 1;
@@ -6926,15 +7194,15 @@ function markerPath(worktreePath) {
6926
7194
  return join16(worktreePath, WORKTREE_MARKER_FILE);
6927
7195
  }
6928
7196
  function writeWorktreeMarker(worktreePath, marker) {
6929
- writeFileSync7(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
7197
+ writeFileSync8(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
6930
7198
  `, "utf8");
6931
7199
  }
6932
7200
  function readWorktreeMarker(worktreePath) {
6933
7201
  const p = markerPath(worktreePath);
6934
- if (!existsSync12(p))
7202
+ if (!existsSync13(p))
6935
7203
  return null;
6936
7204
  try {
6937
- const raw = JSON.parse(readFileSync8(p, "utf8"));
7205
+ const raw = JSON.parse(readFileSync9(p, "utf8"));
6938
7206
  return raw && raw.schemaVersion === WORKTREE_MARKER_SCHEMA ? raw : null;
6939
7207
  } catch {
6940
7208
  return null;
@@ -6969,31 +7237,31 @@ function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
6969
7237
  }
6970
7238
  var legacyPathWarned = /* @__PURE__ */ new Set();
6971
7239
  function warnLegacyWorktreeResidue(legacyPath) {
6972
- if (legacyPathWarned.has(legacyPath) || !existsSync13(legacyPath))
7240
+ if (legacyPathWarned.has(legacyPath) || !existsSync14(legacyPath))
6973
7241
  return;
6974
7242
  legacyPathWarned.add(legacyPath);
6975
7243
  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`);
6976
7244
  }
6977
7245
  var legacyLayoutWarned = /* @__PURE__ */ new Set();
6978
7246
  function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
6979
- if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join17(taskRoot, WORKTREE_MARKER_FILE)))
7247
+ if (legacyLayoutWarned.has(taskRoot) || existsSync14(worktreePath) || !existsSync14(join17(taskRoot, WORKTREE_MARKER_FILE)))
6980
7248
  return;
6981
7249
  legacyLayoutWarned.add(taskRoot);
6982
7250
  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`);
6983
7251
  }
6984
7252
  function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
6985
7253
  const gitignorePath = join17(worktreePath, ".gitignore");
6986
- const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
7254
+ const existing = existsSync14(gitignorePath) ? readFileSync10(gitignorePath, "utf8") : "";
6987
7255
  const requiredLines = [WORKTREE_MARKER_FILE];
6988
7256
  const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
6989
7257
  if (missing.length > 0) {
6990
7258
  const next = `${existing.trimEnd() ? `${existing.trimEnd()}
6991
7259
  ` : ""}${missing.join("\n")}
6992
7260
  `;
6993
- writeFileSync8(gitignorePath, next, "utf8");
7261
+ writeFileSync9(gitignorePath, next, "utf8");
6994
7262
  try {
6995
- execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
6996
- execFileSync8(
7263
+ execFileSync9("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
7264
+ execFileSync9(
6997
7265
  "git",
6998
7266
  ["-C", worktreePath, "-c", "user.name=morula-daemon", "-c", "user.email=morula-daemon@morula.local", "commit", "-q", "-m", "chore: track morula worktree marker"],
6999
7267
  { env, stdio: "pipe" }
@@ -7004,7 +7272,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
7004
7272
  }
7005
7273
  function currentBranch(repoPath) {
7006
7274
  try {
7007
- const out = execFileSync8("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
7275
+ const out = execFileSync9("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
7008
7276
  encoding: "utf8",
7009
7277
  stdio: "pipe"
7010
7278
  }).trim();
@@ -7015,13 +7283,13 @@ function currentBranch(repoPath) {
7015
7283
  }
7016
7284
  function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
7017
7285
  try {
7018
- const current = execFileSync8("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
7286
+ const current = execFileSync9("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
7019
7287
  if (current)
7020
7288
  return current;
7021
7289
  } catch {
7022
7290
  }
7023
7291
  try {
7024
- const out = execFileSync8("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
7292
+ const out = execFileSync9("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
7025
7293
  const first = out.split("\n").map((l) => l.trim()).find((l) => l && !l.includes("->"));
7026
7294
  if (first)
7027
7295
  return first.replace(/^origin\//, "");
@@ -7032,7 +7300,7 @@ function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
7032
7300
  }
7033
7301
  function headCommitSubject(repoPath, env) {
7034
7302
  try {
7035
- const out = execFileSync8("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
7303
+ const out = execFileSync9("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
7036
7304
  encoding: "utf8",
7037
7305
  stdio: "pipe",
7038
7306
  ...env ? { env } : {}
@@ -7044,7 +7312,7 @@ function headCommitSubject(repoPath, env) {
7044
7312
  }
7045
7313
  function hasLocalChanges(repoPath, env) {
7046
7314
  try {
7047
- const out = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], {
7315
+ const out = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], {
7048
7316
  encoding: "utf8",
7049
7317
  stdio: "pipe",
7050
7318
  ...env ? { env } : {}
@@ -7087,10 +7355,10 @@ function indexLockPathFromError(text) {
7087
7355
  return m && m[0].length > "index.lock".length ? m[0] : null;
7088
7356
  }
7089
7357
  function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
7090
- if (!existsSync13(lockPath))
7358
+ if (!existsSync14(lockPath))
7091
7359
  return true;
7092
7360
  const deadline = Date.now() + timeoutMs;
7093
- while (existsSync13(lockPath)) {
7361
+ while (existsSync14(lockPath)) {
7094
7362
  const remain = deadline - Date.now();
7095
7363
  if (remain <= 0)
7096
7364
  return false;
@@ -7100,7 +7368,7 @@ function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, p
7100
7368
  }
7101
7369
  function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
7102
7370
  const runAdd = deps.runAdd ?? ((argv2) => {
7103
- execFileSync8("git", argv2, { stdio: "pipe" });
7371
+ execFileSync9("git", argv2, { stdio: "pipe" });
7104
7372
  });
7105
7373
  const argv = ["-C", repoCachePath, "worktree", "add", "--detach", worktreePath, ref];
7106
7374
  const waitMs = deps.indexLockWaitMs ?? INDEX_LOCK_WAIT_MAX_MS;
@@ -7194,7 +7462,7 @@ function prepareWorktree(input) {
7194
7462
  const requestedBase = input.baseBranch?.trim();
7195
7463
  const hasRef = (ref) => {
7196
7464
  try {
7197
- execFileSync8("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
7465
+ execFileSync9("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
7198
7466
  return true;
7199
7467
  } catch {
7200
7468
  return false;
@@ -7206,14 +7474,14 @@ function prepareWorktree(input) {
7206
7474
  } else {
7207
7475
  const remoteHead = (() => {
7208
7476
  try {
7209
- const out = execFileSync8("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
7477
+ const out = execFileSync9("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
7210
7478
  const name = out.replace(/^refs\/remotes\/origin\//, "");
7211
7479
  if (name && hasRef(name))
7212
7480
  return name;
7213
7481
  } catch {
7214
7482
  }
7215
7483
  try {
7216
- const out = execFileSync8("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
7484
+ const out = execFileSync9("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
7217
7485
  const m = out.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD/m);
7218
7486
  return m?.[1] ?? null;
7219
7487
  } catch {
@@ -7232,10 +7500,10 @@ function prepareWorktree(input) {
7232
7500
  const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
7233
7501
  warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
7234
7502
  warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
7235
- if (existsSync13(worktreePath)) {
7503
+ if (existsSync14(worktreePath)) {
7236
7504
  const isGitRepo = (() => {
7237
7505
  try {
7238
- execFileSync8("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
7506
+ execFileSync9("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
7239
7507
  return true;
7240
7508
  } catch {
7241
7509
  return false;
@@ -7262,11 +7530,11 @@ function prepareWorktree(input) {
7262
7530
  }
7263
7531
  const branch = currentBranch(worktreePath);
7264
7532
  if (!branch) {
7265
- const dirtyOut = execFileSync8("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
7533
+ const dirtyOut = execFileSync9("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
7266
7534
  const dirty = dirtyOut.split("\n").filter(
7267
7535
  (l) => l.trim() && !l.trim().startsWith("?? .agents") && !l.trim().startsWith("?? .agent_context") && !l.trim().startsWith("?? .morula")
7268
7536
  ).join("\n");
7269
- const extraCommits = Number(execFileSync8(
7537
+ const extraCommits = Number(execFileSync9(
7270
7538
  "git",
7271
7539
  ["-C", worktreePath, "rev-list", "--count", `origin/${input.baseBranch}..HEAD`],
7272
7540
  { encoding: "utf8", stdio: "pipe" }
@@ -7274,7 +7542,7 @@ function prepareWorktree(input) {
7274
7542
  const rescueTag = buildRescueTag(input.workContextId, input.code);
7275
7543
  if (dirty.length > 0) {
7276
7544
  try {
7277
- execFileSync8(
7545
+ execFileSync9(
7278
7546
  "git",
7279
7547
  ["-C", worktreePath, "stash", "push", "-u", "-m", `morula-rescue ${rescueTag}`],
7280
7548
  { stdio: "pipe" }
@@ -7285,9 +7553,9 @@ function prepareWorktree(input) {
7285
7553
  }
7286
7554
  }
7287
7555
  if (extraCommits > 1) {
7288
- const headSha2 = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
7556
+ const headSha2 = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
7289
7557
  try {
7290
- execFileSync8("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
7558
+ execFileSync9("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
7291
7559
  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`);
7292
7560
  } catch {
7293
7561
  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");
@@ -7299,8 +7567,8 @@ function prepareWorktree(input) {
7299
7567
  }
7300
7568
  }
7301
7569
  }
7302
- if (!existsSync13(worktreePath)) {
7303
- mkdirSync10(join17(worktreePath, ".."), { recursive: true });
7570
+ if (!existsSync14(worktreePath)) {
7571
+ mkdirSync11(join17(worktreePath, ".."), { recursive: true });
7304
7572
  addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
7305
7573
  writeWorktreeMarker(worktreePath, {
7306
7574
  schemaVersion: 1,
@@ -7316,9 +7584,9 @@ function prepareWorktree(input) {
7316
7584
  }
7317
7585
  {
7318
7586
  const dotMorulaRescue = `${worktreePath}.morula-rescue`;
7319
- if (existsSync13(dotMorulaRescue)) {
7587
+ if (existsSync14(dotMorulaRescue)) {
7320
7588
  const dotMorulaDir = join17(worktreePath, ".morula");
7321
- if (!existsSync13(dotMorulaDir)) {
7589
+ if (!existsSync14(dotMorulaDir)) {
7322
7590
  try {
7323
7591
  renameSync2(dotMorulaRescue, dotMorulaDir);
7324
7592
  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`);
@@ -7330,7 +7598,7 @@ function prepareWorktree(input) {
7330
7598
  }
7331
7599
  }
7332
7600
  }
7333
- const headSha = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
7601
+ const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
7334
7602
  encoding: "utf8",
7335
7603
  stdio: "pipe"
7336
7604
  }).trim();
@@ -7347,9 +7615,9 @@ function prepareWorktree(input) {
7347
7615
  }
7348
7616
  function resolveWorkspaceLayout(root, expected) {
7349
7617
  const primaryDir = join17(root, safeDirName(expected.repoFullName));
7350
- if (existsSync13(join17(primaryDir, WORKTREE_MARKER_FILE)))
7618
+ if (existsSync14(join17(primaryDir, WORKTREE_MARKER_FILE)))
7351
7619
  return { primaryDir, legacy: false };
7352
- if (existsSync13(join17(root, WORKTREE_MARKER_FILE)))
7620
+ if (existsSync14(join17(root, WORKTREE_MARKER_FILE)))
7353
7621
  return { primaryDir: root, legacy: true };
7354
7622
  return null;
7355
7623
  }
@@ -7364,7 +7632,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
7364
7632
  return "MARKER_MISMATCH";
7365
7633
  const { primaryDir, legacy } = layout;
7366
7634
  try {
7367
- const status3 = execFileSync8("git", ["-C", primaryDir, "status", "--porcelain"], {
7635
+ const status3 = execFileSync9("git", ["-C", primaryDir, "status", "--porcelain"], {
7368
7636
  encoding: "utf8",
7369
7637
  stdio: "pipe"
7370
7638
  }).trim();
@@ -7377,7 +7645,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
7377
7645
  if (additionalActivity)
7378
7646
  return "WORKTREE_DIRTY";
7379
7647
  try {
7380
- const unpushed = execFileSync8("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
7648
+ const unpushed = execFileSync9("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
7381
7649
  encoding: "utf8",
7382
7650
  stdio: "pipe"
7383
7651
  }).trim();
@@ -7393,13 +7661,13 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
7393
7661
  }
7394
7662
  function repoHasActivity(repoPath) {
7395
7663
  try {
7396
- const status3 = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
7664
+ const status3 = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
7397
7665
  if (status3)
7398
7666
  return true;
7399
- const branches = execFileSync8("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
7667
+ const branches = execFileSync9("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
7400
7668
  for (const b of branches) {
7401
7669
  try {
7402
- const unpushed = execFileSync8("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
7670
+ const unpushed = execFileSync9("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
7403
7671
  if (unpushed)
7404
7672
  return true;
7405
7673
  } catch {
@@ -7412,7 +7680,7 @@ function repoHasActivity(repoPath) {
7412
7680
  return false;
7413
7681
  }
7414
7682
  function findActivityInReposDir(reposDir) {
7415
- if (!existsSync13(reposDir))
7683
+ if (!existsSync14(reposDir))
7416
7684
  return null;
7417
7685
  let names = [];
7418
7686
  try {
@@ -7422,7 +7690,7 @@ function findActivityInReposDir(reposDir) {
7422
7690
  }
7423
7691
  for (const name of names) {
7424
7692
  const repoPath = join17(reposDir, name);
7425
- if (!existsSync13(join17(repoPath, ".git")))
7693
+ if (!existsSync14(join17(repoPath, ".git")))
7426
7694
  continue;
7427
7695
  if (repoHasActivity(repoPath))
7428
7696
  return repoPath;
@@ -7439,14 +7707,14 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
7439
7707
  }
7440
7708
  const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join17(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
7441
7709
  const legacyReposDir = join17(taskRoot, ".morula", "repos");
7442
- if (existsSync13(legacyReposDir)) {
7710
+ if (existsSync14(legacyReposDir)) {
7443
7711
  try {
7444
7712
  candidates.push(...readdirSync4(legacyReposDir).map((n) => join17(legacyReposDir, n)));
7445
7713
  } catch {
7446
7714
  }
7447
7715
  }
7448
7716
  for (const repoPath of candidates) {
7449
- if (!existsSync13(join17(repoPath, ".git")))
7717
+ if (!existsSync14(join17(repoPath, ".git")))
7450
7718
  continue;
7451
7719
  if (repoHasActivity(repoPath))
7452
7720
  return repoPath;
@@ -7455,7 +7723,7 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
7455
7723
  }
7456
7724
  function cleanupInjectedSkills(dir) {
7457
7725
  const injectedSkillsRoot = join17(dir, ".agents", "skills");
7458
- if (!existsSync13(injectedSkillsRoot))
7726
+ if (!existsSync14(injectedSkillsRoot))
7459
7727
  return;
7460
7728
  try {
7461
7729
  for (const name of readdirSync4(injectedSkillsRoot)) {
@@ -7482,10 +7750,10 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
7482
7750
  if (!e.isDirectory() || e.name.startsWith("."))
7483
7751
  continue;
7484
7752
  const repoPath = join17(taskRoot, e.name);
7485
- if (repoPath === excludeDir || !existsSync13(join17(repoPath, ".git")))
7753
+ if (repoPath === excludeDir || !existsSync14(join17(repoPath, ".git")))
7486
7754
  continue;
7487
7755
  try {
7488
- const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
7756
+ const commonDir = execFileSync9("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
7489
7757
  if (commonDir)
7490
7758
  dirs.add(commonDir);
7491
7759
  } catch {
@@ -7511,18 +7779,18 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
7511
7779
  }
7512
7780
  for (const dir of [repoCachePath, ...siblingCommonDirs]) {
7513
7781
  try {
7514
- execFileSync8("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
7782
+ execFileSync9("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
7515
7783
  } catch {
7516
7784
  }
7517
7785
  }
7518
7786
  }
7519
7787
  function removeWorktree(repoCachePath, worktreePath) {
7520
- if (!existsSync13(worktreePath))
7788
+ if (!existsSync14(worktreePath))
7521
7789
  return;
7522
7790
  try {
7523
7791
  cleanupInjectedSkills(worktreePath);
7524
- execFileSync8("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
7525
- execFileSync8("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
7792
+ execFileSync9("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
7793
+ execFileSync9("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
7526
7794
  } catch {
7527
7795
  throw new Error("GIT_REMOVE_FAILED");
7528
7796
  }
@@ -7618,8 +7886,8 @@ var LocalRepoCheckoutService = class {
7618
7886
  this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
7619
7887
  try {
7620
7888
  const dir = join18(workdir, ".morula");
7621
- mkdirSync11(dir, { recursive: true, mode: 448 });
7622
- writeFileSync9(join18(dir, "checkout.json"), JSON.stringify({
7889
+ mkdirSync12(dir, { recursive: true, mode: 448 });
7890
+ writeFileSync10(join18(dir, "checkout.json"), JSON.stringify({
7623
7891
  taskId,
7624
7892
  port: this.boundPort,
7625
7893
  checkToken
@@ -7687,12 +7955,12 @@ var LocalRepoCheckoutService = class {
7687
7955
  ref: req.ref,
7688
7956
  env
7689
7957
  });
7690
- const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7958
+ const headSha = execFileSync10("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7691
7959
  return {
7692
7960
  ok: true,
7693
7961
  path: worktreePath,
7694
7962
  headSha,
7695
- branchName: execFileSync9("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
7963
+ branchName: execFileSync10("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
7696
7964
  };
7697
7965
  } catch (e) {
7698
7966
  return { ok: false, code: "CHECKOUT_FAILED", error: e instanceof Error ? e.message : String(e) };
@@ -7754,7 +8022,7 @@ var LocalRepoCheckoutService = class {
7754
8022
  env
7755
8023
  });
7756
8024
  syncGitIdentity(task.primaryRepoDir ?? task.workdir, targetPath, env);
7757
- const headSha = execFileSync9("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
8025
+ const headSha = execFileSync10("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7758
8026
  this.recordPreparedRepo(req.taskId, repoFullName, {
7759
8027
  workContextId: serverRes.workContextId,
7760
8028
  baseBranch: baseBranch || void 0,
@@ -7786,11 +8054,11 @@ function isReadOnlyRepoDir(dirName) {
7786
8054
  }
7787
8055
  function resolveRepoTargetDir(taskRoot, base) {
7788
8056
  const direct = join18(taskRoot, base);
7789
- if (!existsSync14(direct) || existsSync14(join18(direct, ".git")))
8057
+ if (!existsSync15(direct) || existsSync15(join18(direct, ".git")))
7790
8058
  return direct;
7791
8059
  for (let i = 2; i < 100; i++) {
7792
8060
  const candidate = join18(taskRoot, `${base}-${i}`);
7793
- if (!existsSync14(candidate))
8061
+ if (!existsSync15(candidate))
7794
8062
  return candidate;
7795
8063
  }
7796
8064
  throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
@@ -7798,16 +8066,16 @@ function resolveRepoTargetDir(taskRoot, base) {
7798
8066
  function materializeReadWorktree(input) {
7799
8067
  const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
7800
8068
  const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
7801
- if (existsSync14(join18(worktreePath, ".git"))) {
8069
+ if (existsSync15(join18(worktreePath, ".git"))) {
7802
8070
  const current = currentBranchName(worktreePath);
7803
8071
  if (current)
7804
8072
  throw new Error(`READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5DF2\u5207\u5230\u5206\u652F ${current}\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09`);
7805
8073
  if (hasLocalChanges2(worktreePath, input.env))
7806
8074
  throw new Error("READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5B58\u5728\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09");
7807
- execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
8075
+ execFileSync10("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
7808
8076
  } else {
7809
- mkdirSync11(join18(worktreePath, ".."), { recursive: true });
7810
- execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
8077
+ mkdirSync12(join18(worktreePath, ".."), { recursive: true });
8078
+ execFileSync10("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
7811
8079
  env: input.env,
7812
8080
  stdio: "pipe"
7813
8081
  });
@@ -7833,7 +8101,7 @@ function resolveReadRef(repoCachePath, env, requestedRef, baseBranch) {
7833
8101
  }
7834
8102
  const remoteHead = (() => {
7835
8103
  try {
7836
- const out = execFileSync9("git", ["-C", repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
8104
+ const out = execFileSync10("git", ["-C", repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7837
8105
  return out.replace(/^refs\/remotes\//, "");
7838
8106
  } catch {
7839
8107
  return null;
@@ -7848,7 +8116,7 @@ function resolveReadRef(repoCachePath, env, requestedRef, baseBranch) {
7848
8116
  }
7849
8117
  function refExists(repoPath, ref, env) {
7850
8118
  try {
7851
- execFileSync9("git", ["-C", repoPath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
8119
+ execFileSync10("git", ["-C", repoPath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
7852
8120
  return true;
7853
8121
  } catch {
7854
8122
  return false;
@@ -7856,7 +8124,7 @@ function refExists(repoPath, ref, env) {
7856
8124
  }
7857
8125
  function currentBranchName(repoPath) {
7858
8126
  try {
7859
- const out = execFileSync9("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf8", stdio: "pipe" }).trim();
8127
+ const out = execFileSync10("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf8", stdio: "pipe" }).trim();
7860
8128
  return out || null;
7861
8129
  } catch {
7862
8130
  return null;
@@ -7864,7 +8132,7 @@ function currentBranchName(repoPath) {
7864
8132
  }
7865
8133
  function hasLocalChanges2(repoPath, env) {
7866
8134
  try {
7867
- const out = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", env, stdio: "pipe" }).trim();
8135
+ const out = execFileSync10("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", env, stdio: "pipe" }).trim();
7868
8136
  return out.length > 0;
7869
8137
  } catch {
7870
8138
  return true;
@@ -7878,7 +8146,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
7878
8146
  } catch {
7879
8147
  return [];
7880
8148
  }
7881
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join18(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync14(join18(dir, ".git"))).sort();
8149
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join18(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync15(join18(dir, ".git"))).sort();
7882
8150
  }
7883
8151
  function parseRepoFullNameFromRemoteUrl(url) {
7884
8152
  const raw = url?.trim();
@@ -7909,7 +8177,7 @@ function collectMaterializedRepoDeliveryTargets(input) {
7909
8177
  recordsByDir.set(record.dir, entry);
7910
8178
  recordsByDirName.set(safeRepoDirName(repoFullName), entry);
7911
8179
  }
7912
- const runGit = input.gitRunner ?? ((dir, args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
8180
+ const runGit = input.gitRunner ?? ((dir, args) => execFileSync10("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
7913
8181
  const targets = [];
7914
8182
  for (const dir of listMaterializedRepoDirs(input.taskRoot, { excludeDirs: excludedDirs })) {
7915
8183
  const known = recordsByDir.get(dir) ?? recordsByDirName.get(basename2(dir));
@@ -7946,7 +8214,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
7946
8214
  const read = (key) => {
7947
8215
  for (const scope of ["--worktree", "--local"]) {
7948
8216
  try {
7949
- const out = execFileSync9("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
8217
+ const out = execFileSync10("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
7950
8218
  if (out)
7951
8219
  return out;
7952
8220
  } catch {
@@ -7957,17 +8225,17 @@ function syncGitIdentity(primaryDir, targetDir, env) {
7957
8225
  const name = read("user.name") ?? "morula-daemon[bot]";
7958
8226
  const email = read("user.email") ?? "morula-daemon@morula.local";
7959
8227
  try {
7960
- execFileSync9("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
7961
- execFileSync9("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
8228
+ execFileSync10("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
8229
+ execFileSync10("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
7962
8230
  } catch {
7963
8231
  }
7964
8232
  }
7965
8233
  function materializeAdditionalRepo(input) {
7966
- if (existsSync14(join18(input.targetPath, ".git"))) {
8234
+ if (existsSync15(join18(input.targetPath, ".git"))) {
7967
8235
  const current = currentBranchName(input.targetPath);
7968
8236
  if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
7969
8237
  const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
7970
- execFileSync9("git", ["-C", input.targetPath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
8238
+ execFileSync10("git", ["-C", input.targetPath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
7971
8239
  }
7972
8240
  return input.targetPath;
7973
8241
  }
@@ -7975,7 +8243,7 @@ function materializeAdditionalRepo(input) {
7975
8243
  let baseRef = null;
7976
8244
  for (const ref of candidates) {
7977
8245
  try {
7978
- execFileSync9("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
8246
+ execFileSync10("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
7979
8247
  baseRef = ref;
7980
8248
  break;
7981
8249
  } catch {
@@ -7983,8 +8251,8 @@ function materializeAdditionalRepo(input) {
7983
8251
  }
7984
8252
  if (!baseRef)
7985
8253
  throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
7986
- mkdirSync11(join18(input.targetPath, ".."), { recursive: true });
7987
- execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
8254
+ mkdirSync12(join18(input.targetPath, ".."), { recursive: true });
8255
+ execFileSync10("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
7988
8256
  env: input.env,
7989
8257
  stdio: "pipe"
7990
8258
  });
@@ -7994,7 +8262,7 @@ function materializeAdditionalRepo(input) {
7994
8262
  // src/daemon/session-host.ts
7995
8263
  init_dist2();
7996
8264
  init_acp_connection();
7997
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
8265
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "node:fs";
7998
8266
  import { join as join19 } from "node:path";
7999
8267
 
8000
8268
  // src/daemon/steward-instructions.ts
@@ -8073,8 +8341,8 @@ var STEWARD_SKILL_BODY = `# \u7A7A\u95F4\u7BA1\u5BB6\u5951\u7EA6\uFF08Morula\uFF
8073
8341
  - **\u4E0D\u8981 @ \u8FD0\u884C\u65F6\uFF08\u667A\u80FD\u4F53\uFF09**\uFF1A\u8BC4\u8BBA\u91CC @ \u8FD0\u884C\u65F6\u662F\u300C\u89E6\u53D1\u67D0\u4E2A\u9636\u6BB5\u6267\u884C\u300D\u7684\u901A\u9053\uFF0C\u5C5E\u6D41\u7A0B\u63A7\u5236\uFF1B\u6D41\u7A0B\u63A7\u5236\u4E00\u5F8B\u8D70\u6D3E\u5355\u4E0E\u72B6\u6001\u63A8\u8FDB\uFF08\u672C\u4F1A\u8BDD\u672A\u6388\u6743\u8BE5\u7528\u6CD5\uFF0C\u89C1 features/steward-task-management.md \xA73.4\uFF09\u3002
8074
8342
  - **\u6240\u6709\u67E5\u8BE2\u90FD\u8981\u663E\u5F0F\u5E26 \`--space\`**\uFF08= \u5F53\u524D\u7A7A\u95F4 ID\uFF0C\u89C1 \`.agent_context/steward-context.md\`\uFF09\u3002
8075
8343
  - \u65B0\u9700\u6C42**\u4E00\u5F8B\u8D70\u6D3E\u5355**\uFF0C\u4E0D\u8981\u81EA\u5DF1\u52A8\u624B\u5B9E\u73B0\uFF0C\u4E5F\u4E0D\u8981\u7528\u300C\u59D4\u6258\u5B50\u4EE3\u7406\u300D\u7C7B\u624B\u6BB5\u7ED5\u5F00\u6D3E\u5355\u94FE\u8DEF\u3002
8076
- - **\u7834\u574F\u6027\u52A8\u4F5C\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u5220\u9664\u4EFB\u52A1\u3001\u53D6\u6D88\u6B63\u5728\u8DD1\u7684\u6267\u884C\u3001\u91CD\u8DD1\u5931\u8D25\u4EFB\u52A1\u3001**\u91CD\u8DD1\u5931\u8D25\u7684 CI \u6D41\u6C34\u7EBF / job**\u3001\u628A\u72B6\u6001\u63A8\u8FDB\u5230\u300C\u5DF2\u5408\u5E76 / \u5DF2\u53D1\u5E03 / \u5DF2\u5173\u95ED\u300D\u2014\u2014\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u8868\u5355\uFF0C\u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C\u3002\u53EF\u9006\u7684\u8BB0\u5F55\u4E0E\u6D41\u8F6C\uFF08\u6539\u5C5E\u6027\u3001\u6539\u8D1F\u8D23\u4EBA\u3001\`backlog \u21C4 todo\` \u63A8\u8FDB\uFF09\u4E0D\u9700\u8981\u786E\u8BA4\u3002
8077
- - **\u63A2\u67E5\u547D\u4EE4\u9762\u53EA\u7528 \`--help\`\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A\u60F3\u786E\u8BA4\u67D0\u6761\u547D\u4EE4\u6216\u53C2\u6570\u5B58\u4E0D\u5B58\u5728\u3001\u5F62\u6001\u5BF9\u4E0D\u5BF9\uFF0C\u5C31\u770B \`$MORULA_CLI <cmd> --help\`\uFF08\u4F8B\u5982 \`$MORULA_CLI ci retry --help\`\uFF09\u3002\u5199\u547D\u4EE4**\u6267\u884C\u5373\u751F\u6548**\uFF08\`morula ci retry\` \u6267\u884C\u5373\u91CD\u8DD1\u3001\`morula task delete\` \u6267\u884C\u5373\u5220\u9664\uFF09\uFF0C\u6CA1\u6709\u300C\u53EA\u8BFB\u63A2\u67E5\u300D\u5F62\u6001\u2014\u2014\u4E3A\u300C\u770B\u770B\u8FD9\u6761\u547D\u4EE4\u5728\u4E0D\u5728\u300D\u800C\u771F\u8DD1\u4E00\u6B21\u5199\u547D\u4EE4\uFF0C\u5C31\u662F\u4E00\u6B21\u672A\u7ECF\u786E\u8BA4\u7684\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u771F\u673A\u4E8B\u6545\uFF1A\u7BA1\u5BB6\u786E\u8BA4 \`ci retry\` \u662F\u5426\u5B58\u5728\u65F6\u771F\u7684\u91CD\u8DD1\u4E86\u4E00\u6761\u6D41\u6C34\u7EBF\uFF09\u3002
8344
+ - **\u7834\u574F\u6027\u52A8\u4F5C\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u5220\u9664\u4EFB\u52A1\u3001\u53D6\u6D88\u6B63\u5728\u8DD1\u7684\u6267\u884C\u3001\u91CD\u8DD1\u5931\u8D25\u4EFB\u52A1\u3001\u628A\u72B6\u6001\u63A8\u8FDB\u5230\u300C\u5DF2\u5408\u5E76 / \u5DF2\u53D1\u5E03 / \u5DF2\u5173\u95ED\u300D\u2014\u2014\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u8868\u5355\uFF0C\u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C\u3002\u53EF\u9006\u7684\u8BB0\u5F55\u4E0E\u6D41\u8F6C\uFF08\u6539\u5C5E\u6027\u3001\u6539\u8D1F\u8D23\u4EBA\u3001\`backlog \u21C4 todo\` \u63A8\u8FDB\uFF09\u4E0D\u9700\u8981\u786E\u8BA4\u3002
8345
+ - **\u63A2\u67E5\u547D\u4EE4\u9762\u53EA\u7528 \`--help\`\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A\u60F3\u786E\u8BA4\u67D0\u6761\u547D\u4EE4\u6216\u53C2\u6570\u5B58\u4E0D\u5B58\u5728\u3001\u5F62\u6001\u5BF9\u4E0D\u5BF9\uFF0C\u5C31\u770B \`$MORULA_CLI <cmd> --help\`\uFF08\u4F8B\u5982 \`$MORULA_CLI task delete --help\`\uFF09\u3002\u5199\u547D\u4EE4**\u6267\u884C\u5373\u751F\u6548**\uFF08\`morula task delete\` \u6267\u884C\u5373\u5220\u9664\uFF09\uFF0C\u6CA1\u6709\u300C\u53EA\u8BFB\u63A2\u67E5\u300D\u5F62\u6001\u2014\u2014\u4E3A\u300C\u770B\u770B\u8FD9\u6761\u547D\u4EE4\u5728\u4E0D\u5728\u300D\u800C\u771F\u8DD1\u4E00\u6B21\u5199\u547D\u4EE4\uFF0C\u5C31\u662F\u4E00\u6B21\u672A\u7ECF\u786E\u8BA4\u7684\u4E0D\u53EF\u9006\u64CD\u4F5C\u3002
8078
8346
 
8079
8347
  ## 1. \u5E38\u7528\u547D\u4EE4\uFF08\u5168\u90E8\u663E\u5F0F\u53C2\u6570\uFF09
8080
8348
 
@@ -8098,17 +8366,11 @@ morula ci status --space <spaceId> [--ref <branch>] [--failed] [--limit <n>] #
8098
8366
  morula ci deployments --space <spaceId> # \u90E8\u7F72\u8BB0\u5F55\uFF08\u53EA\u8BFB\u5C55\u793A\uFF09
8099
8367
  morula ci log --space <spaceId> --job <jobId> [--tail <n>] # \u67D0\u4E2A job \u7684\u65E5\u5FD7\uFF08\u670D\u52A1\u7AEF\u5C3E\u90E8\u622A\u65AD\uFF1Btruncated=true \u8868\u793A\u53EA\u7ED9\u4E86\u672B\u5C3E\uFF09
8100
8368
  morula ci test-report --space <spaceId> [--run <runId>] # \u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u5E73\u53F0\u4E0D\u652F\u6301\u65F6\u660E\u786E\u62A5\u9519\uFF09
8101
- morula ci retry --space <spaceId> [--run <runId>] [--job <jobId>] # \u91CD\u8DD1\u5931\u8D25\u6D41\u6C34\u7EBF\uFF08\u7F3A\u7701 = \u6700\u65B0\u4E00\u6761\u5931\u8D25\u8FD0\u884C\uFF09\uFF1B**\u5FC5\u987B\u5148\u53D1\u786E\u8BA4\u5361**
8102
8369
  \`\`\`
8103
8370
 
8104
- - **\u53EA\u8BFB + \u91CD\u8DD1**\uFF1A\u4F60\u80FD\u770B\u6D41\u6C34\u7EBF\u4E8B\u5B9E\uFF08\u72B6\u6001 / \u9636\u6BB5 / job / \u8017\u65F6 / \u5931\u8D25\u539F\u56E0 / \u65E5\u5FD7\uFF09\uFF0C\u4E5F\u80FD**\u91CD\u8DD1\u5931\u8D25\u7684\u6D41\u6C34\u7EBF\u6216\u5355\u4E2A job**\uFF08\u8D70\u786E\u8BA4\u5361\uFF09\uFF1B
8105
- **\u4E0D\u80FD\u89E6\u53D1\u90E8\u7F72**\uFF08\u89E6\u53D1\u624B\u52A8 job \u6C38\u8FDC\u7531\u4EBA\u505A\uFF1A\u4F60\u6CA1\u6709\u8BE5\u547D\u4EE4\uFF0C\u4E5F\u4E0D\u8981\u8BD5\u56FE\u7ED5\u8FC7\uFF09\u3002
8106
- - **\u91CD\u8DD1\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u5361\uFF08\u5199\u6E05\u54EA\u6761\u6D41\u6C34\u7EBF / \u54EA\u4E2A job\u3001\u5931\u8D25\u539F\u56E0\u3001\u4E3A\u4EC0\u4E48\u5EFA\u8BAE\u91CD\u8DD1\uFF09\uFF0C
8107
- \u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C \`morula ci retry\`\u3002\u5B83\u4E0E \`morula task rerun\` \u540C\u6863\uFF08\u300C\u9700\u4EBA\u786E\u8BA4\u300D\uFF09\u3002
8108
- - **\u786E\u8BA4\u547D\u4EE4\u9762\u7528 \`$MORULA_CLI ci --help\`\uFF08\u6216 \`ci retry --help\`\uFF09\uFF0C\u4E0D\u8981\u7528\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A
8109
- \`morula ci retry\` \u6CA1\u6709\u53EA\u8BFB\u9884\u6F14\u5F62\u6001\uFF0C\u6267\u884C\u5C31\u662F\u4E00\u6B21\u771F\u5B9E\u91CD\u8DD1\uFF1B\u53C2\u6570\u5F62\u6001\u4E0D\u786E\u5B9A\u5C31\u770B \`--help\` \u8F93\u51FA\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u8FD0\u884C\u53BB\u8BD5\u3002
8110
- - \u91CD\u8DD1\u53EA\u5BF9**\u5931\u8D25\u6216\u5DF2\u53D6\u6D88**\u7684\u8FD0\u884C / job \u6709\u6548\uFF1B\u72B6\u6001\u4E0D\u7B26\u4F1A\u660E\u786E\u62A5\u9519\uFF08\`CI_RETRY_NOT_ALLOWED\`\uFF09\uFF0C\u4E0D\u8981\u53CD\u590D\u91CD\u8BD5\u540C\u6837\u7684\u8C03\u7528\u3002
8111
- - \u91CD\u8DD1\u540E**\u56DE\u8BFB\u4E00\u6B21**\uFF08\`morula ci status --space <id>\`\uFF09\u518D\u6C47\u62A5\u2014\u2014\u300C\u5DF2\u91CD\u8DD1\u300D\u8981\u7528\u72B6\u6001\u8BF4\u8BDD\uFF0C\u4E0D\u80FD\u53EA\u770B\u9000\u51FA\u7801\u3002
8371
+ - **CI \u9762\u677F\u7EAF\u53EA\u8BFB**\uFF082026-09-22 \u5B9A\u6848\uFF09\uFF1A\u4F60\u80FD\u770B\u6D41\u6C34\u7EBF\u4E8B\u5B9E\uFF08\u72B6\u6001 / \u9636\u6BB5 / job / \u8017\u65F6 / \u5931\u8D25\u539F\u56E0 / \u65E5\u5FD7\uFF09\uFF0C
8372
+ **\u6CA1\u6709\u4EFB\u4F55\u6D41\u6C34\u7EBF\u5199\u64CD\u4F5C**\u2014\u2014\u4E0D\u63D0\u4F9B\u91CD\u8DD1\u3001\u4E5F\u4E0D\u80FD\u89E6\u53D1\u90E8\u7F72\uFF1B\u9700\u8981\u64CD\u4F5C\u6D41\u6C34\u7EBF\uFF08\u91CD\u8DD1 / \u89E6\u53D1\uFF09\u65F6\uFF0C
8373
+ \u5F15\u5BFC\u7528\u6237\u76F4\u63A5\u5728 VCS \u5E73\u53F0\uFF08GitLab/GitHub\uFF09\u4E0A\u5B8C\u6210\u3002
8112
8374
  - \u6D41\u6C34\u7EBF\u72B6\u6001\u662F**\u5E73\u53F0\u4E8B\u5B9E**\uFF08\u6765\u81EA VCS \u5E73\u53F0\u4E8B\u4EF6\uFF09\uFF0C\u4E0D\u662F\u67D0\u4E2A agent \u7684\u81EA\u8FF0\uFF1B\u88C1\u5B9A\u4E0E\u6C47\u62A5\u4EE5\u5B83\u4E3A\u51C6\u3002
8113
8375
  - \u300C\u5DF2\u53D1\u5E03\u300D\u7531**\u751F\u4EA7\u5206\u652F\u6D41\u6C34\u7EBF\u8DD1\u6210\u529F**\u9A71\u52A8\uFF0C\u90E8\u7F72\u8BB0\u5F55\u53EA\u4F5C\u5C55\u793A\u3001\u4E0D\u9A71\u52A8\u6D41\u8F6C\u3002
8114
8376
  - \u7A7A\u95F4\u6CA1\u7ED1\u4ED3\u5E93\u3001\u6216\u4ED3\u5E93\u6CA1\u6709 CI \u914D\u7F6E\u65F6\uFF0C\u547D\u4EE4\u8FD4\u56DE\u7A7A\u7ED3\u679C\u5E76\u63D0\u793A\u539F\u56E0\uFF08\`CI_NOT_CONFIGURED\`\uFF09\u2014\u2014\u8FD9\u4E0D\u662F\u6545\u969C\uFF0C\u5982\u5B9E\u8F6C\u8FF0\u3002
@@ -8256,7 +8518,7 @@ var SessionHost = class {
8256
8518
  if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
8257
8519
  return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
8258
8520
  const workspaceDir = join19(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
8259
- mkdirSync12(workspaceDir, { recursive: true });
8521
+ mkdirSync13(workspaceDir, { recursive: true });
8260
8522
  this.writeInjections(workspaceDir, cmd);
8261
8523
  const existing = this.sessions.get(cmd.sessionId);
8262
8524
  if (existing?.conn.isAlive) {
@@ -8316,7 +8578,10 @@ var SessionHost = class {
8316
8578
  // 注意:dim 的权限引擎决定 allow/deny/ask,client 无法强制——`permission` 只选 preset。
8317
8579
  onPermissionRequest: async () => ({ outcome: { outcome: "selected", optionId: "allow" } }),
8318
8580
  onElicitation: async (request) => this.handleElicitation(cmd.sessionId, request),
8319
- onExit
8581
+ onExit,
8582
+ // 进程回收留痕走 daemon 日志(issues/dim-acp-process-leak-shared-space-contention.md:
8583
+ // 旧实现回收完全不可见,进程没死也无人知晓)
8584
+ log: (line) => this.log(line)
8320
8585
  };
8321
8586
  const conn = this.options.acpConnectionFactory ? this.options.acpConnectionFactory(connOptions) : new AcpConnection(connOptions);
8322
8587
  try {
@@ -8329,29 +8594,37 @@ var SessionHost = class {
8329
8594
  let resumed = false;
8330
8595
  let resumeFailed = null;
8331
8596
  let configOptions;
8332
- if (cmd.dimSessionId) {
8333
- try {
8334
- const r = await conn.resumeSession({ ...sessionParams, sessionId: cmd.dimSessionId });
8335
- configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
8336
- dimSessionId = cmd.dimSessionId;
8337
- resumed = true;
8338
- } catch {
8597
+ try {
8598
+ if (cmd.dimSessionId) {
8339
8599
  try {
8340
- const r = await conn.loadSession({ ...sessionParams, sessionId: cmd.dimSessionId });
8600
+ const r = await conn.resumeSession({ ...sessionParams, sessionId: cmd.dimSessionId });
8341
8601
  configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
8342
8602
  dimSessionId = cmd.dimSessionId;
8343
8603
  resumed = true;
8344
- } catch (err) {
8345
- resumeFailed = err instanceof Error ? err.message : String(err);
8346
- const created = await conn.newSession(sessionParams);
8347
- configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
8348
- dimSessionId = created.sessionId;
8604
+ } catch {
8605
+ try {
8606
+ const r = await conn.loadSession({ ...sessionParams, sessionId: cmd.dimSessionId });
8607
+ configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
8608
+ dimSessionId = cmd.dimSessionId;
8609
+ resumed = true;
8610
+ } catch (err) {
8611
+ resumeFailed = err instanceof Error ? err.message : String(err);
8612
+ const created = await conn.newSession(sessionParams);
8613
+ configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
8614
+ dimSessionId = created.sessionId;
8615
+ }
8349
8616
  }
8617
+ } else {
8618
+ const created = await conn.newSession(sessionParams);
8619
+ configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
8620
+ dimSessionId = created.sessionId;
8350
8621
  }
8351
- } else {
8352
- const created = await conn.newSession(sessionParams);
8353
- configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
8354
- dimSessionId = created.sessionId;
8622
+ } catch (err) {
8623
+ const message = err instanceof Error ? err.message : String(err);
8624
+ await conn.close().catch(() => {
8625
+ });
8626
+ this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u5EFA\u7ACB\u5931\u8D25\uFF08dim acp \u8FDB\u7A0B\u5DF2\u56DE\u6536\uFF09: ${message}`);
8627
+ return { ok: false, error: `dim acp \u4F1A\u8BDD\u5EFA\u7ACB\u5931\u8D25: ${message}` };
8355
8628
  }
8356
8629
  const appliedConfig = this.configFrom(cmd);
8357
8630
  const permission = appliedConfig.permission || "workspace-write";
@@ -8606,9 +8879,9 @@ ${fileRefs.join("\n")}` : text }];
8606
8879
  if (attachment.isImage) {
8607
8880
  blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
8608
8881
  } else {
8609
- mkdirSync12(dir, { recursive: true });
8882
+ mkdirSync13(dir, { recursive: true });
8610
8883
  const target = join19(dir, safeAttachmentName(attachment.fileName));
8611
- writeFileSync10(target, bytes);
8884
+ writeFileSync11(target, bytes);
8612
8885
  fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
8613
8886
  }
8614
8887
  } catch (err) {
@@ -8701,11 +8974,11 @@ ${fileRefs.join("\n")}` : text }];
8701
8974
  writeInjections(workspaceDir, cmd) {
8702
8975
  writeProjectAgentsMd(workspaceDir, cmd.agentsMd, this.log);
8703
8976
  const skillDir = join19(workspaceDir, ".agents", "skills", STEWARD_SKILL_NAME);
8704
- mkdirSync12(skillDir, { recursive: true });
8705
- writeFileSync10(join19(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
8977
+ mkdirSync13(skillDir, { recursive: true });
8978
+ writeFileSync11(join19(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
8706
8979
  const contextDir = join19(workspaceDir, ".agent_context");
8707
- mkdirSync12(contextDir, { recursive: true });
8708
- writeFileSync10(
8980
+ mkdirSync13(contextDir, { recursive: true });
8981
+ writeFileSync11(
8709
8982
  join19(contextDir, "steward-context.md"),
8710
8983
  buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }),
8711
8984
  "utf8"
@@ -9222,6 +9495,7 @@ var DaemonRunner = class {
9222
9495
  this.dimPath = options.dimPath ?? resolveDimBinary();
9223
9496
  this.model = options.model ?? DEFAULT_MODEL;
9224
9497
  this.log = options.log ?? ((line) => console.log(line));
9498
+ this.acpChildRecordFile = options.acpChildRecordFile ?? null;
9225
9499
  }
9226
9500
  client;
9227
9501
  token;
@@ -9277,6 +9551,13 @@ var DaemonRunner = class {
9277
9551
  sessionHost = null;
9278
9552
  /** LOCAL-AGENT-REPO-READ-001:本地 repo checkout 端点(agent 本轮内读仓库) */
9279
9553
  repoCheckout = null;
9554
+ /**
9555
+ * dim acp 子进程记录文件(issues/dim-acp-process-leak-shared-space-contention.md);
9556
+ * null = 未开启子进程治理(测试直连 runner 的缺省)。
9557
+ */
9558
+ acpChildRecordFile;
9559
+ /** dim acp 孤儿巡检定时器(stop 时清除;unref 不钉住进程) */
9560
+ acpSweepTimer = null;
9280
9561
  async start() {
9281
9562
  if (this.running)
9282
9563
  return;
@@ -9307,7 +9588,8 @@ var DaemonRunner = class {
9307
9588
  this.acpPool = new AcpConnectionPool({
9308
9589
  dimPath: this.dimPath,
9309
9590
  cwd: cfg2?.workdir ?? process.cwd(),
9310
- env: this.isolatedDimEnv
9591
+ env: this.isolatedDimEnv,
9592
+ log: (line) => this.log(line)
9311
9593
  });
9312
9594
  this.log("[daemon] dim executor: acp-v1\uFF08ACP-only\uFF0C\u957F\u9A7B dim acp \u8FDB\u7A0B\uFF09");
9313
9595
  this.sessionHost = new SessionHost({
@@ -9319,6 +9601,19 @@ var DaemonRunner = class {
9319
9601
  log: this.log
9320
9602
  });
9321
9603
  this.log(`[daemon] session host: ready\uFF08\u7A7A\u95F2\u56DE\u6536 ${Math.round(resolveSessionIdleMs() / 6e4)}min\uFF0CMORULA_SESSION_IDLE_MS \u8986\u76D6\uFF09`);
9604
+ if (this.acpChildRecordFile) {
9605
+ enableDimAcpChildRecordFile(this.acpChildRecordFile);
9606
+ void reapRecordedOrphanDimAcpChildren({ file: this.acpChildRecordFile, log: this.log }).then((result) => {
9607
+ if (result.reaped > 0)
9608
+ this.log(`[daemon] \u542F\u52A8\u81EA\u6108\uFF1A\u56DE\u6536\u4E0A\u4E00\u4EE3\u9057\u7559\u7684 ${result.reaped} \u4E2A dim acp \u5B64\u513F\u8FDB\u7A0B`);
9609
+ }).catch(() => {
9610
+ });
9611
+ this.acpSweepTimer = setInterval(() => {
9612
+ void reapOrphanDimAcpChildren({ log: this.log }).catch(() => {
9613
+ });
9614
+ }, ACP_SWEEP_INTERVAL_MS);
9615
+ this.acpSweepTimer.unref?.();
9616
+ }
9322
9617
  this.repoCheckout = new LocalRepoCheckoutService({
9323
9618
  api: this.api,
9324
9619
  cfg: cfg2 ?? loadDaemonConfig(this.store),
@@ -9398,6 +9693,10 @@ var DaemonRunner = class {
9398
9693
  clearTimeout(this.outboxTimer);
9399
9694
  this.outboxTimer = null;
9400
9695
  }
9696
+ if (this.acpSweepTimer) {
9697
+ clearInterval(this.acpSweepTimer);
9698
+ this.acpSweepTimer = null;
9699
+ }
9401
9700
  this.ws.close();
9402
9701
  if (this.acpPool) {
9403
9702
  void this.acpPool.closeAll().catch(() => {
@@ -10144,10 +10443,10 @@ var DaemonRunner = class {
10144
10443
  env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
10145
10444
  repoDir = join20(cfg.workdir, "scratch", taskId);
10146
10445
  taskRoot = repoDir;
10147
- mkdirSync13(repoDir, { recursive: true });
10446
+ mkdirSync14(repoDir, { recursive: true });
10148
10447
  const readme = join20(repoDir, "README.md");
10149
- if (!existsSync15(readme)) {
10150
- writeFileSync11(
10448
+ if (!existsSync16(readme)) {
10449
+ writeFileSync12(
10151
10450
  readme,
10152
10451
  `# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
10153
10452
 
@@ -10445,7 +10744,7 @@ var DaemonRunner = class {
10445
10744
  }, { log: (line) => this.log(`[daemon] ${line}`) });
10446
10745
  const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
10447
10746
  const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
10448
- const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
10747
+ const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync16(resumeWorkDir);
10449
10748
  const input = {
10450
10749
  executionId: `${taskId}:1`,
10451
10750
  // task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
@@ -10639,7 +10938,7 @@ var DaemonRunner = class {
10639
10938
  const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
10640
10939
  try {
10641
10940
  const execEnv = mergeExecEnv(env);
10642
- execFileSync10("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
10941
+ execFileSync11("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
10643
10942
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5206\u652F\u5DF2\u8865\u63A8\u9001: ${branch}`);
10644
10943
  const listRes = await fetch(
10645
10944
  `${baseUrl}/api/v4/projects/${projectPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened`,
@@ -10762,30 +11061,30 @@ function writeCustomSkills(targetDir, skills, log) {
10762
11061
  continue;
10763
11062
  }
10764
11063
  const dir = join20(base, name);
10765
- if (dirname8(dir) !== base) {
11064
+ if (dirname9(dir) !== base) {
10766
11065
  log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
10767
11066
  continue;
10768
11067
  }
10769
- mkdirSync13(dir, { recursive: true });
11068
+ mkdirSync14(dir, { recursive: true });
10770
11069
  const meta = ["---", `name: ${name}`];
10771
11070
  if (skill.description?.trim()) {
10772
11071
  meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
10773
11072
  }
10774
11073
  meta.push("---", "");
10775
- writeFileSync11(join20(dir, "SKILL.md"), `${meta.join("\n")}
11074
+ writeFileSync12(join20(dir, "SKILL.md"), `${meta.join("\n")}
10776
11075
  ${skill.content ?? ""}`, "utf8");
10777
11076
  }
10778
11077
  }
10779
11078
  function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
10780
11079
  const dir = join20(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
10781
11080
  const refsDir = join20(dir, "references");
10782
- mkdirSync13(refsDir, { recursive: true });
11081
+ mkdirSync14(refsDir, { recursive: true });
10783
11082
  const isGitlab = provider === "gitlab" && !!repoBaseUrl;
10784
11083
  const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
10785
11084
  const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
10786
- writeFileSync11(join20(dir, "SKILL.md"), `${front}
11085
+ writeFileSync12(join20(dir, "SKILL.md"), `${front}
10787
11086
  ${body}`, "utf8");
10788
- writeFileSync11(join20(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
11087
+ writeFileSync12(join20(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
10789
11088
  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/`);
10790
11089
  }
10791
11090
  function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
@@ -10817,7 +11116,7 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
10817
11116
  }
10818
11117
  function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
10819
11118
  const briefDir = join20(targetDir, ".agent_context");
10820
- mkdirSync13(briefDir, { recursive: true });
11119
+ mkdirSync14(briefDir, { recursive: true });
10821
11120
  const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
10822
11121
 
10823
11122
  - \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
@@ -10827,7 +11126,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
10827
11126
  ---
10828
11127
 
10829
11128
  ${buildTaskPromptWithContract(detail, void 0)}`;
10830
- writeFileSync11(join20(briefDir, "task-context.md"), brief, "utf8");
11129
+ writeFileSync12(join20(briefDir, "task-context.md"), brief, "utf8");
10831
11130
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
10832
11131
  }
10833
11132
  var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
@@ -10841,15 +11140,15 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
10841
11140
  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`);
10842
11141
  content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
10843
11142
  }
10844
- mkdirSync13(targetDir, { recursive: true });
10845
- writeFileSync11(join20(targetDir, "AGENTS.md"), `${content}
11143
+ mkdirSync14(targetDir, { recursive: true });
11144
+ writeFileSync12(join20(targetDir, "AGENTS.md"), `${content}
10846
11145
  `, "utf8");
10847
11146
  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`);
10848
11147
  return true;
10849
11148
  }
10850
11149
  function gitHead(repoDir) {
10851
11150
  try {
10852
- return execFileSync10("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
11151
+ return execFileSync11("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
10853
11152
  } catch {
10854
11153
  return null;
10855
11154
  }
@@ -10901,7 +11200,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
10901
11200
  }
10902
11201
  async function reportOneRepoTarget(taskId, target, env, ops) {
10903
11202
  const execEnv = mergeExecEnv(env);
10904
- if (!existsSync15(target.dir) || !existsSync15(join20(target.dir, ".git"))) {
11203
+ if (!existsSync16(target.dir) || !existsSync16(join20(target.dir, ".git"))) {
10905
11204
  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`);
10906
11205
  return;
10907
11206
  }
@@ -10916,7 +11215,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
10916
11215
  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`);
10917
11216
  return;
10918
11217
  }
10919
- const headSha = execFileSync10("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
11218
+ const headSha = execFileSync11("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
10920
11219
  const headSubject = headCommitSubject(target.dir, execEnv);
10921
11220
  const probePushState = ops.pushState ?? resolveRemotePushState;
10922
11221
  let dirty = hasLocalChanges(target.dir, execEnv);
@@ -10956,7 +11255,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
10956
11255
  }
10957
11256
  }
10958
11257
  function execGit(repoPath, args, env) {
10959
- return execFileSync10("git", ["-C", repoPath, ...args], {
11258
+ return execFileSync11("git", ["-C", repoPath, ...args], {
10960
11259
  encoding: "utf8",
10961
11260
  stdio: "pipe",
10962
11261
  env,
@@ -10997,7 +11296,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
10997
11296
  if (!taskCode)
10998
11297
  return;
10999
11298
  const execEnv = mergeExecEnv(env);
11000
- const branch = execFileSync10("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
11299
+ const branch = execFileSync11("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
11001
11300
  if (!branch || branch === "main" || branch === "master")
11002
11301
  return;
11003
11302
  if (env.MORULA_GITLAB_URL && env.MORULA_GITLAB_TOKEN) {
@@ -11006,7 +11305,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
11006
11305
  }
11007
11306
  let prList;
11008
11307
  try {
11009
- prList = execFileSync10("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
11308
+ prList = execFileSync11("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
11010
11309
  cwd: repoDir,
11011
11310
  env: execEnv,
11012
11311
  encoding: "utf-8"
@@ -11026,7 +11325,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
11026
11325
  const newBody = `${pr.body ?? ""}
11027
11326
 
11028
11327
  Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4E8E PR \u5173\u8054\u4E0E\u5173\u95ED\u8054\u52A8\uFF09`;
11029
- execFileSync10("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
11328
+ execFileSync11("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
11030
11329
  cwd: repoDir,
11031
11330
  env: execEnv,
11032
11331
  encoding: "utf-8"
@@ -11069,9 +11368,9 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
11069
11368
 
11070
11369
  // src/commands/doctor.ts
11071
11370
  init_dist2();
11072
- import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
11371
+ import { existsSync as existsSync17, readFileSync as readFileSync11, realpathSync } from "node:fs";
11073
11372
  import { homedir as homedir12 } from "node:os";
11074
- import { dirname as dirname9, join as join21 } from "node:path";
11373
+ import { dirname as dirname10, join as join21 } from "node:path";
11075
11374
  init_daemon_config();
11076
11375
  init_daemon_lock();
11077
11376
  init_dim_locate();
@@ -11093,16 +11392,16 @@ async function collectDoctor(context, probe) {
11093
11392
  const env = context.env;
11094
11393
  const configDir = env.MORULA_CONFIG_DIR ?? join21(homedir12(), ".morula-cli");
11095
11394
  const configPath = env.MORULA_CONFIG ?? join21(configDir, "config.yaml");
11096
- const daemonPath = join21(dirname9(configPath), "daemon");
11395
+ const daemonPath = join21(dirname10(configPath), "daemon");
11097
11396
  const logFile = join21(daemonPath, "daemon.log");
11098
11397
  const contextName = context.runtime.context ?? null;
11099
11398
  const baseUrl = context.runtime.baseUrl ?? null;
11100
11399
  const tokenPresent = Boolean(context.runtime.token);
11101
11400
  const tokenSource = context.runtime.tokenSource ?? null;
11102
11401
  let configReadable = false;
11103
- if (existsSync16(configPath)) {
11402
+ if (existsSync17(configPath)) {
11104
11403
  try {
11105
- readFileSync10(configPath, "utf8");
11404
+ readFileSync11(configPath, "utf8");
11106
11405
  configReadable = true;
11107
11406
  } catch {
11108
11407
  configReadable = false;
@@ -11172,7 +11471,7 @@ async function collectDoctor(context, probe) {
11172
11471
  hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
11173
11472
  });
11174
11473
  }
11175
- 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" });
11474
+ checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync17(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" });
11176
11475
  if (!dimFound) {
11177
11476
  checks.push({
11178
11477
  id: "dim.present",
@@ -11283,7 +11582,7 @@ function configFilePath(env) {
11283
11582
  return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
11284
11583
  }
11285
11584
  function daemonDir(configPath) {
11286
- return join22(dirname10(configPath), "daemon");
11585
+ return join22(dirname11(configPath), "daemon");
11287
11586
  }
11288
11587
  var PID_FILE = DAEMON_PID_FILE;
11289
11588
  var LOG_FILE = "daemon.log";
@@ -11481,7 +11780,7 @@ async function run2(context) {
11481
11780
  saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
11482
11781
  }
11483
11782
  const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
11484
- mkdirSync14(resolvedWorkdir, { recursive: true });
11783
+ mkdirSync15(resolvedWorkdir, { recursive: true });
11485
11784
  const lockPath = join22(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
11486
11785
  const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
11487
11786
  if (!acquired.ok) {
@@ -11507,6 +11806,9 @@ async function run2(context) {
11507
11806
  workdir,
11508
11807
  maxConcurrent,
11509
11808
  log: (line) => context.err(line),
11809
+ // dim acp 子进程治理(issues/dim-acp-process-leak-shared-space-contention.md):
11810
+ // spawn 记录落盘(供 daemon 重启后的启动自愈认领自己的孤儿)+ 无归属子进程周期巡检
11811
+ acpChildRecordFile: join22(daemonDir(configFilePath(context.env)), "dim-acp-children.json"),
11510
11812
  // D6:认证失败时重新读 config 换票(不传则退回「启动即快照」旧行为)。
11511
11813
  // 只重算凭证,不重建 client——WS 用返回值更新 socket.auth 后重连。
11512
11814
  resolveToken: () => {
@@ -11559,7 +11861,7 @@ async function start(context) {
11559
11861
  }
11560
11862
  const dir = daemonDir(configFilePath(context.env));
11561
11863
  if (!dryRun)
11562
- mkdirSync14(dir, { recursive: true, mode: 448 });
11864
+ mkdirSync15(dir, { recursive: true, mode: 448 });
11563
11865
  const pidFile = join22(dir, PID_FILE);
11564
11866
  const logFile = join22(dir, LOG_FILE);
11565
11867
  const lockPath = join22(dir, DAEMON_LOCK_FILE);
@@ -11573,8 +11875,8 @@ async function start(context) {
11573
11875
  { hint: "morula-runtime daemon status" }
11574
11876
  );
11575
11877
  }
11576
- if (existsSync17(pidFile)) {
11577
- const pid = Number(readFileSync11(pidFile, "utf8").trim());
11878
+ if (existsSync18(pidFile)) {
11879
+ const pid = Number(readFileSync12(pidFile, "utf8").trim());
11578
11880
  if (isProcessAlive(pid)) {
11579
11881
  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, {
11580
11882
  hint: "morula-runtime daemon status"
@@ -11624,7 +11926,7 @@ async function start(context) {
11624
11926
  hint: `tail -n 50 ${logFile}`
11625
11927
  });
11626
11928
  }
11627
- writeFileSync12(pidFile, String(result.pid), { mode: 384 });
11929
+ writeFileSync13(pidFile, String(result.pid), { mode: 384 });
11628
11930
  context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
11629
11931
  return { running: true, pid: result.pid, logFile, lockFile: lockPath };
11630
11932
  }
@@ -11636,7 +11938,7 @@ async function stop(context) {
11636
11938
  const lockPath = join22(dir, DAEMON_LOCK_FILE);
11637
11939
  const lock = probeDaemonLock(lockPath);
11638
11940
  const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
11639
- const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
11941
+ const filePid = existsSync18(pidFile) ? Number(readFileSync12(pidFile, "utf8").trim()) : null;
11640
11942
  const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
11641
11943
  if (dryRun) {
11642
11944
  return {
@@ -11665,7 +11967,7 @@ async function status2(context) {
11665
11967
  const cfg = loadDaemonConfig(context.store);
11666
11968
  const dir = daemonDir(configFilePath(context.env));
11667
11969
  const lockPath = join22(dir, DAEMON_LOCK_FILE);
11668
- const probe = probeDaemonInstance(dirname10(configFilePath(context.env)));
11970
+ const probe = probeDaemonInstance(dirname11(configFilePath(context.env)));
11669
11971
  return {
11670
11972
  running: probe.running,
11671
11973
  pid: probe.pid,