@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 +571 -228
- package/dist/morula.js +2 -2
- package/dist/plugin/plugin.json +1 -1
- package/package.json +1 -1
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 = "
|
|
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 =
|
|
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.
|
|
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
|
|
4618
|
-
import { dirname as
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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\
|
|
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 (
|
|
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 === "
|
|
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,
|
|
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 \
|
|
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: "
|
|
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: "
|
|
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
|
|
9313
|
-
import { dirname as
|
|
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 (!
|
|
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 (
|
|
9797
|
+
if (existsSync18(join22(repoCachePath, ".git"))) {
|
|
9345
9798
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
9346
9799
|
return;
|
|
9347
9800
|
}
|
|
9348
|
-
|
|
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
|
|
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
|
|
9390
|
-
import { basename as basename2, dirname as
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
9859
|
+
if (!existsSync19(p))
|
|
9407
9860
|
return null;
|
|
9408
9861
|
try {
|
|
9409
|
-
const raw = JSON.parse(
|
|
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) || !
|
|
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) ||
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
10015
|
+
if (!existsSync20(lockPath))
|
|
9563
10016
|
return true;
|
|
9564
10017
|
const deadline = Date.now() + timeoutMs;
|
|
9565
|
-
while (
|
|
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 (
|
|
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 (!
|
|
9775
|
-
|
|
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 (
|
|
10244
|
+
if (existsSync20(dotMorulaRescue)) {
|
|
9792
10245
|
const dotMorulaDir = join24(worktreePath, ".morula");
|
|
9793
|
-
if (!
|
|
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 (
|
|
10275
|
+
if (existsSync20(join24(primaryDir, WORKTREE_MARKER_FILE)))
|
|
9823
10276
|
return { primaryDir, legacy: false };
|
|
9824
|
-
if (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 || !
|
|
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 (!
|
|
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
|
-
|
|
10107
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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) =>
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
11600
|
+
mkdirSync17(dir, { recursive: true });
|
|
11148
11601
|
const target = join27(dir, safeAttachmentName(attachment.fileName));
|
|
11149
|
-
|
|
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
|
-
|
|
11259
|
-
|
|
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
|
-
|
|
11264
|
-
|
|
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
|
-
|
|
11268
|
-
|
|
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
|
-
|
|
13459
|
+
mkdirSync18(repoDir, { recursive: true });
|
|
13006
13460
|
const readme = join28(repoDir, "README.md");
|
|
13007
|
-
if (!
|
|
13008
|
-
|
|
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
|
-
|
|
13065
|
-
if (!
|
|
13066
|
-
|
|
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 &&
|
|
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 (
|
|
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
|
-
|
|
13696
|
-
|
|
14149
|
+
mkdirSync18(dir, { recursive: true });
|
|
14150
|
+
writeFileSync16(join28(dir, "SKILL.md"), content, "utf8");
|
|
13697
14151
|
}
|
|
13698
14152
|
if (promptsBase) {
|
|
13699
|
-
|
|
13700
|
-
|
|
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
|
-
|
|
13724
|
-
|
|
14177
|
+
mkdirSync18(refsDir, { recursive: true });
|
|
14178
|
+
writeFileSync16(join28(dir, "SKILL.md"), entry, "utf8");
|
|
13725
14179
|
for (const r of refs)
|
|
13726
|
-
|
|
14180
|
+
writeFileSync16(join28(refsDir, `${r.name}.md`), r.content, "utf8");
|
|
13727
14181
|
const legacyDir = join28(targetDir, projection.skillsDir, LEGACY_COLLABORATION_SKILL_NAME);
|
|
13728
|
-
|
|
13729
|
-
|
|
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
|
-
|
|
13735
|
-
|
|
14188
|
+
mkdirSync18(promptsDir, { recursive: true });
|
|
14189
|
+
writeFileSync16(join28(promptsDir, `${COLLABORATION_SKILL_NAME}.md`), entry, "utf8");
|
|
13736
14190
|
for (const r of refs)
|
|
13737
|
-
|
|
13738
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14256
|
+
mkdirSync18(targetDir, { recursive: true });
|
|
13803
14257
|
for (const file of projection.memoryFiles) {
|
|
13804
|
-
|
|
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
|
-
|
|
14283
|
+
mkdirSync18(targetDir, { recursive: true });
|
|
13830
14284
|
for (const file of projection.memoryFiles) {
|
|
13831
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
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(
|
|
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 (
|
|
14546
|
+
if (existsSync23(configPath)) {
|
|
14093
14547
|
try {
|
|
14094
|
-
|
|
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}` } :
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
14763
|
-
const pid = Number(
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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;
|