evolcore 0.0.33 → 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 (129) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +12 -1
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +82 -13
  6. package/dist/agents/codex/diagnostics.js +37 -11
  7. package/dist/agents/codex-runner.js +218 -44
  8. package/dist/agents/ecagent-runner.js +73 -20
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/identity.js +1 -1
  11. package/dist/channels/aun.js +16 -6
  12. package/dist/channels/daemon.js +6 -3
  13. package/dist/cli/aun-commands.js +3 -2
  14. package/dist/cli/ctl-command.js +3 -1
  15. package/dist/cli/daemon-commands.js +120 -19
  16. package/dist/cli/fs-command.js +2 -1
  17. package/dist/cli/index.js +20 -6
  18. package/dist/cli/integrity-command.js +196 -0
  19. package/dist/cli/memory-command.js +158 -145
  20. package/dist/cli/thread-command.js +5 -3
  21. package/dist/cli/trigger-command.js +6 -1
  22. package/dist/config/config-field-policy.js +4 -0
  23. package/dist/config/config-manager.js +19 -0
  24. package/dist/config/contact-book-store.js +5 -0
  25. package/dist/config/role-service.js +13 -0
  26. package/dist/config/runtime-env.js +16 -0
  27. package/dist/config-store.js +30 -0
  28. package/dist/core/audit/log-integrity.js +3 -1
  29. package/dist/core/auth/agent-delegation.js +108 -35
  30. package/dist/core/auth/delegation-diagnostics.js +414 -0
  31. package/dist/core/auth/operation-authorizer.js +1 -1
  32. package/dist/core/auth/operation-catalog.js +16 -0
  33. package/dist/core/command/agent-control.js +18 -26
  34. package/dist/core/command/command-handler.js +138 -4
  35. package/dist/core/command/daemon-diagnostics.js +1 -0
  36. package/dist/core/command/menu-catalog.js +3 -0
  37. package/dist/core/command/menu-handler.js +22 -7
  38. package/dist/core/command/menu-protocol.js +1 -0
  39. package/dist/core/command/slash-handler.js +2 -0
  40. package/dist/core/daemon-bootstrap.js +4 -7
  41. package/dist/core/file-integrity/read.js +61 -0
  42. package/dist/core/file-integrity/store.js +262 -0
  43. package/dist/core/ipc-registration.js +36 -1
  44. package/dist/core/memory/batch-meta.js +132 -0
  45. package/dist/core/memory/consolidation-context.js +35 -14
  46. package/dist/core/memory/consolidation-ops-records.js +14 -16
  47. package/dist/core/memory/consolidation.js +91 -26
  48. package/dist/core/memory/index-manager.js +19 -28
  49. package/dist/core/memory/memory-admin.js +19 -14
  50. package/dist/core/memory/memory-document.js +53 -9
  51. package/dist/core/memory/memory-writer.js +30 -15
  52. package/dist/core/message/im-renderer.js +20 -4
  53. package/dist/core/message/message-bridge.js +2 -1
  54. package/dist/core/message/response-engine.js +584 -47
  55. package/dist/core/permission/ec-command-parser.js +93 -4
  56. package/dist/core/permission/protected-paths.js +5 -1
  57. package/dist/core/permission/readonly-shell-query.js +43 -5
  58. package/dist/core/permission/session-path-grants.js +334 -0
  59. package/dist/core/permission/tool-canonical.js +226 -0
  60. package/dist/core/permission/tool-policy.js +30 -62
  61. package/dist/core/role/runtime-policy.js +21 -10
  62. package/dist/core/session/session-fs-store.js +123 -8
  63. package/dist/core/session/session-manager.js +75 -5
  64. package/dist/core/skill/index-generator.js +17 -3
  65. package/dist/core/system-session/grants.js +40 -0
  66. package/dist/core/system-session/memory.js +222 -0
  67. package/dist/core/system-session/registry.js +74 -0
  68. package/dist/core/system-session/types.js +14 -0
  69. package/dist/core/thread/thread-service.js +13 -20
  70. package/dist/eck/kit-renderer.js +7 -2
  71. package/dist/eck/manifest-engine.js +24 -4
  72. package/dist/index.js +11 -2
  73. package/dist/ipc.js +51 -40
  74. package/dist/paths.js +70 -0
  75. package/dist/stats/billing.js +20 -8
  76. package/dist/trigger/feedback.js +6 -1
  77. package/dist/trigger/parser.js +8 -1
  78. package/dist/trigger/system-definitions.js +3 -3
  79. package/dist/utils/cross-platform.js +11 -2
  80. package/dist/utils/error-dict.json +18 -4
  81. package/dist/utils/error-utils.js +121 -0
  82. package/dist/utils/instance-registry.js +13 -8
  83. package/dist/utils/log-writer.js +12 -0
  84. package/dist/utils/process-introspect.js +11 -0
  85. package/kits/docs/context-assembly.md +0 -1
  86. package/kits/docs/evolcore/agent-create.md +5 -4
  87. package/kits/docs/evolcore/agent.md +2 -2
  88. package/kits/docs/evolcore/config.md +2 -2
  89. package/kits/docs/evolcore/ctl.md +2 -2
  90. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  91. package/kits/docs/evolcore/fs-architecture.md +4 -5
  92. package/kits/docs/evolcore/fs.md +1 -1
  93. package/kits/docs/evolcore/group-collaboration.md +2 -2
  94. package/kits/docs/evolcore/group.md +10 -10
  95. package/kits/docs/evolcore/memory-architecture.md +4 -5
  96. package/kits/docs/evolcore/memory-consolidation.md +18 -14
  97. package/kits/docs/evolcore/memory-tags.md +3 -1
  98. package/kits/docs/evolcore/memory.md +24 -12
  99. package/kits/docs/evolcore/model.md +16 -0
  100. package/kits/docs/evolcore/msg.md +7 -7
  101. package/kits/docs/evolcore/thread-chain.md +5 -5
  102. package/kits/docs/prompt-loading-architecture.md +0 -1
  103. package/kits/eck_manifest.json +0 -20
  104. package/kits/eck_manifest.memory.json +0 -12
  105. package/kits/rules/01-overview.md +11 -0
  106. package/kits/rules/03-identity.md +6 -3
  107. package/kits/rules/06-channel.md +1 -1
  108. package/kits/rules/07-memory-and-thread.md +12 -15
  109. package/kits/schemas/_meta.json +7 -1
  110. package/kits/schemas/agent-config.schema.13.json +1 -1
  111. package/kits/schemas/daemon.schema.7.json +140 -0
  112. package/kits/skills/ec-group/SKILL.md +4 -4
  113. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  114. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  115. package/kits/templates/memory-consolidation/forget.md +2 -2
  116. package/kits/templates/memory-consolidation/generalize.md +4 -4
  117. package/kits/templates/memory-consolidation/notes.md +11 -11
  118. package/kits/templates/memory-consolidation/profile.md +3 -3
  119. package/kits/templates/memory-consolidation/promote.md +3 -3
  120. package/kits/templates/memory-consolidation/workflow.md +6 -6
  121. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  122. package/kits/templates/system-fragments/bootstrap.md +4 -3
  123. package/kits/templates/system-fragments/channel.md +2 -2
  124. package/kits/templates/system-fragments/memory.md +16 -9
  125. package/kits/templates/system-fragments/reply.md +6 -6
  126. package/kits/templates/system-fragments/session.md +1 -1
  127. package/package.json +4 -3
  128. package/dist/core/memory/memory-session-policy.js +0 -41
  129. package/dist/core/memory/memory-signature.js +0 -257
