evolcore 0.0.11 → 0.0.13

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/bin/ec-safe-output.js +26 -7
  4. package/dist/agents/claude-runner.js +86 -7
  5. package/dist/agents/codex-app-server-client.js +174 -20
  6. package/dist/agents/codex-runner.js +375 -92
  7. package/dist/agents/ecagent-runner.js +39 -4
  8. package/dist/agents/gemini-runner.js +1 -0
  9. package/dist/aun/aid/client.js +11 -21
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/managed-operation.js +107 -0
  12. package/dist/aun/aid/store.js +13 -7
  13. package/dist/aun/group-fs-download.js +56 -0
  14. package/dist/aun/group-identity.js +1 -2
  15. package/dist/aun/msg/group.js +91 -44
  16. package/dist/aun/msg/index.js +1 -2
  17. package/dist/aun/msg/managed-operation.js +865 -0
  18. package/dist/aun/msg/mention-schema.js +20 -0
  19. package/dist/aun/msg/p2p.js +7 -0
  20. package/dist/aun/msg/upload.js +16 -1
  21. package/dist/aun/outbox.js +31 -2
  22. package/dist/aun/rpc/client.js +66 -0
  23. package/dist/aun/rpc/index.js +1 -2
  24. package/dist/aun/storage/{upload.js → client.js} +11 -2
  25. package/dist/aun/storage/index.js +1 -2
  26. package/dist/channels/aun.js +655 -240
  27. package/dist/channels/daemon.js +13 -0
  28. package/dist/cli/agent-command.js +56 -8
  29. package/dist/cli/agent.js +108 -31
  30. package/dist/cli/aun-commands.js +270 -69
  31. package/dist/cli/bench.js +12 -3
  32. package/dist/cli/cli-argv.js +10 -0
  33. package/dist/cli/config.js +9 -17
  34. package/dist/cli/ctl-command.js +1 -1
  35. package/dist/cli/daemon-commands.js +70 -55
  36. package/dist/cli/fs-command.js +79 -10
  37. package/dist/cli/handoff-command.js +3 -0
  38. package/dist/cli/index.js +30 -2
  39. package/dist/cli/init.js +77 -8
  40. package/dist/cli/managed-command-guard.js +41 -0
  41. package/dist/cli/model.js +11 -4
  42. package/dist/cli/queue-command.js +53 -1
  43. package/dist/cli/response.js +71 -0
  44. package/dist/cli/restart-monitor.js +7 -32
  45. package/dist/cli/trigger-command.js +11 -0
  46. package/dist/config/access-policy.js +1 -2
  47. package/dist/config/builtin-roles.js +9 -0
  48. package/dist/config/config-manager.js +21 -1
  49. package/dist/config/contact-book-store.js +11 -3
  50. package/dist/config/contact-request-service.js +161 -15
  51. package/dist/config/mention-mode.js +8 -24
  52. package/dist/config/peer-role-resolver.js +6 -2
  53. package/dist/config/role-migration-startup.js +53 -0
  54. package/dist/config/role-store.js +1 -1
  55. package/dist/core/auth/agent-delegation.js +1 -2
  56. package/dist/core/auth/auth-gateway.js +29 -1
  57. package/dist/core/auth/authorization-audit.js +15 -0
  58. package/dist/core/auth/operation-authorizer.js +31 -7
  59. package/dist/core/auth/operation-catalog.js +196 -25
  60. package/dist/core/bootstrap-messages.js +25 -0
  61. package/dist/core/bootstrap-service.js +64 -4
  62. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  63. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  64. package/dist/core/capability/skill-discovery.js +55 -0
  65. package/dist/core/channel-loader.js +1 -1
  66. package/dist/core/command/cli-intent-parser.js +9 -0
  67. package/dist/core/command/command-handler.js +650 -42
  68. package/dist/core/command/connect-menu.js +15 -0
  69. package/dist/core/command/group-menu.js +4 -4
  70. package/dist/core/command/menu-catalog.js +370 -0
  71. package/dist/core/command/menu-handler.js +230 -51
  72. package/dist/core/command/menu-protocol.js +94 -2
  73. package/dist/core/command/slash-gate.js +5 -2
  74. package/dist/core/command/slash-handler.js +99 -31
  75. package/dist/core/daemon-file-cache.js +2 -1
  76. package/dist/core/data-migration.js +9 -2
  77. package/dist/core/event-catalog.js +6 -0
  78. package/dist/core/evolagent.js +4 -2
  79. package/dist/core/handoff/dispatcher.js +5 -2
  80. package/dist/core/handoff/runtime.js +157 -22
  81. package/dist/core/handoff/store.js +29 -1
  82. package/dist/core/handoff/types.js +1 -0
  83. package/dist/core/interaction-router.js +10 -0
  84. package/dist/core/message/im-renderer.js +19 -0
  85. package/dist/core/message/logical-queue-bridge.js +1 -0
  86. package/dist/core/message/mention-schema.js +126 -0
  87. package/dist/core/message/message-bridge.js +298 -88
  88. package/dist/core/message/message-log.js +3 -0
  89. package/dist/core/message/message-queue.js +268 -39
  90. package/dist/core/message/message-utils.js +81 -4
  91. package/dist/core/message/response-engine.js +161 -77
  92. package/dist/core/message/stream-debouncer.js +10 -2
  93. package/dist/core/permission/approval-gateway.js +1 -0
  94. package/dist/core/permission/ec-command-parser.js +243 -18
  95. package/dist/core/permission/index.js +1 -1
  96. package/dist/core/permission/mode.js +15 -0
  97. package/dist/core/permission/readonly-shell-query.js +148 -8
  98. package/dist/core/permission/sandbox-runtime.js +43 -1
  99. package/dist/core/permission/tool-policy.js +322 -57
  100. package/dist/core/protected-paths.js +114 -4
  101. package/dist/core/session/session-manager.js +42 -4
  102. package/dist/core/session/session-mapper.js +20 -1
  103. package/dist/eck/detect.js +18 -1
  104. package/dist/eck/group-rules-sync.js +15 -44
  105. package/dist/eck/manifest-engine.js +39 -13
  106. package/dist/index.js +541 -180
  107. package/dist/ipc.js +232 -10
  108. package/dist/paths.js +3 -0
  109. package/dist/response-system/coordinator.js +6 -4
  110. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  111. package/dist/response-system/modes/single-session/index.js +14 -1
  112. package/dist/response-system/selector.js +6 -5
  113. package/dist/trigger/anomaly-store.js +4 -0
  114. package/dist/trigger/feedback.js +64 -11
  115. package/dist/trigger/manager.js +21 -1
  116. package/dist/trigger/parser.js +14 -2
  117. package/dist/trigger/scheduler.js +1 -0
  118. package/dist/trigger/script-executor.js +1 -0
  119. package/dist/trigger/validation.js +67 -6
  120. package/dist/utils/codex-app-server-registry.js +90 -0
  121. package/dist/utils/codex-cli.js +5 -1
  122. package/dist/utils/cross-platform.js +15 -0
  123. package/dist/utils/ecweb-supervisor.js +332 -0
  124. package/dist/utils/error-utils.js +10 -0
  125. package/dist/utils/npm-ops.js +5 -12
  126. package/dist/utils/tool-summary.js +11 -0
  127. package/kits/docs/channels/aun.md +1 -1
  128. package/kits/docs/context-assembly.md +2 -2
  129. package/kits/docs/evolcore/agent.md +11 -6
  130. package/kits/docs/evolcore/aid.md +14 -0
  131. package/kits/docs/evolcore/trigger.md +17 -1
  132. package/kits/docs/prompt-loading-architecture.md +1 -2
  133. package/kits/docs/venues/group.md +1 -1
  134. package/kits/eck_manifest.json +0 -12
  135. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  136. package/kits/rules/04-relation.md +1 -1
  137. package/kits/rules/05-venue.md +2 -2
  138. package/kits/schemas/role-config.schema.1.json +1 -0
  139. package/kits/schemas/single-session.schema.1.json +3 -3
  140. package/kits/schemas/single-session.schema.2.json +3 -3
  141. package/kits/templates/roles/admin.json +27 -0
  142. package/kits/templates/roles/member.json +34 -1
  143. package/kits/templates/roles/visitor.json +35 -3
  144. package/kits/templates/system-fragments/bootstrap.md +1 -1
  145. package/kits/templates/system-fragments/channel.md +1 -1
  146. package/kits/templates/system-fragments/session.md +1 -1
  147. package/package.json +2 -2
  148. package/dist/aun/msg/payload-type.js +0 -27
  149. package/dist/aun/rpc/caller.js +0 -42
  150. package/dist/aun/rpc/connection.js +0 -25
  151. package/dist/aun/storage/manage.js +0 -10
  152. package/dist/config/access-policy-domain.js +0 -18
  153. package/dist/config/config-batch-get.js +0 -11
  154. package/dist/config/owner-policy.js +0 -4
  155. package/dist/config/role-config-v4-startup.js +0 -32
  156. package/dist/config/role-config-v5-startup.js +0 -27
  157. package/dist/core/auth/trigger-authorization.js +0 -15
  158. package/dist/core/command/evol-menu-version-gate.js +0 -39
  159. package/dist/core/interaction-registration.js +0 -10
  160. package/dist/core/permission/execution-sandbox.js +0 -16
  161. package/dist/core/relation/peer-key.js +0 -1
  162. package/dist/core/session/session-key.js +0 -24
  163. package/dist/eck/baseagent-caps.js +0 -18
  164. package/dist/eck/rules-loader.js +0 -28
