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

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/morula.js CHANGED
@@ -41,6 +41,11 @@ function parseArgs(argv) {
41
41
  const positionals = [];
42
42
  const flags = {};
43
43
  const bareFlags = [];
44
+ const valueOccurrences = {};
45
+ const recordValue = (key, value) => {
46
+ flags[key] = value;
47
+ (valueOccurrences[key] ??= []).push(value);
48
+ };
44
49
  let positionalOnly = false;
45
50
  for (let index = 0; index < argv.length; index += 1) {
46
51
  const value = argv[index];
@@ -62,7 +67,7 @@ function parseArgs(argv) {
62
67
  }
63
68
  const equal = value.indexOf("=");
64
69
  if (equal > 2) {
65
- flags[value.slice(2, equal)] = value.slice(equal + 1);
70
+ recordValue(value.slice(2, equal), value.slice(equal + 1));
66
71
  continue;
67
72
  }
68
73
  const key = value.slice(2);
@@ -70,21 +75,34 @@ function parseArgs(argv) {
70
75
  throw usage("invalid empty flag");
71
76
  const next = argv[index + 1];
72
77
  if (next !== void 0 && !next.startsWith("--")) {
73
- flags[key] = next;
78
+ recordValue(key, next);
74
79
  index += 1;
75
80
  } else {
76
81
  flags[key] = true;
77
82
  bareFlags.push(key);
78
83
  }
79
84
  }
85
+ const repeatedFlags = {};
86
+ for (const [key, values] of Object.entries(valueOccurrences)) {
87
+ if (values.length > 1)
88
+ repeatedFlags[key] = values;
89
+ }
80
90
  return {
81
91
  command: positionals.shift(),
82
92
  action: positionals.shift(),
83
93
  positionals,
84
94
  flags,
85
- bareFlags
95
+ bareFlags,
96
+ repeatedFlags
86
97
  };
87
98
  }
99
+ function flagStrings(parsed, name) {
100
+ const repeated = parsed.repeatedFlags[name];
101
+ if (repeated?.length)
102
+ return repeated;
103
+ const single = flagString(parsed.flags, name);
104
+ return single === void 0 ? [] : [single];
105
+ }
88
106
  function flagString(flags, ...names) {
89
107
  for (const name of names) {
90
108
  const value = flags[name];
@@ -686,8 +704,8 @@ function page(ok, message) {
686
704
  }
687
705
 
688
706
  // ../morula-cli-core/dist/build-env.js
689
- var BUILD_VERSION = "0.1.0-alpha.7";
690
- var BUILD_ID = "20260917073516.nogit";
707
+ var BUILD_VERSION = "0.1.0-alpha.8";
708
+ var BUILD_ID = "20260921065825.nogit";
691
709
  var BUILD_CONTEXTS = {
692
710
  "local": "http://localhost:8080/api",
693
711
  "test": "https://morula.basecastle.com/api",
@@ -695,6 +713,42 @@ var BUILD_CONTEXTS = {
695
713
  };
696
714
  var BUILD_DEFAULT_CONTEXT = "prod";
697
715
 
716
+ // ../morula-api-client/dist/agent-task.js
717
+ var AgentTaskApi = class {
718
+ client;
719
+ constructor(client) {
720
+ this.client = client;
721
+ }
722
+ /** 列出 AgentTask(objectId 必填;status/page/pageSize 可选) */
723
+ list(query = {}) {
724
+ return this.client.get("/agent-tasks", query);
725
+ }
726
+ /**
727
+ * 取消执行。
728
+ *
729
+ * 服务端语义(`agent-task.controller.ts` cancel,2026-09-20 起):
730
+ * - 成功 → 200 `{ ok: true, message: 'Task cancelled' }`;
731
+ * - 任务不存在 → **404** `NOT_FOUND`;
732
+ * - 状态不允许(`completed` / `cancelled`)→ **409** `CONFLICT`(带可读原因)。
733
+ *
734
+ * 即:**失败会 reject**(不再 200 假成功,见 issues/agent-task-cancel-returns-200-on-failure.md)——
735
+ * 调用方按异常/退出码判断,不要假定「调用返回了就是已取消」。
736
+ */
737
+ cancel(id) {
738
+ return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/cancel`);
739
+ }
740
+ /** 重跑失败/已取消的执行(服务端复制原任务重新入队,返回**新** taskId) */
741
+ rerun(id) {
742
+ return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/rerun`);
743
+ }
744
+ };
745
+ function requiredId(id) {
746
+ const normalized = id.trim();
747
+ if (!normalized)
748
+ throw new TypeError("agent task id is required");
749
+ return normalized;
750
+ }
751
+
698
752
  // ../morula-api-client/dist/attachment.js
699
753
  var AttachmentApi = class {
700
754
  client;
@@ -740,6 +794,13 @@ var AttachmentApi = class {
740
794
  getDownloadUrl(id) {
741
795
  return this.client.get(`/attachments/${encodeURIComponent(id)}/download-url`);
742
796
  }
797
+ /**
798
+ * 读取附件的解析产物全文(MinerU markdown)。
799
+ * 未解析/解析中/解析失败由服务端在返回体里说明,不当作 HTTP 错误。
800
+ */
801
+ content(id, maxChars) {
802
+ return this.client.get(`/attachments/${encodeURIComponent(id)}/content`, { maxChars });
803
+ }
743
804
  /** 更新附件关联的资源 ID */
744
805
  updateResourceId(id, resourceId) {
745
806
  return this.client.patch(`/attachments/${encodeURIComponent(id)}`, { resourceId });
@@ -770,6 +831,81 @@ var AuthApi = class {
770
831
  }
771
832
  };
772
833
 
834
+ // ../morula-api-client/dist/autopilot.js
835
+ var AutopilotApi = class {
836
+ client;
837
+ constructor(client) {
838
+ this.client = client;
839
+ }
840
+ list(query = {}) {
841
+ return this.client.get("/agent-autopilots", query);
842
+ }
843
+ create(input) {
844
+ return this.client.post("/agent-autopilots", input);
845
+ }
846
+ };
847
+
848
+ // ../morula-api-client/dist/ci.js
849
+ var CiApi = class {
850
+ client;
851
+ constructor(client) {
852
+ this.client = client;
853
+ }
854
+ /** 任务面 CI 视图(本任务关联的流水线运行 + 部署记录 + 归属依据) */
855
+ taskView(taskId, query = {}) {
856
+ return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci`, query);
857
+ }
858
+ /** 任务面部署记录(D3:仅展示,不驱动流转) */
859
+ taskDeployments(taskId) {
860
+ return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci/deployments`);
861
+ }
862
+ /** 空间面 CI 巡检视图(管家主入口;`ref` / `failed` / `limit` 为服务端过滤) */
863
+ spaceView(spaceId, query = {}) {
864
+ return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci`, query);
865
+ }
866
+ /** 空间面部署记录(D3:仅展示) */
867
+ spaceDeployments(spaceId) {
868
+ return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci/deployments`);
869
+ }
870
+ /**
871
+ * job 日志(按 job id;服务端反查所属运行)。
872
+ *
873
+ * 两个面共用本端点:任务凭证按 tenantId 收敛,会话凭证按 `connection.spaceId === scopeId` 收敛。
874
+ * `truncated` 显式返回——日志被尾部截断时调用方必须知道(不能把截断当全文)。
875
+ */
876
+ jobLog(jobId, query = {}) {
877
+ return this.client.get(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/log`, query);
878
+ }
879
+ /** 失败用例清单(平台可选能力:GitLab test_report 有、GitHub 无 → `CAPABILITY_UNSUPPORTED`) */
880
+ testReport(runId) {
881
+ return this.client.get(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/test-report`);
882
+ }
883
+ /**
884
+ * 重跑整条流水线的失败 job(Phase 3;开放面 = 人 + 管家)。
885
+ *
886
+ * 只允许 failure / cancelled 的运行,其余状态服务端回 `CI_RETRY_NOT_ALLOWED`(不是 4xx)。
887
+ * **非幂等**:每次调用都会在平台上产生一次新的重跑,重复调用不会去重。
888
+ */
889
+ retryRun(runId) {
890
+ return this.client.post(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/retry`);
891
+ }
892
+ /**
893
+ * 重跑单个 job(Phase 3;开放面 = 人 + 管家)。
894
+ *
895
+ * 服务端按 jobId 反查所属运行(`CI_JOB_NOT_FOUND` 覆盖「job 不存在」与「反查不到运行」两种情况);
896
+ * 状态门禁与 `retryRun` 相同。非幂等。
897
+ */
898
+ retryJob(jobId) {
899
+ return this.client.post(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/retry`);
900
+ }
901
+ };
902
+ function requiredId2(id, label) {
903
+ const normalized = id.trim();
904
+ if (!normalized)
905
+ throw new TypeError(`${label} is required`);
906
+ return normalized;
907
+ }
908
+
773
909
  // ../morula-api-client/dist/client.js
774
910
  var EXIT_CODE = {
775
911
  OK: 0,
@@ -787,14 +923,16 @@ var MorulaApiError = class extends Error {
787
923
  status;
788
924
  retryable;
789
925
  details;
926
+ errorCode;
790
927
  name = "MorulaApiError";
791
- constructor(code, message, exitCode, status2, retryable = false, details) {
928
+ constructor(code, message, exitCode, status2, retryable = false, details, errorCode) {
792
929
  super(message);
793
930
  this.code = code;
794
931
  this.exitCode = exitCode;
795
932
  this.status = status2;
796
933
  this.retryable = retryable;
797
934
  this.details = details;
935
+ this.errorCode = errorCode;
798
936
  }
799
937
  };
800
938
  var CLI_BUILD_HEADER = "X-Morula-Cli-Build";
@@ -960,28 +1098,35 @@ function isEnvelope(value) {
960
1098
  function errorFromResponse(status2, parsed, raw) {
961
1099
  const message = extractMessage(parsed, raw, status2);
962
1100
  const details = isEnvelope(parsed) ? parsed.data : parsed;
1101
+ const errorCode = extractErrorCode(parsed);
963
1102
  if (status2 === 400 || status2 === 422) {
964
- return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status2, false, details);
1103
+ return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status2, false, details, errorCode);
965
1104
  }
966
1105
  if (status2 === 401) {
967
- return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status2, false, details);
1106
+ return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status2, false, details, errorCode);
968
1107
  }
969
1108
  if (status2 === 403) {
970
- return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status2, false, details);
1109
+ return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status2, false, details, errorCode);
971
1110
  }
972
1111
  if (status2 === 404) {
973
- return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status2, false, details);
1112
+ return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status2, false, details, errorCode);
974
1113
  }
975
1114
  if (status2 === 409) {
976
- return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status2, false, details);
1115
+ return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status2, false, details, errorCode);
977
1116
  }
978
1117
  if (status2 === 429) {
979
- return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status2, true, details);
1118
+ return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status2, true, details, errorCode);
980
1119
  }
981
1120
  if (status2 >= 500) {
982
- return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status2, true, details);
1121
+ return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status2, true, details, errorCode);
983
1122
  }
984
- return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status2, false, details);
1123
+ return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status2, false, details, errorCode);
1124
+ }
1125
+ function extractErrorCode(parsed) {
1126
+ if (!parsed || typeof parsed !== "object")
1127
+ return void 0;
1128
+ const value = parsed.errorCode;
1129
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
985
1130
  }
986
1131
  function extractMessage(parsed, raw, status2) {
987
1132
  if (parsed && typeof parsed === "object" && "message" in parsed) {
@@ -1011,25 +1156,120 @@ var CommentApi = class {
1011
1156
  return this.client.get("/comments", params);
1012
1157
  }
1013
1158
  get(id) {
1014
- return this.client.get(`/comments/${encodeURIComponent(requiredId(id))}`);
1159
+ return this.client.get(`/comments/${encodeURIComponent(requiredId3(id))}`);
1015
1160
  }
1016
1161
  create(input) {
1017
1162
  return this.client.post("/comments", input);
1018
1163
  }
1019
1164
  update(id, input) {
1020
- return this.client.patch(`/comments/${encodeURIComponent(requiredId(id))}`, input);
1165
+ return this.client.patch(`/comments/${encodeURIComponent(requiredId3(id))}`, input);
1021
1166
  }
1022
1167
  delete(id) {
1023
- return this.client.delete(`/comments/${encodeURIComponent(requiredId(id))}`);
1168
+ return this.client.delete(`/comments/${encodeURIComponent(requiredId3(id))}`);
1024
1169
  }
1025
1170
  };
1026
- function requiredId(id) {
1171
+ function requiredId3(id) {
1027
1172
  const normalized = id.trim();
1028
1173
  if (!normalized)
1029
1174
  throw new TypeError("comment id is required");
1030
1175
  return normalized;
1031
1176
  }
1032
1177
 
1178
+ // ../morula-api-client/dist/dashboard.js
1179
+ var DashboardApi = class {
1180
+ client;
1181
+ constructor(client) {
1182
+ this.client = client;
1183
+ }
1184
+ get(spaceId) {
1185
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/dashboard`);
1186
+ }
1187
+ statistics(spaceId) {
1188
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/statistics`);
1189
+ }
1190
+ workload(spaceId) {
1191
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/workload`);
1192
+ }
1193
+ };
1194
+ function requiredId4(id) {
1195
+ const normalized = id.trim();
1196
+ if (!normalized)
1197
+ throw new TypeError("space id is required");
1198
+ return normalized;
1199
+ }
1200
+
1201
+ // ../morula-api-client/dist/document.js
1202
+ var DocumentApi = class {
1203
+ client;
1204
+ constructor(client) {
1205
+ this.client = client;
1206
+ }
1207
+ list(query = {}) {
1208
+ return this.client.get("/documents", query);
1209
+ }
1210
+ get(id) {
1211
+ return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}`);
1212
+ }
1213
+ /** 已解析归档的文档内容(Markdown 正文等) */
1214
+ artifacts(id) {
1215
+ return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}/artifacts`);
1216
+ }
1217
+ };
1218
+ function requiredId5(id) {
1219
+ const normalized = String(id).trim();
1220
+ if (!normalized)
1221
+ throw new TypeError("document id is required");
1222
+ return normalized;
1223
+ }
1224
+
1225
+ // ../morula-api-client/dist/object-meta.js
1226
+ var ObjectMetaApi = class {
1227
+ client;
1228
+ constructor(client) {
1229
+ this.client = client;
1230
+ }
1231
+ list(objectId) {
1232
+ return this.client.get(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta`);
1233
+ }
1234
+ set(objectId, key, value) {
1235
+ return this.client.request(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta/${encodeURIComponent(requiredKey(key))}`, { method: "PUT", body: { value } });
1236
+ }
1237
+ };
1238
+ function requiredId6(id) {
1239
+ const normalized = id.trim();
1240
+ if (!normalized)
1241
+ throw new TypeError("object id is required");
1242
+ return normalized;
1243
+ }
1244
+ function requiredKey(key) {
1245
+ const normalized = key.trim();
1246
+ if (!normalized)
1247
+ throw new TypeError("meta key is required");
1248
+ return normalized;
1249
+ }
1250
+
1251
+ // ../morula-api-client/dist/repo.js
1252
+ var RepoApi = class {
1253
+ client;
1254
+ constructor(client) {
1255
+ this.client = client;
1256
+ }
1257
+ /** 目录清单(path 缺省 = 仓库根) */
1258
+ files(spaceId, query = {}) {
1259
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/files`, query);
1260
+ }
1261
+ /** 单文件内容(path 必填) */
1262
+ read(spaceId, query) {
1263
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/file`, query);
1264
+ }
1265
+ };
1266
+ function requiredId7(id) {
1267
+ const normalized = id.trim();
1268
+ if (!normalized)
1269
+ throw new TypeError("space id is required");
1270
+ return normalized;
1271
+ }
1272
+
1033
1273
  // ../morula-api-client/dist/report.js
1034
1274
  var ReportApi = class {
1035
1275
  client;
@@ -1040,13 +1280,13 @@ var ReportApi = class {
1040
1280
  return this.client.post("/agent-reports", input);
1041
1281
  }
1042
1282
  get(id) {
1043
- return this.client.get(`/agent-reports/${encodeURIComponent(requiredId2(id))}`);
1283
+ return this.client.get(`/agent-reports/${encodeURIComponent(requiredId8(id))}`);
1044
1284
  }
1045
1285
  listByAutopilot(autopilotId, query = {}) {
1046
- return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId2(autopilotId, "autopilot"))}/reports`, { ...query });
1286
+ return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId8(autopilotId, "autopilot"))}/reports`, { ...query });
1047
1287
  }
1048
1288
  };
1049
- function requiredId2(id, label = "report") {
1289
+ function requiredId8(id, label = "report") {
1050
1290
  const normalized = id.trim();
1051
1291
  if (!normalized)
1052
1292
  throw new TypeError(`${label} id is required`);
@@ -1063,19 +1303,19 @@ var SpaceMemberApi = class {
1063
1303
  return this.client.get("/space-members", { spaceId, ...query });
1064
1304
  }
1065
1305
  get(id) {
1066
- return this.client.get(`/space-members/${encodeURIComponent(requiredId3(id))}`);
1306
+ return this.client.get(`/space-members/${encodeURIComponent(requiredId9(id))}`);
1067
1307
  }
1068
1308
  create(input) {
1069
1309
  return this.client.post("/space-members", input);
1070
1310
  }
1071
1311
  update(id, input) {
1072
- return this.client.patch(`/space-members/${encodeURIComponent(requiredId3(id))}`, input);
1312
+ return this.client.patch(`/space-members/${encodeURIComponent(requiredId9(id))}`, input);
1073
1313
  }
1074
1314
  delete(id) {
1075
- return this.client.delete(`/space-members/${encodeURIComponent(requiredId3(id))}`);
1315
+ return this.client.delete(`/space-members/${encodeURIComponent(requiredId9(id))}`);
1076
1316
  }
1077
1317
  };
1078
- function requiredId3(id) {
1318
+ function requiredId9(id) {
1079
1319
  const normalized = id.trim();
1080
1320
  if (!normalized)
1081
1321
  throw new TypeError("space member id is required");
@@ -1092,25 +1332,47 @@ var SpaceApi = class {
1092
1332
  return this.client.get("/spaces", query);
1093
1333
  }
1094
1334
  get(id) {
1095
- return this.client.get(`/spaces/${encodeURIComponent(requiredId4(id))}`);
1335
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId10(id))}`);
1096
1336
  }
