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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -747,7 +747,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
747
747
  var init_build_env = __esm({
748
748
  "../morula-cli-core/dist/build-env.js"() {
749
749
  "use strict";
750
- BUILD_ID = "20261010070134.5226cb13";
750
+ BUILD_ID = "20261010111428.e5cce225";
751
751
  BUILD_CONTEXTS = {
752
752
  "local": "http://localhost:8080/api",
753
753
  "test": "https://morula.basecastle.com/api",
@@ -4380,7 +4380,7 @@ __export(dim_model_exports, {
4380
4380
  listDimModels: () => listDimModels
4381
4381
  });
4382
4382
  import { createRequire } from "node:module";
4383
- import { spawnSync } from "node:child_process";
4383
+ import { spawnSync as spawnSync3 } from "node:child_process";
4384
4384
  import { closeSync as closeSync4, existsSync as existsSync15, openSync as openSync4, readFileSync as readFileSync11, unlinkSync } from "node:fs";
4385
4385
  import { homedir as homedir14, tmpdir } from "node:os";
4386
4386
  import { join as join20 } from "node:path";
@@ -4430,7 +4430,7 @@ function listDimModelsFromCli(dimPath, env) {
4430
4430
  const fd = openSync4(tmpFile, "w");
4431
4431
  let collected = "";
4432
4432
  try {
4433
- const result = spawnSync(dimPath, ["model", "list", "--json"], {
4433
+ const result = spawnSync3(dimPath, ["model", "list", "--json"], {
4434
4434
  stdio: ["ignore", fd, "ignore"],
4435
4435
  // 与 execFileSync 相同的 env 语义:显式 env 时合并 process.env
4436
4436
  ...env ? { env: { ...process.env, ...env } } : {},
@@ -4609,13 +4609,13 @@ init_dist2();
4609
4609
  init_dist2();
4610
4610
 
4611
4611
  // src/build-env.ts
4612
- var BUILD_VERSION2 = "0.1.0-alpha.19";
4612
+ var BUILD_VERSION2 = "0.1.0-alpha.20";
4613
4613
 
4614
4614
  // src/commands/daemon.ts
4615
4615
  init_dist();
4616
4616
  init_dist2();
4617
- import { existsSync as existsSync23, mkdirSync as mkdirSync18, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync16 } from "node:fs";
4618
- import { dirname as dirname15, join as join30 } from "node:path";
4617
+ import { existsSync as existsSync24, mkdirSync as mkdirSync19, readFileSync as readFileSync16, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "node:fs";
4618
+ import { dirname as dirname16, join as join30 } from "node:path";
4619
4619
 
4620
4620
  // src/daemon/daemon-autostart.ts
4621
4621
  init_portal_autostart();
@@ -4684,9 +4684,9 @@ init_portal_server();
4684
4684
  // src/daemon/runner.ts
4685
4685
  init_dist2();
4686
4686
  import { execFileSync as execFileSync11 } from "node:child_process";
4687
- import { existsSync as existsSync21, mkdirSync as mkdirSync17, statSync as statSync5, writeFileSync as writeFileSync15 } from "node:fs";
4687
+ import { existsSync as existsSync22, mkdirSync as mkdirSync18, statSync as statSync5, writeFileSync as writeFileSync16 } from "node:fs";
4688
4688
  import { arch, hostname } from "node:os";
4689
- import { dirname as dirname13, join as join28 } from "node:path";
4689
+ import { dirname as dirname14, join as join28 } from "node:path";
4690
4690
 
4691
4691
  // src/daemon/acp/acp-connection-pool.ts
4692
4692
  var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
@@ -7051,7 +7051,7 @@ function registerPlugin(opts) {
7051
7051
  import { join as join21 } from "node:path";
7052
7052
 
7053
7053
  // src/daemon/providers/claude/claude-preset.ts
7054
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
7054
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
7055
7055
  import { homedir as homedir11 } from "node:os";
7056
7056
  import { join as join17 } from "node:path";
7057
7057
 
@@ -7096,6 +7096,94 @@ function resolveCommandOnPath(name) {
7096
7096
  return null;
7097
7097
  }
7098
7098
 
7099
+ // src/daemon/providers/claude/claude-models.ts
7100
+ import { spawnSync } from "node:child_process";
7101
+ var LIST_MODELS_REQUEST_ID = "morula-list-models";
7102
+ var LIST_MODELS_TIMEOUT_MS = 3e4;
7103
+ var LIST_MODELS_REQUEST = `${JSON.stringify({
7104
+ type: "control_request",
7105
+ request_id: LIST_MODELS_REQUEST_ID,
7106
+ request: { subtype: "list_models" }
7107
+ })}
7108
+ `;
7109
+ var LIST_MODELS_ARGS = [
7110
+ "--print",
7111
+ "--verbose",
7112
+ "--input-format",
7113
+ "stream-json",
7114
+ "--output-format",
7115
+ "stream-json",
7116
+ "--strict-mcp-config"
7117
+ ];
7118
+ function listClaudeModels() {
7119
+ const claude = resolveCommandOnPath("claude");
7120
+ if (!claude)
7121
+ return [];
7122
+ try {
7123
+ const result = spawnSync(claude, LIST_MODELS_ARGS, {
7124
+ input: LIST_MODELS_REQUEST,
7125
+ encoding: "utf8",
7126
+ timeout: LIST_MODELS_TIMEOUT_MS
7127
+ });
7128
+ if (result.error || result.status !== 0 || !result.stdout)
7129
+ return [];
7130
+ return parseClaudeModelCatalog(result.stdout);
7131
+ } catch {
7132
+ return [];
7133
+ }
7134
+ }
7135
+ function parseClaudeModelCatalog(stdout) {
7136
+ for (const line of stdout.split("\n")) {
7137
+ const trimmed = line.trim();
7138
+ if (!trimmed)
7139
+ continue;
7140
+ let parsed;
7141
+ try {
7142
+ parsed = JSON.parse(trimmed);
7143
+ } catch {
7144
+ continue;
7145
+ }
7146
+ if (parsed.type !== "control_response" || parsed.response?.request_id !== LIST_MODELS_REQUEST_ID)
7147
+ continue;
7148
+ if (parsed.response.subtype !== "success" || !Array.isArray(parsed.response.response?.models))
7149
+ return [];
7150
+ return dedupeByResolvedModel(parsed.response.response.models);
7151
+ }
7152
+ return [];
7153
+ }
7154
+ function dedupeByResolvedModel(infos) {
7155
+ const seen = /* @__PURE__ */ new Set();
7156
+ const models2 = [];
7157
+ for (const raw of infos) {
7158
+ const info = raw;
7159
+ const resolved = typeof info.resolvedModel === "string" && info.resolvedModel.trim() ? info.resolvedModel.trim() : typeof info.value === "string" ? info.value.trim() : "";
7160
+ if (!resolved)
7161
+ continue;
7162
+ const key = resolved.toLowerCase();
7163
+ if (seen.has(key))
7164
+ continue;
7165
+ seen.add(key);
7166
+ models2.push({
7167
+ id: resolved,
7168
+ name: typeof info.displayName === "string" && info.displayName ? info.displayName : resolved
7169
+ });
7170
+ }
7171
+ return models2;
7172
+ }
7173
+ function claudeCliVersion() {
7174
+ const claude = resolveCommandOnPath("claude");
7175
+ if (!claude)
7176
+ return void 0;
7177
+ try {
7178
+ const result = spawnSync(claude, ["--version"], { encoding: "utf8", timeout: 15e3 });
7179
+ if (result.error || result.status !== 0 || !result.stdout)
7180
+ return void 0;
7181
+ return result.stdout.trim().match(/\d+\.\d+[\w.-]*/)?.[0];
7182
+ } catch {
7183
+ return void 0;
7184
+ }
7185
+ }
7186
+
7099
7187
  // src/daemon/providers/claude/claude-preset.ts
7100
7188
  var CLAUDE_PROVIDER_KEY = "claude";
7101
7189
  var CLAUDE_DISPLAY_NAME = "Claude Code";
@@ -7130,6 +7218,25 @@ function readHostClaudeOauthToken() {
7130
7218
  return null;
7131
7219
  }
7132
7220
  }
7221
+ function readHostClaudeSettingsEnv() {
7222
+ const file = join17(homedir11(), ".claude", "settings.json");
7223
+ try {
7224
+ if (!existsSync13(file))
7225
+ return {};
7226
+ const parsed = JSON.parse(readFileSync9(file, "utf8"));
7227
+ const env = {};
7228
+ for (const [key, value] of Object.entries(parsed.env ?? {})) {
7229
+ if (typeof value === "string" && value.length > 0)
7230
+ env[key] = value;
7231
+ }
7232
+ return env;
7233
+ } catch {
7234
+ return {};
7235
+ }
7236
+ }
7237
+ function hostClaudeSettingsFile() {
7238
+ return join17(homedir11(), ".claude", "settings.json");
7239
+ }
7133
7240
  function resolveClaudeCredentialEnv(configuredEnv) {
7134
7241
  if (configuredEnv.CLAUDE_CODE_OAUTH_TOKEN) {
7135
7242
  return { env: {}, authStatus: "unknown", authMode: "oauth" };
@@ -7137,6 +7244,13 @@ function resolveClaudeCredentialEnv(configuredEnv) {
7137
7244
  if (configuredEnv.ANTHROPIC_API_KEY) {
7138
7245
  return { env: {}, authStatus: "unknown", authMode: "api-key" };
7139
7246
  }
7247
+ if (configuredEnv.ANTHROPIC_AUTH_TOKEN) {
7248
+ return { env: {}, authStatus: "unknown", authMode: "api-key" };
7249
+ }
7250
+ const hostSettingsEnv = readHostClaudeSettingsEnv();
7251
+ if (hostSettingsEnv.CLAUDE_CODE_OAUTH_TOKEN || hostSettingsEnv.ANTHROPIC_API_KEY || hostSettingsEnv.ANTHROPIC_AUTH_TOKEN) {
7252
+ return { env: {}, authStatus: "unknown", authMode: "api-key" };
7253
+ }
7140
7254
  const hostToken = readHostClaudeOauthToken();
7141
7255
  if (hostToken) {
7142
7256
  return { env: { CLAUDE_CODE_OAUTH_TOKEN: hostToken }, authStatus: "unknown", authMode: "oauth" };
@@ -7146,7 +7260,7 @@ function resolveClaudeCredentialEnv(configuredEnv) {
7146
7260
  authStatus: "unauthenticated",
7147
7261
  authMode: "none",
7148
7262
  lastProbeErrorCode: "CLAUDE_NO_CREDENTIAL",
7149
- lastProbeError: "\u672A\u68C0\u6D4B\u5230 Claude \u767B\u5F55\u6001\uFF08\u5BBF\u4E3B ~/.claude/.credentials.json \u65E0 OAuth token\uFF0C\u4E14\u672A\u914D\u7F6E CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY\uFF09\uFF1B\u5728\u5BBF\u4E3B\u6267\u884C `claude login` \u540E\u91CD\u65B0\u63A2\u6D4B\uFF0C\u6216\u5728 daemon \u914D\u7F6E\u91CC\u4E3A claude \u914D\u7F6E CLAUDE_CODE_OAUTH_TOKEN"
7263
+ lastProbeError: "\u672A\u68C0\u6D4B\u5230 Claude \u767B\u5F55\u6001\uFF08\u5BBF\u4E3B ~/.claude/settings.json \u65E0\u63A5\u5165 env\u3001.credentials.json \u65E0 OAuth token\uFF0C\u4E14\u672A\u914D\u7F6E CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN\uFF09\uFF1B\u5728\u5BBF\u4E3B\u5B8C\u6210 `claude login` \u6216\u914D\u7F6E\u7B2C\u4E09\u65B9\u63A5\u5165\uFF08ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY\uFF09\u540E\u91CD\u65B0\u63A2\u6D4B"
7150
7264
  };
7151
7265
  }
7152
7266
  var CLAUDE_PROVIDER_SPEC = {
@@ -7182,13 +7296,26 @@ var CLAUDE_PROVIDER_SPEC = {
7182
7296
  prepareTaskDataDir: async (home, configuredEnv) => {
7183
7297
  ensureAgentHome(home);
7184
7298
  const cred = resolveClaudeCredentialEnv(configuredEnv);
7299
+ const hostSettings = hostClaudeSettingsFile();
7300
+ try {
7301
+ if (existsSync13(hostSettings))
7302
+ copyFileSync2(hostSettings, join17(home, "settings.json"));
7303
+ } catch {
7304
+ }
7185
7305
  return { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1", ...cred.env };
7186
7306
  },
7187
7307
  probeExtras: async ({ configuredEnv }) => {
7188
7308
  const cred = resolveClaudeCredentialEnv(configuredEnv);
7309
+ const cliVersion = claudeCliVersion();
7189
7310
  return {
7190
7311
  authStatus: cred.authStatus,
7191
7312
  authMode: cred.authMode,
7313
+ // CLI 本体版本(claude --version;握手 agentInfo.version 是 ACP 桥版本,不是 CLI 本体)。
7314
+ // undefined 时不覆盖握手版本(spread 显式 undefined 会把 adapter 层的值抹掉)
7315
+ ...cliVersion ? { cliVersion } : {},
7316
+ // 模型目录(live:claude 控制协议 list_models;第三方网关下 resolvedModel 即实际生效模型;
7317
+ // 拉不到 = 空列表,会话级 configOptions 仍是权威值域)
7318
+ models: listClaudeModels(),
7192
7319
  ...cred.lastProbeErrorCode ? { lastProbeErrorCode: cred.lastProbeErrorCode } : {},
7193
7320
  ...cred.lastProbeError ? { lastProbeError: cred.lastProbeError } : {}
7194
7321
  };
@@ -7204,31 +7331,142 @@ var CLAUDE_PROVIDER_SPEC = {
7204
7331
  };
7205
7332
 
7206
7333
  // src/daemon/providers/codex/codex-preset.ts
7207
- import { existsSync as existsSync14, readFileSync as readFileSync10, symlinkSync, writeFileSync as writeFileSync10 } from "node:fs";
7334
+ import { copyFileSync as copyFileSync3, existsSync as existsSync14, readFileSync as readFileSync10, symlinkSync, writeFileSync as writeFileSync10 } from "node:fs";
7208
7335
  import { homedir as homedir12 } from "node:os";
7209
7336
  import { join as join18 } from "node:path";
7337
+
7338
+ // src/daemon/providers/codex/codex-models.ts
7339
+ import { spawnSync as spawnSync2 } from "node:child_process";
7340
+ var DEBUG_MODELS_TIMEOUT_MS = 3e4;
7341
+ function listCodexModels() {
7342
+ const codex = resolveCommandOnPath("codex");
7343
+ if (!codex)
7344
+ return [];
7345
+ const live = runCodexDebugModels(codex, ["debug", "models"]);
7346
+ if (live.length > 0)
7347
+ return live;
7348
+ return runCodexDebugModels(codex, ["debug", "models", "--bundled"]);
7349
+ }
7350
+ function runCodexDebugModels(codex, args) {
7351
+ try {
7352
+ const result = spawnSync2(codex, args, {
7353
+ encoding: "utf8",
7354
+ timeout: DEBUG_MODELS_TIMEOUT_MS
7355
+ });
7356
+ if (result.error || result.status !== 0 || !result.stdout)
7357
+ return [];
7358
+ return parseCodexModelCatalog(result.stdout);
7359
+ } catch {
7360
+ return [];
7361
+ }
7362
+ }
7363
+ function parseCodexModelCatalog(stdout) {
7364
+ try {
7365
+ const parsed = JSON.parse(stdout);
7366
+ if (!Array.isArray(parsed.models))
7367
+ return [];
7368
+ const models2 = [];
7369
+ for (const raw of parsed.models) {
7370
+ const info = raw;
7371
+ const slug = typeof info.slug === "string" && info.slug.trim() ? info.slug.trim() : "";
7372
+ if (!slug)
7373
+ continue;
7374
+ models2.push({
7375
+ id: slug,
7376
+ name: typeof info.display_name === "string" && info.display_name ? info.display_name : slug
7377
+ });
7378
+ }
7379
+ return models2;
7380
+ } catch {
7381
+ return [];
7382
+ }
7383
+ }
7384
+ function codexCliVersion() {
7385
+ const codex = resolveCommandOnPath("codex");
7386
+ if (!codex)
7387
+ return void 0;
7388
+ try {
7389
+ const result = spawnSync2(codex, ["--version"], { encoding: "utf8", timeout: 15e3 });
7390
+ if (result.error || result.status !== 0 || !result.stdout)
7391
+ return void 0;
7392
+ return result.stdout.trim().match(/\d+\.\d+[\w.-]*/)?.[0];
7393
+ } catch {
7394
+ return void 0;
7395
+ }
7396
+ }
7397
+
7398
+ // src/daemon/providers/codex/codex-preset.ts
7210
7399
  var CODEX_PROVIDER_KEY = "codex";
7211
7400
  var CODEX_DISPLAY_NAME = "Codex";
7212
7401
  var CODEX_ACP_MATCH_ARGS = "codex-acp";
7213
7402
  var CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
7214
7403
  var CODEX_INIT_TIMEOUT_MS = 12e4;
7215
7404
  var CODEX_SUBSCRIPTION_MAX_CONCURRENT = 1;
7405
+ var CODEX_CUSTOM_PROVIDER_ID = "morula_custom";
7216
7406
  function hostCodexAuthFile() {
7217
7407
  return join18(homedir12(), ".codex", "auth.json");
7218
7408
  }
7409
+ function resolveCodexCustomEndpoint(configuredEnv) {
7410
+ const baseUrl = configuredEnv.CODEX_BASE_URL?.trim();
7411
+ if (!baseUrl)
7412
+ return null;
7413
+ return {
7414
+ baseUrl,
7415
+ apiKey: configuredEnv.CODEX_API_KEY?.trim() || void 0,
7416
+ model: configuredEnv.CODEX_MODEL?.trim() || void 0
7417
+ };
7418
+ }
7419
+ function readHostCodexAuthShape() {
7420
+ const file = hostCodexAuthFile();
7421
+ if (!existsSync14(file))
7422
+ return null;
7423
+ try {
7424
+ const parsed = JSON.parse(readFileSync10(file, "utf8"));
7425
+ const tokens = parsed.tokens;
7426
+ const hasToken = !!tokens && (typeof tokens.access_token === "string" && tokens.access_token.length > 0 || typeof tokens.refresh_token === "string" && tokens.refresh_token.length > 0 || typeof tokens.id_token === "string" && tokens.id_token.length > 0);
7427
+ if (hasToken)
7428
+ return "subscription";
7429
+ if (typeof parsed.OPENAI_API_KEY === "string" && parsed.OPENAI_API_KEY.length > 0)
7430
+ return "api-key-file";
7431
+ return null;
7432
+ } catch {
7433
+ return null;
7434
+ }
7435
+ }
7436
+ function hostCodexHasCustomProvider() {
7437
+ const base = readHostCodexConfigToml();
7438
+ if (base === null)
7439
+ return false;
7440
+ return /^[ \t]*model_provider[ \t]*=/m.test(base) && /^\[model_providers\./m.test(base);
7441
+ }
7219
7442
  function resolveCodexAuthMode(configuredEnv) {
7443
+ const custom = resolveCodexCustomEndpoint(configuredEnv);
7444
+ const hostAuth = readHostCodexAuthShape();
7445
+ if (custom) {
7446
+ if (hostAuth) {
7447
+ return {
7448
+ mode: "none",
7449
+ custom,
7450
+ conflictError: "CODEX_AUTH_CONFLICT: \u540C\u65F6\u68C0\u6D4B\u5230\u7B2C\u4E09\u65B9\u7AEF\u70B9\u914D\u7F6E\uFF08CODEX_BASE_URL\uFF09\u4E0E\u5BBF\u4E3B\u767B\u5F55\u6001\uFF08~/.codex/auth.json\uFF09\uFF1B\u4E24\u79CD\u8BA4\u8BC1\u5F62\u6001\u4E92\u65A5\uFF0C\u8BF7\u53EA\u4FDD\u7559\u5176\u4E00\uFF08\u5220\u9664 env \u914D\u7F6E\u6216\u5BBF\u4E3B `codex logout`\uFF09"
7451
+ };
7452
+ }
7453
+ return { mode: "custom-endpoint", custom };
7454
+ }
7220
7455
  const apiKey = configuredEnv.CODEX_API_KEY || configuredEnv.OPENAI_API_KEY || void 0;
7221
- const hasHostAuth = existsSync14(hostCodexAuthFile());
7222
- if (apiKey && hasHostAuth) {
7456
+ if (apiKey && hostAuth) {
7223
7457
  return {
7224
7458
  mode: "none",
7225
- conflictError: "CODEX_AUTH_CONFLICT: \u540C\u65F6\u68C0\u6D4B\u5230 API key \u914D\u7F6E\uFF08CODEX_API_KEY / OPENAI_API_KEY\uFF09\u4E0E\u5BBF\u4E3B\u8BA2\u9605\u767B\u5F55\u6001\uFF08~/.codex/auth.json\uFF09\uFF1B\u4E24\u79CD\u8BA4\u8BC1\u5F62\u6001\u4E92\u65A5\uFF0C\u8BF7\u53EA\u4FDD\u7559\u5176\u4E00\uFF08\u5220\u9664 env \u914D\u7F6E\u6216\u5BBF\u4E3B `codex logout`\uFF09"
7459
+ conflictError: "CODEX_AUTH_CONFLICT: \u540C\u65F6\u68C0\u6D4B\u5230 API key \u914D\u7F6E\uFF08CODEX_API_KEY / OPENAI_API_KEY\uFF09\u4E0E\u5BBF\u4E3B\u767B\u5F55\u6001\uFF08~/.codex/auth.json\uFF09\uFF1B\u4E24\u79CD\u8BA4\u8BC1\u5F62\u6001\u4E92\u65A5\uFF0C\u8BF7\u53EA\u4FDD\u7559\u5176\u4E00\uFF08\u5220\u9664 env \u914D\u7F6E\u6216\u5BBF\u4E3B `codex logout`\uFF09"
7226
7460
  };
7227
7461
  }
7228
7462
  if (apiKey)
7229
7463
  return { mode: "api-key", apiKey };
7230
- if (hasHostAuth)
7464
+ if (hostAuth === "subscription")
7231
7465
  return { mode: "subscription" };
7466
+ if (hostAuth === "api-key-file")
7467
+ return { mode: "host-api-key" };
7468
+ if (hostCodexHasCustomProvider())
7469
+ return { mode: "host-config" };
7232
7470
  return { mode: "none" };
7233
7471
  }
7234
7472
  function resolveCodexAcpLaunch() {
@@ -7253,9 +7491,10 @@ async function prepareCodexTaskHome(home, configuredEnv) {
7253
7491
  if (auth.conflictError)
7254
7492
  throw new Error(auth.conflictError);
7255
7493
  const authFile = join18(home, "auth.json");
7256
- if (auth.mode === "api-key") {
7494
+ if (auth.mode === "custom-endpoint") {
7495
+ } else if (auth.mode === "api-key") {
7257
7496
  writeFileSync10(authFile, JSON.stringify({ OPENAI_API_KEY: auth.apiKey }, null, 2), { mode: 384 });
7258
- } else if (auth.mode === "subscription") {
7497
+ } else if (auth.mode === "subscription" || auth.mode === "host-api-key") {
7259
7498
  const target = hostCodexAuthFile();
7260
7499
  if (!existsSync14(authFile)) {
7261
7500
  symlinkSync(target, authFile);
@@ -7263,10 +7502,71 @@ async function prepareCodexTaskHome(home, configuredEnv) {
7263
7502
  }
7264
7503
  const configToml = join18(home, "config.toml");
7265
7504
  if (!existsSync14(configToml)) {
7266
- writeFileSync10(configToml, '# managed by morula daemoncli_auth_credentials_store = "file"\n', { mode: 384 });
7505
+ writeFileSync10(configToml, buildCodexConfigToml(auth), { mode: 384 });
7267
7506
  }
7507
+ if (auth.mode !== "custom-endpoint" && auth.mode !== "api-key")
7508
+ copyHostCodexReferencedFiles(home);
7268
7509
  return {};
7269
7510
  }
7511
+ function copyHostCodexReferencedFiles(home) {
7512
+ const base = readHostCodexConfigToml();
7513
+ if (base === null)
7514
+ return;
7515
+ const match = base.match(/^[ \t]*model_catalog_json[ \t]*=[ \t]*"([^"]+)"/m);
7516
+ if (!match)
7517
+ return;
7518
+ const fileName = match[1];
7519
+ if (!fileName || fileName.startsWith("/"))
7520
+ return;
7521
+ try {
7522
+ const src = join18(homedir12(), ".codex", fileName);
7523
+ if (existsSync14(src) && !existsSync14(join18(home, fileName)))
7524
+ copyFileSync3(src, join18(home, fileName));
7525
+ } catch {
7526
+ }
7527
+ }
7528
+ function buildCodexConfigToml(auth) {
7529
+ if (auth.mode === "custom-endpoint" && auth.custom) {
7530
+ const custom = auth.custom;
7531
+ const lines = [
7532
+ "# managed by morula daemon",
7533
+ 'cli_auth_credentials_store = "file"'
7534
+ ];
7535
+ if (custom.model)
7536
+ lines.push(`model = ${JSON.stringify(custom.model)}`);
7537
+ lines.push(`model_provider = ${JSON.stringify(CODEX_CUSTOM_PROVIDER_ID)}`, "");
7538
+ lines.push(`[model_providers.${CODEX_CUSTOM_PROVIDER_ID}]`);
7539
+ lines.push(`name = ${JSON.stringify("Custom endpoint (morula)")}`);
7540
+ lines.push(`base_url = ${JSON.stringify(custom.baseUrl)}`);
7541
+ if (custom.apiKey)
7542
+ lines.push('env_key = "CODEX_API_KEY"');
7543
+ return `${lines.join("\n")}
7544
+ `;
7545
+ }
7546
+ const forcedLine = 'cli_auth_credentials_store = "file"';
7547
+ if (auth.mode === "api-key")
7548
+ return `# managed by morula daemon
7549
+ ${forcedLine}
7550
+ `;
7551
+ const base = readHostCodexConfigToml();
7552
+ if (base === null)
7553
+ return `# managed by morula daemon
7554
+ ${forcedLine}
7555
+ `;
7556
+ if (/^[ \t]*cli_auth_credentials_store[ \t]*=/m.test(base))
7557
+ return base.replace(/^[ \t]*cli_auth_credentials_store[ \t]*=.*$/m, forcedLine);
7558
+ return `${base}
7559
+ ${forcedLine}
7560
+ `;
7561
+ }
7562
+ function readHostCodexConfigToml() {
7563
+ try {
7564
+ const file = join18(homedir12(), ".codex", "config.toml");
7565
+ return existsSync14(file) ? readFileSync10(file, "utf8") : null;
7566
+ } catch {
7567
+ return null;
7568
+ }
7569
+ }
7270
7570
  var CODEX_PROVIDER_SPEC = {
7271
7571
  key: CODEX_PROVIDER_KEY,
7272
7572
  displayName: CODEX_DISPLAY_NAME,
@@ -7298,20 +7598,33 @@ var CODEX_PROVIDER_SPEC = {
7298
7598
  prepareTaskDataDir: prepareCodexTaskHome,
7299
7599
  probeExtras: async ({ configuredEnv }) => {
7300
7600
  const auth = resolveCodexAuthMode(configuredEnv);
7601
+ const cliVersion = codexCliVersion();
7602
+ const models2 = listCodexModels();
7301
7603
  if (auth.conflictError) {
7302
- return { authStatus: "error", authMode: "none", lastProbeErrorCode: "CODEX_AUTH_CONFLICT", lastProbeError: auth.conflictError };
7604
+ return { authStatus: "error", authMode: "none", ...cliVersion ? { cliVersion } : {}, models: models2, lastProbeErrorCode: "CODEX_AUTH_CONFLICT", lastProbeError: auth.conflictError };
7605
+ }
7606
+ if (auth.mode === "custom-endpoint") {
7607
+ return { authStatus: "unknown", authMode: auth.custom?.apiKey ? "api-key" : "none", ...cliVersion ? { cliVersion } : {}, models: models2 };
7303
7608
  }
7304
7609
  if (auth.mode === "api-key") {
7305
- return { authStatus: "unknown", authMode: "api-key" };
7610
+ return { authStatus: "unknown", authMode: "api-key", ...cliVersion ? { cliVersion } : {}, models: models2 };
7611
+ }
7612
+ if (auth.mode === "host-api-key") {
7613
+ return { authStatus: "unknown", authMode: "api-key", ...cliVersion ? { cliVersion } : {}, models: models2 };
7614
+ }
7615
+ if (auth.mode === "host-config") {
7616
+ return { authStatus: "unknown", authMode: "api-key", ...cliVersion ? { cliVersion } : {}, models: models2 };
7306
7617
  }
7307
7618
  if (auth.mode === "subscription") {
7308
- return { authStatus: "unknown", authMode: "subscription", maxConcurrentTasks: CODEX_SUBSCRIPTION_MAX_CONCURRENT };
7619
+ return { authStatus: "unknown", authMode: "subscription", ...cliVersion ? { cliVersion } : {}, models: models2, maxConcurrentTasks: CODEX_SUBSCRIPTION_MAX_CONCURRENT };
7309
7620
  }
7310
7621
  return {
7311
7622
  authStatus: "unauthenticated",
7312
7623
  authMode: "none",
7624
+ ...cliVersion ? { cliVersion } : {},
7625
+ models: models2,
7313
7626
  lastProbeErrorCode: "CODEX_NO_CREDENTIAL",
7314
- lastProbeError: "\u672A\u68C0\u6D4B\u5230 Codex \u767B\u5F55\u6001\uFF08\u5BBF\u4E3B ~/.codex/auth.json \u4E0D\u5B58\u5728\uFF0C\u4E14\u672A\u914D\u7F6E CODEX_API_KEY / OPENAI_API_KEY\uFF09\uFF1B\u5728\u5BBF\u4E3B\u6267\u884C `codex login` \u540E\u91CD\u65B0\u63A2\u6D4B\uFF0C\u6216\u5728 daemon \u914D\u7F6E\u91CC\u4E3A codex \u914D\u7F6E API key"
7627
+ lastProbeError: "\u672A\u68C0\u6D4B\u5230 Codex \u767B\u5F55\u6001\uFF08\u5BBF\u4E3B ~/.codex/auth.json \u65E0\u6709\u6548\u51ED\u8BC1\uFF0C\u4E14\u672A\u914D\u7F6E CODEX_API_KEY / OPENAI_API_KEY\uFF09\uFF1B\u5728\u5BBF\u4E3B\u6267\u884C `codex login` \u540E\u91CD\u65B0\u63A2\u6D4B\uFF0C\u6216\u5728 daemon \u914D\u7F6E\u91CC\u4E3A codex \u914D\u7F6E API key\uFF08\u5B98\u65B9\u7AEF\u70B9 CODEX_API_KEY\uFF0C\u7B2C\u4E09\u65B9\u7AEF\u70B9 CODEX_BASE_URL + CODEX_API_KEY\uFF09"
7315
7628
  };
7316
7629
  },
7317
7630
  permissionPreset: null,
@@ -8426,13 +8739,24 @@ var DimAgentAdapter = class {
8426
8739
  lastProbeError = `dim model list \u5931\u8D25\uFF1A${msg}`;
8427
8740
  }
8428
8741
  }
8742
+ let cliVersion;
8743
+ if (installed) {
8744
+ try {
8745
+ const { spawnSync: spawnSync4 } = await import("node:child_process");
8746
+ const result = spawnSync4(this.dimPath, ["--version"], { encoding: "utf8", timeout: 15e3, env: this.probeEnv() });
8747
+ if (!result.error && result.status === 0 && result.stdout)
8748
+ cliVersion = result.stdout.trim().match(/\d+\.\d+[\w.-]*/)?.[0];
8749
+ } catch {
8750
+ }
8751
+ }
8429
8752
  return {
8430
8753
  key: "dim",
8431
- displayName: "Dim Agent CLI",
8754
+ displayName: "DimAgent",
8432
8755
  adapterVersion: DIM_ADAPTER_VERSION,
8433
8756
  protocolVersion: DIM_PROTOCOL_VERSION,
8434
8757
  status: installed ? "available" : "unavailable",
8435
8758
  authStatus,
8759
+ cliVersion,
8436
8760
  capabilities: installed ? await this.probeHandshakeCapabilities() : DIM_CAPABILITIES,
8437
8761
  lastProbeErrorCode,
8438
8762
  lastProbeError
@@ -8560,7 +8884,7 @@ var DIM_PROJECTION = {
8560
8884
  };
8561
8885
  var DIM_PROVIDER_SPEC = {
8562
8886
  key: "dim",
8563
- displayName: "Dim Agent CLI",
8887
+ displayName: "DimAgent",
8564
8888
  launch: resolveDimAcpLaunch(),
8565
8889
  capabilities: DIM_CAPABILITIES,
8566
8890
  // dim 的权限引擎按 preset 生效:对齐 `dim exec --policy workspace-write`(§4.4 私有值)
@@ -9197,6 +9521,135 @@ function sanitizeProviderConfigPatch(raw) {
9197
9521
  return patch;
9198
9522
  }
9199
9523
 
9524
+ // src/daemon/providers/catalog/acp-registry.ts
9525
+ init_daemon_config();
9526
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "node:fs";
9527
+ import { dirname as dirname11 } from "node:path";
9528
+ var DEFAULT_ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
9529
+ var CATALOG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
9530
+ var CATALOG_FETCH_TIMEOUT_MS = 15e3;
9531
+ function resolveRegistryUrl(env = process.env) {
9532
+ const fromEnv = env.MORULA_ACP_REGISTRY_URL?.trim();
9533
+ return (fromEnv || DEFAULT_ACP_REGISTRY_URL).replace(/\/+$/, "");
9534
+ }
9535
+ function catalogCachePath(env = process.env) {
9536
+ return `${daemonDirForEnv(env)}/acp-registry.json`;
9537
+ }
9538
+ function parseRegistryDocument(raw, url, fetchedAt) {
9539
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
9540
+ throw new Error("ACP Registry \u54CD\u5E94\u4E0D\u662F\u5BF9\u8C61");
9541
+ const doc = raw;
9542
+ const agents = Array.isArray(doc.agents) ? doc.agents : [];
9543
+ const entries = [];
9544
+ for (const item of agents) {
9545
+ if (!item || typeof item !== "object" || Array.isArray(item))
9546
+ continue;
9547
+ const a = item;
9548
+ const id = typeof a.id === "string" ? a.id.trim() : "";
9549
+ const name = typeof a.name === "string" ? a.name.trim() : "";
9550
+ if (!id || !name)
9551
+ continue;
9552
+ entries.push({
9553
+ id,
9554
+ name,
9555
+ ...typeof a.version === "string" ? { version: a.version } : {},
9556
+ ...typeof a.description === "string" ? { description: a.description } : {},
9557
+ ...typeof a.repository === "string" ? { repository: a.repository } : {},
9558
+ ...typeof a.website === "string" ? { website: a.website } : {},
9559
+ ...typeof a.license === "string" ? { license: a.license } : {},
9560
+ ...typeof a.icon === "string" ? { icon: a.icon } : {},
9561
+ ...a.distribution && typeof a.distribution === "object" && !Array.isArray(a.distribution) ? { distribution: a.distribution } : {}
9562
+ });
9563
+ }
9564
+ entries.sort((x, y) => x.id.localeCompare(y.id));
9565
+ return {
9566
+ url,
9567
+ fetchedAt,
9568
+ ...typeof doc.version === "string" ? { registryVersion: doc.version } : {},
9569
+ entries
9570
+ };
9571
+ }
9572
+ function readCatalogCache(env = process.env) {
9573
+ const path = catalogCachePath(env);
9574
+ if (!existsSync17(path))
9575
+ return null;
9576
+ try {
9577
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
9578
+ if (!Array.isArray(parsed?.entries) || typeof parsed?.fetchedAt !== "string")
9579
+ return null;
9580
+ return parsed;
9581
+ } catch {
9582
+ return null;
9583
+ }
9584
+ }
9585
+ function listCatalog(env = process.env, now = Date.now()) {
9586
+ const cached = readCatalogCache(env);
9587
+ if (!cached) {
9588
+ return { url: resolveRegistryUrl(env), fetchedAt: "", entries: [], stale: true, source: "empty" };
9589
+ }
9590
+ const age = now - Date.parse(cached.fetchedAt);
9591
+ return { ...cached, stale: !Number.isFinite(age) || age > CATALOG_CACHE_TTL_MS, source: "cache" };
9592
+ }
9593
+ async function refreshCatalog(options = {}) {
9594
+ const env = options.env ?? process.env;
9595
+ const url = options.url?.trim() || resolveRegistryUrl(env);
9596
+ const doFetch = options.fetchImpl ?? fetch;
9597
+ const controller = new AbortController();
9598
+ const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);
9599
+ try {
9600
+ const res = await doFetch(url, { signal: controller.signal, headers: { accept: "application/json" } });
9601
+ if (!res.ok)
9602
+ throw new Error(`ACP Registry \u8FD4\u56DE ${res.status}`);
9603
+ const snapshot = parseRegistryDocument(await res.json(), url, new Date(options.now ?? Date.now()).toISOString());
9604
+ const path = catalogCachePath(env);
9605
+ mkdirSync13(dirname11(path), { recursive: true });
9606
+ writeFileSync11(path, `${JSON.stringify(snapshot, null, 2)}
9607
+ `);
9608
+ return snapshot;
9609
+ } finally {
9610
+ clearTimeout(timer);
9611
+ }
9612
+ }
9613
+ function catalogEntryLaunch(entry) {
9614
+ const dist = entry.distribution;
9615
+ if (!dist)
9616
+ return null;
9617
+ const npx = dist.npx;
9618
+ if (npx && typeof npx.package === "string" && npx.package.trim()) {
9619
+ const extra = Array.isArray(npx.args) ? npx.args.filter((a) => typeof a === "string") : [];
9620
+ return { command: "npx", args: ["-y", npx.package.trim(), ...extra] };
9621
+ }
9622
+ const uvx = dist.uvx;
9623
+ if (uvx && typeof uvx.package === "string" && uvx.package.trim()) {
9624
+ const extra = Array.isArray(uvx.args) ? uvx.args.filter((a) => typeof a === "string") : [];
9625
+ return { command: "uvx", args: [uvx.package.trim(), ...extra] };
9626
+ }
9627
+ return null;
9628
+ }
9629
+ function catalogEntryView(entry) {
9630
+ return { ...entry, launch: catalogEntryLaunch(entry) };
9631
+ }
9632
+
9633
+ // src/daemon/providers/installed-clis.ts
9634
+ function isNpxCommand(command) {
9635
+ return command === "npx" || command === "npx.cmd" || command.endsWith("/npx") || command.endsWith("\\npx.cmd");
9636
+ }
9637
+ function probeInstalledClis() {
9638
+ const installed = [];
9639
+ const seen = /* @__PURE__ */ new Set();
9640
+ for (const entry of listCatalog().entries ?? []) {
9641
+ if (seen.has(entry.id))
9642
+ continue;
9643
+ seen.add(entry.id);
9644
+ const command = catalogEntryLaunch(entry)?.command?.trim();
9645
+ if (!command || isNpxCommand(command))
9646
+ continue;
9647
+ if (resolveCommandOnPath(command))
9648
+ installed.push({ key: entry.id, command });
9649
+ }
9650
+ return installed;
9651
+ }
9652
+
9200
9653
  // src/daemon/providers/inventory.ts
9201
9654
  init_daemon_config();
9202
9655
  var INVENTORY_EXCLUDED = /* @__PURE__ */ new Set(["fake"]);
@@ -9309,8 +9762,8 @@ function pickLegacyDimModels(inventory) {
9309
9762
 
9310
9763
  // src/daemon/repo-cache.ts
9311
9764
  import { execFileSync as execFileSync8 } from "node:child_process";
9312
- import { existsSync as existsSync17, mkdirSync as mkdirSync13 } from "node:fs";
9313
- import { dirname as dirname11, join as join22 } from "node:path";
9765
+ import { existsSync as existsSync18, mkdirSync as mkdirSync14 } from "node:fs";
9766
+ import { dirname as dirname12, join as join22 } from "node:path";
9314
9767
  function ownerOf(repoFullName) {
9315
9768
  const idx = repoFullName.indexOf("/");
9316
9769
  return idx > 0 ? repoFullName.slice(0, idx) : "_default";
@@ -9328,7 +9781,7 @@ function buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName) {
9328
9781
  function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
9329
9782
  const repoCachePath = buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName);
9330
9783
  const currentOrigin = () => {
9331
- if (!existsSync17(join22(repoCachePath, ".git")))
9784
+ if (!existsSync18(join22(repoCachePath, ".git")))
9332
9785
  return null;
9333
9786
  try {
9334
9787
  return execFileSync8("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
@@ -9341,11 +9794,11 @@ function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRem
9341
9794
  return {
9342
9795
  repoCachePath,
9343
9796
  ensure() {
9344
- if (existsSync17(join22(repoCachePath, ".git"))) {
9797
+ if (existsSync18(join22(repoCachePath, ".git"))) {
9345
9798
  syncRemoteUrl(repoCachePath, originUrl(), env);
9346
9799
  return;
9347
9800
  }
9348
- mkdirSync13(dirname11(repoCachePath), { recursive: true });
9801
+ mkdirSync14(dirname12(repoCachePath), { recursive: true });
9349
9802
  const cloneUrl = fetchUrl();
9350
9803
  try {
9351
9804
  execFileSync8("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
@@ -9380,17 +9833,17 @@ function gitErrorText2(err) {
9380
9833
 
9381
9834
  // src/daemon/repo-checkout-service.ts
9382
9835
  import { execFileSync as execFileSync10 } from "node:child_process";
9383
- import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync5, writeFileSync as writeFileSync13 } from "node:fs";
9836
+ import { existsSync as existsSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync5, writeFileSync as writeFileSync14 } from "node:fs";
9384
9837
  import { createServer as createServer3 } from "node:http";
9385
9838
  import { basename as basename3, join as join25 } from "node:path";
9386
9839
 
9387
9840
  // src/daemon/worktree-manager.ts
9388
9841
  import { execFileSync as execFileSync9 } from "node:child_process";
9389
- import { existsSync as existsSync19, mkdirSync as mkdirSync14, readdirSync as readdirSync4, readFileSync as readFileSync13, renameSync as renameSync3, rmSync as rmSync10, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
9390
- import { basename as basename2, dirname as dirname12, join as join24, sep as sep2 } from "node:path";
9842
+ import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync10, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
9843
+ import { basename as basename2, dirname as dirname13, join as join24, sep as sep2 } from "node:path";
9391
9844
 
9392
9845
  // src/daemon/workspace-marker.ts
9393
- import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "node:fs";
9846
+ import { existsSync as existsSync19, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "node:fs";
9394
9847
  import { join as join23 } from "node:path";
9395
9848
  var WORKTREE_MARKER_FILE = ".morula-worktree.json";
9396
9849
  var WORKTREE_MARKER_SCHEMA = 1;
@@ -9398,15 +9851,15 @@ function markerPath(worktreePath) {
9398
9851
  return join23(worktreePath, WORKTREE_MARKER_FILE);
9399
9852
  }
9400
9853
  function writeWorktreeMarker(worktreePath, marker) {
9401
- writeFileSync11(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
9854
+ writeFileSync12(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
9402
9855
  `, "utf8");
9403
9856
  }
9404
9857
  function readWorktreeMarker(worktreePath) {
9405
9858
  const p = markerPath(worktreePath);
9406
- if (!existsSync18(p))
9859
+ if (!existsSync19(p))
9407
9860
  return null;
9408
9861
  try {
9409
- const raw = JSON.parse(readFileSync12(p, "utf8"));
9862
+ const raw = JSON.parse(readFileSync13(p, "utf8"));
9410
9863
  return raw && raw.schemaVersion === WORKTREE_MARKER_SCHEMA ? raw : null;
9411
9864
  } catch {
9412
9865
  return null;
@@ -9441,28 +9894,28 @@ function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
9441
9894
  }
9442
9895
  var legacyPathWarned = /* @__PURE__ */ new Set();
9443
9896
  function warnLegacyWorktreeResidue(legacyPath) {
9444
- if (legacyPathWarned.has(legacyPath) || !existsSync19(legacyPath))
9897
+ if (legacyPathWarned.has(legacyPath) || !existsSync20(legacyPath))
9445
9898
  return;
9446
9899
  legacyPathWarned.add(legacyPath);
9447
9900
  console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5F62\u6001 worktree \u6B8B\u7559\uFF08\u4E0D\u542B runtimeId \u6BB5\uFF09: ${legacyPath} \u2014\u2014 \u8BE5\u8DEF\u5F84\u5DF2\u5F03\u7528\uFF0C\u65B0\u4EFB\u52A1\u4F7F\u7528 <worktree>/<tenant>/<runtimeId8>/<task>\uFF1B\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
9448
9901
  }
9449
9902
  var legacyLayoutWarned = /* @__PURE__ */ new Set();
9450
9903
  function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
9451
- if (legacyLayoutWarned.has(taskRoot) || existsSync19(worktreePath) || !existsSync19(join24(taskRoot, WORKTREE_MARKER_FILE)))
9904
+ if (legacyLayoutWarned.has(taskRoot) || existsSync20(worktreePath) || !existsSync20(join24(taskRoot, WORKTREE_MARKER_FILE)))
9452
9905
  return;
9453
9906
  legacyLayoutWarned.add(taskRoot);
9454
9907
  console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5E03\u5C40\u4EFB\u52A1\u5DE5\u4F5C\u533A\uFF08\u4ED3\u5E93\u76F4\u63A5\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u6839\uFF09: ${taskRoot} \u2014\u2014 \u65B0\u5E03\u5C40\u4F1A\u5728\u5176\u4E0B\u521B\u5EFA\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF0C\u65E7\u76EE\u5F55\u5185\u5BB9\u4E0D\u518D\u88AB\u4F7F\u7528\uFF1B\u5176\u4E2D\u53EF\u80FD\u6709\u672A\u63A8\u9001\u6539\u52A8\uFF0C\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
9455
9908
  }
9456
9909
  function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
9457
9910
  const gitignorePath = join24(worktreePath, ".gitignore");
9458
- const existing = existsSync19(gitignorePath) ? readFileSync13(gitignorePath, "utf8") : "";
9911
+ const existing = existsSync20(gitignorePath) ? readFileSync14(gitignorePath, "utf8") : "";
9459
9912
  const requiredLines = [WORKTREE_MARKER_FILE];
9460
9913
  const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
9461
9914
  if (missing.length > 0) {
9462
9915
  const next = `${existing.trimEnd() ? `${existing.trimEnd()}
9463
9916
  ` : ""}${missing.join("\n")}
9464
9917
  `;
9465
- writeFileSync12(gitignorePath, next, "utf8");
9918
+ writeFileSync13(gitignorePath, next, "utf8");
9466
9919
  try {
9467
9920
  execFileSync9("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
9468
9921
  execFileSync9(
@@ -9559,10 +10012,10 @@ function indexLockPathFromError(text) {
9559
10012
  return m && m[0].length > "index.lock".length ? m[0] : null;
9560
10013
  }
9561
10014
  function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
9562
- if (!existsSync19(lockPath))
10015
+ if (!existsSync20(lockPath))
9563
10016
  return true;
9564
10017
  const deadline = Date.now() + timeoutMs;
9565
- while (existsSync19(lockPath)) {
10018
+ while (existsSync20(lockPath)) {
9566
10019
  const remain = deadline - Date.now();
9567
10020
  if (remain <= 0)
9568
10021
  return false;
@@ -9704,7 +10157,7 @@ function prepareWorktree(input) {
9704
10157
  const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
9705
10158
  warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
9706
10159
  warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
9707
- if (existsSync19(worktreePath)) {
10160
+ if (existsSync20(worktreePath)) {
9708
10161
  const isGitRepo = (() => {
9709
10162
  try {
9710
10163
  execFileSync9("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
@@ -9771,8 +10224,8 @@ function prepareWorktree(input) {
9771
10224
  }
9772
10225
  }
9773
10226
  }
9774
- if (!existsSync19(worktreePath)) {
9775
- mkdirSync14(join24(worktreePath, ".."), { recursive: true });
10227
+ if (!existsSync20(worktreePath)) {
10228
+ mkdirSync15(join24(worktreePath, ".."), { recursive: true });
9776
10229
  addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
9777
10230
  writeWorktreeMarker(worktreePath, {
9778
10231
  schemaVersion: 1,
@@ -9788,9 +10241,9 @@ function prepareWorktree(input) {
9788
10241
  }
9789
10242
  {
9790
10243
  const dotMorulaRescue = `${worktreePath}.morula-rescue`;
9791
- if (existsSync19(dotMorulaRescue)) {
10244
+ if (existsSync20(dotMorulaRescue)) {
9792
10245
  const dotMorulaDir = join24(worktreePath, ".morula");
9793
- if (!existsSync19(dotMorulaDir)) {
10246
+ if (!existsSync20(dotMorulaDir)) {
9794
10247
  try {
9795
10248
  renameSync3(dotMorulaRescue, dotMorulaDir);
9796
10249
  console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: \u65E7\u7248 .morula/ \u5907\u4EFD\u5DF2\u5F52\u8FD8\uFF08checkout \u51ED\u636E\u4E0E\u6B21\u4ED3\u5E93\u4FDD\u7559\uFF09`);
@@ -9819,9 +10272,9 @@ function prepareWorktree(input) {
9819
10272
  }
9820
10273
  function resolveWorkspaceLayout(root, expected) {
9821
10274
  const primaryDir = join24(root, safeDirName(expected.repoFullName));
9822
- if (existsSync19(join24(primaryDir, WORKTREE_MARKER_FILE)))
10275
+ if (existsSync20(join24(primaryDir, WORKTREE_MARKER_FILE)))
9823
10276
  return { primaryDir, legacy: false };
9824
- if (existsSync19(join24(root, WORKTREE_MARKER_FILE)))
10277
+ if (existsSync20(join24(root, WORKTREE_MARKER_FILE)))
9825
10278
  return { primaryDir: root, legacy: true };
9826
10279
  return null;
9827
10280
  }
@@ -9885,7 +10338,7 @@ function repoHasActivity(repoPath) {
9885
10338
  return false;
9886
10339
  }
9887
10340
  function findActivityInReposDir(reposDir) {
9888
- if (!existsSync19(reposDir))
10341
+ if (!existsSync20(reposDir))
9889
10342
  return null;
9890
10343
  let names = [];
9891
10344
  try {
@@ -9895,7 +10348,7 @@ function findActivityInReposDir(reposDir) {
9895
10348
  }
9896
10349
  for (const name of names) {
9897
10350
  const repoPath = join24(reposDir, name);
9898
- if (!existsSync19(join24(repoPath, ".git")))
10351
+ if (!existsSync20(join24(repoPath, ".git")))
9899
10352
  continue;
9900
10353
  if (repoHasActivity(repoPath))
9901
10354
  return repoPath;
@@ -9912,14 +10365,14 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
9912
10365
  }
9913
10366
  const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join24(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
9914
10367
  const legacyReposDir = join24(taskRoot, ".morula", "repos");
9915
- if (existsSync19(legacyReposDir)) {
10368
+ if (existsSync20(legacyReposDir)) {
9916
10369
  try {
9917
10370
  candidates.push(...readdirSync4(legacyReposDir).map((n) => join24(legacyReposDir, n)));
9918
10371
  } catch {
9919
10372
  }
9920
10373
  }
9921
10374
  for (const repoPath of candidates) {
9922
- if (!existsSync19(join24(repoPath, ".git")))
10375
+ if (!existsSync20(join24(repoPath, ".git")))
9923
10376
  continue;
9924
10377
  if (repoHasActivity(repoPath))
9925
10378
  return repoPath;
@@ -9929,7 +10382,7 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
9929
10382
  function cleanupInjectedSkills(dir) {
9930
10383
  const injectedSkillsRoots = [join24(dir, ".agents", "skills"), join24(dir, ".claude", "skills")];
9931
10384
  for (const injectedSkillsRoot of injectedSkillsRoots) {
9932
- if (!existsSync19(injectedSkillsRoot))
10385
+ if (!existsSync20(injectedSkillsRoot))
9933
10386
  continue;
9934
10387
  try {
9935
10388
  for (const name of readdirSync4(injectedSkillsRoot)) {
@@ -9939,7 +10392,7 @@ function cleanupInjectedSkills(dir) {
9939
10392
  }
9940
10393
  if (readdirSync4(injectedSkillsRoot).length === 0) {
9941
10394
  rmSync10(injectedSkillsRoot, { recursive: true, force: true });
9942
- const shell = dirname12(injectedSkillsRoot);
10395
+ const shell = dirname13(injectedSkillsRoot);
9943
10396
  if (readdirSync4(shell).length === 0)
9944
10397
  rmSync10(shell, { recursive: true, force: true });
9945
10398
  }
@@ -9967,7 +10420,7 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
9967
10420
  if (!e.isDirectory() || e.name.startsWith("."))
9968
10421
  continue;
9969
10422
  const repoPath = join24(taskRoot, e.name);
9970
- if (repoPath === excludeDir || !existsSync19(join24(repoPath, ".git")))
10423
+ if (repoPath === excludeDir || !existsSync20(join24(repoPath, ".git")))
9971
10424
  continue;
9972
10425
  try {
9973
10426
  const commonDir = execFileSync9("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
@@ -10002,7 +10455,7 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
10002
10455
  }
10003
10456
  }
10004
10457
  function removeWorktree(repoCachePath, worktreePath) {
10005
- if (!existsSync19(worktreePath))
10458
+ if (!existsSync20(worktreePath))
10006
10459
  return;
10007
10460
  try {
10008
10461
  cleanupInjectedSkills(worktreePath);
@@ -10103,8 +10556,8 @@ var LocalRepoCheckoutService = class {
10103
10556
  this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
10104
10557
  try {
10105
10558
  const dir = join25(workdir, ".morula");
10106
- mkdirSync15(dir, { recursive: true, mode: 448 });
10107
- writeFileSync13(join25(dir, "checkout.json"), JSON.stringify({
10559
+ mkdirSync16(dir, { recursive: true, mode: 448 });
10560
+ writeFileSync14(join25(dir, "checkout.json"), JSON.stringify({
10108
10561
  taskId,
10109
10562
  port: this.boundPort,
10110
10563
  checkToken
@@ -10271,11 +10724,11 @@ function isReadOnlyRepoDir(dirName) {
10271
10724
  }
10272
10725
  function resolveRepoTargetDir(taskRoot, base) {
10273
10726
  const direct = join25(taskRoot, base);
10274
- if (!existsSync20(direct) || existsSync20(join25(direct, ".git")))
10727
+ if (!existsSync21(direct) || existsSync21(join25(direct, ".git")))
10275
10728
  return direct;
10276
10729
  for (let i = 2; i < 100; i++) {
10277
10730
  const candidate = join25(taskRoot, `${base}-${i}`);
10278
- if (!existsSync20(candidate))
10731
+ if (!existsSync21(candidate))
10279
10732
  return candidate;
10280
10733
  }
10281
10734
  throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
@@ -10283,7 +10736,7 @@ function resolveRepoTargetDir(taskRoot, base) {
10283
10736
  function materializeReadWorktree(input) {
10284
10737
  const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
10285
10738
  const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
10286
- if (existsSync20(join25(worktreePath, ".git"))) {
10739
+ if (existsSync21(join25(worktreePath, ".git"))) {
10287
10740
  const current = currentBranchName(worktreePath);
10288
10741
  if (current)
10289
10742
  throw new Error(`READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5DF2\u5207\u5230\u5206\u652F ${current}\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09`);
@@ -10291,7 +10744,7 @@ function materializeReadWorktree(input) {
10291
10744
  throw new Error("READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5B58\u5728\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09");
10292
10745
  execFileSync10("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
10293
10746
  } else {
10294
- mkdirSync15(join25(worktreePath, ".."), { recursive: true });
10747
+ mkdirSync16(join25(worktreePath, ".."), { recursive: true });
10295
10748
  execFileSync10("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
10296
10749
  env: input.env,
10297
10750
  stdio: "pipe"
@@ -10363,7 +10816,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
10363
10816
  } catch {
10364
10817
  return [];
10365
10818
  }
10366
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join25(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename3(dir))).filter((dir) => existsSync20(join25(dir, ".git"))).sort();
10819
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join25(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename3(dir))).filter((dir) => existsSync21(join25(dir, ".git"))).sort();
10367
10820
  }
10368
10821
  function parseRepoFullNameFromRemoteUrl(url) {
10369
10822
  const raw = url?.trim();
@@ -10448,7 +10901,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
10448
10901
  }
10449
10902
  }
10450
10903
  function materializeAdditionalRepo(input) {
10451
- if (existsSync20(join25(input.targetPath, ".git"))) {
10904
+ if (existsSync21(join25(input.targetPath, ".git"))) {
10452
10905
  const current = currentBranchName(input.targetPath);
10453
10906
  if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
10454
10907
  const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
@@ -10468,7 +10921,7 @@ function materializeAdditionalRepo(input) {
10468
10921
  }
10469
10922
  if (!baseRef)
10470
10923
  throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
10471
- mkdirSync15(join25(input.targetPath, ".."), { recursive: true });
10924
+ mkdirSync16(join25(input.targetPath, ".."), { recursive: true });
10472
10925
  execFileSync10("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
10473
10926
  env: input.env,
10474
10927
  stdio: "pipe"
@@ -10479,7 +10932,7 @@ function materializeAdditionalRepo(input) {
10479
10932
  // src/daemon/session-host.ts
10480
10933
  init_dist2();
10481
10934
  init_acp_connection();
10482
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "node:fs";
10935
+ import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "node:fs";
10483
10936
  import { join as join27 } from "node:path";
10484
10937
  init_daemon_config();
10485
10938
 
@@ -10755,7 +11208,7 @@ var SessionHost = class {
10755
11208
  if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
10756
11209
  return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
10757
11210
  const workspaceDir = join27(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
10758
- mkdirSync16(workspaceDir, { recursive: true });
11211
+ mkdirSync17(workspaceDir, { recursive: true });
10759
11212
  const providerKey = cmd.providerKey?.trim() || "dim";
10760
11213
  if (isProviderDisabled(this.options.configStore ?? null, providerKey))
10761
11214
  return { ok: false, error: `PROVIDER_DISABLED: ${providerKey} \u5DF2\u5728\u8BBE\u5907\u672C\u5730\u505C\u7528` };
@@ -11144,9 +11597,9 @@ ${fileRefs.join("\n")}` : text }];
11144
11597
  if (attachment.isImage) {
11145
11598
  blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
11146
11599
  } else {
11147
- mkdirSync16(dir, { recursive: true });
11600
+ mkdirSync17(dir, { recursive: true });
11148
11601
  const target = join27(dir, safeAttachmentName(attachment.fileName));
11149
- writeFileSync14(target, bytes);
11602
+ writeFileSync15(target, bytes);
11150
11603
  fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
11151
11604
  }
11152
11605
  } catch (err) {
@@ -11255,17 +11708,17 @@ ${fileRefs.join("\n")}` : text }];
11255
11708
  const skillContent = buildStewardSkillFile();
11256
11709
  if (projection.skillsDir) {
11257
11710
  const skillDir = join27(workspaceDir, projection.skillsDir, STEWARD_SKILL_NAME);
11258
- mkdirSync16(skillDir, { recursive: true });
11259
- writeFileSync14(join27(skillDir, "SKILL.md"), skillContent, "utf8");
11711
+ mkdirSync17(skillDir, { recursive: true });
11712
+ writeFileSync15(join27(skillDir, "SKILL.md"), skillContent, "utf8");
11260
11713
  }
11261
11714
  if (projection.promptsDir) {
11262
11715
  const promptsDir = join27(workspaceDir, projection.promptsDir);
11263
- mkdirSync16(promptsDir, { recursive: true });
11264
- writeFileSync14(join27(promptsDir, `${STEWARD_SKILL_NAME}.md`), skillContent, "utf8");
11716
+ mkdirSync17(promptsDir, { recursive: true });
11717
+ writeFileSync15(join27(promptsDir, `${STEWARD_SKILL_NAME}.md`), skillContent, "utf8");
11265
11718
  }
11266
11719
  const contextDir = join27(workspaceDir, projection.contextDir);
11267
- mkdirSync16(contextDir, { recursive: true });
11268
- writeFileSync14(
11720
+ mkdirSync17(contextDir, { recursive: true });
11721
+ writeFileSync15(
11269
11722
  join27(contextDir, "steward-context.md"),
11270
11723
  buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }, paths),
11271
11724
  "utf8"
@@ -12491,6 +12944,7 @@ var DaemonRunner = class {
12491
12944
  // 对它显示按钮只会误导用户(能力自报比服务端维护最低版本号更准确,register 即刷新)
12492
12945
  hostCapabilities: { worktreeV2: true, workspaceCleanupV1: false, selfUpgrade: true },
12493
12946
  inventoryRevision: `daemon-v${BUILD_VERSION2}`,
12947
+ installedClis: probeInstalledClis(),
12494
12948
  providers: inventory
12495
12949
  });
12496
12950
  this.runtimeId = registered.id;
@@ -13002,10 +13456,10 @@ var DaemonRunner = class {
13002
13456
  env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
13003
13457
  repoDir = join28(cfg.workdir, "scratch", taskId);
13004
13458
  taskRoot = repoDir;
13005
- mkdirSync17(repoDir, { recursive: true });
13459
+ mkdirSync18(repoDir, { recursive: true });
13006
13460
  const readme = join28(repoDir, "README.md");
13007
- if (!existsSync21(readme)) {
13008
- writeFileSync15(
13461
+ if (!existsSync22(readme)) {
13462
+ writeFileSync16(
13009
13463
  readme,
13010
13464
  `# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
13011
13465
 
@@ -13061,9 +13515,9 @@ var DaemonRunner = class {
13061
13515
  env.GCX_AGENT_MODE = "1";
13062
13516
  try {
13063
13517
  const configPath = gcxConfigPathForTaskRoot(taskRoot);
13064
- mkdirSync17(dirname13(configPath), { recursive: true });
13065
- if (!existsSync21(configPath))
13066
- writeFileSync15(configPath, GCX_CONFIG_SKELETON, "utf8");
13518
+ mkdirSync18(dirname14(configPath), { recursive: true });
13519
+ if (!existsSync22(configPath))
13520
+ writeFileSync16(configPath, GCX_CONFIG_SKELETON, "utf8");
13067
13521
  env.GCX_CONFIG = configPath;
13068
13522
  } catch (error) {
13069
13523
  this.log(`[daemon] \u4EFB\u52A1 ${taskId} GCX_CONFIG \u6CE8\u5165\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4EFB\u52A1\u6267\u884C\uFF09: ${error instanceof Error ? error.message : String(error)}`);
@@ -13361,7 +13815,7 @@ var DaemonRunner = class {
13361
13815
  const supportsResume = this.providerCapabilities.get(providerKey)?.sessionResume === true;
13362
13816
  const resumeSessionId = supportsResume ? detail.acpSessionId ?? detail.lastRun?.acpSessionId ?? null : null;
13363
13817
  const resumeWorkDir = detail.acpWorkDir ?? detail.lastRun?.acpWorkDir ?? null;
13364
- const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync21(resumeWorkDir);
13818
+ const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync22(resumeWorkDir);
13365
13819
  const input = {
13366
13820
  executionId: `${taskId}:1`,
13367
13821
  // task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
@@ -13688,16 +14142,16 @@ function writeCustomSkills(targetDir, skills, log, projection = resolveInstructi
13688
14142
  ${skill.content ?? ""}`;
13689
14143
  if (skillsBase) {
13690
14144
  const dir = join28(skillsBase, name);
13691
- if (dirname13(dir) !== skillsBase) {
14145
+ if (dirname14(dir) !== skillsBase) {
13692
14146
  log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
13693
14147
  continue;
13694
14148
  }
13695
- mkdirSync17(dir, { recursive: true });
13696
- writeFileSync15(join28(dir, "SKILL.md"), content, "utf8");
14149
+ mkdirSync18(dir, { recursive: true });
14150
+ writeFileSync16(join28(dir, "SKILL.md"), content, "utf8");
13697
14151
  }
13698
14152
  if (promptsBase) {
13699
- mkdirSync17(promptsBase, { recursive: true });
13700
- writeFileSync15(join28(promptsBase, `${name}.md`), content, "utf8");
14153
+ mkdirSync18(promptsBase, { recursive: true });
14154
+ writeFileSync16(join28(promptsBase, `${name}.md`), content, "utf8");
13701
14155
  }
13702
14156
  }
13703
14157
  }
@@ -13720,22 +14174,22 @@ ${buildPlatformSkillBody(paths)}`;
13720
14174
  if (projection.skillsDir) {
13721
14175
  const dir = join28(targetDir, projection.skillsDir, COLLABORATION_SKILL_NAME);
13722
14176
  const refsDir = join28(dir, "references");
13723
- mkdirSync17(refsDir, { recursive: true });
13724
- writeFileSync15(join28(dir, "SKILL.md"), entry, "utf8");
14177
+ mkdirSync18(refsDir, { recursive: true });
14178
+ writeFileSync16(join28(dir, "SKILL.md"), entry, "utf8");
13725
14179
  for (const r of refs)
13726
- writeFileSync15(join28(refsDir, `${r.name}.md`), r.content, "utf8");
14180
+ writeFileSync16(join28(refsDir, `${r.name}.md`), r.content, "utf8");
13727
14181
  const legacyDir = join28(targetDir, projection.skillsDir, LEGACY_COLLABORATION_SKILL_NAME);
13728
- mkdirSync17(legacyDir, { recursive: true });
13729
- writeFileSync15(join28(legacyDir, "SKILL.md"), LEGACY_REDIRECT_STUB, "utf8");
14182
+ mkdirSync18(legacyDir, { recursive: true });
14183
+ writeFileSync16(join28(legacyDir, "SKILL.md"), LEGACY_REDIRECT_STUB, "utf8");
13730
14184
  wroteTo.push(projection.skillsDir);
13731
14185
  }
13732
14186
  if (projection.promptsDir) {
13733
14187
  const promptsDir = join28(targetDir, projection.promptsDir);
13734
- mkdirSync17(promptsDir, { recursive: true });
13735
- writeFileSync15(join28(promptsDir, `${COLLABORATION_SKILL_NAME}.md`), entry, "utf8");
14188
+ mkdirSync18(promptsDir, { recursive: true });
14189
+ writeFileSync16(join28(promptsDir, `${COLLABORATION_SKILL_NAME}.md`), entry, "utf8");
13736
14190
  for (const r of refs)
13737
- writeFileSync15(join28(promptsDir, `${COLLABORATION_SKILL_NAME}-${r.name}.md`), r.content, "utf8");
13738
- writeFileSync15(join28(promptsDir, `${LEGACY_COLLABORATION_SKILL_NAME}.md`), LEGACY_REDIRECT_STUB, "utf8");
14191
+ writeFileSync16(join28(promptsDir, `${COLLABORATION_SKILL_NAME}-${r.name}.md`), r.content, "utf8");
14192
+ writeFileSync16(join28(promptsDir, `${LEGACY_COLLABORATION_SKILL_NAME}.md`), LEGACY_REDIRECT_STUB, "utf8");
13739
14193
  wroteTo.push(projection.promptsDir);
13740
14194
  }
13741
14195
  if (wroteTo.length === 0) {
@@ -13774,7 +14228,7 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
13774
14228
  }
13775
14229
  function writeTaskContextBrief(targetDir, taskId, detail, repo, log, projection = resolveInstructionProjection("dim")) {
13776
14230
  const briefDir = join28(targetDir, projection.contextDir);
13777
- mkdirSync17(briefDir, { recursive: true });
14231
+ mkdirSync18(briefDir, { recursive: true });
13778
14232
  const contractPath = projection.contractPath ?? contractPathsFor(projection).contractPath;
13779
14233
  const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
13780
14234
 
@@ -13785,7 +14239,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log, projection
13785
14239
  ---
13786
14240
 
13787
14241
  ${buildTaskPromptWithContract(detail, void 0, projection)}`;
13788
- writeFileSync15(join28(briefDir, "task-context.md"), brief, "utf8");
14242
+ writeFileSync16(join28(briefDir, "task-context.md"), brief, "utf8");
13789
14243
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 ${join28(projection.contextDir, "task-context.md")}`);
13790
14244
  }
13791
14245
  var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
@@ -13799,9 +14253,9 @@ function writeProjectAgentsMd(targetDir, agentsMd, log, projection = resolveInst
13799
14253
  log?.(`[daemon] \u7A7A\u95F4 AGENTS.md \u5185\u5BB9\u8D85\u957F\uFF08${content.length} \u5B57\u7B26 > ${PROJECT_AGENTS_MD_MAX_CHARS}\uFF09\uFF0C\u5DF2\u622A\u65AD\u5199\u5165`);
13800
14254
  content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
13801
14255
  }
13802
- mkdirSync17(targetDir, { recursive: true });
14256
+ mkdirSync18(targetDir, { recursive: true });
13803
14257
  for (const file of projection.memoryFiles) {
13804
- writeFileSync15(join28(targetDir, file), `${content}
14258
+ writeFileSync16(join28(targetDir, file), `${content}
13805
14259
  `, "utf8");
13806
14260
  }
13807
14261
  log?.(`[daemon] \u5DF2\u6CE8\u5165\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\u5230 ${projection.memoryFiles.join(" + ")}\uFF08${content.length} \u5B57\u7B26\uFF0C\u4F5C\u4E3A\u9879\u76EE\u8BF4\u660E\u81EA\u52A8\u52A0\u8F7D\uFF09`);
@@ -13826,9 +14280,9 @@ ${core}` : core;
13826
14280
 
13827
14281
  ${core}` : core;
13828
14282
  }
13829
- mkdirSync17(targetDir, { recursive: true });
14283
+ mkdirSync18(targetDir, { recursive: true });
13830
14284
  for (const file of projection.memoryFiles) {
13831
- writeFileSync15(join28(targetDir, file), `${combined}
14285
+ writeFileSync16(join28(targetDir, file), `${combined}
13832
14286
  `, "utf8");
13833
14287
  }
13834
14288
  log?.(`[daemon] \u5DF2\u6CE8\u5165 ${projection.memoryFiles.join(" + ")}\uFF08\u7A7A\u95F4\u8BF4\u660E ${userContent.length} \u5B57\u7B26 + \u5951\u7EA6\u6838\u5FC3\u6BB5\uFF0C\u6052\u5B9A\u52A0\u8F7D\uFF09`);
@@ -13889,7 +14343,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
13889
14343
  }
13890
14344
  async function reportOneRepoTarget(taskId, target, env, ops) {
13891
14345
  const execEnv = mergeExecEnv(env);
13892
- if (!existsSync21(target.dir) || !existsSync21(join28(target.dir, ".git"))) {
14346
+ if (!existsSync22(target.dir) || !existsSync22(join28(target.dir, ".git"))) {
13893
14347
  ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728\uFF08cleanup/\u5206\u652F\u6539\u540D\u65F6\u5E8F\uFF09`);
13894
14348
  return;
13895
14349
  }
@@ -14057,9 +14511,9 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
14057
14511
 
14058
14512
  // src/commands/doctor.ts
14059
14513
  init_dist2();
14060
- import { existsSync as existsSync22, readFileSync as readFileSync14, realpathSync } from "node:fs";
14514
+ import { existsSync as existsSync23, readFileSync as readFileSync15, realpathSync } from "node:fs";
14061
14515
  import { homedir as homedir15 } from "node:os";
14062
- import { dirname as dirname14, join as join29 } from "node:path";
14516
+ import { dirname as dirname15, join as join29 } from "node:path";
14063
14517
  init_daemon_config();
14064
14518
  init_daemon_lock();
14065
14519
  var BUILTIN_PROVIDER_KEYS2 = ["dim", "claude", "codex"];
@@ -14082,16 +14536,16 @@ async function collectDoctor(context, probe) {
14082
14536
  const env = context.env;
14083
14537
  const configDir = env.MORULA_CONFIG_DIR ?? join29(homedir15(), ".morula-cli");
14084
14538
  const configPath = env.MORULA_CONFIG ?? join29(configDir, "config.yaml");
14085
- const daemonPath = join29(dirname14(configPath), "daemon");
14539
+ const daemonPath = join29(dirname15(configPath), "daemon");
14086
14540
  const logFile = join29(daemonPath, "daemon.log");
14087
14541
  const contextName = context.runtime.context ?? null;
14088
14542
  const baseUrl = context.runtime.baseUrl ?? null;
14089
14543
  const tokenPresent = Boolean(context.runtime.token);
14090
14544
  const tokenSource = context.runtime.tokenSource ?? null;
14091
14545
  let configReadable = false;
14092
- if (existsSync22(configPath)) {
14546
+ if (existsSync23(configPath)) {
14093
14547
  try {
14094
- readFileSync14(configPath, "utf8");
14548
+ readFileSync15(configPath, "utf8");
14095
14549
  configReadable = true;
14096
14550
  } catch {
14097
14551
  configReadable = false;
@@ -14166,7 +14620,7 @@ async function collectDoctor(context, probe) {
14166
14620
  hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
14167
14621
  });
14168
14622
  }
14169
- checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync22(configPath) ? { id: "config.readable", status: "fail", message: `\u914D\u7F6E\u5B58\u5728\u4F46\u4E0D\u53EF\u8BFB\uFF08\u6743\u9650\uFF1F\uFF09\uFF1A${configPath}`, hint: `ls -l ${configPath}` } : { id: "config.readable", status: "warn", message: `\u914D\u7F6E\u6587\u4EF6\u5C1A\u4E0D\u5B58\u5728\uFF1A${configPath}`, hint: "morula-runtime auth login\uFF08\u767B\u5F55\u540E\u751F\u6210\uFF09" });
14623
+ checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync23(configPath) ? { id: "config.readable", status: "fail", message: `\u914D\u7F6E\u5B58\u5728\u4F46\u4E0D\u53EF\u8BFB\uFF08\u6743\u9650\uFF1F\uFF09\uFF1A${configPath}`, hint: `ls -l ${configPath}` } : { id: "config.readable", status: "warn", message: `\u914D\u7F6E\u6587\u4EF6\u5C1A\u4E0D\u5B58\u5728\uFF1A${configPath}`, hint: "morula-runtime auth login\uFF08\u767B\u5F55\u540E\u751F\u6210\uFF09" });
14170
14624
  if (!dimFound) {
14171
14625
  checks.push({
14172
14626
  id: "dim.present",
@@ -14655,7 +15109,7 @@ async function run3(context) {
14655
15109
  saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
14656
15110
  }
14657
15111
  const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
14658
- mkdirSync18(resolvedWorkdir, { recursive: true });
15112
+ mkdirSync19(resolvedWorkdir, { recursive: true });
14659
15113
  const lockPath = join30(daemonDirForEnv(context.env), DAEMON_LOCK_FILE);
14660
15114
  let acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
14661
15115
  const restartWaitMs = Number(context.env.MORULA_DAEMON_RESTART_WAIT_MS ?? 0) || 0;
@@ -14745,7 +15199,7 @@ async function start(context) {
14745
15199
  }
14746
15200
  const dir = daemonDirForEnv(context.env);
14747
15201
  if (!dryRun)
14748
- mkdirSync18(dir, { recursive: true, mode: 448 });
15202
+ mkdirSync19(dir, { recursive: true, mode: 448 });
14749
15203
  const pidFile = join30(dir, PID_FILE);
14750
15204
  const logFile = join30(dir, LOG_FILE);
14751
15205
  const lockPath = join30(dir, DAEMON_LOCK_FILE);
@@ -14759,8 +15213,8 @@ async function start(context) {
14759
15213
  { hint: "morula-runtime daemon status" }
14760
15214
  );
14761
15215
  }
14762
- if (existsSync23(pidFile)) {
14763
- const pid = Number(readFileSync15(pidFile, "utf8").trim());
15216
+ if (existsSync24(pidFile)) {
15217
+ const pid = Number(readFileSync16(pidFile, "utf8").trim());
14764
15218
  if (isProcessAlive(pid)) {
14765
15219
  throw new CliError("ALREADY_RUNNING", EXIT.CONFLICT, `daemon \u5DF2\u5728\u8FD0\u884C\uFF08pid=${pid}\uFF09\uFF1B\u5982\u9700\u5F3A\u5236\u91CD\u542F\u8BF7\u5148\u6267\u884C kill -9 ${pid}`, false, {
14766
15220
  hint: "morula-runtime daemon status"
@@ -14810,7 +15264,7 @@ async function start(context) {
14810
15264
  hint: `tail -n 50 ${logFile}`
14811
15265
  });
14812
15266
  }
14813
- writeFileSync16(pidFile, String(result.pid), { mode: 384 });
15267
+ writeFileSync17(pidFile, String(result.pid), { mode: 384 });
14814
15268
  context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
14815
15269
  return { running: true, pid: result.pid, logFile, lockFile: lockPath };
14816
15270
  }
@@ -14822,7 +15276,7 @@ async function stop(context) {
14822
15276
  const lockPath = join30(dir, DAEMON_LOCK_FILE);
14823
15277
  const lock = probeDaemonLock(lockPath);
14824
15278
  const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
14825
- const filePid = existsSync23(pidFile) ? Number(readFileSync15(pidFile, "utf8").trim()) : null;
15279
+ const filePid = existsSync24(pidFile) ? Number(readFileSync16(pidFile, "utf8").trim()) : null;
14826
15280
  const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
14827
15281
  if (dryRun) {
14828
15282
  return {
@@ -14851,7 +15305,7 @@ async function status2(context) {
14851
15305
  const cfg = loadDaemonConfig(context.store);
14852
15306
  const dir = daemonDirForEnv(context.env);
14853
15307
  const lockPath = join30(dir, DAEMON_LOCK_FILE);
14854
- const probe = probeDaemonInstance(dirname15(configFilePathForEnv(context.env)));
15308
+ const probe = probeDaemonInstance(dirname16(configFilePathForEnv(context.env)));
14855
15309
  return {
14856
15310
  running: probe.running,
14857
15311
  pid: probe.pid,
@@ -14889,117 +15343,6 @@ function parseMaxConcurrent(context) {
14889
15343
  // src/commands/provider.ts
14890
15344
  init_dist2();
14891
15345
  init_daemon_config();
14892
-
14893
- // src/daemon/providers/catalog/acp-registry.ts
14894
- init_daemon_config();
14895
- import { existsSync as existsSync24, mkdirSync as mkdirSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync17 } from "node:fs";
14896
- import { dirname as dirname16 } from "node:path";
14897
- var DEFAULT_ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
14898
- var CATALOG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
14899
- var CATALOG_FETCH_TIMEOUT_MS = 15e3;
14900
- function resolveRegistryUrl(env = process.env) {
14901
- const fromEnv = env.MORULA_ACP_REGISTRY_URL?.trim();
14902
- return (fromEnv || DEFAULT_ACP_REGISTRY_URL).replace(/\/+$/, "");
14903
- }
14904
- function catalogCachePath(env = process.env) {
14905
- return `${daemonDirForEnv(env)}/acp-registry.json`;
14906
- }
14907
- function parseRegistryDocument(raw, url, fetchedAt) {
14908
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
14909
- throw new Error("ACP Registry \u54CD\u5E94\u4E0D\u662F\u5BF9\u8C61");
14910
- const doc = raw;
14911
- const agents = Array.isArray(doc.agents) ? doc.agents : [];
14912
- const entries = [];
14913
- for (const item of agents) {
14914
- if (!item || typeof item !== "object" || Array.isArray(item))
14915
- continue;
14916
- const a = item;
14917
- const id = typeof a.id === "string" ? a.id.trim() : "";
14918
- const name = typeof a.name === "string" ? a.name.trim() : "";
14919
- if (!id || !name)
14920
- continue;
14921
- entries.push({
14922
- id,
14923
- name,
14924
- ...typeof a.version === "string" ? { version: a.version } : {},
14925
- ...typeof a.description === "string" ? { description: a.description } : {},
14926
- ...typeof a.repository === "string" ? { repository: a.repository } : {},
14927
- ...typeof a.website === "string" ? { website: a.website } : {},
14928
- ...typeof a.license === "string" ? { license: a.license } : {},
14929
- ...typeof a.icon === "string" ? { icon: a.icon } : {},
14930
- ...a.distribution && typeof a.distribution === "object" && !Array.isArray(a.distribution) ? { distribution: a.distribution } : {}
14931
- });
14932
- }
14933
- entries.sort((x, y) => x.id.localeCompare(y.id));
14934
- return {
14935
- url,
14936
- fetchedAt,
14937
- ...typeof doc.version === "string" ? { registryVersion: doc.version } : {},
14938
- entries
14939
- };
14940
- }
14941
- function readCatalogCache(env = process.env) {
14942
- const path = catalogCachePath(env);
14943
- if (!existsSync24(path))
14944
- return null;
14945
- try {
14946
- const parsed = JSON.parse(readFileSync16(path, "utf8"));
14947
- if (!Array.isArray(parsed?.entries) || typeof parsed?.fetchedAt !== "string")
14948
- return null;
14949
- return parsed;
14950
- } catch {
14951
- return null;
14952
- }
14953
- }
14954
- function listCatalog(env = process.env, now = Date.now()) {
14955
- const cached = readCatalogCache(env);
14956
- if (!cached) {
14957
- return { url: resolveRegistryUrl(env), fetchedAt: "", entries: [], stale: true, source: "empty" };
14958
- }
14959
- const age = now - Date.parse(cached.fetchedAt);
14960
- return { ...cached, stale: !Number.isFinite(age) || age > CATALOG_CACHE_TTL_MS, source: "cache" };
14961
- }
14962
- async function refreshCatalog(options = {}) {
14963
- const env = options.env ?? process.env;
14964
- const url = options.url?.trim() || resolveRegistryUrl(env);
14965
- const doFetch = options.fetchImpl ?? fetch;
14966
- const controller = new AbortController();
14967
- const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);
14968
- try {
14969
- const res = await doFetch(url, { signal: controller.signal, headers: { accept: "application/json" } });
14970
- if (!res.ok)
14971
- throw new Error(`ACP Registry \u8FD4\u56DE ${res.status}`);
14972
- const snapshot = parseRegistryDocument(await res.json(), url, new Date(options.now ?? Date.now()).toISOString());
14973
- const path = catalogCachePath(env);
14974
- mkdirSync19(dirname16(path), { recursive: true });
14975
- writeFileSync17(path, `${JSON.stringify(snapshot, null, 2)}
14976
- `);
14977
- return snapshot;
14978
- } finally {
14979
- clearTimeout(timer);
14980
- }
14981
- }
14982
- function catalogEntryLaunch(entry) {
14983
- const dist = entry.distribution;
14984
- if (!dist)
14985
- return null;
14986
- const npx = dist.npx;
14987
- if (npx && typeof npx.package === "string" && npx.package.trim()) {
14988
- const extra = Array.isArray(npx.args) ? npx.args.filter((a) => typeof a === "string") : [];
14989
- return { command: "npx", args: ["-y", npx.package.trim(), ...extra] };
14990
- }
14991
- const uvx = dist.uvx;
14992
- if (uvx && typeof uvx.package === "string" && uvx.package.trim()) {
14993
- const extra = Array.isArray(uvx.args) ? uvx.args.filter((a) => typeof a === "string") : [];
14994
- return { command: "uvx", args: [uvx.package.trim(), ...extra] };
14995
- }
14996
- return null;
14997
- }
14998
- function catalogEntryView(entry) {
14999
- return { ...entry, launch: catalogEntryLaunch(entry) };
15000
- }
15001
-
15002
- // src/commands/provider.ts
15003
15346
  var KNOWN_ACTIONS = ["list", "add", "set", "remove", "test", "catalog"];
15004
15347
  async function providerCommand(context) {
15005
15348
  const { action } = context.parsed;