evolcore 0.0.23 → 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.
Files changed (84) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +3 -1
  3. package/bin/codex-managed-acl.ps1 +7 -2
  4. package/bin/codex-managed-hook.mjs +127 -10
  5. package/bin/ec-safe-output.js +85 -3
  6. package/bin/install-codex-managed-hooks.mjs +17 -6
  7. package/dist/agents/baseagent-availability.js +4 -4
  8. package/dist/agents/baseagent.js +29 -1
  9. package/dist/agents/claude-runner.js +148 -13
  10. package/dist/agents/codex/app-server-client.js +18 -8
  11. package/dist/agents/codex/catalog.js +1 -1
  12. package/dist/agents/codex/diagnostics.js +215 -0
  13. package/dist/agents/codex-runner.js +438 -59
  14. package/dist/agents/ecagent-runner.js +17 -3
  15. package/dist/agents/gemini-runner.js +60 -2
  16. package/dist/agents/runner-types.js +1 -1
  17. package/dist/aun/aid/managed-operation.js +28 -6
  18. package/dist/aun/aid/store.js +41 -0
  19. package/dist/aun/msg/managed-operation.js +6 -1
  20. package/dist/aun/outbox.js +56 -18
  21. package/dist/aun/rpc/client.js +4 -5
  22. package/dist/channels/aun.js +147 -76
  23. package/dist/channels/daemon.js +7 -1
  24. package/dist/channels/dingtalk.js +1 -0
  25. package/dist/channels/feishu.js +10 -4
  26. package/dist/channels/wecom.js +2 -1
  27. package/dist/cli/agent-command.js +29 -3
  28. package/dist/cli/agent-create.js +538 -0
  29. package/dist/cli/agent.js +26 -675
  30. package/dist/cli/aun-commands.js +60 -20
  31. package/dist/cli/ctl-command.js +2 -0
  32. package/dist/cli/daemon-commands.js +148 -17
  33. package/dist/cli/index.js +2 -2
  34. package/dist/cli/managed-command-guard.js +3 -0
  35. package/dist/cli/restart-monitor.js +432 -8
  36. package/dist/cli/stats.js +110 -0
  37. package/dist/config/builtin-roles.js +2 -0
  38. package/dist/core/auth/auth-gateway.js +3 -2
  39. package/dist/core/auth/authorization-audit.js +72 -19
  40. package/dist/core/auth/operation-authorizer.js +63 -3
  41. package/dist/core/auth/operation-catalog.js +10 -2
  42. package/dist/core/auth/remote-access-store.js +3 -2
  43. package/dist/core/baseagent-loader.js +4 -4
  44. package/dist/core/bootstrap-messages.js +39 -18
  45. package/dist/core/bootstrap-service.js +32 -26
  46. package/dist/core/capability/capability-manager.js +8 -4
  47. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  48. package/dist/core/command/agent-control.js +7 -2
  49. package/dist/core/command/command-handler.js +87 -25
  50. package/dist/core/command/menu-catalog.js +3 -1
  51. package/dist/core/command/menu-handler.js +274 -70
  52. package/dist/core/command/menu-protocol.js +6 -2
  53. package/dist/core/command/slash-gate.js +1 -3
  54. package/dist/core/command/slash-handler.js +157 -48
  55. package/dist/core/data-migration.js +5 -15
  56. package/dist/core/event-catalog.js +3 -0
  57. package/dist/core/handoff/runtime.js +3 -1
  58. package/dist/core/handoff/store.js +7 -1
  59. package/dist/core/message/im-renderer.js +13 -6
  60. package/dist/core/message/message-bridge.js +5 -36
  61. package/dist/core/message/message-utils.js +6 -3
  62. package/dist/core/message/response-engine.js +73 -21
  63. package/dist/core/model/catalog-service.js +3 -1
  64. package/dist/core/model/model-catalog.js +10 -0
  65. package/dist/core/model/model-permission.js +4 -1
  66. package/dist/core/permission/approval-gateway.js +169 -33
  67. package/dist/core/permission/tool-policy.js +13 -6
  68. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  69. package/dist/core/session/session-manager.js +49 -13
  70. package/dist/core/session/session-renew.js +1 -0
  71. package/dist/index.js +174 -28
  72. package/dist/ipc.js +68 -21
  73. package/dist/paths.js +23 -1
  74. package/dist/stats/billing.js +18 -4
  75. package/dist/utils/atomic-write.js +26 -2
  76. package/dist/utils/cross-platform.js +436 -1
  77. package/dist/utils/error-utils.js +11 -4
  78. package/dist/utils/model-prices.jsonl +23 -20
  79. package/dist/utils/npm-ops.js +24 -1
  80. package/dist/utils/windows-autostart.js +1 -1
  81. package/kits/templates/roles/admin.json +2 -0
  82. package/package.json +7 -3
  83. package/AGENTS.md +0 -62
  84. package/dist/utils/codex-cli.js +0 -43