package/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.0.34 (2026-10-18)
9
+
10
+ ### 模型与上下文
11
+
12
+ - 统一 Claude Code、Codex 和 ecagent 的模型参数解析与上下文窗口统计,减少执行窗口、压缩阈值和用量展示之间的不一致。
13
+ - Claude Code 和 ecagent 按模型参数表应用上下文窗口与输出上限;Codex 优先使用 app-server 提供的实时窗口,缺失时仅补充统计信息,不虚构执行窗口。
14
+ - 统一模型窗口后缀、模型参数覆盖和查询/压缩路径的处理,支持新模型参数在不修改 runner 代码的情况下生效。
15
+
16
+ ### 会话与工具
17
+
18
+ - 完善 Codex 排队任务取消及中断后的状态核验,避免停止操作影响错误轮次。
19
+ - 改进 Shell 工具结果处理,分别保留实际退出码、原始输出和错误摘要,避免将普通输出误当作失败原因。
20
+ - 新增任务授权与会话凭据生命周期诊断,提供可追踪的失效原因和事件信息。
21
+ - 闲置巡检覆盖私聊与群聊,开启前校验必要配置,仅针对未完成且停滞的事项推进或提醒。
22
+ - 补充任务运行结果的结构化错误类型、终止原因和原始诊断信息,区分超时、输出上限、协议不完整及目标会话丢失等终态。
23
+
24
+ ### 错误反馈与运行稳定性
25
+
26
+ - 细化 AUN 错误反馈展示,保留原始错误详情并避免将未知 403 错误套用无差别的重试或“API 访问受限”提示。
27
+ - 对客户端过旧、积分不足、团队上下文超限等已知 403 原因提供针对性提示;未知原因保留诊断信息并停止自动猜测。
28
+ - 支持目标会话丢失、触发器超时和输出上限等错误在消息协议中携带稳定的 `subtype` 与 `reason`。
29
+ - 抑制 CLI 子进程的 Node.js 实验性 SQLite 警告,减少正常命令输出噪声。
30
+ - 支持创建 AID 时明确登记主体类型,避免身份类型缺失。
31
+
32
+ ### 管理与发布
33
+
34
+ - 支持同时具备目标 Agent Owner 与 Daemon Owner 身份的用户,通过菜单配置 Agent Fullaccess。
35
+ - Trigger 创建支持填写说明,并在列表和详情中展示。
36
+ - 优化 Windows 进程探测与启动、停止速度,修正 Node 路径识别,并统一 Claude 可用性检测与实际执行程序。
37
+ - 统一列表类命令参数的解析,减少 PowerShell 下逗号分隔参数与审批校验不一致的问题。
38
+ - 升级 AUN SDK 至 `0.5.20`,并收紧 npm 包发布内容,避免将内部文件完整性写入模块打入发行包。
39
+
40
+ ### 兼容性调整
41
+
42
+ - 首次设定的协作交接统一写入 `persona.md`;`personal.md` 不再自动注入上下文。
43
+ - 移除 `ec memory feedback` 命令,整理反馈改由专用会话按任务要求写入反馈文件。
44
+
8
45
  ## 0.0.33 (2026-10-15)
