evolcore 0.0.32 → 0.0.34
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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
2
2
|
// Codex/OpenAI API 不认识 Claude Code SDK 的窗口后缀,必须传入基础模型 ID。
|
|
3
|
-
import { stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
3
|
+
import { parseModelId, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
4
4
|
import { formatAsyncQuestionAnswer, hasTrustedFullAccessContext, parseAsyncUserQuestions, unsettledInterruptError, withInterruptDeadline } from './runner-types.js';
|
|
5
5
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
6
6
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
7
|
+
import { isShellToolName } from '../core/permission/tool-canonical.js';
|
|
7
8
|
import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
8
9
|
import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, getExistingRelationAttachmentDirs, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
9
10
|
import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
|
|
@@ -11,7 +12,7 @@ import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPat
|
|
|
11
12
|
import { logger } from '../utils/logger.js';
|
|
12
13
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
13
14
|
import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
14
|
-
import { classifyError, ErrorType, isOutputTokenLimitError, isRetryableError } from '../utils/error-utils.js';
|
|
15
|
+
import { classifyError, ErrorType, getToolResultErrorText, isOutputTokenLimitError, isRetryableError } from '../utils/error-utils.js';
|
|
15
16
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
16
17
|
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
17
18
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
@@ -20,6 +21,7 @@ import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-envi
|
|
|
20
21
|
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, containsUnresolvedManagedTmpDirReference, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, canonicalizeEvolcoreCommandArgv, parseCodexToolCommand, parseCodexCarrierCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, parseShellApplyPatchInvocation, } from '../core/permission/ec-command-parser.js';
|
|
21
22
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
22
23
|
import { agentCodexTokenPath, resolvePaths, resolveRoot } from '../paths.js';
|
|
24
|
+
import { findModelSpec } from '../stats/billing.js';
|
|
23
25
|
import { ensureSessionRuntimeDir, getManagedTaskTempDir } from '../cli/task-context.js';
|
|
24
26
|
import { buildTranscriptTurns } from '../core/session/session-transcript.js';
|
|
25
27
|
import { CodexSessionFileAdapter } from '../core/session/adapters/codex-session-file-adapter.js';
|
|
@@ -128,6 +130,24 @@ function codexItemErrorFields(item) {
|
|
|
128
130
|
const errorDetailCode = normalizeToolErrorDetailCode(rawCode);
|
|
129
131
|
return errorDetailCode ? { errorDetailCode } : {};
|
|
130
132
|
}
|
|
133
|
+
function waitForCodexOperation(operation, signal) {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const onAbort = () => {
|
|
136
|
+
signal.removeEventListener('abort', onAbort);
|
|
137
|
+
reject(signal.reason);
|
|
138
|
+
};
|
|
139
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
140
|
+
operation.then(value => {
|
|
141
|
+
signal.removeEventListener('abort', onAbort);
|
|
142
|
+
resolve(value);
|
|
143
|
+
}, error => {
|
|
144
|
+
signal.removeEventListener('abort', onAbort);
|
|
145
|
+
reject(error);
|
|
146
|
+
});
|
|
147
|
+
if (signal.aborted)
|
|
148
|
+
onAbort();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
131
151
|
function isCodexReasoningNotification(method) {
|
|
132
152
|
return method === 'item/reasoning/textDelta'
|
|
133
153
|
|| method === 'item/reasoning/summaryTextDelta'
|
|
@@ -177,10 +197,10 @@ function parseManagedPreToolUseDenial(value) {
|
|
|
177
197
|
reason = 'EvolCore PreToolUse policy denied this tool call';
|
|
178
198
|
const policyCode = /当前汇报命令(?:未通过规范解析|不符合当前会话)/.test(reason)
|
|
179
199
|
? 'proactive_send_invalid'
|
|
180
|
-
:
|
|
181
|
-
? '
|
|
182
|
-
:
|
|
183
|
-
? '
|
|
200
|
+
: /连续违规\s*\d+\s*次,任务已中断/.test(reason)
|
|
201
|
+
? 'proactive_circuit_break'
|
|
202
|
+
: /未包含意图表达,本轮只允许\s+ec\s+(?:group\s+send|msg\s+send)/.test(reason)
|
|
203
|
+
? 'proactive_intent_required'
|
|
184
204
|
: 'managed_pre_tool_use_denied';
|
|
185
205
|
return { policyCode, reason };
|
|
186
206
|
}
|
|
@@ -335,6 +355,11 @@ export async function getCodexAppServerAvailabilityAsync() {
|
|
|
335
355
|
}
|
|
336
356
|
let version;
|
|
337
357
|
try {
|
|
358
|
+
// Each probe is a full Codex CLI launch (~0.3-0.45s on Windows). Run the
|
|
359
|
+
// app-server probe concurrently; its result is only consulted after the
|
|
360
|
+
// version gate passes, so the decision order is unchanged.
|
|
361
|
+
const appServerProbe = probeCli('codex', ['app-server', '--help'], options);
|
|
362
|
+
appServerProbe.catch(() => { });
|
|
338
363
|
version = parseCodexCliVersion(await probeCli('codex', ['--version'], options)) ?? undefined;
|
|
339
364
|
if (!version)
|
|
340
365
|
return { available: false, reason: '无法读取 Codex CLI 版本。' };
|
|
@@ -342,7 +367,7 @@ export async function getCodexAppServerAvailabilityAsync() {
|
|
|
342
367
|
return { available: false, version, requiresUpgrade: true,
|
|
343
368
|
reason: `Codex CLI ${version} 低于最低要求 ${MIN_CODEX_CLI_VERSION}。请升级 Codex CLI:npm install -g @openai/codex@latest` };
|
|
344
369
|
}
|
|
345
|
-
await
|
|
370
|
+
await appServerProbe;
|
|
346
371
|
return { available: true, version };
|
|
347
372
|
}
|
|
348
373
|
catch (error) {
|
|
@@ -359,6 +384,7 @@ export class CodexRunner {
|
|
|
359
384
|
retryInputSemantics = 'at_most_once';
|
|
360
385
|
capabilities;
|
|
361
386
|
model;
|
|
387
|
+
modelContextId;
|
|
362
388
|
effort;
|
|
363
389
|
activeAbortControllers = new Map();
|
|
364
390
|
activeStreams = new Map();
|
|
@@ -378,6 +404,7 @@ export class CodexRunner {
|
|
|
378
404
|
unconfirmedInterrupts = new Map();
|
|
379
405
|
steeringTails = new Map();
|
|
380
406
|
threadOperationLocks = new Map();
|
|
407
|
+
queryOperations = new Map();
|
|
381
408
|
activeOperationReleases = new Map();
|
|
382
409
|
/** Last app-server client that loaded each thread, including unsubscribed threads. */
|
|
383
410
|
threadSubscriptions = new Map();
|
|
@@ -431,7 +458,8 @@ export class CodexRunner {
|
|
|
431
458
|
fileRewind: 'git-head',
|
|
432
459
|
fullaccess: true,
|
|
433
460
|
};
|
|
434
|
-
this.
|
|
461
|
+
this.modelContextId = this.resolvedConfig.model;
|
|
462
|
+
this.model = stripContextWindowSuffix(this.modelContextId);
|
|
435
463
|
if (this.resolvedConfig.effort)
|
|
436
464
|
this.effort = this.resolvedConfig.effort;
|
|
437
465
|
this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
|
|
@@ -1179,7 +1207,11 @@ export class CodexRunner {
|
|
|
1179
1207
|
}
|
|
1180
1208
|
}
|
|
1181
1209
|
// ── ModelSwitcher ──
|
|
1182
|
-
setModel(model) {
|
|
1210
|
+
setModel(model) {
|
|
1211
|
+
this.modelContextId = model;
|
|
1212
|
+
this.model = stripContextWindowSuffix(model);
|
|
1213
|
+
this.resetAppServerClient();
|
|
1214
|
+
}
|
|
1183
1215
|
getModel() { return this.model; }
|
|
1184
1216
|
getModelCatalog() {
|
|
1185
1217
|
return getRunnerCatalog('codex', this.resolvedConfig.agentAid, this.resolvedConfig);
|
|
@@ -1222,6 +1254,16 @@ export class CodexRunner {
|
|
|
1222
1254
|
const digest = createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
|
|
1223
1255
|
return `__evolcore_${label}_${digest}_hclass_v1`;
|
|
1224
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* 系统会话的写入洞:只对模块声明的可写文件逐个开 write 规则,不开目录、不开 glob。
|
|
1259
|
+
* 声明为空(缺批次目录等)时返回空表,沙箱保持 :read-only。
|
|
1260
|
+
*/
|
|
1261
|
+
resolveSystemSessionWriteRules(sessionId) {
|
|
1262
|
+
const writeFiles = this.permissionContexts.get(sessionId)?.systemSessionGrants?.writeFiles ?? [];
|
|
1263
|
+
return Object.fromEntries(writeFiles
|
|
1264
|
+
.filter(file => path.isAbsolute(file))
|
|
1265
|
+
.map(file => [path.resolve(file), 'write']));
|
|
1266
|
+
}
|
|
1225
1267
|
resolveGitMetadataWriteRules(projectPath, mode) {
|
|
1226
1268
|
if (mode === 'readonly')
|
|
1227
1269
|
return {};
|
|
@@ -1298,6 +1340,7 @@ export class CodexRunner {
|
|
|
1298
1340
|
expandLClassReadDeny: process.platform !== 'linux',
|
|
1299
1341
|
}),
|
|
1300
1342
|
...this.resolveGitMetadataWriteRules(projectPath, mode),
|
|
1343
|
+
...this.resolveSystemSessionWriteRules(sessionId),
|
|
1301
1344
|
...(bootstrapAgentMdPath ? {
|
|
1302
1345
|
[path.resolve(bootstrapAgentMdPath)]: mode === 'readonly' ? 'read' : 'write',
|
|
1303
1346
|
} : {}),
|
|
@@ -1330,12 +1373,18 @@ export class CodexRunner {
|
|
|
1330
1373
|
}
|
|
1331
1374
|
buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath, personalMdPath, permissionContext = this.permissionContexts.get(sessionId) ?? {}) {
|
|
1332
1375
|
const decision = resolvePhaseOneExecutionSandbox(mode, permissionContext.role);
|
|
1376
|
+
// 系统会话明确声明不要沙箱:Codex 的 permission profile 以项目目录为中心,
|
|
1377
|
+
// 而系统会话的读写边界($EVOLCORE_HOME 下的批次目录与记忆目录)与 cwd 无关,
|
|
1378
|
+
// 硬套 profile 只会 fail closed。边界改由 systemSessionCheck 逐次调用判定。
|
|
1379
|
+
const sessionSandboxOff = this.permissionContexts.get(sessionId)?.systemSessionSandbox === 'off';
|
|
1380
|
+
const sandboxOff = decision.state === 'off' || sessionSandboxOff;
|
|
1333
1381
|
return {
|
|
1334
1382
|
decision,
|
|
1335
|
-
permissionProfileConfig:
|
|
1336
|
-
?
|
|
1337
|
-
:
|
|
1338
|
-
sandbox:
|
|
1383
|
+
permissionProfileConfig: sandboxOff
|
|
1384
|
+
? undefined
|
|
1385
|
+
: this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath, personalMdPath),
|
|
1386
|
+
sandbox: sandboxOff ? 'danger-full-access' : undefined,
|
|
1387
|
+
sessionSandboxOff,
|
|
1339
1388
|
};
|
|
1340
1389
|
}
|
|
1341
1390
|
isTrustedFullAccessSession(sessionKey) {
|
|
@@ -1465,7 +1514,7 @@ export class CodexRunner {
|
|
|
1465
1514
|
const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
1466
1515
|
? toolInput
|
|
1467
1516
|
: {};
|
|
1468
|
-
const isShellTool = toolName
|
|
1517
|
+
const isShellTool = isShellToolName(toolName); // canonical normalisation via tool-canonical.ts
|
|
1469
1518
|
const recordPreToolUseDenial = async (policyCode, reason) => {
|
|
1470
1519
|
const summary = summarizeToolInputForAudit(toolName, normalizedInput).slice(0, 512);
|
|
1471
1520
|
auditToolPreflightDenial({
|
|
@@ -1533,6 +1582,14 @@ export class CodexRunner {
|
|
|
1533
1582
|
return { ok: true, applicable: true, decision: 'deny', reason };
|
|
1534
1583
|
}
|
|
1535
1584
|
}
|
|
1585
|
+
// 系统会话:模块判定即最终结论,不进入 preflight / 角色 / permissionMode 分支
|
|
1586
|
+
if (context.systemSessionCheck) {
|
|
1587
|
+
const decision = context.systemSessionCheck(isShellTool ? 'Bash' : toolName, normalizedInput);
|
|
1588
|
+
if (decision.allow)
|
|
1589
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
1590
|
+
await recordPreToolUseDenial(decision.code, decision.reason);
|
|
1591
|
+
return { ok: true, applicable: true, decision: 'deny', reason: decision.reason };
|
|
1592
|
+
}
|
|
1536
1593
|
if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
|
|
1537
1594
|
if (this.isTrustedFullAccessSession(sessionKey))
|
|
1538
1595
|
return { ok: true, applicable: true, decision: 'allow' };
|
|
@@ -1623,6 +1680,19 @@ export class CodexRunner {
|
|
|
1623
1680
|
hasActiveStream(key) {
|
|
1624
1681
|
return this.activeStreams.has(key) || this.activeAbortControllers.has(key) || this.activeTurns.has(key);
|
|
1625
1682
|
}
|
|
1683
|
+
hasActiveOperation(sessionId, taskId) {
|
|
1684
|
+
const operations = this.queryOperations.get(sessionId);
|
|
1685
|
+
return !!operations && [...operations].some(operation => !taskId || operation.taskId === taskId);
|
|
1686
|
+
}
|
|
1687
|
+
cancelQueuedTurn(sessionId, identity) {
|
|
1688
|
+
const operation = [...(this.queryOperations.get(sessionId) ?? [])].find(candidate => !candidate.lockAcquired
|
|
1689
|
+
&& candidate.taskId === identity.taskId
|
|
1690
|
+
&& candidate.generation === identity.generation);
|
|
1691
|
+
if (!operation)
|
|
1692
|
+
return false;
|
|
1693
|
+
operation.controller.abort(Object.assign(new Error('Codex queued operation cancelled'), { name: 'AbortError' }));
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1626
1696
|
threadOperationKey(threadId, sessionId) {
|
|
1627
1697
|
// Once a thread id is known it is the shared identity, including when
|
|
1628
1698
|
// multiple EvolCore sessions happen to address the same Codex thread.
|
|
@@ -1659,6 +1729,7 @@ export class CodexRunner {
|
|
|
1659
1729
|
merged.then(() => {
|
|
1660
1730
|
if (primary.tail === merged)
|
|
1661
1731
|
primary.tail = undefined;
|
|
1732
|
+
this.pruneThreadOperationLocks();
|
|
1662
1733
|
});
|
|
1663
1734
|
}
|
|
1664
1735
|
for (const alias of other.aliases) {
|
|
@@ -1694,7 +1765,8 @@ export class CodexRunner {
|
|
|
1694
1765
|
lock.aliases.delete(key);
|
|
1695
1766
|
}
|
|
1696
1767
|
}
|
|
1697
|
-
async acquireThreadOperation(key, operation) {
|
|
1768
|
+
async acquireThreadOperation(key, operation, signal) {
|
|
1769
|
+
signal?.throwIfAborted();
|
|
1698
1770
|
const lock = this.getThreadOperationLock(key);
|
|
1699
1771
|
const previous = lock.tail;
|
|
1700
1772
|
let releaseCurrent;
|
|
@@ -1702,12 +1774,8 @@ export class CodexRunner {
|
|
|
1702
1774
|
releaseCurrent = resolve;
|
|
1703
1775
|
});
|
|
1704
1776
|
lock.tail = current;
|
|
1705
|
-
if (previous) {
|
|
1706
|
-
logger.info(`[CodexRunner] queued ${operation} behind an active operation: ${key}`);
|
|
1707
|
-
await previous;
|
|
1708
|
-
}
|
|
1709
1777
|
let released = false;
|
|
1710
|
-
|
|
1778
|
+
const release = () => {
|
|
1711
1779
|
if (released)
|
|
1712
1780
|
return;
|
|
1713
1781
|
released = true;
|
|
@@ -1716,6 +1784,21 @@ export class CodexRunner {
|
|
|
1716
1784
|
releaseCurrent();
|
|
1717
1785
|
this.pruneThreadOperationLocks();
|
|
1718
1786
|
};
|
|
1787
|
+
try {
|
|
1788
|
+
if (previous) {
|
|
1789
|
+
logger.info(`[CodexRunner] queued ${operation} behind an active operation: ${key}`);
|
|
1790
|
+
await (signal ? waitForCodexOperation(previous, signal) : previous);
|
|
1791
|
+
}
|
|
1792
|
+
signal?.throwIfAborted();
|
|
1793
|
+
return release;
|
|
1794
|
+
}
|
|
1795
|
+
catch (error) {
|
|
1796
|
+
if (signal?.aborted && previous)
|
|
1797
|
+
void previous.then(release);
|
|
1798
|
+
else
|
|
1799
|
+
release();
|
|
1800
|
+
throw error;
|
|
1801
|
+
}
|
|
1719
1802
|
}
|
|
1720
1803
|
async *holdThreadOperation(stream, sessionId, release) {
|
|
1721
1804
|
try {
|
|
@@ -1747,11 +1830,29 @@ export class CodexRunner {
|
|
|
1747
1830
|
? this.cachedThreadForTopic(sessionId, topicAgentSessionId)
|
|
1748
1831
|
: initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
1749
1832
|
const operationKey = this.threadOperationKey(modelOverride?.recovery?.agentSessionId || knownThreadId, sessionId);
|
|
1750
|
-
|
|
1751
|
-
|
|
1833
|
+
const operation = {
|
|
1834
|
+
controller: new AbortController(),
|
|
1835
|
+
taskId: modelOverride?.turn?.taskId ?? this.permissionContexts.get(sessionId)?.taskId,
|
|
1836
|
+
generation: modelOverride?.turn?.generation,
|
|
1837
|
+
lockAcquired: false,
|
|
1838
|
+
};
|
|
1839
|
+
const operations = this.queryOperations.get(sessionId) ?? new Set();
|
|
1840
|
+
operations.add(operation);
|
|
1841
|
+
this.queryOperations.set(sessionId, operations);
|
|
1842
|
+
let releaseLock;
|
|
1843
|
+
const release = () => {
|
|
1844
|
+
operations.delete(operation);
|
|
1845
|
+
if (this.queryOperations.get(sessionId) === operations && operations.size === 0) {
|
|
1846
|
+
this.queryOperations.delete(sessionId);
|
|
1847
|
+
}
|
|
1848
|
+
releaseLock?.();
|
|
1849
|
+
};
|
|
1752
1850
|
try {
|
|
1753
|
-
await this.assertInterruptSettled(sessionId, modelOverride?.recovery?.agentSessionId || knownThreadId);
|
|
1754
|
-
|
|
1851
|
+
await waitForCodexOperation(this.assertInterruptSettled(sessionId, modelOverride?.recovery?.agentSessionId || knownThreadId), operation.controller.signal);
|
|
1852
|
+
releaseLock = await this.acquireThreadOperation(operationKey, 'thread/resume+turn/start', operation.controller.signal);
|
|
1853
|
+
operation.lockAcquired = true;
|
|
1854
|
+
await waitForCodexOperation(this.assertInterruptSettled(sessionId, modelOverride?.recovery?.agentSessionId || knownThreadId), operation.controller.signal);
|
|
1855
|
+
const stream = await this.runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv, operation.controller);
|
|
1755
1856
|
this.activeOperationReleases.set(sessionId, release);
|
|
1756
1857
|
return this.holdThreadOperation(stream, sessionId, release);
|
|
1757
1858
|
}
|
|
@@ -1760,7 +1861,7 @@ export class CodexRunner {
|
|
|
1760
1861
|
throw error;
|
|
1761
1862
|
}
|
|
1762
1863
|
}
|
|
1763
|
-
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
1864
|
+
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv, operationController) {
|
|
1764
1865
|
// A daemon-managed task must never fall back to the app-server's
|
|
1765
1866
|
// process-wide TMPDIR if its session namespace disappeared.
|
|
1766
1867
|
getManagedTaskTempDir(runtimeEnv);
|
|
@@ -1789,7 +1890,8 @@ export class CodexRunner {
|
|
|
1789
1890
|
}
|
|
1790
1891
|
await this.assertInterruptSettled(sessionId, agentSessionId);
|
|
1791
1892
|
let resumingThread = !!agentSessionId;
|
|
1792
|
-
const
|
|
1893
|
+
const modelContextId = modelOverride?.model || this.modelContextId;
|
|
1894
|
+
const callModel = stripContextWindowSuffix(modelContextId);
|
|
1793
1895
|
const callEffort = modelOverride?.effortMode === 'model_default'
|
|
1794
1896
|
? undefined
|
|
1795
1897
|
: modelOverride?.effort ?? this.effort;
|
|
@@ -1901,6 +2003,7 @@ export class CodexRunner {
|
|
|
1901
2003
|
if (!resumingThread && this.ensureThreadCredential(threadId, threadSessionToken) !== threadSessionToken) {
|
|
1902
2004
|
throw new Error('Codex thread session credential could not be bound');
|
|
1903
2005
|
}
|
|
2006
|
+
this.permissionContexts.get(sessionId)?.bindSessionCredential?.(threadSessionToken, threadId);
|
|
1904
2007
|
const turn = modelOverride?.turn;
|
|
1905
2008
|
if (!resumingThread) {
|
|
1906
2009
|
// Keep a newly-created backend private to this runner until turn/start
|
|
@@ -1930,7 +2033,7 @@ export class CodexRunner {
|
|
|
1930
2033
|
}
|
|
1931
2034
|
this.threadProjectPaths.set(threadId, path.resolve(projectPath));
|
|
1932
2035
|
this.bindExternalToolBoundary(threadId, externalToolConfig);
|
|
1933
|
-
const controller = new AbortController();
|
|
2036
|
+
const controller = operationController ?? new AbortController();
|
|
1934
2037
|
this.activeAbortControllers.set(sessionId, controller);
|
|
1935
2038
|
const answer = modelOverride?.asyncQuestionAnswer;
|
|
1936
2039
|
if (answer && answer.providerThreadId !== threadId) {
|
|
@@ -1944,6 +2047,7 @@ export class CodexRunner {
|
|
|
1944
2047
|
sessionId,
|
|
1945
2048
|
projectPath,
|
|
1946
2049
|
model: callModel,
|
|
2050
|
+
modelContextId,
|
|
1947
2051
|
committed: resumingThread,
|
|
1948
2052
|
persistenceFinalized: resumingThread,
|
|
1949
2053
|
streamedAgentMessageIds: new Set(),
|
|
@@ -1957,6 +2061,11 @@ export class CodexRunner {
|
|
|
1957
2061
|
inputId: turn?.inputId,
|
|
1958
2062
|
inputAcceptedEmitted: false,
|
|
1959
2063
|
};
|
|
2064
|
+
let startupInterrupt;
|
|
2065
|
+
const interruptStartedTurn = (turnId) => {
|
|
2066
|
+
startupInterrupt ??= this.interruptAppServerTurn(threadId, turnId);
|
|
2067
|
+
return startupInterrupt;
|
|
2068
|
+
};
|
|
1960
2069
|
const unsubscribe = appServer.onNotification(notification => {
|
|
1961
2070
|
// 仅从 turn/started 锁定权威 turnId — resume 时会有上一轮 turn 的残留通知
|
|
1962
2071
|
// (如 thread/tokenUsage/updated)先于新 turn 到达,不能用它们 latch turnId
|
|
@@ -2000,8 +2109,8 @@ export class CodexRunner {
|
|
|
2000
2109
|
if (startedTurnId && !state.turnId) {
|
|
2001
2110
|
state.turnId = startedTurnId;
|
|
2002
2111
|
this.activeTurns.set(sessionId, { threadId, turnId: startedTurnId });
|
|
2003
|
-
if (this.consumePendingInterrupt(sessionId)) {
|
|
2004
|
-
|
|
2112
|
+
if (controller.signal.aborted || this.consumePendingInterrupt(sessionId)) {
|
|
2113
|
+
void interruptStartedTurn(startedTurnId);
|
|
2005
2114
|
this.activeTurns.delete(sessionId);
|
|
2006
2115
|
}
|
|
2007
2116
|
}
|
|
@@ -2018,8 +2127,9 @@ export class CodexRunner {
|
|
|
2018
2127
|
this.endAppServerQueueWhenSubagentsDone(queue, state);
|
|
2019
2128
|
}
|
|
2020
2129
|
});
|
|
2021
|
-
if (this.consumePendingInterrupt(sessionId)) {
|
|
2130
|
+
if (controller.signal.aborted || this.consumePendingInterrupt(sessionId)) {
|
|
2022
2131
|
controller.abort('User interrupt');
|
|
2132
|
+
queue.end();
|
|
2023
2133
|
this.activeAbortControllers.delete(sessionId);
|
|
2024
2134
|
this.activeStreams.delete(sessionId);
|
|
2025
2135
|
if (!resumingThread)
|
|
@@ -2086,8 +2196,10 @@ export class CodexRunner {
|
|
|
2086
2196
|
// the short settle window below.
|
|
2087
2197
|
await state.confirmProvisional?.(0);
|
|
2088
2198
|
}
|
|
2089
|
-
if (turnId && this.consumePendingInterrupt(sessionId)) {
|
|
2090
|
-
|
|
2199
|
+
if (turnId && (controller.signal.aborted || this.consumePendingInterrupt(sessionId))) {
|
|
2200
|
+
if (!state.turnCompleted && !['completed', 'failed', 'interrupted'].includes(turnResponse.turn?.status)) {
|
|
2201
|
+
await interruptStartedTurn(turnId);
|
|
2202
|
+
}
|
|
2091
2203
|
controller.abort('User interrupt');
|
|
2092
2204
|
this.activeAbortControllers.delete(sessionId);
|
|
2093
2205
|
this.activeStreams.delete(sessionId);
|
|
@@ -2116,14 +2228,21 @@ export class CodexRunner {
|
|
|
2116
2228
|
// ── Interrupt ──
|
|
2117
2229
|
async interrupt(sessionKey) {
|
|
2118
2230
|
this.permissionContexts.get(sessionKey)?.pauseController?.cancel();
|
|
2231
|
+
const operations = this.queryOperations.get(sessionKey);
|
|
2232
|
+
for (const operation of operations ?? []) {
|
|
2233
|
+
if (operation.lockAcquired) {
|
|
2234
|
+
operation.controller.abort(Object.assign(new Error('Codex operation interrupted'), { name: 'AbortError' }));
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2119
2237
|
const controller = this.activeAbortControllers.get(sessionKey);
|
|
2120
2238
|
const activeTurn = this.activeTurns.get(sessionKey);
|
|
2121
|
-
const hadActiveState = !!controller || !!activeTurn || this.activeStreams.has(sessionKey)
|
|
2239
|
+
const hadActiveState = !!controller || !!activeTurn || this.activeStreams.has(sessionKey)
|
|
2240
|
+
|| [...(operations ?? [])].some(operation => operation.lockAcquired);
|
|
2122
2241
|
const interruptTurn = activeTurn
|
|
2123
2242
|
? this.interruptAppServerTurn(activeTurn.threadId, activeTurn.turnId)
|
|
2124
2243
|
: Promise.resolve();
|
|
2125
2244
|
const interruptSubagents = this.interruptSubagentTurns(sessionKey);
|
|
2126
|
-
if (!activeTurn)
|
|
2245
|
+
if (!activeTurn && !operations?.size)
|
|
2127
2246
|
this.rememberPendingInterrupt(sessionKey);
|
|
2128
2247
|
if (controller)
|
|
2129
2248
|
controller.abort('User interrupt');
|
|
@@ -2206,12 +2325,21 @@ export class CodexRunner {
|
|
|
2206
2325
|
const pending = [...this.unconfirmedInterrupts.entries()].filter(([, record]) => record.sessionId === sessionId || (threadId && record.threadId === threadId));
|
|
2207
2326
|
await Promise.all(pending.map(async ([key, record]) => {
|
|
2208
2327
|
try {
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2328
|
+
const response = await withInterruptDeadline(record.client.threadRead(record.threadId, false, 2_000), 'Codex interrupt verification', 2_000);
|
|
2329
|
+
const threadStatus = response.thread?.status?.type;
|
|
2330
|
+
if (threadStatus === 'idle' || threadStatus === 'notLoaded') {
|
|
2331
|
+
if (this.unconfirmedInterrupts.get(key) === record) {
|
|
2332
|
+
this.unconfirmedInterrupts.delete(key);
|
|
2333
|
+
record.unsubscribe?.();
|
|
2334
|
+
}
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
if (threadStatus !== undefined)
|
|
2338
|
+
return;
|
|
2339
|
+
const history = await withInterruptDeadline(record.client.threadRead(record.threadId, true, 2_000), 'Codex interrupt history verification', 2_000);
|
|
2340
|
+
const turn = history.thread?.turns?.find(turn => turn.id === record.turnId);
|
|
2213
2341
|
if (turn && ['completed', 'failed', 'interrupted'].includes(String(turn.status))
|
|
2214
|
-
&& !
|
|
2342
|
+
&& !history.thread?.turns?.some(turn => turn.status === 'inProgress')
|
|
2215
2343
|
&& this.unconfirmedInterrupts.get(key) === record) {
|
|
2216
2344
|
this.unconfirmedInterrupts.delete(key);
|
|
2217
2345
|
record.unsubscribe?.();
|
|
@@ -3219,7 +3347,7 @@ export class CodexRunner {
|
|
|
3219
3347
|
return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
|
|
3220
3348
|
? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
|
|
3221
3349
|
+ (canonicalEcApproval.parseFailure.dialect === 'powershell' && canonicalEcApproval.parseFailure.issue === 'invalid-quote'
|
|
3222
|
-
? '. PowerShell treats
|
|
3350
|
+
? '. PowerShell treats U+2018/U+2019/U+201A/U+201B as quote delimiters too. An EC inline text argument must be ASCII single-quoted; replace any character inside it that looks like a quote with U+02BC or U+FF07, or write the text to the session TMPDIR and pass it with --text-from-file.'
|
|
3223
3351
|
: '')
|
|
3224
3352
|
: 'EC command approval denied because no canonical argv could be derived', canonicalEcApproval?.parseFailure?.issue === 'invalid-quote' ? 'INVALID_QUOTE' : undefined);
|
|
3225
3353
|
}
|
|
@@ -3542,15 +3670,18 @@ export class CodexRunner {
|
|
|
3542
3670
|
...(trackedItem.appContext && typeof trackedItem.appContext === 'object' ? { appContext: trackedItem.appContext } : {}),
|
|
3543
3671
|
};
|
|
3544
3672
|
const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
|
|
3673
|
+
const systemSessionCheck = this.permissionContexts.get(sessionKey)?.systemSessionCheck;
|
|
3545
3674
|
const workspacePath = this.resolvePermissionWorkspacePath(params);
|
|
3546
3675
|
const mode = normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode);
|
|
3547
3676
|
// MCP servers are inherited unless capability config explicitly disables
|
|
3548
3677
|
// them. Apps retain their separate request/bypass lifecycle policy.
|
|
3549
|
-
let allow =
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3678
|
+
let allow = systemSessionCheck
|
|
3679
|
+
? systemSessionCheck(toolName, toolInput).allow
|
|
3680
|
+
: !policyResult?.block
|
|
3681
|
+
&& !!workspacePath
|
|
3682
|
+
&& (isAppCall
|
|
3683
|
+
? mode === 'request' || mode === 'bypass'
|
|
3684
|
+
: mode !== 'readonly');
|
|
3554
3685
|
if (allow) {
|
|
3555
3686
|
try {
|
|
3556
3687
|
for (const question of questions)
|
|
@@ -3838,6 +3969,7 @@ export class CodexRunner {
|
|
|
3838
3969
|
// readonly decision. The map entry alone is stale if the directory was
|
|
3839
3970
|
// replaced between preflight and the approval callback.
|
|
3840
3971
|
managedTempDir: permCtx?.managedTempDir ?? this.getManagedTempDir(sessionKey),
|
|
3972
|
+
grantedReadRoots: permCtx?.grantedReadRoots,
|
|
3841
3973
|
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
3842
3974
|
} : undefined;
|
|
3843
3975
|
if (toolName === 'Bash')
|
|
@@ -3934,6 +4066,13 @@ export class CodexRunner {
|
|
|
3934
4066
|
}
|
|
3935
4067
|
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath, requestId, callId) {
|
|
3936
4068
|
const permissionContext = this.permissionContexts.get(sessionKey);
|
|
4069
|
+
// 系统会话:审批路径同样只看模块判定(FileChange / PermissionGrant 一律被模块拒绝)
|
|
4070
|
+
if (permissionContext?.systemSessionCheck) {
|
|
4071
|
+
const decision = permissionContext.systemSessionCheck(toolName, toolInput);
|
|
4072
|
+
return decision.allow
|
|
4073
|
+
? 'allow'
|
|
4074
|
+
: { decision: 'deny', decisionSource: 'policy', policyCode: decision.code, reason: decision.reason };
|
|
4075
|
+
}
|
|
3937
4076
|
const permissionPrompt = permissionContext?.sendPrompt ?? this.sendPromptFn;
|
|
3938
4077
|
const hasFilesystemPermissions = (toolName === 'Bash' || toolName === 'PermissionGrant')
|
|
3939
4078
|
&& this.hasFilesystemPermissionRequest(toolInput);
|
|
@@ -4336,15 +4475,37 @@ export class CodexRunner {
|
|
|
4336
4475
|
let input;
|
|
4337
4476
|
let result;
|
|
4338
4477
|
let isError = false;
|
|
4478
|
+
let executionFields = {};
|
|
4339
4479
|
switch (type) {
|
|
4340
|
-
case 'commandExecution':
|
|
4480
|
+
case 'commandExecution': {
|
|
4341
4481
|
name = 'Shell';
|
|
4342
4482
|
input = { command: item.command, cwd: item.cwd };
|
|
4343
4483
|
result = item.aggregatedOutput ?? item.output ?? '';
|
|
4344
4484
|
isError = item.exitCode != null
|
|
4345
4485
|
? Number(item.exitCode) !== 0
|
|
4346
4486
|
: item.status === 'failed' || item.status === 'declined';
|
|
4487
|
+
const exitCode = typeof item.exitCode === 'number' && Number.isInteger(item.exitCode)
|
|
4488
|
+
? item.exitCode
|
|
4489
|
+
: undefined;
|
|
4490
|
+
const itemError = item.error;
|
|
4491
|
+
const errorMessage = typeof itemError === 'string'
|
|
4492
|
+
? itemError
|
|
4493
|
+
: itemError && typeof itemError === 'object' && 'message' in itemError
|
|
4494
|
+
&& typeof itemError.message === 'string' ? itemError.message : undefined;
|
|
4495
|
+
executionFields = {
|
|
4496
|
+
...(exitCode !== undefined ? { exit_code: exitCode } : {}),
|
|
4497
|
+
...(item.aggregatedOutput != null ? { output_kind: 'combined' } : {}),
|
|
4498
|
+
...(typeof item.durationMs === 'number' ? { duration_ms: item.durationMs } : {}),
|
|
4499
|
+
...(isError ? {
|
|
4500
|
+
error: getToolResultErrorText({
|
|
4501
|
+
name, result, exitCode,
|
|
4502
|
+
error: errorMessage,
|
|
4503
|
+
reason: item.status === 'declined' ? 'Approval declined' : undefined,
|
|
4504
|
+
}),
|
|
4505
|
+
} : {}),
|
|
4506
|
+
};
|
|
4347
4507
|
break;
|
|
4508
|
+
}
|
|
4348
4509
|
case 'mcpToolCall':
|
|
4349
4510
|
name = `MCP:${item.server}/${item.tool}`;
|
|
4350
4511
|
input = item.arguments;
|
|
@@ -4405,6 +4566,7 @@ export class CodexRunner {
|
|
|
4405
4566
|
...(id ? { tool_use_id: id } : {}),
|
|
4406
4567
|
content: result,
|
|
4407
4568
|
is_error: isError,
|
|
4569
|
+
...executionFields,
|
|
4408
4570
|
},
|
|
4409
4571
|
];
|
|
4410
4572
|
}
|
|
@@ -4965,17 +5127,12 @@ export class CodexRunner {
|
|
|
4965
5127
|
}
|
|
4966
5128
|
: undefined;
|
|
4967
5129
|
const itemErrorText = outputTextForManagedHook(typeof item.error === 'string' ? item.error : item.error?.message);
|
|
4968
|
-
const
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
const managedHookDenial = (item.status === 'failed' || item.status === 'declined' || hookErrorText)
|
|
4972
|
-
? parseManagedPreToolUseDenial([
|
|
4973
|
-
item.aggregatedOutput,
|
|
4974
|
-
item.output,
|
|
4975
|
-
item.result,
|
|
4976
|
-
hookErrorText,
|
|
4977
|
-
].filter(value => value !== undefined && value !== null).map(outputTextForManagedHook).join('\n'))
|
|
5130
|
+
const hookOutput = item.type === 'commandExecution' && item.exitCode == null && !itemErrorText
|
|
5131
|
+
&& (item.status === 'failed' || item.status === 'declined')
|
|
5132
|
+
? [item.aggregatedOutput, item.output, item.result].map(outputTextForManagedHook).find(text => /^\s*(?:Script failed\s+)?(?:Script error:\s*)?(?:Command|Tool call) blocked by PreToolUse hook\s*:/i.test(text))
|
|
4978
5133
|
: undefined;
|
|
5134
|
+
const managedHookDenial = parseManagedPreToolUseDenial(itemErrorText)
|
|
5135
|
+
?? parseManagedPreToolUseDenial(hookOutput);
|
|
4979
5136
|
const managedHookFields = !denial && managedHookDenial
|
|
4980
5137
|
? {
|
|
4981
5138
|
isError: true,
|
|
@@ -5056,9 +5213,11 @@ export class CodexRunner {
|
|
|
5056
5213
|
type: 'tool_result',
|
|
5057
5214
|
name: 'Shell',
|
|
5058
5215
|
result: item.aggregatedOutput ?? '',
|
|
5216
|
+
...(item.aggregatedOutput != null ? { outputKind: 'combined' } : {}),
|
|
5059
5217
|
...(blockedFields ?? {
|
|
5060
5218
|
isError,
|
|
5061
5219
|
...codexItemErrorFields(item),
|
|
5220
|
+
...(itemErrorText ? { error: itemErrorText } : {}),
|
|
5062
5221
|
executed: true,
|
|
5063
5222
|
executionState: isError ? 'failed' : 'completed',
|
|
5064
5223
|
...(exitCode !== undefined ? { exitCode } : {}),
|
|
@@ -5294,15 +5453,29 @@ export class CodexRunner {
|
|
|
5294
5453
|
};
|
|
5295
5454
|
return Object.values(usage).some(value => value !== undefined) ? usage : undefined;
|
|
5296
5455
|
}
|
|
5297
|
-
mapCodexContextUsage(raw) {
|
|
5456
|
+
mapCodexContextUsage(raw, fallbackModel, runtimeWindow) {
|
|
5298
5457
|
if (!raw || typeof raw !== 'object')
|
|
5299
5458
|
return undefined;
|
|
5300
|
-
const totalTokens = this.pickNumber(raw.totalTokens, raw.total_tokens, raw.total);
|
|
5301
|
-
const
|
|
5302
|
-
const
|
|
5303
|
-
|
|
5459
|
+
const totalTokens = this.pickNumber(raw.totalTokens, raw.total_tokens, raw.total, raw.last?.totalTokens, raw.last?.total_tokens);
|
|
5460
|
+
const rawModel = typeof raw.model === 'string' && raw.model.trim() ? raw.model : undefined;
|
|
5461
|
+
const fallbackWindow = fallbackModel ? parseModelId(fallbackModel).contextWindow : undefined;
|
|
5462
|
+
const model = fallbackWindow !== undefined ? fallbackModel : rawModel ?? fallbackModel;
|
|
5463
|
+
if (totalTokens === undefined || totalTokens < 0 || !model)
|
|
5464
|
+
return undefined;
|
|
5465
|
+
const reportedMaxTokens = [runtimeWindow, raw.modelContextWindow, raw.model_context_window, raw.maxTokens, raw.max_tokens, raw.max]
|
|
5466
|
+
.find(value => typeof value === 'number' && Number.isFinite(value) && value > 0);
|
|
5467
|
+
const suffixWindow = parseModelId(model).contextWindow;
|
|
5468
|
+
const maxTokens = reportedMaxTokens ?? suffixWindow ?? findModelSpec(resolveRoot(), model)?.context_window;
|
|
5469
|
+
if (maxTokens === undefined || !Number.isFinite(maxTokens) || maxTokens <= 0)
|
|
5304
5470
|
return undefined;
|
|
5305
|
-
const
|
|
5471
|
+
const nativeWindow = [runtimeWindow, raw.modelContextWindow, raw.model_context_window]
|
|
5472
|
+
.find(value => typeof value === 'number' && Number.isFinite(value) && value > 0);
|
|
5473
|
+
const legacyWindow = [raw.maxTokens, raw.max_tokens, raw.max]
|
|
5474
|
+
.find(value => typeof value === 'number' && Number.isFinite(value) && value > 0);
|
|
5475
|
+
const percentage = (nativeWindow === undefined && (legacyWindow !== undefined || suffixWindow !== undefined)
|
|
5476
|
+
? this.pickNumber(raw.percentage)
|
|
5477
|
+
: undefined)
|
|
5478
|
+
?? Math.round((totalTokens / maxTokens) * 100);
|
|
5306
5479
|
const effort = typeof raw.effort === 'string' ? raw.effort : undefined;
|
|
5307
5480
|
return { totalTokens, maxTokens, percentage, model, effort };
|
|
5308
5481
|
}
|
|
@@ -5310,7 +5483,12 @@ export class CodexRunner {
|
|
|
5310
5483
|
const status = turn.status || 'completed';
|
|
5311
5484
|
const outputLimit = status === 'failed' && isOutputTokenLimitError(new Error(turn.error?.message ?? ''));
|
|
5312
5485
|
const tokenUsage = this.mapCodexTokenUsage(state.tokenUsage?.last ?? turn.tokenUsage ?? turn.usage);
|
|
5313
|
-
const
|
|
5486
|
+
const terminalTokenUsage = turn.tokenUsage ?? turn.usage;
|
|
5487
|
+
const contextUsage = this.mapCodexContextUsage({
|
|
5488
|
+
...state.tokenUsage,
|
|
5489
|
+
...(terminalTokenUsage && typeof terminalTokenUsage === 'object' ? terminalTokenUsage : {}),
|
|
5490
|
+
...(turn.contextUsage ?? state.tokenUsage?.contextUsage ?? state.tokenUsage?.context),
|
|
5491
|
+
}, state.modelContextId ?? state.model, this.pickNumber(state.tokenUsage?.modelContextWindow, state.tokenUsage?.model_context_window, terminalTokenUsage?.modelContextWindow, terminalTokenUsage?.model_context_window));
|
|
5314
5492
|
const terminalReason = status === 'completed'
|
|
5315
5493
|
? undefined
|
|
5316
5494
|
: status === 'interrupted'
|