@@ -517,6 +517,7 @@ export class ResponseEngine {
517
517
  /** 已主动发送开始提示时,忽略 runner 对同一次 compact 的回调。 */
518
518
  announcedCompactStarts = new Set();
519
519
  agentMap;
520
+ legacySingleRunnerMode;
520
521
  primaryRunnerKey;
521
522
  interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
522
523
  timeoutErrors = new Map();
@@ -552,27 +553,42 @@ export class ResponseEngine {
552
553
  * session/config mismatch and must not silently route to a different backend.
553
554
  */
554
555
  getAgent(channel, baseagent, selfAID) {
555
- if (selfAID && baseagent) {
556
- const key = `${selfAID}::${baseagent}`;
556
+ const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
557
+ // Keep the explicit single-backend map shape usable by embedders and
558
+ // focused tests. This is an exact key lookup, not a cross-agent fallback.
559
+ if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
560
+ return this.agentMap.get(requestedBaseagent);
561
+ }
562
+ // A caller that passed one runner explicitly has already selected the
563
+ // backend; this compatibility path does not exist for multi-agent maps.
564
+ if (this.legacySingleRunnerMode && this.agentMap.size === 1
565
+ && this.agentMap.values().next().value?.name === requestedBaseagent) {
566
+ return this.agentMap.values().next().value;
567
+ }
568
+ if (selfAID && requestedBaseagent) {
569
+ const key = `${selfAID}::${requestedBaseagent}`;
557
570
  if (this.agentMap.has(key))
558
571
  return this.agentMap.get(key);
559
572
  }
560
- if (channel && baseagent) {
573
+ if (channel && requestedBaseagent) {
561
574
  const owner = this.agentRegistry?.resolveByChannel(channel);
562
575
  const evolName = owner?.name || '<unknown>';
563
- const key = `${evolName}::${baseagent}`;
576
+ const key = `${evolName}::${requestedBaseagent}`;
564
577
  if (this.agentMap.has(key))
565
578
  return this.agentMap.get(key);
566
- const singleRunnerKey = `<unknown>::${baseagent}`;
567
- if (this.agentMap.has(singleRunnerKey))
568
- return this.agentMap.get(singleRunnerKey);
569
579
  if (owner) {
570
- throw new BaseagentRunnerUnavailableError(evolName, baseagent, this.getAvailableBaseagentsForOwner(evolName));
580
+ throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
571
581
  }
582
+ // Control/test channels may have no registry owner. Resolve only when
583
+ // exactly one loaded runner advertises this baseagent; never choose the
584
+ // primary runner or the first map entry as a backend substitute.
585
+ const matches = [...this.agentMap.entries()]
586
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
587
+ if (matches.length === 1)
588
+ return matches[0][1];
589
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, [...new Set(matches.map(([runnerKey]) => runnerKey.slice(runnerKey.lastIndexOf('::') + 2)))]);
572
590
  }
573
- if (this.agentMap.has(this.primaryRunnerKey))
574
- return this.agentMap.get(this.primaryRunnerKey);
575
- return this.agentMap.values().next().value;
591
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
576
592
  }
577
593
  getTextInferenceProvider(channel, baseagent, selfAID) {
578
594
  if (!baseagent)
@@ -648,11 +664,13 @@ export class ResponseEngine {
648
664
  this.turnCoordinator = new SessionTurnCoordinator(sessionManager);
649
665
  if (agentRunnerOrMap instanceof Map) {
650
666
  this.agentMap = agentRunnerOrMap;
667
+ this.legacySingleRunnerMode = false;
651
668
  this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
652
669
  }
653
670
  else {
654
671
  // 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
655
672
  this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
673
+ this.legacySingleRunnerMode = true;
656
674
  this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
657
675
  }
658
676
  // 监听中断事件,标记被中断的 session
@@ -907,14 +925,10 @@ export class ResponseEngine {
907
925
  if (owning)
908
926
  owning.lastActivity = Date.now();
909
927
  }
910
- inferPrimaryBaseagent() {
911
- const idx = this.primaryRunnerKey.lastIndexOf('::');
912
- return idx >= 0 ? this.primaryRunnerKey.slice(idx + 2) : this.primaryRunnerKey || undefined;
913
- }
914
928
  ensureSessionBaseagent(session, fallback) {
915
- const legacyAgentId = typeof session.agentId === 'string' ? session.agentId : undefined;
916
- if (!session.baseagent) {
917
- session.baseagent = fallback || legacyAgentId || this.inferPrimaryBaseagent();
929
+ void fallback;
930
+ if (typeof session.baseagent !== 'string' || !session.baseagent.trim()) {
931
+ throw new Error(`[ResponseEngine] session ${session.id} has no baseagent binding; refusing implicit fallback`);
918
932
  }
919
933
  }
920
934
  inferDirectSessionIdentity(message, owningAgent) {
@@ -1647,6 +1661,19 @@ export class ResponseEngine {
1647
1661
  totalToolCalls: result.state.totalToolCalls,
1648
1662
  lastToolName: result.state.lastToolName,
1649
1663
  });
1664
+ const idleNotice = timeoutControl.idleNotice;
1665
+ if (idleNotice) {
1666
+ void Promise.resolve(idleNotice({
1667
+ action: result.action,
1668
+ idleSec: result.idleSec,
1669
+ message: result.message,
1670
+ totalEvents: result.state.totalEvents,
1671
+ totalToolCalls: result.state.totalToolCalls,
1672
+ lastToolName: result.state.lastToolName,
1673
+ })).catch(error => {
1674
+ logger.debug(`[ResponseEngine] Idle notice send failed: ${error instanceof Error ? error.message : String(error)}`);
1675
+ });
1676
+ }
1650
1677
  }
1651
1678
  result = monitor.check();
1652
1679
  }
@@ -2401,8 +2428,10 @@ export class ResponseEngine {
2401
2428
  opts.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
2402
2429
  }
2403
2430
  opts.metadata = withTaskMetadata(opts.metadata);
2404
- if (payload.kind.startsWith('status.'))
2431
+ // status.notice is a diagnostic observation, not execution activity.
2432
+ if (payload.kind.startsWith('status.') && payload.kind !== 'status.notice') {
2405
2433
  this.touchAgentActivity(channelKey);
2434
+ }
2406
2435
  const enrichedEnvelope = withEnvelopeReplyContext(envelope, opts);
2407
2436
  snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'sent' });
2408
2437
  return await adapter.send(enrichedEnvelope, payload);
@@ -2413,6 +2442,19 @@ export class ResponseEngine {
2413
2442
  renderer,
2414
2443
  suppressActivities: shouldSuppress(),
2415
2444
  });