package/CHANGELOG.md CHANGED
@@ -3,6 +3,50 @@
3
3
  本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
4
4
  [`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
5
5
 
6
+ ## 0.0.13 (2026-08-20)
7
+
8
+ ### AUN 消息与文件
9
+
10
+ - 显式区分 AUN 私聊与群聊投递路由,强化入站去重、顺序处理、消息队列持久化和失败恢复。
11
+ - 统一 AUN RPC 与存储客户端,扩展群文件写入与下载能力,收敛文件操作和载荷处理边界。
12
+ - 增加群文件下载票据校验和内容哈希验证,补齐短连接 RPC、存储上传及启动期角色配置迁移能力。
13
+
14
+ ### 权限与关系
15
+
16
+ - 收敛角色迁移、权限作用域和最小能力模式,强化 Owner、关系范围与工具执行审计边界。
17
+ - 贯通会话投递元数据和跨会话授权校验,避免托管操作越过当前 Agent、会话或关系范围。
18
+ - 强化受管 `$TMPDIR`、群规则读取和群文件上传的路径隔离,拒绝符号链接、越界目录及不符合权限/所有者要求的路径。
19
+ - 完善菜单授权审计的请求与会话关联信息,扩展只读 Shell 查询、配置批量读取和委派有效期校验。
20
+
21
+ ### 会话与运行时
22
+
23
+ - 完善消息撤回隔离、错误回传、菜单交互、重启命令和 Trigger 反馈的上下文校验。
24
+ - 简化 Agent 运行器与启动流程,修正系统重启路径并清理托管会话凭据。
25
+ - 为 Codex app-server 请求增加 request ID 与载荷指纹重放防护,使重复请求复用结果、冲突请求拒绝;会话重试按至多执行一次处理。
26
+ - 统一 EC Web 守护进程的发现、接管、健康检查和退出清理,修正 Linux 进程启动时间计算,并改进 Codex 初始化/升级探测与 Web 消息路由展示。
27
+
28
+ ## 0.0.12 (2026-08-19)
29
+
30
+ ### AUN 通信
31
+
32
+ - 规范结构化提及在消息收发、队列和响应链路中的传递,完善 @all、目标校验和非法载荷处理。
33
+ - 增强入站去重、断线重投和错误回传,避免消息重复处理与错误循环。
34
+
35
+ ### 会话与 Agent
36
+
37
+ - 串行化 Codex 会话的继续、压缩、分叉和回滚操作,完善 app-server 进程管理和安全清理。
38
+ - 优化 proactive 会话、Handoff 和 Bootstrap 的生命周期处理,隔离并发会话上下文。
39
+
40
+ ### 权限与菜单
41
+
42
+ - 收敛菜单执行上下文和托管命令授权,强化 EC shell 与 Codex 命令解析边界。
43
+ - 完善菜单批量查询、关系上下文、联系人状态和工具拦截审计。
44
+
45
+ ### 运行兼容性
46
+
47
+ - 统一跨平台子进程启动和 Windows 窗口处理,改进升级监控与内置 AUN SDK 管理。
48
+ - 统一 Claude、Codex 的技能发现和运行时上下文注入。
49
+
6
50
  ## 0.0.11 (2026-08-11)
7
51
 
8
52
  ### Agent 初始化与引导
@@ -0,0 +1,80 @@
1
+ import fs from 'node:fs';
2
+ import net from 'node:net';
3
+
4
+ const timeoutMs = 1500;
5
+
6
+ function deny(reason) {
7
+ process.stdout.write(JSON.stringify({
8
+ hookSpecificOutput: {
9
+ hookEventName: 'PreToolUse',
10
+ permissionDecision: 'deny',
11
+ permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
12
+ },
13
+ }));
14
+ // Codex 0.147 app-server runs this managed hook but does not reliably honor
15
+ // the JSON denial alone for thread/shellCommand. Exit 2 is the documented
16
+ // blocking transport; retain the JSON shape for compatible hook consumers.
17
+ process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
18
+ process.exitCode = 2;
19
+ }
20
+
21
+ function queryDaemon(socketPath, payload) {
22
+ return new Promise(resolve => {
23
+ const socket = net.createConnection(socketPath);
24
+ let buffer = '';
25
+ let settled = false;
26
+ let timer;
27
+ const finish = value => {
28
+ if (settled) return;
29
+ settled = true;
30
+ clearTimeout(timer);
31
+ socket.destroy();
32
+ resolve(value);
33
+ };
34
+ timer = setTimeout(() => finish(null), timeoutMs);
35
+ timer.unref?.();
36
+ socket.once('error', () => finish(null));
37
+ socket.once('connect', () => socket.write(JSON.stringify(payload) + '\n'));
38
+ socket.on('data', chunk => {
39
+ buffer += String(chunk);
40
+ const index = buffer.indexOf('\n');
41
+ if (index < 0) return;
42
+ try { finish(JSON.parse(buffer.slice(0, index))); } catch { finish(null); }
43
+ });
44
+ });
45
+ }
46
+
47
+ async function main() {
48
+ const socketPath = process.argv[2];
49
+ let input;
50
+ try {
51
+ input = JSON.parse(fs.readFileSync(0, 'utf8'));
52
+ } catch {
53
+ deny('Invalid Codex PreToolUse payload.');
54
+ return;
55
+ }
56
+ if (!socketPath
57
+ || input?.hook_event_name !== 'PreToolUse'
58
+ || typeof input?.session_id !== 'string'
59
+ || !input.session_id
60
+ || typeof input?.tool_name !== 'string'
61
+ || !input.tool_name) {
62
+ deny('Incomplete Codex PreToolUse payload.');
63
+ return;
64
+ }
65
+
66
+ const result = await queryDaemon(socketPath, {
67
+ type: 'codex-pre-tool-use',
68
+ threadId: input.session_id,
69
+ toolName: typeof input.tool_name === 'string' ? input.tool_name : '',
70
+ toolInput: input.tool_input ?? null,
71
+ });
72
+ if (!result) {
73
+ deny('EvolCore policy service is unavailable.');
74
+ return;
75
+ }
76
+ if (result.ok === true && result.applicable === true && result.decision === 'allow') return;
77
+ deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.');
78
+ }
79
+
80
+ void main();
@@ -82,12 +82,27 @@ function resolveExecutionPaths(payload) {
82
82
  return { workspace, cwd };
83
83
  }
84
84
 
85
+ function resolveManagedTempRoot() {
86
+ const raw = typeof process.env.TMPDIR === 'string' ? process.env.TMPDIR.trim() : '';
87
+ if (!raw || !path.isAbsolute(raw)) fail('managed TMPDIR is unavailable');
88
+ try {
89
+ const root = fs.realpathSync(raw);
90
+ if (!fs.statSync(root).isDirectory()) fail('managed TMPDIR is not a directory');
91
+ return root;
92
+ } catch {
93
+ fail('managed TMPDIR is unavailable');
94
+ }
95
+ }
96
+
85
97
  function openOutputFile(payload, paths) {
86
98
  if (payload.outputPath === undefined) return undefined;
87
- if (typeof payload.outputPath !== 'string' || !payload.outputPath || path.isAbsolute(payload.outputPath)) {
88
- fail('output path must be relative');
89
- }
90
- const target = path.resolve(paths.cwd, payload.outputPath);
99
+ if (typeof payload.outputPath !== 'string' || !payload.outputPath) fail('output path is invalid');
100
+
101
+ const isManagedTempOutput = path.isAbsolute(payload.outputPath);
102
+ const outputRoot = isManagedTempOutput ? resolveManagedTempRoot() : paths.workspace;
103
+ const target = isManagedTempOutput
104
+ ? path.resolve(payload.outputPath)
105
+ : path.resolve(paths.cwd, payload.outputPath);
91
106
  const parent = path.dirname(target);
92
107
  let realParent;
93
108
  try {
@@ -97,9 +112,13 @@ function openOutputFile(payload, paths) {
97
112
  fail('output parent does not exist');
98
113
  }
99
114
  const canonicalTarget = path.join(realParent, path.basename(target));
100
- if (!sameOrDescendant(canonicalTarget, paths.workspace)) fail('output path escapes workspace');
101
- const relative = path.relative(paths.workspace, canonicalTarget);
102
- if (relative === '.git' || relative.startsWith(`.git${path.sep}`)) fail('output path may not target Git metadata');
115
+ if (!sameOrDescendant(canonicalTarget, outputRoot)) {
116
+ fail(isManagedTempOutput ? 'output path escapes managed TMPDIR' : 'output path escapes workspace');
117
+ }
118
+ if (!isManagedTempOutput) {
119
+ const relative = path.relative(paths.workspace, canonicalTarget);
120
+ if (relative === '.git' || relative.startsWith(`.git${path.sep}`)) fail('output path may not target Git metadata');
121
+ }
103
122
 
104
123
  const noFollow = fs.constants.O_NOFOLLOW ?? 0;
105
124
  try {
@@ -14,16 +14,15 @@ import { logger } from '../utils/logger.js';
14
14
  import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
15
15
  import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
16
16
  import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
17
- import { summarizeToolInput } from '../utils/tool-summary.js';
17
+ import { summarizeToolInput, summarizeToolInputForAudit } from '../utils/tool-summary.js';
18
18
  import { encodePath } from '../utils/cross-platform.js';
19
19
  import { getPackageRoot, resolvePaths } from '../paths.js';
20
20
  import { resolveEffective } from '../config/config-manager.js';
21
21
  import { sanitizeSessionTitle } from '../core/session/session-title.js';
22
22
  import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
23
- import { normalizePermissionMode } from '../core/permission/mode.js';
24
- import { resolvePhaseOneExecutionSandbox } from '../core/permission/execution-sandbox.js';
23
+ import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
25
24
  import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, resolveProtectedCandidate, } from '../core/protected-paths.js';
26
- import { buildHClassGuardCommand, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
25
+ import { buildHClassGuardCommand, createSandboxInitializationError, isSandboxInitializationFailure, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
27
26
  import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
28
27
  import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
29
28
  export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
@@ -1759,6 +1758,30 @@ export class AgentRunner {
1759
1758
  else {
1760
1759
  logger.error(`[AgentRunner] Subprocess stream failed (session=${sessionId}) with no captured stderr.`);
1761
1760
  }
1761
+ if (isSandboxInitializationFailure(err, buf ?? [])) {
1762
+ const detail = createSandboxInitializationError(err, buf ?? []);
1763
+ await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
1764
+ code: 'capability_unavailable',
1765
+ severity: 'warning',
1766
+ phase: 'execution',
1767
+ occurredAt: Date.now(),
1768
+ toolName: 'Bash',
1769
+ policyCode: SANDBOX_INITIALIZATION_FAILED,
1770
+ decisionSource: 'infrastructure',
1771
+ agentAid: this.permissionContexts.get(sessionId)?.selfAid,
1772
+ summary: detail.message.slice(0, 512),
1773
+ effect: 'operation_skipped',
1774
+ });
1775
+ yield {
1776
+ type: 'complete',
1777
+ isError: true,
1778
+ subtype: 'infrastructure',
1779
+ terminalReason: SANDBOX_INITIALIZATION_FAILED,
1780
+ errors: [detail.message],
1781
+ queryFinal: true,
1782
+ };
1783
+ return;
1784
+ }
1762
1785
  throw err;
1763
1786
  }
1764
1787
  finally {
@@ -1852,6 +1875,30 @@ export class AgentRunner {
1852
1875
  ...(updatedInput ? { updatedInput } : {}),
1853
1876
  },
1854
1877
  });
1878
+ const recordPreflightDenial = async (toolName, toolInput, preflight, requestId, policyCode) => {
1879
+ const code = policyCode ?? (preflight?.behavior === 'deny' ? preflight.policyCode : undefined);
1880
+ if (!code)
1881
+ return;
1882
+ try {
1883
+ await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
1884
+ code: 'operation_blocked',
1885
+ severity: 'warning',
1886
+ phase: 'execution',
1887
+ occurredAt: Date.now(),
1888
+ toolName,
1889
+ ...(typeof requestId === 'string' && requestId ? { requestId } : {}),
1890
+ policyCode: code,
1891
+ decisionSource: 'policy',
1892
+ agentAid: this.permissionContexts.get(sessionId)?.selfAid,
1893
+ permissionMode: callPermissionMode,
1894
+ summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
1895
+ effect: 'operation_skipped',
1896
+ });
1897
+ }
1898
+ catch (error) {
1899
+ logger.warn(`[AgentRunner] failed to record preflight denial: session=${sessionId} tool=${toolName} code=${code} error=${error instanceof Error ? error.message : String(error)}`);
1900
+ }
1901
+ };
1855
1902
  // A rewritten helper command is trusted only after this hook produced its
1856
1903
  // random one-shot payload from a validated output wrapper.
1857
1904
  const trustedSafeOutputCommands = new Set();
@@ -1896,6 +1943,7 @@ export class AgentRunner {
1896
1943
  // proactive 模式行为策略(首次工具调用必须是 ec msg send)
1897
1944
  const policyResult = this.permissionContexts.get(sessionId)?.policyHook?.(toolName, toolInput);
1898
1945
  if (policyResult?.block) {
1946
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, policyResult.policyCode ?? 'session_policy_hook');
1899
1947
  return hookDecision('deny', policyResult.reason || '当前会话策略拒绝此工具调用', updatedInput);
1900
1948
  }
1901
1949
  const permCtx = this.permissionContexts.get(sessionId);
@@ -1910,14 +1958,17 @@ export class AgentRunner {
1910
1958
  role: permCtx?.role,
1911
1959
  permissionMode: callPermissionMode,
1912
1960
  allowHostProcessCommands: true,
1961
+ allowProtectedMetadata: false,
1913
1962
  safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
1914
1963
  projectPath,
1915
1964
  });
1916
1965
  toolInput = preflight.input;
1917
1966
  if (toolInput !== originalInput)
1918
1967
  updatedInput = toolInput;
1919
- if (preflight.behavior === 'deny')
1968
+ if (preflight.behavior === 'deny') {
1969
+ await recordPreflightDenial(toolName, toolInput, preflight, input.tool_use_id ?? input.toolUseID);
1920
1970
  return hookDecision('deny', preflight.message, updatedInput);
1971
+ }
1921
1972
  if (preflight.behavior === 'allow') {
1922
1973
  const commandAfterPreflight = typeof toolInput.command === 'string' ? toolInput.command : undefined;
1923
1974
  if (commandAfterPreflight && commandAfterPreflight !== commandBeforePreflight) {
@@ -1939,9 +1990,11 @@ export class AgentRunner {
1939
1990
  channel: permCtx?.channel,
1940
1991
  peerId: permCtx?.userId,
1941
1992
  role: permCtx?.role,
1993
+ allowProtectedMetadata: false,
1942
1994
  };
1943
1995
  const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
1944
1996
  if (roResult.behavior === 'deny') {
1997
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, roResult.policyCode ?? 'readonly_mode');
1945
1998
  return hookDecision('deny', roResult.message, updatedInput);
1946
1999
  }
1947
2000
  return hookDecision('allow', undefined, updatedInput);
@@ -1951,6 +2004,7 @@ export class AgentRunner {
1951
2004
  }
1952
2005
  if (isUnknownClaudeTool(toolName)) {
1953
2006
  if (callPermissionMode === 'auto') {
2007
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_unknown_tool');
1954
2008
  return hookDecision('deny', `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`, updatedInput);
1955
2009
  }
1956
2010
  // request/bypass only receive external tools admitted by the session's
@@ -1960,6 +2014,7 @@ export class AgentRunner {
1960
2014
  const danger = checkDangerousCommand(toolName, toolInput);
1961
2015
  if (danger.isDangerous) {
1962
2016
  if (callPermissionMode === 'auto') {
2017
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_dangerous_command');
1963
2018
  return hookDecision('deny', `危险操作已由 auto 模式拒绝:${danger.reason}`, updatedInput);
1964
2019
  }
1965
2020
  if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
@@ -2025,11 +2080,13 @@ export class AgentRunner {
2025
2080
  role: permCtx?.role,
2026
2081
  permissionMode: callPermissionMode,
2027
2082
  allowHostProcessCommands: true,
2083
+ allowProtectedMetadata: false,
2028
2084
  safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
2029
2085
  projectPath,
2030
2086
  });
2031
2087
  input = preflight.input;
2032
2088
  if (preflight.behavior === 'deny') {
2089
+ await recordPreflightDenial(toolName, input, preflight, options.toolUseID);
2033
2090
  return { behavior: 'deny', message: preflight.message, decisionClassification: 'user_reject' };
2034
2091
  }
2035
2092
  if (preflight.behavior === 'allow') {
@@ -2041,6 +2098,7 @@ export class AgentRunner {
2041
2098
  }
2042
2099
  const permissionExpansion = inspectClaudePermissionExpansion(input, options, projectPath);
2043
2100
  if (permissionExpansion.protectedReason) {
2101
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'protected_permission_expansion');
2044
2102
  return {
2045
2103
  behavior: 'deny',
2046
2104
  message: permissionExpansion.protectedReason,
@@ -2049,6 +2107,7 @@ export class AgentRunner {
2049
2107
  }
2050
2108
  if (permissionExpansion.requiresApproval) {
2051
2109
  if (callPermissionMode === 'readonly' || callPermissionMode === 'auto') {
2110
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, callPermissionMode === 'readonly' ? 'readonly_permission_expansion' : 'auto_permission_expansion');
2052
2111
  return {
2053
2112
  behavior: 'deny',
2054
2113
  message: `${callPermissionMode} 模式拒绝 Claude SDK 文件系统或未知权限扩权请求`,
@@ -2089,9 +2148,11 @@ export class AgentRunner {
2089
2148
  channel: permCtx?.channel,
2090
2149
  peerId: permCtx?.userId,
2091
2150
  role: permCtx?.role,
2151
+ allowProtectedMetadata: false,
2092
2152
  };
2093
2153
  const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
2094
2154
  if (roResult.behavior === 'deny') {
2155
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, roResult.policyCode ?? 'readonly_mode');
2095
2156
  return { behavior: 'deny', message: roResult.message, decisionClassification: 'user_reject' };
2096
2157
  }
2097
2158
  return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
@@ -2101,6 +2162,7 @@ export class AgentRunner {
2101
2162
  }
2102
2163
  if (isUnknownClaudeTool(toolName)) {
2103
2164
  if (callPermissionMode === 'auto') {
2165
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_unknown_tool');
2104
2166
  return {
2105
2167
  behavior: 'deny',
2106
2168
  message: `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`,
@@ -2112,6 +2174,7 @@ export class AgentRunner {
2112
2174
  const danger = checkDangerousCommand(toolName, input);
2113
2175
  if (danger.isDangerous) {
2114
2176
  if (callPermissionMode === 'auto') {
2177
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_dangerous_command');
2115
2178
  return { behavior: 'deny', message: `auto 模式拒绝危险操作:${danger.reason}`, decisionClassification: 'user_reject' };
2116
2179
  }
2117
2180
  if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
@@ -2340,12 +2403,28 @@ export class AgentRunner {
2340
2403
  logger.info('[AgentRunner] Creating query with images:', images.length, 'first image size:', images[0]?.data?.length ?? 0);
2341
2404
  logger.debug('[AgentRunner] Skipping resume for image message to avoid history conflict');
2342
2405
  msgStream.push(prompt, images, inputId);
2343
- sdkStream = createQuery(msgStream);
2406
+ try {
2407
+ sdkStream = createQuery(msgStream);
2408
+ }
2409
+ catch (error) {
2410
+ const stderr = this.recentStderr.get(sessionId) ?? [];
2411
+ if (isSandboxInitializationFailure(error, stderr))
2412
+ throw createSandboxInitializationError(error, stderr);
2413
+ throw error;
2414
+ }
2344
2415
  }
2345
2416
  else {
2346
2417
  logger.debug('[AgentRunner] Creating query with text only, agentSessionId:', initialClaudeSessionId);
2347
2418
  msgStream.push(prompt, undefined, inputId);
2348
- sdkStream = createQuery(msgStream, agentSessionId, resumeAt);
2419
+ try {
2420
+ sdkStream = createQuery(msgStream, agentSessionId, resumeAt);
2421
+ }
2422
+ catch (error) {
2423
+ const stderr = this.recentStderr.get(sessionId) ?? [];
2424
+ if (isSandboxInitializationFailure(error, stderr))
2425
+ throw createSandboxInitializationError(error, stderr);
2426
+ throw error;
2427
+ }
2349
2428
  }
2350
2429
  this.activeMessageStreams.set(sessionId, msgStream);
2351
2430
  this.activeQueries.set(sessionId, sdkStream);