@arcships/morula-runtime 0.1.0-alpha.2 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/morula.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 业务命令面发布入口:`morula` = Morula 业务 CLI(task / repo / comment / review / space / attachment / report)。
4
+ *
5
+ * 与 `morula-runtime` 同属 `@arcships/morula-runtime` 单包(docs/features/single-package-cli-distribution.md):
6
+ * bin 指向本文件,本文件 import 构建产物 dist/morula.js(由 @morula/morula-cli 入口 bundle 而来)。
7
+ * 本地开发走 `pnpm dev:morula`(tsx 直接跑源码),不经过本文件。
8
+ */
9
+ import '../dist/morula.js'
package/dist/index.js CHANGED
@@ -1715,7 +1715,7 @@ function page(ok, message) {
1715
1715
  }
1716
1716
 
1717
1717
  // ../morula-cli-core/dist/build-env.js
1718
- var BUILD_ID = "20260913141147.nogit";
1718
+ var BUILD_ID = "20260914094501.nogit";
1719
1719
  var BUILD_CONTEXTS = {
1720
1720
  "local": "http://localhost:8080/api",
1721
1721
  "test": "https://morula.basecastle.com/api",
@@ -2873,7 +2873,7 @@ function redactInputSummary(value) {
2873
2873
  }
2874
2874
 
2875
2875
  // src/build-env.ts
2876
- var BUILD_VERSION2 = "0.1.0-alpha.2";
2876
+ var BUILD_VERSION2 = "0.1.0-alpha.3";
2877
2877
 
2878
2878
  // src/commands/daemon.ts
2879
2879
  import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync10, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
@@ -3672,11 +3672,11 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
3672
3672
 
3673
3673
  | \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
3674
3674
  |------|------|------|
3675
- | CLI \u8BFB\u8BC4\u8BBA | \`morula comment list --object <objectId>\` | \`apps/morula-cli/src/commands/comment.ts\` |
3676
- | CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`apps/morula-cli/src/commands/comment.ts\` |
3677
- | CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`apps/morula-cli/src/commands/task.ts\` |
3675
+ | CLI \u8BFB\u8BC4\u8BBA | \`morula comment list --object <objectId>\` | \`packages/morula-cli/src/commands/comment.ts\` |
3676
+ | CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`packages/morula-cli/src/commands/comment.ts\` |
3677
+ | CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`packages/morula-cli/src/commands/task.ts\` |
3678
3678
  | daemon \u6CE8\u5165\u51ED\u636E\uFF08agent \u65E0\u9700\u81EA\u884C\u767B\u5F55\uFF09 | runner \u6CE8\u5165 \`env\` \u7ED9 Provider \u5B50\u8FDB\u7A0B | \`packages/runtime/src/daemon/runner.ts\` |
3679
- | \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u4ED3\u5E93\u6E05\u5355 / \u5DE5\u5177\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u83B7\u53D6 | \`buildTaskPromptWithContract\`\uFF08\`runner.ts\`\uFF0C\u552F\u4E00\u8FDB dim \u7684 messages[0]\uFF09\uFF1BCLI \`apps/morula-cli/src/commands/task.ts\`\uFF08resolveTaskId \u2192 \`GET /agent-tasks/:id\`\uFF09 | \`packages/runtime/src/daemon/runner.ts\` |
3679
+ | \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u4ED3\u5E93\u6E05\u5355 / \u5DE5\u5177\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u83B7\u53D6 | \`buildTaskPromptWithContract\`\uFF08\`runner.ts\`\uFF0C\u552F\u4E00\u8FDB dim \u7684 messages[0]\uFF09\uFF1BCLI \`packages/morula-cli/src/commands/task.ts\`\uFF08resolveTaskId \u2192 \`GET /agent-tasks/:id\`\uFF09 | \`packages/runtime/src/daemon/runner.ts\` |
3680
3680
 
3681
3681
  ## 3. \u6539\u4EE3\u7801\u9ED8\u8BA4\u4EA7 PR\uFF08\u5206\u652F\u7EAA\u5F8B\uFF0CAgent \u53D6\u540D\uFF09
3682
3682
 
@@ -3694,13 +3694,13 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
3694
3694
  | \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
3695
3695
  |------|------|------|
3696
3696
  | merge \u81EA\u52A8\u5173\u5355\uFF08\u670D\u52A1\u7AEF\u9A71\u52A8\u72B6\u6001\uFF09 | webhook merged \u2192 closeIntent \u2192 Object \u5B8C\u6210 | \`src/modules/vcs-integration/webhook/vcs-event.service.ts\` |
3697
- | agent \u4E0D\u8C03 CLI \u6539\u72B6\u6001 | \u534F\u4F5C\u5951\u7EA6\u7B2C 4 \u6761\u3002**\u6CE8\u610F\uFF1ACLI \u6709 \`morula task state <id> --state <code>\`**\uFF08\u673A\u5668\u534F\u8BAE \`POST /agent-tasks/:id/state\`\uFF0C\u4F9B\u5E73\u53F0/\u81EA\u6D41\u8F6C\u4F7F\u7528\uFF09\u2014\u2014\u662F**\u5951\u7EA6\u8981\u6C42 agent \u4E0D\u8C03\u7528**\u5B83\uFF0C\u4E0D\u662F\u300CCLI \u6CA1\u6709\u8FD9\u4E2A\u547D\u4EE4\u300D | \`apps/morula-cli/src/commands/task.ts\`\uFF08action \`state\`\uFF09\u3001\`src/modules/agent-task/agent-task.controller.ts\`\uFF08\u7AEF\u70B9\uFF09 |
3697
+ | agent \u4E0D\u8C03 CLI \u6539\u72B6\u6001 | \u534F\u4F5C\u5951\u7EA6\u7B2C 4 \u6761\u3002**\u6CE8\u610F\uFF1ACLI \u6709 \`morula task state <id> --state <code>\`**\uFF08\u673A\u5668\u534F\u8BAE \`POST /agent-tasks/:id/state\`\uFF0C\u4F9B\u5E73\u53F0/\u81EA\u6D41\u8F6C\u4F7F\u7528\uFF09\u2014\u2014\u662F**\u5951\u7EA6\u8981\u6C42 agent \u4E0D\u8C03\u7528**\u5B83\uFF0C\u4E0D\u662F\u300CCLI \u6CA1\u6709\u8FD9\u4E2A\u547D\u4EE4\u300D | \`packages/morula-cli/src/commands/task.ts\`\uFF08action \`state\`\uFF09\u3001\`src/modules/agent-task/agent-task.controller.ts\`\uFF08\u7AEF\u70B9\uFF09 |
3698
3698
 
3699
3699
  ## 4.1 \u8BC4\u5BA1\u4EFB\u52A1\u7ED3\u8BBA\u56DE\u4F20\uFF08morula review submit\uFF09
3700
3700
 
3701
3701
  | \u5951\u7EA6 | \u5B9E\u73B0 | \u4F4D\u7F6E |
3702
3702
  |------|------|------|
3703
- | \u8BC4\u5BA1\u7ED3\u8BBA\u673A\u5668\u534F\u8BAE\uFF08approved / changes_requested / commented\uFF0Cchanges_requested \u5FC5\u987B\u5E26 details\uFF09 | \`morula review submit <objectId> --verdict ... [--details] [--pr-number]\` \u2192 \`POST /agent-collaborations/review/submit\`\uFF08\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\uFF09 | \`apps/morula-cli/src/commands/review.ts\`\u3001\`src/modules/agent-task/review-loop.service.ts\` |
3703
+ | \u8BC4\u5BA1\u7ED3\u8BBA\u673A\u5668\u534F\u8BAE\uFF08approved / changes_requested / commented\uFF0Cchanges_requested \u5FC5\u987B\u5E26 details\uFF09 | \`morula review submit <objectId> --verdict ... [--details] [--pr-number]\` \u2192 \`POST /agent-collaborations/review/submit\`\uFF08\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\uFF09 | \`packages/morula-cli/src/commands/review.ts\`\u3001\`src/modules/agent-task/review-loop.service.ts\` |
3704
3704
  | \u8BC4\u5BA1\u5BF9\u8C61\uFF08objectId\uFF09\u7ECF AgentTask \u672C\u4F53\u4E0B\u53D1\uFF08\`morula task get\` \u65E0\u53C2\u8FD4\u56DE\uFF09 | AgentTask \u8BB0\u5F55 \`objectId\` \u5B57\u6BB5\uFF08enqueue \u65F6\u5199\u5165\uFF09 | \`src/modules/agent-task/review-loop.service.ts\` |
3705
3705
 
3706
3706
  ## 5. \u8BC4\u8BBA\u4EA4\u4ED8\u7EAA\u5F8B\uFF08\u4E00\u6761\u6700\u7EC8\u8BC4\u8BBA\uFF0C\u7ED3\u679C\u4E0A\u5E73\u53F0\uFF09
@@ -3792,19 +3792,33 @@ import { execFileSync as execFileSync4 } from "node:child_process";
3792
3792
  import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
3793
3793
  import { join as join8 } from "node:path";
3794
3794
  var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
3795
+ function prependToPath(dirs) {
3796
+ const cur = process.env.PATH ?? "";
3797
+ const parts = new Set(cur.split(":").filter(Boolean));
3798
+ const missing = dirs.filter((d) => !parts.has(d));
3799
+ if (missing.length)
3800
+ process.env.PATH = [...missing, cur].join(":");
3801
+ return missing;
3802
+ }
3795
3803
  function ensureGitOnPath() {
3796
3804
  try {
3797
3805
  execFileSync4("git", ["--version"], { stdio: "pipe" });
3798
3806
  return;
3799
3807
  } catch {
3800
3808
  }
3801
- const cur = process.env.PATH ?? "";
3802
- const missing = GIT_CANDIDATE_DIRS.filter((d) => existsSync5(join8(d, "git")) && !cur.split(":").includes(d));
3809
+ const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync5(join8(d, "git"))));
3803
3810
  if (missing.length) {
3804
- process.env.PATH = [...missing, cur].join(":");
3805
3811
  console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
3806
3812
  }
3807
3813
  }
3814
+ function ensureNodeOnPath() {
3815
+ const nodeDir = process.execPath ? join8(process.execPath, "..") : "";
3816
+ const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync5(join8(d, "node")));
3817
+ const missing = prependToPath(candidates);
3818
+ if (missing.length) {
3819
+ console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
3820
+ }
3821
+ }
3808
3822
  function buildRepoEnv(token, baseUrl) {
3809
3823
  if (baseUrl) {
3810
3824
  const host = new URL(baseUrl).host;
@@ -4751,8 +4765,8 @@ var DimAgentAdapter = class {
4751
4765
  }
4752
4766
  }
4753
4767
  async probe() {
4754
- const { existsSync: existsSync16 } = await import("node:fs");
4755
- const installed = existsSync16(this.dimPath) || this.dimPath === "dim";
4768
+ const { existsSync: existsSync17 } = await import("node:fs");
4769
+ const installed = existsSync17(this.dimPath) || this.dimPath === "dim";
4756
4770
  let authStatus = installed ? "unknown" : "error";
4757
4771
  let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
4758
4772
  let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
@@ -6164,7 +6178,7 @@ var DaemonWsClient = class {
6164
6178
  console.log(`[daemon] WS \u5DF2\u8FDE\u63A5\uFF08${(/* @__PURE__ */ new Date()).toISOString()}\uFF09`);
6165
6179
  });
6166
6180
  socket.on("agent-task:dispatched", (payload) => {
6167
- handlers.onTaskDispatched(payload.taskId);
6181
+ handlers.onTaskDispatched(payload.taskId, payload.runtimeId);
6168
6182
  });
6169
6183
  socket.on("agent-task:status-changed", (payload) => {
6170
6184
  if (payload?.status === "cancelled" && payload.taskId) {
@@ -6359,6 +6373,7 @@ var DaemonRunner = class {
6359
6373
  return;
6360
6374
  this.running = true;
6361
6375
  ensureGitOnPath();
6376
+ ensureNodeOnPath();
6362
6377
  {
6363
6378
  const cfg2 = loadDaemonConfig(this.store);
6364
6379
  try {
@@ -6440,8 +6455,8 @@ var DaemonRunner = class {
6440
6455
  this.repoCheckout?.setRuntimeId(registered.id);
6441
6456
  this.startHeartbeatTimer();
6442
6457
  this.ws.connect({
6443
- onTaskDispatched: (taskId) => {
6444
- void this.handleTask(taskId);
6458
+ onTaskDispatched: (taskId, runtimeId) => {
6459
+ this.onTaskDispatched(taskId, runtimeId);
6445
6460
  },
6446
6461
  // M1-A1:取消信号 → 中断执行中的 dim 进程
6447
6462
  onTaskCancelled: (taskId) => {
@@ -6782,6 +6797,20 @@ var DaemonRunner = class {
6782
6797
  }, Math.max(0, nextAt - Date.now()));
6783
6798
  this.outboxTimer.unref?.();
6784
6799
  }
6800
+ /**
6801
+ * 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
6802
+ * 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
6803
+ * 房间),同租户每台 daemon 都会收到,故**必须按 `runtimeId` 自过滤**——否则每台设备都会替每条任务
6804
+ * 去 claim,任一台设备的一次网络抖动都会把归属设备正在跑的任务判失败(`POST /agent-tasks/:id/result`
6805
+ * 不校验任务归属,见同 issue 根因 ③ / ④)。
6806
+ *
6807
+ * `runtimeId` 缺失(旧服务端)→ 保持旧行为不过滤;poll 兜底本就按 runtimeId 查询,不受影响。
6808
+ */
6809
+ onTaskDispatched(taskId, runtimeId) {
6810
+ if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
6811
+ return;
6812
+ void this.handleTask(taskId);
6813
+ }
6785
6814
  /** 任务处理(幂等:已在处理中则跳过) */
6786
6815
  async handleTask(taskId) {
6787
6816
  if (this.processing.has(taskId))
@@ -6789,6 +6818,7 @@ var DaemonRunner = class {
6789
6818
  this.processing.add(taskId);
6790
6819
  const ctrl = new AbortController();
6791
6820
  this.cancels.set(taskId, ctrl);
6821
+ let claimed = false;
6792
6822
  try {
6793
6823
  if (!this.runtimeId)
6794
6824
  return;
@@ -6797,6 +6827,7 @@ var DaemonRunner = class {
6797
6827
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u9886\u53D6\u5931\u8D25: ${claim.code}`);
6798
6828
  return;
