@arcships/morula-runtime 0.1.0-alpha.15 → 0.1.0-alpha.17
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 +913 -167
- package/dist/morula.js +79 -40
- package/dist/plugin/plugin.json +1 -1
- package/dist/plugin/skills/morula-cli/references/commands.md +34 -30
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +40 -34
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +14 -9
- 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 = "20260930065011.80ac5142";
|
|
744
744
|
BUILD_CONTEXTS = {
|
|
745
745
|
"local": "http://localhost:8080/api",
|
|
746
746
|
"test": "https://morula.basecastle.com/api",
|
|
@@ -841,13 +841,6 @@ var init_attachment = __esm({
|
|
|
841
841
|
getDownloadUrl(id) {
|
|
842
842
|
return this.client.get(`/attachments/${encodeURIComponent(id)}/download-url`);
|
|
843
843
|
}
|
|
844
|
-
/**
|
|
845
|
-
* 读取附件的解析产物全文(MinerU markdown)。
|
|
846
|
-
* 未解析/解析中/解析失败由服务端在返回体里说明,不当作 HTTP 错误。
|
|
847
|
-
*/
|
|
848
|
-
content(id, maxChars) {
|
|
849
|
-
return this.client.get(`/attachments/${encodeURIComponent(id)}/content`, { maxChars });
|
|
850
|
-
}
|
|
851
844
|
/** 更新附件关联的资源 ID */
|
|
852
845
|
updateResourceId(id, resourceId) {
|
|
853
846
|
return this.client.patch(`/attachments/${encodeURIComponent(id)}`, { resourceId });
|
|
@@ -2588,9 +2581,9 @@ async function setupAutostart(context) {
|
|
|
2588
2581
|
await removeLegacyAutostart();
|
|
2589
2582
|
const { command, args } = resolvePortalCommand();
|
|
2590
2583
|
const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join3(homedir2(), ".morula-cli");
|
|
2591
|
-
const
|
|
2592
|
-
mkdirSync2(
|
|
2593
|
-
const logFile = join3(
|
|
2584
|
+
const daemonDir = join3(stateDir2, "daemon");
|
|
2585
|
+
mkdirSync2(daemonDir, { recursive: true, mode: 448 });
|
|
2586
|
+
const logFile = join3(daemonDir, "portal.log");
|
|
2594
2587
|
const plist = buildLaunchAgentPlist({ label: LABEL, command, args, logFile });
|
|
2595
2588
|
const plistPath = launchAgentPath();
|
|
2596
2589
|
mkdirSync2(join3(plistPath, ".."), { recursive: true });
|
|
@@ -2626,7 +2619,13 @@ var init_portal_autostart = __esm({
|
|
|
2626
2619
|
// src/daemon/daemon-config.ts
|
|
2627
2620
|
import { randomUUID } from "node:crypto";
|
|
2628
2621
|
import { homedir as homedir4 } from "node:os";
|
|
2629
|
-
import { join as join5 } from "node:path";
|
|
2622
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
2623
|
+
function configFilePathForEnv(env = process.env) {
|
|
2624
|
+
return env.MORULA_CONFIG ?? join5(env.MORULA_CONFIG_DIR ?? join5(homedir4(), ".morula-cli"), "config.yaml");
|
|
2625
|
+
}
|
|
2626
|
+
function daemonDirForEnv(env = process.env) {
|
|
2627
|
+
return join5(dirname4(configFilePathForEnv(env)), "daemon");
|
|
2628
|
+
}
|
|
2630
2629
|
function loadDaemonConfig(store) {
|
|
2631
2630
|
const config = store.load();
|
|
2632
2631
|
const d = config?.[DAEMON_KEY];
|
|
@@ -2666,7 +2665,7 @@ var init_daemon_config = __esm({
|
|
|
2666
2665
|
// src/daemon/daemon-lock.ts
|
|
2667
2666
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2668
2667
|
import { closeSync as closeSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
2669
|
-
import { dirname as
|
|
2668
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
2670
2669
|
function daemonPidPath(configDir) {
|
|
2671
2670
|
return join6(configDir, "daemon", DAEMON_PID_FILE);
|
|
2672
2671
|
}
|
|
@@ -2769,7 +2768,7 @@ function acquireDaemonLock(lockPath, input) {
|
|
|
2769
2768
|
startedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2770
2769
|
workdir: input.workdir
|
|
2771
2770
|
};
|
|
2772
|
-
mkdirSync4(
|
|
2771
|
+
mkdirSync4(dirname5(lockPath), { recursive: true, mode: 448 });
|
|
2773
2772
|
try {
|
|
2774
2773
|
const fd = openSync2(lockPath, "wx", 384);
|
|
2775
2774
|
try {
|
|
@@ -2854,7 +2853,7 @@ __export(portal_instances_exports, {
|
|
|
2854
2853
|
});
|
|
2855
2854
|
import { readdirSync, statSync } from "node:fs";
|
|
2856
2855
|
import { homedir as homedir5 } from "node:os";
|
|
2857
|
-
import { dirname as
|
|
2856
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
2858
2857
|
function parseBaseUrl(raw) {
|
|
2859
2858
|
if (!raw)
|
|
2860
2859
|
return null;
|
|
@@ -2922,7 +2921,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2922
2921
|
const env = options.env ?? process.env;
|
|
2923
2922
|
const log = options.log ?? (() => {
|
|
2924
2923
|
});
|
|
2925
|
-
const defaultConfigDir =
|
|
2924
|
+
const defaultConfigDir = dirname6(env.MORULA_CONFIG ?? join7(env.MORULA_CONFIG_DIR ?? join7(options.homeDir ?? homedir5(), ".morula-cli"), "config.yaml"));
|
|
2926
2925
|
let index = [];
|
|
2927
2926
|
let signature = "";
|
|
2928
2927
|
function buildIndex() {
|
|
@@ -2939,7 +2938,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2939
2938
|
const normalized = normalizeBaseUrl2(entry.url);
|
|
2940
2939
|
if (!normalized)
|
|
2941
2940
|
continue;
|
|
2942
|
-
const configDir =
|
|
2941
|
+
const configDir = dirname6(file);
|
|
2943
2942
|
built.push({
|
|
2944
2943
|
configDir,
|
|
2945
2944
|
context,
|
|
@@ -3482,7 +3481,7 @@ var init_dim_locate = __esm({
|
|
|
3482
3481
|
// src/daemon/acp/acp-process-registry.ts
|
|
3483
3482
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
3484
3483
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3485
|
-
import { dirname as
|
|
3484
|
+
import { dirname as dirname7 } from "node:path";
|
|
3486
3485
|
function parseLstartToMs(tokens) {
|
|
3487
3486
|
if (tokens.length < 5)
|
|
3488
3487
|
return null;
|
|
@@ -3595,7 +3594,7 @@ function persistRecords() {
|
|
|
3595
3594
|
if (!recordFile)
|
|
3596
3595
|
return;
|
|
3597
3596
|
try {
|
|
3598
|
-
mkdirSync5(
|
|
3597
|
+
mkdirSync5(dirname7(recordFile), { recursive: true });
|
|
3599
3598
|
writeFileSync5(recordFile, JSON.stringify({
|
|
3600
3599
|
v: RECORD_FILE_VERSION,
|
|
3601
3600
|
children: [...ownedChildren.values()]
|
|
@@ -3838,9 +3837,9 @@ var init_acp_connection = __esm({
|
|
|
3838
3837
|
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
3839
3838
|
`);
|
|
3840
3839
|
try {
|
|
3841
|
-
const { existsSync:
|
|
3840
|
+
const { existsSync: existsSync21 } = await import("node:fs");
|
|
3842
3841
|
process.stderr.write(
|
|
3843
|
-
`[daemon] diag: fileExists=${
|
|
3842
|
+
`[daemon] diag: fileExists=${existsSync21(dimPath)} cwdExists=${this.options.cwd ? existsSync21(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
|
|
3844
3843
|
`
|
|
3845
3844
|
);
|
|
3846
3845
|
} catch {
|
|
@@ -4424,14 +4423,13 @@ init_dist2();
|
|
|
4424
4423
|
init_dist2();
|
|
4425
4424
|
|
|
4426
4425
|
// src/build-env.ts
|
|
4427
|
-
var BUILD_VERSION2 = "0.1.0-alpha.
|
|
4426
|
+
var BUILD_VERSION2 = "0.1.0-alpha.17";
|
|
4428
4427
|
|
|
4429
4428
|
// src/commands/daemon.ts
|
|
4430
4429
|
init_dist();
|
|
4431
4430
|
init_dist2();
|
|
4432
|
-
import { existsSync as
|
|
4433
|
-
import {
|
|
4434
|
-
import { dirname as dirname11, join as join22 } from "node:path";
|
|
4431
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync16, readFileSync as readFileSync12, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
4432
|
+
import { dirname as dirname13, join as join23 } from "node:path";
|
|
4435
4433
|
|
|
4436
4434
|
// src/daemon/daemon-autostart.ts
|
|
4437
4435
|
init_portal_autostart();
|
|
@@ -4500,9 +4498,9 @@ init_portal_server();
|
|
|
4500
4498
|
// src/daemon/runner.ts
|
|
4501
4499
|
init_dist2();
|
|
4502
4500
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4503
|
-
import { existsSync as
|
|
4501
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync15, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
|
|
4504
4502
|
import { arch, hostname } from "node:os";
|
|
4505
|
-
import { dirname as
|
|
4503
|
+
import { dirname as dirname11, join as join21 } from "node:path";
|
|
4506
4504
|
|
|
4507
4505
|
// src/daemon/acp/acp-connection-pool.ts
|
|
4508
4506
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -4637,6 +4635,10 @@ var DaemonApi = class {
|
|
|
4637
4635
|
* D1(daemon-server-connection-unstable):心跳是「设备存活」的唯一信号,显式 10s 超时——
|
|
4638
4636
|
* 慢请求快速失败,避免占住单飞槽位(客户端默认 30s,对心跳过松)。
|
|
4639
4637
|
* 说明:`client.post()` 的第三参是 query,不支持 RequestOptions,故直接走 `client.request`。
|
|
4638
|
+
*
|
|
4639
|
+
* `upgradeRequested`(runtime-self-upgrade,沿用 stop 的一次性认领模式):服务端置了
|
|
4640
|
+
* upgradeRequestedAt → 本次心跳认领(服务端已清指令)→ daemon 触发自升级;`upgradeVersion`
|
|
4641
|
+
* 为置指令时的目标版本(可空 = daemon 自行解析 latest)。
|
|
4640
4642
|
*/
|
|
4641
4643
|
heartbeat(runtimeId, runningTaskCount = 0) {
|
|
4642
4644
|
return this.client.request(
|
|
@@ -4647,6 +4649,13 @@ var DaemonApi = class {
|
|
|
4647
4649
|
pollTasks(runtimeId, max = 1) {
|
|
4648
4650
|
return this.client.get("/agent-tasks/poll", { runtimeId, max });
|
|
4649
4651
|
}
|
|
4652
|
+
/**
|
|
4653
|
+
* runtime-self-upgrade:自升级结果回报(机器协议,设备级凭证)。
|
|
4654
|
+
* best-effort 语义由调用方掌握(失败只 log,不影响本地升级结果)。
|
|
4655
|
+
*/
|
|
4656
|
+
reportUpgradeResult(runtimeId, body) {
|
|
4657
|
+
return this.client.post(`/agent-runtimes/${runtimeId}/upgrade-result`, body);
|
|
4658
|
+
}
|
|
4650
4659
|
claim(taskId, runtimeId) {
|
|
4651
4660
|
const res = this.client.post(`/agent-tasks/${taskId}/claim`, void 0, { runtimeId });
|
|
4652
4661
|
res.then((r) => {
|
|
@@ -5205,8 +5214,9 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
5205
5214
|
- **git / gh \u547D\u4EE4\u4E00\u5F8B\u5728\u5BF9\u5E94\u4ED3\u5E93\u76EE\u5F55\u5185\u6267\u884C**\uFF08\`cd <\u5DE5\u4F5C\u533A\u6839>/<\u4ED3\u5E93\u76EE\u5F55>\`\uFF0C\u6216 \`git -C <\u4ED3\u5E93\u76EE\u5F55> ...\`\uFF09\uFF1A
|
|
5206
5215
|
\u5DE5\u4F5C\u533A\u6839\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u5728\u6839\u76EE\u5F55\u8DD1 git \u547D\u4EE4\u5FC5\u7136\u5931\u8D25\u3002\`morula\` CLI \u53EF\u5728\u5DE5\u4F5C\u533A\u6839\u6216\u5176\u4EFB\u610F\u5B50\u76EE\u5F55\u6267\u884C\uFF08\u89C1 0.4\uFF09\u3002
|
|
5207
5216
|
- **\u4E3B\u4ED3\u5E93**\uFF1A\u5E73\u53F0\u5DF2\u89E3\u6790\u51FA\u7684\u4ED3\u5E93\u5728**\u5DE5\u4F5C\u533A\u6839\u4E0B\u5C31\u7EEA**\uFF08\u76EE\u5F55\u540D = \u4ED3\u5E93\u5168\u540D\u628A \`/\` \u6362\u6210 \`-\`\uFF0C\u5982 \`human-resources/xxx\` \u2192 \`human-resources-xxx\`\uFF09\uFF1B\`ls\` \u5DE5\u4F5C\u533A\u6839\u5373\u53EF\u770B\u5230\uFF0C\u76F4\u63A5\u5728\u8BE5\u76EE\u5F55\u5185\u5EFA\u5206\u652F/\u63D0\u4EA4/\u63A8\u5206\u652F/\u63D0 PR\u3002**\u9700\u8981\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u54EA\u4E9B\u4ED3\u5E93\u65F6\u81EA\u5DF1\u67E5**\uFF1A\`morula repo list --space <spaceId>\`\uFF08spaceId \u89C1\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF09\u3002
|
|
5208
|
-
- **\u26A0\uFE0F \`morula repo checkout\` \u7684\u8BED\u4E49 = \u300C\u58F0\u660E/\u5207\u6362\u672C\u4EFB\u52A1\u7684
|
|
5209
|
-
|
|
5217
|
+
- **\u26A0\uFE0F \`morula repo checkout\` \u7684\u8BED\u4E49 = \u300C\u58F0\u660E/\u5207\u6362\u672C\u4EFB\u52A1\u7684\u4E3B\u4ED3\u5E93\u300D**\uFF08\u670D\u52A1\u7AEF generation+1 + daemon \u7269\u5316\u5DE5\u4F5C\u533A\uFF09\uFF1A
|
|
5218
|
+
\u58F0\u660E\u53EA\u4F5C\u7528\u4E8E\u5F53\u524D\u4EFB\u52A1\uFF08\u5F53\u524D\u9636\u6BB5\u7684\u6267\u884C\u8005\uFF09\uFF1B**\u5F00\u53D1\u9636\u6BB5\u4EFB\u52A1\u4E0D\u7EE7\u627F\u5206\u6790\u9636\u6BB5\u7684\u58F0\u660E**\u2014\u2014\u9700\u8981\u6539\u4EE3\u7801\u65F6\u7531\u4F60\u81EA\u5DF1
|
|
5219
|
+
\u6309\u4EFB\u52A1\u63CF\u8FF0/\u5206\u6790\u7ED3\u8BBA checkout\u3002\u56E0\u6B64\u53EA\u5728**\u5224\u5B9A\u51FA\u4E3B\u4ED3\u5E93\u4E4B\u540E**\u8C03\u7528\uFF0C**\u4E00\u6B21\u4EFB\u52A1\u53EA\u58F0\u660E\u4E00\u6B21**\uFF08\u5224\u9519\u8981\u7EA0\u6B63\u65F6\u624D\u518D\u8C03\u4E00\u6B21\uFF09\u3002
|
|
5210
5220
|
\u4E0D\u8981\u7528\u5B83\u300C\u628A\u7A7A\u95F4\u91CC\u7684\u4ED3\u5E93\u9010\u4E2A\u901B\u4E00\u904D\u300D\u2014\u2014\u6BCF\u8C03\u4E00\u6B21\u4E3B\u4ED3\u5E93\u5C31\u8DDF\u7740\u6362\u4E00\u6B21\uFF08\u771F\u673A\u8E29\u5230\uFF1A\u5206\u6790\u9636\u6BB5\u4E3A\u67E5\u8BC1 checkout \u4E86 3 \u4E2A\u4ED3\u5E93\uFF0C
|
|
5211
5221
|
\u4E3B\u4ED3\u5E93\u6700\u7EC8\u505C\u5728\u6700\u540E\u90A3\u4E2A\u300C\u53EA\u770B\u4E86\u4E00\u773C\u300D\u7684\u4ED3\u5E93\u4E0A\uFF09\u3002
|
|
5212
5222
|
- **\u82E5\u4EFB\u52A1\u9700\u8981\u4E86\u89E3\u4ED3\u5E93\u5185\u5BB9/\u4EE3\u7801/\u63D0\u4EA4\u72B6\u6001\uFF08\u8BFB\u573A\u666F\uFF0C\u542B\u7EAF\u95EE\u7B54\uFF09**\uFF1A
|
|
@@ -5241,8 +5251,9 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
5241
5251
|
morula repo checkout <taskId> <repoFullName> [--ref <branch|sha>] # \u62C9\u53D6/\u5207\u6362\u4ED3\u5E93\uFF08\u8FD4\u56DE\u7269\u5316\u8DEF\u5F84\uFF0C\u89C1 0.5\uFF09
|
|
5242
5252
|
morula repo prepare <taskId> <repoFullName> # \u591A\u4ED3\u5E93\u4EFB\u52A1\uFF1A\u7269\u5316\u5176\u4ED6\u4ED3\u5E93\u5230\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\u4E0B\uFF08\u89C1 0.5\uFF09
|
|
5243
5253
|
morula comment list --object <objectId> # \u8BFB\u8BC4\u8BBA\uFF08\u4EFB\u52A1=objectId\uFF09
|
|
5254
|
+
morula comment add --object <objectId> --content <\u6587\u672C> # \u53D1\u8BC4\u8BBA\uFF08\u6700\u7EC8\u603B\u7ED3\u52A0 --final\uFF0C\u89C1\u7B2C 5 \u6761\uFF09
|
|
5244
5255
|
morula task inbox [--peek] # \u8FD0\u884C\u4E2D\u68C0\u67E5\u70B9\uFF1A\u53D6\u672C\u7EBF\u7A0B\u672A\u6D88\u8D39\u6D88\u606F\uFF0C\u7EB3\u5165\u672C\u56DE\u5408\uFF08\u89C1 1.3\uFF09
|
|
5245
|
-
# \u6CE8\uFF1A\
|
|
5256
|
+
# \u6CE8\uFF1A\u4EFB\u52A1\u6536\u5C3E\u5148\u53D1 final \u603B\u7ED3\u8BC4\u8BBA\uFF08morula comment add --final\uFF09\uFF0C\u518D\u4E0A\u62A5\u7ED3\u679C\uFF1B\u670D\u52A1\u7AEF\u53EA\u5728\u4F60\u6CA1\u53D1\u65F6\u515C\u5E95\u4EE3\u53D1\u4E00\u6761\u3002
|
|
5246
5257
|
\`\`\`
|
|
5247
5258
|
- **\u53EF\u7528\u4ED3\u5E93\u6E05\u5355**\uFF1A\u7CFB\u7EDF\u63D0\u793A**\u4E0D\u518D\u5217\u51FA**\u4ED3\u5E93\u2014\u2014\u9700\u8981\u679A\u4E3E\u7A7A\u95F4\u7ED1\u5B9A\u7684\u4ED3\u5E93\u65F6\u7528
|
|
5248
5259
|
\`morula repo list --space <spaceId>\`\uFF08\`<spaceId>\` \u89C1\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF0C\u6216 \`morula task get\` \u8FD4\u56DE\u7684 \`contextSnapshot.spaceId\`\uFF09\u3002
|
|
@@ -5372,26 +5383,34 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
5372
5383
|
- PR \u521B\u5EFA\u88AB\u963B\u585E\uFF08\u6743\u9650/\u6D4B\u8BD5\u5931\u8D25/\u8FDC\u7AEF\u72B6\u6001\u5F02\u5E38\uFF09\u2192 \u5728\u6700\u7EC8\u603B\u7ED3\u4E2D**\u62A5\u544A\u963B\u585E\u539F\u56E0**\uFF0C\u4E0D\u8981\u5047\u88C5\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF1B
|
|
5373
5384
|
- **\u4E0D\u8981**\u5207\u56DE \`main\` \u6216 \`master\` \u76F4\u63A5\u63D0\u4EA4\u3002
|
|
5374
5385
|
|
|
5375
|
-
## 4. \u72B6\u6001\
|
|
5386
|
+
## 4. \u72B6\u6001\u4E0E\u4EFB\u52A1\u64CD\u4F5C\uFF1A\u7ED3\u8BBA\u4F18\u5148\uFF0C\u52A8\u4F5C\u4E3A\u8F85
|
|
5376
5387
|
|
|
5377
|
-
- \u4EFB\u52A1\
|
|
5378
|
-
- \
|
|
5388
|
+
- **\u72B6\u6001\u63A8\u8FDB\u7531\u7ED3\u8BBA\u534F\u8BAE\u4E0E\u4E8B\u5B9E\u4E8B\u4EF6\u9A71\u52A8**\uFF1A\u5206\u6790\u4EFB\u52A1\u4E0A\u62A5 \`analysis-verdict\`\u3001\u8BC4\u5BA1\u4EFB\u52A1\u7528 \`morula review submit\`\u3001\u5408\u5E76/\u53D1\u5E03\u7531 PR \u4E8B\u4EF6\u9A71\u52A8\u2014\u2014**\u4E0D\u8981**\u7528\u72B6\u6001\u52A8\u4F5C\u66FF\u4EE3\u7ED3\u8BBA\u534F\u8BAE\u3002
|
|
5389
|
+
- **\u4F60\u53EF\u4EE5\u5728\u4EFB\u52A1\u4E0A\u505A\u57FA\u7840\u64CD\u4F5C**\uFF08\u50CF\u4EBA\u4E00\u6837\uFF0C\u4F5C\u7528\u57DF\u4EC5\u9650\u672C\u4EFB\u52A1\uFF09\uFF1A
|
|
5390
|
+
- \`morula task state <taskId> --state <in_progress|review> [--reason <\u6587\u672C>]\`\uFF1A\u5728\u300C\u5F00\u53D1\u4E2D / \u5F85\u8BC4\u5BA1\u300D\u8303\u56F4\u5185\u81EA\u63A8\u72B6\u6001\uFF1B**\u5F85\u8BC4\u5BA1 \u2192 \u5F00\u53D1\u4E2D = \u9000\u56DE\u5F00\u53D1**\u2014\u2014\u5E73\u53F0\u4F1A\u81EA\u52A8\u56DE\u9000\u72B6\u6001\u3001\u8BB0\u8BC4\u5BA1\u8F6E\u6B21\u5E76\u6D3E\u53D1\u4FEE\u590D\u4EFB\u52A1\uFF08\u540C\u4E00 PR \u8FED\u4EE3\uFF09\uFF0C**\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u5E94\u8BE5**\u518D\u81EA\u5DF1 @ \u5F00\u53D1\u6267\u884C\u5668\uFF1B\`--reason\` \u5199\u660E\u9000\u56DE\u539F\u56E0\uFF08\u4F1A\u8FDB\u4FEE\u590D\u4EFB\u52A1 prompt\uFF09\u3002
|
|
5391
|
+
- \`morula comment add\`\uFF1A\u89C1\u7B2C 5 \u6761\u8BC4\u8BBA\u7EAA\u5F8B\u3002
|
|
5392
|
+
- \`morula task assign <taskId> --to <principalId>\`\uFF1A\u6307\u6D3E\u672C\u4EFB\u52A1\u8D1F\u8D23\u4EBA\uFF08\u672C\u7A7A\u95F4\u6210\u5458\u6216\u9636\u6BB5\u6267\u884C\u5668\uFF1B\u76EE\u6807\u4E0D\u5B58\u5728\u4F1A\u663E\u5F0F\u62A5\u9519\uFF09\u3002
|
|
5393
|
+
- **\u7EC8\u6001\uFF08\u5DF2\u5408\u5E76/\u5DF2\u53D1\u5E03/\u5DF2\u5173\u95ED\uFF09\u4E0E\u9700\u6C42\u6C60\u72B6\u6001\u4E0D\u7531\u4F60\u58F0\u660E**\uFF1B\u540C\u6001\u91CD\u590D\u63A8\u9001\u662F\u5E42\u7B49\u7684\uFF08\u65E0\u5BB3\uFF09\u3002
|
|
5379
5394
|
|
|
5380
5395
|
## 4.1 \u5206\u6790\u4EFB\u52A1\uFF1A\u5FC5\u987B\u4E0A\u62A5\u5206\u6790\u7ED3\u8BBA\uFF08verdict\uFF09
|
|
5381
5396
|
|
|
5382
5397
|
> **\u4EC5\u5F53\u672C\u4EFB\u52A1\u662F\u300C\u5F85\u5206\u6790\u300D\u4EFB\u52A1\u65F6\u9002\u7528**\uFF08\u4EFB\u52A1\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\u9636\u6BB5\u3001\u8981\u6C42\u505A\u9700\u6C42\u5206\u6790\u4E0E\u65B9\u6848\u62C6\u89E3\u3001\u4E0D\u5199\u4EE3\u7801\uFF09\u3002
|
|
5383
5398
|
> \u5176\u4F59\u4EFB\u52A1\uFF08\u5F00\u53D1 / \u8BC4\u5BA1 / \u8C03\u7814 / \u95EE\u7B54\uFF09\u8DF3\u8FC7\u672C\u8282\uFF0C\u4E0D\u8981\u8C03\u7528\u8BE5\u547D\u4EE4\u3002
|
|
5384
5399
|
|
|
5385
|
-
- \u5206\u6790\u7ED3\u8BBA\u5206\u4E24\u90E8\u5206\uFF0C**\u90FD\u8981\u7ED9**\uFF1A
|
|
5386
|
-
1. **\u4EBA\u770B\u7684**\uFF1A\u5206\u6790\u7ED3\u8BBA\
|
|
5387
|
-
|
|
5400
|
+
- \u5206\u6790\u7ED3\u8BBA\u5206\u4E24\u90E8\u5206\uFF0C**\u90FD\u8981\u7ED9**\uFF08\u987A\u5E8F\uFF1A\u5148\u53D1\u8BC4\u8BBA\uFF0C\u518D\u4E0A\u62A5 verdict\uFF09\uFF1A
|
|
5401
|
+
1. **\u4EBA\u770B\u7684**\uFF1A\u7528**\u4F60\u81EA\u5DF1\u7684\u8BDD**\u628A\u5206\u6790\u7ED3\u8BBA\u53D1\u6210\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u2014\u2014\u9700\u6C42\u662F\u5426\u6E05\u6670\u3001\u600E\u4E48\u505A\u3001\u8303\u56F4\u4E0E\u98CE\u9669\u3001\u9A8C\u6536\u70B9\uFF1A
|
|
5402
|
+
\`\`\`bash
|
|
5403
|
+
morula comment add --object <objectId> --final --content <\u4F60\u7684\u5206\u6790\u7ED3\u8BBA>
|
|
5404
|
+
\`\`\`
|
|
5405
|
+
\u8FD9\u662F\u7528\u6237\u770B\u5230\u7684\u5206\u6790\u4EA4\u4ED8\uFF1A\u7528\u81EA\u5DF1\u7684\u8BDD\u5199\uFF0C\u4E0D\u8981\u5957\u56FA\u5B9A\u683C\u5F0F\uFF0C\u66F4\u4E0D\u8981\u590D\u8FF0\u4EFB\u52A1\u63CF\u8FF0\uFF1B\u7ED3\u8BBA\u5148\u8BF4\u3001\u7406\u7531\u8DDF\u4E0A\u3002
|
|
5406
|
+
2. **\u673A\u5668\u5224\u5B9A\u7684**\uFF1A\u6267\u884C \`morula task analysis-verdict <taskId> --verdict ready|blocked [--reason "\u2026"]\` \u4E0A\u62A5\u7ED3\u8BBA\uFF08\`--reason\` \u53EA\u9700\u4E00\u53E5\u8BDD\uFF0C\u8BE6\u7EC6\u5185\u5BB9\u5728\u8BC4\u8BBA\u91CC\uFF09\u3002
|
|
5388
5407
|
- \`<taskId>\` = \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u8FD4\u56DE\u7684 \`id\`\uFF08\u672C\u4EFB\u52A1 AgentTask UUID\uFF09\u3002
|
|
5389
5408
|
- **\`ready\`** = \u9700\u6C42\u6E05\u6670\u3001\u53EF\u76F4\u63A5\u5F00\u53D1\uFF08\`--reason\` \u53EF\u5199\u4E00\u53E5\u8BDD\u8303\u56F4\u6458\u8981\uFF09\uFF1B
|
|
5390
5409
|
**\`blocked\`** = \u4E0D\u53EF\u505A / \u9700\u6F84\u6E05\uFF08**\u5FC5\u987B**\u5728 \`--reason\` \u6216\u6700\u7EC8\u8F93\u51FA\u91CC\u5199\u6E05\u963B\u585E\u70B9\u4E0E\u9700\u8981\u6F84\u6E05\u7684\u95EE\u9898\uFF09\u3002
|
|
5391
5410
|
- **\u670D\u52A1\u7AEF\u636E\u6B64\u81EA\u52A8\u63A8\u8FDB\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u4EBA\u70B9\u786E\u8BA4**\uFF1A\`ready\` \u2192 \u4EFB\u52A1\u81EA\u52A8\u8FDB\u5165\u300C\u5F00\u53D1\u4E2D\u300D\u5E76\u6D3E\u53D1\u5F00\u53D1\u4EFB\u52A1\uFF1B
|
|
5392
5411
|
\`blocked\` / \u672A\u4E0A\u62A5 \u2192 \u4EFB\u52A1**\u505C\u5728\u300C\u5F85\u5206\u6790\u300D**\uFF0C\u7531\u7528\u6237\u8865\u5145\u9700\u6C42\u540E\u91CD\u6D3E\u5206\u6790\u3002
|
|
5393
5412
|
- **\u5FC5\u987B\u4E0A\u62A5**\uFF1A\u6F0F\u62A5\u4F1A\u8BA9\u4EFB\u52A1\u9759\u9ED8\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\uFF1B\u540C\u4E00\u4EFB\u52A1\u53EA\u9700\u4E0A\u62A5\u4E00\u6B21\uFF08\u91CD\u590D\u4E0A\u62A5\u4EE5\u9996\u6B21\u4E3A\u51C6\uFF0C\u4E0D\u4F1A\u6539\u5199\uFF09\u3002
|
|
5394
|
-
- **\u4E0D\u8981\
|
|
5413
|
+
- \u5206\u6790\u4EFB\u52A1\u7684\u63A8\u8FDB\u7531\u670D\u52A1\u7AEF\u6309 verdict \u51B3\u5B9A\u2014\u2014**\u4E0D\u8981\u7528\u72B6\u6001\u52A8\u4F5C\u66FF\u4EE3\u5206\u6790\u7ED3\u8BBA**\uFF08\u4E0D\u8981\u8C03 \`morula task state\`\uFF09\u3002
|
|
5395
5414
|
|
|
5396
5415
|
## 4.2 \u8BC4\u5BA1\u4EFB\u52A1\uFF1A\u53EA\u8BFB\u8BC4\u5BA1 + \u5FC5\u987B\u56DE\u4F20\u7ED3\u8BBA\uFF08verdict\uFF09
|
|
5397
5416
|
|
|
@@ -5412,9 +5431,11 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
5412
5431
|
- \`<objectId>\` = \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u8FD4\u56DE\u7684 \`objectId\`\uFF1B
|
|
5413
5432
|
- \`--verdict\`\uFF1A\`approved\`\uFF08\u901A\u8FC7\uFF09/ \`changes_requested\`\uFF08\u4E0D\u901A\u8FC7\uFF0C**\u5FC5\u987B**\u5728 \`--details\` \u5199\u660E\u5177\u4F53\u610F\u89C1\uFF1A\u6587\u4EF6/\u884C/\u95EE\u9898/\u5EFA\u8BAE\uFF0C\u5426\u5219\u5F00\u53D1\u65E0\u6CD5\u4FEE\u590D\uFF09/ \`commented\`\uFF08\u6709\u610F\u89C1\u4F46\u4E0D\u963B\u585E\uFF09\uFF1B
|
|
5414
5433
|
- \`--pr-number\`\uFF1A\u8BC4\u5BA1\u5BF9\u8C61\u7684 PR \u7F16\u53F7\uFF08\u6709\u5219\u586B\uFF09\u3002
|
|
5434
|
+
- **\u8BC4\u5BA1\u4EFB\u52A1\u7684\u4EA4\u4ED8\u5C31\u662F\u8FD9\u4EFD\u7ED3\u8BBA**\uFF1A\u5E73\u53F0\u4F1A\u6309\u7ED3\u8BBA\u5199\u6743\u5A01\u8BC4\u8BBA\uFF08\u2705/\u{1F4AC}/\u{1F501}\uFF09\u2014\u2014**\u4E0D\u8981**\u518D\u53D1\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\uFF08\`--final\` \u4F1A\u88AB\u670D\u52A1\u7AEF\u62D2\u7EDD\uFF09\uFF0C\u4E5F\u4E0D\u8981\u5728\u8BC4\u8BBA\u533A\u590D\u8FF0\u7ED3\u8BBA\u3002
|
|
5435
|
+
- **\u7ED3\u8BBA\u65B0\u9C9C\u5EA6\uFF08\u65E0\u65B0\u63D0\u4EA4\u4E0D\u91CD\u590D\u6295 \`approved\`\uFF09**\uFF1A\u63D0\u4EA4\u7ED3\u8BBA\u524D\u5148\u6838\u5BF9\u88AB\u8BC4\u5BA1 MR \u7684 head commit \u4E0E\u4E0A\u4E00\u8F6E\u7ED3\u8BBA\u65F6\u662F\u5426\u76F8\u540C\uFF08\`morula repo mr list\` / \`mr-detail\`\uFF1B\u8BC4\u8BBA\u533A\u4E5F\u6709\u4E0A\u4E00\u8F6E\u7ED3\u8BBA\uFF09\u3002**\u76F8\u540C\u4E14\u65E0\u65B0\u63D0\u4EA4\u65F6\u4E0D\u8981\u91CD\u590D\u6295 \`approved\`**\uFF1A\u672C\u8F6E\u4EFB\u52A1\u4E0A\u4E0B\u6587\u91CC\u6709\u4EBA\u7684\u65B0\u8981\u6C42\uFF08\u5982\u8981\u6C42\u4FEE\u590D\u4E0A\u4E00\u8F6E\u610F\u89C1\uFF09\u2192 \u6309\u4EBA\u7684\u8981\u6C42\u5904\u7406\uFF08\u610F\u89C1\u672A\u4FEE\u590D\u5C31\u6295 \`changes_requested\`\uFF09\uFF1B\u786E\u5B9E\u65E0\u53EF\u8BC4\u5BA1\u5185\u5BB9 \u2192 \u6295 \`commented\` \u8BF4\u660E\u300C\u65E0\u65B0\u63D0\u4EA4\u300D\u5373\u53EF\u3002\u590D\u8FF0\u4E0A\u4E00\u8F6E\u7ED3\u8BBA\u5BF9\u4EFB\u52A1\u6CA1\u6709\u4EFB\u4F55\u63A8\u8FDB\u3002
|
|
5415
5436
|
- **\u7981\u6B62\u8BC4\u5BA1\u81EA\u5DF1\u5F00\u53D1\u7684\u4EFB\u52A1**\uFF1A\u82E5\u5F85\u8BC4\u5BA1\u6539\u52A8\u7531\u4F60\u81EA\u5DF1\u63D0\u4EA4\uFF08\u540C\u4E00\u667A\u80FD\u4F53\u65E2\u5F00\u53D1\u53C8\u8BC4\u5BA1\uFF09\uFF0C\u4E0D\u4E0B\u7ED3\u8BBA\uFF0C\u76F4\u63A5\u62A5\u544A\u8BE5\u914D\u7F6E\u95EE\u9898\u3002
|
|
5416
|
-
- **\u4E0D\u8981\u624B\u52A8\u6D41\u8F6C\u672C\u4EFB\u52A1\u72B6\u6001**\uFF08\u4E0D\u8981\u8C03 \`morula task state\`\uFF09\uFF1A\u8BC4\u5BA1\u7ED3\u8BBA\u7531\u670D\u52A1\u7AEF\u636E\u6B64\u63A8\u8FDB\u3002
|
|
5417
5437
|
- **\u5FC5\u987B\u4E0A\u62A5**\uFF1A\u6F0F\u62A5\u4F1A\u8BA9\u4EFB\u52A1\u505C\u5728\u300C\u5F85\u8BC4\u5BA1\u300D\uFF0C\u5F00\u53D1\u65E0\u6CD5\u8FDB\u5165\u4E0B\u4E00\u8F6E\u3002
|
|
5438
|
+
- \u9000\u56DE\u5F00\u53D1**\u9996\u9009\u7ED3\u8BBA\u534F\u8BAE**\uFF1A\`--verdict changes_requested\`\uFF08\u5E26\u5177\u4F53\u610F\u89C1\uFF09\u4E0E\u670D\u52A1\u7AEF\u7ED3\u8BBA\u94FE\u8DEF\u5B8C\u5168\u7B49\u4EF7\uFF1B\`morula task state --state in_progress\` \u662F\u7B49\u4EF7\u7684\u66FF\u4EE3\u5165\u53E3\uFF08\u89C1\u7B2C 4 \u6761\uFF09\uFF0C\u4E0D\u662F\u7ED5\u8FC7\u3002
|
|
5418
5439
|
|
|
5419
5440
|
## 4.3 \u5728 MR/PR \u4E0A\u7559\u8BC4\u5BA1\u610F\u89C1\uFF08\u5DE1\u68C0/\u5BA1\u67E5\u7C7B\u4EFB\u52A1\uFF0C\u975E\u8BC4\u5BA1\u4EFB\u52A1\u65F6\uFF09
|
|
5420
5441
|
|
|
@@ -5430,36 +5451,30 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
5430
5451
|
- \u82E5\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u8BC4\u8BBA\u3001\u7ED3\u8BBA\u4E0E\u8FD9\u6B21\u4E00\u81F4 \u2192 **\u4E0D\u8981\u91CD\u590D\u8BC4\u8BBA**\uFF1B
|
|
5431
5452
|
- \u26D4 **\u4E0D\u8981\u7528 \`morula review submit\`**\uFF08\u90A3\u662F\u8BC4\u5BA1\u4EFB\u52A1\u7684\u673A\u5668\u534F\u8BAE\uFF0C\u4F1A\u9A71\u52A8\u4E1A\u52A1\u4EFB\u52A1\u72B6\u6001\u56DE\u9000\uFF09\u3002
|
|
5432
5453
|
|
|
5433
|
-
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B
|
|
5434
|
-
|
|
5435
|
-
- **\u4F55\u65F6\u53D1\u8BC4\u8BBA**\uFF1A**\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u4EE5\u667A\u80FD\u4F53\u8EAB\u4EFD\u53D1\u9001**\uFF08\u4EFB\u52A1\u5B8C\u6210\u65F6\u81EA\u52A8\u53D1\u5E03\u5230\u4EFB\u52A1\u5BF9\u8C61\uFF0C\u4F5C\u8005=\u5F53\u524D\u667A\u80FD\u4F53\uFF09\u3002**\u4F60\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u5E94\u8C03\u7528 \`morula comment add\`**\u2014\u2014\u53EA\u8981\u5728\u6700\u7EC8\u8F93\u51FA\u91CC\u7ED9\u51FA\u7528\u6237\u5173\u5FC3\u7684\u7ED3\u8BBA\u5373\u53EF\uFF0C\u670D\u52A1\u7AEF\u4F1A\u4F5C\u4E3A\u8BE5\u667A\u80FD\u4F53\u7684\u5B8C\u6210\u8BC4\u8BBA\u53D1\u5E03\u3002chat \u4F1A\u8BDD\u65E0 objectId \u7684\u4EFB\u52A1\uFF08\u7EAF\u5BF9\u8BDD\u6D3E\u53D1\uFF09\u4E0D\u56DE\u8BC4\u8BBA\u2014\u2014\u6700\u7EC8\u4EA4\u4ED8\u7531 daemon \u56DE\u586B assistant \u6D88\u606F\u3002
|
|
5436
|
-
- **\u7EC8\u7AEF\u8F93\u51FA\u3001\u601D\u8003\u8FC7\u7A0B\u3001\u5DE5\u5177\u65E5\u5FD7\u7528\u6237\u90FD\u770B\u4E0D\u5230**\u2014\u2014\u7528\u6237\u770B\u5230\u7684\u53EA\u6709\u670D\u52A1\u7AEF\u6574\u5408\u540E\u53D1\u5E03\u7684\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u3002
|
|
5437
|
-
- **\u8BC4\u8BBA @\u89E6\u53D1\u4EFB\u52A1\uFF08\u672C\u4EFB\u52A1\u7531\u8BC4\u8BBA\u89E6\u53D1\uFF09**\uFF1A\u670D\u52A1\u7AEF\u4F1A\u628A\u6700\u7EC8\u603B\u7ED3\u81EA\u52A8\u4F5C\u4E3A**\u8BE5\u89E6\u53D1\u8BC4\u8BBA\u7684\u56DE\u590D**\u53D1\u51FA\uFF08\u6302\u5728\u88AB @ \u7684\u8BC4\u8BBA\u4E0B\uFF09\u3002\u4F60\u65E0\u9700\u3001\u4E5F\u4E0D\u8981\u81EA\u5DF1\u8C03\u7528 \`morula comment add\`/add_comment\u3002
|
|
5438
|
-
- **\u9632\u91CD\u590D\uFF08\u670D\u52A1\u7AEF\u4FDD\u8BC1\uFF09**\uFF1A\u670D\u52A1\u7AEF\u5BF9\u300C\u540C\u4E00\u4E2A\u4EFB\u52A1\u53EA\u53D1\u4E00\u6761\u5B8C\u6210\u8BC4\u8BBA\u300D\u505A\u5E42\u7B49\uFF0C\u4E0D\u4F1A\u91CD\u590D\u5237\u5C4F\u3002\u4F60\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u8981\u53BB\u67E5/\u53D1\u591A\u6761\u3002
|
|
5439
|
-
- **\u6700\u7EC8\u8F93\u51FA = \u53EA\u7559\u7ED3\u8BBA**\uFF08\u4E0D\u8BBE\u5B57\u6570\u4E0A\u9650\u3001\u4E0D\u8981\u6C42\u56FA\u5B9A\u683C\u5F0F\uFF0C\u81EA\u7136\u7EC4\u7EC7\u5373\u53EF\uFF09\uFF1A
|
|
5440
|
-
- \u5FC5\u7B54\uFF1A**\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u679C\u5982\u4F55\u3001\u7528\u6237\u9700\u8981\u77E5\u9053\u7684\u540E\u7EED**\u3002
|
|
5441
|
-
- **PR \u6C47\u62A5\u4EC5\u5F53\u300C\u4EFB\u52A1\u6D89\u53CA\u4EE3\u7801\u6539\u52A8\u300D\u6216\u300C\u7528\u6237\u660E\u786E\u95EE\u5230 PR/\u5408\u5E76\u300D\u65F6\u7ED9\u51FA**\uFF1B\u7EAF\u95EE\u7B54/\u8C03\u7814\u7C7B\u4EFB\u52A1\u4E0D\u8981\u51FA\u73B0 PR/\u5206\u652F\u7AE0\u8282\uFF0C\u76F4\u63A5\u56DE\u7B54\u95EE\u9898\u3002
|
|
5442
|
-
- **\u4E0D\u8981\u5199**\uFF1A\u601D\u8003\u8FC7\u7A0B\u3001\u63A2\u7D22\u6B65\u9AA4\u3001\u5DE5\u5177\u65E5\u5FD7\u3001\u7EC8\u7AEF\u8F93\u51FA\u3001\u82F1\u6587\u8FC7\u7A0B\u6027\u53D9\u8FF0\u2014\u2014\u7528\u6237\u53EA\u5173\u5FC3\u300C\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u679C\u600E\u6837\u300D\uFF0C\u4E0D\u5173\u5FC3\u300C\u600E\u4E48\u505A\u7684\u300D\u3002
|
|
5443
|
-
- \u5185\u5BB9\u8F83\u591A\u65F6\u7528\u5C0F\u6807\u9898/\u5217\u8868\u7EC4\u7EC7\uFF0C\u8BA9\u7ED3\u8BBA\u5148\u7F6E\u9876\u3002
|
|
5444
|
-
- **\u8FC7\u7A0B\u6587\u672C\u5FC5\u987B\u52A0\u5206\u9694\u7B26**\uFF1A\u670D\u52A1\u7AEF\u53D6\u4F60\u7684\u6700\u7EC8\u8F93\u51FA\u65F6\u6309\u4F18\u5148\u7EA7\u622A\u53D6\u300C\u603B\u7ED3\u6BB5\u300D\uFF0C**\u6700\u9AD8\u4F18\u5148\u7EA7\u662F\u5206\u9694\u7B26 \`---FINAL---\`**\uFF08\u72EC\u5360\u4E00\u884C\u3001\u524D\u540E\u5404\u7A7A\u4E00\u884C\uFF09\u3002\u82E5\u4F60\u7684\u8F93\u51FA\u91CC\u6DF7\u5165\u4E86\u4E2D\u95F4\u8FC7\u7A0B\uFF08\u82F1\u6587\u63A2\u7D22\u53D9\u8FF0\u3001\u5DE5\u5177\u5C1D\u8BD5\u8BB0\u5F55\u3001\u601D\u8003\u7247\u6BB5\uFF09\uFF0C**\u5FC5\u987B**\u5728\u771F\u6B63\u7684\u603B\u7ED3\u4E4B\u524D\u63D2\u5165\u8BE5\u5206\u9694\u7B26\u2014\u2014\u670D\u52A1\u7AEF\u53EA\u4FDD\u7559\u5B83\u4E4B\u540E\u7684\u5185\u5BB9\uFF1A
|
|
5454
|
+
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B\uFF08\u6700\u7EC8\u603B\u7ED3\u7531\u4F60\u81EA\u5DF1\u53D1\u5E03\uFF09
|
|
5445
5455
|
|
|
5456
|
+
- **\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u4F60\u81EA\u5DF1\u53D1\u5E03**\uFF1A\u4EFB\u52A1\u6536\u5C3E\u65F6\u6267\u884C
|
|
5457
|
+
\`\`\`bash
|
|
5458
|
+
morula comment add --object <objectId> --final --content <\u7ED3\u8BBA>
|
|
5446
5459
|
\`\`\`
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5460
|
+
\u4F5C\u8005=\u4F60\uFF08\u667A\u80FD\u4F53\u8EAB\u4EFD\uFF09\uFF1B\u8BC4\u8BBA\u89E6\u53D1\u4EFB\u52A1\u65F6\u670D\u52A1\u7AEF\u4F1A**\u81EA\u52A8\u628A\u5B83\u6302\u5230\u89E6\u53D1\u8BC4\u8BBA\u4E0B**\u4F5C\u4E3A\u56DE\u590D\uFF08\u65E0\u9700\u81EA\u5DF1\u67E5\u8BC4\u8BBA ID\uFF09\u3002
|
|
5461
|
+
**\u5148\u53D1 final \u8BC4\u8BBA\u3001\u518D\u4E0A\u62A5\u4EFB\u52A1\u7ED3\u679C**\u2014\u2014\u670D\u52A1\u7AEF\u5728\u4F60\u5B8C\u6210\u65F6\u68C0\u67E5\uFF1A\u6CA1\u53D1\u8FC7 final \u624D\u4F1A\u515C\u5E95\u4EE3\u53D1\u4E00\u6761\uFF08\u540C\u4E00\u4EFB\u52A1\u53EA\u4F1A\u4FDD\u7559\u4E00\u6761\u6700\u7EC8\u603B\u7ED3\uFF0C\u91CD\u590D\u53D1\u9001\u4F1A\u88AB\u670D\u52A1\u7AEF\u62D2\u7EDD\uFF0C\u8FD9\u662F\u9884\u671F\u5185\u7684\u5E42\u7B49\u4FDD\u62A4\uFF09\u3002
|
|
5462
|
+
- **\u4F8B\u5916\uFF1A\u8BC4\u5BA1\u4EFB\u52A1\u4E0D\u9700\u8981\uFF08\u4E5F\u4E0D\u5E94\uFF09\u53D1 final**\u2014\u2014\u4F60\u7684\u7ED3\u8BBA\u5DF2\u7ECF\u901A\u8FC7 \`morula review submit\` \u673A\u5668\u534F\u8BAE\u4E0A\u62A5\uFF0C\u5E73\u53F0\u4F1A\u636E\u6B64\u5199\u6743\u5A01\u7ED3\u8BBA\u8BC4\u8BBA\uFF08\u2705 \u901A\u8FC7 / \u{1F4AC} \u975E\u963B\u585E / \u{1F501} \u672A\u901A\u8FC7\uFF09\uFF1B\u518D\u53D1\u4E00\u6761 final \u603B\u7ED3\u4F1A\u8BA9\u540C\u4E00\u7ED3\u8BBA\u5728\u8BC4\u8BBA\u533A**\u8BF4\u4E24\u904D**\uFF08\u670D\u52A1\u7AEF\u4E5F\u4F1A\u62D2\u7EDD\u8BC4\u5BA1\u4EFB\u52A1\u7684 final\uFF09\u3002\u9700\u8981\u53D1 final \u7684\u662F\u5F00\u53D1 / \u4FEE\u590D / \u5206\u6790 / \u95EE\u7B54 / \u8BC4\u8BBA\u89E6\u53D1\u7C7B\u4EFB\u52A1\u2014\u2014\u5185\u5BB9**\u7528\u4F60\u81EA\u5DF1\u7684\u8BDD\u5199**\uFF0C\u4E0D\u8981\u5957\u6A21\u677F\u3002
|
|
5463
|
+
- **\u666E\u901A\u8BC4\u8BBA**\uFF08\u8BF4\u660E\u8FDB\u5C55\u3001@ \u5176\u4ED6\u4EBA\u6216\u8FD0\u884C\u65F6\uFF09\u968F\u65F6\u53EF\u53D1\uFF1A
|
|
5464
|
+
\`\`\`bash
|
|
5465
|
+
morula comment add --object <objectId> --content <\u6587\u672C>
|
|
5466
|
+
morula comment add --object <objectId> --content <\u6587\u672C> --mention-agent <stageExecutorId> # @ \u9636\u6BB5\u8FD0\u884C\u65F6\uFF08\u89E6\u53D1\u8BE5\u6267\u884C\u5668\u5728\u672C\u4EFB\u52A1\u4E0A\u5E72\u6D3B\uFF09
|
|
5454
5467
|
\`\`\`
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
- \
|
|
5468
|
+
chat \u4F1A\u8BDD\u65E0 objectId \u7684\u4EFB\u52A1\uFF08\u7EAF\u5BF9\u8BDD\u6D3E\u53D1\uFF09\u4E0D\u53D1\u8BC4\u8BBA\u2014\u2014\u6700\u7EC8\u4EA4\u4ED8\u7531 daemon \u56DE\u586B assistant \u6D88\u606F\u3002
|
|
5469
|
+
- **\u8BC4\u8BBA\u7EAA\u5F8B\uFF1A\u53EA\u5199\u7ED3\u8BBA\u4E0E\u4E8B\u5B9E**\u2014\u2014\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u679C\u5982\u4F55\u3001\u7528\u6237\u9700\u8981\u77E5\u9053\u7684\u540E\u7EED\uFF1B**\u4E0D\u5199**\u601D\u8003\u8FC7\u7A0B\u3001\u63A2\u7D22\u6B65\u9AA4\u3001\u5DE5\u5177\u65E5\u5FD7\u3001\u7EC8\u7AEF\u8F93\u51FA\u3001\u547D\u4EE4\u56DE\u663E\u3002\u7528\u6237\u53EA\u5173\u5FC3\u300C\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u679C\u600E\u6837\u300D\uFF0C\u4E0D\u5173\u5FC3\u300C\u600E\u4E48\u505A\u7684\u300D\u3002\u5185\u5BB9\u8F83\u591A\u65F6\u7528\u5C0F\u6807\u9898/\u5217\u8868\u7EC4\u7EC7\uFF0C\u7ED3\u8BBA\u5148\u7F6E\u9876\u3002
|
|
5470
|
+
- **PR \u6C47\u62A5\u4EC5\u5F53\u300C\u4EFB\u52A1\u6D89\u53CA\u4EE3\u7801\u6539\u52A8\u300D\u6216\u300C\u7528\u6237\u660E\u786E\u95EE\u5230 PR/\u5408\u5E76\u300D\u65F6\u7ED9\u51FA**\uFF1B\u7EAF\u95EE\u7B54/\u8C03\u7814\u7C7B\u4EFB\u52A1\u4E0D\u8981\u51FA\u73B0 PR/\u5206\u652F\u7AE0\u8282\u3002
|
|
5471
|
+
- **\u4EFB\u52A1\u7ED3\u679C\u6587\u672C\u540C\u6837\u53EA\u5199\u7ED3\u8BBA**\uFF1A\u4E0A\u62A5\u7684\u4EFB\u52A1\u7ED3\u679C\u4F1A\u88AB\u670D\u52A1\u7AEF\u7528\u4E8E\u7F3A\u5931\u515C\u5E95\u4E0E\u901A\u77E5\u6458\u8981\u2014\u2014\u82E5\u7ED3\u679C\u91CC\u6DF7\u5165\u4E86\u4E2D\u95F4\u8FC7\u7A0B\uFF08\u82F1\u6587\u63A2\u7D22\u53D9\u8FF0\u3001\u5DE5\u5177\u5C1D\u8BD5\u8BB0\u5F55\u3001\u601D\u8003\u7247\u6BB5\uFF09\uFF0C**\u5FC5\u987B**\u5728\u771F\u6B63\u7684\u603B\u7ED3\u4E4B\u524D\u63D2\u5165\u72EC\u5360\u4E00\u884C\u7684 \`---FINAL---\` \u5206\u9694\u7B26\uFF0C\u670D\u52A1\u7AEF\u515C\u5E95\u65F6\u53EA\u4FDD\u7559\u5B83\u4E4B\u540E\u7684\u5185\u5BB9\uFF08\`---SUMMARY---\` \u540C\u6837\u517C\u5BB9\uFF1B\u4E0D\u5199\u65F6\u670D\u52A1\u7AEF\u9000\u56DE\u300C\u6309\u603B\u7ED3\u7C7B\u6807\u9898 / \u6C34\u5E73\u7EBF\u731C\u6D4B\u300D\uFF0C\u731C\u4E0D\u4E2D\u4F1A\u628A\u8FC7\u7A0B\u6587\u672C\u4E00\u8D77\u515C\u5E95\u51FA\u53BB\uFF09\u3002
|
|
5458
5472
|
- **\u4E0D\u8981\u628A\u672C\u5730\u7EDD\u5BF9\u8DEF\u5F84\u5F53\u4EA4\u4ED8\u7269**\uFF1A\u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\u53EA\u5C5E\u4E8E\u6267\u884C\u5B83\u7684\u673A\u5668\uFF0C\u8BFB\u8005\u6253\u4E0D\u5F00\uFF1B\u751F\u6210\u7684\u6587\u4EF6**\u5FC5\u987B\u5148\u56DE\u4F20\u5E73\u53F0**\uFF08\u89C1 1.1 \`morula attachment upload\`\uFF09\uFF0C\u4EA4\u4ED8\u7269\u7528\u5E73\u53F0\u4E0B\u8F7D\u94FE\u63A5\u6216\u4ED3\u5E93\u5185\u76F8\u5BF9\u8DEF\u5F84\u3002
|
|
5473
|
+
- \u4E0D\u8981\u5305\u542B\u5DE5\u5177\u8C03\u8BD5\u8FC7\u7A0B\u3001\u5185\u90E8\u65E5\u5FD7\u3001\u4EFB\u4F55\u51ED\u636E\u6216 token \u4FE1\u606F\u3002
|
|
5459
5474
|
|
|
5460
5475
|
## 6. \u7981\u6B62\u4E8B\u9879
|
|
5461
5476
|
|
|
5462
|
-
- \u7981\u6B62\u76F4\u63A5\u8C03\u7528 morula REST API / \u975E CLI \u65B9\u5F0F\u5199\u8BC4\u8BBA\u3001\u6539\u72B6\u6001\u3001\u8BFB\u6570\u636E\
|
|
5477
|
+
- \u7981\u6B62\u76F4\u63A5\u8C03\u7528 morula REST API / \u975E CLI \u65B9\u5F0F\u5199\u8BC4\u8BBA\u3001\u6539\u72B6\u6001\u3001\u8BFB\u6570\u636E\u2014\u2014\u4E0E\u5E73\u53F0\u4EA4\u4E92\u53EA\u8D70 morula CLI\uFF08\u8BC4\u8BBA\u7528\u7B2C 5 \u6761\u7684 \`morula comment add\`\uFF0C\u72B6\u6001\u7528\u7B2C 4 \u6761\u7684 \`morula task state\`\uFF09\u3002
|
|
5463
5478
|
- \u7981\u6B62\u8BFB\u53D6 \`~/.morula-cli\`\u3001\u51ED\u636E\u6587\u4EF6\u3001\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u5BC6\u94A5\u3002
|
|
5464
5479
|
- \u53EA\u5728\u4EFB\u52A1\u6307\u5B9A\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u5185\u64CD\u4F5C\uFF0C\u4E0D\u8981\u63A2\u7D22\u5DE5\u4F5C\u76EE\u5F55\u4E4B\u5916\u7684\u6587\u4EF6\u7CFB\u7EDF\u3002
|
|
5465
5480
|
- \u7981\u6B62\u628A token\u3001\u5B89\u88C5 ID\u3001\u5185\u90E8 URL \u5199\u8FDB\u8BC4\u8BBA\u6216 commit\u3002
|
|
@@ -5486,7 +5501,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
5486
5501
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
5487
5502
|
|------|------|------|
|
|
5488
5503
|
| CLI \u8BFB\u8BC4\u8BBA | \`morula comment list --object <objectId>\` | \`packages/morula-cli/src/commands/comment.ts\` |
|
|
5489
|
-
| CLI \u5199\u8BC4\u8BBA\uFF08**\
|
|
5504
|
+
| CLI \u5199\u8BC4\u8BBA\uFF08**\u7531 agent \u81EA\u5DF1\u53D1**\uFF0C\u542B \`--final\` \u6700\u7EC8\u603B\u7ED3\u4E0E \`--mention-agent\` @ \u8FD0\u884C\u65F6\uFF1B\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\` \u2192 \`POST /comments\`\uFF08agent_task \u51ED\u8BC1\uFF1B\`kind='final'\` \u6309 \`(taskId, kind)\` \u552F\u4E00\u9632\u91CD\uFF09 | \`packages/morula-cli/src/commands/comment.ts\`\u3001\`src/modules/task-management/comment/comment.service.ts\` |
|
|
5490
5505
|
| CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`packages/morula-cli/src/commands/task.ts\` |
|
|
5491
5506
|
| daemon \u6CE8\u5165\u51ED\u636E\uFF08agent \u65E0\u9700\u81EA\u884C\u767B\u5F55\uFF09 | runner \u6CE8\u5165 \`env\` \u7ED9 Provider \u5B50\u8FDB\u7A0B | \`packages/runtime/src/daemon/runner.ts\` |
|
|
5492
5507
|
| \u8C03 CLI \u7528 \`$MORULA_CLI\`\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4E0E\u5F53\u524D daemon \u540C\u6E90\uFF1BPATH \u4E0A\u7684\u88F8 \`morula\` \u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\uFF09 | \`resolveAgentCliPath\`\uFF08\u4F18\u5148\u672C\u5305 \`dist/morula.js\`\uFF0C\u4EA7\u7269\u7F3A\u5931\u56DE\u9000\u63D2\u4EF6\u542F\u52A8\u5668\uFF09\uFF1B\u6CE8\u5165\u70B9\uFF1A\u4EFB\u52A1\u94FE\u8DEF \`env\`\u3001\u4F1A\u8BDD\u94FE\u8DEF \`env\` | \`packages/runtime/src/daemon/plugin-register.ts\`\u3001\`runner.ts\`\u3001\`session-host.ts\` |
|
|
@@ -5505,12 +5520,15 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
5505
5520
|
| delivery \u4EE5\u672C\u5730\u5B9E\u9645\u5206\u652F\u4E3A\u51C6 | \`reportPrDelivery\` \u8BFB \`git branch --show-current\`\uFF0C\u8DF3\u8FC7\u5360\u4F4D/\u4E3B\u5E72 | \`packages/runtime/src/daemon/runner.ts\` |
|
|
5506
5521
|
| \u4EA4\u4ED8\u4E0A\u62A5\u904D\u5386\u672C\u4EFB\u52A1\u5168\u90E8\u4ED3\u5E93 | \u4E3B\u4ED3\u5E93\u76EE\u5F55\uFF08\u5DE5\u4F5C\u533A\u6839\u4E0B\u7684\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF09+ \u5176\u4F59\u540C\u7EA7\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF08prepare \u4EA7\u7269\uFF0C\u78C1\u76D8\u4E8B\u5B9E\uFF1B\u6309 repoFullName \u5F52\u5C5E\u6392\u9664\u4E3B\u4ED3\u5E93\uFF09\uFF0C\u9010\u4ED3\u4E0A\u62A5 branch/pushState\u3001\u9010\u4ED3\u72EC\u7ACB\u6210\u8D25\uFF1B\u6B21\u4ED3\u5E93 \`workContextId\` \u7701\u7565\u65F6\u670D\u52A1\u7AEF\u6309 \`(lastAgentTaskId, repoFullName)\` \u53CD\u67E5 | \`reportPrDelivery\`/\`reportRepoTargets\`\uFF08\`runner.ts\`\uFF09\u3001\`collectMaterializedRepoDeliveryTargets\`\uFF08\`repo-checkout-service.ts\`\uFF09 |
|
|
5507
5522
|
|
|
5508
|
-
## 4. \u72B6\u6001\
|
|
5523
|
+
## 4. \u72B6\u6001\u4E0E\u4EFB\u52A1\u64CD\u4F5C\uFF08\u7ED3\u8BBA\u4F18\u5148\u3001\u52A8\u4F5C\u4E3A\u8F85\uFF1Bproposal stage-runtime-as-task-actor\uFF09
|
|
5509
5524
|
|
|
5510
5525
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
5511
5526
|
|------|------|------|
|
|
5512
|
-
| merge \u81EA\u52A8\u5173\u5355\uFF08\
|
|
5513
|
-
|
|
|
5527
|
+
| merge \u81EA\u52A8\u5173\u5355\uFF08\u4E8B\u5B9E\u4E8B\u4EF6\u9A71\u52A8\u72B6\u6001\uFF09 | webhook merged \u2192 \u6309\u5E73\u53F0\u4E8B\u5B9E\u5B9A\u4F4D\u5DF2\u5173\u8054\u94FE\u63A5 \u2192 \u6C47\u603B \u2192 Object \u5B8C\u6210 | \`src/modules/vcs-integration/webhook/vcs-event.service.ts\`\u3001\`link/vcs-link.service.ts\` |
|
|
5528
|
+
| \u8FD0\u884C\u65F6\u53EF\u58F0\u660E\u72B6\u6001\u96C6\u5408 = \`in_progress\` / \`review\`\uFF08D8 \u843D\u5730\u53E3\u5F84\uFF1B\u7EC8\u6001\u4E0E backlog/todo \u4E0D\u53EF\u58F0\u660E\uFF0CD5\uFF09 | \`resolveRuntimeStateTransition\`\uFF08\u552F\u4E00\u5B9A\u4E49\uFF1A\u63A7\u5236\u5668\u7AEF\u70B9\u4E0E change_object_status \u5DE5\u5177\u5171\u7528\uFF09\uFF1B\u6B63\u5411\u8D70\u4E8B\u4EF6\u7EA7\u89C4\u5219 + ObjectService.update | \`src/modules/agent-task/domain/runtime-state-actions.ts\`\u3001\`agent-task.controller.ts\`\uFF08\`POST :id/state\`\uFF09 |
|
|
5529
|
+
| **\u5F85\u8BC4\u5BA1 \u2192 \u5F00\u53D1\u4E2D = \u9000\u56DE\u5F00\u53D1\uFF0C\u5355\u4E00\u6548\u5E94\u5165\u53E3**\uFF08\u8BB0\u8D26 + \u56DE\u9000 + \u6D3E\u4FEE\u590D + \u53CC\u89E6\u53D1\u53BB\u91CD\uFF1B\u4E0D\u505A\u88F8\u72B6\u6001\u5199\uFF09 | \`ReviewLoopService.runtimeRevertToDev\`\uFF08\u4E0E verdict \u8DEF\u5F84 \`handleRejected\` \u5171\u7528 \`dispatchDevRework\`\uFF1B\u4E8B\u5B9E\u53BB\u91CD = \u5BF9\u8C61\u5DF2\u6709\u672A\u5B8C\u6210 dev \u4EFB\u52A1\u5219\u53EA\u8BB0\u8D26\u4E0D\u6D3E\u5355\uFF09 | \`src/modules/agent-task/review-loop.service.ts\` |
|
|
5530
|
+
| \u4EFB\u52A1\u6D3B\u8DC3\u95E8\u7981\uFF08\u7EC8\u6001\u4EFB\u52A1\u7684 token \u4E0D\u5F97\u518D\u63A8\u72B6\u6001/\u6307\u6D3E\uFF09 | \`ACTIVE_TASK_STATUSES\`\uFF08queued/dispatched/running/awaiting_approval\uFF09 | \`src/modules/agent-task/domain/runtime-state-actions.ts\` |
|
|
5531
|
+
| \u6307\u6D3E\u8D1F\u8D23\u4EBA\uFF08\u76EE\u6807 = \u672C\u7A7A\u95F4\u6210\u5458\u6216\u9636\u6BB5\u6267\u884C\u5668 principal\uFF1B\u5931\u8D25\u663E\u5F0F\u62A5\u9519\uFF09 | \`POST /agent-tasks/:id/assignee\`\uFF08ObjectService.update \u843D\u5E93\uFF0Cagent_task \u5B57\u6BB5\u767D\u540D\u5355\u542B assigneePrincipalId\uFF09 | \`agent-task.controller.ts\`\u3001\`object.service.ts\` |
|
|
5514
5532
|
|
|
5515
5533
|
## 4.1 \u8BC4\u5BA1\u4EFB\u52A1\u7ED3\u8BBA\u56DE\u4F20\uFF08morula review submit\uFF09
|
|
5516
5534
|
|
|
@@ -5519,15 +5537,17 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
5519
5537
|
| \u8BC4\u5BA1\u7ED3\u8BBA\u673A\u5668\u534F\u8BAE\uFF08approved / changes_requested / commented\uFF0Cchanges_requested \u5FC5\u987B\u5E26 details\uFF09 | \`morula review submit <objectId> --verdict ... [--details] [--pr-number]\` \u2192 \`POST /agent-collaborations/review/submit\`\uFF08\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\uFF09 | \`packages/morula-cli/src/commands/review.ts\`\u3001\`src/modules/agent-task/review-loop.service.ts\` |
|
|
5520
5538
|
| \u8BC4\u5BA1\u5BF9\u8C61\uFF08objectId\uFF09\u7ECF AgentTask \u672C\u4F53\u4E0B\u53D1\uFF08\`morula task get\` \u65E0\u53C2\u8FD4\u56DE\uFF09 | AgentTask \u8BB0\u5F55 \`objectId\` \u5B57\u6BB5\uFF08enqueue \u65F6\u5199\u5165\uFF09 | \`src/modules/agent-task/review-loop.service.ts\` |
|
|
5521
5539
|
|
|
5522
|
-
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B\uFF08\
|
|
5540
|
+
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B\uFF08\u6700\u7EC8\u603B\u7ED3\u7531\u8FD0\u884C\u65F6\u81EA\u53D1\uFF0C\u670D\u52A1\u7AEF\u9632\u91CD\u515C\u5E95\uFF09
|
|
5523
5541
|
|
|
5524
5542
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
5525
5543
|
|------|------|------|
|
|
5526
|
-
| \u8BC4\u8BBA\u662F\u7528\u6237\u53EF\u89C1\u7684\
|
|
5527
|
-
| \
|
|
5528
|
-
|
|
|
5544
|
+
| \u8BC4\u8BBA\u662F\u7528\u6237\u53EF\u89C1\u7684\u4EA7\u51FA\u901A\u9053\uFF1B**\u6700\u7EC8\u603B\u7ED3\u7531 agent \u81EA\u5DF1\u53D1**\uFF08\`morula comment add --final\`\uFF0C\u4F5C\u8005=\u667A\u80FD\u4F53\uFF09 | \`POST /comments\`\uFF08agent_task \u51ED\u8BC1\uFF1B\`kind='final'\` \u670D\u52A1\u7AEF\u56DE\u586B \`taskId\`\uFF0C\`(tenantId, taskId, kind)\` \u552F\u4E00\u7EA6\u675F \u2192 \u540C\u4E00\u4EFB\u52A1\u4E00\u6761\uFF09 | \`packages/morula-cli/src/commands/comment.ts\`\u3001\`src/modules/task-management/comment/comment.service.ts\`\uFF08FINAL-COMMENT-001\uFF09 |
|
|
5545
|
+
| **\u670D\u52A1\u7AEF\u7F3A\u5931\u515C\u5E95**\uFF08DP-1\uFF09\uFF1A\u4EFB\u52A1\u5B8C\u6210\u65F6\u68C0\u67E5\u6CA1\u53D1\u8FC7 final \u624D\u4EE3\u53D1\u4E00\u6761\uFF08\u5360\u4F4F\u552F\u4E00\u952E\uFF1B\u8FD0\u884C\u65F6\u8FDF\u5230\u8865\u53D1\u88AB\u62D2\u7EDD\uFF09 | \`task-lifecycle.service.ts\` onTaskCompleted\uFF1A\`runtimeFinalExists\` \u68C0\u67E5 + \u515C\u5E95 create \u5E26 \`taskId/kind\`\uFF08\u5E76\u53D1\u7531 P2002 \u6355\u83B7\uFF09 | \`src/modules/agent-task/task-lifecycle.service.ts\` |
|
|
5546
|
+
| \u515C\u5E95\u8DEF\u5F84\u622A\u53D6\u300C\u603B\u7ED3\u6BB5\u300D\uFF08\`---FINAL---\` \u5206\u9694\u7B26 \u2192 \u603B\u7ED3\u6807\u9898 \u2192 \u6C34\u5E73\u7EBF \u2192 \u539F\u6587\u515C\u5E95\uFF09 | \`cleanFinalOutput\`\uFF08\u4EC5\u515C\u5E95\u4EE3\u53D1\u8DEF\u5F84\u4F7F\u7528\uFF1B\u8FD0\u884C\u65F6\u81EA\u53D1\u8DEF\u5F84\u5185\u5BB9\u7EAA\u5F8B\u9760\u5951\u7EA6\uFF09 | \`src/modules/agent-task/task-lifecycle.service.ts\` |
|
|
5547
|
+
| **\u8BC4\u8BBA @\u89E6\u53D1\u4EFB\u52A1 \u2192 agent \u7684 final \u81EA\u52A8\u6302\u5230\u89E6\u53D1\u8BC4\u8BBA\u4E0B**\uFF08agent \u65E0\u9700\u8BC4\u8BBA ID\uFF09 | \u7ED3\u6784\u6027\u6302\u9760\uFF1A\`CommentService.create\`\uFF08resolveCommentAuthor \u8FD4\u56DE triggerCommentId \u2192 parentCommentId \u9ED8\u8BA4\u503C\uFF09\uFF1B\u751F\u547D\u5468\u671F\u515C\u5E95\uFF1AhasAgentRepliedToTrigger / isMentionReply | LOCAL-AGENT-COMMENT-REPLY-001 |
|
|
5548
|
+
| **@ \u9636\u6BB5\u8FD0\u884C\u65F6**\uFF08\`--mention-agent <stageExecutorId>\`\uFF09\u2192 \u89E6\u53D1\u8BE5\u6267\u884C\u5668\u5728\u4EFB\u52A1\u4E0A\u5E72\u6D3B | mentions \u6570\u7EC4 agent \u7C7B\u578B\u9879 \u2192 \`comment.mentioned\` \u4E8B\u4EF6 \u2192 AgentCollaborationService \u6D3E\u53D1 | \`comment.service.ts\`\u3001\`agent-collaboration.service.ts\` |
|
|
5529
5549
|
| \u7981\u6B62\u672C\u5730\u7EDD\u5BF9\u8DEF\u5F84\u4F5C\u4E3A\u4EA4\u4ED8\u7269 | \u5951\u7EA6\u7B2C 5 \u6761\uFF1B\`reportPrDelivery\` \u53EA\u56DE\u62A5\u4ED3\u5E93\u76F8\u5BF9\u4FE1\u606F | \`packages/runtime/src/daemon/runner.ts\` |
|
|
5530
|
-
| \u5355\u6761\u6700\u7EC8\u8BC4\u8BBA\uFF08\u4E0D\u5237\u8FDB\u5EA6\uFF09 | \u5951\u7EA6\u7B2C 5 \u6761 | \u672C SKILL |
|
|
5550
|
+
| \u5355\u6761\u6700\u7EC8\u8BC4\u8BBA\uFF08\u4E0D\u5237\u8FDB\u5EA6\uFF1B\u8BC4\u8BBA\u53EA\u5199\u7ED3\u8BBA\u4E0E\u4E8B\u5B9E\uFF0CD10\uFF09 | \u5951\u7EA6\u7B2C 5 \u6761 + \u552F\u4E00\u7EA6\u675F | \u672C SKILL |
|
|
5531
5551
|
|
|
5532
5552
|
## 6. \u7981\u6B62\u4E8B\u9879
|
|
5533
5553
|
|
|
@@ -5778,7 +5798,7 @@ function outboxAge(entry) {
|
|
|
5778
5798
|
// src/daemon/plugin-register.ts
|
|
5779
5799
|
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";
|
|
5780
5800
|
import { homedir as homedir10 } from "node:os";
|
|
5781
|
-
import { dirname as
|
|
5801
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
5782
5802
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5783
5803
|
var PLUGIN_NAME = "morula-cli";
|
|
5784
5804
|
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
@@ -5793,9 +5813,9 @@ function defaultPluginsRoot(env = process.env, home = homedir10()) {
|
|
|
5793
5813
|
return join13(resolveDimcodeHome(env, home), "plugins");
|
|
5794
5814
|
}
|
|
5795
5815
|
function resolvePackagePaths(entryUrl) {
|
|
5796
|
-
const distDir =
|
|
5816
|
+
const distDir = dirname8(fileURLToPath2(entryUrl));
|
|
5797
5817
|
return {
|
|
5798
|
-
packageRoot:
|
|
5818
|
+
packageRoot: dirname8(distDir),
|
|
5799
5819
|
distDir,
|
|
5800
5820
|
assetsDir: join13(distDir, "plugin")
|
|
5801
5821
|
};
|
|
@@ -5811,7 +5831,25 @@ if [ -z "$NODE" ]; then
|
|
|
5811
5831
|
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
5812
5832
|
exit 127
|
|
5813
5833
|
fi
|
|
5814
|
-
|
|
5834
|
+
ENTRY=${JSON.stringify(entry)}
|
|
5835
|
+
# \u81EA\u6108\u56DE\u9000\uFF1A\u70D8\u7119\u7684\u5B89\u88C5\u76EE\u5F55\u88AB\u6E05\u7406/\u632A\u52A8\u65F6\uFF0C\u6309 daemon \u6CE8\u5165\u7684\u540C\u6E90\u5165\u53E3\u6216\u5F53\u524D morula-runtime \u91CD\u65B0\u5B9A\u4F4D
|
|
5836
|
+
if [ ! -f "$ENTRY" ] && [ -n "$MORULA_CLI" ] && [ -f "$MORULA_CLI" ]; then
|
|
5837
|
+
ENTRY="$MORULA_CLI"
|
|
5838
|
+
fi
|
|
5839
|
+
if [ ! -f "$ENTRY" ]; then
|
|
5840
|
+
RT="$(command -v morula-runtime 2>/dev/null || true)"
|
|
5841
|
+
if [ -n "$RT" ]; then
|
|
5842
|
+
ALT="$("$NODE" -e 'const{realpathSync}=require("fs");const{dirname,join}=require("path");try{const p=realpathSync(process.argv[1]);process.stdout.write(join(dirname(dirname(p)),"dist","morula.js"))}catch(e){}' "$RT" 2>/dev/null || true)"
|
|
5843
|
+
if [ -n "$ALT" ] && [ -f "$ALT" ]; then
|
|
5844
|
+
ENTRY="$ALT"
|
|
5845
|
+
fi
|
|
5846
|
+
fi
|
|
5847
|
+
fi
|
|
5848
|
+
if [ ! -f "$ENTRY" ]; then
|
|
5849
|
+
echo '{"code":"ERROR","message":"morula.js not found: the install dir was removed or moved; fix: morula-runtime setup (or npm i -g @arcships/morula-runtime)"}' >&2
|
|
5850
|
+
exit 127
|
|
5851
|
+
fi
|
|
5852
|
+
exec "$NODE" "$ENTRY" "$@"
|
|
5815
5853
|
`;
|
|
5816
5854
|
}
|
|
5817
5855
|
function assetsReady(paths) {
|
|
@@ -6068,7 +6106,7 @@ function isAcpInitRetryableError(error) {
|
|
|
6068
6106
|
async function withAcpInitRetry(setupOnce, deps = {}) {
|
|
6069
6107
|
const delays = deps.delaysMs ?? ACP_INIT_RETRY_DELAYS_MS;
|
|
6070
6108
|
const isRetryable = deps.isRetryable ?? isAcpInitRetryableError;
|
|
6071
|
-
const
|
|
6109
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6072
6110
|
const maxAttempts = delays.length + 1;
|
|
6073
6111
|
let lastError;
|
|
6074
6112
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -6083,7 +6121,7 @@ async function withAcpInitRetry(setupOnce, deps = {}) {
|
|
|
6083
6121
|
error: lastError instanceof Error ? lastError.message : String(lastError ?? "")
|
|
6084
6122
|
});
|
|
6085
6123
|
if (delayMs > 0)
|
|
6086
|
-
await
|
|
6124
|
+
await sleep(delayMs);
|
|
6087
6125
|
if (deps.isAborted?.())
|
|
6088
6126
|
throw new Error("\u4EFB\u52A1\u88AB\u53D6\u6D88");
|
|
6089
6127
|
}
|
|
@@ -6835,8 +6873,8 @@ var DimAgentAdapter = class {
|
|
|
6835
6873
|
}
|
|
6836
6874
|
}
|
|
6837
6875
|
async probe() {
|
|
6838
|
-
const { existsSync:
|
|
6839
|
-
const installed =
|
|
6876
|
+
const { existsSync: existsSync21 } = await import("node:fs");
|
|
6877
|
+
const installed = existsSync21(this.dimPath) || this.dimPath === "dim";
|
|
6840
6878
|
let authStatus = installed ? "unknown" : "error";
|
|
6841
6879
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
6842
6880
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -7129,7 +7167,7 @@ function pickLegacyDimModels(inventory) {
|
|
|
7129
7167
|
// src/daemon/repo-cache.ts
|
|
7130
7168
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
7131
7169
|
import { existsSync as existsSync12, mkdirSync as mkdirSync10 } from "node:fs";
|
|
7132
|
-
import { dirname as
|
|
7170
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7133
7171
|
function ownerOf(repoFullName) {
|
|
7134
7172
|
const idx = repoFullName.indexOf("/");
|
|
7135
7173
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
@@ -7164,7 +7202,7 @@ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRem
|
|
|
7164
7202
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
7165
7203
|
return;
|
|
7166
7204
|
}
|
|
7167
|
-
mkdirSync10(
|
|
7205
|
+
mkdirSync10(dirname9(repoCachePath), { recursive: true });
|
|
7168
7206
|
const cloneUrl = fetchUrl();
|
|
7169
7207
|
try {
|
|
7170
7208
|
execFileSync8("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
@@ -8282,11 +8320,333 @@ function materializeAdditionalRepo(input) {
|
|
|
8282
8320
|
return input.targetPath;
|
|
8283
8321
|
}
|
|
8284
8322
|
|
|
8323
|
+
// src/daemon/self-upgrade.ts
|
|
8324
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8325
|
+
import { closeSync as closeSync4, existsSync as existsSync16, mkdirSync as mkdirSync13, openSync as openSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
8326
|
+
import { dirname as dirname10, join as join19 } from "node:path";
|
|
8327
|
+
var RUNTIME_PACKAGE = "@arcships/morula-runtime";
|
|
8328
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
8329
|
+
var REGISTRY_TIMEOUT_MS = 1e4;
|
|
8330
|
+
var NPM_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8331
|
+
var NPM_ROLLBACK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8332
|
+
var RESTART_LOCK_WAIT_MS = 6e4;
|
|
8333
|
+
var RESTART_LIVENESS_CHECK_MS = 5e3;
|
|
8334
|
+
function resolveRegistry(env = process.env) {
|
|
8335
|
+
return (env.MORULA_NPM_REGISTRY ?? DEFAULT_NPM_REGISTRY).replace(/\/+$/, "");
|
|
8336
|
+
}
|
|
8337
|
+
function parseVersion(input) {
|
|
8338
|
+
if (typeof input !== "string")
|
|
8339
|
+
return null;
|
|
8340
|
+
const match = input.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
8341
|
+
if (!match)
|
|
8342
|
+
return null;
|
|
8343
|
+
const prerelease = match[4] ? match[4].split(".").map((part) => /^\d+$/.test(part) ? Number(part) : part) : [];
|
|
8344
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), prerelease };
|
|
8345
|
+
}
|
|
8346
|
+
function compareVersions(a, b) {
|
|
8347
|
+
const pa = parseVersion(a);
|
|
8348
|
+
const pb = parseVersion(b);
|
|
8349
|
+
if (!pa || !pb)
|
|
8350
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
8351
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
8352
|
+
if (pa[key] !== pb[key])
|
|
8353
|
+
return pa[key] < pb[key] ? -1 : 1;
|
|
8354
|
+
}
|
|
8355
|
+
if (pa.prerelease.length === 0 && pb.prerelease.length === 0)
|
|
8356
|
+
return 0;
|
|
8357
|
+
if (pa.prerelease.length === 0)
|
|
8358
|
+
return 1;
|
|
8359
|
+
if (pb.prerelease.length === 0)
|
|
8360
|
+
return -1;
|
|
8361
|
+
for (let i = 0; i < Math.max(pa.prerelease.length, pb.prerelease.length); i++) {
|
|
8362
|
+
const sa = pa.prerelease[i];
|
|
8363
|
+
const sb = pb.prerelease[i];
|
|
8364
|
+
if (sa === void 0)
|
|
8365
|
+
return -1;
|
|
8366
|
+
if (sb === void 0)
|
|
8367
|
+
return 1;
|
|
8368
|
+
if (sa === sb)
|
|
8369
|
+
continue;
|
|
8370
|
+
if (typeof sa === "number" && typeof sb === "number")
|
|
8371
|
+
return sa < sb ? -1 : 1;
|
|
8372
|
+
if (typeof sa === "number")
|
|
8373
|
+
return -1;
|
|
8374
|
+
if (typeof sb === "number")
|
|
8375
|
+
return 1;
|
|
8376
|
+
return sa < sb ? -1 : 1;
|
|
8377
|
+
}
|
|
8378
|
+
return 0;
|
|
8379
|
+
}
|
|
8380
|
+
function isUpgradeAvailable(current, latest) {
|
|
8381
|
+
if (!parseVersion(current) || !parseVersion(latest))
|
|
8382
|
+
return false;
|
|
8383
|
+
return compareVersions(current, latest) < 0;
|
|
8384
|
+
}
|
|
8385
|
+
function run2(command, args, opts) {
|
|
8386
|
+
return new Promise((resolve2) => {
|
|
8387
|
+
let child;
|
|
8388
|
+
try {
|
|
8389
|
+
child = spawn4(command, args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
8390
|
+
} catch (error) {
|
|
8391
|
+
resolve2({ ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : String(error) });
|
|
8392
|
+
return;
|
|
8393
|
+
}
|
|
8394
|
+
let stdout = "";
|
|
8395
|
+
let stderr = "";
|
|
8396
|
+
let settled = false;
|
|
8397
|
+
const timer = setTimeout(() => {
|
|
8398
|
+
if (!settled) {
|
|
8399
|
+
settled = true;
|
|
8400
|
+
try {
|
|
8401
|
+
child.kill("SIGKILL");
|
|
8402
|
+
} catch {
|
|
8403
|
+
}
|
|
8404
|
+
resolve2({ ok: false, code: null, stdout, stderr, error: `timeout after ${Math.round(opts.timeoutMs / 1e3)}s` });
|
|
8405
|
+
}
|
|
8406
|
+
}, opts.timeoutMs);
|
|
8407
|
+
timer.unref?.();
|
|
8408
|
+
child.stdout?.on("data", (d) => {
|
|
8409
|
+
stdout += d.toString();
|
|
8410
|
+
});
|
|
8411
|
+
child.stderr?.on("data", (d) => {
|
|
8412
|
+
stderr += d.toString();
|
|
8413
|
+
});
|
|
8414
|
+
child.on("error", (error) => {
|
|
8415
|
+
if (settled)
|
|
8416
|
+
return;
|
|
8417
|
+
settled = true;
|
|
8418
|
+
clearTimeout(timer);
|
|
8419
|
+
resolve2({ ok: false, code: null, stdout, stderr, error: error.message });
|
|
8420
|
+
});
|
|
8421
|
+
child.on("close", (code) => {
|
|
8422
|
+
if (settled)
|
|
8423
|
+
return;
|
|
8424
|
+
settled = true;
|
|
8425
|
+
clearTimeout(timer);
|
|
8426
|
+
resolve2({ ok: code === 0, code, stdout, stderr });
|
|
8427
|
+
});
|
|
8428
|
+
});
|
|
8429
|
+
}
|
|
8430
|
+
async function resolveNpmCommand() {
|
|
8431
|
+
const nodeDir = dirname10(process.execPath);
|
|
8432
|
+
const candidates = [
|
|
8433
|
+
{ command: "npm", args: [] },
|
|
8434
|
+
{ command: join19(nodeDir, "npm"), args: [] },
|
|
8435
|
+
{ command: process.execPath, args: [join19(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")] }
|
|
8436
|
+
];
|
|
8437
|
+
for (const candidate of candidates) {
|
|
8438
|
+
const res = await run2(candidate.command, [...candidate.args, "--version"], { timeoutMs: 15e3 });
|
|
8439
|
+
if (res.ok && /^\d+\.\d+\.\d+/.test(res.stdout.trim()))
|
|
8440
|
+
return candidate;
|
|
8441
|
+
}
|
|
8442
|
+
return null;
|
|
8443
|
+
}
|
|
8444
|
+
async function resolveNpmGlobalLayout() {
|
|
8445
|
+
const npm = await resolveNpmCommand();
|
|
8446
|
+
if (!npm)
|
|
8447
|
+
throw new Error("\u672A\u627E\u5230\u53EF\u7528\u7684 npm\uFF08\u5347\u7EA7\u9700\u8981 npm install -g\uFF1B\u8BF7\u786E\u8BA4 node/npm \u5B89\u88C5\u5B8C\u6574\uFF09");
|
|
8448
|
+
const prefixRes = await run2(npm.command, [...npm.args, "prefix", "-g"], { timeoutMs: 3e4 });
|
|
8449
|
+
const rootRes = await run2(npm.command, [...npm.args, "root", "-g"], { timeoutMs: 3e4 });
|
|
8450
|
+
const prefix = prefixRes.ok ? prefixRes.stdout.trim() : "";
|
|
8451
|
+
const nodeModulesRoot = rootRes.ok ? rootRes.stdout.trim() : "";
|
|
8452
|
+
if (!prefix || !nodeModulesRoot)
|
|
8453
|
+
throw new Error(`\u89E3\u6790 npm \u5168\u5C40\u5E03\u5C40\u5931\u8D25\uFF08prefix=${prefix || "?"} root=${nodeModulesRoot || "?"}\uFF09`);
|
|
8454
|
+
return {
|
|
8455
|
+
prefix,
|
|
8456
|
+
nodeModulesRoot,
|
|
8457
|
+
runtimePackageRoot: join19(nodeModulesRoot, RUNTIME_PACKAGE),
|
|
8458
|
+
runtimeBinPath: process.platform === "win32" ? join19(prefix, `${RUNTIME_PACKAGE.split("/")[1] ?? "morula-runtime"}.cmd`) : join19(prefix, "bin", "morula-runtime")
|
|
8459
|
+
};
|
|
8460
|
+
}
|
|
8461
|
+
async function fetchLatestRuntimeVersion(opts = {}) {
|
|
8462
|
+
const registry2 = opts.registry ?? resolveRegistry();
|
|
8463
|
+
const url = `${registry2}/${encodeURIComponent(RUNTIME_PACKAGE)}/latest`;
|
|
8464
|
+
const controller = new AbortController();
|
|
8465
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? REGISTRY_TIMEOUT_MS);
|
|
8466
|
+
timer.unref?.();
|
|
8467
|
+
try {
|
|
8468
|
+
const res = await fetch(url, { signal: controller.signal, headers: { Accept: "application/json" } });
|
|
8469
|
+
if (!res.ok)
|
|
8470
|
+
throw new Error(`registry ${res.status}`);
|
|
8471
|
+
const body = await res.json();
|
|
8472
|
+
const version = typeof body.version === "string" ? body.version.trim() : "";
|
|
8473
|
+
if (!version)
|
|
8474
|
+
throw new Error("registry \u54CD\u5E94\u7F3A\u5C11 version");
|
|
8475
|
+
return version;
|
|
8476
|
+
} catch (error) {
|
|
8477
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8478
|
+
throw new Error(`\u67E5\u8BE2 npm registry \u5931\u8D25\uFF08${url}\uFF09: ${message}`);
|
|
8479
|
+
} finally {
|
|
8480
|
+
clearTimeout(timer);
|
|
8481
|
+
}
|
|
8482
|
+
}
|
|
8483
|
+
async function npmContext(registry2) {
|
|
8484
|
+
const npm = await resolveNpmCommand();
|
|
8485
|
+
if (!npm)
|
|
8486
|
+
throw new Error("\u672A\u627E\u5230\u53EF\u7528\u7684 npm\uFF08\u5347\u7EA7\u9700\u8981 npm install -g\uFF1B\u8BF7\u786E\u8BA4 node/npm \u5B89\u88C5\u5B8C\u6574\uFF09");
|
|
8487
|
+
const layout = await resolveNpmGlobalLayout();
|
|
8488
|
+
return { npm, layout };
|
|
8489
|
+
}
|
|
8490
|
+
async function npmInstallGlobal(ctx, version, registry2, timeoutMs) {
|
|
8491
|
+
const args = [...ctx.npm.args, "install", "-g", `${RUNTIME_PACKAGE}@${version}`];
|
|
8492
|
+
if (registry2 && registry2 !== DEFAULT_NPM_REGISTRY)
|
|
8493
|
+
args.push("--registry", registry2);
|
|
8494
|
+
return run2(ctx.npm.command, args, { timeoutMs });
|
|
8495
|
+
}
|
|
8496
|
+
async function verifyRuntimeInstall(binPath, expected) {
|
|
8497
|
+
if (!existsSync16(binPath))
|
|
8498
|
+
return { ok: false, version: null, reason: `bin \u4E0D\u5B58\u5728\uFF1A${binPath}` };
|
|
8499
|
+
const res = await run2(binPath, ["--version"], { timeoutMs: 6e4 });
|
|
8500
|
+
if (!res.ok)
|
|
8501
|
+
return { ok: false, version: null, reason: `--version \u6267\u884C\u5931\u8D25\uFF08${res.error ?? `exit ${res.code}`}\uFF09\uFF1A${res.stderr.trim().slice(0, 300) || "\u65E0\u8F93\u51FA"}` };
|
|
8502
|
+
try {
|
|
8503
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
8504
|
+
const version = typeof parsed.version === "string" ? parsed.version : null;
|
|
8505
|
+
if (!version)
|
|
8506
|
+
return { ok: false, version: null, reason: `--version \u8F93\u51FA\u7F3A\u5C11 version\uFF1A${res.stdout.trim().slice(0, 200)}` };
|
|
8507
|
+
if (expected && version !== expected)
|
|
8508
|
+
return { ok: false, version, reason: `\u5B89\u88C5\u540E\u7248\u672C\u4E0D\u7B26\uFF1A\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${version}` };
|
|
8509
|
+
return { ok: true, version };
|
|
8510
|
+
} catch {
|
|
8511
|
+
return { ok: false, version: null, reason: `--version \u8F93\u51FA\u975E JSON\uFF1A${res.stdout.trim().slice(0, 200)}` };
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8514
|
+
async function performRuntimeUpgrade(opts) {
|
|
8515
|
+
const log = opts.log ?? (() => {
|
|
8516
|
+
});
|
|
8517
|
+
const registry2 = opts.registry ?? resolveRegistry();
|
|
8518
|
+
const steps = [];
|
|
8519
|
+
const from = opts.currentVersion;
|
|
8520
|
+
let target;
|
|
8521
|
+
if (opts.targetVersion) {
|
|
8522
|
+
target = opts.targetVersion;
|
|
8523
|
+
} else {
|
|
8524
|
+
target = await fetchLatestRuntimeVersion({ registry: registry2 });
|
|
8525
|
+
steps.push(`registry latest: ${target}`);
|
|
8526
|
+
}
|
|
8527
|
+
if (!isUpgradeAvailable(from, target)) {
|
|
8528
|
+
steps.push(`\u5F53\u524D ${from} \u5DF2\u4E0D\u4F4E\u4E8E ${target}\uFF0C\u65E0\u9700\u5347\u7EA7`);
|
|
8529
|
+
return { status: "up_to_date", fromVersion: from, targetVersion: target, steps };
|
|
8530
|
+
}
|
|
8531
|
+
log(`[upgrade] ${from} \u2192 ${target}\uFF08${registry2}\uFF09`);
|
|
8532
|
+
const ctx = await npmContext(registry2);
|
|
8533
|
+
steps.push(`npm install -g ${RUNTIME_PACKAGE}@${target}`);
|
|
8534
|
+
const install = await npmInstallGlobal(ctx, target, registry2, NPM_INSTALL_TIMEOUT_MS);
|
|
8535
|
+
if (!install.ok) {
|
|
8536
|
+
const error = `\u5B89\u88C5\u5931\u8D25\uFF1A${install.error ?? `exit ${install.code}`} ${install.stderr.trim().slice(0, 300)}`.trim();
|
|
8537
|
+
steps.push(error);
|
|
8538
|
+
log(`[upgrade] ${error}`);
|
|
8539
|
+
const still = await verifyRuntimeInstall(ctx.layout.runtimeBinPath);
|
|
8540
|
+
if (still.ok) {
|
|
8541
|
+
steps.push(`\u539F\u7248\u672C ${still.version ?? from} \u5B89\u88C5\u5B8C\u597D\uFF0C\u4FDD\u6301\u4E0D\u53D8`);
|
|
8542
|
+
return { status: "failed", fromVersion: from, toVersion: null, error, steps };
|
|
8543
|
+
}
|
|
8544
|
+
const rollback = await npmInstallGlobal(ctx, from, registry2, NPM_ROLLBACK_TIMEOUT_MS);
|
|
8545
|
+
if (rollback.ok) {
|
|
8546
|
+
steps.push(`\u5DF2\u56DE\u88C5\u539F\u7248\u672C ${from}`);
|
|
8547
|
+
return { status: "rolled_back", fromVersion: from, toVersion: null, error, steps };
|
|
8548
|
+
}
|
|
8549
|
+
steps.push(`\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25\uFF1A${rollback.error ?? `exit ${rollback.code}`}`);
|
|
8550
|
+
return { status: "failed", fromVersion: from, toVersion: null, error: `${error}\uFF1B\u4E14\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25\uFF0C\u5F53\u524D\u5168\u5C40\u5B89\u88C5\u53EF\u80FD\u635F\u574F\uFF08\u8BF7\u624B\u52A8\u6267\u884C npm i -g ${RUNTIME_PACKAGE}@${from}\uFF09`, steps };
|
|
8551
|
+
}
|
|
8552
|
+
const verified = await verifyRuntimeInstall(ctx.layout.runtimeBinPath, target);
|
|
8553
|
+
if (!verified.ok) {
|
|
8554
|
+
const error = `\u65B0\u5B89\u88C5\u9A8C\u8BC1\u5931\u8D25\uFF1A${verified.reason ?? "\u672A\u77E5\u539F\u56E0"}`;
|
|
8555
|
+
steps.push(error);
|
|
8556
|
+
log(`[upgrade] ${error}`);
|
|
8557
|
+
const rollback = await npmInstallGlobal(ctx, from, registry2, NPM_ROLLBACK_TIMEOUT_MS);
|
|
8558
|
+
if (rollback.ok) {
|
|
8559
|
+
const rollbackVerified = await verifyRuntimeInstall(ctx.layout.runtimeBinPath, from);
|
|
8560
|
+
if (rollbackVerified.ok) {
|
|
8561
|
+
steps.push(`\u5DF2\u56DE\u88C5\u539F\u7248\u672C ${from} \u5E76\u9A8C\u8BC1\u53EF\u7528`);
|
|
8562
|
+
return { status: "rolled_back", fromVersion: from, toVersion: null, error, steps };
|
|
8563
|
+
}
|
|
8564
|
+
steps.push(`\u56DE\u88C5\u540E\u9A8C\u8BC1\u5931\u8D25\uFF1A${rollbackVerified.reason ?? "\u672A\u77E5"}\uFF08\u8BF7\u624B\u52A8\u6267\u884C npm i -g ${RUNTIME_PACKAGE}@${from}\uFF09`);
|
|
8565
|
+
return { status: "failed", fromVersion: from, toVersion: null, error: `${error}\uFF1B\u56DE\u88C5 ${from} \u540E\u9A8C\u8BC1\u4ECD\u5931\u8D25`, steps };
|
|
8566
|
+
}
|
|
8567
|
+
steps.push(`\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25\uFF1A${rollback.error ?? `exit ${rollback.code}`}\uFF08\u8BF7\u624B\u52A8\u6267\u884C npm i -g ${RUNTIME_PACKAGE}@${from}\uFF09`);
|
|
8568
|
+
return { status: "failed", fromVersion: from, toVersion: null, error: `${error}\uFF1B\u4E14\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25`, steps };
|
|
8569
|
+
}
|
|
8570
|
+
steps.push(`\u65B0\u7248\u672C ${verified.version} \u9A8C\u8BC1\u901A\u8FC7`);
|
|
8571
|
+
const warnings = [];
|
|
8572
|
+
const setup = await run2(ctx.layout.runtimeBinPath, ["setup"], { timeoutMs: 6e4 });
|
|
8573
|
+
if (setup.ok) {
|
|
8574
|
+
steps.push("\u63D2\u4EF6\u76EE\u5F55\u5DF2\u91CD\u70D8\u7119\uFF08morula-runtime setup\uFF09");
|
|
8575
|
+
} else {
|
|
8576
|
+
const warning = `\u63D2\u4EF6\u76EE\u5F55\u91CD\u70D8\u7119\u5931\u8D25\uFF08${setup.error ?? `exit ${setup.code}`}\uFF09\u2014\u2014 \u4E0D\u5F71\u54CD\u672C\u6B21\u5347\u7EA7\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C morula-runtime setup`;
|
|
8577
|
+
warnings.push(warning);
|
|
8578
|
+
steps.push(warning);
|
|
8579
|
+
log(`[upgrade] ${warning}`);
|
|
8580
|
+
}
|
|
8581
|
+
log(`[upgrade] \u5347\u7EA7\u5B8C\u6210\uFF1A${from} \u2192 ${verified.version}`);
|
|
8582
|
+
return { status: "succeeded", fromVersion: from, toVersion: verified.version ?? target, warnings, steps };
|
|
8583
|
+
}
|
|
8584
|
+
async function spawnUpgradedDaemon(opts) {
|
|
8585
|
+
let packageRoot = opts.runtimePackageRoot;
|
|
8586
|
+
if (!packageRoot || !existsSync16(join19(packageRoot, "dist", "index.js"))) {
|
|
8587
|
+
const layout = await resolveNpmGlobalLayout();
|
|
8588
|
+
packageRoot = layout.runtimePackageRoot;
|
|
8589
|
+
}
|
|
8590
|
+
const entry = join19(packageRoot, "dist", "index.js");
|
|
8591
|
+
if (!existsSync16(entry))
|
|
8592
|
+
return { pid: 0, entry, error: `\u65B0\u5305\u5165\u53E3\u4E0D\u5B58\u5728\uFF1A${entry}`, waitExit: async () => ({ exited: true, code: null }) };
|
|
8593
|
+
mkdirSync13(dirname10(opts.logFile), { recursive: true, mode: 448 });
|
|
8594
|
+
let logFd;
|
|
8595
|
+
try {
|
|
8596
|
+
logFd = openSync4(opts.logFile, "a", 384);
|
|
8597
|
+
} catch (error) {
|
|
8598
|
+
return { pid: 0, entry, error: `\u65E0\u6CD5\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6 ${opts.logFile}: ${error instanceof Error ? error.message : String(error)}`, waitExit: async () => ({ exited: true, code: null }) };
|
|
8599
|
+
}
|
|
8600
|
+
try {
|
|
8601
|
+
const child = spawn4(process.execPath, [entry, "daemon", "run"], {
|
|
8602
|
+
cwd: process.cwd(),
|
|
8603
|
+
detached: true,
|
|
8604
|
+
stdio: ["ignore", logFd, logFd],
|
|
8605
|
+
env: {
|
|
8606
|
+
...opts.env,
|
|
8607
|
+
MORULA_DAEMON_NAME: opts.daemonConfig?.name ?? "",
|
|
8608
|
+
MORULA_DAEMON_WORKDIR: opts.daemonConfig?.workdir ?? "",
|
|
8609
|
+
MORULA_DAEMON_MAX_CONCURRENT: String(opts.daemonConfig?.maxConcurrent ?? ""),
|
|
8610
|
+
MORULA_DAEMON_RESTART_WAIT_MS: String(RESTART_LOCK_WAIT_MS)
|
|
8611
|
+
}
|
|
8612
|
+
});
|
|
8613
|
+
child.unref();
|
|
8614
|
+
if (!child.pid)
|
|
8615
|
+
return { pid: 0, entry, error: "spawn \u672A\u8FD4\u56DE pid", waitExit: async () => ({ exited: true, code: null }) };
|
|
8616
|
+
const exitPromise = new Promise((resolve2) => {
|
|
8617
|
+
child.on("exit", (code) => {
|
|
8618
|
+
resolve2(code ?? null);
|
|
8619
|
+
});
|
|
8620
|
+
});
|
|
8621
|
+
const waitExit = async (ms) => {
|
|
8622
|
+
return await Promise.race([
|
|
8623
|
+
exitPromise.then((code) => ({ exited: true, code })),
|
|
8624
|
+
new Promise((resolve2) => {
|
|
8625
|
+
const timer = setTimeout(() => {
|
|
8626
|
+
resolve2({ exited: false, code: null });
|
|
8627
|
+
}, ms);
|
|
8628
|
+
timer.unref?.();
|
|
8629
|
+
})
|
|
8630
|
+
]);
|
|
8631
|
+
};
|
|
8632
|
+
mkdirSync13(dirname10(opts.pidFile), { recursive: true, mode: 448 });
|
|
8633
|
+
writeFileSync11(opts.pidFile, String(child.pid), { mode: 384 });
|
|
8634
|
+
return { pid: child.pid, entry, waitExit };
|
|
8635
|
+
} catch (error) {
|
|
8636
|
+
return { pid: 0, entry, error: error instanceof Error ? error.message : String(error), waitExit: async () => ({ exited: true, code: null }) };
|
|
8637
|
+
} finally {
|
|
8638
|
+
try {
|
|
8639
|
+
closeSync4(logFd);
|
|
8640
|
+
} catch {
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
|
|
8285
8645
|
// src/daemon/session-host.ts
|
|
8286
8646
|
init_dist2();
|
|
8287
8647
|
init_acp_connection();
|
|
8288
|
-
import { mkdirSync as
|
|
8289
|
-
import { join as
|
|
8648
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync12 } from "node:fs";
|
|
8649
|
+
import { join as join20 } from "node:path";
|
|
8290
8650
|
|
|
8291
8651
|
// src/daemon/steward-instructions.ts
|
|
8292
8652
|
var STEWARD_SYSTEM_INSTRUCTIONS = `\u4F60\u662F\u300C\u7A7A\u95F4\u7BA1\u5BB6\u667A\u80FD\u4F53\u300D\uFF08Project Steward\uFF09\u3002
|
|
@@ -8540,8 +8900,8 @@ var SessionHost = class {
|
|
|
8540
8900
|
async open(cmd) {
|
|
8541
8901
|
if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
|
|
8542
8902
|
return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
|
|
8543
|
-
const workspaceDir =
|
|
8544
|
-
|
|
8903
|
+
const workspaceDir = join20(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
|
|
8904
|
+
mkdirSync14(workspaceDir, { recursive: true });
|
|
8545
8905
|
this.writeInjections(workspaceDir, cmd);
|
|
8546
8906
|
const existing = this.sessions.get(cmd.sessionId);
|
|
8547
8907
|
if (existing?.conn.isAlive) {
|
|
@@ -8827,7 +9187,7 @@ var SessionHost = class {
|
|
|
8827
9187
|
---
|
|
8828
9188
|
|
|
8829
9189
|
${cmd.prompt}`;
|
|
8830
|
-
const
|
|
9190
|
+
const run5 = (async () => {
|
|
8831
9191
|
try {
|
|
8832
9192
|
const { blocks, fileRefs } = await this.materializeAttachments(managed.workspaceDir, cmd.attachments ?? []);
|
|
8833
9193
|
const promptBlocks = [{ type: "text", text: fileRefs.length > 0 ? `${text}
|
|
@@ -8874,8 +9234,8 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8874
9234
|
this.touch(managed);
|
|
8875
9235
|
}
|
|
8876
9236
|
})();
|
|
8877
|
-
managed.promptInFlight =
|
|
8878
|
-
return
|
|
9237
|
+
managed.promptInFlight = run5;
|
|
9238
|
+
return run5;
|
|
8879
9239
|
}
|
|
8880
9240
|
/**
|
|
8881
9241
|
* 附件物化(§五 P2-3):
|
|
@@ -8890,7 +9250,7 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8890
9250
|
const fileRefs = [];
|
|
8891
9251
|
if (attachments.length === 0)
|
|
8892
9252
|
return { blocks, fileRefs };
|
|
8893
|
-
const dir =
|
|
9253
|
+
const dir = join20(workspaceDir, "attachments");
|
|
8894
9254
|
for (const attachment of attachments) {
|
|
8895
9255
|
try {
|
|
8896
9256
|
const res = await fetch(attachment.downloadUrl, { signal: AbortSignal.timeout(ATTACHMENT_FETCH_TIMEOUT_MS) });
|
|
@@ -8900,9 +9260,9 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8900
9260
|
if (attachment.isImage) {
|
|
8901
9261
|
blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
|
|
8902
9262
|
} else {
|
|
8903
|
-
|
|
8904
|
-
const target =
|
|
8905
|
-
|
|
9263
|
+
mkdirSync14(dir, { recursive: true });
|
|
9264
|
+
const target = join20(dir, safeAttachmentName(attachment.fileName));
|
|
9265
|
+
writeFileSync12(target, bytes);
|
|
8906
9266
|
fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
|
|
8907
9267
|
}
|
|
8908
9268
|
} catch (err) {
|
|
@@ -8998,13 +9358,13 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8998
9358
|
/** 工作区注入物(幂等覆盖;daemon 侧组装,P1-6) */
|
|
8999
9359
|
writeInjections(workspaceDir, cmd) {
|
|
9000
9360
|
writeProjectAgentsMd(workspaceDir, cmd.agentsMd, this.log);
|
|
9001
|
-
const skillDir =
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
const contextDir =
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9361
|
+
const skillDir = join20(workspaceDir, ".agents", "skills", STEWARD_SKILL_NAME);
|
|
9362
|
+
mkdirSync14(skillDir, { recursive: true });
|
|
9363
|
+
writeFileSync12(join20(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
|
|
9364
|
+
const contextDir = join20(workspaceDir, ".agent_context");
|
|
9365
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
9366
|
+
writeFileSync12(
|
|
9367
|
+
join20(contextDir, "steward-context.md"),
|
|
9008
9368
|
buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }),
|
|
9009
9369
|
"utf8"
|
|
9010
9370
|
);
|
|
@@ -9548,6 +9908,12 @@ var DaemonRunner = class {
|
|
|
9548
9908
|
outboxConsecutiveFailures = 0;
|
|
9549
9909
|
outboxPausedUntil = 0;
|
|
9550
9910
|
processing = /* @__PURE__ */ new Set();
|
|
9911
|
+
/**
|
|
9912
|
+
* runtime-self-upgrade:远程升级执行中标志。
|
|
9913
|
+
* 置位期间 tick / WS 派发**不领新任务**——升级完成后 daemon 会重启,中途接的任务会被打断
|
|
9914
|
+
* (心跳超时 → 服务端回收重派),违背「升级不破坏正在执行的任务」的约束。
|
|
9915
|
+
*/
|
|
9916
|
+
upgradeInProgress = false;
|
|
9551
9917
|
/** M1-A1:任务级取消信号(WS cancelled 事件 / 状态轮询触发 abort) */
|
|
9552
9918
|
cancels = /* @__PURE__ */ new Map();
|
|
9553
9919
|
/** Phase 2:审批决策等待表(approvalId → resolver;WS 事件 resolve,轮询兜底) */
|
|
@@ -9689,7 +10055,10 @@ var DaemonRunner = class {
|
|
|
9689
10055
|
models: dimModels,
|
|
9690
10056
|
// LOCAL-AGENT-COLLAB-001 Phase 1:宿主协议 + Provider inventory
|
|
9691
10057
|
hostProtocolVersion: 1,
|
|
9692
|
-
|
|
10058
|
+
// selfUpgrade(runtime-self-upgrade 评审 P2):本 daemon 认识心跳响应里的 upgradeRequested
|
|
10059
|
+
// 指令并具备自升级能力。管理页据此门控远程升级按钮——旧版 daemon 会静默吞掉指令,
|
|
10060
|
+
// 对它显示按钮只会误导用户(能力自报比服务端维护最低版本号更准确,register 即刷新)
|
|
10061
|
+
hostCapabilities: { worktreeV2: true, workspaceCleanupV1: false, selfUpgrade: true },
|
|
9693
10062
|
inventoryRevision: `daemon-v${BUILD_VERSION2}`,
|
|
9694
10063
|
providers: inventory
|
|
9695
10064
|
});
|
|
@@ -9707,10 +10076,33 @@ var DaemonRunner = class {
|
|
|
9707
10076
|
await this.tick();
|
|
9708
10077
|
if (!this.running)
|
|
9709
10078
|
break;
|
|
9710
|
-
await
|
|
10079
|
+
await this.waitForPollInterval();
|
|
9711
10080
|
}
|
|
9712
10081
|
this.stop();
|
|
9713
10082
|
}
|
|
10083
|
+
/** 下一拍等待(POLL_INTERVAL_MS,可被 wakeMainLoop 提前唤醒) */
|
|
10084
|
+
pollWake = null;
|
|
10085
|
+
waitForPollInterval() {
|
|
10086
|
+
return new Promise((resolve2) => {
|
|
10087
|
+
const timer = setTimeout(() => {
|
|
10088
|
+
this.pollWake = null;
|
|
10089
|
+
resolve2();
|
|
10090
|
+
}, POLL_INTERVAL_MS);
|
|
10091
|
+
timer.unref?.();
|
|
10092
|
+
this.pollWake = () => {
|
|
10093
|
+
clearTimeout(timer);
|
|
10094
|
+
this.pollWake = null;
|
|
10095
|
+
resolve2();
|
|
10096
|
+
};
|
|
10097
|
+
});
|
|
10098
|
+
}
|
|
10099
|
+
/**
|
|
10100
|
+
* 唤醒主循环的 poll 等待(评审 P4 配套):远程升级完成(running=false)后立即生效,
|
|
10101
|
+
* 不必最多干等 15s 的 poll 间隔——旧进程越快退出释放锁,新进程越快接管,自重启窗口越小。
|
|
10102
|
+
*/
|
|
10103
|
+
wakeMainLoop() {
|
|
10104
|
+
this.pollWake?.();
|
|
10105
|
+
}
|
|
9714
10106
|
stop() {
|
|
9715
10107
|
this.running = false;
|
|
9716
10108
|
this.stopHeartbeatTimer();
|
|
@@ -9775,6 +10167,10 @@ var DaemonRunner = class {
|
|
|
9775
10167
|
this.running = false;
|
|
9776
10168
|
return;
|
|
9777
10169
|
}
|
|
10170
|
+
if (res?.upgradeRequested) {
|
|
10171
|
+
this.log(`[daemon] \u6536\u5230\u670D\u52A1\u7AEF\u8FDC\u7A0B\u5347\u7EA7\u6307\u4EE4${res.upgradeVersion ? `\uFF08\u76EE\u6807 ${res.upgradeVersion}\uFF09` : ""}`);
|
|
10172
|
+
void this.handleRemoteUpgrade(res.upgradeVersion ?? null);
|
|
10173
|
+
}
|
|
9778
10174
|
if (!res.ok && res.status === "unknown") {
|
|
9779
10175
|
this.log("[daemon] \u672C\u8BBE\u5907\u5DF2\u5728\u670D\u52A1\u7AEF\u6CE8\u9500\uFF08runtime \u8BB0\u5F55\u4E0D\u5B58\u5728\uFF09\uFF0C\u81EA\u52A8\u9000\u51FA");
|
|
9780
10176
|
this.running = false;
|
|
@@ -9793,6 +10189,95 @@ var DaemonRunner = class {
|
|
|
9793
10189
|
this.heartbeatInFlight = false;
|
|
9794
10190
|
}
|
|
9795
10191
|
}
|
|
10192
|
+
/**
|
|
10193
|
+
* runtime-self-upgrade:远程升级指令执行(心跳认领后异步触发,见 sendHeartbeat)。
|
|
10194
|
+
*
|
|
10195
|
+
* 边界:
|
|
10196
|
+
* - 有任务在跑(processing 非空)→ 回报 busy 拒绝(指令已消耗,页面可重新下发);
|
|
10197
|
+
* - 空闲 → performRuntimeUpgrade(安装/验证/失败回装/重烘焙插件目录);期间心跳照常、不领新任务;
|
|
10198
|
+
* - 成功 → 用**新包入口** detached 拉起新 daemon(等旧进程释放单实例锁)→ 回报 → 本实例退出;
|
|
10199
|
+
* - 失败/回退 → 回报后**继续用原版本运行**(升级失败不中断服务)。
|
|
10200
|
+
*/
|
|
10201
|
+
async handleRemoteUpgrade(targetVersion) {
|
|
10202
|
+
if (!this.runtimeId)
|
|
10203
|
+
return;
|
|
10204
|
+
if (this.upgradeInProgress) {
|
|
10205
|
+
this.log("[daemon] \u5347\u7EA7\u5DF2\u5728\u8FDB\u884C\uFF0C\u5FFD\u7565\u91CD\u590D\u6307\u4EE4");
|
|
10206
|
+
return;
|
|
10207
|
+
}
|
|
10208
|
+
this.upgradeInProgress = true;
|
|
10209
|
+
const fromVersion = BUILD_VERSION2;
|
|
10210
|
+
try {
|
|
10211
|
+
if (this.processing.size > 0) {
|
|
10212
|
+
const error = `${this.processing.size} \u4E2A\u4EFB\u52A1\u6267\u884C\u4E2D`;
|
|
10213
|
+
this.log(`[daemon] \u5347\u7EA7\u6307\u4EE4\u88AB\u62D2\u7EDD\uFF1A${error}`);
|
|
10214
|
+
await this.reportUpgradeResultSafe({ status: "busy", fromVersion, error });
|
|
10215
|
+
return;
|
|
10216
|
+
}
|
|
10217
|
+
const report = await performRuntimeUpgrade({
|
|
10218
|
+
currentVersion: fromVersion,
|
|
10219
|
+
targetVersion,
|
|
10220
|
+
log: (line) => this.log(line)
|
|
10221
|
+
});
|
|
10222
|
+
if (report.status === "up_to_date") {
|
|
10223
|
+
this.log(`[daemon] \u5F53\u524D ${fromVersion} \u5DF2\u4E0D\u4F4E\u4E8E ${report.targetVersion}\uFF0C\u65E0\u9700\u5347\u7EA7`);
|
|
10224
|
+
await this.reportUpgradeResultSafe({ status: "succeeded", fromVersion, toVersion: fromVersion, restart: false });
|
|
10225
|
+
return;
|
|
10226
|
+
}
|
|
10227
|
+
if (report.status !== "succeeded") {
|
|
10228
|
+
this.log(`[daemon] \u5347\u7EA7\u5931\u8D25\uFF08${report.status}\uFF09\uFF1A${report.error}\u2014\u2014\u7EE7\u7EED\u7528 ${fromVersion} \u8FD0\u884C`);
|
|
10229
|
+
await this.reportUpgradeResultSafe({
|
|
10230
|
+
status: report.status,
|
|
10231
|
+
fromVersion,
|
|
10232
|
+
toVersion: report.toVersion ?? void 0,
|
|
10233
|
+
error: report.error
|
|
10234
|
+
});
|
|
10235
|
+
return;
|
|
10236
|
+
}
|
|
10237
|
+
const cfg = loadDaemonConfig(this.store);
|
|
10238
|
+
const dir = daemonDirForEnv(process.env);
|
|
10239
|
+
const spawned = await spawnUpgradedDaemon({
|
|
10240
|
+
env: process.env,
|
|
10241
|
+
logFile: join21(dir, "daemon.log"),
|
|
10242
|
+
pidFile: join21(dir, "daemon.pid"),
|
|
10243
|
+
daemonConfig: { name: cfg?.name, workdir: cfg?.workdir, maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT }
|
|
10244
|
+
});
|
|
10245
|
+
if (spawned.pid <= 0) {
|
|
10246
|
+
const error = `\u65B0\u7248\u672C ${report.toVersion} \u5DF2\u5B89\u88C5\uFF0C\u4F46\u81EA\u52A8\u91CD\u542F\u5931\u8D25\uFF1A${spawned.error ?? "\u672A\u77E5\u539F\u56E0"}\uFF08\u672C\u5B9E\u4F8B\u7EE7\u7EED\u8FD0\u884C\u65E7\u4EE3\u7801\uFF0C\u8BF7\u624B\u52A8\u6267\u884C morula-runtime daemon start \u91CD\u542F\uFF09`;
|
|
10247
|
+
this.log(`[daemon] ${error}`);
|
|
10248
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, toVersion: report.toVersion, error });
|
|
10249
|
+
return;
|
|
10250
|
+
}
|
|
10251
|
+
this.log(`[daemon] \u65B0 daemon \u5DF2\u62C9\u8D77\uFF08pid=${spawned.pid}\uFF09\uFF0C\u89C2\u5BDF ${Math.round(RESTART_LIVENESS_CHECK_MS / 1e3)}s \u786E\u8BA4\u63A5\u7BA1\u2026`);
|
|
10252
|
+
const liveness = await spawned.waitExit(RESTART_LIVENESS_CHECK_MS);
|
|
10253
|
+
if (liveness.exited) {
|
|
10254
|
+
const error = `\u65B0\u7248\u672C ${report.toVersion} \u5DF2\u5B89\u88C5\uFF0C\u4F46\u65B0 daemon \u8FDB\u7A0B\u7ACB\u5373\u9000\u51FA\uFF08code=${liveness.code ?? "\u672A\u77E5"}\uFF0C\u8BE6\u89C1 ${join21(dir, "daemon.log")}\uFF09\u2014\u2014\u672C\u5B9E\u4F8B\u7EE7\u7EED\u8FD0\u884C\u65E7\u4EE3\u7801\uFF0C\u8BF7\u68C0\u67E5\u65E5\u5FD7\u540E\u624B\u52A8 morula-runtime daemon start`;
|
|
10255
|
+
this.log(`[daemon] ${error}`);
|
|
10256
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, toVersion: report.toVersion, error });
|
|
10257
|
+
return;
|
|
10258
|
+
}
|
|
10259
|
+
this.log("[daemon] \u65B0\u8FDB\u7A0B\u8FD0\u884C\u4E2D\uFF08\u7B49\u672C\u5B9E\u4F8B\u9000\u51FA\u91CA\u653E\u9501\u540E\u63A5\u7BA1\uFF09\uFF0C\u56DE\u62A5\u6210\u529F\u5E76\u9000\u51FA");
|
|
10260
|
+
await this.reportUpgradeResultSafe({ status: "succeeded", fromVersion, toVersion: report.toVersion, restart: true });
|
|
10261
|
+
this.running = false;
|
|
10262
|
+
this.wakeMainLoop();
|
|
10263
|
+
} catch (error) {
|
|
10264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10265
|
+
this.log(`[daemon] \u81EA\u5347\u7EA7\u5F02\u5E38\uFF1A${message}\u2014\u2014\u7EE7\u7EED\u7528 ${fromVersion} \u8FD0\u884C`);
|
|
10266
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, error: message });
|
|
10267
|
+
} finally {
|
|
10268
|
+
this.upgradeInProgress = false;
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
10271
|
+
/** 升级结果回报(best-effort:服务端不可达不影响本地升级流程) */
|
|
10272
|
+
async reportUpgradeResultSafe(body) {
|
|
10273
|
+
if (!this.runtimeId)
|
|
10274
|
+
return;
|
|
10275
|
+
try {
|
|
10276
|
+
await this.api.reportUpgradeResult(this.runtimeId, body);
|
|
10277
|
+
} catch (error) {
|
|
10278
|
+
this.log(`[daemon] \u5347\u7EA7\u7ED3\u679C\u56DE\u62A5\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u5730\u6D41\u7A0B\uFF09: ${error instanceof Error ? error.message : String(error)}`);
|
|
10279
|
+
}
|
|
10280
|
+
}
|
|
9796
10281
|
/**
|
|
9797
10282
|
* tick:poll 兜底 + 取消轮询 + cleanup 补拉(心跳与 outbox 补投均已独立于本循环)。
|
|
9798
10283
|
* D1:整轮加时间预算——超预算记 WARN 并丢弃本轮等待下一拍,避免单个慢请求把 tick 拖死。
|
|
@@ -9821,6 +10306,8 @@ var DaemonRunner = class {
|
|
|
9821
10306
|
async tickOnce() {
|
|
9822
10307
|
if (!this.runtimeId)
|
|
9823
10308
|
return;
|
|
10309
|
+
if (this.upgradeInProgress)
|
|
10310
|
+
return;
|
|
9824
10311
|
try {
|
|
9825
10312
|
const tasks = await this.api.pollTasks(this.runtimeId);
|
|
9826
10313
|
for (const task of tasks) {
|
|
@@ -10220,6 +10707,10 @@ var DaemonRunner = class {
|
|
|
10220
10707
|
async handleTask(taskId) {
|
|
10221
10708
|
if (this.processing.has(taskId))
|
|
10222
10709
|
return;
|
|
10710
|
+
if (this.upgradeInProgress) {
|
|
10711
|
+
this.log(`[daemon] \u5347\u7EA7\u8FDB\u884C\u4E2D\uFF0C\u5FFD\u7565\u4EFB\u52A1\u6D3E\u53D1 task=${taskId}`);
|
|
10712
|
+
return;
|
|
10713
|
+
}
|
|
10223
10714
|
this.processing.add(taskId);
|
|
10224
10715
|
const ctrl = new AbortController();
|
|
10225
10716
|
this.cancels.set(taskId, ctrl);
|
|
@@ -10489,12 +10980,12 @@ var DaemonRunner = class {
|
|
|
10489
10980
|
});
|
|
10490
10981
|
if (spaceToken?.token)
|
|
10491
10982
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
10492
|
-
repoDir =
|
|
10983
|
+
repoDir = join21(cfg.workdir, "scratch", taskId);
|
|
10493
10984
|
taskRoot = repoDir;
|
|
10494
|
-
|
|
10495
|
-
const readme =
|
|
10496
|
-
if (!
|
|
10497
|
-
|
|
10985
|
+
mkdirSync15(repoDir, { recursive: true });
|
|
10986
|
+
const readme = join21(repoDir, "README.md");
|
|
10987
|
+
if (!existsSync17(readme)) {
|
|
10988
|
+
writeFileSync13(
|
|
10498
10989
|
readme,
|
|
10499
10990
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
10500
10991
|
|
|
@@ -10792,7 +11283,7 @@ var DaemonRunner = class {
|
|
|
10792
11283
|
}, { log: (line) => this.log(`[daemon] ${line}`) });
|
|
10793
11284
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
10794
11285
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
10795
|
-
const canResume = !!resumeSessionId && !!resumeWorkDir &&
|
|
11286
|
+
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync17(resumeWorkDir);
|
|
10796
11287
|
const input = {
|
|
10797
11288
|
executionId: `${taskId}:1`,
|
|
10798
11289
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
@@ -11053,7 +11544,7 @@ var DaemonRunner = class {
|
|
|
11053
11544
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
11054
11545
|
return;
|
|
11055
11546
|
}
|
|
11056
|
-
const managedRoot =
|
|
11547
|
+
const managedRoot = join21(cfg.workdir, "worktrees", this.tenantId);
|
|
11057
11548
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
11058
11549
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
11059
11550
|
ok: false,
|
|
@@ -11092,47 +11583,44 @@ var DaemonRunner = class {
|
|
|
11092
11583
|
}
|
|
11093
11584
|
}
|
|
11094
11585
|
};
|
|
11095
|
-
function sleep(ms) {
|
|
11096
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
11097
|
-
}
|
|
11098
11586
|
function isDebugLogEnabled() {
|
|
11099
11587
|
return isFlagEnabled("MORULA_DAEMON_DEBUG_LOG", false);
|
|
11100
11588
|
}
|
|
11101
11589
|
var spaceTokenFailureLogged = false;
|
|
11102
11590
|
function writeCustomSkills(targetDir, skills, log) {
|
|
11103
11591
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
11104
|
-
const base =
|
|
11592
|
+
const base = join21(targetDir, ".agents", "skills");
|
|
11105
11593
|
for (const skill of skills) {
|
|
11106
11594
|
const name = skill.name.trim();
|
|
11107
11595
|
if (!NAME_RE.test(name)) {
|
|
11108
11596
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
11109
11597
|
continue;
|
|
11110
11598
|
}
|
|
11111
|
-
const dir =
|
|
11112
|
-
if (
|
|
11599
|
+
const dir = join21(base, name);
|
|
11600
|
+
if (dirname11(dir) !== base) {
|
|
11113
11601
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
11114
11602
|
continue;
|
|
11115
11603
|
}
|
|
11116
|
-
|
|
11604
|
+
mkdirSync15(dir, { recursive: true });
|
|
11117
11605
|
const meta = ["---", `name: ${name}`];
|
|
11118
11606
|
if (skill.description?.trim()) {
|
|
11119
11607
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
11120
11608
|
}
|
|
11121
11609
|
meta.push("---", "");
|
|
11122
|
-
|
|
11610
|
+
writeFileSync13(join21(dir, "SKILL.md"), `${meta.join("\n")}
|
|
11123
11611
|
${skill.content ?? ""}`, "utf8");
|
|
11124
11612
|
}
|
|
11125
11613
|
}
|
|
11126
11614
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
11127
|
-
const dir =
|
|
11128
|
-
const refsDir =
|
|
11129
|
-
|
|
11615
|
+
const dir = join21(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
11616
|
+
const refsDir = join21(dir, "references");
|
|
11617
|
+
mkdirSync15(refsDir, { recursive: true });
|
|
11130
11618
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
11131
11619
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
11132
11620
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
11133
|
-
|
|
11621
|
+
writeFileSync13(join21(dir, "SKILL.md"), `${front}
|
|
11134
11622
|
${body}`, "utf8");
|
|
11135
|
-
|
|
11623
|
+
writeFileSync13(join21(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
11136
11624
|
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/`);
|
|
11137
11625
|
}
|
|
11138
11626
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -11163,8 +11651,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
11163
11651
|
};
|
|
11164
11652
|
}
|
|
11165
11653
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
11166
|
-
const briefDir =
|
|
11167
|
-
|
|
11654
|
+
const briefDir = join21(targetDir, ".agent_context");
|
|
11655
|
+
mkdirSync15(briefDir, { recursive: true });
|
|
11168
11656
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
11169
11657
|
|
|
11170
11658
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -11174,7 +11662,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
11174
11662
|
---
|
|
11175
11663
|
|
|
11176
11664
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
11177
|
-
|
|
11665
|
+
writeFileSync13(join21(briefDir, "task-context.md"), brief, "utf8");
|
|
11178
11666
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
11179
11667
|
}
|
|
11180
11668
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -11188,8 +11676,8 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
11188
11676
|
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`);
|
|
11189
11677
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
11190
11678
|
}
|
|
11191
|
-
|
|
11192
|
-
|
|
11679
|
+
mkdirSync15(targetDir, { recursive: true });
|
|
11680
|
+
writeFileSync13(join21(targetDir, "AGENTS.md"), `${content}
|
|
11193
11681
|
`, "utf8");
|
|
11194
11682
|
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`);
|
|
11195
11683
|
return true;
|
|
@@ -11248,7 +11736,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
11248
11736
|
}
|
|
11249
11737
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
11250
11738
|
const execEnv = mergeExecEnv(env);
|
|
11251
|
-
if (!
|
|
11739
|
+
if (!existsSync17(target.dir) || !existsSync17(join21(target.dir, ".git"))) {
|
|
11252
11740
|
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`);
|
|
11253
11741
|
return;
|
|
11254
11742
|
}
|
|
@@ -11416,9 +11904,9 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
11416
11904
|
|
|
11417
11905
|
// src/commands/doctor.ts
|
|
11418
11906
|
init_dist2();
|
|
11419
|
-
import { existsSync as
|
|
11907
|
+
import { existsSync as existsSync18, readFileSync as readFileSync11, realpathSync } from "node:fs";
|
|
11420
11908
|
import { homedir as homedir12 } from "node:os";
|
|
11421
|
-
import { dirname as
|
|
11909
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
11422
11910
|
init_daemon_config();
|
|
11423
11911
|
init_daemon_lock();
|
|
11424
11912
|
init_dim_locate();
|
|
@@ -11438,16 +11926,16 @@ async function doctorCommand(context) {
|
|
|
11438
11926
|
async function collectDoctor(context, probe) {
|
|
11439
11927
|
const checks = [];
|
|
11440
11928
|
const env = context.env;
|
|
11441
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
11442
|
-
const configPath = env.MORULA_CONFIG ??
|
|
11443
|
-
const daemonPath =
|
|
11444
|
-
const logFile =
|
|
11929
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join22(homedir12(), ".morula-cli");
|
|
11930
|
+
const configPath = env.MORULA_CONFIG ?? join22(configDir, "config.yaml");
|
|
11931
|
+
const daemonPath = join22(dirname12(configPath), "daemon");
|
|
11932
|
+
const logFile = join22(daemonPath, "daemon.log");
|
|
11445
11933
|
const contextName = context.runtime.context ?? null;
|
|
11446
11934
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
11447
11935
|
const tokenPresent = Boolean(context.runtime.token);
|
|
11448
11936
|
const tokenSource = context.runtime.tokenSource ?? null;
|
|
11449
11937
|
let configReadable = false;
|
|
11450
|
-
if (
|
|
11938
|
+
if (existsSync18(configPath)) {
|
|
11451
11939
|
try {
|
|
11452
11940
|
readFileSync11(configPath, "utf8");
|
|
11453
11941
|
configReadable = true;
|
|
@@ -11455,9 +11943,9 @@ async function collectDoctor(context, probe) {
|
|
|
11455
11943
|
configReadable = false;
|
|
11456
11944
|
}
|
|
11457
11945
|
}
|
|
11458
|
-
const lockPath =
|
|
11946
|
+
const lockPath = join22(daemonPath, DAEMON_LOCK_FILE);
|
|
11459
11947
|
const lock = probeDaemonLock(lockPath);
|
|
11460
|
-
const pidFile =
|
|
11948
|
+
const pidFile = join22(daemonPath, DAEMON_PID_FILE);
|
|
11461
11949
|
const filePid = readPidFile(pidFile);
|
|
11462
11950
|
const daemonPid = probeDaemonInstance(configDir).pid;
|
|
11463
11951
|
const cfg = loadDaemonConfig(context.store);
|
|
@@ -11509,8 +11997,8 @@ async function collectDoctor(context, probe) {
|
|
|
11509
11997
|
hint: "morula-runtime auth login"
|
|
11510
11998
|
});
|
|
11511
11999
|
} else if (probe) {
|
|
11512
|
-
const
|
|
11513
|
-
checks.push(
|
|
12000
|
+
const check2 = await probeAuth(context);
|
|
12001
|
+
checks.push(check2);
|
|
11514
12002
|
} else {
|
|
11515
12003
|
checks.push({
|
|
11516
12004
|
id: "auth.token",
|
|
@@ -11519,7 +12007,7 @@ async function collectDoctor(context, probe) {
|
|
|
11519
12007
|
hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
|
|
11520
12008
|
});
|
|
11521
12009
|
}
|
|
11522
|
-
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } :
|
|
12010
|
+
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync18(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" });
|
|
11523
12011
|
if (!dimFound) {
|
|
11524
12012
|
checks.push({
|
|
11525
12013
|
id: "dim.present",
|
|
@@ -11626,19 +12114,13 @@ function detectInstallMode() {
|
|
|
11626
12114
|
}
|
|
11627
12115
|
|
|
11628
12116
|
// src/commands/daemon.ts
|
|
11629
|
-
function configFilePath(env) {
|
|
11630
|
-
return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
|
|
11631
|
-
}
|
|
11632
|
-
function daemonDir(configPath) {
|
|
11633
|
-
return join22(dirname11(configPath), "daemon");
|
|
11634
|
-
}
|
|
11635
12117
|
var PID_FILE = DAEMON_PID_FILE;
|
|
11636
12118
|
var LOG_FILE = "daemon.log";
|
|
11637
12119
|
var DAEMON_EXIT_FALLBACK_MS = 3e3;
|
|
11638
12120
|
async function daemonCommand(context) {
|
|
11639
12121
|
const { action } = context.parsed;
|
|
11640
12122
|
if (action === "run")
|
|
11641
|
-
return
|
|
12123
|
+
return run3(context);
|
|
11642
12124
|
if (action === "start")
|
|
11643
12125
|
return start(context);
|
|
11644
12126
|
if (action === "stop")
|
|
@@ -11815,7 +12297,7 @@ async function models(context) {
|
|
|
11815
12297
|
contextWindow: m.contextWindow
|
|
11816
12298
|
}));
|
|
11817
12299
|
}
|
|
11818
|
-
async function
|
|
12300
|
+
async function run3(context) {
|
|
11819
12301
|
rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent"]);
|
|
11820
12302
|
const armBoundedExit = () => {
|
|
11821
12303
|
setTimeout(() => process.exit(0), DAEMON_EXIT_FALLBACK_MS).unref();
|
|
@@ -11828,9 +12310,18 @@ async function run2(context) {
|
|
|
11828
12310
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
11829
12311
|
}
|
|
11830
12312
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
11831
|
-
|
|
11832
|
-
const lockPath =
|
|
11833
|
-
|
|
12313
|
+
mkdirSync16(resolvedWorkdir, { recursive: true });
|
|
12314
|
+
const lockPath = join23(daemonDirForEnv(context.env), DAEMON_LOCK_FILE);
|
|
12315
|
+
let acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
12316
|
+
const restartWaitMs = Number(context.env.MORULA_DAEMON_RESTART_WAIT_MS ?? 0) || 0;
|
|
12317
|
+
if (!acquired.ok && restartWaitMs > 0) {
|
|
12318
|
+
const deadline = Date.now() + restartWaitMs;
|
|
12319
|
+
context.err(`[daemon] \u81EA\u5347\u7EA7\u91CD\u542F\uFF1A\u7B49\u5F85\u65E7\u5B9E\u4F8B\u91CA\u653E\u5355\u5B9E\u4F8B\u9501\uFF08\u6700\u591A ${Math.round(restartWaitMs / 1e3)}s\uFF09`);
|
|
12320
|
+
while (!acquired.ok && Date.now() < deadline) {
|
|
12321
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
12322
|
+
acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
12323
|
+
}
|
|
12324
|
+
}
|
|
11834
12325
|
if (!acquired.ok) {
|
|
11835
12326
|
const pid = acquired.existing?.pid;
|
|
11836
12327
|
if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
|
|
@@ -11856,7 +12347,7 @@ async function run2(context) {
|
|
|
11856
12347
|
log: (line) => context.err(line),
|
|
11857
12348
|
// dim acp 子进程治理(issues/dim-acp-process-leak-shared-space-contention.md):
|
|
11858
12349
|
// spawn 记录落盘(供 daemon 重启后的启动自愈认领自己的孤儿)+ 无归属子进程周期巡检
|
|
11859
|
-
acpChildRecordFile:
|
|
12350
|
+
acpChildRecordFile: join23(daemonDirForEnv(context.env), "dim-acp-children.json"),
|
|
11860
12351
|
// D6:认证失败时重新读 config 换票(不传则退回「启动即快照」旧行为)。
|
|
11861
12352
|
// 只重算凭证,不重建 client——WS 用返回值更新 socket.auth 后重连。
|
|
11862
12353
|
resolveToken: () => {
|
|
@@ -11907,12 +12398,12 @@ async function start(context) {
|
|
|
11907
12398
|
throw error;
|
|
11908
12399
|
identityError = error instanceof Error ? error.message : String(error);
|
|
11909
12400
|
}
|
|
11910
|
-
const dir =
|
|
12401
|
+
const dir = daemonDirForEnv(context.env);
|
|
11911
12402
|
if (!dryRun)
|
|
11912
|
-
|
|
11913
|
-
const pidFile =
|
|
11914
|
-
const logFile =
|
|
11915
|
-
const lockPath =
|
|
12403
|
+
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
12404
|
+
const pidFile = join23(dir, PID_FILE);
|
|
12405
|
+
const logFile = join23(dir, LOG_FILE);
|
|
12406
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
11916
12407
|
const lock = probeDaemonLock(lockPath);
|
|
11917
12408
|
if (lock.state === "active") {
|
|
11918
12409
|
throw new CliError(
|
|
@@ -11923,7 +12414,7 @@ async function start(context) {
|
|
|
11923
12414
|
{ hint: "morula-runtime daemon status" }
|
|
11924
12415
|
);
|
|
11925
12416
|
}
|
|
11926
|
-
if (
|
|
12417
|
+
if (existsSync19(pidFile)) {
|
|
11927
12418
|
const pid = Number(readFileSync12(pidFile, "utf8").trim());
|
|
11928
12419
|
if (isProcessAlive(pid)) {
|
|
11929
12420
|
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, {
|
|
@@ -11974,19 +12465,19 @@ async function start(context) {
|
|
|
11974
12465
|
hint: `tail -n 50 ${logFile}`
|
|
11975
12466
|
});
|
|
11976
12467
|
}
|
|
11977
|
-
|
|
12468
|
+
writeFileSync14(pidFile, String(result.pid), { mode: 384 });
|
|
11978
12469
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
11979
12470
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
11980
12471
|
}
|
|
11981
12472
|
async function stop(context) {
|
|
11982
12473
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
11983
12474
|
const dryRun = isDryRun(context);
|
|
11984
|
-
const dir =
|
|
11985
|
-
const pidFile =
|
|
11986
|
-
const lockPath =
|
|
12475
|
+
const dir = daemonDirForEnv(context.env);
|
|
12476
|
+
const pidFile = join23(dir, PID_FILE);
|
|
12477
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
11987
12478
|
const lock = probeDaemonLock(lockPath);
|
|
11988
12479
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
11989
|
-
const filePid =
|
|
12480
|
+
const filePid = existsSync19(pidFile) ? Number(readFileSync12(pidFile, "utf8").trim()) : null;
|
|
11990
12481
|
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
11991
12482
|
if (dryRun) {
|
|
11992
12483
|
return {
|
|
@@ -12013,9 +12504,9 @@ async function stop(context) {
|
|
|
12013
12504
|
async function status2(context) {
|
|
12014
12505
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
12015
12506
|
const cfg = loadDaemonConfig(context.store);
|
|
12016
|
-
const dir =
|
|
12017
|
-
const lockPath =
|
|
12018
|
-
const probe = probeDaemonInstance(
|
|
12507
|
+
const dir = daemonDirForEnv(context.env);
|
|
12508
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
12509
|
+
const probe = probeDaemonInstance(dirname13(configFilePathForEnv(context.env)));
|
|
12019
12510
|
return {
|
|
12020
12511
|
running: probe.running,
|
|
12021
12512
|
pid: probe.pid,
|
|
@@ -12024,7 +12515,7 @@ async function status2(context) {
|
|
|
12024
12515
|
name: cfg?.name ?? null,
|
|
12025
12516
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
12026
12517
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
12027
|
-
logFile:
|
|
12518
|
+
logFile: join23(dir, LOG_FILE),
|
|
12028
12519
|
lockFile: lockPath
|
|
12029
12520
|
};
|
|
12030
12521
|
}
|
|
@@ -12081,11 +12572,228 @@ async function setupCommand(context) {
|
|
|
12081
12572
|
};
|
|
12082
12573
|
}
|
|
12083
12574
|
|
|
12575
|
+
// src/commands/upgrade.ts
|
|
12576
|
+
init_dist2();
|
|
12577
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync17, readFileSync as readFileSync13, rmSync as rmSync11 } from "node:fs";
|
|
12578
|
+
import { join as join24 } from "node:path";
|
|
12579
|
+
init_daemon_config();
|
|
12580
|
+
init_daemon_lock();
|
|
12581
|
+
var PID_FILE2 = DAEMON_PID_FILE;
|
|
12582
|
+
var LOG_FILE2 = "daemon.log";
|
|
12583
|
+
async function upgradeCommand(context) {
|
|
12584
|
+
const { action } = context.parsed;
|
|
12585
|
+
if (action === "check")
|
|
12586
|
+
return check(context);
|
|
12587
|
+
if (action === "run")
|
|
12588
|
+
return run4(context);
|
|
12589
|
+
throw usage("usage: morula-runtime upgrade <check|run>", {
|
|
12590
|
+
hint: "morula-runtime upgrade --help"
|
|
12591
|
+
});
|
|
12592
|
+
}
|
|
12593
|
+
async function check(context) {
|
|
12594
|
+
rejectUnknownFlags(context.parsed.flags, ["registry"]);
|
|
12595
|
+
const registry2 = flagString(context.parsed.flags, "registry") ?? resolveRegistry(context.env);
|
|
12596
|
+
let latest = null;
|
|
12597
|
+
let error = null;
|
|
12598
|
+
try {
|
|
12599
|
+
latest = await fetchLatestRuntimeVersion({ registry: registry2 });
|
|
12600
|
+
} catch (e) {
|
|
12601
|
+
error = e instanceof Error ? e.message : String(e);
|
|
12602
|
+
}
|
|
12603
|
+
if (latest === null) {
|
|
12604
|
+
context.setExitCode(EXIT.UPSTREAM);
|
|
12605
|
+
return {
|
|
12606
|
+
ok: false,
|
|
12607
|
+
current: BUILD_VERSION2,
|
|
12608
|
+
latest: null,
|
|
12609
|
+
updateAvailable: null,
|
|
12610
|
+
registry: registry2,
|
|
12611
|
+
error,
|
|
12612
|
+
hint: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE\u6216 registry \u4E0D\u53EF\u7528\uFF1B\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u76F4\u63A5\u6267\u884C morula-runtime upgrade run --yes\uFF08run \u4F1A\u81EA\u884C\u89E3\u6790\u76EE\u6807\u7248\u672C\uFF09"
|
|
12613
|
+
};
|
|
12614
|
+
}
|
|
12615
|
+
const updateAvailable = isUpgradeAvailable(BUILD_VERSION2, latest);
|
|
12616
|
+
return {
|
|
12617
|
+
ok: true,
|
|
12618
|
+
current: BUILD_VERSION2,
|
|
12619
|
+
latest,
|
|
12620
|
+
updateAvailable,
|
|
12621
|
+
registry: registry2,
|
|
12622
|
+
...updateAvailable ? { hint: "morula-runtime upgrade run --yes\uFF08\u5347\u7EA7\u4F1A\u5148\u786E\u8BA4\u65E0\u4EFB\u52A1\u6267\u884C\u4E2D\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u88C5\u539F\u7248\u672C\uFF09" } : {}
|
|
12623
|
+
};
|
|
12624
|
+
}
|
|
12625
|
+
function probeDaemonPid(env) {
|
|
12626
|
+
const dir = daemonDirForEnv(env);
|
|
12627
|
+
const pidFile = join24(dir, PID_FILE2);
|
|
12628
|
+
const lockPath = join24(dir, DAEMON_LOCK_FILE);
|
|
12629
|
+
const lock = probeDaemonLock(lockPath);
|
|
12630
|
+
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
12631
|
+
const filePid = existsSync20(pidFile) ? Number(readFileSync13(pidFile, "utf8").trim()) : null;
|
|
12632
|
+
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
12633
|
+
return { pid, pidFile, lockPath, lockState: lock.state };
|
|
12634
|
+
}
|
|
12635
|
+
async function reportUpgradeResult(context, body) {
|
|
12636
|
+
const cfg = loadDaemonConfig(context.store);
|
|
12637
|
+
if (!context.runtime.token || !cfg?.runtimeId)
|
|
12638
|
+
return;
|
|
12639
|
+
try {
|
|
12640
|
+
await context.client.http.post(`/agent-runtimes/${cfg.runtimeId}/upgrade-result`, body);
|
|
12641
|
+
} catch (error) {
|
|
12642
|
+
context.err(`[upgrade] \u5347\u7EA7\u7ED3\u679C\u56DE\u62A5\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u5730\u7ED3\u679C\uFF09: ${error instanceof Error ? error.message : String(error)}`);
|
|
12643
|
+
}
|
|
12644
|
+
}
|
|
12645
|
+
async function countActiveTasks(context, runtimeId) {
|
|
12646
|
+
const res = await context.client.http.get(
|
|
12647
|
+
"/agent-tasks",
|
|
12648
|
+
{ runtimeId, statusGroup: "active", page: 1, pageSize: 100 }
|
|
12649
|
+
);
|
|
12650
|
+
return (res?.data ?? []).filter((t) => ["dispatched", "running", "awaiting_approval"].includes(t.status)).length;
|
|
12651
|
+
}
|
|
12652
|
+
async function run4(context) {
|
|
12653
|
+
rejectUnknownFlags(context.parsed.flags, ["yes", "version", "registry"]);
|
|
12654
|
+
const yes = flagBoolean(context.parsed.flags, "yes");
|
|
12655
|
+
const targetVersion = flagString(context.parsed.flags, "version") ?? null;
|
|
12656
|
+
const registry2 = flagString(context.parsed.flags, "registry") ?? resolveRegistry(context.env);
|
|
12657
|
+
if (!yes) {
|
|
12658
|
+
throw usage("upgrade run \u9700\u8981 --yes \u663E\u5F0F\u786E\u8BA4\uFF08\u5148\u8DD1 morula-runtime upgrade check \u67E5\u770B\u5DEE\u8DDD\uFF09", {
|
|
12659
|
+
hint: "morula-runtime upgrade run --yes"
|
|
12660
|
+
});
|
|
12661
|
+
}
|
|
12662
|
+
const dir = daemonDirForEnv(context.env);
|
|
12663
|
+
const pidFile = join24(dir, PID_FILE2);
|
|
12664
|
+
const lockPath = join24(dir, DAEMON_LOCK_FILE);
|
|
12665
|
+
const probe = probeDaemonPid(context.env);
|
|
12666
|
+
const wasRunning = probe.pid !== null;
|
|
12667
|
+
const cfgForSafety = loadDaemonConfig(context.store);
|
|
12668
|
+
let activeTasks = 0;
|
|
12669
|
+
if (wasRunning && cfgForSafety?.runtimeId) {
|
|
12670
|
+
if (!context.runtime.token) {
|
|
12671
|
+
throw new CliError(
|
|
12672
|
+
"TASKS_UNKNOWN",
|
|
12673
|
+
EXIT.UNAUTHENTICATED,
|
|
12674
|
+
"daemon \u8FD0\u884C\u4E2D\u4F46\u5F53\u524D\u672A\u767B\u5F55\uFF0C\u65E0\u6CD5\u786E\u8BA4\u662F\u5426\u6709\u4EFB\u52A1\u6267\u884C\u4E2D\uFF0C\u5DF2\u62D2\u7EDD\u5347\u7EA7",
|
|
12675
|
+
false,
|
|
12676
|
+
{ hint: "morula-runtime auth login \u540E\u91CD\u8BD5\uFF1B\u6216\u786E\u8BA4\u53EF\u4E2D\u65AD\u540E\u5148 morula-runtime daemon stop" }
|
|
12677
|
+
);
|
|
12678
|
+
}
|
|
12679
|
+
try {
|
|
12680
|
+
activeTasks = await countActiveTasks(context, cfgForSafety.runtimeId);
|
|
12681
|
+
} catch (error) {
|
|
12682
|
+
throw new CliError(
|
|
12683
|
+
"TASKS_UNKNOWN",
|
|
12684
|
+
EXIT.UPSTREAM,
|
|
12685
|
+
`daemon \u8FD0\u884C\u4E2D\u4E14\u65E0\u6CD5\u786E\u8BA4\u4EFB\u52A1\u72B6\u6001\uFF08${error instanceof Error ? error.message : String(error)}\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u5347\u7EA7`,
|
|
12686
|
+
false,
|
|
12687
|
+
{ hint: "\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u5148 morula-runtime daemon stop \u518D\u5347\u7EA7\uFF08\u5347\u7EA7\u540E daemon \u4E0D\u4F1A\u81EA\u52A8\u62C9\u8D77\uFF09" }
|
|
12688
|
+
);
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
if (activeTasks > 0) {
|
|
12692
|
+
throw new CliError(
|
|
12693
|
+
"TASKS_RUNNING",
|
|
12694
|
+
EXIT.CONFLICT,
|
|
12695
|
+
`daemon \u6709 ${activeTasks} \u4E2A\u4EFB\u52A1\u6267\u884C\u4E2D\uFF0C\u62D2\u7EDD\u5347\u7EA7\uFF08\u5347\u7EA7\u4F1A\u7834\u574F\u6B63\u5728\u6267\u884C\u7684\u4EFB\u52A1\uFF09`,
|
|
12696
|
+
false,
|
|
12697
|
+
{ hint: "\u7B49\u4EFB\u52A1\u5B8C\u6210\u540E\u518D\u5347\u7EA7\uFF1B\u6216\u786E\u8BA4\u53EF\u4E2D\u65AD\u540E\u5148 morula-runtime daemon stop" }
|
|
12698
|
+
);
|
|
12699
|
+
}
|
|
12700
|
+
const report = await performRuntimeUpgrade({
|
|
12701
|
+
currentVersion: BUILD_VERSION2,
|
|
12702
|
+
targetVersion,
|
|
12703
|
+
registry: registry2,
|
|
12704
|
+
log: (line) => context.err(line)
|
|
12705
|
+
});
|
|
12706
|
+
if (report.status === "up_to_date") {
|
|
12707
|
+
await reportUpgradeResult(context, { status: "succeeded", fromVersion: BUILD_VERSION2, toVersion: BUILD_VERSION2, restart: false });
|
|
12708
|
+
return { ok: true, upToDate: true, current: BUILD_VERSION2, target: report.targetVersion, steps: report.steps };
|
|
12709
|
+
}
|
|
12710
|
+
if (report.status !== "succeeded") {
|
|
12711
|
+
await reportUpgradeResult(context, { status: report.status, fromVersion: report.fromVersion, toVersion: report.toVersion ?? void 0, error: report.error });
|
|
12712
|
+
context.setExitCode(EXIT.OTHER);
|
|
12713
|
+
return {
|
|
12714
|
+
ok: false,
|
|
12715
|
+
status: report.status,
|
|
12716
|
+
from: report.fromVersion,
|
|
12717
|
+
to: report.toVersion,
|
|
12718
|
+
error: report.error,
|
|
12719
|
+
steps: report.steps,
|
|
12720
|
+
hint: report.status === "rolled_back" ? "\u5DF2\u56DE\u88C5\u539F\u7248\u672C\uFF1B\u53EF\u67E5\u770B steps \u5B9A\u4F4D\u5931\u8D25\u539F\u56E0\u540E\u91CD\u8BD5" : "\u539F\u7248\u672C\u4FDD\u6301\u4E0D\u53D8\uFF1B\u53EF\u67E5\u770B steps \u5B9A\u4F4D\u5931\u8D25\u539F\u56E0\u540E\u91CD\u8BD5"
|
|
12721
|
+
};
|
|
12722
|
+
}
|
|
12723
|
+
const cfg = loadDaemonConfig(context.store);
|
|
12724
|
+
let restart = null;
|
|
12725
|
+
let deferredRestart = false;
|
|
12726
|
+
if (wasRunning) {
|
|
12727
|
+
if (cfgForSafety?.runtimeId && context.runtime.token) {
|
|
12728
|
+
const stillBusy = await countActiveTasks(context, cfgForSafety.runtimeId);
|
|
12729
|
+
if (stillBusy > 0) {
|
|
12730
|
+
deferredRestart = true;
|
|
12731
|
+
context.err(`[upgrade] daemon \u6709 ${stillBusy} \u4E2A\u4EFB\u52A1\u5728\u5347\u7EA7\u671F\u95F4\u88AB\u6D3E\u53D1\uFF0C\u4FDD\u7559\u5176\u8FD0\u884C\u2014\u2014\u65B0\u7248\u672C ${report.toVersion} \u5DF2\u5B89\u88C5\uFF0C\u4E0B\u6B21 daemon \u91CD\u542F\u65F6\u751F\u6548\uFF08morula-runtime daemon stop && morula-runtime daemon start\uFF09`);
|
|
12732
|
+
}
|
|
12733
|
+
}
|
|
12734
|
+
if (!deferredRestart) {
|
|
12735
|
+
if (probe.pid !== null) {
|
|
12736
|
+
const stopped = await stopDaemonByPid(probe.pid);
|
|
12737
|
+
context.err(`[upgrade] \u65E7 daemon\uFF08pid=${probe.pid}\uFF09\u5DF2\u505C\u6B62\uFF08${stopped.outcome}\uFF09`);
|
|
12738
|
+
}
|
|
12739
|
+
if (probe.lockState === "stale")
|
|
12740
|
+
releaseDaemonLock(lockPath);
|
|
12741
|
+
else if (probe.pid !== null)
|
|
12742
|
+
releaseDaemonLock(lockPath, probe.pid);
|
|
12743
|
+
rmSync11(pidFile, { force: true });
|
|
12744
|
+
mkdirSync17(dir, { recursive: true, mode: 448 });
|
|
12745
|
+
restart = await spawnUpgradedDaemon({
|
|
12746
|
+
env: context.env,
|
|
12747
|
+
logFile: join24(dir, LOG_FILE2),
|
|
12748
|
+
pidFile,
|
|
12749
|
+
daemonConfig: { name: cfg?.name, workdir: cfg?.workdir, maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT }
|
|
12750
|
+
});
|
|
12751
|
+
if (restart.pid > 0) {
|
|
12752
|
+
const liveness = await restart.waitExit(RESTART_LIVENESS_CHECK_MS);
|
|
12753
|
+
if (liveness.exited) {
|
|
12754
|
+
context.err(`[upgrade] \u65B0 daemon \u8FDB\u7A0B\u7ACB\u5373\u9000\u51FA\uFF08code=${liveness.code ?? "\u672A\u77E5"}\uFF09\uFF0C\u8BE6\u89C1 ${join24(dir, LOG_FILE2)}\u2014\u2014\u8BF7\u68C0\u67E5\u65E5\u5FD7\u540E\u624B\u52A8 morula-runtime daemon start`);
|
|
12755
|
+
restart = { pid: 0, entry: restart.entry, error: `\u65B0 daemon \u8FDB\u7A0B\u7ACB\u5373\u9000\u51FA\uFF08code=${liveness.code ?? "\u672A\u77E5"}\uFF09`, waitExit: async () => ({ exited: true, code: liveness.code ?? null }) };
|
|
12756
|
+
} else {
|
|
12757
|
+
context.err(`[upgrade] \u65B0 daemon \u5DF2\u62C9\u8D77\uFF08pid=${restart.pid}\uFF09\uFF0C\u65E5\u5FD7: ${join24(dir, LOG_FILE2)}`);
|
|
12758
|
+
}
|
|
12759
|
+
} else {
|
|
12760
|
+
context.err(`[upgrade] \u65B0 daemon \u62C9\u8D77\u5931\u8D25\uFF1A${restart.error ?? "\u672A\u77E5\u539F\u56E0"}\u2014\u2014\u8BF7\u624B\u52A8\u6267\u884C morula-runtime daemon start`);
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12763
|
+
} else {
|
|
12764
|
+
context.err("[upgrade] daemon \u672A\u5728\u8FD0\u884C\uFF0C\u4FDD\u6301\u505C\u6B62\u72B6\u6001\uFF08\u9700\u8981\u65F6\u624B\u52A8 morula-runtime daemon start\uFF09");
|
|
12765
|
+
}
|
|
12766
|
+
const spawnFailed = wasRunning && !deferredRestart && restart !== null && restart.pid <= 0;
|
|
12767
|
+
await reportUpgradeResult(context, {
|
|
12768
|
+
status: spawnFailed ? "failed" : "succeeded",
|
|
12769
|
+
fromVersion: report.fromVersion,
|
|
12770
|
+
toVersion: report.toVersion,
|
|
12771
|
+
restart: !spawnFailed && wasRunning && !deferredRestart,
|
|
12772
|
+
...spawnFailed ? { error: `\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\u4F46\u81EA\u52A8\u91CD\u542F\u5931\u8D25\uFF1A${restart?.error ?? "\u672A\u77E5\u539F\u56E0"}` } : {}
|
|
12773
|
+
});
|
|
12774
|
+
if (spawnFailed) {
|
|
12775
|
+
context.setExitCode(EXIT.OTHER);
|
|
12776
|
+
}
|
|
12777
|
+
return {
|
|
12778
|
+
ok: !spawnFailed,
|
|
12779
|
+
status: spawnFailed ? "restart_failed" : deferredRestart ? "installed_pending_restart" : "succeeded",
|
|
12780
|
+
from: report.fromVersion,
|
|
12781
|
+
to: report.toVersion,
|
|
12782
|
+
// deferredRestart(评审 P3):安装期有任务被派发 → 不打断 daemon,新版本下次重启生效
|
|
12783
|
+
restarted: !spawnFailed && wasRunning && !deferredRestart,
|
|
12784
|
+
pid: !spawnFailed && wasRunning && !deferredRestart ? restart?.pid ?? null : null,
|
|
12785
|
+
...deferredRestart ? { note: "\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\uFF1Bdaemon \u6709\u4EFB\u52A1\u5728\u6267\u884C\u672A\u91CD\u542F\uFF0C\u4E0B\u6B21 daemon \u91CD\u542F\u65F6\u751F\u6548" } : {},
|
|
12786
|
+
warnings: report.warnings,
|
|
12787
|
+
steps: report.steps
|
|
12788
|
+
};
|
|
12789
|
+
}
|
|
12790
|
+
|
|
12084
12791
|
// src/commands/registry-runtime.ts
|
|
12085
12792
|
var COMMANDS = {
|
|
12086
12793
|
auth: authCommand,
|
|
12087
12794
|
daemon: daemonCommand,
|
|
12088
|
-
setup: setupCommand
|
|
12795
|
+
setup: setupCommand,
|
|
12796
|
+
upgrade: upgradeCommand
|
|
12089
12797
|
};
|
|
12090
12798
|
var EXIT_CODES = {
|
|
12091
12799
|
0: "success",
|
|
@@ -12107,7 +12815,7 @@ var GLOBAL_FLAGS2 = {
|
|
|
12107
12815
|
};
|
|
12108
12816
|
var SPEC = {
|
|
12109
12817
|
name: "morula-runtime",
|
|
12110
|
-
summary: "Device-level CLI for the Morula local agent runtime: authentication, daemon lifecycle (run/start/stop/status), local assistant portal, model inventory, and
|
|
12818
|
+
summary: "Device-level CLI for the Morula local agent runtime: authentication, daemon lifecycle (run/start/stop/status), local assistant portal, model inventory, device diagnostics, and runtime self-upgrade (check + confirmed run). Success output is JSON on stdout; errors are JSON on stderr.",
|
|
12111
12819
|
resources: [
|
|
12112
12820
|
{
|
|
12113
12821
|
resource: "auth",
|
|
@@ -12278,6 +12986,44 @@ var SPEC = {
|
|
|
12278
12986
|
"canonical form is the bare `morula-runtime setup`; postinstall runs the same registration automatically",
|
|
12279
12987
|
"only writes <root>/morula-cli; never deletes other plugins or the legacy ~/.agents/plugins/morula-cli (reported as legacyRoot)"
|
|
12280
12988
|
]
|
|
12989
|
+
},
|
|
12990
|
+
{
|
|
12991
|
+
resource: "upgrade",
|
|
12992
|
+
summary: "runtime self-upgrade: compare the installed version against the latest npm release, then upgrade this device after explicit confirmation (installs, verifies, rolls back on failure, and re-bakes the plugin launcher)",
|
|
12993
|
+
actions: [
|
|
12994
|
+
{
|
|
12995
|
+
action: "check",
|
|
12996
|
+
summary: "read-only: compare current version with the latest published version on the npm registry (one network call)",
|
|
12997
|
+
flags: [
|
|
12998
|
+
{ name: "registry", type: "string", valueHint: "<url>", description: "npm registry to query (default https://registry.npmjs.org, or $MORULA_NPM_REGISTRY)" }
|
|
12999
|
+
],
|
|
13000
|
+
examples: ["morula-runtime upgrade check"],
|
|
13001
|
+
writes: "none",
|
|
13002
|
+
interactive: false,
|
|
13003
|
+
idempotent: true,
|
|
13004
|
+
emits: ["ok", "current", "latest", "updateAvailable", "registry"]
|
|
13005
|
+
},
|
|
13006
|
+
{
|
|
13007
|
+
action: "run",
|
|
13008
|
+
summary: "upgrade this device to the target version (default: registry latest). REQUIRES --yes (no silent auto-upgrade). Refuses while the daemon has tasks running; stops an idle daemon, upgrades, verifies (rolls back on failure), re-runs setup, then restarts the daemon",
|
|
13009
|
+
flags: [
|
|
13010
|
+
{ name: "yes", type: "boolean", description: "REQUIRED: explicit confirmation; without it the command refuses (exit 2). No silent auto-upgrade" },
|
|
13011
|
+
{ name: "version", type: "string", valueHint: "<semver>", description: "target version (default: latest from the registry)" },
|
|
13012
|
+
{ name: "registry", type: "string", valueHint: "<url>", description: "npm registry to install from (default https://registry.npmjs.org, or $MORULA_NPM_REGISTRY)" }
|
|
13013
|
+
],
|
|
13014
|
+
examples: ["morula-runtime upgrade run --yes", "morula-runtime upgrade run --yes --version 0.1.0-alpha.16"],
|
|
13015
|
+
writes: "local",
|
|
13016
|
+
interactive: false,
|
|
13017
|
+
idempotent: false,
|
|
13018
|
+
emits: ["ok", "status", "from", "to", "restarted", "pid", "steps"]
|
|
13019
|
+
}
|
|
13020
|
+
],
|
|
13021
|
+
notes: [
|
|
13022
|
+
"check: exit 0 = compared (see updateAvailable); 7 = registry unreachable (see error/hint)",
|
|
13023
|
+
"run: exit 0 = upgraded or already up to date; 6 (conflict) = daemon has running tasks (upgrade refused); 1 = install/verify failed (previous version kept or re-installed, see steps/error)",
|
|
13024
|
+
"the daemon restart only happens when the daemon was running before the upgrade; a stopped daemon stays stopped",
|
|
13025
|
+
"a stopped daemon is upgraded but not started \u2014 start it manually with morula-runtime daemon start"
|
|
13026
|
+
]
|
|
12281
13027
|
}
|
|
12282
13028
|
]
|
|
12283
13029
|
};
|