evolcore 0.0.22 → 0.0.24
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/CHANGELOG.md +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { getRunnerCatalog, peekRunnerCatalog, catalogEfforts, resolveCatalogModel, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
1
2
|
import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages, resolveSettings as sdkResolveSettings, } from '@anthropic-ai/claude-agent-sdk';
|
|
2
3
|
import { spawn } from 'child_process';
|
|
3
|
-
import {
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { ensureDir } from '../utils/atomic-write.js';
|
|
4
6
|
import { resolveAnthropicConfig } from './baseagent.js';
|
|
5
7
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
6
8
|
import { DEFAULT_PERMISSION_MODE } from '../types.js';
|
|
@@ -29,6 +31,42 @@ import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../cor
|
|
|
29
31
|
import { getManagedTaskTempDir, isManagedSessionRuntimeDir } from '../cli/task-context.js';
|
|
30
32
|
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
31
33
|
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
|
|
34
|
+
const resolveModule = createRequire(import.meta.url);
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the platform package explicitly instead of relying on the SDK's
|
|
37
|
+
* internal optional-dependency lookup. The latter is sensitive to the module
|
|
38
|
+
* installation/loader context and can report a missing binary even when the
|
|
39
|
+
* package is present in the same node_modules tree.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveBundledClaudeExecutable() {
|
|
42
|
+
if (process.platform !== 'win32')
|
|
43
|
+
return undefined;
|
|
44
|
+
const nativePackage = process.arch === 'arm64'
|
|
45
|
+
? '@anthropic-ai/claude-agent-sdk-win32-arm64'
|
|
46
|
+
: process.arch === 'x64'
|
|
47
|
+
? '@anthropic-ai/claude-agent-sdk-win32-x64'
|
|
48
|
+
: undefined;
|
|
49
|
+
if (!nativePackage)
|
|
50
|
+
return undefined;
|
|
51
|
+
const binarySpecifier = `${nativePackage}/claude.exe`;
|
|
52
|
+
try {
|
|
53
|
+
const executable = resolveModule.resolve(binarySpecifier);
|
|
54
|
+
if (fs.existsSync(executable))
|
|
55
|
+
return executable;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Fall through to a direct sibling lookup for package layouts where the
|
|
59
|
+
// optional package does not expose its binary through exports.
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const sdkEntry = resolveModule.resolve('@anthropic-ai/claude-agent-sdk');
|
|
63
|
+
const executable = path.resolve(path.dirname(sdkEntry), '..', '@anthropic-ai', nativePackage.slice('@anthropic-ai/'.length), 'claude.exe');
|
|
64
|
+
return fs.existsSync(executable) ? executable : undefined;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
32
70
|
export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
|
|
33
71
|
// Built-in tools execute inside the Claude runtime and are covered by the
|
|
34
72
|
// runner's filesystem/shell policy. MCP and future unknown tools can perform
|
|
@@ -108,6 +146,40 @@ function appendQueryParams(baseUrl, queryParams) {
|
|
|
108
146
|
return baseUrl;
|
|
109
147
|
}
|
|
110
148
|
}
|
|
149
|
+
function claudeSessionFilePath(projectPath, agentSessionId) {
|
|
150
|
+
return path.join(os.homedir(), '.claude', 'projects', encodePath(projectPath), `${agentSessionId}.jsonl`);
|
|
151
|
+
}
|
|
152
|
+
/** A discovered Claude session is resumable only after its transcript header is durable. */
|
|
153
|
+
function isClaudeSessionFileReady(projectPath, agentSessionId) {
|
|
154
|
+
const sessionFile = claudeSessionFilePath(projectPath, agentSessionId);
|
|
155
|
+
if (!fs.existsSync(sessionFile))
|
|
156
|
+
return false;
|
|
157
|
+
try {
|
|
158
|
+
const content = fs.readFileSync(sessionFile, 'utf8');
|
|
159
|
+
for (const line of content.split('\n')) {
|
|
160
|
+
if (!line.trim())
|
|
161
|
+
continue;
|
|
162
|
+
try {
|
|
163
|
+
const data = JSON.parse(line);
|
|
164
|
+
if (data.sessionId && data.version)
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
catch { }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
async function waitForClaudeSessionFile(projectPath, agentSessionId, timeoutMs = 250) {
|
|
174
|
+
const deadline = Date.now() + timeoutMs;
|
|
175
|
+
do {
|
|
176
|
+
if (isClaudeSessionFileReady(projectPath, agentSessionId))
|
|
177
|
+
return true;
|
|
178
|
+
if (Date.now() >= deadline)
|
|
179
|
+
return false;
|
|
180
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
181
|
+
} while (true);
|
|
182
|
+
}
|
|
111
183
|
function stableClaudePermissionValue(value, depth = 0) {
|
|
112
184
|
if (depth > 8)
|
|
113
185
|
return '[truncated]';
|
|
@@ -261,103 +333,6 @@ async function assertClaudeSettingSourcesHaveLiteralSandboxPaths(cwd, settingSou
|
|
|
261
333
|
}
|
|
262
334
|
}
|
|
263
335
|
}
|
|
264
|
-
// ── 模型别名解析 ──
|
|
265
|
-
// SDK 内置的别名表可能落后于代理实际可用的最新模型,
|
|
266
|
-
// 因此优先从 {baseUrl}/models 动态获取各系列最新版本,失败则使用持久化的最近成功值。
|
|
267
|
-
// 已验证可用但尚未出现在 /models 列表中的模型 ID 会被注入候选列表,
|
|
268
|
-
// 等列表更新后注入自动变为 no-op。
|
|
269
|
-
const MODEL_FAMILIES = ['opus', 'sonnet', 'haiku'];
|
|
270
|
-
/** 已验证可用但可能尚未出现在 /models 列表中的模型 ID(注入候选) */
|
|
271
|
-
const INJECTED_MODELS = [];
|
|
272
|
-
/** 启动默认值:某网关尚无任何成功刷新记录时使用。 */
|
|
273
|
-
const BOOTSTRAP_MODEL_ALIASES = {
|
|
274
|
-
'opus': 'claude-opus-4-8',
|
|
275
|
-
'sonnet': 'claude-sonnet-4-6',
|
|
276
|
-
'haiku': 'claude-haiku-4-5-20251001',
|
|
277
|
-
};
|
|
278
|
-
const MODEL_ALIAS_TTL_MS = 5 * 60 * 1000; // 5min
|
|
279
|
-
const MODEL_ALIAS_STORE_VERSION = 1;
|
|
280
|
-
const modelAliasCache = new Map(); // key: baseUrl
|
|
281
|
-
const modelAliasFallbacks = new Map(); // key: baseUrl,最近一次成功解析值
|
|
282
|
-
const modelAliasInFlight = new Set(); // 去重并发刷新
|
|
283
|
-
const loadedModelAliasFallbacks = new Set(); // key: store path + gateway hash
|
|
284
|
-
function normalizeModelGatewayUrl(baseUrl) {
|
|
285
|
-
return baseUrl.trim().replace(/\/+$/, '');
|
|
286
|
-
}
|
|
287
|
-
function modelGatewayKey(baseUrl) {
|
|
288
|
-
return crypto.createHash('sha256').update(normalizeModelGatewayUrl(baseUrl)).digest('hex');
|
|
289
|
-
}
|
|
290
|
-
function modelAliasStorePath() {
|
|
291
|
-
return resolvePaths().claudeModelCache;
|
|
292
|
-
}
|
|
293
|
-
function sanitizeModelAliases(value) {
|
|
294
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
295
|
-
return {};
|
|
296
|
-
const aliases = {};
|
|
297
|
-
for (const family of MODEL_FAMILIES) {
|
|
298
|
-
const model = value[family];
|
|
299
|
-
if (typeof model !== 'string')
|
|
300
|
-
continue;
|
|
301
|
-
const pattern = new RegExp(`^claude-${family}-[A-Za-z0-9._-]+$`);
|
|
302
|
-
if (pattern.test(model))
|
|
303
|
-
aliases[family] = model;
|
|
304
|
-
}
|
|
305
|
-
return aliases;
|
|
306
|
-
}
|
|
307
|
-
function readPersistedAliasStore() {
|
|
308
|
-
try {
|
|
309
|
-
const value = atomicReadJson(modelAliasStorePath());
|
|
310
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
311
|
-
return { $schema_version: MODEL_ALIAS_STORE_VERSION, gateways: {} };
|
|
312
|
-
}
|
|
313
|
-
const raw = value;
|
|
314
|
-
if (raw.$schema_version !== MODEL_ALIAS_STORE_VERSION || !raw.gateways || typeof raw.gateways !== 'object' || Array.isArray(raw.gateways)) {
|
|
315
|
-
return { $schema_version: MODEL_ALIAS_STORE_VERSION, gateways: {} };
|
|
316
|
-
}
|
|
317
|
-
const gateways = {};
|
|
318
|
-
for (const [key, entry] of Object.entries(raw.gateways)) {
|
|
319
|
-
if (!/^[a-f0-9]{64}$/.test(key) || !entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
320
|
-
continue;
|
|
321
|
-
const record = entry;
|
|
322
|
-
const aliases = sanitizeModelAliases(record.aliases);
|
|
323
|
-
if (Object.keys(aliases).length === 0)
|
|
324
|
-
continue;
|
|
325
|
-
gateways[key] = {
|
|
326
|
-
aliases,
|
|
327
|
-
updatedAt: typeof record.updatedAt === 'number' && Number.isFinite(record.updatedAt) ? record.updatedAt : 0,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
return { $schema_version: MODEL_ALIAS_STORE_VERSION, gateways };
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
logger.warn(`[AgentRunner] Failed to load persisted model aliases: ${error instanceof Error ? error.message : String(error)}`);
|
|
334
|
-
return { $schema_version: MODEL_ALIAS_STORE_VERSION, gateways: {} };
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
function loadPersistedModelAliases(baseUrl) {
|
|
338
|
-
const cacheKey = normalizeModelGatewayUrl(baseUrl);
|
|
339
|
-
const storePath = modelAliasStorePath();
|
|
340
|
-
const gatewayKey = modelGatewayKey(cacheKey);
|
|
341
|
-
const loadKey = `${storePath}\0${gatewayKey}`;
|
|
342
|
-
if (loadedModelAliasFallbacks.has(loadKey))
|
|
343
|
-
return;
|
|
344
|
-
loadedModelAliasFallbacks.add(loadKey);
|
|
345
|
-
const persisted = readPersistedAliasStore().gateways[gatewayKey];
|
|
346
|
-
if (!persisted)
|
|
347
|
-
return;
|
|
348
|
-
modelAliasFallbacks.set(cacheKey, persisted.aliases);
|
|
349
|
-
logger.info(`[AgentRunner] Loaded persisted model aliases: ${JSON.stringify(persisted.aliases)}`);
|
|
350
|
-
}
|
|
351
|
-
function persistModelAliases(baseUrl, aliases, updatedAt) {
|
|
352
|
-
try {
|
|
353
|
-
const store = readPersistedAliasStore();
|
|
354
|
-
store.gateways[modelGatewayKey(baseUrl)] = { aliases, updatedAt };
|
|
355
|
-
atomicWriteJson(modelAliasStorePath(), store);
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
logger.warn(`[AgentRunner] Failed to persist model aliases: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
336
|
// ── 网关价格缓存(从 /v1/models 的 pricing/effective_pricing 提取)─────────────
|
|
362
337
|
// 与别名刷新同范式:按 baseUrl 缓存,1h TTL,stale-while-revalidate(缺失/过期时
|
|
363
338
|
// fire-and-forget 触发刷新,本轮先用旧值或回退,不阻塞查询)。
|
|
@@ -432,94 +407,6 @@ async function refreshGatewayPricing(baseUrl, apiKey, requestHeaders) {
|
|
|
432
407
|
gatewayPricingInFlight.delete(baseUrl);
|
|
433
408
|
}
|
|
434
409
|
}
|
|
435
|
-
/** 从模型 ID 列表中提取各 claude 系列的最新版本(按 major.minor 取最高,minor 可省略) */
|
|
436
|
-
function deriveAliasesFromModelIds(ids) {
|
|
437
|
-
// 注入已验证可用的模型(如果列表中已有则去重无影响)
|
|
438
|
-
const allIds = [...new Set([...ids, ...INJECTED_MODELS])];
|
|
439
|
-
const best = {};
|
|
440
|
-
for (const id of allIds) {
|
|
441
|
-
const m = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:-(\d+))?/);
|
|
442
|
-
if (!m)
|
|
443
|
-
continue;
|
|
444
|
-
const [, family, majorStr, minorStr] = m;
|
|
445
|
-
const major = parseInt(majorStr, 10);
|
|
446
|
-
const minor = minorStr ? parseInt(minorStr, 10) : 0;
|
|
447
|
-
const cur = best[family];
|
|
448
|
-
if (!cur || major > cur.major || (major === cur.major && minor > cur.minor)) {
|
|
449
|
-
best[family] = { id, major, minor };
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const aliases = {};
|
|
453
|
-
for (const family of MODEL_FAMILIES) {
|
|
454
|
-
const info = best[family];
|
|
455
|
-
if (info)
|
|
456
|
-
aliases[family] = info.id;
|
|
457
|
-
}
|
|
458
|
-
return aliases;
|
|
459
|
-
}
|
|
460
|
-
/** 异步刷新某 baseUrl 的别名缓存(失败静默,不抛出) */
|
|
461
|
-
async function refreshModelAliases(baseUrl, apiKey, requestHeaders) {
|
|
462
|
-
const cacheKey = normalizeModelGatewayUrl(baseUrl);
|
|
463
|
-
if (modelAliasInFlight.has(cacheKey))
|
|
464
|
-
return;
|
|
465
|
-
modelAliasInFlight.add(cacheKey);
|
|
466
|
-
loadPersistedModelAliases(cacheKey);
|
|
467
|
-
try {
|
|
468
|
-
const url = `${cacheKey}/models`;
|
|
469
|
-
const controller = new AbortController();
|
|
470
|
-
const timer = setTimeout(() => controller.abort(), 5000);
|
|
471
|
-
const resp = await fetch(url, {
|
|
472
|
-
signal: controller.signal,
|
|
473
|
-
headers: {
|
|
474
|
-
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
475
|
-
...requestHeaders,
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
clearTimeout(timer);
|
|
479
|
-
if (!resp.ok)
|
|
480
|
-
return;
|
|
481
|
-
const json = await resp.json();
|
|
482
|
-
const ids = Array.isArray(json?.data)
|
|
483
|
-
? json.data.map((m) => m?.id).filter((x) => typeof x === 'string')
|
|
484
|
-
: [];
|
|
485
|
-
const aliases = deriveAliasesFromModelIds(ids);
|
|
486
|
-
if (ids.length > 0 || Object.keys(aliases).length > 0) {
|
|
487
|
-
const updatedAt = Date.now();
|
|
488
|
-
modelAliasCache.set(cacheKey, { aliases, ids, fetchedAt: updatedAt });
|
|
489
|
-
if (Object.keys(aliases).length > 0) {
|
|
490
|
-
const previousAliases = modelAliasFallbacks.get(cacheKey) || {};
|
|
491
|
-
const latestAliases = { ...previousAliases, ...aliases };
|
|
492
|
-
modelAliasFallbacks.set(cacheKey, latestAliases);
|
|
493
|
-
persistModelAliases(cacheKey, latestAliases, updatedAt);
|
|
494
|
-
}
|
|
495
|
-
logger.info(`[AgentRunner] Refreshed models from ${url}: ${ids.length} ids, aliases ${JSON.stringify(aliases)}`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
// 网络/解析失败:保留该网关最近一次成功结果,不打断查询
|
|
500
|
-
}
|
|
501
|
-
finally {
|
|
502
|
-
modelAliasInFlight.delete(cacheKey);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
/** 将短别名展开为完整 model ID,已是完整 ID 则原样返回 */
|
|
506
|
-
function resolveModelAlias(model, baseUrl) {
|
|
507
|
-
// 非短别名(已经是完整 ID)直接返回
|
|
508
|
-
if (!MODEL_FAMILIES.includes(model))
|
|
509
|
-
return model;
|
|
510
|
-
const family = model;
|
|
511
|
-
// 优先使用动态缓存
|
|
512
|
-
if (baseUrl) {
|
|
513
|
-
const cacheKey = normalizeModelGatewayUrl(baseUrl);
|
|
514
|
-
loadPersistedModelAliases(cacheKey);
|
|
515
|
-
const cached = modelAliasCache.get(cacheKey);
|
|
516
|
-
if (cached && (Date.now() - cached.fetchedAt < MODEL_ALIAS_TTL_MS)) {
|
|
517
|
-
return cached.aliases[family] || modelAliasFallbacks.get(cacheKey)?.[family] || BOOTSTRAP_MODEL_ALIASES[family];
|
|
518
|
-
}
|
|
519
|
-
return modelAliasFallbacks.get(cacheKey)?.[family] || BOOTSTRAP_MODEL_ALIASES[family];
|
|
520
|
-
}
|
|
521
|
-
return BOOTSTRAP_MODEL_ALIASES[family];
|
|
522
|
-
}
|
|
523
410
|
/**
|
|
524
411
|
* 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
|
|
525
412
|
* 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
|
|
@@ -531,10 +418,6 @@ function applyContextWindow(modelId) {
|
|
|
531
418
|
return `${modelId}[1m]`;
|
|
532
419
|
return modelId;
|
|
533
420
|
}
|
|
534
|
-
/** 解析别名 + 追加 1M 后缀,得到最终交给 SDK 的 model 串。 */
|
|
535
|
-
function resolveSdkModel(model, baseUrl) {
|
|
536
|
-
return applyContextWindow(resolveModelAlias(model, baseUrl));
|
|
537
|
-
}
|
|
538
421
|
class MessageStream {
|
|
539
422
|
queue = [];
|
|
540
423
|
waiting = null;
|
|
@@ -692,9 +575,10 @@ export class AgentRunner {
|
|
|
692
575
|
this.agentAid = runtime?.agentAid ?? legacy?.['evolcoreAgentAid'];
|
|
693
576
|
this.agentConfig = runtime?.agentConfig ?? legacy?.['evolcoreAgentConfig'];
|
|
694
577
|
this.onSessionIdUpdate = onSessionIdUpdate;
|
|
578
|
+
this.claudeExecutablePath = resolveBundledClaudeExecutable();
|
|
695
579
|
if (config) {
|
|
696
580
|
const anthropic = resolveAnthropicConfig(config);
|
|
697
|
-
this.claudeExecutablePath = anthropic.pathToClaudeCodeExecutable;
|
|
581
|
+
this.claudeExecutablePath = anthropic.pathToClaudeCodeExecutable || this.claudeExecutablePath;
|
|
698
582
|
this.customHeaders = buildModelRequestHeaders({
|
|
699
583
|
baseagent: 'claude',
|
|
700
584
|
baseUrl: anthropic.baseUrl,
|
|
@@ -780,31 +664,22 @@ export class AgentRunner {
|
|
|
780
664
|
},
|
|
781
665
|
};
|
|
782
666
|
}
|
|
667
|
+
catalogConnection() {
|
|
668
|
+
return { baseUrl: this.baseUrl, apiKey: this.apiKey, headers: this.customHeaders, queryParams: this.customQueryParams };
|
|
669
|
+
}
|
|
670
|
+
getModelCatalog() {
|
|
671
|
+
return getRunnerCatalog('claude', this.agentAid, this.catalogConnection());
|
|
672
|
+
}
|
|
783
673
|
async listModels() {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const stale = !cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS);
|
|
789
|
-
// 缓存为空(首次打开)→ 等待刷新;缓存仅过期 → 后台刷新不阻塞
|
|
790
|
-
if (!cached) {
|
|
791
|
-
await refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
|
|
792
|
-
cached = modelAliasCache.get(cacheKey);
|
|
793
|
-
}
|
|
794
|
-
else if (stale) {
|
|
795
|
-
refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
|
|
796
|
-
}
|
|
797
|
-
// 有缓存时返回网关 /models 的全量原始 ID
|
|
798
|
-
if (cached && cached.ids.length > 0)
|
|
799
|
-
return cached.ids;
|
|
800
|
-
}
|
|
801
|
-
// 无 baseUrl / 刷新超时或失败 → 回退短别名
|
|
802
|
-
const fallback = this.baseUrl ? modelAliasFallbacks.get(normalizeModelGatewayUrl(this.baseUrl)) : undefined;
|
|
803
|
-
return Object.values({ ...BOOTSTRAP_MODEL_ALIASES, ...fallback });
|
|
804
|
-
}
|
|
805
|
-
/** 将短别名解析为当前代理实际使用的完整 model ID(仅用于展示,不改变持久化值) */
|
|
674
|
+
const catalog = await this.getModelCatalog();
|
|
675
|
+
assertCatalogSelectable(catalog);
|
|
676
|
+
return catalog.models.map(m => m.id);
|
|
677
|
+
}
|
|
806
678
|
resolveModelId(model) {
|
|
807
|
-
return
|
|
679
|
+
return resolveCatalogModel(peekRunnerCatalog('claude', this.agentAid, this.catalogConnection()), model);
|
|
680
|
+
}
|
|
681
|
+
async getAvailableEfforts(model) {
|
|
682
|
+
return catalogEfforts(await this.getModelCatalog(), model, 'claude');
|
|
808
683
|
}
|
|
809
684
|
setEffort(effort) {
|
|
810
685
|
this.effort = effort;
|
|
@@ -1454,8 +1329,9 @@ export class AgentRunner {
|
|
|
1454
1329
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
1455
1330
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
1456
1331
|
*/
|
|
1457
|
-
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn) {
|
|
1332
|
+
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false) {
|
|
1458
1333
|
let lastSessionId;
|
|
1334
|
+
let pendingSessionId;
|
|
1459
1335
|
let hasTurnActivity = false;
|
|
1460
1336
|
let ignoredPreTurnResult = false;
|
|
1461
1337
|
let waitingForBackgroundCompletion = false;
|
|
@@ -1475,19 +1351,41 @@ export class AgentRunner {
|
|
|
1475
1351
|
let lastAssistantUuid;
|
|
1476
1352
|
// 流式收集各次大模型调用(fallback:SDK iterations 为空时使用)
|
|
1477
1353
|
const collectedCalls = [];
|
|
1354
|
+
const activatePendingSession = async (waitForFile) => {
|
|
1355
|
+
const candidate = pendingSessionId;
|
|
1356
|
+
if (!candidate)
|
|
1357
|
+
return undefined;
|
|
1358
|
+
if (!isResuming && topicBinding) {
|
|
1359
|
+
const ready = waitForFile
|
|
1360
|
+
? await waitForClaudeSessionFile(projectPath, candidate)
|
|
1361
|
+
: isClaudeSessionFileReady(projectPath, candidate);
|
|
1362
|
+
if (!ready)
|
|
1363
|
+
return undefined;
|
|
1364
|
+
}
|
|
1365
|
+
if (!await this.acceptDiscoveredSessionId(sessionId, candidate, turn)) {
|
|
1366
|
+
logger.warn(`[AgentRunner] Rejected stale backend discovery: sessionId=${sessionId} backend=${candidate} turn=${turn?.turnId ?? 'legacy'}`);
|
|
1367
|
+
const error = new Error('backend discovery rejected by active turn lease');
|
|
1368
|
+
error.code = 'TOPIC_BACKEND_DISCOVERY_REJECTED';
|
|
1369
|
+
throw error;
|
|
1370
|
+
}
|
|
1371
|
+
pendingSessionId = undefined;
|
|
1372
|
+
return candidate;
|
|
1373
|
+
};
|
|
1478
1374
|
try {
|
|
1479
1375
|
for await (const event of sdkStream) {
|
|
1376
|
+
const activatedBeforeEvent = await activatePendingSession(false);
|
|
1377
|
+
if (activatedBeforeEvent)
|
|
1378
|
+
yield { type: 'session_id', sessionId: activatedBeforeEvent };
|
|
1480
1379
|
// 提取 session_id(任意 SDK 事件都可能携带)
|
|
1481
1380
|
if (event.session_id && event.session_id !== lastSessionId) {
|
|
1482
1381
|
lastSessionId = event.session_id;
|
|
1483
|
-
|
|
1484
|
-
|
|
1382
|
+
pendingSessionId = event.session_id;
|
|
1383
|
+
const activated = await activatePendingSession(event.type === 'result');
|
|
1384
|
+
if (activated) {
|
|
1385
|
+
yield { type: 'session_id', sessionId: activated };
|
|
1485
1386
|
}
|
|
1486
|
-
else {
|
|
1487
|
-
logger.
|
|
1488
|
-
const error = new Error('backend discovery rejected by active turn lease');
|
|
1489
|
-
error.code = 'TOPIC_BACKEND_DISCOVERY_REJECTED';
|
|
1490
|
-
throw error;
|
|
1387
|
+
else if (!isResuming) {
|
|
1388
|
+
logger.info(`[AgentRunner] Deferring backend activation until Claude session file is durable: sessionId=${sessionId} backend=${event.session_id}`);
|
|
1491
1389
|
}
|
|
1492
1390
|
}
|
|
1493
1391
|
if (event.type === 'user' && event.uuid === inputId) {
|
|
@@ -1582,7 +1480,17 @@ export class AgentRunner {
|
|
|
1582
1480
|
}
|
|
1583
1481
|
if (event.type === 'stream_event') {
|
|
1584
1482
|
const streamEvent = event.event;
|
|
1585
|
-
if (streamEvent?.type === '
|
|
1483
|
+
if (streamEvent?.type === 'content_block_start'
|
|
1484
|
+
&& (streamEvent.content_block?.type === 'thinking' || streamEvent.content_block?.type === 'redacted_thinking')) {
|
|
1485
|
+
hasTurnActivity = true;
|
|
1486
|
+
yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
|
|
1487
|
+
}
|
|
1488
|
+
else if (streamEvent?.type === 'content_block_delta'
|
|
1489
|
+
&& streamEvent.delta?.type === 'thinking_delta') {
|
|
1490
|
+
hasTurnActivity = true;
|
|
1491
|
+
yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
|
|
1492
|
+
}
|
|
1493
|
+
else if (streamEvent?.type === 'message_start' && streamEvent.message?.usage) {
|
|
1586
1494
|
hasTurnActivity = true;
|
|
1587
1495
|
lastAssistantWasSynthetic = streamEvent.message.model === '<synthetic>';
|
|
1588
1496
|
lastModelCall = {
|
|
@@ -1616,6 +1524,13 @@ export class AgentRunner {
|
|
|
1616
1524
|
}
|
|
1617
1525
|
continue;
|
|
1618
1526
|
}
|
|
1527
|
+
// During redacted thinking Claude may emit token estimates instead of
|
|
1528
|
+
// readable thinking deltas. They still prove the model is active.
|
|
1529
|
+
if (event.type === 'system' && event.subtype === 'thinking_tokens') {
|
|
1530
|
+
hasTurnActivity = true;
|
|
1531
|
+
yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1619
1534
|
// system: compact_boundary → compact
|
|
1620
1535
|
if (event.type === 'system' && event.subtype === 'compact_boundary') {
|
|
1621
1536
|
yield {
|
|
@@ -1686,7 +1601,11 @@ export class AgentRunner {
|
|
|
1686
1601
|
// 统计本轮 base agent 全部输出字符数(text + tool_use input)
|
|
1687
1602
|
let turnOutputChars = 0;
|
|
1688
1603
|
for (const content of event.message.content) {
|
|
1689
|
-
if (content.type === '
|
|
1604
|
+
if (content.type === 'thinking' || content.type === 'redacted_thinking') {
|
|
1605
|
+
hasTurnActivity = true;
|
|
1606
|
+
yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
|
|
1607
|
+
}
|
|
1608
|
+
else if (content.type === 'tool_use') {
|
|
1690
1609
|
const inputStr = typeof content.input === 'string' ? content.input : JSON.stringify(content.input || '');
|
|
1691
1610
|
turnOutputChars += inputStr.length;
|
|
1692
1611
|
}
|
|
@@ -1729,6 +1648,12 @@ export class AgentRunner {
|
|
|
1729
1648
|
}
|
|
1730
1649
|
// result → complete(含 permission_denials 提取)
|
|
1731
1650
|
if (event.type === 'result') {
|
|
1651
|
+
// The SDK may announce session_id on the initial system event and omit
|
|
1652
|
+
// it from the terminal result. Give the transcript a short grace
|
|
1653
|
+
// period here before deciding that a topic backend is unavailable.
|
|
1654
|
+
const activatedAtResult = await activatePendingSession(true);
|
|
1655
|
+
if (activatedAtResult)
|
|
1656
|
+
yield { type: 'session_id', sessionId: activatedAtResult };
|
|
1732
1657
|
// Most SDK streams surface denials as user-message tool_result blocks.
|
|
1733
1658
|
// The terminal summary is only a fallback for incomplete streams.
|
|
1734
1659
|
if (Array.isArray(event.permission_denials)) {
|
|
@@ -1858,6 +1783,15 @@ export class AgentRunner {
|
|
|
1858
1783
|
modelCalls,
|
|
1859
1784
|
assistantUuid: lastAssistantUuid,
|
|
1860
1785
|
};
|
|
1786
|
+
// A topic binding must never outlive the provider transcript that can
|
|
1787
|
+
// resume it. If Claude reports success before its session file becomes
|
|
1788
|
+
// durable, fail this turn while leaving the topic unbound; /renew is
|
|
1789
|
+
// then available for older sessions that already contain a bad bind.
|
|
1790
|
+
if (!event.is_error && topicBinding && pendingSessionId) {
|
|
1791
|
+
const error = new Error(`bound topic backend is unavailable: ${pendingSessionId}`);
|
|
1792
|
+
error.code = 'TOPIC_BACKEND_UNAVAILABLE';
|
|
1793
|
+
throw error;
|
|
1794
|
+
}
|
|
1861
1795
|
// Fatal results belong to the query transport itself and must terminate
|
|
1862
1796
|
// even when the current input was not echoed back by the CLI.
|
|
1863
1797
|
if (event.is_error) {
|
|
@@ -1967,13 +1901,9 @@ export class AgentRunner {
|
|
|
1967
1901
|
// 记录当前 evolcore session ID,用于 Agent ctl 环境变量注入
|
|
1968
1902
|
// 同步用户级配置到内存
|
|
1969
1903
|
this.syncFromUserSettings();
|
|
1970
|
-
//
|
|
1904
|
+
// Run preparation and model menus resolve aliases from the same snapshot.
|
|
1905
|
+
const runCatalog = await this.getModelCatalog();
|
|
1971
1906
|
if (this.baseUrl) {
|
|
1972
|
-
const cacheKey = normalizeModelGatewayUrl(this.baseUrl);
|
|
1973
|
-
const cached = modelAliasCache.get(cacheKey);
|
|
1974
|
-
if (!cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS)) {
|
|
1975
|
-
refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
|
|
1976
|
-
}
|
|
1977
1907
|
// 顺带预热网关价格缓存(1h TTL),让本轮结束写库时已就绪
|
|
1978
1908
|
const pricing = gatewayPricingCache.get(this.baseUrl);
|
|
1979
1909
|
if (!pricing || (Date.now() - pricing.fetchedAt > GATEWAY_PRICING_TTL_MS)) {
|
|
@@ -2539,7 +2469,7 @@ export class AgentRunner {
|
|
|
2539
2469
|
else {
|
|
2540
2470
|
logger.info(`[AgentRunner] systemPromptAppend: none`);
|
|
2541
2471
|
}
|
|
2542
|
-
const sdkModel =
|
|
2472
|
+
const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel));
|
|
2543
2473
|
const capabilityOptions = await this.resolveCapabilityRunOptions(projectPath);
|
|
2544
2474
|
const managedSettings = callPermissionMode === 'fullaccess'
|
|
2545
2475
|
? undefined
|
|
@@ -2814,7 +2744,7 @@ export class AgentRunner {
|
|
|
2814
2744
|
this.interruptFns.set(sessionId, () => sdkStream.interrupt());
|
|
2815
2745
|
}
|
|
2816
2746
|
// 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
|
|
2817
|
-
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn);
|
|
2747
|
+
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding);
|
|
2818
2748
|
const self = this;
|
|
2819
2749
|
return (async function* () {
|
|
2820
2750
|
try {
|
|
@@ -2946,16 +2876,19 @@ export class AgentRunner {
|
|
|
2946
2876
|
}
|
|
2947
2877
|
});
|
|
2948
2878
|
}
|
|
2949
|
-
runSessionCommand(prompt, agentSessionId, projectPath, modelOverride) {
|
|
2950
|
-
const callModel = modelOverride?.model || this.model;
|
|
2879
|
+
async runSessionCommand(prompt, agentSessionId, projectPath, modelOverride, useModel = true) {
|
|
2880
|
+
const callModel = modelOverride?.model || (useModel ? this.model : undefined);
|
|
2951
2881
|
const callEffort = (modelOverride?.effortMode === 'model_default'
|
|
2952
2882
|
? undefined
|
|
2953
|
-
: modelOverride?.effort ?? this.effort);
|
|
2883
|
+
: modelOverride?.effort ?? (useModel ? this.effort : undefined));
|
|
2884
|
+
const catalog = useModel ? await this.getModelCatalog() : undefined;
|
|
2885
|
+
if (catalog)
|
|
2886
|
+
assertCatalogSelectable(catalog);
|
|
2954
2887
|
return query({
|
|
2955
2888
|
prompt,
|
|
2956
2889
|
options: {
|
|
2957
2890
|
cwd: projectPath,
|
|
2958
|
-
model:
|
|
2891
|
+
...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel)) } : {}),
|
|
2959
2892
|
resume: agentSessionId,
|
|
2960
2893
|
maxTurns: 1,
|
|
2961
2894
|
tools: [],
|
|
@@ -2964,9 +2897,12 @@ export class AgentRunner {
|
|
|
2964
2897
|
strictMcpConfig: true,
|
|
2965
2898
|
settingSources: [],
|
|
2966
2899
|
managedSettings: buildClaudeManagedLockdownSettings(),
|
|
2967
|
-
...(callEffort ? { effort: callEffort } : {}),
|
|
2900
|
+
...(catalog && callEffort ? { effort: callEffort } : {}),
|
|
2968
2901
|
permissionMode: this.toSdkPermissionMode(),
|
|
2969
|
-
env: this.getAgentEnv()
|
|
2902
|
+
env: this.getAgentEnv(),
|
|
2903
|
+
...(this.claudeExecutablePath
|
|
2904
|
+
? { pathToClaudeCodeExecutable: this.claudeExecutablePath }
|
|
2905
|
+
: {})
|
|
2970
2906
|
}
|
|
2971
2907
|
});
|
|
2972
2908
|
}
|
|
@@ -2979,7 +2915,7 @@ export class AgentRunner {
|
|
|
2979
2915
|
let lastEvent;
|
|
2980
2916
|
try {
|
|
2981
2917
|
logger.info(`[AgentRunner] Compacting session: session=${sessionId} agent=${agentSessionId} project=${projectPath}`);
|
|
2982
|
-
const stream = this.runSessionCommand('/compact', agentSessionId, projectPath, modelOverride);
|
|
2918
|
+
const stream = await this.runSessionCommand('/compact', agentSessionId, projectPath, modelOverride);
|
|
2983
2919
|
this.activeStreams.set(sessionId, stream);
|
|
2984
2920
|
try {
|
|
2985
2921
|
let boundary;
|
|
@@ -3049,7 +2985,9 @@ export class AgentRunner {
|
|
|
3049
2985
|
async clearSession(sessionId, agentSessionId, projectPath) {
|
|
3050
2986
|
try {
|
|
3051
2987
|
logger.info(`[AgentRunner] Clearing session via SDK: ${agentSessionId}`);
|
|
3052
|
-
|
|
2988
|
+
// Clearing is a backend lifecycle operation. It does not depend on any
|
|
2989
|
+
// Session model or model configuration/selection.
|
|
2990
|
+
const stream = await this.runSessionCommand('/clear', agentSessionId, projectPath, undefined, false);
|
|
3053
2991
|
this.activeStreams.set(sessionId, stream);
|
|
3054
2992
|
try {
|
|
3055
2993
|
let cleared = false;
|
|
@@ -3216,6 +3154,7 @@ export class AgentRunner {
|
|
|
3216
3154
|
strictMcpConfig: true,
|
|
3217
3155
|
settingSources: [],
|
|
3218
3156
|
managedSettings: buildClaudeManagedLockdownSettings(),
|
|
3157
|
+
...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
|
|
3219
3158
|
permissionMode: this.toSdkPermissionMode(),
|
|
3220
3159
|
stderr: (data) => { stderrChunks.push(data); },
|
|
3221
3160
|
env: this.getAgentEnv(),
|