9
46
 
10
47
  ### 话题与会话
package/bin/ec.js CHANGED
@@ -11,6 +11,7 @@ const repoRoot = path.resolve(here, '..');
11
11
  const srcEntry = path.join(repoRoot, 'src', 'cli', 'index.ts');
12
12
  const distEntry = path.join(repoRoot, 'dist', 'cli', 'index.js');
13
13
  const args = process.argv.slice(2);
14
+ const nodeOptions = ['--no-warnings=ExperimentalWarning'];
14
15
 
15
16
  // tsx derives its cache directory from `process.geteuid()` on POSIX and
16
17
  // `os.userInfo()` on Windows. In managed Windows sessions the latter can
@@ -26,13 +27,13 @@ if (fs.existsSync(srcEntry)) {
26
27
  const imports = tsxWindowsCompatImport
27
28
  ? ['--import', tsxWindowsCompatImport, '--import', tsxImport]
28
29
  : ['--import', tsxImport];
29
- const result = spawnSync(process.execPath, [...imports, srcEntry, ...args], { stdio: 'inherit' });
30
+ const result = spawnSync(process.execPath, [...nodeOptions, ...imports, srcEntry, ...args], { stdio: 'inherit' });
30
31
  process.exit(result.status ?? (result.error ? 1 : 0));
31
32
  } catch {}
