evolcore 0.0.19 → 0.0.21
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 +43 -1
- package/README.md +60 -9
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +383 -111
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +405 -137
- package/dist/agents/ecagent-runner.js +174 -63
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +66 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +86 -9
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +26 -11
- package/dist/aun/outbox.js +295 -68
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +1004 -273
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +81 -121
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +76 -24
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +226 -24
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +29 -16
- package/dist/config/lifecycle.js +16 -5
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +161 -12
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +155 -4
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +27 -38
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +9 -4
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +495 -72
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +465 -56
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +205 -13
- package/dist/core/permission/tool-policy.js +673 -62
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +18 -9
- package/dist/index.js +283 -65
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +10 -5
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +421 -0
- package/kits/schemas/daemon.schema.5.json +135 -0
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +119 -0
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +7 -3
- package/skills/eclink/SKILL.md +2 -0
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* Implements the same interface surface as AgentRunner (claude-runner.ts)
|
|
6
6
|
* so MessageProcessor and CommandHandler can work with it transparently.
|
|
7
7
|
*/
|
|
8
|
+
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
8
9
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
9
10
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
10
|
-
import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
11
|
-
import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate } from '../core/protected-paths.js';
|
|
11
|
+
import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
12
|
+
import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate } from '../core/permission/protected-paths.js';
|
|
12
13
|
import { CodexAppServerClient } from './codex-app-server-client.js';
|
|
13
14
|
import { resolveOpenaiConfig } from './baseagent.js';
|
|
14
15
|
import { logger } from '../utils/logger.js';
|
|
@@ -16,14 +17,14 @@ import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
|
16
17
|
import { auditCodexApprovalDecision, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
17
18
|
import { isRetryableError } from '../utils/error-utils.js';
|
|
18
19
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
19
|
-
import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
20
|
+
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
20
21
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
21
22
|
import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
22
23
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
23
|
-
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent,
|
|
24
|
+
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, hasCodexCmdCarrierEcIntent, parseCodexToolCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
|
|
24
25
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
25
26
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
26
|
-
import { ensureProcessManagedTempDir } from '../cli/task-context.js';
|
|
27
|
+
import { ensureProcessManagedTempDir, getManagedTaskTempDir } from '../cli/task-context.js';
|
|
27
28
|
import { buildSessionTurnList } from '../core/session/session-turns.js';
|
|
28
29
|
import { execFileSync } from 'child_process';
|
|
29
30
|
import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
|
|
@@ -39,6 +40,13 @@ const MIME_EXT = {
|
|
|
39
40
|
'image/gif': '.gif',
|
|
40
41
|
'image/webp': '.webp',
|
|
41
42
|
};
|
|
43
|
+
const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
|
|
44
|
+
export function formatApprovalLogSummary(summary) {
|
|
45
|
+
const singleLine = summary.replace(/\s+/gu, ' ').trim();
|
|
46
|
+
if (singleLine.length <= APPROVAL_LOG_SUMMARY_MAX_LENGTH)
|
|
47
|
+
return singleLine;
|
|
48
|
+
return singleLine.slice(0, APPROVAL_LOG_SUMMARY_MAX_LENGTH - 3).trimEnd() + '...';
|
|
49
|
+
}
|
|
42
50
|
class AsyncEventQueue {
|
|
43
51
|
queue = [];
|
|
44
52
|
done = false;
|
|
@@ -267,16 +275,21 @@ export class CodexRunner {
|
|
|
267
275
|
permissionContexts = new Map();
|
|
268
276
|
resolvedConfig;
|
|
269
277
|
pendingInterruptTtlMs = 30_000;
|
|
270
|
-
constructor(config, callbacks) {
|
|
271
|
-
|
|
278
|
+
constructor(config, callbacks, runtime) {
|
|
279
|
+
// Compatibility for legacy direct callers that still pass the removed
|
|
280
|
+
// internal fields in a synthetic config. AgentLoader uses runtime binding.
|
|
281
|
+
const legacy = config.agents?.codex;
|
|
282
|
+
const binding = {
|
|
283
|
+
agentAid: runtime?.agentAid ?? legacy?.['evolcoreAgentAid'],
|
|
284
|
+
agentConfig: runtime?.agentConfig ?? legacy?.['evolcoreAgentConfig'],
|
|
285
|
+
};
|
|
286
|
+
this.resolvedConfig = resolveOpenaiConfig(config, undefined, binding);
|
|
272
287
|
this.resolvedConfig.headers = buildModelRequestHeaders({
|
|
273
288
|
baseagent: 'codex',
|
|
274
289
|
baseUrl: this.resolvedConfig.baseUrl,
|
|
275
|
-
agentAid:
|
|
290
|
+
agentAid: this.resolvedConfig.agentAid,
|
|
276
291
|
configuredHeaders: this.resolvedConfig.headers,
|
|
277
292
|
});
|
|
278
|
-
this.resolvedConfig.evolcoreAgentAid = config.agents?.codex?.evolcoreAgentAid;
|
|
279
|
-
this.resolvedConfig.evolcoreAgentConfig = config.agents?.codex?.evolcoreAgentConfig;
|
|
280
293
|
this.capabilities = {
|
|
281
294
|
clear: false,
|
|
282
295
|
compact: true,
|
|
@@ -288,6 +301,7 @@ export class CodexRunner {
|
|
|
288
301
|
planApproval: false,
|
|
289
302
|
// Current file rewind is intentionally degraded: it restores touched files from Git HEAD.
|
|
290
303
|
fileRewind: 'git-head',
|
|
304
|
+
fullaccess: true,
|
|
291
305
|
};
|
|
292
306
|
this.model = this.resolvedConfig.model;
|
|
293
307
|
if (this.resolvedConfig.effort)
|
|
@@ -316,7 +330,7 @@ export class CodexRunner {
|
|
|
316
330
|
createDelegationCarrier() {
|
|
317
331
|
return randomBytes(32).toString('base64url');
|
|
318
332
|
}
|
|
319
|
-
|
|
333
|
+
approvedLiteralEvolcoreCommand(command, managedTempDir, dialect = 'posix') {
|
|
320
334
|
const parseOptions = {
|
|
321
335
|
allowManagedTmpDir: true,
|
|
322
336
|
dialect,
|
|
@@ -324,47 +338,100 @@ export class CodexRunner {
|
|
|
324
338
|
};
|
|
325
339
|
const classification = classifyEvolcoreShellCommand(command, parseOptions);
|
|
326
340
|
if (classification.kind === 'bounded-output')
|
|
327
|
-
return classification.command.argv;
|
|
328
|
-
if (classification.kind !== 'literal')
|
|
329
|
-
return undefined;
|
|
341
|
+
return { argv: classification.command.argv };
|
|
330
342
|
// Keep the approval/delegation parser aligned with the preflight parser:
|
|
331
343
|
// the session-managed `$TMPDIR` token is a permitted EC path reference.
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
return
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
344
|
+
const parsed = parseLiteralShellCommand(command, parseOptions);
|
|
345
|
+
if (!parsed.ok)
|
|
346
|
+
return { parseFailure: parsed };
|
|
347
|
+
if (parsed.argv[0] !== 'ec')
|
|
348
|
+
return {};
|
|
349
|
+
if (containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)) {
|
|
350
|
+
return { parseFailure: {
|
|
351
|
+
issue: 'unsafe-expansion',
|
|
352
|
+
offset: 0,
|
|
353
|
+
tokenIndex: 0,
|
|
354
|
+
dialect: dialect ?? 'posix',
|
|
355
|
+
inputLength: command.length,
|
|
356
|
+
} };
|
|
357
|
+
}
|
|
358
|
+
return { argv: parsed.argv };
|
|
359
|
+
}
|
|
360
|
+
resolveCanonicalEcApproval(toolInput, managedTempDir) {
|
|
338
361
|
const explicitArgv = Array.isArray(toolInput.commandArgv)
|
|
339
362
|
&& toolInput.commandArgv.every(value => typeof value === 'string')
|
|
340
363
|
? toolInput.commandArgv
|
|
341
364
|
: undefined;
|
|
342
365
|
if (explicitArgv?.[0] === 'ec') {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
366
|
+
if (explicitArgv.some(value => value.includes('\0'))) {
|
|
367
|
+
return { parseFailure: {
|
|
368
|
+
issue: 'invalid-control-char', offset: 0,
|
|
369
|
+
tokenIndex: explicitArgv.findIndex(value => value.includes('\0')),
|
|
370
|
+
dialect: 'posix', inputLength: 0,
|
|
371
|
+
} };
|
|
372
|
+
}
|
|
373
|
+
return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
|
|
374
|
+
? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'posix', inputLength: 0 } }
|
|
375
|
+
: { argv: explicitArgv };
|
|
347
376
|
}
|
|
348
377
|
const explicitCarrier = explicitArgv
|
|
349
378
|
? resolveCodexShellCarrierArgv(explicitArgv)
|
|
350
379
|
: undefined;
|
|
351
380
|
if (explicitCarrier !== undefined) {
|
|
352
|
-
return this.
|
|
381
|
+
return this.approvedLiteralEvolcoreCommand(explicitCarrier.command, managedTempDir, explicitCarrier.dialect);
|
|
382
|
+
}
|
|
383
|
+
if (hasCodexCmdCarrierEcIntent(toolInput)) {
|
|
384
|
+
const parsed = parseCodexToolCommand(toolInput);
|
|
385
|
+
if (!parsed.ok)
|
|
386
|
+
return { parseFailure: parsed };
|
|
387
|
+
if (parsed.argv[0] === 'ec') {
|
|
388
|
+
return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)
|
|
389
|
+
? { parseFailure: {
|
|
390
|
+
issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
|
|
391
|
+
dialect: 'cmd', inputLength: 0,
|
|
392
|
+
} }
|
|
393
|
+
: { argv: parsed.argv };
|
|
394
|
+
}
|
|
395
|
+
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
396
|
+
const classification = classifyEvolcoreShellCommand(command, { dialect: 'cmd' });
|
|
397
|
+
return {
|
|
398
|
+
parseFailure: {
|
|
399
|
+
issue: classification.kind === 'composite' ? classification.issue : 'shell-composition',
|
|
400
|
+
offset: 0,
|
|
401
|
+
tokenIndex: 0,
|
|
402
|
+
dialect: 'cmd',
|
|
403
|
+
inputLength: command.length,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
353
406
|
}
|
|
354
407
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
355
|
-
const
|
|
356
|
-
if (directArgv)
|
|
357
|
-
return directArgv;
|
|
358
|
-
const wrappedArgv = parseLiteralShellArgv(command, {
|
|
408
|
+
const canonicalCommand = parseCodexShellCommandArgv(command, {
|
|
359
409
|
allowManagedTmpDir: true,
|
|
360
410
|
...(managedTempDir ? { managedTempDir } : {}),
|
|
361
411
|
});
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
412
|
+
if (canonicalCommand?.[0] === 'ec') {
|
|
413
|
+
return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand)
|
|
414
|
+
? { parseFailure: {
|
|
415
|
+
issue: 'unsafe-expansion',
|
|
416
|
+
offset: 0,
|
|
417
|
+
tokenIndex: 0,
|
|
418
|
+
dialect: 'posix',
|
|
419
|
+
inputLength: command.length,
|
|
420
|
+
} }
|
|
421
|
+
: { argv: canonicalCommand };
|
|
422
|
+
}
|
|
423
|
+
const outer = parseLiteralShellCommand(command, {
|
|
424
|
+
allowManagedTmpDir: true,
|
|
425
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
426
|
+
});
|
|
427
|
+
if (!outer.ok) {
|
|
428
|
+
const classification = classifyEvolcoreShellCommand(command, {
|
|
429
|
+
allowManagedTmpDir: true,
|
|
430
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
431
|
+
});
|
|
432
|
+
return classification.kind === 'composite' ? { parseFailure: outer } : {};
|
|
433
|
+
}
|
|
434
|
+
return {};
|
|
368
435
|
}
|
|
369
436
|
isManagedEvolcoreCommandIntent(toolInput) {
|
|
370
437
|
if (Array.isArray(toolInput.commandArgv)) {
|
|
@@ -374,10 +441,11 @@ export class CodexRunner {
|
|
|
374
441
|
if (!rawArgv.every(value => typeof value === 'string'))
|
|
375
442
|
return false;
|
|
376
443
|
const shellCarrier = resolveCodexShellCarrierArgv(rawArgv);
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
444
|
+
return hasCodexCmdCarrierEcIntent(toolInput)
|
|
445
|
+
|| shellCarrier !== undefined
|
|
446
|
+
&& classifyEvolcoreShellCommand(shellCarrier.command, {
|
|
447
|
+
dialect: shellCarrier.dialect,
|
|
448
|
+
}).kind !== 'none';
|
|
381
449
|
}
|
|
382
450
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
383
451
|
return classifyEvolcoreShellCommand(command).kind !== 'none';
|
|
@@ -430,8 +498,7 @@ export class CodexRunner {
|
|
|
430
498
|
}
|
|
431
499
|
}
|
|
432
500
|
async persistDelegationCarrier(sessionManager, sessionId, threadId, carrier) {
|
|
433
|
-
if (typeof sessionManager?.getSessionById !== 'function'
|
|
434
|
-
|| typeof sessionManager?.updateSession !== 'function')
|
|
501
|
+
if (typeof sessionManager?.getSessionById !== 'function')
|
|
435
502
|
return;
|
|
436
503
|
try {
|
|
437
504
|
const session = await sessionManager.getSessionById(sessionId);
|
|
@@ -440,12 +507,16 @@ export class CodexRunner {
|
|
|
440
507
|
const current = session.metadata?.[CODEX_DELEGATION_CARRIER_METADATA_KEY];
|
|
441
508
|
if (current?.threadId === threadId && current?.carrier === carrier)
|
|
442
509
|
return;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
...(session.metadata ?? {}),
|
|
510
|
+
if (typeof sessionManager.patchSessionMetadata === 'function') {
|
|
511
|
+
await sessionManager.patchSessionMetadata(sessionId, {
|
|
446
512
|
[CODEX_DELEGATION_CARRIER_METADATA_KEY]: { threadId, carrier },
|
|
447
|
-
}
|
|
448
|
-
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
else if (typeof sessionManager.updateSession === 'function') {
|
|
516
|
+
await sessionManager.updateSession(sessionId, {
|
|
517
|
+
metadata: { [CODEX_DELEGATION_CARRIER_METADATA_KEY]: { threadId, carrier } },
|
|
518
|
+
});
|
|
519
|
+
}
|
|
449
520
|
}
|
|
450
521
|
catch (error) {
|
|
451
522
|
logger.warn(`[CodexRunner] failed to persist delegation carrier: session=${sessionId} thread=${threadId} error=${error}`);
|
|
@@ -485,7 +556,7 @@ export class CodexRunner {
|
|
|
485
556
|
return Object.keys(merged).length > 0 ? merged : null;
|
|
486
557
|
}
|
|
487
558
|
async resolveCapabilityThreadConfig(projectPath) {
|
|
488
|
-
const agentConfig = this.resolvedConfig.
|
|
559
|
+
const agentConfig = this.resolvedConfig.agentConfig;
|
|
489
560
|
if (!agentConfig) {
|
|
490
561
|
return {
|
|
491
562
|
skills: { config: [{ name: 'eclink', enabled: false }] },
|
|
@@ -732,6 +803,8 @@ export class CodexRunner {
|
|
|
732
803
|
chatModes = new Map();
|
|
733
804
|
/** 将权限模式映射为 Codex app-server 的 approvalPolicy(纯函数,无副作用,供 per-call 派生用)。 */
|
|
734
805
|
toApprovalPolicy(mode) {
|
|
806
|
+
if (mode === 'fullaccess')
|
|
807
|
+
return 'never';
|
|
735
808
|
// `untrusted` guarantees that commands outside Codex's trusted read-only
|
|
736
809
|
// set reach the EvolCore bridge for EvolCore's per-mode decision.
|
|
737
810
|
void mode;
|
|
@@ -814,6 +887,12 @@ export class CodexRunner {
|
|
|
814
887
|
filesystem: {
|
|
815
888
|
...buildCodexProtectedFilesystemRules(resolveRoot(), {
|
|
816
889
|
denyLClassRead: mode === 'readonly',
|
|
890
|
+
// The app-server itself is launched behind EvolCore's Linux
|
|
891
|
+
// H-class guard, which already mounts every L-class directory
|
|
892
|
+
// read-only. Avoid Codex's per-file glob expansion inside that
|
|
893
|
+
// nested mount namespace; it can fail while creating a missing
|
|
894
|
+
// target such as triggers/history.jsonl.
|
|
895
|
+
expandLClassReadDeny: process.platform !== 'linux',
|
|
817
896
|
}),
|
|
818
897
|
...this.resolveGitMetadataWriteRules(projectPath, mode),
|
|
819
898
|
...(bootstrapAgentMdPath ? {
|
|
@@ -841,14 +920,17 @@ export class CodexRunner {
|
|
|
841
920
|
sandbox: decision.state === 'off' ? 'danger-full-access' : undefined,
|
|
842
921
|
};
|
|
843
922
|
}
|
|
844
|
-
|
|
923
|
+
isTrustedFullAccessSession(sessionKey) {
|
|
924
|
+
return hasTrustedFullAccessContext(this.permissionContexts.get(sessionKey));
|
|
925
|
+
}
|
|
926
|
+
buildLifecycleLockdownConfig(permissionMode) {
|
|
845
927
|
return {
|
|
846
928
|
features: {
|
|
847
929
|
// The app-server is launched with allow_managed_hooks_only=true, so
|
|
848
930
|
// enabling the feature exposes only EvolCore's requirements-managed
|
|
849
931
|
// PreToolUse hook. Disabling it here suppresses that managed hook too
|
|
850
932
|
// and bypasses the pause/proactive policy boundary entirely.
|
|
851
|
-
hooks:
|
|
933
|
+
hooks: permissionMode !== 'fullaccess',
|
|
852
934
|
},
|
|
853
935
|
};
|
|
854
936
|
}
|
|
@@ -895,6 +977,11 @@ export class CodexRunner {
|
|
|
895
977
|
return { ok: true, applicable: true, decision: 'deny', reason: '工具调用已取消' };
|
|
896
978
|
}
|
|
897
979
|
}
|
|
980
|
+
if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
|
|
981
|
+
return this.isTrustedFullAccessSession(sessionKey)
|
|
982
|
+
? { ok: true, applicable: true, decision: 'allow' }
|
|
983
|
+
: { ok: false, applicable: true, decision: 'deny', reason: 'Untrusted fullaccess execution context' };
|
|
984
|
+
}
|
|
898
985
|
// A tracked interactive thread still needs an authenticated, explicit
|
|
899
986
|
// allow so the managed hook can fail closed when no runner matches.
|
|
900
987
|
if (!context.preToolUsePolicyHook)
|
|
@@ -922,6 +1009,7 @@ export class CodexRunner {
|
|
|
922
1009
|
this.activeStreams.delete(key);
|
|
923
1010
|
this.activeAbortControllers.delete(key);
|
|
924
1011
|
this.pendingInterrupts.delete(key);
|
|
1012
|
+
this.chatModes.delete(key);
|
|
925
1013
|
const release = this.activeOperationReleases.get(key);
|
|
926
1014
|
if (release) {
|
|
927
1015
|
this.activeOperationReleases.delete(key);
|
|
@@ -1032,8 +1120,22 @@ export class CodexRunner {
|
|
|
1032
1120
|
}
|
|
1033
1121
|
}
|
|
1034
1122
|
// ── Core: runQuery ──
|
|
1123
|
+
cachedThreadForTopic(sessionId, agentSessionId) {
|
|
1124
|
+
if (!agentSessionId)
|
|
1125
|
+
return undefined;
|
|
1126
|
+
return this.activeSessions.get(sessionId) === agentSessionId ? agentSessionId : undefined;
|
|
1127
|
+
}
|
|
1035
1128
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
1036
|
-
const
|
|
1129
|
+
const topicBinding = modelOverride?.backend?.kind === 'topic';
|
|
1130
|
+
if (topicBinding && (!modelOverride?.turn || modelOverride.turn.sessionId !== sessionId || !this.onSessionIdUpdate)) {
|
|
1131
|
+
const error = new Error('topic backend run requires an active TurnLease and binding callback');
|
|
1132
|
+
error.code = 'TOPIC_TURN_LEASE_REQUIRED';
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
const topicAgentSessionId = topicBinding ? modelOverride?.backend?.agentSessionId ?? null : undefined;
|
|
1136
|
+
const knownThreadId = topicBinding
|
|
1137
|
+
? this.cachedThreadForTopic(sessionId, topicAgentSessionId)
|
|
1138
|
+
: initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
1037
1139
|
const operationKey = this.threadOperationKey(knownThreadId, sessionId);
|
|
1038
1140
|
const release = await this.acquireThreadOperation(operationKey, 'thread/resume+turn/start');
|
|
1039
1141
|
try {
|
|
@@ -1047,7 +1149,14 @@ export class CodexRunner {
|
|
|
1047
1149
|
}
|
|
1048
1150
|
}
|
|
1049
1151
|
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
1050
|
-
|
|
1152
|
+
// A daemon-managed task must never fall back to the app-server's
|
|
1153
|
+
// process-wide TMPDIR if its session namespace disappeared.
|
|
1154
|
+
getManagedTaskTempDir(runtimeEnv);
|
|
1155
|
+
const topicBinding = modelOverride?.backend?.kind === 'topic';
|
|
1156
|
+
const topicAgentSessionId = topicBinding ? modelOverride?.backend?.agentSessionId ?? null : undefined;
|
|
1157
|
+
let agentSessionId = topicBinding
|
|
1158
|
+
? (topicAgentSessionId || undefined)
|
|
1159
|
+
: initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
1051
1160
|
const resumingThread = !!agentSessionId;
|
|
1052
1161
|
const callModel = modelOverride?.model || this.model;
|
|
1053
1162
|
const callEffort = modelOverride?.effortMode === 'model_default'
|
|
@@ -1057,8 +1166,10 @@ export class CodexRunner {
|
|
|
1057
1166
|
// 写入 chatModes 供异步审批回调按 sessionKey 读取,并据此派生本次 thread 的 approvalPolicy/sandbox,
|
|
1058
1167
|
// 不依赖共享的 this.approvalPolicy/this.sandboxMode(多会话并发互不污染)。
|
|
1059
1168
|
const requestedPermissionMode = modelOverride?.permissionMode || this.currentMode;
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1169
|
+
const callMode = normalizeExecutionPermissionMode(requestedPermissionMode);
|
|
1170
|
+
if (callMode === 'fullaccess' && !hasTrustedFullAccessContext(this.permissionContexts.get(sessionId))) {
|
|
1171
|
+
throw new Error('Codex fullaccess requires a trusted per-call execution authorization');
|
|
1172
|
+
}
|
|
1062
1173
|
this.chatModes.set(sessionId, callMode);
|
|
1063
1174
|
const callApprovalPolicy = this.toApprovalPolicy(callMode);
|
|
1064
1175
|
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath);
|
|
@@ -1066,8 +1177,11 @@ export class CodexRunner {
|
|
|
1066
1177
|
this.dropExpiredPendingInterrupt(sessionId);
|
|
1067
1178
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
1068
1179
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
|
|
1069
|
-
const externalToolConfig =
|
|
1070
|
-
|
|
1180
|
+
const externalToolConfig = callMode === 'fullaccess'
|
|
1181
|
+
? {}
|
|
1182
|
+
: await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, callMode);
|
|
1183
|
+
if (callMode !== 'fullaccess')
|
|
1184
|
+
await this.assertProactiveHookAvailability(sessionId, appServer);
|
|
1071
1185
|
const managedTempInstruction = this.managedTempInstruction(sessionId);
|
|
1072
1186
|
const developerInstructions = [systemPromptAppend, managedTempInstruction].filter(Boolean).join('\n\n') || undefined;
|
|
1073
1187
|
let knownDelegationCarrier = agentSessionId
|
|
@@ -1075,11 +1189,12 @@ export class CodexRunner {
|
|
|
1075
1189
|
: undefined;
|
|
1076
1190
|
if (agentSessionId && !knownDelegationCarrier) {
|
|
1077
1191
|
knownDelegationCarrier = await this.loadPersistedDelegationCarrier(sessionManager, sessionId, agentSessionId);
|
|
1078
|
-
if (knownDelegationCarrier) {
|
|
1079
|
-
this.threadDelegationCarriers.set(agentSessionId, knownDelegationCarrier);
|
|
1080
|
-
}
|
|
1081
1192
|
}
|
|
1082
1193
|
const delegationCarrierCandidate = knownDelegationCarrier ?? this.createDelegationCarrier();
|
|
1194
|
+
// Keep newly discovered carrier state local until generation-aware
|
|
1195
|
+
// activation accepts the backend. A stale run must not publish a carrier
|
|
1196
|
+
// that a later generation could mistake for its own thread.
|
|
1197
|
+
let carrierToPublish;
|
|
1083
1198
|
const hasTaskDelegation = typeof runtimeEnv?.[AGENT_DELEGATION_TOKEN_ENV] === 'string';
|
|
1084
1199
|
const threadOptions = {
|
|
1085
1200
|
model: callModel,
|
|
@@ -1087,7 +1202,7 @@ export class CodexRunner {
|
|
|
1087
1202
|
approvalPolicy: effectiveApprovalPolicy,
|
|
1088
1203
|
approvalsReviewer: 'user',
|
|
1089
1204
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
1090
|
-
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()), callMode),
|
|
1205
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig(callMode)), callMode),
|
|
1091
1206
|
...(developerInstructions ? { developerInstructions } : {}),
|
|
1092
1207
|
};
|
|
1093
1208
|
logger.info(`[CodexRunner] runQuery permMode=${requestedPermissionMode}->${callMode} ` +
|
|
@@ -1099,11 +1214,17 @@ export class CodexRunner {
|
|
|
1099
1214
|
if (!threadId)
|
|
1100
1215
|
throw new Error('Codex app-server did not return a thread id');
|
|
1101
1216
|
if (!resumingThread) {
|
|
1102
|
-
|
|
1103
|
-
await this.persistDelegationCarrier(sessionManager, sessionId, threadId, delegationCarrierCandidate);
|
|
1217
|
+
carrierToPublish = { threadId, carrier: delegationCarrierCandidate };
|
|
1104
1218
|
}
|
|
1105
1219
|
else if (hasTaskDelegation && !knownDelegationCarrier) {
|
|
1106
|
-
|
|
1220
|
+
// A BOUND topic cannot silently fork to migrate carrier metadata: its
|
|
1221
|
+
// active-turn activation is allowed to confirm only the authoritative
|
|
1222
|
+
// backend ID, so a fork would deterministically conflict. threadResume
|
|
1223
|
+
// already received the candidate in this turn's environment config;
|
|
1224
|
+
// publish it for the same thread after binding confirmation instead.
|
|
1225
|
+
let recoveredCarrier = topicBinding
|
|
1226
|
+
? delegationCarrierCandidate
|
|
1227
|
+
: await this.readThreadDelegationCarrier(appServer, threadId);
|
|
1107
1228
|
if (!recoveredCarrier) {
|
|
1108
1229
|
const staleThreadId = threadId;
|
|
1109
1230
|
logger.warn(`[CodexRunner] loaded thread has no recoverable delegation carrier; forking once to preserve history: thread=${staleThreadId}`);
|
|
@@ -1121,19 +1242,35 @@ export class CodexRunner {
|
|
|
1121
1242
|
threadId = forkedThreadId;
|
|
1122
1243
|
recoveredCarrier = delegationCarrierCandidate;
|
|
1123
1244
|
}
|
|
1124
|
-
|
|
1125
|
-
await this.persistDelegationCarrier(sessionManager, sessionId, threadId, recoveredCarrier);
|
|
1245
|
+
carrierToPublish = { threadId, carrier: recoveredCarrier };
|
|
1126
1246
|
}
|
|
1127
1247
|
else if (knownDelegationCarrier) {
|
|
1128
|
-
|
|
1129
|
-
await this.persistDelegationCarrier(sessionManager, sessionId, threadId, knownDelegationCarrier);
|
|
1248
|
+
carrierToPublish = { threadId, carrier: knownDelegationCarrier };
|
|
1130
1249
|
}
|
|
1131
1250
|
agentSessionId = threadId;
|
|
1251
|
+
const turn = modelOverride?.turn;
|
|
1252
|
+
const bindingResult = this.onSessionIdUpdate
|
|
1253
|
+
? ((turn === undefined
|
|
1254
|
+
? await this.onSessionIdUpdate(sessionId, threadId)
|
|
1255
|
+
: await this.onSessionIdUpdate(sessionId, threadId, { turn }))
|
|
1256
|
+
?? 'legacy_updated')
|
|
1257
|
+
: 'legacy_updated';
|
|
1258
|
+
const bindingAccepted = bindingResult === 'activated'
|
|
1259
|
+
|| bindingResult === 'already_active'
|
|
1260
|
+
|| bindingResult === 'legacy_updated';
|
|
1261
|
+
if (!bindingAccepted) {
|
|
1262
|
+
const error = new Error(`backend discovery rejected: ${bindingResult}`);
|
|
1263
|
+
error.code = 'TOPIC_BACKEND_DISCOVERY_REJECTED';
|
|
1264
|
+
throw error;
|
|
1265
|
+
}
|
|
1132
1266
|
this.activeSessions.set(sessionId, threadId);
|
|
1267
|
+
if (carrierToPublish) {
|
|
1268
|
+
this.threadDelegationCarriers.set(carrierToPublish.threadId, carrierToPublish.carrier);
|
|
1269
|
+
await this.persistDelegationCarrier(sessionManager, sessionId, carrierToPublish.threadId, carrierToPublish.carrier);
|
|
1270
|
+
}
|
|
1133
1271
|
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${threadId}`);
|
|
1134
1272
|
this.threadProjectPaths.set(threadId, path.resolve(projectPath));
|
|
1135
1273
|
this.bindExternalToolBoundary(threadId, externalToolConfig);
|
|
1136
|
-
this.onSessionIdUpdate?.(sessionId, threadId);
|
|
1137
1274
|
const controller = new AbortController();
|
|
1138
1275
|
this.activeAbortControllers.set(sessionId, controller);
|
|
1139
1276
|
const tempFiles = [];
|
|
@@ -1180,7 +1317,7 @@ export class CodexRunner {
|
|
|
1180
1317
|
this.activeAbortControllers.delete(sessionId);
|
|
1181
1318
|
this.activeStreams.delete(sessionId);
|
|
1182
1319
|
logger.info(`[CodexRunner] Applied pending interrupt before turn start: ${sessionId}`);
|
|
1183
|
-
return this.transformAppServerStream(queue, sessionId, state, unsubscribe, tempFiles);
|
|
1320
|
+
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, tempFiles);
|
|
1184
1321
|
}
|
|
1185
1322
|
try {
|
|
1186
1323
|
const turnResponse = await appServer.turnStart(threadId, input, {
|
|
@@ -1216,7 +1353,7 @@ export class CodexRunner {
|
|
|
1216
1353
|
this.cleanupTempFiles(tempFiles);
|
|
1217
1354
|
throw error;
|
|
1218
1355
|
}
|
|
1219
|
-
return this.transformAppServerStream(queue, sessionId, state, unsubscribe, tempFiles);
|
|
1356
|
+
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, tempFiles);
|
|
1220
1357
|
}
|
|
1221
1358
|
// ── Interrupt ──
|
|
1222
1359
|
async interrupt(sessionKey) {
|
|
@@ -1266,40 +1403,82 @@ export class CodexRunner {
|
|
|
1266
1403
|
// ── Session commands ──
|
|
1267
1404
|
updateSessionId(sessionId, agentSessionId) {
|
|
1268
1405
|
const previousThreadId = this.activeSessions.get(sessionId);
|
|
1269
|
-
|
|
1270
|
-
this.
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1406
|
+
const applyLocal = () => {
|
|
1407
|
+
const currentThreadId = this.activeSessions.get(sessionId);
|
|
1408
|
+
if (currentThreadId !== previousThreadId && !(previousThreadId === undefined && currentThreadId === undefined))
|
|
1409
|
+
return;
|
|
1410
|
+
if (previousThreadId && previousThreadId !== agentSessionId) {
|
|
1411
|
+
this.threadProjectPaths.delete(previousThreadId);
|
|
1412
|
+
this.threadDelegationCarriers.delete(previousThreadId);
|
|
1413
|
+
this.unbindExternalToolBoundary(previousThreadId);
|
|
1414
|
+
this.clearTrackedApprovalItemsForThread(previousThreadId);
|
|
1415
|
+
}
|
|
1416
|
+
if (agentSessionId)
|
|
1417
|
+
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${agentSessionId}`);
|
|
1418
|
+
this.pruneThreadOperationLocks();
|
|
1419
|
+
if (agentSessionId)
|
|
1420
|
+
this.activeSessions.set(sessionId, agentSessionId);
|
|
1421
|
+
else
|
|
1422
|
+
this.activeSessions.delete(sessionId);
|
|
1423
|
+
};
|
|
1424
|
+
if (!this.onSessionIdUpdate) {
|
|
1425
|
+
applyLocal();
|
|
1426
|
+
return;
|
|
1281
1427
|
}
|
|
1282
|
-
this.
|
|
1283
|
-
|
|
1428
|
+
void this.onSessionIdUpdate(sessionId, agentSessionId).then(result => {
|
|
1429
|
+
if (result === 'activated' || result === 'already_active' || result === 'legacy_updated' || !agentSessionId) {
|
|
1430
|
+
applyLocal();
|
|
1431
|
+
}
|
|
1432
|
+
}).catch(error => {
|
|
1433
|
+
logger.warn(`[CodexRunner] session binding callback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1434
|
+
if (!agentSessionId)
|
|
1435
|
+
applyLocal();
|
|
1436
|
+
});
|
|
1284
1437
|
}
|
|
1285
1438
|
async closeSession(sessionId) {
|
|
1439
|
+
const capturedThreadId = this.activeSessions.get(sessionId);
|
|
1440
|
+
const capturedPermissionContext = this.permissionContexts.get(sessionId);
|
|
1441
|
+
const capturedStream = this.activeStreams.get(sessionId);
|
|
1442
|
+
const capturedController = this.activeAbortControllers.get(sessionId);
|
|
1443
|
+
const capturedTurn = this.activeTurns.get(sessionId);
|
|
1286
1444
|
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
this.
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
this.
|
|
1298
|
-
this.
|
|
1299
|
-
|
|
1445
|
+
// Do not call interrupt(sessionId) here: a new run may have replaced the
|
|
1446
|
+
// session-scoped maps while an older close was waiting. Interrupt and
|
|
1447
|
+
// clean only the exact lifecycle captured at entry.
|
|
1448
|
+
if (capturedTurn) {
|
|
1449
|
+
await this.interruptAppServerTurn(capturedTurn.threadId, capturedTurn.turnId).catch(() => { });
|
|
1450
|
+
}
|
|
1451
|
+
capturedController?.abort('Session closed');
|
|
1452
|
+
const lifecycleReplaced = (capturedStream !== undefined && this.activeStreams.get(sessionId) !== capturedStream)
|
|
1453
|
+
|| (capturedController !== undefined && this.activeAbortControllers.get(sessionId) !== capturedController)
|
|
1454
|
+
|| (capturedTurn !== undefined && this.activeTurns.get(sessionId) !== capturedTurn)
|
|
1455
|
+
|| (capturedPermissionContext !== undefined && this.permissionContexts.get(sessionId) !== capturedPermissionContext)
|
|
1456
|
+
|| (capturedThreadId !== undefined && this.activeSessions.get(sessionId) !== capturedThreadId);
|
|
1457
|
+
if (capturedStream !== undefined && this.activeStreams.get(sessionId) === capturedStream)
|
|
1458
|
+
this.activeStreams.delete(sessionId);
|
|
1459
|
+
if (capturedController !== undefined && this.activeAbortControllers.get(sessionId) === capturedController)
|
|
1460
|
+
this.activeAbortControllers.delete(sessionId);
|
|
1461
|
+
if (capturedTurn !== undefined && this.activeTurns.get(sessionId) === capturedTurn)
|
|
1462
|
+
this.activeTurns.delete(sessionId);
|
|
1463
|
+
if (this.pendingInterrupts.has(sessionId) && !lifecycleReplaced)
|
|
1464
|
+
this.pendingInterrupts.delete(sessionId);
|
|
1465
|
+
if (this.permissionContexts.get(sessionId) === capturedPermissionContext)
|
|
1466
|
+
this.permissionContexts.delete(sessionId);
|
|
1467
|
+
// Session-level configuration and thread indexes are shared by later
|
|
1468
|
+
// runs. Only remove them when the captured lifecycle still owns the key.
|
|
1469
|
+
if (!lifecycleReplaced) {
|
|
1470
|
+
if (this.activeSessions.get(sessionId) === capturedThreadId)
|
|
1471
|
+
this.activeSessions.delete(sessionId);
|
|
1472
|
+
this.chatModes.delete(sessionId);
|
|
1473
|
+
this.cleanupSessionTempDir(sessionId);
|
|
1474
|
+
if (capturedThreadId) {
|
|
1475
|
+
this.threadProjectPaths.delete(capturedThreadId);
|
|
1476
|
+
this.threadDelegationCarriers.delete(capturedThreadId);
|
|
1477
|
+
this.unbindExternalToolBoundary(capturedThreadId);
|
|
1478
|
+
}
|
|
1479
|
+
this.clearTrackedApprovalItemsForThread(capturedThreadId);
|
|
1480
|
+
this.pruneThreadOperationLocks();
|
|
1300
1481
|
}
|
|
1301
|
-
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1302
|
-
this.pruneThreadOperationLocks();
|
|
1303
1482
|
}
|
|
1304
1483
|
resolveSessionFile(agentSessionId, _projectPath) {
|
|
1305
1484
|
// Codex session 文件: ~/.codex/sessions/YYYY/MM/DD/rollout-*-{threadId}.jsonl
|
|
@@ -1333,54 +1512,67 @@ export class CodexRunner {
|
|
|
1333
1512
|
this.unbindExternalToolBoundary(threadId);
|
|
1334
1513
|
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1335
1514
|
this.pruneThreadOperationLocks();
|
|
1336
|
-
this.onSessionIdUpdate?.(sessionId, '');
|
|
1515
|
+
await this.onSessionIdUpdate?.(sessionId, '');
|
|
1337
1516
|
return true;
|
|
1338
1517
|
}
|
|
1339
|
-
async compactSession(_sessionId, agentSessionId, _projectPath) {
|
|
1518
|
+
async compactSession(_sessionId, agentSessionId, _projectPath, modelOverride) {
|
|
1519
|
+
const startedAt = Date.now();
|
|
1520
|
+
const finish = (result) => {
|
|
1521
|
+
logger.info(`[CodexRunner] Compact result: thread=${agentSessionId} ok=${result.ok} code=${result.ok ? 'ok' : result.code} durationMs=${result.durationMs}`);
|
|
1522
|
+
return result;
|
|
1523
|
+
};
|
|
1340
1524
|
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId, _sessionId), 'compact');
|
|
1341
1525
|
try {
|
|
1342
1526
|
const appServer = this.getAppServerClient();
|
|
1343
1527
|
this.onCompactStart?.(_sessionId);
|
|
1344
1528
|
try {
|
|
1345
|
-
|
|
1529
|
+
await this.startAndWaitForCompact(appServer, agentSessionId);
|
|
1530
|
+
return finish({ ok: true, durationMs: Date.now() - startedAt });
|
|
1346
1531
|
}
|
|
1347
1532
|
catch (error) {
|
|
1348
1533
|
if (!this.isThreadNotFoundError(error))
|
|
1349
1534
|
throw error;
|
|
1350
1535
|
logger.info(`[CodexRunner] Compact thread not loaded, resuming before compact: ${agentSessionId}`);
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1536
|
+
const compactMode = normalizeExecutionPermissionMode(modelOverride?.permissionMode ?? this.chatModes.get(_sessionId) ?? this.currentMode);
|
|
1537
|
+
const compactModel = modelOverride?.model || this.model;
|
|
1538
|
+
const compactEffort = modelOverride?.effortMode === 'model_default'
|
|
1539
|
+
? undefined
|
|
1540
|
+
: modelOverride?.effort ?? this.effort;
|
|
1353
1541
|
const compactPolicy = this.toApprovalPolicy(compactMode);
|
|
1354
1542
|
const executionSandbox = this.buildExecutionSandboxOptions(_sessionId, compactMode, _projectPath);
|
|
1355
1543
|
const capabilityConfig = await this.resolveCapabilityThreadConfig(_projectPath);
|
|
1356
|
-
const externalToolConfig =
|
|
1357
|
-
|
|
1544
|
+
const externalToolConfig = compactMode === 'fullaccess'
|
|
1545
|
+
? {}
|
|
1546
|
+
: await this.resolveExternalToolApprovalConfig(appServer, _projectPath, capabilityConfig, compactMode);
|
|
1547
|
+
if (compactMode !== 'fullaccess')
|
|
1548
|
+
await this.assertProactiveHookAvailability(_sessionId, appServer);
|
|
1358
1549
|
const compactDelegationCarrier = this.threadDelegationCarriers.get(agentSessionId)
|
|
1359
1550
|
?? this.createDelegationCarrier();
|
|
1360
1551
|
await appServer.threadResume(agentSessionId, _projectPath, {
|
|
1361
|
-
model:
|
|
1362
|
-
effort:
|
|
1552
|
+
model: compactModel,
|
|
1553
|
+
effort: compactEffort,
|
|
1363
1554
|
approvalPolicy: compactPolicy,
|
|
1364
1555
|
approvalsReviewer: 'user',
|
|
1365
1556
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
1366
|
-
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()), compactMode),
|
|
1557
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig(compactMode)), compactMode),
|
|
1367
1558
|
});
|
|
1368
1559
|
this.threadProjectPaths.set(agentSessionId, path.resolve(_projectPath));
|
|
1369
1560
|
this.threadDelegationCarriers.set(agentSessionId, compactDelegationCarrier);
|
|
1370
1561
|
this.bindExternalToolBoundary(agentSessionId, externalToolConfig);
|
|
1371
|
-
|
|
1562
|
+
await this.startAndWaitForCompact(appServer, agentSessionId);
|
|
1563
|
+
return finish({ ok: true, durationMs: Date.now() - startedAt });
|
|
1372
1564
|
}
|
|
1373
1565
|
}
|
|
1374
1566
|
catch (error) {
|
|
1375
|
-
|
|
1376
|
-
return false;
|
|
1567
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1568
|
+
return finish({ ok: false, code: 'sdk_error', message, durationMs: Date.now() - startedAt });
|
|
1377
1569
|
}
|
|
1378
1570
|
finally {
|
|
1379
1571
|
release();
|
|
1380
1572
|
}
|
|
1381
1573
|
}
|
|
1382
|
-
async compact(sessionId, agentSessionId, projectPath) {
|
|
1383
|
-
return this.compactSession(sessionId, agentSessionId, projectPath);
|
|
1574
|
+
async compact(sessionId, agentSessionId, projectPath, modelOverride) {
|
|
1575
|
+
return this.compactSession(sessionId, agentSessionId, projectPath, modelOverride);
|
|
1384
1576
|
}
|
|
1385
1577
|
async startAndWaitForCompact(appServer, threadId) {
|
|
1386
1578
|
const completion = this.waitForThreadCompacted(appServer, threadId, Date.now());
|
|
@@ -1527,7 +1719,7 @@ export class CodexRunner {
|
|
|
1527
1719
|
approvalPolicy: this.toApprovalPolicy(mode),
|
|
1528
1720
|
approvalsReviewer: 'user',
|
|
1529
1721
|
...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
|
|
1530
|
-
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()), mode),
|
|
1722
|
+
config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig(mode)), mode),
|
|
1531
1723
|
});
|
|
1532
1724
|
const forkedThreadId = response.thread?.id;
|
|
1533
1725
|
if (!forkedThreadId)
|
|
@@ -1654,7 +1846,12 @@ export class CodexRunner {
|
|
|
1654
1846
|
}
|
|
1655
1847
|
if (request.method === 'item/tool/requestUserInput') {
|
|
1656
1848
|
const trackedItem = this.findTrackedApprovalItem(params);
|
|
1849
|
+
const requestSessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
|
|
1850
|
+
const trustedFullAccess = (this.chatModes.get(requestSessionKey) ?? this.currentMode) === 'fullaccess'
|
|
1851
|
+
&& this.isTrustedFullAccessSession(requestSessionKey);
|
|
1657
1852
|
if (trackedItem?.type === 'mcpToolCall') {
|
|
1853
|
+
if (trustedFullAccess)
|
|
1854
|
+
return this.handleMcpToolApproval(params, trackedItem);
|
|
1658
1855
|
const unattendedDenial = await this.denyUnattendedExternalToolApproval(params, `MCP:${String(trackedItem.server || 'unknown')}/${String(trackedItem.tool || 'unknown')}`, request.id);
|
|
1659
1856
|
if (unattendedDenial)
|
|
1660
1857
|
return unattendedDenial;
|
|
@@ -1664,6 +1861,12 @@ export class CodexRunner {
|
|
|
1664
1861
|
return this.handleToolRequestUserInput(params);
|
|
1665
1862
|
}
|
|
1666
1863
|
if (this.looksLikeExternalToolApproval(params.questions)) {
|
|
1864
|
+
// In trusted fullaccess, an untracked approval prompt is still an SDK
|
|
1865
|
+
// permission surface, not ordinary model/user interaction. The
|
|
1866
|
+
// fullaccess branch in handleMcpToolApproval deliberately does not
|
|
1867
|
+
// require capability-bound metadata and selects a one-shot allow.
|
|
1868
|
+
if (trustedFullAccess)
|
|
1869
|
+
return this.handleMcpToolApproval(params, {});
|
|
1667
1870
|
const unattendedDenial = await this.denyUnattendedExternalToolApproval(params, 'MCP:untracked', request.id);
|
|
1668
1871
|
if (unattendedDenial)
|
|
1669
1872
|
return unattendedDenial;
|
|
@@ -1690,6 +1893,17 @@ export class CodexRunner {
|
|
|
1690
1893
|
? 'FileChange'
|
|
1691
1894
|
: 'Bash';
|
|
1692
1895
|
const toolInput = this.buildPermissionInput(request.method, params);
|
|
1896
|
+
// A trusted fullaccess run has already been explicitly authorized by the
|
|
1897
|
+
// daemon owner. Do not route its approvals through ordinary capability,
|
|
1898
|
+
// workspace, or unattended-trigger policy checks.
|
|
1899
|
+
if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
|
|
1900
|
+
if (!this.isTrustedFullAccessSession(sessionKey)) {
|
|
1901
|
+
return this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1902
|
+
}
|
|
1903
|
+
const response = this.toAppServerApprovalResponse(request.method, 'allow', toolInput, false);
|
|
1904
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'allow', 'approval', undefined, 'trusted fullaccess execution');
|
|
1905
|
+
return response;
|
|
1906
|
+
}
|
|
1693
1907
|
// proactive 模式行为策略(首次工具调用必须是 ec msg send)
|
|
1694
1908
|
const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
|
|
1695
1909
|
if (policyResult?.block) {
|
|
@@ -1705,6 +1919,11 @@ export class CodexRunner {
|
|
|
1705
1919
|
toolName,
|
|
1706
1920
|
...(request.id !== undefined ? { requestId: String(request.id) } : {}),
|
|
1707
1921
|
policyCode: policyResult.policyCode ?? 'session_policy_hook',
|
|
1922
|
+
decisionSource: 'policy',
|
|
1923
|
+
agentAid: permissionContext?.selfAid,
|
|
1924
|
+
agentName: permissionContext?.agentName,
|
|
1925
|
+
sessionId: sessionKey,
|
|
1926
|
+
permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
|
|
1708
1927
|
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1709
1928
|
effect: 'operation_skipped',
|
|
1710
1929
|
});
|
|
@@ -1727,13 +1946,15 @@ export class CodexRunner {
|
|
|
1727
1946
|
}
|
|
1728
1947
|
const operationCwd = this.resolvePermissionOperationCwd(params, workspacePath, toolName);
|
|
1729
1948
|
const sessionMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1730
|
-
|
|
1949
|
+
const logSummary = formatApprovalLogSummary(summary);
|
|
1950
|
+
logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${logSummary}`);
|
|
1731
1951
|
const isCommandApproval = request.method === 'item/commandExecution/requestApproval'
|
|
1732
1952
|
|| request.method === 'execCommandApproval';
|
|
1733
1953
|
const managedEcIntent = isCommandApproval && this.isManagedEvolcoreCommandIntent(toolInput);
|
|
1734
|
-
const
|
|
1735
|
-
? this.
|
|
1954
|
+
const canonicalEcApproval = isCommandApproval
|
|
1955
|
+
? this.resolveCanonicalEcApproval(toolInput, this.getManagedTempDir(sessionKey))
|
|
1736
1956
|
: undefined;
|
|
1957
|
+
const approvedCommand = canonicalEcApproval?.argv;
|
|
1737
1958
|
const denyInfrastructure = async (policyCode, message) => {
|
|
1738
1959
|
logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
|
|
1739
1960
|
try {
|
|
@@ -1747,8 +1968,9 @@ export class CodexRunner {
|
|
|
1747
1968
|
policyCode,
|
|
1748
1969
|
decisionSource: 'infrastructure',
|
|
1749
1970
|
agentAid: permissionContext?.selfAid,
|
|
1971
|
+
agentName: permissionContext?.agentName,
|
|
1750
1972
|
sessionId: sessionKey,
|
|
1751
|
-
permissionMode:
|
|
1973
|
+
permissionMode: normalizeExecutionPermissionMode(sessionMode),
|
|
1752
1974
|
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1753
1975
|
effect: 'operation_skipped',
|
|
1754
1976
|
});
|
|
@@ -1757,11 +1979,13 @@ export class CodexRunner {
|
|
|
1757
1979
|
logger.warn(`[CodexRunner] failed to record infrastructure approval denial: session=${sessionKey} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1758
1980
|
}
|
|
1759
1981
|
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1760
|
-
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message);
|
|
1982
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message, canonicalEcApproval?.parseFailure);
|
|
1761
1983
|
return response;
|
|
1762
1984
|
};
|
|
1763
1985
|
if (managedEcIntent && !approvedCommand) {
|
|
1764
|
-
return await denyInfrastructure('ec_command_not_canonical',
|
|
1986
|
+
return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
|
|
1987
|
+
? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
|
|
1988
|
+
: 'EC command approval denied because no canonical argv could be derived');
|
|
1765
1989
|
}
|
|
1766
1990
|
try {
|
|
1767
1991
|
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id);
|
|
@@ -1798,20 +2022,22 @@ export class CodexRunner {
|
|
|
1798
2022
|
throw error;
|
|
1799
2023
|
}
|
|
1800
2024
|
}
|
|
1801
|
-
logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode, reason) {
|
|
2025
|
+
logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode, reason, parseFailure) {
|
|
1802
2026
|
const context = this.permissionContexts.get(sessionKey);
|
|
1803
2027
|
auditCodexApprovalDecision({
|
|
1804
2028
|
requestId: request.id !== undefined ? String(request.id) : undefined,
|
|
1805
2029
|
method: request.method,
|
|
1806
2030
|
sessionId: sessionKey,
|
|
1807
2031
|
agentAid: context?.selfAid,
|
|
2032
|
+
agentName: context?.agentName,
|
|
1808
2033
|
toolName,
|
|
1809
2034
|
decision,
|
|
1810
2035
|
decisionSource,
|
|
1811
2036
|
policyCode,
|
|
1812
|
-
permissionMode:
|
|
2037
|
+
permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
|
|
1813
2038
|
role: context?.role,
|
|
1814
2039
|
reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
|
|
2040
|
+
parseFailure,
|
|
1815
2041
|
});
|
|
1816
2042
|
logger.info(`[CodexRunner] app-server approval audit ${JSON.stringify({
|
|
1817
2043
|
requestId: request.id !== undefined ? String(request.id) : undefined,
|
|
@@ -1821,6 +2047,7 @@ export class CodexRunner {
|
|
|
1821
2047
|
decision,
|
|
1822
2048
|
decisionSource,
|
|
1823
2049
|
...(policyCode ? { policyCode } : {}),
|
|
2050
|
+
...(parseFailure ? { parseFailure } : {}),
|
|
1824
2051
|
reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
|
|
1825
2052
|
executed: false,
|
|
1826
2053
|
})}`);
|
|
@@ -1910,6 +2137,12 @@ export class CodexRunner {
|
|
|
1910
2137
|
? { requestId: String(requestId) }
|
|
1911
2138
|
: {}),
|
|
1912
2139
|
policy: context.approvalRouting?.approverPolicy ?? 'requester',
|
|
2140
|
+
policyCode: 'permission_mode_denied',
|
|
2141
|
+
decisionSource: 'policy',
|
|
2142
|
+
agentAid: context.selfAid,
|
|
2143
|
+
agentName: context.agentName,
|
|
2144
|
+
sessionId: sessionKey,
|
|
2145
|
+
permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
|
|
1913
2146
|
summary: `${toolName} runtime authorization denied by unattended Trigger policy`,
|
|
1914
2147
|
effect: 'operation_skipped',
|
|
1915
2148
|
});
|
|
@@ -1924,6 +2157,27 @@ export class CodexRunner {
|
|
|
1924
2157
|
const questions = Array.isArray(params.questions) ? params.questions : [];
|
|
1925
2158
|
if (questions.length === 0)
|
|
1926
2159
|
throw new Error('Codex MCP 审批未包含问题,已拒绝');
|
|
2160
|
+
const sessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
|
|
2161
|
+
if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
|
|
2162
|
+
if (!this.isTrustedFullAccessSession(sessionKey))
|
|
2163
|
+
return this.denyUntrackedExternalToolApproval(params);
|
|
2164
|
+
const answers = {};
|
|
2165
|
+
for (const question of questions) {
|
|
2166
|
+
const questionId = typeof question.id === 'string' ? question.id : `q-${Object.keys(answers).length + 1}`;
|
|
2167
|
+
const labels = Array.isArray(question.options)
|
|
2168
|
+
? question.options.map((option) => typeof option?.label === 'string' ? option.label.trim() : '').filter(Boolean)
|
|
2169
|
+
: [];
|
|
2170
|
+
let answer = '';
|
|
2171
|
+
try {
|
|
2172
|
+
answer = this.selectMcpApprovalAnswer(question, true);
|
|
2173
|
+
}
|
|
2174
|
+
catch {
|
|
2175
|
+
answer = labels[0] ?? 'allow';
|
|
2176
|
+
}
|
|
2177
|
+
answers[questionId] = { answers: [answer] };
|
|
2178
|
+
}
|
|
2179
|
+
return { answers };
|
|
2180
|
+
}
|
|
1927
2181
|
const hasExactIdentity = typeof trackedItem.server === 'string'
|
|
1928
2182
|
&& trackedItem.server.length > 0
|
|
1929
2183
|
&& typeof trackedItem.tool === 'string'
|
|
@@ -1940,7 +2194,6 @@ export class CodexRunner {
|
|
|
1940
2194
|
logger.warn(`[CodexRunner] MCP approval denied because the hardened external-tool config is unbound: thread=${threadId ?? '<missing>'}`);
|
|
1941
2195
|
return this.denyUntrackedExternalToolApproval(params);
|
|
1942
2196
|
}
|
|
1943
|
-
const sessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
|
|
1944
2197
|
const server = trackedItem.server;
|
|
1945
2198
|
const tool = trackedItem.tool;
|
|
1946
2199
|
const isAppCall = typeof trackedItem.mcpAppResourceUri === 'string'
|
|
@@ -1964,7 +2217,7 @@ export class CodexRunner {
|
|
|
1964
2217
|
};
|
|
1965
2218
|
const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
|
|
1966
2219
|
const workspacePath = this.resolvePermissionWorkspacePath(params);
|
|
1967
|
-
const mode =
|
|
2220
|
+
const mode = normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode);
|
|
1968
2221
|
// MCP servers are inherited unless capability config explicitly disables
|
|
1969
2222
|
// them. Apps retain their separate request/bypass lifecycle policy.
|
|
1970
2223
|
let allow = !policyResult?.block
|
|
@@ -2084,7 +2337,8 @@ export class CodexRunner {
|
|
|
2084
2337
|
chatmode: context.chatmode,
|
|
2085
2338
|
replyContext: context.replyContext,
|
|
2086
2339
|
});
|
|
2087
|
-
|
|
2340
|
+
const receipt = await sendInteractionPayload(context.adapter, envelope, interaction, undefined, context.replyContext);
|
|
2341
|
+
sent = isInteractionSendAccepted(receipt);
|
|
2088
2342
|
}
|
|
2089
2343
|
if (!sent) {
|
|
2090
2344
|
await sendPrompt(renderActionAsText(interaction));
|
|
@@ -2239,7 +2493,7 @@ export class CodexRunner {
|
|
|
2239
2493
|
// Revalidate ownership, mode, and parent containment at the final
|
|
2240
2494
|
// readonly decision. The map entry alone is stale if the directory was
|
|
2241
2495
|
// replaced between preflight and the approval callback.
|
|
2242
|
-
managedTempDir: this.getManagedTempDir(sessionKey),
|
|
2496
|
+
managedTempDir: permCtx?.managedTempDir ?? this.getManagedTempDir(sessionKey),
|
|
2243
2497
|
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2244
2498
|
} : undefined;
|
|
2245
2499
|
if (toolName === 'Bash')
|
|
@@ -2355,8 +2609,14 @@ export class CodexRunner {
|
|
|
2355
2609
|
&& this.fileChangeRequiresExpansion(toolInput, workspacePath, sessionKey);
|
|
2356
2610
|
// per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
|
|
2357
2611
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
2358
|
-
const mode =
|
|
2359
|
-
|
|
2612
|
+
const mode = normalizeExecutionPermissionMode(rawMode);
|
|
2613
|
+
if (rawMode === 'fullaccess') {
|
|
2614
|
+
if (!this.isTrustedFullAccessSession(sessionKey)) {
|
|
2615
|
+
return 'deny';
|
|
2616
|
+
}
|
|
2617
|
+
return 'allow';
|
|
2618
|
+
}
|
|
2619
|
+
const recordBlockedOperation = async (policyCode, operationInput = toolInput, denialReason = 'policy denied', matchedPath) => {
|
|
2360
2620
|
const summary = summarizeToolInputForAudit(toolName, operationInput).slice(0, 512);
|
|
2361
2621
|
auditToolPreflightDenial({
|
|
2362
2622
|
toolName,
|
|
@@ -2365,6 +2625,7 @@ export class CodexRunner {
|
|
|
2365
2625
|
summary,
|
|
2366
2626
|
sessionId: sessionKey,
|
|
2367
2627
|
agentAid: permissionContext?.selfAid,
|
|
2628
|
+
agentName: permissionContext?.agentName,
|
|
2368
2629
|
permissionMode: mode,
|
|
2369
2630
|
channel: permissionContext?.channel,
|
|
2370
2631
|
actorId: permissionContext?.userId,
|
|
@@ -2372,6 +2633,7 @@ export class CodexRunner {
|
|
|
2372
2633
|
selfAid: permissionContext?.selfAid,
|
|
2373
2634
|
requestId: typeof requestId === 'string' || typeof requestId === 'number' ? String(requestId) : undefined,
|
|
2374
2635
|
taskId: permissionContext?.taskId,
|
|
2636
|
+
matchedPath,
|
|
2375
2637
|
});
|
|
2376
2638
|
try {
|
|
2377
2639
|
await permissionContext?.recordExecutionAnomaly?.({
|
|
@@ -2384,6 +2646,7 @@ export class CodexRunner {
|
|
|
2384
2646
|
policyCode,
|
|
2385
2647
|
decisionSource: 'policy',
|
|
2386
2648
|
agentAid: permissionContext?.selfAid,
|
|
2649
|
+
agentName: permissionContext?.agentName,
|
|
2387
2650
|
permissionMode: mode,
|
|
2388
2651
|
summary,
|
|
2389
2652
|
effect: 'operation_skipped',
|
|
@@ -2398,7 +2661,7 @@ export class CodexRunner {
|
|
|
2398
2661
|
// An unavailable registered root must fail closed; the policy helper
|
|
2399
2662
|
// distinguishes this explicit empty value from runners without a
|
|
2400
2663
|
// session-temp capability.
|
|
2401
|
-
managedTempDir: this.getManagedTempDir(sessionKey) ?? '',
|
|
2664
|
+
managedTempDir: permissionContext?.managedTempDir ?? this.getManagedTempDir(sessionKey) ?? '',
|
|
2402
2665
|
allowProtectedMetadata: false,
|
|
2403
2666
|
selfAid: permissionContext?.selfAid,
|
|
2404
2667
|
channel: permissionContext?.channel,
|
|
@@ -2413,7 +2676,7 @@ export class CodexRunner {
|
|
|
2413
2676
|
logger.warn(`[CodexRunner] tool preflight denied: session=${sessionKey} tool=${toolName} ` +
|
|
2414
2677
|
`policy=${preflight.policyCode ?? 'unknown'} reason=${preflight.message ?? 'policy denied'}`);
|
|
2415
2678
|
if (preflight.policyCode) {
|
|
2416
|
-
await recordBlockedOperation(preflight.policyCode, toolInput, preflight.message ?? 'tool preflight denied');
|
|
2679
|
+
await recordBlockedOperation(preflight.policyCode, toolInput, preflight.message ?? 'tool preflight denied', preflight.matchedPath);
|
|
2417
2680
|
}
|
|
2418
2681
|
return 'deny';
|
|
2419
2682
|
}
|
|
@@ -2685,7 +2948,8 @@ export class CodexRunner {
|
|
|
2685
2948
|
const input = [{ type: 'text', text: prompt, text_elements: [] }];
|
|
2686
2949
|
if (!images?.length)
|
|
2687
2950
|
return input;
|
|
2688
|
-
const tmpDir = runtimeEnv
|
|
2951
|
+
const tmpDir = getManagedTaskTempDir(runtimeEnv)
|
|
2952
|
+
?? runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR
|
|
2689
2953
|
?? (sessionId ? this.getManagedTempDir(sessionId) : undefined)
|
|
2690
2954
|
?? process.env.TMPDIR;
|
|
2691
2955
|
if (!tmpDir || !path.isAbsolute(tmpDir))
|
|
@@ -2728,7 +2992,7 @@ export class CodexRunner {
|
|
|
2728
2992
|
}
|
|
2729
2993
|
return !state.turnId || !turnId || turnId === state.turnId;
|
|
2730
2994
|
}
|
|
2731
|
-
async *transformAppServerStream(notifications, sessionId, state, unsubscribe, tempFiles) {
|
|
2995
|
+
async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, tempFiles) {
|
|
2732
2996
|
try {
|
|
2733
2997
|
yield { type: 'session_id', sessionId: state.threadId };
|
|
2734
2998
|
for await (const notification of notifications) {
|
|
@@ -2739,8 +3003,14 @@ export class CodexRunner {
|
|
|
2739
3003
|
}
|
|
2740
3004
|
finally {
|
|
2741
3005
|
unsubscribe();
|
|
2742
|
-
this.activeAbortControllers.
|
|
2743
|
-
|
|
3006
|
+
if (this.activeAbortControllers.get(sessionId) === controller) {
|
|
3007
|
+
this.activeAbortControllers.delete(sessionId);
|
|
3008
|
+
}
|
|
3009
|
+
const activeTurn = this.activeTurns.get(sessionId);
|
|
3010
|
+
if (activeTurn?.threadId === state.threadId
|
|
3011
|
+
&& (!state.turnId || activeTurn.turnId === state.turnId)) {
|
|
3012
|
+
this.activeTurns.delete(sessionId);
|
|
3013
|
+
}
|
|
2744
3014
|
this.cleanupTempFiles(tempFiles);
|
|
2745
3015
|
}
|
|
2746
3016
|
}
|
|
@@ -3361,11 +3631,9 @@ export class CodexAgentPlugin {
|
|
|
3361
3631
|
agents: {
|
|
3362
3632
|
codex: {
|
|
3363
3633
|
...(override || {}),
|
|
3364
|
-
evolcoreAgentAid: agent.config.aid,
|
|
3365
|
-
evolcoreAgentConfig: agent.config,
|
|
3366
3634
|
},
|
|
3367
3635
|
},
|
|
3368
3636
|
};
|
|
3369
|
-
return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks) };
|
|
3637
|
+
return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks, { agentAid: agent.config.aid, agentConfig: agent.config }) };
|
|
3370
3638
|
}
|
|
3371
3639
|
}
|