@arcships/morula-runtime 0.1.0-alpha.8 → 0.1.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -740,7 +740,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
740
740
  var init_build_env = __esm({
741
741
  "../morula-cli-core/dist/build-env.js"() {
742
742
  "use strict";
743
- BUILD_ID = "20260921065825.nogit";
743
+ BUILD_ID = "20260921135326.48e72dd8";
744
744
  BUILD_CONTEXTS = {
745
745
  "local": "http://localhost:8080/api",
746
746
  "test": "https://morula.basecastle.com/api",
@@ -974,6 +974,27 @@ var init_ci = __esm({
974
974
  });
975
975
 
976
976
  // ../morula-api-client/dist/client.js
977
+ function describeFetchFailure(error) {
978
+ const message = error instanceof Error ? error.message : String(error);
979
+ const detail = formatFetchCause(error?.cause);
980
+ return detail ? `${message} (cause: ${detail})` : message;
981
+ }
982
+ function formatFetchCause(cause) {
983
+ if (cause === void 0 || cause === null)
984
+ return "";
985
+ if (cause instanceof Error) {
986
+ const detail = cause;
987
+ const parts = [
988
+ typeof detail.code === "string" ? detail.code : "",
989
+ typeof detail.errno === "number" || typeof detail.errno === "string" ? `errno=${detail.errno}` : "",
990
+ typeof detail.syscall === "string" ? `syscall=${detail.syscall}` : "",
991
+ // cause 的 message 常与外层重复('fetch failed'),只有不同才带
992
+ detail.message && detail.message !== "fetch failed" ? detail.message : ""
993
+ ].filter(Boolean);
994
+ return parts.join(" ").slice(0, CAUSE_DETAIL_MAX_LENGTH);
995
+ }
996
+ return String(cause).slice(0, CAUSE_DETAIL_MAX_LENGTH);
997
+ }
977
998
  function sanitizeHeaderValue(raw, maxLength) {
978
999
  if (!raw)
979
1000
  return void 0;
@@ -1061,7 +1082,7 @@ function extractMessage(parsed, raw, status3) {
1061
1082
  return raw.slice(0, 500);
1062
1083
  return `HTTP ${status3}`;
1063
1084
  }
1064
- var EXIT_CODE, MorulaApiError, CLI_BUILD_HEADER, CLI_CAPS_HEADER, CLI_BUILD_MAX_LENGTH, CLI_CAPS_MAX_LENGTH, DEFAULT_TIMEOUT_MS, MorulaApiClient;
1085
+ var EXIT_CODE, MorulaApiError, CLI_BUILD_HEADER, CLI_CAPS_HEADER, CLI_BUILD_MAX_LENGTH, CLI_CAPS_MAX_LENGTH, DEFAULT_TIMEOUT_MS, CAUSE_DETAIL_MAX_LENGTH, MorulaApiClient;
1065
1086
  var init_client = __esm({
1066
1087
  "../morula-api-client/dist/client.js"() {
1067
1088
  "use strict";
@@ -1098,6 +1119,7 @@ var init_client = __esm({
1098
1119
  CLI_BUILD_MAX_LENGTH = 64;
1099
1120
  CLI_CAPS_MAX_LENGTH = 255;
1100
1121
  DEFAULT_TIMEOUT_MS = 3e4;
1122
+ CAUSE_DETAIL_MAX_LENGTH = 200;
1101
1123
  MorulaApiClient = class {
1102
1124
  baseUrl;
1103
1125
  token;
@@ -1195,7 +1217,7 @@ var init_client = __esm({
1195
1217
  if (controller.signal.aborted) {
1196
1218
  throw new MorulaApiError("TIMEOUT", `request timed out after ${timeoutMs}ms`, EXIT_CODE.UPSTREAM, void 0, true);
1197
1219
  }
1198
- throw new MorulaApiError("NETWORK", `request failed: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
1220
+ throw new MorulaApiError("NETWORK", `request failed: ${describeFetchFailure(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
1199
1221
  } finally {
1200
1222
  clearTimeout(timer);
1201
1223
  }
@@ -4125,7 +4147,7 @@ init_dist2();
4125
4147
  init_dist2();
4126
4148
 
4127
4149
  // src/build-env.ts
4128
- var BUILD_VERSION2 = "0.1.0-alpha.8";
4150
+ var BUILD_VERSION2 = "0.1.0-alpha.9";
4129
4151
 
4130
4152
  // src/commands/daemon.ts
4131
4153
  init_dist();
@@ -4374,6 +4396,17 @@ var DaemonApi = class {
4374
4396
  ...token ? { token } : {}
4375
4397
  });
4376
4398
  }
4399
+ /**
4400
+ * 终态结果回传(§6.2-⑤)。
4401
+ *
4402
+ * `runtimeId` **必传**:服务端按它校验任务归属(不匹配 → `RUNTIME_MISMATCH`,不落终态),
4403
+ * 缺失同样被拒(P1-a 定案「立即拒绝」——旧版 daemon 的任务会全部失败,升级期须先升执行设备,
4404
+ * 见 issues/agent-task-false-failure-on-daemon-fetch-error.md)。
4405
+ *
4406
+ * `cancelled: true`(取消留痕,issues/daemon-stream-events-silently-stop-mid-run.md 未实施项 5):
4407
+ * 本轮**被人取消**时只补记已产出内容(`result.text` → 服务端 `agent_tasks.result.cancelled.lastOutput`),
4408
+ * **不改任务状态**、不触发生命周期。旧服务端经 `whitelist` 剥离该字段 → 退化为原「终态回传被幂等丢弃」。
4409
+ */
4377
4410
  submitResult(taskId, body) {
4378
4411
  return this.taskRequest(taskId, `/agent-tasks/${taskId}/result`, { body });
4379
4412
  }
@@ -4782,6 +4815,11 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
4782
4815
  - **\u7A7A\u95F4\u4ED3\u5E93\u6E05\u5355**\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u5224\u65AD\u4F9D\u636E\uFF09\uFF1A\`morula repo list --space <spaceId>\`\u2014\u2014\u63D0\u793A\u8BCD\u4E0D\u5217\u4ED3\u5E93\uFF0C\u9700\u8981\u65F6\u81EA\u5DF1\u67E5\u3002
4783
4816
  - **\u300C\u4F60\u7684\u76EE\u6807\u300D**\uFF1A\u7CFB\u7EDF\u63D0\u793A\u300C\u4F60\u7684\u76EE\u6807\u300D\u6BB5 = \u672C\u6B21\u4EFB\u52A1\u7684\u539F\u59CB\u8BC9\u6C42\uFF0C\u636E\u6B64\u5224\u65AD\u8981\u505A\u4EC0\u4E48\u3002
4784
4817
 
4818
+ > **\u8C03 CLI \u4E00\u5F8B\u7528 \`$MORULA_CLI\`**\uFF08daemon \u6CE8\u5165\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6307\u5411\u4E0E\u672C\u6B21\u8FD0\u884C\u540C\u6E90\u7684\u6784\u5EFA\uFF1B\u4E0B\u6587\u6240\u6709 \`morula xxx\`
4819
+ > \u90FD\u6309 \`$MORULA_CLI xxx\` \u6267\u884C\uFF09\u3002PATH \u4E0A\u7684\u88F8 \`morula\` \u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\u672C\uFF08\u7F3A\u547D\u4EE4\u9762\u3001\u4E14\u4E0D\u8BA4\u4F1A\u8BDD/\u4EFB\u52A1
4820
+ > \u51ED\u8BC1 \u2192 \u5199\u64CD\u4F5C\u7F72\u540D\u9519\u8BEF\uFF09\uFF0C\u4E0D\u8981\u7528\u5B83\uFF1B\`env VAR=... $MORULA_CLI xxx\` \u8FD9\u7C7B env \u5305\u88C5\u4E0B\u540C\u6837\u53EF\u7528\u3002
4821
+ > \u4EC5\u5F53 \`$MORULA_CLI\` \u4E3A\u7A7A\u65F6\u624D\u9000\u56DE\u88F8 \`morula\`\u3002
4822
+
4785
4823
  > **\u5DE5\u4F5C\u76EE\u5F55 = \u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839**\uFF1A\u6839\u4E0B\u653E \`AGENTS.md\`\uFF08\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\uFF0C\u5DF2\u4F5C\u4E3A project.instructions \u81EA\u52A8\u52A0\u8F7D\uFF09\u3001
4786
4824
  > \`.agents/\`\u3001\`.agent_context/\`\u3001\`.morula/\`\uFF1B**\u5404\u4ED3\u5E93\u662F\u5DE5\u4F5C\u533A\u6839\u4E0B\u7684\u5B50\u76EE\u5F55**\uFF08\u89C1 0.5\uFF09\u3002
4787
4825
  > \u4E0B\u6587\u7684 \`AGENTS.md\`/\`.agents/\`/\`.agent_context/\` \u8DEF\u5F84\u90FD\u76F8\u5BF9\u8FD9\u4E2A\u5DE5\u4F5C\u533A\u6839\u3002
@@ -4789,6 +4827,11 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
4789
4827
  > **\u4EFB\u52A1\u4E0A\u4E0B\u6587\u6587\u4EF6**\uFF1A\u540C\u4E00\u5957\u4E0A\u4E0B\u6587\u4E5F\u5199\u5728\u5DE5\u4F5C\u76EE\u5F55 \`.agent_context/task-context.md\`\uFF08\u4E0E\u9996\u6761\u7CFB\u7EDF\u63D0\u793A\u540C\u6E90\uFF09\u3002
4790
4828
  > \u9700\u8981\u518D\u6B21\u786E\u8BA4\u4EFB\u52A1\u8FB9\u754C\u65F6**\u8BFB\u53D6\u8BE5\u6587\u4EF6**\uFF08\`cat .agent_context/task-context.md\`\uFF09\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u63A8\u65AD\u3002
4791
4829
 
4830
+ > **\u672C\u5951\u7EA6\u81EA\u8EAB\u7684\u8BFB\u53D6\u65B9\u5F0F**\uFF1A\u5951\u7EA6\u4EE5\u9879\u76EE\u7EA7 skill \u5F62\u5F0F\u6CE8\u5165\u5728 \`.agents/skills/morula-working-on-issues/SKILL.md\`\u3002
4831
+ > \u7528 \`skill\` \u5DE5\u5177\u6309\u540D\u52A0\u8F7D**\u53EF\u80FD\u62A5 \`Unknown skill\`**\uFF08\u8BE5\u5DE5\u5177\u7684\u53D1\u73B0\u8303\u56F4\u4E0D\u542B\u9879\u76EE\u7EA7\u76EE\u5F55\uFF09\u2014\u2014\u6B64\u65F6
4832
+ > **\u76F4\u63A5\u8BFB\u6587\u4EF6**\uFF08\`cat .agents/skills/morula-working-on-issues/SKILL.md\` \u6216 read \u5DE5\u5177\uFF09\uFF0C
4833
+ > **\u4E0D\u8981\u56E0\u4E3A\u52A0\u8F7D\u5931\u8D25\u5C31\u8DF3\u8FC7\u5951\u7EA6**\uFF08issues/injected-contract-skill-not-loadable-by-skill-tool.md\uFF09\u3002
4834
+
4792
4835
  \u8BE6\u60C5\u89C1\u4E0B\u6587\u5404\u8282\u5BF9 TaskID / \u5BF9\u5916\u7F16\u53F7\u7684\u5177\u4F53\u7528\u6CD5\u3002
4793
4836
 
4794
4837
  ## 0.1 \u901A\u7528\u884C\u4E3A\u7EA6\u675F
@@ -5145,6 +5188,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
5145
5188
  | CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`packages/morula-cli/src/commands/comment.ts\` |
5146
5189
  | 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\` |
5147
5190
  | 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\` |
5191
+ | \u8C03 CLI \u7528 \`$MORULA_CLI\`\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4E0E\u5F53\u524D daemon \u540C\u6E90\uFF1BPATH \u4E0A\u7684\u88F8 \`morula\` \u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\uFF09 | \`resolveAgentCliPath\`\uFF08\u4F18\u5148\u672C\u5305 \`dist/morula.js\`\uFF0C\u4EA7\u7269\u7F3A\u5931\u56DE\u9000\u63D2\u4EF6\u542F\u52A8\u5668\uFF09\uFF1B\u6CE8\u5165\u70B9\uFF1A\u4EFB\u52A1\u94FE\u8DEF \`env\`\u3001\u4F1A\u8BDD\u94FE\u8DEF \`env\` | \`packages/runtime/src/daemon/plugin-register.ts\`\u3001\`runner.ts\`\u3001\`session-host.ts\` |
5148
5192
  | \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u8EAB\u4EFD\u4E0E\u8FB9\u754C = \u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u94A5\u5319 taskId+spaceId / \u5DE5\u5177\u5165\u53E3\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u4E0E\u4ED3\u5E93\u6E05\u5355\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u3001\`morula repo list --space\` \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\` |
5149
5193
  | \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 |
5150
5194
  | \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 |
@@ -5490,6 +5534,13 @@ function pluginBinDir(env = process.env, home = homedir10()) {
5490
5534
  function pluginBinPath(env = process.env, home = homedir10()) {
5491
5535
  return join13(pluginBinDir(env, home), "morula");
5492
5536
  }
5537
+ function ownCliEntryPath(moduleUrl = import.meta.url) {
5538
+ const entry = join13(resolvePackagePaths(moduleUrl).packageRoot, "dist", "morula.js");
5539
+ return existsSync9(entry) ? entry : null;
5540
+ }
5541
+ function resolveAgentCliPath(moduleUrl = import.meta.url, env = process.env, home = homedir10()) {
5542
+ return ownCliEntryPath(moduleUrl) ?? pluginBinPath(env, home);
5543
+ }
5493
5544
  function prependPluginBinToPath(currentPath, env = process.env, home = homedir10()) {
5494
5545
  const binDir = pluginBinDir(env, home);
5495
5546
  const segments = (currentPath ?? "").split(":").filter(Boolean);
@@ -6782,11 +6833,18 @@ function ownerOf(repoFullName) {
6782
6833
  const idx = repoFullName.indexOf("/");
6783
6834
  return idx > 0 ? repoFullName.slice(0, idx) : "_default";
6784
6835
  }
6785
- function buildRepoCachePath(workdir, tenantId, repoFullName) {
6786
- return join15(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
6836
+ function runtimePathSegment(runtimeId) {
6837
+ return runtimeId.slice(0, 8);
6787
6838
  }
6788
- function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
6789
- const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
6839
+ function buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName) {
6840
+ const runtimeSegment = runtimePathSegment(runtimeId);
6841
+ if (!runtimeSegment) {
6842
+ throw new Error("REPO_CACHE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 repo cache \u8DEF\u5F84");
6843
+ }
6844
+ return join15(workdir, "repos", tenantId, runtimeSegment, ownerOf(repoFullName), repoFullName);
6845
+ }
6846
+ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
6847
+ const repoCachePath = buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName);
6790
6848
  const currentOrigin = () => {
6791
6849
  if (!existsSync11(join15(repoCachePath, ".git")))
6792
6850
  return null;
@@ -6885,7 +6943,7 @@ function safeDirName(name) {
6885
6943
  return safe || "task";
6886
6944
  }
6887
6945
  function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
6888
- const runtimeSegment = runtimeId.slice(0, 8);
6946
+ const runtimeSegment = runtimePathSegment(runtimeId);
6889
6947
  if (!runtimeSegment) {
6890
6948
  throw new Error("WORKTREE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 worktree \u8DEF\u5F84");
6891
6949
  }
@@ -7120,7 +7178,7 @@ function reportForeignResidue(worktreePath, forensics, cleaned) {
7120
7178
  console.warn(`[daemon] ${detail}`);
7121
7179
  }
7122
7180
  function prepareWorktree(input) {
7123
- const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
7181
+ const cache = openRepoCache(input.workdir, input.tenantId, input.runtimeId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
7124
7182
  cache.ensure();
7125
7183
  cache.fetch();
7126
7184
  const requestedBase = input.baseBranch?.trim();
@@ -7603,6 +7661,7 @@ var LocalRepoCheckoutService = class {
7603
7661
  const cache = openRepoCache(
7604
7662
  this.opts.cfg.workdir,
7605
7663
  this.opts.tenantId,
7664
+ this.runtimeId,
7606
7665
  repoFullName,
7607
7666
  env,
7608
7667
  baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
@@ -7667,6 +7726,7 @@ var LocalRepoCheckoutService = class {
7667
7726
  const cache = openRepoCache(
7668
7727
  this.opts.cfg.workdir,
7669
7728
  this.opts.tenantId,
7729
+ this.runtimeId,
7670
7730
  repoFullName,
7671
7731
  env,
7672
7732
  baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
@@ -7921,144 +7981,6 @@ function materializeAdditionalRepo(input) {
7921
7981
  return input.targetPath;
7922
7982
  }
7923
7983
 
7924
- // src/daemon/task-messages.ts
7925
- var TASK_MESSAGE_QUEUE_MAX = 200;
7926
- function asRecord(value) {
7927
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
7928
- }
7929
- function asNonEmptyString(value) {
7930
- return typeof value === "string" && value.length > 0 ? value : null;
7931
- }
7932
- function normalizeAgentMessage(value) {
7933
- const row = asRecord(value);
7934
- if (!row)
7935
- return null;
7936
- const id = asNonEmptyString(row.id);
7937
- if (!id)
7938
- return null;
7939
- const seq = row.seq;
7940
- if (typeof seq !== "number" || !Number.isFinite(seq))
7941
- return null;
7942
- return {
7943
- id,
7944
- seq,
7945
- authorType: typeof row.authorType === "string" ? row.authorType : "unknown",
7946
- authorPrincipalId: asNonEmptyString(row.authorPrincipalId),
7947
- authorName: asNonEmptyString(row.authorName),
7948
- content: typeof row.content === "string" ? row.content : "",
7949
- mode: typeof row.mode === "string" ? row.mode : "queue",
7950
- deliveryState: typeof row.deliveryState === "string" ? row.deliveryState : "pending",
7951
- deliveryError: asNonEmptyString(row.deliveryError),
7952
- targetTaskId: asNonEmptyString(row.targetTaskId),
7953
- anchorEventSeq: typeof row.anchorEventSeq === "number" ? row.anchorEventSeq : null,
7954
- turnNo: typeof row.turnNo === "number" ? row.turnNo : null,
7955
- createdAt: typeof row.createdAt === "string" ? row.createdAt : ""
7956
- };
7957
- }
7958
- function parseTaskMessageFrame(value) {
7959
- const frame = asRecord(value);
7960
- if (!frame)
7961
- return null;
7962
- const taskId = asNonEmptyString(frame.taskId);
7963
- if (!taskId || !Array.isArray(frame.messages))
7964
- return null;
7965
- const messages = frame.messages.map(normalizeAgentMessage).filter((message) => message !== null);
7966
- if (messages.length === 0)
7967
- return null;
7968
- return {
7969
- type: "agent-task:message",
7970
- taskId,
7971
- objectId: asNonEmptyString(frame.objectId),
7972
- anchorKey: typeof frame.anchorKey === "string" ? frame.anchorKey : "",
7973
- messages
7974
- };
7975
- }
7976
- function upsertTaskMessages(existing, incoming, max = TASK_MESSAGE_QUEUE_MAX) {
7977
- const byId = /* @__PURE__ */ new Map();
7978
- for (const message of existing)
7979
- byId.set(message.id, message);
7980
- for (const message of incoming)
7981
- byId.set(message.id, message);
7982
- const merged = [...byId.values()].sort((a, b) => a.seq - b.seq || a.id.localeCompare(b.id));
7983
- const dropped = Math.max(0, merged.length - max);
7984
- return { messages: dropped > 0 ? merged.slice(dropped) : merged, dropped };
7985
- }
7986
- function selectConsumedMessages(messages, taskId) {
7987
- return messages.filter((message) => message.targetTaskId === taskId);
7988
- }
7989
- function selectSteerMessages(messages, taskId) {
7990
- 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);
7991
- }
7992
- function renderMessageList(messages) {
7993
- 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");
7994
- }
7995
- function buildSteerPrompt(input) {
7996
- 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";
7997
- 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" : "";
7998
- const originalPrompt = input.taskPrompt?.trim() ? `
7999
-
8000
- \u3010\u539F\u59CB\u4EFB\u52A1\u9700\u6C42\uFF08\u672C\u8F6E\u662F\u540C\u4E00\u6B21\u5DE5\u4F5C\u7684\u5EF6\u7EED\uFF09\u3011
8001
- ${input.taskPrompt.trim()}` : "";
8002
- return `\u3010\u8FD0\u884C\u4E2D\u6253\u65AD \xB7 \u8BF7\u5148\u6309\u8FD9\u4E9B\u610F\u89C1\u8C03\u6574\uFF0C\u518D\u7EE7\u7EED\u3011
8003
- \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
8004
-
8005
- ${opinions}
8006
-
8007
- \u8981\u6C42\uFF1A
8008
- 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
8009
- 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
8010
- 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}`;
8011
- }
8012
-
8013
- // src/daemon/stage-contracts.ts
8014
- var STAGE_CONTRACTS = {
8015
- todo: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F85\u5206\u6790\uFF08\u9700\u6C42\u5206\u6790\uFF09
8016
-
8017
- \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
8018
-
8019
- \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`,
8020
- in_progress: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F00\u53D1\u5B9E\u73B0
8021
-
8022
- \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
8023
-
8024
- \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`,
8025
- review: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1ACode Review
8026
-
8027
- \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
8028
-
8029
- \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`
8030
- };
8031
- 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
8032
-
8033
- \u6309\u4EFB\u52A1\u8BF4\u660E\uFF08runbook\uFF09\u6267\u884C\uFF0C\u4EA7\u51FA**\u62A5\u544A**\uFF1A\`morula report create --title <\u6807\u9898> --content-file <md>\`
8034
- \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
8035
-
8036
- - \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
8037
- - \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
8038
- - \u4E0D\u8981\u8C03\u7528 \`morula task state\` / \`morula task analysis-verdict\` / \`morula review submit\`\uFF1A
8039
- \u672C\u4EFB\u52A1\u6CA1\u6709\u5173\u8054\u4E1A\u52A1\u4EFB\u52A1\uFF0C\u8FD9\u4E9B\u8C03\u7528\u4F1A\u88AB\u5E73\u53F0\u62D2\u7EDD\u3002
8040
- - \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
8041
- **\u5199\u4E4B\u524D\u5FC5\u987B\u5148 \`morula repo mr get <taskId> --mr <iid> --notes\` \u8BFB\u5DF2\u6709\u8BA8\u8BBA**\uFF1A
8042
- \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
8043
- \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
8044
- \u4F9B\u4F60\u81EA\u5DF1\u5728\u4E0B\u4E00\u8F6E\u8BC6\u522B\u5386\u53F2\u8BC4\u8BBA\u3002
8045
-
8046
- ## \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
8047
-
8048
- - \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
8049
- \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
8050
- - \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
8051
- - \u8981\u7ED9\u4EBA\u751F\u6210\u5F85\u8DDF\u8FDB\u7684\u4EFB\u52A1\uFF1A\`morula task create --space <spaceId> --title <\u6807\u9898> --description <\u5185\u5BB9>\`
8052
- \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
8053
- \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`;
8054
- function buildStageContract(stageCode, source) {
8055
- if (source === "autopilot")
8056
- return AUTOPILOT_CONTRACT;
8057
- if (!stageCode)
8058
- return "";
8059
- return STAGE_CONTRACTS[stageCode] ?? "";
8060
- }
8061
-
8062
7984
  // src/daemon/session-host.ts
8063
7985
  init_dist2();
8064
7986
  init_acp_connection();
@@ -8142,6 +8064,7 @@ var STEWARD_SKILL_BODY = `# \u7A7A\u95F4\u7BA1\u5BB6\u5951\u7EA6\uFF08Morula\uFF
8142
8064
  - **\u6240\u6709\u67E5\u8BE2\u90FD\u8981\u663E\u5F0F\u5E26 \`--space\`**\uFF08= \u5F53\u524D\u7A7A\u95F4 ID\uFF0C\u89C1 \`.agent_context/steward-context.md\`\uFF09\u3002
8143
8065
  - \u65B0\u9700\u6C42**\u4E00\u5F8B\u8D70\u6D3E\u5355**\uFF0C\u4E0D\u8981\u81EA\u5DF1\u52A8\u624B\u5B9E\u73B0\uFF0C\u4E5F\u4E0D\u8981\u7528\u300C\u59D4\u6258\u5B50\u4EE3\u7406\u300D\u7C7B\u624B\u6BB5\u7ED5\u5F00\u6D3E\u5355\u94FE\u8DEF\u3002
8144
8066
  - **\u7834\u574F\u6027\u52A8\u4F5C\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u5220\u9664\u4EFB\u52A1\u3001\u53D6\u6D88\u6B63\u5728\u8DD1\u7684\u6267\u884C\u3001\u91CD\u8DD1\u5931\u8D25\u4EFB\u52A1\u3001**\u91CD\u8DD1\u5931\u8D25\u7684 CI \u6D41\u6C34\u7EBF / job**\u3001\u628A\u72B6\u6001\u63A8\u8FDB\u5230\u300C\u5DF2\u5408\u5E76 / \u5DF2\u53D1\u5E03 / \u5DF2\u5173\u95ED\u300D\u2014\u2014\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u8868\u5355\uFF0C\u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C\u3002\u53EF\u9006\u7684\u8BB0\u5F55\u4E0E\u6D41\u8F6C\uFF08\u6539\u5C5E\u6027\u3001\u6539\u8D1F\u8D23\u4EBA\u3001\`backlog \u21C4 todo\` \u63A8\u8FDB\uFF09\u4E0D\u9700\u8981\u786E\u8BA4\u3002
8067
+ - **\u63A2\u67E5\u547D\u4EE4\u9762\u53EA\u7528 \`--help\`\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A\u60F3\u786E\u8BA4\u67D0\u6761\u547D\u4EE4\u6216\u53C2\u6570\u5B58\u4E0D\u5B58\u5728\u3001\u5F62\u6001\u5BF9\u4E0D\u5BF9\uFF0C\u5C31\u770B \`$MORULA_CLI <cmd> --help\`\uFF08\u4F8B\u5982 \`$MORULA_CLI ci retry --help\`\uFF09\u3002\u5199\u547D\u4EE4**\u6267\u884C\u5373\u751F\u6548**\uFF08\`morula ci retry\` \u6267\u884C\u5373\u91CD\u8DD1\u3001\`morula task delete\` \u6267\u884C\u5373\u5220\u9664\uFF09\uFF0C\u6CA1\u6709\u300C\u53EA\u8BFB\u63A2\u67E5\u300D\u5F62\u6001\u2014\u2014\u4E3A\u300C\u770B\u770B\u8FD9\u6761\u547D\u4EE4\u5728\u4E0D\u5728\u300D\u800C\u771F\u8DD1\u4E00\u6B21\u5199\u547D\u4EE4\uFF0C\u5C31\u662F\u4E00\u6B21\u672A\u7ECF\u786E\u8BA4\u7684\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u771F\u673A\u4E8B\u6545\uFF1A\u7BA1\u5BB6\u786E\u8BA4 \`ci retry\` \u662F\u5426\u5B58\u5728\u65F6\u771F\u7684\u91CD\u8DD1\u4E86\u4E00\u6761\u6D41\u6C34\u7EBF\uFF09\u3002
8145
8068
 
8146
8069
  ## 1. \u5E38\u7528\u547D\u4EE4\uFF08\u5168\u90E8\u663E\u5F0F\u53C2\u6570\uFF09
8147
8070
 
@@ -8172,6 +8095,8 @@ morula ci retry --space <spaceId> [--run <runId>] [--job <jobId>] #
8172
8095
  **\u4E0D\u80FD\u89E6\u53D1\u90E8\u7F72**\uFF08\u89E6\u53D1\u624B\u52A8 job \u6C38\u8FDC\u7531\u4EBA\u505A\uFF1A\u4F60\u6CA1\u6709\u8BE5\u547D\u4EE4\uFF0C\u4E5F\u4E0D\u8981\u8BD5\u56FE\u7ED5\u8FC7\uFF09\u3002
8173
8096
  - **\u91CD\u8DD1\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u5361\uFF08\u5199\u6E05\u54EA\u6761\u6D41\u6C34\u7EBF / \u54EA\u4E2A job\u3001\u5931\u8D25\u539F\u56E0\u3001\u4E3A\u4EC0\u4E48\u5EFA\u8BAE\u91CD\u8DD1\uFF09\uFF0C
8174
8097
  \u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C \`morula ci retry\`\u3002\u5B83\u4E0E \`morula task rerun\` \u540C\u6863\uFF08\u300C\u9700\u4EBA\u786E\u8BA4\u300D\uFF09\u3002
8098
+ - **\u786E\u8BA4\u547D\u4EE4\u9762\u7528 \`$MORULA_CLI ci --help\`\uFF08\u6216 \`ci retry --help\`\uFF09\uFF0C\u4E0D\u8981\u7528\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A
8099
+ \`morula ci retry\` \u6CA1\u6709\u53EA\u8BFB\u9884\u6F14\u5F62\u6001\uFF0C\u6267\u884C\u5C31\u662F\u4E00\u6B21\u771F\u5B9E\u91CD\u8DD1\uFF1B\u53C2\u6570\u5F62\u6001\u4E0D\u786E\u5B9A\u5C31\u770B \`--help\` \u8F93\u51FA\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u8FD0\u884C\u53BB\u8BD5\u3002
8175
8100
  - \u91CD\u8DD1\u53EA\u5BF9**\u5931\u8D25\u6216\u5DF2\u53D6\u6D88**\u7684\u8FD0\u884C / job \u6709\u6548\uFF1B\u72B6\u6001\u4E0D\u7B26\u4F1A\u660E\u786E\u62A5\u9519\uFF08\`CI_RETRY_NOT_ALLOWED\`\uFF09\uFF0C\u4E0D\u8981\u53CD\u590D\u91CD\u8BD5\u540C\u6837\u7684\u8C03\u7528\u3002
8176
8101
  - \u91CD\u8DD1\u540E**\u56DE\u8BFB\u4E00\u6B21**\uFF08\`morula ci status --space <id>\`\uFF09\u518D\u6C47\u62A5\u2014\u2014\u300C\u5DF2\u91CD\u8DD1\u300D\u8981\u7528\u72B6\u6001\u8BF4\u8BDD\uFF0C\u4E0D\u80FD\u53EA\u770B\u9000\u51FA\u7801\u3002
8177
8102
  - \u6D41\u6C34\u7EBF\u72B6\u6001\u662F**\u5E73\u53F0\u4E8B\u5B9E**\uFF08\u6765\u81EA VCS \u5E73\u53F0\u4E8B\u4EF6\uFF09\uFF0C\u4E0D\u662F\u67D0\u4E2A agent \u7684\u81EA\u8FF0\uFF1B\u88C1\u5B9A\u4E0E\u6C47\u62A5\u4EE5\u5B83\u4E3A\u51C6\u3002
@@ -8211,7 +8136,7 @@ morula steward dispatch --space <spaceId> --title "<\u4E00\u53E5\u8BDD\u76EE\u68
8211
8136
  ### \u591A\u4ED3\u5E93\u7A7A\u95F4\uFF08\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u591A\u4E2A\u4ED3\u5E93\u65F6\uFF09
8212
8137
 
8213
8138
  - \u88C1\u5B9A\u65F6\u53EF\u7ED3\u5408\u4ED3\u5E93\u6E05\u5355\u7ED9\u51FA**\u65B9\u5411\u7EA7**\u5224\u65AD\uFF08\u8FD9\u4E2A\u9700\u6C42\u5927\u6982\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\uFF09\uFF0C\u4F46\u300C\u6BCF\u4E2A\u4ED3\u5E93\u5177\u4F53\u6539\u4EC0\u4E48\u300D\u7531\u5F85\u5206\u6790\u8FD0\u884C\u65F6\u5728\u5206\u6790\u4EFB\u52A1\u4E2D\u4EA7\u51FA\uFF1B\u4E0D\u786E\u5B9A\u5C31\u5199\u300C\u5177\u4F53\u6D89\u53CA\u4ED3\u5E93\u4EE5\u5206\u6790\u7ED3\u8BBA\u4E3A\u51C6\u300D\uFF0C\u4E0D\u8981\u51ED\u7A7A\u65AD\u8A00\u3002
8214
- - **\u6D3E\u5355\u65F6\u5FC5\u987B\u628A\u6D89\u53CA\u4ED3\u5E93\u4F20\u7ED9 \`--repo\`**\uFF08\u5199\u4ED3\u5E93\u540D\uFF0C\u5982 \`nextim/morula-web\`\uFF09\uFF1A\u591A\u4ED3\u5E93\u7A7A\u95F4\u91CC\u5B83\u662F\u4EFB\u52A1\u300C\u5173\u8054\u4ED3\u5E93\u300D\u7684\u552F\u4E00\u786E\u5B9A\u6027\u6765\u6E90\uFF0C\u7F3A\u5931\u4F1A\u5BFC\u81F4\u8BE5\u4EFB\u52A1\u4E0D\u521B\u5EFA\u5BF9\u5E94 Issue\u3001\u4E5F\u65E0\u6CD5\u5728\u4EFB\u52A1\u91CC\u770B\u51FA\u8BE5\u6539\u54EA\u4E2A\u4ED3\u5E93\u3002\u5224\u65AD\u4E0D\u51FA\u552F\u4E00\u4ED3\u5E93\u65F6**\u7559\u7A7A**\uFF0C\u4E0D\u8981\u778E\u586B\u3002
8139
+ - **\u6D3E\u5355\u65F6\u5FC5\u987B\u628A\u6D89\u53CA\u4ED3\u5E93\u4F20\u7ED9 \`--repo\`**\uFF08\u5199\u4ED3\u5E93\u540D\uFF0C\u5982 \`<owner>/<repo>\`\uFF1B**\u540D\u5B57\u53EA\u80FD\u53D6\u81EA\u672C\u7A7A\u95F4\u4ED3\u5E93\u6E05\u5355\u91CC\u5B9E\u9645\u51FA\u73B0\u8FC7\u7684\u4ED3\u5E93\uFF0C\u4E0D\u5F97\u7167\u6284\u793A\u4F8B**\uFF09\uFF1A\u591A\u4ED3\u5E93\u7A7A\u95F4\u91CC\u5B83\u662F\u4EFB\u52A1\u300C\u5173\u8054\u4ED3\u5E93\u300D\u7684\u552F\u4E00\u786E\u5B9A\u6027\u6765\u6E90\uFF0C\u7F3A\u5931\u4F1A\u5BFC\u81F4\u8BE5\u4EFB\u52A1\u4E0D\u521B\u5EFA\u5BF9\u5E94 Issue\u3001\u4E5F\u65E0\u6CD5\u5728\u4EFB\u52A1\u91CC\u770B\u51FA\u8BE5\u6539\u54EA\u4E2A\u4ED3\u5E93\u3002\u5224\u65AD\u4E0D\u51FA\u552F\u4E00\u4ED3\u5E93\u65F6**\u7559\u7A7A**\uFF0C\u4E0D\u8981\u778E\u586B\u3002
8215
8140
 
8216
8141
  ### \u7BA1\u7406\u4EFB\u52A1\uFF08\u6539\u5C5E\u6027 / \u63A8\u8FDB\u72B6\u6001 / \u5EFA\u5355 / \u5220\u5355\uFF09
8217
8142
  \`\`\`bash
@@ -8358,9 +8283,10 @@ var SessionHost = class {
8358
8283
  // 新命令不存在,且旧 CLI 不认 MORULA_SESSION_TOKEN(回落用户凭证)→ 写操作署名错、
8359
8284
  // 写面作用域校验被绕过。PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),
8360
8285
  // 可靠的是 `MORULA_CLI` 绝对路径(契约要求 agent 用它)。
8361
- // 见 issues/steward-cli-resolves-stale-bundled-morula.md。
8286
+ // MORULA_CLI 优先本包自带入口(与当前会话同源),产物缺失才回退插件启动器。
8287
+ // 见 issues/steward-cli-resolves-stale-bundled-morula.md、issues/agent-cli-stale-build-missing-steward-commands.md。
8362
8288
  PATH: prependPluginBinToPath(process.env.PATH),
8363
- MORULA_CLI: pluginBinPath(),
8289
+ MORULA_CLI: resolveAgentCliPath(),
8364
8290
  ...this.options.isolatedDimEnv ?? {}
8365
8291
  };
8366
8292
  const onExit = (code) => {
@@ -8864,6 +8790,144 @@ ${fileRefs.join("\n")}` : text }];
8864
8790
  }
8865
8791
  };
8866
8792
 
8793
+ // src/daemon/stage-contracts.ts
8794
+ var STAGE_CONTRACTS = {
8795
+ todo: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F85\u5206\u6790\uFF08\u9700\u6C42\u5206\u6790\uFF09
8796
+
8797
+ \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
8798
+
8799
+ \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`,
8800
+ in_progress: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1A\u5F00\u53D1\u5B9E\u73B0
8801
+
8802
+ \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
8803
+
8804
+ \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`,
8805
+ review: `## \u672C\u4EFB\u52A1\u9636\u6BB5\uFF1ACode Review
8806
+
8807
+ \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
8808
+
8809
+ \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`
8810
+ };
8811
+ 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
8812
+
8813
+ \u6309\u4EFB\u52A1\u8BF4\u660E\uFF08runbook\uFF09\u6267\u884C\uFF0C\u4EA7\u51FA**\u62A5\u544A**\uFF1A\`morula report create --title <\u6807\u9898> --content-file <md>\`
8814
+ \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
8815
+
8816
+ - \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
8817
+ - \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
8818
+ - \u4E0D\u8981\u8C03\u7528 \`morula task state\` / \`morula task analysis-verdict\` / \`morula review submit\`\uFF1A
8819
+ \u672C\u4EFB\u52A1\u6CA1\u6709\u5173\u8054\u4E1A\u52A1\u4EFB\u52A1\uFF0C\u8FD9\u4E9B\u8C03\u7528\u4F1A\u88AB\u5E73\u53F0\u62D2\u7EDD\u3002
8820
+ - \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
8821
+ **\u5199\u4E4B\u524D\u5FC5\u987B\u5148 \`morula repo mr get <taskId> --mr <iid> --notes\` \u8BFB\u5DF2\u6709\u8BA8\u8BBA**\uFF1A
8822
+ \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
8823
+ \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
8824
+ \u4F9B\u4F60\u81EA\u5DF1\u5728\u4E0B\u4E00\u8F6E\u8BC6\u522B\u5386\u53F2\u8BC4\u8BBA\u3002
8825
+
8826
+ ## \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
8827
+
8828
+ - \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
8829
+ \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
8830
+ - \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
8831
+ - \u8981\u7ED9\u4EBA\u751F\u6210\u5F85\u8DDF\u8FDB\u7684\u4EFB\u52A1\uFF1A\`morula task create --space <spaceId> --title <\u6807\u9898> --description <\u5185\u5BB9>\`
8832
+ \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
8833
+ \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`;
8834
+ function buildStageContract(stageCode, source) {
8835
+ if (source === "autopilot")
8836
+ return AUTOPILOT_CONTRACT;
8837
+ if (!stageCode)
8838
+ return "";
8839
+ return STAGE_CONTRACTS[stageCode] ?? "";
8840
+ }
8841
+
8842
+ // src/daemon/task-messages.ts
8843
+ var TASK_MESSAGE_QUEUE_MAX = 200;
8844
+ function asRecord(value) {
8845
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
8846
+ }
8847
+ function asNonEmptyString(value) {
8848
+ return typeof value === "string" && value.length > 0 ? value : null;
8849
+ }
8850
+ function normalizeAgentMessage(value) {
8851
+ const row = asRecord(value);
8852
+ if (!row)
8853
+ return null;
8854
+ const id = asNonEmptyString(row.id);
8855
+ if (!id)
8856
+ return null;
8857
+ const seq = row.seq;
8858
+ if (typeof seq !== "number" || !Number.isFinite(seq))
8859
+ return null;
8860
+ return {
8861
+ id,
8862
+ seq,
8863
+ authorType: typeof row.authorType === "string" ? row.authorType : "unknown",
8864
+ authorPrincipalId: asNonEmptyString(row.authorPrincipalId),
8865
+ authorName: asNonEmptyString(row.authorName),
8866
+ content: typeof row.content === "string" ? row.content : "",
8867
+ mode: typeof row.mode === "string" ? row.mode : "queue",
8868
+ deliveryState: typeof row.deliveryState === "string" ? row.deliveryState : "pending",
8869
+ deliveryError: asNonEmptyString(row.deliveryError),
8870
+ targetTaskId: asNonEmptyString(row.targetTaskId),
8871
+ anchorEventSeq: typeof row.anchorEventSeq === "number" ? row.anchorEventSeq : null,
8872
+ turnNo: typeof row.turnNo === "number" ? row.turnNo : null,
8873
+ createdAt: typeof row.createdAt === "string" ? row.createdAt : ""
8874
+ };
8875
+ }
8876
+ function parseTaskMessageFrame(value) {
8877
+ const frame = asRecord(value);
8878
+ if (!frame)
8879
+ return null;
8880
+ const taskId = asNonEmptyString(frame.taskId);
8881
+ if (!taskId || !Array.isArray(frame.messages))
8882
+ return null;
8883
+ const messages = frame.messages.map(normalizeAgentMessage).filter((message) => message !== null);
8884
+ if (messages.length === 0)
8885
+ return null;
8886
+ return {
8887
+ type: "agent-task:message",
8888
+ taskId,
8889
+ objectId: asNonEmptyString(frame.objectId),
8890
+ anchorKey: typeof frame.anchorKey === "string" ? frame.anchorKey : "",
8891
+ messages
8892
+ };
8893
+ }
8894
+ function upsertTaskMessages(existing, incoming, max = TASK_MESSAGE_QUEUE_MAX) {
8895
+ const byId = /* @__PURE__ */ new Map();
8896
+ for (const message of existing)
8897
+ byId.set(message.id, message);
8898
+ for (const message of incoming)
8899
+ byId.set(message.id, message);
8900
+ const merged = [...byId.values()].sort((a, b) => a.seq - b.seq || a.id.localeCompare(b.id));
8901
+ const dropped = Math.max(0, merged.length - max);
8902
+ return { messages: dropped > 0 ? merged.slice(dropped) : merged, dropped };
8903
+ }
8904
+ function selectConsumedMessages(messages, taskId) {
8905
+ return messages.filter((message) => message.targetTaskId === taskId);
8906
+ }
8907
+ function selectSteerMessages(messages, taskId) {
8908
+ 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);
8909
+ }
8910
+ function renderMessageList(messages) {
8911
+ 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");
8912
+ }
8913
+ function buildSteerPrompt(input) {
8914
+ 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";
8915
+ 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" : "";
8916
+ const originalPrompt = input.taskPrompt?.trim() ? `
8917
+
8918
+ \u3010\u539F\u59CB\u4EFB\u52A1\u9700\u6C42\uFF08\u672C\u8F6E\u662F\u540C\u4E00\u6B21\u5DE5\u4F5C\u7684\u5EF6\u7EED\uFF09\u3011
8919
+ ${input.taskPrompt.trim()}` : "";
8920
+ return `\u3010\u8FD0\u884C\u4E2D\u6253\u65AD \xB7 \u8BF7\u5148\u6309\u8FD9\u4E9B\u610F\u89C1\u8C03\u6574\uFF0C\u518D\u7EE7\u7EED\u3011
8921
+ \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
8922
+
8923
+ ${opinions}
8924
+
8925
+ \u8981\u6C42\uFF1A
8926
+ 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
8927
+ 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
8928
+ 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}`;
8929
+ }
8930
+
8867
8931
  // src/daemon/ws-client.ts
8868
8932
  import { createHash as createHash2 } from "node:crypto";
8869
8933
  import { io } from "socket.io-client";
@@ -8979,7 +9043,7 @@ var DaemonWsClient = class {
8979
9043
  handlers.onTaskResubscribed?.(taskId);
8980
9044
  });
8981
9045
  socket.on("agent-task:dispatched", (payload) => {
8982
- handlers.onTaskDispatched(payload.taskId, payload.runtimeId);
9046
+ handlers.onTaskDispatched(payload.taskId, payload.runtimeId, payload.daemonId);
8983
9047
  });
8984
9048
  socket.on("agent-task:status-changed", (payload) => {
8985
9049
  if (payload?.status === "cancelled" && payload.taskId) {
@@ -9113,6 +9177,7 @@ var OUTBOX_MAX_ATTEMPTS_PER_ROUND = 2;
9113
9177
  var PROGRESS_BATCH_MAX = 500;
9114
9178
  var OBSERVATION_WARN_INTERVAL_MS = 3e4;
9115
9179
  var TERMINAL_OBSERVATION_DRAIN_BUDGET_MS = Number(process.env.MORULA_TERMINAL_DRAIN_BUDGET_MS ?? 2e3);
9180
+ var CANCEL_TRACE_TEXT_MAX = 4e3;
9116
9181
  async function drainObservationTail(args) {
9117
9182
  const startedAt = Date.now();
9118
9183
  const progressDrained = await waitWithin(args.progress, args.budgetMs);
@@ -9134,7 +9199,7 @@ async function waitWithin(promise, ms) {
9134
9199
  clearTimeout(timer);
9135
9200
  return done;
9136
9201
  }
9137
- var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT"];
9202
+ var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT", "RUNTIME_MISMATCH"];
9138
9203
  var DEFAULT_MODEL = "deepseek-v4-flash";
9139
9204
  var DaemonRunner = class {
9140
9205
  constructor(client, token, store, tenantId, options = {}) {
@@ -9156,6 +9221,12 @@ var DaemonRunner = class {
9156
9221
  ws;
9157
9222
  log;
9158
9223
  runtimeId = null;
9224
+ /**
9225
+ * 本进程的**实例标识**(`daemonId`,config dir 内持久化):同机多实例注册时会复用同一行
9226
+ * `AgentRuntime`(同一 runtimeId),下行命令靠 `runtimeId` + `daemonId` 双重过滤才能定向到实例
9227
+ * (issues/duplicate-daemon-same-runtime-double-execution.md)。
9228
+ */
9229
+ daemonId = null;
9159
9230
  running = false;
9160
9231
  /** D1:心跳定时器(独立于 tick)与单飞标志——上一拍未返回则跳过本拍,避免慢请求堆积 */
9161
9232
  heartbeatTimer = null;
@@ -9293,6 +9364,7 @@ var DaemonRunner = class {
9293
9364
  providers: inventory
9294
9365
  });
9295
9366
  this.runtimeId = registered.id;
9367
+ this.daemonId = cfg.daemonId;
9296
9368
  if (cfg.runtimeId !== registered.id) {
9297
9369
  saveDaemonConfig(this.store, { runtimeId: registered.id });
9298
9370
  }
@@ -9443,6 +9515,41 @@ var DaemonRunner = class {
9443
9515
  this.log(`[daemon] cleanup poll \u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
9444
9516
  }
9445
9517
  }
9518
+ /**
9519
+ * 取消留痕(issues/daemon-stream-events-silently-stop-mid-run.md 未实施项 5;用户 2026-09-21 定案「取消也留痕」)。
9520
+ *
9521
+ * 取消路径原本直接 `return`:这一轮**已产出**的内容(dim 已写完的回答、中断前的部分回答)在平台侧
9522
+ * 零留存(唯一痕迹是 `agent_failed{status:cancelled}`)→「取消 = 这轮白跑且无法追溯」。
9523
+ *
9524
+ * 契约(定案):**不得**把任务状态改回 completed —— 服务端只把 `result.text` 合并进
9525
+ * `agent_tasks.result.cancelled.lastOutput`(既有列,与取消时的 who/when/step 同一条记录)。
9526
+ *
9527
+ * best-effort 边界:**不走 outbox、不重试、失败只 log** —— 留痕不得让取消流程变慢或失败;
9528
+ * 无已产出内容时不发请求(不制造空留痕噪声)。旧服务端不认识 `cancelled` 字段(whitelist 剥离)
9529
+ * → 行为退化为原样,不会报错。
9530
+ */
9531
+ async reportCancelTrace(taskId, text, reason) {
9532
+ const produced = (text ?? "").trim();
9533
+ if (!produced) {
9534
+ this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u88AB\u53D6\u6D88\uFF0C\u672C\u8F6E\u65E0\u5DF2\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4E0D\u5199\u53D6\u6D88\u7559\u75D5\uFF08${reason}\uFF09`);
9535
+ return;
9536
+ }
9537
+ try {
9538
+ const res = await this.api.submitResult(taskId, {
9539
+ runtimeId: this.runtimeId ?? "",
9540
+ ok: true,
9541
+ cancelled: true,
9542
+ // 与服务端 `result.text` 的 4000 上限同口径:够追溯「这轮产出了什么」
9543
+ result: { text: produced.slice(0, CANCEL_TRACE_TEXT_MAX) }
9544
+ });
9545
+ if (res?.ok)
9546
+ this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u88AB\u53D6\u6D88\uFF1A\u5DF2\u8865\u8BB0\u672C\u8F6E\u4EA7\u51FA ${produced.length} \u5B57\uFF08\u670D\u52A1\u7AEF\u53EA\u7559\u75D5\uFF0C\u4E0D\u6539\u4EFB\u52A1\u72B6\u6001\uFF09`);
9547
+ else
9548
+ this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u53D6\u6D88\u7559\u75D5\u672A\u843D\u5E93\uFF08${res?.code ?? "unknown"}\uFF09\u2014\u2014 \u4E0D\u5F71\u54CD\u53D6\u6D88\u672C\u8EAB`);
9549
+ } catch (error) {
9550
+ this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u53D6\u6D88\u7559\u75D5\u4E0A\u62A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
9551
+ }
9552
+ }
9446
9553
  /**
9447
9554
  * M1-A2:终态结果可靠回传——先写盘 → 单次尝试 → 成功删盘(顺序语义不变)。
9448
9555
  *
@@ -9481,7 +9588,7 @@ var DaemonRunner = class {
9481
9588
  let res = null;
9482
9589
  let transportError = null;
9483
9590
  try {
9484
- res = await this.api.submitResult(taskId, body);
9591
+ res = await this.api.submitResult(taskId, { ...body, runtimeId: this.runtimeId ?? "" });
9485
9592
  } catch (err) {
9486
9593
  transportError = err?.message ?? String(err);
9487
9594
  }
@@ -9632,8 +9739,8 @@ var DaemonRunner = class {
9632
9739
  */
9633
9740
  buildWsHandlers() {
9634
9741
  return {
9635
- onTaskDispatched: (taskId, runtimeId) => {
9636
- this.onTaskDispatched(taskId, runtimeId);
9742
+ onTaskDispatched: (taskId, runtimeId, daemonId) => {
9743
+ this.onTaskDispatched(taskId, runtimeId, daemonId);
9637
9744
  },
9638
9745
  // M1-A1:取消信号 → 中断执行中的 dim 进程
9639
9746
  onTaskCancelled: (taskId) => {
@@ -9666,7 +9773,7 @@ var DaemonRunner = class {
9666
9773
  },
9667
9774
  // 会话宿主命令(steward-core-acp-migration §4.4,P1-5):转发给 SessionHost。
9668
9775
  // 服务端发到 **tenant 房间**(同租户每台 daemon 都收到,WS 无 per-runtime 房间),
9669
- // 故与 `agent-task:dispatched` 同口径按 `runtimeId` 自过滤。
9776
+ // 故与 `agent-task:dispatched` 同口径按 `runtimeId` + `daemonId` 自过滤。
9670
9777
  onSessionCommand: (kind, payload) => {
9671
9778
  void this.handleSessionCommand(kind, payload);
9672
9779
  }
@@ -9676,7 +9783,10 @@ var DaemonRunner = class {
9676
9783
  * 会话宿主命令分发。
9677
9784
  *
9678
9785
  * 两道闸:
9679
- * ① **runtimeId 自过滤**(tenant 广播 → 只有归属设备处理;缺失时不过滤,兼容旧服务端);
9786
+ * ① **runtimeId + daemonId 自过滤**(tenant 广播 → 只有归属设备处理;字段缺失时不过滤,兼容旧服务端)。
9787
+ * daemonId 是**实例**维度(issues/duplicate-daemon-same-runtime-double-execution.md):同机多实例
9788
+ * 注册时会复用同一行 AgentRuntime(同一 runtimeId),缺了它两个实例会各自 `open` 同一 sessionId
9789
+ * 并各跑一轮 prompt(实测:同一轮两份正文事件、两个 daemon 都「会话已打开」);
9680
9790
  * ② **按会话串行**:服务端每轮 prompt 前会补发 `open` 做兜底,而 socket.io 只保证「送达有序」,
9681
9791
  * 不保证「异步处理有序」——`void handleSessionCommand()` 会让 open/prompt 交错执行,
9682
9792
  * prompt 可能先于 open 完成而拿到 SESSION_NOT_OPEN。故按 sessionId 串成一条 promise 链。
@@ -9686,6 +9796,9 @@ var DaemonRunner = class {
9686
9796
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : void 0;
9687
9797
  if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
9688
9798
  return Promise.resolve();
9799
+ const daemonId = typeof payload.daemonId === "string" ? payload.daemonId : void 0;
9800
+ if (daemonId && this.daemonId && daemonId !== this.daemonId)
9801
+ return Promise.resolve();
9689
9802
  const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : "";
9690
9803
  if (!sessionId) {
9691
9804
  this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u7F3A\u5C11 sessionId\uFF08\u5FFD\u7565\uFF09`);
@@ -9733,10 +9846,17 @@ var DaemonRunner = class {
9733
9846
  * 不校验任务归属,见同 issue 根因 ③ / ④)。
9734
9847
  *
9735
9848
  * `runtimeId` 缺失(旧服务端)→ 保持旧行为不过滤;poll 兜底本就按 runtimeId 查询,不受影响。
9849
+ *
9850
+ * `daemonId`(issues/duplicate-daemon-same-runtime-double-execution.md):同机多实例注册时会复用
9851
+ * 同一行 `AgentRuntime`(同一 runtimeId,服务端按设备收敛重复行)→ 单靠 runtimeId 两个实例都会处理。
9852
+ * 故服务端下行带**实例标识**,两个字段都匹配才处理;`daemonId` 缺失(旧服务端)→ 退回只按 runtimeId
9853
+ * 过滤,否则新 daemon 接旧服务端会全丢事件。
9736
9854
  */
9737
- onTaskDispatched(taskId, runtimeId) {
9855
+ onTaskDispatched(taskId, runtimeId, daemonId) {
9738
9856
  if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
9739
9857
  return;
9858
+ if (daemonId && this.daemonId && daemonId !== this.daemonId)
9859
+ return;
9740
9860
  void this.handleTask(taskId);
9741
9861
  }
9742
9862
  /** 任务处理(幂等:已在处理中则跳过) */
@@ -9955,9 +10075,10 @@ var DaemonRunner = class {
9955
10075
  // 插件 bin 前置 + 显式 CLI 路径(与会话链路同源):任务里的 agent 也必须用本包物化的 `morula`,
9956
10076
  // 而不是宿主/桌面应用自带的旧版(旧版不认任务/会话凭证,且缺部分命令旗标 → 契约指令直接失败)。
9957
10077
  // PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),可靠的是 `MORULA_CLI` 绝对路径。
9958
- // 见 issues/steward-cli-resolves-stale-bundled-morula.md。
10078
+ // MORULA_CLI 优先本包自带入口(与当前 daemon 同源),产物缺失才回退插件启动器。
10079
+ // 见 issues/steward-cli-resolves-stale-bundled-morula.md、issues/agent-cli-stale-build-missing-steward-commands.md。
9959
10080
  PATH: prependPluginBinToPath(process.env.PATH),
9960
- MORULA_CLI: pluginBinPath()
10081
+ MORULA_CLI: resolveAgentCliPath()
9961
10082
  };
9962
10083
  let repoDir = cfg.workdir;
9963
10084
  let taskRoot = cfg.workdir;
@@ -10118,6 +10239,8 @@ var DaemonRunner = class {
10118
10239
  dimWorkDir: taskRoot
10119
10240
  });
10120
10241
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5B8C\u6210`);
10242
+ if (signal?.aborted)
10243
+ await this.reportCancelTrace(taskId, result.finalText, "\u6536\u5C3E\u9636\u6BB5\u88AB\u53D6\u6D88");
10121
10244
  const isReviewTask = detail.kind === "review";
10122
10245
  const isAnalysisTask = detail.stageCode === "todo";
10123
10246
  if (!isReviewTask && !isAnalysisTask && isFlagEnabled("MORULA_LOCAL_AGENT_DELIVERY_V1", true))
@@ -10126,6 +10249,7 @@ var DaemonRunner = class {
10126
10249
  const error = result.error?.message ?? "dim \u6267\u884C\u5931\u8D25";
10127
10250
  if (signal?.aborted) {
10128
10251
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88\uFF0C\u8DF3\u8FC7\u7ED3\u679C\u56DE\u4F20: ${error}`);
10252
+ await this.reportCancelTrace(taskId, result.finalText, error);
10129
10253
  return;
10130
10254
  }
10131
10255
  await this.submitResultReliable(taskId, { ok: false, error });
@@ -10591,7 +10715,7 @@ var DaemonRunner = class {
10591
10715
  repoFullName: wc.repoFullName,
10592
10716
  branchName: wc.branchName
10593
10717
  };
10594
- const repoCachePath = buildRepoCachePath(cfg.workdir, this.tenantId, wc.repoFullName);
10718
+ const repoCachePath = buildRepoCachePath(cfg.workdir, this.tenantId, this.runtimeId, wc.repoFullName);
10595
10719
  const errorCode = validateRemovable(wc.worktreePath, expected, repoCachePath);
10596
10720
  if (errorCode) {
10597
10721
  this.log(`[daemon] cleanup ${item.id} \u62D2\u7EDD\u5220\u9664: ${errorCode}`);
@@ -10688,6 +10812,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
10688
10812
 
10689
10813
  - \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
10690
10814
  - \u5DE5\u4F5C\u76EE\u5F55\uFF1A\u672C\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF08\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\uFF1B\u4ED3\u5E93\u662F\u5B83\u4E0B\u9762\u7684\u5B50\u76EE\u5F55\uFF0Cgit \u64CD\u4F5C\u5728\u4ED3\u5E93\u5B50\u76EE\u5F55\u5185\uFF09
10815
+ - \u534F\u4F5C\u5951\u7EA6\uFF08\u5FC5\u8BFB\uFF09\uFF1A\`.agents/skills/morula-working-on-issues/SKILL.md\`\uFF08\u76F8\u5BF9\u672C\u76EE\u5F55\uFF09\u3002\u82E5 \`skill\` \u5DE5\u5177\u62A5 \`Unknown skill\`\uFF0C**\u76F4\u63A5\u8BFB\u8BE5\u6587\u4EF6**\uFF08read/cat\uFF09\uFF0C\u4E0D\u8981\u56E0\u4E3A\u52A0\u8F7D\u5931\u8D25\u5C31\u8DF3\u8FC7\u5951\u7EA6\u3002
10691
10816
 
10692
10817
  ---
10693
10818
 
@@ -11152,6 +11277,7 @@ function daemonDir(configPath) {
11152
11277
  }
11153
11278
  var PID_FILE = DAEMON_PID_FILE;
11154
11279
  var LOG_FILE = "daemon.log";
11280
+ var DAEMON_EXIT_FALLBACK_MS = 3e3;
11155
11281
  async function daemonCommand(context) {
11156
11282
  const { action } = context.parsed;
11157
11283
  if (action === "run")
@@ -11334,6 +11460,9 @@ async function models(context) {
11334
11460
  }
11335
11461
  async function run2(context) {
11336
11462
  rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent"]);
11463
+ const armBoundedExit = () => {
11464
+ setTimeout(() => process.exit(0), DAEMON_EXIT_FALLBACK_MS).unref();
11465
+ };
11337
11466
  const name = flagString(context.parsed.flags, "name") ?? void 0;
11338
11467
  const workdir = flagString(context.parsed.flags, "workdir") ?? void 0;
11339
11468
  const maxConcurrent = parseMaxConcurrent(context);
@@ -11349,6 +11478,7 @@ async function run2(context) {
11349
11478
  const pid = acquired.existing?.pid;
11350
11479
  if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
11351
11480
  context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
11481
+ armBoundedExit();
11352
11482
  return { running: false, yieldedTo: pid ?? null };
11353
11483
  }
11354
11484
  throw new CliError(
@@ -11389,12 +11519,14 @@ async function run2(context) {
11389
11519
  if (isAuthRejected(error)) {
11390
11520
  context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
11391
11521
  context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
11522
+ armBoundedExit();
11392
11523
  return { running: false, stoppedReason: "auth" };
11393
11524
  }
11394
11525
  throw error;
11395
11526
  } finally {
11396
11527
  releaseLock();
11397
11528
  }
11529
+ armBoundedExit();
11398
11530
  return { running: false };
11399
11531
  }
11400
11532
  function isAuthRejected(error) {
package/dist/morula.js CHANGED
@@ -704,8 +704,8 @@ function page(ok, message) {
704
704
  }
705
705
 
706
706
  // ../morula-cli-core/dist/build-env.js
707
- var BUILD_VERSION = "0.1.0-alpha.8";
708
- var BUILD_ID = "20260921065825.nogit";
707
+ var BUILD_VERSION = "0.1.0-alpha.9";
708
+ var BUILD_ID = "20260921135326.48e72dd8";
709
709
  var BUILD_CONTEXTS = {
710
710
  "local": "http://localhost:8080/api",
711
711
  "test": "https://morula.basecastle.com/api",
@@ -940,6 +940,28 @@ var CLI_CAPS_HEADER = "X-Morula-Cli-Caps";
940
940
  var CLI_BUILD_MAX_LENGTH = 64;
941
941
  var CLI_CAPS_MAX_LENGTH = 255;
942
942
  var DEFAULT_TIMEOUT_MS = 3e4;
943
+ var CAUSE_DETAIL_MAX_LENGTH = 200;
944
+ function describeFetchFailure(error) {
945
+ const message = error instanceof Error ? error.message : String(error);
946
+ const detail = formatFetchCause(error?.cause);
947
+ return detail ? `${message} (cause: ${detail})` : message;
948
+ }
949
+ function formatFetchCause(cause) {
950
+ if (cause === void 0 || cause === null)
951
+ return "";
952
+ if (cause instanceof Error) {
953
+ const detail = cause;
954
+ const parts = [
955
+ typeof detail.code === "string" ? detail.code : "",
956
+ typeof detail.errno === "number" || typeof detail.errno === "string" ? `errno=${detail.errno}` : "",
957
+ typeof detail.syscall === "string" ? `syscall=${detail.syscall}` : "",
958
+ // cause 的 message 常与外层重复('fetch failed'),只有不同才带
959
+ detail.message && detail.message !== "fetch failed" ? detail.message : ""
960
+ ].filter(Boolean);
961
+ return parts.join(" ").slice(0, CAUSE_DETAIL_MAX_LENGTH);
962
+ }
963
+ return String(cause).slice(0, CAUSE_DETAIL_MAX_LENGTH);
964
+ }
943
965
  var MorulaApiClient = class {
944
966
  baseUrl;
945
967
  token;
@@ -1037,7 +1059,7 @@ var MorulaApiClient = class {
1037
1059
  if (controller.signal.aborted) {
1038
1060
  throw new MorulaApiError("TIMEOUT", `request timed out after ${timeoutMs}ms`, EXIT_CODE.UPSTREAM, void 0, true);
1039
1061
  }
1040
- throw new MorulaApiError("NETWORK", `request failed: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
1062
+ throw new MorulaApiError("NETWORK", `request failed: ${describeFetchFailure(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
1041
1063
  } finally {
1042
1064
  clearTimeout(timer);
1043
1065
  }
@@ -3829,7 +3851,7 @@ var SPEC = {
3829
3851
  actions: [
3830
3852
  {
3831
3853
  action: "status",
3832
- summary: "pipeline runs + deployments for a task (or a space); hasCiConfig:false comes with a notice explaining the repo has no CI config (not an error); on the task face --repo filters runs client-side by repoFullName and echoes repoFilter (the server does NOT support repo filtering)",
3854
+ summary: 'pipeline runs + deployments for a task (or a space); hasCiConfig:false comes with a notice explaining the repo has no CI config (not an error); on the space face repos[] answers per repo (a multi-repo space never answers hasCiConfig for "the space" \u2014 it is null there); on the task face --repo filters runs client-side by repoFullName and echoes repoFilter (the server does NOT support repo filtering)',
3833
3855
  args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
3834
3856
  flags: [
3835
3857
  { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
@@ -3841,7 +3863,7 @@ var SPEC = {
3841
3863
  examples: ["morula ci status <taskId>", "morula ci status --space <id> --failed"],
3842
3864
  writes: "none",
3843
3865
  idempotent: true,
3844
- emits: ["ok", "taskId", "spaceId", "runs", "deployments", "hasCiConfig", "notice", "repoFilter"]
3866
+ emits: ["ok", "taskId", "spaceId", "runs", "deployments", "repos", "hasCiConfig", "notice", "repoFilter"]
3845
3867
  },
3846
3868
  {
3847
3869
  action: "log",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morula-cli",
3
- "version": "0.1.0-alpha.8",
3
+ "version": "0.1.0-alpha.9",
4
4
  "description": "Use Morula CLI task management from DimCode.",
5
5
  "interface": {
6
6
  "displayName": "Morula CLI",
@@ -1412,7 +1412,7 @@ CI/CD pipeline facts and pipeline operations: pipeline runs / job logs / deploym
1412
1412
 
1413
1413
  **用法**:`morula ci status [<taskId>] [--space <id>] [--repo <owner/name>] [--ref <branch>] [--failed] [--limit <n>]`
1414
1414
 
1415
- pipeline runs + deployments for a task (or a space); hasCiConfig:false comes with a notice explaining the repo has no CI config (not an error); on the task face --repo filters runs client-side by repoFullName and echoes repoFilter (the server does NOT support repo filtering)
1415
+ pipeline runs + deployments for a task (or a space); hasCiConfig:false comes with a notice explaining the repo has no CI config (not an error); on the space face repos[] answers per repo (a multi-repo space never answers hasCiConfig for "the space" — it is null there); on the task face --repo filters runs client-side by repoFullName and echoes repoFilter (the server does NOT support repo filtering)
1416
1416
 
1417
1417
  | 位置参数 | 必填 | 说明 |
1418
1418
  |---|---|---|
@@ -1433,7 +1433,7 @@ morula ci status <taskId>
1433
1433
  morula ci status --space <id> --failed
1434
1434
  ```
1435
1435
 
1436
- > `writes: none` · `interactive: no` · `idempotent: yes` · `emits: ok, taskId, spaceId, runs, deployments, hasCiConfig, notice, repoFilter`
1436
+ > `writes: none` · `interactive: no` · `idempotent: yes` · `emits: ok, taskId, spaceId, runs, deployments, repos, hasCiConfig, notice, repoFilter`
1437
1437
 
1438
1438
  ### `morula ci log`
1439
1439
 
@@ -26,6 +26,11 @@ allowed-tools: Bash(morula *), Bash(git *), Bash(gh *)
26
26
  - **空间仓库清单**(多仓库任务判断依据):`morula repo list --space <spaceId>`——提示词不列仓库,需要时自己查。
27
27
  - **「你的目标」**:系统提示「你的目标」段 = 本次任务的原始诉求,据此判断要做什么。
28
28
 
29
+ > **调 CLI 一律用 `$MORULA_CLI`**(daemon 注入的绝对路径,指向与本次运行同源的构建;下文所有 `morula xxx`
30
+ > 都按 `$MORULA_CLI xxx` 执行)。PATH 上的裸 `morula` 可能是宿主自带的旧版本(缺命令面、且不认会话/任务
31
+ > 凭证 → 写操作署名错误),不要用它;`env VAR=... $MORULA_CLI xxx` 这类 env 包装下同样可用。
32
+ > 仅当 `$MORULA_CLI` 为空时才退回裸 `morula`。
33
+
29
34
  > **工作目录 = 任务工作区根**:根下放 `AGENTS.md`(空间项目说明,已作为 project.instructions 自动加载)、
30
35
  > `.agents/`、`.agent_context/`、`.morula/`;**各仓库是工作区根下的子目录**(见 0.5)。
31
36
  > 下文的 `AGENTS.md`/`.agents/`/`.agent_context/` 路径都相对这个工作区根。
@@ -33,6 +38,11 @@ allowed-tools: Bash(morula *), Bash(git *), Bash(gh *)
33
38
  > **任务上下文文件**:同一套上下文也写在工作目录 `.agent_context/task-context.md`(与首条系统提示同源)。
34
39
  > 需要再次确认任务边界时**读取该文件**(`cat .agent_context/task-context.md`),不要凭记忆推断。
35
40
 
41
+ > **本契约自身的读取方式**:契约以项目级 skill 形式注入在 `.agents/skills/morula-working-on-issues/SKILL.md`。
42
+ > 用 `skill` 工具按名加载**可能报 `Unknown skill`**(该工具的发现范围不含项目级目录)——此时
43
+ > **直接读文件**(`cat .agents/skills/morula-working-on-issues/SKILL.md` 或 read 工具),
44
+ > **不要因为加载失败就跳过契约**(issues/injected-contract-skill-not-loadable-by-skill-tool.md)。
45
+
36
46
  详情见下文各节对 TaskID / 对外编号的具体用法。
37
47
 
38
48
  ## 0.1 通用行为约束
@@ -18,6 +18,7 @@
18
18
  | CLI 写评论(**契约禁止 agent 自行调用**:完成评论由服务端统一发送,见本文件第 5 条) | `morula comment add`(命令存在,但本契约要求不调用) | `packages/morula-cli/src/commands/comment.ts` |
19
19
  | CLI 任务详情(**无参**取本任务;带参取业务对象) | `morula task get` → `GET /agent-tasks/:id`;`morula task get <objectId>` → `GET /objects/:id` | `packages/morula-cli/src/commands/task.ts` |
20
20
  | daemon 注入凭据(agent 无需自行登录) | runner 注入 `env` 给 Provider 子进程 | `packages/runtime/src/daemon/runner.ts` |
21
+ | 调 CLI 用 `$MORULA_CLI`(绝对路径,与当前 daemon 同源;PATH 上的裸 `morula` 可能是宿主自带的旧版) | `resolveAgentCliPath`(优先本包 `dist/morula.js`,产物缺失回退插件启动器);注入点:任务链路 `env`、会话链路 `env` | `packages/runtime/src/daemon/plugin-register.ts`、`runner.ts`、`session-host.ts` |
21
22
  | 系统提示注入(身份与边界 = 阶段职责 / 目标 / 钥匙 taskId+spaceId / 工具入口指引);任务详情与仓库清单由 `morula task get`(无参)、`morula repo list --space` 获取 | `buildTaskPromptWithContract`(`runner.ts`,唯一进 dim 的 messages[0]);CLI `packages/morula-cli/src/commands/task.ts`(resolveTaskId → `GET /agent-tasks/:id`) | `packages/runtime/src/daemon/runner.ts` |
22
23
  | 运行中检查点(本回合内取用户意见) | CLI `morula task inbox [--peek]` → `GET /agent-tasks/:id/thread/inbox`(机器协议;默认取走即消费,落 `consumed` + 当回合号);契约时机见本 SKILL 1.3 | `packages/morula-cli/src/commands/task.ts`、`src/modules/agent-task/chat/object-agent-chat.controller.ts`、`object-agent-message.service.ts`(`getThreadInbox`) |
23
24
  | 运行中打断(同一会话重开一轮) | daemon 在安全点(`inFlightTools === 0`)`session/cancel` → `session/resume` → 用新意见重开 prompt;回合号与中断点上报为 `turn_started` / `turn_interrupted` 事件 | `packages/runtime/src/daemon/acp/dim-acp-executor.ts`(回合循环)、`runner.ts`(`buildSteerPort`)、`src/modules/agent-task/chat/object-agent-message.service.ts`(`steerDeliveryStrategy`) |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcships/morula-runtime",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.8",
4
+ "version": "0.1.0-alpha.9",
5
5
  "description": "Device-level CLI for the Morula local agent runtime (daemon/portal/models/auth) and the Morula business CLI (morula), distributed via npm",
6
6
  "bin": {
7
7
  "morula-runtime": "./bin/morula-runtime.js",