evolcore 0.0.21 → 0.0.22

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/bin/codex-managed-hook.mjs +3 -0
  3. package/bin/install-codex-managed-hooks.mjs +3 -1
  4. package/dist/agents/claude-runner.js +14 -0
  5. package/dist/agents/codex-app-server-client.js +31 -5
  6. package/dist/agents/codex-runner.js +926 -121
  7. package/dist/aun/outbox.js +7 -0
  8. package/dist/channels/aun.js +209 -35
  9. package/dist/cli/daemon-commands.js +29 -8
  10. package/dist/cli/task-context.js +4 -0
  11. package/dist/cli/trigger-command.js +13 -4
  12. package/dist/config/config-field-policy.js +3 -0
  13. package/dist/config/config-manager.js +32 -5
  14. package/dist/config/contact-book-store.js +25 -3
  15. package/dist/core/auth/agent-delegation.js +12 -0
  16. package/dist/core/auth/auth-gateway.js +8 -0
  17. package/dist/core/auth/authorization-audit.js +66 -6
  18. package/dist/core/bootstrap-messages.js +8 -0
  19. package/dist/core/bootstrap-service.js +93 -25
  20. package/dist/core/command/command-handler.js +21 -0
  21. package/dist/core/command/menu-handler.js +9 -0
  22. package/dist/core/command/menu-protocol.js +1 -1
  23. package/dist/core/command/slash-handler.js +41 -18
  24. package/dist/core/data-migration.js +11 -1
  25. package/dist/core/event-catalog.js +32 -0
  26. package/dist/core/handoff/runtime.js +23 -3
  27. package/dist/core/message/im-renderer.js +7 -3
  28. package/dist/core/message/message-bridge.js +60 -2
  29. package/dist/core/message/message-log.js +33 -0
  30. package/dist/core/message/message-queue.js +21 -0
  31. package/dist/core/message/response-engine.js +172 -41
  32. package/dist/core/permission/ec-command-parser.js +272 -70
  33. package/dist/core/permission/protected-paths.js +11 -10
  34. package/dist/core/permission/tool-error-code.js +12 -0
  35. package/dist/core/permission/tool-policy.js +46 -5
  36. package/dist/core/session/session-manager.js +30 -0
  37. package/dist/core/session/session-renew.js +18 -1
  38. package/dist/core/session/session-turn-coordinator.js +5 -1
  39. package/dist/index.js +64 -5
  40. package/dist/ipc.js +97 -17
  41. package/dist/paths.js +18 -0
  42. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  43. package/dist/stats/price-resolver.js +4 -0
  44. package/dist/trigger/feedback.js +14 -2
  45. package/dist/trigger/parser.js +10 -1
  46. package/dist/trigger/scheduler.js +20 -3
  47. package/dist/utils/logger.js +9 -4
  48. package/dist/utils/tool-summary.js +59 -0
  49. package/dist/utils/windows-shell-trust.js +201 -0
  50. package/kits/docs/evolcore/INDEX.md +2 -2
  51. package/kits/docs/evolcore/agent-create.md +146 -0
  52. package/kits/docs/evolcore/agent.md +6 -0
  53. package/kits/docs/evolcore/group-collaboration.md +251 -0
  54. package/kits/docs/evolcore/group-rules.md +1 -19
  55. package/kits/docs/evolcore/group.md +3 -1
  56. package/kits/docs/evolcore/trigger.md +6 -3
  57. package/kits/docs/prompt-loading-architecture.md +6 -0
  58. package/kits/eck_message_manifest.json +6 -6
  59. package/kits/schemas/_meta.json +3 -2
  60. package/kits/schemas/agent-config.schema.12.json +427 -0
  61. package/kits/templates/message-fragments/item.md +1 -1
  62. package/kits/templates/system-fragments/bootstrap.md +2 -1
  63. package/kits/templates/system-fragments/commands.md +2 -2
  64. package/package.json +2 -2
