evolcore 0.0.10 → 0.0.12
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 +44 -0
- package/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -11,7 +11,7 @@ import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
|
11
11
|
import { logger } from '../../utils/logger.js';
|
|
12
12
|
import { isHostChinese } from '../../utils/locale.js';
|
|
13
13
|
import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
14
|
-
import {
|
|
14
|
+
import { isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
15
15
|
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
16
16
|
import { getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
17
17
|
import { renderKitSections } from '../../eck/kit-renderer.js';
|
|
@@ -33,7 +33,6 @@ import { eligibleFallbackModels } from '../model/model-fallback.js';
|
|
|
33
33
|
import { RetryScheduler } from './retry-scheduler.js';
|
|
34
34
|
import { constrainRuntimePermissionMode, resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
35
35
|
import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
|
|
36
|
-
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
37
36
|
import { authorizationConfigRevision, checkRoleAccess, getFirstStaticAgentOwner, listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, roleToSessionIdentity, } from '../../config/peer-role-resolver.js';
|
|
38
37
|
import { insertUsageEvent, insertContextBreakdown, insertModelCalls } from '../../stats/writer.js';
|
|
39
38
|
import { normalizeUsage } from '../../stats/normalizer.js';
|
|
@@ -727,7 +726,19 @@ export class ResponseEngine {
|
|
|
727
726
|
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
728
727
|
if (!selfAid)
|
|
729
728
|
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'self agent not found' };
|
|
730
|
-
return this.
|
|
729
|
+
return this.returnAuthorizedHandoffResult({
|
|
730
|
+
sessionId: params.sessionId,
|
|
731
|
+
handoffId: params.handoffId,
|
|
732
|
+
content: params.content,
|
|
733
|
+
}, selfAid);
|
|
734
|
+
}
|
|
735
|
+
/** Execute a handoff return after the daemon IPC boundary has authorized the task delegation. */
|
|
736
|
+
async returnAuthorizedHandoffResult(params, selfAid) {
|
|
737
|
+
if (!this.handoffRuntime) {
|
|
738
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff runtime not configured' };
|
|
739
|
+
}
|
|
740
|
+
const runtime = this.activeTaskRuntimeContexts.get(params.sessionId);
|
|
741
|
+
return await this.handoffRuntime.returnHandoff({
|
|
731
742
|
selfAid,
|
|
732
743
|
currentSessionId: params.sessionId,
|
|
733
744
|
handoffId: params.handoffId,
|
|
@@ -1152,6 +1163,14 @@ export class ResponseEngine {
|
|
|
1152
1163
|
async processMessage(message) {
|
|
1153
1164
|
const idleMs = (this.globalSettings.idleMonitor?.timeout ?? 120) * 1000;
|
|
1154
1165
|
const totalExecutionMs = this.totalExecutionLimitMs();
|
|
1166
|
+
if (message.handoffDelivery
|
|
1167
|
+
&& this.handoffRuntime
|
|
1168
|
+
&& this.handoffRuntime.isDiscardedDelivery(message)) {
|
|
1169
|
+
logger.info(`[ResponseEngine] Skip stale handoff delivery before session resolution:`
|
|
1170
|
+
+ ` direction=${message.handoffDelivery.direction}`
|
|
1171
|
+
+ ` handoff=${message.handoffDelivery.handoffId}`);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1155
1174
|
// 先解析会话,再优先用 session.metadata.channelKey 精确定位实例级 adapter
|
|
1156
1175
|
// message.channel 现在存实例名(channelName),可直接用于精确路由
|
|
1157
1176
|
const { session, absoluteProjectPath } = await this.resolveSession(message);
|
|
@@ -1720,11 +1739,11 @@ export class ResponseEngine {
|
|
|
1720
1739
|
messageId: message.messageId, peerId: message.peerId, content: message.content,
|
|
1721
1740
|
peerType: message.peerType || session.metadata?.peerType,
|
|
1722
1741
|
chatType: chatType, isMentioned: message.isMentioned,
|
|
1723
|
-
mentionAids: message.mentionAids, source: message.source,
|
|
1742
|
+
mentions: message.mentions, mentionAids: message.mentionAids, source: message.source,
|
|
1724
1743
|
},
|
|
1725
1744
|
modeConfig: resolvedMode.context.modeConfig,
|
|
1726
1745
|
state: modeState,
|
|
1727
|
-
isSendCommand: (toolName, toolInput) =>
|
|
1746
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, session.selfAID || message.selfAID),
|
|
1728
1747
|
logger,
|
|
1729
1748
|
} : null;
|
|
1730
1749
|
if (resolvedMode?.mode.beforeProcess && modeProcessCtx) {
|
|
@@ -2118,11 +2137,21 @@ export class ResponseEngine {
|
|
|
2118
2137
|
},
|
|
2119
2138
|
};
|
|
2120
2139
|
})();
|
|
2140
|
+
let effectivePermissionMode = 'readonly';
|
|
2121
2141
|
const recordExecutionAnomaly = triggerRunId
|
|
2122
2142
|
? (anomaly) => {
|
|
2123
|
-
recordTriggerExecutionAnomaly(triggerRunId,
|
|
2143
|
+
recordTriggerExecutionAnomaly(triggerRunId, {
|
|
2144
|
+
...anomaly,
|
|
2145
|
+
agentAid: anomaly.agentAid ?? session.selfAID ?? message.selfAID,
|
|
2146
|
+
sessionId: anomaly.sessionId ?? session.id,
|
|
2147
|
+
permissionMode: anomaly.permissionMode ?? effectivePermissionMode,
|
|
2148
|
+
});
|
|
2124
2149
|
}
|
|
2125
2150
|
: undefined;
|
|
2151
|
+
const runModeConfig = resolvedMode?.mode.configureRun && modeProcessCtx
|
|
2152
|
+
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
2153
|
+
: undefined;
|
|
2154
|
+
const pureSessionPolicyHook = runModeConfig?.policyHook;
|
|
2126
2155
|
// 设置权限审批的交互上下文(支持交互卡片)
|
|
2127
2156
|
agent.setPermissionContext?.(session.id, {
|
|
2128
2157
|
sendPrompt: permissionPromptForOrigin,
|
|
@@ -2144,6 +2173,7 @@ export class ResponseEngine {
|
|
|
2144
2173
|
approvalRouting,
|
|
2145
2174
|
approvalInteractionPolicy: authorizationIdentity ? 'deny' : 'interactive',
|
|
2146
2175
|
recordExecutionAnomaly,
|
|
2176
|
+
preToolUsePolicyHook: pureSessionPolicyHook,
|
|
2147
2177
|
armApprovedDelegationCommand: this.agentDelegationRegistry
|
|
2148
2178
|
? (carrierToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
|
|
2149
2179
|
carrierToken,
|
|
@@ -2233,10 +2263,7 @@ export class ResponseEngine {
|
|
|
2233
2263
|
// [迁移点2] policyHook 由 ProactiveMode.configureRun 提供(插件决策);
|
|
2234
2264
|
// 引擎补充副作用:违规时注入提醒到模型上下文 + 行为探针。
|
|
2235
2265
|
policyHook: (() => {
|
|
2236
|
-
const
|
|
2237
|
-
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
2238
|
-
: undefined;
|
|
2239
|
-
const pluginHook = runConfig?.policyHook;
|
|
2266
|
+
const pluginHook = pureSessionPolicyHook;
|
|
2240
2267
|
if (!pluginHook)
|
|
2241
2268
|
return undefined;
|
|
2242
2269
|
return (toolName, toolInput) => {
|
|
@@ -2250,9 +2277,9 @@ export class ResponseEngine {
|
|
|
2250
2277
|
agent.injectUserMessage?.(session.id, errorMsg);
|
|
2251
2278
|
return result;
|
|
2252
2279
|
}
|
|
2253
|
-
//
|
|
2254
|
-
|
|
2255
|
-
if (wasFirstPending
|
|
2280
|
+
// 这里只记录审批结果。firstToolDone 必须等真实 tool_use 事件再推进,
|
|
2281
|
+
// 避免伪造 managed hook IPC 提前解除门禁。
|
|
2282
|
+
if (wasFirstPending) {
|
|
2256
2283
|
snapshot.set(session.id, taskId, { policyHook: { triggered: true, blocked: false, toolName } });
|
|
2257
2284
|
}
|
|
2258
2285
|
return undefined;
|
|
@@ -2290,8 +2317,6 @@ export class ResponseEngine {
|
|
|
2290
2317
|
let handoffPromptRendered = false;
|
|
2291
2318
|
let runtimeEnv;
|
|
2292
2319
|
try {
|
|
2293
|
-
// 动态构建运行时上下文提示
|
|
2294
|
-
const contextParts = [];
|
|
2295
2320
|
// 通道能力
|
|
2296
2321
|
const supportsFileMarker = currentChannelType !== 'aun' && !isProactive && !!channelInfo.adapter.capabilities?.file;
|
|
2297
2322
|
const capParts = [];
|
|
@@ -2301,14 +2326,11 @@ export class ResponseEngine {
|
|
|
2301
2326
|
capParts.push('图片输出');
|
|
2302
2327
|
if (!isProactive && channelInfo.adapter.capabilities?.file)
|
|
2303
2328
|
capParts.push('文件发送');
|
|
2304
|
-
// Personal
|
|
2329
|
+
// Personal state is exposed to the manifest as vars. The manifest owns
|
|
2330
|
+
// loading persona.md and working.md into the system prompt.
|
|
2305
2331
|
const owningAgent = owningAgentForTask;
|
|
2306
2332
|
const persona = owningAgent?.getPersona?.() || undefined;
|
|
2307
2333
|
const working = owningAgent?.getWorkingMemory?.() || undefined;
|
|
2308
|
-
if (persona)
|
|
2309
|
-
contextParts.push(persona);
|
|
2310
|
-
if (working)
|
|
2311
|
-
contextParts.push(`[当前关注]\n${working}`);
|
|
2312
2334
|
// 计算 peerKey:群聊固定按 groupId/channelId,私聊按发送者 peerId。
|
|
2313
2335
|
// 这样单条和积压合并批次不会因队列状态不同而切换关系级配置。
|
|
2314
2336
|
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
@@ -2316,7 +2338,6 @@ export class ResponseEngine {
|
|
|
2316
2338
|
// Trigger 只能降低本次调用权限,不能突破当前角色的权限上限。
|
|
2317
2339
|
// 作为 per-call 入参随 modelOverride 传入 runQuery —— 与 model/effort 同构,
|
|
2318
2340
|
// 不写 AgentRunner 实例字段,多对端/多会话并发共享同一 runner 实例时互不污染。
|
|
2319
|
-
let effectivePermissionMode;
|
|
2320
2341
|
const triggerPermissionModeOverride = message.triggerMeta?.permissionModeOverride;
|
|
2321
2342
|
try {
|
|
2322
2343
|
effectivePermissionMode = constrainRuntimePermissionMode({
|
|
@@ -2532,9 +2553,10 @@ export class ResponseEngine {
|
|
|
2532
2553
|
channel: currentChannelType || null,
|
|
2533
2554
|
venueUid: undefined,
|
|
2534
2555
|
// 群 @ 处理模式 / 客户端类型 / 权限模式
|
|
2535
|
-
// 优先 agent/relation mentionMode 配置,fallback 到服务器
|
|
2556
|
+
// 优先 agent/relation mentionMode 配置,fallback 到服务器 mention_mode 缓存。
|
|
2536
2557
|
mentionMode: effectiveAgentConfig?.mentionMode
|
|
2537
|
-
??
|
|
2558
|
+
?? session.metadata?.mentionMode
|
|
2559
|
+
?? message.mentionMode
|
|
2538
2560
|
?? undefined,
|
|
2539
2561
|
clientType: message.clientType || undefined,
|
|
2540
2562
|
permissionMode: effectivePermissionMode,
|
|
@@ -2583,17 +2605,13 @@ export class ResponseEngine {
|
|
|
2583
2605
|
const sessionManifests = this.agentRegistry?.resolveByChannel(channelKey)?.config?.sessionManifests;
|
|
2584
2606
|
const manifestFile = sessionManifests?.[sessionType] ?? 'eck_manifest.json';
|
|
2585
2607
|
const kitContext = renderKitSections(kitCtx, manifestFile);
|
|
2586
|
-
|
|
2587
|
-
contextParts.push(kitContext);
|
|
2588
|
-
effectiveSystemPrompt = [options?.systemPromptAppend, ...contextParts].filter(Boolean).join('\n') || undefined;
|
|
2608
|
+
effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
|
|
2589
2609
|
// ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
|
|
2590
2610
|
try {
|
|
2591
2611
|
const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
|
|
2592
2612
|
const cbModel = effectiveModel || agentModel || 'unknown';
|
|
2593
2613
|
const cbMaxTokens = 200000; // 保守默认,后续可从 model-catalog 取
|
|
2594
2614
|
const systemPromptTokens = estTokens(options?.systemPromptAppend);
|
|
2595
|
-
const personaTokens = estTokens(persona);
|
|
2596
|
-
const workingTokens = estTokens(working);
|
|
2597
2615
|
const kitTokens = estTokens(kitContext);
|
|
2598
2616
|
const totalEst = estTokens(effectiveSystemPrompt);
|
|
2599
2617
|
insertContextBreakdown(resolveRoot(), {
|
|
@@ -2603,7 +2621,7 @@ export class ResponseEngine {
|
|
|
2603
2621
|
turn_count: 0, // 按 ts 排序得轮次
|
|
2604
2622
|
model: cbModel,
|
|
2605
2623
|
max_tokens: cbMaxTokens,
|
|
2606
|
-
system_prompt: systemPromptTokens
|
|
2624
|
+
system_prompt: systemPromptTokens,
|
|
2607
2625
|
system_tools: 0, // 工具 schema 不在此层,留 0(后续 runner 层补)
|
|
2608
2626
|
mcp_tools: 0,
|
|
2609
2627
|
custom_agents: 0,
|
|
@@ -2629,6 +2647,7 @@ export class ResponseEngine {
|
|
|
2629
2647
|
encrypted: message.encrypted,
|
|
2630
2648
|
content: message.content, timestamp: message.timestamp,
|
|
2631
2649
|
images: message.images,
|
|
2650
|
+
mentions: message.mentions,
|
|
2632
2651
|
mentionAids: message.mentionAids,
|
|
2633
2652
|
}];
|
|
2634
2653
|
const peerItems = (() => {
|
|
@@ -3916,12 +3935,10 @@ export class ResponseEngine {
|
|
|
3916
3935
|
await this.sessionManager.updateSession(session.id, { metadata: session.metadata });
|
|
3917
3936
|
}
|
|
3918
3937
|
}
|
|
3919
|
-
//
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
logger.info(`[ResponseEngine] dispatchMode sync: sessionId=${session.id} ${session.metadata?.dispatchMode ?? 'none'} -> ${message.dispatchMode}`);
|
|
3924
|
-
session.metadata = { ...(session.metadata || {}), dispatchMode: message.dispatchMode };
|
|
3938
|
+
// 同步服务端 mention_mode,供同一群会话的上下文和菜单展示使用。
|
|
3939
|
+
if (message.chatType === 'group' && message.mentionMode && session.metadata?.mentionMode !== message.mentionMode) {
|
|
3940
|
+
logger.info(`[ResponseEngine] mentionMode sync: sessionId=${session.id} ${session.metadata?.mentionMode ?? 'none'} -> ${message.mentionMode}`);
|
|
3941
|
+
session.metadata = { ...(session.metadata || {}), mentionMode: message.mentionMode };
|
|
3925
3942
|
await this.sessionManager.updateSession(session.id, { metadata: session.metadata });
|
|
3926
3943
|
}
|
|
3927
3944
|
// chatMode 策略由 agent/relation behavior 配置在处理阶段解析;此处不再写 session 级参数。
|
|
@@ -4293,6 +4310,7 @@ export class ResponseEngine {
|
|
|
4293
4310
|
}
|
|
4294
4311
|
// [迁移点4] 工具汇报提醒:由 ProactiveMode.onToolUse 实现
|
|
4295
4312
|
if (modeHooks?.mode?.onToolUse) {
|
|
4313
|
+
const proactiveState = modeHooks.state.get('proactive');
|
|
4296
4314
|
modeHooks.mode.onToolUse({
|
|
4297
4315
|
session,
|
|
4298
4316
|
state: modeHooks.state,
|
|
@@ -4300,7 +4318,7 @@ export class ResponseEngine {
|
|
|
4300
4318
|
toolInput: event.input || {},
|
|
4301
4319
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
4302
4320
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
4303
|
-
isSendCommand: (toolName, toolInput) =>
|
|
4321
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', session.selfAID),
|
|
4304
4322
|
logger,
|
|
4305
4323
|
});
|
|
4306
4324
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* model-catalog: 模型目录与单模型元数据。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 目录来源按 baseagent 隔离:Codex 预创建优先使用 CLI 动态目录;
|
|
5
|
+
* 其他场景探测本 baseagent 的 {baseUrl}/v1/models 与 {baseUrl}/models,
|
|
6
|
+
* 最后只回退到同 baseagent 的内置目录。
|
|
6
7
|
*
|
|
7
8
|
* baseUrl/apiKey 复用 baseagent 凭据解析链(与发消息同源),命令不接收 url/key 参数。
|
|
8
9
|
* 单模型详情(info)现阶段全部为 mock,{baseUrl}/models 接口补全字段后自动接真实数据。
|
|
@@ -10,28 +11,52 @@
|
|
|
10
11
|
* 详见 docs/model-command-design.md。
|
|
11
12
|
*/
|
|
12
13
|
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
|
|
14
|
+
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
13
15
|
import { activeBaseagent } from './config-scope.js';
|
|
14
16
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
17
|
+
import { loadDefaults } from '../../config-store.js';
|
|
18
|
+
import { execCodexCliSync } from '../../utils/codex-cli.js';
|
|
15
19
|
/** 取指定 baseagent 的 baseUrl + apiKey(复用现有解析链)。 */
|
|
16
20
|
function resolveCreds(self, ba) {
|
|
17
21
|
const baseagent = ba || activeBaseagent(self);
|
|
18
|
-
//
|
|
19
|
-
const config = self
|
|
22
|
+
// A new Agent has no self config yet, so resolve its gateway from defaults.
|
|
23
|
+
const config = self
|
|
24
|
+
? resolveEffective({ self }, { cache: true, expand: true })
|
|
25
|
+
: { baseagents: loadDefaults()?.baseagents ?? {} };
|
|
20
26
|
const block = (config.baseagents || {});
|
|
21
27
|
try {
|
|
22
28
|
if (baseagent === 'codex') {
|
|
23
29
|
const c = block.codex;
|
|
24
30
|
const r = resolveOpenaiConfig({ agents: { codex: c } }, c);
|
|
25
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
baseUrl: r.baseUrl,
|
|
33
|
+
apiKey: r.apiKey,
|
|
34
|
+
agentAid: self,
|
|
35
|
+
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
36
|
+
};
|
|
26
37
|
}
|
|
27
38
|
if (baseagent === 'ecagent') {
|
|
28
39
|
const c = block.ecagent;
|
|
29
40
|
const r = resolveEcagentConfig({ agents: { ecagent: c } }, c);
|
|
30
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
baseUrl: r.baseUrl,
|
|
43
|
+
apiKey: r.apiKey,
|
|
44
|
+
agentAid: self,
|
|
45
|
+
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
46
|
+
};
|
|
31
47
|
}
|
|
48
|
+
if (baseagent === 'gemini')
|
|
49
|
+
return {};
|
|
50
|
+
if (baseagent !== 'claude')
|
|
51
|
+
return {};
|
|
32
52
|
const c = block.claude;
|
|
33
53
|
const r = resolveAnthropicConfig({ agents: { claude: c } }, c);
|
|
34
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
baseUrl: r.baseUrl,
|
|
56
|
+
apiKey: r.apiKey,
|
|
57
|
+
agentAid: self,
|
|
58
|
+
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
59
|
+
};
|
|
35
60
|
}
|
|
36
61
|
catch {
|
|
37
62
|
return {};
|
|
@@ -97,15 +122,21 @@ export function parseModelList(json, url) {
|
|
|
97
122
|
}
|
|
98
123
|
return genericParse(json);
|
|
99
124
|
}
|
|
100
|
-
|
|
125
|
+
const MODEL_LIST_TIMEOUT_MS = 2000;
|
|
126
|
+
const MODEL_CATALOG_TTL_MS = 30_000;
|
|
127
|
+
const CODEX_CLI_CATALOG_TIMEOUT_MS = 2000;
|
|
128
|
+
async function fetchModelList(url, apiKey, requestHeaders) {
|
|
129
|
+
let timer;
|
|
101
130
|
try {
|
|
102
131
|
const controller = new AbortController();
|
|
103
|
-
|
|
132
|
+
timer = setTimeout(() => controller.abort(), MODEL_LIST_TIMEOUT_MS);
|
|
104
133
|
const resp = await fetch(url, {
|
|
105
134
|
signal: controller.signal,
|
|
106
|
-
headers:
|
|
135
|
+
headers: {
|
|
136
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
137
|
+
...requestHeaders,
|
|
138
|
+
},
|
|
107
139
|
});
|
|
108
|
-
clearTimeout(timer);
|
|
109
140
|
if (!resp.ok)
|
|
110
141
|
return null;
|
|
111
142
|
const json = await resp.json();
|
|
@@ -115,6 +146,10 @@ async function fetchModelList(url, apiKey) {
|
|
|
115
146
|
catch {
|
|
116
147
|
return null;
|
|
117
148
|
}
|
|
149
|
+
finally {
|
|
150
|
+
if (timer)
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
118
153
|
}
|
|
119
154
|
/** 内置 mock catalog(接口未就绪时的兜底;实测可用模型,见 docs 附录 A)。 */
|
|
120
155
|
const MOCK_CATALOG = [
|
|
@@ -131,6 +166,53 @@ const MOCK_CATALOG = [
|
|
|
131
166
|
{ id: 'glm-4.7', owned_by: 'zhipu' },
|
|
132
167
|
{ id: 'MiniMax-M2.7', owned_by: 'minimax' },
|
|
133
168
|
];
|
|
169
|
+
const CODEX_BUILTIN_CATALOG = [
|
|
170
|
+
{ id: 'gpt-5.5', owned_by: 'openai' },
|
|
171
|
+
{ id: 'gpt-5.4', owned_by: 'openai' },
|
|
172
|
+
{ id: 'gpt-5.4-mini', owned_by: 'openai' },
|
|
173
|
+
{ id: 'gpt-5.3-codex', owned_by: 'openai' },
|
|
174
|
+
{ id: 'gpt-5.2', owned_by: 'openai' },
|
|
175
|
+
];
|
|
176
|
+
const GEMINI_BUILTIN_CATALOG = [
|
|
177
|
+
{ id: 'gemini-2.5-pro', owned_by: 'google' },
|
|
178
|
+
{ id: 'gemini-2.5-flash', owned_by: 'google' },
|
|
179
|
+
{ id: 'gemini-2.5-flash-lite', owned_by: 'google' },
|
|
180
|
+
];
|
|
181
|
+
const catalogCache = new Map();
|
|
182
|
+
const catalogInFlight = new Map();
|
|
183
|
+
export function clearModelCatalogCache() {
|
|
184
|
+
catalogCache.clear();
|
|
185
|
+
catalogInFlight.clear();
|
|
186
|
+
}
|
|
187
|
+
/** A failed gateway may only fall back within the selected baseagent. */
|
|
188
|
+
export function builtinCatalog(baseagent) {
|
|
189
|
+
if (baseagent === 'codex')
|
|
190
|
+
return CODEX_BUILTIN_CATALOG.map(model => ({ ...model }));
|
|
191
|
+
if (baseagent === 'gemini')
|
|
192
|
+
return GEMINI_BUILTIN_CATALOG.map(model => ({ ...model }));
|
|
193
|
+
if (baseagent === 'ecagent')
|
|
194
|
+
return [{ id: 'claude-sonnet-4-6', owned_by: 'anthropic' }];
|
|
195
|
+
return MOCK_CATALOG.map(model => ({ ...model }));
|
|
196
|
+
}
|
|
197
|
+
function codexCliCatalog() {
|
|
198
|
+
try {
|
|
199
|
+
const output = execCodexCliSync(['debug', 'models'], {
|
|
200
|
+
encoding: 'utf-8',
|
|
201
|
+
timeout: CODEX_CLI_CATALOG_TIMEOUT_MS,
|
|
202
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
203
|
+
});
|
|
204
|
+
const payload = JSON.parse(String(output));
|
|
205
|
+
const models = Array.isArray(payload?.models)
|
|
206
|
+
? payload.models
|
|
207
|
+
.filter((model) => model?.visibility === 'list' && typeof model?.slug === 'string')
|
|
208
|
+
.map((model) => ({ id: model.slug, owned_by: 'openai' }))
|
|
209
|
+
: [];
|
|
210
|
+
return models.length > 0 ? models : null;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
134
216
|
export function modelMatches(agent, candidate, current) {
|
|
135
217
|
if (!current)
|
|
136
218
|
return false;
|
|
@@ -166,24 +248,61 @@ function withAliases(models, ba) {
|
|
|
166
248
|
.map(a => ({ id: a, owned_by: 'alias' }));
|
|
167
249
|
return [...aliasEntries, ...models];
|
|
168
250
|
}
|
|
169
|
-
/**
|
|
251
|
+
/** 拉取模型目录,并按 self + baseagent 缓存短时间结果。 */
|
|
170
252
|
export async function getCatalog(self, ba) {
|
|
171
253
|
const baseagent = ba || activeBaseagent(self);
|
|
172
|
-
const
|
|
254
|
+
const cacheKey = `${self ?? '<defaults>'}:${baseagent}`;
|
|
255
|
+
const cached = catalogCache.get(cacheKey);
|
|
256
|
+
if (cached && cached.expiresAt > Date.now())
|
|
257
|
+
return cached.value;
|
|
258
|
+
const pending = catalogInFlight.get(cacheKey);
|
|
259
|
+
if (pending)
|
|
260
|
+
return pending;
|
|
261
|
+
const promise = loadCatalog(self, baseagent)
|
|
262
|
+
.then(value => {
|
|
263
|
+
catalogCache.set(cacheKey, { value, expiresAt: Date.now() + MODEL_CATALOG_TTL_MS });
|
|
264
|
+
return value;
|
|
265
|
+
})
|
|
266
|
+
.finally(() => catalogInFlight.delete(cacheKey));
|
|
267
|
+
catalogInFlight.set(cacheKey, promise);
|
|
268
|
+
return promise;
|
|
269
|
+
}
|
|
270
|
+
async function loadCatalog(self, baseagent) {
|
|
271
|
+
// Pre-create has no runner yet. Mirror CodexRunner's dynamic CLI fallback
|
|
272
|
+
// without changing existing self-scoped catalog resolution.
|
|
273
|
+
if (!self && baseagent === 'codex') {
|
|
274
|
+
const cliModels = codexCliCatalog();
|
|
275
|
+
if (cliModels)
|
|
276
|
+
return { models: cliModels, source: 'codex-cli' };
|
|
277
|
+
}
|
|
278
|
+
const { baseUrl, apiKey, headers } = resolveCreds(self, baseagent);
|
|
173
279
|
if (baseUrl) {
|
|
174
280
|
const base = baseUrl.replace(/\/+$/, '');
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
281
|
+
const urls = [...new Set([
|
|
282
|
+
base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`,
|
|
283
|
+
`${base}/models`,
|
|
284
|
+
])];
|
|
285
|
+
// Probe compatible routes concurrently so a dead route does not add
|
|
286
|
+
// another full timeout before the other route is attempted.
|
|
287
|
+
const results = await Promise.all(urls.map(url => fetchModelList(url, apiKey, headers)));
|
|
288
|
+
const hitIndex = results.findIndex((models) => !!models);
|
|
289
|
+
if (hitIndex >= 0) {
|
|
290
|
+
return {
|
|
291
|
+
models: withAliases(results[hitIndex], baseagent),
|
|
292
|
+
source: hitIndex === 0 ? 'v1/models' : 'models',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// This remote directory is Claude-only. Never use it for Codex or Gemini.
|
|
297
|
+
if (baseagent === 'claude') {
|
|
298
|
+
const remote = await fetchModelList(REMOTE_CATALOG_URL, undefined, buildModelRequestHeaders({
|
|
299
|
+
baseagent: 'claude',
|
|
300
|
+
baseUrl: REMOTE_CATALOG_URL,
|
|
301
|
+
}));
|
|
302
|
+
if (remote)
|
|
303
|
+
return { models: withAliases(remote, baseagent), source: 'remote' };
|
|
181
304
|
}
|
|
182
|
-
|
|
183
|
-
const remote = await fetchModelList(REMOTE_CATALOG_URL, apiKey);
|
|
184
|
-
if (remote)
|
|
185
|
-
return { models: withAliases(remote, baseagent), source: 'remote' };
|
|
186
|
-
return { models: withAliases(MOCK_CATALOG, baseagent), source: 'mock' };
|
|
305
|
+
return { models: withAliases(builtinCatalog(baseagent), baseagent), source: 'builtin' };
|
|
187
306
|
}
|
|
188
307
|
/** claude 系判定:完整 ID 或别名(opus/sonnet/haiku)。 */
|
|
189
308
|
function isClaudeFamily(id) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { resolveAnthropicConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
|
|
1
|
+
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
|
|
2
|
+
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
2
3
|
import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
|
|
3
4
|
import { constrainResolvedModelForRole } from './model-permission.js';
|
|
4
5
|
import { getCatalog } from './model-catalog.js';
|
|
@@ -17,17 +18,34 @@ async function fetchWithTimeout(url, opts, timeoutMs) {
|
|
|
17
18
|
}
|
|
18
19
|
function resolveGatewayCreds(self, baseagent) {
|
|
19
20
|
try {
|
|
20
|
-
const config = self ? resolveEffective({ self }, { cache: true }) : { baseagents: {} };
|
|
21
|
+
const config = self ? resolveEffective({ self }, { cache: true, expand: true }) : { baseagents: {} };
|
|
21
22
|
const block = config.baseagents || {};
|
|
22
23
|
if (baseagent === 'codex') {
|
|
23
24
|
const codexCfg = block.codex || {};
|
|
24
25
|
const resolved = resolveOpenaiConfig({ agents: { codex: codexCfg } }, codexCfg);
|
|
25
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
baseUrl: resolved.baseUrl,
|
|
28
|
+
apiKey: resolved.apiKey,
|
|
29
|
+
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
30
|
+
};
|
|
26
31
|
}
|
|
27
32
|
if (baseagent === 'claude') {
|
|
28
33
|
const claudeCfg = block.claude || {};
|
|
29
34
|
const resolved = resolveAnthropicConfig({ agents: { claude: claudeCfg } }, claudeCfg);
|
|
30
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
baseUrl: resolved.baseUrl,
|
|
37
|
+
apiKey: resolved.apiKey,
|
|
38
|
+
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (baseagent === 'ecagent') {
|
|
42
|
+
const ecagentCfg = block.ecagent || {};
|
|
43
|
+
const resolved = resolveEcagentConfig({ agents: { ecagent: ecagentCfg } }, ecagentCfg);
|
|
44
|
+
return {
|
|
45
|
+
baseUrl: resolved.baseUrl,
|
|
46
|
+
apiKey: resolved.apiKey,
|
|
47
|
+
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
48
|
+
};
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
51
|
catch {
|
|
@@ -46,7 +64,7 @@ async function responseError(resp) {
|
|
|
46
64
|
}
|
|
47
65
|
return String(message).slice(0, 80);
|
|
48
66
|
}
|
|
49
|
-
export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
|
|
67
|
+
export async function probeModel(baseUrl, apiKey, modelId, timeoutMs, requestHeaders) {
|
|
50
68
|
const base = baseUrl.replace(/\/+$/, '');
|
|
51
69
|
const authHeaders = apiKey
|
|
52
70
|
? { 'x-api-key': apiKey, Authorization: `Bearer ${apiKey}` }
|
|
@@ -56,7 +74,7 @@ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
|
|
|
56
74
|
const anthropicStartedAt = Date.now();
|
|
57
75
|
const anthropic = await fetchWithTimeout(`${base}/v1/messages`, {
|
|
58
76
|
method: 'POST',
|
|
59
|
-
headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders },
|
|
77
|
+
headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders, ...requestHeaders },
|
|
60
78
|
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
61
79
|
}, Math.min(5000, remainingMs()));
|
|
62
80
|
const anthropicMs = Date.now() - anthropicStartedAt;
|
|
@@ -74,7 +92,7 @@ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
|
|
|
74
92
|
const openaiStartedAt = Date.now();
|
|
75
93
|
const openai = await fetchWithTimeout(`${base}/v1/chat/completions`, {
|
|
76
94
|
method: 'POST',
|
|
77
|
-
headers: { 'Content-Type': 'application/json', ...authHeaders },
|
|
95
|
+
headers: { 'Content-Type': 'application/json', ...authHeaders, ...requestHeaders },
|
|
78
96
|
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
79
97
|
}, Math.min(5000, remainingMs()));
|
|
80
98
|
const openaiMs = Date.now() - openaiStartedAt;
|
|
@@ -99,7 +117,7 @@ export async function runModelCheck(input) {
|
|
|
99
117
|
steps.push(step);
|
|
100
118
|
input.onStep?.(step);
|
|
101
119
|
};
|
|
102
|
-
const { baseUrl, apiKey } = resolveGatewayCreds(input.self, input.baseagent);
|
|
120
|
+
const { baseUrl, apiKey, headers } = resolveGatewayCreds(input.self, input.baseagent);
|
|
103
121
|
if (!baseUrl) {
|
|
104
122
|
log({ label: '网关地址', ok: false, detail: '未配置受支持的自定义网关,跳过 API 探测' });
|
|
105
123
|
}
|
|
@@ -107,7 +125,7 @@ export async function runModelCheck(input) {
|
|
|
107
125
|
const startedAt = Date.now();
|
|
108
126
|
const ping = await fetchWithTimeout(`${baseUrl.replace(/\/+$/, '')}/v1/models`, {
|
|
109
127
|
method: 'GET',
|
|
110
|
-
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
128
|
+
headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
|
|
111
129
|
}, 4000);
|
|
112
130
|
const elapsed = Date.now() - startedAt;
|
|
113
131
|
log(ping
|
|
@@ -151,7 +169,7 @@ export async function runModelCheck(input) {
|
|
|
151
169
|
];
|
|
152
170
|
for (let index = 0; index < targets.length; index += 2) {
|
|
153
171
|
const batch = await Promise.all(targets.slice(index, index + 2).map(async (target) => {
|
|
154
|
-
const result = await probeModel(baseUrl, apiKey, target.probe, 10_000);
|
|
172
|
+
const result = await probeModel(baseUrl, apiKey, target.probe, 10_000, headers);
|
|
155
173
|
return { id: target.display, ...result };
|
|
156
174
|
}));
|
|
157
175
|
for (const result of batch) {
|