@arcships/morula-runtime 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +571 -269
- package/dist/morula.js +9 -83
- package/dist/plugin/plugin.json +1 -1
- package/dist/plugin/skills/morula-cli/references/commands.md +4 -26
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +15 -12
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +2 -2
- package/package.json +1 -1
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 = "
|
|
743
|
+
BUILD_ID = "20260922142838.0c791f70";
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
3825
|
+
const { existsSync: existsSync19 } = await import("node:fs");
|
|
3587
3826
|
process.stderr.write(
|
|
3588
|
-
`[daemon] diag: fileExists=${
|
|
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
|
-
/**
|
|
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
|
-
|
|
3761
|
-
|
|
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
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
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
|
|
3896
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as
|
|
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
|
|
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) =>
|
|
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 =
|
|
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 (
|
|
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.
|
|
4411
|
+
var BUILD_VERSION2 = "0.1.0-alpha.12";
|
|
4151
4412
|
|
|
4152
4413
|
// src/commands/daemon.ts
|
|
4153
4414
|
init_dist();
|
|
4154
4415
|
init_dist2();
|
|
4155
|
-
import { existsSync as
|
|
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
|
|
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
|
|
4226
|
-
import { existsSync as
|
|
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
|
|
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 /
|
|
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>\` + \`/\` +
|
|
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
|
|
5136
|
+
git switch -c <type>/<slug>
|
|
4873
5137
|
\`\`\`
|
|
4874
|
-
\u4F8B\uFF1A\
|
|
4875
|
-
\u5206\u652F\u540D**\u5230 \`<slug>\` \u4E3A\u6B62\u5C31\u7ED3\u675F** \u2014\u2014
|
|
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
|
|
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\
|
|
4886
|
-
- **\u5206\u652F\u540D\u5230 slug \u5C31\u7ED3\u675F**\
|
|
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 /
|
|
5042
|
-
\u63A8\u8350\u5199\u6CD5\uFF1Atitle \u4EE5\u300CSTR-101: \u63CF\u8FF0\u300D\u5F00\u5934\uFF0C\u6216
|
|
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
|
|
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
|
|
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
|
|
5278
|
-
import { existsSync as
|
|
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
|
-
|
|
5559
|
+
execFileSync7("git", ["--version"], { stdio: "pipe" });
|
|
5292
5560
|
return;
|
|
5293
5561
|
} catch {
|
|
5294
5562
|
}
|
|
5295
|
-
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) =>
|
|
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 &&
|
|
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 (
|
|
5616
|
+
if (existsSync8(join11(repoDir, ".git"))) {
|
|
5349
5617
|
if (remoteUrl)
|
|
5350
5618
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
5351
5619
|
try {
|
|
5352
5620
|
if (daemonRemoteUrl) {
|
|
5353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5634
|
+
mkdirSync7(repoDir, { recursive: true });
|
|
5367
5635
|
try {
|
|
5368
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
5393
|
-
return !!gitDir &&
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5421
|
-
|
|
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
|
-
|
|
5427
|
-
|
|
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
|
|
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
|
-
|
|
5443
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
5739
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
5472
5740
|
try {
|
|
5473
|
-
|
|
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
|
|
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
|
|
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 =
|
|
5773
|
+
const distDir = dirname7(fileURLToPath2(entryUrl));
|
|
5506
5774
|
return {
|
|
5507
|
-
packageRoot:
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
5571
|
-
const manifest = JSON.parse(
|
|
5572
|
-
|
|
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
|
-
|
|
5844
|
+
mkdirSync9(skillsDest, { recursive: true });
|
|
5577
5845
|
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
5578
5846
|
const binDir = join13(pluginDir, "bin");
|
|
5579
|
-
|
|
5847
|
+
mkdirSync9(binDir, { recursive: true });
|
|
5580
5848
|
const binPath = join13(binDir, "morula");
|
|
5581
|
-
|
|
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:
|
|
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:
|
|
6548
|
-
const installed =
|
|
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
|
|
6840
|
-
import { existsSync as
|
|
6841
|
-
import { dirname as
|
|
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 (!
|
|
7127
|
+
if (!existsSync12(join15(repoCachePath, ".git")))
|
|
6860
7128
|
return null;
|
|
6861
7129
|
try {
|
|
6862
|
-
return
|
|
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 (
|
|
7140
|
+
if (existsSync12(join15(repoCachePath, ".git"))) {
|
|
6873
7141
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
6874
7142
|
return;
|
|
6875
7143
|
}
|
|
6876
|
-
|
|
7144
|
+
mkdirSync10(dirname8(repoCachePath), { recursive: true });
|
|
6877
7145
|
const cloneUrl = fetchUrl();
|
|
6878
7146
|
try {
|
|
6879
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6912
|
-
import { existsSync as
|
|
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
|
|
6917
|
-
import { existsSync as
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
7202
|
+
if (!existsSync13(p))
|
|
6935
7203
|
return null;
|
|
6936
7204
|
try {
|
|
6937
|
-
const raw = JSON.parse(
|
|
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) || !
|
|
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) ||
|
|
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 =
|
|
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
|
-
|
|
7261
|
+
writeFileSync9(gitignorePath, next, "utf8");
|
|
6994
7262
|
try {
|
|
6995
|
-
|
|
6996
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
7358
|
+
if (!existsSync14(lockPath))
|
|
7091
7359
|
return true;
|
|
7092
7360
|
const deadline = Date.now() + timeoutMs;
|
|
7093
|
-
while (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
7503
|
+
if (existsSync14(worktreePath)) {
|
|
7236
7504
|
const isGitRepo = (() => {
|
|
7237
7505
|
try {
|
|
7238
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
7556
|
+
const headSha2 = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
7289
7557
|
try {
|
|
7290
|
-
|
|
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 (!
|
|
7303
|
-
|
|
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 (
|
|
7587
|
+
if (existsSync14(dotMorulaRescue)) {
|
|
7320
7588
|
const dotMorulaDir = join17(worktreePath, ".morula");
|
|
7321
|
-
if (!
|
|
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 =
|
|
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 (
|
|
7618
|
+
if (existsSync14(join17(primaryDir, WORKTREE_MARKER_FILE)))
|
|
7351
7619
|
return { primaryDir, legacy: false };
|
|
7352
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 || !
|
|
7753
|
+
if (repoPath === excludeDir || !existsSync14(join17(repoPath, ".git")))
|
|
7486
7754
|
continue;
|
|
7487
7755
|
try {
|
|
7488
|
-
const commonDir =
|
|
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
|
-
|
|
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 (!
|
|
7788
|
+
if (!existsSync14(worktreePath))
|
|
7521
7789
|
return;
|
|
7522
7790
|
try {
|
|
7523
7791
|
cleanupInjectedSkills(worktreePath);
|
|
7524
|
-
|
|
7525
|
-
|
|
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
|
-
|
|
7622
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
8075
|
+
execFileSync10("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
7808
8076
|
} else {
|
|
7809
|
-
|
|
7810
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
7961
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7987
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
8105
|
-
**\
|
|
8106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8333
|
-
|
|
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.
|
|
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
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
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
|
-
}
|
|
8352
|
-
const
|
|
8353
|
-
|
|
8354
|
-
|
|
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
|
-
|
|
8882
|
+
mkdirSync13(dir, { recursive: true });
|
|
8610
8883
|
const target = join19(dir, safeAttachmentName(attachment.fileName));
|
|
8611
|
-
|
|
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
|
-
|
|
8705
|
-
|
|
8977
|
+
mkdirSync13(skillDir, { recursive: true });
|
|
8978
|
+
writeFileSync11(join19(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
|
|
8706
8979
|
const contextDir = join19(workspaceDir, ".agent_context");
|
|
8707
|
-
|
|
8708
|
-
|
|
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
|
-
|
|
10446
|
+
mkdirSync14(repoDir, { recursive: true });
|
|
10148
10447
|
const readme = join20(repoDir, "README.md");
|
|
10149
|
-
if (!
|
|
10150
|
-
|
|
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 &&
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11085
|
+
writeFileSync12(join20(dir, "SKILL.md"), `${front}
|
|
10787
11086
|
${body}`, "utf8");
|
|
10788
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10845
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 (
|
|
11402
|
+
if (existsSync17(configPath)) {
|
|
11104
11403
|
try {
|
|
11105
|
-
|
|
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}` } :
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
11577
|
-
const pid = Number(
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
11970
|
+
const probe = probeDaemonInstance(dirname11(configFilePath(context.env)));
|
|
11669
11971
|
return {
|
|
11670
11972
|
running: probe.running,
|
|
11671
11973
|
pid: probe.pid,
|