evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -7,22 +7,23 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
9
9
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
10
|
-
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
11
|
-
import { resolvePhaseOneExecutionSandbox } from '../core/permission/execution-sandbox.js';
|
|
10
|
+
import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
12
11
|
import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate } from '../core/protected-paths.js';
|
|
13
12
|
import { CodexAppServerClient } from './codex-app-server-client.js';
|
|
14
13
|
import { resolveOpenaiConfig } from './baseagent.js';
|
|
15
14
|
import { logger } from '../utils/logger.js';
|
|
16
15
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
16
|
+
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
17
17
|
import { isRetryableError } from '../utils/error-utils.js';
|
|
18
18
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
19
19
|
import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
20
20
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
21
21
|
import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
22
22
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
23
|
-
import { classifyEvolcoreShellCommand, parseLiteralShellArgv, unwrapCodexShellCommandArgv } from '../core/permission/ec-command-parser.js';
|
|
23
|
+
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, parseLiteralShellArgv, unwrapCodexShellCommandArgv, } from '../core/permission/ec-command-parser.js';
|
|
24
24
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
25
25
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
26
|
+
import { ensureProcessManagedTempDir } from '../cli/task-context.js';
|
|
26
27
|
import { buildSessionTurnList } from '../core/session/session-turns.js';
|
|
27
28
|
import { execFileSync } from 'child_process';
|
|
28
29
|
import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
|
|
@@ -76,6 +77,39 @@ class AsyncEventQueue {
|
|
|
76
77
|
}
|
|
77
78
|
const managedTempDirsForExitCleanup = new Map();
|
|
78
79
|
let managedTempExitCleanupRegistered = false;
|
|
80
|
+
function isTrustedManagedTempDirectory(directory, parent) {
|
|
81
|
+
try {
|
|
82
|
+
const parentPath = path.resolve(parent);
|
|
83
|
+
const directoryPath = path.resolve(directory);
|
|
84
|
+
// The session directory must be a direct child of the private parent.
|
|
85
|
+
// This lexical check is required before canonicalization so a replaced
|
|
86
|
+
// parent symlink cannot turn an old session path into another tree.
|
|
87
|
+
if (path.dirname(directoryPath) !== parentPath)
|
|
88
|
+
return false;
|
|
89
|
+
const trustedDirectory = (candidate) => {
|
|
90
|
+
const stat = fs.lstatSync(candidate);
|
|
91
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
92
|
+
return false;
|
|
93
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
94
|
+
return false;
|
|
95
|
+
return (stat.mode & 0o077) === 0;
|
|
96
|
+
};
|
|
97
|
+
if (!trustedDirectory(parentPath) || !trustedDirectory(directoryPath))
|
|
98
|
+
return false;
|
|
99
|
+
const canonicalParent = resolveProtectedCandidate(parentPath);
|
|
100
|
+
const canonicalDirectory = resolveProtectedCandidate(directoryPath);
|
|
101
|
+
// Keep the canonical and lexical paths identical after the lstat checks;
|
|
102
|
+
// this rejects symlink replacement in either node or an existing ancestor.
|
|
103
|
+
return canonicalParent === parentPath
|
|
104
|
+
&& canonicalDirectory === directoryPath
|
|
105
|
+
&& canonicalDirectory !== canonicalParent
|
|
106
|
+
&& path.dirname(canonicalDirectory) === canonicalParent
|
|
107
|
+
&& isSameOrDescendant(canonicalDirectory, canonicalParent);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
79
113
|
function registerManagedTempDirForExitCleanup(directory, parent) {
|
|
80
114
|
managedTempDirsForExitCleanup.set(directory, parent);
|
|
81
115
|
if (managedTempExitCleanupRegistered)
|
|
@@ -84,10 +118,8 @@ function registerManagedTempDirForExitCleanup(directory, parent) {
|
|
|
84
118
|
process.once('exit', () => {
|
|
85
119
|
for (const [candidatePath, parentPath] of managedTempDirsForExitCleanup) {
|
|
86
120
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (candidate !== parentCandidate && isSameOrDescendant(candidate, parentCandidate)) {
|
|
90
|
-
fs.rmSync(candidate, { recursive: true, force: true });
|
|
121
|
+
if (isTrustedManagedTempDirectory(candidatePath, parentPath)) {
|
|
122
|
+
fs.rmSync(path.resolve(candidatePath), { recursive: true, force: true });
|
|
91
123
|
}
|
|
92
124
|
}
|
|
93
125
|
catch {
|
|
@@ -121,7 +153,7 @@ const CODEX_DELEGATION_CARRIER_METADATA_KEY = '__codexDelegationCarrierV1';
|
|
|
121
153
|
let codexCatalogCache = null;
|
|
122
154
|
// The permission bridge is version-locked to the audited approval schema and
|
|
123
155
|
// managed PreToolUse hook behavior used by proactive first-tool enforcement.
|
|
124
|
-
export const MIN_CODEX_CLI_VERSION = '0.
|
|
156
|
+
export const MIN_CODEX_CLI_VERSION = '0.144.1';
|
|
125
157
|
export function parseCodexCliVersion(output) {
|
|
126
158
|
const match = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/);
|
|
127
159
|
return match?.[1] ?? null;
|
|
@@ -144,19 +176,18 @@ export function getCodexCliVersion() {
|
|
|
144
176
|
}
|
|
145
177
|
export function getCodexAppServerAvailability() {
|
|
146
178
|
if (!resolveCodexCliPath()) {
|
|
147
|
-
|
|
148
|
-
return { available: false, reason: `未检测到可用 Codex CLI。${upgradeHint}` };
|
|
179
|
+
return { available: false, reason: '未检测到 Codex CLI。' };
|
|
149
180
|
}
|
|
150
181
|
const version = getCodexCliVersion();
|
|
151
|
-
const upgradeHint = '请升级 Codex CLI:npm install -g @openai/codex@latest';
|
|
152
182
|
if (!version) {
|
|
153
|
-
return { available: false, reason:
|
|
183
|
+
return { available: false, reason: '无法读取 Codex CLI 版本。' };
|
|
154
184
|
}
|
|
155
185
|
if (!isCodexCliVersionSupported(version)) {
|
|
156
186
|
return {
|
|
157
187
|
available: false,
|
|
158
188
|
version,
|
|
159
|
-
reason: `Codex CLI ${version} 低于最低要求 ${MIN_CODEX_CLI_VERSION}
|
|
189
|
+
reason: `Codex CLI ${version} 低于最低要求 ${MIN_CODEX_CLI_VERSION}。请升级 Codex CLI:npm install -g @openai/codex@latest`,
|
|
190
|
+
requiresUpgrade: true,
|
|
160
191
|
};
|
|
161
192
|
}
|
|
162
193
|
try {
|
|
@@ -168,7 +199,7 @@ export function getCodexAppServerAvailability() {
|
|
|
168
199
|
return { available: true, version };
|
|
169
200
|
}
|
|
170
201
|
catch {
|
|
171
|
-
return { available: false, version, reason: `Codex CLI ${version} 不支持 app-server
|
|
202
|
+
return { available: false, version, reason: `Codex CLI ${version} 不支持 app-server。` };
|
|
172
203
|
}
|
|
173
204
|
}
|
|
174
205
|
export function isCodexAppServerAvailable() {
|
|
@@ -208,6 +239,9 @@ export function getCodexEfforts(model) {
|
|
|
208
239
|
// ── Codex Runner ──
|
|
209
240
|
export class CodexRunner {
|
|
210
241
|
name = 'codex';
|
|
242
|
+
// turn/start persists a new user turn before runQuery resolves. App-server
|
|
243
|
+
// has no retry-in-place API for a completed or failed turn.
|
|
244
|
+
retryInputSemantics = 'at_most_once';
|
|
211
245
|
capabilities;
|
|
212
246
|
model;
|
|
213
247
|
effort;
|
|
@@ -282,51 +316,82 @@ export class CodexRunner {
|
|
|
282
316
|
createDelegationCarrier() {
|
|
283
317
|
return randomBytes(32).toString('base64url');
|
|
284
318
|
}
|
|
285
|
-
approvedLiteralEvolcoreCommandArgv(command) {
|
|
319
|
+
approvedLiteralEvolcoreCommandArgv(command, managedTempDir) {
|
|
286
320
|
const classification = classifyEvolcoreShellCommand(command);
|
|
287
321
|
if (classification.kind === 'bounded-output')
|
|
288
322
|
return classification.command.argv;
|
|
289
323
|
if (classification.kind !== 'literal')
|
|
290
324
|
return undefined;
|
|
291
|
-
|
|
325
|
+
// Keep the approval/delegation parser aligned with the preflight parser:
|
|
326
|
+
// the session-managed `$TMPDIR` token is a permitted EC path reference.
|
|
327
|
+
const argv = parseLiteralShellArgv(command, {
|
|
328
|
+
allowManagedTmpDir: true,
|
|
329
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
330
|
+
});
|
|
331
|
+
if (argv && containsLiteralManagedTmpDirOutsideSendContent(argv))
|
|
332
|
+
return undefined;
|
|
292
333
|
return argv?.[0] === 'ec' ? argv : undefined;
|
|
293
334
|
}
|
|
294
|
-
approvedEvolcoreCommandArgv(toolInput) {
|
|
335
|
+
approvedEvolcoreCommandArgv(toolInput, managedTempDir) {
|
|
295
336
|
const explicitArgv = Array.isArray(toolInput.commandArgv)
|
|
296
337
|
&& toolInput.commandArgv.every(value => typeof value === 'string')
|
|
297
338
|
? toolInput.commandArgv
|
|
298
339
|
: undefined;
|
|
299
|
-
if (explicitArgv?.[0] === 'ec')
|
|
300
|
-
return explicitArgv
|
|
340
|
+
if (explicitArgv?.[0] === 'ec') {
|
|
341
|
+
return explicitArgv.some(value => value.includes('\0'))
|
|
342
|
+
|| containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
|
|
343
|
+
? undefined
|
|
344
|
+
: explicitArgv;
|
|
345
|
+
}
|
|
301
346
|
const explicitWrappedCommand = explicitArgv
|
|
302
347
|
? unwrapCodexShellCommandArgv(explicitArgv)
|
|
303
348
|
: undefined;
|
|
304
349
|
if (explicitWrappedCommand !== undefined) {
|
|
305
|
-
return this.approvedLiteralEvolcoreCommandArgv(explicitWrappedCommand);
|
|
350
|
+
return this.approvedLiteralEvolcoreCommandArgv(explicitWrappedCommand, managedTempDir);
|
|
306
351
|
}
|
|
307
352
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
308
|
-
const directArgv = this.approvedLiteralEvolcoreCommandArgv(command);
|
|
353
|
+
const directArgv = this.approvedLiteralEvolcoreCommandArgv(command, managedTempDir);
|
|
309
354
|
if (directArgv)
|
|
310
355
|
return directArgv;
|
|
311
|
-
const wrappedArgv = parseLiteralShellArgv(command
|
|
356
|
+
const wrappedArgv = parseLiteralShellArgv(command, {
|
|
357
|
+
allowManagedTmpDir: true,
|
|
358
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
359
|
+
});
|
|
312
360
|
const wrappedCommand = wrappedArgv
|
|
313
361
|
? unwrapCodexShellCommandArgv(wrappedArgv)
|
|
314
362
|
: undefined;
|
|
315
363
|
return wrappedCommand === undefined
|
|
316
364
|
? undefined
|
|
317
|
-
: this.approvedLiteralEvolcoreCommandArgv(wrappedCommand);
|
|
365
|
+
: this.approvedLiteralEvolcoreCommandArgv(wrappedCommand, managedTempDir);
|
|
318
366
|
}
|
|
319
|
-
|
|
367
|
+
isManagedEvolcoreCommandIntent(toolInput) {
|
|
368
|
+
if (Array.isArray(toolInput.commandArgv)) {
|
|
369
|
+
const rawArgv = toolInput.commandArgv;
|
|
370
|
+
if (rawArgv[0] === 'ec')
|
|
371
|
+
return true;
|
|
372
|
+
if (!rawArgv.every(value => typeof value === 'string'))
|
|
373
|
+
return false;
|
|
374
|
+
const wrappedCommand = unwrapCodexShellCommandArgv(rawArgv);
|
|
375
|
+
return wrappedCommand !== undefined
|
|
376
|
+
&& classifyEvolcoreShellCommand(wrappedCommand).kind !== 'none';
|
|
377
|
+
}
|
|
378
|
+
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
379
|
+
return classifyEvolcoreShellCommand(command).kind !== 'none';
|
|
380
|
+
}
|
|
381
|
+
armApprovedDelegationCommand(sessionId, argv) {
|
|
320
382
|
const threadId = this.activeSessions.get(sessionId);
|
|
321
383
|
const carrierToken = threadId ? this.threadDelegationCarriers.get(threadId) : undefined;
|
|
322
|
-
const argv = this.approvedEvolcoreCommandArgv(toolInput);
|
|
323
|
-
if (!carrierToken || !argv)
|
|
324
|
-
return false;
|
|
325
384
|
const commandHash = hashDelegatedCommandArgv(argv);
|
|
326
385
|
const arm = this.permissionContexts.get(sessionId)?.armApprovedDelegationCommand;
|
|
327
|
-
if (!commandHash || !arm)
|
|
386
|
+
if (!carrierToken || !commandHash || !arm)
|
|
328
387
|
return false;
|
|
329
|
-
|
|
388
|
+
try {
|
|
389
|
+
return arm(carrierToken, commandHash);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
logger.warn(`[CodexRunner] delegation arm callback failed: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
330
395
|
}
|
|
331
396
|
async readThreadDelegationCarrier(appServer, threadId) {
|
|
332
397
|
try {
|
|
@@ -750,6 +815,8 @@ export class CodexRunner {
|
|
|
750
815
|
...(mode !== 'readonly' && managedTempDir ? {
|
|
751
816
|
[managedTempDir]: 'write',
|
|
752
817
|
[path.join(managedTempDir, '**')]: 'write',
|
|
818
|
+
[path.join(path.dirname(managedTempDir), 'evolcore-locks')]: 'write',
|
|
819
|
+
[path.join(path.dirname(managedTempDir), 'evolcore-locks', '**')]: 'write',
|
|
753
820
|
} : {}),
|
|
754
821
|
},
|
|
755
822
|
network,
|
|
@@ -789,6 +856,17 @@ export class CodexRunner {
|
|
|
789
856
|
}
|
|
790
857
|
setSendPrompt(fn) { this.sendPromptFn = fn; }
|
|
791
858
|
setPermissionContext(sessionId, context) { this.permissionContexts.set(sessionId, context); }
|
|
859
|
+
async assertProactiveHookAvailability(sessionId, appServer) {
|
|
860
|
+
if (this.permissionContexts.get(sessionId)?.chatmode !== 'proactive')
|
|
861
|
+
return;
|
|
862
|
+
if (process.platform === 'win32') {
|
|
863
|
+
await appServer.assertManagedHooksAvailable();
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (process.platform !== 'linux') {
|
|
867
|
+
throw new Error('Codex proactive managed PreToolUse enforcement requires Linux or an installed Windows managed hook');
|
|
868
|
+
}
|
|
869
|
+
}
|
|
792
870
|
async evaluatePreToolUse(threadId, toolName, toolInput) {
|
|
793
871
|
const sessionKey = this.findSessionKeyByThread(threadId);
|
|
794
872
|
const activeThread = this.activeSessions.get(sessionKey);
|
|
@@ -953,9 +1031,6 @@ export class CodexRunner {
|
|
|
953
1031
|
}
|
|
954
1032
|
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
955
1033
|
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
|
-
}
|
|
959
1034
|
const resumingThread = !!agentSessionId;
|
|
960
1035
|
const callModel = modelOverride?.model || this.model;
|
|
961
1036
|
const callEffort = modelOverride?.effortMode === 'model_default'
|
|
@@ -975,6 +1050,7 @@ export class CodexRunner {
|
|
|
975
1050
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
976
1051
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
|
|
977
1052
|
const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, callMode);
|
|
1053
|
+
await this.assertProactiveHookAvailability(sessionId, appServer);
|
|
978
1054
|
const managedTempInstruction = this.managedTempInstruction(sessionId);
|
|
979
1055
|
const developerInstructions = [systemPromptAppend, managedTempInstruction].filter(Boolean).join('\n\n') || undefined;
|
|
980
1056
|
let knownDelegationCarrier = agentSessionId
|
|
@@ -1044,7 +1120,7 @@ export class CodexRunner {
|
|
|
1044
1120
|
const controller = new AbortController();
|
|
1045
1121
|
this.activeAbortControllers.set(sessionId, controller);
|
|
1046
1122
|
const tempFiles = [];
|
|
1047
|
-
const input = this.buildAppServerInput(prompt, images, tempFiles);
|
|
1123
|
+
const input = this.buildAppServerInput(prompt, images, tempFiles, sessionId, runtimeEnv);
|
|
1048
1124
|
const queue = new AsyncEventQueue();
|
|
1049
1125
|
controller.signal.addEventListener('abort', () => queue.end(), { once: true });
|
|
1050
1126
|
const state = {
|
|
@@ -1259,6 +1335,7 @@ export class CodexRunner {
|
|
|
1259
1335
|
const executionSandbox = this.buildExecutionSandboxOptions(_sessionId, compactMode, _projectPath);
|
|
1260
1336
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(_projectPath);
|
|
1261
1337
|
const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, _projectPath, capabilityConfig, compactMode);
|
|
1338
|
+
await this.assertProactiveHookAvailability(_sessionId, appServer);
|
|
1262
1339
|
const compactDelegationCarrier = this.threadDelegationCarriers.get(agentSessionId)
|
|
1263
1340
|
?? this.createDelegationCarrier();
|
|
1264
1341
|
await appServer.threadResume(agentSessionId, _projectPath, {
|
|
@@ -1422,6 +1499,8 @@ export class CodexRunner {
|
|
|
1422
1499
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
|
|
1423
1500
|
const appServer = this.getAppServerClient();
|
|
1424
1501
|
const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, mode);
|
|
1502
|
+
if (sessionKey)
|
|
1503
|
+
await this.assertProactiveHookAvailability(sessionKey, appServer);
|
|
1425
1504
|
const delegationCarrier = this.createDelegationCarrier();
|
|
1426
1505
|
const response = await appServer.threadFork(agentSessionId, projectPath, title, {
|
|
1427
1506
|
model: this.model,
|
|
@@ -1614,31 +1693,77 @@ export class CodexRunner {
|
|
|
1614
1693
|
catch (error) {
|
|
1615
1694
|
logger.warn(`[CodexRunner] failed to record policy-hook denial: session=${sessionKey} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1616
1695
|
}
|
|
1617
|
-
|
|
1696
|
+
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1697
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'policy', policyResult.policyCode ?? 'session_policy_hook');
|
|
1698
|
+
return response;
|
|
1618
1699
|
}
|
|
1619
1700
|
const summary = this.summarizeAppServerRequest(request.method, params);
|
|
1620
1701
|
const reason = params.reason || params.decisionReason || undefined;
|
|
1621
1702
|
const workspacePath = this.resolvePermissionWorkspacePath(params);
|
|
1622
1703
|
if (!workspacePath) {
|
|
1623
1704
|
logger.warn(`[CodexRunner] approval denied because thread workspace is unknown: method=${request.method} thread=${params.threadId ?? params.conversationId ?? '<missing>'}`);
|
|
1624
|
-
|
|
1705
|
+
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1706
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', 'approval_workspace_unknown');
|
|
1707
|
+
return response;
|
|
1625
1708
|
}
|
|
1626
1709
|
const operationCwd = this.resolvePermissionOperationCwd(params, workspacePath, toolName);
|
|
1627
1710
|
const sessionMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1628
1711
|
logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${summary}`);
|
|
1712
|
+
const isCommandApproval = request.method === 'item/commandExecution/requestApproval'
|
|
1713
|
+
|| request.method === 'execCommandApproval';
|
|
1714
|
+
const managedEcIntent = isCommandApproval && this.isManagedEvolcoreCommandIntent(toolInput);
|
|
1715
|
+
const approvedCommand = isCommandApproval
|
|
1716
|
+
? this.approvedEvolcoreCommandArgv(toolInput, this.getManagedTempDir(sessionKey))
|
|
1717
|
+
: undefined;
|
|
1718
|
+
const denyInfrastructure = async (policyCode, message) => {
|
|
1719
|
+
logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
|
|
1720
|
+
try {
|
|
1721
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
1722
|
+
code: 'operation_blocked',
|
|
1723
|
+
severity: 'warning',
|
|
1724
|
+
phase: 'execution',
|
|
1725
|
+
occurredAt: Date.now(),
|
|
1726
|
+
toolName,
|
|
1727
|
+
...(request.id !== undefined ? { requestId: String(request.id) } : {}),
|
|
1728
|
+
policyCode,
|
|
1729
|
+
decisionSource: 'infrastructure',
|
|
1730
|
+
agentAid: permissionContext?.selfAid,
|
|
1731
|
+
sessionId: sessionKey,
|
|
1732
|
+
permissionMode: normalizePermissionMode(sessionMode).mode,
|
|
1733
|
+
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1734
|
+
effect: 'operation_skipped',
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1738
|
+
logger.warn(`[CodexRunner] failed to record infrastructure approval denial: session=${sessionKey} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1739
|
+
}
|
|
1740
|
+
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1741
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode);
|
|
1742
|
+
return response;
|
|
1743
|
+
};
|
|
1744
|
+
if (managedEcIntent && !approvedCommand) {
|
|
1745
|
+
return await denyInfrastructure('ec_command_not_canonical', 'EC command approval denied because no canonical argv could be derived');
|
|
1746
|
+
}
|
|
1629
1747
|
try {
|
|
1630
1748
|
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id);
|
|
1631
|
-
if (decision !== 'deny'
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1749
|
+
if (decision !== 'deny' && managedEcIntent && approvedCommand) {
|
|
1750
|
+
const carrierThreadIds = [
|
|
1751
|
+
this.activeSessions.get(sessionKey),
|
|
1752
|
+
typeof params.threadId === 'string' ? params.threadId : undefined,
|
|
1753
|
+
typeof params.conversationId === 'string' ? params.conversationId : undefined,
|
|
1754
|
+
];
|
|
1755
|
+
const hasDelegationCarrier = carrierThreadIds.some(threadId => !!threadId && this.threadDelegationCarriers.has(threadId));
|
|
1756
|
+
const canArmDelegation = typeof permissionContext?.armApprovedDelegationCommand === 'function';
|
|
1757
|
+
// A carrier-bearing thread is managed even if its permission context
|
|
1758
|
+
// was not wired with an arm callback; it must fail closed here rather
|
|
1759
|
+
// than reaching daemon IPC with an unarmed carrier.
|
|
1760
|
+
if ((hasDelegationCarrier || canArmDelegation)
|
|
1761
|
+
&& !this.armApprovedDelegationCommand(sessionKey, approvedCommand)) {
|
|
1762
|
+
return await denyInfrastructure('delegation_not_armed', 'EC command approval failed to arm delegation');
|
|
1639
1763
|
}
|
|
1640
1764
|
}
|
|
1641
1765
|
const response = this.toAppServerApprovalResponse(request.method, decision, toolInput, unattended);
|
|
1766
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, decision === 'deny' ? 'deny' : 'allow', decision === 'deny' ? (sessionMode === 'auto' || sessionMode === 'readonly' ? 'policy' : 'approval') : 'approval');
|
|
1642
1767
|
logger.info(`[CodexRunner] app-server approval response id=${request.id} method=${request.method} decision=${decision} response=${JSON.stringify(response)}`);
|
|
1643
1768
|
return response;
|
|
1644
1769
|
}
|
|
@@ -1648,6 +1773,18 @@ export class CodexRunner {
|
|
|
1648
1773
|
throw error;
|
|
1649
1774
|
}
|
|
1650
1775
|
}
|
|
1776
|
+
logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode) {
|
|
1777
|
+
logger.info(`[CodexRunner] app-server approval audit ${JSON.stringify({
|
|
1778
|
+
requestId: request.id !== undefined ? String(request.id) : undefined,
|
|
1779
|
+
method: request.method,
|
|
1780
|
+
sessionId: sessionKey,
|
|
1781
|
+
toolName,
|
|
1782
|
+
decision,
|
|
1783
|
+
decisionSource,
|
|
1784
|
+
...(policyCode ? { policyCode } : {}),
|
|
1785
|
+
executed: false,
|
|
1786
|
+
})}`);
|
|
1787
|
+
}
|
|
1651
1788
|
classifyMcpApprovalLabel(label) {
|
|
1652
1789
|
const normalized = label.trim().toLowerCase().replace(/[\s_-]+/g, ' ');
|
|
1653
1790
|
const negative = /\b(decline|deny|reject|cancel|no|block|stop)\b|do not|don't|not\s+(?:accept|approve|allow)|拒绝|不同意|不允许|取消|否|停止/i.test(normalized);
|
|
@@ -2058,10 +2195,15 @@ export class CodexRunner {
|
|
|
2058
2195
|
channel: permCtx?.channel,
|
|
2059
2196
|
peerId: permCtx?.userId,
|
|
2060
2197
|
role: permCtx?.role,
|
|
2198
|
+
allowProtectedMetadata: false,
|
|
2199
|
+
// Revalidate ownership, mode, and parent containment at the final
|
|
2200
|
+
// readonly decision. The map entry alone is stale if the directory was
|
|
2201
|
+
// replaced between preflight and the approval callback.
|
|
2202
|
+
managedTempDir: this.getManagedTempDir(sessionKey),
|
|
2061
2203
|
} : undefined;
|
|
2062
2204
|
if (toolName === 'Bash')
|
|
2063
2205
|
return checkReadonly(toolName, input, projectPath, readonlyContext);
|
|
2064
|
-
return { behavior: 'deny', message: '🔒 只读模式:权限升级和文件写入请求已拒绝' };
|
|
2206
|
+
return { behavior: 'deny', message: '🔒 只读模式:权限升级和文件写入请求已拒绝', policyCode: 'readonly_permission_or_write' };
|
|
2065
2207
|
}
|
|
2066
2208
|
permissionProfiles(input) {
|
|
2067
2209
|
return [input.permissions, input.additionalPermissions]
|
|
@@ -2174,6 +2316,22 @@ export class CodexRunner {
|
|
|
2174
2316
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
2175
2317
|
const mode = normalizePermissionMode(rawMode).mode;
|
|
2176
2318
|
const recordBlockedOperation = async (policyCode, operationInput = toolInput) => {
|
|
2319
|
+
const summary = summarizeToolInputForAudit(toolName, operationInput).slice(0, 512);
|
|
2320
|
+
auditToolPreflightDenial({
|
|
2321
|
+
toolName,
|
|
2322
|
+
policyCode,
|
|
2323
|
+
reason: 'policy denied',
|
|
2324
|
+
summary,
|
|
2325
|
+
sessionId: sessionKey,
|
|
2326
|
+
agentAid: permissionContext?.selfAid,
|
|
2327
|
+
permissionMode: mode,
|
|
2328
|
+
channel: permissionContext?.channel,
|
|
2329
|
+
actorId: permissionContext?.userId,
|
|
2330
|
+
role: permissionContext?.role,
|
|
2331
|
+
selfAid: permissionContext?.selfAid,
|
|
2332
|
+
requestId: typeof requestId === 'string' || typeof requestId === 'number' ? String(requestId) : undefined,
|
|
2333
|
+
taskId: permissionContext?.taskId,
|
|
2334
|
+
});
|
|
2177
2335
|
try {
|
|
2178
2336
|
await permissionContext?.recordExecutionAnomaly?.({
|
|
2179
2337
|
code: 'operation_blocked',
|
|
@@ -2183,7 +2341,10 @@ export class CodexRunner {
|
|
|
2183
2341
|
toolName,
|
|
2184
2342
|
...(typeof requestId === 'string' || typeof requestId === 'number' ? { requestId: String(requestId) } : {}),
|
|
2185
2343
|
policyCode,
|
|
2186
|
-
|
|
2344
|
+
decisionSource: 'policy',
|
|
2345
|
+
agentAid: permissionContext?.selfAid,
|
|
2346
|
+
permissionMode: mode,
|
|
2347
|
+
summary,
|
|
2187
2348
|
effect: 'operation_skipped',
|
|
2188
2349
|
});
|
|
2189
2350
|
}
|
|
@@ -2193,6 +2354,11 @@ export class CodexRunner {
|
|
|
2193
2354
|
};
|
|
2194
2355
|
const preflight = evaluateToolPreflight(toolName, toolInput, {
|
|
2195
2356
|
sessionId: sessionKey,
|
|
2357
|
+
// An unavailable registered root must fail closed; the policy helper
|
|
2358
|
+
// distinguishes this explicit empty value from runners without a
|
|
2359
|
+
// session-temp capability.
|
|
2360
|
+
managedTempDir: this.getManagedTempDir(sessionKey) ?? '',
|
|
2361
|
+
allowProtectedMetadata: false,
|
|
2196
2362
|
selfAid: permissionContext?.selfAid,
|
|
2197
2363
|
channel: permissionContext?.channel,
|
|
2198
2364
|
userId: permissionContext?.userId,
|
|
@@ -2202,6 +2368,8 @@ export class CodexRunner {
|
|
|
2202
2368
|
workspacePath,
|
|
2203
2369
|
});
|
|
2204
2370
|
if (preflight.behavior === 'deny') {
|
|
2371
|
+
logger.warn(`[CodexRunner] tool preflight denied: session=${sessionKey} tool=${toolName} ` +
|
|
2372
|
+
`policy=${preflight.policyCode ?? 'unknown'} reason=${preflight.message ?? 'policy denied'}`);
|
|
2205
2373
|
if (preflight.policyCode) {
|
|
2206
2374
|
await recordBlockedOperation(preflight.policyCode);
|
|
2207
2375
|
}
|
|
@@ -2231,7 +2399,7 @@ export class CodexRunner {
|
|
|
2231
2399
|
if (mode === 'readonly') {
|
|
2232
2400
|
const readonly = this.checkCodexReadonly(toolName, checkedInput, operationCwd, sessionKey);
|
|
2233
2401
|
if (readonly.behavior === 'deny') {
|
|
2234
|
-
await recordBlockedOperation('readonly_mode', checkedInput);
|
|
2402
|
+
await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', checkedInput);
|
|
2235
2403
|
return 'deny';
|
|
2236
2404
|
}
|
|
2237
2405
|
return 'allow';
|
|
@@ -2468,11 +2636,15 @@ export class CodexRunner {
|
|
|
2468
2636
|
this.onCompactStart = callback;
|
|
2469
2637
|
}
|
|
2470
2638
|
// ── Event stream transformation ──
|
|
2471
|
-
buildAppServerInput(prompt, images, tempFiles) {
|
|
2639
|
+
buildAppServerInput(prompt, images, tempFiles, sessionId, runtimeEnv) {
|
|
2472
2640
|
const input = [{ type: 'text', text: prompt, text_elements: [] }];
|
|
2473
2641
|
if (!images?.length)
|
|
2474
2642
|
return input;
|
|
2475
|
-
const tmpDir =
|
|
2643
|
+
const tmpDir = runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR
|
|
2644
|
+
?? (sessionId ? this.getManagedTempDir(sessionId) : undefined)
|
|
2645
|
+
?? process.env.TMPDIR;
|
|
2646
|
+
if (!tmpDir || !path.isAbsolute(tmpDir))
|
|
2647
|
+
throw new Error('managed TMPDIR is unset or not absolute');
|
|
2476
2648
|
for (let i = 0; i < images.length; i++) {
|
|
2477
2649
|
const img = images[i];
|
|
2478
2650
|
const ext = MIME_EXT[img.mimeType || ''] || '.jpg';
|
|
@@ -2636,19 +2808,18 @@ export class CodexRunner {
|
|
|
2636
2808
|
*reconcileOpenAppServerToolCalls(state, turnId) {
|
|
2637
2809
|
if (state.openToolCalls.size === 0)
|
|
2638
2810
|
return;
|
|
2639
|
-
const
|
|
2811
|
+
const openCalls = [...state.openToolCalls.entries()];
|
|
2812
|
+
const callIds = openCalls.map(([callId]) => callId);
|
|
2640
2813
|
logger.warn(`[CodexRunner] Turn completed with ${callIds.length} open tool call(s): ` +
|
|
2641
2814
|
`thread=${state.threadId} turn=${turnId || state.turnId || 'unknown'} calls=${callIds.join(',')}`);
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2815
|
+
yield {
|
|
2816
|
+
type: 'notice',
|
|
2817
|
+
severity: 'warn',
|
|
2818
|
+
subtype: 'missing-item-completed',
|
|
2819
|
+
message: 'Turn completed before tool completion was received',
|
|
2820
|
+
closedToolCallIds: callIds,
|
|
2821
|
+
missingItems: openCalls.map(([itemId, toolName]) => ({ itemId, toolName })),
|
|
2822
|
+
};
|
|
2652
2823
|
state.openToolCalls.clear();
|
|
2653
2824
|
}
|
|
2654
2825
|
*mapAppServerItemStarted(item, state) {
|
|
@@ -2962,7 +3133,8 @@ export class CodexRunner {
|
|
|
2962
3133
|
}
|
|
2963
3134
|
managedTempParent() {
|
|
2964
3135
|
const uid = typeof process.getuid === 'function' ? process.getuid() : 'user';
|
|
2965
|
-
|
|
3136
|
+
const base = ensureProcessManagedTempDir();
|
|
3137
|
+
return path.join(resolveProtectedCandidate(base), `evolcore-codex-${uid}`);
|
|
2966
3138
|
}
|
|
2967
3139
|
ensurePrivateDirectory(directory) {
|
|
2968
3140
|
try {
|
|
@@ -2999,6 +3171,7 @@ export class CodexRunner {
|
|
|
2999
3171
|
try {
|
|
3000
3172
|
const parent = this.managedTempParent();
|
|
3001
3173
|
this.ensurePrivateDirectory(parent);
|
|
3174
|
+
this.ensurePrivateDirectory(path.join(parent, 'evolcore-locks'));
|
|
3002
3175
|
// Include a per-runner nonce so a daemon restart never reuses contents
|
|
3003
3176
|
// left behind by an unclean shutdown for the same EvolCore session id.
|
|
3004
3177
|
const digest = createHash('sha256')
|
|
@@ -3019,39 +3192,28 @@ export class CodexRunner {
|
|
|
3019
3192
|
}
|
|
3020
3193
|
}
|
|
3021
3194
|
isTrustedSessionTempDir(directory) {
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const stat = fs.lstatSync(candidate);
|
|
3030
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
3031
|
-
return false;
|
|
3032
|
-
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
3033
|
-
return false;
|
|
3034
|
-
if ((stat.mode & 0o077) !== 0)
|
|
3035
|
-
return false;
|
|
3036
|
-
}
|
|
3037
|
-
return canonical !== parent && isSameOrDescendant(canonical, parent);
|
|
3038
|
-
}
|
|
3039
|
-
catch {
|
|
3040
|
-
return false;
|
|
3041
|
-
}
|
|
3195
|
+
return isTrustedManagedTempDirectory(directory, this.managedTempParent());
|
|
3196
|
+
}
|
|
3197
|
+
getManagedTempDir(sessionId) {
|
|
3198
|
+
const directory = this.ensureSessionTempDir(sessionId);
|
|
3199
|
+
if (!directory || !this.isTrustedSessionTempDir(directory))
|
|
3200
|
+
return undefined;
|
|
3201
|
+
return resolveProtectedCandidate(directory);
|
|
3042
3202
|
}
|
|
3043
3203
|
cleanupSessionTempDir(sessionId) {
|
|
3044
3204
|
const directory = this.sessionTempDirs.get(sessionId);
|
|
3045
3205
|
this.sessionTempDirs.delete(sessionId);
|
|
3046
3206
|
if (!directory)
|
|
3047
3207
|
return;
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3208
|
+
// Reuse the same ownership, mode, lexical-parent, and canonicalization
|
|
3209
|
+
// checks used at execution time. If the path changed, leave it in place
|
|
3210
|
+
// rather than recursively removing an attacker-selected target.
|
|
3211
|
+
if (!this.isTrustedSessionTempDir(directory)) {
|
|
3051
3212
|
managedTempDirsForExitCleanup.delete(directory);
|
|
3052
3213
|
logger.warn(`[CodexRunner] refused to clean unmanaged TMPDIR: session=${sessionId} path=${directory}`);
|
|
3053
3214
|
return;
|
|
3054
3215
|
}
|
|
3216
|
+
const candidate = path.resolve(directory);
|
|
3055
3217
|
try {
|
|
3056
3218
|
fs.rmSync(candidate, { recursive: true, force: true });
|
|
3057
3219
|
managedTempDirsForExitCleanup.delete(directory);
|
|
@@ -3063,7 +3225,7 @@ export class CodexRunner {
|
|
|
3063
3225
|
managedTempInstruction(sessionId) {
|
|
3064
3226
|
if (!this.ensureSessionTempDir(sessionId))
|
|
3065
3227
|
return undefined;
|
|
3066
|
-
return '临时文件必须使用环境变量 `$TMPDIR`。它指向当前 EvolCore 会话的受管临时工作区;不要直接使用 `/tmp
|
|
3228
|
+
return '临时文件必须使用环境变量 `$TMPDIR`。它指向当前 EvolCore 会话的受管临时工作区;不要直接使用 `/tmp`,也不要使用其他会话的临时目录。当前是可持久化 thread 的恢复轮次,早先上下文中的 `/tmp/evolcore-codex-*` 绝对路径可能已经失效;不要重用旧路径。若文件不存在,请先在当前 `$TMPDIR` 重新创建,再继续执行后续 `ec` 命令。';
|
|
3067
3229
|
}
|
|
3068
3230
|
buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrier) {
|
|
3069
3231
|
const managedTempDir = this.ensureSessionTempDir(sessionId);
|
|
@@ -3072,6 +3234,8 @@ export class CodexRunner {
|
|
|
3072
3234
|
set: {
|
|
3073
3235
|
EVOLCORE_SESSION_ID: sessionId,
|
|
3074
3236
|
EVOLCORE_HOME: resolveRoot(),
|
|
3237
|
+
...(managedTempDir ? { EVOLCORE_SESSION_RUNTIME_DIR: managedTempDir } : {}),
|
|
3238
|
+
...(managedTempDir ? { EVOLCORE_RUNTIME_LOCK_DIR: path.join(path.dirname(managedTempDir), 'evolcore-locks') } : {}),
|
|
3075
3239
|
...(delegationCarrier ? { [AGENT_DELEGATION_TOKEN_ENV]: delegationCarrier } : {}),
|
|
3076
3240
|
...(managedTempDir ? { TMPDIR: managedTempDir } : {}),
|
|
3077
3241
|
},
|