6799
6829
  }
6830
+ claimed = true;
6800
6831
  await this.executeTask(taskId, ctrl.signal);
6801
6832
  } catch (error) {
6802
6833
  if (ctrl.signal.aborted) {
@@ -6804,6 +6835,10 @@ var DaemonRunner = class {
6804
6835
  return;
6805
6836
  }
6806
6837
  const message = error instanceof Error ? error.message : String(error);
6838
+ if (!claimed) {
6839
+ this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u672A\u53D6\u5F97\u6267\u884C\u6743\uFF0C\u5FFD\u7565\u672C\u6B21\u5F02\u5E38\uFF08\u4E0D\u4E0A\u62A5\u5931\u8D25\uFF09: ${message}`);
6840
+ return;
6841
+ }
6807
6842
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5904\u7406\u5F02\u5E38: ${message}`);
6808
6843
  await this.submitResultReliable(taskId, { ok: false, error: message }).catch(() => {
6809
6844
  });
@@ -8278,10 +8313,128 @@ function parseMaxConcurrent(context) {
8278
8313
  return value;
8279
8314
  }
8280
8315
 
8316
+ // src/daemon/plugin-register.ts
8317
+ import { chmodSync as chmodSync3, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync4, readFileSync as readFileSync11, rmSync as rmSync8, statSync as statSync3, writeFileSync as writeFileSync10 } from "node:fs";
8318
+ import { homedir as homedir11 } from "node:os";
8319
+ import { dirname as dirname8, join as join19 } from "node:path";
8320
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
8321
+ var PLUGIN_NAME = "morula-cli";
8322
+ var MANIFEST_DIR_NAME = ".codex-plugin";
8323
+ function legacyAgentsPluginDir(home = homedir11()) {
8324
+ return join19(home, ".agents", "plugins", PLUGIN_NAME);
8325
+ }
8326
+ function resolveDimcodeHome(env = process.env, home = homedir11()) {
8327
+ const fromEnv = env.DIMCODE_HOME?.trim();
8328
+ return fromEnv || join19(home, ".dimcode", "v2");
8329
+ }
8330
+ function defaultPluginsRoot(env = process.env, home = homedir11()) {
8331
+ return join19(resolveDimcodeHome(env, home), "plugins");
8332
+ }
8333
+ function resolvePackagePaths(entryUrl) {
8334
+ const distDir = dirname8(fileURLToPath2(entryUrl));
8335
+ return {
8336
+ packageRoot: dirname8(distDir),
8337
+ distDir,
8338
+ assetsDir: join19(distDir, "plugin")
8339
+ };
8340
+ }
8341
+ function buildLauncherScript(opts) {
8342
+ const entry = join19(opts.packageRoot, "dist", "morula.js");
8343
+ return `#!/bin/sh
8344
+ # Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
8345
+ # \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
8346
+ NODE=${JSON.stringify(opts.nodePath)}
8347
+ [ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
8348
+ if [ -z "$NODE" ]; then
8349
+ echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
8350
+ exit 127
8351
+ fi
8352
+ exec "$NODE" ${JSON.stringify(entry)} "$@"
8353
+ `;
8354
+ }
8355
+ function assetsReady(paths) {
8356
+ return existsSync16(join19(paths.assetsDir, "plugin.json")) && existsSync16(join19(paths.assetsDir, "skills"));
8357
+ }
8358
+ function listSkillNames(assetsDir) {
8359
+ const dir = join19(assetsDir, "skills");
8360
+ if (!existsSync16(dir))
8361
+ return [];
8362
+ return readdirSync4(dir).filter((name) => {
8363
+ try {
8364
+ return statSync3(join19(dir, name)).isDirectory();
8365
+ } catch {
8366
+ return false;
8367
+ }
8368
+ }).sort();
8369
+ }
8370
+ function registerPlugin(opts) {
8371
+ const { pluginsRoot, paths, version } = opts;
8372
+ const nodePath = opts.nodePath ?? process.execPath;
8373
+ const pluginDir = join19(pluginsRoot, PLUGIN_NAME);
8374
+ if (!assetsReady(paths))
8375
+ throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
8376
+ const skillsSrc = join19(paths.assetsDir, "skills");
8377
+ const skills = listSkillNames(paths.assetsDir);
8378
+ const manifestDir = join19(pluginDir, MANIFEST_DIR_NAME);
8379
+ mkdirSync12(manifestDir, { recursive: true });
8380
+ const manifest = JSON.parse(readFileSync11(join19(paths.assetsDir, "plugin.json"), "utf8"));
8381
+ writeFileSync10(join19(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
8382
+ `);
8383
+ const skillsDest = join19(pluginDir, "skills");
8384
+ rmSync8(skillsDest, { recursive: true, force: true });
8385
+ mkdirSync12(skillsDest, { recursive: true });
8386
+ cpSync(skillsSrc, skillsDest, { recursive: true });
8387
+ const binDir = join19(pluginDir, "bin");
8388
+ mkdirSync12(binDir, { recursive: true });
8389
+ const binPath = join19(binDir, "morula");
8390
+ writeFileSync10(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
8391
+ chmodSync3(binPath, 493);
8392
+ const legacy = legacyAgentsPluginDir(opts.home);
8393
+ return {
8394
+ pluginDir,
8395
+ binPath,
8396
+ skills,
8397
+ version,
8398
+ node: nodePath,
8399
+ legacyDir: existsSync16(join19(legacy, MANIFEST_DIR_NAME)) ? legacy : null
8400
+ };
8401
+ }
8402
+
8403
+ // src/commands/setup.ts
8404
+ var KNOWN_FLAGS = ["root", "help"];
8405
+ async function setupCommand(context) {
8406
+ rejectUnknownFlags(context.parsed.flags, KNOWN_FLAGS);
8407
+ if (context.parsed.action && context.parsed.action !== "setup")
8408
+ throw usage("usage: morula-runtime setup [--root <dir>]", { hint: "morula-runtime setup --help" });
8409
+ const rootOverride = flagString(context.parsed.flags, "root");
8410
+ const pluginsRoot = rootOverride ?? defaultPluginsRoot(context.env);
8411
+ const paths = resolvePackagePaths(import.meta.url);
8412
+ let result;
8413
+ try {
8414
+ result = registerPlugin({ pluginsRoot, paths, version: BUILD_VERSION2 });
8415
+ } catch (error) {
8416
+ throw new CliError("ERROR", EXIT.OTHER, error instanceof Error ? error.message : String(error), false, {
8417
+ hint: "\u6E90\u7801\u6A21\u5F0F\u8BF7\u5148 `pnpm --filter @arcships/morula-runtime run build:dist`\uFF1B\u5168\u5C40\u5B89\u88C5\u8BF7\u91CD\u88C5\u8BE5\u5305"
8418
+ });
8419
+ }
8420
+ return {
8421
+ ok: true,
8422
+ pluginRoot: result.pluginDir,
8423
+ version: result.version,
8424
+ binPath: result.binPath,
8425
+ skills: result.skills,
8426
+ node: result.node,
8427
+ legacyRoot: result.legacyDir,
8428
+ ...result.legacyDir ? { hint: "\u68C0\u6D4B\u5230\u65E7\u7684\u540C\u540D\u63D2\u4EF6\u76EE\u5F55\uFF0C\u8BF7\u5220\u9664\u4EE5\u514D\u4E24\u4E2A\u63D2\u4EF6\u6839\u540C\u540D\u51B2\u7A81\uFF08Dim \u4F18\u5148\u547D\u4E2D <DIMCODE_HOME>/plugins\uFF09" } : {},
8429
+ note: "Dim \u5728**\u65B0 session** \u624D\u4F1A\u91CD\u65B0\u626B\u63CF\u63D2\u4EF6\u6839\uFF1B\u5DF2\u5F00\u7740\u7684\u4F1A\u8BDD\u4E0D\u4F1A\u52A0\u8F7D\u65B0 skills\u3002"
8430
+ };
8431
+ }
8432
+
8281
8433
  // src/commands/registry-runtime.ts
8282
8434
  var COMMANDS = {
8283
8435
  auth: authCommand,
8284
- daemon: daemonCommand
8436
+ daemon: daemonCommand,
8437
+ setup: setupCommand
8285
8438
  };
8286
8439
  var EXIT_CODES = {
8287
8440
  0: "success",
@@ -8437,6 +8590,28 @@ var SPEC = {
8437
8590
  notes: [
8438
8591
  'exit code 0 = all checks passed (warn is ok); 1 = at least one check failed -> see checks[] to tell "found problems" from "command failed"'
8439
8592
  ]
8593
+ },
8594
+ {
8595
+ resource: "setup",
8596
+ summary: "materialize the Dim plugin directory (skills + bin launcher) for this package",
8597
+ actions: [
8598
+ {
8599
+ action: "setup",
8600
+ summary: "write <DIMCODE_HOME>/plugins/morula-cli (manifest + skills + bin/morula launcher); offline and idempotent",
8601
+ flags: [
8602
+ { name: "root", type: "string", valueHint: "<dir>", description: "plugins root to install into (default: $DIMCODE_HOME/plugins or ~/.dimcode/v2/plugins)" }
8603
+ ],
8604
+ examples: ["morula-runtime setup", "morula-runtime setup --root /tmp/plugins"],
8605
+ writes: "local",
8606
+ interactive: false,
8607
+ idempotent: true,
8608
+ emits: ["ok", "pluginRoot", "version", "binPath", "skills", "node", "legacyRoot"]
8609
+ }
8610
+ ],
8611
+ notes: [
8612
+ "canonical form is the bare `morula-runtime setup`; postinstall runs the same registration automatically",
8613
+ "only writes <root>/morula-cli; never deletes other plugins or the legacy ~/.agents/plugins/morula-cli (reported as legacyRoot)"
8614
+ ]
8440
8615
  }
8441
8616
  ]
8442
8617
  };