evolcore 0.0.27 → 0.0.29-test.0
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 +38 -15
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +29 -14
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +13 -2
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +444 -106
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/client.js +1 -0
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +262 -86
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +116 -149
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/diag.js +1 -1
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/menu-client.js +4 -0
- package/dist/cli/restart-monitor.js +127 -23
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +80 -24
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +331 -79
- package/dist/core/command/menu-protocol.js +45 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +230 -79
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +90 -20
- package/dist/core/message/message-bridge.js +333 -23
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +596 -90
- package/dist/core/permission/ec-command-parser.js +1044 -87
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +277 -24
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +482 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +722 -171
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/welcome.js +37 -8
- package/dist/utils/windows-shell-trust.js +37 -0
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
|
@@ -5,9 +5,9 @@ import { formatAsyncQuestionAnswer, hasTrustedFullAccessContext, parseAsyncUserQ
|
|
|
5
5
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
6
6
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
7
7
|
import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
8
|
-
import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
8
|
+
import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, getExistingRelationAttachmentDirs, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
9
9
|
import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
|
|
10
|
-
import { resolveOpenaiConfig, resolveCodexCliPath } from './baseagent.js';
|
|
10
|
+
import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPath, } from './baseagent.js';
|
|
11
11
|
import { logger } from '../utils/logger.js';
|
|
12
12
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
13
13
|
import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
@@ -17,26 +17,20 @@ import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from
|
|
|
17
17
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
18
18
|
import { AGENT_DELEGATION_TOKEN_ENV, CODEX_SESSION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
19
19
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
20
|
-
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, parseCodexToolCommand, parseCodexCarrierCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
|
|
20
|
+
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, containsUnresolvedManagedTmpDirReference, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, canonicalizeEvolcoreCommandArgv, parseCodexToolCommand, parseCodexCarrierCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, parseShellApplyPatchInvocation, } from '../core/permission/ec-command-parser.js';
|
|
21
21
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
22
|
-
import {
|
|
22
|
+
import { agentCodexTokenPath, resolvePaths, resolveRoot } from '../paths.js';
|
|
23
23
|
import { ensureSessionRuntimeDir, getManagedTaskTempDir } from '../cli/task-context.js';
|
|
24
|
-
import {
|
|
24
|
+
import { buildTranscriptTurns } from '../core/session/session-transcript.js';
|
|
25
25
|
import { CodexSessionFileAdapter } from '../core/session/adapters/codex-session-file-adapter.js';
|
|
26
26
|
import { execFileSync } from 'child_process';
|
|
27
27
|
import { probeCli, probeCliSync } from '../utils/cross-platform.js';
|
|
28
28
|
import { createHash, randomBytes } from 'crypto';
|
|
29
29
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
30
|
+
import { normalizeToolErrorClassification, normalizeToolErrorCode, normalizeToolErrorDetailCode, } from '../core/permission/tool-error-code.js';
|
|
30
31
|
import fs from 'fs';
|
|
31
32
|
import path from 'path';
|
|
32
33
|
import os from 'os';
|
|
33
|
-
// ── MIME → 扩展名映射 ──
|
|
34
|
-
const MIME_EXT = {
|
|
35
|
-
'image/png': '.png',
|
|
36
|
-
'image/jpeg': '.jpg',
|
|
37
|
-
'image/gif': '.gif',
|
|
38
|
-
'image/webp': '.webp',
|
|
39
|
-
};
|
|
40
34
|
const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
|
|
41
35
|
const CODEX_INTERRUPT_TIMEOUT_MS = 5_000;
|
|
42
36
|
function toAgentErrorType(message) {
|
|
@@ -92,7 +86,7 @@ class AsyncEventQueue {
|
|
|
92
86
|
}
|
|
93
87
|
}
|
|
94
88
|
}
|
|
95
|
-
function
|
|
89
|
+
function fallbackErrorCodeForToolDenial(decisionSource, policyCode) {
|
|
96
90
|
if (decisionSource === 'policy')
|
|
97
91
|
return 'POLICY_DENIED';
|
|
98
92
|
if (decisionSource === 'approval')
|
|
@@ -103,6 +97,35 @@ function errorCodeForToolDenial(decisionSource, policyCode) {
|
|
|
103
97
|
return 'INVALID_ARGUMENT';
|
|
104
98
|
return 'EXECUTION_FAILED';
|
|
105
99
|
}
|
|
100
|
+
function errorCodeForToolDenial(decisionSource, policyCode) {
|
|
101
|
+
return normalizeToolErrorClassification({
|
|
102
|
+
errorCode: fallbackErrorCodeForToolDenial(decisionSource, policyCode),
|
|
103
|
+
decisionSource,
|
|
104
|
+
policyCode,
|
|
105
|
+
executed: false,
|
|
106
|
+
executionState: 'blocked',
|
|
107
|
+
}).errorCode;
|
|
108
|
+
}
|
|
109
|
+
function errorDetailCodeForToolDenial(decisionSource, policyCode, errorDetailCode) {
|
|
110
|
+
return normalizeToolErrorClassification({
|
|
111
|
+
errorCode: fallbackErrorCodeForToolDenial(decisionSource, policyCode),
|
|
112
|
+
errorDetailCode,
|
|
113
|
+
decisionSource,
|
|
114
|
+
policyCode,
|
|
115
|
+
executed: false,
|
|
116
|
+
executionState: 'blocked',
|
|
117
|
+
}).errorDetailCode;
|
|
118
|
+
}
|
|
119
|
+
function codexItemErrorFields(item) {
|
|
120
|
+
const rawCode = item?.error?.code ?? item?.error?.errorCode;
|
|
121
|
+
if (typeof rawCode !== 'string' || !rawCode.trim())
|
|
122
|
+
return {};
|
|
123
|
+
const errorCode = normalizeToolErrorCode(rawCode);
|
|
124
|
+
if (errorCode)
|
|
125
|
+
return { errorCode };
|
|
126
|
+
const errorDetailCode = normalizeToolErrorDetailCode(rawCode);
|
|
127
|
+
return errorDetailCode ? { errorDetailCode } : {};
|
|
128
|
+
}
|
|
106
129
|
function isCodexReasoningNotification(method) {
|
|
107
130
|
return method === 'item/reasoning/textDelta'
|
|
108
131
|
|| method === 'item/reasoning/summaryTextDelta'
|
|
@@ -212,10 +235,13 @@ function compactCodexProbeText(value, maxLength = 600) {
|
|
|
212
235
|
const text = value.replace(/\s+/gu, ' ').trim();
|
|
213
236
|
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3).trimEnd()}...`;
|
|
214
237
|
}
|
|
215
|
-
function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
|
|
238
|
+
function logCodexProbeFailure(args, startedAt, error, timeoutMs, launch) {
|
|
216
239
|
const detail = error;
|
|
217
240
|
logger.warn(`[CodexProbe] CLI probe failed: ${JSON.stringify({
|
|
218
241
|
command: ['codex', ...args],
|
|
242
|
+
resolvedPath: launch?.resolvedPath ?? null,
|
|
243
|
+
launcherKind: launch?.launcherKind ?? null,
|
|
244
|
+
launcherPath: launch?.launcherPath ?? null,
|
|
219
245
|
path: detail?.path ?? null,
|
|
220
246
|
spawnargs: detail?.spawnargs ?? null,
|
|
221
247
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -229,13 +255,13 @@ function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
|
|
|
229
255
|
stderr: compactCodexProbeText(detail?.stderr),
|
|
230
256
|
})}`);
|
|
231
257
|
}
|
|
232
|
-
function probeCodexCli(args) {
|
|
233
|
-
return probeCliSync('codex', args, { onFailure: logCodexProbeFailure });
|
|
258
|
+
function probeCodexCli(args, executable) {
|
|
259
|
+
return probeCliSync('codex', args, { executable, onFailure: logCodexProbeFailure });
|
|
234
260
|
}
|
|
235
|
-
export function getCodexCliVersion() {
|
|
261
|
+
export function getCodexCliVersion(executable) {
|
|
236
262
|
const startedAt = Date.now();
|
|
237
263
|
try {
|
|
238
|
-
const output = probeCodexCli(['--version']);
|
|
264
|
+
const output = probeCodexCli(['--version'], executable);
|
|
239
265
|
const version = parseCodexCliVersion(output);
|
|
240
266
|
if (!version) {
|
|
241
267
|
logCodexProbeFailure(['--version'], startedAt, {
|
|
@@ -254,9 +280,15 @@ export function getCodexCliVersion() {
|
|
|
254
280
|
export function getCodexAppServerAvailability() {
|
|
255
281
|
const cliPath = resolveCodexCliPath();
|
|
256
282
|
if (!cliPath) {
|
|
257
|
-
|
|
283
|
+
const powershellShim = resolveCodexPowerShellShimPath();
|
|
284
|
+
return {
|
|
285
|
+
available: false,
|
|
286
|
+
reason: powershellShim
|
|
287
|
+
? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
|
|
288
|
+
: '未检测到 Codex CLI。',
|
|
289
|
+
};
|
|
258
290
|
}
|
|
259
|
-
const version = getCodexCliVersion();
|
|
291
|
+
const version = getCodexCliVersion(cliPath);
|
|
260
292
|
if (!version) {
|
|
261
293
|
return { available: false, reason: '无法读取 Codex CLI 版本。' };
|
|
262
294
|
}
|
|
@@ -269,7 +301,7 @@ export function getCodexAppServerAvailability() {
|
|
|
269
301
|
};
|
|
270
302
|
}
|
|
271
303
|
try {
|
|
272
|
-
probeCodexCli(['app-server', '--help']);
|
|
304
|
+
probeCodexCli(['app-server', '--help'], cliPath);
|
|
273
305
|
return { available: true, version };
|
|
274
306
|
}
|
|
275
307
|
catch (error) {
|
|
@@ -284,10 +316,21 @@ export function isCodexAppServerAvailable() {
|
|
|
284
316
|
}
|
|
285
317
|
/** Daemon startup/hot reload use asynchronous probes; sync CLI callers share the cache. */
|
|
286
318
|
export async function getCodexAppServerAvailabilityAsync() {
|
|
319
|
+
const cliPath = resolveCodexCliPath();
|
|
287
320
|
const options = {
|
|
288
321
|
timeouts: [CODEX_CLI_PROBE_TIMEOUT_MS, CODEX_CLI_PROBE_RETRY_TIMEOUT_MS],
|
|
322
|
+
executable: cliPath ?? undefined,
|
|
289
323
|
onFailure: logCodexProbeFailure,
|
|
290
324
|
};
|
|
325
|
+
if (!cliPath) {
|
|
326
|
+
const powershellShim = resolveCodexPowerShellShimPath();
|
|
327
|
+
return {
|
|
328
|
+
available: false,
|
|
329
|
+
reason: powershellShim
|
|
330
|
+
? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
|
|
331
|
+
: '未检测到 Codex CLI。',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
291
334
|
let version;
|
|
292
335
|
try {
|
|
293
336
|
version = parseCodexCliVersion(await probeCli('codex', ['--version'], options)) ?? undefined;
|
|
@@ -348,6 +391,7 @@ export class CodexRunner {
|
|
|
348
391
|
/** Fallback authorization lifecycle ids for app-server requests without an RPC id. */
|
|
349
392
|
approvalLifecycleIds = new WeakMap();
|
|
350
393
|
resolvedConfig;
|
|
394
|
+
codexVersion;
|
|
351
395
|
pendingInterruptTtlMs = 30_000;
|
|
352
396
|
/** Fallback for direct runner callers that do not inject SessionManager. */
|
|
353
397
|
codexSessionFileAdapter = new CodexSessionFileAdapter();
|
|
@@ -389,6 +433,7 @@ export class CodexRunner {
|
|
|
389
433
|
if (this.resolvedConfig.effort)
|
|
390
434
|
this.effort = this.resolvedConfig.effort;
|
|
391
435
|
this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
|
|
436
|
+
this.codexVersion = runtime?.codexVersion;
|
|
392
437
|
}
|
|
393
438
|
getAppServerClient(fullAccess = false) {
|
|
394
439
|
const existing = fullAccess ? this.fullAccessAppServerClient : this.appServerClient;
|
|
@@ -396,6 +441,7 @@ export class CodexRunner {
|
|
|
396
441
|
return existing;
|
|
397
442
|
const client = new CodexAppServerClient({
|
|
398
443
|
apiKey: this.resolvedConfig.apiKey,
|
|
444
|
+
codexVersion: this.codexVersion,
|
|
399
445
|
authType: this.resolvedConfig.authType,
|
|
400
446
|
baseUrl: this.resolvedConfig.baseUrl,
|
|
401
447
|
provider: this.resolvedConfig.provider,
|
|
@@ -406,6 +452,7 @@ export class CodexRunner {
|
|
|
406
452
|
enableRequestUserInput: this.resolvedConfig.enableRequestUserInput,
|
|
407
453
|
approvalsReviewer: 'user',
|
|
408
454
|
protectEvolcoreFilesystem: !fullAccess,
|
|
455
|
+
selfAid: this.resolvedConfig.agentAid,
|
|
409
456
|
// A completed turn is explicitly unsubscribed below. This is only an
|
|
410
457
|
// optimization on newer Codex versions; old versions retain the thread
|
|
411
458
|
// until the cross-client archive fallback runs.
|
|
@@ -590,6 +637,13 @@ export class CodexRunner {
|
|
|
590
637
|
this.threadSubscriptions.set(threadId, { client: targetClient, state: 'subscribed' });
|
|
591
638
|
}
|
|
592
639
|
approvedLiteralEvolcoreCommand(command, managedTempDir, dialect = 'posix', expectedCredentialToken, executable) {
|
|
640
|
+
if (/\\\$(?:TMPDIR|\{TMPDIR\})/i.test(command)
|
|
641
|
+
&& /\bec\s+fs\s+(?:cp|ls|stat|find|cat|mkdir|rm|mv|upload)\b/i.test(command)) {
|
|
642
|
+
return { parseFailure: {
|
|
643
|
+
issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
|
|
644
|
+
dialect, inputLength: command.length,
|
|
645
|
+
} };
|
|
646
|
+
}
|
|
593
647
|
const parseOptions = {
|
|
594
648
|
allowManagedTmpDir: true,
|
|
595
649
|
dialect,
|
|
@@ -621,11 +675,21 @@ export class CodexRunner {
|
|
|
621
675
|
} };
|
|
622
676
|
}
|
|
623
677
|
return {
|
|
624
|
-
argv:
|
|
678
|
+
argv: canonicalizeEvolcoreCommandArgv(parsed.argv),
|
|
625
679
|
...(parsed.credentialToken ? { credentialToken: parsed.credentialToken } : {}),
|
|
626
680
|
};
|
|
627
681
|
}
|
|
628
682
|
resolveCanonicalEcApproval(toolInput, managedTempDir, expectedCredentialToken) {
|
|
683
|
+
const rawCommand = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
684
|
+
if (/\\\$(?:TMPDIR|\{TMPDIR\})/i.test(rawCommand)
|
|
685
|
+
&& /\bec\s+fs\s+(?:cp|ls|stat|find|cat|mkdir|rm|mv|upload)\b/i.test(rawCommand)
|
|
686
|
+
&& !/\bec\s+(?:msg|group)\s+send\b/i.test(rawCommand)
|
|
687
|
+
&& !/\bec\s+trigger\s+(?:create|set|update)\b/i.test(rawCommand)) {
|
|
688
|
+
return { parseFailure: {
|
|
689
|
+
issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
|
|
690
|
+
dialect: 'posix', inputLength: rawCommand.length,
|
|
691
|
+
} };
|
|
692
|
+
}
|
|
629
693
|
const explicitArgv = Array.isArray(toolInput.commandArgv)
|
|
630
694
|
&& toolInput.commandArgv.every(value => typeof value === 'string')
|
|
631
695
|
? toolInput.commandArgv
|
|
@@ -638,9 +702,9 @@ export class CodexRunner {
|
|
|
638
702
|
dialect: 'posix', inputLength: 0,
|
|
639
703
|
} };
|
|
640
704
|
}
|
|
641
|
-
return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
|
|
705
|
+
return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv) || containsUnresolvedManagedTmpDirReference(explicitArgv)
|
|
642
706
|
? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'posix', inputLength: 0 } }
|
|
643
|
-
: { argv:
|
|
707
|
+
: { argv: canonicalizeEvolcoreCommandArgv(explicitArgv) };
|
|
644
708
|
}
|
|
645
709
|
const explicitCarrier = explicitArgv
|
|
646
710
|
? resolveCodexShellCarrierArgv(explicitArgv)
|
|
@@ -648,6 +712,21 @@ export class CodexRunner {
|
|
|
648
712
|
if (explicitCarrier !== undefined) {
|
|
649
713
|
return this.approvedLiteralEvolcoreCommand(explicitCarrier.command, managedTempDir, explicitCarrier.dialect, expectedCredentialToken, explicitCarrier.executable);
|
|
650
714
|
}
|
|
715
|
+
if (explicitArgv?.length === 1 && typeof explicitArgv[0] === 'string') {
|
|
716
|
+
const parsed = parseCodexToolCommand({ commandArgv: explicitArgv }, {
|
|
717
|
+
allowManagedTmpDir: true,
|
|
718
|
+
verifyShellExecutable: process.platform === 'win32',
|
|
719
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
720
|
+
...(expectedCredentialToken !== undefined ? { expectedCredentialToken } : {}),
|
|
721
|
+
});
|
|
722
|
+
if (parsed.ok && parsed.argv[0] === 'ec') {
|
|
723
|
+
return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv) || containsUnresolvedManagedTmpDirReference(parsed.argv)
|
|
724
|
+
? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'powershell', inputLength: explicitArgv[0].length } }
|
|
725
|
+
: { argv: canonicalizeEvolcoreCommandArgv(parsed.argv) };
|
|
726
|
+
}
|
|
727
|
+
if (!parsed.ok)
|
|
728
|
+
return { parseFailure: parsed };
|
|
729
|
+
}
|
|
651
730
|
if (hasCodexCmdCarrierEcIntent(toolInput)) {
|
|
652
731
|
const parsed = parseCodexToolCommand(toolInput, {
|
|
653
732
|
verifyShellExecutable: process.platform === 'win32',
|
|
@@ -656,13 +735,13 @@ export class CodexRunner {
|
|
|
656
735
|
if (!parsed.ok)
|
|
657
736
|
return { parseFailure: parsed };
|
|
658
737
|
if (parsed.argv[0] === 'ec') {
|
|
659
|
-
return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)
|
|
738
|
+
return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv) || containsUnresolvedManagedTmpDirReference(parsed.argv)
|
|
660
739
|
? { parseFailure: {
|
|
661
740
|
issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
|
|
662
741
|
dialect: 'cmd', inputLength: 0,
|
|
663
742
|
} }
|
|
664
743
|
: {
|
|
665
|
-
argv:
|
|
744
|
+
argv: canonicalizeEvolcoreCommandArgv(parsed.argv),
|
|
666
745
|
...(parsed.credentialToken ? { credentialToken: parsed.credentialToken } : {}),
|
|
667
746
|
};
|
|
668
747
|
}
|
|
@@ -686,7 +765,7 @@ export class CodexRunner {
|
|
|
686
765
|
...(expectedCredentialToken !== undefined ? { expectedCredentialToken } : {}),
|
|
687
766
|
});
|
|
688
767
|
if (canonicalCommand?.[0] === 'ec') {
|
|
689
|
-
return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand)
|
|
768
|
+
return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand) || containsUnresolvedManagedTmpDirReference(canonicalCommand)
|
|
690
769
|
? { parseFailure: {
|
|
691
770
|
issue: 'unsafe-expansion',
|
|
692
771
|
offset: 0,
|
|
@@ -694,7 +773,7 @@ export class CodexRunner {
|
|
|
694
773
|
dialect: 'posix',
|
|
695
774
|
inputLength: command.length,
|
|
696
775
|
} }
|
|
697
|
-
: { argv: canonicalCommand };
|
|
776
|
+
: { argv: canonicalizeEvolcoreCommandArgv(canonicalCommand) };
|
|
698
777
|
}
|
|
699
778
|
const outer = parseLiteralShellCommand(command, {
|
|
700
779
|
allowManagedTmpDir: true,
|
|
@@ -724,6 +803,10 @@ export class CodexRunner {
|
|
|
724
803
|
|| shellCarrier !== undefined
|
|
725
804
|
&& classifyEvolcoreShellCommand(shellCarrier.command, {
|
|
726
805
|
dialect: shellCarrier.dialect,
|
|
806
|
+
}).kind !== 'none'
|
|
807
|
+
|| rawArgv.length === 1
|
|
808
|
+
&& classifyEvolcoreShellCommand(rawArgv[0], {
|
|
809
|
+
dialect: 'powershell',
|
|
727
810
|
}).kind !== 'none';
|
|
728
811
|
}
|
|
729
812
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
@@ -1195,8 +1278,15 @@ export class CodexRunner {
|
|
|
1195
1278
|
enabled: true,
|
|
1196
1279
|
unix_sockets: { [resolvePaths().instanceSocket]: 'allow' },
|
|
1197
1280
|
};
|
|
1281
|
+
const relationAttachmentDenies = {};
|
|
1282
|
+
for (const directory of getExistingRelationAttachmentDirs(resolveRoot(), {
|
|
1283
|
+
selfAid: this.resolvedConfig.agentAid,
|
|
1284
|
+
onlyOtherAgents: true,
|
|
1285
|
+
}))
|
|
1286
|
+
relationAttachmentDenies[directory] = 'deny';
|
|
1198
1287
|
const filesystem = {
|
|
1199
1288
|
...buildCodexProtectedFilesystemRules(resolveRoot(), {
|
|
1289
|
+
selfAid: this.resolvedConfig.agentAid,
|
|
1200
1290
|
denyLClassRead: mode === 'readonly',
|
|
1201
1291
|
// The app-server itself is launched behind EvolCore's Linux
|
|
1202
1292
|
// H-class guard, which already mounts every L-class directory
|
|
@@ -1212,6 +1302,7 @@ export class CodexRunner {
|
|
|
1212
1302
|
...(personalMdPath ? {
|
|
1213
1303
|
[path.resolve(personalMdPath)]: mode === 'readonly' ? 'read' : 'write',
|
|
1214
1304
|
} : {}),
|
|
1305
|
+
...relationAttachmentDenies,
|
|
1215
1306
|
...(mode !== 'readonly' && managedTempDir ? {
|
|
1216
1307
|
[managedTempDir]: 'write',
|
|
1217
1308
|
[managedTempDir + '/**']: 'write',
|
|
@@ -1372,6 +1463,7 @@ export class CodexRunner {
|
|
|
1372
1463
|
const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
1373
1464
|
? toolInput
|
|
1374
1465
|
: {};
|
|
1466
|
+
const isShellTool = toolName === 'Bash' || toolName === 'Shell' || toolName === 'exec_command';
|
|
1375
1467
|
const recordPreToolUseDenial = async (policyCode, reason) => {
|
|
1376
1468
|
const summary = summarizeToolInputForAudit(toolName, normalizedInput).slice(0, 512);
|
|
1377
1469
|
auditToolPreflightDenial({
|
|
@@ -1446,6 +1538,28 @@ export class CodexRunner {
|
|
|
1446
1538
|
await recordPreToolUseDenial('fullaccess_context_untrusted', reason);
|
|
1447
1539
|
return { ok: false, applicable: true, decision: 'deny', reason };
|
|
1448
1540
|
}
|
|
1541
|
+
const preflightProjectPath = this.threadProjectPaths.get(threadId)
|
|
1542
|
+
?? (activeThread ? this.threadProjectPaths.get(activeThread) : undefined)
|
|
1543
|
+
?? process.cwd();
|
|
1544
|
+
const preflight = evaluateToolPreflight(isShellTool ? 'Bash' : toolName, normalizedInput, {
|
|
1545
|
+
sessionId: sessionKey,
|
|
1546
|
+
managedTempDir: context.managedTempDir ?? this.getManagedTempDir(sessionKey) ?? '',
|
|
1547
|
+
sessionCredential: sessionToken,
|
|
1548
|
+
allowProtectedMetadata: false,
|
|
1549
|
+
selfAid: context.selfAid,
|
|
1550
|
+
channel: context.channel,
|
|
1551
|
+
userId: context.userId,
|
|
1552
|
+
role: context.role,
|
|
1553
|
+
permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
|
|
1554
|
+
allowReadonlySourceDiagnostics: context.allowReadonlySourceDiagnostics === true,
|
|
1555
|
+
projectPath: preflightProjectPath,
|
|
1556
|
+
workspacePath: preflightProjectPath,
|
|
1557
|
+
});
|
|
1558
|
+
if (preflight.behavior === 'deny') {
|
|
1559
|
+
const reason = preflight.message ?? 'tool preflight denied';
|
|
1560
|
+
await recordPreToolUseDenial(preflight.policyCode ?? 'tool_preflight_denied', reason);
|
|
1561
|
+
return { ok: true, applicable: true, decision: 'deny', reason };
|
|
1562
|
+
}
|
|
1449
1563
|
// A tracked interactive thread still needs an authenticated, explicit
|
|
1450
1564
|
// allow so the managed hook can fail closed when no runner matches.
|
|
1451
1565
|
if (!context.preToolUsePolicyHook)
|
|
@@ -1687,7 +1801,7 @@ export class CodexRunner {
|
|
|
1687
1801
|
}
|
|
1688
1802
|
this.chatModes.set(sessionId, callMode);
|
|
1689
1803
|
const callApprovalPolicy = this.toApprovalPolicy(callMode);
|
|
1690
|
-
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath);
|
|
1804
|
+
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath, undefined);
|
|
1691
1805
|
const appServer = this.getAppServerClient(callMode === 'fullaccess');
|
|
1692
1806
|
this.dropExpiredPendingInterrupt(sessionId);
|
|
1693
1807
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
@@ -1816,12 +1930,11 @@ export class CodexRunner {
|
|
|
1816
1930
|
this.bindExternalToolBoundary(threadId, externalToolConfig);
|
|
1817
1931
|
const controller = new AbortController();
|
|
1818
1932
|
this.activeAbortControllers.set(sessionId, controller);
|
|
1819
|
-
const tempFiles = [];
|
|
1820
1933
|
const answer = modelOverride?.asyncQuestionAnswer;
|
|
1821
1934
|
if (answer && answer.providerThreadId !== threadId) {
|
|
1822
1935
|
throw new Error('Async question reply cannot resume a different Codex thread');
|
|
1823
1936
|
}
|
|
1824
|
-
const input = this.buildAppServerInput(answer ? formatAsyncQuestionAnswer(answer) : prompt, images
|
|
1937
|
+
const input = this.buildAppServerInput(answer ? formatAsyncQuestionAnswer(answer) : prompt, images);
|
|
1825
1938
|
const queue = new AsyncEventQueue();
|
|
1826
1939
|
controller.signal.addEventListener('abort', () => queue.end(), { once: true });
|
|
1827
1940
|
const state = {
|
|
@@ -1908,7 +2021,7 @@ export class CodexRunner {
|
|
|
1908
2021
|
if (!resumingThread)
|
|
1909
2022
|
this.cleanupProvisionalThread(sessionId, threadId);
|
|
1910
2023
|
logger.info(`[CodexRunner] Applied pending interrupt before turn start: ${sessionId}`);
|
|
1911
|
-
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer
|
|
2024
|
+
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer);
|
|
1912
2025
|
}
|
|
1913
2026
|
try {
|
|
1914
2027
|
const turnResponse = await appServer.turnStart(threadId, input, {
|
|
@@ -1990,12 +2103,11 @@ export class CodexRunner {
|
|
|
1990
2103
|
this.activeAbortControllers.delete(sessionId);
|
|
1991
2104
|
this.activeTurns.delete(sessionId);
|
|
1992
2105
|
this.pendingInterrupts.delete(sessionId);
|
|
1993
|
-
this.cleanupTempFiles(tempFiles);
|
|
1994
2106
|
if (!resumingThread)
|
|
1995
2107
|
this.cleanupProvisionalThread(sessionId, threadId);
|
|
1996
2108
|
throw error;
|
|
1997
2109
|
}
|
|
1998
|
-
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer
|
|
2110
|
+
return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer);
|
|
1999
2111
|
}
|
|
2000
2112
|
// ── Interrupt ──
|
|
2001
2113
|
async interrupt(sessionKey) {
|
|
@@ -2444,15 +2556,9 @@ export class CodexRunner {
|
|
|
2444
2556
|
// They follow the thread (not the daemon process) so a daemon restart does
|
|
2445
2557
|
// not strand a resumable thread: the token file is re-read on resume, and
|
|
2446
2558
|
// it is removed lazily once the rollout file itself disappears.
|
|
2447
|
-
threadCredentialsDir() {
|
|
2448
|
-
const aid = this.resolvedConfig.agentAid;
|
|
2449
|
-
if (!aid)
|
|
2450
|
-
return undefined;
|
|
2451
|
-
return path.join(agentSessionsDir(aid), 'codex-tokens');
|
|
2452
|
-
}
|
|
2453
2559
|
threadCredentialPath(threadId) {
|
|
2454
|
-
const
|
|
2455
|
-
return
|
|
2560
|
+
const aid = this.resolvedConfig.agentAid;
|
|
2561
|
+
return aid ? agentCodexTokenPath(aid, threadId) : undefined;
|
|
2456
2562
|
}
|
|
2457
2563
|
createThreadCredential() {
|
|
2458
2564
|
return randomBytes(32).toString('hex');
|
|
@@ -2592,7 +2698,7 @@ export class CodexRunner {
|
|
|
2592
2698
|
const appServer = this.getAppServerClient();
|
|
2593
2699
|
const history = await this.readForkHistory(appServer, agentSessionId);
|
|
2594
2700
|
const messages = this.mapThreadToSessionMessages(history, agentSessionId);
|
|
2595
|
-
const turns =
|
|
2701
|
+
const turns = buildTranscriptTurns(messages);
|
|
2596
2702
|
const targetIndex = turns.findIndex(turn => turn.assistantUuid === assistantMessageId);
|
|
2597
2703
|
if (targetIndex < 0)
|
|
2598
2704
|
throw new Error('fork point not found');
|
|
@@ -2610,7 +2716,7 @@ export class CodexRunner {
|
|
|
2610
2716
|
if (typeof turn.id !== 'string' || !turn.id)
|
|
2611
2717
|
continue;
|
|
2612
2718
|
for (const item of this.getTurnItems(turn)) {
|
|
2613
|
-
if (item.
|
|
2719
|
+
if (item.id === assistantMessageId)
|
|
2614
2720
|
return turn.id;
|
|
2615
2721
|
}
|
|
2616
2722
|
// Older app-server histories may omit item ids; the mapper then uses the
|
|
@@ -3016,7 +3122,7 @@ export class CodexRunner {
|
|
|
3016
3122
|
? this.resolveCanonicalEcApproval(toolInput, this.getManagedTempDir(sessionKey), sessionCredential)
|
|
3017
3123
|
: undefined;
|
|
3018
3124
|
const approvedCommand = canonicalEcApproval?.argv;
|
|
3019
|
-
const denyInfrastructure = async (policyCode, message) => {
|
|
3125
|
+
const denyInfrastructure = async (policyCode, message, errorDetailCode) => {
|
|
3020
3126
|
logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
|
|
3021
3127
|
try {
|
|
3022
3128
|
await permissionContext?.recordExecutionAnomaly?.({
|
|
@@ -3044,6 +3150,7 @@ export class CodexRunner {
|
|
|
3044
3150
|
decision: 'deny',
|
|
3045
3151
|
decisionSource: 'infrastructure',
|
|
3046
3152
|
policyCode,
|
|
3153
|
+
...(errorDetailCode ? { errorDetailCode } : {}),
|
|
3047
3154
|
reason: message,
|
|
3048
3155
|
});
|
|
3049
3156
|
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message, canonicalEcApproval?.parseFailure);
|
|
@@ -3054,15 +3161,28 @@ export class CodexRunner {
|
|
|
3054
3161
|
if (canonicalEcApproval?.parseFailure?.issue === 'untrusted-shell') {
|
|
3055
3162
|
return await denyInfrastructure('ec_shell_untrusted_executable', 'EC command approval denied because the shell executable failed Windows Authenticode trust validation');
|
|
3056
3163
|
}
|
|
3164
|
+
if (canonicalEcApproval?.parseFailure?.issue === 'unsupported-escape') {
|
|
3165
|
+
return await denyInfrastructure('ec_shell_unsupported_escape', 'EC inline text does not support PowerShell `n. Use literal \\n in the argument instead.');
|
|
3166
|
+
}
|
|
3167
|
+
if (canonicalEcApproval?.parseFailure?.issue === 'lossy-cmd-shim') {
|
|
3168
|
+
return await denyInfrastructure('ec_shell_lossy_cmd_shim', 'PowerShell cannot pass physical multiline EC arguments through ec.cmd intact. Use ec ... via the PowerShell-native shim instead.');
|
|
3169
|
+
}
|
|
3057
3170
|
return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
|
|
3058
3171
|
? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
|
|
3059
|
-
|
|
3172
|
+
+ (canonicalEcApproval.parseFailure.dialect === 'powershell' && canonicalEcApproval.parseFailure.issue === 'invalid-quote'
|
|
3173
|
+
? '. PowerShell treats smart quotes as delimiters too; escape quotes for PowerShell or use --text-from-file for message text.'
|
|
3174
|
+
: '')
|
|
3175
|
+
: 'EC command approval denied because no canonical argv could be derived', canonicalEcApproval?.parseFailure?.issue === 'invalid-quote' ? 'INVALID_QUOTE' : undefined);
|
|
3060
3176
|
}
|
|
3061
3177
|
try {
|
|
3062
3178
|
const resolution = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id, typeof params.itemId === 'string' ? params.itemId : typeof params.item_id === 'string' ? params.item_id : undefined);
|
|
3063
3179
|
const decision = typeof resolution === 'string' ? resolution : resolution.decision;
|
|
3064
3180
|
const resolutionMeta = typeof resolution === 'string' ? undefined : resolution;
|
|
3065
3181
|
if (decision !== 'deny' && managedEcIntent && approvedCommand) {
|
|
3182
|
+
if (containsLiteralManagedTmpDirOutsideSendContent(approvedCommand)
|
|
3183
|
+
|| containsUnresolvedManagedTmpDirReference(approvedCommand)) {
|
|
3184
|
+
return await denyInfrastructure('ec_command_unsafe_tmpdir_reference', 'EC command contains an unresolved or unsafe managed TMPDIR reference');
|
|
3185
|
+
}
|
|
3066
3186
|
const credentialThreadIds = [
|
|
3067
3187
|
this.activeSessions.get(sessionKey),
|
|
3068
3188
|
this.provisionalSessions.get(sessionKey),
|
|
@@ -3097,6 +3217,7 @@ export class CodexRunner {
|
|
|
3097
3217
|
decision: 'deny',
|
|
3098
3218
|
decisionSource,
|
|
3099
3219
|
...(policyCode ? { policyCode } : {}),
|
|
3220
|
+
...(resolutionMeta?.errorDetailCode ? { errorDetailCode: resolutionMeta.errorDetailCode } : {}),
|
|
3100
3221
|
reason: denialReason,
|
|
3101
3222
|
});
|
|
3102
3223
|
setImmediate(() => this.steerPolicyReason(sessionKey, policyCode, denialReason));
|
|
@@ -3140,6 +3261,7 @@ export class CodexRunner {
|
|
|
3140
3261
|
decision,
|
|
3141
3262
|
decisionSource,
|
|
3142
3263
|
policyCode,
|
|
3264
|
+
...(parseFailure?.issue === 'invalid-quote' ? { errorDetailCode: 'INVALID_QUOTE' } : {}),
|
|
3143
3265
|
permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
|
|
3144
3266
|
role: context?.role,
|
|
3145
3267
|
taskId: context?.taskId,
|
|
@@ -3661,6 +3783,7 @@ export class CodexRunner {
|
|
|
3661
3783
|
channel: permCtx?.channel,
|
|
3662
3784
|
peerId: permCtx?.userId,
|
|
3663
3785
|
role: permCtx?.role,
|
|
3786
|
+
selfAid: permCtx?.selfAid ?? this.resolvedConfig.agentAid,
|
|
3664
3787
|
allowProtectedMetadata: false,
|
|
3665
3788
|
// Revalidate ownership, mode, and parent containment at the final
|
|
3666
3789
|
// readonly decision. The map entry alone is stale if the directory was
|
|
@@ -3769,9 +3892,17 @@ export class CodexRunner {
|
|
|
3769
3892
|
&& this.hasNetworkPermissionRequest(toolInput);
|
|
3770
3893
|
const hasUnknownPermissions = (toolName === 'Bash' || toolName === 'PermissionGrant')
|
|
3771
3894
|
&& this.hasUnknownPermissionRequest(toolInput);
|
|
3895
|
+
const applyPatch = (toolName === 'Bash' || toolName === 'Shell' || toolName === 'exec_command')
|
|
3896
|
+
? parseShellApplyPatchInvocation(toolInput)
|
|
3897
|
+
: { kind: 'not-apply-patch' };
|
|
3898
|
+
const applyPatchFileChanges = applyPatch.kind === 'file-change' ? applyPatch.fileChanges : undefined;
|
|
3899
|
+
const applyPatchInput = applyPatchFileChanges
|
|
3900
|
+
? { ...toolInput, fileChanges: applyPatchFileChanges }
|
|
3901
|
+
: toolInput;
|
|
3772
3902
|
const hasPermissionExpansion = hasFilesystemPermissions || hasNetworkPermissions || hasUnknownPermissions;
|
|
3773
3903
|
const hasFileChangeExpansion = toolName === 'FileChange'
|
|
3774
|
-
|
|
3904
|
+
? this.fileChangeRequiresExpansion(toolInput, workspacePath, sessionKey)
|
|
3905
|
+
: !!applyPatchFileChanges && this.fileChangeRequiresExpansion(applyPatchInput, workspacePath, sessionKey);
|
|
3775
3906
|
// per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
|
|
3776
3907
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
3777
3908
|
const mode = normalizeExecutionPermissionMode(rawMode);
|
|
@@ -3850,12 +3981,17 @@ export class CodexRunner {
|
|
|
3850
3981
|
decision: 'deny',
|
|
3851
3982
|
decisionSource: 'policy',
|
|
3852
3983
|
policyCode: preflight.policyCode ?? 'tool_preflight_denied',
|
|
3984
|
+
...(preflight.errorDetailCode ? { errorDetailCode: preflight.errorDetailCode } : {}),
|
|
3853
3985
|
reason: preflight.message ?? 'tool preflight denied',
|
|
3854
3986
|
};
|
|
3855
3987
|
}
|
|
3856
3988
|
if (preflight.behavior === 'allow')
|
|
3857
3989
|
return 'allow';
|
|
3858
3990
|
const checkedInput = preflight.input;
|
|
3991
|
+
const policyInput = applyPatchFileChanges
|
|
3992
|
+
? { ...checkedInput, fileChanges: applyPatchFileChanges }
|
|
3993
|
+
: checkedInput;
|
|
3994
|
+
const policyToolName = applyPatchFileChanges ? 'FileChange' : toolName;
|
|
3859
3995
|
if (toolName === 'FileChange') {
|
|
3860
3996
|
const hasGrantRoot = typeof checkedInput.grantRoot === 'string'
|
|
3861
3997
|
&& checkedInput.grantRoot.length > 0;
|
|
@@ -3875,9 +4011,9 @@ export class CodexRunner {
|
|
|
3875
4011
|
return 'deny';
|
|
3876
4012
|
const grantScope = `codex:${this.permissionProfileName(sessionKey)}:${mode}`;
|
|
3877
4013
|
if (mode === 'readonly') {
|
|
3878
|
-
const readonly = this.checkCodexReadonly(
|
|
4014
|
+
const readonly = this.checkCodexReadonly(policyToolName, policyInput, operationCwd, sessionKey);
|
|
3879
4015
|
if (readonly.behavior === 'deny') {
|
|
3880
|
-
await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode',
|
|
4016
|
+
await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', policyInput, readonly.message);
|
|
3881
4017
|
return 'deny';
|
|
3882
4018
|
}
|
|
3883
4019
|
return 'allow';
|
|
@@ -3888,7 +4024,7 @@ export class CodexRunner {
|
|
|
3888
4024
|
// auto 模式下检查危险命令,危险命令自动拒绝
|
|
3889
4025
|
if (mode === 'auto') {
|
|
3890
4026
|
if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions) {
|
|
3891
|
-
await recordBlockedOperation('auto_permission_expansion',
|
|
4027
|
+
await recordBlockedOperation('auto_permission_expansion', policyInput);
|
|
3892
4028
|
return 'deny';
|
|
3893
4029
|
}
|
|
3894
4030
|
if (toolName === 'PermissionGrant' && !hasNetworkPermissions) {
|
|
@@ -3908,8 +4044,8 @@ export class CodexRunner {
|
|
|
3908
4044
|
if (requiresSandboxExpansionApproval) {
|
|
3909
4045
|
if (!this.permissionGateway || !permissionPrompt)
|
|
3910
4046
|
return 'deny';
|
|
3911
|
-
if (
|
|
3912
|
-
return this.permissionGateway.requestPermission(sessionKey,
|
|
4047
|
+
if (policyToolName === 'FileChange') {
|
|
4048
|
+
return this.permissionGateway.requestPermission(sessionKey, policyToolName, policyInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, { fileChangeCwd: workspacePath });
|
|
3913
4049
|
}
|
|
3914
4050
|
return this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope);
|
|
3915
4051
|
}
|
|
@@ -3954,20 +4090,80 @@ export class CodexRunner {
|
|
|
3954
4090
|
}
|
|
3955
4091
|
throw new Error('Unsupported Codex app-server request: ' + method);
|
|
3956
4092
|
}
|
|
3957
|
-
async rollbackSessionTurns(agentSessionId,
|
|
3958
|
-
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'rollback');
|
|
4093
|
+
async rollbackSessionTurns(agentSessionId, projectPath, numTurns, targetAssistantMessageId) {
|
|
4094
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'resume+rollback');
|
|
3959
4095
|
try {
|
|
3960
|
-
return await this.rollbackSessionTurnsLocked(agentSessionId, numTurns);
|
|
4096
|
+
return await this.rollbackSessionTurnsLocked(agentSessionId, projectPath, numTurns, targetAssistantMessageId);
|
|
3961
4097
|
}
|
|
3962
4098
|
finally {
|
|
3963
4099
|
release();
|
|
3964
4100
|
}
|
|
3965
4101
|
}
|
|
3966
|
-
async rollbackSessionTurnsLocked(agentSessionId, numTurns) {
|
|
4102
|
+
async rollbackSessionTurnsLocked(agentSessionId, projectPath, numTurns, targetAssistantMessageId) {
|
|
3967
4103
|
if (numTurns < 1)
|
|
3968
4104
|
return true;
|
|
3969
|
-
const
|
|
3970
|
-
|
|
4105
|
+
const appServer = this.getAppServerClientForThread(agentSessionId);
|
|
4106
|
+
const previousAppServer = await this.prepareThreadSubscription(appServer, agentSessionId);
|
|
4107
|
+
let resumed = false;
|
|
4108
|
+
try {
|
|
4109
|
+
let resumeResponse;
|
|
4110
|
+
try {
|
|
4111
|
+
resumeResponse = await appServer.threadResume(agentSessionId, projectPath);
|
|
4112
|
+
}
|
|
4113
|
+
catch (error) {
|
|
4114
|
+
if (isCodexActiveWriterError(error) && previousAppServer && previousAppServer !== appServer) {
|
|
4115
|
+
await this.archiveThreadForHandoff(previousAppServer, appServer, agentSessionId);
|
|
4116
|
+
resumeResponse = await appServer.threadResume(agentSessionId, projectPath);
|
|
4117
|
+
}
|
|
4118
|
+
else {
|
|
4119
|
+
throw error;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
this.markThreadSubscribed(appServer, agentSessionId);
|
|
4123
|
+
this.threadProjectPaths.set(agentSessionId, path.resolve(projectPath));
|
|
4124
|
+
resumed = true;
|
|
4125
|
+
if (resumeResponse.thread?.historyMode === 'paginated') {
|
|
4126
|
+
const beforeTurnId = await this.findPaginatedRewindTurnId(appServer, agentSessionId, numTurns, targetAssistantMessageId);
|
|
4127
|
+
const response = await appServer.threadRevert(agentSessionId, beforeTurnId);
|
|
4128
|
+
return !!response.thread;
|
|
4129
|
+
}
|
|
4130
|
+
const response = await appServer.threadRollback(agentSessionId, numTurns);
|
|
4131
|
+
return !!response.thread;
|
|
4132
|
+
}
|
|
4133
|
+
finally {
|
|
4134
|
+
if (resumed) {
|
|
4135
|
+
await this.releaseThreadSubscription(appServer, agentSessionId, 'rollback-finished');
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
async findPaginatedRewindTurnId(appServer, threadId, numTurns, targetAssistantMessageId) {
|
|
4140
|
+
if (targetAssistantMessageId) {
|
|
4141
|
+
const history = await this.readForkHistory(appServer, threadId);
|
|
4142
|
+
const targetTurnId = this.findTurnIdForAssistantMessage(history, targetAssistantMessageId);
|
|
4143
|
+
if (!targetTurnId)
|
|
4144
|
+
throw new Error('Codex paginated rewind target turn not found');
|
|
4145
|
+
return targetTurnId;
|
|
4146
|
+
}
|
|
4147
|
+
let cursor;
|
|
4148
|
+
let seenCursor;
|
|
4149
|
+
let seenTurns = 0;
|
|
4150
|
+
do {
|
|
4151
|
+
const page = await appServer.threadTurnsList(threadId, 100, cursor);
|
|
4152
|
+
for (const turn of page.data ?? []) {
|
|
4153
|
+
if (seenTurns === numTurns - 1) {
|
|
4154
|
+
if (typeof turn.id !== 'string' || !turn.id)
|
|
4155
|
+
throw new Error('Codex paginated thread rewind target has no turn id');
|
|
4156
|
+
return turn.id;
|
|
4157
|
+
}
|
|
4158
|
+
seenTurns++;
|
|
4159
|
+
}
|
|
4160
|
+
const nextCursor = typeof page.nextCursor === 'string' && page.nextCursor ? page.nextCursor : undefined;
|
|
4161
|
+
if (!nextCursor || nextCursor === seenCursor)
|
|
4162
|
+
break;
|
|
4163
|
+
seenCursor = nextCursor;
|
|
4164
|
+
cursor = nextCursor;
|
|
4165
|
+
} while (true);
|
|
4166
|
+
throw new Error('Codex paginated thread has fewer than ' + numTurns + ' turns');
|
|
3971
4167
|
}
|
|
3972
4168
|
async rewindFiles(agentSessionId, projectPath, userMessageId) {
|
|
3973
4169
|
const messages = await this.getSessionMessages(agentSessionId, projectPath);
|
|
@@ -4036,6 +4232,26 @@ export class CodexRunner {
|
|
|
4036
4232
|
parent_tool_use_id: null,
|
|
4037
4233
|
});
|
|
4038
4234
|
}
|
|
4235
|
+
else if (item.id && [
|
|
4236
|
+
'commandExecution', 'mcpToolCall', 'dynamicToolCall', 'fileChange',
|
|
4237
|
+
'webSearch', 'collabAgentToolCall', 'imageView', 'sleep', 'imageGeneration',
|
|
4238
|
+
].includes(item.type || '')) {
|
|
4239
|
+
messages.push({
|
|
4240
|
+
type: 'assistant',
|
|
4241
|
+
uuid: item.id,
|
|
4242
|
+
session_id: threadId,
|
|
4243
|
+
message: { role: 'assistant', content: this.mapCodexToolItemToContent(item) },
|
|
4244
|
+
parent_tool_use_id: null,
|
|
4245
|
+
});
|
|
4246
|
+
if (item.type === 'fileChange')
|
|
4247
|
+
messages.push({
|
|
4248
|
+
type: 'system',
|
|
4249
|
+
uuid: `${item.id}-file-change`,
|
|
4250
|
+
session_id: threadId,
|
|
4251
|
+
message: { role: 'system', content: this.mapFileChangeToContent(item) },
|
|
4252
|
+
parent_tool_use_id: null,
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4039
4255
|
else if (item.type === 'file_change') {
|
|
4040
4256
|
messages.push({
|
|
4041
4257
|
type: 'system',
|
|
@@ -4049,6 +4265,86 @@ export class CodexRunner {
|
|
|
4049
4265
|
}
|
|
4050
4266
|
return messages;
|
|
4051
4267
|
}
|
|
4268
|
+
/** Preserve the same bounded display data that live tool cards receive. */
|
|
4269
|
+
mapCodexToolItemToContent(item) {
|
|
4270
|
+
const id = typeof item.id === 'string' ? item.id : undefined;
|
|
4271
|
+
const type = item.type || 'tool';
|
|
4272
|
+
let name = type;
|
|
4273
|
+
let input;
|
|
4274
|
+
let result;
|
|
4275
|
+
let isError = false;
|
|
4276
|
+
switch (type) {
|
|
4277
|
+
case 'commandExecution':
|
|
4278
|
+
name = 'Shell';
|
|
4279
|
+
input = { command: item.command, cwd: item.cwd };
|
|
4280
|
+
result = item.aggregatedOutput ?? item.output ?? '';
|
|
4281
|
+
isError = item.exitCode != null
|
|
4282
|
+
? Number(item.exitCode) !== 0
|
|
4283
|
+
: item.status === 'failed' || item.status === 'declined';
|
|
4284
|
+
break;
|
|
4285
|
+
case 'mcpToolCall':
|
|
4286
|
+
name = `MCP:${item.server}/${item.tool}`;
|
|
4287
|
+
input = item.arguments;
|
|
4288
|
+
result = item.result ?? item.contentItems;
|
|
4289
|
+
isError = item.status === 'failed';
|
|
4290
|
+
break;
|
|
4291
|
+
case 'dynamicToolCall':
|
|
4292
|
+
name = item.namespace ? `${item.namespace}:${item.tool}` : String(item.tool || 'tool');
|
|
4293
|
+
input = item.arguments;
|
|
4294
|
+
result = item.result ?? item.contentItems;
|
|
4295
|
+
isError = item.success === false || item.status === 'failed';
|
|
4296
|
+
break;
|
|
4297
|
+
case 'fileChange':
|
|
4298
|
+
name = 'FileChange';
|
|
4299
|
+
input = { changes: item.changes };
|
|
4300
|
+
result = item.changes;
|
|
4301
|
+
isError = item.status === 'failed';
|
|
4302
|
+
break;
|
|
4303
|
+
case 'webSearch':
|
|
4304
|
+
name = 'WebSearch';
|
|
4305
|
+
input = { query: item.query, ...(item.action != null ? { action: item.action } : {}) };
|
|
4306
|
+
result = input;
|
|
4307
|
+
break;
|
|
4308
|
+
case 'collabAgentToolCall':
|
|
4309
|
+
name = `Collab:${item.tool}`;
|
|
4310
|
+
input = {
|
|
4311
|
+
receiverThreadIds: item.receiverThreadIds,
|
|
4312
|
+
...(item.prompt != null ? { prompt: item.prompt } : {}),
|
|
4313
|
+
...(item.model != null ? { model: item.model } : {}),
|
|
4314
|
+
...(item.reasoningEffort != null ? { reasoningEffort: item.reasoningEffort } : {}),
|
|
4315
|
+
};
|
|
4316
|
+
result = item.contentItems ?? { receiverThreadIds: item.receiverThreadIds, agentsStates: item.agentsStates };
|
|
4317
|
+
isError = item.status === 'failed';
|
|
4318
|
+
break;
|
|
4319
|
+
case 'imageView':
|
|
4320
|
+
name = 'ViewImage';
|
|
4321
|
+
input = { path: item.path };
|
|
4322
|
+
result = input;
|
|
4323
|
+
break;
|
|
4324
|
+
case 'sleep':
|
|
4325
|
+
name = 'Sleep';
|
|
4326
|
+
input = { durationMs: item.durationMs };
|
|
4327
|
+
result = input;
|
|
4328
|
+
break;
|
|
4329
|
+
case 'imageGeneration':
|
|
4330
|
+
name = 'ImageGeneration';
|
|
4331
|
+
input = item.revisedPrompt != null ? { prompt: item.revisedPrompt } : {};
|
|
4332
|
+
result = item.result ?? item.output;
|
|
4333
|
+
break;
|
|
4334
|
+
default:
|
|
4335
|
+
input = item.input;
|
|
4336
|
+
result = item.result ?? item.output;
|
|
4337
|
+
}
|
|
4338
|
+
return [
|
|
4339
|
+
{ type: 'tool_use', id, name, input },
|
|
4340
|
+
{
|
|
4341
|
+
type: 'tool_result',
|
|
4342
|
+
...(id ? { tool_use_id: id } : {}),
|
|
4343
|
+
content: result,
|
|
4344
|
+
is_error: isError,
|
|
4345
|
+
},
|
|
4346
|
+
];
|
|
4347
|
+
}
|
|
4052
4348
|
getTurnItems(turn) {
|
|
4053
4349
|
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
4054
4350
|
const input = Array.isArray(turn?.input) ? turn.input : [];
|
|
@@ -4117,36 +4413,18 @@ export class CodexRunner {
|
|
|
4117
4413
|
this.onCompactStart = callback;
|
|
4118
4414
|
}
|
|
4119
4415
|
// ── Event stream transformation ──
|
|
4120
|
-
buildAppServerInput(prompt, images
|
|
4416
|
+
buildAppServerInput(prompt, images) {
|
|
4121
4417
|
const input = [{ type: 'text', text: prompt, text_elements: [] }];
|
|
4122
4418
|
if (!images?.length)
|
|
4123
4419
|
return input;
|
|
4124
|
-
const
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
throw new Error('managed TMPDIR is unset or not absolute');
|
|
4130
|
-
for (let i = 0; i < images.length; i++) {
|
|
4131
|
-
const img = images[i];
|
|
4132
|
-
const ext = MIME_EXT[img.mimeType || ''] || '.jpg';
|
|
4133
|
-
const tmpPath = path.join(tmpDir, `evolcore-img-${Date.now()}-${i}${ext}`);
|
|
4134
|
-
fs.writeFileSync(tmpPath, Buffer.from(img.data, 'base64'));
|
|
4135
|
-
tempFiles.push(tmpPath);
|
|
4136
|
-
input.push({ type: 'localImage', path: tmpPath });
|
|
4137
|
-
}
|
|
4138
|
-
logger.info(`[CodexRunner] Attached ${images.length} image(s) as localImage`);
|
|
4139
|
-
return input;
|
|
4140
|
-
}
|
|
4141
|
-
cleanupTempFiles(tempFiles) {
|
|
4142
|
-
if (!tempFiles?.length)
|
|
4143
|
-
return;
|
|
4144
|
-
for (const tempFile of tempFiles) {
|
|
4145
|
-
try {
|
|
4146
|
-
fs.unlinkSync(tempFile);
|
|
4147
|
-
}
|
|
4148
|
-
catch { /* ignore */ }
|
|
4420
|
+
for (const image of images) {
|
|
4421
|
+
input.push({
|
|
4422
|
+
type: 'image',
|
|
4423
|
+
url: `data:${image.mimeType || 'image/png'};base64,${image.data}`,
|
|
4424
|
+
});
|
|
4149
4425
|
}
|
|
4426
|
+
logger.info(`[CodexRunner] Attached ${images.length} image(s) as base64 data URL`);
|
|
4427
|
+
return input;
|
|
4150
4428
|
}
|
|
4151
4429
|
extractTurnId(notification) {
|
|
4152
4430
|
const params = notification.params || {};
|
|
@@ -4267,7 +4545,7 @@ export class CodexRunner {
|
|
|
4267
4545
|
}
|
|
4268
4546
|
queue.end();
|
|
4269
4547
|
}
|
|
4270
|
-
async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, appServer
|
|
4548
|
+
async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, appServer) {
|
|
4271
4549
|
try {
|
|
4272
4550
|
let sessionIdEmitted = state.committed;
|
|
4273
4551
|
if (sessionIdEmitted)
|
|
@@ -4321,7 +4599,6 @@ export class CodexRunner {
|
|
|
4321
4599
|
for (const childThreadId of state.activeSubagentThreads)
|
|
4322
4600
|
this.unregisterChildThread(childThreadId);
|
|
4323
4601
|
state.activeSubagentThreads.clear();
|
|
4324
|
-
this.cleanupTempFiles(tempFiles);
|
|
4325
4602
|
}
|
|
4326
4603
|
}
|
|
4327
4604
|
*mapAppServerNotification(notification, sessionId, state) {
|
|
@@ -4339,7 +4616,8 @@ export class CodexRunner {
|
|
|
4339
4616
|
case 'item/started': {
|
|
4340
4617
|
for (const event of this.mapAppServerItemStarted(params.item, state)) {
|
|
4341
4618
|
this.trackAppServerToolLifecycle(event, state);
|
|
4342
|
-
yield event
|
|
4619
|
+
yield event.type === 'tool_use' && params.item?.id
|
|
4620
|
+
? { ...event, assistantUuid: params.item.id } : event;
|
|
4343
4621
|
}
|
|
4344
4622
|
break;
|
|
4345
4623
|
}
|
|
@@ -4371,7 +4649,8 @@ export class CodexRunner {
|
|
|
4371
4649
|
state.completedItemIds.add(item.id);
|
|
4372
4650
|
for (const event of this.mapAppServerItemCompleted(item, state)) {
|
|
4373
4651
|
this.trackAppServerToolLifecycle(event, state);
|
|
4374
|
-
yield event
|
|
4652
|
+
yield event.type === 'tool_result' && item?.id
|
|
4653
|
+
? { ...event, assistantUuid: item.id } : event;
|
|
4375
4654
|
}
|
|
4376
4655
|
break;
|
|
4377
4656
|
}
|
|
@@ -4603,6 +4882,9 @@ export class CodexRunner {
|
|
|
4603
4882
|
isError: true,
|
|
4604
4883
|
error: denial.reason,
|
|
4605
4884
|
errorCode: errorCodeForToolDenial(denial.decisionSource, denial.policyCode),
|
|
4885
|
+
...(errorDetailCodeForToolDenial(denial.decisionSource, denial.policyCode, denial.errorDetailCode)
|
|
4886
|
+
? { errorDetailCode: errorDetailCodeForToolDenial(denial.decisionSource, denial.policyCode, denial.errorDetailCode) }
|
|
4887
|
+
: {}),
|
|
4606
4888
|
decision: 'deny',
|
|
4607
4889
|
decisionSource: denial.decisionSource,
|
|
4608
4890
|
policyCode: denial.policyCode,
|
|
@@ -4629,6 +4911,9 @@ export class CodexRunner {
|
|
|
4629
4911
|
isError: true,
|
|
4630
4912
|
error: managedHookDenial.reason,
|
|
4631
4913
|
errorCode: 'POLICY_DENIED',
|
|
4914
|
+
...(errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode)
|
|
4915
|
+
? { errorDetailCode: errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode) }
|
|
4916
|
+
: {}),
|
|
4632
4917
|
decision: 'deny',
|
|
4633
4918
|
decisionSource: 'policy',
|
|
4634
4919
|
policyCode: managedHookDenial.policyCode,
|
|
@@ -4680,24 +4965,39 @@ export class CodexRunner {
|
|
|
4680
4965
|
const text = typeof item.text === 'string' && item.text ? item.text : buffered;
|
|
4681
4966
|
if (text) {
|
|
4682
4967
|
state.lastAssistantUuid = item.id || state.turnId;
|
|
4683
|
-
yield {
|
|
4968
|
+
yield {
|
|
4969
|
+
type: 'text', text, assistantUuid: state.lastAssistantUuid,
|
|
4970
|
+
...(item.phase === 'commentary' || item.phase === 'final_answer' ? { phase: item.phase } : {}),
|
|
4971
|
+
};
|
|
4684
4972
|
}
|
|
4685
4973
|
if (item.id)
|
|
4686
4974
|
state.agentMessageDeltaText.delete(item.id);
|
|
4687
4975
|
}
|
|
4688
4976
|
break;
|
|
4689
4977
|
case 'commandExecution':
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4978
|
+
{
|
|
4979
|
+
const exitCode = typeof item.exitCode === 'number' && Number.isInteger(item.exitCode)
|
|
4980
|
+
? item.exitCode
|
|
4981
|
+
: undefined;
|
|
4982
|
+
const isError = exitCode !== undefined
|
|
4983
|
+
? exitCode !== 0
|
|
4984
|
+
: item.status === 'failed' || item.status === 'declined';
|
|
4985
|
+
yield {
|
|
4986
|
+
type: 'tool_result',
|
|
4987
|
+
name: 'Shell',
|
|
4988
|
+
result: item.aggregatedOutput ?? '',
|
|
4989
|
+
...(blockedFields ?? {
|
|
4990
|
+
isError,
|
|
4991
|
+
...codexItemErrorFields(item),
|
|
4992
|
+
executed: true,
|
|
4993
|
+
executionState: isError ? 'failed' : 'completed',
|
|
4994
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
4995
|
+
}),
|
|
4996
|
+
...(blockedFields ? {} : exitCode !== undefined ? { exitCode } : {}),
|
|
4997
|
+
callId: item.id,
|
|
4998
|
+
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
4999
|
+
};
|
|
5000
|
+
}
|
|
4701
5001
|
break;
|
|
4702
5002
|
case 'mcpToolCall':
|
|
4703
5003
|
yield {
|
|
@@ -4706,7 +5006,11 @@ export class CodexRunner {
|
|
|
4706
5006
|
result: item.result,
|
|
4707
5007
|
...(blockedFields ?? { isError: item.status === 'failed' }),
|
|
4708
5008
|
...(blockedFields ? {} : { error: item.error?.message }),
|
|
4709
|
-
...(
|
|
5009
|
+
...(blockedFields ? {} : codexItemErrorFields(item)),
|
|
5010
|
+
...(blockedFields ?? {
|
|
5011
|
+
executed: true,
|
|
5012
|
+
executionState: item.status === 'failed' ? 'failed' : 'completed',
|
|
5013
|
+
}),
|
|
4710
5014
|
callId: item.id,
|
|
4711
5015
|
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
4712
5016
|
};
|
|
@@ -4717,7 +5021,11 @@ export class CodexRunner {
|
|
|
4717
5021
|
name: item.namespace ? `${item.namespace}:${item.tool}` : item.tool,
|
|
4718
5022
|
result: item.contentItems,
|
|
4719
5023
|
...(blockedFields ?? { isError: item.success === false || item.status === 'failed' }),
|
|
4720
|
-
...(
|
|
5024
|
+
...(blockedFields ? {} : codexItemErrorFields(item)),
|
|
5025
|
+
...(blockedFields ?? {
|
|
5026
|
+
executed: true,
|
|
5027
|
+
executionState: item.success === false || item.status === 'failed' ? 'failed' : 'completed',
|
|
5028
|
+
}),
|
|
4721
5029
|
callId: item.id,
|
|
4722
5030
|
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
4723
5031
|
};
|
|
@@ -4731,12 +5039,34 @@ export class CodexRunner {
|
|
|
4731
5039
|
yield editEvent;
|
|
4732
5040
|
}
|
|
4733
5041
|
}
|
|
4734
|
-
yield {
|
|
5042
|
+
yield {
|
|
5043
|
+
type: 'tool_result',
|
|
5044
|
+
name: 'Edit',
|
|
5045
|
+
result: item.changes,
|
|
5046
|
+
...(blockedFields ?? {
|
|
5047
|
+
isError: item.status === 'failed',
|
|
5048
|
+
executed: true,
|
|
5049
|
+
executionState: item.status === 'failed' ? 'failed' : 'completed',
|
|
5050
|
+
...codexItemErrorFields(item),
|
|
5051
|
+
}),
|
|
5052
|
+
callId: item.id,
|
|
5053
|
+
};
|
|
4735
5054
|
}
|
|
4736
5055
|
else {
|
|
4737
5056
|
const desc = this.normalizeFileChanges(item.changes).map((change) => this.describeFileChange(change)).join(', ');
|
|
4738
5057
|
yield { type: 'tool_use', name: 'FileChange', input: { description: desc }, callId: item.id };
|
|
4739
|
-
yield {
|
|
5058
|
+
yield {
|
|
5059
|
+
type: 'tool_result',
|
|
5060
|
+
name: 'FileChange',
|
|
5061
|
+
result: item.changes,
|
|
5062
|
+
...(blockedFields ?? {
|
|
5063
|
+
isError: item.status === 'failed',
|
|
5064
|
+
executed: true,
|
|
5065
|
+
executionState: item.status === 'failed' ? 'failed' : 'completed',
|
|
5066
|
+
...codexItemErrorFields(item),
|
|
5067
|
+
}),
|
|
5068
|
+
callId: item.id,
|
|
5069
|
+
};
|
|
4740
5070
|
}
|
|
4741
5071
|
break;
|
|
4742
5072
|
case 'webSearch':
|
|
@@ -4941,7 +5271,7 @@ export class CodexRunner {
|
|
|
4941
5271
|
const managedTempDir = ensureSessionRuntimeDir(sessionId);
|
|
4942
5272
|
if (!managedTempDir)
|
|
4943
5273
|
return undefined;
|
|
4944
|
-
return `临时文件必须使用环境变量 \`$TMPDIR\`。当前值是 \`${managedTempDir}\`,它是当前 EvolCore 会话唯一允许的临时工作区;不要直接使用 \`/tmp\`,也不要使用其他会话的临时目录。该目录随 EvolCore sessionId 保持稳定;若文件不存在,请先在当前 \`$TMPDIR\` 重新创建,再继续执行后续 \`ec\`
|
|
5274
|
+
return `临时文件必须使用环境变量 \`$TMPDIR\`。当前值是 \`${managedTempDir}\`,它是当前 EvolCore 会话唯一允许的临时工作区;不要直接使用 \`/tmp\`,也不要使用其他会话的临时目录。该目录随 EvolCore sessionId 保持稳定;若文件不存在,请先在当前 \`$TMPDIR\` 重新创建,再继续执行后续 \`ec\` 命令。文件修改优先使用 Codex 原生 FileChange/Edit;若当前 Codex 版本未暴露该工具,可使用独立且可解析的 \`apply_patch\` patch。不得把 patch 与其它命令拼接、放入管道或使用任意重定向;目标文件仍受 workspace、H/L 路径和当前权限模式限制。`;
|
|
4945
5275
|
}
|
|
4946
5276
|
buildEvolcoreShellEnvironmentConfig(sessionId, sessionToken, taskDelegationToken) {
|
|
4947
5277
|
const managedTempDir = ensureSessionRuntimeDir(sessionId);
|
|
@@ -5077,6 +5407,14 @@ export class CodexAgentPlugin {
|
|
|
5077
5407
|
},
|
|
5078
5408
|
},
|
|
5079
5409
|
};
|
|
5080
|
-
return {
|
|
5410
|
+
return {
|
|
5411
|
+
evolagentName: agent.name,
|
|
5412
|
+
baseagent: 'codex',
|
|
5413
|
+
agent: new CodexRunner(merged, callbacks, {
|
|
5414
|
+
agentAid: agent.config.aid,
|
|
5415
|
+
agentConfig: agent.config,
|
|
5416
|
+
codexVersion: availability.version,
|
|
5417
|
+
}),
|
|
5418
|
+
};
|
|
5081
5419
|
}
|
|
5082
5420
|
}
|