evolcore 0.0.32 → 0.0.34

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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -1,6 +1,6 @@
1
1
  import { getRunnerCatalog, peekRunnerCatalog, catalogEfforts, resolveCatalogModel, assertCatalogSelectable } from '../core/model/model-catalog.js';
2
2
  import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages, resolveSettings as sdkResolveSettings, } from '@anthropic-ai/claude-agent-sdk';
3
- import { spawn } from 'child_process';
3
+ import { spawn, execFileSync } from 'child_process';
4
4
  import { createRequire } from 'module';
5
5
  import { ensureDir } from '../utils/atomic-write.js';
6
6
  import { resolveAnthropicConfig } from './baseagent.js';
@@ -30,7 +30,8 @@ import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-p
30
30
  import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
31
31
  import { getManagedTaskTempDir, isManagedSessionRuntimeDir } from '../cli/task-context.js';
32
32
  import { unsettledInterruptError, withInterruptDeadline, hasTrustedFullAccessContext } from './runner-types.js';
33
- import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel, sdkAutoCompactWindowForModel } from './runner-types.js';
33
+ import { resolveModelSpec } from '../stats/billing.js';
34
+ import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, realContextWindowForModel, autoCompactWindowForModel, sdkAutoCompactWindowForModel } from './runner-types.js';
34
35
  import { hasContextWindowSuffix, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
35
36
  const resolveModule = createRequire(import.meta.url);
36
37
  const CLAUDE_INTERRUPT_TIMEOUT_MS = 5_000;
@@ -148,6 +149,22 @@ export function resolveBundledClaudeExecutable() {
148
149
  return undefined;
149
150
  }
150
151
  }
