@arcships/morula-runtime 0.1.0-alpha.3 → 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 +0 -0
- package/dist/index.js +1014 -176
- package/dist/morula.js +183 -36
- package/dist/plugin/plugin.json +1 -1
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +53 -0
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +8 -6
- package/package.json +1 -1
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]);
|
|
3261
3358
|
}
|
|
3262
|
-
/**
|
|
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
|
+
});
|
|
3373
|
+
}
|
|
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
|
|
@@ -3677,6 +3896,8 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
3677
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
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
|
|
|
@@ -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) {
|
|
@@ -4135,9 +4317,18 @@ function normalizeOptionId(decision) {
|
|
|
4135
4317
|
var DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
4136
4318
|
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4137
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;
|
|
4138
4323
|
var PROMPT_RPC_TIMEOUT_GRACE_MS = 1e4;
|
|
4139
4324
|
var WATCHDOG_TICK_MS = 15e3;
|
|
4140
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
|
+
}
|
|
4141
4332
|
var ACP_INIT_RETRY_DELAYS_MS = [2e3, 8e3];
|
|
4142
4333
|
function isAcpInitRetryableError(error) {
|
|
4143
4334
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
@@ -4232,6 +4423,11 @@ function extractProviderError(text) {
|
|
|
4232
4423
|
}
|
|
4233
4424
|
return null;
|
|
4234
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
|
+
}
|
|
4235
4431
|
function resolveAcpModelId(models2, requested, configOptions) {
|
|
4236
4432
|
if (!requested.trim())
|
|
4237
4433
|
return null;
|
|
@@ -4278,7 +4474,9 @@ var DimAcpExecutor = class {
|
|
|
4278
4474
|
let ownsConnection = false;
|
|
4279
4475
|
let sessionId = "";
|
|
4280
4476
|
let finalText = "";
|
|
4281
|
-
let
|
|
4477
|
+
let turnNo = 1;
|
|
4478
|
+
const inFlightToolCalls = /* @__PURE__ */ new Map();
|
|
4479
|
+
const inFlightTools = () => inFlightToolCalls.size;
|
|
4282
4480
|
let lastActivityAt = Date.now();
|
|
4283
4481
|
let watchdogTimer = null;
|
|
4284
4482
|
let watchdogFired = false;
|
|
@@ -4336,10 +4534,12 @@ var DimAcpExecutor = class {
|
|
|
4336
4534
|
if (idle <= 0)
|
|
4337
4535
|
return;
|
|
4338
4536
|
lastActivityAt = Date.now();
|
|
4339
|
-
|
|
4537
|
+
inFlightToolCalls.clear();
|
|
4340
4538
|
watchdogTimer = setInterval(() => {
|
|
4539
|
+
maybeInterruptAtSafePoint();
|
|
4540
|
+
enforceTaskWallClock();
|
|
4341
4541
|
const toolBudget = Number(process.env.MORULA_DIM_TOOL_IDLE_TIMEOUT_MS) || DEFAULT_TOOL_IDLE_TIMEOUT_MS;
|
|
4342
|
-
const budget = inFlightTools > 0 ? toolBudget : idle;
|
|
4542
|
+
const budget = inFlightTools() > 0 ? toolBudget : idle;
|
|
4343
4543
|
if (Date.now() - lastActivityAt > budget) {
|
|
4344
4544
|
watchdogFired = true;
|
|
4345
4545
|
if (sessionId && conn)
|
|
@@ -4348,7 +4548,8 @@ var DimAcpExecutor = class {
|
|
|
4348
4548
|
finish({
|
|
4349
4549
|
ok: false,
|
|
4350
4550
|
error: `daemon: idle watchdog fired\uFF08${Math.round(budget / 1e3)}s \u65E0\u6D3B\u52A8\uFF09`,
|
|
4351
|
-
sessionId: sessionId || void 0
|
|
4551
|
+
sessionId: sessionId || void 0,
|
|
4552
|
+
turns: turnNo
|
|
4352
4553
|
});
|
|
4353
4554
|
}
|
|
4354
4555
|
}, WATCHDOG_TICK_MS);
|
|
@@ -4357,6 +4558,60 @@ var DimAcpExecutor = class {
|
|
|
4357
4558
|
const markActivity = () => {
|
|
4358
4559
|
lastActivityAt = Date.now();
|
|
4359
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
|
+
}
|
|
4360
4615
|
const setupOnce = async () => {
|
|
4361
4616
|
if (pool) {
|
|
4362
4617
|
conn = await pool.acquire();
|
|
@@ -4382,12 +4637,23 @@ var DimAcpExecutor = class {
|
|
|
4382
4637
|
}
|
|
4383
4638
|
});
|
|
4384
4639
|
setup().then(async () => {
|
|
4385
|
-
|
|
4386
|
-
if (
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
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();
|
|
4391
4657
|
opts.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
4392
4658
|
startWatchdog();
|
|
4393
4659
|
const bridge = new AcpPermissionBridge();
|
|
@@ -4451,56 +4717,142 @@ var DimAcpExecutor = class {
|
|
|
4451
4717
|
pool?.touch();
|
|
4452
4718
|
markActivity();
|
|
4453
4719
|
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
4454
|
-
const
|
|
4455
|
-
|
|
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;
|
|
4456
4785
|
pool?.touch();
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
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;
|
|
4479
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;
|
|
4480
4845
|
}
|
|
4481
|
-
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
4482
|
-
stopWatchdog();
|
|
4483
|
-
pool?.touch();
|
|
4484
|
-
const usage2 = result.usage ?? mapper.latestUsage;
|
|
4485
|
-
if (result.stopReason === "cancelled") {
|
|
4486
|
-
finish({ ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88", sessionId });
|
|
4487
|
-
} else if (result.stopReason === "max_tokens") {
|
|
4488
|
-
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "max_tokens", usage: usage2 }), finalText);
|
|
4489
|
-
finish({ ok: false, error: "dim \u8FBE\u5230 max_tokens \u4E0A\u9650", tokenUsage: usage2, sessionId });
|
|
4490
|
-
} else if (result.stopReason === "refusal") {
|
|
4491
|
-
opts.onEvent?.(mapper.runEndedMessage({ stopReason: "refusal", usage: usage2 }), finalText);
|
|
4492
|
-
const providerError = extractProviderError(finalText);
|
|
4493
|
-
finish({ ok: false, error: providerError ?? "\u6A21\u578B\u62D2\u7EDD\u6267\u884C", tokenUsage: usage2, sessionId });
|
|
4494
|
-
} else {
|
|
4495
4846
|
opts.onEvent?.(mapper.runEndedMessage({ stopReason: result.stopReason, usage: usage2 }), finalText);
|
|
4496
|
-
finish({ ok: true, text: finalText, tokenUsage: usage2
|
|
4847
|
+
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
4848
|
+
return;
|
|
4497
4849
|
}
|
|
4498
4850
|
}).catch((err) => {
|
|
4499
4851
|
const message = err instanceof Error ? err.message : String(err);
|
|
4500
4852
|
if (sessionId && conn && message.includes("\u8D85\u65F6")) {
|
|
4501
4853
|
conn.cancel(sessionId);
|
|
4502
4854
|
}
|
|
4503
|
-
finish({ ok: false, error: message, sessionId: sessionId || void 0 });
|
|
4855
|
+
finish({ ok: false, error: message, sessionId: sessionId || void 0, turns: turnNo, interrupted: interruptedTurns > 0 });
|
|
4504
4856
|
});
|
|
4505
4857
|
});
|
|
4506
4858
|
}
|
|
@@ -4653,6 +5005,8 @@ function createDimEventNormalizer(opts) {
|
|
|
4653
5005
|
type: "tool_call_completed",
|
|
4654
5006
|
callId: String(callId),
|
|
4655
5007
|
toolName: typeof p.toolName === "string" ? p.toolName : void 0,
|
|
5008
|
+
// P3:运行中打断留下的孤儿调用(无真实结果)→ 时间线标中断点,不伪装成正常完成
|
|
5009
|
+
...p.aborted === true ? { aborted: true } : {},
|
|
4656
5010
|
...outputSummary !== void 0 ? { outputSummary } : {}
|
|
4657
5011
|
});
|
|
4658
5012
|
break;
|
|
@@ -4733,6 +5087,16 @@ var DIM_CAPABILITIES = {
|
|
|
4733
5087
|
authentication: "required",
|
|
4734
5088
|
sessionResume: true,
|
|
4735
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,
|
|
4736
5100
|
modelSelection: "workspace_config",
|
|
4737
5101
|
systemPromptMode: "native",
|
|
4738
5102
|
skillInjection: "agents_skills",
|
|
@@ -4824,6 +5188,7 @@ var DimAgentAdapter = class {
|
|
|
4824
5188
|
return await new Promise((resolve2) => {
|
|
4825
5189
|
const events = [];
|
|
4826
5190
|
let sessionId = null;
|
|
5191
|
+
let reportedSessionId = null;
|
|
4827
5192
|
const normalizer = createDimEventNormalizer({
|
|
4828
5193
|
onEvent: (event) => {
|
|
4829
5194
|
events.push(event);
|
|
@@ -4836,6 +5201,10 @@ var DimAgentAdapter = class {
|
|
|
4836
5201
|
const onEvent = (msg, finalText) => {
|
|
4837
5202
|
if (msg.type === "ready" && typeof msg.sessionId === "string" && msg.sessionId) {
|
|
4838
5203
|
sessionId = msg.sessionId;
|
|
5204
|
+
if (reportedSessionId !== sessionId) {
|
|
5205
|
+
reportedSessionId = sessionId;
|
|
5206
|
+
input.onEvent({ type: "session", providerSessionId: sessionId });
|
|
5207
|
+
}
|
|
4839
5208
|
}
|
|
4840
5209
|
normalizer.handle(msg, finalText);
|
|
4841
5210
|
};
|
|
@@ -4868,7 +5237,9 @@ var DimAgentAdapter = class {
|
|
|
4868
5237
|
}
|
|
4869
5238
|
},
|
|
4870
5239
|
timeoutMs: input.timeoutMs,
|
|
4871
|
-
signal: input.signal
|
|
5240
|
+
signal: input.signal,
|
|
5241
|
+
// P3:运行中打断端口由上层(runner)提供;未提供时执行器保持单回合语义
|
|
5242
|
+
steer: input.steer ?? null
|
|
4872
5243
|
}).then((result) => {
|
|
4873
5244
|
const finalText = finalAssistantSelector(events);
|
|
4874
5245
|
resolve2({
|
|
@@ -4878,7 +5249,9 @@ var DimAgentAdapter = class {
|
|
|
4878
5249
|
usage: result.tokenUsage ? normalizeUsage(result.tokenUsage) : null,
|
|
4879
5250
|
exitCode: result.ok ? 0 : null,
|
|
4880
5251
|
error: result.ok ? null : { code: "DIM_RUN_FAILED", message: result.error ?? "dim \u6267\u884C\u5931\u8D25" },
|
|
4881
|
-
resumeFailed: result.resumeFailed
|
|
5252
|
+
resumeFailed: result.resumeFailed,
|
|
5253
|
+
turns: result.turns,
|
|
5254
|
+
interrupted: result.interrupted
|
|
4882
5255
|
});
|
|
4883
5256
|
}).catch((err) => {
|
|
4884
5257
|
resolve2({
|
|
@@ -4907,6 +5280,8 @@ var FAKE_CAPABILITIES = {
|
|
|
4907
5280
|
approvalMode: "none",
|
|
4908
5281
|
authentication: "none",
|
|
4909
5282
|
sessionResume: false,
|
|
5283
|
+
midRunSteer: false,
|
|
5284
|
+
checkpointInbox: false,
|
|
4910
5285
|
modelSelection: "explicit",
|
|
4911
5286
|
systemPromptMode: "native",
|
|
4912
5287
|
skillInjection: "prompt_only",
|
|
@@ -6105,6 +6480,144 @@ function materializeAdditionalRepo(input) {
|
|
|
6105
6480
|
return input.targetPath;
|
|
6106
6481
|
}
|
|
6107
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
|
+
|
|
6108
6621
|
// src/daemon/ws-client.ts
|
|
6109
6622
|
import { createHash as createHash2 } from "node:crypto";
|
|
6110
6623
|
import { io } from "socket.io-client";
|
|
@@ -6135,10 +6648,49 @@ var DaemonWsClient = class {
|
|
|
6135
6648
|
authReloads = 0;
|
|
6136
6649
|
reconnectAttempts = 0;
|
|
6137
6650
|
pollOnly = false;
|
|
6651
|
+
/**
|
|
6652
|
+
* P1:已订阅的任务房间(taskId → afterSeq)。**订阅意图独立于连接状态**——
|
|
6653
|
+
* 连接未就绪(初始连接中 / 重连中 / 凭证重载重建 socket)时先登记,连上后由 `joinTaskRooms()` 重放,
|
|
6654
|
+
* 否则「连上之前 claim 到的任务」永远收不到消息(服务端不发 tenant 广播)。
|
|
6655
|
+
*/
|
|
6656
|
+
taskRooms = /* @__PURE__ */ new Map();
|
|
6138
6657
|
/** 是否已降级为 poll-only(认证长期失败;runner 的 poll 兜底继续工作) */
|
|
6139
6658
|
get isPollOnly() {
|
|
6140
6659
|
return this.pollOnly;
|
|
6141
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
|
+
}
|
|
6142
6694
|
/** 当前凭证指纹(日志用;不泄露本体) */
|
|
6143
6695
|
get tokenFingerprint() {
|
|
6144
6696
|
return tokenFingerprint(this.currentToken);
|
|
@@ -6176,6 +6728,9 @@ var DaemonWsClient = class {
|
|
|
6176
6728
|
this.reconnectAttempts = 0;
|
|
6177
6729
|
this.authReloads = 0;
|
|
6178
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);
|
|
6179
6734
|
});
|
|
6180
6735
|
socket.on("agent-task:dispatched", (payload) => {
|
|
6181
6736
|
handlers.onTaskDispatched(payload.taskId, payload.runtimeId);
|
|
@@ -6194,6 +6749,11 @@ var DaemonWsClient = class {
|
|
|
6194
6749
|
void reason;
|
|
6195
6750
|
handlers.onDisconnected();
|
|
6196
6751
|
});
|
|
6752
|
+
socket.on("agent-task:message", (payload) => {
|
|
6753
|
+
const event = parseTaskMessageFrame(payload);
|
|
6754
|
+
if (event)
|
|
6755
|
+
handlers.onTaskMessage(event);
|
|
6756
|
+
});
|
|
6197
6757
|
socket.on("connect_error", (error) => {
|
|
6198
6758
|
this.onConnectError(socket, error);
|
|
6199
6759
|
});
|
|
@@ -6291,30 +6851,6 @@ var DaemonWsClient = class {
|
|
|
6291
6851
|
}
|
|
6292
6852
|
};
|
|
6293
6853
|
|
|
6294
|
-
// src/daemon/stage-contracts.ts
|
|
6295
|
-
var STAGE_CONTRACTS = {
|
|
6296
|
-
todo: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F85\u5206\u6790\uFF08\u9700\u6C42\u5206\u6790\uFF09
|
|
6297
|
-
|
|
6298
|
-
\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
|
|
6299
|
-
|
|
6300
|
-
\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`,
|
|
6301
|
-
in_progress: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F00\u53D1\u5B9E\u73B0
|
|
6302
|
-
|
|
6303
|
-
\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
|
|
6304
|
-
|
|
6305
|
-
\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`,
|
|
6306
|
-
review: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1ACode Review
|
|
6307
|
-
|
|
6308
|
-
\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
|
|
6309
|
-
|
|
6310
|
-
\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`
|
|
6311
|
-
};
|
|
6312
|
-
function buildStageContract(stageCode) {
|
|
6313
|
-
if (!stageCode)
|
|
6314
|
-
return "";
|
|
6315
|
-
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
6316
|
-
}
|
|
6317
|
-
|
|
6318
6854
|
// src/daemon/runner.ts
|
|
6319
6855
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
6320
6856
|
var POLL_INTERVAL_MS = 15e3;
|
|
@@ -6322,6 +6858,30 @@ var TICK_BUDGET_MS = 6e4;
|
|
|
6322
6858
|
var OUTBOX_REPLAY_MAX_FAILURES = 5;
|
|
6323
6859
|
var OUTBOX_REPLAY_PAUSE_MS = 5 * 6e4;
|
|
6324
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
|
+
}
|
|
6325
6885
|
var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT"];
|
|
6326
6886
|
var DEFAULT_MODEL = "deepseek-v4-flash";
|
|
6327
6887
|
var DaemonRunner = class {
|
|
@@ -6360,6 +6920,16 @@ var DaemonRunner = class {
|
|
|
6360
6920
|
cancels = /* @__PURE__ */ new Map();
|
|
6361
6921
|
/** Phase 2:审批决策等待表(approvalId → resolver;WS 事件 resolve,轮询兜底) */
|
|
6362
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();
|
|
6363
6933
|
dimPath;
|
|
6364
6934
|
model;
|
|
6365
6935
|
/** 独立 dim 数据空间 env 覆盖(DIMCODE_HOME=~/.morula/dimcode;空对象=初始化失败回退共享空间) */
|
|
@@ -6454,31 +7024,7 @@ var DaemonRunner = class {
|
|
|
6454
7024
|
this.log(`[daemon] \u5DF2\u6CE8\u518C runtime=${registered.id} daemonId=${cfg.daemonId}`);
|
|
6455
7025
|
this.repoCheckout?.setRuntimeId(registered.id);
|
|
6456
7026
|
this.startHeartbeatTimer();
|
|
6457
|
-
this.ws.connect(
|
|
6458
|
-
onTaskDispatched: (taskId, runtimeId) => {
|
|
6459
|
-
this.onTaskDispatched(taskId, runtimeId);
|
|
6460
|
-
},
|
|
6461
|
-
// M1-A1:取消信号 → 中断执行中的 dim 进程
|
|
6462
|
-
onTaskCancelled: (taskId) => {
|
|
6463
|
-
const ctrl = this.cancels.get(taskId);
|
|
6464
|
-
if (ctrl) {
|
|
6465
|
-
this.log(`[daemon] \u6536\u5230\u53D6\u6D88\u4FE1\u53F7 task=${taskId}\uFF0C\u4E2D\u65AD\u6267\u884C`);
|
|
6466
|
-
ctrl.abort();
|
|
6467
|
-
this.cancels.delete(taskId);
|
|
6468
|
-
}
|
|
6469
|
-
},
|
|
6470
|
-
onDisconnected: () => {
|
|
6471
|
-
},
|
|
6472
|
-
// Phase 2:审批决策 → resolve 等待者(daemon 侧 ACP permission/response)
|
|
6473
|
-
onApprovalDecision: (event) => {
|
|
6474
|
-
const waiter = this.pendingApprovals.get(event.approvalId);
|
|
6475
|
-
if (waiter) {
|
|
6476
|
-
clearTimeout(waiter.timer);
|
|
6477
|
-
this.pendingApprovals.delete(event.approvalId);
|
|
6478
|
-
waiter.resolve(event.decision);
|
|
6479
|
-
}
|
|
6480
|
-
}
|
|
6481
|
-
});
|
|
7027
|
+
this.ws.connect(this.buildWsHandlers());
|
|
6482
7028
|
void this.replayOutbox();
|
|
6483
7029
|
while (this.running) {
|
|
6484
7030
|
await this.tick();
|
|
@@ -6608,6 +7154,7 @@ var DaemonRunner = class {
|
|
|
6608
7154
|
}
|
|
6609
7155
|
} catch {
|
|
6610
7156
|
}
|
|
7157
|
+
void this.catchUpTaskMessages(taskId);
|
|
6611
7158
|
}
|
|
6612
7159
|
void this.replayOutbox();
|
|
6613
7160
|
try {
|
|
@@ -6797,6 +7344,48 @@ var DaemonRunner = class {
|
|
|
6797
7344
|
}, Math.max(0, nextAt - Date.now()));
|
|
6798
7345
|
this.outboxTimer.unref?.();
|
|
6799
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
|
+
}
|
|
6800
7389
|
/**
|
|
6801
7390
|
* 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
|
|
6802
7391
|
* 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
|
|
@@ -6828,6 +7417,8 @@ var DaemonRunner = class {
|
|
|
6828
7417
|
return;
|
|
6829
7418
|
}
|
|
6830
7419
|
claimed = true;
|
|
7420
|
+
this.ws.subscribeTask(taskId, 0);
|
|
7421
|
+
void this.catchUpTaskMessages(taskId);
|
|
6831
7422
|
await this.executeTask(taskId, ctrl.signal);
|
|
6832
7423
|
} catch (error) {
|
|
6833
7424
|
if (ctrl.signal.aborted) {
|
|
@@ -6845,9 +7436,148 @@ var DaemonRunner = class {
|
|
|
6845
7436
|
} finally {
|
|
6846
7437
|
this.cancels.delete(taskId);
|
|
6847
7438
|
this.processing.delete(taskId);
|
|
7439
|
+
this.ws.unsubscribeTask(taskId);
|
|
7440
|
+
this.pendingMessages.delete(taskId);
|
|
6848
7441
|
this.api.clearTaskToken(taskId);
|
|
6849
7442
|
}
|
|
6850
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
|
+
}
|
|
6851
7581
|
async executeTask(taskId, signal) {
|
|
6852
7582
|
const detail = await this.api.getTask(taskId);
|
|
6853
7583
|
this.log(`[daemon] \u5F00\u59CB\u4EFB\u52A1 ${taskId}\uFF08${detail.executor.executorName}\uFF09`);
|
|
@@ -6961,7 +7691,15 @@ var DaemonRunner = class {
|
|
|
6961
7691
|
writeCustomSkills(taskRoot, customSkills, this.log);
|
|
6962
7692
|
this.log(`[daemon] \u5DF2\u6CE8\u5165 ${customSkills.length} \u4E2A\u81EA\u5B9A\u4E49 skill \u5230 .agents/skills/`);
|
|
6963
7693
|
}
|
|
6964
|
-
|
|
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
|
+
);
|
|
6965
7703
|
writeTaskContextBrief(taskRoot, taskId, detail, repo, this.log);
|
|
6966
7704
|
writeProjectAgentsMd(taskRoot, detail.projectAgentsMd, this.log);
|
|
6967
7705
|
return { taskRoot, repoDir, worktreePath, env, repoBaseUrl };
|
|
@@ -7120,6 +7858,20 @@ var DaemonRunner = class {
|
|
|
7120
7858
|
let thoughtBuf = "";
|
|
7121
7859
|
let thoughtTimer = null;
|
|
7122
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
|
+
};
|
|
7123
7875
|
const progressBuf = [];
|
|
7124
7876
|
let progressTimer = null;
|
|
7125
7877
|
let progressChain = Promise.resolve();
|
|
@@ -7129,13 +7881,28 @@ var DaemonRunner = class {
|
|
|
7129
7881
|
if (batch.length === 0)
|
|
7130
7882
|
return progressChain;
|
|
7131
7883
|
progressChain = progressChain.then(async () => {
|
|
7132
|
-
|
|
7133
|
-
|
|
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) => ({
|
|
7134
7892
|
stage: p.stage,
|
|
7135
7893
|
summary: p.summary ? String(redactValue(p.summary)) : void 0,
|
|
7136
7894
|
toolName: p.toolName,
|
|
7137
7895
|
toolInput: p.toolInput !== void 0 ? redactInputSummary(p.toolInput) : void 0
|
|
7138
|
-
}).
|
|
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);
|
|
7139
7906
|
});
|
|
7140
7907
|
}
|
|
7141
7908
|
});
|
|
@@ -7153,9 +7920,23 @@ var DaemonRunner = class {
|
|
|
7153
7920
|
return;
|
|
7154
7921
|
streamFlushing = true;
|
|
7155
7922
|
const batch = streamChunks.splice(0);
|
|
7923
|
+
streamInFlight += batch.length;
|
|
7156
7924
|
try {
|
|
7157
7925
|
for (const c of batch) {
|
|
7158
|
-
|
|
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);
|
|
7159
7940
|
});
|
|
7160
7941
|
}
|
|
7161
7942
|
} finally {
|
|
@@ -7172,17 +7953,19 @@ var DaemonRunner = class {
|
|
|
7172
7953
|
void flushStreamChunks(false);
|
|
7173
7954
|
}
|
|
7174
7955
|
};
|
|
7175
|
-
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);
|
|
7176
7959
|
const input = {
|
|
7177
7960
|
executionId: `${taskId}:1`,
|
|
7178
7961
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
7179
|
-
cwd:
|
|
7962
|
+
cwd: canResume ? resumeWorkDir : taskRoot,
|
|
7180
7963
|
// LOCAL-AGENT-FIX-003:把协作契约前置到任务上下文(userPrompt = Multica 的 messages[0]);
|
|
7181
7964
|
// dim 是 systemPromptMode:'native',systemPrompt 不进 LLM,契约/al上下文均以 userPrompt 承载。
|
|
7182
7965
|
userPrompt: buildTaskPromptWithContract(detail, repoBaseUrl),
|
|
7183
7966
|
env,
|
|
7184
7967
|
modelId: requestedModelId ?? this.model,
|
|
7185
|
-
resumeSessionId,
|
|
7968
|
+
resumeSessionId: canResume ? resumeSessionId : null,
|
|
7186
7969
|
approvalPolicy: {
|
|
7187
7970
|
version: 1,
|
|
7188
7971
|
readonly: false,
|
|
@@ -7195,6 +7978,8 @@ var DaemonRunner = class {
|
|
|
7195
7978
|
approvalDecider: isFlagEnabled("MORULA_DIM_APPROVAL_MODE", false) && detail.executor.approvalMode === "manual" ? this.createApprovalDecider(taskId, `${taskId}:1`) : void 0,
|
|
7196
7979
|
// M1-A1:取消信号透传(WS cancelled / 轮询兜底 → abort → 进程组中断)
|
|
7197
7980
|
signal,
|
|
7981
|
+
// P3:运行中打断端口(安全点中断 → 同会话续开一轮;能力门控见 §三 E4)
|
|
7982
|
+
steer: this.buildSteerPort(taskId, detail),
|
|
7198
7983
|
onEvent: (event) => {
|
|
7199
7984
|
if (event.type === "progress") {
|
|
7200
7985
|
if (event.summary || event.toolName)
|
|
@@ -7255,6 +8040,15 @@ var DaemonRunner = class {
|
|
|
7255
8040
|
streamChunks.push({ chunkType: "usage", seqHint: nextChunkSeq(), data: event.usage });
|
|
7256
8041
|
void flushStreamChunks(true);
|
|
7257
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
|
+
}
|
|
7258
8052
|
}
|
|
7259
8053
|
};
|
|
7260
8054
|
const result = await provider.run(input);
|
|
@@ -7278,10 +8072,24 @@ var DaemonRunner = class {
|
|
|
7278
8072
|
clearTimeout(progressTimer);
|
|
7279
8073
|
progressTimer = null;
|
|
7280
8074
|
}
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
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
|
|
7284
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
|
+
}
|
|
7285
8093
|
return result;
|
|
7286
8094
|
}
|
|
7287
8095
|
/**
|
|
@@ -7491,16 +8299,44 @@ function writeCustomSkills(targetDir, skills, log) {
|
|
|
7491
8299
|
${skill.content ?? ""}`, "utf8");
|
|
7492
8300
|
}
|
|
7493
8301
|
}
|
|
7494
|
-
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch) {
|
|
8302
|
+
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
7495
8303
|
const dir = join15(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
7496
8304
|
const refsDir = join15(dir, "references");
|
|
7497
8305
|
mkdirSync9(refsDir, { recursive: true });
|
|
7498
|
-
const
|
|
7499
|
-
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);
|
|
7500
8309
|
writeFileSync8(join15(dir, "SKILL.md"), `${front}
|
|
7501
8310
|
${body}`, "utf8");
|
|
7502
8311
|
writeFileSync8(join15(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
7503
|
-
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
|
+
};
|
|
7504
8340
|
}
|
|
7505
8341
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
7506
8342
|
const briefDir = join15(targetDir, ".agent_context");
|
|
@@ -7596,7 +8432,8 @@ ${multiRepoList}
|
|
|
7596
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
|
|
7597
8433
|
|
|
7598
8434
|
`;
|
|
7599
|
-
const
|
|
8435
|
+
const taskSource = detail.contextSnapshot?.source;
|
|
8436
|
+
const stageContract = buildStageContract(detail.stageCode, typeof taskSource === "string" ? taskSource : void 0);
|
|
7600
8437
|
const stageContractBlock = stageContract ? `${stageContract}
|
|
7601
8438
|
|
|
7602
8439
|
` : "";
|
|
@@ -7796,6 +8633,7 @@ import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync
|
|
|
7796
8633
|
import { homedir as homedir8 } from "node:os";
|
|
7797
8634
|
import { dirname as dirname6, join as join16 } from "node:path";
|
|
7798
8635
|
init_daemon_config();
|
|
8636
|
+
init_dim_locate();
|
|
7799
8637
|
async function doctorCommand(context) {
|
|
7800
8638
|
const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
|
|
7801
8639
|
const { facts, checks } = await collectDoctor(context, probe);
|