1097
1337
  create(input) {
1098
1338
  return this.client.post("/spaces", input);
1099
1339
  }
1100
1340
  update(id, input) {
1101
- return this.client.patch(`/spaces/${encodeURIComponent(requiredId4(id))}`, input);
1341
+ return this.client.patch(`/spaces/${encodeURIComponent(requiredId10(id))}`, input);
1102
1342
  }
1103
1343
  delete(id) {
1104
- return this.client.delete(`/spaces/${encodeURIComponent(requiredId4(id))}`);
1344
+ return this.client.delete(`/spaces/${encodeURIComponent(requiredId10(id))}`);
1105
1345
  }
1106
1346
  };
1107
- function requiredId4(id) {
1347
+ function requiredId10(id) {
1108
1348
  const normalized = id.trim();
1109
1349
  if (!normalized)
1110
1350
  throw new TypeError("space id is required");
1111
1351
  return normalized;
1112
1352
  }
1113
1353
 
1354
+ // ../morula-api-client/dist/steward.js
1355
+ var StewardApi = class {
1356
+ client;
1357
+ constructor(client) {
1358
+ this.client = client;
1359
+ }
1360
+ /** 管家信息(未接管的空间返回 null) */
1361
+ get(projectId) {
1362
+ return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}`);
1363
+ }
1364
+ /** 派单:建任务并派发(目标状态由 state 决定,backlog 不派发执行) */
1365
+ dispatch(projectId, input) {
1366
+ return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}/dispatch`, input);
1367
+ }
1368
+ };
1369
+ function requiredId11(id) {
1370
+ const normalized = id.trim();
1371
+ if (!normalized)
1372
+ throw new TypeError("project id is required");
1373
+ return normalized;
1374
+ }
1375
+
1114
1376
  // ../morula-api-client/dist/task.js
1115
1377
  var TaskApi = class {
1116
1378
  client;
@@ -1121,16 +1383,16 @@ var TaskApi = class {
1121
1383
  return this.client.get("/objects", query);
1122
1384
  }
1123
1385
  get(id) {
1124
- return this.client.get(`/objects/${encodeURIComponent(requiredId5(id))}`);
1386
+ return this.client.get(`/objects/${encodeURIComponent(requiredId12(id))}`);
1125
1387
  }
1126
1388
  create(input) {
1127
1389
  return this.client.post("/objects", input);
1128
1390
  }
1129
1391
  update(id, input) {
1130
- return this.client.patch(`/objects/${encodeURIComponent(requiredId5(id))}`, input);
1392
+ return this.client.patch(`/objects/${encodeURIComponent(requiredId12(id))}`, input);
1131
1393
  }
1132
1394
  delete(id) {
1133
- return this.client.delete(`/objects/${encodeURIComponent(requiredId5(id))}`);
1395
+ return this.client.delete(`/objects/${encodeURIComponent(requiredId12(id))}`);
1134
1396
  }
1135
1397
  /** 查询空间字段模板(字段 key → id 映射、options) */
1136
1398
  getSchemaFields(spaceId) {
@@ -1159,7 +1421,7 @@ var TaskApi = class {
1159
1421
  return this.client.delete(`/object-field-values`, { objectId, schemaFieldId });
1160
1422
  }
1161
1423
  };
1162
- function requiredId5(id) {
1424
+ function requiredId12(id) {
1163
1425
  const normalized = id.trim();
1164
1426
  if (!normalized)
1165
1427
  throw new TypeError("task id is required");
@@ -1183,23 +1445,39 @@ var WebhookApi = class {
1183
1445
  // ../morula-api-client/dist/index.js
1184
1446
  var MorulaClient = class {
1185
1447
  http;
1448
+ agentTasks;
1186
1449
  attachments;
1187
1450
  auth;
1451
+ autopilots;
1452
+ ci;
1188
1453
  comments;
1454
+ dashboard;
1455
+ documents;
1456
+ objectMeta;
1189
1457
  reports;
1458
+ repos;
1190
1459
  tasks;
1191
1460
  spaces;
1192
1461
  spaceMembers;
1462
+ stewards;
1193
1463
  webhooks;
1194
1464
  constructor(options) {
1195
1465
  this.http = new MorulaApiClient(options);
1466
+ this.agentTasks = new AgentTaskApi(this.http);
1196
1467
  this.attachments = new AttachmentApi(this.http);
1197
1468
  this.auth = new AuthApi(this.http);
1469
+ this.autopilots = new AutopilotApi(this.http);
1470
+ this.ci = new CiApi(this.http);
1198
1471
  this.comments = new CommentApi(this.http);
1472
+ this.dashboard = new DashboardApi(this.http);
1473
+ this.documents = new DocumentApi(this.http);
1474
+ this.objectMeta = new ObjectMetaApi(this.http);
1199
1475
  this.reports = new ReportApi(this.http);
1476
+ this.repos = new RepoApi(this.http);
1200
1477
  this.tasks = new TaskApi(this.http);
1201
1478
  this.spaces = new SpaceApi(this.http);
1202
1479
  this.spaceMembers = new SpaceMemberApi(this.http);
1480
+ this.stewards = new StewardApi(this.http);
1203
1481
  this.webhooks = new WebhookApi(this.http);
1204
1482
  }
1205
1483
  };
@@ -1255,7 +1533,8 @@ function renderActionHelp(base, resource, action) {
1255
1533
  ...f.valueHint ? { valueHint: f.valueHint } : {},
1256
1534
  required: f.required ?? false,
1257
1535
  ...f.enum ? { enum: f.enum } : {},
1258
- ...f.default !== void 0 ? { default: f.default } : {}
1536
+ ...f.default !== void 0 ? { default: f.default } : {},
1537
+ ...f.repeatable ? { repeatable: true } : {}
1259
1538
  })),
1260
1539
  examples: action.examples ?? [],
1261
1540
  writes: action.writes ?? "none",
@@ -1396,6 +1675,16 @@ function validateArgs(resource, action, parsed, options = {}) {
1396
1675
  hint: `${usageHint} (e.g. --${flag} ${valuePart})`
1397
1676
  });
1398
1677
  }