152
+ /**
153
+ * 探测内置 claude.exe 的版本。检测层与执行层共用同一份二进制,所以这里探到的
154
+ * 版本就是会话真正会跑的那个版本,可与 SDK 锁定的 claudeCodeVersion 对照。
155
+ */
156
+ export function probeBundledClaudeVersion() {
157
+ const executable = resolveBundledClaudeExecutable();
158
+ if (!executable)
159
+ return undefined;
160
+ try {
161
+ const raw = execFileSync(executable, ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
162
+ return raw.trim().split(/\s+/)[0] || undefined;
163
+ }
164
+ catch {
165
+ return undefined;
166
+ }
167
+ }
151
168
  export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
152
169
  // Built-in tools execute inside the Claude runtime and are covered by the
153
170
  // runner's filesystem/shell policy. MCP and future unknown tools can perform
@@ -494,19 +511,28 @@ async function refreshGatewayPricing(baseUrl, apiKey, requestHeaders) {
494
511
  *
495
512
  * 若网关把窗口变体作为独立条目下发(目录里同时有 `claude-opus-5` 与
496
513
  * `claude-opus-5[1m]`),裸 ID 就是用户明确选的非 1M 变体,不能擅自升级,
497
- * 否则两条列表项的实际效果无法区分。只有目录未提供任何窗口变体时,才沿用
498
- * 按本地名单补 `[1m]` 的旧行为。
514
+ * 否则两条列表项的实际效果无法区分。只有目录未提供任何窗口变体时,才按已解析的
515
+ * 模型参数(缺失时才回退本地名单)补 `[1m]`。
499
516
  */
500
- function applyContextWindow(modelId, catalog) {
517
+ function applyContextWindow(modelId, contextWindow, catalog) {
501
518
  if (hasContextWindowSuffix(modelId))
502
519
  return modelId; // 窗口已显式指定
503
520
  const variantListed = catalog?.models.some(m => hasContextWindowSuffix(m.id) && stripContextWindowSuffix(m.id) === modelId);
504
521
  if (variantListed)
505
522
  return modelId;
506
- if (isOneMillionContextModel(modelId))
523
+ if (contextWindow === 1_000_000)
507
524
  return `${modelId}[1m]`;
508
525
  return modelId;
509
526
  }
527
+ function resolveClaudeModelContext(modelId, catalog) {
528
+ const modelSpec = resolveModelSpec(resolvePaths().root, modelId, undefined, {
529
+ context_window: realContextWindowForModel(modelId),
530
+ });
531
+ return {
532
+ sdkModel: applyContextWindow(modelId, modelSpec.context_window, catalog),
533
+ contextWindow: modelSpec.context_window,
534
+ };
535
+ }
510
536
  class MessageStream {
511
537
  queue = [];
512
538
  waiting = null;
@@ -586,6 +612,8 @@ export class AgentRunner {
586
612
  permissionGateway;
587
613
  sendPromptFn;
588
614
  permissionContexts = new Map();
615
+ /** tool_use_id → 模型返回序号(turnCount),供 PreToolUse 钩子把 turn 传给 policyHook */
616
+ toolTurnByCallId = new Map();
589
617
  claudeExecutablePath;
590
618
  /** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
591
619
  recentStderr = new Map();
@@ -714,6 +742,9 @@ export class AgentRunner {
714
742
  ANTHROPIC_API_KEY: this.apiKey,
715
743
  PATH: process.env.PATH,
716
744
  DISABLE_AUTOUPDATER: '1',
745
+ // 记忆只走 EvolCore(ec memory):关闭 Claude Code 原生 auto-memory 的
746
+ // 读写,避免两套记忆并存污染上下文。
747
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
717
748
  ...(anthropicBaseUrl ? { ANTHROPIC_BASE_URL: anthropicBaseUrl } : {}),
718
749
  ...(this.customHeaders
719
750
  ? { ANTHROPIC_CUSTOM_HEADERS: Object.entries(this.customHeaders).map(([name, value]) => `${name}: ${value}`).join('\n') }
@@ -779,6 +810,9 @@ export class AgentRunner {
779
810
  settings: {
780
811
  ...settings,
781
812
  skillOverrides: { ...skillOverrides, eclink: 'off' },
813
+ // 记忆只走 EvolCore(ec memory):关闭 Claude Code 原生 auto-memory,
814
+ // 与 getAgentEnv 的 CLAUDE_CODE_DISABLE_AUTO_MEMORY 双保险。
815
+ autoMemoryEnabled: false,
782
816
  },
783
817
  };
784
818
  }
@@ -1447,7 +1481,7 @@ export class AgentRunner {
1447
1481
  * SDK 原始事件 → 标准 AgentEvent 转换
1448
1482
  * 所有 SDK 特有的事件类型引用封装在此方法内
1449
1483
  */
1450
- async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false, recoverySessionId) {
1484
+ async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false, recoverySessionId, resolvedContextWindow) {
1451
1485
  let lastSessionId;
1452
1486
  let pendingSessionId;
1453
1487
  let hasTurnActivity = false;
@@ -1765,6 +1799,8 @@ export class AgentRunner {
1765
1799
  if (content.type === 'tool_use') {
1766
1800
  if (content.id)
1767
1801
  toolUseNames.set(content.id, content.name);
1802
+ if (content.id)
1803
+ this.toolTurnByCallId.set(content.id, turnCount);
1768
1804
  if (content.id && content.name === 'Bash' && content.input?.dangerouslyDisableSandbox !== true
1769
1805
  && resolvePhaseOneExecutionSandbox(permissionMode, this.permissionContexts.get(sessionId)?.role).state !== 'off') {
1770
1806
  sandboxedBashCalls.add(content.id);
@@ -1772,7 +1808,9 @@ export class AgentRunner {
1772
1808
  yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars, assistantUuid: event.uuid };
1773
1809
  }
1774
1810
  else if (content.type === 'text' && content.text) {
1775
- yield { type: 'text', text: content.text, outputTokens: turnOutputChars, turn: turnCount, assistantUuid: event.uuid };
1811
+ // 携带出身标记:`<synthetic>` assistant 是 API/传输故障的错误回显,
1812
+ // 消费端据此把它与模型正文区分开,不必靠字符串或计数猜。
1813
+ yield { type: 'text', text: content.text, outputTokens: turnOutputChars, turn: turnCount, assistantUuid: event.uuid, synthetic: isSyntheticAssistant || undefined };
1776
1814
  }
1777
1815
  }
1778
1816
  }
@@ -1787,6 +1825,8 @@ export class AgentRunner {
1787
1825
  : block.content != null ? JSON.stringify(block.content) : '';
1788
1826
  if (block.tool_use_id)
1789
1827
  emittedToolResultIds.add(block.tool_use_id);
1828
+ if (block.tool_use_id)
1829
+ this.toolTurnByCallId.delete(block.tool_use_id);
1790
1830
  if (sandboxedBashCalls.delete(block.tool_use_id) && block.is_error === true
1791
1831
  && isSandboxInitializationToolResult(block.content)) {
1792
1832
  const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, block.tool_use_id, permissionMode, new Error(resultContent), [], 'session');
@@ -1876,7 +1916,7 @@ export class AgentRunner {
1876
1916
  const effectiveModel = callModel ?? this.model;
1877
1917
  const isClaudeModel = isClaudeContextUsageModel(effectiveModel);
1878
1918
  const totalTokens = contextTokensForUsage(u, !!isClaudeModel);
1879
- const contextWindowTokens = realContextWindowForModel(sdkModel);
1919
+ const contextWindowTokens = resolvedContextWindow ?? realContextWindowForModel(sdkModel);
1880
1920
  const autoCompactTokens = autoCompactWindowForModel(sdkModel);
1881
1921
  const aggregateContextUsage = totalTokens > 0 ? {
1882
1922
  totalTokens,
@@ -2186,7 +2226,17 @@ export class AgentRunner {
2186
2226
  if (callPermissionMode === 'fullaccess' && !hasTrustedFullAccessContext(runPermissionContext)) {
2187
2227
  throw new Error('Claude fullaccess requires a trusted per-call execution authorization');
2188
2228
  }
2189
- const executionSandbox = resolvePhaseOneExecutionSandbox(callPermissionMode, runPermissionContext?.role);
2229
+ // 系统会话明确声明不要沙箱时,直接按「沙箱关闭」处理。SDK 沙箱以项目目录为中心,
2230
+ // 而系统会话的读写边界($EVOLCORE_HOME 下的批次目录与记忆目录)与 cwd 无关,
2231
+ // 启用之后只会把声明内的路径也挡在外面。边界由 systemSessionCheck 逐次判定。
2232
+ const executionSandbox = runPermissionContext?.systemSessionSandbox === 'off'
2233
+ ? {
2234
+ permissionMode: normalizeExecutionPermissionMode(callPermissionMode),
2235
+ role: runPermissionContext?.role,
2236
+ state: 'off',
2237
+ reason: 'phase-one-required',
2238
+ }
2239
+ : resolvePhaseOneExecutionSandbox(callPermissionMode, runPermissionContext?.role);
2190
2240
  const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
2191
2241
  if (executionSandbox.state !== 'off') {
2192
2242
  const activeSandboxFailure = getActiveSandboxInitializationFailure(undefined, undefined, sessionId);
@@ -2341,6 +2391,19 @@ export class AgentRunner {
2341
2391
  delete toolInput.pages;
2342
2392
  }
2343
2393
  let updatedInput = toolInput === originalInput ? undefined : toolInput;
2394
+ // 系统会话:模块判定即最终结论,不进入下面任何按角色 / permissionMode 的分支
2395
+ const systemSessionCheck = this.permissionContexts.get(sessionId)?.systemSessionCheck;
2396
+ if (systemSessionCheck) {
2397
+ const decision = systemSessionCheck(toolName, toolInput);
2398
+ if (!decision.allow) {
2399
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, decision.code);
2400
+ return hookDecision('deny', decision.reason, updatedInput);
2401
+ }
2402
+ // ec 命令与普通托管 ec 命令一样在沙箱外执行(需要连 daemon IPC);只读查询留在沙箱内
2403
+ return hookDecision('allow', undefined, decision.ecCommand
2404
+ ? { ...toolInput, dangerouslyDisableSandbox: true }
2405
+ : updatedInput);
2406
+ }
2344
2407
  if (callPermissionMode === 'fullaccess') {
2345
2408
  if (!hasTrustedFullAccessContext(this.permissionContexts.get(sessionId))) {
2346
2409
  return hookDecision('deny', 'fullaccess execution authorization is no longer active', updatedInput);
@@ -2359,7 +2422,20 @@ export class AgentRunner {
2359
2422
  });
2360
2423
  }
2361
2424
  // proactive 模式行为策略(首次工具调用必须是 ec msg send)
2362
- const policyResult = this.permissionContexts.get(sessionId)?.policyHook?.(toolName, toolInput);
2425
+ const sessionPolicyHook = this.permissionContexts.get(sessionId)?.policyHook;
2426
+ let policyResult;
2427
+ if (sessionPolicyHook) {
2428
+ // 钩子可能先于流里的 tool_use 事件到达:按 tool_use_id 取模型返回序号,取不到短等(最多 200ms)
2429
+ const policyCallId = (input.tool_use_id ?? input.toolUseID);
2430
+ let policyTurn = policyCallId ? this.toolTurnByCallId.get(policyCallId) : undefined;
2431
+ for (let waited = 0; policyCallId && policyTurn === undefined && waited < 200; waited += 20) {
2432
+ await new Promise(resolve => setTimeout(resolve, 20));
2433
+ policyTurn = this.toolTurnByCallId.get(policyCallId);
2434
+ }
2435
+ if (policyCallId)
2436
+ this.toolTurnByCallId.delete(policyCallId);
2437
+ policyResult = sessionPolicyHook(toolName, toolInput, { turn: policyTurn });
2438
+ }
2363
2439
  if (policyResult?.block) {
2364
2440
  await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, policyResult.policyCode ?? 'session_policy_hook');
2365
2441
  return hookDecision('deny', policyResult.reason || '当前会话策略拒绝此工具调用', updatedInput);
@@ -2408,6 +2484,7 @@ export class AgentRunner {
2408
2484
  const readonlyContext = {
2409
2485
  sessionId,
2410
2486
  managedTempDir: permCtx?.managedTempDir,
2487
+ grantedReadRoots: permCtx?.grantedReadRoots,
2411
2488
  channel: permCtx?.channel,
2412
2489
  peerId: permCtx?.userId,
2413
2490
  role: permCtx?.role,
@@ -2471,6 +2548,20 @@ export class AgentRunner {
2471
2548
  // SDK-level canUseTool 回调:接入 PermissionGateway 的用户审批入口
2472
2549
  // 黑名单已在 PreToolUse hook 拦截,危险命令在此处进入审批流程
2473
2550
  const canUseToolCallback = async (toolName, input, options) => {
2551
+ // 系统会话:与 PreToolUse 同一判定,SDK 若仍走到这里也不进入交互 / 角色分支
2552
+ const systemSessionCheck = this.permissionContexts.get(sessionId)?.systemSessionCheck;
2553
+ if (systemSessionCheck) {
2554
+ const decision = systemSessionCheck(toolName, input);
2555
+ if (!decision.allow) {
2556
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, decision.code);
2557
+ return { behavior: 'deny', message: decision.reason, decisionClassification: 'user_reject' };
2558
+ }
2559
+ return {
2560
+ behavior: 'allow',
2561
+ updatedInput: decision.ecCommand ? { ...input, dangerouslyDisableSandbox: true } : input,
2562
+ decisionClassification: 'user_permanent',
2563
+ };
2564
+ }
2474
2565
  // 特殊处理:AskUserQuestion 工具(SDK 内置的用户交互工具)
2475
2566
  // 这不是权限审批,而是收集用户答案,需要构造表单卡片
2476
2567
  if (toolName === 'AskUserQuestion') {
@@ -2585,6 +2676,7 @@ export class AgentRunner {
2585
2676
  const readonlyContext = {
2586
2677
  sessionId,
2587
2678
  managedTempDir: permCtx?.managedTempDir,
2679
+ grantedReadRoots: permCtx?.grantedReadRoots,
2588
2680
  channel: permCtx?.channel,
2589
2681
  peerId: permCtx?.userId,
2590
2682
  role: permCtx?.role,
@@ -2673,7 +2765,8 @@ export class AgentRunner {
2673
2765
  else {
2674
2766
  logger.info(`[AgentRunner] systemPromptAppend: none`);
2675
2767
  }
2676
- const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel), runCatalog);
2768
+ const catalogModel = resolveCatalogModel(runCatalog, callModel);
2769
+ const { sdkModel, contextWindow } = resolveClaudeModelContext(catalogModel, runCatalog);
2677
2770
  const capabilityOptions = await this.resolveCapabilityRunOptions(projectPath);
2678
2771
  const managedSettings = callPermissionMode === 'fullaccess'
2679
2772
  ? undefined
@@ -2760,7 +2853,7 @@ export class AgentRunner {
2760
2853
  ...(executionSandbox.state === 'off' && callPermissionMode !== 'fullaccess' && process.platform === 'linux' ? {
2761
2854
  spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr, runPermissionContext?.selfAid ?? this.agentAid),
2762
2855
  } : {}),
2763
- autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel),
2856
+ autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel, contextWindow),
2764
2857
  advisorModel: 'haiku',
2765
2858
  canUseTool: canUseToolCallback,
2766
2859
  permissionMode: sdkPermissionMode,
@@ -2986,7 +3079,7 @@ export class AgentRunner {
2986
3079
  this.interruptFns.set(sessionId, () => sdkStream.interrupt());
2987
3080
  }
2988
3081
  // 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
2989
- const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding, recoverySessionId);
3082
+ const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding, recoverySessionId, contextWindow);
2990
3083
  const self = this;
2991
3084
  return (async function* () {
2992
3085
  try {
@@ -3184,11 +3277,14 @@ export class AgentRunner {
3184
3277
  const catalog = useModel ? await this.getModelCatalog() : undefined;
3185
3278
  if (catalog)
3186
3279
  assertCatalogSelectable(catalog);
3280
+ const resolvedModel = catalog && callModel
3281
+ ? resolveClaudeModelContext(resolveCatalogModel(catalog, callModel), catalog)
3282
+ : undefined;
3187
3283
  return query({
3188
3284
  prompt,
3189
3285
  options: {
3190
3286
  cwd: projectPath,
3191
- ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel), catalog) } : {}),
3287
+ ...(resolvedModel ? { model: resolvedModel.sdkModel } : {}),
3192
3288
  resume: agentSessionId,
3193
3289
  maxTurns: 1,
3194
3290
  tools: [],
@@ -745,6 +745,9 @@ export class CodexAppServerClient {
745
745
  }
746
746
  buildProcessArgs() {
747
747
  const args = ['app-server', '--listen', 'stdio://'];
748
+ // 记忆只走 EvolCore(ec memory):显式关闭 Codex 原生 memories,
749
+ // 不依赖其默认值(当前 stable 默认 false,此处写死防未来翻转)。
750
+ args.push('--config', 'features.memories=false');
748
751
  if (this.options.enableRequestUserInput !== false) {
749
752
  args.push('--enable', 'default_mode_request_user_input');
750
753
  }
@@ -2,7 +2,9 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
+ import { createHash } from 'node:crypto';
5
6
  import { parse as parseToml } from 'smol-toml';
7
+ const managedHookInstaller = await import(new URL('../../../bin/install-codex-managed-hooks.mjs', import.meta.url).href);
6
8
  export function checkCodexProjectPath(value, options = {}) {
7
9
  const configured = typeof value === 'string' ? value.trim() : '';
8
10
  if (!configured) {
@@ -63,12 +65,6 @@ function decodeProbeOutput(buffer) {
63
65
  return new TextDecoder('gb18030').decode(buffer);
64
66
  }
65
67
  }
66
- function windowsCommandArg(value) {
67
- return '"' + value.replaceAll('"', '\\"') + '"';
68
- }
69
- function expectedWindowsHookCommand(nodePath, hookPath) {
70
- return windowsCommandArg(nodePath) + ' ' + windowsCommandArg(hookPath) + ' %EVOLCORE_INSTANCE_SOCKET%';
71
- }
72
68
  /**
73
69
  * Run synthetic Windows shell probes for the managed Codex hook.
74
70
  *
@@ -102,13 +98,20 @@ export function runCodexHookLaunchDiagnostic(options = {}) {
102
98
  throw new Error('No Windows managed hook directory configured.');
103
99
  const managedDir = path.resolve(options.managedDir || configuredDir);
104
100
  const hookPath = path.join(managedDir, 'pre-tool-use.mjs');
105
- const expected = expectedWindowsHookCommand(nodePath, hookPath);
101
+ let expected;
106
102
  const handlers = Array.isArray(config.hooks?.PreToolUse)
107
103
  ? config.hooks.PreToolUse.flatMap((group) => group?.hooks || [])
108
104
  : [];
109
- const handler = handlers.find((item) => (item?.type === 'command'
110
- && (item.command_windows ?? item.commandWindows) === expected));
111
- if (!handler || /[\r\n%;&|<>]/.test(nodePath + hookPath)) {
105
+ const handler = handlers.find((item) => {
106
+ if (item?.type !== 'command' || item.async === true)
107
+ return false;
108
+ const command = parseManagedCommand(item.command_windows ?? item.commandWindows, hookPath);
109
+ if (!command || !sameRuntimePath(command.nodePath, nodePath, options.platform ?? process.platform))
110
+ return false;
111
+ expected = item.command_windows ?? item.commandWindows;
112
+ return true;
113
+ });
114
+ if (!handler || !expected || /[\r\n%;&|<>]/.test(nodePath + hookPath)) {
112
115
  throw new Error('Managed command differs from the expected EvolCore command for this Node path; no hooks executed.');
113
116
  }
114
117
  fs.mkdirSync(path.dirname(output), { recursive: true, mode: 0o700 });
@@ -180,19 +183,6 @@ function realDirectoryState(directory) {
180
183
  return 'unavailable';
181
184
  }
182
185
  }
183
- /** Read back the package root that managedHookLauncher() embedded. */
184
- function launcherPackageRoot(launcher) {
185
- const match = /^\s*const packageRoot = ("(?:[^"\\]|\\.)*")\s*;/m.exec(launcher);
186
- if (!match)
187
- return null;
188
- try {
189
- const value = JSON.parse(match[1]);
190
- return typeof value === 'string' && value ? value : null;
191
- }
192
- catch {
193
- return null;
194
- }
195
- }
196
186
  /**
197
187
  * A `npm link` install reaches the package through a symlinked global path, so
198
188
  * the launcher and the running CLI legitimately spell the same package root
@@ -213,6 +203,45 @@ function samePackageRoot(embedded, expected) {
213
203
  };
214
204
  return normalize(embedded) === normalize(expected) || resolved(embedded) === resolved(expected);
215
205
  }
206
+ /**
207
+ * Windows accepts an executable path without its `.exe` suffix. Older
208
+ * installations may therefore bind `...\\node` while the current Node
209
+ * process reports `...\\node.exe`; these are the same runtime and must not
210
+ * trigger an elevated repair on every startup.
211
+ */
212
+ function sameRuntimePath(embedded, expected, platform) {
213
+ if (samePackageRoot(embedded, expected))
214
+ return true;
215
+ if (platform !== 'win32' || !embedded)
216
+ return false;
217
+ const appendExe = (value) => path.extname(value) ? value : `${value}.exe`;
218
+ return samePackageRoot(appendExe(embedded), expected)
219
+ || samePackageRoot(embedded, appendExe(expected));
220
+ }
221
+ function normalizedPath(value) {
222
+ return path.normalize(String(value || '')).replace(/[\\/]+$/, '').toLowerCase();
223
+ }
224
+ function parseManagedCommand(value, hookPath) {
225
+ if (typeof value !== 'string')
226
+ return null;
227
+ const match = /^"([^"\r\n]+)"\s+"([^"\r\n]+)"\s+%EVOLCORE_INSTANCE_SOCKET%$/i.exec(value.trim());
228
+ if (!match || !path.isAbsolute(match[1]) || /[%!]/.test(match[1] + match[2]))
229
+ return null;
230
+ if (normalizedPath(match[2]) !== normalizedPath(hookPath))
231
+ return null;
232
+ return { nodePath: match[1] };
233
+ }
234
+ function runtimeFileMayExist(file) {
235
+ try {
236
+ return fs.statSync(file).isFile();
237
+ }
238
+ catch (error) {
239
+ return !['ENOENT', 'ENOTDIR'].includes(error?.code || '');
240
+ }
241
+ }
242
+ function managedBinding(source, launcher, hookPath) {
243
+ return managedHookInstaller.managedBinding(source, launcher, hookPath);
244
+ }
216
245
  /** Read-only inspection of the machine-wide Windows Codex managed hook. */
217
246
  export function inspectCodexManagedHook(options = {}) {
218
247
  const paths = codexManagedHookPaths(options);
@@ -283,32 +312,35 @@ export function inspectCodexManagedHook(options = {}) {
283
312
  catch (error) {
284
313
  return { status: 'unavailable', requirements, managedDir, hookPath, reason: error instanceof Error ? error.message : String(error) };
285
314
  }
286
- const normalized = (value) => path.normalize(String(value || '')).toLowerCase();
287
315
  const expectedPackageRoot = path.resolve(options.packageRoot || process.cwd());
288
- const launcherValid = launcher.includes('EVOLCORE_MANAGED_HOOK_LAUNCHER')
289
- && samePackageRoot(launcherPackageRoot(launcher), expectedPackageRoot);
290
- const configuredDir = config.hooks?.windows_managed_dir ?? config.hooks?.managed_dir;
291
- const groups = config.hooks?.PreToolUse;
292
- const canonicalCommand = (value) => {
293
- if (typeof value !== 'string' || !value || /[\r\n;&|<>]/.test(value))
294
- return false;
295
- const withoutSocket = value.replace(/\s+%EVOLCORE_INSTANCE_SOCKET%\s*$/i, '').trim();
296
- return withoutSocket.replace(/["']+$/, '').toLowerCase().endsWith(normalized(hookPath));
297
- };
298
- const hookLoaded = Array.isArray(groups) && groups.some((group) => group?.matcher === '*'
299
- && Array.isArray(group.hooks) && group.hooks.some((handler) => {
300
- const command = typeof handler?.command === 'string' ? handler.command : '';
301
- const windowsCommand = typeof handler?.command_windows === 'string' ? handler.command_windows : '';
302
- return handler?.type === 'command' && handler.async !== true
303
- && canonicalCommand(command) && canonicalCommand(windowsCommand);
304
- }));
305
- const valid = config.allow_managed_hooks_only === true
306
- && config.features?.hooks === true
307
- && normalized(configuredDir) === normalized(managedDir)
308
- && launcherValid && hookLoaded;
309
- return valid
310
- ? { status: 'installed', requirements, managedDir, hookPath }
311
- : { status: 'unsafe', requirements, managedDir, hookPath, reason: 'managed-hook policy is missing, altered, or points outside the canonical target' };
316
+ try {
317
+ const binding = managedBinding(source, launcher, hookPath);
318
+ if (!binding || config.allow_managed_hooks_only !== true || config.features?.hooks !== true
319
+ || normalizedPath(config.hooks?.windows_managed_dir) !== normalizedPath(managedDir)
320
+ || (config.hooks?.managed_dir !== undefined
321
+ && normalizedPath(config.hooks.managed_dir) !== normalizedPath(managedDir))) {
322
+ throw new Error('managed-hook policy is missing, altered, or points outside the canonical target');
323
+ }
324
+ const { embeddedPackageRoot, managedNodePath } = binding;
325
+ const packageMatches = samePackageRoot(embeddedPackageRoot, expectedPackageRoot);
326
+ const nodeMatches = sameRuntimePath(managedNodePath, options.nodePath || process.execPath, options.platform ?? process.platform);
327
+ const repairFingerprint = createHash('sha256').update(source).update('\0').update(launcher).digest('hex');
328
+ if (packageMatches && nodeMatches)
329
+ return { status: 'installed', requirements, managedDir, hookPath };
330
+ return {
331
+ status: 'stale', requirements, managedDir, hookPath,
332
+ managedPackageRoot: embeddedPackageRoot, managedNodePath, repairFingerprint,
333
+ repairAmbiguous: runtimeFileMayExist(path.join(embeddedPackageRoot, 'bin', 'codex-managed-hook.mjs'))
334
+ && runtimeFileMayExist(managedNodePath),
335
+ reason: !packageMatches && !nodeMatches
336
+ ? 'managed-hook points to a different EvolCore package root and Node runtime'
337
+ : !packageMatches ? 'managed-hook points to a different EvolCore package root'
338
+ : 'managed-hook points to a different Node runtime',
339
+ };
340
+ }
341
+ catch (error) {
342
+ return { status: 'unsafe', requirements, managedDir, hookPath, reason: error instanceof Error ? error.message : String(error) };
343
+ }
312
344
  }
313
345
  function powershellLiteral(value) {
314
346
  return `'${value.replaceAll("'", "''")}'`;
@@ -319,7 +351,10 @@ function windowsArgument(value) {
319
351
  }
320
352
  export function buildCodexManagedHookElevationCommand(options) {
321
353
  const argumentLine = [options.installer, '--package-root', options.packageRoot,
322
- '--requirements', options.requirements, '--managed-dir', options.managedDir, '--repair']
354
+ '--requirements', options.requirements, '--managed-dir', options.managedDir,
355
+ '--node-path', options.nodePath, '--repair',
356
+ ...(options.expectedFingerprint ? ['--expected-fingerprint', options.expectedFingerprint] : []),
357
+ ...(options.confirmRebind ? ['--confirm-rebind'] : [])]
323
358
  .map(windowsArgument).join(' ');
324
359
  return [
325
360
  "$ErrorActionPreference = 'Stop'",
@@ -334,11 +369,11 @@ export function buildCodexManagedHookElevationCommand(options) {
334
369
  ].join('\n');
335
370
  }
336
371
  /**
337
- * Ensure the machine-wide Windows hook before starting the daemon. Missing
338
- * files are repaired through one UAC elevation; altered policy remains
372
+ * Ensure the machine-wide Windows hook before starting the daemon. Missing or
373
+ * stale files are repaired through one UAC elevation; altered policy remains
339
374
  * fail-closed and is never overwritten automatically.
340
375
  */
341
- export function ensureCodexManagedHook(options) {
376
+ export async function ensureCodexManagedHook(options) {
342
377
  const platformName = options.platform ?? process.platform;
343
378
  const paths = codexManagedHookPaths({
344
379
  platform: platformName,
@@ -358,8 +393,27 @@ export function ensureCodexManagedHook(options) {
358
393
  let state = inspectCodexManagedHook(inspectOptions);
359
394
  if (state.status === 'installed')
360
395
  return { ...state, repaired: false };
361
- if (state.status !== 'missing')
396
+ if (state.status !== 'missing' && state.status !== 'stale')
362
397
  return { ...state, repaired: false };
398
+ let confirmedRebind = false;
399
+ if (state.status === 'stale' && state.repairAmbiguous) {
400
+ try {
401
+ confirmedRebind = await options.repairDecision?.(state) === true;
402
+ }
403
+ catch (error) {
404
+ return { ...state, repaired: false, error: error instanceof Error ? error.message : String(error) };
405
+ }
406
+ }
407
+ if (state.status === 'stale' && state.repairAmbiguous && !confirmedRebind) {
408
+ return { ...state, repaired: false, cancelled: true, error: 'managed-hook repair cancelled or requires confirmation' };
409
+ }
410
+ const rechecked = inspectCodexManagedHook(inspectOptions);
411
+ if (rechecked.status === 'installed')
412
+ return { ...rechecked, repaired: false };
413
+ if (rechecked.status !== state.status || rechecked.repairFingerprint !== state.repairFingerprint
414
+ || rechecked.repairAmbiguous !== state.repairAmbiguous) {
415
+ return { ...rechecked, repaired: false, error: 'managed-hook state changed during the startup check; retry to confirm the current binding' };
416
+ }
363
417
  const installer = path.join(path.resolve(options.packageRoot), 'bin', 'install-codex-managed-hooks.mjs');
364
418
  if (!fs.existsSync(installer)) {
365
419
  return { ...state, repaired: false, error: `managed-hook installer is missing: ${installer}` };
@@ -370,6 +424,10 @@ export function ensureCodexManagedHook(options) {
370
424
  nodePath: process.execPath, installer: script, packageRoot,
371
425
  requirements: path.resolve(inspectOptions.requirements),
372
426
  managedDir: path.resolve(inspectOptions.managedDir),
427
+ ...(state.status === 'stale' ? {
428
+ expectedFingerprint: state.repairFingerprint,
429
+ confirmRebind: confirmedRebind,
430
+ } : {}),
373
431
  });
374
432
  const result = spawnSync(powershell, [
375
433
  // Do not use -NonInteractive here: Start-Process -Verb RunAs must be
@@ -385,7 +443,7 @@ export function ensureCodexManagedHook(options) {
385
443
  }
386
444
  return { ok: true };
387
445
  });
388
- options.onInstallRequired?.();
446
+ options.onInstallRequired?.(state);
389
447
  let elevated;
390
448
  try {
391
449
  elevated = runElevated(installer, path.resolve(options.packageRoot));