evolcore 0.0.35 → 0.0.37
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 +42 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +177 -85
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +115 -36
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
|
@@ -10,6 +10,8 @@ import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRu
|
|
|
10
10
|
import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
|
|
11
11
|
import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPath, } from './baseagent.js';
|
|
12
12
|
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { resolveProviderService } from '../config/providers.js';
|
|
14
|
+
import { catalogHash } from '../core/model/catalog-service.js';
|
|
13
15
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
14
16
|
import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
15
17
|
import { classifyError, ErrorType, getToolResultErrorText, isOutputTokenLimitError, isRetryableError } from '../utils/error-utils.js';
|
|
@@ -31,12 +33,19 @@ import { createHash, randomBytes } from 'crypto';
|
|
|
31
33
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
32
34
|
import { normalizeToolErrorClassification, normalizeToolErrorCode, normalizeToolErrorDetailCode, } from '../core/permission/tool-error-code.js';
|
|
33
35
|
import fs from 'fs';
|
|
36
|
+
import { assertOwnershipAuthorization } from '../core/ownership/owner-resolver.js';
|
|
34
37
|
import path from 'path';
|
|
35
38
|
import os from 'os';
|
|
36
39
|
const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
|
|
37
40
|
const CODEX_INTERRUPT_TIMEOUT_MS = 5_000;
|
|
38
41
|
function toAgentErrorType(message) {
|
|
39
|
-
|
|
42
|
+
// Forgetting the provider thread is the only remedy for a missing model.
|
|
43
|
+
// classifyError must keep returning MODEL_UNAVAILABLE for it, but the runner
|
|
44
|
+
// must not present it as a generic unknown failure.
|
|
45
|
+
const classification = classifyError(new Error(message));
|
|
46
|
+
if (classification === ErrorType.MODEL_UNAVAILABLE)
|
|
47
|
+
return ErrorType.MODEL_UNAVAILABLE;
|
|
48
|
+
switch (classification) {
|
|
40
49
|
case ErrorType.CONTEXT_TOO_LONG: return 'context_too_long';
|
|
41
50
|
case ErrorType.AUTH_ERROR: return 'auth';
|
|
42
51
|
case ErrorType.API_ERROR:
|
|
@@ -172,6 +181,13 @@ function isCodexReasoningNotification(method) {
|
|
|
172
181
|
|| method === 'item/reasoning/summaryPartAdded';
|
|
173
182
|
}
|
|
174
183
|
const CODEX_THREAD_HANDOFF_TIMEOUT_MS = 2_000;
|
|
184
|
+
/**
|
|
185
|
+
* A turn/start that never reached the backend leaves the previous writer being
|
|
186
|
+
* released asynchronously. The next resume in that same window is rejected with
|
|
187
|
+
* "already has an active writer"; retrying the resume — never the input — is safe
|
|
188
|
+
* because the failed turn consumed nothing.
|
|
189
|
+
*/
|
|
190
|
+
const CODEX_RESUME_WRITER_RETRY_DELAYS_MS = [150, 500, 1_000];
|
|
175
191
|
function stableSecurityValue(value) {
|
|
176
192
|
if (value === undefined)
|
|
177
193
|
return 'undefined';
|
|
@@ -430,6 +446,8 @@ export class CodexRunner {
|
|
|
430
446
|
appServerClient = null;
|
|
431
447
|
/** Trusted fullaccess calls use a separate unmasked app-server process. */
|
|
432
448
|
fullAccessAppServerClient = null;
|
|
449
|
+
retiredProviderClients = new Set();
|
|
450
|
+
providerClientRevisions = new WeakMap();
|
|
433
451
|
onSessionIdUpdate;
|
|
434
452
|
onCompactStart;
|
|
435
453
|
permissionGateway;
|
|
@@ -483,18 +501,27 @@ export class CodexRunner {
|
|
|
483
501
|
this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
|
|
484
502
|
this.codexVersion = runtime?.codexVersion;
|
|
485
503
|
}
|
|
486
|
-
getAppServerClient(fullAccess = false) {
|
|
504
|
+
getAppServerClient(fullAccess = false, providerOverride) {
|
|
487
505
|
const existing = fullAccess ? this.fullAccessAppServerClient : this.appServerClient;
|
|
488
|
-
|
|
506
|
+
const connection = this.currentProviderConfig(providerOverride);
|
|
507
|
+
const revision = connection.providerId ? catalogHash({
|
|
508
|
+
providerId: connection.providerId,
|
|
509
|
+
baseUrl: connection.baseUrl, apiKey: connection.apiKey,
|
|
510
|
+
headers: connection.headers, queryParams: connection.queryParams,
|
|
511
|
+
}) : undefined;
|
|
512
|
+
if (existing && (!revision || this.providerClientRevisions.get(existing) === revision))
|
|
489
513
|
return existing;
|
|
514
|
+
if (existing)
|
|
515
|
+
this.retiredProviderClients.add(existing);
|
|
490
516
|
const client = new CodexAppServerClient({
|
|
491
|
-
apiKey:
|
|
517
|
+
apiKey: connection.apiKey,
|
|
492
518
|
codexVersion: this.codexVersion,
|
|
493
|
-
authType:
|
|
494
|
-
baseUrl:
|
|
495
|
-
provider:
|
|
496
|
-
|
|
497
|
-
|
|
519
|
+
authType: connection.authType,
|
|
520
|
+
baseUrl: connection.baseUrl,
|
|
521
|
+
provider: connection.providerId ? `evolcore_registry_${catalogHash(connection.providerId).slice(0, 20)}` : connection.provider,
|
|
522
|
+
isolatedProvider: connection.providerId !== undefined,
|
|
523
|
+
headers: connection.headers,
|
|
524
|
+
queryParams: connection.queryParams,
|
|
498
525
|
model: this.model,
|
|
499
526
|
effort: this.effort,
|
|
500
527
|
enableRequestUserInput: this.resolvedConfig.enableRequestUserInput,
|
|
@@ -506,6 +533,8 @@ export class CodexRunner {
|
|
|
506
533
|
// until the cross-client archive fallback runs.
|
|
507
534
|
onServerRequest: request => this.handleAppServerRequest(request),
|
|
508
535
|
});
|
|
536
|
+
if (revision)
|
|
537
|
+
this.providerClientRevisions.set(client, revision);
|
|
509
538
|
client.onNotification(notification => {
|
|
510
539
|
this.trackApprovalItemNotification(notification);
|
|
511
540
|
if (notification.method !== 'thread/closed')
|
|
@@ -529,6 +558,21 @@ export class CodexRunner {
|
|
|
529
558
|
}
|
|
530
559
|
return client;
|
|
531
560
|
}
|
|
561
|
+
currentProviderConfig(providerOverride) {
|
|
562
|
+
const providerId = providerOverride ?? this.resolvedConfig.providerId;
|
|
563
|
+
if (!providerId)
|
|
564
|
+
return this.resolvedConfig;
|
|
565
|
+
const provider = resolveProviderService(providerId, 'codex');
|
|
566
|
+
if (!provider.apiKey)
|
|
567
|
+
throw new Error(`Provider ${provider.providerId} has no API key`);
|
|
568
|
+
return {
|
|
569
|
+
...this.resolvedConfig,
|
|
570
|
+
...provider,
|
|
571
|
+
apiKey: provider.apiKey,
|
|
572
|
+
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: provider.baseUrl,
|
|
573
|
+
agentAid: this.resolvedConfig.agentAid, configuredHeaders: provider.headers }),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
532
576
|
getAppServerClientForThread(threadId) {
|
|
533
577
|
const subscribed = this.threadSubscriptions.get(threadId);
|
|
534
578
|
if (subscribed)
|
|
@@ -612,6 +656,43 @@ export class CodexRunner {
|
|
|
612
656
|
});
|
|
613
657
|
return await releasePromise;
|
|
614
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Resume a thread whose previous writer may still be releasing. Unlike the
|
|
661
|
+
* cross-client handoff, this only covers the same connection: the owner is
|
|
662
|
+
* unsubscribed (and awaited) once before the bounded retries, and any other
|
|
663
|
+
* active-writer rejection is released to the caller unchanged.
|
|
664
|
+
*/
|
|
665
|
+
async resumeThreadAllowingWriterRelease(appServer, threadId, projectPath, threadOptions, previousAppServer) {
|
|
666
|
+
try {
|
|
667
|
+
return await appServer.threadResume(threadId, projectPath, threadOptions);
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
if (!isCodexActiveWriterError(error) || (previousAppServer && previousAppServer !== appServer))
|
|
671
|
+
throw error;
|
|
672
|
+
}
|
|
673
|
+
const subscription = this.threadSubscriptions.get(threadId);
|
|
674
|
+
if (subscription?.client === appServer && subscription.state === 'subscribed') {
|
|
675
|
+
await this.releaseThreadSubscription(appServer, threadId, 'writer-release-wait', {
|
|
676
|
+
waitForClose: true,
|
|
677
|
+
timeoutMs: CODEX_THREAD_HANDOFF_TIMEOUT_MS,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
for (const delayMs of CODEX_RESUME_WRITER_RETRY_DELAYS_MS) {
|
|
681
|
+
await new Promise(resolve => {
|
|
682
|
+
const timer = setTimeout(resolve, delayMs);
|
|
683
|
+
timer.unref?.();
|
|
684
|
+
});
|
|
685
|
+
try {
|
|
686
|
+
return await appServer.threadResume(threadId, projectPath, threadOptions);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
if (!isCodexActiveWriterError(error))
|
|
690
|
+
throw error;
|
|
691
|
+
logger.warn(`[CodexRunner] Thread writer still active after ${delayMs}ms: thread=${threadId}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return await appServer.threadResume(threadId, projectPath, threadOptions);
|
|
695
|
+
}
|
|
615
696
|
async prepareThreadSubscription(client, threadId) {
|
|
616
697
|
const existing = this.threadSubscriptions.get(threadId);
|
|
617
698
|
if (!existing)
|
|
@@ -860,7 +941,7 @@ export class CodexRunner {
|
|
|
860
941
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
861
942
|
return classifyEvolcoreShellCommand(command).kind !== 'none';
|
|
862
943
|
}
|
|
863
|
-
armApprovedDelegationCommand(sessionId, argv) {
|
|
944
|
+
armApprovedDelegationCommand(sessionId, argv, approvalId) {
|
|
864
945
|
const threadId = this.activeSessions.get(sessionId) ?? this.provisionalSessions.get(sessionId);
|
|
865
946
|
const sessionToken = threadId ? this.ensureThreadCredentialLoaded(threadId) : undefined;
|
|
866
947
|
const commandHash = hashDelegatedCommandArgv(argv);
|
|
@@ -868,7 +949,7 @@ export class CodexRunner {
|
|
|
868
949
|
if (!sessionToken || !commandHash || !arm)
|
|
869
950
|
return false;
|
|
870
951
|
try {
|
|
871
|
-
return arm(sessionToken, commandHash);
|
|
952
|
+
return arm(sessionToken, commandHash, approvalId);
|
|
872
953
|
}
|
|
873
954
|
catch (error) {
|
|
874
955
|
logger.warn(`[CodexRunner] delegation arm callback failed: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -959,8 +1040,9 @@ export class CodexRunner {
|
|
|
959
1040
|
this.pruneThreadOperationLocks();
|
|
960
1041
|
}
|
|
961
1042
|
resetAppServerClient() {
|
|
962
|
-
const clients = [this.appServerClient, this.fullAccessAppServerClient]
|
|
1043
|
+
const clients = [...this.retiredProviderClients, this.appServerClient, this.fullAccessAppServerClient]
|
|
963
1044
|
.filter((client) => !!client);
|
|
1045
|
+
this.retiredProviderClients.clear();
|
|
964
1046
|
this.appServerClient = null;
|
|
965
1047
|
this.fullAccessAppServerClient = null;
|
|
966
1048
|
this.threadSubscriptions.clear();
|
|
@@ -1232,7 +1314,7 @@ export class CodexRunner {
|
|
|
1232
1314
|
}
|
|
1233
1315
|
getModel() { return this.model; }
|
|
1234
1316
|
getModelCatalog() {
|
|
1235
|
-
return getRunnerCatalog('codex', this.resolvedConfig.agentAid, this.
|
|
1317
|
+
return getRunnerCatalog('codex', this.resolvedConfig.agentAid, this.currentProviderConfig());
|
|
1236
1318
|
}
|
|
1237
1319
|
async listModels() {
|
|
1238
1320
|
const catalog = await this.getModelCatalog();
|
|
@@ -1356,6 +1438,7 @@ export class CodexRunner {
|
|
|
1356
1438
|
// nested mount namespace; it can fail while creating a missing
|
|
1357
1439
|
// target such as triggers/history.jsonl.
|
|
1358
1440
|
expandLClassReadDeny: process.platform !== 'linux',
|
|
1441
|
+
outerHClassGuard: process.platform === 'linux',
|
|
1359
1442
|
}),
|
|
1360
1443
|
...this.resolveGitMetadataWriteRules(projectPath, mode),
|
|
1361
1444
|
...this.resolveSystemSessionWriteRules(sessionId),
|
|
@@ -1532,6 +1615,12 @@ export class CodexRunner {
|
|
|
1532
1615
|
const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
1533
1616
|
? toolInput
|
|
1534
1617
|
: {};
|
|
1618
|
+
try {
|
|
1619
|
+
await assertOwnershipAuthorization(context);
|
|
1620
|
+
}
|
|
1621
|
+
catch (error) {
|
|
1622
|
+
return { ok: true, applicable: true, decision: 'deny', reason: error instanceof Error ? error.message : String(error) };
|
|
1623
|
+
}
|
|
1535
1624
|
const isShellTool = isShellToolName(toolName); // canonical normalisation via tool-canonical.ts
|
|
1536
1625
|
const recordPreToolUseDenial = async (policyCode, reason) => {
|
|
1537
1626
|
const summary = summarizeToolInputForAudit(toolName, normalizedInput).slice(0, 512);
|
|
@@ -1924,7 +2013,7 @@ export class CodexRunner {
|
|
|
1924
2013
|
this.chatModes.set(sessionId, callMode);
|
|
1925
2014
|
const callApprovalPolicy = this.toApprovalPolicy(callMode);
|
|
1926
2015
|
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath, undefined);
|
|
1927
|
-
const appServer = this.getAppServerClient(callMode === 'fullaccess');
|
|
2016
|
+
const appServer = this.getAppServerClient(callMode === 'fullaccess', modelOverride?.provider);
|
|
1928
2017
|
this.dropExpiredPendingInterrupt(sessionId);
|
|
1929
2018
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
1930
2019
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
|
|
@@ -1965,7 +2054,7 @@ export class CodexRunner {
|
|
|
1965
2054
|
// Release that one thread before asking this client to resume it.
|
|
1966
2055
|
previousAppServer = await this.prepareThreadSubscription(appServer, agentSessionId);
|
|
1967
2056
|
try {
|
|
1968
|
-
threadResponse = await
|
|
2057
|
+
threadResponse = await this.resumeThreadAllowingWriterRelease(appServer, agentSessionId, projectPath, threadOptions, previousAppServer);
|
|
1969
2058
|
}
|
|
1970
2059
|
catch (error) {
|
|
1971
2060
|
if (isCodexActiveWriterError(error) && previousAppServer && previousAppServer !== appServer) {
|
|
@@ -2075,6 +2164,7 @@ export class CodexRunner {
|
|
|
2075
2164
|
completedTurnIds: new Set(),
|
|
2076
2165
|
openToolCalls: new Map(),
|
|
2077
2166
|
activeSubagentThreads: new Set(),
|
|
2167
|
+
subagentUsage: new Map(),
|
|
2078
2168
|
turnCompleted: false,
|
|
2079
2169
|
inputId: turn?.inputId,
|
|
2080
2170
|
inputAcceptedEmitted: false,
|
|
@@ -2088,7 +2178,14 @@ export class CodexRunner {
|
|
|
2088
2178
|
// 仅从 turn/started 锁定权威 turnId — resume 时会有上一轮 turn 的残留通知
|
|
2089
2179
|
// (如 thread/tokenUsage/updated)先于新 turn 到达,不能用它们 latch turnId
|
|
2090
2180
|
const params = notification.params || {};
|
|
2091
|
-
const notifThreadId = params.threadId ?? params.thread_id;
|
|
2181
|
+
const notifThreadId = params.threadId ?? params.thread_id ?? params.thread?.id;
|
|
2182
|
+
const spawnedBy = params.thread?.source?.subAgent?.thread_spawn?.parent_thread_id;
|
|
2183
|
+
if (notification.method === 'thread/started' && typeof notifThreadId === 'string'
|
|
2184
|
+
&& (spawnedBy === threadId || this.childThreadSessions.get(notifThreadId) === sessionId)) {
|
|
2185
|
+
const usage = state.subagentUsage.get(notifThreadId) ?? {};
|
|
2186
|
+
usage.model = typeof params.thread?.model === 'string' ? params.thread.model : usage.model;
|
|
2187
|
+
state.subagentUsage.set(notifThreadId, usage);
|
|
2188
|
+
}
|
|
2092
2189
|
const item = params.item && typeof params.item === 'object' ? params.item : undefined;
|
|
2093
2190
|
const emittedByChild = typeof notifThreadId === 'string'
|
|
2094
2191
|
&& this.childThreadSessions.get(notifThreadId) === sessionId;
|
|
@@ -2097,6 +2194,23 @@ export class CodexRunner {
|
|
|
2097
2194
|
this.trackSubagentLifecycle(notification, state, appServer);
|
|
2098
2195
|
if (emittedByChild && notifThreadId !== threadId) {
|
|
2099
2196
|
const childTurnId = this.extractTurnId(notification);
|
|
2197
|
+
if (notification.method === 'thread/tokenUsage/updated') {
|
|
2198
|
+
const activeTurn = this.activeSubagentTurns.get(notifThreadId);
|
|
2199
|
+
if (childTurnId && activeTurn?.turnId === childTurnId) {
|
|
2200
|
+
this.recordSubagentUsage(state, notifThreadId, childTurnId, params.tokenUsage);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (notification.method === 'turn/completed') {
|
|
2204
|
+
const terminalUsage = params.turn?.tokenUsage ?? params.turn?.usage;
|
|
2205
|
+
if (terminalUsage && childTurnId && !state.subagentUsage?.get(notifThreadId)?.seenTurns?.has(childTurnId)) {
|
|
2206
|
+
this.recordSubagentUsage(state, notifThreadId, childTurnId, terminalUsage);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (notification.method === 'model/rerouted' && typeof params.toModel === 'string') {
|
|
2210
|
+
const childUsage = state.subagentUsage.get(notifThreadId) ?? {};
|
|
2211
|
+
childUsage.model = params.toModel;
|
|
2212
|
+
state.subagentUsage.set(notifThreadId, childUsage);
|
|
2213
|
+
}
|
|
2100
2214
|
if (notification.method === 'turn/started' && childTurnId) {
|
|
2101
2215
|
this.activeSubagentTurns.set(notifThreadId, {
|
|
2102
2216
|
sessionId,
|
|
@@ -3010,6 +3124,7 @@ export class CodexRunner {
|
|
|
3010
3124
|
return;
|
|
3011
3125
|
}
|
|
3012
3126
|
if (notification.method === 'item/completed' && key) {
|
|
3127
|
+
this.revokeApprovalForItem(threadId, itemId);
|
|
3013
3128
|
this.trackedApprovalItems.delete(key);
|
|
3014
3129
|
return;
|
|
3015
3130
|
}
|
|
@@ -3021,6 +3136,7 @@ export class CodexRunner {
|
|
|
3021
3136
|
: undefined;
|
|
3022
3137
|
for (const [trackedKey, tracked] of this.trackedApprovalItems) {
|
|
3023
3138
|
if (tracked.threadId === threadId && (!completedTurnId || tracked.turnId === completedTurnId)) {
|
|
3139
|
+
this.revokeApprovalForItem(threadId, tracked.itemId);
|
|
3024
3140
|
this.trackedApprovalItems.delete(trackedKey);
|
|
3025
3141
|
}
|
|
3026
3142
|
}
|
|
@@ -3043,6 +3159,14 @@ export class CodexRunner {
|
|
|
3043
3159
|
? conversationIdValue
|
|
3044
3160
|
: undefined;
|
|
3045
3161
|
}
|
|
3162
|
+
revokeApprovalForItem(threadId, itemId) {
|
|
3163
|
+
if (!threadId || typeof itemId !== 'string' || !itemId)
|
|
3164
|
+
return;
|
|
3165
|
+
const sessionId = this.findSessionKeyByThread(threadId);
|
|
3166
|
+
if (!sessionId)
|
|
3167
|
+
return;
|
|
3168
|
+
this.permissionContexts.get(sessionId)?.revokeApprovedDelegationCommand?.(itemId);
|
|
3169
|
+
}
|
|
3046
3170
|
clearPendingToolDenialsForTurn(threadId, turnId) {
|
|
3047
3171
|
for (const key of this.pendingToolDenials.keys()) {
|
|
3048
3172
|
let parsed;
|
|
@@ -3311,12 +3435,14 @@ export class CodexRunner {
|
|
|
3311
3435
|
logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${logSummary}`);
|
|
3312
3436
|
const isCommandApproval = request.method === 'item/commandExecution/requestApproval'
|
|
3313
3437
|
|| request.method === 'execCommandApproval';
|
|
3314
|
-
const managedEcIntent = isCommandApproval && this.isManagedEvolcoreCommandIntent(toolInput);
|
|
3315
3438
|
const sessionCredential = this.sessionTokenForSession(sessionKey, typeof params.threadId === 'string' ? params.threadId : undefined);
|
|
3316
3439
|
const canonicalEcApproval = isCommandApproval
|
|
3317
3440
|
? this.resolveCanonicalEcApproval(toolInput, this.getManagedTempDir(sessionKey), sessionCredential)
|
|
3318
3441
|
: undefined;
|
|
3319
3442
|
const approvedCommand = canonicalEcApproval?.argv;
|
|
3443
|
+
// Canonical parsing is authoritative. Intent classification remains a
|
|
3444
|
+
// fail-closed signal for malformed EC-shaped commands that cannot be armed.
|
|
3445
|
+
const managedEcIntent = isCommandApproval && (approvedCommand?.[0] === 'ec' || this.isManagedEvolcoreCommandIntent(toolInput));
|
|
3320
3446
|
const denyInfrastructure = async (policyCode, message, errorDetailCode) => {
|
|
3321
3447
|
logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
|
|
3322
3448
|
try {
|
|
@@ -3395,7 +3521,7 @@ export class CodexRunner {
|
|
|
3395
3521
|
return await denyInfrastructure('delegation_token_mismatch', 'EC command approval contains a delegation token different from the active thread credential');
|
|
3396
3522
|
}
|
|
3397
3523
|
if ((hasSessionCredential || canArmDelegation)
|
|
3398
|
-
&& !this.armApprovedDelegationCommand(sessionKey, approvedCommand)) {
|
|
3524
|
+
&& !this.armApprovedDelegationCommand(sessionKey, approvedCommand, approvalCallId ?? approvalRequestId)) {
|
|
3399
3525
|
return await denyInfrastructure('delegation_not_armed', 'EC command approval failed to arm delegation');
|
|
3400
3526
|
}
|
|
3401
3527
|
}
|
|
@@ -4084,6 +4210,12 @@ export class CodexRunner {
|
|
|
4084
4210
|
}
|
|
4085
4211
|
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath, requestId, callId) {
|
|
4086
4212
|
const permissionContext = this.permissionContexts.get(sessionKey);
|
|
4213
|
+
try {
|
|
4214
|
+
await assertOwnershipAuthorization(permissionContext);
|
|
4215
|
+
}
|
|
4216
|
+
catch {
|
|
4217
|
+
return 'deny';
|
|
4218
|
+
}
|
|
4087
4219
|
// 系统会话:审批路径同样只看模块判定(FileChange / PermissionGrant 一律被模块拒绝)
|
|
4088
4220
|
if (permissionContext?.systemSessionCheck) {
|
|
4089
4221
|
const decision = permissionContext.systemSessionCheck(toolName, toolInput);
|
|
@@ -4267,7 +4399,7 @@ export class CodexRunner {
|
|
|
4267
4399
|
if (policyToolName === 'FileChange') {
|
|
4268
4400
|
return resolveRuntimeApproval(await this.permissionGateway.requestPermission(sessionKey, policyToolName, policyInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, { fileChangeCwd: workspacePath }));
|
|
4269
4401
|
}
|
|
4270
|
-
return resolveRuntimeApproval(await this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope));
|
|
4402
|
+
return resolveRuntimeApproval(await this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, toolName === 'PermissionGrant' ? { filesystemCwd: operationCwd, workspacePath } : undefined));
|
|
4271
4403
|
}
|
|
4272
4404
|
if (mode === 'request' || mode === 'bypass') {
|
|
4273
4405
|
const dangerous = await requestDangerousCommandPermission(this.permissionGateway, sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), grantScope, mode);
|
|
@@ -4735,8 +4867,16 @@ export class CodexRunner {
|
|
|
4735
4867
|
}
|
|
4736
4868
|
}
|
|
4737
4869
|
trackSubagentLifecycle(notification, state, appServer) {
|
|
4870
|
+
const subagentUsage = state.subagentUsage ?? (state.subagentUsage = new Map());
|
|
4738
4871
|
const params = (notification.params || {});
|
|
4739
4872
|
const item = params.item && typeof params.item === 'object' ? params.item : undefined;
|
|
4873
|
+
if (item?.type === 'collabAgentToolCall' && typeof item.model === 'string') {
|
|
4874
|
+
for (const childThreadId of Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : []) {
|
|
4875
|
+
if (typeof childThreadId !== 'string')
|
|
4876
|
+
continue;
|
|
4877
|
+
subagentUsage.set(childThreadId, { ...subagentUsage.get(childThreadId), model: item.model });
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4740
4880
|
if (!this.isSubagentActivityItem(item))
|
|
4741
4881
|
return;
|
|
4742
4882
|
const childThreadId = this.subagentThreadId(item);
|
|
@@ -4745,18 +4885,29 @@ export class CodexRunner {
|
|
|
4745
4885
|
const kind = typeof item.kind === 'string' ? item.kind.toLowerCase() : '';
|
|
4746
4886
|
if (kind === 'started') {
|
|
4747
4887
|
state.activeSubagentThreads.add(childThreadId);
|
|
4888
|
+
subagentUsage.set(childThreadId, {
|
|
4889
|
+
...subagentUsage.get(childThreadId),
|
|
4890
|
+
subagentType: this.subagentType(item),
|
|
4891
|
+
status: 'started',
|
|
4892
|
+
});
|
|
4748
4893
|
this.registerChildThread(state.sessionId, childThreadId, state.projectPath);
|
|
4749
4894
|
if (appServer)
|
|
4750
4895
|
this.markThreadSubscribed(appServer, childThreadId);
|
|
4751
4896
|
}
|
|
4752
4897
|
if (kind === 'completed' || kind === 'failed' || kind === 'stopped' || kind === 'killed') {
|
|
4753
4898
|
state.activeSubagentThreads.delete(childThreadId);
|
|
4899
|
+
subagentUsage.set(childThreadId, {
|
|
4900
|
+
...subagentUsage.get(childThreadId),
|
|
4901
|
+
subagentType: this.subagentType(item),
|
|
4902
|
+
status: kind,
|
|
4903
|
+
});
|
|
4754
4904
|
if (appServer)
|
|
4755
4905
|
void this.releaseThreadSubscription(appServer, childThreadId, 'subagent-finished');
|
|
4756
4906
|
this.unregisterChildThread(childThreadId);
|
|
4757
4907
|
}
|
|
4758
4908
|
}
|
|
4759
4909
|
primeSubagentStateFromTurn(turn, state, appServer) {
|
|
4910
|
+
const subagentUsage = state.subagentUsage ?? (state.subagentUsage = new Map());
|
|
4760
4911
|
for (const item of this.getTurnItems(turn)) {
|
|
4761
4912
|
if (!this.isSubagentActivityItem(item))
|
|
4762
4913
|
continue;
|
|
@@ -4766,12 +4917,22 @@ export class CodexRunner {
|
|
|
4766
4917
|
continue;
|
|
4767
4918
|
if (kind === 'started') {
|
|
4768
4919
|
state.activeSubagentThreads.add(childThreadId);
|
|
4920
|
+
subagentUsage.set(childThreadId, {
|
|
4921
|
+
...subagentUsage.get(childThreadId),
|
|
4922
|
+
subagentType: this.subagentType(item),
|
|
4923
|
+
status: 'started',
|
|
4924
|
+
});
|
|
4769
4925
|
this.registerChildThread(state.sessionId, childThreadId, state.projectPath);
|
|
4770
4926
|
if (appServer)
|
|
4771
4927
|
this.markThreadSubscribed(appServer, childThreadId);
|
|
4772
4928
|
}
|
|
4773
4929
|
else if (kind === 'completed' || kind === 'failed' || kind === 'stopped' || kind === 'killed') {
|
|
4774
4930
|
state.activeSubagentThreads.delete(childThreadId);
|
|
4931
|
+
subagentUsage.set(childThreadId, {
|
|
4932
|
+
...subagentUsage.get(childThreadId),
|
|
4933
|
+
subagentType: this.subagentType(item),
|
|
4934
|
+
status: kind,
|
|
4935
|
+
});
|
|
4775
4936
|
if (appServer)
|
|
4776
4937
|
void this.releaseThreadSubscription(appServer, childThreadId, 'subagent-finished');
|
|
4777
4938
|
this.unregisterChildThread(childThreadId);
|
|
@@ -4790,6 +4951,7 @@ export class CodexRunner {
|
|
|
4790
4951
|
}
|
|
4791
4952
|
async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, appServer) {
|
|
4792
4953
|
try {
|
|
4954
|
+
let completedTurn;
|
|
4793
4955
|
let sessionIdEmitted = state.committed;
|
|
4794
4956
|
if (sessionIdEmitted)
|
|
4795
4957
|
yield { type: 'session_id', sessionId: state.threadId };
|
|
@@ -4810,8 +4972,15 @@ export class CodexRunner {
|
|
|
4810
4972
|
throw persistenceError;
|
|
4811
4973
|
}
|
|
4812
4974
|
}
|
|
4813
|
-
|
|
4975
|
+
for (const event of this.mapAppServerNotification(notification, sessionId, state)) {
|
|
4976
|
+
if (event.type === 'complete')
|
|
4977
|
+
completedTurn = notification.params?.turn ?? {};
|
|
4978
|
+
else
|
|
4979
|
+
yield event;
|
|
4980
|
+
}
|
|
4814
4981
|
}
|
|
4982
|
+
if (completedTurn)
|
|
4983
|
+
yield this.mapAppServerTurnComplete(completedTurn, state);
|
|
4815
4984
|
}
|
|
4816
4985
|
finally {
|
|
4817
4986
|
if (!state.committed && !state.persistenceFinalized && state.confirmProvisional) {
|
|
@@ -5503,16 +5672,83 @@ export class CodexRunner {
|
|
|
5503
5672
|
const effort = typeof raw.effort === 'string' ? raw.effort : undefined;
|
|
5504
5673
|
return { totalTokens, maxTokens, percentage, model, effort };
|
|
5505
5674
|
}
|
|
5675
|
+
recordSubagentUsage(state, threadId, turnId, raw) {
|
|
5676
|
+
if (threadId === state.threadId || !raw || typeof raw !== 'object')
|
|
5677
|
+
return;
|
|
5678
|
+
const entries = state.subagentUsage ?? (state.subagentUsage = new Map());
|
|
5679
|
+
const child = entries.get(threadId) ?? {};
|
|
5680
|
+
const total = this.mapCodexTokenUsage(raw.total);
|
|
5681
|
+
const last = this.mapCodexTokenUsage(raw.last ?? (raw.total ? undefined : raw));
|
|
5682
|
+
const fields = ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens'];
|
|
5683
|
+
const previous = child.total;
|
|
5684
|
+
const reset = total && previous && fields.some(field => (total[field] ?? 0) < (previous[field] ?? 0));
|
|
5685
|
+
const usage = total && previous && !reset
|
|
5686
|
+
? Object.fromEntries(fields.map(field => [field, Math.max(0, (total[field] ?? 0) - (previous[field] ?? 0))]))
|
|
5687
|
+
: last;
|
|
5688
|
+
child.tokenUsage = raw;
|
|
5689
|
+
if (total)
|
|
5690
|
+
child.total = total;
|
|
5691
|
+
child.model = typeof raw.model === 'string' ? raw.model
|
|
5692
|
+
: typeof raw.context?.model === 'string' ? raw.context.model : child.model;
|
|
5693
|
+
if (usage) {
|
|
5694
|
+
(child.seenTurns ??= new Set()).add(turnId);
|
|
5695
|
+
if (fields.some(field => (usage[field] ?? 0) > 0)) {
|
|
5696
|
+
const model = child.model || 'unknown';
|
|
5697
|
+
const byModel = child.usageByModel ?? (child.usageByModel = new Map());
|
|
5698
|
+
const prior = byModel.get(model);
|
|
5699
|
+
byModel.set(model, Object.fromEntries(fields.map(field => [field, (prior?.[field] ?? 0) + (usage[field] ?? 0)])));
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
entries.set(threadId, child);
|
|
5703
|
+
}
|
|
5506
5704
|
mapAppServerTurnComplete(turn, state) {
|
|
5705
|
+
const subagentUsage = state.subagentUsage ?? (state.subagentUsage = new Map());
|
|
5507
5706
|
const status = turn.status || 'completed';
|
|
5508
5707
|
const outputLimit = status === 'failed' && isOutputTokenLimitError(new Error(turn.error?.message ?? ''));
|
|
5509
|
-
const
|
|
5708
|
+
const parentTokenUsage = this.mapCodexTokenUsage(state.tokenUsage?.last ?? turn.tokenUsage ?? turn.usage);
|
|
5709
|
+
const subagentUsages = [...subagentUsage.entries()]
|
|
5710
|
+
.filter(([threadId]) => threadId !== state.threadId)
|
|
5711
|
+
.flatMap(([threadId, subagent]) => [...(subagent.usageByModel ?? new Map())]
|
|
5712
|
+
.map(([model, tokenUsage]) => ({ threadId, model, tokenUsage })));
|
|
5713
|
+
// 窗口标识与实时窗口由 main 侧补全:优先 modelContextId,原始窗口需包含
|
|
5714
|
+
// 子代理终端上报的 modelContextWindow。
|
|
5510
5715
|
const terminalTokenUsage = turn.tokenUsage ?? turn.usage;
|
|
5511
5716
|
const contextUsage = this.mapCodexContextUsage({
|
|
5512
5717
|
...state.tokenUsage,
|
|
5513
5718
|
...(terminalTokenUsage && typeof terminalTokenUsage === 'object' ? terminalTokenUsage : {}),
|
|
5514
5719
|
...(turn.contextUsage ?? state.tokenUsage?.contextUsage ?? state.tokenUsage?.context),
|
|
5515
5720
|
}, state.modelContextId ?? state.model, this.pickNumber(state.tokenUsage?.modelContextWindow, state.tokenUsage?.model_context_window, terminalTokenUsage?.modelContextWindow, terminalTokenUsage?.model_context_window));
|
|
5721
|
+
const modelCalls = [
|
|
5722
|
+
...(parentTokenUsage ? [{
|
|
5723
|
+
call_index: 0,
|
|
5724
|
+
model: state.model,
|
|
5725
|
+
source: 'turn',
|
|
5726
|
+
providerThreadId: state.threadId,
|
|
5727
|
+
tokenUsage: parentTokenUsage,
|
|
5728
|
+
contextUsage,
|
|
5729
|
+
degraded: true,
|
|
5730
|
+
}] : []),
|
|
5731
|
+
...subagentUsages
|
|
5732
|
+
.filter(({ tokenUsage }) => !!tokenUsage)
|
|
5733
|
+
.map(({ threadId, model, tokenUsage }, index) => ({
|
|
5734
|
+
call_index: (parentTokenUsage ? 1 : 0) + index,
|
|
5735
|
+
model,
|
|
5736
|
+
source: 'subagent',
|
|
5737
|
+
providerThreadId: threadId,
|
|
5738
|
+
tokenUsage: tokenUsage,
|
|
5739
|
+
degraded: true,
|
|
5740
|
+
})),
|
|
5741
|
+
];
|
|
5742
|
+
const reportedUsages = [parentTokenUsage, ...subagentUsages.map(item => item.tokenUsage)]
|
|
5743
|
+
.filter((usage) => !!usage);
|
|
5744
|
+
const tokenUsage = reportedUsages.length
|
|
5745
|
+
? {
|
|
5746
|
+
input_tokens: reportedUsages.reduce((sum, usage) => sum + (usage.input_tokens ?? 0), 0),
|
|
5747
|
+
output_tokens: reportedUsages.reduce((sum, usage) => sum + (usage.output_tokens ?? 0), 0),
|
|
5748
|
+
cache_read_input_tokens: reportedUsages.reduce((sum, usage) => sum + (usage.cache_read_input_tokens ?? 0), 0),
|
|
5749
|
+
cache_creation_input_tokens: reportedUsages.reduce((sum, usage) => sum + (usage.cache_creation_input_tokens ?? 0), 0),
|
|
5750
|
+
}
|
|
5751
|
+
: undefined;
|
|
5516
5752
|
const terminalReason = status === 'completed'
|
|
5517
5753
|
? undefined
|
|
5518
5754
|
: status === 'interrupted'
|
|
@@ -5533,6 +5769,7 @@ export class CodexRunner {
|
|
|
5533
5769
|
model: state.model,
|
|
5534
5770
|
tokenUsage,
|
|
5535
5771
|
contextUsage,
|
|
5772
|
+
modelCalls: modelCalls.length ? modelCalls : undefined,
|
|
5536
5773
|
};
|
|
5537
5774
|
}
|
|
5538
5775
|
getManagedTempDir(sessionId) {
|
|
@@ -5555,6 +5792,8 @@ export class CodexRunner {
|
|
|
5555
5792
|
set: {
|
|
5556
5793
|
EVOLCORE_SESSION_ID: sessionId,
|
|
5557
5794
|
EVOLCORE_HOME: resolveRoot(),
|
|
5795
|
+
// Let managed PowerShell resolve the native ec.ps1 shim without changing user policy.
|
|
5796
|
+
...(process.platform === 'win32' ? { PSExecutionPolicyPreference: 'Bypass' } : {}),
|
|
5558
5797
|
...(managedTempDir ? { EVOLCORE_SESSION_RUNTIME_DIR: managedTempDir } : {}),
|
|
5559
5798
|
...(managedTempDir ? { EVOLCORE_RUNTIME_LOCK_DIR: path.join(managedTempDir, 'locks') } : {}),
|
|
5560
5799
|
...(taskDelegationToken ? { [AGENT_DELEGATION_TOKEN_ENV]: taskDelegationToken } : {}),
|
|
@@ -5642,9 +5881,11 @@ export class CodexRunner {
|
|
|
5642
5881
|
this.pruneThreadOperationLocks();
|
|
5643
5882
|
this.threadOperationLocks.clear();
|
|
5644
5883
|
await Promise.all([
|
|
5884
|
+
...[...this.retiredProviderClients].map(client => client.close()),
|
|
5645
5885
|
this.appServerClient?.close(),
|
|
5646
5886
|
this.fullAccessAppServerClient?.close(),
|
|
5647
5887
|
]);
|
|
5888
|
+
this.retiredProviderClients.clear();
|
|
5648
5889
|
this.codexSessionFileAdapter.close();
|
|
5649
5890
|
this.appServerClient = null;
|
|
5650
5891
|
this.fullAccessAppServerClient = null;
|