1678
+ for (const [name, values] of Object.entries(parsed.repeatedFlags)) {
1679
+ if (values.length < 2)
1680
+ continue;
1681
+ const spec = declaredFlags.find((f) => f.name === name);
1682
+ if (spec?.repeatable)
1683
+ continue;
1684
+ throw usage(`duplicate flag --${name} (given ${values.length} times; only the last value would be used)`, {
1685
+ hint: usageHint
1686
+ });
1687
+ }
1399
1688
  for (const spec of declaredFlags) {
1400
1689
  if (!spec.required)
1401
1690
  continue;
@@ -1515,6 +1804,8 @@ function resolveToken(flags, env, context, baseUrl, saved, options) {
1515
1804
  return { token: explicitToken, tokenSource: "flag" };
1516
1805
  if (!options.excludeTaskToken && env.MORULA_TASK_TOKEN)
1517
1806
  return { token: env.MORULA_TASK_TOKEN, tokenSource: "env:MORULA_TASK_TOKEN" };
1807
+ if (!options.excludeTaskToken && env.MORULA_SESSION_TOKEN)
1808
+ return { token: env.MORULA_SESSION_TOKEN, tokenSource: "env:MORULA_SESSION_TOKEN" };
1518
1809
  if (env.MORULA_TOKEN)
1519
1810
  return { token: env.MORULA_TOKEN, tokenSource: "env:MORULA_TOKEN" };
1520
1811
  const savedToken = saved?.url && normalizeBaseUrl(saved.url) === baseUrl ? saved.token : void 0;
@@ -1627,6 +1918,10 @@ async function run(argv, deps = {}) {
1627
1918
  code: error.code,
1628
1919
  message: error.message,
1629
1920
  ...error.retryable ? { retryable: true } : {},
1921
+ // 服务端业务错误码(如 SESSION_SCOPE_DENIED):`code` 是按 HTTP 状态派生的粗档位,
1922
+ // 行为契约要判断的是业务码——不打印出来,契约就永远不可执行
1923
+ // (issues/api-client-drops-business-error-code.md)。退出码仍由 `error.exitCode` 决定。
1924
+ ...error instanceof MorulaApiError && error.errorCode ? { errorCode: error.errorCode } : {},
1630
1925
  // 新增字段(可选):可执行的下一步 + typo 建议
1631
1926
  ...error instanceof CliError && error.hint ? { hint: error.hint } : {},
1632
1927
  ...error instanceof CliError && error.didYouMean ? { didYouMean: error.didYouMean } : {}
@@ -1838,6 +2133,13 @@ async function attachmentCommand(context) {
1838
2133
  rejectUnknownFlags(flags, []);
1839
2134
  return context.client.attachments.get(requireId(positionals, "attachment get"));
1840
2135
  }
2136
+ if (action === "content") {
2137
+ rejectUnknownFlags(flags, ["max-chars"]);
2138
+ return context.client.attachments.content(
2139
+ requireId(positionals, "attachment content"),
2140
+ flagNumber(flags, "max-chars")
2141
+ );
2142
+ }
1841
2143
  if (action === "download-url") {
1842
2144
  rejectUnknownFlags(flags, []);
1843
2145
  return context.client.attachments.getDownloadUrl(requireId(positionals, "attachment download-url"));
@@ -1855,6 +2157,198 @@ function requireId(positionals, command) {
1855
2157
  return id;
1856
2158
  }
1857
2159
 
2160
+ // ../morula-cli/src/commands/autopilot.ts
2161
+ async function autopilotCommand(context) {
2162
+ if (!context.runtime.token)
2163
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2164
+ const { action, flags } = context.parsed;
2165
+ if (action === "list") {
2166
+ rejectUnknownFlags(flags, ["space", "page", "page-size"]);
2167
+ return context.client.autopilots.list({
2168
+ spaceId: flagString(flags, "space"),
2169
+ page: flagNumber(flags, "page"),
2170
+ pageSize: flagNumber(flags, "page-size")
2171
+ });
2172
+ }
2173
+ if (action === "create") {
2174
+ rejectUnknownFlags(flags, ["space", "name", "runbook", "executor", "description", "cron", "vcs-connection"]);
2175
+ const spaceId = requireFlag(flags, "space");
2176
+ const name = requireFlag(flags, "name");
2177
+ const runbook = requireFlag(flags, "runbook");
2178
+ const executorKey = requireFlag(flags, "executor");
2179
+ const description = flagString(flags, "description");
2180
+ const cronExpr = flagString(flags, "cron");
2181
+ const vcsConnectionId = flagString(flags, "vcs-connection");
2182
+ return context.client.autopilots.create({
2183
+ spaceId,
2184
+ name,
2185
+ runbook,
2186
+ executorKey,
2187
+ ...description !== void 0 ? { description } : {},
2188
+ ...cronExpr !== void 0 ? { cronExpr } : {},
2189
+ ...vcsConnectionId !== void 0 ? { vcsConnectionId } : {}
2190
+ });
2191
+ }
2192
+ throw usage("usage: morula autopilot <list|create>");
2193
+ }
2194
+
2195
+ // ../morula-cli/src/commands/ci.ts
2196
+ async function ciCommand(context) {
2197
+ if (!context.runtime.token)
2198
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2199
+ const { action } = context.parsed;
2200
+ if (action === "status")
2201
+ return ciStatus(context);
2202
+ if (action === "log")
2203
+ return ciLog(context);
2204
+ if (action === "deployments")
2205
+ return ciDeployments(context);
2206
+ if (action === "test-report")
2207
+ return ciTestReport(context);
2208
+ if (action === "retry")
2209
+ return ciRetry(context);
2210
+ throw usage("usage: morula ci <status|log|deployments|test-report|retry>");
2211
+ }
2212
+ async function ciStatus(context) {
2213
+ const { positionals, flags } = context.parsed;
2214
+ rejectUnknownFlags(flags, ["space", "repo", "ref", "failed", "limit"]);
2215
+ const repo = flagString(flags, "repo");
2216
+ const ref = flagString(flags, "ref");
2217
+ const failed = flagBoolean(flags, "failed");
2218
+ const limit = flagNumber(flags, "limit");
2219
+ if (limit != null && (!Number.isInteger(limit) || limit <= 0))
2220
+ throw usage("--limit \u5FC5\u987B\u662F\u6B63\u6574\u6570");
2221
+ const face = resolveFace(context, "usage: morula ci status <taskId> [--repo <owner/name>] [--limit <n>] | morula ci status --space <spaceId> [--ref <branch>] [--failed] [--limit <n>]");
2222
+ if (face.kind === "space") {
2223
+ if (repo)
2224
+ throw usage("--repo \u53EA\u5728\u4EFB\u52A1\u9762\u53EF\u7528\uFF1A\u7A7A\u95F4\u9762\u8BF7\u7528 --ref \u8FC7\u6EE4\u5206\u652F\uFF08\u670D\u52A1\u7AEF\u4E0D\u652F\u6301\u6309\u4ED3\u5E93\u8FC7\u6EE4\uFF09");
2225
+ const result2 = await context.client.ci.spaceView(face.spaceId, {
2226
+ ...ref ? { ref } : {},
2227
+ ...failed ? { failed: true } : {},
2228
+ ...limit ? { limit } : {}
2229
+ });
2230
+ if (result2.ok === false)
2231
+ throw ciFailure(result2);
2232
+ return result2;
2233
+ }
2234
+ const result = await context.client.ci.taskView(face.taskId, { ...limit ? { limit } : {} });
2235
+ if (result.ok === false)
2236
+ throw ciFailure(result);
2237
+ if (!repo)
2238
+ return result;
2239
+ const runs = result.runs.filter((run2) => run2.repoFullName === repo);
2240
+ return { ...result, runs, repoFilter: { repo, total: result.runs.length, matched: runs.length } };
2241
+ }
2242
+ async function ciLog(context) {
2243
+ const { flags } = context.parsed;
2244
+ rejectUnknownFlags(flags, ["space", "job", "tail"]);
2245
+ const jobId = flagString(flags, "job");
2246
+ if (!jobId)
2247
+ throw usage("usage: morula ci log <taskId> --job <jobId> [--tail <n>] | morula ci log --space <spaceId> --job <jobId> [--tail <n>]");
2248
+ const tail = flagNumber(flags, "tail");
2249
+ if (tail != null && (!Number.isInteger(tail) || tail <= 0))
2250
+ throw usage("--tail \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08\u4FDD\u7559\u7684\u65E5\u5FD7\u5C3E\u90E8\u5B57\u8282\u6570\uFF09");
2251
+ resolveFace(context, "usage: morula ci log <taskId> --job <jobId> [--tail <n>] | morula ci log --space <spaceId> --job <jobId> [--tail <n>]");
2252
+ const result = await context.client.ci.jobLog(jobId, { ...tail ? { tail } : {} });
2253
+ if (result.ok === false)
2254
+ throw ciFailure(result);
2255
+ return result;
2256
+ }
2257
+ async function ciDeployments(context) {
2258
+ const { flags } = context.parsed;
2259
+ rejectUnknownFlags(flags, ["space"]);
2260
+ const face = resolveFace(context, "usage: morula ci deployments <taskId> | morula ci deployments --space <spaceId>");
2261
+ const result = face.kind === "space" ? await context.client.ci.spaceDeployments(face.spaceId) : await context.client.ci.taskDeployments(face.taskId);
2262
+ if (result.ok === false)
2263
+ throw ciFailure(result);
2264
+ return result;
2265
+ }
2266
+ async function ciTestReport(context) {
2267
+ const { flags } = context.parsed;
2268
+ rejectUnknownFlags(flags, ["space", "run"]);
2269
+ const runFlag = flagString(flags, "run");
2270
+ const face = resolveFace(context, "usage: morula ci test-report <taskId> [--run <runId>] | morula ci test-report --space <spaceId> [--run <runId>]");
2271
+ let runId = runFlag;
2272
+ if (!runId) {
2273
+ const view = face.kind === "space" ? await context.client.ci.spaceView(face.spaceId) : await context.client.ci.taskView(face.taskId);
2274
+ if (view.ok === false)
2275
+ throw ciFailure(view);
2276
+ runId = view.runs[0]?.id;
2277
+ if (!runId) {
2278
+ throw new CliError(
2279
+ "CI_RUN_NOT_FOUND",
2280
+ EXIT.NOT_FOUND,
2281
+ face.kind === "space" ? "\u8BE5\u7A7A\u95F4\u6CA1\u6709\u6D41\u6C34\u7EBF\u8FD0\u884C\u8BB0\u5F55\uFF0C\u53D6\u4E0D\u5230\u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u53EF\u5148\u7528 morula ci status --space <id> \u786E\u8BA4\uFF09" : "\u672C\u4EFB\u52A1\u6CA1\u6709\u6D41\u6C34\u7EBF\u8FD0\u884C\u8BB0\u5F55\uFF0C\u53D6\u4E0D\u5230\u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u53EF\u5148\u7528 morula ci status <taskId> \u786E\u8BA4\uFF09"
2282
+ );
2283
+ }
2284
+ }
2285
+ const result = await context.client.ci.testReport(runId);
2286
+ if (result.ok === false)
2287
+ throw ciFailure(result);
2288
+ return result;
2289
+ }
2290
+ async function ciRetry(context) {
2291
+ const { positionals, flags } = context.parsed;
2292
+ rejectUnknownFlags(flags, ["space", "run", "job"]);
2293
+ const usageHint = "usage: morula ci retry --space <spaceId> [--run <runId>] [--job <jobId>]";
2294
+ if (positionals.length)
2295
+ throw usage(`\u91CD\u8DD1\u53EA\u5728\u7A7A\u95F4\u9762\u5F00\u653E\uFF1A\u8BF7\u7528 --space <spaceId>\uFF08\u4EFB\u52A1\u9762\u4E0D\u63D0\u4F9B retry\uFF09\u3002${usageHint}`);
2296
+ const runId = flagString(flags, "run");
2297
+ const jobId = flagString(flags, "job");
2298
+ if (runId && jobId)
2299
+ throw usage(`--run \u4E0E --job \u4E92\u65A5\uFF0C\u53EA\u80FD\u7ED9\u4E00\u4E2A\uFF08${usageHint}\uFF09`);
2300
+ const spaceId = flagString(flags, "space");
2301
+ if (!spaceId)
2302
+ throw usage(usageHint);
2303
+ if (jobId) {
2304
+ const result2 = await context.client.ci.retryJob(jobId);
2305
+ if (result2.ok === false)
2306
+ throw ciFailure(result2, "\u64CD\u4F5C");
2307
+ return result2;
2308
+ }
2309
+ let targetRunId = runId;
2310
+ if (!targetRunId) {
2311
+ const view = await context.client.ci.spaceView(spaceId, { failed: true });
2312
+ if (view.ok === false)
2313
+ throw ciFailure(view);
2314
+ targetRunId = view.runs[0]?.id;
2315
+ if (!targetRunId) {
2316
+ throw new CliError(
2317
+ "CI_RUN_NOT_FOUND",
2318
+ EXIT.NOT_FOUND,
2319
+ `\u7A7A\u95F4 ${spaceId} \u6CA1\u6709\u5931\u8D25\u7684\u6D41\u6C34\u7EBF\u8FD0\u884C\u53EF\u91CD\u8DD1\uFF08\u53EF\u5148\u7528 morula ci status --space ${spaceId} --failed \u786E\u8BA4\uFF09`
2320
+ );
2321
+ }
2322
+ }
2323
+ const result = await context.client.ci.retryRun(targetRunId);
2324
+ if (result.ok === false)
2325
+ throw ciFailure(result, "\u64CD\u4F5C");
2326
+ return result;
2327
+ }
2328
+ function resolveFace(context, usageHint) {
2329
+ const taskId = context.parsed.positionals[0]?.trim();
2330
+ const spaceId = flagString(context.parsed.flags, "space");
2331
+ if (taskId && spaceId)
2332
+ throw usage(`<taskId> \u4E0E --space \u4E92\u65A5\uFF0C\u53EA\u80FD\u7ED9\u4E00\u4E2A\uFF08${usageHint}\uFF09`);
2333
+ if (!taskId && !spaceId)
2334
+ throw usage(usageHint);
2335
+ return taskId ? { kind: "task", taskId } : { kind: "space", spaceId };
2336
+ }
2337
+ function ciFailure(failure, verb = "\u67E5\u8BE2") {
2338
+ return new CliError(
2339
+ failure.code,
2340
+ exitForCiCode(failure.code),
2341
+ `CI ${verb}\u5931\u8D25: ${failure.message || "\u672A\u77E5\u539F\u56E0"}\uFF08${failure.code}\uFF09`
2342
+ );
2343
+ }
2344
+ function exitForCiCode(code) {
2345
+ if (code === "CI_RETRY_NOT_ALLOWED" || code === "CI_JOB_NOT_MANUAL")
2346
+ return EXIT.CONFLICT;
2347
+ if (code === "CI_JOB_NOT_FOUND" || code === "CI_RUN_NOT_FOUND" || code === "TASK_NOT_FOUND" || code === "OBJECT_NOT_FOUND")
2348
+ return EXIT.NOT_FOUND;
2349
+ return EXIT.OTHER;
2350
+ }
2351
+
1858
2352
  // ../morula-cli/src/commands/comment.ts
1859
2353
  function detectCommentFormat(text) {
1860
2354
  if (!text)
@@ -1888,16 +2382,19 @@ async function commentCommand(context) {
1888
2382
  return context.client.comments.get(id);
1889
2383
  }
1890
2384
  if (action === "add") {
1891
- rejectUnknownFlags(flags, ["object", "content", "parent", "format"]);
2385
+ rejectUnknownFlags(flags, ["object", "content", "parent", "format", "mention"]);
1892
2386
  const objectId = requireFlag(flags, "object");
1893
2387
  const content = requireFlag(flags, "content");
1894
2388
  const formatFlag = flagString(flags, "format");
1895
2389
  const format = formatFlag === "markdown" || formatFlag === "plain" || formatFlag === "richtext" ? formatFlag : detectCommentFormat(content);
2390
+ const mentions = parseMentions(flagStrings(context.parsed, "mention"));
1896
2391
  const input = compact({
1897
2392
  objectId,
1898
2393
  content,
1899
2394
  parentCommentId: flagString(flags, "parent"),
1900
- contentFormat: format
2395
+ contentFormat: format,
2396
+ // 无 --mention 时**不带** mentions 字段(服务端按缺省处理,不发任何通知)
2397
+ ...mentions.length ? { mentions } : {}
1901
2398
  });
1902
2399
  return context.client.comments.create(input);
1903
2400
  }
@@ -1923,6 +2420,89 @@ async function commentCommand(context) {
1923
2420
  function compact(value) {
1924
2421
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
1925
2422
  }
2423
+ function parseMentions(rawValues) {
2424
+ const seen = /* @__PURE__ */ new Set();
2425
+ const mentions = [];
2426
+ for (const raw of rawValues) {
2427
+ for (const part of raw.split(",")) {
2428
+ const principalId = part.trim();
2429
+ if (!principalId || seen.has(principalId))
2430
+ continue;
2431
+ seen.add(principalId);
2432
+ mentions.push({ type: "member", principalId });
2433
+ }
2434
+ }
2435
+ return mentions;
2436
+ }
2437
+
2438
+ // ../morula-cli/src/commands/dashboard.ts
2439
+ async function dashboardCommand(context) {
2440
+ if (!context.runtime.token)
2441
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2442
+ const { action, flags } = context.parsed;
2443
+ if (action === "get") {
2444
+ rejectUnknownFlags(flags, ["space"]);
2445
+ return context.client.dashboard.get(requireFlag(flags, "space"));
2446
+ }
2447
+ if (action === "statistics") {
2448
+ rejectUnknownFlags(flags, ["space"]);
2449
+ return context.client.dashboard.statistics(requireFlag(flags, "space"));
2450
+ }
2451
+ if (action === "workload") {
2452
+ rejectUnknownFlags(flags, ["space"]);
2453
+ return context.client.dashboard.workload(requireFlag(flags, "space"));
2454
+ }
2455
+ throw usage("usage: morula dashboard <get|statistics|workload>");
2456
+ }
2457
+
2458
+ // ../morula-cli/src/commands/document.ts
2459
+ async function documentCommand(context) {
2460
+ if (!context.runtime.token)
2461
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2462
+ const { action, positionals, flags } = context.parsed;
2463
+ if (action === "list") {
2464
+ rejectUnknownFlags(flags, ["page", "page-size"]);
2465
+ return context.client.documents.list({
2466
+ page: flagNumber(flags, "page"),
2467
+ pageSize: flagNumber(flags, "page-size")
2468
+ });
2469
+ }
2470
+ if (action === "get") {
2471
+ rejectUnknownFlags(flags, []);
2472
+ return context.client.documents.get(requireId2(positionals, "document get"));
2473
+ }
2474
+ if (action === "artifacts") {
2475
+ rejectUnknownFlags(flags, []);
2476
+ return context.client.documents.artifacts(requireId2(positionals, "document artifacts"));
2477
+ }
2478
+ throw usage("usage: morula document <list|get|artifacts>");
2479
+ }
2480
+ function requireId2(positionals, command) {
2481
+ const id = positionals[0]?.trim();
2482
+ if (!id)
2483
+ throw usage(`usage: morula ${command} <id>`);
2484
+ return id;
2485
+ }
2486
+
2487
+ // ../morula-cli/src/commands/object-meta.ts
2488
+ async function objectMetaCommand(context) {
2489
+ if (!context.runtime.token)
2490
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2491
+ const { action, flags } = context.parsed;
2492
+ if (action === "get") {
2493
+ rejectUnknownFlags(flags, ["object"]);
2494
+ return context.client.objectMeta.list(requireFlag(flags, "object"));
2495
+ }
2496
+ if (action === "set") {
2497
+ rejectUnknownFlags(flags, ["object", "key", "value"]);
2498
+ return context.client.objectMeta.set(
2499
+ requireFlag(flags, "object"),
2500
+ requireFlag(flags, "key"),
2501
+ requireFlag(flags, "value")
2502
+ );
2503
+ }
2504
+ throw usage("usage: morula object-meta <get|set>");
2505
+ }
1926
2506
 
1927
2507
  // ../morula-cli/src/commands/repo.ts
1928
2508
  import { execFileSync } from "node:child_process";
@@ -2000,7 +2580,30 @@ async function repoCommand(context) {
2000
2580
  if (action === "mr" && positionals[0] === "diff") {
2001
2581
  return mrDiff(context, flags);
2002
2582
  }
2003
- throw usage("usage: morula repo <list|checkout|prepare|mr create|mr list|mr comment|mr get|mr diff>");
2583
+ if (action === "files") {
2584
+ rejectUnknownFlags(flags, ["space", "repo", "path", "ref"]);
2585
+ const spaceId = flagString(flags, "space");
2586
+ if (!spaceId)
2587
+ throw usage("usage: morula repo files --space <id> [--repo <owner/name>] [--path <p>] [--ref <r>]");
2588
+ return context.client.repos.files(spaceId, {
2589
+ repo: flagString(flags, "repo"),
2590
+ path: flagString(flags, "path"),
2591
+ ref: flagString(flags, "ref")
2592
+ });
2593
+ }
2594
+ if (action === "read") {
2595
+ rejectUnknownFlags(flags, ["space", "repo", "path", "ref"]);
2596
+ const spaceId = flagString(flags, "space");
2597
+ const path = flagString(flags, "path");
2598
+ if (!spaceId || !path)
2599
+ throw usage("usage: morula repo read --space <id> --path <p> [--repo <owner/name>] [--ref <r>]");
2600
+ return context.client.repos.read(spaceId, {
2601
+ path,
2602
+ repo: flagString(flags, "repo"),
2603
+ ref: flagString(flags, "ref")
2604
+ });
2605
+ }
2606
+ throw usage("usage: morula repo <list|checkout|prepare|files|read|mr create|mr list|mr comment|mr get|mr diff>");
2004
2607
  }
2005
2608
  function resolveMrBase(flagBase, env = process.env) {
2006
2609
  return env.MORULA_BASE_BRANCH?.trim() || flagBase?.trim() || "main";
@@ -2114,7 +2717,7 @@ async function mrCreateViaPushOptions(input) {
2114
2717
  const list = listRes.ok ? await listRes.json().catch(() => null) : null;
2115
2718
  const existing = Array.isArray(list) ? list[0] : null;
2116
2719
  if (existing?.iid) {
2117
- console.log("[morula-cli] mr create \u5206\u652F\u5DF2\u6709 opened MR\uFF0C\u590D\u7528\u73B0\u6709 MR");
2720
+ process.stderr.write("[morula-cli] mr create \u5206\u652F\u5DF2\u6709 opened MR\uFF0C\u590D\u7528\u73B0\u6709 MR\n");
2118
2721
  return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl: existing.web_url ?? `${gitlabBase}/${repoFullName}/-/merge_requests/${existing.iid}` };
2119
2722
  }
2120
2723
  if (listRes.ok) {
@@ -2499,7 +3102,901 @@ async function spaceCommand(context) {
2499
3102
  throw usage("usage: morula space get <id>");
2500
3103
  return context.client.spaces.get(id);
2501
3104
  }
2502
- throw usage("usage: morula space <list|get>");
3105
+ if (action === "create") {
3106
+ rejectUnknownFlags(flags, ["name", "description", "code", "visibility"]);
3107
+ return context.client.spaces.create({
3108
+ name: requireFlag(flags, "name"),
3109
+ description: flagString(flags, "description"),
3110
+ code: flagString(flags, "code"),
3111
+ visibility: flagString(flags, "visibility")
3112
+ });
3113
+ }
3114
+ if (action === "update") {
3115
+ rejectUnknownFlags(flags, ["name", "description", "visibility"]);
3116
+ const id = requireId3(positionals, "space update");
3117
+ const input = {
3118
+ name: flagString(flags, "name"),
3119
+ description: flagString(flags, "description"),
3120
+ visibility: flagString(flags, "visibility")
3121
+ };
3122
+ if (Object.values(input).every((value) => value === void 0))
3123
+ throw usage("space update \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B57\u6BB5\u65D7\u6807\uFF08--name / --description / --visibility\uFF09");
3124
+ return context.client.spaces.update(id, input);
3125
+ }
3126
+ if (action === "delete") {
3127
+ rejectUnknownFlags(flags, []);
3128
+ return context.client.spaces.delete(requireId3(positionals, "space delete"));
3129
+ }
3130
+ throw usage("usage: morula space <list|get|create|update|delete>");
3131
+ }
3132
+ function requireId3(positionals, command) {
3133
+ const id = positionals[0]?.trim();
3134
+ if (!id)
3135
+ throw usage(`usage: morula ${command} <id>`);
3136
+ return id;
3137
+ }
3138
+
3139
+ // ../morula-cli/src/commands/spec.ts
3140
+ var SPEC = {
3141
+ name: "morula",
3142
+ summary: "Agent-first CLI for Morula task management (plugin surface, injected into task workspaces). Success output is JSON on stdout; errors are JSON on stderr.",
3143
+ resources: [
3144
+ {
3145
+ resource: "auth",
3146
+ summary: "SkyDoor authentication (shared config with morula-runtime)",
3147
+ actions: [
3148
+ {
3149
+ action: "login",
3150
+ summary: "browser SkyDoor SSO using PKCE and loopback callback (writes ~/.morula-cli/config.yaml)",
3151
+ flags: [{ name: "url", type: "string", valueHint: "<baseUrl>", description: "server base URL to log in to" }],
3152
+ examples: ["morula auth login"],
3153
+ writes: "local",
3154
+ interactive: true,
3155
+ emits: ["loggedIn", "context", "baseUrl", "expiresIn"]
3156
+ },
3157
+ {
3158
+ action: "logout",
3159
+ summary: "remove the current context token",
3160
+ flags: [],
3161
+ examples: ["morula auth logout"],
3162
+ writes: "local",
3163
+ idempotent: true,
3164
+ emits: ["loggedIn", "context"]
3165
+ },
3166
+ {
3167
+ action: "status",
3168
+ summary: "show context, endpoint, and authentication state",
3169
+ flags: [],
3170
+ examples: ["morula auth status"],
3171
+ writes: "none",
3172
+ idempotent: true,
3173
+ emits: ["loggedIn", "context", "baseUrl", "user", "expired"]
3174
+ }
3175
+ ]
3176
+ },
3177
+ {
3178
+ resource: "task",
3179
+ summary: "task (business Object) read/write + AgentTask machine protocol (branch/state/analysis-verdict/inbox)",
3180
+ actions: [
3181
+ {
3182
+ action: "list",
3183
+ summary: "list tasks (--mine: \u4EC5\u6211\u8D1F\u8D23\u7684\uFF1B--field key=value \u81EA\u5B9A\u4E49\u5B57\u6BB5\u7B5B\u9009\uFF0C\u9700 --space)",
3184
+ flags: [
3185
+ { name: "space", type: "string", valueHint: "<id>", description: "space id" },
3186
+ { name: "mine", type: "boolean", description: "\u4EC5\u6211\u8D1F\u8D23\u7684\uFF08\u4E0E --assignee \u4E92\u65A5\uFF09" },
3187
+ { name: "assignee", type: "string", valueHint: "<principalId>", description: "\u6309\u8D1F\u8D23\u4EBA\u8FC7\u6EE4" },
3188
+ { name: "principal", type: "string", valueHint: "<id>", description: "\u6309\u521B\u5EFA\u4EBA\u8FC7\u6EE4" },
3189
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3190
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" },
3191
+ { name: "search", type: "string", valueHint: "<text>", description: "\u5173\u952E\u8BCD\u641C\u7D22" },
3192
+ { name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7\u8FC7\u6EE4" },
3193
+ { name: "state", type: "string", valueHint: "<id>", description: "\u72B6\u6001\u8FC7\u6EE4" },
3194
+ { name: "sort", type: "string", valueHint: "<field>", description: "\u6392\u5E8F\u5B57\u6BB5" },
3195
+ { name: "order", type: "string", valueHint: "<asc|desc>", description: "\u6392\u5E8F\u65B9\u5411" },
3196
+ { name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5\u7B49\u503C\u7B5B\u9009\uFF08\u591A\u6761\u4EF6 AND\uFF1B\u9700 --space \u89E3\u6790\u5B57\u6BB5\u6A21\u677F\uFF09" }
3197
+ ],
3198
+ examples: ["morula task list --space <id>", "morula task list --mine", "morula task list --space <id> --field priority=p1"],
3199
+ writes: "none",
3200
+ idempotent: true
3201
+ },
3202
+ {
3203
+ action: "create",
3204
+ summary: "create task (\u5FC5\u586B --space/--title\uFF1B\u843D backlog \u7531\u670D\u52A1\u7AEF\u51B3\u5B9A\u521D\u59CB\u72B6\u6001)",
3205
+ flags: [
3206
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3207
+ { name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
3208
+ { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0\uFF08\u683C\u5F0F\u81EA\u52A8\u68C0\u6D4B\uFF09" },
3209
+ { name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7" },
3210
+ { name: "state", type: "string", valueHint: "<id>", description: "\u521D\u59CB\u72B6\u6001\uFF08\u4E1A\u52A1 Object state id\uFF09" },
3211
+ { name: "owner", type: "string", valueHint: "<principalId>", description: "\u8D1F\u8D23\u4EBA\uFF08\u4EC5\u521B\u5EFA\u65F6\uFF09" },
3212
+ { name: "assignee", type: "string", valueHint: "<principalId>", description: "\u7ECF\u529E\u4EBA" },
3213
+ { name: "visibility", type: "string", valueHint: "<value>", description: "\u53EF\u89C1\u6027" },
3214
+ { name: "estimate-hours", type: "number", valueHint: "<n>", description: "\u9884\u4F30\u5DE5\u65F6" },
3215
+ { name: "schema", type: "string", valueHint: "<id>", description: "\u5B57\u6BB5\u6A21\u677F" },
3216
+ { name: "sort-order", type: "number", valueHint: "<n>", description: "\u6392\u5E8F\u53F7" },
3217
+ { name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5 upsert\uFF08\u521B\u5EFA\u540E\u5199\u5165\uFF09" }
3218
+ ],
3219
+ examples: ["morula task create --space <id> --title <text>"],
3220
+ writes: "remote"
3221
+ },
3222
+ {
3223
+ action: "get",
3224
+ summary: "get task details. No argument = the current AgentTask (id resolved from MORULA_TASK_ID / .morula/checkout.json). With an id = the business Object (title/description)",
3225
+ args: [{ name: "objectId", required: false, description: "\u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09ID\uFF1B\u7701\u7565 = \u5F53\u524D AgentTask \u8BE6\u60C5" }],
3226
+ flags: [],
3227
+ examples: ["morula task get", "morula task get <objectId>"],
3228
+ writes: "none",
3229
+ idempotent: true
3230
+ },
3231
+ {
3232
+ action: "update",
3233
+ summary: "update task (--state here is the business Object state id, NOT the AgentTask state code \u2014 see `task state`)",
3234
+ args: [{ name: "id", required: true, description: "\u4EFB\u52A1\uFF08Object\uFF09ID" }],
3235
+ flags: [
3236
+ { name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898" },
3237
+ { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
3238
+ { name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7" },
3239
+ { name: "state", type: "string", valueHint: "<id>", description: "\u72B6\u6001\uFF08\u4E1A\u52A1 Object state id\uFF09" },
3240
+ { name: "assignee", type: "string", valueHint: "<principalId>", description: "\u7ECF\u529E\u4EBA" },
3241
+ { name: "visibility", type: "string", valueHint: "<value>", description: "\u53EF\u89C1\u6027" },
3242
+ { name: "estimate-hours", type: "number", valueHint: "<n>", description: "\u9884\u4F30\u5DE5\u65F6" },
3243
+ { name: "schema", type: "string", valueHint: "<id>", description: "\u5B57\u6BB5\u6A21\u677F" },
3244
+ { name: "sort-order", type: "number", valueHint: "<n>", description: "\u6392\u5E8F\u53F7" },
3245
+ { name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5 upsert\uFF08\u7A7A value = \u6E05\u7A7A\uFF09" }
3246
+ ],
3247
+ examples: ["morula task update <id> --title <text>"],
3248
+ writes: "remote"
3249
+ },
3250
+ {
3251
+ action: "delete",
3252
+ summary: "hard delete task",
3253
+ args: [{ name: "id", required: true, description: "\u4EFB\u52A1\uFF08Object\uFF09ID" }],
3254
+ flags: [],
3255
+ examples: ["morula task delete <id>"],
3256
+ writes: "remote"
3257
+ },
3258
+ {
3259
+ action: "branch",
3260
+ summary: "report the final branch name for an agent task (machine protocol; agent naming, runtimeId from env; --repo: secondary repo in a multi-repo task)",
3261
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08morula task get \u65E0\u53C2\u8FD4\u56DE\u7684 id\uFF09" }],
3262
+ flags: [
3263
+ { name: "name", type: "string", valueHint: "<branch>", description: "\u5206\u652F\u540D\uFF08\u5FC5\u586B\uFF09", required: true },
3264
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u6B21\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u7684\u5206\u652F\u9010\u4ED3\u4E0A\u62A5\uFF09" }
3265
+ ],
3266
+ examples: ["morula task branch <taskId> --name <type>/<code>-<slug>"],
3267
+ writes: "remote"
3268
+ },
3269
+ {
3270
+ action: "state",
3271
+ summary: "self-transition task state (machine protocol; agent drives todo\u2192in_progress\u2192review, runtimeId from env)",
3272
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3273
+ flags: [{ name: "state", type: "string", valueHint: "<in_progress|review>", description: "\u76EE\u6807\u72B6\u6001 code\uFF08\u5FC5\u586B\uFF09", required: true }],
3274
+ examples: ["morula task state <taskId> --state in_progress"],
3275
+ writes: "remote"
3276
+ },
3277
+ {
3278
+ action: "analysis-verdict",
3279
+ summary: "report the task-analysis verdict (machine protocol; ready \u2192 platform auto-starts development, blocked \u2192 task stays in analysis)",
3280
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3281
+ flags: [
3282
+ { name: "verdict", type: "string", valueHint: "<ready|blocked>", enum: ["ready", "blocked"], description: "\u5206\u6790\u7ED3\u8BBA\uFF08\u5FC5\u586B\uFF1B\u4EC5\u4EFB\u52A1\u5206\u6790\u4EFB\u52A1\u53EF\u4E0A\u62A5\uFF09", required: true },
3283
+ { name: "reason", type: "string", valueHint: "<text>", description: "blocked \u65F6\u7684\u963B\u585E/\u5F85\u6F84\u6E05\u539F\u56E0" }
3284
+ ],
3285
+ examples: ["morula task analysis-verdict <taskId> --verdict ready"],
3286
+ writes: "remote"
3287
+ },
3288
+ {
3289
+ action: "inbox",
3290
+ summary: "checkpoint: pull this thread's unconsumed messages into the current turn (machine protocol; --peek reads without marking them consumed)",
3291
+ flags: [
3292
+ { name: "peek", type: "boolean", description: "\u53EA\u8BFB\uFF0C\u4E0D\u6807\u8BB0\u6D88\u8D39" },
3293
+ { name: "after-seq", type: "number", valueHint: "<n>", description: "\u4ECE\u8BE5 seq \u4E4B\u540E\u62C9\u53D6" }
3294
+ ],
3295
+ examples: ["morula task inbox", "morula task inbox --peek"],
3296
+ writes: "remote"
3297
+ },
3298
+ {
3299
+ action: "agent-tasks",
3300
+ summary: "list the AgentTask executions of a business task (Object). AgentTask UUID \u2260 \u4E1A\u52A1\u4EFB\u52A1 id \u2014 cancel/rerun \u8981\u7684\u662F\u8FD9\u91CC\u7684 id",
3301
+ flags: [
3302
+ { name: "object", type: "string", valueHint: "<objectId>", description: "\u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
3303
+ { name: "status", type: "string", valueHint: "<queued|dispatched|running|awaiting_approval|completed|failed|cancelled>", description: "\u6309\u5355\u4E2A\u72B6\u6001\u8FC7\u6EE4\uFF08\u7F3A\u7701 = \u5168\u90E8\uFF1B\u670D\u52A1\u7AEF\u4E0D\u505A\u679A\u4E3E\u6821\u9A8C\uFF0C\u975E\u6CD5\u503C\u53EA\u8FD4\u56DE\u7A7A\uFF09" },
3304
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3305
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570\uFF08\u670D\u52A1\u7AEF\u7F3A\u7701 20\uFF09" }
3306
+ ],
3307
+ examples: ["morula task agent-tasks --object <objectId>", "morula task agent-tasks --object <objectId> --status failed"],
3308
+ writes: "none",
3309
+ idempotent: true
3310
+ },
3311
+ {
3312
+ action: "cancel",
3313
+ summary: "cancel a running execution (POST /agent-tasks/:id/cancel). queued/dispatched/running/awaiting_approval/failed \u53EF\u53D6\u6D88\uFF1Bcompleted/cancelled \u4E0D\u53EF\uFF08409 CONFLICT\uFF09\uFF1B\u4EFB\u52A1\u4E0D\u5B58\u5728 404 NOT_FOUND\u3002\u6210\u529F\u8FD4\u56DE { ok: true }\u2014\u2014**\u975E\u96F6\u9000\u51FA\u5373\u672A\u53D6\u6D88**\uFF0C\u4E0D\u8981\u5411\u7528\u6237\u6C47\u62A5\u300C\u5DF2\u505C\u6389\u300D",
3314
+ args: [{ name: "taskId", required: true, description: "AgentTask UUID\uFF08\u6765\u81EA morula task agent-tasks\uFF09" }],
3315
+ flags: [],
3316
+ examples: ["morula task cancel <taskId>"],
3317
+ writes: "remote"
3318
+ },
3319
+ {
3320
+ action: "rerun",
3321
+ summary: "rerun a failed/cancelled execution (POST /agent-tasks/:id/rerun; copies the task and re-queues it \u2192 returns a NEW taskId). \u4EC5 failed/cancelled \u53EF\u91CD\u8DD1\uFF0C\u5176\u4ED6\u72B6\u6001 ok:false + code CONFLICT\uFF1B10s \u5185\u5DF2\u6709\u6D3B\u8DC3\u91CD\u8DD1\u4E3A ALREADY_QUEUED",
3322
+ args: [{ name: "taskId", required: true, description: "AgentTask UUID\uFF08\u6765\u81EA morula task agent-tasks\uFF09" }],
3323
+ flags: [],
3324
+ examples: ["morula task rerun <taskId>"],
3325
+ writes: "remote"
3326
+ }
3327
+ ]
3328
+ },
3329
+ {
3330
+ resource: "space",
3331
+ summary: "space (project) read/write",
3332
+ actions: [
3333
+ {
3334
+ action: "list",
3335
+ summary: "list visible spaces",
3336
+ flags: [
3337
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3338
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" },
3339
+ { name: "keyword", type: "string", valueHint: "<text>", description: "\u5173\u952E\u8BCD" }
3340
+ ],
3341
+ examples: ["morula space list"],
3342
+ writes: "none",
3343
+ idempotent: true
3344
+ },
3345
+ {
3346
+ action: "get",
3347
+ summary: "get space details",
3348
+ args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
3349
+ flags: [],
3350
+ examples: ["morula space get <id>"],
3351
+ writes: "none",
3352
+ idempotent: true
3353
+ },
3354
+ {
3355
+ action: "create",
3356
+ summary: "create space (project)",
3357
+ flags: [
3358
+ { name: "name", type: "string", valueHint: "<text>", description: "\u7A7A\u95F4\u540D\u79F0\uFF08\u5FC5\u586B\uFF09", required: true },
3359
+ { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
3360
+ { name: "code", type: "string", valueHint: "<code>", description: "\u7A7A\u95F4\u7F16\u53F7\uFF08\u7F3A\u7701\u7531\u670D\u52A1\u7AEF\u751F\u6210\uFF09" },
3361
+ { name: "visibility", type: "string", valueHint: "<private|internal|public>", enum: ["private", "internal", "public"], description: "\u53EF\u89C1\u6027" }
3362
+ ],
3363
+ examples: ["morula space create --name <text>"],
3364
+ writes: "remote"
3365
+ },
3366
+ {
3367
+ action: "update",
3368
+ summary: "update space (\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\u65D7\u6807)",
3369
+ args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
3370
+ flags: [
3371
+ { name: "name", type: "string", valueHint: "<text>", description: "\u7A7A\u95F4\u540D\u79F0" },
3372
+ { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
3373
+ { name: "visibility", type: "string", valueHint: "<private|internal|public>", enum: ["private", "internal", "public"], description: "\u53EF\u89C1\u6027" }
3374
+ ],
3375
+ examples: ["morula space update <id> --name <text>"],
3376
+ writes: "remote"
3377
+ },
3378
+ {
3379
+ action: "delete",
3380
+ summary: "delete space",
3381
+ args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
3382
+ flags: [],
3383
+ examples: ["morula space delete <id>"],
3384
+ writes: "remote"
3385
+ }
3386
+ ]
3387
+ },
3388
+ {
3389
+ resource: "space-member",
3390
+ summary: "space member management (admin operations)",
3391
+ actions: [
3392
+ {
3393
+ action: "list",
3394
+ summary: "list space members",
3395
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3396
+ examples: ["morula space-member list --space <id>"],
3397
+ writes: "none",
3398
+ idempotent: true
3399
+ },
3400
+ {
3401
+ action: "add",
3402
+ summary: "add member to space",
3403
+ flags: [
3404
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3405
+ { name: "principal", type: "string", valueHint: "<id>", description: "\u6210\u5458 principalId\uFF08\u5FC5\u586B\uFF09", required: true },
3406
+ { name: "role", type: "string", valueHint: "<admin|member>", enum: ["admin", "member"], description: "\u89D2\u8272\uFF08\u7F3A\u7701 member\uFF09" }
3407
+ ],
3408
+ examples: ["morula space-member add --space <id> --principal <id>"],
3409
+ writes: "remote"
3410
+ },
3411
+ {
3412
+ action: "update",
3413
+ summary: "update member role",
3414
+ args: [{ name: "memberId", required: true, description: "\u6210\u5458\u884C ID" }],
3415
+ flags: [{ name: "role", type: "string", valueHint: "<admin|member>", enum: ["admin", "member"], description: "\u65B0\u89D2\u8272\uFF08\u5FC5\u586B\uFF09", required: true }],
3416
+ examples: ["morula space-member update <memberId> --role admin"],
3417
+ writes: "remote"
3418
+ },
3419
+ {
3420
+ action: "remove",
3421
+ summary: "remove member from space",
3422
+ args: [{ name: "memberId", required: true, description: "\u6210\u5458\u884C ID" }],
3423
+ flags: [],
3424
+ examples: ["morula space-member remove <memberId>"],
3425
+ writes: "remote"
3426
+ }
3427
+ ]
3428
+ },
3429
+ {
3430
+ resource: "steward",
3431
+ summary: "space steward (project-level agent): read takeover state / dispatch a task with an explicit target state",
3432
+ actions: [
3433
+ {
3434
+ action: "get",
3435
+ summary: "get the steward record of a space (null = \u672A\u63A5\u7BA1)",
3436
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4\uFF08\u9879\u76EE\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3437
+ examples: ["morula steward get --space <id>"],
3438
+ writes: "none",
3439
+ idempotent: true
3440
+ },
3441
+ {
3442
+ action: "dispatch",
3443
+ summary: "dispatch a task: create the task and hand it to the pipeline. --state backlog = \u843D\u5F85\u89C4\u5212\uFF08\u4E0D\u6D3E\u53D1\u6267\u884C\uFF0C\u670D\u52A1\u7AEF\u4E0D\u5EFA AgentTask\uFF09\uFF1B\u7F3A\u7701 todo\uFF08\u5F85\u5206\u6790\uFF09",
3444
+ flags: [
3445
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4\uFF08\u9879\u76EE\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
3446
+ { name: "title", type: "string", valueHint: "<text>", description: "\u4EFB\u52A1\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
3447
+ { name: "description", type: "string", valueHint: "<text>", description: "\u4EFB\u52A1\u63CF\u8FF0" },
3448
+ { name: "state", type: "string", valueHint: "<backlog|todo|in_progress>", enum: ["backlog", "todo", "in_progress"], description: "\u76EE\u6807\u521D\u59CB\u72B6\u6001\uFF08\u7F3A\u7701 todo\uFF09" },
3449
+ { name: "assignee", type: "string", valueHint: "<principalId>", description: "\u6307\u5B9A\u6267\u884C\u8005" },
3450
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u6307\u5B9A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u7531\u670D\u52A1\u7AEF\u89E3\u6790\uFF09" },
3451
+ { name: "client-message-id", type: "string", valueHint: "<id>", description: "\u5E42\u7B49\u952E\uFF1A\u540C\u4E00\u952E\u91CD\u590D\u6D3E\u5355\u8FD4\u56DE\u539F\u7ED3\u679C" }
3452
+ ],
3453
+ examples: ["morula steward dispatch --space <id> --title <text>", "morula steward dispatch --space <id> --title <text> --state backlog"],
3454
+ writes: "remote"
3455
+ }
3456
+ ]
3457
+ },
3458
+ {
3459
+ resource: "autopilot",
3460
+ summary: "agent autopilots (scheduled/webhook-triggered automated runs; list only shows your own)",
3461
+ actions: [
3462
+ {
3463
+ action: "list",
3464
+ summary: "list autopilots created by the current user",
3465
+ flags: [
3466
+ { name: "space", type: "string", valueHint: "<id>", description: "\u6309\u7A7A\u95F4\u8FC7\u6EE4" },
3467
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3468
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
3469
+ ],
3470
+ examples: ["morula autopilot list", "morula autopilot list --space <id>"],
3471
+ writes: "none",
3472
+ idempotent: true
3473
+ },
3474
+ {
3475
+ action: "create",
3476
+ summary: "create an autopilot (--executor is a runtime key; the server currently only accepts `dim`)",
3477
+ flags: [
3478
+ { name: "space", type: "string", valueHint: "<id>", description: "\u5F52\u5C5E\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3479
+ { name: "name", type: "string", valueHint: "<text>", description: "\u540D\u79F0\uFF08\u540C\u79DF\u6237\u552F\u4E00\uFF0C\u5FC5\u586B\uFF09", required: true },
3480
+ { name: "runbook", type: "string", valueHint: "<text>", description: "Runbook\uFF1A\u6BCF\u6B21\u89E6\u53D1\u7ED9\u8FD0\u884C\u65F6\u7684\u76EE\u6807/\u80CC\u666F/\u6B65\u9AA4\uFF08\u5FC5\u586B\uFF09", required: true },
3481
+ { name: "executor", type: "string", valueHint: "<key>", description: "\u6267\u884C\u8005\u8FD0\u884C\u65F6 key\uFF08\u5FC5\u586B\uFF1B\u5F53\u524D\u4EC5 dim\uFF09", required: true },
3482
+ { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
3483
+ { name: "cron", type: "string", valueHint: "<expr>", description: "cron \u8868\u8FBE\u5F0F\uFF085 \u5B57\u6BB5\uFF09\uFF1B\u7F3A\u7701 = \u4EC5 webhook \u89E6\u53D1" },
3484
+ { name: "vcs-connection", type: "string", valueHint: "<id>", description: "\u9650\u5B9A\u4ED3\u5E93\u8FDE\u63A5\uFF08\u7F3A\u7701\u4E0D\u9650\uFF09" }
3485
+ ],
3486
+ examples: ["morula autopilot create --space <id> --name <text> --runbook <text> --executor dim"],
3487
+ writes: "remote"
3488
+ }
3489
+ ]
3490
+ },
3491
+ {
3492
+ resource: "dashboard",
3493
+ summary: "space dashboard / statistics / member workload (read-only aggregates)",
3494
+ actions: [
3495
+ {
3496
+ action: "get",
3497
+ summary: "get the space dashboard aggregate",
3498
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3499
+ examples: ["morula dashboard get --space <id>"],
3500
+ writes: "none",
3501
+ idempotent: true
3502
+ },
3503
+ {
3504
+ action: "statistics",
3505
+ summary: "get space task statistics",
3506
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3507
+ examples: ["morula dashboard statistics --space <id>"],
3508
+ writes: "none",
3509
+ idempotent: true
3510
+ },
3511
+ {
3512
+ action: "workload",
3513
+ summary: "get per-member workload in a space",
3514
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3515
+ examples: ["morula dashboard workload --space <id>"],
3516
+ writes: "none",
3517
+ idempotent: true
3518
+ }
3519
+ ]
3520
+ },
3521
+ {
3522
+ resource: "object-meta",
3523
+ summary: "task-level agent metadata (D-06 high-signal keys only)",
3524
+ actions: [
3525
+ {
3526
+ action: "get",
3527
+ summary: "read all agent metadata of a task",
3528
+ flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3529
+ examples: ["morula object-meta get --object <id>"],
3530
+ writes: "none",
3531
+ idempotent: true
3532
+ },
3533
+ {
3534
+ action: "set",
3535
+ summary: "write one agent metadata key. Key must be in the high-signal whitelist: pr_url / pr_number / pipeline_status / deploy_url / waiting_on / blocked_reason / decision",
3536
+ flags: [
3537
+ { name: "object", type: "string", valueHint: "<id>", description: "\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
3538
+ { name: "key", type: "string", valueHint: "<key>", description: "\u5143\u6570\u636E\u952E\uFF08\u5FC5\u586B\uFF0C\u767D\u540D\u5355\uFF09", required: true },
3539
+ { name: "value", type: "string", valueHint: "<text>", description: "\u503C\uFF08\u5FC5\u586B\uFF0C\u975E\u7A7A\uFF09", required: true }
3540
+ ],
3541
+ examples: ["morula object-meta set --object <id> --key pr_url --value <url>"],
3542
+ writes: "remote"
3543
+ }
3544
+ ]
3545
+ },
3546
+ {
3547
+ resource: "document",
3548
+ summary: "document artifacts (ag_artifact; id is a numeric auto-increment id, not a uuid)",
3549
+ actions: [
3550
+ {
3551
+ action: "list",
3552
+ summary: "list document artifacts",
3553
+ flags: [
3554
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3555
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
3556
+ ],
3557
+ examples: ["morula document list"],
3558
+ writes: "none",
3559
+ idempotent: true
3560
+ },
3561
+ {
3562
+ action: "get",
3563
+ summary: "get document metadata (\u4E0D\u542B\u5B8C\u6574\u6B63\u6587)",
3564
+ args: [{ name: "id", required: true, description: "\u6587\u6863 ID\uFF08\u6570\u5B57\uFF0C\u6765\u81EA document list\uFF09" }],
3565
+ flags: [],
3566
+ examples: ["morula document get <id>"],
3567
+ writes: "none",
3568
+ idempotent: true
3569
+ },
3570
+ {
3571
+ action: "artifacts",
3572
+ summary: "read the parsed document content (markdown)",
3573
+ args: [{ name: "id", required: true, description: "\u6587\u6863 ID\uFF08\u6570\u5B57\uFF0C\u6765\u81EA document list\uFF09" }],
3574
+ flags: [],
3575
+ examples: ["morula document artifacts <id>"],
3576
+ writes: "none",
3577
+ idempotent: true
3578
+ }
3579
+ ]
3580
+ },
3581
+ {
3582
+ resource: "comment",
3583
+ summary: "object comments (agent summaries live here)",
3584
+ actions: [
3585
+ {
3586
+ action: "list",
3587
+ summary: "list comments for an object",
3588
+ flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3589
+ examples: ["morula comment list --object <id>"],
3590
+ writes: "none",
3591
+ idempotent: true
3592
+ },
3593
+ {
3594
+ action: "get",
3595
+ summary: "get comment details",
3596
+ args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
3597
+ flags: [],
3598
+ examples: ["morula comment get <id>"],
3599
+ writes: "none",
3600
+ idempotent: true
3601
+ },
3602
+ {
3603
+ action: "add",
3604
+ summary: "add comment (format auto-detected; use markdown for agent final summaries). --mention \u53EF @ \u4EBA\uFF08\u89E6\u53D1\u901A\u77E5/\u50AC\u529E\uFF09",
3605
+ flags: [
3606
+ { name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3607
+ { name: "content", type: "string", valueHint: "<text>", description: "\u8BC4\u8BBA\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09", required: true },
3608
+ { name: "parent", type: "string", valueHint: "<id>", description: "\u7236\u8BC4\u8BBA\uFF08\u56DE\u590D\uFF09" },
3609
+ { name: "format", type: "string", valueHint: "<markdown|plain|richtext>", enum: ["markdown", "plain", "richtext"], description: "\u5185\u5BB9\u683C\u5F0F\uFF08\u7F3A\u7701\u81EA\u52A8\u68C0\u6D4B\uFF09" },
3610
+ { name: "mention", type: "string", valueHint: "<principalId[,principalId]>", repeatable: true, description: "@ \u4EBA\uFF1A\u88AB\u63D0\u53CA\u8005\u7684 principalId\uFF0C\u53EF\u91CD\u590D\u4F20\uFF08--mention a --mention b\uFF09\u6216\u7528\u9017\u53F7\u5206\u9694\u591A\u4E2A\uFF08\u4E24\u79CD\u6DF7\u7528\u81EA\u52A8\u5408\u5E76\u53BB\u91CD\u4FDD\u5E8F\uFF09\uFF1B\u53EA\u652F\u6301\u4EBA\uFF0C\u4E0D\u652F\u6301 @ \u8FD0\u884C\u65F6/\u667A\u80FD\u4F53" }
3611
+ ],
3612
+ examples: ["morula comment add --object <id> --content <text>", "morula comment add --object <id> --content <text> --mention <principalId>", "morula comment add --object <id> --content <text> --mention <principalIdA> --mention <principalIdB>"],
3613
+ writes: "remote"
3614
+ },
3615
+ {
3616
+ action: "update",
3617
+ summary: "update comment",
3618
+ args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
3619
+ flags: [{ name: "content", type: "string", valueHint: "<text>", description: "\u65B0\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09", required: true }],
3620
+ examples: ["morula comment update <id> --content <text>"],
3621
+ writes: "remote"
3622
+ },
3623
+ {
3624
+ action: "delete",
3625
+ summary: "hard delete comment",
3626
+ args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
3627
+ flags: [],
3628
+ examples: ["morula comment delete <id>"],
3629
+ writes: "remote"
3630
+ }
3631
+ ]
3632
+ },
3633
+ {
3634
+ resource: "review",
3635
+ summary: "Code Review verdict submission (review task machine protocol)",
3636
+ actions: [
3637
+ {
3638
+ action: "submit",
3639
+ summary: "submit Code Review verdict (machine protocol; review task token)",
3640
+ args: [{ name: "objectId", required: true, description: "\u8BC4\u5BA1\u5BF9\u8C61 ID\uFF08\u7CFB\u7EDF\u63D0\u793A\u300C\u8BC4\u5BA1\u5BF9\u8C61\u300D\uFF09" }],
3641
+ flags: [
3642
+ { name: "verdict", type: "string", valueHint: "<approved|changes_requested|commented>", enum: ["approved", "changes_requested", "commented"], description: "\u8BC4\u5BA1\u7ED3\u8BBA\uFF08\u5FC5\u586B\uFF09", required: true },
3643
+ { name: "details", type: "string", valueHint: "<text>", description: "\u8BC4\u5BA1\u610F\u89C1\uFF08changes_requested \u5FC5\u586B\uFF1A\u6587\u4EF6/\u884C/\u95EE\u9898/\u5EFA\u8BAE\uFF09" },
3644
+ { name: "pr-number", type: "number", valueHint: "<n>", description: "\u8BC4\u5BA1\u5BF9\u8C61\u7684 PR \u7F16\u53F7\uFF08\u6709\u5219\u586B\uFF09" }
3645
+ ],
3646
+ examples: ["morula review submit <objectId> --verdict approved"],
3647
+ writes: "remote"
3648
+ }
3649
+ ]
3650
+ },
3651
+ {
3652
+ resource: "report",
3653
+ summary: "autopilot reports (conclusion-heavy output of automated runs)",
3654
+ actions: [
3655
+ {
3656
+ action: "create",
3657
+ summary: "create an autopilot report (\u6B63\u6587\u4E8C\u9009\u4E00\uFF1A--content \u77ED\u6B63\u6587 / --content-file \u957F Markdown\uFF1B--autopilot \u7701\u7565\u65F6\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\u6240\u5C5E\u81EA\u52A8\u5316)",
3658
+ flags: [
3659
+ { name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
3660
+ { name: "content", type: "string", valueHint: "<markdown>", description: "\u77ED\u6B63\u6587\uFF08\u4E0E --content-file \u4E8C\u9009\u4E00\uFF09" },
3661
+ { name: "content-file", type: "string", valueHint: "<path>", description: "\u957F Markdown \u62A5\u544A\u6587\u4EF6\uFF08\u4E0E --content \u4E8C\u9009\u4E00\uFF09" },
3662
+ { name: "space", type: "string", valueHint: "<id>", description: "\u5F52\u5C5E\u7A7A\u95F4" },
3663
+ { name: "autopilot", type: "string", valueHint: "<id>", description: "\u6240\u5C5E\u81EA\u52A8\u5316\uFF08\u7701\u7565\u65F6\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\uFF09" }
3664
+ ],
3665
+ examples: ["morula report create --title <t> --content-file <path>"],
3666
+ writes: "remote"
3667
+ },
3668
+ {
3669
+ action: "get",
3670
+ summary: "get report details (\u542B Markdown \u6B63\u6587)",
3671
+ args: [{ name: "id", required: true, description: "\u62A5\u544A ID" }],
3672
+ flags: [],
3673
+ examples: ["morula report get <id>"],
3674
+ writes: "none",
3675
+ idempotent: true
3676
+ },
3677
+ {
3678
+ action: "list",
3679
+ summary: "list reports of an autopilot",
3680
+ flags: [
3681
+ { name: "autopilot", type: "string", valueHint: "<id>", description: "\u81EA\u52A8\u5316 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3682
+ { name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
3683
+ { name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
3684
+ ],
3685
+ examples: ["morula report list --autopilot <id>"],
3686
+ writes: "none",
3687
+ idempotent: true
3688
+ }
3689
+ ]
3690
+ },
3691
+ {
3692
+ resource: "repo",
3693
+ summary: "task-space VCS repos: materialize checkout / MR read & comment (server channel; never approves/merges)",
3694
+ actions: [
3695
+ {
3696
+ action: "list",
3697
+ summary: "list task-space VCS connections (the space repo list \u2014 the task prompt does NOT list repos; query this when a task may span multiple repos)",
3698
+ flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3699
+ examples: ["morula repo list --space <id>"],
3700
+ writes: "none",
3701
+ idempotent: true
3702
+ },
3703
+ {
3704
+ action: "checkout",
3705
+ summary: "materialize a repo into the task workdir (local daemon endpoint preferred, works same-turn; falls back to server machine protocol which resolves scope + workContext)",
3706
+ args: [
3707
+ { name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" },
3708
+ { name: "repoFullName", required: true, description: "\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }
3709
+ ],
3710
+ flags: [{ name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u68C0\u51FA\u5F15\u7528\uFF08\u7F3A\u7701 = \u7ED1\u5B9A\u5206\u652F\uFF09" }],
3711
+ examples: ["morula repo checkout <taskId> <owner/name>"],
3712
+ writes: "local",
3713
+ idempotent: true
3714
+ },
3715
+ {
3716
+ action: "prepare",
3717
+ summary: "materialize a secondary repo into the task workdir (same level as the primary repo dir)",
3718
+ args: [
3719
+ { name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" },
3720
+ { name: "repoFullName", required: true, description: "\u6B21\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }
3721
+ ],
3722
+ flags: [],
3723
+ examples: ["morula repo prepare <taskId> <owner/name>"],
3724
+ writes: "local",
3725
+ idempotent: true
3726
+ },
3727
+ {
3728
+ action: "files",
3729
+ summary: "list files in a space repo via the server (read-only, non-task: no local checkout, no AgentTask context)",
3730
+ flags: [
3731
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3732
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u4ED3\u5E93\u5168\u540D\uFF08\u7F3A\u7701 = \u7A7A\u95F4\u4E3B\u4ED3\u5E93\uFF09" },
3733
+ { name: "path", type: "string", valueHint: "<path>", description: "\u76EE\u5F55\u8DEF\u5F84\uFF08\u7F3A\u7701 = \u4ED3\u5E93\u6839\uFF09" },
3734
+ { name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u5206\u652F / tag / commit\uFF08\u7F3A\u7701 = \u9ED8\u8BA4\u5206\u652F\uFF09" }
3735
+ ],
3736
+ examples: ["morula repo files --space <id>", "morula repo files --space <id> --path src"],
3737
+ writes: "none",
3738
+ idempotent: true
3739
+ },
3740
+ {
3741
+ action: "read",
3742
+ summary: "read a single file from a space repo via the server (read-only, non-task)",
3743
+ flags: [
3744
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3745
+ { name: "path", type: "string", valueHint: "<path>", description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u5FC5\u586B\uFF09", required: true },
3746
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u4ED3\u5E93\u5168\u540D\uFF08\u7F3A\u7701 = \u7A7A\u95F4\u4E3B\u4ED3\u5E93\uFF09" },
3747
+ { name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u5206\u652F / tag / commit\uFF08\u7F3A\u7701 = \u9ED8\u8BA4\u5206\u652F\uFF09" }
3748
+ ],
3749
+ examples: ["morula repo read --space <id> --path src/index.ts"],
3750
+ writes: "none",
3751
+ idempotent: true
3752
+ },
3753
+ {
3754
+ action: "mr create",
3755
+ summary: "create a GitLab MR for the task via the server channel (target project forced to the bound repo, PR linked immediately; push the branch first)",
3756
+ args: [{ name: "repoFullName", required: true, description: "\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }],
3757
+ flags: [
3758
+ { name: "branch", type: "string", valueHint: "<branch>", description: "\u6E90\u5206\u652F\uFF08\u5FC5\u586B\uFF09", required: true },
3759
+ { name: "base", type: "string", valueHint: "<base>", description: "\u76EE\u6807\u5206\u652F\uFF08\u7F3A\u7701 MORULA_BASE_BRANCH / main\uFF09" },
3760
+ { name: "task-code", type: "string", valueHint: "<code>", description: "\u4EFB\u52A1\u7F16\u53F7\uFF08MR \u6807\u9898/\u6B63\u6587\u56DE\u94FE\uFF09" }
3761
+ ],
3762
+ examples: ["morula repo mr create <owner/name> --branch <b> --task-code <code>"],
3763
+ writes: "remote"
3764
+ },
3765
+ {
3766
+ action: "mr list",
3767
+ summary: "list the task-space repos' merge requests (read-only; includes createdAt/updatedAt/author; never approves/merges)",
3768
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3769
+ flags: [
3770
+ { name: "state", type: "string", valueHint: "<opened|closed|merged>", enum: ["opened", "closed", "merged"], description: "MR \u72B6\u6001\u8FC7\u6EE4\uFF08\u7F3A\u7701 opened\uFF09" },
3771
+ { name: "limit", type: "number", valueHint: "<n>", description: "\u6761\u6570\u4E0A\u9650" },
3772
+ { name: "author", type: "string", valueHint: "<username>", description: "\u6309\u4F5C\u8005\u8FC7\u6EE4" },
3773
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" }
3774
+ ],
3775
+ examples: ["morula repo mr list <taskId> --state opened"],
3776
+ writes: "none",
3777
+ idempotent: true
3778
+ },
3779
+ {
3780
+ action: "mr comment",
3781
+ summary: "write a review comment on an MR/PR (write-only: never approves or merges). \u5199\u4E4B\u524D\u5FC5\u987B\u5148 `morula repo mr get <taskId> --mr <iid> --notes` \u8BFB\u5DF2\u6709\u8BA8\u8BBA\uFF1A\u82E5\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u7ED3\u8BBA\u4E14\u7ED3\u8BBA\u672A\u53D8\uFF0C\u4E0D\u8981\u518D\u8BC4\u8BBA\u3002\u5185\u5BB9\u4E3A\u7ED3\u8BBA + \u5173\u952E\u95EE\u9898\uFF08\u6587\u4EF6:\u884C + \u5EFA\u8BAE\uFF09\uFF0C\u672B\u5C3E\u52A0\u7F72\u540D\u884C\uFF1B\u957F\u5185\u5BB9\u5199\u6210\u62A5\u544A\u540E\u7528\u6458\u8981\u5F15\u7528\uFF08morula report create \u2192 \u8BC4\u8BBA\u91CC\u7ED9\u8981\u70B9\uFF09",
3782
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3783
+ flags: [
3784
+ { name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
3785
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
3786
+ { name: "connection", type: "string", valueHint: "<id>", description: "\u8FDE\u63A5 ID\uFF08\u591A\u8FDE\u63A5\u573A\u666F\uFF09" },
3787
+ { name: "content", type: "string", valueHint: "<text>", description: "\u8BC4\u8BBA\u6B63\u6587\uFF08\u4E0E --content-file \u4E8C\u9009\u4E00\uFF09" },
3788
+ { name: "content-file", type: "string", valueHint: "<path>", description: "\u8BC4\u8BBA\u6B63\u6587\u6587\u4EF6\uFF08\u4E0E --content \u4E8C\u9009\u4E00\uFF09" }
3789
+ ],
3790
+ examples: ["morula repo mr comment <taskId> --mr <iid> --content-file <path>"],
3791
+ writes: "remote"
3792
+ },
3793
+ {
3794
+ action: "mr get",
3795
+ summary: "get a single MR detail (read-only; --ci attaches pipeline status where none = no pipeline, not an error; --notes attaches existing comments \u2014 read this before writing a comment to avoid duplicates)",
3796
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3797
+ flags: [
3798
+ { name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
3799
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
3800
+ { name: "ci", type: "boolean", description: "\u9644\u5E26 pipeline \u72B6\u6001" },
3801
+ { name: "notes", type: "boolean", description: "\u9644\u5E26\u5DF2\u6709\u8BC4\u8BBA\uFF08\u5199\u8BC4\u8BBA\u524D\u5148\u8BFB\uFF0C\u9632\u91CD\u590D\uFF09" }
3802
+ ],
3803
+ examples: ["morula repo mr get <taskId> --mr <iid> --notes"],
3804
+ writes: "none",
3805
+ idempotent: true
3806
+ },
3807
+ {
3808
+ action: "mr diff",
3809
+ summary: "read an MR diff via the server (read-only; works without a local checkout; truncated flag is set when output was capped)",
3810
+ args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
3811
+ flags: [
3812
+ { name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
3813
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
3814
+ { name: "max-files", type: "number", valueHint: "<n>", description: "\u6587\u4EF6\u6570\u4E0A\u9650" },
3815
+ { name: "max-chars", type: "number", valueHint: "<n>", description: "\u5B57\u7B26\u6570\u4E0A\u9650" }
3816
+ ],
3817
+ examples: ["morula repo mr diff <taskId> --mr <iid>"],
3818
+ writes: "none",
3819
+ idempotent: true
3820
+ }
3821
+ ],
3822
+ notes: [
3823
+ "mr \u662F\u591A\u7EA7\u5B50\u547D\u4EE4\u5F62\u6001\uFF08action=mr\u3001\u5B50\u547D\u4EE4\u4F4D\u5728 positionals[0]\uFF09\uFF1A\u5951\u7EA6\u6309\u5B50\u547D\u4EE4\u62C6\u5206\u58F0\u660E\uFF08mr create / mr list / ...\uFF09\uFF0C\u4E25\u683C\u6821\u9A8C\u53EA\u5BF9\u5355\u7EA7 action \u751F\u6548"
3824
+ ]
3825
+ },
3826
+ {
3827
+ resource: "ci",
3828
+ summary: "CI/CD pipeline facts and pipeline operations: pipeline runs / job logs / deployments / failed test cases (read-only) plus retry of a failed pipeline or job (Phase 3 write, space face only). Read faces share one command surface \u2014 task face `<taskId>` (AgentTask credential, self-check your MR pipeline) vs space face `--space <spaceId>` (steward/session credential, space-wide inspection); the two are mutually exclusive. Retry is space-face ONLY (person + steward); trigger-deploy (play a manual job) stays person-only and is intentionally absent from the CLI",
3829
+ actions: [
3830
+ {
3831
+ 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)",
3833
+ args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
3834
+ flags: [
3835
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
3836
+ { name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u53EA\u770B\u8BE5\u4ED3\u5E93\u7684\u8FD0\u884C\uFF08\u4EFB\u52A1\u9762\u4E13\u7528\uFF0C\u5BA2\u6237\u7AEF\u8FC7\u6EE4\uFF09" },
3837
+ { name: "ref", type: "string", valueHint: "<branch>", description: "\u6309\u5206\u652F/tag \u8FC7\u6EE4\uFF08\u7A7A\u95F4\u9762\u4E13\u7528\uFF09" },
3838
+ { name: "failed", type: "boolean", description: "\u53EA\u770B\u5931\u8D25\u7684\u8FD0\u884C\uFF08\u7A7A\u95F4\u9762\u4E13\u7528\uFF09" },
3839
+ { name: "limit", type: "number", valueHint: "<n>", description: "\u8FD0\u884C\u6761\u6570\u4E0A\u9650\uFF08\u670D\u52A1\u7AEF\u9ED8\u8BA4 20\uFF0C\u4E0A\u9650 100\uFF09" }
3840
+ ],
3841
+ examples: ["morula ci status <taskId>", "morula ci status --space <id> --failed"],
3842
+ writes: "none",
3843
+ idempotent: true,
3844
+ emits: ["ok", "taskId", "spaceId", "runs", "deployments", "hasCiConfig", "notice", "repoFilter"]
3845
+ },
3846
+ {
3847
+ action: "log",
3848
+ summary: "job log by job id (the server resolves the owning run; both faces use this same endpoint \u2014 the task/space flag only declares which face you are calling from). `truncated:true` means the log was tail-cut, not complete",
3849
+ args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
3850
+ flags: [
3851
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
3852
+ { name: "job", type: "string", valueHint: "<jobId>", description: "job ID\uFF08\u5FC5\u586B\uFF1B\u6765\u81EA ci status \u7684 jobs[].id\uFF09", required: true },
3853
+ { name: "tail", type: "number", valueHint: "<n>", description: "\u4FDD\u7559\u7684\u65E5\u5FD7\u5C3E\u90E8\u5B57\u8282\u6570\uFF08\u670D\u52A1\u7AEF\u9ED8\u8BA4 64KB\uFF09" }
3854
+ ],
3855
+ examples: ["morula ci log <taskId> --job <jobId> --tail 20000"],
3856
+ writes: "none",
3857
+ idempotent: true,
3858
+ emits: ["ok", "runId", "jobId", "log", "truncated"]
3859
+ },
3860
+ {
3861
+ action: "deployments",
3862
+ summary: "deployment records for a task (or a space) \u2014 display only: deployments do NOT drive lifecycle state (released is driven solely by a successful production-branch pipeline run)",
3863
+ args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
3864
+ flags: [
3865
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" }
3866
+ ],
3867
+ examples: ["morula ci deployments <taskId>"],
3868
+ writes: "none",
3869
+ idempotent: true,
3870
+ emits: ["ok", "spaceId", "deployments"]
3871
+ },
3872
+ {
3873
+ action: "test-report",
3874
+ summary: "failed test cases of a run; --run omitted resolves to the newest run in the CI view \u2014 no run at all is CI_RUN_NOT_FOUND (exit 3), never an empty report; provider without this capability (GitHub) returns CAPABILITY_UNSUPPORTED",
3875
+ args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
3876
+ flags: [
3877
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
3878
+ { name: "run", type: "string", valueHint: "<runId>", description: "\u8FD0\u884C ID\uFF08\u7F3A\u7701 = CI \u89C6\u56FE\u91CC\u6700\u65B0\u4E00\u6761\u8FD0\u884C\uFF09" }
3879
+ ],
3880
+ examples: ["morula ci test-report <taskId>"],
3881
+ writes: "none",
3882
+ idempotent: true,
3883
+ emits: ["ok", "runId", "report"]
3884
+ },
3885
+ {
3886
+ action: "retry",
3887
+ summary: "re-run a failed pipeline (Phase 3; space face ONLY \u2014 a positional <taskId> is a usage error, retry is not open to the task face; person + steward, and a steward must go through an elicitation confirm card). Omit both --run and --job to retry the newest FAILED run of the space (no failed run at all \u2192 CI_RUN_NOT_FOUND exit 3). Only failure/cancelled runs or jobs can be retried (CI_RETRY_NOT_ALLOWED exit 6). NOT idempotent: every call starts another platform re-run",
3888
+ flags: [
3889
+ { name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF1Bretry \u53EA\u6709\u7A7A\u95F4\u9762\uFF0C\u4EFB\u52A1\u9762\u4E0D\u5F00\u653E\uFF09", required: true },
3890
+ { name: "run", type: "string", valueHint: "<runId>", description: "\u8981\u91CD\u8DD1\u7684\u8FD0\u884C ID\uFF08\u4E0E --job \u4E92\u65A5\uFF1B\u7F3A\u7701 = \u8BE5\u7A7A\u95F4\u6700\u65B0\u4E00\u6761\u5931\u8D25\u8FD0\u884C\uFF09" },
3891
+ { name: "job", type: "string", valueHint: "<jobId>", description: "\u8981\u91CD\u8DD1\u7684\u5355\u4E2A job ID\uFF08\u4E0E --run \u4E92\u65A5\uFF1B\u6765\u81EA ci status \u7684 jobs[].id\uFF09" }
3892
+ ],
3893
+ examples: ["morula ci retry --space <id>", "morula ci retry --space <id> --job <jobId>"],
3894
+ writes: "remote",
3895
+ idempotent: false,
3896
+ emits: ["ok", "action", "runId", "jobId", "externalId", "ref", "acceptedAt"]
3897
+ }
3898
+ ],
3899
+ notes: [
3900
+ "\u4E24\u4E2A\u9762\uFF08\u4EFB\u52A1\u9762 <taskId> / \u7A7A\u95F4\u9762 --space <spaceId>\uFF09\u4E92\u65A5\uFF1A\u540C\u65F6\u7ED9 \u2192 usage error(2)\uFF0C\u90FD\u4E0D\u7ED9\u540C\u6837 usage error\u3002\u51ED\u8BC1\u4E0E\u4F5C\u7528\u57DF\u4E0D\u540C\uFF0C\u731C\u9519\u4F1A\u53D8\u6210\u9759\u9ED8\u8D8A\u6743\u6216\u9759\u9ED8\u5931\u8D25",
3901
+ "\u53EA\u8BFB\u9762\uFF1Astatus / log / deployments / test-report \u53EA\u8BFB\uFF08writes: none\uFF09\uFF1Bretry \u662F Phase 3 \u7684\u552F\u4E00\u5199\u9762\uFF08writes: remote\uFF0C\u975E\u5E42\u7B49\uFF09\uFF0C\u4E14**\u53EA\u6709\u7A7A\u95F4\u9762**\u2014\u2014\u7ED9\u4F4D\u7F6E\u53C2\u6570 <taskId> \u2192 usage error(2)",
3902
+ "\u89E6\u53D1\u624B\u52A8 job\uFF08play\uFF0C\u90E8\u7F72\uFF09\u4E0D\u8FDB CLI\uFF1A\u4EC5\u4EBA\u9762\uFF08D5/D6\uFF09\uFF0C\u5165\u53E3\u53EA\u5728 web \u786E\u8BA4\u5F39\u7A97",
3903
+ "\u670D\u52A1\u7AEF\u4E1A\u52A1\u5931\u8D25\u662F HTTP 200 + `{ ok:false, code, message }`\uFF1ACI_JOB_NOT_FOUND / CI_RUN_NOT_FOUND / TASK_NOT_FOUND / OBJECT_NOT_FOUND \u2192 exit 3\uFF1BCI_RETRY_NOT_ALLOWED / CI_JOB_NOT_MANUAL \u2192 exit 6\uFF1BCAPABILITY_UNSUPPORTED / NO_CONNECTION / \u5176\u4F59 \u2192 exit 1"
3904
+ ]
3905
+ },
3906
+ {
3907
+ resource: "attachment",
3908
+ summary: "file attachments for objects / comments / chat messages (local file upload)",
3909
+ actions: [
3910
+ {
3911
+ action: "upload",
3912
+ summary: "upload a local file and attach it to a task (--object) / comment (--comment) / chat message (--ag-message); for pure chat tasks pass --task <agentTaskId> to attach to the chat session; omit target to auto-merge (object if bound, else chat message)",
3913
+ args: [{ name: "file", required: true, description: "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84" }],
3914
+ flags: [
3915
+ { name: "object", type: "string", valueHint: "<id>", description: "\u6302\u5230\u5BF9\u8C61" },
3916
+ { name: "comment", type: "string", valueHint: "<id>", description: "\u6302\u5230\u8BC4\u8BBA" },
3917
+ { name: "ag-message", type: "boolean", description: "\u6302\u5230 Chat \u4F1A\u8BDD\u6700\u65B0\u6D88\u606F" },
3918
+ { name: "task", type: "string", valueHint: "<agentTaskId>", description: "\u7EAF Chat \u4EFB\u52A1\u5F52\u5E76\u5230\u4F1A\u8BDD\u6D88\u606F" },
3919
+ { name: "inline", type: "boolean", description: "\u5185\u8054\u9644\u4EF6" }
3920
+ ],
3921
+ examples: ["morula attachment upload <file> --object <id>"],
3922
+ writes: "remote"
3923
+ },
3924
+ {
3925
+ action: "list",
3926
+ summary: "list attachments for an object",
3927
+ flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
3928
+ examples: ["morula attachment list --object <id>"],
3929
+ writes: "none",
3930
+ idempotent: true
3931
+ },
3932
+ {
3933
+ action: "get",
3934
+ summary: "get attachment details",
3935
+ args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
3936
+ flags: [],
3937
+ examples: ["morula attachment get <id>"],
3938
+ writes: "none",
3939
+ idempotent: true
3940
+ },
3941
+ {
3942
+ action: "content",
3943
+ summary: "read the parsed text content (MinerU markdown) of an attachment; unparsed/parsing/failed is reported in the body, not as an HTTP error",
3944
+ args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
3945
+ flags: [{ name: "max-chars", type: "number", valueHint: "<n>", description: "\u6700\u5927\u8FD4\u56DE\u5B57\u7B26\u6570\uFF08\u670D\u52A1\u7AEF\u7F3A\u7701 8000\uFF09" }],
3946
+ examples: ["morula attachment content <id>", "morula attachment content <id> --max-chars 20000"],
3947
+ writes: "none",
3948
+ idempotent: true
3949
+ },
3950
+ {
3951
+ action: "download-url",
3952
+ summary: "get signed download URL for an attachment",
3953
+ args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
3954
+ flags: [],
3955
+ examples: ["morula attachment download-url <id>"],
3956
+ writes: "none",
3957
+ idempotent: true
3958
+ },
3959
+ {
3960
+ action: "delete",
3961
+ summary: "delete attachment",
3962
+ args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
3963
+ flags: [],
3964
+ examples: ["morula attachment delete <id>"],
3965
+ writes: "remote"
3966
+ }
3967
+ ]
3968
+ }
3969
+ ]
3970
+ };
3971
+
3972
+ // ../morula-cli/src/commands/steward.ts
3973
+ async function stewardCommand(context) {
3974
+ if (!context.runtime.token)
3975
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
3976
+ const { action, flags } = context.parsed;
3977
+ if (action === "get") {
3978
+ rejectUnknownFlags(flags, ["space"]);
3979
+ return context.client.stewards.get(requireFlag(flags, "space"));
3980
+ }
3981
+ if (action === "dispatch") {
3982
+ rejectUnknownFlags(flags, ["space", "title", "description", "state", "assignee", "repo", "client-message-id"]);
3983
+ const projectId = requireFlag(flags, "space");
3984
+ const title = requireFlag(flags, "title");
3985
+ const state = flagString(flags, "state");
3986
+ const description = flagString(flags, "description");
3987
+ const assigneePrincipalId = flagString(flags, "assignee");
3988
+ const repoFullName = flagString(flags, "repo");
3989
+ const clientMessageId = flagString(flags, "client-message-id");
3990
+ return context.client.stewards.dispatch(projectId, {
3991
+ title,
3992
+ ...description !== void 0 ? { description } : {},
3993
+ ...state !== void 0 ? { state } : {},
3994
+ ...assigneePrincipalId !== void 0 ? { assigneePrincipalId } : {},
3995
+ ...repoFullName !== void 0 ? { repoFullName } : {},
3996
+ ...clientMessageId !== void 0 ? { clientMessageId } : {}
3997
+ });
3998
+ }
3999
+ throw usage("usage: morula steward <get|dispatch>");
2503
4000
  }
2504
4001
 
2505
4002
  // ../morula-cli/src/commands/task.ts
@@ -2598,7 +4095,7 @@ async function taskCommand(context) {
2598
4095
  morula task get <objectId> # \u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09\u8BE6\u60C5\uFF08\u542B\u81EA\u5B9A\u4E49\u5B57\u6BB5\u503C\uFF09`);
2599
4096
  return context.client.http.get(`/agent-tasks/${encodeURIComponent(taskId)}`);
2600
4097
  }
2601
- const id = requireId2(positionals, "task get");
4098
+ const id = requireId4(positionals, "task get");
2602
4099
  const task = await context.client.tasks.get(id);
2603
4100
  if (!task)
2604
4101
  return null;
@@ -2633,7 +4130,7 @@ async function taskCommand(context) {
2633
4130
  }
2634
4131
  if (action === "update") {
2635
4132
  rejectUnknownFlags(flags, WRITE_FLAGS.filter((flag) => flag !== "space" && flag !== "owner"));
2636
- const id = requireId2(positionals, "task update");
4133
+ const id = requireId4(positionals, "task update");
2637
4134
  const input = taskInput(flags, false);
2638
4135
  const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
2639
4136
  if (!Object.keys(input).length && !flagString(flags, "field"))
@@ -2664,12 +4161,12 @@ async function taskCommand(context) {
2664
4161
  }
2665
4162
  if (action === "delete") {
2666
4163
  rejectUnknownFlags(flags, []);
2667
- const id = requireId2(positionals, "task delete");
4164
+ const id = requireId4(positionals, "task delete");
2668
4165
  return context.client.tasks.delete(id);
2669
4166
  }
2670
4167
  if (action === "branch") {
2671
4168
  rejectUnknownFlags(flags, ["name", "repo"]);
2672
- const id = requireId2(positionals, "task branch");
4169
+ const id = requireId4(positionals, "task branch");
2673
4170
  const branchName = requireFlag(flags, "name");
2674
4171
  const runtimeId = context.env.MORULA_RUNTIME_ID;
2675
4172
  if (!runtimeId)
@@ -2683,7 +4180,7 @@ async function taskCommand(context) {
2683
4180
  }
2684
4181
  if (action === "state") {
2685
4182
  rejectUnknownFlags(flags, ["state"]);
2686
- const id = requireId2(positionals, "task state");
4183
+ const id = requireId4(positionals, "task state");
2687
4184
  const stateCode = requireFlag(flags, "state");
2688
4185
  const runtimeId = context.env.MORULA_RUNTIME_ID;
2689
4186
  if (!runtimeId)
@@ -2695,7 +4192,7 @@ async function taskCommand(context) {
2695
4192
  }
2696
4193
  if (action === "analysis-verdict") {
2697
4194
  rejectUnknownFlags(flags, ["verdict", "reason"]);
2698
- const id = requireId2(positionals, "task analysis-verdict");
4195
+ const id = requireId4(positionals, "task analysis-verdict");
2699
4196
  const verdict = requireFlag(flags, "verdict");
2700
4197
  if (verdict !== "ready" && verdict !== "blocked")
2701
4198
  throw usage(`--verdict \u5FC5\u987B\u662F ready / blocked
@@ -2758,7 +4255,37 @@ async function taskCommand(context) {
2758
4255
  ...messages.length === 0 ? { hint: "\u672C\u7EBF\u7A0B\u6CA1\u6709\u65B0\u6D88\u606F\uFF1A\u6309\u539F\u8BA1\u5212\u7EE7\u7EED\uFF0C\u4E0D\u8981\u5C55\u5F00\u8BA8\u8BBA\u3002" } : {}
2759
4256
  };
2760
4257
  }
2761
- throw usage("usage: morula task <list|create|get|update|delete|branch|state|analysis-verdict|inbox>");
4258
+ if (action === "agent-tasks") {
4259
+ rejectUnknownFlags(flags, ["object", "status", "page", "page-size"]);
4260
+ const objectId = requireFlag(flags, "object");
4261
+ return context.client.agentTasks.list(compact3({
4262
+ objectId,
4263
+ status: flagString(flags, "status"),
4264
+ page: flagNumber(flags, "page"),
4265
+ pageSize: flagNumber(flags, "page-size")
4266
+ }));
4267
+ }
4268
+ if (action === "cancel") {
4269
+ rejectUnknownFlags(flags, []);
4270
+ const id = requireId4(positionals, "task cancel");
4271
+ return context.client.agentTasks.cancel(id);
4272
+ }
4273
+ if (action === "rerun") {
4274
+ rejectUnknownFlags(flags, []);
4275
+ const id = requireId4(positionals, "task rerun");
4276
+ const result = await context.client.agentTasks.rerun(id);
4277
+ if (result?.ok === false)
4278
+ throw new CliError(result.code ?? "RERUN_FAILED", rerunExitCode(result.code), `\u91CD\u8DD1\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
4279
+ return result;
4280
+ }
4281
+ throw usage("usage: morula task <list|create|get|update|delete|branch|state|analysis-verdict|inbox|agent-tasks|cancel|rerun>");
4282
+ }
4283
+ function rerunExitCode(code) {
4284
+ if (code === "NOT_FOUND")
4285
+ return EXIT.NOT_FOUND;
4286
+ if (code === "CONFLICT" || code === "ALREADY_QUEUED")
4287
+ return EXIT.CONFLICT;
4288
+ return EXIT.OTHER;
2762
4289
  }
2763
4290
  function taskInput(flags, create) {
2764
4291
  const description = flagString(flags, "description");
@@ -2782,7 +4309,7 @@ function taskInput(flags, create) {
2782
4309
  }
2783
4310
  return values;
2784
4311
  }
2785
- function requireId2(positionals, command) {
4312
+ function requireId4(positionals, command) {
2786
4313
  const id = positionals[0]?.trim();
2787
4314
  if (!id)
2788
4315
  throw usage(`usage: morula ${command} <id>`);
@@ -2799,13 +4326,19 @@ function unauthenticated() {
2799
4326
  var COMMANDS = {
2800
4327
  "attachment": attachmentCommand,
2801
4328
  "auth": authCommand,
4329
+ "autopilot": autopilotCommand,
4330
+ "ci": ciCommand,
2802
4331
  "comment": commentCommand,
4332
+ "dashboard": dashboardCommand,
4333
+ "document": documentCommand,
4334
+ "object-meta": objectMetaCommand,
2803
4335
  "report": reportCommand,
2804
4336
  "repo": repoCommand,
2805
4337
  "review": reviewCommand,
2806
4338
  "task": taskCommand,
2807
4339
  "space": spaceCommand,
2808
- "space-member": spaceMemberCommand
4340
+ "space-member": spaceMemberCommand,
4341
+ "steward": stewardCommand
2809
4342
  };
2810
4343
  var HELP = {
2811
4344
  name: "morula",
@@ -2813,70 +4346,12 @@ var HELP = {
2813
4346
  build: BUILD_ID,
2814
4347
  summary: "Agent-first CLI for Morula task management. Success output is JSON on stdout; errors are JSON on stderr.",
2815
4348
  usage: "morula <resource> <action> [id] [--flags]",
2816
- commands: {
2817
- "auth": {
2818
- login: "browser SkyDoor SSO using PKCE and loopback callback",
2819
- logout: "remove the current context token",
2820
- status: "show context, endpoint, and authentication state"
2821
- },
2822
- "task": {
2823
- "list [--space <id>] [--mine] [--assignee <id>] [--page <n>] [--page-size <n>] [--search <text>] [--priority <low|medium|high|urgent>] [--state <id>]": "list tasks (--mine: \u4EC5\u6211\u8D1F\u8D23\u7684)",
2824
- "create --space <id> --title <text> [--priority <value>] [--assignee <id>]": "create task",
2825
- "get [id]": "get task details. No argument = the current AgentTask (id resolved from MORULA_TASK_ID / .morula/checkout.json). With an id = the business Object (title/description)",
2826
- "update <id> [--title <text>] [--state <id>] [--priority <value>] [--sort-order <n>]": "update task (--state here is the business Object state id, NOT the AgentTask state code \u2014 see `task state`)",
2827
- "delete <id>": "hard delete task",
2828
- "branch <id> --name <name> [--repo <owner/name>]": "report the final branch name for an agent task (machine protocol; agent naming, runtimeId from env; --repo: secondary repo in a multi-repo task)",
2829
- "state <id> --state <code>": "self-transition task state (machine protocol; agent drives todo\u2192in_progress\u2192review, runtimeId from env)",
2830
- "analysis-verdict <taskId> --verdict <ready|blocked> [--reason <text>]": "report the task-analysis verdict (machine protocol; ready \u2192 platform auto-starts development, blocked \u2192 task stays in analysis)",
2831
- "inbox [--peek]": "checkpoint: pull this thread's unconsumed messages into the current turn (machine protocol; --peek reads without marking them consumed)"
2832
- },
2833
- "space": {
2834
- "list [--page <n>] [--page-size <n>] [--keyword <text>]": "list visible spaces",
2835
- "get <id>": "get space details"
2836
- },
2837
- "space-member": {
2838
- "list --space <id>": "list space members",
2839
- "add --space <id> --principal <id> [--role <admin|member>]": "add member to space",
2840
- "update <member-id> --role <admin|member>": "update member role",
2841
- "remove <member-id>": "remove member from space"
2842
- },
2843
- "comment": {
2844
- "list --object <id>": "list comments for an object",
2845
- "get <id>": "get comment details",
2846
- "add --object <id> --content <text> [--parent <id>] [--format markdown|plain|richtext]": "add comment (format auto-detected; use markdown for agent final summaries)",
2847
- "update <id> --content <text>": "update comment",
2848
- "delete <id>": "hard delete comment"
2849
- },
2850
- "review": {
2851
- "submit <objectId> --verdict <approved|changes_requested|commented> [--details <text>] [--pr-number <n>]": "submit Code Review verdict (machine protocol; review task token)"
2852
- },
2853
- "report": {
2854
- "create --title <text> [--content <markdown> | --content-file <path>] [--space <id>] [--autopilot <id>]": "create an autopilot report (\u6B63\u6587\u4E8C\u9009\u4E00\uFF1A--content \u77ED\u6B63\u6587 / --content-file \u957F Markdown\uFF1B--autopilot \u7701\u7565\u65F6\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\u6240\u5C5E\u81EA\u52A8\u5316)",
2855
- "get <id>": "get report details (\u542B Markdown \u6B63\u6587)",
2856
- "list --autopilot <id> [--page <n>] [--page-size <n>]": "list reports of an autopilot"
2857
- },
2858
- "repo": {
2859
- "list --space <id>": "list task-space VCS connections (available repos; note: the system prompt already lists them for a task \u2014 you rarely need this)",
2860
- "checkout <taskId> <repoFullName> [--ref <branch|sha>]": "materialize a repo into the task workdir (local daemon endpoint preferred, works same-turn; falls back to server machine protocol which resolves scope + workContext)",
2861
- "prepare <taskId> <repoFullName>": "materialize a secondary repo into the task workdir (same level as the primary repo dir)",
2862
- "mr create <repoFullName> --branch <branch> [--base <base>] [--task-code <code>]": "create a GitLab MR for the task via the server channel (target project forced to the bound repo, PR linked immediately; push the branch first)",
2863
- "mr list <taskId> [--state opened|closed|merged] [--limit <n>] [--author <username>] [--repo <owner/name>]": "list the task-space repos' merge requests (read-only; includes createdAt/updatedAt/author; never approves/merges)",
2864
- "mr comment <taskId> --mr <iid> [--repo <owner/name>] (--content <text> | --content-file <path>)": "write a review comment on an MR/PR (write-only: never approves or merges). \u5199\u4E4B\u524D\u5FC5\u987B\u5148 `morula repo mr get <taskId> --mr <iid> --notes` \u8BFB\u5DF2\u6709\u8BA8\u8BBA\uFF1A\u82E5\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u7ED3\u8BBA\u4E14\u7ED3\u8BBA\u672A\u53D8\uFF0C\u4E0D\u8981\u518D\u8BC4\u8BBA\u3002\u5185\u5BB9\u4E3A\u7ED3\u8BBA + \u5173\u952E\u95EE\u9898\uFF08\u6587\u4EF6:\u884C + \u5EFA\u8BAE\uFF09\uFF0C\u672B\u5C3E\u52A0\u7F72\u540D\u884C\u300C> \u7531\u81EA\u52A8\u5316\u300C<\u540D\u79F0>\u300D\u4E8E <\u65E5\u671F> \u751F\u6210\u300D\uFF1B\u4E0D\u8981\u8D34\u5927\u6BB5\u65E5\u5FD7\u6216\u590D\u8FF0 diff\uFF1B\u957F\u5185\u5BB9\u5199\u6210\u62A5\u544A\u540E\u7528\u6458\u8981\u5F15\u7528\uFF08morula report create \u2192 \u8BC4\u8BBA\u91CC\u7ED9\u8981\u70B9\uFF09",
2865
- "mr get <taskId> --mr <iid> [--repo <owner/name>] [--ci] [--notes]": "get a single MR detail (read-only; --ci attaches pipeline status where none = no pipeline, not an error; --notes attaches existing comments \u2014 read this before writing a comment to avoid duplicates)",
2866
- "mr diff <taskId> --mr <iid> [--repo <owner/name>] [--max-files <n>] [--max-chars <n>]": "read an MR diff via the server (read-only; works without a local checkout; truncated flag is set when output was capped)"
2867
- },
2868
- "attachment": {
2869
- "upload <file> [--object <id>] [--comment <id>] [--ag-message] [--task <agentTaskId>] [--inline]": "upload a local file and attach it to a task (--object) / comment (--comment) / chat message (--ag-message); for pure chat tasks pass --task <agentTaskId> to attach to the chat session; omit target to auto-merge (object if bound, else chat message)",
2870
- "list --object <id>": "list attachments for an object",
2871
- "get <id>": "get attachment details",
2872
- "download-url <id>": "get signed download URL for an attachment",
2873
- "delete <id>": "delete attachment"
2874
- }
2875
- },
4349
+ commands: renderTopLevelCommands(SPEC),
2876
4350
  globalFlags: {
2877
4351
  "--env <context>": "select test or a saved context",
2878
4352
  "--url <baseUrl>": "override API base URL",
2879
4353
  "--token <token>": "override token for this invocation (not persisted)",
4354
+ "--json": "no-op; output is always JSON (accepted for familiarity)",
2880
4355
  "--help": "print this machine-readable command surface",
2881
4356
  "--version": "print version JSON"
2882
4357
  },
@@ -2893,4 +4368,4 @@ var HELP = {
2893
4368
  };
2894
4369
 
2895
4370
  // ../morula-cli/src/index.ts
2896
- runEntry({ commands: COMMANDS, help: HELP });
4371
+ runEntry({ commands: COMMANDS, help: HELP, spec: SPEC });