@@ -37,7 +37,7 @@ import { resolveAgentLifecycle } from '../../config/lifecycle.js';
37
37
  import { authorizationConfigRevision, checkRoleAccess, getFirstStaticAgentOwner, listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, roleToSessionIdentity, } from '../../config/peer-role-resolver.js';
38
38
  import { insertUsageEvent, insertContextBreakdown, insertModelCalls } from '../../stats/writer.js';
39
39
  import { normalizeUsage } from '../../stats/normalizer.js';
40
- import { resolvePrices } from '../../stats/price-resolver.js';
40
+ import { resolvePrices, roundCostForOutput } from '../../stats/price-resolver.js';
41
41
  import { getBudgetStatus } from '../../stats/budget.js';
42
42
  import { formatUsageSubjectKey, getRoleBudgetStatus } from '../../stats/role-budget.js';
43
43
  import { snapshot } from './response-snapshot.js';
@@ -47,7 +47,7 @@ import { registerBuiltinModes } from '../../response-system/modes/index.js';
47
47
  import { deriveSessionTitle, shouldAutoFillSessionTitle } from '../session/session-title.js';
48
48
  import { SessionTurnCoordinator } from '../session/session-turn-coordinator.js';
49
49
  import { recordTriggerExecutionAnomaly } from '../../trigger/anomaly-store.js';
50
- import { classifyToolErrorCode } from '../permission/tool-error-code.js';
50
+ import { classifyToolErrorCode, normalizeToolErrorCode } from '../permission/tool-error-code.js';
51
51
  import { normalizeExecutionPermissionMode } from '../permission/mode.js';
52
52
  import { buildToolLifecycleEventKey } from '../audit/event-key.js';
53
53
  import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
@@ -637,20 +637,44 @@ export class ResponseEngine {
637
637
  // 监听中断事件,标记被中断的 session
638
638
  this.eventBus.subscribe('task:interrupted', (event) => {
639
639
  if ('sessionId' in event && event.sessionId) {
640
+ const eventIdentity = event;
641
+ const eventTaskId = eventIdentity.taskId;
642
+ const activeTask = this.activeTaskSpans.get(event.sessionId);
643
+ // A delayed interruption from an older task must never invalidate a
644
+ // newer task that has already claimed the same session.
645
+ if (eventTaskId && activeTask && activeTask.taskId !== eventTaskId)
646
+ return;
640
647
  const reason = (event.reason || 'new_message');
641
- this.turnCoordinator.invalidate(event.sessionId, reason);
648
+ const currentTurn = this.turnCoordinator.invalidate(event.sessionId, reason, { taskId: eventTaskId, generation: eventIdentity.generation });
649
+ if ((eventTaskId || eventIdentity.generation !== undefined) && !currentTurn)
650
+ return;
642
651
  this.interruptedSessions.set(event.sessionId, reason);
643
652
  this.cancelRetryDelays(event.sessionId);
644
- this.agentDelegationRegistry?.revokeSession(event.sessionId);
653
+ // The event can be delayed relative to a newer task on the same
654
+ // session. Revoke only when the producer supplied the matching task;
655
+ // the interrupt path below performs the authoritative task-scoped
656
+ // revocation for legacy events without a taskId.
657
+ const interruptedTaskId = eventTaskId;
658
+ if (interruptedTaskId && activeTask?.taskId === interruptedTaskId) {
659
+ this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId);
660
+ }
645
661
  }
646
662
  });
647
663
  this.eventBus.subscribe('task:completed', event => {
648
- if ('sessionId' in event && event.sessionId)
649
- this.activeTaskSpans.delete(event.sessionId);
664
+ if ('sessionId' in event && event.sessionId) {
665
+ const taskId = event.taskId;
666
+ const active = this.activeTaskSpans.get(event.sessionId);
667
+ if (!taskId || active?.taskId === taskId)
668
+ this.activeTaskSpans.delete(event.sessionId);
669
+ }
650
670
  });
