evolcore 0.0.24 → 0.0.26
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 +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
2
|
+
// Codex/OpenAI API 不认识 Claude Code SDK 的窗口后缀,必须传入基础模型 ID。
|
|
3
|
+
import { stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
2
4
|
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
3
5
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
4
6
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
@@ -9,7 +11,7 @@ import { resolveOpenaiConfig, resolveCodexCliPath } from './baseagent.js';
|
|
|
9
11
|
import { logger } from '../utils/logger.js';
|
|
10
12
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
11
13
|
import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
12
|
-
import { isRetryableError } from '../utils/error-utils.js';
|
|
14
|
+
import { classifyError, ErrorType, isOutputTokenLimitError, isRetryableError } from '../utils/error-utils.js';
|
|
13
15
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
14
16
|
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
15
17
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
@@ -36,19 +38,22 @@ const MIME_EXT = {
|
|
|
36
38
|
'image/webp': '.webp',
|
|
37
39
|
};
|
|
38
40
|
const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
|
|
41
|
+
function toAgentErrorType(message) {
|
|
42
|
+
switch (classifyError(new Error(message))) {
|
|
43
|
+
case ErrorType.CONTEXT_TOO_LONG: return 'context_too_long';
|
|
44
|
+
case ErrorType.AUTH_ERROR: return 'auth';
|
|
45
|
+
case ErrorType.API_ERROR:
|
|
46
|
+
case ErrorType.STREAM_ERROR:
|
|
47
|
+
case ErrorType.SDK_TIMEOUT: return 'network';
|
|
48
|
+
default: return 'unknown';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
export function formatApprovalLogSummary(summary) {
|
|
40
52
|
const singleLine = summary.replace(/\s+/gu, ' ').trim();
|
|
41
53
|
if (singleLine.length <= APPROVAL_LOG_SUMMARY_MAX_LENGTH)
|
|
42
54
|
return singleLine;
|
|
43
55
|
return singleLine.slice(0, APPROVAL_LOG_SUMMARY_MAX_LENGTH - 3).trimEnd() + '...';
|
|
44
56
|
}
|
|
45
|
-
/**
|
|
46
|
-
* 规范化模型 ID:去掉 Claude Code SDK 专有的上下文窗口后缀 [1m]/[200k]
|
|
47
|
-
* Codex/OpenAI API 不认识这些后缀,必须传入标准模型 ID
|
|
48
|
-
*/
|
|
49
|
-
function stripContextWindowSuffix(modelId) {
|
|
50
|
-
return modelId.replace(/\[(?:1m|200k)\]$/i, '');
|
|
51
|
-
}
|
|
52
57
|
// ── Codex 模型目录(动态获取,含 effort) ──
|
|
53
58
|
class AsyncEventQueue {
|
|
54
59
|
queue = [];
|
|
@@ -198,11 +203,13 @@ function parseManagedPreToolUseDenial(value) {
|
|
|
198
203
|
.trim();
|
|
199
204
|
if (!reason)
|
|
200
205
|
reason = 'EvolCore PreToolUse policy denied this tool call';
|
|
201
|
-
const policyCode =
|
|
202
|
-
? '
|
|
203
|
-
:
|
|
204
|
-
? '
|
|
205
|
-
:
|
|
206
|
+
const policyCode = /当前汇报命令(?:未通过规范解析|不符合当前会话)/.test(reason)
|
|
207
|
+
? 'proactive_send_invalid'
|
|
208
|
+
: /工具调用已达到\s*\d+\s*次/.test(reason)
|
|
209
|
+
? 'proactive_tool_report_required'
|
|
210
|
+
: /请先用\s+ec\s+(?:group\s+send|msg\s+send)/.test(reason)
|
|
211
|
+
? 'proactive_first_send_required'
|
|
212
|
+
: 'managed_pre_tool_use_denied';
|
|
206
213
|
return { policyCode, reason };
|
|
207
214
|
}
|
|
208
215
|
function outputTextForManagedHook(value) {
|
|
@@ -442,6 +449,7 @@ export class CodexRunner {
|
|
|
442
449
|
return existing;
|
|
443
450
|
const client = new CodexAppServerClient({
|
|
444
451
|
apiKey: this.resolvedConfig.apiKey,
|
|
452
|
+
authType: this.resolvedConfig.authType,
|
|
445
453
|
baseUrl: this.resolvedConfig.baseUrl,
|
|
446
454
|
provider: this.resolvedConfig.provider,
|
|
447
455
|
headers: this.resolvedConfig.headers,
|
|
@@ -1298,7 +1306,7 @@ export class CodexRunner {
|
|
|
1298
1306
|
}
|
|
1299
1307
|
return Object.fromEntries([...paths].map(candidate => [candidate, 'write']));
|
|
1300
1308
|
}
|
|
1301
|
-
buildPermissionProfileConfig(sessionId, mode, projectPath, bootstrapAgentMdPath) {
|
|
1309
|
+
buildPermissionProfileConfig(sessionId, mode, projectPath, bootstrapAgentMdPath, personalMdPath) {
|
|
1302
1310
|
const profileName = this.permissionProfileName(sessionId);
|
|
1303
1311
|
const managedTempDir = this.ensureSessionTempDir(sessionId);
|
|
1304
1312
|
const network = {
|
|
@@ -1324,6 +1332,9 @@ export class CodexRunner {
|
|
|
1324
1332
|
...(bootstrapAgentMdPath ? {
|
|
1325
1333
|
[path.resolve(bootstrapAgentMdPath)]: mode === 'readonly' ? 'read' : 'write',
|
|
1326
1334
|
} : {}),
|
|
1335
|
+
...(personalMdPath ? {
|
|
1336
|
+
[path.resolve(personalMdPath)]: mode === 'readonly' ? 'read' : 'write',
|
|
1337
|
+
} : {}),
|
|
1327
1338
|
...(mode !== 'readonly' && managedTempDir ? {
|
|
1328
1339
|
[managedTempDir]: 'write',
|
|
1329
1340
|
[managedTempDir + '/**']: 'write',
|
|
@@ -1336,12 +1347,12 @@ export class CodexRunner {
|
|
|
1336
1347
|
},
|
|
1337
1348
|
};
|
|
1338
1349
|
}
|
|
1339
|
-
buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath) {
|
|
1350
|
+
buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath, personalMdPath) {
|
|
1340
1351
|
const decision = resolvePhaseOneExecutionSandbox(mode, this.permissionContexts.get(sessionId)?.role);
|
|
1341
1352
|
return {
|
|
1342
1353
|
decision,
|
|
1343
1354
|
permissionProfileConfig: decision.state === 'active'
|
|
1344
|
-
? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath)
|
|
1355
|
+
? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath, personalMdPath)
|
|
1345
1356
|
: undefined,
|
|
1346
1357
|
sandbox: decision.state === 'off' ? 'danger-full-access' : undefined,
|
|
1347
1358
|
};
|
|
@@ -1758,7 +1769,7 @@ export class CodexRunner {
|
|
|
1758
1769
|
}
|
|
1759
1770
|
this.chatModes.set(sessionId, callMode);
|
|
1760
1771
|
const callApprovalPolicy = this.toApprovalPolicy(callMode);
|
|
1761
|
-
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath);
|
|
1772
|
+
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath);
|
|
1762
1773
|
const appServer = this.getAppServerClient(callMode === 'fullaccess');
|
|
1763
1774
|
this.dropExpiredPendingInterrupt(sessionId);
|
|
1764
1775
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
@@ -2604,7 +2615,7 @@ export class CodexRunner {
|
|
|
2604
2615
|
release();
|
|
2605
2616
|
}
|
|
2606
2617
|
}
|
|
2607
|
-
async forkSessionLocked(agentSessionId, projectPath, title) {
|
|
2618
|
+
async forkSessionLocked(agentSessionId, projectPath, title, lastTurnId) {
|
|
2608
2619
|
const sessionKey = this.findSessionKeyByThread(agentSessionId);
|
|
2609
2620
|
const mode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
2610
2621
|
const executionSandbox = this.buildExecutionSandboxOptions(sessionKey, mode, projectPath);
|
|
@@ -2622,6 +2633,7 @@ export class CodexRunner {
|
|
|
2622
2633
|
approvalsReviewer: 'user',
|
|
2623
2634
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
2624
2635
|
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig(mode)), mode),
|
|
2636
|
+
...(lastTurnId ? { lastTurnId } : {}),
|
|
2625
2637
|
});
|
|
2626
2638
|
const forkedThreadId = response.thread?.id;
|
|
2627
2639
|
if (!forkedThreadId)
|
|
@@ -2636,29 +2648,80 @@ export class CodexRunner {
|
|
|
2636
2648
|
async forkSessionAt(agentSessionId, projectPath, assistantMessageId, title) {
|
|
2637
2649
|
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'forkAt');
|
|
2638
2650
|
try {
|
|
2639
|
-
const
|
|
2651
|
+
const appServer = this.getAppServerClientForThread(agentSessionId);
|
|
2652
|
+
const history = await this.readForkHistory(appServer, agentSessionId);
|
|
2653
|
+
const messages = this.mapThreadToSessionMessages(history, agentSessionId);
|
|
2654
|
+
const turns = buildSessionTurnList(messages);
|
|
2640
2655
|
const targetIndex = turns.findIndex(turn => turn.assistantUuid === assistantMessageId);
|
|
2641
2656
|
if (targetIndex < 0)
|
|
2642
2657
|
throw new Error('fork point not found');
|
|
2643
|
-
const
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
try {
|
|
2648
|
-
const rolledBack = await this.rollbackSessionTurnsLocked(forkedThreadId, turnsToDiscard);
|
|
2649
|
-
if (!rolledBack)
|
|
2650
|
-
throw new Error('forked thread rollback failed');
|
|
2651
|
-
}
|
|
2652
|
-
finally {
|
|
2653
|
-
rollbackRelease();
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
return forkedThreadId;
|
|
2658
|
+
const targetTurnId = this.findTurnIdForAssistantMessage(history, assistantMessageId);
|
|
2659
|
+
if (!targetTurnId)
|
|
2660
|
+
throw new Error('fork point turn not found');
|
|
2661
|
+
return await this.forkSessionLocked(agentSessionId, projectPath, title, targetTurnId);
|
|
2657
2662
|
}
|
|
2658
2663
|
finally {
|
|
2659
2664
|
release();
|
|
2660
2665
|
}
|
|
2661
2666
|
}
|
|
2667
|
+
findTurnIdForAssistantMessage(response, assistantMessageId) {
|
|
2668
|
+
for (const turn of response.thread?.turns ?? []) {
|
|
2669
|
+
if (typeof turn.id !== 'string' || !turn.id)
|
|
2670
|
+
continue;
|
|
2671
|
+
for (const item of this.getTurnItems(turn)) {
|
|
2672
|
+
if (item.type === 'agentMessage' && item.id === assistantMessageId)
|
|
2673
|
+
return turn.id;
|
|
2674
|
+
}
|
|
2675
|
+
// Older app-server histories may omit item ids; the mapper then uses the
|
|
2676
|
+
// turn id as the assistant message id fallback.
|
|
2677
|
+
if (turn.id === assistantMessageId)
|
|
2678
|
+
return turn.id;
|
|
2679
|
+
}
|
|
2680
|
+
return undefined;
|
|
2681
|
+
}
|
|
2682
|
+
async readForkHistory(appServer, threadId) {
|
|
2683
|
+
let directRead;
|
|
2684
|
+
let directReadError;
|
|
2685
|
+
try {
|
|
2686
|
+
directRead = await appServer.threadRead(threadId, true);
|
|
2687
|
+
if ((directRead.thread?.turns?.length ?? 0) > 0)
|
|
2688
|
+
return directRead;
|
|
2689
|
+
}
|
|
2690
|
+
catch (error) {
|
|
2691
|
+
directReadError = error;
|
|
2692
|
+
}
|
|
2693
|
+
// Paginated threads reject full-history thread/read. Hydrate turns through
|
|
2694
|
+
// the experimental paging RPC, retaining full items so assistant message
|
|
2695
|
+
// IDs can be mapped to their containing turn.
|
|
2696
|
+
const turns = [];
|
|
2697
|
+
let cursor;
|
|
2698
|
+
const seenCursors = new Set();
|
|
2699
|
+
try {
|
|
2700
|
+
do {
|
|
2701
|
+
const page = await appServer.threadTurnsList(threadId, 100, cursor);
|
|
2702
|
+
if (Array.isArray(page.data)) {
|
|
2703
|
+
turns.push(...page.data.filter((turn) => !!turn && typeof turn === 'object'));
|
|
2704
|
+
}
|
|
2705
|
+
const nextCursor = typeof page.nextCursor === 'string' && page.nextCursor ? page.nextCursor : undefined;
|
|
2706
|
+
if (!nextCursor || seenCursors.has(nextCursor))
|
|
2707
|
+
break;
|
|
2708
|
+
seenCursors.add(nextCursor);
|
|
2709
|
+
cursor = nextCursor;
|
|
2710
|
+
} while (true);
|
|
2711
|
+
}
|
|
2712
|
+
catch (error) {
|
|
2713
|
+
if (directReadError)
|
|
2714
|
+
throw directReadError;
|
|
2715
|
+
throw error;
|
|
2716
|
+
}
|
|
2717
|
+
// Pages are returned newest-first when following `nextCursor`; restore
|
|
2718
|
+
// chronological order before mapping turns to session messages.
|
|
2719
|
+
if (turns.length > 0)
|
|
2720
|
+
return { thread: { ...(directRead?.thread ?? {}), id: threadId, turns: turns.reverse() } };
|
|
2721
|
+
if (directRead)
|
|
2722
|
+
return directRead;
|
|
2723
|
+
throw directReadError instanceof Error ? directReadError : new Error('Codex thread history unavailable');
|
|
2724
|
+
}
|
|
2662
2725
|
async setSessionName(agentSessionId, name) {
|
|
2663
2726
|
return this.getAppServerClientForThread(agentSessionId).threadSetName(agentSessionId, name);
|
|
2664
2727
|
}
|
|
@@ -4412,24 +4475,46 @@ export class CodexRunner {
|
|
|
4412
4475
|
this.clearPendingToolDenialsForTurn(state.threadId, turnId);
|
|
4413
4476
|
yield* this.reconcileOpenAppServerToolCalls(state, turnId);
|
|
4414
4477
|
if (turn.status === 'failed' && turn.error?.message) {
|
|
4478
|
+
logger.warn(`[CodexRunner] turn failed: ${JSON.stringify({
|
|
4479
|
+
sessionId, threadId: state.threadId, turnId, model: state.model,
|
|
4480
|
+
errorType: classifyError(new Error(turn.error.message)),
|
|
4481
|
+
message: formatApprovalLogSummary(turn.error.message),
|
|
4482
|
+
lastReportedUsage: state.tokenUsage?.last,
|
|
4483
|
+
})}`);
|
|
4415
4484
|
if (isRetryableError(new Error(turn.error.message))) {
|
|
4416
4485
|
throw new Error(turn.error.message);
|
|
4417
4486
|
}
|
|
4418
|
-
yield { type: 'error', error: turn.error.message, errorType:
|
|
4487
|
+
yield { type: 'error', error: turn.error.message, errorType: toAgentErrorType(turn.error.message) };
|
|
4419
4488
|
}
|
|
4420
4489
|
yield this.mapAppServerTurnComplete(turn, state);
|
|
4421
4490
|
break;
|
|
4422
4491
|
}
|
|
4423
4492
|
case 'error': {
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4493
|
+
// Current app-server uses error.message; older builds used message.
|
|
4494
|
+
const message = typeof params.error?.message === 'string' ? params.error.message : params.message;
|
|
4495
|
+
if (typeof message !== 'string' || !message.trim()) {
|
|
4496
|
+
logger.debug('[CodexRunner] app-server error notification without a diagnostic message');
|
|
4427
4497
|
break;
|
|
4428
4498
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4499
|
+
logger.warn(`[CodexRunner] error notification: ${JSON.stringify({
|
|
4500
|
+
sessionId, threadId: state.threadId, turnId: state.turnId, model: state.model,
|
|
4501
|
+
willRetry: params.willRetry ?? null,
|
|
4502
|
+
errorType: classifyError(new Error(message)), message: formatApprovalLogSummary(message),
|
|
4503
|
+
})}`);
|
|
4504
|
+
// Codex owns retries within this turn. Throwing here closes our subscription
|
|
4505
|
+
// before its recovery can finish; runQuery must not submit the input again.
|
|
4506
|
+
if (params.willRetry === true) {
|
|
4507
|
+
yield {
|
|
4508
|
+
type: 'notice', severity: 'warn', subtype: 'codex-model-retry',
|
|
4509
|
+
message: isOutputTokenLimitError(new Error(message))
|
|
4510
|
+
? '模型响应达到输出上限,Codex 正在当前轮内重试。'
|
|
4511
|
+
: 'Codex 正在当前轮内重试模型请求。',
|
|
4512
|
+
};
|
|
4513
|
+
break;
|
|
4431
4514
|
}
|
|
4432
|
-
|
|
4515
|
+
if (isRetryableError(new Error(message)))
|
|
4516
|
+
throw new Error(message);
|
|
4517
|
+
yield { type: 'error', error: message, errorType: toAgentErrorType(message) };
|
|
4433
4518
|
break;
|
|
4434
4519
|
}
|
|
4435
4520
|
}
|
|
@@ -4860,16 +4945,17 @@ export class CodexRunner {
|
|
|
4860
4945
|
}
|
|
4861
4946
|
mapAppServerTurnComplete(turn, state) {
|
|
4862
4947
|
const status = turn.status || 'completed';
|
|
4948
|
+
const outputLimit = status === 'failed' && isOutputTokenLimitError(new Error(turn.error?.message ?? ''));
|
|
4863
4949
|
const tokenUsage = this.mapCodexTokenUsage(state.tokenUsage?.last ?? turn.tokenUsage ?? turn.usage);
|
|
4864
4950
|
const contextUsage = this.mapCodexContextUsage(turn.contextUsage ?? state.tokenUsage?.contextUsage ?? state.tokenUsage?.context);
|
|
4865
4951
|
const terminalReason = status === 'completed'
|
|
4866
4952
|
? undefined
|
|
4867
4953
|
: status === 'interrupted'
|
|
4868
4954
|
? 'aborted_streaming'
|
|
4869
|
-
: status;
|
|
4955
|
+
: outputLimit ? 'max_output_tokens' : status;
|
|
4870
4956
|
return {
|
|
4871
4957
|
type: 'complete',
|
|
4872
|
-
subtype: status === 'completed' ? 'success' : status,
|
|
4958
|
+
subtype: status === 'completed' ? 'success' : outputLimit ? 'max_output_tokens' : status,
|
|
4873
4959
|
isError: status === 'failed',
|
|
4874
4960
|
errors: turn.error?.message ? [turn.error.message] : undefined,
|
|
4875
4961
|
terminalReason,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
2
|
+
// Ecagent/OpenAI API 不认识 Claude Code SDK 的窗口后缀,必须传入基础模型 ID。
|
|
3
|
+
import { stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
2
4
|
import fs from 'fs';
|
|
3
5
|
import path from 'path';
|
|
4
6
|
import { AgentHarness, JsonlSessionRepo, NodeExecutionEnv, } from '../../ecagent/dist/node.js';
|
|
@@ -26,13 +28,6 @@ const MAX_TOOL_OUTPUT = 200_000;
|
|
|
26
28
|
const MAX_SCAN_FILES = 5_000;
|
|
27
29
|
const MAX_SEARCH_RESULTS = 500;
|
|
28
30
|
const DEFAULT_BASH_TIMEOUT_MS = 120_000;
|
|
29
|
-
/**
|
|
30
|
-
* 规范化模型 ID:去掉 Claude Code SDK 专有的上下文窗口后缀 [1m]/[200k]
|
|
31
|
-
* Ecagent/OpenAI API 不认识这些后缀,必须传入标准模型 ID
|
|
32
|
-
*/
|
|
33
|
-
function stripContextWindowSuffix(modelId) {
|
|
34
|
-
return modelId.replace(/\[(?:1m|200k)\]$/i, '');
|
|
35
|
-
}
|
|
36
31
|
class AsyncEventQueue {
|
|
37
32
|
values = [];
|
|
38
33
|
waiters = [];
|
|
@@ -468,26 +463,27 @@ function isWithin(root, candidate) {
|
|
|
468
463
|
const relative = path.relative(root, candidate);
|
|
469
464
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
470
465
|
}
|
|
471
|
-
export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath, fullaccess = false) {
|
|
466
|
+
export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath, fullaccess = false, personalMdPath) {
|
|
472
467
|
const root = fs.realpathSync(projectPath);
|
|
473
468
|
const resolved = path.resolve(root, candidate || '.');
|
|
474
469
|
const allowedBootstrapFile = bootstrapAgentMdPath
|
|
475
470
|
? path.resolve(bootstrapAgentMdPath)
|
|
476
471
|
: undefined;
|
|
477
|
-
const
|
|
478
|
-
|
|
472
|
+
const allowedPersonalFile = personalMdPath ? path.resolve(personalMdPath) : undefined;
|
|
473
|
+
const isManagedIdentityFile = resolved === allowedBootstrapFile || resolved === allowedPersonalFile;
|
|
474
|
+
if (!fullaccess && !isWithin(root, resolved) && !isManagedIdentityFile) {
|
|
479
475
|
throw new Error(`Path is outside the project: ${candidate}`);
|
|
480
476
|
}
|
|
481
|
-
if (
|
|
477
|
+
if (isManagedIdentityFile) {
|
|
482
478
|
const parent = path.dirname(resolved);
|
|
483
479
|
const canonicalParent = fs.realpathSync(parent);
|
|
484
480
|
if (!isWithin(parent, canonicalParent) || !isWithin(canonicalParent, parent)) {
|
|
485
|
-
throw new Error(`
|
|
481
|
+
throw new Error(`Managed identity file parent resolves outside its declared path: ${candidate}`);
|
|
486
482
|
}
|
|
487
483
|
if (fs.existsSync(resolved)) {
|
|
488
484
|
const canonicalFile = fs.realpathSync(resolved);
|
|
489
485
|
if (!isWithin(resolved, canonicalFile) || !isWithin(canonicalFile, resolved)) {
|
|
490
|
-
throw new Error(`
|
|
486
|
+
throw new Error(`Managed identity file resolves outside its declared path: ${candidate}`);
|
|
491
487
|
}
|
|
492
488
|
}
|
|
493
489
|
return resolved;
|
|
@@ -550,7 +546,7 @@ async function walkFiles(root, signal) {
|
|
|
550
546
|
function textResult(text, details = {}) {
|
|
551
547
|
return { content: [{ type: 'text', text: truncate(text) }], details };
|
|
552
548
|
}
|
|
553
|
-
function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false) {
|
|
549
|
+
function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false, personalMdPath) {
|
|
554
550
|
const read = {
|
|
555
551
|
name: 'Read', label: 'Read', description: 'Read a UTF-8 text file from the project.',
|
|
556
552
|
parameters: Type.Object({
|
|
@@ -559,7 +555,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
559
555
|
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
|
560
556
|
}),
|
|
561
557
|
async execute(_id, params, signal) {
|
|
562
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess);
|
|
558
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath);
|
|
563
559
|
const stat = await fs.promises.stat(file);
|
|
564
560
|
if (stat.size > MAX_READ_BYTES)
|
|
565
561
|
throw new Error(`File exceeds ${MAX_READ_BYTES} byte read limit`);
|
|
@@ -625,7 +621,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
625
621
|
name: 'Write', label: 'Write', description: 'Create or completely overwrite a project file.',
|
|
626
622
|
parameters: Type.Object({ file_path: Type.String(), content: Type.String() }), executionMode: 'sequential',
|
|
627
623
|
async execute(_id, params, signal) {
|
|
628
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess);
|
|
624
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess, personalMdPath);
|
|
629
625
|
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
|
630
626
|
await fs.promises.writeFile(file, params.content, { encoding: 'utf8', signal });
|
|
631
627
|
return textResult(`Wrote ${Buffer.byteLength(params.content)} bytes to ${path.relative(projectPath, file)}`, { file });
|
|
@@ -638,7 +634,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
638
634
|
replace_all: Type.Optional(Type.Boolean()),
|
|
639
635
|
}), executionMode: 'sequential',
|
|
640
636
|
async execute(_id, params, signal) {
|
|
641
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess);
|
|
637
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath);
|
|
642
638
|
const value = await fs.promises.readFile(file, { encoding: 'utf8', signal });
|
|
643
639
|
if (!params.old_string)
|
|
644
640
|
throw new Error('old_string must not be empty');
|
|
@@ -858,7 +854,7 @@ export class EcagentRunner {
|
|
|
858
854
|
models,
|
|
859
855
|
model,
|
|
860
856
|
thinkingLevel: normalizeThinkingLevel(effort),
|
|
861
|
-
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess),
|
|
857
|
+
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess, request.modelOverride?.personalMdPath),
|
|
862
858
|
systemPrompt: [DEFAULT_SYSTEM_PROMPT, request.systemPromptAppend].filter(Boolean).join('\n\n'),
|
|
863
859
|
streamOptions: {
|
|
864
860
|
timeoutMs: this.config.timeoutMs,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getCatalog, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
2
|
+
// Gemini API 不认识 Claude Code SDK 的窗口后缀,必须传入基础模型 ID。
|
|
3
|
+
import { stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
2
4
|
/**
|
|
3
5
|
* Gemini Agent Runner
|
|
4
6
|
*
|
|
@@ -33,13 +35,6 @@ const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?(?:\x1b\\|\x07)/g;
|
|
|
33
35
|
function stripAnsi(text) {
|
|
34
36
|
return text.replace(ANSI_RE, '');
|
|
35
37
|
}
|
|
36
|
-
/**
|
|
37
|
-
* 规范化模型 ID:去掉 Claude Code SDK 专有的上下文窗口后缀 [1m]/[200k]
|
|
38
|
-
* Gemini API 不认识这些后缀,必须传入标准模型 ID
|
|
39
|
-
*/
|
|
40
|
-
function stripContextWindowSuffix(modelId) {
|
|
41
|
-
return modelId.replace(/\[(?:1m|200k)\]$/i, '');
|
|
42
|
-
}
|
|
43
38
|
// ── MIME → 扩展名映射 ──
|
|
44
39
|
const MIME_EXT = {
|
|
45
40
|
'image/png': '.png',
|
|
@@ -147,6 +142,7 @@ const GEMINI_EC_COMMAND_PATTERNS = [
|
|
|
147
142
|
// quoted arguments.
|
|
148
143
|
String.raw `\x00"command":"[ ]*ec[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))*"\x00`,
|
|
149
144
|
];
|
|
145
|
+
const GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN = String.raw `^\{\x00"command":"[ ]*ec[ ]+agent[ ]+persona(?:[ ]+show|[ ]+--(?:text|file)[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))+)[ ]*"\x00\}$`;
|
|
150
146
|
export function resolveGeminiPermissionProfile(value, bootstrap = false) {
|
|
151
147
|
if (value === 'fullaccess')
|
|
152
148
|
return { mode: 'fullaccess', approvalMode: 'yolo' };
|
|
@@ -186,6 +182,9 @@ function geminiExactShellCommandPattern(command) {
|
|
|
186
182
|
// is_background or additional_permissions during bootstrap.
|
|
187
183
|
return `^\\{\\x00"command":"[ ]*${escapeGeminiArgsRegex(command)}[ ]*"\\x00\\}$`;
|
|
188
184
|
}
|
|
185
|
+
function geminiManagedIdentityPathPattern(filePath) {
|
|
186
|
+
return geminiJsonFieldEqualsPattern(['file_path'], path.resolve(filePath));
|
|
187
|
+
}
|
|
189
188
|
function appendGeminiPolicyRule(lines, toolName, decision, priority, argsPattern) {
|
|
190
189
|
lines.push('[[rule]]');
|
|
191
190
|
lines.push(`toolName = ${Array.isArray(toolName) ? JSON.stringify(toolName) : JSON.stringify(toolName)}`);
|
|
@@ -194,7 +193,7 @@ function appendGeminiPolicyRule(lines, toolName, decision, priority, argsPattern
|
|
|
194
193
|
lines.push(`decision = "${decision}"`);
|
|
195
194
|
lines.push(`priority = ${priority}`, '');
|
|
196
195
|
}
|
|
197
|
-
export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
|
|
196
|
+
export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath, personalMdPath) {
|
|
198
197
|
const lines = [];
|
|
199
198
|
if (bootstrapAgentMdPath) {
|
|
200
199
|
const bootstrap = normalizeGeminiBootstrapAgentMdPath(bootstrapAgentMdPath);
|
|
@@ -212,6 +211,12 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
|
|
|
212
211
|
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 990, writePathPattern);
|
|
213
212
|
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec aid agentmd put ${bootstrap.aid}`));
|
|
214
213
|
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec agent ready ${bootstrap.aid}`));
|
|
214
|
+
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN);
|
|
215
|
+
if (personalMdPath) {
|
|
216
|
+
const personalPathPattern = geminiManagedIdentityPathPattern(personalMdPath);
|
|
217
|
+
appendGeminiPolicyRule(lines, 'read_file', 'allow', 989, personalPathPattern);
|
|
218
|
+
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 989, personalPathPattern);
|
|
219
|
+
}
|
|
215
220
|
// These are Gemini's non-I/O task bookkeeping tools. They do not expose
|
|
216
221
|
// project files, shell, network, MCP, or user-interaction capabilities.
|
|
217
222
|
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_LIFECYCLE_TOOLS, 'allow', 950);
|
|
@@ -231,6 +236,11 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
|
|
|
231
236
|
for (const pattern of GEMINI_L_CLASS_PATTERNS) {
|
|
232
237
|
appendGeminiPolicyRule(lines, GEMINI_L_CLASS_WRITE_TOOLS, 'deny', 997, pattern);
|
|
233
238
|
}
|
|
239
|
+
if (personalMdPath) {
|
|
240
|
+
const personalPathPattern = geminiManagedIdentityPathPattern(personalMdPath);
|
|
241
|
+
appendGeminiPolicyRule(lines, 'read_file', 'allow', 989, personalPathPattern);
|
|
242
|
+
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 989, personalPathPattern);
|
|
243
|
+
}
|
|
234
244
|
if (profile.mode === 'readonly') {
|
|
235
245
|
appendGeminiPolicyRule(lines, GEMINI_READONLY_TOOLS, 'allow', 950);
|
|
236
246
|
appendGeminiPolicyRule(lines, '*', 'deny', 900);
|
|
@@ -365,6 +375,9 @@ export class GeminiRunner {
|
|
|
365
375
|
const bootstrapAgentMdPath = modelOverride?.bootstrapAgentMdPath
|
|
366
376
|
? normalizeGeminiBootstrapAgentMdPath(modelOverride.bootstrapAgentMdPath).filePath
|
|
367
377
|
: undefined;
|
|
378
|
+
const personalMdPath = modelOverride?.personalMdPath
|
|
379
|
+
? path.resolve(modelOverride.personalMdPath)
|
|
380
|
+
: undefined;
|
|
368
381
|
const permissionProfile = resolveGeminiPermissionProfile(requestedPermissionMode, !!bootstrapAgentMdPath);
|
|
369
382
|
const callModel = modelOverride?.model || this.model;
|
|
370
383
|
const managedTaskTmpDir = getManagedTaskTempDir(runtimeEnv);
|
|
@@ -382,6 +395,7 @@ export class GeminiRunner {
|
|
|
382
395
|
...(runtimeDir ? [runtimeDir] : []),
|
|
383
396
|
...(runtimeLockDir ? [runtimeLockDir] : []),
|
|
384
397
|
...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
|
|
398
|
+
...(personalMdPath ? [personalMdPath] : []),
|
|
385
399
|
],
|
|
386
400
|
});
|
|
387
401
|
if (executionPermissionMode !== 'fullaccess' && !sandboxProbe && workspaceContainsHClassPaths(projectPath)) {
|
|
@@ -410,7 +424,7 @@ export class GeminiRunner {
|
|
|
410
424
|
}
|
|
411
425
|
else {
|
|
412
426
|
const policyPath = path.join(managedTmpDir, `evolcore-gemini-permission-${crypto.randomUUID()}.toml`);
|
|
413
|
-
fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath), { mode: 0o600, flag: 'wx' });
|
|
427
|
+
fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath, personalMdPath), { mode: 0o600, flag: 'wx' });
|
|
414
428
|
tempFiles.push(policyPath);
|
|
415
429
|
args.push(...buildGeminiPermissionArgs(permissionProfile, policyPath, !!sandboxProbe));
|
|
416
430
|
}
|
|
@@ -452,6 +466,7 @@ export class GeminiRunner {
|
|
|
452
466
|
...(runtimeDir ? [runtimeDir] : []),
|
|
453
467
|
...(runtimeLockDir ? [runtimeLockDir] : []),
|
|
454
468
|
...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
|
|
469
|
+
...(personalMdPath ? [personalMdPath] : []),
|
|
455
470
|
],
|
|
456
471
|
readonlyPaths: tempFiles,
|
|
457
472
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseModelId, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
1
2
|
export const BASEAGENT_RUNNER_UNAVAILABLE = 'BASEAGENT_RUNNER_UNAVAILABLE';
|
|
2
3
|
export class BaseagentRunnerUnavailableError extends Error {
|
|
3
4
|
evolagentName;
|
|
@@ -69,20 +70,22 @@ const CLAUDE_CONTEXT_MODEL_ALIASES = ['opus', 'sonnet', 'haiku'];
|
|
|
69
70
|
export function isClaudeContextUsageModel(model) {
|
|
70
71
|
if (!model)
|
|
71
72
|
return false;
|
|
72
|
-
const baseModel = model
|
|
73
|
+
const baseModel = stripContextWindowSuffix(model);
|
|
73
74
|
return /^claude-/i.test(baseModel) || CLAUDE_CONTEXT_MODEL_ALIASES.includes(baseModel);
|
|
74
75
|
}
|
|
75
76
|
export function isOneMillionContextModel(model) {
|
|
76
77
|
if (!model)
|
|
77
78
|
return false;
|
|
79
|
+
// 显式后缀是网关给出的权威窗口,优先于本地前缀表:`claude-opus-5[200k]`
|
|
80
|
+
// 即使基础 ID 在 1M 名单里,也必须按 200k 处理。
|
|
81
|
+
const { base, contextWindow } = parseModelId(model);
|
|
82
|
+
if (contextWindow !== undefined)
|
|
83
|
+
return contextWindow >= 1_000_000;
|
|
78
84
|
if (ONE_M_CONTEXT_MODEL_ALIASES.includes(model))
|
|
79
85
|
return true;
|
|
80
|
-
if (
|
|
86
|
+
if (/deepseek-v4/i.test(base))
|
|
81
87
|
return true;
|
|
82
|
-
|
|
83
|
-
return true;
|
|
84
|
-
const baseModel = model.replace(/\[1m\]$/i, '');
|
|
85
|
-
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => baseModel === prefix || baseModel.startsWith(`${prefix}-`));
|
|
88
|
+
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => base === prefix || base.startsWith(`${prefix}-`));
|
|
86
89
|
}
|
|
87
90
|
/**
|
|
88
91
|
* EvolCore's conservative local context-window catalog: 1M models = 1,000,000;
|
|
@@ -94,17 +97,22 @@ export function realContextWindowForModel(model) {
|
|
|
94
97
|
return isOneMillionContextModel(model) ? 1000000 : 200000;
|
|
95
98
|
}
|
|
96
99
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
+
* EvolCore auto-compact threshold: fixed at 650k tokens regardless of model
|
|
101
|
+
* context window. Large-window models (1M) compress at 650k; small-window
|
|
102
|
+
* models (200k/128k) never reach this threshold and rely on the SDK's own
|
|
103
|
+
* built-in compaction instead.
|
|
100
104
|
*/
|
|
101
|
-
export function
|
|
102
|
-
|
|
103
|
-
return undefined;
|
|
104
|
-
const reserve = Math.min(10_000, Math.floor(contextWindow * 0.05));
|
|
105
|
-
return Math.min(350_000, Math.max(1, contextWindow - reserve));
|
|
105
|
+
export function autoCompactWindowForModel(_model) {
|
|
106
|
+
return 650_000;
|
|
106
107
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Threshold passed to the Claude Code SDK's autoCompactWindow option.
|
|
110
|
+
* Kept window-aware so the SDK's own built-in compaction continues to fire
|
|
111
|
+
* at the right point for small-context models (200k/128k), where EvolCore's
|
|
112
|
+
* fixed 650k threshold would never trigger.
|
|
113
|
+
*/
|
|
114
|
+
export function sdkAutoCompactWindowForModel(model) {
|
|
115
|
+
const contextWindow = realContextWindowForModel(model);
|
|
116
|
+
const reserve = Math.min(10_000, Math.floor(contextWindow * 0.05));
|
|
117
|
+
return Math.min(650_000, Math.max(1, contextWindow - reserve));
|
|
110
118
|
}
|