32
33
  }
33
34
 
34
35
  if (fs.existsSync(distEntry)) {
35
- const result = spawnSync(process.execPath, [distEntry, ...args], { stdio: 'inherit' });
36
+ const result = spawnSync(process.execPath, [...nodeOptions, distEntry, ...args], { stdio: 'inherit' });
36
37
  process.exit(result.status ?? (result.error ? 1 : 0));
37
38
  }
38
39
 
@@ -285,6 +285,17 @@ export function samePackageRoot(embedded, expected) {
285
285
  return normalize(embedded) === normalize(expected) || resolved(embedded) === resolved(expected);
286
286
  }
287
287
 
288
+ // Windows accepts an executable path without its `.exe` suffix. Keep an older
289
+ // `...\\node` binding from being treated as a different runtime than
290
+ // `...\\node.exe` during installer verification.
291
+ function sameRuntimePath(embedded, expected) {
292
+ if (samePackageRoot(embedded, expected)) return true;
293
+ if (process.platform !== 'win32' || !embedded) return false;
294
+ const appendExe = value => path.extname(value) ? value : `${value}.exe`;
295
+ return samePackageRoot(appendExe(embedded), expected)
296
+ || samePackageRoot(embedded, appendExe(expected));
297
+ }
298
+
288
299
  function normalizedPath(value) {
289
300
  return path.normalize(String(value || '')).replace(/[\\/]+$/, '').toLowerCase();
290
301
  }
