@arcships/morula-runtime 0.1.0-alpha.18 → 0.1.0-alpha.19

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
@@ -704,8 +704,8 @@ function page(ok, message) {
704
704
  }
705
705
 
706
706
  // ../morula-cli-core/dist/build-env.js
707
- var BUILD_VERSION = "0.1.0-alpha.18";
708
- var BUILD_ID = "20260930091135.016ba78e";
707
+ var BUILD_VERSION = "0.1.0-alpha.19";
708
+ var BUILD_ID = "20261010070134.5226cb13";
709
709
  var BUILD_CONTEXTS = {
710
710
  "local": "http://localhost:8080/api",
711
711
  "test": "https://morula.basecastle.com/api",
@@ -1261,6 +1261,28 @@ function requiredKey(key) {
1261
1261
  return normalized;
1262
1262
  }
1263
1263
 
1264
+ // ../morula-api-client/dist/observability.js
1265
+ var ObservabilityApi = class {
1266
+ client;
1267
+ constructor(client) {
1268
+ this.client = client;
1269
+ }
1270
+ /** 任务面运行环境(任务凭证;不要传空间) */
1271
+ taskEnv(taskId, query = {}) {
1272
+ return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId7(taskId, "task id"))}/observability/env`, query);
1273
+ }
1274
+ /** 用户面运行环境(`--space <spaceId>`) */
1275
+ spaceEnv(spaceId, query = {}) {
1276
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId, "space id"))}/observability/env`, query);
1277
+ }
1278
+ };
1279
+ function requiredId7(id, label) {
1280
+ const normalized = id.trim();
1281
+ if (!normalized)
1282
+ throw new TypeError(`${label} is required`);
1283
+ return normalized;
1284
+ }
1285
+
1264
1286
  // ../morula-api-client/dist/repo.js
1265
1287
  var RepoApi = class {
1266
1288
  client;
@@ -1269,14 +1291,14 @@ var RepoApi = class {
1269
1291
  }
1270
1292
  /** 目录清单(path 缺省 = 仓库根) */
1271
1293
  files(spaceId, query = {}) {
1272
- return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/files`, query);
1294
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId8(spaceId))}/repo/files`, query);
1273
1295
  }
1274
1296
  /** 单文件内容(path 必填) */
1275
1297
  read(spaceId, query) {
1276
- return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/file`, query);
1298
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId8(spaceId))}/repo/file`, query);
1277
1299
  }
1278
1300
  };
1279
- function requiredId7(id) {
1301
+ function requiredId8(id) {
1280
1302
  const normalized = id.trim();
1281
1303
  if (!normalized)
1282
1304
  throw new TypeError("space id is required");
@@ -1293,13 +1315,13 @@ var ReportApi = class {
1293
1315
  return this.client.post("/agent-reports", input);
1294
1316
  }
1295
1317
  get(id) {
1296
- return this.client.get(`/agent-reports/${encodeURIComponent(requiredId8(id))}`);
1318
+ return this.client.get(`/agent-reports/${encodeURIComponent(requiredId9(id))}`);
1297
1319
  }
1298
1320
  listByAutopilot(autopilotId, query = {}) {
1299
- return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId8(autopilotId, "autopilot"))}/reports`, { ...query });
1321
+ return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId9(autopilotId, "autopilot"))}/reports`, { ...query });
1300
1322
  }
1301
1323
  };
