evolcore 0.0.25 → 0.0.27
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 +632 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +203 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +172 -33
- package/dist/agents/codex/app-server-client.js +76 -31
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +434 -446
- package/dist/agents/ecagent-runner.js +174 -34
- package/dist/agents/gemini-runner.js +8 -7
- package/dist/agents/runner-types.js +75 -19
- package/dist/aun/aid/agentmd.js +151 -36
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +4 -4
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +162 -39
- package/dist/aun/{menu-client.js → menu/client.js} +85 -6
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +85 -19
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +19 -9
- package/dist/aun/msg/upload.js +43 -80
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +842 -318
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +212 -89
- package/dist/cli/agent-create.js +626 -11
- package/dist/cli/agent.js +125 -13
- package/dist/cli/aun-commands.js +487 -337
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +55 -33
- package/dist/cli/contact.js +72 -15
- package/dist/cli/ctl-command.js +24 -24
- package/dist/cli/daemon-commands.js +140 -63
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +70 -70
- package/dist/cli/handoff-command.js +46 -14
- package/dist/cli/index.js +91 -79
- package/dist/cli/init.js +34 -1
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +5 -1
- package/dist/cli/menu-client.js +51 -32
- package/dist/cli/model.js +3 -3
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +31 -31
- package/dist/cli/response.js +32 -32
- package/dist/cli/restart-monitor.js +103 -42
- package/dist/cli/stats.js +51 -51
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +74 -64
- package/dist/config/builtin-roles.js +11 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +160 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +97 -8
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +30 -7
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +7 -1
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +53 -49
- package/dist/core/auth/operation-authorizer.js +90 -7
- package/dist/core/auth/operation-catalog.js +145 -4
- package/dist/core/auth/remote-access-store.js +155 -10
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +111 -20
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +159 -65
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +46 -7
- package/dist/core/command/menu-handler.js +392 -100
- package/dist/core/command/menu-protocol.js +45 -6
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +123 -43
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +146 -34
- package/dist/core/message/message-bridge.js +163 -20
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +99 -41
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +755 -144
- 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/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +112 -41
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +108 -17
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +9 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +588 -217
- package/dist/ipc.js +193 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +61 -2
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +26 -14
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +196 -23
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +60 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +69 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +379 -367
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +76 -71
- package/kits/docs/evolcore/agent-create.md +202 -165
- package/kits/docs/evolcore/agent.md +99 -67
- package/kits/docs/evolcore/aid.md +68 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +241 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +244 -244
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +267 -152
- package/kits/docs/evolcore/menu-client.md +58 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +169 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +53 -50
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +623 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +42 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +217 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +166 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +76 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +65 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +451 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +215 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +101 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +90 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +53 -34
- package/kits/templates/system-fragments/channel.md +28 -64
- package/kits/templates/system-fragments/commands.md +36 -28
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/reply.md +56 -0
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +104 -98
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
1
|
+
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable, TRANSPORT_EFFORTS, resolveCatalogModel, } 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';
|
|
@@ -10,29 +12,23 @@ import { requestDangerousCommandPermission } from '../core/permission/approval-g
|
|
|
10
12
|
import { normalizeExecutionPermissionMode, normalizePermissionMode } from '../core/permission/mode.js';
|
|
11
13
|
import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
12
14
|
import { logger } from '../utils/logger.js';
|
|
15
|
+
import { ErrorType } from '../utils/error-utils.js';
|
|
13
16
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
14
17
|
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
15
18
|
import { resolveEcagentConfig } from './baseagent.js';
|
|
16
19
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
17
20
|
import { appendRequestPath } from './request-identity.js';
|
|
18
21
|
import { getManagedTaskTempDir } from '../cli/task-context.js';
|
|
19
|
-
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
22
|
+
import { unsettledInterruptError, withInterruptDeadline, hasTrustedFullAccessContext } from './runner-types.js';
|
|
20
23
|
const PROVIDER_ID = 'evolcore-gateway';
|
|
21
|
-
const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
|
|
22
|
-
Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
|
|
24
|
+
const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
|
|
25
|
+
Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
|
|
23
26
|
Use the provided tools for filesystem and shell work. Never claim a tool action succeeded unless its result confirms success.`;
|
|
24
27
|
const MAX_READ_BYTES = 1024 * 1024;
|
|
25
28
|
const MAX_TOOL_OUTPUT = 200_000;
|
|
26
29
|
const MAX_SCAN_FILES = 5_000;
|
|
27
30
|
const MAX_SEARCH_RESULTS = 500;
|
|
28
31
|
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
32
|
class AsyncEventQueue {
|
|
37
33
|
values = [];
|
|
38
34
|
waiters = [];
|
|
@@ -684,6 +680,8 @@ function extractToolResult(result) {
|
|
|
684
680
|
}
|
|
685
681
|
function errorType(message) {
|
|
686
682
|
const normalized = message.toLowerCase();
|
|
683
|
+
if (normalized.includes('invalid jsonl session file') || normalized.includes('session file'))
|
|
684
|
+
return ErrorType.SESSION_UNAVAILABLE;
|
|
687
685
|
if (normalized.includes('[auth]') || normalized.includes('401') || normalized.includes('403'))
|
|
688
686
|
return 'auth';
|
|
689
687
|
if (normalized.includes('context') && (normalized.includes('long') || normalized.includes('length')))
|
|
@@ -695,6 +693,7 @@ function errorType(message) {
|
|
|
695
693
|
export class EcagentRunner {
|
|
696
694
|
name = 'ecagent';
|
|
697
695
|
capabilities = {
|
|
696
|
+
taskRecovery: true,
|
|
698
697
|
fullaccess: true,
|
|
699
698
|
clear: true, compact: true, fork: true, askUserQuestion: false, planApproval: false, fileRewind: 'unsupported',
|
|
700
699
|
};
|
|
@@ -705,6 +704,9 @@ export class EcagentRunner {
|
|
|
705
704
|
effort;
|
|
706
705
|
permissionMode = DEFAULT_PERMISSION_MODE;
|
|
707
706
|
runtimes = new Map();
|
|
707
|
+
/** Serialize harness lifecycles that share one durable ecagent session file. */
|
|
708
|
+
sessionRunTails = new Map();
|
|
709
|
+
sessionRuns = new Map();
|
|
708
710
|
activeStreams = new Map();
|
|
709
711
|
/** Queue identity prevents an old run's finally from consuming a new run's interrupt. */
|
|
710
712
|
pendingInterrupts = new Map();
|
|
@@ -760,7 +762,15 @@ export class EcagentRunner {
|
|
|
760
762
|
return catalog.models.map(m => m.id);
|
|
761
763
|
}
|
|
762
764
|
async getAvailableEfforts(model) {
|
|
763
|
-
|
|
765
|
+
const catalog = await this.getModelCatalog();
|
|
766
|
+
const efforts = catalogEfforts(catalog, model, 'ecagent');
|
|
767
|
+
if (efforts.length > 0)
|
|
768
|
+
return efforts;
|
|
769
|
+
// The embedded transport advertises reasoning support for every model it
|
|
770
|
+
// constructs. Older gateways may omit reasoningLevels from /models; keep
|
|
771
|
+
// menu.options aligned with the actual runner capability in that case.
|
|
772
|
+
const entry = catalog.models.find(item => item.id === resolveCatalogModel(catalog, model));
|
|
773
|
+
return entry && entry.reasoningLevels === undefined ? [...TRANSPORT_EFFORTS.ecagent] : efforts;
|
|
764
774
|
}
|
|
765
775
|
registerStream(key, stream) { this.activeStreams.set(key, stream); }
|
|
766
776
|
cleanupStream(key) {
|
|
@@ -804,6 +814,7 @@ export class EcagentRunner {
|
|
|
804
814
|
// Keep managed tasks from silently inheriting the daemon-wide TMPDIR when
|
|
805
815
|
// their session-owned runtime directory is unavailable.
|
|
806
816
|
getManagedTaskTempDir(runtimeEnv);
|
|
817
|
+
this.assertInterruptSettled(sessionId);
|
|
807
818
|
const topicBinding = modelOverride?.backend?.kind === 'topic';
|
|
808
819
|
if (topicBinding && (!modelOverride?.turn || modelOverride.turn.sessionId !== sessionId || !this.onSessionIdUpdate)) {
|
|
809
820
|
const error = new Error('topic backend run requires an active TurnLease and binding callback');
|
|
@@ -811,20 +822,43 @@ export class EcagentRunner {
|
|
|
811
822
|
throw error;
|
|
812
823
|
}
|
|
813
824
|
const queue = new AsyncEventQueue();
|
|
825
|
+
const lifecycle = { queue, cancelled: false };
|
|
826
|
+
const runs = this.sessionRuns.get(sessionId) ?? new Set();
|
|
827
|
+
runs.add(lifecycle);
|
|
828
|
+
this.sessionRuns.set(sessionId, runs);
|
|
814
829
|
this.activeStreams.set(sessionId, queue);
|
|
815
|
-
|
|
830
|
+
const request = {
|
|
816
831
|
sessionId, prompt, projectPath: path.resolve(projectPath), initialAgentSessionId, images,
|
|
817
832
|
systemPromptAppend, modelOverride, runtimeEnv,
|
|
833
|
+
};
|
|
834
|
+
const previous = this.sessionRunTails.get(sessionId) ?? Promise.resolve();
|
|
835
|
+
const run = previous.catch(() => undefined).then(() => {
|
|
836
|
+
if (!lifecycle.cancelled)
|
|
837
|
+
return this.executeRun(queue, request, lifecycle);
|
|
818
838
|
});
|
|
839
|
+
lifecycle.done = run;
|
|
840
|
+
this.sessionRunTails.set(sessionId, run);
|
|
841
|
+
const release = () => {
|
|
842
|
+
runs.delete(lifecycle);
|
|
843
|
+
if (this.sessionRuns.get(sessionId) === runs && runs.size === 0)
|
|
844
|
+
this.sessionRuns.delete(sessionId);
|
|
845
|
+
if (this.sessionRunTails.get(sessionId) === run)
|
|
846
|
+
this.sessionRunTails.delete(sessionId);
|
|
847
|
+
};
|
|
848
|
+
void run.then(release, release);
|
|
819
849
|
return queue;
|
|
820
850
|
}
|
|
821
|
-
async executeRun(queue, request) {
|
|
822
|
-
const stats = {
|
|
851
|
+
async executeRun(queue, request, lifecycle) {
|
|
852
|
+
const stats = {
|
|
853
|
+
startedAt: Date.now(), text: '', textBlocks: new Map(), numTurns: 0, usage: {}, modelCalls: [],
|
|
854
|
+
};
|
|
823
855
|
let completeSent = false;
|
|
824
856
|
try {
|
|
825
857
|
await fs.promises.mkdir(request.projectPath, { recursive: true });
|
|
826
858
|
const previousRuntime = this.runtimes.get(request.sessionId);
|
|
827
859
|
const runtime = await this.getOrCreateRuntime(request.sessionId, request.projectPath, request.initialAgentSessionId, request.modelOverride);
|
|
860
|
+
if (lifecycle.cancelled)
|
|
861
|
+
throw new Error('ecagent run interrupted');
|
|
828
862
|
if (previousRuntime !== runtime) {
|
|
829
863
|
queue.push({ type: 'session_id', sessionId: runtime.agentSessionId });
|
|
830
864
|
}
|
|
@@ -875,13 +909,16 @@ export class EcagentRunner {
|
|
|
875
909
|
throw new Error('ecagent run interrupted');
|
|
876
910
|
}
|
|
877
911
|
harness.on('tool_call', async (event, signal) => {
|
|
912
|
+
if (lifecycle.cancelled || signal?.aborted)
|
|
913
|
+
return { block: true, reason: '工具调用已取消' };
|
|
878
914
|
const pauseController = this.permissionContexts.get(request.sessionId)?.pauseController;
|
|
879
915
|
if (pauseController) {
|
|
880
916
|
const pauseResult = await pauseController.waitAtToolBoundary(signal);
|
|
881
917
|
if (pauseResult === 'cancelled')
|
|
882
918
|
return { block: true, reason: '工具调用已取消' };
|
|
883
919
|
}
|
|
884
|
-
|
|
920
|
+
const decision = await this.authorizeTool(request.sessionId, request.projectPath, permissionMode, event.toolName, event.input);
|
|
921
|
+
return lifecycle.cancelled || signal?.aborted ? { block: true, reason: '工具调用已取消' } : decision;
|
|
885
922
|
});
|
|
886
923
|
const unsubscribe = harness.subscribe(event => this.mapHarnessEvent(queue, event, stats, model, effort));
|
|
887
924
|
queue.push({ type: 'state_changed', state: 'running' });
|
|
@@ -895,20 +932,36 @@ export class EcagentRunner {
|
|
|
895
932
|
}
|
|
896
933
|
finally {
|
|
897
934
|
unsubscribe();
|
|
898
|
-
|
|
935
|
+
this.finishTextBlocks(queue, stats);
|
|
936
|
+
if (runtime.harness === harness)
|
|
937
|
+
runtime.harness = undefined;
|
|
899
938
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
939
|
+
const outputLimit = result.stopReason === 'length';
|
|
940
|
+
if (result.stopReason === 'error' || result.stopReason === 'aborted' || outputLimit) {
|
|
941
|
+
const message = result.errorMessage || (result.stopReason === 'aborted'
|
|
942
|
+
? 'ecagent run interrupted'
|
|
943
|
+
: outputLimit
|
|
944
|
+
? 'ecagent response reached the output limit (max_output_tokens)'
|
|
945
|
+
: 'ecagent run failed');
|
|
946
|
+
queue.push({ type: 'error', error: message, errorType: outputLimit ? ErrorType.MAX_OUTPUT_TOKENS : errorType(message) });
|
|
903
947
|
}
|
|
904
948
|
const title = await runtime.session.getSessionName();
|
|
905
949
|
queue.push({ type: 'state_changed', state: 'idle' });
|
|
906
950
|
queue.push({
|
|
907
951
|
type: 'complete',
|
|
908
952
|
result: stats.text,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
953
|
+
...(outputLimit ? { subtype: 'max_output_tokens' } : {}),
|
|
954
|
+
isError: result.stopReason === 'error' || result.stopReason === 'aborted' || outputLimit,
|
|
955
|
+
errors: result.errorMessage
|
|
956
|
+
? [result.errorMessage]
|
|
957
|
+
: outputLimit
|
|
958
|
+
? ['max_output_tokens']
|
|
959
|
+
: undefined,
|
|
960
|
+
terminalReason: result.stopReason === 'aborted'
|
|
961
|
+
? 'interrupted'
|
|
962
|
+
: outputLimit
|
|
963
|
+
? 'max_output_tokens'
|
|
964
|
+
: result.stopReason,
|
|
912
965
|
durationMs: Date.now() - stats.startedAt,
|
|
913
966
|
ttftMs: stats.firstTextAt ? stats.firstTextAt - stats.startedAt : undefined,
|
|
914
967
|
sessionTitle: title,
|
|
@@ -921,6 +974,7 @@ export class EcagentRunner {
|
|
|
921
974
|
completeSent = true;
|
|
922
975
|
}
|
|
923
976
|
catch (error) {
|
|
977
|
+
this.finishTextBlocks(queue, stats);
|
|
924
978
|
const message = error instanceof Error ? error.message : String(error);
|
|
925
979
|
const interrupted = message === 'ecagent run interrupted';
|
|
926
980
|
logger.error(`[EcagentRunner] run failed for session ${request.sessionId}:`, error);
|
|
@@ -953,7 +1007,11 @@ export class EcagentRunner {
|
|
|
953
1007
|
const targetId = topicBinding
|
|
954
1008
|
? (requestedTopicAgentSessionId || undefined)
|
|
955
1009
|
: requestedId || current?.agentSessionId;
|
|
956
|
-
|
|
1010
|
+
const recoverySessionId = modelOverride?.recovery?.agentSessionId;
|
|
1011
|
+
if (modelOverride?.recovery && (!recoverySessionId || targetId !== recoverySessionId)) {
|
|
1012
|
+
throw new Error('Task recovery requires the original ecagent session binding');
|
|
1013
|
+
}
|
|
1014
|
+
if (current && !recoverySessionId
|
|
957
1015
|
&& current.projectPath === projectPath
|
|
958
1016
|
&& (!targetId || current.agentSessionId === targetId)
|
|
959
1017
|
&& (!topicBinding || (!!targetId && current.agentSessionId === targetId)))
|
|
@@ -964,6 +1022,9 @@ export class EcagentRunner {
|
|
|
964
1022
|
if (targetId) {
|
|
965
1023
|
const metadata = (await repo.list({ cwd: projectPath })).find(item => item.id === targetId);
|
|
966
1024
|
if (!metadata) {
|
|
1025
|
+
if (recoverySessionId) {
|
|
1026
|
+
throw new Error(`Task recovery backend is unavailable: ${recoverySessionId}`);
|
|
1027
|
+
}
|
|
967
1028
|
if (topicBinding) {
|
|
968
1029
|
const error = new Error(`bound topic backend is unavailable: ${targetId}`);
|
|
969
1030
|
error.code = 'TOPIC_BACKEND_UNAVAILABLE';
|
|
@@ -984,6 +1045,9 @@ export class EcagentRunner {
|
|
|
984
1045
|
: await repo.create({ cwd: projectPath, id: sessionId });
|
|
985
1046
|
}
|
|
986
1047
|
const metadata = await session.getMetadata();
|
|
1048
|
+
if (recoverySessionId && metadata.id !== recoverySessionId) {
|
|
1049
|
+
throw new Error('Task recovery cannot replace the original ecagent session');
|
|
1050
|
+
}
|
|
987
1051
|
const runtime = { agentSessionId: metadata.id, projectPath, repo, session };
|
|
988
1052
|
if ((topicBinding && metadata.id !== requestedTopicAgentSessionId) || metadata.id !== targetId) {
|
|
989
1053
|
const turn = modelOverride?.turn;
|
|
@@ -1002,6 +1066,29 @@ export class EcagentRunner {
|
|
|
1002
1066
|
this.runtimes.set(sessionId, runtime);
|
|
1003
1067
|
return runtime;
|
|
1004
1068
|
}
|
|
1069
|
+
endTextBlock(queue, stats, contentIndex, content) {
|
|
1070
|
+
if (stats.textBlocks.get(contentIndex)?.ended)
|
|
1071
|
+
return;
|
|
1072
|
+
// Commit synchronously before enqueueing. message_end/finally may close the
|
|
1073
|
+
// same block again, but cannot append or project its text a second time.
|
|
1074
|
+
stats.textBlocks.set(contentIndex, { text: content, ended: true });
|
|
1075
|
+
stats.text += content;
|
|
1076
|
+
// Match Codex: only the completed body enters the common runner protocol.
|
|
1077
|
+
queue.push({ type: 'text', text: content, turn: stats.numTurns + 1 });
|
|
1078
|
+
}
|
|
1079
|
+
/** Recover a missing text_end from the final message, or the received deltas. */
|
|
1080
|
+
finishTextBlocks(queue, stats, message) {
|
|
1081
|
+
if (message) {
|
|
1082
|
+
message.content.forEach((block, index) => {
|
|
1083
|
+
if (block.type === 'text')
|
|
1084
|
+
this.endTextBlock(queue, stats, index, block.text);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
for (const [index, block] of stats.textBlocks) {
|
|
1088
|
+
if (!block.ended)
|
|
1089
|
+
this.endTextBlock(queue, stats, index, block.text);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1005
1092
|
mapHarnessEvent(queue, event, stats, model, effort) {
|
|
1006
1093
|
if (event.type === 'message_update'
|
|
1007
1094
|
&& (event.assistantMessageEvent.type === 'thinking_start'
|
|
@@ -1010,14 +1097,38 @@ export class EcagentRunner {
|
|
|
1010
1097
|
queue.push({ type: 'heartbeat', source: 'reasoning', turn: stats.numTurns + 1 });
|
|
1011
1098
|
return;
|
|
1012
1099
|
}
|
|
1013
|
-
if (event.type === 'message_update'
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1100
|
+
if (event.type === 'message_update') {
|
|
1101
|
+
const update = event.assistantMessageEvent;
|
|
1102
|
+
if (update.type === 'text_start') {
|
|
1103
|
+
if (!stats.textBlocks.has(update.contentIndex)) {
|
|
1104
|
+
stats.textBlocks.set(update.contentIndex, { text: '', ended: false });
|
|
1105
|
+
queue.push({ type: 'text_start', contentIndex: update.contentIndex, turn: stats.numTurns + 1 });
|
|
1106
|
+
}
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (update.type === 'text_delta') {
|
|
1110
|
+
const block = stats.textBlocks.get(update.contentIndex) ?? { text: '', ended: false };
|
|
1111
|
+
if (block.ended)
|
|
1112
|
+
return;
|
|
1113
|
+
if (!stats.firstTextAt)
|
|
1114
|
+
stats.firstTextAt = Date.now();
|
|
1115
|
+
block.text += update.delta;
|
|
1116
|
+
stats.textBlocks.set(update.contentIndex, block);
|
|
1117
|
+
// Keep the stream alive without exposing partial text to the renderer.
|
|
1118
|
+
queue.push({ type: 'heartbeat', source: 'text', turn: stats.numTurns + 1 });
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (update.type === 'text_end') {
|
|
1122
|
+
this.endTextBlock(queue, stats, update.contentIndex, update.content);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (event.type === 'message_end' && event.message.role === 'assistant') {
|
|
1127
|
+
this.finishTextBlocks(queue, stats, event.message);
|
|
1018
1128
|
return;
|
|
1019
1129
|
}
|
|
1020
1130
|
if (event.type === 'tool_execution_start') {
|
|
1131
|
+
this.finishTextBlocks(queue, stats);
|
|
1021
1132
|
queue.push({ type: 'tool_use', name: event.toolName, input: event.args, callId: event.toolCallId, turn: stats.numTurns + 1 });
|
|
1022
1133
|
return;
|
|
1023
1134
|
}
|
|
@@ -1030,6 +1141,8 @@ export class EcagentRunner {
|
|
|
1030
1141
|
return;
|
|
1031
1142
|
}
|
|
1032
1143
|
if (event.type === 'turn_end' && event.message.role === 'assistant') {
|
|
1144
|
+
this.finishTextBlocks(queue, stats, event.message);
|
|
1145
|
+
stats.textBlocks.clear();
|
|
1033
1146
|
stats.numTurns++;
|
|
1034
1147
|
const usage = event.message.usage;
|
|
1035
1148
|
stats.usage.input_tokens = Number(stats.usage.input_tokens ?? 0) + usage.input;
|
|
@@ -1155,13 +1268,36 @@ export class EcagentRunner {
|
|
|
1155
1268
|
this.permissionContexts.get(sessionKey)?.pauseController?.cancel();
|
|
1156
1269
|
this.permissionGateway?.cancelAll(sessionKey, 'interrupted');
|
|
1157
1270
|
const harness = this.runtimes.get(sessionKey)?.harness;
|
|
1158
|
-
|
|
1159
|
-
|
|
1271
|
+
const runs = [...(this.sessionRuns.get(sessionKey) ?? [])];
|
|
1272
|
+
for (const run of runs) {
|
|
1273
|
+
if (run.cancelled)
|
|
1274
|
+
continue;
|
|
1275
|
+
run.cancelled = true;
|
|
1276
|
+
run.queue.push({ type: 'complete', isError: true, terminalReason: 'interrupted' });
|
|
1277
|
+
run.queue.end();
|
|
1278
|
+
}
|
|
1279
|
+
if (runs.length === 0 && !harness) {
|
|
1280
|
+
const stream = this.activeStreams.get(sessionKey);
|
|
1281
|
+
if (stream)
|
|
1282
|
+
this.pendingInterrupts.set(sessionKey, stream);
|
|
1160
1283
|
return;
|
|
1161
1284
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1285
|
+
// Ending the event stream releases the message queue, not the durable
|
|
1286
|
+
// session writer. Keep the run tail until all old code has actually exited.
|
|
1287
|
+
try {
|
|
1288
|
+
await withInterruptDeadline(Promise.all([
|
|
1289
|
+
harness?.abort(),
|
|
1290
|
+
...runs.map(run => run.done),
|
|
1291
|
+
]), 'ecagent');
|
|
1292
|
+
}
|
|
1293
|
+
catch (error) {
|
|
1294
|
+
logger.warn(`[EcagentRunner] Interrupted execution still requires quiescence: session=${sessionKey} error=${String(error)}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
assertInterruptSettled(sessionId) {
|
|
1298
|
+
if ([...(this.sessionRuns.get(sessionId) ?? [])].some(run => run.cancelled)) {
|
|
1299
|
+
throw unsettledInterruptError('ecagent');
|
|
1300
|
+
}
|
|
1165
1301
|
}
|
|
1166
1302
|
injectUserMessage(sessionId, text) {
|
|
1167
1303
|
const harness = this.runtimes.get(sessionId)?.harness;
|
|
@@ -1174,6 +1310,7 @@ export class EcagentRunner {
|
|
|
1174
1310
|
const runtime = this.runtimes.get(sessionId);
|
|
1175
1311
|
const stream = this.activeStreams.get(sessionId);
|
|
1176
1312
|
await this.interrupt(sessionId).catch(() => { });
|
|
1313
|
+
this.assertInterruptSettled(sessionId);
|
|
1177
1314
|
if (runtime && this.runtimes.get(sessionId) === runtime)
|
|
1178
1315
|
this.runtimes.delete(sessionId);
|
|
1179
1316
|
if (stream && this.activeStreams.get(sessionId) === stream) {
|
|
@@ -1188,6 +1325,7 @@ export class EcagentRunner {
|
|
|
1188
1325
|
try {
|
|
1189
1326
|
const currentRuntime = this.runtimes.get(sessionId);
|
|
1190
1327
|
await this.interrupt(sessionId);
|
|
1328
|
+
this.assertInterruptSettled(sessionId);
|
|
1191
1329
|
const env = new NodeExecutionEnv({ cwd: projectPath });
|
|
1192
1330
|
const repo = new JsonlSessionRepo({ fs: env, sessionsRoot: this.sessionsRoot() });
|
|
1193
1331
|
const metadata = (await repo.list({ cwd: path.resolve(projectPath) })).find(item => item.id === agentSessionId);
|
|
@@ -1333,6 +1471,8 @@ export class EcagentRunner {
|
|
|
1333
1471
|
async dispose() {
|
|
1334
1472
|
await Promise.allSettled([...this.runtimes.keys()].map(sessionId => this.closeSession(sessionId)));
|
|
1335
1473
|
this.runtimes.clear();
|
|
1474
|
+
this.sessionRuns.clear();
|
|
1475
|
+
this.sessionRunTails.clear();
|
|
1336
1476
|
this.activeStreams.clear();
|
|
1337
1477
|
this.pendingInterrupts.clear();
|
|
1338
1478
|
}
|
|
@@ -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
|
*
|
|
@@ -10,6 +12,7 @@ import { getCatalog, assertCatalogSelectable } from '../core/model/model-catalog
|
|
|
10
12
|
* GeminiRunner → spawn `gemini -p ...` → stdout JSONL stream
|
|
11
13
|
*/
|
|
12
14
|
import { spawn } from 'child_process';
|
|
15
|
+
import { MAIN_CLI_BIN } from '../product.js';
|
|
13
16
|
import { createInterface } from 'readline';
|
|
14
17
|
import fs from 'fs';
|
|
15
18
|
import path from 'path';
|
|
@@ -33,13 +36,6 @@ const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?(?:\x1b\\|\x07)/g;
|
|
|
33
36
|
function stripAnsi(text) {
|
|
34
37
|
return text.replace(ANSI_RE, '');
|
|
35
38
|
}
|
|
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
39
|
// ── MIME → 扩展名映射 ──
|
|
44
40
|
const MIME_EXT = {
|
|
45
41
|
'image/png': '.png',
|
|
@@ -148,6 +144,10 @@ const GEMINI_EC_COMMAND_PATTERNS = [
|
|
|
148
144
|
String.raw `\x00"command":"[ ]*ec[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))*"\x00`,
|
|
149
145
|
];
|
|
150
146
|
const GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN = String.raw `^\{\x00"command":"[ ]*ec[ ]+agent[ ]+persona(?:[ ]+show|[ ]+--(?:text|file)[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))+)[ ]*"\x00\}$`;
|
|
147
|
+
// A single-quoted shell argument can safely carry multiline Markdown, backticks,
|
|
148
|
+
// dollar signs and semicolons. Outside that argument only --format json is legal.
|
|
149
|
+
const GEMINI_WELCOME_ESCAPED_QUOTE = escapeGeminiArgsRegex(JSON.stringify("'\\''").slice(1, -1));
|
|
150
|
+
const GEMINI_BOOTSTRAP_WELCOME_COMMAND_PATTERN = String.raw `^\{\x00"command":"[ ]*${escapeGeminiArgsRegex(MAIN_CLI_BIN)}[ ]+agent[ ]+welcome[ ]+(?:show|--text[ ]+'(?:[^'"\x00\\]|\\(?:\\|"|n|r|t)|${GEMINI_WELCOME_ESCAPED_QUOTE})*')(?:[ ]+--format[ ]+json)?[ ]*"\x00\}$`;
|
|
151
151
|
export function resolveGeminiPermissionProfile(value, bootstrap = false) {
|
|
152
152
|
if (value === 'fullaccess')
|
|
153
153
|
return { mode: 'fullaccess', approvalMode: 'yolo' };
|
|
@@ -217,6 +217,7 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath, personalMd
|
|
|
217
217
|
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec aid agentmd put ${bootstrap.aid}`));
|
|
218
218
|
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec agent ready ${bootstrap.aid}`));
|
|
219
219
|
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN);
|
|
220
|
+
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, GEMINI_BOOTSTRAP_WELCOME_COMMAND_PATTERN);
|
|
220
221
|
if (personalMdPath) {
|
|
221
222
|
const personalPathPattern = geminiManagedIdentityPathPattern(personalMdPath);
|
|
222
223
|
appendGeminiPolicyRule(lines, 'read_file', 'allow', 989, personalPathPattern);
|
|
@@ -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;
|
|
@@ -12,6 +13,30 @@ export class BaseagentRunnerUnavailableError extends Error {
|
|
|
12
13
|
this.name = 'BaseagentRunnerUnavailableError';
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
/** Codex's native asynchronous reply envelope used by its rich clients. */
|
|
17
|
+
export function formatAsyncQuestionAnswer(answer) {
|
|
18
|
+
return '<send_user_message_question_reply>' + JSON.stringify({
|
|
19
|
+
questionItemId: answer.questionItemId,
|
|
20
|
+
question: answer.question,
|
|
21
|
+
answer: answer.answer,
|
|
22
|
+
}) + '</send_user_message_question_reply>';
|
|
23
|
+
}
|
|
24
|
+
export function parseAsyncUserQuestions(value) {
|
|
25
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 20) {
|
|
26
|
+
throw new Error('Invalid asynchronous question list');
|
|
27
|
+
}
|
|
28
|
+
return value.map(question => {
|
|
29
|
+
if (!question || typeof question !== 'object' || typeof question.title !== 'string'
|
|
30
|
+
|| !question.title.trim() || question.title.length > 16_384) {
|
|
31
|
+
throw new Error('Invalid asynchronous question title');
|
|
32
|
+
}
|
|
33
|
+
if (question.options != null && (!Array.isArray(question.options) || !question.options.length
|
|
34
|
+
|| question.options.length > 20 || question.options.some((option) => typeof option !== 'string' || !option.trim() || option.length > 4_096))) {
|
|
35
|
+
throw new Error('Invalid asynchronous question options');
|
|
36
|
+
}
|
|
37
|
+
return { title: question.title, ...(question.options ? { options: [...question.options] } : {}) };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
15
40
|
export function hasTrustedFullAccessContext(context) {
|
|
16
41
|
const authorization = context?.executionPermission;
|
|
17
42
|
if (!authorization
|
|
@@ -19,8 +44,10 @@ export function hasTrustedFullAccessContext(context) {
|
|
|
19
44
|
|| authorization.processRole !== 'fullaccess-run'
|
|
20
45
|
|| authorization.dataScope !== 'daemon')
|
|
21
46
|
return false;
|
|
22
|
-
if (authorization.source === 'fullaccess-command') {
|
|
23
|
-
return typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
47
|
+
if (authorization.source === 'fullaccess-command' || authorization.source === 'agent-policy') {
|
|
48
|
+
return typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
49
|
+
&& (authorization.source !== 'agent-policy'
|
|
50
|
+
|| (typeof authorization.agentAid === 'string' && authorization.agentAid.length > 0));
|
|
24
51
|
}
|
|
25
52
|
return authorization.source === 'trigger'
|
|
26
53
|
&& typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
@@ -31,6 +58,28 @@ export function hasTrustedFullAccessContext(context) {
|
|
|
31
58
|
// being mistaken for a runnable privileged invocation.
|
|
32
59
|
&& typeof authorization.attemptId === 'string' && authorization.attemptId.length > 0;
|
|
33
60
|
}
|
|
61
|
+
/** Bounds waiting only; the caller must retain ownership until execution stops. */
|
|
62
|
+
export async function withInterruptDeadline(operation, label, timeoutMs = 5_000) {
|
|
63
|
+
let timer;
|
|
64
|
+
try {
|
|
65
|
+
return await Promise.race([
|
|
66
|
+
operation,
|
|
67
|
+
new Promise((_, reject) => {
|
|
68
|
+
timer = setTimeout(() => reject(new Error(`${label}: interrupt deadline exceeded`)), timeoutMs);
|
|
69
|
+
timer.unref?.();
|
|
70
|
+
}),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (timer)
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function unsettledInterruptError(runner) {
|
|
79
|
+
return Object.assign(new Error(`${runner} 的上一次执行尚未确认停止,本次任务未启动;旧执行结束后可在原会话继续。`), {
|
|
80
|
+
code: 'RUNNER_INTERRUPT_UNCONFIRMED',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
34
83
|
// ── 类型守卫 ──
|
|
35
84
|
export function hasModelSwitcher(agent) {
|
|
36
85
|
return typeof agent.setModel === 'function' && typeof agent.listModels === 'function';
|
|
@@ -69,20 +118,22 @@ const CLAUDE_CONTEXT_MODEL_ALIASES = ['opus', 'sonnet', 'haiku'];
|
|
|
69
118
|
export function isClaudeContextUsageModel(model) {
|
|
70
119
|
if (!model)
|
|
71
120
|
return false;
|
|
72
|
-
const baseModel = model
|
|
121
|
+
const baseModel = stripContextWindowSuffix(model);
|
|
73
122
|
return /^claude-/i.test(baseModel) || CLAUDE_CONTEXT_MODEL_ALIASES.includes(baseModel);
|
|
74
123
|
}
|
|
75
124
|
export function isOneMillionContextModel(model) {
|
|
76
125
|
if (!model)
|
|
77
126
|
return false;
|
|
127
|
+
// 显式后缀是网关给出的权威窗口,优先于本地前缀表:`claude-opus-5[200k]`
|
|
128
|
+
// 即使基础 ID 在 1M 名单里,也必须按 200k 处理。
|
|
129
|
+
const { base, contextWindow } = parseModelId(model);
|
|
130
|
+
if (contextWindow !== undefined)
|
|
131
|
+
return contextWindow >= 1_000_000;
|
|
78
132
|
if (ONE_M_CONTEXT_MODEL_ALIASES.includes(model))
|
|
79
133
|
return true;
|
|
80
|
-
if (
|
|
81
|
-
return true;
|
|
82
|
-
if (/deepseek-v4/i.test(model))
|
|
134
|
+
if (/deepseek-v4/i.test(base))
|
|
83
135
|
return true;
|
|
84
|
-
|
|
85
|
-
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => baseModel === prefix || baseModel.startsWith(`${prefix}-`));
|
|
136
|
+
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => base === prefix || base.startsWith(`${prefix}-`));
|
|
86
137
|
}
|
|
87
138
|
/**
|
|
88
139
|
* EvolCore's conservative local context-window catalog: 1M models = 1,000,000;
|
|
@@ -94,17 +145,22 @@ export function realContextWindowForModel(model) {
|
|
|
94
145
|
return isOneMillionContextModel(model) ? 1000000 : 200000;
|
|
95
146
|
}
|
|
96
147
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
148
|
+
* EvolCore auto-compact threshold: fixed at 650k tokens regardless of model
|
|
149
|
+
* context window. Large-window models (1M) compress at 650k; small-window
|
|
150
|
+
* models (200k/128k) never reach this threshold and rely on the SDK's own
|
|
151
|
+
* built-in compaction instead.
|
|
100
152
|
*/
|
|
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));
|
|
153
|
+
export function autoCompactWindowForModel(_model) {
|
|
154
|
+
return 650_000;
|
|
106
155
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Threshold passed to the Claude Code SDK's autoCompactWindow option.
|
|
158
|
+
* Kept window-aware so the SDK's own built-in compaction continues to fire
|
|
159
|
+
* at the right point for small-context models (200k/128k), where EvolCore's
|
|
160
|
+
* fixed 650k threshold would never trigger.
|
|
161
|
+
*/
|
|
162
|
+
export function sdkAutoCompactWindowForModel(model) {
|
|
163
|
+
const contextWindow = realContextWindowForModel(model);
|
|
164
|
+
const reserve = Math.min(10_000, Math.floor(contextWindow * 0.05));
|
|
165
|
+
return Math.min(650_000, Math.max(1, contextWindow - reserve));
|
|
110
166
|
}
|