@@ -391,7 +402,7 @@ export function inspectManagedHookInstallation({ requirements, managedDir, packa
391
402
  }
392
403
  const { embeddedPackageRoot, managedNodePath } = binding;
393
404
  const packageMatches = samePackageRoot(embeddedPackageRoot, path.resolve(packageRoot || process.cwd()));
394
- const nodeMatches = samePackageRoot(managedNodePath, nodePath);
405
+ const nodeMatches = sameRuntimePath(managedNodePath, nodePath);
395
406
  const state = {
396
407
  requirements: canonicalRequirements, managedDir: canonicalManagedDir, hookPath,
397
408
  managedPackageRoot: embeddedPackageRoot, managedNodePath,
@@ -1,39 +1,17 @@
1
1
  import { normalizeBaseagent } from './baseagent.js';
2
2
  import { commandExists } from '../utils/cross-platform.js';
3
3
  import { getCodexAppServerAvailability } from './codex-runner.js';
4
- import { createRequire } from 'module';
5
- import fs from 'fs';
6
- import path from 'path';
7
- const _resolveModule = createRequire(import.meta.url);
4
+ import { probeBundledClaudeVersion, resolveBundledClaudeExecutable } from './claude-runner.js';
8
5
  /**
9
- * Check whether the bundled Claude SDK native binary is present on disk.
10
- * This covers Windows installations where the npm global shim may be absent
11
- * but the SDK ships claude.exe alongside evolcore's own node_modules.
6
+ * Claude 的检测判据就是执行判据:会话只会在 Windows 上跑内置的、与 SDK 锁定版本
7
+ * 配套的 claude.exe。PATH 里的全局 claude 执行层从不使用,因此不能作为可用判据——
8
+ * 那个假阳性正是「检测说可用、会话全失败」持续 21 小时没被发现的成因。
12
9
  */
13
- function isBundledClaudeAvailable() {
14
- if (process.platform !== 'win32')
15
- return false;
16
- const nativePackage = process.arch === 'arm64'
17
- ? '@anthropic-ai/claude-agent-sdk-win32-arm64'
18
- : process.arch === 'x64'
19
- ? '@anthropic-ai/claude-agent-sdk-win32-x64'
20
- : undefined;
21
- if (!nativePackage)
22
- return false;
23
- try {
24
- const exe = _resolveModule.resolve(`${nativePackage}/claude.exe`);
25
- if (fs.existsSync(exe))
26
- return true;
27
- }
28
- catch { /* fall through */ }
29
- try {
30
- const sdkEntry = _resolveModule.resolve('@anthropic-ai/claude-agent-sdk');
31
- const exe = path.resolve(path.dirname(sdkEntry), '..', '@anthropic-ai', nativePackage.slice('@anthropic-ai/'.length), 'claude.exe');
32
- return fs.existsSync(exe);
33
- }
34
- catch {
35
- return false;
36
- }
10
+ function claudeExecutable() {
11
+ if (process.platform === 'win32')
12
+ return resolveBundledClaudeExecutable();
13
+ // 非 Windows 平台由 SDK 自己解析平台包,此处沿用命令探测。
14
+ return commandExists('claude') ? 'claude' : undefined;
37
15
  }
38
16
  export const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
39
17
  export const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
@@ -49,19 +27,25 @@ export function detectAvailableBaseagents(codexAvailability = getCodexAppServerA
49
27
  if (baseagent === 'codex')
50
28
  return codexAvailability.available;
51
29
  if (baseagent === 'claude')
52
- return commandExists(baseagent) || isBundledClaudeAvailable();
30
+ return claudeExecutable() !== undefined;
53
31
  return commandExists(baseagent);
54
32
  });
55
33
  }
56
34
  export function detectBaseagentDetails(codexAvailability = getCodexAppServerAvailability()) {
57
- const claude = commandExists('claude') || isBundledClaudeAvailable();
35
+ const claude = claudeExecutable();
36
+ const claudeVersion = claude ? probeBundledClaudeVersion() : undefined;
58
37
  const gemini = commandExists('gemini');
59
38
  return [
60
39
  {
61
40
  id: 'claude',
62
- detected: claude,
63
- detectionSource: claude ? ['PATH'] : [],
64
- reason: claude ? undefined : '未检测到 Claude CLI。',
41
+ detected: claude !== undefined,
42
+ detectionSource: claude ? [process.platform === 'win32' ? 'bundled SDK binary' : 'PATH'] : [],
43
+ reason: claude
44
+ ? undefined
45
+ : process.platform === 'win32'
46
+ ? '未找到内置 claude.exe(@anthropic-ai/claude-agent-sdk-win32-x64)。请重新安装 SDK 的平台包。'
47
+ : '未检测到 Claude CLI。',
48
+ version: claudeVersion,
65
49
  },
66
50
  {
67
51
  id: 'codex',
@@ -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;
@@ -1455,7 +1481,7 @@ export class AgentRunner {
1455
1481
  * SDK 原始事件 → 标准 AgentEvent 转换
1456
1482
  * 所有 SDK 特有的事件类型引用封装在此方法内
1457
1483
  */
1458
- 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) {
1459
1485
  let lastSessionId;
1460
1486
  let pendingSessionId;
1461
1487
  let hasTurnActivity = false;
@@ -1890,7 +1916,7 @@ export class AgentRunner {
1890
1916
  const effectiveModel = callModel ?? this.model;
1891
1917
  const isClaudeModel = isClaudeContextUsageModel(effectiveModel);
1892
1918
  const totalTokens = contextTokensForUsage(u, !!isClaudeModel);
1893
- const contextWindowTokens = realContextWindowForModel(sdkModel);
1919
+ const contextWindowTokens = resolvedContextWindow ?? realContextWindowForModel(sdkModel);
1894
1920
  const autoCompactTokens = autoCompactWindowForModel(sdkModel);
1895
1921
  const aggregateContextUsage = totalTokens > 0 ? {
1896
1922
  totalTokens,
@@ -2200,7 +2226,17 @@ export class AgentRunner {
2200
2226
  if (callPermissionMode === 'fullaccess' && !hasTrustedFullAccessContext(runPermissionContext)) {
2201
2227
  throw new Error('Claude fullaccess requires a trusted per-call execution authorization');
2202
2228
  }
2203
- 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);
2204
2240
  const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
2205
2241
  if (executionSandbox.state !== 'off') {
2206
2242
  const activeSandboxFailure = getActiveSandboxInitializationFailure(undefined, undefined, sessionId);
@@ -2355,6 +2391,19 @@ export class AgentRunner {
2355
2391
  delete toolInput.pages;
2356
2392
  }
2357
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
+ }
2358
2407
  if (callPermissionMode === 'fullaccess') {
2359
2408
  if (!hasTrustedFullAccessContext(this.permissionContexts.get(sessionId))) {
2360
2409
  return hookDecision('deny', 'fullaccess execution authorization is no longer active', updatedInput);
@@ -2435,6 +2484,7 @@ export class AgentRunner {
2435
2484
  const readonlyContext = {
2436
2485
  sessionId,
2437
2486
  managedTempDir: permCtx?.managedTempDir,
2487
+ grantedReadRoots: permCtx?.grantedReadRoots,
2438
2488
  channel: permCtx?.channel,
2439
2489
  peerId: permCtx?.userId,
2440
2490
  role: permCtx?.role,
@@ -2498,6 +2548,20 @@ export class AgentRunner {
2498
2548
  // SDK-level canUseTool 回调:接入 PermissionGateway 的用户审批入口
2499
2549
  // 黑名单已在 PreToolUse hook 拦截,危险命令在此处进入审批流程
2500
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
+ }
2501
2565
  // 特殊处理:AskUserQuestion 工具(SDK 内置的用户交互工具)
2502
2566
  // 这不是权限审批,而是收集用户答案,需要构造表单卡片
2503
2567
  if (toolName === 'AskUserQuestion') {
@@ -2612,6 +2676,7 @@ export class AgentRunner {
2612
2676
  const readonlyContext = {
2613
2677
  sessionId,
2614
2678
  managedTempDir: permCtx?.managedTempDir,
2679
+ grantedReadRoots: permCtx?.grantedReadRoots,
2615
2680
  channel: permCtx?.channel,
2616
2681
  peerId: permCtx?.userId,
2617
2682
  role: permCtx?.role,
@@ -2700,7 +2765,8 @@ export class AgentRunner {
2700
2765
  else {
2701
2766
  logger.info(`[AgentRunner] systemPromptAppend: none`);
2702
2767
  }
2703
- const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel), runCatalog);
2768
+ const catalogModel = resolveCatalogModel(runCatalog, callModel);
2769
+ const { sdkModel, contextWindow } = resolveClaudeModelContext(catalogModel, runCatalog);
2704
2770
  const capabilityOptions = await this.resolveCapabilityRunOptions(projectPath);
2705
2771
  const managedSettings = callPermissionMode === 'fullaccess'
2706
2772
  ? undefined
@@ -2787,7 +2853,7 @@ export class AgentRunner {
2787
2853
  ...(executionSandbox.state === 'off' && callPermissionMode !== 'fullaccess' && process.platform === 'linux' ? {
2788
2854
  spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr, runPermissionContext?.selfAid ?? this.agentAid),
2789
2855
  } : {}),
2790
- autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel),
2856
+ autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel, contextWindow),
2791
2857
  advisorModel: 'haiku',
2792
2858
  canUseTool: canUseToolCallback,
2793
2859
  permissionMode: sdkPermissionMode,
@@ -3013,7 +3079,7 @@ export class AgentRunner {
3013
3079
  this.interruptFns.set(sessionId, () => sdkStream.interrupt());
3014
3080
  }
3015
3081
  // 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
3016
- 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);
3017
3083
  const self = this;
3018
3084
  return (async function* () {
3019
3085
  try {
@@ -3211,11 +3277,14 @@ export class AgentRunner {
3211
3277
  const catalog = useModel ? await this.getModelCatalog() : undefined;
3212
3278
  if (catalog)
3213
3279
  assertCatalogSelectable(catalog);
3280
+ const resolvedModel = catalog && callModel
3281
+ ? resolveClaudeModelContext(resolveCatalogModel(catalog, callModel), catalog)
3282
+ : undefined;
3214
3283
  return query({
3215
3284
  prompt,
3216
3285
  options: {
3217
3286
  cwd: projectPath,
3218
- ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel), catalog) } : {}),
3287
+ ...(resolvedModel ? { model: resolvedModel.sdkModel } : {}),
3219
3288
  resume: agentSessionId,
3220
3289
  maxTurns: 1,
3221
3290
  tools: [],
@@ -65,12 +65,6 @@ function decodeProbeOutput(buffer) {
65
65
  return new TextDecoder('gb18030').decode(buffer);
66
66
  }
67
67
  }
68
- function windowsCommandArg(value) {
69
- return '"' + value.replaceAll('"', '\\"') + '"';
70
- }
71
- function expectedWindowsHookCommand(nodePath, hookPath) {
72
- return windowsCommandArg(nodePath) + ' ' + windowsCommandArg(hookPath) + ' %EVOLCORE_INSTANCE_SOCKET%';
73
- }
74
68
  /**
75
69
  * Run synthetic Windows shell probes for the managed Codex hook.
76
70
  *
@@ -104,13 +98,20 @@ export function runCodexHookLaunchDiagnostic(options = {}) {
104
98
  throw new Error('No Windows managed hook directory configured.');
105
99
  const managedDir = path.resolve(options.managedDir || configuredDir);
106
100
  const hookPath = path.join(managedDir, 'pre-tool-use.mjs');
107
- const expected = expectedWindowsHookCommand(nodePath, hookPath);
101
+ let expected;
108
102
  const handlers = Array.isArray(config.hooks?.PreToolUse)
109
103
  ? config.hooks.PreToolUse.flatMap((group) => group?.hooks || [])
110
104
  : [];
111
- const handler = handlers.find((item) => (item?.type === 'command'
112
- && (item.command_windows ?? item.commandWindows) === expected));
113
- 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)) {
114
115
  throw new Error('Managed command differs from the expected EvolCore command for this Node path; no hooks executed.');
115
116
  }
116
117
  fs.mkdirSync(path.dirname(output), { recursive: true, mode: 0o700 });
@@ -202,9 +203,34 @@ function samePackageRoot(embedded, expected) {
202
203
  };
203
204
  return normalize(embedded) === normalize(expected) || resolved(embedded) === resolved(expected);
204
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
+ }
205
221
  function normalizedPath(value) {
206
222
  return path.normalize(String(value || '')).replace(/[\\/]+$/, '').toLowerCase();
207
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
+ }
208
234
  function runtimeFileMayExist(file) {
209
235
  try {
210
236
  return fs.statSync(file).isFile();
@@ -297,7 +323,7 @@ export function inspectCodexManagedHook(options = {}) {
297
323
  }
298
324
  const { embeddedPackageRoot, managedNodePath } = binding;
299
325
  const packageMatches = samePackageRoot(embeddedPackageRoot, expectedPackageRoot);
300
- const nodeMatches = samePackageRoot(managedNodePath, options.nodePath || process.execPath);
326
+ const nodeMatches = sameRuntimePath(managedNodePath, options.nodePath || process.execPath, options.platform ?? process.platform);
301
327
  const repairFingerprint = createHash('sha256').update(source).update('\0').update(launcher).digest('hex');
302
328
  if (packageMatches && nodeMatches)
303
329
  return { status: 'installed', requirements, managedDir, hookPath };