1302
- function requiredId8(id, label = "report") {
1324
+ function requiredId9(id, label = "report") {
1303
1325
  const normalized = id.trim();
1304
1326
  if (!normalized)
1305
1327
  throw new TypeError(`${label} id is required`);
@@ -1316,19 +1338,19 @@ var SpaceMemberApi = class {
1316
1338
  return this.client.get("/space-members", { spaceId, ...query });
1317
1339
  }
1318
1340
  get(id) {
1319
- return this.client.get(`/space-members/${encodeURIComponent(requiredId9(id))}`);
1341
+ return this.client.get(`/space-members/${encodeURIComponent(requiredId10(id))}`);
1320
1342
  }
1321
1343
  create(input) {
1322
1344
  return this.client.post("/space-members", input);
1323
1345
  }
1324
1346
  update(id, input) {
1325
- return this.client.patch(`/space-members/${encodeURIComponent(requiredId9(id))}`, input);
1347
+ return this.client.patch(`/space-members/${encodeURIComponent(requiredId10(id))}`, input);
1326
1348
  }
1327
1349
  delete(id) {
1328
- return this.client.delete(`/space-members/${encodeURIComponent(requiredId9(id))}`);
1350
+ return this.client.delete(`/space-members/${encodeURIComponent(requiredId10(id))}`);
1329
1351
  }
1330
1352
  };
1331
- function requiredId9(id) {
1353
+ function requiredId10(id) {
1332
1354
  const normalized = id.trim();
1333
1355
  if (!normalized)
1334
1356
  throw new TypeError("space member id is required");
@@ -1345,19 +1367,19 @@ var SpaceApi = class {
1345
1367
  return this.client.get("/spaces", query);
1346
1368
  }
1347
1369
  get(id) {
1348
- return this.client.get(`/spaces/${encodeURIComponent(requiredId10(id))}`);
1370
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId11(id))}`);
1349
1371
  }
1350
1372
  create(input) {
1351
1373
  return this.client.post("/spaces", input);
1352
1374
  }
1353
1375
  update(id, input) {
1354
- return this.client.patch(`/spaces/${encodeURIComponent(requiredId10(id))}`, input);
1376
+ return this.client.patch(`/spaces/${encodeURIComponent(requiredId11(id))}`, input);
1355
1377
  }
1356
1378
  delete(id) {
1357
- return this.client.delete(`/spaces/${encodeURIComponent(requiredId10(id))}`);
1379
+ return this.client.delete(`/spaces/${encodeURIComponent(requiredId11(id))}`);
1358
1380
  }
1359
1381
  };
1360
- function requiredId10(id) {
1382
+ function requiredId11(id) {
1361
1383
  const normalized = id.trim();
1362
1384
  if (!normalized)
1363
1385
  throw new TypeError("space id is required");
@@ -1372,14 +1394,14 @@ var StewardApi = class {
1372
1394
  }
1373
1395
  /** 管家信息(未接管的空间返回 null) */
1374
1396
  get(projectId) {
1375
- return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}`);
1397
+ return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId12(projectId))}`);
1376
1398
  }
1377
1399
  /** 派单:建任务并派发(目标状态由 state 决定,backlog 不派发执行) */
1378
1400
  dispatch(projectId, input) {
1379
- return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}/dispatch`, input);
1401
+ return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId12(projectId))}/dispatch`, input);
1380
1402
  }
1381
1403
  };
1382
- function requiredId11(id) {
1404
+ function requiredId12(id) {
1383
1405
  const normalized = id.trim();
1384
1406
  if (!normalized)
1385
1407
  throw new TypeError("project id is required");
@@ -1396,16 +1418,16 @@ var TaskApi = class {
1396
1418
  return this.client.get("/objects", query);
1397
1419
  }
1398
1420
  get(id) {
1399
- return this.client.get(`/objects/${encodeURIComponent(requiredId12(id))}`);
1421
+ return this.client.get(`/objects/${encodeURIComponent(requiredId13(id))}`);
1400
1422
  }
1401
1423
  create(input) {
1402
1424
  return this.client.post("/objects", input);
1403
1425
  }
1404
1426
  update(id, input) {
1405
- return this.client.patch(`/objects/${encodeURIComponent(requiredId12(id))}`, input);
1427
+ return this.client.patch(`/objects/${encodeURIComponent(requiredId13(id))}`, input);
1406
1428
  }
1407
1429
  delete(id) {
1408
- return this.client.delete(`/objects/${encodeURIComponent(requiredId12(id))}`);
1430
+ return this.client.delete(`/objects/${encodeURIComponent(requiredId13(id))}`);
1409
1431
  }
1410
1432
  /** 查询空间字段模板(字段 key → id 映射、options) */
1411
1433
  getSchemaFields(spaceId) {
@@ -1434,7 +1456,7 @@ var TaskApi = class {
1434
1456
  return this.client.delete(`/object-field-values`, { objectId, schemaFieldId });
1435
1457
  }
1436
1458
  };
1437
- function requiredId12(id) {
1459
+ function requiredId13(id) {
1438
1460
  const normalized = id.trim();
1439
1461
  if (!normalized)
1440
1462
  throw new TypeError("task id is required");
@@ -1467,6 +1489,7 @@ var MorulaClient = class {
1467
1489
  dashboard;
1468
1490
  documents;
1469
1491
  objectMeta;
1492
+ observability;
1470
1493
  reports;
1471
1494
  repos;
1472
1495
  tasks;
@@ -1485,6 +1508,7 @@ var MorulaClient = class {
1485
1508
  this.dashboard = new DashboardApi(this.http);
1486
1509
  this.documents = new DocumentApi(this.http);
1487
1510
  this.objectMeta = new ObjectMetaApi(this.http);
1511
+ this.observability = new ObservabilityApi(this.http);
1488
1512
  this.reports = new ReportApi(this.http);
1489
1513
  this.repos = new RepoApi(this.http);
1490
1514
  this.tasks = new TaskApi(this.http);
@@ -1498,6 +1522,17 @@ function createMorulaClient(options) {
1498
1522
  return new MorulaClient(options);
1499
1523
  }
1500
1524
 
1525
+ // ../morula-cli-core/dist/context.js
1526
+ function rawText(text) {
1527
+ return { kind: "raw-text", text };
1528
+ }
1529
+ function rawTextOf(value) {
1530
+ if (typeof value !== "object" || value === null)
1531
+ return null;
1532
+ const candidate = value;
1533
+ return candidate.kind === "raw-text" && typeof candidate.text === "string" ? candidate.text : null;
1534
+ }
1535
+
1501
1536
  // ../morula-cli-core/dist/help.js
1502
1537
  var HELP_SCHEMA_VERSION = 1;
1503
1538
  function actionUsage(action) {
@@ -1923,7 +1958,8 @@ async function run(argv, deps = {}) {
1923
1958
  clientCaps: CLI_CAPABILITIES.join(",")
1924
1959
  });
1925
1960
  const result = await handler({ parsed, client, store, runtime, env, err, setExitCode });
1926
- out(JSON.stringify(result, null, 2));
1961
+ const raw = rawTextOf(result);
1962
+ out(raw ?? JSON.stringify(result, null, 2));
1927
1963
  return requestedExit ?? EXIT.OK;
1928
1964
  } catch (error) {
1929
1965
  if (error instanceof CliError || error instanceof MorulaApiError) {
@@ -2993,6 +3029,101 @@ async function tryLocalPrepare(taskId, repoFullName) {
2993
3029
  }
2994
3030
  }
2995
3031
 
3032
+ // ../morula-cli/src/commands/obs.ts
3033
+ var OBS_FLAGS = ["source", "space"];
3034
+ var OBS_USAGE = "usage: morula obs env [--source <name|id>] [--space <spaceId>] [--json]";
3035
+ async function obsCommand(context) {
3036
+ if (!context.runtime.token) {
3037
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login", false, {
3038
+ hint: "morula auth login"
3039
+ });
3040
+ }
3041
+ const { action, flags } = context.parsed;
3042
+ rejectUnknownFlags(flags, [...OBS_FLAGS]);
3043
+ if (action !== "env")
3044
+ throw usage(OBS_USAGE);
3045
+ return obsEnv(context);
3046
+ }
3047
+ async function obsEnv(context) {
3048
+ const { flags } = context.parsed;
3049
+ const source = flagString(flags, "source")?.trim() || void 0;
3050
+ const face = await resolveFace2(context);
3051
+ const view = await fetchEnv(context, face, source);
3052
+ if (view.server === null || view.token === null)
3053
+ context.setExitCode(EXIT.NOT_FOUND);
3054
+ return flagBoolean(flags, "json") ? view : rawText(renderEnvScript(view));
3055
+ }
3056
+ async function resolveFace2(context) {
3057
+ const spaceId = flagString(context.parsed.flags, "space")?.trim();
3058
+ const taskId = await resolveTaskId(context);
3059
+ if (spaceId && taskId) {
3060
+ throw usage(`--space \u4E0E\u4EFB\u52A1\u9762\u4E92\u65A5\uFF0C\u53EA\u80FD\u7ED9\u4E00\u4E2A\uFF08${OBS_USAGE}\uFF09`, {
3061
+ hint: "\u7528\u6237\u8EAB\u4EFD\u8BF7\u5728\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5916\u8FD0\u884C\uFF08\u6216\u5148 unset MORULA_TASK_ID\uFF09"
3062
+ });
3063
+ }
3064
+ if (spaceId)
3065
+ return { kind: "space", spaceId };
3066
+ if (taskId)
3067
+ return { kind: "task", taskId };
3068
+ throw usage(`morula obs env \u9700\u8981\u8EAB\u4EFD\u9762\uFF1A\u4EFB\u52A1\u9762\uFF08MORULA_TASK_ID / .morula/checkout.json\uFF09\u6216\u7528\u6237\u9762\uFF08--space <spaceId>\uFF09`, {
3069
+ hint: "morula obs env --space <spaceId> # \u7528\u6237\u8EAB\u4EFD\uFF1B\u6216\u5728\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5185\u8FD0\u884C"
3070
+ });
3071
+ }
3072
+ async function fetchEnv(context, face, source) {
3073
+ const query = source ? { source } : {};
3074
+ try {
3075
+ return face.kind === "space" ? await context.client.observability.spaceEnv(face.spaceId, query) : await context.client.observability.taskEnv(face.taskId, query);
3076
+ } catch (error) {
3077
+ throw withEnvHint(error, face, source);
3078
+ }
3079
+ }
3080
+ function withEnvHint(error, face, source) {
3081
+ if (!(error instanceof MorulaApiError))
3082
+ return error;
3083
+ if (error.status === 401) {
3084
+ return new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, `\u6362\u53D6\u53EF\u89C2\u6D4B\u6027\u8FD0\u884C\u73AF\u5883\u5931\u8D25\uFF1A${error.message}`, false, {
3085
+ hint: face.kind === "space" ? "morula auth login" : "\u672C\u4EFB\u52A1\u7684\u51ED\u8BC1\u5DF2\u5931\u6548\uFF1A\u786E\u8BA4\u4EFB\u52A1\u4ECD\u5728\u6267\u884C\u4E2D\uFF0C\u6216\u6539\u7528 --space <spaceId> \u4EE5\u7528\u6237\u8EAB\u4EFD\u6362\u53D6"
3086
+ });
3087
+ }
3088
+ if (error.status === 403) {
3089
+ return new CliError("FORBIDDEN", EXIT.FORBIDDEN, `\u6362\u53D6\u53EF\u89C2\u6D4B\u6027\u8FD0\u884C\u73AF\u5883\u5931\u8D25\uFF1A${error.message}`, false, {
3090
+ hint: face.kind === "space" ? `\u786E\u8BA4 --space ${face.spaceId} \u662F\u5F53\u524D\u8EAB\u4EFD\u6709\u6743\u9650\u7684\u7A7A\u95F4` : "\u672C\u4EFB\u52A1\u7684\u51ED\u8BC1\u65E0\u6743\u8BBF\u95EE\u8BE5\u7A7A\u95F4\u7684\u53EF\u89C2\u6D4B\u6027\u6570\u636E\u6E90\uFF1A\u8054\u7CFB\u7A7A\u95F4\u7BA1\u7406\u5458\u6838\u5BF9\u914D\u7F6E"
3091
+ });
3092
+ }
3093
+ if (error.status === 404) {
3094
+ return new CliError(
3095
+ "OBS_SOURCE_NOT_FOUND",
3096
+ EXIT.NOT_FOUND,
3097
+ source ? `--source ${source} \u5728\u672C\u7A7A\u95F4\u4E0D\u5B58\u5728\uFF1A${error.message}` : `\u672C\u7A7A\u95F4\u6CA1\u6709\u53EF\u7528\u7684\u53EF\u89C2\u6D4B\u6027\u6570\u636E\u6E90\uFF1A${error.message}`,
3098
+ false,
3099
+ { hint: "morula obs env --json # \u4E0D\u5E26 --source \u7528\u9ED8\u8BA4\u6E90\u6362\u53D6\uFF1Bcontext.sources \u5217\u51FA\u672C\u7A7A\u95F4\u5168\u90E8\u53EF\u7528\u6E90" }
3100
+ );
3101
+ }
3102
+ return error;
3103
+ }
3104
+ function renderEnvScript(view) {
3105
+ if (view.server === null || view.token === null) {
3106
+ const lines2 = view.notice ? commentLines(view.notice) : ["# \u672C\u7A7A\u95F4\u6CA1\u6709\u53EF\u7528\u7684\u53EF\u89C2\u6D4B\u6027\u6570\u636E\u6E90\uFF08\u672A\u914D\u7F6E\uFF0C\u6216\u5168\u90E8\u5DF2\u505C\u7528\uFF09"];
3107
+ lines2.push("# \u914D\u7F6E\u597D\u6570\u636E\u6E90\u540E\u91CD\u65B0\u6267\u884C\uFF1Amorula obs env");
3108
+ return lines2.join("\n");
3109
+ }
3110
+ const lines = [`# \u4EE4\u724C 30 \u5206\u949F\u540E\u8FC7\u671F${view.expiresAt ? `\uFF08${view.expiresAt}\uFF09` : ""}\uFF1B\u6362\u6E90\u8BF7\u52A0 --source <name|id>`];
3111
+ if (view.notice)
3112
+ lines.push(...commentLines(view.notice));
3113
+ lines.push(`export GRAFANA_SERVER=${shellQuote(view.server)}`);
3114
+ lines.push(`export GRAFANA_TOKEN=${shellQuote(view.token)}`);
3115
+ const orgId = view.context?.orgId;
3116
+ if (orgId !== void 0 && orgId !== null)
3117
+ lines.push(`export GRAFANA_ORG_ID=${shellQuote(String(orgId))}`);
3118
+ return lines.join("\n");
3119
+ }
3120
+ function shellQuote(value) {
3121
+ return `'${value.replace(/'/g, `'\\''`)}'`;
3122
+ }
3123
+ function commentLines(text) {
3124
+ return text.split("\n").map((line) => `# ${line}`.trimEnd());
3125
+ }
3126
+
2996
3127
  // ../morula-cli/src/commands/report.ts
