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.
- package/CHANGELOG.md +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- 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 {
|
|
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
|
-
*
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
...(
|
|
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
|
-
|
|
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) =>
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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,
|
|
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));
|