evolcore 0.0.10 → 0.0.12
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 +44 -0
- package/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -13,18 +13,21 @@ import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtecte
|
|
|
13
13
|
import { CodexAppServerClient } from './codex-app-server-client.js';
|
|
14
14
|
import { resolveOpenaiConfig } from './baseagent.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
16
17
|
import { isRetryableError } from '../utils/error-utils.js';
|
|
17
18
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
18
19
|
import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
19
20
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
20
21
|
import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
21
|
-
import {
|
|
22
|
+
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
23
|
+
import { classifyEvolcoreShellCommand, parseLiteralShellArgv, unwrapCodexShellCommandArgv } from '../core/permission/ec-command-parser.js';
|
|
22
24
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
23
25
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
24
26
|
import { buildSessionTurnList } from '../core/session/session-turns.js';
|
|
25
27
|
import { execFileSync } from 'child_process';
|
|
26
28
|
import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
|
|
27
29
|
import { createHash, randomBytes } from 'crypto';
|
|
30
|
+
import { buildModelRequestHeaders } from './request-identity.js';
|
|
28
31
|
import fs from 'fs';
|
|
29
32
|
import path from 'path';
|
|
30
33
|
import os from 'os';
|
|
@@ -116,10 +119,9 @@ const CODEX_CATALOG_FALLBACK = [
|
|
|
116
119
|
];
|
|
117
120
|
const CODEX_DELEGATION_CARRIER_METADATA_KEY = '__codexDelegationCarrierV1';
|
|
118
121
|
let codexCatalogCache = null;
|
|
119
|
-
// The permission bridge is version-locked to the
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
export const MIN_CODEX_CLI_VERSION = '0.144.1';
|
|
122
|
+
// The permission bridge is version-locked to the audited approval schema and
|
|
123
|
+
// managed PreToolUse hook behavior used by proactive first-tool enforcement.
|
|
124
|
+
export const MIN_CODEX_CLI_VERSION = '0.147.0';
|
|
123
125
|
export function parseCodexCliVersion(output) {
|
|
124
126
|
const match = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/);
|
|
125
127
|
return match?.[1] ?? null;
|
|
@@ -221,6 +223,8 @@ export class CodexRunner {
|
|
|
221
223
|
trackedApprovalItems = new Map();
|
|
222
224
|
threadDelegationCarriers = new Map();
|
|
223
225
|
pendingInterrupts = new Map();
|
|
226
|
+
threadOperationLocks = new Map();
|
|
227
|
+
activeOperationReleases = new Map();
|
|
224
228
|
appServerClient = null;
|
|
225
229
|
onSessionIdUpdate;
|
|
226
230
|
onCompactStart;
|
|
@@ -231,6 +235,12 @@ export class CodexRunner {
|
|
|
231
235
|
pendingInterruptTtlMs = 30_000;
|
|
232
236
|
constructor(config, callbacks) {
|
|
233
237
|
this.resolvedConfig = resolveOpenaiConfig(config);
|
|
238
|
+
this.resolvedConfig.headers = buildModelRequestHeaders({
|
|
239
|
+
baseagent: 'codex',
|
|
240
|
+
baseUrl: this.resolvedConfig.baseUrl,
|
|
241
|
+
agentAid: config.agents?.codex?.evolcoreAgentAid,
|
|
242
|
+
configuredHeaders: this.resolvedConfig.headers,
|
|
243
|
+
});
|
|
234
244
|
this.resolvedConfig.evolcoreAgentAid = config.agents?.codex?.evolcoreAgentAid;
|
|
235
245
|
this.resolvedConfig.evolcoreAgentConfig = config.agents?.codex?.evolcoreAgentConfig;
|
|
236
246
|
this.capabilities = {
|
|
@@ -281,15 +291,6 @@ export class CodexRunner {
|
|
|
281
291
|
const argv = parseLiteralShellArgv(command);
|
|
282
292
|
return argv?.[0] === 'ec' ? argv : undefined;
|
|
283
293
|
}
|
|
284
|
-
unwrapCodexShellCommandArgv(argv) {
|
|
285
|
-
if (argv.length !== 3 || argv[1] !== '-lc')
|
|
286
|
-
return undefined;
|
|
287
|
-
const shell = argv[0];
|
|
288
|
-
if (!['bash', '/bin/bash', '/usr/bin/bash', 'sh', '/bin/sh', '/usr/bin/sh'].includes(shell)) {
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
return argv[2];
|
|
292
|
-
}
|
|
293
294
|
approvedEvolcoreCommandArgv(toolInput) {
|
|
294
295
|
const explicitArgv = Array.isArray(toolInput.commandArgv)
|
|
295
296
|
&& toolInput.commandArgv.every(value => typeof value === 'string')
|
|
@@ -298,7 +299,7 @@ export class CodexRunner {
|
|
|
298
299
|
if (explicitArgv?.[0] === 'ec')
|
|
299
300
|
return explicitArgv;
|
|
300
301
|
const explicitWrappedCommand = explicitArgv
|
|
301
|
-
?
|
|
302
|
+
? unwrapCodexShellCommandArgv(explicitArgv)
|
|
302
303
|
: undefined;
|
|
303
304
|
if (explicitWrappedCommand !== undefined) {
|
|
304
305
|
return this.approvedLiteralEvolcoreCommandArgv(explicitWrappedCommand);
|
|
@@ -309,7 +310,7 @@ export class CodexRunner {
|
|
|
309
310
|
return directArgv;
|
|
310
311
|
const wrappedArgv = parseLiteralShellArgv(command);
|
|
311
312
|
const wrappedCommand = wrappedArgv
|
|
312
|
-
?
|
|
313
|
+
? unwrapCodexShellCommandArgv(wrappedArgv)
|
|
313
314
|
: undefined;
|
|
314
315
|
return wrappedCommand === undefined
|
|
315
316
|
? undefined
|
|
@@ -416,14 +417,35 @@ export class CodexRunner {
|
|
|
416
417
|
}
|
|
417
418
|
async resolveCapabilityThreadConfig(projectPath) {
|
|
418
419
|
const agentConfig = this.resolvedConfig.evolcoreAgentConfig;
|
|
419
|
-
if (!agentConfig)
|
|
420
|
-
return {
|
|
420
|
+
if (!agentConfig) {
|
|
421
|
+
return {
|
|
422
|
+
skills: { config: [{ name: 'eclink', enabled: false }] },
|
|
423
|
+
};
|
|
424
|
+
}
|
|
421
425
|
try {
|
|
422
|
-
|
|
426
|
+
const resolved = await resolveCodexCapabilityThreadConfigForProject(agentConfig, projectPath, 'codex');
|
|
427
|
+
const skills = this.configRecord(resolved.skills);
|
|
428
|
+
const configured = Array.isArray(skills?.config)
|
|
429
|
+
? skills.config.filter(entry => {
|
|
430
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
431
|
+
return true;
|
|
432
|
+
return entry.name !== 'eclink';
|
|
433
|
+
})
|
|
434
|
+
: [];
|
|
435
|
+
return this.mergeThreadConfig(resolved, {
|
|
436
|
+
skills: {
|
|
437
|
+
...(skills ?? {}),
|
|
438
|
+
config: [...configured, { name: 'eclink', enabled: false }],
|
|
439
|
+
},
|
|
440
|
+
}) ?? {
|
|
441
|
+
skills: { config: [{ name: 'eclink', enabled: false }] },
|
|
442
|
+
};
|
|
423
443
|
}
|
|
424
444
|
catch (error) {
|
|
425
445
|
logger.warn(`[CodexRunner] Failed to resolve Codex capability thread config: ${error}`);
|
|
426
|
-
return {
|
|
446
|
+
return {
|
|
447
|
+
skills: { config: [{ name: 'eclink', enabled: false }] },
|
|
448
|
+
};
|
|
427
449
|
}
|
|
428
450
|
}
|
|
429
451
|
configRecord(value) {
|
|
@@ -721,7 +743,9 @@ export class CodexRunner {
|
|
|
721
743
|
[profileName]: {
|
|
722
744
|
extends: mode === 'readonly' ? ':read-only' : ':workspace',
|
|
723
745
|
filesystem: {
|
|
724
|
-
...buildCodexProtectedFilesystemRules(resolveRoot()
|
|
746
|
+
...buildCodexProtectedFilesystemRules(resolveRoot(), {
|
|
747
|
+
denyLClassRead: mode === 'readonly',
|
|
748
|
+
}),
|
|
725
749
|
...this.resolveGitMetadataWriteRules(projectPath, mode),
|
|
726
750
|
...(mode !== 'readonly' && managedTempDir ? {
|
|
727
751
|
[managedTempDir]: 'write',
|
|
@@ -765,6 +789,35 @@ export class CodexRunner {
|
|
|
765
789
|
}
|
|
766
790
|
setSendPrompt(fn) { this.sendPromptFn = fn; }
|
|
767
791
|
setPermissionContext(sessionId, context) { this.permissionContexts.set(sessionId, context); }
|
|
792
|
+
async evaluatePreToolUse(threadId, toolName, toolInput) {
|
|
793
|
+
const sessionKey = this.findSessionKeyByThread(threadId);
|
|
794
|
+
const activeThread = this.activeSessions.get(sessionKey);
|
|
795
|
+
const context = this.permissionContexts.get(sessionKey);
|
|
796
|
+
if (!activeThread || activeThread !== threadId) {
|
|
797
|
+
return { ok: true, applicable: false };
|
|
798
|
+
}
|
|
799
|
+
const proactiveSession = context?.chatmode === 'proactive';
|
|
800
|
+
if (!context || (proactiveSession && !context.preToolUsePolicyHook)) {
|
|
801
|
+
return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore proactive session policy context is unavailable' };
|
|
802
|
+
}
|
|
803
|
+
// A tracked interactive thread still needs an authenticated, explicit
|
|
804
|
+
// allow so the managed hook can fail closed when no runner matches.
|
|
805
|
+
if (!context.preToolUsePolicyHook)
|
|
806
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
807
|
+
const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
808
|
+
? toolInput
|
|
809
|
+
: {};
|
|
810
|
+
try {
|
|
811
|
+
const result = context.preToolUsePolicyHook(toolName, normalizedInput);
|
|
812
|
+
if (result?.block) {
|
|
813
|
+
return { ok: true, applicable: true, decision: 'deny', reason: result.reason };
|
|
814
|
+
}
|
|
815
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
return { ok: false, applicable: true, decision: 'deny', reason: `policy hook failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
819
|
+
}
|
|
820
|
+
}
|
|
768
821
|
setPermissionGateway(gw) { this.permissionGateway = gw; }
|
|
769
822
|
// ── Stream management (needed by MessageProcessor) ──
|
|
770
823
|
registerStream(key, stream) {
|
|
@@ -774,13 +827,135 @@ export class CodexRunner {
|
|
|
774
827
|
this.activeStreams.delete(key);
|
|
775
828
|
this.activeAbortControllers.delete(key);
|
|
776
829
|
this.pendingInterrupts.delete(key);
|
|
830
|
+
const release = this.activeOperationReleases.get(key);
|
|
831
|
+
if (release) {
|
|
832
|
+
this.activeOperationReleases.delete(key);
|
|
833
|
+
release();
|
|
834
|
+
}
|
|
777
835
|
}
|
|
778
836
|
hasActiveStream(key) {
|
|
779
837
|
return this.activeStreams.has(key) || this.activeAbortControllers.has(key) || this.activeTurns.has(key);
|
|
780
838
|
}
|
|
839
|
+
threadOperationKey(threadId, sessionId) {
|
|
840
|
+
// Once a thread id is known it is the shared identity, including when
|
|
841
|
+
// multiple EvolCore sessions happen to address the same Codex thread.
|
|
842
|
+
if (threadId)
|
|
843
|
+
return `thread:${threadId}`;
|
|
844
|
+
if (sessionId)
|
|
845
|
+
return `session:${sessionId}`;
|
|
846
|
+
return 'session:unknown';
|
|
847
|
+
}
|
|
848
|
+
getThreadOperationLock(key) {
|
|
849
|
+
const existing = this.threadOperationLocks.get(key);
|
|
850
|
+
if (existing)
|
|
851
|
+
return existing;
|
|
852
|
+
const lock = { aliases: new Set([key]) };
|
|
853
|
+
this.threadOperationLocks.set(key, lock);
|
|
854
|
+
return lock;
|
|
855
|
+
}
|
|
856
|
+
bindThreadOperationKeys(...keys) {
|
|
857
|
+
const uniqueKeys = [...new Set(keys.filter(Boolean))];
|
|
858
|
+
if (uniqueKeys.length < 2)
|
|
859
|
+
return;
|
|
860
|
+
const locks = uniqueKeys.map(key => this.getThreadOperationLock(key));
|
|
861
|
+
const primary = locks[0];
|
|
862
|
+
for (const other of locks.slice(1)) {
|
|
863
|
+
if (other === primary)
|
|
864
|
+
continue;
|
|
865
|
+
const tails = [primary.tail, other.tail].filter((tail) => !!tail);
|
|
866
|
+
if (tails.length === 1) {
|
|
867
|
+
primary.tail = tails[0];
|
|
868
|
+
}
|
|
869
|
+
else if (tails.length > 1) {
|
|
870
|
+
const merged = Promise.all(tails).then(() => undefined);
|
|
871
|
+
primary.tail = merged;
|
|
872
|
+
merged.then(() => {
|
|
873
|
+
if (primary.tail === merged)
|
|
874
|
+
primary.tail = undefined;
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
for (const alias of other.aliases) {
|
|
878
|
+
primary.aliases.add(alias);
|
|
879
|
+
this.threadOperationLocks.set(alias, primary);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
isActiveThreadOperationAlias(key) {
|
|
884
|
+
if (key.startsWith('session:'))
|
|
885
|
+
return this.activeSessions.has(key.slice('session:'.length));
|
|
886
|
+
if (key.startsWith('thread:')) {
|
|
887
|
+
const threadId = key.slice('thread:'.length);
|
|
888
|
+
for (const activeThreadId of this.activeSessions.values()) {
|
|
889
|
+
if (activeThreadId === threadId)
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
pruneThreadOperationLocks() {
|
|
897
|
+
for (const [key, lock] of this.threadOperationLocks) {
|
|
898
|
+
if (lock.tail || this.isActiveThreadOperationAlias(key))
|
|
899
|
+
continue;
|
|
900
|
+
this.threadOperationLocks.delete(key);
|
|
901
|
+
lock.aliases.delete(key);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async acquireThreadOperation(key, operation) {
|
|
905
|
+
const lock = this.getThreadOperationLock(key);
|
|
906
|
+
const previous = lock.tail;
|
|
907
|
+
let releaseCurrent;
|
|
908
|
+
const current = new Promise(resolve => {
|
|
909
|
+
releaseCurrent = resolve;
|
|
910
|
+
});
|
|
911
|
+
lock.tail = current;
|
|
912
|
+
if (previous) {
|
|
913
|
+
logger.info(`[CodexRunner] queued ${operation} behind an active operation: ${key}`);
|
|
914
|
+
await previous;
|
|
915
|
+
}
|
|
916
|
+
let released = false;
|
|
917
|
+
return () => {
|
|
918
|
+
if (released)
|
|
919
|
+
return;
|
|
920
|
+
released = true;
|
|
921
|
+
if (lock.tail === current)
|
|
922
|
+
lock.tail = undefined;
|
|
923
|
+
releaseCurrent();
|
|
924
|
+
this.pruneThreadOperationLocks();
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async *holdThreadOperation(stream, sessionId, release) {
|
|
928
|
+
try {
|
|
929
|
+
for await (const item of stream)
|
|
930
|
+
yield item;
|
|
931
|
+
}
|
|
932
|
+
finally {
|
|
933
|
+
if (this.activeOperationReleases.get(sessionId) === release) {
|
|
934
|
+
this.activeOperationReleases.delete(sessionId);
|
|
935
|
+
}
|
|
936
|
+
release();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
781
939
|
// ── Core: runQuery ──
|
|
782
940
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
941
|
+
const knownThreadId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
942
|
+
const operationKey = this.threadOperationKey(knownThreadId, sessionId);
|
|
943
|
+
const release = await this.acquireThreadOperation(operationKey, 'thread/resume+turn/start');
|
|
944
|
+
try {
|
|
945
|
+
const stream = await this.runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv);
|
|
946
|
+
this.activeOperationReleases.set(sessionId, release);
|
|
947
|
+
return this.holdThreadOperation(stream, sessionId, release);
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
release();
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
783
955
|
let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
956
|
+
if (this.permissionContexts.get(sessionId)?.chatmode === 'proactive' && process.platform !== 'linux') {
|
|
957
|
+
throw new Error('Codex proactive managed PreToolUse enforcement currently requires Linux');
|
|
958
|
+
}
|
|
784
959
|
const resumingThread = !!agentSessionId;
|
|
785
960
|
const callModel = modelOverride?.model || this.model;
|
|
786
961
|
const callEffort = modelOverride?.effortMode === 'model_default'
|
|
@@ -819,7 +994,7 @@ export class CodexRunner {
|
|
|
819
994
|
approvalPolicy: effectiveApprovalPolicy,
|
|
820
995
|
approvalsReviewer: 'user',
|
|
821
996
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
822
|
-
config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()),
|
|
997
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()), callMode),
|
|
823
998
|
...(developerInstructions ? { developerInstructions } : {}),
|
|
824
999
|
};
|
|
825
1000
|
logger.info(`[CodexRunner] runQuery permMode=${requestedPermissionMode}->${callMode} ` +
|
|
@@ -862,6 +1037,7 @@ export class CodexRunner {
|
|
|
862
1037
|
}
|
|
863
1038
|
agentSessionId = threadId;
|
|
864
1039
|
this.activeSessions.set(sessionId, threadId);
|
|
1040
|
+
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${threadId}`);
|
|
865
1041
|
this.threadProjectPaths.set(threadId, path.resolve(projectPath));
|
|
866
1042
|
this.bindExternalToolBoundary(threadId, externalToolConfig);
|
|
867
1043
|
this.onSessionIdUpdate?.(sessionId, threadId);
|
|
@@ -1004,10 +1180,12 @@ export class CodexRunner {
|
|
|
1004
1180
|
}
|
|
1005
1181
|
if (agentSessionId) {
|
|
1006
1182
|
this.activeSessions.set(sessionId, agentSessionId);
|
|
1183
|
+
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${agentSessionId}`);
|
|
1007
1184
|
}
|
|
1008
1185
|
else {
|
|
1009
1186
|
this.activeSessions.delete(sessionId);
|
|
1010
1187
|
}
|
|
1188
|
+
this.pruneThreadOperationLocks();
|
|
1011
1189
|
this.onSessionIdUpdate?.(sessionId, agentSessionId);
|
|
1012
1190
|
}
|
|
1013
1191
|
async closeSession(sessionId) {
|
|
@@ -1026,6 +1204,7 @@ export class CodexRunner {
|
|
|
1026
1204
|
this.unbindExternalToolBoundary(threadId);
|
|
1027
1205
|
}
|
|
1028
1206
|
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1207
|
+
this.pruneThreadOperationLocks();
|
|
1029
1208
|
}
|
|
1030
1209
|
resolveSessionFile(agentSessionId, _projectPath) {
|
|
1031
1210
|
// Codex session 文件: ~/.codex/sessions/YYYY/MM/DD/rollout-*-{threadId}.jsonl
|
|
@@ -1058,10 +1237,12 @@ export class CodexRunner {
|
|
|
1058
1237
|
this.threadDelegationCarriers.delete(threadId);
|
|
1059
1238
|
this.unbindExternalToolBoundary(threadId);
|
|
1060
1239
|
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1240
|
+
this.pruneThreadOperationLocks();
|
|
1061
1241
|
this.onSessionIdUpdate?.(sessionId, '');
|
|
1062
1242
|
return true;
|
|
1063
1243
|
}
|
|
1064
1244
|
async compactSession(_sessionId, agentSessionId, _projectPath) {
|
|
1245
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId, _sessionId), 'compact');
|
|
1065
1246
|
try {
|
|
1066
1247
|
const appServer = this.getAppServerClient();
|
|
1067
1248
|
this.onCompactStart?.(_sessionId);
|
|
@@ -1086,7 +1267,7 @@ export class CodexRunner {
|
|
|
1086
1267
|
approvalPolicy: compactPolicy,
|
|
1087
1268
|
approvalsReviewer: 'user',
|
|
1088
1269
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
1089
|
-
config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()),
|
|
1270
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()), compactMode),
|
|
1090
1271
|
});
|
|
1091
1272
|
this.threadProjectPaths.set(agentSessionId, path.resolve(_projectPath));
|
|
1092
1273
|
this.threadDelegationCarriers.set(agentSessionId, compactDelegationCarrier);
|
|
@@ -1098,6 +1279,9 @@ export class CodexRunner {
|
|
|
1098
1279
|
logger.error('[CodexRunner] Compact failed:', error);
|
|
1099
1280
|
return false;
|
|
1100
1281
|
}
|
|
1282
|
+
finally {
|
|
1283
|
+
release();
|
|
1284
|
+
}
|
|
1101
1285
|
}
|
|
1102
1286
|
async compact(sessionId, agentSessionId, projectPath) {
|
|
1103
1287
|
return this.compactSession(sessionId, agentSessionId, projectPath);
|
|
@@ -1223,6 +1407,15 @@ export class CodexRunner {
|
|
|
1223
1407
|
return newest?.path;
|
|
1224
1408
|
}
|
|
1225
1409
|
async forkSession(agentSessionId, projectPath, title) {
|
|
1410
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'fork');
|
|
1411
|
+
try {
|
|
1412
|
+
return await this.forkSessionLocked(agentSessionId, projectPath, title);
|
|
1413
|
+
}
|
|
1414
|
+
finally {
|
|
1415
|
+
release();
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
async forkSessionLocked(agentSessionId, projectPath, title) {
|
|
1226
1419
|
const sessionKey = this.findSessionKeyByThread(agentSessionId);
|
|
1227
1420
|
const mode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1228
1421
|
const executionSandbox = this.buildExecutionSandboxOptions(sessionKey, mode, projectPath);
|
|
@@ -1236,7 +1429,7 @@ export class CodexRunner {
|
|
|
1236
1429
|
approvalPolicy: this.toApprovalPolicy(mode),
|
|
1237
1430
|
approvalsReviewer: 'user',
|
|
1238
1431
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
1239
|
-
config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()),
|
|
1432
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()), mode),
|
|
1240
1433
|
});
|
|
1241
1434
|
const forkedThreadId = response.thread?.id;
|
|
1242
1435
|
if (!forkedThreadId)
|
|
@@ -1247,18 +1440,30 @@ export class CodexRunner {
|
|
|
1247
1440
|
return forkedThreadId;
|
|
1248
1441
|
}
|
|
1249
1442
|
async forkSessionAt(agentSessionId, projectPath, assistantMessageId, title) {
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
const
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1443
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'forkAt');
|
|
1444
|
+
try {
|
|
1445
|
+
const turns = buildSessionTurnList(await this.getSessionMessages(agentSessionId, projectPath));
|
|
1446
|
+
const targetIndex = turns.findIndex(turn => turn.assistantUuid === assistantMessageId);
|
|
1447
|
+
if (targetIndex < 0)
|
|
1448
|
+
throw new Error('fork point not found');
|
|
1449
|
+
const forkedThreadId = await this.forkSessionLocked(agentSessionId, projectPath, title);
|
|
1450
|
+
const turnsToDiscard = turns.length - targetIndex - 1;
|
|
1451
|
+
if (turnsToDiscard > 0) {
|
|
1452
|
+
const rollbackRelease = await this.acquireThreadOperation(this.threadOperationKey(forkedThreadId), 'rollback');
|
|
1453
|
+
try {
|
|
1454
|
+
const rolledBack = await this.rollbackSessionTurnsLocked(forkedThreadId, turnsToDiscard);
|
|
1455
|
+
if (!rolledBack)
|
|
1456
|
+
throw new Error('forked thread rollback failed');
|
|
1457
|
+
}
|
|
1458
|
+
finally {
|
|
1459
|
+
rollbackRelease();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return forkedThreadId;
|
|
1463
|
+
}
|
|
1464
|
+
finally {
|
|
1465
|
+
release();
|
|
1260
1466
|
}
|
|
1261
|
-
return forkedThreadId;
|
|
1262
1467
|
}
|
|
1263
1468
|
async setSessionName(agentSessionId, name) {
|
|
1264
1469
|
return this.getAppServerClient().threadSetName(agentSessionId, name);
|
|
@@ -1390,6 +1595,25 @@ export class CodexRunner {
|
|
|
1390
1595
|
// proactive 模式行为策略(首次工具调用必须是 ec msg send)
|
|
1391
1596
|
const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
|
|
1392
1597
|
if (policyResult?.block) {
|
|
1598
|
+
logger.info(`[CodexRunner] app-server approval declined by session policy: session=${sessionKey} ` +
|
|
1599
|
+
`requestId=${request.id ?? '<missing>'} code=${policyResult.policyCode ?? 'session_policy_hook'} ` +
|
|
1600
|
+
`reason=${policyResult.reason ?? 'policy denied'} executed=false`);
|
|
1601
|
+
try {
|
|
1602
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
1603
|
+
code: 'operation_blocked',
|
|
1604
|
+
severity: 'warning',
|
|
1605
|
+
phase: 'execution',
|
|
1606
|
+
occurredAt: Date.now(),
|
|
1607
|
+
toolName,
|
|
1608
|
+
...(request.id !== undefined ? { requestId: String(request.id) } : {}),
|
|
1609
|
+
policyCode: policyResult.policyCode ?? 'session_policy_hook',
|
|
1610
|
+
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1611
|
+
effect: 'operation_skipped',
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
catch (error) {
|
|
1615
|
+
logger.warn(`[CodexRunner] failed to record policy-hook denial: session=${sessionKey} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1616
|
+
}
|
|
1393
1617
|
return this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1394
1618
|
}
|
|
1395
1619
|
const summary = this.summarizeAppServerRequest(request.method, params);
|
|
@@ -1403,7 +1627,7 @@ export class CodexRunner {
|
|
|
1403
1627
|
const sessionMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1404
1628
|
logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${summary}`);
|
|
1405
1629
|
try {
|
|
1406
|
-
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd);
|
|
1630
|
+
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id);
|
|
1407
1631
|
if (decision !== 'deny'
|
|
1408
1632
|
&& (request.method === 'item/commandExecution/requestApproval' || request.method === 'execCommandApproval')
|
|
1409
1633
|
&& this.approvedEvolcoreCommandArgv(toolInput)) {
|
|
@@ -1934,7 +2158,7 @@ export class CodexRunner {
|
|
|
1934
2158
|
return true;
|
|
1935
2159
|
});
|
|
1936
2160
|
}
|
|
1937
|
-
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath) {
|
|
2161
|
+
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath, requestId) {
|
|
1938
2162
|
const permissionContext = this.permissionContexts.get(sessionKey);
|
|
1939
2163
|
const permissionPrompt = permissionContext?.sendPrompt ?? this.sendPromptFn;
|
|
1940
2164
|
const hasFilesystemPermissions = (toolName === 'Bash' || toolName === 'PermissionGrant')
|
|
@@ -1949,8 +2173,27 @@ export class CodexRunner {
|
|
|
1949
2173
|
// per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
|
|
1950
2174
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1951
2175
|
const mode = normalizePermissionMode(rawMode).mode;
|
|
2176
|
+
const recordBlockedOperation = async (policyCode, operationInput = toolInput) => {
|
|
2177
|
+
try {
|
|
2178
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
2179
|
+
code: 'operation_blocked',
|
|
2180
|
+
severity: 'warning',
|
|
2181
|
+
phase: 'execution',
|
|
2182
|
+
occurredAt: Date.now(),
|
|
2183
|
+
toolName,
|
|
2184
|
+
...(typeof requestId === 'string' || typeof requestId === 'number' ? { requestId: String(requestId) } : {}),
|
|
2185
|
+
policyCode,
|
|
2186
|
+
summary: summarizeToolInputForAudit(toolName, operationInput).slice(0, 512),
|
|
2187
|
+
effect: 'operation_skipped',
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
catch (error) {
|
|
2191
|
+
logger.warn(`[CodexRunner] failed to record blocked operation: session=${sessionKey} tool=${toolName} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
1952
2194
|
const preflight = evaluateToolPreflight(toolName, toolInput, {
|
|
1953
2195
|
sessionId: sessionKey,
|
|
2196
|
+
selfAid: permissionContext?.selfAid,
|
|
1954
2197
|
channel: permissionContext?.channel,
|
|
1955
2198
|
userId: permissionContext?.userId,
|
|
1956
2199
|
role: permissionContext?.role,
|
|
@@ -1958,8 +2201,12 @@ export class CodexRunner {
|
|
|
1958
2201
|
projectPath: operationCwd,
|
|
1959
2202
|
workspacePath,
|
|
1960
2203
|
});
|
|
1961
|
-
if (preflight.behavior === 'deny')
|
|
2204
|
+
if (preflight.behavior === 'deny') {
|
|
2205
|
+
if (preflight.policyCode) {
|
|
2206
|
+
await recordBlockedOperation(preflight.policyCode);
|
|
2207
|
+
}
|
|
1962
2208
|
return 'deny';
|
|
2209
|
+
}
|
|
1963
2210
|
if (preflight.behavior === 'allow')
|
|
1964
2211
|
return 'allow';
|
|
1965
2212
|
const checkedInput = preflight.input;
|
|
@@ -1983,8 +2230,10 @@ export class CodexRunner {
|
|
|
1983
2230
|
const grantScope = `codex:${this.permissionProfileName(sessionKey)}:${mode}`;
|
|
1984
2231
|
if (mode === 'readonly') {
|
|
1985
2232
|
const readonly = this.checkCodexReadonly(toolName, checkedInput, operationCwd, sessionKey);
|
|
1986
|
-
if (readonly.behavior === 'deny')
|
|
2233
|
+
if (readonly.behavior === 'deny') {
|
|
2234
|
+
await recordBlockedOperation('readonly_mode', checkedInput);
|
|
1987
2235
|
return 'deny';
|
|
2236
|
+
}
|
|
1988
2237
|
return 'allow';
|
|
1989
2238
|
}
|
|
1990
2239
|
if (toolName.startsWith('MCP:')) {
|
|
@@ -1992,13 +2241,19 @@ export class CodexRunner {
|
|
|
1992
2241
|
}
|
|
1993
2242
|
// auto 模式下检查危险命令,危险命令自动拒绝
|
|
1994
2243
|
if (mode === 'auto') {
|
|
1995
|
-
if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions)
|
|
2244
|
+
if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions) {
|
|
2245
|
+
await recordBlockedOperation('auto_permission_expansion', checkedInput);
|
|
1996
2246
|
return 'deny';
|
|
1997
|
-
|
|
2247
|
+
}
|
|
2248
|
+
if (toolName === 'PermissionGrant' && !hasNetworkPermissions) {
|
|
2249
|
+
await recordBlockedOperation('auto_permission_expansion', checkedInput);
|
|
1998
2250
|
return 'deny';
|
|
2251
|
+
}
|
|
1999
2252
|
const dangerous = checkDangerousCommand(toolName, checkedInput);
|
|
2000
|
-
if (dangerous.isDangerous)
|
|
2253
|
+
if (dangerous.isDangerous) {
|
|
2254
|
+
await recordBlockedOperation('auto_dangerous_command', checkedInput);
|
|
2001
2255
|
return 'deny';
|
|
2256
|
+
}
|
|
2002
2257
|
return 'allow';
|
|
2003
2258
|
}
|
|
2004
2259
|
const requiresSandboxExpansionApproval = hasFileChangeExpansion
|
|
@@ -2051,6 +2306,15 @@ export class CodexRunner {
|
|
|
2051
2306
|
throw new Error('Unsupported Codex app-server request: ' + method);
|
|
2052
2307
|
}
|
|
2053
2308
|
async rollbackSessionTurns(agentSessionId, _projectPath, numTurns) {
|
|
2309
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'rollback');
|
|
2310
|
+
try {
|
|
2311
|
+
return await this.rollbackSessionTurnsLocked(agentSessionId, numTurns);
|
|
2312
|
+
}
|
|
2313
|
+
finally {
|
|
2314
|
+
release();
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
async rollbackSessionTurnsLocked(agentSessionId, numTurns) {
|
|
2054
2318
|
if (numTurns < 1)
|
|
2055
2319
|
return true;
|
|
2056
2320
|
const response = await this.getAppServerClient().threadRollback(agentSessionId, numTurns);
|
|
@@ -2089,7 +2353,11 @@ export class CodexRunner {
|
|
|
2089
2353
|
}
|
|
2090
2354
|
readGitHeadFile(projectPath, filePath) {
|
|
2091
2355
|
try {
|
|
2092
|
-
return execFileSync('git', ['show', `HEAD:${filePath.replace(/\\/g, '/')}`], {
|
|
2356
|
+
return execFileSync('git', ['show', `HEAD:${filePath.replace(/\\/g, '/')}`], {
|
|
2357
|
+
cwd: projectPath,
|
|
2358
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
2359
|
+
windowsHide: process.platform === 'win32',
|
|
2360
|
+
});
|
|
2093
2361
|
}
|
|
2094
2362
|
catch {
|
|
2095
2363
|
return null;
|
|
@@ -2810,6 +3078,24 @@ export class CodexRunner {
|
|
|
2810
3078
|
},
|
|
2811
3079
|
};
|
|
2812
3080
|
}
|
|
3081
|
+
sanitizeThreadEnvironmentConfig(config, permissionMode) {
|
|
3082
|
+
if (!config)
|
|
3083
|
+
return null;
|
|
3084
|
+
const policy = this.configRecord(config.shell_environment_policy);
|
|
3085
|
+
const set = this.configRecord(policy?.set);
|
|
3086
|
+
if (!policy || !set)
|
|
3087
|
+
return config;
|
|
3088
|
+
const safeSet = sanitizeShellExecutionEnvironment(set, {
|
|
3089
|
+
lockExecutableResolution: permissionMode === 'readonly',
|
|
3090
|
+
});
|
|
3091
|
+
return {
|
|
3092
|
+
...config,
|
|
3093
|
+
shell_environment_policy: {
|
|
3094
|
+
...policy,
|
|
3095
|
+
set: safeSet,
|
|
3096
|
+
},
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
2813
3099
|
async dispose() {
|
|
2814
3100
|
// Abort all active streams
|
|
2815
3101
|
for (const [key, controller] of this.activeAbortControllers) {
|
|
@@ -2827,6 +3113,8 @@ export class CodexRunner {
|
|
|
2827
3113
|
this.pendingInterrupts.clear();
|
|
2828
3114
|
this.permissionContexts.clear();
|
|
2829
3115
|
this.chatModes.clear();
|
|
3116
|
+
this.pruneThreadOperationLocks();
|
|
3117
|
+
this.threadOperationLocks.clear();
|
|
2830
3118
|
for (const sessionId of [...this.sessionTempDirs.keys()])
|
|
2831
3119
|
this.cleanupSessionTempDir(sessionId);
|
|
2832
3120
|
await this.appServerClient?.close();
|