@arcships/morula-runtime 0.1.0-alpha.14 → 0.1.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -740,7 +740,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
|
|
|
740
740
|
var init_build_env = __esm({
|
|
741
741
|
"../morula-cli-core/dist/build-env.js"() {
|
|
742
742
|
"use strict";
|
|
743
|
-
BUILD_ID = "
|
|
743
|
+
BUILD_ID = "20260929103157.019b0b1c";
|
|
744
744
|
BUILD_CONTEXTS = {
|
|
745
745
|
"local": "http://localhost:8080/api",
|
|
746
746
|
"test": "https://morula.basecastle.com/api",
|
|
@@ -2588,9 +2588,9 @@ async function setupAutostart(context) {
|
|
|
2588
2588
|
await removeLegacyAutostart();
|
|
2589
2589
|
const { command, args } = resolvePortalCommand();
|
|
2590
2590
|
const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join3(homedir2(), ".morula-cli");
|
|
2591
|
-
const
|
|
2592
|
-
mkdirSync2(
|
|
2593
|
-
const logFile = join3(
|
|
2591
|
+
const daemonDir = join3(stateDir2, "daemon");
|
|
2592
|
+
mkdirSync2(daemonDir, { recursive: true, mode: 448 });
|
|
2593
|
+
const logFile = join3(daemonDir, "portal.log");
|
|
2594
2594
|
const plist = buildLaunchAgentPlist({ label: LABEL, command, args, logFile });
|
|
2595
2595
|
const plistPath = launchAgentPath();
|
|
2596
2596
|
mkdirSync2(join3(plistPath, ".."), { recursive: true });
|
|
@@ -2626,7 +2626,13 @@ var init_portal_autostart = __esm({
|
|
|
2626
2626
|
// src/daemon/daemon-config.ts
|
|
2627
2627
|
import { randomUUID } from "node:crypto";
|
|
2628
2628
|
import { homedir as homedir4 } from "node:os";
|
|
2629
|
-
import { join as join5 } from "node:path";
|
|
2629
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
2630
|
+
function configFilePathForEnv(env = process.env) {
|
|
2631
|
+
return env.MORULA_CONFIG ?? join5(env.MORULA_CONFIG_DIR ?? join5(homedir4(), ".morula-cli"), "config.yaml");
|
|
2632
|
+
}
|
|
2633
|
+
function daemonDirForEnv(env = process.env) {
|
|
2634
|
+
return join5(dirname4(configFilePathForEnv(env)), "daemon");
|
|
2635
|
+
}
|
|
2630
2636
|
function loadDaemonConfig(store) {
|
|
2631
2637
|
const config = store.load();
|
|
2632
2638
|
const d = config?.[DAEMON_KEY];
|
|
@@ -2666,7 +2672,7 @@ var init_daemon_config = __esm({
|
|
|
2666
2672
|
// src/daemon/daemon-lock.ts
|
|
2667
2673
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2668
2674
|
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
|
|
2675
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
2670
2676
|
function daemonPidPath(configDir) {
|
|
2671
2677
|
return join6(configDir, "daemon", DAEMON_PID_FILE);
|
|
2672
2678
|
}
|
|
@@ -2769,7 +2775,7 @@ function acquireDaemonLock(lockPath, input) {
|
|
|
2769
2775
|
startedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2770
2776
|
workdir: input.workdir
|
|
2771
2777
|
};
|
|
2772
|
-
mkdirSync4(
|
|
2778
|
+
mkdirSync4(dirname5(lockPath), { recursive: true, mode: 448 });
|
|
2773
2779
|
try {
|
|
2774
2780
|
const fd = openSync2(lockPath, "wx", 384);
|
|
2775
2781
|
try {
|
|
@@ -2854,7 +2860,7 @@ __export(portal_instances_exports, {
|
|
|
2854
2860
|
});
|
|
2855
2861
|
import { readdirSync, statSync } from "node:fs";
|
|
2856
2862
|
import { homedir as homedir5 } from "node:os";
|
|
2857
|
-
import { dirname as
|
|
2863
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
2858
2864
|
function parseBaseUrl(raw) {
|
|
2859
2865
|
if (!raw)
|
|
2860
2866
|
return null;
|
|
@@ -2922,7 +2928,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2922
2928
|
const env = options.env ?? process.env;
|
|
2923
2929
|
const log = options.log ?? (() => {
|
|
2924
2930
|
});
|
|
2925
|
-
const defaultConfigDir =
|
|
2931
|
+
const defaultConfigDir = dirname6(env.MORULA_CONFIG ?? join7(env.MORULA_CONFIG_DIR ?? join7(options.homeDir ?? homedir5(), ".morula-cli"), "config.yaml"));
|
|
2926
2932
|
let index = [];
|
|
2927
2933
|
let signature = "";
|
|
2928
2934
|
function buildIndex() {
|
|
@@ -2939,7 +2945,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2939
2945
|
const normalized = normalizeBaseUrl2(entry.url);
|
|
2940
2946
|
if (!normalized)
|
|
2941
2947
|
continue;
|
|
2942
|
-
const configDir =
|
|
2948
|
+
const configDir = dirname6(file);
|
|
2943
2949
|
built.push({
|
|
2944
2950
|
configDir,
|
|
2945
2951
|
context,
|
|
@@ -3482,7 +3488,7 @@ var init_dim_locate = __esm({
|
|
|
3482
3488
|
// src/daemon/acp/acp-process-registry.ts
|
|
3483
3489
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
3484
3490
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3485
|
-
import { dirname as
|
|
3491
|
+
import { dirname as dirname7 } from "node:path";
|
|
3486
3492
|
function parseLstartToMs(tokens) {
|
|
3487
3493
|
if (tokens.length < 5)
|
|
3488
3494
|
return null;
|
|
@@ -3595,7 +3601,7 @@ function persistRecords() {
|
|
|
3595
3601
|
if (!recordFile)
|
|
3596
3602
|
return;
|
|
3597
3603
|
try {
|
|
3598
|
-
mkdirSync5(
|
|
3604
|
+
mkdirSync5(dirname7(recordFile), { recursive: true });
|
|
3599
3605
|
writeFileSync5(recordFile, JSON.stringify({
|
|
3600
3606
|
v: RECORD_FILE_VERSION,
|
|
3601
3607
|
children: [...ownedChildren.values()]
|
|
@@ -3838,9 +3844,9 @@ var init_acp_connection = __esm({
|
|
|
3838
3844
|
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
3839
3845
|
`);
|
|
3840
3846
|
try {
|
|
3841
|
-
const { existsSync:
|
|
3847
|
+
const { existsSync: existsSync21 } = await import("node:fs");
|
|
3842
3848
|
process.stderr.write(
|
|
3843
|
-
`[daemon] diag: fileExists=${
|
|
3849
|
+
`[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
3850
|
`
|
|
3845
3851
|
);
|
|
3846
3852
|
} catch {
|
|
@@ -4424,14 +4430,13 @@ init_dist2();
|
|
|
4424
4430
|
init_dist2();
|
|
4425
4431
|
|
|
4426
4432
|
// src/build-env.ts
|
|
4427
|
-
var BUILD_VERSION2 = "0.1.0-alpha.
|
|
4433
|
+
var BUILD_VERSION2 = "0.1.0-alpha.16";
|
|
4428
4434
|
|
|
4429
4435
|
// src/commands/daemon.ts
|
|
4430
4436
|
init_dist();
|
|
4431
4437
|
init_dist2();
|
|
4432
|
-
import { existsSync as
|
|
4433
|
-
import {
|
|
4434
|
-
import { dirname as dirname11, join as join22 } from "node:path";
|
|
4438
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync16, readFileSync as readFileSync12, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
4439
|
+
import { dirname as dirname13, join as join23 } from "node:path";
|
|
4435
4440
|
|
|
4436
4441
|
// src/daemon/daemon-autostart.ts
|
|
4437
4442
|
init_portal_autostart();
|
|
@@ -4500,9 +4505,9 @@ init_portal_server();
|
|
|
4500
4505
|
// src/daemon/runner.ts
|
|
4501
4506
|
init_dist2();
|
|
4502
4507
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4503
|
-
import { existsSync as
|
|
4508
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync15, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
|
|
4504
4509
|
import { arch, hostname } from "node:os";
|
|
4505
|
-
import { dirname as
|
|
4510
|
+
import { dirname as dirname11, join as join21 } from "node:path";
|
|
4506
4511
|
|
|
4507
4512
|
// src/daemon/acp/acp-connection-pool.ts
|
|
4508
4513
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -4637,6 +4642,10 @@ var DaemonApi = class {
|
|
|
4637
4642
|
* D1(daemon-server-connection-unstable):心跳是「设备存活」的唯一信号,显式 10s 超时——
|
|
4638
4643
|
* 慢请求快速失败,避免占住单飞槽位(客户端默认 30s,对心跳过松)。
|
|
4639
4644
|
* 说明:`client.post()` 的第三参是 query,不支持 RequestOptions,故直接走 `client.request`。
|
|
4645
|
+
*
|
|
4646
|
+
* `upgradeRequested`(runtime-self-upgrade,沿用 stop 的一次性认领模式):服务端置了
|
|
4647
|
+
* upgradeRequestedAt → 本次心跳认领(服务端已清指令)→ daemon 触发自升级;`upgradeVersion`
|
|
4648
|
+
* 为置指令时的目标版本(可空 = daemon 自行解析 latest)。
|
|
4640
4649
|
*/
|
|
4641
4650
|
heartbeat(runtimeId, runningTaskCount = 0) {
|
|
4642
4651
|
return this.client.request(
|
|
@@ -4647,6 +4656,13 @@ var DaemonApi = class {
|
|
|
4647
4656
|
pollTasks(runtimeId, max = 1) {
|
|
4648
4657
|
return this.client.get("/agent-tasks/poll", { runtimeId, max });
|
|
4649
4658
|
}
|
|
4659
|
+
/**
|
|
4660
|
+
* runtime-self-upgrade:自升级结果回报(机器协议,设备级凭证)。
|
|
4661
|
+
* best-effort 语义由调用方掌握(失败只 log,不影响本地升级结果)。
|
|
4662
|
+
*/
|
|
4663
|
+
reportUpgradeResult(runtimeId, body) {
|
|
4664
|
+
return this.client.post(`/agent-runtimes/${runtimeId}/upgrade-result`, body);
|
|
4665
|
+
}
|
|
4650
4666
|
claim(taskId, runtimeId) {
|
|
4651
4667
|
const res = this.client.post(`/agent-tasks/${taskId}/claim`, void 0, { runtimeId });
|
|
4652
4668
|
res.then((r) => {
|
|
@@ -5076,7 +5092,7 @@ ${repoLine}- **\u4E0D\u8981**\u4F7F\u7528 \`gh\`\uFF08GitHub CLI \u5728\u672C\u5
|
|
|
5076
5092
|
\`\`\`
|
|
5077
5093
|
\u5185\u5BB9\u4E3A\u7ED3\u8BBA + \u5173\u952E\u95EE\u9898\uFF08\u6587\u4EF6:\u884C + \u5EFA\u8BAE\uFF09\uFF0C\u672B\u5C3E\u5E26\u7F72\u540D\u884C \`> \u7531\u81EA\u52A8\u5316\u300C<\u81EA\u52A8\u5316\u540D\u79F0>\u300D\u4E8E <\u65E5\u671F> \u751F\u6210\`\uFF08\u4F9B\u4E0B\u4E00\u8F6E\u81EA\u8BC6\u522B\uFF09\uFF1B
|
|
5078
5094
|
**\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
|
|
5079
|
-
- \u5173\u8054/\u5173\u5355\u8BED\u4E49\u4E0E\u4E0A\u6587\u4E00\u81F4\
|
|
5095
|
+
- \u5173\u8054/\u5173\u5355\u8BED\u4E49\u4E0E\u4E0A\u6587\u4E00\u81F4\uFF1APR \u5173\u8054**\u53EA\u7531\u5E73\u53F0\u901A\u9053\u4EA7\u751F**\uFF08\u670D\u52A1\u7AEF\u4E0D\u8BFB PR \u6587\u672C\uFF09\uFF1B\u5173\u8054\u4E0A\u7684 PR merge \u540E\u670D\u52A1\u7AEF\u6309\u65E2\u6709\u6D41\u7A0B\u63A8\u8FDB\u4EFB\u52A1\u72B6\u6001\u3002
|
|
5080
5096
|
`;
|
|
5081
5097
|
}
|
|
5082
5098
|
var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u5951\u7EA6\uFF09
|
|
@@ -5130,15 +5146,20 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
5130
5146
|
> \u7EAF\u95EE\u7B54\u3001\u8C03\u7814\u3001\u5206\u6790\u3001\u672C\u5730\u8D77\u670D\u52A1\u7C7B\u4EFB\u52A1\uFF1A**\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u63D0 PR\u3001\u6700\u7EC8\u8BC4\u8BBA\u4E0D\u51FA\u73B0 PR \u7AE0\u8282**\uFF0C\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002
|
|
5131
5147
|
|
|
5132
5148
|
- \u672C\u4EFB\u52A1\u7684\u5BF9\u5916\u7F16\u53F7\uFF08\u5728\u4EFB\u52A1\u770B\u677F URL / \u5217\u8868\u53EF\u89C1\uFF09\u53EF\u80FD\u5F62\u5982 \`STR-101\` \u6216 \`AGENT-463963\`\u3002
|
|
5133
|
-
- **PR \u5173\u8054**\
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
- \
|
|
5149
|
+
- **PR \u5173\u8054\u53EA\u7531\u5E73\u53F0\u901A\u9053\u4EA7\u751F**\uFF082026-09-24 \u5B9A\u6848\uFF09\uFF1A**\u53EA\u6709\u7ECF\u5E73\u53F0\u901A\u9053\u5EFA\u51FA\u6765\u7684 PR \u4F1A\u81EA\u52A8\u5173\u8054\u5230\u4EFB\u52A1**\u2014\u2014
|
|
5150
|
+
\`morula repo mr create\`\uFF08\u63A8\u8350\uFF09\u6216 \`gh pr create\` \u540E\u8C03 \`report_pr\` \u5DE5\u5177\u4E0A\u62A5\u3002\u5E73\u53F0\u6309\u81EA\u5DF1\u7684\u4E8B\u5B9E\uFF08\u54EA\u4E2A\u4EFB\u52A1\u3001
|
|
5151
|
+
\u54EA\u6761\u5206\u652F\u3001\u54EA\u4E2A workContext\uFF09\u8BA4\u9886\uFF0C**\u4E0D\u8BFB PR \u6587\u672C**\u3002
|
|
5152
|
+
- **\u4E0D\u8981\u5728 PR \u6807\u9898/\u6B63\u6587\u91CC\u9760\u4EFB\u52A1\u7F16\u53F7\u300C\u6807\u8BB0\u5F52\u5C5E\u300D**\uFF1A\u670D\u52A1\u7AEF**\u4E0D\u518D\u626B\u63CF** title/branch/body \u627E\u4EFB\u52A1\u7F16\u53F7\u3002
|
|
5153
|
+
\u4EBA\u5DE5\u5728 GitLab/GitHub \u4E0A\u624B\u5DE5\u5EFA\u7684 PR \u5E73\u53F0**\u4E0D\u5173\u8054**\uFF08\u4E0D\u8FDB\u5173\u8054\u533A\u3001\u4E0D\u89E6\u53D1\u81EA\u52A8\u8BC4\u5BA1\u3001\u4E0D\u9A71\u52A8\u4EFB\u52A1\u72B6\u6001\uFF09\u3002
|
|
5154
|
+
- **\u5173\u5355**\uFF1A\u5173\u8054\u4E0A\u7684 PR \u5408\u5E76\u540E\uFF0C\u670D\u52A1\u7AEF\u6309\u65E2\u6709\u6D41\u7A0B\u81EA\u52A8\u63A8\u8FDB\u4EFB\u52A1\u72B6\u6001\uFF08\u5168\u90E8 MR \u5408\u5E76 \u2192 \u5DF2\u5408\u5E76\uFF1B\u8FD9\u4E9B\u63D0\u4EA4\u8FDB\u5165\u751F\u4EA7\u5206\u652F \u2192 \u5DF2\u53D1\u5E03\uFF09\u3002
|
|
5155
|
+
\u4E0D\u9700\u8981 \`Closes\` \u4E4B\u7C7B\u5173\u952E\u5B57\u2014\u2014**\u5173\u8054\u672C\u8EAB\u5C31\u662F\u6388\u6743**\u3002
|
|
5156
|
+
- \u4ECD\u7136\u5EFA\u8BAE PR \u6807\u9898\u4EE5\u300C\`<\u4EFB\u52A1\u7F16\u53F7>: \u63CF\u8FF0\`\u300D\u5F00\u5934\u3001\u6B63\u6587\u5199\u4E00\u53E5 \`Closes <\u4EFB\u52A1\u7F16\u53F7>\`\uFF1A**\u8FD9\u662F\u7ED9\u4EBA\u770B\u7684\u53EF\u8BFB\u6027**\uFF08\u5E73\u53F0\u4E0D\u518D\u4F9D\u8D56\u5B83\uFF09\uFF0C
|
|
5157
|
+
\u4E14\u662F\u4EFB\u52A1\u4E0E PR \u4E4B\u95F4\u6700\u7701\u4E8B\u7684\u5BF9\u7167\u7EBF\u7D22\u3002
|
|
5137
5158
|
|
|
5138
5159
|
## 0.3 \u5206\u652F\u547D\u540D\uFF1A\u7531\u4F60\uFF08Agent\uFF09\u53D6\u540D\u5E76\u4E0A\u62A5
|
|
5139
5160
|
|
|
5140
5161
|
- **\u4EFB\u52A1 ID \u83B7\u53D6**\uFF1A\u672C\u4EFB\u52A1\u7684 AgentTask ID\uFF08UUID\uFF09\u5728\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF08\u4E5F\u662F \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u8FD4\u56DE\u7684 \`id\`\uFF0C\`.agent_context/task-context.md\` \u5F00\u5934\u4EA6\u6709\uFF09\u3002\u9700\u8981\u8C03\u7528 \`morula task branch\` \u65F6\u7528\u5B83\u4F5C\u4E3A taskId\u3002
|
|
5141
|
-
- **\u91CD\u8981\u533A\u5206**\uFF1A\`morula task branch <taskId>\` \u7684 taskId \u7528**\u672C\u4EFB\u52A1 UUID**\uFF08\u673A\u5668\u534F\u8BAE\uFF09\
|
|
5162
|
+
- **\u91CD\u8981\u533A\u5206**\uFF1A\`morula task branch <taskId>\` \u7684 taskId \u7528**\u672C\u4EFB\u52A1 UUID**\uFF08\u673A\u5668\u534F\u8BAE\uFF09\uFF1BMR \u6807\u9898\u91CC\u7684\u4EFB\u52A1\u7F16\u53F7\u7528**\u5BF9\u5916\u7F16\u53F7 code**\uFF08\u5982 \`AGENT-958201\`\uFF09\u2014\u2014\u4F46\u6CE8\u610F**\u7F16\u53F7\u53EA\u5F71\u54CD\u53EF\u8BFB\u6027\uFF0C\u4E0D\u518D\u5F71\u54CD\u5173\u8054**\uFF08\u670D\u52A1\u7AEF\u4E0D\u8BFB PR \u6587\u672C\uFF0C\u89C1\u7B2C 0.2 / 2 \u6761\uFF09\u3002
|
|
5142
5163
|
- **\u53EA\u6709\u9700\u8981\u6539\u4EE3\u7801\u5E76\u63A8\u5206\u652F\u7684\u4EFB\u52A1\u624D\u53D6\u540D**\uFF1B\u7EAF\u95EE\u7B54/\u53EA\u8BFB\u8C03\u7814/\u672C\u5730\u8D77\u670D\u52A1\u4E0D\u5EFA\u5206\u652F\uFF08\u89C1\u7B2C 3 \u6761\u51B3\u7B56\u6811\uFF09\u3002
|
|
5143
5164
|
- **\u5148\u68C0\u67E5\u5F53\u524D\u5206\u652F**\uFF1A\u82E5\u4ED3\u5E93\u5DF2\u6709 feature \u5206\u652F\uFF08\u540C\u4EFB\u52A1/\u540C\u4E00\u6539\u52A8\u95ED\u73AF\u7684\u540E\u7EED\u8F6E\u6B21\u590D\u7528\uFF09\uFF0C**\u76F4\u63A5\u7EE7\u7EED\u7528\u5F53\u524D\u5206\u652F\uFF0C\u4E0D\u8981\u65B0\u5EFA**\uFF08\u5728\u4ED3\u5E93\u76EE\u5F55\u5185\u6267\u884C\uFF09\uFF1A
|
|
5144
5165
|
\`\`\`bash
|
|
@@ -5200,8 +5221,9 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
5200
5221
|
- **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
|
|
5201
5222
|
\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
|
|
5202
5223
|
- **\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
|
|
5203
|
-
- **\u26A0\uFE0F \`morula repo checkout\` \u7684\u8BED\u4E49 = \u300C\u58F0\u660E/\u5207\u6362\u672C\u4EFB\u52A1\u7684
|
|
5204
|
-
|
|
5224
|
+
- **\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
|
|
5225
|
+
\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
|
|
5226
|
+
\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
|
|
5205
5227
|
\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
|
|
5206
5228
|
\u4E3B\u4ED3\u5E93\u6700\u7EC8\u505C\u5728\u6700\u540E\u90A3\u4E2A\u300C\u53EA\u770B\u4E86\u4E00\u773C\u300D\u7684\u4ED3\u5E93\u4E0A\uFF09\u3002
|
|
5207
5229
|
- **\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
|
|
@@ -5220,7 +5242,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
5220
5242
|
- **\u82E5\u4EFB\u52A1\u6D89\u53CA\u591A\u4E2A\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\uFF09**\uFF1A\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\u7531**\u4F60**\u7ED3\u5408\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\uFF08\u4EFB\u52A1\u63CF\u8FF0\u91CC\u7684\u300C\u6D89\u53CA\u4ED3\u5E93\u300D\u662F\u7BA1\u5BB6\u7684\u521D\u59CB\u5224\u65AD\uFF1B\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u54EA\u4E9B\u4ED3\u5E93\u7528 \`morula repo list --space <spaceId>\` \u67E5\uFF1B\u6267\u884C\u4E2D\u53D1\u73B0\u8FD8\u9700\u6539\u52A8\u5176\u4ED6\u4ED3\u5E93\u65F6\uFF0C\u81EA\u884C prepare \u62C9\u53D6\uFF09\uFF0C**\u4E0D\u9700\u8981\u5411\u7528\u6237\u9010\u4E2A\u786E\u8BA4**\uFF1A
|
|
5221
5243
|
1. \u4E3B\u4ED3\u5E93\uFF1A\u5728\u5B83\u7684\u5B50\u76EE\u5F55\u5185\u76F4\u63A5 git \u64CD\u4F5C\uFF08\u5DE5\u4F5C\u533A\u6839\u4E0D\u662F\u4ED3\u5E93\uFF09\uFF1B
|
|
5222
5244
|
2. \u5176\u4ED6\u4ED3\u5E93\u7528 \`morula repo prepare <taskId> <repoFullName>\` \u7269\u5316\u5230\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\u4E0B\uFF08**\u4E0E\u4E3B\u4ED3\u5E93\u540C\u7EA7\u7684\u5B50\u76EE\u5F55**\uFF0C\u672C\u5730 daemon \u7AEF\u70B9\uFF0C\u672C\u8F6E\u7ACB\u5373\u7269\u5316\u5E76\u8FD4\u56DE\u8DEF\u5F84\uFF1Bgit \u534F\u8BAE\u62C9\u53D6\uFF0C\u4E0D\u8D70\u5E73\u53F0 API\uFF09\uFF1B
|
|
5223
|
-
3. \u5728\u54EA\u4E2A\u4ED3\u5E93\u6539\u52A8\uFF0C\u5C31\u5728\u54EA\u4E2A\u4ED3\u5E93\u5185\u5EFA\u5206\u652F\uFF08\u7B2C 0.3 \u6761\u547D\u540D\u89C4\u8303\uFF09\u3001\u63A8\u5206\u652F\u5E76\u5EFA PR/MR
|
|
5245
|
+
3. \u5728\u54EA\u4E2A\u4ED3\u5E93\u6539\u52A8\uFF0C\u5C31\u5728\u54EA\u4E2A\u4ED3\u5E93\u5185\u5EFA\u5206\u652F\uFF08\u7B2C 0.3 \u6761\u547D\u540D\u89C4\u8303\uFF09\u3001\u63A8\u5206\u652F\u5E76**\u7ECF\u5E73\u53F0\u901A\u9053\u5EFA PR/MR**\uFF08\`morula repo mr create\` \u4F18\u5148\uFF1B\u6807\u9898\u5E26\u4EFB\u52A1\u7F16\u53F7\u4EC5\u4E3A\u53EF\u8BFB\u6027\uFF09\u3002**\u53EA\u6709\u7ECF\u5E73\u53F0\u901A\u9053\u5EFA\u7684 PR \u624D\u4F1A\u5173\u8054\u5230\u672C\u4EFB\u52A1**\uFF08\u670D\u52A1\u7AEF\u4E0D\u8BFB PR \u6587\u672C\uFF0C\u89C1\u7B2C 2 \u6761\uFF09\u3002**\u5206\u652F\u540D\u9010\u4ED3\u4E0A\u62A5**\uFF1A\u4E3B\u4ED3\u5E93\uFF08\u5176\u5B50\u76EE\u5F55\uFF09\u7528 \`morula task branch <taskId> --name <\u5206\u652F\u540D>\`\uFF1B\u6B21\u4ED3\u5E93\uFF08\u540C\u7EA7\u5B50\u76EE\u5F55\uFF09\u7528 \`morula task branch <taskId> --name <\u5206\u652F\u540D> --repo <owner/name>\` \u2014\u2014 \u670D\u52A1\u7AEF\u636E\u6B64\u628A\u5206\u652F\u540D\u5199\u5230\u8BE5\u4ED3\u5E93\u7684\u5DE5\u4F5C\u4E0A\u4E0B\u6587\uFF08\u5E73\u53F0\u4FA7\u9010\u4ED3\u5EFA MR \u515C\u5E95\u3001\u4EA4\u4ED8\u6E05\u5355\u90FD\u4F9D\u8D56\u5B83\uFF1B\u4E0D\u62A5\u5219\u4E00\u76F4\u505C\u5728\u5360\u4F4D\u5206\u652F\u540D \`morula/tmp-*\`\uFF0C\u5E73\u53F0\u627E\u4E0D\u5230\u4F60\u7684\u5206\u652F\uFF09\uFF1B
|
|
5224
5246
|
4. prepare \u76EE\u5F55\u4E8C\u6B21\u8C03\u7528\u4F1A fetch \u66F4\u65B0\u4F46**\u4E0D\u52A8\u5DE5\u4F5C\u533A**\u2014\u2014\u8DE8\u8F6E\u590D\u7528\u65F6\u5148 \`git status\` \u786E\u8BA4\u73B0\u573A\u3002
|
|
5225
5247
|
- **\u5B89\u5168\u8FB9\u754C**\uFF1A\u53EA\u5728\u672C\u4EFB\u52A1\u7684\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\uFF08\u53CA\u5176\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF09\u5185\u64CD\u4F5C\uFF1B\u7981\u6B62\u63A2\u7D22/\u4FEE\u6539\u5DE5\u4F5C\u533A\u6839\u4E4B\u5916\u7684\u8DEF\u5F84\uFF08\u5C24\u5176 daemon \u6839\u76EE\u5F55\u4E0B\u5176\u4ED6\u4EFB\u52A1\u7684 \`repos/\`\u3001\`worktrees/\`\u3001\`scratch/\`\uFF09\u3002
|
|
5226
5248
|
- **\u672C\u5730\u9A8C\u8BC1\u670D\u52A1\u7AEF\u53E3\u9694\u79BB**\uFF08issues/local-backend-watch-instability.md\uFF09\uFF1A\u82E5\u4EFB\u52A1\u9700\u8981\u542F\u52A8\u672C\u5730\u670D\u52A1\u9A8C\u8BC1\uFF08\u5982 \`npm run dev\` / \`nest start\` \u7B49\uFF09\uFF0C**\u5FC5\u987B\u4F7F\u7528\u975E\u5E73\u53F0\u5360\u7528\u7AEF\u53E3**\u2014\u2014\u7ED1\u5B9A \`127.0.0.1\` \u7684\u9AD8\u4F4D\u968F\u673A\u7AEF\u53E3\uFF08\u5982 127.0.0.1:18080+\uFF09\uFF0C**\u7981\u6B62**\u4F7F\u7528 \`8080\`\uFF08\u5E73\u53F0\u540E\u7AEF\uFF09\u53CA \`15173\`\uFF08\u5E73\u53F0\u524D\u7AEF\uFF09\u7B49\u5E73\u53F0\u5360\u7528\u7AEF\u53E3\uFF0C\u4E5F\u4E0D\u8981\u4F9D\u8D56\u7EC8\u7AEF\u4F1A\u8BDD\u5B58\u6D3B\uFF08\u540E\u53F0\u8FDB\u7A0B\u65B9\u5F0F\u542F\u52A8\uFF0C\u7528 \`nohup\`/\`disown\` \u6216 \`&!\` \u8131\u79BB\uFF09\uFF1B\u542F\u52A8\u540E\u81EA\u6D4B\u53EF\u7528\u5373\u53EF\uFF0C\u4E0D\u9700\u8981\uFF08\u4E5F\u4E0D\u5E94\uFF09\u8BA9\u5E73\u53F0\u4FA7\u8BBF\u95EE\u3002\u4E91\u7AEF\u6C99\u7BB1\u5316\u540E\u7531\u6C99\u7BB1\u7F51\u7EDC\u7B56\u7565\u515C\u5E95\u3002
|
|
@@ -5320,13 +5342,15 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
5320
5342
|
|
|
5321
5343
|
> \u4E3A\u4EC0\u4E48\u662F\u8FD9\u56DB\u4E2A\u65F6\u673A\uFF1A\u68C0\u67E5\u662F**\u5DE5\u5177\u8C03\u7528**\uFF0C\u6709 token \u4E0E\u5EF6\u8FDF\u5F00\u9500\uFF1B\u300C\u65E0\u6D88\u606F\u7ACB\u5373\u7EE7\u7EED\u300D\u662F\u4E3A\u4E86\u4E0D\u8BA9\u68C0\u67E5\u70B9\u628A\u4E3B\u7EBF\u5E26\u504F\u3002
|
|
5322
5344
|
|
|
5323
|
-
## 2. PR \
|
|
5345
|
+
## 2. PR \u5173\u8054\uFF1A\u53EA\u8D70\u5E73\u53F0\u901A\u9053
|
|
5324
5346
|
|
|
5325
|
-
- **\
|
|
5326
|
-
\
|
|
5327
|
-
- **\
|
|
5328
|
-
|
|
5329
|
-
-
|
|
5347
|
+
- **\u5173\u8054\u7684\u552F\u4E00\u6765\u6E90\u662F\u5E73\u53F0\u901A\u9053**\uFF082026-09-24 \u5B9A\u6848\uFF09\uFF1A\`morula repo mr create\`\uFF08\u63A8\u8350\uFF09\u5EFA PR \u65F6\u670D\u52A1\u7AEF\u5F53\u573A\u843D\u5173\u8054\uFF1B
|
|
5348
|
+
\u6216 \`gh pr create\` \u4E4B\u540E\u8C03 \`report_pr\` \u5DE5\u5177\u4E0A\u62A5\u3002**\u670D\u52A1\u7AEF\u4E0D\u8BFB PR \u6587\u672C**\u2014\u2014title/branch/body \u91CC\u5199\u4EC0\u4E48\u4EFB\u52A1\u7F16\u53F7\u90FD\u4E0D\u4F1A\u5173\u8054\u3002
|
|
5349
|
+
- **\u4EBA\u5DE5\u5728 GitLab/GitHub \u4E0A\u624B\u5DE5\u5EFA\u7684 PR\uFF0C\u5E73\u53F0\u4E0D\u5173\u8054**\uFF1A\u4E0D\u8FDB\u4EFB\u52A1\u5173\u8054\u533A\u3001\u4E0D\u89E6\u53D1\u81EA\u52A8\u8BC4\u5BA1\u3001\u4E0D\u9A71\u52A8\u4EFB\u52A1\u72B6\u6001\u3002
|
|
5350
|
+
\u5982\u679C\u4F60\u5E0C\u671B\u4E00\u6761 PR \u5173\u8054\u5230\u672C\u4EFB\u52A1\uFF0C\u5C31\u5FC5\u987B\u8D70\u4E0A\u9762\u4E24\u6761\u5E73\u53F0\u901A\u9053\u4E4B\u4E00\u3002
|
|
5351
|
+
- **\u5173\u5355\u4E0D\u9700\u8981\u5173\u952E\u5B57**\uFF1A\u5173\u8054\u4E0A\u7684 PR \u5408\u5E76\u540E\uFF0C\u670D\u52A1\u7AEF\u6309\u65E2\u6709\u6D41\u7A0B\u63A8\u8FDB\u4EFB\u52A1\u72B6\u6001
|
|
5352
|
+
\uFF08\u5168\u90E8 MR \u5408\u5E76 \u2192 \u5DF2\u5408\u5E76\uFF1B\u8FD9\u4E9B\u63D0\u4EA4\u8FDB\u5165\u751F\u4EA7\u5206\u652F \u2192 \u5DF2\u53D1\u5E03\uFF09\u3002**\u5173\u8054\u672C\u8EAB\u5C31\u662F\u6388\u6743**\u3002
|
|
5353
|
+
- \u63A8\u8350\uFF08\u53EF\u8BFB\u6027\uFF0C\u4E0D\u662F\u5173\u8054\u624B\u6BB5\uFF09\uFF1Atitle \u4EE5\u300CSTR-101: \u63CF\u8FF0\u300D\u5F00\u5934\u3001body \u5199\u4E00\u53E5 \`Closes STR-101\`\u2014\u2014\u4FBF\u4E8E\u4EBA\u548C\u5E73\u53F0\u65E5\u5FD7\u5BF9\u7167\u3002
|
|
5330
5354
|
|
|
5331
5355
|
## 3. \u4EC0\u4E48\u65F6\u5019\u63A8\u5206\u652F\u3001\u63D0 PR\uFF08\u51B3\u7B56\u6811\uFF09
|
|
5332
5356
|
|
|
@@ -5469,10 +5493,10 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
5469
5493
|
|
|
5470
5494
|
| \u5951\u7EA6 | \u670D\u52A1\u7AEF\u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
5471
5495
|
|------|-----------|------|
|
|
5472
|
-
|
|
|
5473
|
-
|
|
|
5474
|
-
|
|
|
5475
|
-
|
|
|
5496
|
+
| PR \u5173\u8054**\u53EA\u7531\u5E73\u53F0\u901A\u9053\u4EA7\u751F**\uFF08\u670D\u52A1\u7AEF\u4E0D\u8BFB PR \u6587\u672C\uFF09 | \u5E73\u53F0\u901A\u9053\u5EFA\u94FE\uFF08\`reportPrLinkByAgent\`\uFF09+ webhook \u6309 \`(\u79DF\u6237, \u4ED3\u5E93, PR \u53F7)\` \u5B9A\u4F4D**\u5DF2\u5B58\u5728**\u7684\u94FE\u63A5 \u2192 \u53EA\u66F4\u65B0\u72B6\u6001 | \`src/modules/vcs-integration/link/vcs-link.service.ts\`\uFF08\`findLinkedRows\` / \`handlePullRequest\`\uFF09 |
|
|
5497
|
+
| \u672A\u5173\u8054\u7684 PR\uFF08\u4EBA\u5DE5\u624B\u5DE5\u5EFA\uFF09\u2192 \u4E0D\u5EFA\u94FE\u3001\u4E0D\u6D41\u8F6C\u3001\u7559\u75D5 | \u540C\u4E0A\uFF08\u5B9A\u4F4D\u4E0D\u5230\u5373\u8FD4\u56DE\uFF0C\u5199\u300C\u672A\u5173\u8054\u4EFB\u4F55\u4EFB\u52A1\u300D\u65E5\u5FD7\uFF09 | \u540C\u4E0A |
|
|
5498
|
+
| merge \u5173\u5355\uFF1A**\u94FE\u63A5\u5B58\u5728\u5373\u6388\u6743**\uFF08\u65E0\u9700 \`Closes\` \u5173\u952E\u5B57\uFF09 | merged/closed \u2192 \u591A\u4ED3\u5E93\u6C47\u603B \u2192 \`closeObject\` | \`vcs-link.service.ts\`\uFF08\`closeObject\`\uFF09 |
|
|
5499
|
+
| \u7EC8\u6001\u4E0D\u53EF\u88AB\u964D\u7EA7\uFF08\`released\` \u4E0D\u88AB \`closed\`/\`merged\` \u8986\u76D6\uFF1B\`merged \u2192 released\` \u653E\u884C\uFF09 | \`isTerminalDowngrade\`\uFF08\u9886\u57DF\u5C42\u552F\u4E00\u5B9A\u4E49\uFF09+ \`ObjectService.update\` \u4E0D\u53D8\u91CF | \`domain/lifecycle-state.ts\`\u3001\`object.service.ts\` |
|
|
5476
5500
|
|
|
5477
5501
|
## 2. \u4E0E\u5E73\u53F0\u4EA4\u4E92\u53EA\u80FD\u8D70 morula CLI
|
|
5478
5502
|
|
|
@@ -5502,7 +5526,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
5502
5526
|
|
|
5503
5527
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
5504
5528
|
|------|------|------|
|
|
5505
|
-
| merge \u81EA\u52A8\u5173\u5355\uFF08\u670D\u52A1\u7AEF\u9A71\u52A8\u72B6\u6001\uFF09 | webhook merged \u2192
|
|
5529
|
+
| merge \u81EA\u52A8\u5173\u5355\uFF08\u670D\u52A1\u7AEF\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\` |
|
|
5506
5530
|
| agent \u4E0D\u8C03 CLI \u6539\u72B6\u6001 | \u534F\u4F5C\u5951\u7EA6\u7B2C 4 \u6761\u3002**\u6CE8\u610F\uFF1ACLI \u6709 \`morula task state <id> --state <code>\`**\uFF08\u673A\u5668\u534F\u8BAE \`POST /agent-tasks/:id/state\`\uFF0C\u4F9B\u5E73\u53F0/\u81EA\u6D41\u8F6C\u4F7F\u7528\uFF09\u2014\u2014\u662F**\u5951\u7EA6\u8981\u6C42 agent \u4E0D\u8C03\u7528**\u5B83\uFF0C\u4E0D\u662F\u300CCLI \u6CA1\u6709\u8FD9\u4E2A\u547D\u4EE4\u300D | \`packages/morula-cli/src/commands/task.ts\`\uFF08action \`state\`\uFF09\u3001\`src/modules/agent-task/agent-task.controller.ts\`\uFF08\u7AEF\u70B9\uFF09 |
|
|
5507
5531
|
|
|
5508
5532
|
## 4.1 \u8BC4\u5BA1\u4EFB\u52A1\u7ED3\u8BBA\u56DE\u4F20\uFF08morula review submit\uFF09
|
|
@@ -5771,7 +5795,7 @@ function outboxAge(entry) {
|
|
|
5771
5795
|
// src/daemon/plugin-register.ts
|
|
5772
5796
|
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";
|
|
5773
5797
|
import { homedir as homedir10 } from "node:os";
|
|
5774
|
-
import { dirname as
|
|
5798
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
5775
5799
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5776
5800
|
var PLUGIN_NAME = "morula-cli";
|
|
5777
5801
|
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
@@ -5786,9 +5810,9 @@ function defaultPluginsRoot(env = process.env, home = homedir10()) {
|
|
|
5786
5810
|
return join13(resolveDimcodeHome(env, home), "plugins");
|
|
5787
5811
|
}
|
|
5788
5812
|
function resolvePackagePaths(entryUrl) {
|
|
5789
|
-
const distDir =
|
|
5813
|
+
const distDir = dirname8(fileURLToPath2(entryUrl));
|
|
5790
5814
|
return {
|
|
5791
|
-
packageRoot:
|
|
5815
|
+
packageRoot: dirname8(distDir),
|
|
5792
5816
|
distDir,
|
|
5793
5817
|
assetsDir: join13(distDir, "plugin")
|
|
5794
5818
|
};
|
|
@@ -5804,7 +5828,25 @@ if [ -z "$NODE" ]; then
|
|
|
5804
5828
|
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
5805
5829
|
exit 127
|
|
5806
5830
|
fi
|
|
5807
|
-
|
|
5831
|
+
ENTRY=${JSON.stringify(entry)}
|
|
5832
|
+
# \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
|
|
5833
|
+
if [ ! -f "$ENTRY" ] && [ -n "$MORULA_CLI" ] && [ -f "$MORULA_CLI" ]; then
|
|
5834
|
+
ENTRY="$MORULA_CLI"
|
|
5835
|
+
fi
|
|
5836
|
+
if [ ! -f "$ENTRY" ]; then
|
|
5837
|
+
RT="$(command -v morula-runtime 2>/dev/null || true)"
|
|
5838
|
+
if [ -n "$RT" ]; then
|
|
5839
|
+
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)"
|
|
5840
|
+
if [ -n "$ALT" ] && [ -f "$ALT" ]; then
|
|
5841
|
+
ENTRY="$ALT"
|
|
5842
|
+
fi
|
|
5843
|
+
fi
|
|
5844
|
+
fi
|
|
5845
|
+
if [ ! -f "$ENTRY" ]; then
|
|
5846
|
+
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
|
|
5847
|
+
exit 127
|
|
5848
|
+
fi
|
|
5849
|
+
exec "$NODE" "$ENTRY" "$@"
|
|
5808
5850
|
`;
|
|
5809
5851
|
}
|
|
5810
5852
|
function assetsReady(paths) {
|
|
@@ -6061,7 +6103,7 @@ function isAcpInitRetryableError(error) {
|
|
|
6061
6103
|
async function withAcpInitRetry(setupOnce, deps = {}) {
|
|
6062
6104
|
const delays = deps.delaysMs ?? ACP_INIT_RETRY_DELAYS_MS;
|
|
6063
6105
|
const isRetryable = deps.isRetryable ?? isAcpInitRetryableError;
|
|
6064
|
-
const
|
|
6106
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6065
6107
|
const maxAttempts = delays.length + 1;
|
|
6066
6108
|
let lastError;
|
|
6067
6109
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -6076,7 +6118,7 @@ async function withAcpInitRetry(setupOnce, deps = {}) {
|
|
|
6076
6118
|
error: lastError instanceof Error ? lastError.message : String(lastError ?? "")
|
|
6077
6119
|
});
|
|
6078
6120
|
if (delayMs > 0)
|
|
6079
|
-
await
|
|
6121
|
+
await sleep(delayMs);
|
|
6080
6122
|
if (deps.isAborted?.())
|
|
6081
6123
|
throw new Error("\u4EFB\u52A1\u88AB\u53D6\u6D88");
|
|
6082
6124
|
}
|
|
@@ -6828,8 +6870,8 @@ var DimAgentAdapter = class {
|
|
|
6828
6870
|
}
|
|
6829
6871
|
}
|
|
6830
6872
|
async probe() {
|
|
6831
|
-
const { existsSync:
|
|
6832
|
-
const installed =
|
|
6873
|
+
const { existsSync: existsSync21 } = await import("node:fs");
|
|
6874
|
+
const installed = existsSync21(this.dimPath) || this.dimPath === "dim";
|
|
6833
6875
|
let authStatus = installed ? "unknown" : "error";
|
|
6834
6876
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
6835
6877
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -7122,7 +7164,7 @@ function pickLegacyDimModels(inventory) {
|
|
|
7122
7164
|
// src/daemon/repo-cache.ts
|
|
7123
7165
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
7124
7166
|
import { existsSync as existsSync12, mkdirSync as mkdirSync10 } from "node:fs";
|
|
7125
|
-
import { dirname as
|
|
7167
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7126
7168
|
function ownerOf(repoFullName) {
|
|
7127
7169
|
const idx = repoFullName.indexOf("/");
|
|
7128
7170
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
@@ -7157,7 +7199,7 @@ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRem
|
|
|
7157
7199
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
7158
7200
|
return;
|
|
7159
7201
|
}
|
|
7160
|
-
mkdirSync10(
|
|
7202
|
+
mkdirSync10(dirname9(repoCachePath), { recursive: true });
|
|
7161
7203
|
const cloneUrl = fetchUrl();
|
|
7162
7204
|
try {
|
|
7163
7205
|
execFileSync8("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
@@ -8275,11 +8317,333 @@ function materializeAdditionalRepo(input) {
|
|
|
8275
8317
|
return input.targetPath;
|
|
8276
8318
|
}
|
|
8277
8319
|
|
|
8320
|
+
// src/daemon/self-upgrade.ts
|
|
8321
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8322
|
+
import { closeSync as closeSync4, existsSync as existsSync16, mkdirSync as mkdirSync13, openSync as openSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
8323
|
+
import { dirname as dirname10, join as join19 } from "node:path";
|
|
8324
|
+
var RUNTIME_PACKAGE = "@arcships/morula-runtime";
|
|
8325
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
8326
|
+
var REGISTRY_TIMEOUT_MS = 1e4;
|
|
8327
|
+
var NPM_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8328
|
+
var NPM_ROLLBACK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8329
|
+
var RESTART_LOCK_WAIT_MS = 6e4;
|
|
8330
|
+
var RESTART_LIVENESS_CHECK_MS = 5e3;
|
|
8331
|
+
function resolveRegistry(env = process.env) {
|
|
8332
|
+
return (env.MORULA_NPM_REGISTRY ?? DEFAULT_NPM_REGISTRY).replace(/\/+$/, "");
|
|
8333
|
+
}
|
|
8334
|
+
function parseVersion(input) {
|
|
8335
|
+
if (typeof input !== "string")
|
|
8336
|
+
return null;
|
|
8337
|
+
const match = input.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
8338
|
+
if (!match)
|
|
8339
|
+
return null;
|
|
8340
|
+
const prerelease = match[4] ? match[4].split(".").map((part) => /^\d+$/.test(part) ? Number(part) : part) : [];
|
|
8341
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), prerelease };
|
|
8342
|
+
}
|
|
8343
|
+
function compareVersions(a, b) {
|
|
8344
|
+
const pa = parseVersion(a);
|
|
8345
|
+
const pb = parseVersion(b);
|
|
8346
|
+
if (!pa || !pb)
|
|
8347
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
8348
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
8349
|
+
if (pa[key] !== pb[key])
|
|
8350
|
+
return pa[key] < pb[key] ? -1 : 1;
|
|
8351
|
+
}
|
|
8352
|
+
if (pa.prerelease.length === 0 && pb.prerelease.length === 0)
|
|
8353
|
+
return 0;
|
|
8354
|
+
if (pa.prerelease.length === 0)
|
|
8355
|
+
return 1;
|
|
8356
|
+
if (pb.prerelease.length === 0)
|
|
8357
|
+
return -1;
|
|
8358
|
+
for (let i = 0; i < Math.max(pa.prerelease.length, pb.prerelease.length); i++) {
|
|
8359
|
+
const sa = pa.prerelease[i];
|
|
8360
|
+
const sb = pb.prerelease[i];
|
|
8361
|
+
if (sa === void 0)
|
|
8362
|
+
return -1;
|
|
8363
|
+
if (sb === void 0)
|
|
8364
|
+
return 1;
|
|
8365
|
+
if (sa === sb)
|
|
8366
|
+
continue;
|
|
8367
|
+
if (typeof sa === "number" && typeof sb === "number")
|
|
8368
|
+
return sa < sb ? -1 : 1;
|
|
8369
|
+
if (typeof sa === "number")
|
|
8370
|
+
return -1;
|
|
8371
|
+
if (typeof sb === "number")
|
|
8372
|
+
return 1;
|
|
8373
|
+
return sa < sb ? -1 : 1;
|
|
8374
|
+
}
|
|
8375
|
+
return 0;
|
|
8376
|
+
}
|
|
8377
|
+
function isUpgradeAvailable(current, latest) {
|
|
8378
|
+
if (!parseVersion(current) || !parseVersion(latest))
|
|
8379
|
+
return false;
|
|
8380
|
+
return compareVersions(current, latest) < 0;
|
|
8381
|
+
}
|
|
8382
|
+
function run2(command, args, opts) {
|
|
8383
|
+
return new Promise((resolve2) => {
|
|
8384
|
+
let child;
|
|
8385
|
+
try {
|
|
8386
|
+
child = spawn4(command, args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
8387
|
+
} catch (error) {
|
|
8388
|
+
resolve2({ ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : String(error) });
|
|
8389
|
+
return;
|
|
8390
|
+
}
|
|
8391
|
+
let stdout = "";
|
|
8392
|
+
let stderr = "";
|
|
8393
|
+
let settled = false;
|
|
8394
|
+
const timer = setTimeout(() => {
|
|
8395
|
+
if (!settled) {
|
|
8396
|
+
settled = true;
|
|
8397
|
+
try {
|
|
8398
|
+
child.kill("SIGKILL");
|
|
8399
|
+
} catch {
|
|
8400
|
+
}
|
|
8401
|
+
resolve2({ ok: false, code: null, stdout, stderr, error: `timeout after ${Math.round(opts.timeoutMs / 1e3)}s` });
|
|
8402
|
+
}
|
|
8403
|
+
}, opts.timeoutMs);
|
|
8404
|
+
timer.unref?.();
|
|
8405
|
+
child.stdout?.on("data", (d) => {
|
|
8406
|
+
stdout += d.toString();
|
|
8407
|
+
});
|
|
8408
|
+
child.stderr?.on("data", (d) => {
|
|
8409
|
+
stderr += d.toString();
|
|
8410
|
+
});
|
|
8411
|
+
child.on("error", (error) => {
|
|
8412
|
+
if (settled)
|
|
8413
|
+
return;
|
|
8414
|
+
settled = true;
|
|
8415
|
+
clearTimeout(timer);
|
|
8416
|
+
resolve2({ ok: false, code: null, stdout, stderr, error: error.message });
|
|
8417
|
+
});
|
|
8418
|
+
child.on("close", (code) => {
|
|
8419
|
+
if (settled)
|
|
8420
|
+
return;
|
|
8421
|
+
settled = true;
|
|
8422
|
+
clearTimeout(timer);
|
|
8423
|
+
resolve2({ ok: code === 0, code, stdout, stderr });
|
|
8424
|
+
});
|
|
8425
|
+
});
|
|
8426
|
+
}
|
|
8427
|
+
async function resolveNpmCommand() {
|
|
8428
|
+
const nodeDir = dirname10(process.execPath);
|
|
8429
|
+
const candidates = [
|
|
8430
|
+
{ command: "npm", args: [] },
|
|
8431
|
+
{ command: join19(nodeDir, "npm"), args: [] },
|
|
8432
|
+
{ command: process.execPath, args: [join19(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")] }
|
|
8433
|
+
];
|
|
8434
|
+
for (const candidate of candidates) {
|
|
8435
|
+
const res = await run2(candidate.command, [...candidate.args, "--version"], { timeoutMs: 15e3 });
|
|
8436
|
+
if (res.ok && /^\d+\.\d+\.\d+/.test(res.stdout.trim()))
|
|
8437
|
+
return candidate;
|
|
8438
|
+
}
|
|
8439
|
+
return null;
|
|
8440
|
+
}
|
|
8441
|
+
async function resolveNpmGlobalLayout() {
|
|
8442
|
+
const npm = await resolveNpmCommand();
|
|
8443
|
+
if (!npm)
|
|
8444
|
+
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");
|
|
8445
|
+
const prefixRes = await run2(npm.command, [...npm.args, "prefix", "-g"], { timeoutMs: 3e4 });
|
|
8446
|
+
const rootRes = await run2(npm.command, [...npm.args, "root", "-g"], { timeoutMs: 3e4 });
|
|
8447
|
+
const prefix = prefixRes.ok ? prefixRes.stdout.trim() : "";
|
|
8448
|
+
const nodeModulesRoot = rootRes.ok ? rootRes.stdout.trim() : "";
|
|
8449
|
+
if (!prefix || !nodeModulesRoot)
|
|
8450
|
+
throw new Error(`\u89E3\u6790 npm \u5168\u5C40\u5E03\u5C40\u5931\u8D25\uFF08prefix=${prefix || "?"} root=${nodeModulesRoot || "?"}\uFF09`);
|
|
8451
|
+
return {
|
|
8452
|
+
prefix,
|
|
8453
|
+
nodeModulesRoot,
|
|
8454
|
+
runtimePackageRoot: join19(nodeModulesRoot, RUNTIME_PACKAGE),
|
|
8455
|
+
runtimeBinPath: process.platform === "win32" ? join19(prefix, `${RUNTIME_PACKAGE.split("/")[1] ?? "morula-runtime"}.cmd`) : join19(prefix, "bin", "morula-runtime")
|
|
8456
|
+
};
|
|
8457
|
+
}
|
|
8458
|
+
async function fetchLatestRuntimeVersion(opts = {}) {
|
|
8459
|
+
const registry2 = opts.registry ?? resolveRegistry();
|
|
8460
|
+
const url = `${registry2}/${encodeURIComponent(RUNTIME_PACKAGE)}/latest`;
|
|
8461
|
+
const controller = new AbortController();
|
|
8462
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? REGISTRY_TIMEOUT_MS);
|
|
8463
|
+
timer.unref?.();
|
|
8464
|
+
try {
|
|
8465
|
+
const res = await fetch(url, { signal: controller.signal, headers: { Accept: "application/json" } });
|
|
8466
|
+
if (!res.ok)
|
|
8467
|
+
throw new Error(`registry ${res.status}`);
|
|
8468
|
+
const body = await res.json();
|
|
8469
|
+
const version = typeof body.version === "string" ? body.version.trim() : "";
|
|
8470
|
+
if (!version)
|
|
8471
|
+
throw new Error("registry \u54CD\u5E94\u7F3A\u5C11 version");
|
|
8472
|
+
return version;
|
|
8473
|
+
} catch (error) {
|
|
8474
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8475
|
+
throw new Error(`\u67E5\u8BE2 npm registry \u5931\u8D25\uFF08${url}\uFF09: ${message}`);
|
|
8476
|
+
} finally {
|
|
8477
|
+
clearTimeout(timer);
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
async function npmContext(registry2) {
|
|
8481
|
+
const npm = await resolveNpmCommand();
|
|
8482
|
+
if (!npm)
|
|
8483
|
+
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");
|
|
8484
|
+
const layout = await resolveNpmGlobalLayout();
|
|
8485
|
+
return { npm, layout };
|
|
8486
|
+
}
|
|
8487
|
+
async function npmInstallGlobal(ctx, version, registry2, timeoutMs) {
|
|
8488
|
+
const args = [...ctx.npm.args, "install", "-g", `${RUNTIME_PACKAGE}@${version}`];
|
|
8489
|
+
if (registry2 && registry2 !== DEFAULT_NPM_REGISTRY)
|
|
8490
|
+
args.push("--registry", registry2);
|
|
8491
|
+
return run2(ctx.npm.command, args, { timeoutMs });
|
|
8492
|
+
}
|
|
8493
|
+
async function verifyRuntimeInstall(binPath, expected) {
|
|
8494
|
+
if (!existsSync16(binPath))
|
|
8495
|
+
return { ok: false, version: null, reason: `bin \u4E0D\u5B58\u5728\uFF1A${binPath}` };
|
|
8496
|
+
const res = await run2(binPath, ["--version"], { timeoutMs: 6e4 });
|
|
8497
|
+
if (!res.ok)
|
|
8498
|
+
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"}` };
|
|
8499
|
+
try {
|
|
8500
|
+
const parsed = JSON.parse(res.stdout.trim());
|
|
8501
|
+
const version = typeof parsed.version === "string" ? parsed.version : null;
|
|
8502
|
+
if (!version)
|
|
8503
|
+
return { ok: false, version: null, reason: `--version \u8F93\u51FA\u7F3A\u5C11 version\uFF1A${res.stdout.trim().slice(0, 200)}` };
|
|
8504
|
+
if (expected && version !== expected)
|
|
8505
|
+
return { ok: false, version, reason: `\u5B89\u88C5\u540E\u7248\u672C\u4E0D\u7B26\uFF1A\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${version}` };
|
|
8506
|
+
return { ok: true, version };
|
|
8507
|
+
} catch {
|
|
8508
|
+
return { ok: false, version: null, reason: `--version \u8F93\u51FA\u975E JSON\uFF1A${res.stdout.trim().slice(0, 200)}` };
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
async function performRuntimeUpgrade(opts) {
|
|
8512
|
+
const log = opts.log ?? (() => {
|
|
8513
|
+
});
|
|
8514
|
+
const registry2 = opts.registry ?? resolveRegistry();
|
|
8515
|
+
const steps = [];
|
|
8516
|
+
const from = opts.currentVersion;
|
|
8517
|
+
let target;
|
|
8518
|
+
if (opts.targetVersion) {
|
|
8519
|
+
target = opts.targetVersion;
|
|
8520
|
+
} else {
|
|
8521
|
+
target = await fetchLatestRuntimeVersion({ registry: registry2 });
|
|
8522
|
+
steps.push(`registry latest: ${target}`);
|
|
8523
|
+
}
|
|
8524
|
+
if (!isUpgradeAvailable(from, target)) {
|
|
8525
|
+
steps.push(`\u5F53\u524D ${from} \u5DF2\u4E0D\u4F4E\u4E8E ${target}\uFF0C\u65E0\u9700\u5347\u7EA7`);
|
|
8526
|
+
return { status: "up_to_date", fromVersion: from, targetVersion: target, steps };
|
|
8527
|
+
}
|
|
8528
|
+
log(`[upgrade] ${from} \u2192 ${target}\uFF08${registry2}\uFF09`);
|
|
8529
|
+
const ctx = await npmContext(registry2);
|
|
8530
|
+
steps.push(`npm install -g ${RUNTIME_PACKAGE}@${target}`);
|
|
8531
|
+
const install = await npmInstallGlobal(ctx, target, registry2, NPM_INSTALL_TIMEOUT_MS);
|
|
8532
|
+
if (!install.ok) {
|
|
8533
|
+
const error = `\u5B89\u88C5\u5931\u8D25\uFF1A${install.error ?? `exit ${install.code}`} ${install.stderr.trim().slice(0, 300)}`.trim();
|
|
8534
|
+
steps.push(error);
|
|
8535
|
+
log(`[upgrade] ${error}`);
|
|
8536
|
+
const still = await verifyRuntimeInstall(ctx.layout.runtimeBinPath);
|
|
8537
|
+
if (still.ok) {
|
|
8538
|
+
steps.push(`\u539F\u7248\u672C ${still.version ?? from} \u5B89\u88C5\u5B8C\u597D\uFF0C\u4FDD\u6301\u4E0D\u53D8`);
|
|
8539
|
+
return { status: "failed", fromVersion: from, toVersion: null, error, steps };
|
|
8540
|
+
}
|
|
8541
|
+
const rollback = await npmInstallGlobal(ctx, from, registry2, NPM_ROLLBACK_TIMEOUT_MS);
|
|
8542
|
+
if (rollback.ok) {
|
|
8543
|
+
steps.push(`\u5DF2\u56DE\u88C5\u539F\u7248\u672C ${from}`);
|
|
8544
|
+
return { status: "rolled_back", fromVersion: from, toVersion: null, error, steps };
|
|
8545
|
+
}
|
|
8546
|
+
steps.push(`\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25\uFF1A${rollback.error ?? `exit ${rollback.code}`}`);
|
|
8547
|
+
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 };
|
|
8548
|
+
}
|
|
8549
|
+
const verified = await verifyRuntimeInstall(ctx.layout.runtimeBinPath, target);
|
|
8550
|
+
if (!verified.ok) {
|
|
8551
|
+
const error = `\u65B0\u5B89\u88C5\u9A8C\u8BC1\u5931\u8D25\uFF1A${verified.reason ?? "\u672A\u77E5\u539F\u56E0"}`;
|
|
8552
|
+
steps.push(error);
|
|
8553
|
+
log(`[upgrade] ${error}`);
|
|
8554
|
+
const rollback = await npmInstallGlobal(ctx, from, registry2, NPM_ROLLBACK_TIMEOUT_MS);
|
|
8555
|
+
if (rollback.ok) {
|
|
8556
|
+
const rollbackVerified = await verifyRuntimeInstall(ctx.layout.runtimeBinPath, from);
|
|
8557
|
+
if (rollbackVerified.ok) {
|
|
8558
|
+
steps.push(`\u5DF2\u56DE\u88C5\u539F\u7248\u672C ${from} \u5E76\u9A8C\u8BC1\u53EF\u7528`);
|
|
8559
|
+
return { status: "rolled_back", fromVersion: from, toVersion: null, error, steps };
|
|
8560
|
+
}
|
|
8561
|
+
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`);
|
|
8562
|
+
return { status: "failed", fromVersion: from, toVersion: null, error: `${error}\uFF1B\u56DE\u88C5 ${from} \u540E\u9A8C\u8BC1\u4ECD\u5931\u8D25`, steps };
|
|
8563
|
+
}
|
|
8564
|
+
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`);
|
|
8565
|
+
return { status: "failed", fromVersion: from, toVersion: null, error: `${error}\uFF1B\u4E14\u56DE\u88C5\u539F\u7248\u672C\u5931\u8D25`, steps };
|
|
8566
|
+
}
|
|
8567
|
+
steps.push(`\u65B0\u7248\u672C ${verified.version} \u9A8C\u8BC1\u901A\u8FC7`);
|
|
8568
|
+
const warnings = [];
|
|
8569
|
+
const setup = await run2(ctx.layout.runtimeBinPath, ["setup"], { timeoutMs: 6e4 });
|
|
8570
|
+
if (setup.ok) {
|
|
8571
|
+
steps.push("\u63D2\u4EF6\u76EE\u5F55\u5DF2\u91CD\u70D8\u7119\uFF08morula-runtime setup\uFF09");
|
|
8572
|
+
} else {
|
|
8573
|
+
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`;
|
|
8574
|
+
warnings.push(warning);
|
|
8575
|
+
steps.push(warning);
|
|
8576
|
+
log(`[upgrade] ${warning}`);
|
|
8577
|
+
}
|
|
8578
|
+
log(`[upgrade] \u5347\u7EA7\u5B8C\u6210\uFF1A${from} \u2192 ${verified.version}`);
|
|
8579
|
+
return { status: "succeeded", fromVersion: from, toVersion: verified.version ?? target, warnings, steps };
|
|
8580
|
+
}
|
|
8581
|
+
async function spawnUpgradedDaemon(opts) {
|
|
8582
|
+
let packageRoot = opts.runtimePackageRoot;
|
|
8583
|
+
if (!packageRoot || !existsSync16(join19(packageRoot, "dist", "index.js"))) {
|
|
8584
|
+
const layout = await resolveNpmGlobalLayout();
|
|
8585
|
+
packageRoot = layout.runtimePackageRoot;
|
|
8586
|
+
}
|
|
8587
|
+
const entry = join19(packageRoot, "dist", "index.js");
|
|
8588
|
+
if (!existsSync16(entry))
|
|
8589
|
+
return { pid: 0, entry, error: `\u65B0\u5305\u5165\u53E3\u4E0D\u5B58\u5728\uFF1A${entry}`, waitExit: async () => ({ exited: true, code: null }) };
|
|
8590
|
+
mkdirSync13(dirname10(opts.logFile), { recursive: true, mode: 448 });
|
|
8591
|
+
let logFd;
|
|
8592
|
+
try {
|
|
8593
|
+
logFd = openSync4(opts.logFile, "a", 384);
|
|
8594
|
+
} catch (error) {
|
|
8595
|
+
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 }) };
|
|
8596
|
+
}
|
|
8597
|
+
try {
|
|
8598
|
+
const child = spawn4(process.execPath, [entry, "daemon", "run"], {
|
|
8599
|
+
cwd: process.cwd(),
|
|
8600
|
+
detached: true,
|
|
8601
|
+
stdio: ["ignore", logFd, logFd],
|
|
8602
|
+
env: {
|
|
8603
|
+
...opts.env,
|
|
8604
|
+
MORULA_DAEMON_NAME: opts.daemonConfig?.name ?? "",
|
|
8605
|
+
MORULA_DAEMON_WORKDIR: opts.daemonConfig?.workdir ?? "",
|
|
8606
|
+
MORULA_DAEMON_MAX_CONCURRENT: String(opts.daemonConfig?.maxConcurrent ?? ""),
|
|
8607
|
+
MORULA_DAEMON_RESTART_WAIT_MS: String(RESTART_LOCK_WAIT_MS)
|
|
8608
|
+
}
|
|
8609
|
+
});
|
|
8610
|
+
child.unref();
|
|
8611
|
+
if (!child.pid)
|
|
8612
|
+
return { pid: 0, entry, error: "spawn \u672A\u8FD4\u56DE pid", waitExit: async () => ({ exited: true, code: null }) };
|
|
8613
|
+
const exitPromise = new Promise((resolve2) => {
|
|
8614
|
+
child.on("exit", (code) => {
|
|
8615
|
+
resolve2(code ?? null);
|
|
8616
|
+
});
|
|
8617
|
+
});
|
|
8618
|
+
const waitExit = async (ms) => {
|
|
8619
|
+
return await Promise.race([
|
|
8620
|
+
exitPromise.then((code) => ({ exited: true, code })),
|
|
8621
|
+
new Promise((resolve2) => {
|
|
8622
|
+
const timer = setTimeout(() => {
|
|
8623
|
+
resolve2({ exited: false, code: null });
|
|
8624
|
+
}, ms);
|
|
8625
|
+
timer.unref?.();
|
|
8626
|
+
})
|
|
8627
|
+
]);
|
|
8628
|
+
};
|
|
8629
|
+
mkdirSync13(dirname10(opts.pidFile), { recursive: true, mode: 448 });
|
|
8630
|
+
writeFileSync11(opts.pidFile, String(child.pid), { mode: 384 });
|
|
8631
|
+
return { pid: child.pid, entry, waitExit };
|
|
8632
|
+
} catch (error) {
|
|
8633
|
+
return { pid: 0, entry, error: error instanceof Error ? error.message : String(error), waitExit: async () => ({ exited: true, code: null }) };
|
|
8634
|
+
} finally {
|
|
8635
|
+
try {
|
|
8636
|
+
closeSync4(logFd);
|
|
8637
|
+
} catch {
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8278
8642
|
// src/daemon/session-host.ts
|
|
8279
8643
|
init_dist2();
|
|
8280
8644
|
init_acp_connection();
|
|
8281
|
-
import { mkdirSync as
|
|
8282
|
-
import { join as
|
|
8645
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync12 } from "node:fs";
|
|
8646
|
+
import { join as join20 } from "node:path";
|
|
8283
8647
|
|
|
8284
8648
|
// src/daemon/steward-instructions.ts
|
|
8285
8649
|
var STEWARD_SYSTEM_INSTRUCTIONS = `\u4F60\u662F\u300C\u7A7A\u95F4\u7BA1\u5BB6\u667A\u80FD\u4F53\u300D\uFF08Project Steward\uFF09\u3002
|
|
@@ -8533,8 +8897,8 @@ var SessionHost = class {
|
|
|
8533
8897
|
async open(cmd) {
|
|
8534
8898
|
if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
|
|
8535
8899
|
return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
|
|
8536
|
-
const workspaceDir =
|
|
8537
|
-
|
|
8900
|
+
const workspaceDir = join20(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
|
|
8901
|
+
mkdirSync14(workspaceDir, { recursive: true });
|
|
8538
8902
|
this.writeInjections(workspaceDir, cmd);
|
|
8539
8903
|
const existing = this.sessions.get(cmd.sessionId);
|
|
8540
8904
|
if (existing?.conn.isAlive) {
|
|
@@ -8820,7 +9184,7 @@ var SessionHost = class {
|
|
|
8820
9184
|
---
|
|
8821
9185
|
|
|
8822
9186
|
${cmd.prompt}`;
|
|
8823
|
-
const
|
|
9187
|
+
const run5 = (async () => {
|
|
8824
9188
|
try {
|
|
8825
9189
|
const { blocks, fileRefs } = await this.materializeAttachments(managed.workspaceDir, cmd.attachments ?? []);
|
|
8826
9190
|
const promptBlocks = [{ type: "text", text: fileRefs.length > 0 ? `${text}
|
|
@@ -8867,8 +9231,8 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8867
9231
|
this.touch(managed);
|
|
8868
9232
|
}
|
|
8869
9233
|
})();
|
|
8870
|
-
managed.promptInFlight =
|
|
8871
|
-
return
|
|
9234
|
+
managed.promptInFlight = run5;
|
|
9235
|
+
return run5;
|
|
8872
9236
|
}
|
|
8873
9237
|
/**
|
|
8874
9238
|
* 附件物化(§五 P2-3):
|
|
@@ -8883,7 +9247,7 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8883
9247
|
const fileRefs = [];
|
|
8884
9248
|
if (attachments.length === 0)
|
|
8885
9249
|
return { blocks, fileRefs };
|
|
8886
|
-
const dir =
|
|
9250
|
+
const dir = join20(workspaceDir, "attachments");
|
|
8887
9251
|
for (const attachment of attachments) {
|
|
8888
9252
|
try {
|
|
8889
9253
|
const res = await fetch(attachment.downloadUrl, { signal: AbortSignal.timeout(ATTACHMENT_FETCH_TIMEOUT_MS) });
|
|
@@ -8893,9 +9257,9 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8893
9257
|
if (attachment.isImage) {
|
|
8894
9258
|
blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
|
|
8895
9259
|
} else {
|
|
8896
|
-
|
|
8897
|
-
const target =
|
|
8898
|
-
|
|
9260
|
+
mkdirSync14(dir, { recursive: true });
|
|
9261
|
+
const target = join20(dir, safeAttachmentName(attachment.fileName));
|
|
9262
|
+
writeFileSync12(target, bytes);
|
|
8899
9263
|
fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
|
|
8900
9264
|
}
|
|
8901
9265
|
} catch (err) {
|
|
@@ -8991,13 +9355,13 @@ ${fileRefs.join("\n")}` : text }];
|
|
|
8991
9355
|
/** 工作区注入物(幂等覆盖;daemon 侧组装,P1-6) */
|
|
8992
9356
|
writeInjections(workspaceDir, cmd) {
|
|
8993
9357
|
writeProjectAgentsMd(workspaceDir, cmd.agentsMd, this.log);
|
|
8994
|
-
const skillDir =
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
const contextDir =
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9358
|
+
const skillDir = join20(workspaceDir, ".agents", "skills", STEWARD_SKILL_NAME);
|
|
9359
|
+
mkdirSync14(skillDir, { recursive: true });
|
|
9360
|
+
writeFileSync12(join20(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
|
|
9361
|
+
const contextDir = join20(workspaceDir, ".agent_context");
|
|
9362
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
9363
|
+
writeFileSync12(
|
|
9364
|
+
join20(contextDir, "steward-context.md"),
|
|
9001
9365
|
buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }),
|
|
9002
9366
|
"utf8"
|
|
9003
9367
|
);
|
|
@@ -9541,6 +9905,12 @@ var DaemonRunner = class {
|
|
|
9541
9905
|
outboxConsecutiveFailures = 0;
|
|
9542
9906
|
outboxPausedUntil = 0;
|
|
9543
9907
|
processing = /* @__PURE__ */ new Set();
|
|
9908
|
+
/**
|
|
9909
|
+
* runtime-self-upgrade:远程升级执行中标志。
|
|
9910
|
+
* 置位期间 tick / WS 派发**不领新任务**——升级完成后 daemon 会重启,中途接的任务会被打断
|
|
9911
|
+
* (心跳超时 → 服务端回收重派),违背「升级不破坏正在执行的任务」的约束。
|
|
9912
|
+
*/
|
|
9913
|
+
upgradeInProgress = false;
|
|
9544
9914
|
/** M1-A1:任务级取消信号(WS cancelled 事件 / 状态轮询触发 abort) */
|
|
9545
9915
|
cancels = /* @__PURE__ */ new Map();
|
|
9546
9916
|
/** Phase 2:审批决策等待表(approvalId → resolver;WS 事件 resolve,轮询兜底) */
|
|
@@ -9682,7 +10052,10 @@ var DaemonRunner = class {
|
|
|
9682
10052
|
models: dimModels,
|
|
9683
10053
|
// LOCAL-AGENT-COLLAB-001 Phase 1:宿主协议 + Provider inventory
|
|
9684
10054
|
hostProtocolVersion: 1,
|
|
9685
|
-
|
|
10055
|
+
// selfUpgrade(runtime-self-upgrade 评审 P2):本 daemon 认识心跳响应里的 upgradeRequested
|
|
10056
|
+
// 指令并具备自升级能力。管理页据此门控远程升级按钮——旧版 daemon 会静默吞掉指令,
|
|
10057
|
+
// 对它显示按钮只会误导用户(能力自报比服务端维护最低版本号更准确,register 即刷新)
|
|
10058
|
+
hostCapabilities: { worktreeV2: true, workspaceCleanupV1: false, selfUpgrade: true },
|
|
9686
10059
|
inventoryRevision: `daemon-v${BUILD_VERSION2}`,
|
|
9687
10060
|
providers: inventory
|
|
9688
10061
|
});
|
|
@@ -9700,10 +10073,33 @@ var DaemonRunner = class {
|
|
|
9700
10073
|
await this.tick();
|
|
9701
10074
|
if (!this.running)
|
|
9702
10075
|
break;
|
|
9703
|
-
await
|
|
10076
|
+
await this.waitForPollInterval();
|
|
9704
10077
|
}
|
|
9705
10078
|
this.stop();
|
|
9706
10079
|
}
|
|
10080
|
+
/** 下一拍等待(POLL_INTERVAL_MS,可被 wakeMainLoop 提前唤醒) */
|
|
10081
|
+
pollWake = null;
|
|
10082
|
+
waitForPollInterval() {
|
|
10083
|
+
return new Promise((resolve2) => {
|
|
10084
|
+
const timer = setTimeout(() => {
|
|
10085
|
+
this.pollWake = null;
|
|
10086
|
+
resolve2();
|
|
10087
|
+
}, POLL_INTERVAL_MS);
|
|
10088
|
+
timer.unref?.();
|
|
10089
|
+
this.pollWake = () => {
|
|
10090
|
+
clearTimeout(timer);
|
|
10091
|
+
this.pollWake = null;
|
|
10092
|
+
resolve2();
|
|
10093
|
+
};
|
|
10094
|
+
});
|
|
10095
|
+
}
|
|
10096
|
+
/**
|
|
10097
|
+
* 唤醒主循环的 poll 等待(评审 P4 配套):远程升级完成(running=false)后立即生效,
|
|
10098
|
+
* 不必最多干等 15s 的 poll 间隔——旧进程越快退出释放锁,新进程越快接管,自重启窗口越小。
|
|
10099
|
+
*/
|
|
10100
|
+
wakeMainLoop() {
|
|
10101
|
+
this.pollWake?.();
|
|
10102
|
+
}
|
|
9707
10103
|
stop() {
|
|
9708
10104
|
this.running = false;
|
|
9709
10105
|
this.stopHeartbeatTimer();
|
|
@@ -9768,6 +10164,10 @@ var DaemonRunner = class {
|
|
|
9768
10164
|
this.running = false;
|
|
9769
10165
|
return;
|
|
9770
10166
|
}
|
|
10167
|
+
if (res?.upgradeRequested) {
|
|
10168
|
+
this.log(`[daemon] \u6536\u5230\u670D\u52A1\u7AEF\u8FDC\u7A0B\u5347\u7EA7\u6307\u4EE4${res.upgradeVersion ? `\uFF08\u76EE\u6807 ${res.upgradeVersion}\uFF09` : ""}`);
|
|
10169
|
+
void this.handleRemoteUpgrade(res.upgradeVersion ?? null);
|
|
10170
|
+
}
|
|
9771
10171
|
if (!res.ok && res.status === "unknown") {
|
|
9772
10172
|
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");
|
|
9773
10173
|
this.running = false;
|
|
@@ -9786,6 +10186,95 @@ var DaemonRunner = class {
|
|
|
9786
10186
|
this.heartbeatInFlight = false;
|
|
9787
10187
|
}
|
|
9788
10188
|
}
|
|
10189
|
+
/**
|
|
10190
|
+
* runtime-self-upgrade:远程升级指令执行(心跳认领后异步触发,见 sendHeartbeat)。
|
|
10191
|
+
*
|
|
10192
|
+
* 边界:
|
|
10193
|
+
* - 有任务在跑(processing 非空)→ 回报 busy 拒绝(指令已消耗,页面可重新下发);
|
|
10194
|
+
* - 空闲 → performRuntimeUpgrade(安装/验证/失败回装/重烘焙插件目录);期间心跳照常、不领新任务;
|
|
10195
|
+
* - 成功 → 用**新包入口** detached 拉起新 daemon(等旧进程释放单实例锁)→ 回报 → 本实例退出;
|
|
10196
|
+
* - 失败/回退 → 回报后**继续用原版本运行**(升级失败不中断服务)。
|
|
10197
|
+
*/
|
|
10198
|
+
async handleRemoteUpgrade(targetVersion) {
|
|
10199
|
+
if (!this.runtimeId)
|
|
10200
|
+
return;
|
|
10201
|
+
if (this.upgradeInProgress) {
|
|
10202
|
+
this.log("[daemon] \u5347\u7EA7\u5DF2\u5728\u8FDB\u884C\uFF0C\u5FFD\u7565\u91CD\u590D\u6307\u4EE4");
|
|
10203
|
+
return;
|
|
10204
|
+
}
|
|
10205
|
+
this.upgradeInProgress = true;
|
|
10206
|
+
const fromVersion = BUILD_VERSION2;
|
|
10207
|
+
try {
|
|
10208
|
+
if (this.processing.size > 0) {
|
|
10209
|
+
const error = `${this.processing.size} \u4E2A\u4EFB\u52A1\u6267\u884C\u4E2D`;
|
|
10210
|
+
this.log(`[daemon] \u5347\u7EA7\u6307\u4EE4\u88AB\u62D2\u7EDD\uFF1A${error}`);
|
|
10211
|
+
await this.reportUpgradeResultSafe({ status: "busy", fromVersion, error });
|
|
10212
|
+
return;
|
|
10213
|
+
}
|
|
10214
|
+
const report = await performRuntimeUpgrade({
|
|
10215
|
+
currentVersion: fromVersion,
|
|
10216
|
+
targetVersion,
|
|
10217
|
+
log: (line) => this.log(line)
|
|
10218
|
+
});
|
|
10219
|
+
if (report.status === "up_to_date") {
|
|
10220
|
+
this.log(`[daemon] \u5F53\u524D ${fromVersion} \u5DF2\u4E0D\u4F4E\u4E8E ${report.targetVersion}\uFF0C\u65E0\u9700\u5347\u7EA7`);
|
|
10221
|
+
await this.reportUpgradeResultSafe({ status: "succeeded", fromVersion, toVersion: fromVersion, restart: false });
|
|
10222
|
+
return;
|
|
10223
|
+
}
|
|
10224
|
+
if (report.status !== "succeeded") {
|
|
10225
|
+
this.log(`[daemon] \u5347\u7EA7\u5931\u8D25\uFF08${report.status}\uFF09\uFF1A${report.error}\u2014\u2014\u7EE7\u7EED\u7528 ${fromVersion} \u8FD0\u884C`);
|
|
10226
|
+
await this.reportUpgradeResultSafe({
|
|
10227
|
+
status: report.status,
|
|
10228
|
+
fromVersion,
|
|
10229
|
+
toVersion: report.toVersion ?? void 0,
|
|
10230
|
+
error: report.error
|
|
10231
|
+
});
|
|
10232
|
+
return;
|
|
10233
|
+
}
|
|
10234
|
+
const cfg = loadDaemonConfig(this.store);
|
|
10235
|
+
const dir = daemonDirForEnv(process.env);
|
|
10236
|
+
const spawned = await spawnUpgradedDaemon({
|
|
10237
|
+
env: process.env,
|
|
10238
|
+
logFile: join21(dir, "daemon.log"),
|
|
10239
|
+
pidFile: join21(dir, "daemon.pid"),
|
|
10240
|
+
daemonConfig: { name: cfg?.name, workdir: cfg?.workdir, maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT }
|
|
10241
|
+
});
|
|
10242
|
+
if (spawned.pid <= 0) {
|
|
10243
|
+
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`;
|
|
10244
|
+
this.log(`[daemon] ${error}`);
|
|
10245
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, toVersion: report.toVersion, error });
|
|
10246
|
+
return;
|
|
10247
|
+
}
|
|
10248
|
+
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`);
|
|
10249
|
+
const liveness = await spawned.waitExit(RESTART_LIVENESS_CHECK_MS);
|
|
10250
|
+
if (liveness.exited) {
|
|
10251
|
+
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`;
|
|
10252
|
+
this.log(`[daemon] ${error}`);
|
|
10253
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, toVersion: report.toVersion, error });
|
|
10254
|
+
return;
|
|
10255
|
+
}
|
|
10256
|
+
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");
|
|
10257
|
+
await this.reportUpgradeResultSafe({ status: "succeeded", fromVersion, toVersion: report.toVersion, restart: true });
|
|
10258
|
+
this.running = false;
|
|
10259
|
+
this.wakeMainLoop();
|
|
10260
|
+
} catch (error) {
|
|
10261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10262
|
+
this.log(`[daemon] \u81EA\u5347\u7EA7\u5F02\u5E38\uFF1A${message}\u2014\u2014\u7EE7\u7EED\u7528 ${fromVersion} \u8FD0\u884C`);
|
|
10263
|
+
await this.reportUpgradeResultSafe({ status: "failed", fromVersion, error: message });
|
|
10264
|
+
} finally {
|
|
10265
|
+
this.upgradeInProgress = false;
|
|
10266
|
+
}
|
|
10267
|
+
}
|
|
10268
|
+
/** 升级结果回报(best-effort:服务端不可达不影响本地升级流程) */
|
|
10269
|
+
async reportUpgradeResultSafe(body) {
|
|
10270
|
+
if (!this.runtimeId)
|
|
10271
|
+
return;
|
|
10272
|
+
try {
|
|
10273
|
+
await this.api.reportUpgradeResult(this.runtimeId, body);
|
|
10274
|
+
} catch (error) {
|
|
10275
|
+
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)}`);
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
9789
10278
|
/**
|
|
9790
10279
|
* tick:poll 兜底 + 取消轮询 + cleanup 补拉(心跳与 outbox 补投均已独立于本循环)。
|
|
9791
10280
|
* D1:整轮加时间预算——超预算记 WARN 并丢弃本轮等待下一拍,避免单个慢请求把 tick 拖死。
|
|
@@ -9814,6 +10303,8 @@ var DaemonRunner = class {
|
|
|
9814
10303
|
async tickOnce() {
|
|
9815
10304
|
if (!this.runtimeId)
|
|
9816
10305
|
return;
|
|
10306
|
+
if (this.upgradeInProgress)
|
|
10307
|
+
return;
|
|
9817
10308
|
try {
|
|
9818
10309
|
const tasks = await this.api.pollTasks(this.runtimeId);
|
|
9819
10310
|
for (const task of tasks) {
|
|
@@ -10213,6 +10704,10 @@ var DaemonRunner = class {
|
|
|
10213
10704
|
async handleTask(taskId) {
|
|
10214
10705
|
if (this.processing.has(taskId))
|
|
10215
10706
|
return;
|
|
10707
|
+
if (this.upgradeInProgress) {
|
|
10708
|
+
this.log(`[daemon] \u5347\u7EA7\u8FDB\u884C\u4E2D\uFF0C\u5FFD\u7565\u4EFB\u52A1\u6D3E\u53D1 task=${taskId}`);
|
|
10709
|
+
return;
|
|
10710
|
+
}
|
|
10216
10711
|
this.processing.add(taskId);
|
|
10217
10712
|
const ctrl = new AbortController();
|
|
10218
10713
|
this.cancels.set(taskId, ctrl);
|
|
@@ -10482,12 +10977,12 @@ var DaemonRunner = class {
|
|
|
10482
10977
|
});
|
|
10483
10978
|
if (spaceToken?.token)
|
|
10484
10979
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
10485
|
-
repoDir =
|
|
10980
|
+
repoDir = join21(cfg.workdir, "scratch", taskId);
|
|
10486
10981
|
taskRoot = repoDir;
|
|
10487
|
-
|
|
10488
|
-
const readme =
|
|
10489
|
-
if (!
|
|
10490
|
-
|
|
10982
|
+
mkdirSync15(repoDir, { recursive: true });
|
|
10983
|
+
const readme = join21(repoDir, "README.md");
|
|
10984
|
+
if (!existsSync17(readme)) {
|
|
10985
|
+
writeFileSync13(
|
|
10491
10986
|
readme,
|
|
10492
10987
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
10493
10988
|
|
|
@@ -10785,7 +11280,7 @@ var DaemonRunner = class {
|
|
|
10785
11280
|
}, { log: (line) => this.log(`[daemon] ${line}`) });
|
|
10786
11281
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
10787
11282
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
10788
|
-
const canResume = !!resumeSessionId && !!resumeWorkDir &&
|
|
11283
|
+
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync17(resumeWorkDir);
|
|
10789
11284
|
const input = {
|
|
10790
11285
|
executionId: `${taskId}:1`,
|
|
10791
11286
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
@@ -11046,7 +11541,7 @@ var DaemonRunner = class {
|
|
|
11046
11541
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
11047
11542
|
return;
|
|
11048
11543
|
}
|
|
11049
|
-
const managedRoot =
|
|
11544
|
+
const managedRoot = join21(cfg.workdir, "worktrees", this.tenantId);
|
|
11050
11545
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
11051
11546
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
11052
11547
|
ok: false,
|
|
@@ -11085,47 +11580,44 @@ var DaemonRunner = class {
|
|
|
11085
11580
|
}
|
|
11086
11581
|
}
|
|
11087
11582
|
};
|
|
11088
|
-
function sleep(ms) {
|
|
11089
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
11090
|
-
}
|
|
11091
11583
|
function isDebugLogEnabled() {
|
|
11092
11584
|
return isFlagEnabled("MORULA_DAEMON_DEBUG_LOG", false);
|
|
11093
11585
|
}
|
|
11094
11586
|
var spaceTokenFailureLogged = false;
|
|
11095
11587
|
function writeCustomSkills(targetDir, skills, log) {
|
|
11096
11588
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
11097
|
-
const base =
|
|
11589
|
+
const base = join21(targetDir, ".agents", "skills");
|
|
11098
11590
|
for (const skill of skills) {
|
|
11099
11591
|
const name = skill.name.trim();
|
|
11100
11592
|
if (!NAME_RE.test(name)) {
|
|
11101
11593
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
11102
11594
|
continue;
|
|
11103
11595
|
}
|
|
11104
|
-
const dir =
|
|
11105
|
-
if (
|
|
11596
|
+
const dir = join21(base, name);
|
|
11597
|
+
if (dirname11(dir) !== base) {
|
|
11106
11598
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
11107
11599
|
continue;
|
|
11108
11600
|
}
|
|
11109
|
-
|
|
11601
|
+
mkdirSync15(dir, { recursive: true });
|
|
11110
11602
|
const meta = ["---", `name: ${name}`];
|
|
11111
11603
|
if (skill.description?.trim()) {
|
|
11112
11604
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
11113
11605
|
}
|
|
11114
11606
|
meta.push("---", "");
|
|
11115
|
-
|
|
11607
|
+
writeFileSync13(join21(dir, "SKILL.md"), `${meta.join("\n")}
|
|
11116
11608
|
${skill.content ?? ""}`, "utf8");
|
|
11117
11609
|
}
|
|
11118
11610
|
}
|
|
11119
11611
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
11120
|
-
const dir =
|
|
11121
|
-
const refsDir =
|
|
11122
|
-
|
|
11612
|
+
const dir = join21(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
11613
|
+
const refsDir = join21(dir, "references");
|
|
11614
|
+
mkdirSync15(refsDir, { recursive: true });
|
|
11123
11615
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
11124
11616
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
11125
11617
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
11126
|
-
|
|
11618
|
+
writeFileSync13(join21(dir, "SKILL.md"), `${front}
|
|
11127
11619
|
${body}`, "utf8");
|
|
11128
|
-
|
|
11620
|
+
writeFileSync13(join21(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
11129
11621
|
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/`);
|
|
11130
11622
|
}
|
|
11131
11623
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -11156,8 +11648,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
11156
11648
|
};
|
|
11157
11649
|
}
|
|
11158
11650
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
11159
|
-
const briefDir =
|
|
11160
|
-
|
|
11651
|
+
const briefDir = join21(targetDir, ".agent_context");
|
|
11652
|
+
mkdirSync15(briefDir, { recursive: true });
|
|
11161
11653
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
11162
11654
|
|
|
11163
11655
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -11167,7 +11659,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
11167
11659
|
---
|
|
11168
11660
|
|
|
11169
11661
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
11170
|
-
|
|
11662
|
+
writeFileSync13(join21(briefDir, "task-context.md"), brief, "utf8");
|
|
11171
11663
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
11172
11664
|
}
|
|
11173
11665
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -11181,8 +11673,8 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
11181
11673
|
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`);
|
|
11182
11674
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
11183
11675
|
}
|
|
11184
|
-
|
|
11185
|
-
|
|
11676
|
+
mkdirSync15(targetDir, { recursive: true });
|
|
11677
|
+
writeFileSync13(join21(targetDir, "AGENTS.md"), `${content}
|
|
11186
11678
|
`, "utf8");
|
|
11187
11679
|
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`);
|
|
11188
11680
|
return true;
|
|
@@ -11241,7 +11733,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
11241
11733
|
}
|
|
11242
11734
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
11243
11735
|
const execEnv = mergeExecEnv(env);
|
|
11244
|
-
if (!
|
|
11736
|
+
if (!existsSync17(target.dir) || !existsSync17(join21(target.dir, ".git"))) {
|
|
11245
11737
|
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`);
|
|
11246
11738
|
return;
|
|
11247
11739
|
}
|
|
@@ -11409,9 +11901,9 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
11409
11901
|
|
|
11410
11902
|
// src/commands/doctor.ts
|
|
11411
11903
|
init_dist2();
|
|
11412
|
-
import { existsSync as
|
|
11904
|
+
import { existsSync as existsSync18, readFileSync as readFileSync11, realpathSync } from "node:fs";
|
|
11413
11905
|
import { homedir as homedir12 } from "node:os";
|
|
11414
|
-
import { dirname as
|
|
11906
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
11415
11907
|
init_daemon_config();
|
|
11416
11908
|
init_daemon_lock();
|
|
11417
11909
|
init_dim_locate();
|
|
@@ -11431,16 +11923,16 @@ async function doctorCommand(context) {
|
|
|
11431
11923
|
async function collectDoctor(context, probe) {
|
|
11432
11924
|
const checks = [];
|
|
11433
11925
|
const env = context.env;
|
|
11434
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
11435
|
-
const configPath = env.MORULA_CONFIG ??
|
|
11436
|
-
const daemonPath =
|
|
11437
|
-
const logFile =
|
|
11926
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join22(homedir12(), ".morula-cli");
|
|
11927
|
+
const configPath = env.MORULA_CONFIG ?? join22(configDir, "config.yaml");
|
|
11928
|
+
const daemonPath = join22(dirname12(configPath), "daemon");
|
|
11929
|
+
const logFile = join22(daemonPath, "daemon.log");
|
|
11438
11930
|
const contextName = context.runtime.context ?? null;
|
|
11439
11931
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
11440
11932
|
const tokenPresent = Boolean(context.runtime.token);
|
|
11441
11933
|
const tokenSource = context.runtime.tokenSource ?? null;
|
|
11442
11934
|
let configReadable = false;
|
|
11443
|
-
if (
|
|
11935
|
+
if (existsSync18(configPath)) {
|
|
11444
11936
|
try {
|
|
11445
11937
|
readFileSync11(configPath, "utf8");
|
|
11446
11938
|
configReadable = true;
|
|
@@ -11448,9 +11940,9 @@ async function collectDoctor(context, probe) {
|
|
|
11448
11940
|
configReadable = false;
|
|
11449
11941
|
}
|
|
11450
11942
|
}
|
|
11451
|
-
const lockPath =
|
|
11943
|
+
const lockPath = join22(daemonPath, DAEMON_LOCK_FILE);
|
|
11452
11944
|
const lock = probeDaemonLock(lockPath);
|
|
11453
|
-
const pidFile =
|
|
11945
|
+
const pidFile = join22(daemonPath, DAEMON_PID_FILE);
|
|
11454
11946
|
const filePid = readPidFile(pidFile);
|
|
11455
11947
|
const daemonPid = probeDaemonInstance(configDir).pid;
|
|
11456
11948
|
const cfg = loadDaemonConfig(context.store);
|
|
@@ -11502,8 +11994,8 @@ async function collectDoctor(context, probe) {
|
|
|
11502
11994
|
hint: "morula-runtime auth login"
|
|
11503
11995
|
});
|
|
11504
11996
|
} else if (probe) {
|
|
11505
|
-
const
|
|
11506
|
-
checks.push(
|
|
11997
|
+
const check2 = await probeAuth(context);
|
|
11998
|
+
checks.push(check2);
|
|
11507
11999
|
} else {
|
|
11508
12000
|
checks.push({
|
|
11509
12001
|
id: "auth.token",
|
|
@@ -11512,7 +12004,7 @@ async function collectDoctor(context, probe) {
|
|
|
11512
12004
|
hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
|
|
11513
12005
|
});
|
|
11514
12006
|
}
|
|
11515
|
-
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } :
|
|
12007
|
+
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" });
|
|
11516
12008
|
if (!dimFound) {
|
|
11517
12009
|
checks.push({
|
|
11518
12010
|
id: "dim.present",
|
|
@@ -11619,19 +12111,13 @@ function detectInstallMode() {
|
|
|
11619
12111
|
}
|
|
11620
12112
|
|
|
11621
12113
|
// src/commands/daemon.ts
|
|
11622
|
-
function configFilePath(env) {
|
|
11623
|
-
return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
|
|
11624
|
-
}
|
|
11625
|
-
function daemonDir(configPath) {
|
|
11626
|
-
return join22(dirname11(configPath), "daemon");
|
|
11627
|
-
}
|
|
11628
12114
|
var PID_FILE = DAEMON_PID_FILE;
|
|
11629
12115
|
var LOG_FILE = "daemon.log";
|
|
11630
12116
|
var DAEMON_EXIT_FALLBACK_MS = 3e3;
|
|
11631
12117
|
async function daemonCommand(context) {
|
|
11632
12118
|
const { action } = context.parsed;
|
|
11633
12119
|
if (action === "run")
|
|
11634
|
-
return
|
|
12120
|
+
return run3(context);
|
|
11635
12121
|
if (action === "start")
|
|
11636
12122
|
return start(context);
|
|
11637
12123
|
if (action === "stop")
|
|
@@ -11808,7 +12294,7 @@ async function models(context) {
|
|
|
11808
12294
|
contextWindow: m.contextWindow
|
|
11809
12295
|
}));
|
|
11810
12296
|
}
|
|
11811
|
-
async function
|
|
12297
|
+
async function run3(context) {
|
|
11812
12298
|
rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent"]);
|
|
11813
12299
|
const armBoundedExit = () => {
|
|
11814
12300
|
setTimeout(() => process.exit(0), DAEMON_EXIT_FALLBACK_MS).unref();
|
|
@@ -11821,9 +12307,18 @@ async function run2(context) {
|
|
|
11821
12307
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
11822
12308
|
}
|
|
11823
12309
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
11824
|
-
|
|
11825
|
-
const lockPath =
|
|
11826
|
-
|
|
12310
|
+
mkdirSync16(resolvedWorkdir, { recursive: true });
|
|
12311
|
+
const lockPath = join23(daemonDirForEnv(context.env), DAEMON_LOCK_FILE);
|
|
12312
|
+
let acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
12313
|
+
const restartWaitMs = Number(context.env.MORULA_DAEMON_RESTART_WAIT_MS ?? 0) || 0;
|
|
12314
|
+
if (!acquired.ok && restartWaitMs > 0) {
|
|
12315
|
+
const deadline = Date.now() + restartWaitMs;
|
|
12316
|
+
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`);
|
|
12317
|
+
while (!acquired.ok && Date.now() < deadline) {
|
|
12318
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
12319
|
+
acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
12320
|
+
}
|
|
12321
|
+
}
|
|
11827
12322
|
if (!acquired.ok) {
|
|
11828
12323
|
const pid = acquired.existing?.pid;
|
|
11829
12324
|
if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
|
|
@@ -11849,7 +12344,7 @@ async function run2(context) {
|
|
|
11849
12344
|
log: (line) => context.err(line),
|
|
11850
12345
|
// dim acp 子进程治理(issues/dim-acp-process-leak-shared-space-contention.md):
|
|
11851
12346
|
// spawn 记录落盘(供 daemon 重启后的启动自愈认领自己的孤儿)+ 无归属子进程周期巡检
|
|
11852
|
-
acpChildRecordFile:
|
|
12347
|
+
acpChildRecordFile: join23(daemonDirForEnv(context.env), "dim-acp-children.json"),
|
|
11853
12348
|
// D6:认证失败时重新读 config 换票(不传则退回「启动即快照」旧行为)。
|
|
11854
12349
|
// 只重算凭证,不重建 client——WS 用返回值更新 socket.auth 后重连。
|
|
11855
12350
|
resolveToken: () => {
|
|
@@ -11900,12 +12395,12 @@ async function start(context) {
|
|
|
11900
12395
|
throw error;
|
|
11901
12396
|
identityError = error instanceof Error ? error.message : String(error);
|
|
11902
12397
|
}
|
|
11903
|
-
const dir =
|
|
12398
|
+
const dir = daemonDirForEnv(context.env);
|
|
11904
12399
|
if (!dryRun)
|
|
11905
|
-
|
|
11906
|
-
const pidFile =
|
|
11907
|
-
const logFile =
|
|
11908
|
-
const lockPath =
|
|
12400
|
+
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
12401
|
+
const pidFile = join23(dir, PID_FILE);
|
|
12402
|
+
const logFile = join23(dir, LOG_FILE);
|
|
12403
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
11909
12404
|
const lock = probeDaemonLock(lockPath);
|
|
11910
12405
|
if (lock.state === "active") {
|
|
11911
12406
|
throw new CliError(
|
|
@@ -11916,7 +12411,7 @@ async function start(context) {
|
|
|
11916
12411
|
{ hint: "morula-runtime daemon status" }
|
|
11917
12412
|
);
|
|
11918
12413
|
}
|
|
11919
|
-
if (
|
|
12414
|
+
if (existsSync19(pidFile)) {
|
|
11920
12415
|
const pid = Number(readFileSync12(pidFile, "utf8").trim());
|
|
11921
12416
|
if (isProcessAlive(pid)) {
|
|
11922
12417
|
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, {
|
|
@@ -11967,19 +12462,19 @@ async function start(context) {
|
|
|
11967
12462
|
hint: `tail -n 50 ${logFile}`
|
|
11968
12463
|
});
|
|
11969
12464
|
}
|
|
11970
|
-
|
|
12465
|
+
writeFileSync14(pidFile, String(result.pid), { mode: 384 });
|
|
11971
12466
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
11972
12467
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
11973
12468
|
}
|
|
11974
12469
|
async function stop(context) {
|
|
11975
12470
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
11976
12471
|
const dryRun = isDryRun(context);
|
|
11977
|
-
const dir =
|
|
11978
|
-
const pidFile =
|
|
11979
|
-
const lockPath =
|
|
12472
|
+
const dir = daemonDirForEnv(context.env);
|
|
12473
|
+
const pidFile = join23(dir, PID_FILE);
|
|
12474
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
11980
12475
|
const lock = probeDaemonLock(lockPath);
|
|
11981
12476
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
11982
|
-
const filePid =
|
|
12477
|
+
const filePid = existsSync19(pidFile) ? Number(readFileSync12(pidFile, "utf8").trim()) : null;
|
|
11983
12478
|
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
11984
12479
|
if (dryRun) {
|
|
11985
12480
|
return {
|
|
@@ -12006,9 +12501,9 @@ async function stop(context) {
|
|
|
12006
12501
|
async function status2(context) {
|
|
12007
12502
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
12008
12503
|
const cfg = loadDaemonConfig(context.store);
|
|
12009
|
-
const dir =
|
|
12010
|
-
const lockPath =
|
|
12011
|
-
const probe = probeDaemonInstance(
|
|
12504
|
+
const dir = daemonDirForEnv(context.env);
|
|
12505
|
+
const lockPath = join23(dir, DAEMON_LOCK_FILE);
|
|
12506
|
+
const probe = probeDaemonInstance(dirname13(configFilePathForEnv(context.env)));
|
|
12012
12507
|
return {
|
|
12013
12508
|
running: probe.running,
|
|
12014
12509
|
pid: probe.pid,
|
|
@@ -12017,7 +12512,7 @@ async function status2(context) {
|
|
|
12017
12512
|
name: cfg?.name ?? null,
|
|
12018
12513
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
12019
12514
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
12020
|
-
logFile:
|
|
12515
|
+
logFile: join23(dir, LOG_FILE),
|
|
12021
12516
|
lockFile: lockPath
|
|
12022
12517
|
};
|
|
12023
12518
|
}
|
|
@@ -12074,11 +12569,228 @@ async function setupCommand(context) {
|
|
|
12074
12569
|
};
|
|
12075
12570
|
}
|
|
12076
12571
|
|
|
12572
|
+
// src/commands/upgrade.ts
|
|
12573
|
+
init_dist2();
|
|
12574
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync17, readFileSync as readFileSync13, rmSync as rmSync11 } from "node:fs";
|
|
12575
|
+
import { join as join24 } from "node:path";
|
|
12576
|
+
init_daemon_config();
|
|
12577
|
+
init_daemon_lock();
|
|
12578
|
+
var PID_FILE2 = DAEMON_PID_FILE;
|
|
12579
|
+
var LOG_FILE2 = "daemon.log";
|
|
12580
|
+
async function upgradeCommand(context) {
|
|
12581
|
+
const { action } = context.parsed;
|
|
12582
|
+
if (action === "check")
|
|
12583
|
+
return check(context);
|
|
12584
|
+
if (action === "run")
|
|
12585
|
+
return run4(context);
|
|
12586
|
+
throw usage("usage: morula-runtime upgrade <check|run>", {
|
|
12587
|
+
hint: "morula-runtime upgrade --help"
|
|
12588
|
+
});
|
|
12589
|
+
}
|
|
12590
|
+
async function check(context) {
|
|
12591
|
+
rejectUnknownFlags(context.parsed.flags, ["registry"]);
|
|
12592
|
+
const registry2 = flagString(context.parsed.flags, "registry") ?? resolveRegistry(context.env);
|
|
12593
|
+
let latest = null;
|
|
12594
|
+
let error = null;
|
|
12595
|
+
try {
|
|
12596
|
+
latest = await fetchLatestRuntimeVersion({ registry: registry2 });
|
|
12597
|
+
} catch (e) {
|
|
12598
|
+
error = e instanceof Error ? e.message : String(e);
|
|
12599
|
+
}
|
|
12600
|
+
if (latest === null) {
|
|
12601
|
+
context.setExitCode(EXIT.UPSTREAM);
|
|
12602
|
+
return {
|
|
12603
|
+
ok: false,
|
|
12604
|
+
current: BUILD_VERSION2,
|
|
12605
|
+
latest: null,
|
|
12606
|
+
updateAvailable: null,
|
|
12607
|
+
registry: registry2,
|
|
12608
|
+
error,
|
|
12609
|
+
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"
|
|
12610
|
+
};
|
|
12611
|
+
}
|
|
12612
|
+
const updateAvailable = isUpgradeAvailable(BUILD_VERSION2, latest);
|
|
12613
|
+
return {
|
|
12614
|
+
ok: true,
|
|
12615
|
+
current: BUILD_VERSION2,
|
|
12616
|
+
latest,
|
|
12617
|
+
updateAvailable,
|
|
12618
|
+
registry: registry2,
|
|
12619
|
+
...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" } : {}
|
|
12620
|
+
};
|
|
12621
|
+
}
|
|
12622
|
+
function probeDaemonPid(env) {
|
|
12623
|
+
const dir = daemonDirForEnv(env);
|
|
12624
|
+
const pidFile = join24(dir, PID_FILE2);
|
|
12625
|
+
const lockPath = join24(dir, DAEMON_LOCK_FILE);
|
|
12626
|
+
const lock = probeDaemonLock(lockPath);
|
|
12627
|
+
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
12628
|
+
const filePid = existsSync20(pidFile) ? Number(readFileSync13(pidFile, "utf8").trim()) : null;
|
|
12629
|
+
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
12630
|
+
return { pid, pidFile, lockPath, lockState: lock.state };
|
|
12631
|
+
}
|
|
12632
|
+
async function reportUpgradeResult(context, body) {
|
|
12633
|
+
const cfg = loadDaemonConfig(context.store);
|
|
12634
|
+
if (!context.runtime.token || !cfg?.runtimeId)
|
|
12635
|
+
return;
|
|
12636
|
+
try {
|
|
12637
|
+
await context.client.http.post(`/agent-runtimes/${cfg.runtimeId}/upgrade-result`, body);
|
|
12638
|
+
} catch (error) {
|
|
12639
|
+
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)}`);
|
|
12640
|
+
}
|
|
12641
|
+
}
|
|
12642
|
+
async function countActiveTasks(context, runtimeId) {
|
|
12643
|
+
const res = await context.client.http.get(
|
|
12644
|
+
"/agent-tasks",
|
|
12645
|
+
{ runtimeId, statusGroup: "active", page: 1, pageSize: 100 }
|
|
12646
|
+
);
|
|
12647
|
+
return (res?.data ?? []).filter((t) => ["dispatched", "running", "awaiting_approval"].includes(t.status)).length;
|
|
12648
|
+
}
|
|
12649
|
+
async function run4(context) {
|
|
12650
|
+
rejectUnknownFlags(context.parsed.flags, ["yes", "version", "registry"]);
|
|
12651
|
+
const yes = flagBoolean(context.parsed.flags, "yes");
|
|
12652
|
+
const targetVersion = flagString(context.parsed.flags, "version") ?? null;
|
|
12653
|
+
const registry2 = flagString(context.parsed.flags, "registry") ?? resolveRegistry(context.env);
|
|
12654
|
+
if (!yes) {
|
|
12655
|
+
throw usage("upgrade run \u9700\u8981 --yes \u663E\u5F0F\u786E\u8BA4\uFF08\u5148\u8DD1 morula-runtime upgrade check \u67E5\u770B\u5DEE\u8DDD\uFF09", {
|
|
12656
|
+
hint: "morula-runtime upgrade run --yes"
|
|
12657
|
+
});
|
|
12658
|
+
}
|
|
12659
|
+
const dir = daemonDirForEnv(context.env);
|
|
12660
|
+
const pidFile = join24(dir, PID_FILE2);
|
|
12661
|
+
const lockPath = join24(dir, DAEMON_LOCK_FILE);
|
|
12662
|
+
const probe = probeDaemonPid(context.env);
|
|
12663
|
+
const wasRunning = probe.pid !== null;
|
|
12664
|
+
const cfgForSafety = loadDaemonConfig(context.store);
|
|
12665
|
+
let activeTasks = 0;
|
|
12666
|
+
if (wasRunning && cfgForSafety?.runtimeId) {
|
|
12667
|
+
if (!context.runtime.token) {
|
|
12668
|
+
throw new CliError(
|
|
12669
|
+
"TASKS_UNKNOWN",
|
|
12670
|
+
EXIT.UNAUTHENTICATED,
|
|
12671
|
+
"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",
|
|
12672
|
+
false,
|
|
12673
|
+
{ hint: "morula-runtime auth login \u540E\u91CD\u8BD5\uFF1B\u6216\u786E\u8BA4\u53EF\u4E2D\u65AD\u540E\u5148 morula-runtime daemon stop" }
|
|
12674
|
+
);
|
|
12675
|
+
}
|
|
12676
|
+
try {
|
|
12677
|
+
activeTasks = await countActiveTasks(context, cfgForSafety.runtimeId);
|
|
12678
|
+
} catch (error) {
|
|
12679
|
+
throw new CliError(
|
|
12680
|
+
"TASKS_UNKNOWN",
|
|
12681
|
+
EXIT.UPSTREAM,
|
|
12682
|
+
`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`,
|
|
12683
|
+
false,
|
|
12684
|
+
{ 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" }
|
|
12685
|
+
);
|
|
12686
|
+
}
|
|
12687
|
+
}
|
|
12688
|
+
if (activeTasks > 0) {
|
|
12689
|
+
throw new CliError(
|
|
12690
|
+
"TASKS_RUNNING",
|
|
12691
|
+
EXIT.CONFLICT,
|
|
12692
|
+
`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`,
|
|
12693
|
+
false,
|
|
12694
|
+
{ hint: "\u7B49\u4EFB\u52A1\u5B8C\u6210\u540E\u518D\u5347\u7EA7\uFF1B\u6216\u786E\u8BA4\u53EF\u4E2D\u65AD\u540E\u5148 morula-runtime daemon stop" }
|
|
12695
|
+
);
|
|
12696
|
+
}
|
|
12697
|
+
const report = await performRuntimeUpgrade({
|
|
12698
|
+
currentVersion: BUILD_VERSION2,
|
|
12699
|
+
targetVersion,
|
|
12700
|
+
registry: registry2,
|
|
12701
|
+
log: (line) => context.err(line)
|
|
12702
|
+
});
|
|
12703
|
+
if (report.status === "up_to_date") {
|
|
12704
|
+
await reportUpgradeResult(context, { status: "succeeded", fromVersion: BUILD_VERSION2, toVersion: BUILD_VERSION2, restart: false });
|
|
12705
|
+
return { ok: true, upToDate: true, current: BUILD_VERSION2, target: report.targetVersion, steps: report.steps };
|
|
12706
|
+
}
|
|
12707
|
+
if (report.status !== "succeeded") {
|
|
12708
|
+
await reportUpgradeResult(context, { status: report.status, fromVersion: report.fromVersion, toVersion: report.toVersion ?? void 0, error: report.error });
|
|
12709
|
+
context.setExitCode(EXIT.OTHER);
|
|
12710
|
+
return {
|
|
12711
|
+
ok: false,
|
|
12712
|
+
status: report.status,
|
|
12713
|
+
from: report.fromVersion,
|
|
12714
|
+
to: report.toVersion,
|
|
12715
|
+
error: report.error,
|
|
12716
|
+
steps: report.steps,
|
|
12717
|
+
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"
|
|
12718
|
+
};
|
|
12719
|
+
}
|
|
12720
|
+
const cfg = loadDaemonConfig(context.store);
|
|
12721
|
+
let restart = null;
|
|
12722
|
+
let deferredRestart = false;
|
|
12723
|
+
if (wasRunning) {
|
|
12724
|
+
if (cfgForSafety?.runtimeId && context.runtime.token) {
|
|
12725
|
+
const stillBusy = await countActiveTasks(context, cfgForSafety.runtimeId);
|
|
12726
|
+
if (stillBusy > 0) {
|
|
12727
|
+
deferredRestart = true;
|
|
12728
|
+
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`);
|
|
12729
|
+
}
|
|
12730
|
+
}
|
|
12731
|
+
if (!deferredRestart) {
|
|
12732
|
+
if (probe.pid !== null) {
|
|
12733
|
+
const stopped = await stopDaemonByPid(probe.pid);
|
|
12734
|
+
context.err(`[upgrade] \u65E7 daemon\uFF08pid=${probe.pid}\uFF09\u5DF2\u505C\u6B62\uFF08${stopped.outcome}\uFF09`);
|
|
12735
|
+
}
|
|
12736
|
+
if (probe.lockState === "stale")
|
|
12737
|
+
releaseDaemonLock(lockPath);
|
|
12738
|
+
else if (probe.pid !== null)
|
|
12739
|
+
releaseDaemonLock(lockPath, probe.pid);
|
|
12740
|
+
rmSync11(pidFile, { force: true });
|
|
12741
|
+
mkdirSync17(dir, { recursive: true, mode: 448 });
|
|
12742
|
+
restart = await spawnUpgradedDaemon({
|
|
12743
|
+
env: context.env,
|
|
12744
|
+
logFile: join24(dir, LOG_FILE2),
|
|
12745
|
+
pidFile,
|
|
12746
|
+
daemonConfig: { name: cfg?.name, workdir: cfg?.workdir, maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT }
|
|
12747
|
+
});
|
|
12748
|
+
if (restart.pid > 0) {
|
|
12749
|
+
const liveness = await restart.waitExit(RESTART_LIVENESS_CHECK_MS);
|
|
12750
|
+
if (liveness.exited) {
|
|
12751
|
+
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`);
|
|
12752
|
+
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 }) };
|
|
12753
|
+
} else {
|
|
12754
|
+
context.err(`[upgrade] \u65B0 daemon \u5DF2\u62C9\u8D77\uFF08pid=${restart.pid}\uFF09\uFF0C\u65E5\u5FD7: ${join24(dir, LOG_FILE2)}`);
|
|
12755
|
+
}
|
|
12756
|
+
} else {
|
|
12757
|
+
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`);
|
|
12758
|
+
}
|
|
12759
|
+
}
|
|
12760
|
+
} else {
|
|
12761
|
+
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");
|
|
12762
|
+
}
|
|
12763
|
+
const spawnFailed = wasRunning && !deferredRestart && restart !== null && restart.pid <= 0;
|
|
12764
|
+
await reportUpgradeResult(context, {
|
|
12765
|
+
status: spawnFailed ? "failed" : "succeeded",
|
|
12766
|
+
fromVersion: report.fromVersion,
|
|
12767
|
+
toVersion: report.toVersion,
|
|
12768
|
+
restart: !spawnFailed && wasRunning && !deferredRestart,
|
|
12769
|
+
...spawnFailed ? { error: `\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\u4F46\u81EA\u52A8\u91CD\u542F\u5931\u8D25\uFF1A${restart?.error ?? "\u672A\u77E5\u539F\u56E0"}` } : {}
|
|
12770
|
+
});
|
|
12771
|
+
if (spawnFailed) {
|
|
12772
|
+
context.setExitCode(EXIT.OTHER);
|
|
12773
|
+
}
|
|
12774
|
+
return {
|
|
12775
|
+
ok: !spawnFailed,
|
|
12776
|
+
status: spawnFailed ? "restart_failed" : deferredRestart ? "installed_pending_restart" : "succeeded",
|
|
12777
|
+
from: report.fromVersion,
|
|
12778
|
+
to: report.toVersion,
|
|
12779
|
+
// deferredRestart(评审 P3):安装期有任务被派发 → 不打断 daemon,新版本下次重启生效
|
|
12780
|
+
restarted: !spawnFailed && wasRunning && !deferredRestart,
|
|
12781
|
+
pid: !spawnFailed && wasRunning && !deferredRestart ? restart?.pid ?? null : null,
|
|
12782
|
+
...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" } : {},
|
|
12783
|
+
warnings: report.warnings,
|
|
12784
|
+
steps: report.steps
|
|
12785
|
+
};
|
|
12786
|
+
}
|
|
12787
|
+
|
|
12077
12788
|
// src/commands/registry-runtime.ts
|
|
12078
12789
|
var COMMANDS = {
|
|
12079
12790
|
auth: authCommand,
|
|
12080
12791
|
daemon: daemonCommand,
|
|
12081
|
-
setup: setupCommand
|
|
12792
|
+
setup: setupCommand,
|
|
12793
|
+
upgrade: upgradeCommand
|
|
12082
12794
|
};
|
|
12083
12795
|
var EXIT_CODES = {
|
|
12084
12796
|
0: "success",
|
|
@@ -12100,7 +12812,7 @@ var GLOBAL_FLAGS2 = {
|
|
|
12100
12812
|
};
|
|
12101
12813
|
var SPEC = {
|
|
12102
12814
|
name: "morula-runtime",
|
|
12103
|
-
summary: "Device-level CLI for the Morula local agent runtime: authentication, daemon lifecycle (run/start/stop/status), local assistant portal, model inventory, and
|
|
12815
|
+
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.",
|
|
12104
12816
|
resources: [
|
|
12105
12817
|
{
|
|
12106
12818
|
resource: "auth",
|
|
@@ -12271,6 +12983,44 @@ var SPEC = {
|
|
|
12271
12983
|
"canonical form is the bare `morula-runtime setup`; postinstall runs the same registration automatically",
|
|
12272
12984
|
"only writes <root>/morula-cli; never deletes other plugins or the legacy ~/.agents/plugins/morula-cli (reported as legacyRoot)"
|
|
12273
12985
|
]
|
|
12986
|
+
},
|
|
12987
|
+
{
|
|
12988
|
+
resource: "upgrade",
|
|
12989
|
+
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)",
|
|
12990
|
+
actions: [
|
|
12991
|
+
{
|
|
12992
|
+
action: "check",
|
|
12993
|
+
summary: "read-only: compare current version with the latest published version on the npm registry (one network call)",
|
|
12994
|
+
flags: [
|
|
12995
|
+
{ name: "registry", type: "string", valueHint: "<url>", description: "npm registry to query (default https://registry.npmjs.org, or $MORULA_NPM_REGISTRY)" }
|
|
12996
|
+
],
|
|
12997
|
+
examples: ["morula-runtime upgrade check"],
|
|
12998
|
+
writes: "none",
|
|
12999
|
+
interactive: false,
|
|
13000
|
+
idempotent: true,
|
|
13001
|
+
emits: ["ok", "current", "latest", "updateAvailable", "registry"]
|
|
13002
|
+
},
|
|
13003
|
+
{
|
|
13004
|
+
action: "run",
|
|
13005
|
+
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",
|
|
13006
|
+
flags: [
|
|
13007
|
+
{ name: "yes", type: "boolean", description: "REQUIRED: explicit confirmation; without it the command refuses (exit 2). No silent auto-upgrade" },
|
|
13008
|
+
{ name: "version", type: "string", valueHint: "<semver>", description: "target version (default: latest from the registry)" },
|
|
13009
|
+
{ name: "registry", type: "string", valueHint: "<url>", description: "npm registry to install from (default https://registry.npmjs.org, or $MORULA_NPM_REGISTRY)" }
|
|
13010
|
+
],
|
|
13011
|
+
examples: ["morula-runtime upgrade run --yes", "morula-runtime upgrade run --yes --version 0.1.0-alpha.16"],
|
|
13012
|
+
writes: "local",
|
|
13013
|
+
interactive: false,
|
|
13014
|
+
idempotent: false,
|
|
13015
|
+
emits: ["ok", "status", "from", "to", "restarted", "pid", "steps"]
|
|
13016
|
+
}
|
|
13017
|
+
],
|
|
13018
|
+
notes: [
|
|
13019
|
+
"check: exit 0 = compared (see updateAvailable); 7 = registry unreachable (see error/hint)",
|
|
13020
|
+
"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)",
|
|
13021
|
+
"the daemon restart only happens when the daemon was running before the upgrade; a stopped daemon stays stopped",
|
|
13022
|
+
"a stopped daemon is upgraded but not started \u2014 start it manually with morula-runtime daemon start"
|
|
13023
|
+
]
|
|
12274
13024
|
}
|
|
12275
13025
|
]
|
|
12276
13026
|
};
|