651
671
  this.eventBus.subscribe('task:error', event => {
652
- if ('sessionId' in event && event.sessionId)
653
- this.activeTaskSpans.delete(event.sessionId);
672
+ if ('sessionId' in event && event.sessionId) {
673
+ const taskId = event.taskId;
674
+ const active = this.activeTaskSpans.get(event.sessionId);
675
+ if (!taskId || active?.taskId === taskId)
676
+ this.activeTaskSpans.delete(event.sessionId);
677
+ }
654
678
  });
655
679
  // 初始化响应模式协调器,注册内置模式(interactive/proactive)
656
680
  const registry = new ResponseModeRegistry();
@@ -1274,7 +1298,7 @@ export class ResponseEngine {
1274
1298
  causation: opts.causation ?? message.causation,
1275
1299
  });
1276
1300
  }
1277
- publishTriggerExecutionSkipped(message, reason, causation) {
1301
+ publishTriggerExecutionSkipped(message, reason, causation, interruption) {
1278
1302
  const terminal = this.claimTriggerTerminal(message);
1279
1303
  if (!terminal)
1280
1304
  return;
@@ -1287,6 +1311,7 @@ export class ResponseEngine {
1287
1311
  attemptId: trigger.attemptId,
1288
1312
  originTriggerId: trigger.triggerId,
1289
1313
  reason,
1314
+ ...(interruption ?? {}),
1290
1315
  targetChannel: message.channel,
1291
1316
  targetChannelId: message.channelId,
1292
1317
  fireTime: trigger.fireTime,
@@ -1294,16 +1319,25 @@ export class ResponseEngine {
1294
1319
  });
1295
1320
  }
1296
1321
  /** Close the internal daemon conversation before returning from an interrupted Trigger turn. */
1297
- async publishTriggerExecutionInterrupted(message, adapter, envelope, reason, causation) {
1322
+ async publishTriggerExecutionInterrupted(message, adapter, envelope, reason, causation, generation) {
1323
+ const interruption = reason === 'stale_generation'
1324
+ ? {
1325
+ reasonCode: 'stale_generation',
1326
+ decisionSource: 'infrastructure',
1327
+ executionState: 'interrupted',
1328
+ ...(generation?.expected !== undefined ? { generation: generation.expected } : {}),
1329
+ ...(generation?.current !== undefined ? { currentGeneration: generation.current } : {}),
1330
+ }
1331
+ : undefined;
1298
1332
  if (this.isTrustedDaemonTrigger(message)) {
1299
1333
  await adapter.send(envelope, {
1300
1334
  kind: 'status.interrupted',
1301
- metadata: { reason },
1335
+ metadata: { reason, ...(interruption ?? {}) },
1302
1336
  }).catch(error => {
1303
1337
  logger.warn(`[ResponseEngine] Failed to close interrupted Trigger run=${message.triggerMeta?.runId ?? '<unknown>'}: ${error instanceof Error ? error.message : String(error)}`);
1304
1338
  });
1305
1339
  }
1306
- this.publishTriggerExecutionSkipped(message, reason, causation);
1340
+ this.publishTriggerExecutionSkipped(message, reason, causation, interruption);
1307
1341
  }
1308
1342
  publishTriggerExecutionCompleted(message, messageId, durationMs, causation) {
1309
1343
  const terminal = this.claimTriggerTerminal(message);
@@ -2130,6 +2164,8 @@ export class ResponseEngine {
2130
2164
  this.eventBus.publish({
2131
2165
  type: 'task:error',
2132
2166
  sessionId: session.id,
2167
+ taskId,
2168
+ generation: turnLease?.generation,
2133
2169
  error: timeoutError.message,
2134
2170
  errorType,
2135
2171
  agentName: agentNameForStats,
@@ -2255,7 +2291,10 @@ export class ResponseEngine {
2255
2291
  const contentPreview = formatInboundMessageLogText(message.content);
2256
2292
  logger.info(`[${message.channel}] ${message.channelId}: ${contentPreview}${imageInfo}${modeInfo}${e2eeInfo}`);
2257
2293
  // 构建 peer 标识(优先 peerName,退化到 peerId / channelId)
2258
- const peerName = session.metadata?.peerName ?? message.peerName;
2294
+ // Group sessions are shared by multiple senders. The current inbound
2295
+ // message is authoritative; session metadata is only a fallback for
2296
+ // legacy paths that do not carry a per-message display name.
2297
+ const peerName = message.peerName ?? session.metadata?.peerName;
2259
2298
  const peerId = session.metadata?.peerId ?? message.peerId ?? message.channelId;
2260
2299
  const peerShort = peerId ? peerId.split('.')[0].split(':')[0] : '?';
2261
2300
  const peerLabel = peerName && peerName !== peerShort ? `${peerShort}(${peerName})` : peerShort;
@@ -2266,7 +2305,16 @@ export class ResponseEngine {
2266
2305
  turnLease = await this.turnCoordinator.begin(session, taskId);
2267
2306
  // 记录开始处理
2268
2307
  const taskEncrypt = message.replyContext?.metadata?.encrypted != null ? !!(message.replyContext.metadata.encrypted) : undefined;
2269
- this.eventBus.publish({ type: 'task:started', sessionId: session.id, agentName: agentNameForStats, encrypt: taskEncrypt, chatmode, causation: taskCausation });
2308
+ this.eventBus.publish({
2309
+ type: 'task:started',
2310
+ sessionId: session.id,
2311
+ taskId,
2312
+ generation: turnLease.generation,
2313
+ agentName: agentNameForStats,
2314
+ encrypt: taskEncrypt,
2315
+ chatmode,
2316
+ causation: taskCausation,
2317
+ });
2270
2318
  this.touchAgentActivity(channelKey);
2271
2319
  // Upgrade the channel acknowledgement at task start, before compaction and
2272
2320
  // runner setup, so processing feedback remains visible for the whole run.
@@ -2292,6 +2340,7 @@ export class ResponseEngine {
2292
2340
  adapter,
2293
2341
  envelope,
2294
2342
  agentAid: session.selfAID,
2343
+ permissionMode: () => effectivePermissionMode,
2295
2344
  flushDelay: (options?.flushDelay ?? this.agentRegistry?.resolveByChannel(channelKey)?.config?.flush_delay ?? 3) * 1000,
2296
2345
  suppressActivityItems: isProactive ? false : middleOutputMode !== 'all',
2297
2346
  suppressIntermediateText: isProactive ? false : middleOutputMode === 'none',
@@ -3108,7 +3157,7 @@ export class ResponseEngine {
3108
3157
  }];
3109
3158
  const peerItems = (() => {
3110
3159
  if (message.handoffDelivery && this.handoffRuntime) {
3111
- const items = this.handoffRuntime.buildPromptItems(message);
3160
+ const items = this.handoffRuntime.buildPromptItems(message, peerRole);
3112
3161
  if (items.length > 0) {
3113
3162
  v2HandoffIds = Array.from(new Set(items.flatMap(item => (item.handoff?.handoffIds?.length
3114
3163
  ? item.handoff.handoffIds
@@ -3205,12 +3254,14 @@ export class ResponseEngine {
3205
3254
  peerName: peerName || undefined,
3206
3255
  peerType: message.peerType || session.metadata?.peerType || undefined,
3207
3256
  peerRole,
3257
+ permissionMode: effectivePermissionMode,
3208
3258
  ...(fullAccessAuthorization ? {
3209
3259
  processRole: fullAccessAuthorization.processRole,
3210
3260
  dataScope: fullAccessAuthorization.dataScope,
3211
3261
  authorizedBy: fullAccessAuthorization.authorizedBy,
3212
3262
  executionSource: fullAccessAuthorization.source,
3213
3263
  } : {}),
3264
+ daemonRuntimeEpoch: this.agentDelegationRegistry?.getRuntimeEpoch(),
3214
3265
  threadId: session.threadId || undefined,
3215
3266
  sessionRuntimeDir,
3216
3267
  runtimeLockDir: ensureRuntimeLockDir(sessionRuntimeDir),
@@ -3547,7 +3598,10 @@ export class ResponseEngine {
3547
3598
  }
3548
3599
  this.agentDelegationRegistry?.revokeTask(session.id, taskId);
3549
3600
  logger.info(`[ResponseEngine] Stale turn stopped before publish: session=${session.id} task=${taskId}`);
3550
- await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation);
3601
+ await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
3602
+ expected: turnLease?.generation,
3603
+ current: this.turnCoordinator.current(session).generation,
3604
+ });
3551
3605
  return;
3552
3606
  }
3553
3607
  if (!commitDecision.ok) {
@@ -3559,7 +3613,10 @@ export class ResponseEngine {
3559
3613
  }
3560
3614
  this.agentDelegationRegistry?.revokeTask(session.id, taskId);
3561
3615
  logger.info(`[ResponseEngine] Stale turn stopped before protocol rejection: session=${session.id} task=${taskId}`);
3562
- await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation);
3616
+ await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
3617
+ expected: turnLease?.generation,
3618
+ current: this.turnCoordinator.current(session).generation,
3619
+ });
3563
3620
  return;
3564
3621
  }
3565
3622
  const reason = streamResult.protocolIncompleteReason || commitDecision.reason;
@@ -3761,7 +3818,10 @@ export class ResponseEngine {
3761
3818
  if (!commitDecision.ok) {
3762
3819
  logger.info(`[ResponseEngine] Turn commit rejected after flush: session=${session.id} task=${taskId} reason=${commitDecision.reason}`);
3763
3820
  if (commitDecision.reason === 'stale_generation') {
3764
- await this.publishTriggerExecutionInterrupted(message, adapter, envelope, commitDecision.reason, taskCausation);
3821
+ await this.publishTriggerExecutionInterrupted(message, adapter, envelope, commitDecision.reason, taskCausation, {
3822
+ expected: turnLease?.generation,
3823
+ current: this.turnCoordinator.current(session).generation,
3824
+ });
3765
3825
  }
3766
3826
  else {
3767
3827
  this.publishTriggerExecutionFailure(message, `turn_commit_rejected:${commitDecision.reason}`, {
@@ -3848,6 +3908,8 @@ export class ResponseEngine {
3848
3908
  this.eventBus.publish({
3849
3909
  type: 'task:error',
3850
3910
  sessionId: session.id,
3911
+ taskId,
3912
+ generation: turnLease?.generation,
3851
3913
  error: errorSummary,
3852
3914
  errorType,
3853
3915
  agentName: agentNameForStats,
@@ -3965,12 +4027,18 @@ export class ResponseEngine {
3965
4027
  cache_read_tokens: sum.cache_read_tokens,
3966
4028
  cache_creation_tokens: sum.cache_creation_tokens,
3967
4029
  // 顶层 cost_usd/cost_cny 保持向后兼容 = 网关实际价
3968
- cost_usd: sum.cost_gateway_usd,
3969
- cost_cny: sum.cost_gateway_cny,
4030
+ cost_usd: roundCostForOutput(sum.cost_gateway_usd),
4031
+ cost_cny: roundCostForOutput(sum.cost_gateway_cny),
3970
4032
  call_count: sum.calls,
3971
4033
  cost: {
3972
- official: { usd: sum.cost_official_usd, cny: sum.cost_official_cny },
3973
- gateway: { usd: sum.cost_gateway_usd, cny: sum.cost_gateway_cny },
4034
+ official: {
4035
+ usd: roundCostForOutput(sum.cost_official_usd),
4036
+ cny: roundCostForOutput(sum.cost_official_cny),
4037
+ },
4038
+ gateway: {
4039
+ usd: roundCostForOutput(sum.cost_gateway_usd),
4040
+ cny: roundCostForOutput(sum.cost_gateway_cny),
4041
+ },
3974
4042
  },
3975
4043
  };
3976
4044
  }
@@ -3988,10 +4056,13 @@ export class ResponseEngine {
3988
4056
  }
3989
4057
  else {
3990
4058
  // cost 同时给原价(official)与网关实际价(gateway);顶层 cost_usd/cost_cny 保持向后兼容 = 网关价。
3991
- const gatewayUsd = turnCost.gateway?.usd ?? turnCost.official?.usd ?? 0;
3992
- const gatewayCny = turnCost.gateway?.cny ?? turnCost.official?.cny ?? 0;
4059
+ const gatewayUsd = roundCostForOutput(turnCost.gateway?.usd ?? turnCost.official?.usd ?? 0);
4060
+ const gatewayCny = roundCostForOutput(turnCost.gateway?.cny ?? turnCost.official?.cny ?? 0);
3993
4061
  const turnCostBlock = {
3994
- official: { usd: turnCost.official?.usd ?? 0, cny: turnCost.official?.cny ?? 0 },
4062
+ official: {
4063
+ usd: roundCostForOutput(turnCost.official?.usd ?? 0),
4064
+ cny: roundCostForOutput(turnCost.official?.cny ?? 0),
4065
+ },
3995
4066
  gateway: { usd: gatewayUsd, cny: gatewayCny },
3996
4067
  };
3997
4068
  // 最后一次访问:本轮可能有多次大模型调用(numTurns>1),整轮的 turnCostBlock 不等于
@@ -4006,10 +4077,13 @@ export class ResponseEngine {
4006
4077
  model: lastModel, turns: 1,
4007
4078
  });
4008
4079
  const lp = resolvePrices(resolveRoot(), lastEvent, agent.getGatewayPricing?.());
4009
- const lpGwUsd = lp.gateway?.usd ?? lp.official?.usd ?? 0;
4010
- const lpGwCny = lp.gateway?.cny ?? lp.official?.cny ?? 0;
4080
+ const lpGwUsd = roundCostForOutput(lp.gateway?.usd ?? lp.official?.usd ?? 0);
4081
+ const lpGwCny = roundCostForOutput(lp.gateway?.cny ?? lp.official?.cny ?? 0);
4011
4082
  lastModelCall = { ...lastModelCall, cost: {
4012
- official: { usd: lp.official?.usd ?? 0, cny: lp.official?.cny ?? 0 },
4083
+ official: {
4084
+ usd: roundCostForOutput(lp.official?.usd ?? 0),
4085
+ cny: roundCostForOutput(lp.official?.cny ?? 0),
4086
+ },
4013
4087
  gateway: { usd: lpGwUsd, cny: lpGwCny },
4014
4088
  } };
4015
4089
  }
@@ -4057,6 +4131,8 @@ export class ResponseEngine {
4057
4131
  this.eventBus.publish({
4058
4132
  type: 'task:completed',
4059
4133
  sessionId: session.id,
4134
+ taskId,
4135
+ generation: turnLease.generation,
4060
4136
  channel: message.channel,
4061
4137
  channelId: message.channelId,
4062
4138
  terminalReason: streamResult.terminalReason,
@@ -4188,6 +4264,8 @@ export class ResponseEngine {
4188
4264
  this.eventBus.publish({
4189
4265
  type: 'task:error',
4190
4266
  sessionId: session.id,
4267
+ taskId,
4268
+ generation: turnLease?.generation,
4191
4269
  error: errorMsg,
4192
4270
  errorType,
4193
4271
  agentName: agentNameForStats,
@@ -4208,14 +4286,15 @@ export class ResponseEngine {
4208
4286
  }
4209
4287
  // 发送用户友好的错误消息
4210
4288
  // 用户主动中断(新消息打断 或 /stop 命令)时静默,不发送错误提示
4211
- // processEventStream 已通过 renderer 发过错误时也跳过
4289
+ // 普通渠道可跳过 renderer 已发送的重复错误;daemon trigger 仍必须
4290
+ // 发送结构化终态,否则 DaemonChannel 会一直等到 watchdog。
4212
4291
  const retryExhaustedCount = getRetryExhaustedCount(error);
4213
4292
  const modelFallbackExhaustedMessage = getModelFallbackExhaustedMessage(error);
4214
4293
  const retryInputAlreadySubmitted = wasRetryInputAlreadySubmitted(error);
4215
4294
  if (isUserInterrupt) {
4216
4295
  logger.info(`[ResponseEngine] User interrupt by new_message, skip sending error message`);
4217
4296
  }
4218
- else if (error?._errorAlreadySent && !retryExhaustedCount && !isTimeout && !isTotalExecutionTimeout) {
4297
+ else if (error?._errorAlreadySent && !daemonTrigger && !retryExhaustedCount && !isTimeout && !isTotalExecutionTimeout) {
4219
4298
  logger.info(`[ResponseEngine] Error already sent via renderer, skip sending duplicate message`);
4220
4299
  }
4221
4300
  else {
@@ -4633,11 +4712,17 @@ export class ResponseEngine {
4633
4712
  if (event.type === 'complete') {
4634
4713
  event = normalizeCompleteAgentEvent(event);
4635
4714
  }
4636
- if (event.type === 'tool_result' && event.isError && !event.errorCode) {
4637
- event = {
4638
- ...event,
4639
- errorCode: classifyToolErrorCode({ error: event.error, result: event.result }),
4640
- };
4715
+ if (event.type === 'tool_result' && event.isError) {
4716
+ const classificationMissing = !normalizeToolErrorCode(event.errorCode)
4717
+ && event.decisionSource !== 'policy'
4718
+ && event.decisionSource !== 'approval';
4719
+ const errorCode = classifyToolErrorCode({
4720
+ errorCode: event.errorCode,
4721
+ decisionSource: event.decisionSource,
4722
+ });
4723
+ if (event.errorCode !== errorCode) {
4724
+ event = { ...event, errorCode, ...(classificationMissing ? { classificationMissing: true } : {}) };
4725
+ }
4641
4726
  }
4642
4727
  // 每收到事件重置空闲超时
4643
4728
  const toolName = event.type === 'tool_use' ? event.name : undefined;
@@ -4740,7 +4825,13 @@ export class ResponseEngine {
4740
4825
  eventDetail = ` tool=${event.name}${desc ? ` desc="${desc}"` : ''}`;
4741
4826
  }
4742
4827
  else if (event.type === 'tool_result') {
4743
- eventDetail = ` tool=${event.name} ok=${!event.isError}`;
4828
+ const decision = event.decision ?? (event.isError ? 'error' : 'allow');
4829
+ const executed = event.executed ?? !event.isError;
4830
+ const executionState = event.executionState
4831
+ ?? (decision === 'deny' ? 'blocked' : event.isError ? 'failed' : 'completed');
4832
+ eventDetail = ` tool=${event.name} ok=${decision === 'allow' && executed}`
4833
+ + ` decision=${decision} executed=${executed} executionState=${executionState}`
4834
+ + (event.policyCode ? ` policy=${event.policyCode}` : '');
4744
4835
  }
4745
4836
  const frameworkEvents = new Set(['session_id', 'state_changed', 'status']);
4746
4837
  if (frameworkEvents.has(event.type)) {
@@ -5006,6 +5097,16 @@ export class ResponseEngine {
5006
5097
  toolName: event.name,
5007
5098
  toolInput: event.input || {},
5008
5099
  injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
5100
+ recordReminder: reminder => {
5101
+ this.eventBus.publish({
5102
+ type: 'runner:proactive-reminder',
5103
+ sessionId: session.id,
5104
+ ...reminder,
5105
+ injection: 'requested',
5106
+ timestamp: Date.now(),
5107
+ causation,
5108
+ });
5109
+ },
5009
5110
  getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
5010
5111
  isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
5011
5112
  logger,
@@ -5031,6 +5132,7 @@ export class ResponseEngine {
5031
5132
  sessionId: session.id,
5032
5133
  callId: event.callId,
5033
5134
  correlationId: event.correlationId,
5135
+ requestId: event.requestId,
5034
5136
  });
5035
5137
  this.eventBus.publish({
5036
5138
  type: 'tool:result',
@@ -5039,16 +5141,29 @@ export class ResponseEngine {
5039
5141
  sessionId: session.id,
5040
5142
  toolName: event.name,
5041
5143
  isError: event.isError,
5042
- ...(event.isError ? { errorCode: classifyToolErrorCode({ errorCode: event.errorCode, error: event.error, result: event.result }) } : {}),
5144
+ ...(event.isError ? {
5145
+ errorCode: classifyToolErrorCode({
5146
+ errorCode: event.errorCode,
5147
+ decisionSource: event.decisionSource,
5148
+ }),
5149
+ ...((event.classificationMissing || (!normalizeToolErrorCode(event.errorCode)
5150
+ && event.decisionSource !== 'policy'
5151
+ && event.decisionSource !== 'approval')) ? { classificationMissing: true } : {}),
5152
+ } : {}),
5043
5153
  ...(event.callId ? { callId: event.callId } : {}),
5044
5154
  ...(event.correlationId || event.callId ? { correlationId: event.correlationId ?? event.callId } : {}),
5155
+ ...(event.requestId ? { requestId: event.requestId } : {}),
5045
5156
  agentName: lifecycleAgentName,
5046
5157
  agentAid: lifecycleAgentAid ?? 'unknown',
5047
5158
  permissionMode: lifecyclePermissionMode,
5048
- decision: event.isError ? 'error' : 'allow',
5049
- decisionSource: 'runner',
5050
- executed: true,
5051
- executionState: event.isError ? 'failed' : 'completed',
5159
+ decision: event.decision ?? (event.isError ? 'error' : 'allow'),
5160
+ decisionSource: event.decisionSource ?? 'runner',
5161
+ ...(event.policyCode ? { policyCode: event.policyCode } : {}),
5162
+ ...(event.reason ? { reason: event.reason } : {}),
5163
+ executed: event.executed ?? (event.decision === 'deny' ? false : true),
5164
+ executionState: event.executionState ?? (event.decision === 'deny'
5165
+ ? 'blocked'
5166
+ : event.isError ? 'failed' : 'completed'),
5052
5167
  timestamp: Date.now(),
5053
5168
  causation,
5054
5169
  });
@@ -5095,7 +5210,23 @@ export class ResponseEngine {
5095
5210
  result: event.result,
5096
5211
  isError: event.isError,
5097
5212
  error: event.error,
5213
+ decision: event.decision,
5214
+ decisionSource: event.decisionSource,
5215
+ executed: event.executed,
5216
+ executionState: event.executionState,
5217
+ policyCode: event.policyCode,
5218
+ requestId: event.requestId,
5098
5219
  injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
5220
+ recordReminder: reminder => {
5221
+ this.eventBus.publish({
5222
+ type: 'runner:proactive-reminder',
5223
+ sessionId: session.id,
5224
+ ...reminder,
5225
+ injection: 'requested',
5226
+ timestamp: Date.now(),
5227
+ causation,
5228
+ });
5229
+ },
5099
5230
  getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
5100
5231
  isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
5101
5232
  logger,