@arcships/morula-runtime 0.1.0-alpha.2 → 0.1.0-alpha.4
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/bin/morula-runtime.js +0 -0
- package/bin/morula.js +9 -0
- package/dist/index.js +1202 -189
- package/dist/morula.js +2896 -0
- package/dist/plugin/plugin.json +10 -0
- package/dist/plugin/skills/morula-cli/SKILL.md +218 -0
- package/dist/plugin/skills/morula-cli/references/authentication.md +21 -0
- package/dist/plugin/skills/morula-cli/references/commands.md +401 -0
- package/dist/plugin/skills/morula-cli/references/eval-scenarios.md +42 -0
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +368 -0
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +79 -0
- package/package.json +9 -4
- package/scripts/postinstall.mjs +38 -0
package/dist/index.js
CHANGED
|
@@ -362,6 +362,66 @@ var init_portal_server = __esm({
|
|
|
362
362
|
}
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
// src/daemon/dim-locate.ts
|
|
366
|
+
import { accessSync, constants } from "node:fs";
|
|
367
|
+
import { homedir as homedir4 } from "node:os";
|
|
368
|
+
import { join as join6 } from "node:path";
|
|
369
|
+
function isExecutable(p) {
|
|
370
|
+
try {
|
|
371
|
+
accessSync(p, constants.X_OK);
|
|
372
|
+
return true;
|
|
373
|
+
} catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function knownDimLocations() {
|
|
378
|
+
const home = homedir4();
|
|
379
|
+
return [
|
|
380
|
+
"/Applications/DimAgent.app/Contents/Resources/runtime/cli/dim",
|
|
381
|
+
"/Applications/Dim.app/Contents/Resources/runtime/cli/dim",
|
|
382
|
+
join6(home, "Applications", "DimAgent.app", "Contents", "Resources", "runtime", "cli", "dim"),
|
|
383
|
+
join6(home, ".dimcode", "bin", "dim"),
|
|
384
|
+
join6(home, ".local", "bin", "dim")
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
function resolveDimCandidates(primary) {
|
|
388
|
+
const out = [];
|
|
389
|
+
const push = (p) => {
|
|
390
|
+
if (p && !out.includes(p))
|
|
391
|
+
out.push(p);
|
|
392
|
+
};
|
|
393
|
+
push(primary);
|
|
394
|
+
for (const c of knownDimLocations())
|
|
395
|
+
push(c);
|
|
396
|
+
for (const dir of (process.env.PATH ?? "").split(":").filter(Boolean))
|
|
397
|
+
push(join6(dir, "dim"));
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
function resolveDimBinary() {
|
|
401
|
+
const explicit = process.env.MORULA_DIM_PATH?.trim();
|
|
402
|
+
if (explicit) {
|
|
403
|
+
return isExecutable(explicit) ? explicit : "dim";
|
|
404
|
+
}
|
|
405
|
+
for (const candidate of knownDimLocations()) {
|
|
406
|
+
if (isExecutable(candidate)) {
|
|
407
|
+
return candidate;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
411
|
+
for (const dir of pathDirs) {
|
|
412
|
+
const candidate = join6(dir, "dim");
|
|
413
|
+
if (isExecutable(candidate)) {
|
|
414
|
+
return candidate;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return "dim";
|
|
418
|
+
}
|
|
419
|
+
var init_dim_locate = __esm({
|
|
420
|
+
"src/daemon/dim-locate.ts"() {
|
|
421
|
+
"use strict";
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
365
425
|
// src/daemon/acp/acp-connection.ts
|
|
366
426
|
var acp_connection_exports = {};
|
|
367
427
|
__export(acp_connection_exports, {
|
|
@@ -400,6 +460,7 @@ var DEFAULT_ACP_INIT_TIMEOUT_MS, cachedDimVersion, requestSeq, AcpConnection;
|
|
|
400
460
|
var init_acp_connection = __esm({
|
|
401
461
|
"src/daemon/acp/acp-connection.ts"() {
|
|
402
462
|
"use strict";
|
|
463
|
+
init_dim_locate();
|
|
403
464
|
DEFAULT_ACP_INIT_TIMEOUT_MS = 45e3;
|
|
404
465
|
cachedDimVersion = null;
|
|
405
466
|
requestSeq = 0;
|
|
@@ -424,6 +485,46 @@ var init_acp_connection = __esm({
|
|
|
424
485
|
setPermissionHandler(handler) {
|
|
425
486
|
this.permissionHandler = handler;
|
|
426
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* P5(issues/daemon-dim-spawn-enoent.md):按候选顺序尝试 spawn `dim acp` 子进程。
|
|
490
|
+
* 以 `spawn` 事件判成功(进程已创建);`error` 事件(ENOENT/EACCES 等,进程未起来)
|
|
491
|
+
* 则回收本次尝试并换下一个候选。全部失败抛最后一个错误。
|
|
492
|
+
*/
|
|
493
|
+
async spawnFirstUsable(candidates) {
|
|
494
|
+
let lastError = null;
|
|
495
|
+
for (const dimPath of candidates) {
|
|
496
|
+
const child = spawn3(dimPath, ["acp"], {
|
|
497
|
+
cwd: this.options.cwd,
|
|
498
|
+
env: { ...process.env, ...this.options.env },
|
|
499
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
500
|
+
detached: process.platform !== "win32"
|
|
501
|
+
});
|
|
502
|
+
try {
|
|
503
|
+
await new Promise((resolve2, reject) => {
|
|
504
|
+
child.once("error", reject);
|
|
505
|
+
child.once("spawn", resolve2);
|
|
506
|
+
});
|
|
507
|
+
return child;
|
|
508
|
+
} catch (error) {
|
|
509
|
+
lastError = error;
|
|
510
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
511
|
+
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
512
|
+
`);
|
|
513
|
+
try {
|
|
514
|
+
const { existsSync: existsSync17 } = await import("node:fs");
|
|
515
|
+
process.stderr.write(
|
|
516
|
+
`[daemon] diag: fileExists=${existsSync17(dimPath)} cwdExists=${this.options.cwd ? existsSync17(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
|
|
517
|
+
`
|
|
518
|
+
);
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
child.removeAllListeners("error");
|
|
522
|
+
child.removeAllListeners("spawn");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
526
|
+
throw new Error(`dim acp \u8FDB\u7A0B\u9519\u8BEF: ${detail}\uFF08\u5DF2\u5C1D\u8BD5 ${candidates.length} \u4E2A\u5019\u9009\u8DEF\u5F84\uFF09`);
|
|
527
|
+
}
|
|
427
528
|
get isAlive() {
|
|
428
529
|
return this.child !== null && this.child.exitCode === null && !this.exited;
|
|
429
530
|
}
|
|
@@ -435,12 +536,7 @@ var init_acp_connection = __esm({
|
|
|
435
536
|
if (this.isAlive)
|
|
436
537
|
return;
|
|
437
538
|
this.exited = false;
|
|
438
|
-
const child =
|
|
439
|
-
cwd: this.options.cwd,
|
|
440
|
-
env: { ...process.env, ...this.options.env },
|
|
441
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
442
|
-
detached: process.platform !== "win32"
|
|
443
|
-
});
|
|
539
|
+
const child = await this.spawnFirstUsable(resolveDimCandidates(this.options.dimPath));
|
|
444
540
|
this.child = child;
|
|
445
541
|
child.stderr.on("data", (d) => {
|
|
446
542
|
const text = d.toString().trim();
|
|
@@ -691,10 +787,10 @@ __export(dim_home_exports, {
|
|
|
691
787
|
});
|
|
692
788
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
693
789
|
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync3 } from "node:fs";
|
|
694
|
-
import { homedir as
|
|
695
|
-
import { join as
|
|
790
|
+
import { homedir as homedir5 } from "node:os";
|
|
791
|
+
import { join as join7 } from "node:path";
|
|
696
792
|
function isolatedDimHome() {
|
|
697
|
-
return
|
|
793
|
+
return join7(homedir5(), ".morula", "dimcode");
|
|
698
794
|
}
|
|
699
795
|
function resolveIsolatedDimEnv() {
|
|
700
796
|
return {};
|
|
@@ -1715,7 +1811,7 @@ function page(ok, message) {
|
|
|
1715
1811
|
}
|
|
1716
1812
|
|
|
1717
1813
|
// ../morula-cli-core/dist/build-env.js
|
|
1718
|
-
var BUILD_ID = "
|
|
1814
|
+
var BUILD_ID = "20260916074556.nogit";
|
|
1719
1815
|
var BUILD_CONTEXTS = {
|
|
1720
1816
|
"local": "http://localhost:8080/api",
|
|
1721
1817
|
"test": "https://morula.basecastle.com/api",
|
|
@@ -2073,9 +2169,6 @@ var ReportApi = class {
|
|
|
2073
2169
|
listByAutopilot(autopilotId, query = {}) {
|
|
2074
2170
|
return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId2(autopilotId, "autopilot"))}/reports`, { ...query });
|
|
2075
2171
|
}
|
|
2076
|
-
archive(id) {
|
|
2077
|
-
return this.client.patch(`/agent-reports/${encodeURIComponent(requiredId2(id))}`, { status: "archived" });
|
|
2078
|
-
}
|
|
2079
2172
|
};
|
|
2080
2173
|
function requiredId2(id, label = "report") {
|
|
2081
2174
|
const normalized = id.trim();
|
|
@@ -2873,7 +2966,7 @@ function redactInputSummary(value) {
|
|
|
2873
2966
|
}
|
|
2874
2967
|
|
|
2875
2968
|
// src/build-env.ts
|
|
2876
|
-
var BUILD_VERSION2 = "0.1.0-alpha.
|
|
2969
|
+
var BUILD_VERSION2 = "0.1.0-alpha.4";
|
|
2877
2970
|
|
|
2878
2971
|
// src/commands/daemon.ts
|
|
2879
2972
|
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync10, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -3256,10 +3349,34 @@ var DaemonApi = class {
|
|
|
3256
3349
|
submitCleanupResult(id, runtimeId, body) {
|
|
3257
3350
|
return this.client.post(`/agent-workspace-cleanups/${id}/result`, body, { runtimeId });
|
|
3258
3351
|
}
|
|
3352
|
+
/**
|
|
3353
|
+
* 单条进度上报(兼容入口;runner 已改为批量 `recordProgressBatch`)。
|
|
3354
|
+
* 与批量走同一条端点/同一份契约:`items` 缺省时服务端按单条语义处理顶层字段。
|
|
3355
|
+
*/
|
|
3259
3356
|
recordProgress(taskId, body) {
|
|
3260
|
-
return this.
|
|
3357
|
+
return this.recordProgressBatch(taskId, [body]);
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* 批量进度上报(issues/daemon-stream-events-silently-stop-mid-run.md 根因 A):
|
|
3361
|
+
* 每 500ms 一批、**一次请求**提交,吞吐不再被单条 RTT 钉在 ~27 req/s
|
|
3362
|
+
* (绑定推理模型的任务实测产出 ~110 片段/s,逐条发必然积压数分钟,并把终态回传堵在后面)。
|
|
3363
|
+
*
|
|
3364
|
+
* 兼容性:顶层字段带该批首条 —— 旧服务端没有 `items` 支持(`whitelist` 会剥离该字段),
|
|
3365
|
+
* 收到后降级为「每批只落 1 条」,而不是整批 400 丢失。
|
|
3366
|
+
*/
|
|
3367
|
+
recordProgressBatch(taskId, items) {
|
|
3368
|
+
if (items.length === 0)
|
|
3369
|
+
return Promise.resolve({ ok: true, inserted: 0 });
|
|
3370
|
+
return this.taskRequest(taskId, `/agent-tasks/${taskId}/progress`, {
|
|
3371
|
+
body: { ...items[0], items }
|
|
3372
|
+
});
|
|
3261
3373
|
}
|
|
3262
|
-
/**
|
|
3374
|
+
/**
|
|
3375
|
+
* M2-B4:执行事件(工具调用等)→ agent_events(taskRunId+seq 唯一,前端时间线消费)。
|
|
3376
|
+
*
|
|
3377
|
+
* P3:`turnNo` 为多回合预留位(`turn_started` / `turn_interrupted` 用它标记回合边界);
|
|
3378
|
+
* 服务端 `agent_events.turn_no` 直接落该值,也是「检查点消费标第几回合」的判据来源。
|
|
3379
|
+
*/
|
|
3263
3380
|
recordEvent(taskId, body) {
|
|
3264
3381
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/events`, { body });
|
|
3265
3382
|
}
|
|
@@ -3267,6 +3384,46 @@ var DaemonApi = class {
|
|
|
3267
3384
|
recordStreamChunk(taskId, body) {
|
|
3268
3385
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/stream-chunk`, { body });
|
|
3269
3386
|
}
|
|
3387
|
+
/**
|
|
3388
|
+
* 会话指针回传(P0 §5.4 前置修复):只写 `dimSessionId`/`dimWorkDir`,不改任务状态。
|
|
3389
|
+
*
|
|
3390
|
+
* 取消路径下 daemon 跳过 submitResult,指针若只随 result 回传就永久丢失 → 续跑/接着聊不可能。
|
|
3391
|
+
* 失败不抛(调用方 fire-and-forget)——丢指针只影响续跑能力,不该反过来影响任务执行。
|
|
3392
|
+
*/
|
|
3393
|
+
reportSessionPointer(taskId, body) {
|
|
3394
|
+
return this.taskRequest(
|
|
3395
|
+
taskId,
|
|
3396
|
+
`/agent-tasks/${taskId}/session`,
|
|
3397
|
+
{ body }
|
|
3398
|
+
);
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* P1(features/stage-runtime-live-chat.md §5.2 机器协议):对话消息回执。
|
|
3402
|
+
*
|
|
3403
|
+
* 语义边界(P1 必须严格遵守,误回执会丢话):
|
|
3404
|
+
* - 只对 `targetTaskId === 本任务` 的消息发 `consumed`——那才是被合并进本轮 prompt 的消息;
|
|
3405
|
+
* - `delivered` **不**由 daemon 发(服务端创建 follow-up run 时置位);
|
|
3406
|
+
* - `interrupting` 属 P3,P1 不发。
|
|
3407
|
+
*/
|
|
3408
|
+
ackMessage(taskId, messageId, body) {
|
|
3409
|
+
return this.taskRequest(
|
|
3410
|
+
taskId,
|
|
3411
|
+
`/agent-tasks/${taskId}/messages/${messageId}/ack`,
|
|
3412
|
+
{ body }
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* P1:对话补齐(WS 是 best-effort,拉取才是 correctness path)。
|
|
3417
|
+
*
|
|
3418
|
+
* `afterSeq` / `limit` 走 **query**(不是 token,见 `taskRequest` 注释);`limit` 缺省即不传,
|
|
3419
|
+
* 由服务端默认值决定。旧服务端无该端点 → 404,调用方只记日志(补齐失败不影响本轮执行)。
|
|
3420
|
+
*/
|
|
3421
|
+
listTaskChat(taskId, options = {}) {
|
|
3422
|
+
return this.taskRequest(taskId, `/agent-tasks/${taskId}/chat`, {
|
|
3423
|
+
method: "GET",
|
|
3424
|
+
query: { afterSeq: options.afterSeq ?? 0, limit: options.limit }
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3270
3427
|
/** 任务级审批请求上报(Phase 2):返回 { ok, approvalId } */
|
|
3271
3428
|
requestApproval(taskId, body) {
|
|
3272
3429
|
return this.taskRequest(
|
|
@@ -3317,17 +3474,19 @@ function buildGithubSkillBody(baseBranch = "main") {
|
|
|
3317
3474
|
return COLLABORATION_SKILL_BODY.replaceAll("__MORULA_BASE__", base);
|
|
3318
3475
|
}
|
|
3319
3476
|
function buildGitlabSkillBody(repoFullName, baseUrl, baseBranch = "main") {
|
|
3320
|
-
const projectPath = encodeURIComponent(repoFullName);
|
|
3321
3477
|
const host = new URL(baseUrl).host;
|
|
3322
3478
|
const base = baseBranch?.trim() || "main";
|
|
3479
|
+
const repo = repoFullName?.trim();
|
|
3323
3480
|
const body = COLLABORATION_SKILL_BODY.replaceAll("__MORULA_BASE__", base);
|
|
3481
|
+
const repoLine = repo ? `- **\u672C\u4EFB\u52A1\u4E3B\u4ED3\u5E93**\uFF1A\`${repo}\`\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u6309\u4F60\u5B9E\u9645\u6539\u52A8\u7684\u4ED3\u5E93\u9010\u4E2A\u5EFA MR\uFF0C\u89C1 \xA70.5 \u4ED3\u5E93\u4E0E\u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002
|
|
3482
|
+
` : "";
|
|
3324
3483
|
return `${body}
|
|
3325
3484
|
|
|
3326
3485
|
## GitLab \u5E73\u53F0\u8BF4\u660E\uFF08\u672C\u4EFB\u52A1\u4ED3\u5E93\u662F GitLab\uFF0C\u8986\u76D6\u4E0A\u6587 gh \u76F8\u5173\u6307\u4EE4\uFF09
|
|
3327
3486
|
|
|
3328
3487
|
\u672C\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6765\u81EA **GitLab**\uFF08${host}\uFF09\uFF0C\u4E0D\u662F GitHub\uFF1A
|
|
3329
3488
|
|
|
3330
|
-
- **\u4E0D\u8981**\u4F7F\u7528 \`gh\`\uFF08GitHub CLI \u5728\u672C\u5E73\u53F0\u4E0D\u53EF\u7528\u3001\u4F1A\u5931\u8D25\uFF09\u3002
|
|
3489
|
+
${repoLine}- **\u4E0D\u8981**\u4F7F\u7528 \`gh\`\uFF08GitHub CLI \u5728\u672C\u5E73\u53F0\u4E0D\u53EF\u7528\u3001\u4F1A\u5931\u8D25\uFF09\u3002
|
|
3331
3490
|
- **\u8BA4\u8BC1**\uFF1A\u672C\u73AF\u5883 Git remote \u5DF2\u914D\u7F6E\u4E3A **SSH**\uFF08\`git@${host}:\`\uFF09\uFF0C\u8BA4\u8BC1\u8D70\u672C\u673A SSH agent / keychain\uFF0C**\u65E0\u9700\u5BC6\u7801\u3001\u65E0\u9700 HTTPS token\u3001\u65E0\u9700\u624B\u52A8\u8F93\u5165\u51ED\u636E**\u3002
|
|
3332
3491
|
- \`git fetch origin\` / \`git pull\` / \`git push\` \u76F4\u63A5\u53EF\u7528\uFF1B**\u4E0D\u8981\u5207\u6362\u6210 HTTPS \u5730\u5740\u91CD\u8BD5**\uFF08HTTPS \u65E0\u51ED\u636E\u4F1A \`HTTP Basic: Access denied\`\uFF09\u3002
|
|
3333
3492
|
- \u9700\u8981\u5237\u65B0\u8FDC\u7AEF\u6700\u65B0\u4EE3\u7801\u65F6\u76F4\u63A5 \`git fetch origin\`\uFF08\u6362\u5206\u652F\u7528 \`git fetch origin <branch>\` \u540E \`git switch -c\` \u6216 \`git checkout -b\`\uFF09\uFF0C**\u4E0D\u8981**\u5BFB\u627E \`MORULA_GITLAB_TOKEN\` \u6216\u7528 curl \u62C9\u53D6\u3002
|
|
@@ -3345,9 +3504,16 @@ function buildGitlabSkillBody(repoFullName, baseUrl, baseBranch = "main") {
|
|
|
3345
3504
|
\`\`\`bash
|
|
3346
3505
|
morula repo mr list <taskId> --state opened
|
|
3347
3506
|
\`\`\`
|
|
3348
|
-
\u8FD4\u56DE\u4EFB\u52A1\u7A7A\u95F4\u4ED3\u5E93\u7684 open MR\uFF08iid / title / sourceBranch / targetBranch / webUrl\uFF09\u3002\u5BA1\u67E5\u5355\u4E2A MR \u7684 diff \u4ECD\u7528 \`morula repo checkout\` \u540E \`git diff\`\u3002
|
|
3507
|
+
\u8FD4\u56DE\u4EFB\u52A1\u7A7A\u95F4\u4ED3\u5E93\u7684 open MR\uFF08iid / title / sourceBranch / targetBranch / webUrl / createdAt / updatedAt / author\uFF09\u3002\u5BA1\u67E5\u5355\u4E2A MR \u7684 diff \u4ECD\u7528 \`morula repo checkout\` \u540E \`git diff\`\u3002
|
|
3349
3508
|
- \u672C\u547D\u4EE4\u53EA\u8BFB\uFF08\u4E0D approve / \u4E0D merge / \u4E0D\u8BC4\u8BBA\u2014\u2014**\u5408\u5E76\u7531\u4EBA\u51B3\u5B9A**\uFF0C\u89C1\u5951\u7EA6\u7B2C 4 \u6761\uFF09\uFF1B
|
|
3350
3509
|
- \u82E5\u8FD4\u56DE \`NO_CONNECTION\`\uFF08\u4EFB\u52A1\u672A\u7ED1\u5B9A\u4ED3\u5E93\u8FDE\u63A5\uFF09\uFF0C\u8BF4\u660E\u672C\u4EFB\u52A1\u4E0D\u662F MR \u5DE1\u68C0\u573A\u666F\uFF0C\u76F4\u63A5\u6309\u4EFB\u52A1\u8981\u6C42\u56DE\u5E94\uFF0C\u4E0D\u8981\u81EA\u884C\u731C\u6D4B\u3002
|
|
3510
|
+
- **\u5728 MR \u4E0A\u7559\u8BC4\u5BA1\u610F\u89C1\uFF08\u5DE1\u68C0/\u5BA1\u67E5\u7C7B\u4EFB\u52A1\uFF0C\u975E\u8BC4\u5BA1\u4EFB\u52A1\u65F6\uFF09**\uFF1A\u7528 \`morula repo mr comment\`\uFF0C**\u4E00\u6761 MR \u6700\u591A\u4E00\u6761\u8BC4\u8BBA**\uFF1A
|
|
3511
|
+
\`\`\`bash
|
|
3512
|
+
morula repo mr get <taskId> --mr <iid> --notes # \u5199\u4E4B\u524D\u5148\u8BFB\u5DF2\u6709\u8BA8\u8BBA\uFF08\u5E73\u53F0\u4E0D\u505A\u53BB\u91CD\uFF0C\u91CD\u590D\u7531\u4F60\u907F\u514D\uFF09
|
|
3513
|
+
morula repo mr comment <taskId> --mr <iid> --content-file <path>
|
|
3514
|
+
\`\`\`
|
|
3515
|
+
\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
|
|
3516
|
+
**\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
|
|
3351
3517
|
- \u5173\u8054/\u5173\u5355\u8BED\u4E49\u4E0E\u4E0A\u6587\u4E00\u81F4\uFF1AMR \u7684 title / body / \u6E90\u5206\u652F\u51FA\u73B0\u4EFB\u52A1\u7F16\u53F7 \u2192 \u81EA\u52A8\u5173\u8054\uFF1B\`Closes <\u4EFB\u52A1\u7F16\u53F7>\` \u2192 merge \u65F6\u81EA\u52A8\u6D41\u8F6C\u4EFB\u52A1\u3002
|
|
3352
3518
|
`;
|
|
3353
3519
|
}
|
|
@@ -3483,6 +3649,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
3483
3649
|
morula repo checkout <taskId> <repoFullName> [--ref <branch|sha>] # \u62C9\u53D6/\u5207\u6362\u4ED3\u5E93\uFF08\u8FD4\u56DE\u7269\u5316\u8DEF\u5F84\uFF0C\u89C1 0.5\uFF09
|
|
3484
3650
|
morula repo prepare <taskId> <repoFullName> # \u591A\u4ED3\u5E93\u4EFB\u52A1\uFF1A\u7269\u5316\u5176\u4ED6\u4ED3\u5E93\u5230\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\u4E0B\uFF08\u89C1 0.5\uFF09
|
|
3485
3651
|
morula comment list --object <objectId> # \u8BFB\u8BC4\u8BBA\uFF08\u4EFB\u52A1=objectId\uFF09
|
|
3652
|
+
morula task inbox [--peek] # \u8FD0\u884C\u4E2D\u68C0\u67E5\u70B9\uFF1A\u53D6\u672C\u7EBF\u7A0B\u672A\u6D88\u8D39\u6D88\u606F\uFF0C\u7EB3\u5165\u672C\u56DE\u5408\uFF08\u89C1 1.3\uFF09
|
|
3486
3653
|
# \u6CE8\uFF1A\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF08\u4F5C\u8005=\u667A\u80FD\u4F53\uFF09\uFF0C\u4E0D\u8981\u8C03\u7528 comment add \u81EA\u884C\u7F6E\u8BC4\u3002
|
|
3487
3654
|
\`\`\`
|
|
3488
3655
|
- **\u53EF\u7528\u4ED3\u5E93\u6E05\u5355**\u7531\u7CFB\u7EDF\u63D0\u793A\u76F4\u63A5\u7ED9\u51FA\uFF08\u300C\u53EF\u7528\u4ED3\u5E93\u300D/\u300C\u591A\u4ED3\u5E93\u4EFB\u52A1\u300D\u6BB5\uFF09\uFF0C**\u4E0D\u9700\u8981**\u81EA\u5DF1\u67E5\uFF1B\u53EA\u6709\u5728\u7CFB\u7EDF\u63D0\u793A\u672A\u7ED9\u51FA\u3001\u53C8\u786E\u5B9E\u9700\u8981\u679A\u4E3E\u65F6\u624D\u7528
|
|
@@ -3528,6 +3695,44 @@ morula report create --title "<\u4E00\u53E5\u8BDD\u6807\u9898>" --content "<Mark
|
|
|
3528
3695
|
- \u62A5\u544A\u843D\u5E93\u540E\u521B\u5EFA\u8005\u4E0E\u8BA2\u9605\u8005\u4F1A\u6536\u5230\u8F7B\u901A\u77E5\uFF08\u53EA\u542B\u6807\u9898\u4E0E\u8DF3\u8F6C\uFF09\uFF1B
|
|
3529
3696
|
- \u4E0D\u4EA7\u51FA\u4E5F\u6CA1\u5173\u7CFB\uFF1A\u4EFB\u52A1\u7EC8\u6001\u65F6\u5E73\u53F0\u4F1A\u7528\u4F60\u7684\u6700\u7EC8\u8F93\u51FA\u515C\u5E95\u843D\u4E00\u4EFD\u62A5\u544A\uFF08\u4F46\u515C\u5E95\u4E0D\u5982\u4F60\u4E3B\u52A8\u7EC4\u7EC7\u7684\u5185\u5BB9\u6E05\u6670\uFF0C\u4FE1\u606F\u7C7B\u4EA7\u51FA**\u4F18\u5148\u4E3B\u52A8\u4EA7\u51FA**\uFF09\u3002
|
|
3530
3697
|
|
|
3698
|
+
## 1.3 \u8FD0\u884C\u4E2D\u53CD\u9988\uFF1A\u68C0\u67E5\u70B9\u4E0E\u6253\u65AD\uFF08\u672C\u56DE\u5408\u5185\u751F\u6548\uFF09
|
|
3699
|
+
|
|
3700
|
+
\u7528\u6237\u53EF\u4EE5\u5728**\u4F60\u6B63\u5728\u8FD0\u884C\u7684\u8FD9\u4E00\u4E2A\u56DE\u5408\u5185**\u53D1\u6765\u610F\u89C1\u2014\u2014\u4E0D\u5FC5\u7B49\u672C\u8F6E\u7ED3\u675F\u3001\u4E5F\u4E0D\u5FC5\u91CD\u5F00\u4EFB\u52A1\u3002\u4F60\u8981\u5728\u4E24\u5904\u914D\u5408\uFF1A
|
|
3701
|
+
|
|
3702
|
+
**\u2460 \u4E3B\u52A8\u67E5\u68C0\u67E5\u70B9\uFF08\`morula task inbox\`\uFF09\u2014\u2014\u53EA\u5728\u8FD9\u56DB\u4E2A\u65F6\u673A\u67E5\uFF0C\u4E0D\u8981\u66F4\u9891\u7E41**
|
|
3703
|
+
|
|
3704
|
+
\`\`\`bash
|
|
3705
|
+
morula task inbox # \u53D6\u8D70\u672C\u7EBF\u7A0B\u672A\u6D88\u8D39\u7684\u6D88\u606F\uFF08\u670D\u52A1\u7AEF\u6807\u8BB0\u300C\u672C\u56DE\u5408\u5DF2\u6D88\u8D39\u300D\uFF09
|
|
3706
|
+
morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\u6CA1\u6709\u65B0\u610F\u89C1\uFF0C\u4E0D\u6807\u8BB0\u6D88\u8D39\uFF09
|
|
3707
|
+
\`\`\`
|
|
3708
|
+
|
|
3709
|
+
\u56DB\u4E2A\u65F6\u673A\uFF08\u95ED\u73AF\u679A\u4E3E\uFF0C**\u4E0D\u8981**\u6BCF\u4E00\u6B65\u90FD\u67E5\uFF09\uFF1A
|
|
3710
|
+
|
|
3711
|
+
1. \u4E00\u6279\u6587\u4EF6\u6539\u52A8**\u5B8C\u6210\u4E4B\u540E**\uFF08\u51C6\u5907\u8FDB\u5165\u4E0B\u4E00\u6279\u4E4B\u524D\uFF09\uFF1B
|
|
3712
|
+
2. \`git commit\` **\u4E4B\u524D**\uFF1B
|
|
3713
|
+
3. \`git push\` / \u5EFA PR **\u4E4B\u524D**\uFF1B
|
|
3714
|
+
4. \u6536\u5230\u300C\u8FD0\u884C\u4E2D\u88AB\u6253\u65AD\u300D\u63D0\u793A\u65F6\uFF08\u89C1 \u2461\uFF09\u3002
|
|
3715
|
+
|
|
3716
|
+
\u62FF\u5230\u6D88\u606F\u540E\uFF1A
|
|
3717
|
+
|
|
3718
|
+
- **\u6709\u6D88\u606F** \u2192 \u9010\u6761\u7EB3\u5165\u672C\u56DE\u5408\uFF0C\u6309\u7528\u6237\u7684\u610F\u89C1\u8C03\u6574\uFF0C\u5E76\u5728\u6700\u7EC8\u603B\u7ED3\u91CC**\u56DE\u6267\u5F0F\u8BF4\u660E**\uFF08\u300C\u5DF2\u6309\u8981\u6C42\u6539\u4E3A X\u300D\uFF09\uFF1B
|
|
3719
|
+
- **\u6CA1\u6709\u6D88\u606F** \u2192 **\u7ACB\u5373\u7EE7\u7EED\u624B\u4E0A\u7684\u4E8B\uFF0C\u4E0D\u8981\u5C55\u5F00\u8BA8\u8BBA\u3001\u4E0D\u8981\u590D\u8FF0\u68C0\u67E5\u8FC7\u7A0B\u3001\u4E0D\u8981\u5728\u603B\u7ED3\u91CC\u63D0\u300C\u68C0\u67E5\u8FC7\u6CA1\u6709\u65B0\u610F\u89C1\u300D**\uFF1B
|
|
3720
|
+
- \u7528\u6237\u7684\u610F\u89C1**\u4E0D\u5F97\u63A8\u7FFB\u5B83\u6CA1\u6D89\u53CA\u7684\u90E8\u5206**\uFF1A\u5DF2\u5B8C\u6210\u7684\u3001\u4E0E\u610F\u89C1\u65E0\u5173\u7684\u6539\u52A8\u4FDD\u7559\uFF1B\u610F\u89C1\u4E0E\u539F\u59CB\u9700\u6C42\u51B2\u7A81\u65F6**\u4EE5\u610F\u89C1\u4E3A\u51C6**\u5E76\u5728\u603B\u7ED3\u91CC\u8BF4\u660E\u5DEE\u5F02\uFF1B
|
|
3721
|
+
- \u4E0D\u8981\u53CD\u590D\u8F6E\u8BE2\uFF08\u4E00\u6B21\u68C0\u67E5\u5C31\u662F\u4E00\u6B21\u5DE5\u5177\u8C03\u7528\uFF0C\u6709\u5F00\u9500\uFF09\u3002
|
|
3722
|
+
|
|
3723
|
+
**\u2461 \u88AB\u6253\u65AD\u65F6\uFF08\u7528\u6237\u6309\u4E86\u300C\u7ACB\u5373\u6253\u65AD\u300D\uFF09\u4F60\u4F1A\u5728\u540C\u4E00\u4F1A\u8BDD\u6536\u5230\u65B0\u7684\u6307\u4EE4**
|
|
3724
|
+
|
|
3725
|
+
- \u4E0A\u4E00\u56DE\u5408\u5728**\u5B89\u5168\u70B9**\u88AB\u4E2D\u65AD\uFF1A\u5176\u4E2D\u7684\u5DE5\u5177\u8FDB\u5EA6\u5DF2\u4E22\u5F03\uFF0C**\u88AB\u4E2D\u65AD\u7684\u5DE5\u5177\u8C03\u7528\u6CA1\u6709\u7ED3\u679C**\uFF08\u65F6\u95F4\u7EBF\u6807\u4E3A\u5DF2\u4E2D\u65AD\uFF09\u2014\u2014\u9700\u8981\u65F6\u91CD\u65B0\u6267\u884C\uFF0C\u4E0D\u8981\u5047\u5B9A\u5B83\u5DF2\u6210\u529F\uFF1B
|
|
3726
|
+
- \u65B0\u6D88\u606F\u5C31\u662F\u7528\u6237\u7684\u6700\u65B0\u610F\u89C1\uFF1A\u5148\u6309\u5B83\u8C03\u6574\u65B9\u5411\uFF0C\u518D\u7EE7\u7EED\u5B8C\u6210\u672C\u4EFB\u52A1\uFF1B\u5DE5\u4F5C\u533A\u6587\u4EF6\u4E0E git \u72B6\u6001\u4ECD\u5728\uFF0C\u4E0D\u7528\u91CD\u505A\u5DF2\u5B8C\u6210\u7684\u90E8\u5206\u3002
|
|
3727
|
+
|
|
3728
|
+
**\u2462 \u7528\u6237\u610F\u89C1\u4E0E\u300C\u8981\u4E0D\u8981\u8FD4\u5DE5\u300D\u7531\u4F60\u81EA\u5DF1\u5224\u65AD\uFF0C\u5E73\u53F0\u4E0D\u4EE3\u505A**
|
|
3729
|
+
|
|
3730
|
+
- \u5E73\u53F0**\u4E0D\u4F1A**\u56E0\u4E3A\u7528\u6237\u8BF4\u4E86\u53E5\u8BDD\u5C31\u56DE\u9000\u9636\u6BB5\u3001\u6539\u4EFB\u52A1\u72B6\u6001\u6216\u6D3E\u8FD4\u5DE5\u4EFB\u52A1\u2014\u2014**\u90A3\u662F\u4F60\u7684\u5224\u65AD**\uFF1A\u8BC4\u4F30\u540E\u8BA4\u4E3A\u5FC5\u987B\u63A8\u5012\u91CD\u505A\uFF0C\u5C31\u6309\u4F60\u81EA\u5DF1\u7684\u534F\u8BAE\u8868\u8FBE\uFF08\u8BC4\u5BA1\u9636\u6BB5\u7528 \`morula review submit\` \u7ED9\u51FA\u7ED3\u8BBA\uFF1B\u5206\u6790\u9636\u6BB5\u7528 \`morula task analysis-verdict\`\uFF09\uFF0C\u72B6\u6001\u6D41\u8F6C\u7531\u670D\u52A1\u7AEF\u6309\u65E2\u6709\u89C4\u5219\u9A71\u52A8\uFF1B
|
|
3731
|
+
- \u4F60\u53EA\u8DDF**\u5F53\u524D\u9636\u6BB5**\u5BF9\u8BDD\uFF1A\u6D88\u606F\u5C31\u662F\u53D1\u7ED9\u5F53\u524D\u9636\u6BB5\u8D1F\u8D23\u667A\u80FD\u4F53\u7684\u3002**\u4E0D\u8981**\u56E0\u4E3A\u6536\u5230\u610F\u89C1\u5C31\u53BB\u64CD\u4F5C\u522B\u7684\u9636\u6BB5\u6216\u522B\u7684\u5BF9\u8C61\u7684\u8D44\u6E90\uFF1B
|
|
3732
|
+
- **\u8BC4\u5BA1\u9636\u6BB5\u4F60\u53EA\u505A\u8BC4\u5BA1**\uFF08\u770B diff\u3001\u6838\u5BF9\u76EE\u6807\u3001\u51FA\u7ED3\u8BBA\uFF09\uFF0C**\u4E0D\u8981\u6539\u4EE3\u7801\u3001\u4E0D\u8981\u5EFA\u5206\u652F/\u63D0 PR**\u3002
|
|
3733
|
+
|
|
3734
|
+
> \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
|
|
3735
|
+
|
|
3531
3736
|
## 2. PR \u94FE\u63A5\u4E0E\u5173\u5355\u610F\u56FE\u662F\u4E24\u5957\u4E0D\u540C\u7684\u5951\u7EA6
|
|
3532
3737
|
|
|
3533
3738
|
- **\u94FE\u63A5\u901A\u9053**\uFF1APR \u7684 title / branch / body \u4E2D\u51FA\u73B0\u4EFB\u52A1\u7F16\u53F7 \u2192 \u670D\u52A1\u7AEF\u81EA\u52A8\u5173\u8054 PR \u5230\u4EFB\u52A1\u3002
|
|
@@ -3617,6 +3822,20 @@ morula report create --title "<\u4E00\u53E5\u8BDD\u6807\u9898>" --content "<Mark
|
|
|
3617
3822
|
- **\u4E0D\u8981\u624B\u52A8\u6D41\u8F6C\u672C\u4EFB\u52A1\u72B6\u6001**\uFF08\u4E0D\u8981\u8C03 \`morula task state\`\uFF09\uFF1A\u8BC4\u5BA1\u7ED3\u8BBA\u7531\u670D\u52A1\u7AEF\u636E\u6B64\u63A8\u8FDB\u3002
|
|
3618
3823
|
- **\u5FC5\u987B\u4E0A\u62A5**\uFF1A\u6F0F\u62A5\u4F1A\u8BA9\u4EFB\u52A1\u505C\u5728\u300C\u5F85\u8BC4\u5BA1\u300D\uFF0C\u5F00\u53D1\u65E0\u6CD5\u8FDB\u5165\u4E0B\u4E00\u8F6E\u3002
|
|
3619
3824
|
|
|
3825
|
+
## 4.3 \u5728 MR/PR \u4E0A\u7559\u8BC4\u5BA1\u610F\u89C1\uFF08\u5DE1\u68C0/\u5BA1\u67E5\u7C7B\u4EFB\u52A1\uFF0C\u975E\u8BC4\u5BA1\u4EFB\u52A1\u65F6\uFF09
|
|
3826
|
+
|
|
3827
|
+
> **\u4EC5\u5F53\u672C\u4EFB\u52A1\u662F\u5DE1\u68C0/\u5BA1\u67E5\u7C7B\uFF08\u5982\u81EA\u52A8\u5316\u5B9A\u65F6 PR Review\uFF09\uFF0C\u4E14\u786E\u5B9E\u53D1\u73B0\u503C\u5F97\u63D0\u51FA\u7684\u95EE\u9898\u65F6\u9002\u7528**\uFF1B\u5F00\u53D1/\u5206\u6790/\u95EE\u7B54\u4EFB\u52A1\u8DF3\u8FC7\u672C\u8282\u3002
|
|
3828
|
+
|
|
3829
|
+
- **\u6B63\u786E\u59FF\u52BF**\uFF1A\u7528 \`morula repo mr comment\`\uFF08\u670D\u52A1\u7AEF\u673A\u5668\u534F\u8BAE\uFF0C\u53EA\u5199\u8BC4\u8BBA\uFF1A\u4E0D approve\u3001\u4E0D merge\u3001\u4E0D close\uFF09\uFF0C**\u4E00\u6761 MR \u6700\u591A\u4E00\u6761\u8BC4\u8BBA**\uFF1A
|
|
3830
|
+
\`\`\`bash
|
|
3831
|
+
morula repo mr get <taskId> --mr <iid> --notes # \u5199\u4E4B\u524D\u5148\u8BFB\u5DF2\u6709\u8BA8\u8BBA\uFF08\u5E73\u53F0\u4E0D\u505A\u53BB\u91CD\uFF0C\u91CD\u590D\u7531\u4F60\u907F\u514D\uFF09
|
|
3832
|
+
morula repo mr comment <taskId> --mr <iid> --content-file <path>
|
|
3833
|
+
\`\`\`
|
|
3834
|
+
- \u5185\u5BB9\u4E3A**\u7ED3\u8BBA + \u5173\u952E\u95EE\u9898**\uFF08\u6587\u4EF6:\u884C + \u95EE\u9898 + \u5EFA\u8BAE\uFF09\uFF0C\u4E0D\u8981\u8D34\u5927\u6BB5\u65E5\u5FD7\u6216\u590D\u8FF0 diff\uFF1B\u957F\u5185\u5BB9\u5199\u6210\u62A5\u544A\uFF08\`morula report create\`\uFF09\u540E\u7528\u6458\u8981\u5F15\u7528\uFF1B
|
|
3835
|
+
- \u672B\u5C3E\u5E26\u7F72\u540D\u884C \`> \u7531\u81EA\u52A8\u5316\u300C<\u81EA\u52A8\u5316\u540D\u79F0>\u300D\u4E8E <\u65E5\u671F> \u751F\u6210\`\u2014\u2014\u4F9B\u4EBA\u4E0E agent \u4E0B\u4E00\u8F6E\u8BC6\u522B\u8FD9\u662F\u672C\u81EA\u52A8\u5316\u7684\u5386\u53F2\u8BC4\u8BBA\uFF1B
|
|
3836
|
+
- \u82E5\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u8BC4\u8BBA\u3001\u7ED3\u8BBA\u4E0E\u8FD9\u6B21\u4E00\u81F4 \u2192 **\u4E0D\u8981\u91CD\u590D\u8BC4\u8BBA**\uFF1B
|
|
3837
|
+
- \u26D4 **\u4E0D\u8981\u7528 \`morula review submit\`**\uFF08\u90A3\u662F\u8BC4\u5BA1\u4EFB\u52A1\u7684\u673A\u5668\u534F\u8BAE\uFF0C\u4F1A\u9A71\u52A8\u4E1A\u52A1\u4EFB\u52A1\u72B6\u6001\u56DE\u9000\uFF09\u3002
|
|
3838
|
+
|
|
3620
3839
|
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B
|
|
3621
3840
|
|
|
3622
3841
|
- **\u4F55\u65F6\u53D1\u8BC4\u8BBA**\uFF1A**\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u4EE5\u667A\u80FD\u4F53\u8EAB\u4EFD\u53D1\u9001**\uFF08\u4EFB\u52A1\u5B8C\u6210\u65F6\u81EA\u52A8\u53D1\u5E03\u5230\u4EFB\u52A1\u5BF9\u8C61\uFF0C\u4F5C\u8005=\u5F53\u524D\u667A\u80FD\u4F53\uFF09\u3002**\u4F60\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u5E94\u8C03\u7528 \`morula comment add\`**\u2014\u2014\u53EA\u8981\u5728\u6700\u7EC8\u8F93\u51FA\u91CC\u7ED9\u51FA\u7528\u6237\u5173\u5FC3\u7684\u7ED3\u8BBA\u5373\u53EF\uFF0C\u670D\u52A1\u7AEF\u4F1A\u4F5C\u4E3A\u8BE5\u667A\u80FD\u4F53\u7684\u5B8C\u6210\u8BC4\u8BBA\u53D1\u5E03\u3002chat \u4F1A\u8BDD\u65E0 objectId \u7684\u4EFB\u52A1\uFF08\u7EAF\u5BF9\u8BDD\u6D3E\u53D1\uFF09\u4E0D\u56DE\u8BC4\u8BBA\u2014\u2014\u6700\u7EC8\u4EA4\u4ED8\u7531 daemon \u56DE\u586B assistant \u6D88\u606F\u3002
|
|
@@ -3672,11 +3891,13 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
3672
3891
|
|
|
3673
3892
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
3674
3893
|
|------|------|------|
|
|
3675
|
-
| CLI \u8BFB\u8BC4\u8BBA | \`morula comment list --object <objectId>\` | \`
|
|
3676
|
-
| CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`
|
|
3677
|
-
| CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`
|
|
3894
|
+
| CLI \u8BFB\u8BC4\u8BBA | \`morula comment list --object <objectId>\` | \`packages/morula-cli/src/commands/comment.ts\` |
|
|
3895
|
+
| CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`packages/morula-cli/src/commands/comment.ts\` |
|
|
3896
|
+
| CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`packages/morula-cli/src/commands/task.ts\` |
|
|
3678
3897
|
| daemon \u6CE8\u5165\u51ED\u636E\uFF08agent \u65E0\u9700\u81EA\u884C\u767B\u5F55\uFF09 | runner \u6CE8\u5165 \`env\` \u7ED9 Provider \u5B50\u8FDB\u7A0B | \`packages/runtime/src/daemon/runner.ts\` |
|
|
3679
|
-
| \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u4ED3\u5E93\u6E05\u5355 / \u5DE5\u5177\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u83B7\u53D6 | \`buildTaskPromptWithContract\`\uFF08\`runner.ts\`\uFF0C\u552F\u4E00\u8FDB dim \u7684 messages[0]\uFF09\uFF1BCLI \`
|
|
3898
|
+
| \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u4ED3\u5E93\u6E05\u5355 / \u5DE5\u5177\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u83B7\u53D6 | \`buildTaskPromptWithContract\`\uFF08\`runner.ts\`\uFF0C\u552F\u4E00\u8FDB dim \u7684 messages[0]\uFF09\uFF1BCLI \`packages/morula-cli/src/commands/task.ts\`\uFF08resolveTaskId \u2192 \`GET /agent-tasks/:id\`\uFF09 | \`packages/runtime/src/daemon/runner.ts\` |
|
|
3899
|
+
| \u8FD0\u884C\u4E2D\u68C0\u67E5\u70B9\uFF08\u672C\u56DE\u5408\u5185\u53D6\u7528\u6237\u610F\u89C1\uFF09 | CLI \`morula task inbox [--peek]\` \u2192 \`GET /agent-tasks/:id/thread/inbox\`\uFF08\u673A\u5668\u534F\u8BAE\uFF1B\u9ED8\u8BA4\u53D6\u8D70\u5373\u6D88\u8D39\uFF0C\u843D \`consumed\` + \u5F53\u56DE\u5408\u53F7\uFF09\uFF1B\u5951\u7EA6\u65F6\u673A\u89C1\u672C SKILL 1.3 | \`packages/morula-cli/src/commands/task.ts\`\u3001\`src/modules/agent-task/chat/object-agent-chat.controller.ts\`\u3001\`object-agent-message.service.ts\`\uFF08\`getThreadInbox\`\uFF09 |
|
|
3900
|
+
| \u8FD0\u884C\u4E2D\u6253\u65AD\uFF08\u540C\u4E00\u4F1A\u8BDD\u91CD\u5F00\u4E00\u8F6E\uFF09 | daemon \u5728\u5B89\u5168\u70B9\uFF08\`inFlightTools === 0\`\uFF09\`session/cancel\` \u2192 \`session/resume\` \u2192 \u7528\u65B0\u610F\u89C1\u91CD\u5F00 prompt\uFF1B\u56DE\u5408\u53F7\u4E0E\u4E2D\u65AD\u70B9\u4E0A\u62A5\u4E3A \`turn_started\` / \`turn_interrupted\` \u4E8B\u4EF6 | \`packages/runtime/src/daemon/acp/dim-acp-executor.ts\`\uFF08\u56DE\u5408\u5FAA\u73AF\uFF09\u3001\`runner.ts\`\uFF08\`buildSteerPort\`\uFF09\u3001\`src/modules/agent-task/chat/object-agent-message.service.ts\`\uFF08\`steerDeliveryStrategy\`\uFF09 |
|
|
3680
3901
|
|
|
3681
3902
|
## 3. \u6539\u4EE3\u7801\u9ED8\u8BA4\u4EA7 PR\uFF08\u5206\u652F\u7EAA\u5F8B\uFF0CAgent \u53D6\u540D\uFF09
|
|
3682
3903
|
|
|
@@ -3694,13 +3915,13 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
3694
3915
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
3695
3916
|
|------|------|------|
|
|
3696
3917
|
| merge \u81EA\u52A8\u5173\u5355\uFF08\u670D\u52A1\u7AEF\u9A71\u52A8\u72B6\u6001\uFF09 | webhook merged \u2192 closeIntent \u2192 Object \u5B8C\u6210 | \`src/modules/vcs-integration/webhook/vcs-event.service.ts\` |
|
|
3697
|
-
| 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 | \`
|
|
3918
|
+
| 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 |
|
|
3698
3919
|
|
|
3699
3920
|
## 4.1 \u8BC4\u5BA1\u4EFB\u52A1\u7ED3\u8BBA\u56DE\u4F20\uFF08morula review submit\uFF09
|
|
3700
3921
|
|
|
3701
3922
|
| \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
|
|
3702
3923
|
|------|------|------|
|
|
3703
|
-
| \u8BC4\u5BA1\u7ED3\u8BBA\u673A\u5668\u534F\u8BAE\uFF08approved / changes_requested / commented\uFF0Cchanges_requested \u5FC5\u987B\u5E26 details\uFF09 | \`morula review submit <objectId> --verdict ... [--details] [--pr-number]\` \u2192 \`POST /agent-collaborations/review/submit\`\uFF08\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\uFF09 | \`
|
|
3924
|
+
| \u8BC4\u5BA1\u7ED3\u8BBA\u673A\u5668\u534F\u8BAE\uFF08approved / changes_requested / commented\uFF0Cchanges_requested \u5FC5\u987B\u5E26 details\uFF09 | \`morula review submit <objectId> --verdict ... [--details] [--pr-number]\` \u2192 \`POST /agent-collaborations/review/submit\`\uFF08\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\uFF09 | \`packages/morula-cli/src/commands/review.ts\`\u3001\`src/modules/agent-task/review-loop.service.ts\` |
|
|
3704
3925
|
| \u8BC4\u5BA1\u5BF9\u8C61\uFF08objectId\uFF09\u7ECF AgentTask \u672C\u4F53\u4E0B\u53D1\uFF08\`morula task get\` \u65E0\u53C2\u8FD4\u56DE\uFF09 | AgentTask \u8BB0\u5F55 \`objectId\` \u5B57\u6BB5\uFF08enqueue \u65F6\u5199\u5165\uFF09 | \`src/modules/agent-task/review-loop.service.ts\` |
|
|
3705
3926
|
|
|
3706
3927
|
## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B\uFF08\u4E00\u6761\u6700\u7EC8\u8BC4\u8BBA\uFF0C\u7ED3\u679C\u4E0A\u5E73\u53F0\uFF09
|
|
@@ -3738,46 +3959,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
3738
3959
|
// src/daemon/runner.ts
|
|
3739
3960
|
init_daemon_config();
|
|
3740
3961
|
init_dim_home();
|
|
3741
|
-
|
|
3742
|
-
// src/daemon/dim-locate.ts
|
|
3743
|
-
import { accessSync, constants } from "node:fs";
|
|
3744
|
-
import { homedir as homedir5 } from "node:os";
|
|
3745
|
-
import { join as join7 } from "node:path";
|
|
3746
|
-
function isExecutable(p) {
|
|
3747
|
-
try {
|
|
3748
|
-
accessSync(p, constants.X_OK);
|
|
3749
|
-
return true;
|
|
3750
|
-
} catch {
|
|
3751
|
-
return false;
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
function resolveDimBinary() {
|
|
3755
|
-
const explicit = process.env.MORULA_DIM_PATH?.trim();
|
|
3756
|
-
if (explicit) {
|
|
3757
|
-
return isExecutable(explicit) ? explicit : "dim";
|
|
3758
|
-
}
|
|
3759
|
-
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
3760
|
-
for (const dir of pathDirs) {
|
|
3761
|
-
const candidate = join7(dir, "dim");
|
|
3762
|
-
if (isExecutable(candidate)) {
|
|
3763
|
-
return candidate;
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
const home = homedir5();
|
|
3767
|
-
const knownLocations = [
|
|
3768
|
-
"/Applications/DimAgent.app/Contents/Resources/runtime/cli/dim",
|
|
3769
|
-
"/Applications/Dim.app/Contents/Resources/runtime/cli/dim",
|
|
3770
|
-
join7(home, "Applications", "DimAgent.app", "Contents", "Resources", "runtime", "cli", "dim"),
|
|
3771
|
-
join7(home, ".dimcode", "bin", "dim"),
|
|
3772
|
-
join7(home, ".local", "bin", "dim")
|
|
3773
|
-
];
|
|
3774
|
-
for (const candidate of knownLocations) {
|
|
3775
|
-
if (isExecutable(candidate)) {
|
|
3776
|
-
return candidate;
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
return "dim";
|
|
3780
|
-
}
|
|
3962
|
+
init_dim_locate();
|
|
3781
3963
|
|
|
3782
3964
|
// src/daemon/flags.ts
|
|
3783
3965
|
function isFlagEnabled(name, defaultValue = true) {
|
|
@@ -3792,19 +3974,33 @@ import { execFileSync as execFileSync4 } from "node:child_process";
|
|
|
3792
3974
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
3793
3975
|
import { join as join8 } from "node:path";
|
|
3794
3976
|
var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
|
|
3977
|
+
function prependToPath(dirs) {
|
|
3978
|
+
const cur = process.env.PATH ?? "";
|
|
3979
|
+
const parts = new Set(cur.split(":").filter(Boolean));
|
|
3980
|
+
const missing = dirs.filter((d) => !parts.has(d));
|
|
3981
|
+
if (missing.length)
|
|
3982
|
+
process.env.PATH = [...missing, cur].join(":");
|
|
3983
|
+
return missing;
|
|
3984
|
+
}
|
|
3795
3985
|
function ensureGitOnPath() {
|
|
3796
3986
|
try {
|
|
3797
3987
|
execFileSync4("git", ["--version"], { stdio: "pipe" });
|
|
3798
3988
|
return;
|
|
3799
3989
|
} catch {
|
|
3800
3990
|
}
|
|
3801
|
-
const
|
|
3802
|
-
const missing = GIT_CANDIDATE_DIRS.filter((d) => existsSync5(join8(d, "git")) && !cur.split(":").includes(d));
|
|
3991
|
+
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync5(join8(d, "git"))));
|
|
3803
3992
|
if (missing.length) {
|
|
3804
|
-
process.env.PATH = [...missing, cur].join(":");
|
|
3805
3993
|
console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
3806
3994
|
}
|
|
3807
3995
|
}
|
|
3996
|
+
function ensureNodeOnPath() {
|
|
3997
|
+
const nodeDir = process.execPath ? join8(process.execPath, "..") : "";
|
|
3998
|
+
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync5(join8(d, "node")));
|
|
3999
|
+
const missing = prependToPath(candidates);
|
|
4000
|
+
if (missing.length) {
|
|
4001
|
+
console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
3808
4004
|
function buildRepoEnv(token, baseUrl) {
|
|
3809
4005
|
if (baseUrl) {
|
|
3810
4006
|
const host = new URL(baseUrl).host;
|
|
@@ -4121,9 +4317,18 @@ function normalizeOptionId(decision) {
|
|
|
4121
4317
|
var DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
4122
4318
|
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4123
4319
|
var DEFAULT_TOOL_IDLE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
4320
|
+
var DEFAULT_MAX_TURNS = 8;
|
|
4321
|
+
var DEFAULT_TASK_WALL_CLOCK_MS = 2 * 60 * 60 * 1e3;
|
|
4322
|
+
var DEFAULT_STEER_SAFETY_MS = 30 * 1e3;
|
|
4124
4323
|
var PROMPT_RPC_TIMEOUT_GRACE_MS = 1e4;
|
|
4125
4324
|
var WATCHDOG_TICK_MS = 15e3;
|
|
4126
4325
|
var KILL_GRACE_MS = 3e3;
|
|
4326
|
+
function resolveSteerSafetyMs(raw = process.env.MORULA_STEER_SAFETY_MS) {
|
|
4327
|
+
if (raw === void 0 || raw === "")
|
|
4328
|
+
return DEFAULT_STEER_SAFETY_MS;
|
|
4329
|
+
const parsed = Number(raw);
|
|
4330
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.trunc(parsed) : DEFAULT_STEER_SAFETY_MS;
|
|
4331
|
+
}
|
|
4127
4332
|
var ACP_INIT_RETRY_DELAYS_MS = [2e3, 8e3];
|
|
4128
4333
|
function isAcpInitRetryableError(error) {
|
|
4129
4334
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
@@ -4218,6 +4423,11 @@ function extractProviderError(text) {
|
|
|
4218
4423
|
}
|
|
4219
4424
|
return null;
|
|
4220
4425
|
}
|
|
4426
|
+
function payloadToolCallId(msg) {
|
|
4427
|
+
const payload = msg.payload ?? {};
|
|
4428
|
+
const raw = payload.toolCall?.id ?? payload.toolCallId ?? payload.id;
|
|
4429
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
4430
|
+
}
|
|
4221
4431
|
function resolveAcpModelId(models2, requested, configOptions) {
|
|
4222
4432
|
if (!requested.trim())
|
|
4223
4433
|
return null;
|
|
@@ -4264,7 +4474,9 @@ var DimAcpExecutor = class {
|
|
|
4264
4474
|
let ownsConnection = false;
|
|
4265
4475
|
let sessionId = "";
|
|
4266
4476
|
let finalText = "";
|
|
4267
|
-
let
|
|
4477
|
+
let turnNo = 1;
|
|
4478
|
+
const inFlightToolCalls = /* @__PURE__ */ new Map();
|
|
4479
|
+
const inFlightTools = () => inFlightToolCalls.size;
|
|
4268
4480
|
let lastActivityAt = Date.now();
|
|
4269
4481
|
let watchdogTimer = null;
|
|
4270
4482
|
let watchdogFired = false;
|
|
@@ -4322,10 +4534,12 @@ var DimAcpExecutor = class {
|
|
|
4322
4534
|
if (idle <= 0)
|
|
4323
4535
|
return;
|
|
4324
4536
|
lastActivityAt = Date.now();
|
|
4325
|
-
|
|
4537
|
+
inFlightToolCalls.clear();
|
|
4326
4538
|
watchdogTimer = setInterval(() => {
|
|
4539
|
+
maybeInterruptAtSafePoint();
|
|
4540
|
+
enforceTaskWallClock();
|
|
4327
4541
|
const toolBudget = Number(process.env.MORULA_DIM_TOOL_IDLE_TIMEOUT_MS) || DEFAULT_TOOL_IDLE_TIMEOUT_MS;
|
|
4328
|
-
const budget = inFlightTools > 0 ? toolBudget : idle;
|
|
4542
|
+
const budget = inFlightTools() > 0 ? toolBudget : idle;
|
|
4329
4543
|
if (Date.now() - lastActivityAt > budget) {
|
|
4330
4544
|
watchdogFired = true;
|
|
4331
4545
|
if (sessionId && conn)
|
|
@@ -4334,7 +4548,8 @@ var DimAcpExecutor = class {
|
|
|
4334
4548
|
finish({
|
|
4335
4549
|
ok: false,
|
|
4336
4550
|
error: `daemon: idle watchdog fired\uFF08${Math.round(budget / 1e3)}s \u65E0\u6D3B\u52A8\uFF09`,
|
|
4337
|
-
sessionId: sessionId || void 0
|
|
4551
|
+
sessionId: sessionId || void 0,
|
|
4552
|
+
turns: turnNo
|
|
4338
4553
|
});
|
|
4339
4554
|
}
|
|
4340
4555
|
}, WATCHDOG_TICK_MS);
|
|
@@ -4343,6 +4558,60 @@ var DimAcpExecutor = class {
|
|
|
4343
4558
|
const markActivity = () => {
|
|
4344
4559
|
lastActivityAt = Date.now();
|
|
4345
4560
|
};
|
|
4561
|
+
let activeSteer = null;
|
|
4562
|
+
let steerWaitingSince = 0;
|
|
4563
|
+
let wallClockFired = false;
|
|
4564
|
+
let interruptedTurns = 0;
|
|
4565
|
+
let turnKind = "initial";
|
|
4566
|
+
const steerSafetyMs = resolveSteerSafetyMs();
|
|
4567
|
+
const taskStartedAt = Date.now();
|
|
4568
|
+
const maxTurns = Math.max(1, Math.trunc(opts.steer?.maxTurns ?? DEFAULT_MAX_TURNS));
|
|
4569
|
+
const taskWallClockMs = opts.steer?.wallClockMs ?? 0;
|
|
4570
|
+
const wallClockExceeded = () => taskWallClockMs > 0 && Date.now() - taskStartedAt > taskWallClockMs;
|
|
4571
|
+
function maybeInterruptAtSafePoint() {
|
|
4572
|
+
if (!opts.steer || activeSteer || !conn || !sessionId)
|
|
4573
|
+
return;
|
|
4574
|
+
const pending = opts.steer.peek();
|
|
4575
|
+
if (!pending) {
|
|
4576
|
+
steerWaitingSince = 0;
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
let waitedMs = 0;
|
|
4580
|
+
if (inFlightTools() > 0) {
|
|
4581
|
+
if (steerWaitingSince === 0)
|
|
4582
|
+
steerWaitingSince = Date.now();
|
|
4583
|
+
waitedMs = Date.now() - steerWaitingSince;
|
|
4584
|
+
if (steerSafetyMs === 0 || waitedMs < steerSafetyMs)
|
|
4585
|
+
return;
|
|
4586
|
+
}
|
|
4587
|
+
activeSteer = pending;
|
|
4588
|
+
steerWaitingSince = 0;
|
|
4589
|
+
opts.steer.onInterrupting({ turnNo, messageId: pending.messageId });
|
|
4590
|
+
process.stderr.write(`[daemon] \u5B89\u5168\u70B9\u6253\u65AD\uFF1Aturn=${turnNo} message=${pending.messageId} inFlightTools=${inFlightTools()} waited=${waitedMs}ms
|
|
4591
|
+
`);
|
|
4592
|
+
conn.cancel(sessionId);
|
|
4593
|
+
}
|
|
4594
|
+
function enforceTaskWallClock() {
|
|
4595
|
+
if (wallClockFired || !wallClockExceeded())
|
|
4596
|
+
return;
|
|
4597
|
+
wallClockFired = true;
|
|
4598
|
+
process.stderr.write(`[daemon] \u4EFB\u52A1\u5899\u949F\u8D85\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u7EC8\u6B62\u672C\u8F6E\u5E76\u8981\u6C42\u4EBA\u4ECB\u5165
|
|
4599
|
+
`);
|
|
4600
|
+
if (sessionId && conn)
|
|
4601
|
+
conn.cancel(sessionId);
|
|
4602
|
+
}
|
|
4603
|
+
function emitAbortedTools() {
|
|
4604
|
+
const aborted = [];
|
|
4605
|
+
for (const [callId, toolName] of inFlightToolCalls) {
|
|
4606
|
+
aborted.push(toolName);
|
|
4607
|
+
opts.onEvent?.({
|
|
4608
|
+
eventType: "tool:completed",
|
|
4609
|
+
payload: { toolCallId: callId, toolName, toolResult: "\uFF08\u8FD0\u884C\u4E2D\u6253\u65AD\uFF1A\u8BE5\u5DE5\u5177\u8C03\u7528\u88AB\u4E2D\u65AD\uFF0C\u65E0\u7ED3\u679C\uFF09", aborted: true }
|
|
4610
|
+
}, finalText);
|
|
4611
|
+
}
|
|
4612
|
+
inFlightToolCalls.clear();
|
|
4613
|
+
return aborted;
|
|
4614
|
+
}
|
|
4346
4615
|
const setupOnce = async () => {
|
|
4347
4616
|
if (pool) {
|
|
4348
4617
|
conn = await pool.acquire();
|
|
@@ -4368,12 +4637,23 @@ var DimAcpExecutor = class {
|
|
|
4368
4637
|
}
|
|
4369
4638
|
});
|
|
4370
4639
|
setup().then(async () => {
|
|
4371
|
-
|
|
4372
|
-
if (
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4640
|
+
const armTurnTimer = () => {
|
|
4641
|
+
if (timer)
|
|
4642
|
+
clearTimeout(timer);
|
|
4643
|
+
timer = setTimeout(() => {
|
|
4644
|
+
if (sessionId && conn)
|
|
4645
|
+
conn.cancel(sessionId);
|
|
4646
|
+
killTree(conn);
|
|
4647
|
+
finish({
|
|
4648
|
+
ok: false,
|
|
4649
|
+
error: `dim \u5355\u56DE\u5408\u8D85\u65F6\uFF08${Math.round(timeout / 1e3)}s\uFF09`,
|
|
4650
|
+
sessionId: sessionId || void 0,
|
|
4651
|
+
turns: turnNo,
|
|
4652
|
+
interrupted: interruptedTurns > 0
|
|
4653
|
+
});
|
|
4654
|
+
}, timeout);
|
|
4655
|
+
};
|
|
4656
|
+
armTurnTimer();
|
|
4377
4657
|
opts.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
4378
4658
|
startWatchdog();
|
|
4379
4659
|
const bridge = new AcpPermissionBridge();
|
|
@@ -4437,56 +4717,142 @@ var DimAcpExecutor = class {
|
|
|
4437
4717
|
pool?.touch();
|
|
4438
4718
|
markActivity();
|
|
4439
4719
|
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
4440
|
-
const
|
|
4441
|
-
|
|
4720
|
+
const reopenSessionAfterInterrupt = async () => {
|
|
4721
|
+
try {
|
|
4722
|
+
await conn.resumeSession({ ...sessionParams, sessionId });
|
|
4723
|
+
return true;
|
|
4724
|
+
} catch (e1) {
|
|
4725
|
+
process.stderr.write(`[daemon] \u6253\u65AD\u540E\u4F1A\u8BDD\u7EED\u5F00\u5931\u8D25\uFF08resume\uFF09\uFF0C\u6539\u8BD5 load: ${errMessage(e1)}
|
|
4726
|
+
`);
|
|
4727
|
+
}
|
|
4728
|
+
try {
|
|
4729
|
+
await conn.loadSession({ ...sessionParams, sessionId });
|
|
4730
|
+
return true;
|
|
4731
|
+
} catch (e2) {
|
|
4732
|
+
process.stderr.write(`[daemon] \u6253\u65AD\u540E\u4F1A\u8BDD\u7EED\u5F00\u5931\u8D25\uFF08load\uFF09\uFF0C\u56DE\u9000\u65B0\u5EFA\u4F1A\u8BDD\uFF08\u4E0A\u4E0B\u6587\u672A\u4FDD\u7559\uFF09: ${errMessage(e2)}
|
|
4733
|
+
`);
|
|
4734
|
+
}
|
|
4735
|
+
const created = await conn.newSession(sessionParams);
|
|
4736
|
+
sessionId = created.sessionId;
|
|
4737
|
+
await conn.setConfigOption(sessionId, "permission", "workspace-write").catch(() => {
|
|
4738
|
+
});
|
|
4739
|
+
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
4740
|
+
return false;
|
|
4741
|
+
};
|
|
4742
|
+
let promptText = opts.userPrompt;
|
|
4743
|
+
let sessionReset = false;
|
|
4744
|
+
for (; ; ) {
|
|
4745
|
+
armTurnTimer();
|
|
4746
|
+
activeSteer = null;
|
|
4747
|
+
opts.steer?.onTurnStarted?.({ turnNo, kind: turnKind });
|
|
4748
|
+
const result = await conn.prompt(sessionId, promptText, (update) => {
|
|
4749
|
+
markActivity();
|
|
4750
|
+
pool?.touch();
|
|
4751
|
+
const { messages } = mapper.map(sessionId, update);
|
|
4752
|
+
for (const msg of messages) {
|
|
4753
|
+
if (msg.eventType === "text:delta") {
|
|
4754
|
+
const delta = typeof msg.payload?.delta === "string" ? msg.payload.delta : "";
|
|
4755
|
+
finalText += delta;
|
|
4756
|
+
opts.onProgress({ stage: "output", summary: delta });
|
|
4757
|
+
opts.onEvent?.(msg, finalText);
|
|
4758
|
+
} else if (msg.eventType === "thinking:delta") {
|
|
4759
|
+
const delta = typeof msg.payload?.delta === "string" ? msg.payload.delta : "";
|
|
4760
|
+
opts.onProgress({ stage: "thinking", summary: delta });
|
|
4761
|
+
opts.onEvent?.(msg, finalText);
|
|
4762
|
+
} else if (msg.eventType === "tool:started") {
|
|
4763
|
+
const callId = payloadToolCallId(msg) ?? `tool-${Date.now()}`;
|
|
4764
|
+
inFlightToolCalls.set(callId, typeof msg.payload?.toolName === "string" ? msg.payload.toolName : "tool");
|
|
4765
|
+
opts.onEvent?.(msg, finalText);
|
|
4766
|
+
opts.onProgress({
|
|
4767
|
+
stage: "tool_call",
|
|
4768
|
+
toolName: typeof msg.payload?.toolName === "string" ? msg.payload.toolName : "tool",
|
|
4769
|
+
toolInput: typeof msg.payload?.toolInput === "object" && msg.payload?.toolInput !== null ? msg.payload.toolInput : void 0,
|
|
4770
|
+
summary: typeof msg.payload?.toolName === "string" ? msg.payload.toolName : "tool"
|
|
4771
|
+
});
|
|
4772
|
+
} else {
|
|
4773
|
+
if (msg.eventType === "tool:completed" || msg.eventType === "tool:failed") {
|
|
4774
|
+
const callId = payloadToolCallId(msg);
|
|
4775
|
+
if (callId)
|
|
4776
|
+
inFlightToolCalls.delete(callId);
|
|
4777
|
+
}
|
|
4778
|
+
opts.onEvent?.(msg, finalText);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
maybeInterruptAtSafePoint();
|
|
4782
|
+
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
4783
|
+
if (settled)
|
|
4784
|
+
return;
|
|
4442
4785
|
pool?.touch();
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4786
|
+
markActivity();
|
|
4787
|
+
const usage2 = result.usage ?? mapper.latestUsage;
|
|
4788
|
+
const cancelled = result.stopReason === "cancelled";
|
|
4789
|
+
const base = { sessionId, turns: turnNo, interrupted: interruptedTurns > 0 };
|
|
4790
|
+
if (wallClockFired || wallClockExceeded()) {
|
|
4791
|
+
const abortedTools = emitAbortedTools();
|
|
4792
|
+
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "wall-clock", abortedTools });
|
|
4793
|
+
killTree(conn);
|
|
4794
|
+
finish({
|
|
4795
|
+
...base,
|
|
4796
|
+
ok: false,
|
|
4797
|
+
error: `\u5DF2\u8FBE\u4EFB\u52A1\u5899\u949F\u4E0A\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u5DF2\u7EC8\u6B62\u672C\u8F6E\uFF0C\u8BF7\u4EBA\u4ECB\u5165`
|
|
4798
|
+
});
|
|
4799
|
+
return;
|
|
4800
|
+
}
|
|
4801
|
+
if (cancelled && activeSteer) {
|
|
4802
|
+
interruptedTurns += 1;
|
|
4803
|
+
const abortedTools = emitAbortedTools();
|
|
4804
|
+
const nextTurn = turnNo + 1;
|
|
4805
|
+
const withinBudget = nextTurn <= maxTurns;
|
|
4806
|
+
opts.steer?.onTurnInterrupted?.({ turnNo, reason: withinBudget ? "steer" : "max-turns", abortedTools });
|
|
4807
|
+
if (!withinBudget) {
|
|
4808
|
+
process.stderr.write(`[daemon] \u5DF2\u8FBE\u6700\u5927\u56DE\u5408\u6570 ${maxTurns}\uFF0C\u5269\u4F59\u6253\u65AD\u8BF7\u6C42\u4EA4\u7531\u4E0B\u4E00\u8F6E
|
|
4809
|
+
`);
|
|
4810
|
+
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
4811
|
+
return;
|
|
4465
4812
|
}
|
|
4813
|
+
turnKind = "after-interrupt";
|
|
4814
|
+
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
4815
|
+
turnNo = nextTurn;
|
|
4816
|
+
sessionReset = !await reopenSessionAfterInterrupt();
|
|
4817
|
+
continue;
|
|
4818
|
+
}
|
|
4819
|
+
if (cancelled) {
|
|
4820
|
+
finish({ ...base, ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88" });
|
|
4821
|
+
return;
|
|
4822
|
+
}
|
|
4823
|
+
if (result.stopReason === "max_tokens") {
|
|
4824
|
+
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "max_tokens", usage: usage2 }), finalText);
|
|
4825
|
+
finish({ ...base, ok: false, error: "dim \u8FBE\u5230 max_tokens \u4E0A\u9650", tokenUsage: usage2 });
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
if (result.stopReason === "refusal") {
|
|
4829
|
+
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "refusal", usage: usage2 }), finalText);
|
|
4830
|
+
const providerError = extractProviderError(finalText);
|
|
4831
|
+
finish({ ...base, ok: false, error: providerError ?? "\u6A21\u578B\u62D2\u7EDD\u6267\u884C", tokenUsage: usage2 });
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
const leftover = activeSteer ?? opts.steer?.peek() ?? null;
|
|
4835
|
+
if (leftover && turnNo + 1 <= maxTurns && !wallClockExceeded()) {
|
|
4836
|
+
const nextTurn = turnNo + 1;
|
|
4837
|
+
turnKind = "appended";
|
|
4838
|
+
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
4839
|
+
turnNo = nextTurn;
|
|
4840
|
+
sessionReset = false;
|
|
4841
|
+
activeSteer = null;
|
|
4842
|
+
process.stderr.write(`[daemon] \u56DE\u5408\u5DF2\u81EA\u7136\u7ED3\u675F\uFF0C\u6253\u65AD\u8BF7\u6C42\u964D\u7EA7\u4E3A\u8FFD\u52A0\u4E00\u8F6E\uFF08turn=${nextTurn}\uFF09
|
|
4843
|
+
`);
|
|
4844
|
+
continue;
|
|
4466
4845
|
}
|
|
4467
|
-
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
4468
|
-
stopWatchdog();
|
|
4469
|
-
pool?.touch();
|
|
4470
|
-
const usage2 = result.usage ?? mapper.latestUsage;
|
|
4471
|
-
if (result.stopReason === "cancelled") {
|
|
4472
|
-
finish({ ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88", sessionId });
|
|
4473
|
-
} else if (result.stopReason === "max_tokens") {
|
|
4474
|
-
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "max_tokens", usage: usage2 }), finalText);
|
|
4475
|
-
finish({ ok: false, error: "dim \u8FBE\u5230 max_tokens \u4E0A\u9650", tokenUsage: usage2, sessionId });
|
|
4476
|
-
} else if (result.stopReason === "refusal") {
|
|
4477
|
-
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "refusal", usage: usage2 }), finalText);
|
|
4478
|
-
const providerError = extractProviderError(finalText);
|
|
4479
|
-
finish({ ok: false, error: providerError ?? "\u6A21\u578B\u62D2\u7EDD\u6267\u884C", tokenUsage: usage2, sessionId });
|
|
4480
|
-
} else {
|
|
4481
4846
|
opts.onEvent?.(mapper.runEndedMessage({ stopReason: result.stopReason, usage: usage2 }), finalText);
|
|
4482
|
-
finish({ ok: true, text: finalText, tokenUsage: usage2
|
|
4847
|
+
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
4848
|
+
return;
|
|
4483
4849
|
}
|
|
4484
4850
|
}).catch((err) => {
|
|
4485
4851
|
const message = err instanceof Error ? err.message : String(err);
|
|
4486
4852
|
if (sessionId && conn && message.includes("\u8D85\u65F6")) {
|
|
4487
4853
|
conn.cancel(sessionId);
|
|
4488
4854
|
}
|
|
4489
|
-
finish({ ok: false, error: message, sessionId: sessionId || void 0 });
|
|
4855
|
+
finish({ ok: false, error: message, sessionId: sessionId || void 0, turns: turnNo, interrupted: interruptedTurns > 0 });
|
|
4490
4856
|
});
|
|
4491
4857
|
});
|
|
4492
4858
|
}
|
|
@@ -4639,6 +5005,8 @@ function createDimEventNormalizer(opts) {
|
|
|
4639
5005
|
type: "tool_call_completed",
|
|
4640
5006
|
callId: String(callId),
|
|
4641
5007
|
toolName: typeof p.toolName === "string" ? p.toolName : void 0,
|
|
5008
|
+
// P3:运行中打断留下的孤儿调用(无真实结果)→ 时间线标中断点,不伪装成正常完成
|
|
5009
|
+
...p.aborted === true ? { aborted: true } : {},
|
|
4642
5010
|
...outputSummary !== void 0 ? { outputSummary } : {}
|
|
4643
5011
|
});
|
|
4644
5012
|
break;
|
|
@@ -4719,6 +5087,16 @@ var DIM_CAPABILITIES = {
|
|
|
4719
5087
|
authentication: "required",
|
|
4720
5088
|
sessionResume: true,
|
|
4721
5089
|
// ACP session/resume + session/load 跨任务续跑(2026-08-12 实测可用)
|
|
5090
|
+
// E4 能力矩阵(features/stage-runtime-live-chat.md §三):
|
|
5091
|
+
// - `checkpointInbox`(P2):合作式检查点已落地(`morula task inbox` + 契约 skill 的四处时机),
|
|
5092
|
+
// agent 在本回合内主动拉取未消费消息并纳入**本回合**;
|
|
5093
|
+
// - `midRunSteer`(P3):安全点打断已落地(等 `inFlightTools === 0` → session/cancel →
|
|
5094
|
+
// session/resume 同会话重开一轮,见 acp/dim-acp-executor.ts 的回合循环);
|
|
5095
|
+
// 破坏性档留一键回退:`MORULA_LIVE_CHAT_STEER=0` 时能力位同步为 false
|
|
5096
|
+
// (前端自动隐藏入口、后端 typed 拒绝),避免「按钮亮着但 daemon 不打断」的假能力。
|
|
5097
|
+
// 能力位只在实现真正可用后置 true —— 前端据此渲染入口,后端据此 typed 拒绝,同源判据。
|
|
5098
|
+
midRunSteer: isFlagEnabled("MORULA_LIVE_CHAT_STEER", true),
|
|
5099
|
+
checkpointInbox: true,
|
|
4722
5100
|
modelSelection: "workspace_config",
|
|
4723
5101
|
systemPromptMode: "native",
|
|
4724
5102
|
skillInjection: "agents_skills",
|
|
@@ -4751,8 +5129,8 @@ var DimAgentAdapter = class {
|
|
|
4751
5129
|
}
|
|
4752
5130
|
}
|
|
4753
5131
|
async probe() {
|
|
4754
|
-
const { existsSync:
|
|
4755
|
-
const installed =
|
|
5132
|
+
const { existsSync: existsSync17 } = await import("node:fs");
|
|
5133
|
+
const installed = existsSync17(this.dimPath) || this.dimPath === "dim";
|
|
4756
5134
|
let authStatus = installed ? "unknown" : "error";
|
|
4757
5135
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
4758
5136
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -4810,6 +5188,7 @@ var DimAgentAdapter = class {
|
|
|
4810
5188
|
return await new Promise((resolve2) => {
|
|
4811
5189
|
const events = [];
|
|
4812
5190
|
let sessionId = null;
|
|
5191
|
+
let reportedSessionId = null;
|
|
4813
5192
|
const normalizer = createDimEventNormalizer({
|
|
4814
5193
|
onEvent: (event) => {
|
|
4815
5194
|
events.push(event);
|
|
@@ -4822,6 +5201,10 @@ var DimAgentAdapter = class {
|
|
|
4822
5201
|
const onEvent = (msg, finalText) => {
|
|
4823
5202
|
if (msg.type === "ready" && typeof msg.sessionId === "string" && msg.sessionId) {
|
|
4824
5203
|
sessionId = msg.sessionId;
|
|
5204
|
+
if (reportedSessionId !== sessionId) {
|
|
5205
|
+
reportedSessionId = sessionId;
|
|
5206
|
+
input.onEvent({ type: "session", providerSessionId: sessionId });
|
|
5207
|
+
}
|
|
4825
5208
|
}
|
|
4826
5209
|
normalizer.handle(msg, finalText);
|
|
4827
5210
|
};
|
|
@@ -4854,7 +5237,9 @@ var DimAgentAdapter = class {
|
|
|
4854
5237
|
}
|
|
4855
5238
|
},
|
|
4856
5239
|
timeoutMs: input.timeoutMs,
|
|
4857
|
-
signal: input.signal
|
|
5240
|
+
signal: input.signal,
|
|
5241
|
+
// P3:运行中打断端口由上层(runner)提供;未提供时执行器保持单回合语义
|
|
5242
|
+
steer: input.steer ?? null
|
|
4858
5243
|
}).then((result) => {
|
|
4859
5244
|
const finalText = finalAssistantSelector(events);
|
|
4860
5245
|
resolve2({
|
|
@@ -4864,7 +5249,9 @@ var DimAgentAdapter = class {
|
|
|
4864
5249
|
usage: result.tokenUsage ? normalizeUsage(result.tokenUsage) : null,
|
|
4865
5250
|
exitCode: result.ok ? 0 : null,
|
|
4866
5251
|
error: result.ok ? null : { code: "DIM_RUN_FAILED", message: result.error ?? "dim \u6267\u884C\u5931\u8D25" },
|
|
4867
|
-
resumeFailed: result.resumeFailed
|
|
5252
|
+
resumeFailed: result.resumeFailed,
|
|
5253
|
+
turns: result.turns,
|
|
5254
|
+
interrupted: result.interrupted
|
|
4868
5255
|
});
|
|
4869
5256
|
}).catch((err) => {
|
|
4870
5257
|
resolve2({
|
|
@@ -4893,6 +5280,8 @@ var FAKE_CAPABILITIES = {
|
|
|
4893
5280
|
approvalMode: "none",
|
|
4894
5281
|
authentication: "none",
|
|
4895
5282
|
sessionResume: false,
|
|
5283
|
+
midRunSteer: false,
|
|
5284
|
+
checkpointInbox: false,
|
|
4896
5285
|
modelSelection: "explicit",
|
|
4897
5286
|
systemPromptMode: "native",
|
|
4898
5287
|
skillInjection: "prompt_only",
|
|
@@ -6091,6 +6480,144 @@ function materializeAdditionalRepo(input) {
|
|
|
6091
6480
|
return input.targetPath;
|
|
6092
6481
|
}
|
|
6093
6482
|
|
|
6483
|
+
// src/daemon/task-messages.ts
|
|
6484
|
+
var TASK_MESSAGE_QUEUE_MAX = 200;
|
|
6485
|
+
function asRecord(value) {
|
|
6486
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6487
|
+
}
|
|
6488
|
+
function asNonEmptyString(value) {
|
|
6489
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
6490
|
+
}
|
|
6491
|
+
function normalizeAgentMessage(value) {
|
|
6492
|
+
const row = asRecord(value);
|
|
6493
|
+
if (!row)
|
|
6494
|
+
return null;
|
|
6495
|
+
const id = asNonEmptyString(row.id);
|
|
6496
|
+
if (!id)
|
|
6497
|
+
return null;
|
|
6498
|
+
const seq = row.seq;
|
|
6499
|
+
if (typeof seq !== "number" || !Number.isFinite(seq))
|
|
6500
|
+
return null;
|
|
6501
|
+
return {
|
|
6502
|
+
id,
|
|
6503
|
+
seq,
|
|
6504
|
+
authorType: typeof row.authorType === "string" ? row.authorType : "unknown",
|
|
6505
|
+
authorPrincipalId: asNonEmptyString(row.authorPrincipalId),
|
|
6506
|
+
authorName: asNonEmptyString(row.authorName),
|
|
6507
|
+
content: typeof row.content === "string" ? row.content : "",
|
|
6508
|
+
mode: typeof row.mode === "string" ? row.mode : "queue",
|
|
6509
|
+
deliveryState: typeof row.deliveryState === "string" ? row.deliveryState : "pending",
|
|
6510
|
+
deliveryError: asNonEmptyString(row.deliveryError),
|
|
6511
|
+
targetTaskId: asNonEmptyString(row.targetTaskId),
|
|
6512
|
+
anchorEventSeq: typeof row.anchorEventSeq === "number" ? row.anchorEventSeq : null,
|
|
6513
|
+
turnNo: typeof row.turnNo === "number" ? row.turnNo : null,
|
|
6514
|
+
createdAt: typeof row.createdAt === "string" ? row.createdAt : ""
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
function parseTaskMessageFrame(value) {
|
|
6518
|
+
const frame = asRecord(value);
|
|
6519
|
+
if (!frame)
|
|
6520
|
+
return null;
|
|
6521
|
+
const taskId = asNonEmptyString(frame.taskId);
|
|
6522
|
+
if (!taskId || !Array.isArray(frame.messages))
|
|
6523
|
+
return null;
|
|
6524
|
+
const messages = frame.messages.map(normalizeAgentMessage).filter((message) => message !== null);
|
|
6525
|
+
if (messages.length === 0)
|
|
6526
|
+
return null;
|
|
6527
|
+
return {
|
|
6528
|
+
type: "agent-task:message",
|
|
6529
|
+
taskId,
|
|
6530
|
+
objectId: asNonEmptyString(frame.objectId),
|
|
6531
|
+
anchorKey: typeof frame.anchorKey === "string" ? frame.anchorKey : "",
|
|
6532
|
+
messages
|
|
6533
|
+
};
|
|
6534
|
+
}
|
|
6535
|
+
function upsertTaskMessages(existing, incoming, max = TASK_MESSAGE_QUEUE_MAX) {
|
|
6536
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6537
|
+
for (const message of existing)
|
|
6538
|
+
byId.set(message.id, message);
|
|
6539
|
+
for (const message of incoming)
|
|
6540
|
+
byId.set(message.id, message);
|
|
6541
|
+
const merged = [...byId.values()].sort((a, b) => a.seq - b.seq || a.id.localeCompare(b.id));
|
|
6542
|
+
const dropped = Math.max(0, merged.length - max);
|
|
6543
|
+
return { messages: dropped > 0 ? merged.slice(dropped) : merged, dropped };
|
|
6544
|
+
}
|
|
6545
|
+
function selectConsumedMessages(messages, taskId) {
|
|
6546
|
+
return messages.filter((message) => message.targetTaskId === taskId);
|
|
6547
|
+
}
|
|
6548
|
+
function selectSteerMessages(messages, taskId) {
|
|
6549
|
+
return messages.filter((message) => message.mode === "steer").filter((message) => message.deliveryState !== "consumed" && message.deliveryState !== "withdrawn").filter((message) => message.targetTaskId === taskId).sort((a, b) => a.seq - b.seq);
|
|
6550
|
+
}
|
|
6551
|
+
function renderMessageList(messages) {
|
|
6552
|
+
return messages.map((message) => `${message.seq}. \u3010${message.authorType === "user" ? "\u4EBA" : message.authorType === "system" ? "\u5E73\u53F0" : "\u8FD0\u884C\u65F6"}\u3011${message.content}`).join("\n\n");
|
|
6553
|
+
}
|
|
6554
|
+
function buildSteerPrompt(input) {
|
|
6555
|
+
const opinions = input.messages.length > 0 ? renderMessageList(input.messages) : "\uFF08\u6253\u65AD\u8BF7\u6C42\u672A\u88AB\u53D6\u5230\uFF0C\u8BF7\u6309\u539F\u59CB\u9700\u6C42\u7EE7\u7EED\uFF09";
|
|
6556
|
+
const resetNote = input.sessionReset ? "\n- \u26A0\uFE0F \u4F1A\u8BDD\u5DF2\u91CD\u5EFA\uFF1A\u4E0A\u4E00\u56DE\u5408\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587**\u672A\u4FDD\u7559**\uFF0C\u8BF7\u4EE5\u672C\u56DE\u5408\u7ED9\u51FA\u7684\u4FE1\u606F\u4E3A\u51C6\uFF08\u5DE5\u4F5C\u533A\u6587\u4EF6\u4E0E git \u72B6\u6001\u4ECD\u5728\uFF09\u3002" : "";
|
|
6557
|
+
const originalPrompt = input.taskPrompt?.trim() ? `
|
|
6558
|
+
|
|
6559
|
+
\u3010\u539F\u59CB\u4EFB\u52A1\u9700\u6C42\uFF08\u672C\u8F6E\u662F\u540C\u4E00\u6B21\u5DE5\u4F5C\u7684\u5EF6\u7EED\uFF09\u3011
|
|
6560
|
+
${input.taskPrompt.trim()}` : "";
|
|
6561
|
+
return `\u3010\u8FD0\u884C\u4E2D\u6253\u65AD \xB7 \u8BF7\u5148\u6309\u8FD9\u4E9B\u610F\u89C1\u8C03\u6574\uFF0C\u518D\u7EE7\u7EED\u3011
|
|
6562
|
+
\u4F60\u6B63\u5728\u6267\u884C\u7B2C ${input.turnNo - 1} \u56DE\u5408\uFF0C\u4EBA\u5728\u8FD0\u884C\u8FC7\u7A0B\u4E2D\u63D0\u51FA\u4E86\u4EE5\u4E0B\u610F\u89C1\uFF08\u6309\u63D0\u51FA\u987A\u5E8F\uFF09\uFF1A
|
|
6563
|
+
|
|
6564
|
+
${opinions}
|
|
6565
|
+
|
|
6566
|
+
\u8981\u6C42\uFF1A
|
|
6567
|
+
1. \u5148\u9010\u6761\u5904\u7406\u4E0A\u8FF0\u610F\u89C1\uFF0C\u518D\u7EE7\u7EED\u5B8C\u6210\u672C\u4EFB\u52A1\uFF1B\u610F\u89C1\u4E0E\u539F\u59CB\u9700\u6C42\u51B2\u7A81\u65F6**\u4EE5\u610F\u89C1\u4E3A\u51C6**\uFF0C\u5E76\u5728\u6700\u7EC8\u603B\u7ED3\u91CC\u8BF4\u660E\u5DEE\u5F02\u3002
|
|
6568
|
+
2. \u4E0D\u8981\u63A8\u7FFB\u4E0A\u8FF0\u610F\u89C1\u672A\u6D89\u53CA\u7684\u90E8\u5206\uFF1B\u5DF2\u7ECF\u5B8C\u6210\u4E14\u4E0D\u5728\u610F\u89C1\u8303\u56F4\u5185\u7684\u6539\u52A8\u4FDD\u7559\u3002
|
|
6569
|
+
3. \u4E0A\u4E00\u56DE\u5408\u5728\u5B89\u5168\u70B9\u88AB\u4E2D\u65AD\uFF0C\u5176\u4E2D\u7684\u5DE5\u5177\u8FDB\u5EA6\u5DF2\u4E22\u5F03\u3001\u88AB\u4E2D\u65AD\u7684\u8C03\u7528**\u6CA1\u6709\u7ED3\u679C**\uFF08\u65F6\u95F4\u7EBF\u4E0A\u6807\u4E3A\u5DF2\u4E2D\u65AD\uFF09\u2014\u2014\u9700\u8981\u65F6\u91CD\u65B0\u6267\u884C\uFF0C\u4E0D\u8981\u5047\u5B9A\u5B83\u5DF2\u6210\u529F\u3002${resetNote}${originalPrompt}`;
|
|
6570
|
+
}
|
|
6571
|
+
|
|
6572
|
+
// src/daemon/stage-contracts.ts
|
|
6573
|
+
var STAGE_CONTRACTS = {
|
|
6574
|
+
todo: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F85\u5206\u6790\uFF08\u9700\u6C42\u5206\u6790\uFF09
|
|
6575
|
+
|
|
6576
|
+
\u53EA\u505A\u9700\u6C42\u5206\u6790\uFF0C\u4E0D\u5199\u4EE3\u7801\uFF1A\u5224\u65AD\u9700\u6C42\u662F\u5426\u6E05\u6670\u53EF\u5F00\u53D1\uFF0C\u4EA7\u51FA\u53EF\u6267\u884C\u7684\u65B9\u6848\u3001\u8303\u56F4\u4E0E\u98CE\u9669\u3001\u9A8C\u6536\u70B9\uFF0C\u4E3A\u5F00\u53D1\u63D0\u4F9B\u8F93\u5165\u3002\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u63D0 PR\u3002
|
|
6577
|
+
|
|
6578
|
+
\u7ED3\u8BBA\u5FC5\u987B\u4EE5\u673A\u5668\u534F\u8BAE\u4E0A\u62A5\uFF08\u547D\u4EE4\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula task --help\`\uFF09\uFF0C\u5426\u5219\u4EFB\u52A1\u4F1A\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\u3002`,
|
|
6579
|
+
in_progress: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F00\u53D1\u5B9E\u73B0
|
|
6580
|
+
|
|
6581
|
+
\u6309\u4EFB\u52A1\u63CF\u8FF0\u4E0E\u5206\u6790\u7ED3\u8BBA\u5B8C\u6210\u4EE3\u7801\u6539\u52A8\uFF0C\u672C\u5730\u81EA\u9A8C\u8BC1\uFF08\u6784\u5EFA/\u6D4B\u8BD5\uFF09\u901A\u8FC7\u540E\u63D0\u4EA4 MR\u3002\u53EA\u505A\u6700\u5C0F\u5FC5\u8981\u6539\u52A8\uFF0C\u4E0D\u505A\u65E0\u5173\u91CD\u6784\uFF1B\u4E0D\u5408\u5E76 MR\u3001\u4E0D\u505A\u8BC4\u5BA1\u7ED3\u8BBA\u3002
|
|
6582
|
+
|
|
6583
|
+
\u63D0\u4EA4 MR \u5FC5\u987B\u8D70\u5E73\u53F0\u901A\u9053\uFF08\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula repo --help\`\uFF09\uFF1B\u88AB\u73AF\u5883\u963B\u65AD\u65F6\u5982\u5B9E\u62A5\u544A\u963B\u585E\u539F\u56E0\uFF08PR_BLOCKED + \u539F\u56E0\uFF09\uFF0C\u4E0D\u8981\u5047\u88C5\u5B8C\u6210\u3002`,
|
|
6584
|
+
review: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1ACode Review
|
|
6585
|
+
|
|
6586
|
+
\u53EA\u8BFB\u8BC4\u5BA1\uFF0C\u4E0D\u4FEE\u6539\u4EE3\u7801\uFF1A\u57FA\u4E8E**\u771F\u5B9E diff**\uFF08PR \u5206\u652F\u76F8\u5BF9\u76EE\u6807\u5206\u652F\uFF09\u8BC4\u4F30\u6B63\u786E\u6027 / \u5B89\u5168\u6027 / \u98CE\u683C / \u6D4B\u8BD5\u8986\u76D6 / \u662F\u5426\u7B26\u5408\u539F\u59CB\u9700\u6C42\uFF0C\u7ED9\u51FA\u660E\u786E\u7ED3\u8BBA\u4E0E\u5177\u4F53\u610F\u89C1\u3002\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u5408\u5E76 MR\u3001\u4E0D\u8BC4\u5BA1\u81EA\u5DF1\u5F00\u53D1\u7684\u4EFB\u52A1\u3002
|
|
6587
|
+
|
|
6588
|
+
\u7ED3\u8BBA\u5FC5\u987B\u4EE5\u673A\u5668\u534F\u8BAE\u4E0A\u62A5\uFF08\u547D\u4EE4\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula review --help\`\uFF09\uFF0C\u672A\u4E0A\u62A5\u5219\u4EFB\u52A1\u505C\u5728\u300C\u5F85\u8BC4\u5BA1\u300D\u3002`
|
|
6589
|
+
};
|
|
6590
|
+
var AUTOPILOT_CONTRACT = `## \u672C\u4EFB\u52A1\uFF1A\u81EA\u52A8\u5316\u5DE1\u68C0\uFF08\u7531\u300C\u81EA\u52A8\u5316\u300D\u7684\u5B9A\u65F6 / Webhook / \u624B\u52A8\u89E6\u53D1\uFF09
|
|
6591
|
+
|
|
6592
|
+
\u6309\u4EFB\u52A1\u8BF4\u660E\uFF08runbook\uFF09\u6267\u884C\uFF0C\u4EA7\u51FA**\u62A5\u544A**\uFF1A\`morula report create --title <\u6807\u9898> --content-file <md>\`
|
|
6593
|
+
\uFF08\u7701\u7565 --autopilot \u65F6\u670D\u52A1\u7AEF\u81EA\u52A8\u5F52\u5C5E\u672C\u81EA\u52A8\u5316\uFF1B\u957F\u5185\u5BB9\u4E00\u5F8B\u7528 --content-file\uFF09\u3002
|
|
6594
|
+
|
|
6595
|
+
- \u53EA\u8BFB\u4E3A\u4E3B\uFF1A\u4E0D\u6539\u4E1A\u52A1\u6570\u636E\u3001\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u63D0 PR\uFF08\u9664\u975E\u4EFB\u52A1\u8BF4\u660E\u660E\u786E\u8981\u6C42\uFF09\u3002
|
|
6596
|
+
- \u7ED3\u8BBA\u5199\u8FDB\u6700\u7EC8\u8F93\u51FA\uFF0C\u5E76\u540C\u6B65\u843D\u4E00\u4EFD\u62A5\u544A\uFF1B\u62A5\u544A\u91CC\u7ED9\u300C\u6587\u4EF6:\u884C + \u4E25\u91CD\u5EA6 + \u5EFA\u8BAE\u300D\u8FD9\u7C7B\u53EF\u6267\u884C\u4FE1\u606F\u3002
|
|
6597
|
+
- \u4E0D\u8981\u8C03\u7528 \`morula task state\` / \`morula task analysis-verdict\` / \`morula review submit\`\uFF1A
|
|
6598
|
+
\u672C\u4EFB\u52A1\u6CA1\u6709\u5173\u8054\u4E1A\u52A1\u4EFB\u52A1\uFF0C\u8FD9\u4E9B\u8C03\u7528\u4F1A\u88AB\u5E73\u53F0\u62D2\u7EDD\u3002
|
|
6599
|
+
- \u9700\u8981\u5728 MR \u4E0A\u7559\u8BC4\u5BA1\u610F\u89C1\u65F6\u7528 \`morula repo mr comment\`\uFF08\u53EA\u5199\u8BC4\u8BBA\uFF0C\u4E0D approve\u3001\u4E0D merge\uFF09\u3002
|
|
6600
|
+
**\u5199\u4E4B\u524D\u5FC5\u987B\u5148 \`morula repo mr get <taskId> --mr <iid> --notes\` \u8BFB\u5DF2\u6709\u8BA8\u8BBA**\uFF1A
|
|
6601
|
+
\u5982\u679C\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u8BC4\u8BBA\u3001\u7ED3\u8BBA\u4E0E\u8FD9\u6B21\u4E00\u81F4\uFF0C\u5C31\u4E0D\u8981\u518D\u8BC4\u8BBA\uFF08\u5E73\u53F0\u4E0D\u505A\u53BB\u91CD\uFF0C\u91CD\u590D\u7531\u4F60\u907F\u514D\uFF09\uFF1B
|
|
6602
|
+
\u53EA\u6709\u51FA\u73B0\u65B0\u95EE\u9898\u6216\u7ED3\u8BBA\u53D8\u5316\u65F6\u624D\u5199\u65B0\u8BC4\u8BBA\u3002\u8BC4\u8BBA\u672B\u5C3E\u5E26\u7F72\u540D\u884C \`> \u7531\u81EA\u52A8\u5316\u300C<\u540D\u79F0>\u300D\u4E8E <\u65E5\u671F> \u751F\u6210\`\uFF0C
|
|
6603
|
+
\u4F9B\u4F60\u81EA\u5DF1\u5728\u4E0B\u4E00\u8F6E\u8BC6\u522B\u5386\u53F2\u8BC4\u8BBA\u3002
|
|
6604
|
+
|
|
6605
|
+
## \u80FD\u529B\u5165\u53E3\uFF08\u6267\u884C\u65B9\u5F0F\u7531\u5E73\u53F0\u8D1F\u8D23\uFF0C\u4EFB\u52A1\u8BF4\u660E\u91CC\u4E0D\u4F1A\u5199\u8FD9\u4E9B\uFF09
|
|
6606
|
+
|
|
6607
|
+
- \u672C\u4EFB\u52A1\u4E0E\u5E73\u53F0\u4EA4\u4E92**\u53EA\u80FD**\u901A\u8FC7 \`morula\` CLI\uFF1A\u672C\u4EFB\u52A1**\u6CA1\u6709**\u5E73\u53F0\u5DE5\u5177 / MCP \u8C03\u7528\u9762
|
|
6608
|
+
\uFF08\`create_object\`\u3001\`create_report\` \u8FD9\u7C7B\u5DE5\u5177\u5728\u672C\u4EFB\u52A1\u5185\u4E0D\u53EF\u7528\uFF09\uFF1B\u547D\u4EE4\u53C2\u6570\u4EE5 \`morula <resource> --help\` \u4E3A\u51C6\u3002
|
|
6609
|
+
- \u4ED3\u5E93\u8BFB\u53D6\u4E0E\u6539\u52A8\u3001\u5DE5\u4F5C\u533A\u5E03\u5C40\u3001\u5206\u652F\u4E0E MR \u89C4\u5219\u89C1\u5DF2\u6CE8\u5165\u7684\u534F\u4F5C\u5951\u7EA6 skill\uFF08\xA70.5 \u4ED3\u5E93\u4E0E\u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002
|
|
6610
|
+
- \u8981\u7ED9\u4EBA\u751F\u6210\u5F85\u8DDF\u8FDB\u7684\u4EFB\u52A1\uFF1A\`morula task create --space <spaceId> --title <\u6807\u9898> --description <\u5185\u5BB9>\`
|
|
6611
|
+
\uFF08\`<spaceId>\` \u53D6 \`morula task get\` \u8FD4\u56DE\u7684 \`contextSnapshot\`\uFF09\uFF1B\u65B0\u5EFA\u4EFB\u52A1\u9ED8\u8BA4\u843D\u300C\u5F85\u89C4\u5212\u300D\uFF0C
|
|
6612
|
+
\u7531\u4EBA\u786E\u8BA4\u540E\u518D\u6D3E\u53D1\u4FEE\u590D\u3002\u62A5\u544A\u4E0E\u4EFB\u52A1\u662F\u4E24\u4E2A\u57DF\uFF0C\u4E0D\u8981\u628A\u5F85\u529E\u4E8B\u9879\u5199\u8FDB\u62A5\u544A\u6B63\u6587\u5F53\u4EFB\u52A1\u7528\u3002`;
|
|
6613
|
+
function buildStageContract(stageCode, source) {
|
|
6614
|
+
if (source === "autopilot")
|
|
6615
|
+
return AUTOPILOT_CONTRACT;
|
|
6616
|
+
if (!stageCode)
|
|
6617
|
+
return "";
|
|
6618
|
+
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6094
6621
|
// src/daemon/ws-client.ts
|
|
6095
6622
|
import { createHash as createHash2 } from "node:crypto";
|
|
6096
6623
|
import { io } from "socket.io-client";
|
|
@@ -6121,10 +6648,49 @@ var DaemonWsClient = class {
|
|
|
6121
6648
|
authReloads = 0;
|
|
6122
6649
|
reconnectAttempts = 0;
|
|
6123
6650
|
pollOnly = false;
|
|
6651
|
+
/**
|
|
6652
|
+
* P1:已订阅的任务房间(taskId → afterSeq)。**订阅意图独立于连接状态**——
|
|
6653
|
+
* 连接未就绪(初始连接中 / 重连中 / 凭证重载重建 socket)时先登记,连上后由 `joinTaskRooms()` 重放,
|
|
6654
|
+
* 否则「连上之前 claim 到的任务」永远收不到消息(服务端不发 tenant 广播)。
|
|
6655
|
+
*/
|
|
6656
|
+
taskRooms = /* @__PURE__ */ new Map();
|
|
6124
6657
|
/** 是否已降级为 poll-only(认证长期失败;runner 的 poll 兜底继续工作) */
|
|
6125
6658
|
get isPollOnly() {
|
|
6126
6659
|
return this.pollOnly;
|
|
6127
6660
|
}
|
|
6661
|
+
/** 当前已订阅的任务房间(诊断/测试用只读快照) */
|
|
6662
|
+
get subscribedTaskIds() {
|
|
6663
|
+
return [...this.taskRooms.keys()];
|
|
6664
|
+
}
|
|
6665
|
+
/**
|
|
6666
|
+
* P1:订阅 `task:<taskId>` 房间(幂等)——本 run 的对话消息(`agent-task:message`)只发进该房间。
|
|
6667
|
+
*
|
|
6668
|
+
* 连接未就绪时只登记(连上后 `joinTaskRooms()` 补发),保证「先 claim 后连上」也不漏帧。
|
|
6669
|
+
* `afterSeq`:与前端同口径的订阅水位(补齐由 HTTP 拉取负责,见 runner 的 catch-up)。
|
|
6670
|
+
*/
|
|
6671
|
+
subscribeTask(taskId, afterSeq = 0) {
|
|
6672
|
+
this.taskRooms.set(taskId, afterSeq);
|
|
6673
|
+
if (this.socket?.connected)
|
|
6674
|
+
this.socket.emit("subscribe:task", { taskId, afterSeq });
|
|
6675
|
+
}
|
|
6676
|
+
/**
|
|
6677
|
+
* P1:退订 `task:<taskId>` 房间(run 终态调用;未订阅时是 no-op)。
|
|
6678
|
+
* 意图表与 socket 两侧都要清——否则重连重放会把已结束任务的房间重新订上(泄漏:消息队列只增不减)。
|
|
6679
|
+
*/
|
|
6680
|
+
unsubscribeTask(taskId) {
|
|
6681
|
+
if (!this.taskRooms.delete(taskId))
|
|
6682
|
+
return;
|
|
6683
|
+
if (this.socket?.connected)
|
|
6684
|
+
this.socket.emit("unsubscribe:task", { taskId });
|
|
6685
|
+
}
|
|
6686
|
+
/** 把已登记的任务房间订阅重放到当前 socket(首次连接 / 重连 / 换票重建都走这里) */
|
|
6687
|
+
joinTaskRooms() {
|
|
6688
|
+
const socket = this.socket;
|
|
6689
|
+
if (!socket)
|
|
6690
|
+
return;
|
|
6691
|
+
for (const [taskId, afterSeq] of this.taskRooms)
|
|
6692
|
+
socket.emit("subscribe:task", { taskId, afterSeq });
|
|
6693
|
+
}
|
|
6128
6694
|
/** 当前凭证指纹(日志用;不泄露本体) */
|
|
6129
6695
|
get tokenFingerprint() {
|
|
6130
6696
|
return tokenFingerprint(this.currentToken);
|
|
@@ -6162,9 +6728,12 @@ var DaemonWsClient = class {
|
|
|
6162
6728
|
this.reconnectAttempts = 0;
|
|
6163
6729
|
this.authReloads = 0;
|
|
6164
6730
|
console.log(`[daemon] WS \u5DF2\u8FDE\u63A5\uFF08${(/* @__PURE__ */ new Date()).toISOString()}\uFF09`);
|
|
6731
|
+
this.joinTaskRooms();
|
|
6732
|
+
for (const taskId of this.taskRooms.keys())
|
|
6733
|
+
handlers.onTaskResubscribed?.(taskId);
|
|
6165
6734
|
});
|
|
6166
6735
|
socket.on("agent-task:dispatched", (payload) => {
|
|
6167
|
-
handlers.onTaskDispatched(payload.taskId);
|
|
6736
|
+
handlers.onTaskDispatched(payload.taskId, payload.runtimeId);
|
|
6168
6737
|
});
|
|
6169
6738
|
socket.on("agent-task:status-changed", (payload) => {
|
|
6170
6739
|
if (payload?.status === "cancelled" && payload.taskId) {
|
|
@@ -6180,6 +6749,11 @@ var DaemonWsClient = class {
|
|
|
6180
6749
|
void reason;
|
|
6181
6750
|
handlers.onDisconnected();
|
|
6182
6751
|
});
|
|
6752
|
+
socket.on("agent-task:message", (payload) => {
|
|
6753
|
+
const event = parseTaskMessageFrame(payload);
|
|
6754
|
+
if (event)
|
|
6755
|
+
handlers.onTaskMessage(event);
|
|
6756
|
+
});
|
|
6183
6757
|
socket.on("connect_error", (error) => {
|
|
6184
6758
|
this.onConnectError(socket, error);
|
|
6185
6759
|
});
|
|
@@ -6277,30 +6851,6 @@ var DaemonWsClient = class {
|
|
|
6277
6851
|
}
|
|
6278
6852
|
};
|
|
6279
6853
|
|
|
6280
|
-
// src/daemon/stage-contracts.ts
|
|
6281
|
-
var STAGE_CONTRACTS = {
|
|
6282
|
-
todo: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F85\u5206\u6790\uFF08\u9700\u6C42\u5206\u6790\uFF09
|
|
6283
|
-
|
|
6284
|
-
\u53EA\u505A\u9700\u6C42\u5206\u6790\uFF0C\u4E0D\u5199\u4EE3\u7801\uFF1A\u5224\u65AD\u9700\u6C42\u662F\u5426\u6E05\u6670\u53EF\u5F00\u53D1\uFF0C\u4EA7\u51FA\u53EF\u6267\u884C\u7684\u65B9\u6848\u3001\u8303\u56F4\u4E0E\u98CE\u9669\u3001\u9A8C\u6536\u70B9\uFF0C\u4E3A\u5F00\u53D1\u63D0\u4F9B\u8F93\u5165\u3002\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u63D0 PR\u3002
|
|
6285
|
-
|
|
6286
|
-
\u7ED3\u8BBA\u5FC5\u987B\u4EE5\u673A\u5668\u534F\u8BAE\u4E0A\u62A5\uFF08\u547D\u4EE4\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula task --help\`\uFF09\uFF0C\u5426\u5219\u4EFB\u52A1\u4F1A\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\u3002`,
|
|
6287
|
-
in_progress: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F00\u53D1\u5B9E\u73B0
|
|
6288
|
-
|
|
6289
|
-
\u6309\u4EFB\u52A1\u63CF\u8FF0\u4E0E\u5206\u6790\u7ED3\u8BBA\u5B8C\u6210\u4EE3\u7801\u6539\u52A8\uFF0C\u672C\u5730\u81EA\u9A8C\u8BC1\uFF08\u6784\u5EFA/\u6D4B\u8BD5\uFF09\u901A\u8FC7\u540E\u63D0\u4EA4 MR\u3002\u53EA\u505A\u6700\u5C0F\u5FC5\u8981\u6539\u52A8\uFF0C\u4E0D\u505A\u65E0\u5173\u91CD\u6784\uFF1B\u4E0D\u5408\u5E76 MR\u3001\u4E0D\u505A\u8BC4\u5BA1\u7ED3\u8BBA\u3002
|
|
6290
|
-
|
|
6291
|
-
\u63D0\u4EA4 MR \u5FC5\u987B\u8D70\u5E73\u53F0\u901A\u9053\uFF08\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula repo --help\`\uFF09\uFF1B\u88AB\u73AF\u5883\u963B\u65AD\u65F6\u5982\u5B9E\u62A5\u544A\u963B\u585E\u539F\u56E0\uFF08PR_BLOCKED + \u539F\u56E0\uFF09\uFF0C\u4E0D\u8981\u5047\u88C5\u5B8C\u6210\u3002`,
|
|
6292
|
-
review: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1ACode Review
|
|
6293
|
-
|
|
6294
|
-
\u53EA\u8BFB\u8BC4\u5BA1\uFF0C\u4E0D\u4FEE\u6539\u4EE3\u7801\uFF1A\u57FA\u4E8E**\u771F\u5B9E diff**\uFF08PR \u5206\u652F\u76F8\u5BF9\u76EE\u6807\u5206\u652F\uFF09\u8BC4\u4F30\u6B63\u786E\u6027 / \u5B89\u5168\u6027 / \u98CE\u683C / \u6D4B\u8BD5\u8986\u76D6 / \u662F\u5426\u7B26\u5408\u539F\u59CB\u9700\u6C42\uFF0C\u7ED9\u51FA\u660E\u786E\u7ED3\u8BBA\u4E0E\u5177\u4F53\u610F\u89C1\u3002\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u5408\u5E76 MR\u3001\u4E0D\u8BC4\u5BA1\u81EA\u5DF1\u5F00\u53D1\u7684\u4EFB\u52A1\u3002
|
|
6295
|
-
|
|
6296
|
-
\u7ED3\u8BBA\u5FC5\u987B\u4EE5\u673A\u5668\u534F\u8BAE\u4E0A\u62A5\uFF08\u547D\u4EE4\u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \u4E0E \`morula review --help\`\uFF09\uFF0C\u672A\u4E0A\u62A5\u5219\u4EFB\u52A1\u505C\u5728\u300C\u5F85\u8BC4\u5BA1\u300D\u3002`
|
|
6297
|
-
};
|
|
6298
|
-
function buildStageContract(stageCode) {
|
|
6299
|
-
if (!stageCode)
|
|
6300
|
-
return "";
|
|
6301
|
-
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
6302
|
-
}
|
|
6303
|
-
|
|
6304
6854
|
// src/daemon/runner.ts
|
|
6305
6855
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
6306
6856
|
var POLL_INTERVAL_MS = 15e3;
|
|
@@ -6308,6 +6858,30 @@ var TICK_BUDGET_MS = 6e4;
|
|
|
6308
6858
|
var OUTBOX_REPLAY_MAX_FAILURES = 5;
|
|
6309
6859
|
var OUTBOX_REPLAY_PAUSE_MS = 5 * 6e4;
|
|
6310
6860
|
var OUTBOX_MAX_ATTEMPTS_PER_ROUND = 2;
|
|
6861
|
+
var PROGRESS_BATCH_MAX = 500;
|
|
6862
|
+
var OBSERVATION_WARN_INTERVAL_MS = 3e4;
|
|
6863
|
+
var TERMINAL_OBSERVATION_DRAIN_BUDGET_MS = Number(process.env.MORULA_TERMINAL_DRAIN_BUDGET_MS ?? 2e3);
|
|
6864
|
+
async function drainObservationTail(args) {
|
|
6865
|
+
const startedAt = Date.now();
|
|
6866
|
+
const progressDrained = await waitWithin(args.progress, args.budgetMs);
|
|
6867
|
+
const remaining = args.budgetMs - (Date.now() - startedAt);
|
|
6868
|
+
await waitWithin(args.stream, Math.max(0, remaining));
|
|
6869
|
+
return { progressDrained, streamDrained: !args.streamPending() };
|
|
6870
|
+
}
|
|
6871
|
+
async function waitWithin(promise, ms) {
|
|
6872
|
+
if (ms <= 0)
|
|
6873
|
+
return false;
|
|
6874
|
+
let timer;
|
|
6875
|
+
const timeout = new Promise((resolve2) => {
|
|
6876
|
+
timer = setTimeout(resolve2, ms, false);
|
|
6877
|
+
timer.unref?.();
|
|
6878
|
+
});
|
|
6879
|
+
const settled = promise.then(() => true, () => true);
|
|
6880
|
+
const done = await Promise.race([settled, timeout]);
|
|
6881
|
+
if (timer)
|
|
6882
|
+
clearTimeout(timer);
|
|
6883
|
+
return done;
|
|
6884
|
+
}
|
|
6311
6885
|
var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT"];
|
|
6312
6886
|
var DEFAULT_MODEL = "deepseek-v4-flash";
|
|
6313
6887
|
var DaemonRunner = class {
|
|
@@ -6346,6 +6920,16 @@ var DaemonRunner = class {
|
|
|
6346
6920
|
cancels = /* @__PURE__ */ new Map();
|
|
6347
6921
|
/** Phase 2:审批决策等待表(approvalId → resolver;WS 事件 resolve,轮询兜底) */
|
|
6348
6922
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
6923
|
+
/**
|
|
6924
|
+
* P1(features/stage-runtime-live-chat.md §P1-9):本 run 的对话消息内存队列
|
|
6925
|
+
* (taskId → 按 seq 升序的消息)。与 `cancels` / `pendingApprovals` 同款:进程内 per-task 状态,
|
|
6926
|
+
* `handleTask` 收尾清空(跨任务不泄漏)。
|
|
6927
|
+
*
|
|
6928
|
+
* 用途只有两个:① ACP 会话建立瞬间对「本轮吃掉的」消息回执 `consumed`;
|
|
6929
|
+
* ② WS 帧与 HTTP 补齐的**去重合并**(同一条消息两条通道都会来)。
|
|
6930
|
+
* 消息的权威状态与投递都在服务端,这里只是镜像(不做投递决策)。
|
|
6931
|
+
*/
|
|
6932
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
6349
6933
|
dimPath;
|
|
6350
6934
|
model;
|
|
6351
6935
|
/** 独立 dim 数据空间 env 覆盖(DIMCODE_HOME=~/.morula/dimcode;空对象=初始化失败回退共享空间) */
|
|
@@ -6359,6 +6943,7 @@ var DaemonRunner = class {
|
|
|
6359
6943
|
return;
|
|
6360
6944
|
this.running = true;
|
|
6361
6945
|
ensureGitOnPath();
|
|
6946
|
+
ensureNodeOnPath();
|
|
6362
6947
|
{
|
|
6363
6948
|
const cfg2 = loadDaemonConfig(this.store);
|
|
6364
6949
|
try {
|
|
@@ -6439,31 +7024,7 @@ var DaemonRunner = class {
|
|
|
6439
7024
|
this.log(`[daemon] \u5DF2\u6CE8\u518C runtime=${registered.id} daemonId=${cfg.daemonId}`);
|
|
6440
7025
|
this.repoCheckout?.setRuntimeId(registered.id);
|
|
6441
7026
|
this.startHeartbeatTimer();
|
|
6442
|
-
this.ws.connect(
|
|
6443
|
-
onTaskDispatched: (taskId) => {
|
|
6444
|
-
void this.handleTask(taskId);
|
|
6445
|
-
},
|
|
6446
|
-
// M1-A1:取消信号 → 中断执行中的 dim 进程
|
|
6447
|
-
onTaskCancelled: (taskId) => {
|
|
6448
|
-
const ctrl = this.cancels.get(taskId);
|
|
6449
|
-
if (ctrl) {
|
|
6450
|
-
this.log(`[daemon] \u6536\u5230\u53D6\u6D88\u4FE1\u53F7 task=${taskId}\uFF0C\u4E2D\u65AD\u6267\u884C`);
|
|
6451
|
-
ctrl.abort();
|
|
6452
|
-
this.cancels.delete(taskId);
|
|
6453
|
-
}
|
|
6454
|
-
},
|
|
6455
|
-
onDisconnected: () => {
|
|
6456
|
-
},
|
|
6457
|
-
// Phase 2:审批决策 → resolve 等待者(daemon 侧 ACP permission/response)
|
|
6458
|
-
onApprovalDecision: (event) => {
|
|
6459
|
-
const waiter = this.pendingApprovals.get(event.approvalId);
|
|
6460
|
-
if (waiter) {
|
|
6461
|
-
clearTimeout(waiter.timer);
|
|
6462
|
-
this.pendingApprovals.delete(event.approvalId);
|
|
6463
|
-
waiter.resolve(event.decision);
|
|
6464
|
-
}
|
|
6465
|
-
}
|
|
6466
|
-
});
|
|
7027
|
+
this.ws.connect(this.buildWsHandlers());
|
|
6467
7028
|
void this.replayOutbox();
|
|
6468
7029
|
while (this.running) {
|
|
6469
7030
|
await this.tick();
|
|
@@ -6593,6 +7154,7 @@ var DaemonRunner = class {
|
|
|
6593
7154
|
}
|
|
6594
7155
|
} catch {
|
|
6595
7156
|
}
|
|
7157
|
+
void this.catchUpTaskMessages(taskId);
|
|
6596
7158
|
}
|
|
6597
7159
|
void this.replayOutbox();
|
|
6598
7160
|
try {
|
|
@@ -6782,6 +7344,62 @@ var DaemonRunner = class {
|
|
|
6782
7344
|
}, Math.max(0, nextAt - Date.now()));
|
|
6783
7345
|
this.outboxTimer.unref?.();
|
|
6784
7346
|
}
|
|
7347
|
+
/**
|
|
7348
|
+
* WS 下行事件 → runner 逻辑的接线(§6.2 主通道)。
|
|
7349
|
+
*
|
|
7350
|
+
* 抽成方法而不是内联在 `start()` 里:`start()` 需要注册/网络才跑得起来,
|
|
7351
|
+
* 拆开后单测可直接拿到 handlers 驱动事件(不必起 socket、不必真连服务端)。
|
|
7352
|
+
*/
|
|
7353
|
+
buildWsHandlers() {
|
|
7354
|
+
return {
|
|
7355
|
+
onTaskDispatched: (taskId, runtimeId) => {
|
|
7356
|
+
this.onTaskDispatched(taskId, runtimeId);
|
|
7357
|
+
},
|
|
7358
|
+
// M1-A1:取消信号 → 中断执行中的 dim 进程
|
|
7359
|
+
onTaskCancelled: (taskId) => {
|
|
7360
|
+
const ctrl = this.cancels.get(taskId);
|
|
7361
|
+
if (ctrl) {
|
|
7362
|
+
this.log(`[daemon] \u6536\u5230\u53D6\u6D88\u4FE1\u53F7 task=${taskId}\uFF0C\u4E2D\u65AD\u6267\u884C`);
|
|
7363
|
+
ctrl.abort();
|
|
7364
|
+
this.cancels.delete(taskId);
|
|
7365
|
+
}
|
|
7366
|
+
},
|
|
7367
|
+
onDisconnected: () => {
|
|
7368
|
+
},
|
|
7369
|
+
// Phase 2:审批决策 → resolve 等待者(daemon 侧 ACP permission/response)
|
|
7370
|
+
onApprovalDecision: (event) => {
|
|
7371
|
+
const waiter = this.pendingApprovals.get(event.approvalId);
|
|
7372
|
+
if (waiter) {
|
|
7373
|
+
clearTimeout(waiter.timer);
|
|
7374
|
+
this.pendingApprovals.delete(event.approvalId);
|
|
7375
|
+
waiter.resolve(event.decision);
|
|
7376
|
+
}
|
|
7377
|
+
},
|
|
7378
|
+
// P1:本 run 的对话消息(房间 task:<taskId>)→ 去重合并进内存队列;
|
|
7379
|
+
// 回执时机是「ACP 会话建立瞬间」(见 runOnce 的 session 分支),不是收到就回执。
|
|
7380
|
+
onTaskMessage: (event) => {
|
|
7381
|
+
this.enqueueTaskMessages(event);
|
|
7382
|
+
},
|
|
7383
|
+
// P1:重连后房间已重放 → 立即按 afterSeq 重新补齐(断连期间的帧不会补发,只能拉)
|
|
7384
|
+
onTaskResubscribed: (taskId) => {
|
|
7385
|
+
void this.catchUpTaskMessages(taskId);
|
|
7386
|
+
}
|
|
7387
|
+
};
|
|
7388
|
+
}
|
|
7389
|
+
/**
|
|
7390
|
+
* 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
|
|
7391
|
+
* 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
|
|
7392
|
+
* 房间),同租户每台 daemon 都会收到,故**必须按 `runtimeId` 自过滤**——否则每台设备都会替每条任务
|
|
7393
|
+
* 去 claim,任一台设备的一次网络抖动都会把归属设备正在跑的任务判失败(`POST /agent-tasks/:id/result`
|
|
7394
|
+
* 不校验任务归属,见同 issue 根因 ③ / ④)。
|
|
7395
|
+
*
|
|
7396
|
+
* `runtimeId` 缺失(旧服务端)→ 保持旧行为不过滤;poll 兜底本就按 runtimeId 查询,不受影响。
|
|
7397
|
+
*/
|
|
7398
|
+
onTaskDispatched(taskId, runtimeId) {
|
|
7399
|
+
if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
|
|
7400
|
+
return;
|
|
7401
|
+
void this.handleTask(taskId);
|
|
7402
|
+
}
|
|
6785
7403
|
/** 任务处理(幂等:已在处理中则跳过) */
|
|
6786
7404
|
async handleTask(taskId) {
|
|
6787
7405
|
if (this.processing.has(taskId))
|
|
@@ -6789,6 +7407,7 @@ var DaemonRunner = class {
|
|
|
6789
7407
|
this.processing.add(taskId);
|
|
6790
7408
|
const ctrl = new AbortController();
|
|
6791
7409
|
this.cancels.set(taskId, ctrl);
|
|
7410
|
+
let claimed = false;
|
|
6792
7411
|
try {
|
|
6793
7412
|
if (!this.runtimeId)
|
|
6794
7413
|
return;
|
|
@@ -6797,6 +7416,9 @@ var DaemonRunner = class {
|
|
|
6797
7416
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u9886\u53D6\u5931\u8D25: ${claim.code}`);
|
|
6798
7417
|
return;
|
|
6799
7418
|
}
|
|
7419
|
+
claimed = true;
|
|
7420
|
+
this.ws.subscribeTask(taskId, 0);
|
|
7421
|
+
void this.catchUpTaskMessages(taskId);
|
|
6800
7422
|
await this.executeTask(taskId, ctrl.signal);
|
|
6801
7423
|
} catch (error) {
|
|
6802
7424
|
if (ctrl.signal.aborted) {
|
|
@@ -6804,15 +7426,158 @@ var DaemonRunner = class {
|
|
|
6804
7426
|
return;
|
|
6805
7427
|
}
|
|
6806
7428
|
const message = error instanceof Error ? error.message : String(error);
|
|
7429
|
+
if (!claimed) {
|
|
7430
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u672A\u53D6\u5F97\u6267\u884C\u6743\uFF0C\u5FFD\u7565\u672C\u6B21\u5F02\u5E38\uFF08\u4E0D\u4E0A\u62A5\u5931\u8D25\uFF09: ${message}`);
|
|
7431
|
+
return;
|
|
7432
|
+
}
|
|
6807
7433
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5904\u7406\u5F02\u5E38: ${message}`);
|
|
6808
7434
|
await this.submitResultReliable(taskId, { ok: false, error: message }).catch(() => {
|
|
6809
7435
|
});
|
|
6810
7436
|
} finally {
|
|
6811
7437
|
this.cancels.delete(taskId);
|
|
6812
7438
|
this.processing.delete(taskId);
|
|
7439
|
+
this.ws.unsubscribeTask(taskId);
|
|
7440
|
+
this.pendingMessages.delete(taskId);
|
|
6813
7441
|
this.api.clearTaskToken(taskId);
|
|
6814
7442
|
}
|
|
6815
7443
|
}
|
|
7444
|
+
/**
|
|
7445
|
+
* P1 消息入队(§P1-9 第 2/3 条):`agent-task:message` 帧与 `GET /chat` 补齐共用同一条合并路径
|
|
7446
|
+
* (合并规则见 `upsertTaskMessages`:按 id 去重、按 seq 升序、超上限丢最旧)。
|
|
7447
|
+
*
|
|
7448
|
+
* 只接受**正在本机处理**的任务:房间退订后仍可能到达迟到帧,别的任务/别的设备的帧也可能因重连重放而来。
|
|
7449
|
+
* 若不设这道闸,队列会出现无人回收的 taskId(收尾只清 `processing` 里的任务)→ 跨任务泄漏。
|
|
7450
|
+
*/
|
|
7451
|
+
enqueueTaskMessages(frame) {
|
|
7452
|
+
if (!this.processing.has(frame.taskId))
|
|
7453
|
+
return;
|
|
7454
|
+
const { messages, dropped } = upsertTaskMessages(this.pendingMessages.get(frame.taskId) ?? [], frame.messages);
|
|
7455
|
+
this.pendingMessages.set(frame.taskId, messages);
|
|
7456
|
+
if (dropped > 0)
|
|
7457
|
+
this.log(`[daemon] \u4EFB\u52A1 ${frame.taskId} \u5BF9\u8BDD\u6D88\u606F\u961F\u5217\u8D85\u4E0A\u9650 ${TASK_MESSAGE_QUEUE_MAX}\uFF0C\u5DF2\u4E22\u5F03\u6700\u65E7 ${dropped} \u6761`);
|
|
7458
|
+
}
|
|
7459
|
+
/**
|
|
7460
|
+
* P1 补齐(§P1-9 第 3 条):订阅后 / 重连重订阅后拉本线程消息,与 WS 帧走同一条去重合并。
|
|
7461
|
+
*
|
|
7462
|
+
* **拉取才是 correctness path**(WS 只是唤醒,断了就不重发):这是「跑通期间收到的消息一条不丢」的保证。
|
|
7463
|
+
* 失败**只记日志**:补齐是兜底能力,不能反过来打断正在跑的 run(旧服务端无此端点 → 404 也走这里)。
|
|
7464
|
+
*/
|
|
7465
|
+
async catchUpTaskMessages(taskId) {
|
|
7466
|
+
try {
|
|
7467
|
+
const chat = await this.api.listTaskChat(taskId, { afterSeq: 0 });
|
|
7468
|
+
const rows = chat?.messages ?? [];
|
|
7469
|
+
if (rows.length === 0)
|
|
7470
|
+
return;
|
|
7471
|
+
this.enqueueTaskMessages({
|
|
7472
|
+
type: "agent-task:message",
|
|
7473
|
+
taskId,
|
|
7474
|
+
objectId: chat?.thread?.objectId ?? null,
|
|
7475
|
+
anchorKey: chat?.thread?.anchorKey ?? "",
|
|
7476
|
+
messages: rows
|
|
7477
|
+
});
|
|
7478
|
+
} catch (error) {
|
|
7479
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5BF9\u8BDD\u6D88\u606F\u8865\u9F50\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
/**
|
|
7483
|
+
* P1/P3 消费回执:对**本轮真正进入执行上下文**的消息回执 `consumed{turnNo}`。
|
|
7484
|
+
*
|
|
7485
|
+
* 判据严格限定 `targetTaskId === taskId`:这类消息才是服务端合并进本轮 prompt 的。
|
|
7486
|
+
* `targetTaskId` 为 null / 别的 run 的消息**仍在服务端排队**,会由投递器合并进后续 follow-up run ——
|
|
7487
|
+
* 对它们回执等于把话丢掉。`delivered` 不回执(服务端创建 follow-up run 时自己置位)。
|
|
7488
|
+
*
|
|
7489
|
+
* `excludeSteer`(P3):首回合**不能**把打断请求算成「已消费」——它没进首回合的 prompt,
|
|
7490
|
+
* 而是要在下一个安全点打断后由 `consumeSteer` 注入下一回合(见 `buildSteerPort`)。
|
|
7491
|
+
*
|
|
7492
|
+
* fire-and-forget + `.catch(log)`:服务端行状态是权威,丢一次回执不影响消息本身,
|
|
7493
|
+
* 但绝不能因此拖慢或打断本轮执行。回执发出后即从内存队列移除(本 run 只消费一次)。
|
|
7494
|
+
*/
|
|
7495
|
+
ackConsumedMessages(taskId, turnNo = 1, options = {}) {
|
|
7496
|
+
const queued = this.pendingMessages.get(taskId);
|
|
7497
|
+
if (!queued || queued.length === 0)
|
|
7498
|
+
return [];
|
|
7499
|
+
const consumed = selectConsumedMessages(queued, taskId).filter((message) => !options.excludeSteer || message.mode !== "steer");
|
|
7500
|
+
if (consumed.length === 0)
|
|
7501
|
+
return [];
|
|
7502
|
+
for (const message of consumed) {
|
|
7503
|
+
void this.api.ackMessage(taskId, message.id, { state: "consumed", turnNo }).catch((error) => {
|
|
7504
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u6D88\u606F\u56DE\u6267\u5931\u8D25 message=${message.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7505
|
+
});
|
|
7506
|
+
}
|
|
7507
|
+
const consumedIds = new Set(consumed.map((message) => message.id));
|
|
7508
|
+
this.pendingMessages.set(taskId, queued.filter((message) => !consumedIds.has(message.id)));
|
|
7509
|
+
return consumed.map((message) => message.id);
|
|
7510
|
+
}
|
|
7511
|
+
/**
|
|
7512
|
+
* P3:本 run 待处理的打断请求(按 seq 升序,取一条用于触发;`consume` 时全部取走)。
|
|
7513
|
+
* 判据见 `selectSteerMessages`(严格要求已投给本 run)。
|
|
7514
|
+
*/
|
|
7515
|
+
pendingSteerMessages(taskId) {
|
|
7516
|
+
return selectSteerMessages(this.pendingMessages.get(taskId) ?? [], taskId);
|
|
7517
|
+
}
|
|
7518
|
+
/**
|
|
7519
|
+
* 取走打断请求并给出下一回合的 prompt(P3)。取走即回执 `consumed{turnNo}`——
|
|
7520
|
+
* 此刻这句话**确实**进了下一回合的 prompt,语义诚实(不是「猜它进去了」)。
|
|
7521
|
+
*/
|
|
7522
|
+
consumeSteer(taskId, detail, turnNo, sessionReset) {
|
|
7523
|
+
const messages = this.pendingSteerMessages(taskId);
|
|
7524
|
+
if (messages.length > 0) {
|
|
7525
|
+
const ids = new Set(messages.map((message) => message.id));
|
|
7526
|
+
this.pendingMessages.set(taskId, (this.pendingMessages.get(taskId) ?? []).filter((message) => !ids.has(message.id)));
|
|
7527
|
+
for (const message of messages) {
|
|
7528
|
+
void this.api.ackMessage(taskId, message.id, { state: "consumed", turnNo }).catch((error) => {
|
|
7529
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u6253\u65AD\u6D88\u606F\u56DE\u6267\u5931\u8D25 message=${message.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7532
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u6253\u65AD\u8BF7\u6C42\u5DF2\u6CE8\u5165\u7B2C ${turnNo} \u56DE\u5408 prompt\uFF08${messages.length} \u6761\uFF09`);
|
|
7533
|
+
} else {
|
|
7534
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u8FDB\u5165\u7B2C ${turnNo} \u56DE\u5408\uFF0C\u4F46\u6253\u65AD\u8BF7\u6C42\u5DF2\u88AB\u6E05\u7A7A\uFF08\u6309\u539F\u59CB\u9700\u6C42\u7EE7\u7EED\uFF09`);
|
|
7535
|
+
}
|
|
7536
|
+
return buildSteerPrompt({
|
|
7537
|
+
turnNo,
|
|
7538
|
+
messages,
|
|
7539
|
+
taskPrompt: detail.taskPrompt,
|
|
7540
|
+
sessionReset
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* P3:运行中打断端口(provider 侧的「怎么停、下一轮说什么」全从这里取,见 `ProviderSteerPort`)。
|
|
7545
|
+
*
|
|
7546
|
+
* `MORULA_LIVE_CHAT_STEER=0` 时不提供端口 = 退回单回合(steer 消息留在队列里,
|
|
7547
|
+
* run 终态由服务端 D27 按 follow-up 轮次投递)——破坏性档必须有一键回退路径(§八)。
|
|
7548
|
+
*/
|
|
7549
|
+
buildSteerPort(taskId, detail) {
|
|
7550
|
+
if (!isFlagEnabled("MORULA_LIVE_CHAT_STEER", true))
|
|
7551
|
+
return null;
|
|
7552
|
+
return {
|
|
7553
|
+
peek: () => {
|
|
7554
|
+
const [first] = this.pendingSteerMessages(taskId);
|
|
7555
|
+
return first ? { messageId: first.id } : null;
|
|
7556
|
+
},
|
|
7557
|
+
onInterrupting: ({ turnNo, messageId }) => {
|
|
7558
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u6253\u65AD\u4E2D\uFF08\u7B2C ${turnNo} \u56DE\u5408\uFF0C\u5B89\u5168\u70B9\u4E2D\u65AD\uFF09message=${messageId}`);
|
|
7559
|
+
void this.api.ackMessage(taskId, messageId, { state: "interrupting", turnNo }).catch((error) => {
|
|
7560
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u6253\u65AD\u4E2D\u56DE\u6267\u5931\u8D25 message=${messageId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7561
|
+
});
|
|
7562
|
+
},
|
|
7563
|
+
consume: ({ turnNo, sessionReset }) => this.consumeSteer(taskId, detail, turnNo, sessionReset === true),
|
|
7564
|
+
onTurnStarted: ({ turnNo, kind }) => {
|
|
7565
|
+
void this.api.recordEvent(taskId, { type: "turn_started", turnNo, output: { kind } }).catch(() => {
|
|
7566
|
+
});
|
|
7567
|
+
if (kind === "initial")
|
|
7568
|
+
this.ackConsumedMessages(taskId, turnNo, { excludeSteer: true });
|
|
7569
|
+
},
|
|
7570
|
+
onTurnInterrupted: ({ turnNo, reason, abortedTools }) => {
|
|
7571
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u7B2C ${turnNo} \u56DE\u5408\u88AB\u4E2D\u65AD\uFF08${reason}${abortedTools.length > 0 ? `\uFF0C\u5728\u98DE\u5DE5\u5177 ${abortedTools.length} \u4E2A\u5DF2\u6807 aborted` : ""}\uFF09`);
|
|
7572
|
+
void this.api.recordEvent(taskId, {
|
|
7573
|
+
type: "turn_interrupted",
|
|
7574
|
+
turnNo,
|
|
7575
|
+
output: { reason, abortedTools }
|
|
7576
|
+
}).catch(() => {
|
|
7577
|
+
});
|
|
7578
|
+
}
|
|
7579
|
+
};
|
|
7580
|
+
}
|
|
6816
7581
|
async executeTask(taskId, signal) {
|
|
6817
7582
|
const detail = await this.api.getTask(taskId);
|
|
6818
7583
|
this.log(`[daemon] \u5F00\u59CB\u4EFB\u52A1 ${taskId}\uFF08${detail.executor.executorName}\uFF09`);
|
|
@@ -6926,7 +7691,15 @@ var DaemonRunner = class {
|
|
|
6926
7691
|
writeCustomSkills(taskRoot, customSkills, this.log);
|
|
6927
7692
|
this.log(`[daemon] \u5DF2\u6CE8\u5165 ${customSkills.length} \u4E2A\u81EA\u5B9A\u4E49 skill \u5230 .agents/skills/`);
|
|
6928
7693
|
}
|
|
6929
|
-
|
|
7694
|
+
const contractPlatform = resolveContractPlatform(detail.availableRepos, repo, repoBaseUrl, detail.workContext?.baseBranch);
|
|
7695
|
+
writeCollaborationSkill(
|
|
7696
|
+
taskRoot,
|
|
7697
|
+
contractPlatform.repoFullName,
|
|
7698
|
+
contractPlatform.baseUrl,
|
|
7699
|
+
this.log,
|
|
7700
|
+
contractPlatform.baseBranch,
|
|
7701
|
+
contractPlatform.provider
|
|
7702
|
+
);
|
|
6930
7703
|
writeTaskContextBrief(taskRoot, taskId, detail, repo, this.log);
|
|
6931
7704
|
writeProjectAgentsMd(taskRoot, detail.projectAgentsMd, this.log);
|
|
6932
7705
|
return { taskRoot, repoDir, worktreePath, env, repoBaseUrl };
|
|
@@ -7085,6 +7858,20 @@ var DaemonRunner = class {
|
|
|
7085
7858
|
let thoughtBuf = "";
|
|
7086
7859
|
let thoughtTimer = null;
|
|
7087
7860
|
let streamFlushing = false;
|
|
7861
|
+
const observationWarnAt = {};
|
|
7862
|
+
const observationStats = { progressFailed: 0, progressDropped: 0, streamFailed: 0, streamDropped: 0 };
|
|
7863
|
+
let progressDropAll = false;
|
|
7864
|
+
let streamDropAll = false;
|
|
7865
|
+
let progressPending = 0;
|
|
7866
|
+
let streamInFlight = 0;
|
|
7867
|
+
const streamPendingCount = () => streamChunks.length + streamInFlight;
|
|
7868
|
+
const logObservation = (channel, event, reason, total) => {
|
|
7869
|
+
const now = Date.now();
|
|
7870
|
+
if (now - (observationWarnAt[channel] ?? 0) < OBSERVATION_WARN_INTERVAL_MS)
|
|
7871
|
+
return;
|
|
7872
|
+
observationWarnAt[channel] = now;
|
|
7873
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} ${channel} ${event}\uFF08\u7D2F\u8BA1 ${total} \u6761\uFF09\uFF1A${reason}`);
|
|
7874
|
+
};
|
|
7088
7875
|
const progressBuf = [];
|
|
7089
7876
|
let progressTimer = null;
|
|
7090
7877
|
let progressChain = Promise.resolve();
|
|
@@ -7094,13 +7881,28 @@ var DaemonRunner = class {
|
|
|
7094
7881
|
if (batch.length === 0)
|
|
7095
7882
|
return progressChain;
|
|
7096
7883
|
progressChain = progressChain.then(async () => {
|
|
7097
|
-
|
|
7098
|
-
|
|
7884
|
+
if (progressDropAll)
|
|
7885
|
+
return;
|
|
7886
|
+
progressPending += batch.length;
|
|
7887
|
+
for (let offset = 0; offset < batch.length; offset += PROGRESS_BATCH_MAX) {
|
|
7888
|
+
if (progressDropAll)
|
|
7889
|
+
return;
|
|
7890
|
+
const slice = batch.slice(offset, offset + PROGRESS_BATCH_MAX);
|
|
7891
|
+
await this.api.recordProgressBatch(taskId, slice.map((p) => ({
|
|
7099
7892
|
stage: p.stage,
|
|
7100
7893
|
summary: p.summary ? String(redactValue(p.summary)) : void 0,
|
|
7101
7894
|
toolName: p.toolName,
|
|
7102
7895
|
toolInput: p.toolInput !== void 0 ? redactInputSummary(p.toolInput) : void 0
|
|
7103
|
-
}).
|
|
7896
|
+
}))).then((res) => {
|
|
7897
|
+
progressPending -= slice.length;
|
|
7898
|
+
if (res && res.ok === false) {
|
|
7899
|
+
observationStats.progressFailed += slice.length;
|
|
7900
|
+
logObservation("progress", "\u4E0A\u62A5\u672A\u843D\u5E93", String(res.code ?? "unknown"), observationStats.progressFailed);
|
|
7901
|
+
}
|
|
7902
|
+
}).catch((err) => {
|
|
7903
|
+
progressPending -= slice.length;
|
|
7904
|
+
observationStats.progressFailed += slice.length;
|
|
7905
|
+
logObservation("progress", "\u4E0A\u62A5\u5931\u8D25", err?.message ?? String(err), observationStats.progressFailed);
|
|
7104
7906
|
});
|
|
7105
7907
|
}
|
|
7106
7908
|
});
|
|
@@ -7118,9 +7920,23 @@ var DaemonRunner = class {
|
|
|
7118
7920
|
return;
|
|
7119
7921
|
streamFlushing = true;
|
|
7120
7922
|
const batch = streamChunks.splice(0);
|
|
7923
|
+
streamInFlight += batch.length;
|
|
7121
7924
|
try {
|
|
7122
7925
|
for (const c of batch) {
|
|
7123
|
-
|
|
7926
|
+
if (streamDropAll) {
|
|
7927
|
+
streamInFlight -= 1;
|
|
7928
|
+
continue;
|
|
7929
|
+
}
|
|
7930
|
+
await this.api.recordStreamChunk(taskId, c).then((res) => {
|
|
7931
|
+
streamInFlight -= 1;
|
|
7932
|
+
if (res && res.ok === false) {
|
|
7933
|
+
observationStats.streamFailed += 1;
|
|
7934
|
+
logObservation("stream", "\u4E0A\u62A5\u672A\u843D\u5E93", String(res.code ?? "unknown"), observationStats.streamFailed);
|
|
7935
|
+
}
|
|
7936
|
+
}).catch((err) => {
|
|
7937
|
+
streamInFlight -= 1;
|
|
7938
|
+
observationStats.streamFailed += 1;
|
|
7939
|
+
logObservation("stream", "\u4E0A\u62A5\u5931\u8D25", err?.message ?? String(err), observationStats.streamFailed);
|
|
7124
7940
|
});
|
|
7125
7941
|
}
|
|
7126
7942
|
} finally {
|
|
@@ -7137,17 +7953,19 @@ var DaemonRunner = class {
|
|
|
7137
7953
|
void flushStreamChunks(false);
|
|
7138
7954
|
}
|
|
7139
7955
|
};
|
|
7140
|
-
const resumeSessionId = providerKey === "dim" ? detail.lastRun?.dimSessionId ?? null : null;
|
|
7956
|
+
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
7957
|
+
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
7958
|
+
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync12(resumeWorkDir);
|
|
7141
7959
|
const input = {
|
|
7142
7960
|
executionId: `${taskId}:1`,
|
|
7143
7961
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
7144
|
-
cwd:
|
|
7962
|
+
cwd: canResume ? resumeWorkDir : taskRoot,
|
|
7145
7963
|
// LOCAL-AGENT-FIX-003:把协作契约前置到任务上下文(userPrompt = Multica 的 messages[0]);
|
|
7146
7964
|
// dim 是 systemPromptMode:'native',systemPrompt 不进 LLM,契约/al上下文均以 userPrompt 承载。
|
|
7147
7965
|
userPrompt: buildTaskPromptWithContract(detail, repoBaseUrl),
|
|
7148
7966
|
env,
|
|
7149
7967
|
modelId: requestedModelId ?? this.model,
|
|
7150
|
-
resumeSessionId,
|
|
7968
|
+
resumeSessionId: canResume ? resumeSessionId : null,
|
|
7151
7969
|
approvalPolicy: {
|
|
7152
7970
|
version: 1,
|
|
7153
7971
|
readonly: false,
|
|
@@ -7160,6 +7978,8 @@ var DaemonRunner = class {
|
|
|
7160
7978
|
approvalDecider: isFlagEnabled("MORULA_DIM_APPROVAL_MODE", false) && detail.executor.approvalMode === "manual" ? this.createApprovalDecider(taskId, `${taskId}:1`) : void 0,
|
|
7161
7979
|
// M1-A1:取消信号透传(WS cancelled / 轮询兜底 → abort → 进程组中断)
|
|
7162
7980
|
signal,
|
|
7981
|
+
// P3:运行中打断端口(安全点中断 → 同会话续开一轮;能力门控见 §三 E4)
|
|
7982
|
+
steer: this.buildSteerPort(taskId, detail),
|
|
7163
7983
|
onEvent: (event) => {
|
|
7164
7984
|
if (event.type === "progress") {
|
|
7165
7985
|
if (event.summary || event.toolName)
|
|
@@ -7220,6 +8040,15 @@ var DaemonRunner = class {
|
|
|
7220
8040
|
streamChunks.push({ chunkType: "usage", seqHint: nextChunkSeq(), data: event.usage });
|
|
7221
8041
|
void flushStreamChunks(true);
|
|
7222
8042
|
}
|
|
8043
|
+
if (event.type === "session" && event.providerSessionId) {
|
|
8044
|
+
void this.api.reportSessionPointer(taskId, {
|
|
8045
|
+
dimSessionId: event.providerSessionId,
|
|
8046
|
+
// 与传给 provider 的 cwd 同源(session 绑定 cwd,续跑要求一致,见上方 resumeWorkDir 说明)
|
|
8047
|
+
dimWorkDir: canResume ? resumeWorkDir : taskRoot
|
|
8048
|
+
}).catch((error) => {
|
|
8049
|
+
this.log(`[daemon] \u4F1A\u8BDD\u6307\u9488\u56DE\u4F20\u5931\u8D25 task=${taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8050
|
+
});
|
|
8051
|
+
}
|
|
7223
8052
|
}
|
|
7224
8053
|
};
|
|
7225
8054
|
const result = await provider.run(input);
|
|
@@ -7243,10 +8072,24 @@ var DaemonRunner = class {
|
|
|
7243
8072
|
clearTimeout(progressTimer);
|
|
7244
8073
|
progressTimer = null;
|
|
7245
8074
|
}
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
await
|
|
8075
|
+
const progressDrain = flushProgress();
|
|
8076
|
+
const streamDrain = flushStreamChunks(true);
|
|
8077
|
+
const drained = await drainObservationTail({
|
|
8078
|
+
progress: progressDrain,
|
|
8079
|
+
stream: streamDrain,
|
|
8080
|
+
streamPending: () => streamPendingCount() > 0,
|
|
8081
|
+
budgetMs: TERMINAL_OBSERVATION_DRAIN_BUDGET_MS
|
|
7249
8082
|
});
|
|
8083
|
+
if (!drained.progressDrained) {
|
|
8084
|
+
progressDropAll = true;
|
|
8085
|
+
observationStats.progressDropped += progressPending;
|
|
8086
|
+
logObservation("progress", "\u6536\u5C3E\u4E22\u5F03\u6B8B\u4F59", `\u6392\u7A7A\u8D85\u9884\u7B97 ${TERMINAL_OBSERVATION_DRAIN_BUDGET_MS}ms\uFF0C\u6B8B\u4F59\u4E0D\u518D\u53D1\u9001`, observationStats.progressDropped);
|
|
8087
|
+
}
|
|
8088
|
+
if (!drained.streamDrained) {
|
|
8089
|
+
streamDropAll = true;
|
|
8090
|
+
observationStats.streamDropped += streamPendingCount();
|
|
8091
|
+
logObservation("stream", "\u6536\u5C3E\u4E22\u5F03\u6B8B\u4F59", `\u6392\u7A7A\u8D85\u9884\u7B97 ${TERMINAL_OBSERVATION_DRAIN_BUDGET_MS}ms\uFF0C\u6B8B\u4F59\u4E0D\u518D\u53D1\u9001`, observationStats.streamDropped);
|
|
8092
|
+
}
|
|
7250
8093
|
return result;
|
|
7251
8094
|
}
|
|
7252
8095
|
/**
|
|
@@ -7456,16 +8299,44 @@ function writeCustomSkills(targetDir, skills, log) {
|
|
|
7456
8299
|
${skill.content ?? ""}`, "utf8");
|
|
7457
8300
|
}
|
|
7458
8301
|
}
|
|
7459
|
-
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch) {
|
|
8302
|
+
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
7460
8303
|
const dir = join15(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
7461
8304
|
const refsDir = join15(dir, "references");
|
|
7462
8305
|
mkdirSync9(refsDir, { recursive: true });
|
|
7463
|
-
const
|
|
7464
|
-
const
|
|
8306
|
+
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
8307
|
+
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
8308
|
+
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
7465
8309
|
writeFileSync8(join15(dir, "SKILL.md"), `${front}
|
|
7466
8310
|
${body}`, "utf8");
|
|
7467
8311
|
writeFileSync8(join15(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
7468
|
-
log?.(`[daemon] \u5DF2\u6CE8\u5165\u534F\u4F5C\u5951\u7EA6 skill ${COLLABORATION_SKILL_NAME}\uFF08${
|
|
8312
|
+
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/`);
|
|
8313
|
+
}
|
|
8314
|
+
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
8315
|
+
const repos = (availableRepos ?? []).filter((r) => !!r?.repoFullName);
|
|
8316
|
+
const bound = boundRepo ? repos.find((r) => r.repoFullName === boundRepo) ?? null : null;
|
|
8317
|
+
const preferred = bound ?? repos[0] ?? null;
|
|
8318
|
+
if (boundBaseUrl) {
|
|
8319
|
+
return {
|
|
8320
|
+
provider: "gitlab",
|
|
8321
|
+
repoFullName: boundRepo,
|
|
8322
|
+
baseUrl: boundBaseUrl,
|
|
8323
|
+
baseBranch: boundBaseBranch ?? preferred?.defaultBranch ?? void 0
|
|
8324
|
+
};
|
|
8325
|
+
}
|
|
8326
|
+
const gitlabRepo = repos.find((r) => (r.baseUrl ?? "").trim() !== "");
|
|
8327
|
+
if (gitlabRepo) {
|
|
8328
|
+
return {
|
|
8329
|
+
provider: "gitlab",
|
|
8330
|
+
repoFullName: gitlabRepo.repoFullName,
|
|
8331
|
+
baseUrl: gitlabRepo.baseUrl ?? void 0,
|
|
8332
|
+
baseBranch: boundBaseBranch ?? gitlabRepo.defaultBranch ?? void 0
|
|
8333
|
+
};
|
|
8334
|
+
}
|
|
8335
|
+
return {
|
|
8336
|
+
provider: "github",
|
|
8337
|
+
repoFullName: boundRepo ?? preferred?.repoFullName ?? null,
|
|
8338
|
+
baseBranch: boundBaseBranch ?? preferred?.defaultBranch ?? void 0
|
|
8339
|
+
};
|
|
7469
8340
|
}
|
|
7470
8341
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
7471
8342
|
const briefDir = join15(targetDir, ".agent_context");
|
|
@@ -7561,7 +8432,8 @@ ${multiRepoList}
|
|
|
7561
8432
|
- \u4EFB\u52A1\u8BE6\u60C5\uFF08\u4EFB\u52A1\u7F16\u53F7 / \u5173\u8054\u5BF9\u8C61 / \u4E1A\u52A1\u76EE\u6807 / \u8BC4\u8BBA / PR\uFF09\uFF1A\`morula task get\`\uFF08\u65E0\u53C2\uFF0CtaskId \u7531\u6267\u884C\u73AF\u5883\u63D0\u4F9B\uFF09\u3002
|
|
7562
8433
|
|
|
7563
8434
|
`;
|
|
7564
|
-
const
|
|
8435
|
+
const taskSource = detail.contextSnapshot?.source;
|
|
8436
|
+
const stageContract = buildStageContract(detail.stageCode, typeof taskSource === "string" ? taskSource : void 0);
|
|
7565
8437
|
const stageContractBlock = stageContract ? `${stageContract}
|
|
7566
8438
|
|
|
7567
8439
|
` : "";
|
|
@@ -7761,6 +8633,7 @@ import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync
|
|
|
7761
8633
|
import { homedir as homedir8 } from "node:os";
|
|
7762
8634
|
import { dirname as dirname6, join as join16 } from "node:path";
|
|
7763
8635
|
init_daemon_config();
|
|
8636
|
+
init_dim_locate();
|
|
7764
8637
|
async function doctorCommand(context) {
|
|
7765
8638
|
const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
|
|
7766
8639
|
const { facts, checks } = await collectDoctor(context, probe);
|
|
@@ -8278,10 +9151,128 @@ function parseMaxConcurrent(context) {
|
|
|
8278
9151
|
return value;
|
|
8279
9152
|
}
|
|
8280
9153
|
|
|
9154
|
+
// src/daemon/plugin-register.ts
|
|
9155
|
+
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync4, readFileSync as readFileSync11, rmSync as rmSync8, statSync as statSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
9156
|
+
import { homedir as homedir11 } from "node:os";
|
|
9157
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9158
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9159
|
+
var PLUGIN_NAME = "morula-cli";
|
|
9160
|
+
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9161
|
+
function legacyAgentsPluginDir(home = homedir11()) {
|
|
9162
|
+
return join19(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9163
|
+
}
|
|
9164
|
+
function resolveDimcodeHome(env = process.env, home = homedir11()) {
|
|
9165
|
+
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9166
|
+
return fromEnv || join19(home, ".dimcode", "v2");
|
|
9167
|
+
}
|
|
9168
|
+
function defaultPluginsRoot(env = process.env, home = homedir11()) {
|
|
9169
|
+
return join19(resolveDimcodeHome(env, home), "plugins");
|
|
9170
|
+
}
|
|
9171
|
+
function resolvePackagePaths(entryUrl) {
|
|
9172
|
+
const distDir = dirname8(fileURLToPath2(entryUrl));
|
|
9173
|
+
return {
|
|
9174
|
+
packageRoot: dirname8(distDir),
|
|
9175
|
+
distDir,
|
|
9176
|
+
assetsDir: join19(distDir, "plugin")
|
|
9177
|
+
};
|
|
9178
|
+
}
|
|
9179
|
+
function buildLauncherScript(opts) {
|
|
9180
|
+
const entry = join19(opts.packageRoot, "dist", "morula.js");
|
|
9181
|
+
return `#!/bin/sh
|
|
9182
|
+
# Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
|
|
9183
|
+
# \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
|
|
9184
|
+
NODE=${JSON.stringify(opts.nodePath)}
|
|
9185
|
+
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
9186
|
+
if [ -z "$NODE" ]; then
|
|
9187
|
+
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
9188
|
+
exit 127
|
|
9189
|
+
fi
|
|
9190
|
+
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
9191
|
+
`;
|
|
9192
|
+
}
|
|
9193
|
+
function assetsReady(paths) {
|
|
9194
|
+
return existsSync16(join19(paths.assetsDir, "plugin.json")) && existsSync16(join19(paths.assetsDir, "skills"));
|
|
9195
|
+
}
|
|
9196
|
+
function listSkillNames(assetsDir) {
|
|
9197
|
+
const dir = join19(assetsDir, "skills");
|
|
9198
|
+
if (!existsSync16(dir))
|
|
9199
|
+
return [];
|
|
9200
|
+
return readdirSync4(dir).filter((name) => {
|
|
9201
|
+
try {
|
|
9202
|
+
return statSync3(join19(dir, name)).isDirectory();
|
|
9203
|
+
} catch {
|
|
9204
|
+
return false;
|
|
9205
|
+
}
|
|
9206
|
+
}).sort();
|
|
9207
|
+
}
|
|
9208
|
+
function registerPlugin(opts) {
|
|
9209
|
+
const { pluginsRoot, paths, version } = opts;
|
|
9210
|
+
const nodePath = opts.nodePath ?? process.execPath;
|
|
9211
|
+
const pluginDir = join19(pluginsRoot, PLUGIN_NAME);
|
|
9212
|
+
if (!assetsReady(paths))
|
|
9213
|
+
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
9214
|
+
const skillsSrc = join19(paths.assetsDir, "skills");
|
|
9215
|
+
const skills = listSkillNames(paths.assetsDir);
|
|
9216
|
+
const manifestDir = join19(pluginDir, MANIFEST_DIR_NAME);
|
|
9217
|
+
mkdirSync12(manifestDir, { recursive: true });
|
|
9218
|
+
const manifest = JSON.parse(readFileSync11(join19(paths.assetsDir, "plugin.json"), "utf8"));
|
|
9219
|
+
writeFileSync10(join19(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
9220
|
+
`);
|
|
9221
|
+
const skillsDest = join19(pluginDir, "skills");
|
|
9222
|
+
rmSync8(skillsDest, { recursive: true, force: true });
|
|
9223
|
+
mkdirSync12(skillsDest, { recursive: true });
|
|
9224
|
+
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
9225
|
+
const binDir = join19(pluginDir, "bin");
|
|
9226
|
+
mkdirSync12(binDir, { recursive: true });
|
|
9227
|
+
const binPath = join19(binDir, "morula");
|
|
9228
|
+
writeFileSync10(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
9229
|
+
chmodSync3(binPath, 493);
|
|
9230
|
+
const legacy = legacyAgentsPluginDir(opts.home);
|
|
9231
|
+
return {
|
|
9232
|
+
pluginDir,
|
|
9233
|
+
binPath,
|
|
9234
|
+
skills,
|
|
9235
|
+
version,
|
|
9236
|
+
node: nodePath,
|
|
9237
|
+
legacyDir: existsSync16(join19(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
9238
|
+
};
|
|
9239
|
+
}
|
|
9240
|
+
|
|
9241
|
+
// src/commands/setup.ts
|
|
9242
|
+
var KNOWN_FLAGS = ["root", "help"];
|
|
9243
|
+
async function setupCommand(context) {
|
|
9244
|
+
rejectUnknownFlags(context.parsed.flags, KNOWN_FLAGS);
|
|
9245
|
+
if (context.parsed.action && context.parsed.action !== "setup")
|
|
9246
|
+
throw usage("usage: morula-runtime setup [--root <dir>]", { hint: "morula-runtime setup --help" });
|
|
9247
|
+
const rootOverride = flagString(context.parsed.flags, "root");
|
|
9248
|
+
const pluginsRoot = rootOverride ?? defaultPluginsRoot(context.env);
|
|
9249
|
+
const paths = resolvePackagePaths(import.meta.url);
|
|
9250
|
+
let result;
|
|
9251
|
+
try {
|
|
9252
|
+
result = registerPlugin({ pluginsRoot, paths, version: BUILD_VERSION2 });
|
|
9253
|
+
} catch (error) {
|
|
9254
|
+
throw new CliError("ERROR", EXIT.OTHER, error instanceof Error ? error.message : String(error), false, {
|
|
9255
|
+
hint: "\u6E90\u7801\u6A21\u5F0F\u8BF7\u5148 `pnpm --filter @arcships/morula-runtime run build:dist`\uFF1B\u5168\u5C40\u5B89\u88C5\u8BF7\u91CD\u88C5\u8BE5\u5305"
|
|
9256
|
+
});
|
|
9257
|
+
}
|
|
9258
|
+
return {
|
|
9259
|
+
ok: true,
|
|
9260
|
+
pluginRoot: result.pluginDir,
|
|
9261
|
+
version: result.version,
|
|
9262
|
+
binPath: result.binPath,
|
|
9263
|
+
skills: result.skills,
|
|
9264
|
+
node: result.node,
|
|
9265
|
+
legacyRoot: result.legacyDir,
|
|
9266
|
+
...result.legacyDir ? { hint: "\u68C0\u6D4B\u5230\u65E7\u7684\u540C\u540D\u63D2\u4EF6\u76EE\u5F55\uFF0C\u8BF7\u5220\u9664\u4EE5\u514D\u4E24\u4E2A\u63D2\u4EF6\u6839\u540C\u540D\u51B2\u7A81\uFF08Dim \u4F18\u5148\u547D\u4E2D <DIMCODE_HOME>/plugins\uFF09" } : {},
|
|
9267
|
+
note: "Dim \u5728**\u65B0 session** \u624D\u4F1A\u91CD\u65B0\u626B\u63CF\u63D2\u4EF6\u6839\uFF1B\u5DF2\u5F00\u7740\u7684\u4F1A\u8BDD\u4E0D\u4F1A\u52A0\u8F7D\u65B0 skills\u3002"
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
|
|
8281
9271
|
// src/commands/registry-runtime.ts
|
|
8282
9272
|
var COMMANDS = {
|
|
8283
9273
|
auth: authCommand,
|
|
8284
|
-
daemon: daemonCommand
|
|
9274
|
+
daemon: daemonCommand,
|
|
9275
|
+
setup: setupCommand
|
|
8285
9276
|
};
|
|
8286
9277
|
var EXIT_CODES = {
|
|
8287
9278
|
0: "success",
|
|
@@ -8437,6 +9428,28 @@ var SPEC = {
|
|
|
8437
9428
|
notes: [
|
|
8438
9429
|
'exit code 0 = all checks passed (warn is ok); 1 = at least one check failed -> see checks[] to tell "found problems" from "command failed"'
|
|
8439
9430
|
]
|
|
9431
|
+
},
|
|
9432
|
+
{
|
|
9433
|
+
resource: "setup",
|
|
9434
|
+
summary: "materialize the Dim plugin directory (skills + bin launcher) for this package",
|
|
9435
|
+
actions: [
|
|
9436
|
+
{
|
|
9437
|
+
action: "setup",
|
|
9438
|
+
summary: "write <DIMCODE_HOME>/plugins/morula-cli (manifest + skills + bin/morula launcher); offline and idempotent",
|
|
9439
|
+
flags: [
|
|
9440
|
+
{ name: "root", type: "string", valueHint: "<dir>", description: "plugins root to install into (default: $DIMCODE_HOME/plugins or ~/.dimcode/v2/plugins)" }
|
|
9441
|
+
],
|
|
9442
|
+
examples: ["morula-runtime setup", "morula-runtime setup --root /tmp/plugins"],
|
|
9443
|
+
writes: "local",
|
|
9444
|
+
interactive: false,
|
|
9445
|
+
idempotent: true,
|
|
9446
|
+
emits: ["ok", "pluginRoot", "version", "binPath", "skills", "node", "legacyRoot"]
|
|
9447
|
+
}
|
|
9448
|
+
],
|
|
9449
|
+
notes: [
|
|
9450
|
+
"canonical form is the bare `morula-runtime setup`; postinstall runs the same registration automatically",
|
|
9451
|
+
"only writes <root>/morula-cli; never deletes other plugins or the legacy ~/.agents/plugins/morula-cli (reported as legacyRoot)"
|
|
9452
|
+
]
|
|
8440
9453
|
}
|
|
8441
9454
|
]
|
|
8442
9455
|
};
|