2445
+ // Idle notify/warn are operational diagnostic notices. Keep them on the
2446
+ // current task's renderer so envelope, stale-turn, background, and
2447
+ // channel-specific suppression rules remain identical to other sends.
2448
+ if (timeoutControl) {
2449
+ timeoutControl.idleNotice = async ({ action, idleSec, message: idleMessage, totalEvents, totalToolCalls, lastToolName }) => {
2450
+ await renderer.sendStatusNotice(idleMessage, action === 'warn' ? 'warn' : 'info', `idle-${action}`, {
2451
+ idleSec,
2452
+ totalEvents,
2453
+ totalToolCalls,
2454
+ lastToolName,
2455
+ });
2456
+ };
2457
+ }
2416
2458
  renderer.addLifecycle('started');
2417
2459
  if (isProactive) {
2418
2460
  logger.info(`[ResponseEngine] proactive mode: outputs via thought.put task=${taskId}`);
@@ -3094,6 +3136,7 @@ export class ResponseEngine {
3094
3136
  sessionId: session.id,
3095
3137
  sessionName: session.name || undefined,
3096
3138
  sessionCreatedAt: session.createdAt ? new Date(session.createdAt).toISOString() : undefined,
3139
+ managedTempDir: sessionRuntimeDir,
3097
3140
  // 时区(把 ISO 时间戳转本地时间用)+ OS 环境
3098
3141
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
3099
3142
  tzOffset: currentTzOffset(),
@@ -3142,7 +3185,10 @@ export class ResponseEngine {
3142
3185
  try {
3143
3186
  const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
3144
3187
  const cbModel = effectiveModel || agentModel || 'unknown';
3145
- const cbMaxTokens = 200000; // 保守默认,后续可从 model-catalog 取
3188
+ // 从统一模型参数表获取模型的真实 context_window
3189
+ const { resolveModelSpec } = await import('../../stats/billing.js');
3190
+ const modelSpec = resolveModelSpec(resolveRoot(), cbModel);
3191
+ const cbMaxTokens = modelSpec.context_window;
3146
3192
  const systemPromptTokens = estTokens(options?.systemPromptAppend);
3147
3193
  const kitTokens = estTokens(kitContext);
3148
3194
  const totalEst = estTokens(effectiveSystemPrompt);
@@ -3315,6 +3361,8 @@ export class ResponseEngine {
3315
3361
  selfAid,
3316
3362
  peerKey,
3317
3363
  issuedRole: peerRole,
3364
+ permissionMode: effectivePermissionMode,
3365
+ ...(sessionRuntimeDir ? { sessionRuntimeDir } : {}),
3318
3366
  ...(fullAccessAuthorization ? { executionIdentity: fullAccessAuthorization } : {}),
3319
3367
  });
3320
3368
  runtimeEnv[AGENT_DELEGATION_TOKEN_ENV] = delegationToken;
@@ -4572,7 +4620,7 @@ export class ResponseEngine {
4572
4620
  : undefined;
4573
4621
  const owningAgent = this.agentRegistry?.resolveByChannel(message.channel);
4574
4622
  const projectPath = owningAgent?.projectPath || process.cwd();
4575
- const resolvedBaseagent = message.baseagent || owningAgent?.baseagent || (!this.agentRegistry ? this.inferPrimaryBaseagent() : undefined);
4623
+ const resolvedBaseagent = message.baseagent || owningAgent?.baseagent;
4576
4624
  // 优先复用 MessageBridge 入队前已解析的身份,避免二次独立解析产生角色漂移
4577
4625
  // (曾观测到瞬时竞态下把 owner 误判为 none,导致合法用户被静默拒绝)。
4578
4626
  // 仅当消息不经 MessageBridge.register()(如 trigger 直接投递)时才回退重新解析。
@@ -4762,6 +4810,10 @@ export class ResponseEngine {
4762
4810
  // 每收到事件重置空闲超时
4763
4811
  const toolName = event.type === 'tool_use' ? event.name : undefined;
4764
4812
  resetTimer(event.type, toolName);
4813
+ // Provider reasoning/thinking is an internal liveness signal only.
4814
+ // It must never be projected as progress, activity, text, or transcript.
4815
+ if (event.type === 'heartbeat')
4816
+ continue;
4765
4817
  if (event.type === 'input_accepted') {
4766
4818
  inputAccepted = !turnLease || event.inputId === turnLease.inputId;
4767
4819
  logger.debug(`[ResponseEngine] Input accepted: session=${session.id} input=${event.inputId}`);
@@ -43,7 +43,9 @@ export function parseModelList(json, _url) {
43
43
  const rawId = typeof item === 'string' ? item : item?.id ?? item?.slug ?? item?.name ?? item?.model;
44
44
  if (typeof rawId !== 'string' || !rawId.trim())
45
45
  continue;
46
- const id = rawId.trim();
46
+ // 规范化模型 ID:去掉 Claude Code SDK 的上下文窗口后缀 [1m]/[200k]
47
+ // 这些后缀只在传给 claude baseagent 时由 applyContextWindow 添加
48
+ const id = rawId.trim().replace(/\[(?:1m|200k)\]$/i, '');
47
49
  if (out.has(id))
48
50
  continue;
49
51
  const rawLevels = item?.reasoningLevels ?? item?.supportedReasoningEfforts ?? item?.supported_reasoning_levels;
@@ -106,6 +106,16 @@ export function resolveCatalogContext(self, ba) {
106
106
  }
107
107
  return context;
108
108
  }
109
+ const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
110
+ /** Query the catalog for a host-level default connection, without Agent scope. */
111
+ export async function getBaseagentCatalog(baseagent, options = {}) {
112
+ if (!HOST_BASEAGENTS.includes(baseagent)) {
113
+ throw Object.assign(new Error(`Invalid baseagent: ${baseagent}. Valid: ${HOST_BASEAGENTS.join(', ')}`), { code: 'INVALID_ARGUMENT' });
114
+ }
115
+ const normalized = baseagent;
116
+ const snapshot = await getCatalog(undefined, normalized, options.refresh === true);
117
+ return { ...snapshot, baseagent: normalized };
118
+ }
109
119
  /** Used by the IPC handler and the owner process. Never calls IPC recursively. */
110
120
  export async function getLocalCatalog(self, ba, refresh = false) {
111
121
  return service.get(resolveCatalogContext(self, ba), refresh);
@@ -7,12 +7,15 @@ export function isModelAllowedByPatterns(model, allowedModels) {
7
7
  : model === pattern);
8
8
  }
9
9
  function normalizeBaseagent(baseagent) {
10
- return (baseagent || 'claude').trim() || 'claude';
10
+ const normalized = typeof baseagent === 'string' ? baseagent.trim() : '';
11
+ return normalized || undefined;
11
12
  }
12
13
  export function getRoleModelPermission(role, baseagent, selfAid) {
13
14
  const ba = normalizeBaseagent(baseagent);
14
15
  if (!role)
15
16
  return { allowOverride: false, constrained: true };
17
+ if (!ba)
18
+ return { allowOverride: false, constrained: true, allowedModels: [] };
16
19
  const selected = getFieldPermission(role, `baseagents.${ba}.model`, selfAid);
17
20
  if (!selected || !Array.isArray(selected.allowedModels)) {
18
21
  return { allowOverride: true, constrained: false };
@@ -8,7 +8,7 @@ import { createRootCausation, deriveCausation, normalizeCausation } from '../cau
8
8
  import { recordCausationSpan } from '../causation/audit.js';
9
9
  import { checkDangerousCommand } from './tool-policy.js';
10
10
  import { resolveProtectedCandidate } from './protected-paths.js';
11
- import { auditPermissionDecision } from '../auth/authorization-audit.js';
11
+ import { auditPermissionDecision, auditPermissionLifecycle } from '../auth/authorization-audit.js';
12
12
  export async function requestDangerousCommandPermission(gateway, sessionId, toolName, input, sendPrompt, context, grantScope = 'default', mode = 'request') {
13
13
  const dangerCheck = checkDangerousCommand(toolName, input);
14
14
  if (!dangerCheck.isDangerous) {
@@ -448,22 +448,40 @@ export class PermissionGateway {
448
448
  if (sessionGrant)
449
449
  this.addTemporaryGrant(pending, decisionCausation);
450
450
  pending.resolve(decision);
451
- if (!approved) {
452
- auditPermissionDecision({
453
- requestId,
454
- sessionId,
455
- toolName: pending.toolName,
456
- decisionSource: 'approval',
457
- policyCode: 'user_approval_denied',
458
- reason: 'owner approval denied',
459
- permissionMode: pending.permissionMode,
460
- agentAid: pending.selfAid,
461
- agentName: pending.agentName,
462
- role: pending.role,
463
- taskId: pending.taskId,
464
- summary: pending.auditSummary,
465
- });
466
- }
451
+ auditPermissionLifecycle({
452
+ phase: 'response',
453
+ requestId,
454
+ sessionId,
455
+ toolName: pending.toolName,
456
+ decision: approved ? 'allow' : 'deny',
457
+ decisionSource: 'approval',
458
+ policyCode: approved ? undefined : 'user_approval_denied',
459
+ reason: approved ? 'owner approval accepted' : 'owner approval denied',
460
+ permissionMode: pending.permissionMode,
461
+ agentAid: pending.selfAid,
462
+ agentName: pending.agentName,
463
+ role: pending.role,
464
+ taskId: pending.taskId,
465
+ summary: pending.auditSummary,
466
+ response: action,
467
+ });
468
+ auditPermissionLifecycle({
469
+ phase: 'final',
470
+ requestId,
471
+ sessionId,
472
+ toolName: pending.toolName,
473
+ decision: approved ? 'allow' : 'deny',
474
+ decisionSource: 'approval',
475
+ policyCode: approved ? undefined : 'user_approval_denied',
476
+ reason: approved ? 'owner approval accepted' : 'owner approval denied',
477
+ permissionMode: pending.permissionMode,
478
+ agentAid: pending.selfAid,
479
+ agentName: pending.agentName,
480
+ role: pending.role,
481
+ taskId: pending.taskId,
482
+ summary: pending.auditSummary,
483
+ response: action,
484
+ });
467
485
  void this.notifyPermissionState(pending, approved
468
486
  ? { state: 'approved', requestId, toolName: pending.toolName }
469
487
  : {
@@ -502,6 +520,23 @@ export class PermissionGateway {
502
520
  pending.resolve('deny');
503
521
  const decisionSource = 'infrastructure';
504
522
  const policyCode = cancelled ? 'approval_cancelled' : `approval_${reason}`;
523
+ auditPermissionLifecycle({
524
+ phase: 'response',
525
+ requestId,
526
+ sessionId,
527
+ toolName: pending.toolName,
528
+ decision: 'deny',
529
+ decisionSource,
530
+ policyCode,
531
+ reason,
532
+ permissionMode: pending.permissionMode,
533
+ agentAid: pending.selfAid,
534
+ agentName: pending.agentName,
535
+ role: pending.role,
536
+ taskId: pending.taskId,
537
+ summary: pending.auditSummary,
538
+ response: reason,
539
+ });
505
540
  auditPermissionDecision({
506
541
  requestId,
507
542
  sessionId,
@@ -565,7 +600,25 @@ export class PermissionGateway {
565
600
  const auditSummary = summarizeToolInputForAudit(challenge.toolName, challenge.toolInput).slice(0, 512);
566
601
  if (!approval || !interactionRouter) {
567
602
  await sendPrompt('当前会话权限不足,且没有可用的 owner 审批通道。');
603
+ auditPermissionLifecycle({
604
+ phase: 'response',
605
+ requestId: challenge.id,
606
+ sessionId,
607
+ toolName: challenge.toolName,
608
+ decision: 'deny',
609
+ decisionSource: 'infrastructure',
610
+ policyCode: 'approval_route_unavailable',
611
+ reason: 'owner approval routing context is unavailable',
612
+ permissionMode: context.permissionMode,
613
+ agentAid: context.selfAid,
614
+ agentName: context.agentName,
615
+ role: context.role,
616
+ taskId: context.taskId,
617
+ summary: auditSummary,
618
+ response: 'unavailable',
619
+ });
568
620
  auditPermissionDecision({
621
+ requestId: challenge.id,
569
622
  sessionId,
570
623
  toolName: challenge.toolName,
571
624
  decisionSource: 'infrastructure',
@@ -783,6 +836,23 @@ export class PermissionGateway {
783
836
  this.syncManagementRevalidationTimer();
784
837
  pending.resolve('deny');
785
838
  const decisionSource = reason === 'denied' ? 'approval' : 'infrastructure';
839
+ auditPermissionLifecycle({
840
+ phase: 'response',
841
+ requestId,
842
+ sessionId,
843
+ toolName: pending.toolName,
844
+ decision: 'deny',
845
+ decisionSource,
846
+ policyCode: `approval_${reason}`,
847
+ reason,
848
+ permissionMode: pending.permissionMode,
849
+ agentAid: pending.selfAid,
850
+ agentName: pending.agentName,
851
+ role: pending.role,
852
+ taskId: pending.taskId,
853
+ summary: pending.auditSummary,
854
+ response: reason,
855
+ });
786
856
  auditPermissionDecision({
787
857
  requestId,
788
858
  sessionId,
@@ -915,6 +985,20 @@ export class PermissionGateway {
915
985
  refs: { permissionRequestId: requestId, taskId: context.taskId, sessionId },
916
986
  });
917
987
  this.eventBus?.publish({ type: 'permission:requested', sessionId, requestId, toolName, input: displaySummary, causation: requestCausation });
988
+ auditPermissionLifecycle({
989
+ phase: 'request',
990
+ requestId,
991
+ sessionId,
992
+ toolName,
993
+ decision: 'pending',
994
+ decisionSource: 'approval',
995
+ permissionMode: context?.permissionMode,
996
+ agentAid: context?.selfAid,
997
+ agentName: context?.agentName,
998
+ role: context?.role,
999
+ taskId: context?.taskId,
1000
+ summary: auditSummary,
1001
+ });
918
1002
  const route = planApprovalRoute(challenge, context);
919
1003
  logger.info(`[PermissionGateway] approval_route request=${requestId} policy=${challenge.approverPolicy}`
920
1004
  + ` route=${route.kind}`
@@ -955,6 +1039,23 @@ export class PermissionGateway {
955
1039
  taskId: context.taskId,
956
1040
  summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
957
1041
  });
1042
+ auditPermissionLifecycle({
1043
+ phase: 'response',
1044
+ requestId,
1045
+ sessionId,
1046
+ toolName,
1047
+ decision: 'deny',
1048
+ decisionSource: 'infrastructure',
1049
+ policyCode: 'approval_route_unavailable',
1050
+ reason: route.reason,
1051
+ permissionMode: context.permissionMode,
1052
+ agentAid: context.selfAid,
1053
+ agentName: context.agentName,
1054
+ role: context.role,
1055
+ taskId: context.taskId,
1056
+ summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
1057
+ response: 'unavailable',
1058
+ });
958
1059
  return 'deny';
959
1060
  }
960
1061
  if (route.kind === 'handoff') {
@@ -1125,6 +1226,40 @@ export class PermissionGateway {
1125
1226
  pending.resolve(normalizedDecision === 'deny' ? 'deny' : 'allow');
1126
1227
  this.pending.delete(requestId);
1127
1228
  this.syncManagementRevalidationTimer();
1229
+ auditPermissionLifecycle({
1230
+ phase: 'response',
1231
+ requestId,
1232
+ sessionId,
1233
+ toolName: pending.toolName,
1234
+ decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
1235
+ decisionSource: 'approval',
1236
+ policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
1237
+ reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
1238
+ permissionMode: pending.permissionMode,
1239
+ agentAid: pending.selfAid,
1240
+ agentName: pending.agentName,
1241
+ role: pending.role,
1242
+ taskId: pending.taskId,
1243
+ summary: pending.auditSummary,
1244
+ response: normalizedDecision,
1245
+ });
1246
+ auditPermissionLifecycle({
1247
+ phase: 'final',
1248
+ requestId,
1249
+ sessionId,
1250
+ toolName: pending.toolName,
1251
+ decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
1252
+ decisionSource: 'approval',
1253
+ policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
1254
+ reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
1255
+ permissionMode: pending.permissionMode,
1256
+ agentAid: pending.selfAid,
1257
+ agentName: pending.agentName,
1258
+ role: pending.role,
1259
+ taskId: pending.taskId,
1260
+ summary: pending.auditSummary,
1261
+ response: normalizedDecision,
1262
+ });
1128
1263
  const decisionCausation = deriveCausation(pending.causation);
1129
1264
  if (normalizedDecision === 'always') {
1130
1265
  this.addTemporaryGrant(pending, decisionCausation);
@@ -1136,22 +1271,6 @@ export class PermissionGateway {
1136
1271
  });
1137
1272
  const decisionSource = 'approval';
1138
1273
  const policyCode = normalizedDecision === 'deny' ? 'user_approval_denied' : undefined;
1139
- if (normalizedDecision === 'deny') {
1140
- auditPermissionDecision({
1141
- requestId,
1142
- sessionId,
1143
- toolName: pending.toolName,
1144
- decisionSource,
1145
- policyCode: 'user_approval_denied',
1146
- reason: 'user approval denied',
1147
- permissionMode: pending.permissionMode,
1148
- agentAid: pending.selfAid,
1149
- agentName: pending.agentName,
1150
- role: pending.role,
1151
- taskId: pending.taskId,
1152
- summary: pending.auditSummary,
1153
- });
1154
- }
1155
1274
  this.eventBus?.publish({ type: 'permission:resolved', sessionId, requestId, toolName: pending.toolName, approved: normalizedDecision !== 'deny', ...(policyCode ? { decisionSource, policyCode } : { decisionSource }), causation: decisionCausation });
1156
1275
  return true;
1157
1276
  }
@@ -1215,6 +1334,23 @@ export class PermissionGateway {
1215
1334
  this.clearPendingResources(pending);
1216
1335
  pending.interactionRouter?.cancel(requestId, reason);
1217
1336
  pending.resolve('deny');
1337
+ auditPermissionLifecycle({
1338
+ phase: 'response',
1339
+ requestId,
1340
+ sessionId,
1341
+ toolName: pending.toolName,
1342
+ decision: 'deny',
1343
+ decisionSource: 'infrastructure',
1344
+ policyCode: `approval_${reason}`,
1345
+ reason,
1346
+ permissionMode: pending.permissionMode,
1347
+ agentAid: pending.selfAid,
1348
+ agentName: pending.agentName,
1349
+ role: pending.role,
1350
+ taskId: pending.taskId,
1351
+ summary: pending.auditSummary,
1352
+ response: reason,
1353
+ });
1218
1354
  auditPermissionDecision({
1219
1355
  requestId,
1220
1356
  sessionId,
@@ -2161,10 +2161,11 @@ export function evaluateToolPreflight(toolName, input, context) {
2161
2161
  : command;
2162
2162
  const tempCheck = checkManagedTempShellCommand(commandForPolicy, managedTempOptions(context));
2163
2163
  if (tempCheck.kind === 'deny') {
2164
+ const currentTmpDir = resolveManagedTempDir(managedTempOptions(context).env);
2164
2165
  return {
2165
2166
  behavior: 'deny',
2166
2167
  input,
2167
- message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内:${tempCheck.reason}`,
2168
+ message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内(当前值:${currentTmpDir ?? '<不可用>'}):${tempCheck.reason}`,
2168
2169
  policyCode: 'temporary_path_outside_tmpdir',
2169
2170
  };
2170
2171
  }
@@ -2215,14 +2216,20 @@ export function evaluateToolPreflight(toolName, input, context) {
2215
2216
  const ownAid = !!managedAidArgv
2216
2217
  && !!context.selfAid
2217
2218
  && managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
2218
- const isSelfManagedAidRead = !!managedAidArgv
2219
+ const isManagedAidPublicRead = !!context.selfAid
2220
+ && !!managedAidArgv
2219
2221
  && managedAidArgv[0] === 'ec'
2220
2222
  && managedAidArgv[1] === 'aid'
2221
- && ownAid
2222
- && ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
2223
+ && ((managedAidArgv.length === 4 && managedAidArgv[2] === 'lookup')
2223
2224
  || (managedAidArgv.length === 5
2224
2225
  && managedAidArgv[2] === 'agentmd'
2225
2226
  && managedAidArgv[3] === 'get'));
2227
+ const isSelfManagedAidShow = !!managedAidArgv
2228
+ && managedAidArgv.length === 4
2229
+ && managedAidArgv[0] === 'ec'
2230
+ && managedAidArgv[1] === 'aid'
2231
+ && managedAidArgv[2] === 'show'
2232
+ && ownAid;
2226
2233
  const isSelfAgentMdPublish = !!managedAidArgv
2227
2234
  && managedAidArgv.length === 5
2228
2235
  && managedAidArgv[0] === 'ec'
@@ -2232,14 +2239,14 @@ export function evaluateToolPreflight(toolName, input, context) {
2232
2239
  && ownAid;
2233
2240
  const canPublishSelfAgentMd = isSelfAgentMdPublish
2234
2241
  && ['request', 'bypass'].includes(context.permissionMode ?? '');
2235
- const isAllowedManagedAidCommand = isSelfManagedAidRead || canPublishSelfAgentMd;
2242
+ const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd;
2236
2243
  if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
2237
2244
  return {
2238
2245
  behavior: 'deny',
2239
2246
  input,
2240
2247
  message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
2241
2248
  ? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
2242
- : '🔒 EvolCore 托管会话仅允许针对当前 self AID 精确调用 `ec aid show`、`ec aid lookup` 或 `ec aid agentmd get`;`ec aid agentmd put` 还要求 request 或 bypass。可选后缀仅限 `--format json`。禁止枚举、切换或操作其他身份。',
2249
+ : '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put。',
2243
2250
  policyCode: 'managed_ec_aid_scope_forbidden',
2244
2251
  };
2245
2252
  }