2997
3128
  import { readFile as readFile2 } from "node:fs/promises";
2998
3129
  async function reportCommand(context) {
@@ -3550,12 +3681,12 @@ var SPEC = {
3550
3681
  },
3551
3682
  {
3552
3683
  action: "create",
3553
- summary: "create an autopilot (--executor is a runtime key; the server currently only accepts `dim`)",
3684
+ summary: "create an autopilot (--executor is a runtime key from the built-in catalog or one your tenant has reported)",
3554
3685
  flags: [
3555
3686
  { name: "space", type: "string", valueHint: "<id>", description: "\u5F52\u5C5E\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
3556
3687
  { name: "name", type: "string", valueHint: "<text>", description: "\u540D\u79F0\uFF08\u540C\u79DF\u6237\u552F\u4E00\uFF0C\u5FC5\u586B\uFF09", required: true },
3557
3688
  { 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 },
3558
- { 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 },
3689
+ { name: "executor", type: "string", valueHint: "<key>", description: "\u6267\u884C\u8005\u8FD0\u884C\u65F6 key\uFF08\u5FC5\u586B\uFF1B\u5185\u7F6E dim / claude / codex \u6216\u672C\u79DF\u6237\u5DF2\u4E0A\u62A5\u7684 provider\uFF09", required: true },
3559
3690
  { name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
3560
3691
  { name: "cron", type: "string", valueHint: "<expr>", description: "cron \u8868\u8FBE\u5F0F\uFF085 \u5B57\u6BB5\uFF09\uFF1B\u7F3A\u7701 = \u4EC5 webhook \u89E6\u53D1" },
3561
3692
  { name: "vcs-connection", type: "string", valueHint: "<id>", description: "\u9650\u5B9A\u4ED3\u5E93\u8FDE\u63A5\uFF08\u7F3A\u7701\u4E0D\u9650\uFF09" }
@@ -4004,6 +4135,33 @@ var SPEC = {
4004
4135
  "\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\uFF1BCAPABILITY_UNSUPPORTED / NO_CONNECTION / \u5176\u4F59 \u2192 exit 1"
4005
4136
  ]
4006
4137
  },
4138
+ {
4139
+ resource: "obs",
4140
+ summary: "observability runtime environment ONLY \u2014 this is not a query surface. It hands this machine the gateway entry (GRAFANA_SERVER) + a 30-minute, space-scoped, read-only token (GRAFANA_TOKEN) + minimal context (current source + every usable source of the space), so the agent queries line-side facts DIRECTLY with the runtime-managed engine (gcx) through the platform gateway; write paths are refused by the gateway with 403. The platform defines no query command surface: the engine owns the commands and the syntax (gcx --help / gcx commands). Task face by default (task credential, space inferred); --space <spaceId> for the user face",
4141
+ actions: [
4142
+ {
4143
+ action: "env",
4144
+ summary: "exchange the running environment. Default output is a shell export you can `eval` directly (GRAFANA_SERVER / GRAFANA_TOKEN / GRAFANA_ORG_ID); --json returns the raw response (server / token / expiresAt / context / notice). The env is issued PER SOURCE (server carries the sourceId), so switching sources or renewing the 30-minute token means calling this again with --source <name|id>. Nothing is written to disk (the token never touches a file)",
4145
+ flags: [
4146
+ { name: "source", type: "string", valueHint: "<name|id>", description: "\u6570\u636E\u6E90 name \u6216 id\uFF08\u7F3A\u7701\u7528\u7A7A\u95F4\u9ED8\u8BA4\u6E90\uFF1Benv \u6309\u6E90\u7B7E\u53D1\uFF0C\u6362\u6E90\u8BF7\u91CD\u65B0\u8C03\u7528\uFF09" },
4147
+ { name: "space", type: "string", valueHint: "<spaceId>", description: "\u7A7A\u95F4 ID\uFF08\u7528\u6237\u8EAB\u4EFD\uFF1B\u7F3A\u7701\u8D70\u4EFB\u52A1\u9762\uFF0C\u7A7A\u95F4\u7531\u4EFB\u52A1\u51ED\u8BC1\u63A8\u65AD\uFF1B\u4E0E\u4EFB\u52A1\u9762\u4E92\u65A5\uFF09" },
4148
+ { name: "json", type: "boolean", description: "\u8F93\u51FA\u8FD0\u884C\u73AF\u5883\u63A5\u53E3\u7684\u539F\u59CB\u54CD\u5E94 JSON\uFF08server / token / expiresAt / context / notice\uFF09\uFF0C\u800C\u4E0D\u662F\u53EF eval \u7684 shell export" }
4149
+ ],
4150
+ examples: ['eval "$(morula obs env)"', "morula obs env --source staging --json"],
4151
+ writes: "none",
4152
+ interactive: false,
4153
+ idempotent: true,
4154
+ emits: ["server", "token", "expiresAt", "context", "notice"]
4155
+ }
4156
+ ],
4157
+ notes: [
4158
+ "\u53EA\u4EA4\u4ED8\u8FD0\u884C\u73AF\u5883\uFF0C\u4E0D\u662F\u67E5\u8BE2\u547D\u4EE4\u9762\uFF1A\u67E5\u8BE2\u7531 agent \u76F4\u63A5\u7528\u53D7\u7BA1\u5F15\u64CE\uFF08`gcx`\uFF09\u53D1\u8D77\u2014\u2014\u547D\u4EE4\u4E0E\u8BED\u6CD5\u662F\u5F15\u64CE\u7684\u8FD0\u884C\u671F\u8D44\u4EA7\uFF08`gcx --help` / `gcx commands`\uFF09\uFF0C\u5E73\u53F0\u4E0D\u5217\u4E3E\u3001\u4E0D\u4EE3\u7406",
4159
+ "\u53EA\u8BFB\u8FB9\u754C\u5728\u5E73\u53F0\u7F51\u5173\uFF08\u5199\u65B9\u6CD5\u4E00\u5F8B 403\uFF09\uFF0C\u4E0D\u5728 CLI\uFF1A\u672C resource \u6CA1\u6709\u5199\u64CD\u4F5C\uFF0C\u4E5F\u6CA1\u6709 raw / \u900F\u4F20\u5B50\u547D\u4EE4",
4160
+ "**\u4E0D\u5199\u4EFB\u4F55\u6587\u4EF6**\u3001\u65E0\u526F\u4F5C\u7528\uFF1A\u4EE4\u724C\u53EA\u6253\u5370\uFF0C\u4E0D\u8FDB argv\u3001\u4E0D\u8FDB\u65E5\u5FD7\u3001\u4E0D\u843D\u76D8",
4161
+ "\u6CA1\u6709\u53EF\u7528\u6E90\uFF08\u672A\u914D\u7F6E / \u5168\u90E8\u505C\u7528 / \u6CA1\u6709\u9ED8\u8BA4\u6E90 / \u6307\u5B9A\u6E90\u5DF2\u505C\u7528\uFF09\u2192 200 + `server` / `token` \u4E3A null + `notice`\uFF1A\u9ED8\u8BA4\u8F93\u51FA\u53EA\u7ED9\u6CE8\u91CA\uFF08\u4EBA\u8BDD + \u53EF\u6267\u884C\u4E0B\u4E00\u6B65\uFF09\uFF0C**\u9000\u51FA\u7801 3\uFF08not found\uFF09**",
4162
+ "\u9000\u51FA\u7801\uFF1A\u7F3A\u8EAB\u4EFD\u9762 / \u4E24\u9762\u540C\u65F6\u7ED9 \u2192 2\uFF1B\u6CA1\u6709\u53EF\u7528\u8FD0\u884C\u73AF\u5883\u3001`--source` \u4E0D\u5B58\u5728 \u2192 3\uFF1B\u6362\u53D6 401 \u2192 4\uFF1B\u6362\u53D6 403 \u2192 5\uFF1B\u7F51\u7EDC / \u4E0A\u6E38 \u2192 7\uFF08\u90FD\u5E26\u53EF\u6267\u884C hint\uFF09"
4163
+ ]
4164
+ },
4007
4165
  {
4008
4166
  resource: "attachment",
4009
4167
  summary: "file attachments for objects / comments / chat messages (local file upload)",
@@ -4447,6 +4605,7 @@ var COMMANDS = {
4447
4605
  "dashboard": dashboardCommand,
4448
4606
  "document": documentCommand,
4449
4607
  "object-meta": objectMetaCommand,
4608
+ "obs": obsCommand,
4450
4609
  "report": reportCommand,
4451
4610
  "repo": repoCommand,
4452
4611
  "review": reviewCommand,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morula-cli",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "Use Morula CLI task management from DimCode.",
5
5
  "interface": {
6
6
  "displayName": "Morula CLI",
@@ -754,14 +754,14 @@ morula autopilot list --space <id>
754
754
 
755
755
  **用法**:`morula autopilot create [--space <id>] [--name <text>] [--runbook <text>] [--executor <key>] [--description <text>] [--cron <expr>] [--vcs-connection <id>]`
756
756
 
757
- create an autopilot (--executor is a runtime key; the server currently only accepts `dim`)
757
+ create an autopilot (--executor is a runtime key from the built-in catalog or one your tenant has reported)
758
758
 
759
759
  | Flag | 类型 | 必填 | 取值 | 默认 | 说明 |
760
760
  |---|---|---|---|---|---|
761
761
  | `--space <id>` | string | 是 | - | - | 归属空间 ID(必填) |
762
762
  | `--name <text>` | string | 是 | - | - | 名称(同租户唯一,必填) |
763
763
  | `--runbook <text>` | string | 是 | - | - | Runbook:每次触发给运行时的目标/背景/步骤(必填) |
764
- | `--executor <key>` | string | 是 | - | - | 执行者运行时 key(必填;当前仅 dim) |
764
+ | `--executor <key>` | string | 是 | - | - | 执行者运行时 key(必填;内置 dim / claude / codex 或本租户已上报的 provider) |
765
765
  | `--description <text>` | string | - | - | - | 描述 |
766
766
  | `--cron <expr>` | string | - | - | - | cron 表达式(5 字段);缺省 = 仅 webhook 触发 |
767
767
  | `--vcs-connection <id>` | string | - | - | - | 限定仓库连接(缺省不限) |
@@ -1593,6 +1593,42 @@ morula ci retry --space <id> --job <jobId>
1593
1593
 
1594
1594
  ---
1595
1595
 
1596
+ ## `morula obs`
1597
+
1598
+ observability runtime environment ONLY — this is not a query surface. It hands this machine the gateway entry (GRAFANA_SERVER) + a 30-minute, space-scoped, read-only token (GRAFANA_TOKEN) + minimal context (current source + every usable source of the space), so the agent queries line-side facts DIRECTLY with the runtime-managed engine (gcx) through the platform gateway; write paths are refused by the gateway with 403. The platform defines no query command surface: the engine owns the commands and the syntax (gcx --help / gcx commands). Task face by default (task credential, space inferred); --space <spaceId> for the user face
1599
+
1600
+ > 只交付运行环境,不是查询命令面:查询由 agent 直接用受管引擎(`gcx`)发起——命令与语法是引擎的运行期资产(`gcx --help` / `gcx commands`),平台不列举、不代理
1601
+ > 只读边界在平台网关(写方法一律 403),不在 CLI:本 resource 没有写操作,也没有 raw / 透传子命令
1602
+ > **不写任何文件**、无副作用:令牌只打印,不进 argv、不进日志、不落盘
1603
+ > 没有可用源(未配置 / 全部停用 / 没有默认源 / 指定源已停用)→ 200 + `server` / `token` 为 null + `notice`:默认输出只给注释(人话 + 可执行下一步),**退出码 3(not found)**
1604
+ > 退出码:缺身份面 / 两面同时给 → 2;没有可用运行环境、`--source` 不存在 → 3;换取 401 → 4;换取 403 → 5;网络 / 上游 → 7(都带可执行 hint)
1605
+
1606
+ ### `morula obs env`
1607
+
1608
+ **用法**:`morula obs env [--source <name|id>] [--space <spaceId>] [--json]`
1609
+
1610
+ exchange the running environment. Default output is a shell export you can `eval` directly (GRAFANA_SERVER / GRAFANA_TOKEN / GRAFANA_ORG_ID); --json returns the raw response (server / token / expiresAt / context / notice). The env is issued PER SOURCE (server carries the sourceId), so switching sources or renewing the 30-minute token means calling this again with --source <name|id>. Nothing is written to disk (the token never touches a file)
1611
+
1612
+ | Flag | 类型 | 必填 | 取值 | 默认 | 说明 |
1613
+ |---|---|---|---|---|---|
1614
+ | `--source <name\|id>` | string | - | `name` `id` | - | 数据源 name 或 id(缺省用空间默认源;env 按源签发,换源请重新调用) |
1615
+ | `--space <spaceId>` | string | - | - | - | 空间 ID(用户身份;缺省走任务面,空间由任务凭证推断;与任务面互斥) |
1616
+ | `--json` | boolean | - | - | - | 输出运行环境接口的原始响应 JSON(server / token / expiresAt / context / notice),而不是可 eval 的 shell export |
1617
+
1618
+ **示例**
1619
+
1620
+ ```bash
1621
+ eval "$(morula obs env)"
1622
+ morula obs env --source staging --json
1623
+ ```
1624
+
1625
+ > `writes: none` · `interactive: no` · `idempotent: yes` · `emits: server, token, expiresAt, context, notice`
1626
+
1627
+ <!-- hand:start obs -->
1628
+ <!-- hand:end obs -->
1629
+
1630
+ ---
1631
+
1596
1632
  ## `morula attachment`
1597
1633
 
1598
1634
  file attachments for objects / comments / chat messages (local file upload)
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: morula-platform
3
+ description: "Use when a Morula task needs platform interaction beyond the runtime brief — branch naming and PR/MR channel rules, checkout and multi-repo workspace, status and verdict protocols, in-run feedback, delivery and final-comment discipline, or live incident queries. Open only the reference your task needs (routing table inside); never read them all."
4
+ user-invocable: false
5
+ allowed-tools: Bash(morula *), Bash(git *), Bash(gh *)
6
+ ---
7
+
8
+ > 本文件是随 CLI 插件分发的**文档副本**。daemon 执行任务时会按平台在任务工作区
9
+ > `.agents/skills/morula-platform/` 注入**权威版本**(GitLab 任务会在 git-workflow.md 附带平台说明段,
10
+ > 并按执行 CLI 写入原生发现路径),两者正文同源。命令细节以工作区内注入的那份与 `morula <resource> --help` 为准。
11
+
12
+ # Morula 平台契约(morula-platform)
13
+
14
+ 本 SKILL 是 Morula 平台对本地智能体(daemon 执行的 AgentTask)的**操作手册入口**。
15
+ 分工:系统提示(runtime brief)管你的身份、目标与钥匙;工作区根 `AGENTS.md` 的
16
+ 「Morula 平台协作契约」段是**不变量与命令索引**(恒常生效,先读它);本 SKILL 只做按域手册的路由。
17
+
18
+ 每份手册自包含、互不依赖。按任务需要打开对应的一份或几份(通常一两份),
19
+ **不要**因为不确定就把全部手册读一遍。
20
+
21
+ ## Routing
22
+
23
+ | 手册 | 任务涉及 |
24
+ |---|---|
25
+ | `.agents/skills/morula-platform/references/workspace.md` | 开工确认现场(git status / 分支现场)、工作区布局与主仓库物化、任务详情与评论获取、环境前置(依赖 / prisma generate)、本地服务端口隔离 |
26
+ | `.agents/skills/morula-platform/references/git-workflow.md` | 分支命名与上报、`repo checkout` / `prepare` 语义、多仓库、推分支、建 PR/MR(含 GitLab 平台说明)、PR 关联与关单 |
27
+ | `.agents/skills/morula-platform/references/task-protocols.md` | 状态流转(task state / assign)、分析任务 verdict、评审任务 submit、运行中反馈(inbox / 打断)、MR 巡检评论 |
28
+ | `.agents/skills/morula-platform/references/delivery.md` | 最终总结评论纪律、`---FINAL---` 分隔符、attachment 文件回传、report 报告、交付物表述 |
29
+ | `.agents/skills/morula-platform/references/observability.md` | 排查线上行为 / 故障:gcx 引擎、数据源选择、只读边界、结果纪律 |
30
+
31
+ 跨任务类型的通用约束(CLI-only、不碰凭据、结论先行、纯问答不建分支等)在 `AGENTS.md` 契约核心段,
32
+ 不在本表内重复。
33
+
34
+ ## 降级说明
35
+
36
+ 若 `skill` 工具按名加载本 SKILL 报 `Unknown skill`(发现范围不含项目级目录),直接读本文件与
37
+ references 文件即可,**不要因为加载失败就跳过契约**。
38
+
39
+ ## References
40
+
41
+ `.agents/skills/morula-platform/references/working-on-issues-source-map.md` —— 每条契约对应的服务端/CLI 实现位置(`file:line`),
42
+ 用于校准契约与实现一致。
@@ -0,0 +1,60 @@
1
+ # 交付纪律(评论 / 文件 / 报告)
2
+
3
+ > 评论/附件/报告命令索引见 `AGENTS.md` 契约核心段。
4
+
5
+ ## 生成文件必须回传平台(智能体 → 平台)
6
+
7
+ 若你的交付物是**文件**(报告、文档、图表格、xlsx、图片、压缩包等),且用户需要在线查看/下载,**必须**用 `morula attachment upload` 把本机文件回传到平台,而不是在消息里给一个本地相对路径/文件名(用户打不开):
8
+
9
+ - **纯 Chat 会话任务(推荐)**:本任务不关联业务对象(task-context.md 无 objectId),**必须带 `--task <AgentTaskID>`**(AgentTask ID = `morula task get`(无参)返回的 `id`(或 task-context.md 开头的 UUID)),服务端据此把文件挂到本 Chat 会话消息:
10
+ ```bash
11
+ morula attachment upload <file> --task <AgentTaskID>
12
+ ```
13
+ - **默认智能归并**(有绑定对象时):不传目标参数,服务端自动决定——有任务绑定对象挂对象:
14
+ ```bash
15
+ morula attachment upload <file>
16
+ ```
17
+ - **显式挂任务对象**(用户从任务/评论发起、任务有 objectId):
18
+ ```bash
19
+ morula attachment upload <file> --object <objectId>
20
+ ```
21
+ - **显式挂指定评论**:`morula attachment upload <file> --comment <commentId>`
22
+ - **显式挂 Chat 会话消息**:`morula attachment upload <file> --ag-message --task <AgentTaskID>`
23
+
24
+ **回传后**:命令输出 JSON,含 `downloadUrl` 与附件 `id`。在最终总结里**引用下载链接**(`http...` 或附件的可下载地址),并说明文件名即可——不要把本地绝对路径/相对文件名当交付物。<br>
25
+ **文件类型不限**,单文件上限 20MB;超限需拆分或说明,不要硬传失败。
26
+
27
+ ## 自动化的信息类产出:用 `morula report create`
28
+
29
+ 当本任务是**自动化(定时/触发)派发**的,且你的产出是给人看的**信息类结果**(巡检/扫描结论、摘要、分析),用报告承载:
30
+
31
+ ```bash
32
+ morula report create --title "<一句话标题>" --content "<Markdown 正文>" # 正文长时用 --content-file <path>
33
+ ```
34
+
35
+ - 报告落在**本次执行所属自动化的「报告」入口**(不在任务看板、不占任务编号),归属由平台自动绑定,不用传;
36
+ - 一次执行可产出多份(一份报告一个主题);**要建工单请用任务,不要把待办事项写进报告正文**;
37
+ - 报告落库后创建者与订阅者会收到轻通知(只含标题与跳转);
38
+ - 不产出也没关系:任务终态时平台会用你的最终输出兜底落一份报告(但兜底不如你主动组织的内容清晰,信息类产出**优先主动产出**)。
39
+
40
+ ## 评论交付纪律(最终总结由你自己发布)
41
+
42
+ - **最终总结评论由你自己发布**:任务收尾时执行
43
+ ```bash
44
+ morula comment add --object <objectId> --final --content <结论>
45
+ ```
46
+ 作者=你(智能体身份);评论触发任务时服务端会**自动把它挂到触发评论下**作为回复(无需自己查评论 ID)。
47
+ **完成后必须在任务评论区留下一条你自己的总结性评论(做了什么、结果、后续),再上报任务结果**——这是交付的一部分,一条评论都没发不算完成。服务端只在你一条评论都没发时才兜底代发。
48
+ `--final` 是防重标记(同一任务重复执行只保留一条总结,重试/重跑不会发第二条;重复发送被服务端拒绝是预期内的幂等保护),不是交付判据——收尾总结推荐带上。
49
+ - **例外:评审任务不需要(也不应)发 final**——你的结论已经通过 `morula review submit` 机器协议上报,平台会据此写权威结论评论(✅ 通过 / 💬 非阻塞 / 🔁 未通过);再发一条 final 总结会让同一结论在评论区**说两遍**(服务端也会拒绝评审任务的 final)。需要发 final 的是开发 / 修复 / 分析 / 问答 / 评论触发类任务——内容**用你自己的话写**,不要套模板。
50
+ - **普通评论**(说明进展、@ 其他人或运行时)随时可发:
51
+ ```bash
52
+ morula comment add --object <objectId> --content <文本>
53
+ morula comment add --object <objectId> --content <文本> --mention-agent <stageExecutorId> # @ 阶段运行时(触发该执行器在本任务上干活)
54
+ ```
55
+ chat 会话无 objectId 的任务(纯对话派发)不发评论——最终交付由 daemon 回填 assistant 消息。
56
+ - **评论纪律:只写结论与事实**——做了什么、结果如何、用户需要知道的后续;**不写**思考过程、探索步骤、工具日志、终端输出、命令回显。用户只关心「做了什么、结果怎样」,不关心「怎么做的」。内容较多时用小标题/列表组织,结论先置顶。
57
+ - **PR 汇报仅当「任务涉及代码改动」或「用户明确问到 PR/合并」时给出**;纯问答/调研类任务不要出现 PR/分支章节。
58
+ - **任务结果文本同样只写结论**:上报的任务结果会被服务端用于缺失兜底与通知摘要——若结果里混入了中间过程(英文探索叙述、工具尝试记录、思考片段),**必须**在真正的总结之前插入独占一行的 `---FINAL---` 分隔符,服务端兜底时只保留它之后的内容(`---SUMMARY---` 同样兼容;不写时服务端退回「按总结类标题 / 水平线猜测」,猜不中会把过程文本一起兜底出去)。
59
+ - **不要把本地绝对路径当交付物**:工作目录路径只属于执行它的机器,读者打不开;生成的文件**必须先回传平台**(见上「生成文件必须回传平台」),交付物用平台下载链接或仓库内相对路径。
60
+ - 不要包含工具调试过程、内部日志、任何凭据或 token 信息。