@openclaw/codex 2026.5.16-beta.1 → 2026.5.16-beta.3

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.
Files changed (23) hide show
  1. package/dist/{client-CoctX13d.js → client-CA7amCZ8.js} +1 -1
  2. package/dist/{client-factory-Be6RD28K.js → client-factory-DwzPofwS.js} +1 -1
  3. package/dist/{command-handlers-CbJPJwK_.js → command-handlers-C4F8BChC.js} +6 -6
  4. package/dist/{compact-CKWfgifa.js → compact-96QPqIAu.js} +97 -3
  5. package/dist/{computer-use-DsJxRRtm.js → computer-use-BX02ojP4.js} +2 -2
  6. package/dist/{config-0rd3LnKg.js → config-3ATInASk.js} +7 -1
  7. package/dist/harness.js +9 -6
  8. package/dist/index.js +6 -6
  9. package/dist/media-understanding-provider.js +4 -4
  10. package/dist/{models-GCYf5s8J.js → models-BZLMz9WS.js} +2 -2
  11. package/dist/{node-cli-sessions-xstpFJmx.js → node-cli-sessions-BLDFnUEF.js} +4 -4
  12. package/dist/{plugin-activation-aQOmRQwA.js → plugin-activation-BDU4e-NA.js} +1 -1
  13. package/dist/{protocol-validators-CSY0BFBo.js → protocol-validators-BGBspNmF.js} +12 -40
  14. package/dist/provider.js +2 -2
  15. package/dist/{request-CKYiRqsN.js → request-Czoa60Nt.js} +3 -3
  16. package/dist/{run-attempt-frGYsF9Y.js → run-attempt-B3nk5oT1.js} +501 -89
  17. package/dist/{session-binding-CMTXuyoz.js → session-binding-DbdVqMzW.js} +14 -2
  18. package/dist/{shared-client-BwUqd3lh.js → shared-client-CnbrvEfV.js} +2 -2
  19. package/dist/{side-question-CDcvaIKA.js → side-question-D3nH-Jk1.js} +7 -7
  20. package/dist/test-api.js +2 -2
  21. package/dist/{thread-lifecycle-BzY-GLGu.js → thread-lifecycle-D2sxRPdz.js} +157 -36
  22. package/dist/{vision-tools-CyhFwJIY.js → vision-tools-Cm_YicZb.js} +106 -6
  23. package/package.json +4 -4
@@ -1,28 +1,48 @@
1
- import { l as resolveCodexPluginsPolicy, o as readCodexPluginConfig, s as resolveCodexAppServerRuntimeOptions, u as withMcpElicitationsApprovalPolicy } from "./config-0rd3LnKg.js";
2
- import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-CSY0BFBo.js";
1
+ import { c as resolveCodexAppServerRuntimeOptions, d as withMcpElicitationsApprovalPolicy, i as isCodexAppServerApprovalPolicyAllowedByRequirements, s as readCodexPluginConfig, u as resolveCodexPluginsPolicy } from "./config-3ATInASk.js";
2
+ import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-BGBspNmF.js";
3
3
  import { t as isJsonObject } from "./protocol-C9UWI98H.js";
4
- import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CoctX13d.js";
5
- import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-CKYiRqsN.js";
4
+ import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CA7amCZ8.js";
5
+ import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-Czoa60Nt.js";
6
6
  import { d as resolveCodexUsageLimitResetAtMs, f as shouldRefreshCodexRateLimitsForUsageLimitMessage, r as formatCodexDisplayText, u as formatCodexUsageLimitErrorMessage } from "./command-formatters-BRW7_Nu7.js";
7
- import { c as resolveCodexAppServerAuthAccountCacheKey, d as resolveCodexAppServerEnvApiKeyCacheKey, l as resolveCodexAppServerAuthProfileId, n as clearSharedCodexAppServerClientIfCurrent, s as refreshCodexAppServerAuthTokens, u as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-BwUqd3lh.js";
8
- import { i as readCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-CMTXuyoz.js";
9
- import { a as defaultCodexAppInventoryCache } from "./plugin-activation-aQOmRQwA.js";
10
- import { _ as resolveCodexContextEngineProjectionReserveTokens, b as normalizeCodexDynamicToolName, d as buildCodexPluginThreadConfig, f as buildCodexPluginThreadConfigInputFingerprint, g as resolveCodexContextEngineProjectionMaxChars, h as projectContextEngineAssemblyForCodex, m as shouldBuildCodexPluginThreadConfig, o as buildTurnStartParams, p as mergeCodexThreadConfigs, r as buildDeveloperInstructions, s as codexDynamicToolsFingerprint, t as areCodexDynamicToolFingerprintsCompatible, u as startOrResumeThread, v as createCodexDynamicToolBridge, y as filterCodexDynamicTools } from "./thread-lifecycle-BzY-GLGu.js";
11
- import { t as defaultCodexAppServerClientFactory } from "./client-factory-Be6RD28K.js";
12
- import { n as handleCodexAppServerElicitationRequest, r as handleCodexAppServerApprovalRequest, t as filterToolsForVisionInputs } from "./vision-tools-CyhFwJIY.js";
13
- import { t as ensureCodexComputerUse } from "./computer-use-DsJxRRtm.js";
7
+ import { c as resolveCodexAppServerAuthAccountCacheKey, d as resolveCodexAppServerEnvApiKeyCacheKey, l as resolveCodexAppServerAuthProfileId, n as clearSharedCodexAppServerClientIfCurrent, s as refreshCodexAppServerAuthTokens, u as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-CnbrvEfV.js";
8
+ import { i as readCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-DbdVqMzW.js";
9
+ import { a as defaultCodexAppInventoryCache } from "./plugin-activation-BDU4e-NA.js";
10
+ import { S as normalizeCodexDynamicToolName, _ as projectContextEngineAssemblyForCodex, b as createCodexDynamicToolBridge, c as codexDynamicToolsFingerprint, f as startOrResumeThread, g as shouldBuildCodexPluginThreadConfig, h as mergeCodexThreadConfigs, i as buildDeveloperInstructions, l as isContextEngineBindingCompatible, m as buildCodexPluginThreadConfigInputFingerprint, p as buildCodexPluginThreadConfig, r as buildContextEngineBinding, s as buildTurnStartParams, t as areCodexDynamicToolFingerprintsCompatible, v as resolveCodexContextEngineProjectionMaxChars, x as filterCodexDynamicTools, y as resolveCodexContextEngineProjectionReserveTokens } from "./thread-lifecycle-D2sxRPdz.js";
11
+ import { t as defaultCodexAppServerClientFactory } from "./client-factory-DwzPofwS.js";
12
+ import { n as handleCodexAppServerElicitationRequest, r as handleCodexAppServerApprovalRequest, t as filterToolsForVisionInputs } from "./vision-tools-Cm_YicZb.js";
13
+ import { t as ensureCodexComputerUse } from "./computer-use-BX02ojP4.js";
14
14
  import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-dvhq-4pi.js";
15
15
  import { createHash } from "node:crypto";
16
16
  import nodeFs from "node:fs";
17
- import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, loadCodexBundleMcpThreadConfig, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveContextEngineOwnerPluginId, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
17
+ import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, hasBeforeToolCallPolicy, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, loadCodexBundleMcpThreadConfig, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveContextEngineOwnerPluginId, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
18
18
  import fs from "node:fs/promises";
19
19
  import path from "node:path";
20
20
  import { markAuthProfileBlockedUntil, resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
21
21
  import { appendRegularFile, pathExists } from "openclaw/plugin-sdk/security-runtime";
22
+ import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
22
23
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
23
24
  import { createRunningTaskRun, finalizeTaskRunByRunId, recordTaskRunProgressByRunId } from "openclaw/plugin-sdk/codex-native-task-runtime";
24
25
  import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "@earendil-works/pi-coding-agent";
25
- import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
26
+ //#region extensions/codex/src/app-server/local-runtime-attribution.ts
27
+ const OPENAI_PROVIDER_ID = "openai";
28
+ const OPENAI_RESPONSES_API = "openai-responses";
29
+ const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
30
+ const OPENAI_CODEX_RESPONSES_API = "openai-codex-responses";
31
+ function normalizeRuntimeId(value) {
32
+ return value?.trim().toLowerCase() ?? "";
33
+ }
34
+ function resolveCodexLocalRuntimeAttribution(params) {
35
+ const authProfileProvider = normalizeRuntimeId(params.runtimePlan?.auth?.authProfileProviderForAuth);
36
+ if (normalizeRuntimeId(params.runtimePlan?.observability.harnessId) === "codex" && authProfileProvider !== OPENAI_PROVIDER_ID && normalizeRuntimeId(params.model.provider) === OPENAI_PROVIDER_ID && normalizeRuntimeId(params.model.api) === OPENAI_RESPONSES_API) return {
37
+ provider: OPENAI_CODEX_PROVIDER_ID,
38
+ api: OPENAI_CODEX_RESPONSES_API
39
+ };
40
+ return {
41
+ provider: params.provider,
42
+ api: params.model.api
43
+ };
44
+ }
45
+ //#endregion
26
46
  //#region extensions/codex/src/app-server/native-subagent-task-mirror.ts
27
47
  const CODEX_NATIVE_SUBAGENT_RUNTIME = "subagent";
28
48
  const CODEX_NATIVE_SUBAGENT_TASK_KIND = "codex-native";
@@ -860,6 +880,7 @@ var CodexAppServerEventProjector = class {
860
880
  data: {
861
881
  phase: "end",
862
882
  backend: "codex-app-server",
883
+ completed: true,
863
884
  threadId: this.threadId,
864
885
  turnId: this.turnId,
865
886
  itemId
@@ -1017,8 +1038,14 @@ var CodexAppServerEventProjector = class {
1017
1038
  const text = extractRawAssistantText(item);
1018
1039
  if (!text) return;
1019
1040
  const itemId = readString$2(item, "id") ?? `raw-assistant-${this.assistantItemOrder.length + 1}`;
1041
+ const phase = readString$2(item, "phase");
1042
+ if (phase) this.assistantPhaseByItem.set(itemId, phase);
1020
1043
  this.rememberAssistantItem(itemId);
1021
1044
  this.assistantTextByItem.set(itemId, text);
1045
+ if (phase === "commentary") this.emitCommentaryProgress({
1046
+ itemId,
1047
+ text
1048
+ });
1022
1049
  }
1023
1050
  recordNativeGeneratedMedia(item) {
1024
1051
  if (item?.type !== "imageGeneration") return;
@@ -1327,6 +1354,7 @@ var CodexAppServerEventProjector = class {
1327
1354
  return await readCodexMirroredSessionHistoryMessages(this.params.sessionFile) ?? [];
1328
1355
  }
1329
1356
  createAssistantMessage(text) {
1357
+ const attribution = resolveCodexLocalRuntimeAttribution(this.params);
1330
1358
  const usage = this.tokenUsage ? {
1331
1359
  input: this.tokenUsage.input ?? 0,
1332
1360
  output: this.tokenUsage.output ?? 0,
@@ -1341,8 +1369,8 @@ var CodexAppServerEventProjector = class {
1341
1369
  type: "text",
1342
1370
  text
1343
1371
  }],
1344
- api: this.params.model.api ?? "openai-codex-responses",
1345
- provider: this.params.provider,
1372
+ api: attribution.api ?? "openai-codex-responses",
1373
+ provider: attribution.provider,
1346
1374
  model: this.params.modelId,
1347
1375
  usage,
1348
1376
  stopReason: this.aborted ? "aborted" : this.promptError ? "error" : "stop",
@@ -1351,14 +1379,15 @@ var CodexAppServerEventProjector = class {
1351
1379
  };
1352
1380
  }
1353
1381
  createAssistantMirrorMessage(title, text) {
1382
+ const attribution = resolveCodexLocalRuntimeAttribution(this.params);
1354
1383
  return {
1355
1384
  role: "assistant",
1356
1385
  content: [{
1357
1386
  type: "text",
1358
1387
  text: `${title}:\n${text}`
1359
1388
  }],
1360
- api: this.params.model.api ?? "openai-codex-responses",
1361
- provider: this.params.provider,
1389
+ api: attribution.api ?? "openai-codex-responses",
1390
+ provider: attribution.provider,
1362
1391
  model: this.params.modelId,
1363
1392
  usage: ZERO_USAGE,
1364
1393
  stopReason: "stop",
@@ -1367,6 +1396,7 @@ var CodexAppServerEventProjector = class {
1367
1396
  }
1368
1397
  createToolCallMessage(params) {
1369
1398
  const args = normalizeToolTranscriptArguments(params.arguments);
1399
+ const attribution = resolveCodexLocalRuntimeAttribution(this.params);
1370
1400
  return {
1371
1401
  role: "assistant",
1372
1402
  content: [{
@@ -1376,8 +1406,8 @@ var CodexAppServerEventProjector = class {
1376
1406
  arguments: args,
1377
1407
  input: args
1378
1408
  }],
1379
- api: this.params.model.api ?? "openai-codex-responses",
1380
- provider: this.params.provider,
1409
+ api: attribution.api ?? "openai-codex-responses",
1410
+ provider: attribution.provider,
1381
1411
  model: this.params.modelId,
1382
1412
  usage: ZERO_USAGE,
1383
1413
  stopReason: "toolUse",
@@ -1732,22 +1762,40 @@ const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
1732
1762
  permission_request: "PermissionRequest",
1733
1763
  before_agent_finalize: "Stop"
1734
1764
  };
1765
+ const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
1766
+ pre_tool_use: "pre_tool_use",
1767
+ post_tool_use: "post_tool_use",
1768
+ permission_request: "permission_request",
1769
+ before_agent_finalize: "stop"
1770
+ };
1771
+ const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
1735
1772
  function buildCodexNativeHookRelayConfig(params) {
1736
1773
  const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
1737
1774
  const config = { "features.hooks": true };
1775
+ const hookState = {};
1738
1776
  for (const event of events) {
1739
1777
  const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
1740
- config[`hooks.${codexEvent}`] = [{
1741
- matcher: null,
1742
- hooks: [{
1743
- type: "command",
1744
- command: params.relay.commandForEvent(event),
1745
- timeout: normalizeHookTimeoutSec(params.hookTimeoutSec),
1746
- async: false,
1778
+ const command = params.relay.commandForEvent(event);
1779
+ const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
1780
+ config[`hooks.${codexEvent}`] = [{ hooks: [{
1781
+ type: "command",
1782
+ command,
1783
+ timeout,
1784
+ async: false,
1785
+ statusMessage: "OpenClaw native hook relay"
1786
+ }] }];
1787
+ const state = {
1788
+ enabled: true,
1789
+ trusted_hash: codexCommandHookTrustedHash({
1790
+ event,
1791
+ command,
1792
+ timeout,
1747
1793
  statusMessage: "OpenClaw native hook relay"
1748
- }]
1749
- }];
1794
+ })
1795
+ };
1796
+ for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
1750
1797
  }
1798
+ config["hooks.state"] = hookState;
1751
1799
  return config;
1752
1800
  }
1753
1801
  function buildCodexNativeHookRelayDisabledConfig() {
@@ -1762,6 +1810,26 @@ function buildCodexNativeHookRelayDisabledConfig() {
1762
1810
  function normalizeHookTimeoutSec(value) {
1763
1811
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : 5;
1764
1812
  }
1813
+ function codexCommandHookTrustedHash(params) {
1814
+ const identity = {
1815
+ event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
1816
+ hooks: [{
1817
+ async: false,
1818
+ command: params.command,
1819
+ statusMessage: params.statusMessage,
1820
+ timeout: params.timeout,
1821
+ type: "command"
1822
+ }]
1823
+ };
1824
+ return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
1825
+ }
1826
+ function sortJsonValue(value) {
1827
+ if (!value || typeof value !== "object") return value;
1828
+ if (Array.isArray(value)) return value.map(sortJsonValue);
1829
+ const sorted = {};
1830
+ for (const key of Object.keys(value).toSorted()) sorted[key] = sortJsonValue(value[key]);
1831
+ return sorted;
1832
+ }
1765
1833
  //#endregion
1766
1834
  //#region extensions/codex/src/app-server/trajectory.ts
1767
1835
  const SENSITIVE_FIELD_RE = /(?:authorization|cookie|credential|key|password|passwd|secret|token)/iu;
@@ -1844,6 +1912,7 @@ function createCodexTrajectoryRecorder(params) {
1844
1912
  });
1845
1913
  let queue = Promise.resolve();
1846
1914
  let seq = 0;
1915
+ const attribution = resolveCodexLocalRuntimeAttribution(params.attempt);
1847
1916
  return {
1848
1917
  filePath,
1849
1918
  recordEvent: (type, data) => {
@@ -1860,9 +1929,9 @@ function createCodexTrajectoryRecorder(params) {
1860
1929
  sessionKey: params.attempt.sessionKey,
1861
1930
  runId: params.attempt.runId,
1862
1931
  workspaceDir: params.cwd,
1863
- provider: params.attempt.provider,
1932
+ provider: attribution.provider,
1864
1933
  modelId: params.attempt.modelId,
1865
- modelApi: params.attempt.model.api,
1934
+ modelApi: attribution.api,
1866
1935
  data: data ? sanitizeValue(data) : void 0
1867
1936
  });
1868
1937
  if (!line) return;
@@ -2148,6 +2217,7 @@ const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
2148
2217
  const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
2149
2218
  const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
2150
2219
  const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
2220
+ const CODEX_NATIVE_HOOK_RELAY_RENEW_INTERVAL_MS = 6e4;
2151
2221
  const CODEX_STEER_ALL_DEBOUNCE_MS = 500;
2152
2222
  const LOG_FIELD_MAX_LENGTH = 160;
2153
2223
  const CODEX_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
@@ -2315,6 +2385,22 @@ function restrictCodexAppServerSandboxForOpenClawSandbox(appServer, sandbox) {
2315
2385
  sandbox: "workspace-write"
2316
2386
  };
2317
2387
  }
2388
+ function resolveCodexAppServerForOpenClawToolPolicy(params) {
2389
+ if (!params.shouldPromote || !params.canUseUntrustedApprovalPolicy || params.appServer.approvalPolicy !== "never") return params.appServer;
2390
+ const explicitMode = params.pluginConfig.appServer?.mode !== void 0 || isCodexAppServerPolicyMode(params.env.OPENCLAW_CODEX_APP_SERVER_MODE);
2391
+ const explicitApprovalPolicy = params.pluginConfig.appServer?.approvalPolicy !== void 0 || isCodexAppServerApprovalPolicy(params.env.OPENCLAW_CODEX_APP_SERVER_APPROVAL_POLICY);
2392
+ if (explicitMode || explicitApprovalPolicy) return params.appServer;
2393
+ return {
2394
+ ...params.appServer,
2395
+ approvalPolicy: "untrusted"
2396
+ };
2397
+ }
2398
+ function isCodexAppServerPolicyMode(value) {
2399
+ return value === "guardian" || value === "yolo";
2400
+ }
2401
+ function isCodexAppServerApprovalPolicy(value) {
2402
+ return value === "never" || value === "on-request" || value === "on-failure" || value === "untrusted";
2403
+ }
2318
2404
  async function runCodexAppServerAttempt(params, options = {}) {
2319
2405
  const attemptStartedAt = Date.now();
2320
2406
  const attemptClientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory;
@@ -2330,7 +2416,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
2330
2416
  });
2331
2417
  const effectiveWorkspace = sandbox?.enabled ? sandbox.workspaceAccess === "rw" ? resolvedWorkspace : sandbox.workspaceDir : resolvedWorkspace;
2332
2418
  await fs.mkdir(effectiveWorkspace, { recursive: true });
2333
- const appServer = restrictCodexAppServerSandboxForOpenClawSandbox(configuredAppServer, sandbox);
2419
+ const appServer = resolveCodexAppServerForOpenClawToolPolicy({
2420
+ appServer: restrictCodexAppServerSandboxForOpenClawSandbox(configuredAppServer, sandbox),
2421
+ pluginConfig,
2422
+ env: process.env,
2423
+ shouldPromote: hasBeforeToolCallPolicy(),
2424
+ canUseUntrustedApprovalPolicy: configuredAppServer.start.transport !== "stdio" || isCodexAppServerApprovalPolicyAllowedByRequirements("untrusted")
2425
+ });
2334
2426
  let pluginAppServer = appServer;
2335
2427
  const nativeHookRelayEvents = resolveCodexNativeHookRelayEvents({
2336
2428
  configuredEvents: options.nativeHookRelay?.events,
@@ -2364,6 +2456,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
2364
2456
  sessionKey: sandboxSessionKey,
2365
2457
  ...startupAuthProfileId ? { authProfileId: startupAuthProfileId } : {}
2366
2458
  };
2459
+ let activeSessionId = params.sessionId;
2460
+ let activeSessionFile = params.sessionFile;
2461
+ const buildActiveRunAttemptParams = () => ({
2462
+ ...runtimeParams,
2463
+ sessionId: activeSessionId,
2464
+ sessionFile: activeSessionFile
2465
+ });
2466
+ const adoptContextEngineCompactionTranscript = (compactResult) => {
2467
+ if (compactResult.result?.sessionId) activeSessionId = compactResult.result.sessionId;
2468
+ if (compactResult.result?.sessionFile) activeSessionFile = compactResult.result.sessionFile;
2469
+ };
2367
2470
  const startupAuthAccountCacheKey = await resolveCodexAppServerAuthAccountCacheKey({
2368
2471
  authProfileId: startupAuthProfileId,
2369
2472
  authProfileStore: params.authProfileStore,
@@ -2406,8 +2509,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
2406
2509
  runId: params.runId
2407
2510
  }
2408
2511
  });
2409
- const hadSessionFile = await pathExists(params.sessionFile);
2410
- let historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? [];
2512
+ const hadSessionFile = await pathExists(activeSessionFile);
2513
+ let historyMessages = await readMirroredSessionHistoryMessages(activeSessionFile) ?? [];
2411
2514
  const hookContextWindowFields = {
2412
2515
  ...params.contextWindowInfo?.tokens ? { contextTokenBudget: params.contextWindowInfo.tokens } : params.contextTokenBudget ? { contextTokenBudget: params.contextTokenBudget } : {},
2413
2516
  ...params.contextWindowInfo?.source ? { contextWindowSource: params.contextWindowInfo.source } : {},
@@ -2424,27 +2527,28 @@ async function runCodexAppServerAttempt(params, options = {}) {
2424
2527
  channelId: params.messageChannel ?? params.messageProvider ?? void 0,
2425
2528
  ...hookContextWindowFields
2426
2529
  };
2530
+ const activeContextEnginePluginId = activeContextEngine ? resolveContextEngineOwnerPluginId(activeContextEngine) : void 0;
2531
+ const buildActiveContextEngineRuntimeContext = () => buildHarnessContextEngineRuntimeContext({
2532
+ attempt: buildActiveRunAttemptParams(),
2533
+ workspaceDir: effectiveWorkspace,
2534
+ agentDir,
2535
+ activeAgentId: sessionAgentId,
2536
+ contextEnginePluginId: activeContextEnginePluginId,
2537
+ tokenBudget: params.contextTokenBudget
2538
+ });
2427
2539
  if (activeContextEngine) {
2428
- const activeContextEnginePluginId = resolveContextEngineOwnerPluginId(activeContextEngine);
2429
2540
  await bootstrapHarnessContextEngine({
2430
2541
  hadSessionFile,
2431
2542
  contextEngine: activeContextEngine,
2432
- sessionId: params.sessionId,
2543
+ sessionId: activeSessionId,
2433
2544
  sessionKey: sandboxSessionKey,
2434
- sessionFile: params.sessionFile,
2435
- runtimeContext: buildHarnessContextEngineRuntimeContext({
2436
- attempt: runtimeParams,
2437
- workspaceDir: effectiveWorkspace,
2438
- agentDir,
2439
- activeAgentId: sessionAgentId,
2440
- contextEnginePluginId: activeContextEnginePluginId,
2441
- tokenBudget: params.contextTokenBudget
2442
- }),
2545
+ sessionFile: activeSessionFile,
2546
+ runtimeContext: buildActiveContextEngineRuntimeContext(),
2443
2547
  runMaintenance: runHarnessContextEngineMaintenance,
2444
2548
  config: params.config,
2445
2549
  warn: (message) => embeddedAgentLog.warn(message)
2446
2550
  });
2447
- historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? historyMessages;
2551
+ historyMessages = await readMirroredSessionHistoryMessages(activeSessionFile) ?? historyMessages;
2448
2552
  }
2449
2553
  const baseDeveloperInstructions = buildDeveloperInstructions(params);
2450
2554
  const workspaceBootstrapContext = await buildCodexWorkspaceBootstrapContext({
@@ -2458,10 +2562,18 @@ async function runCodexAppServerAttempt(params, options = {}) {
2458
2562
  let promptText = params.prompt;
2459
2563
  let developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions);
2460
2564
  let prePromptMessageCount = historyMessages.length;
2461
- if (activeContextEngine) try {
2565
+ let contextEngineProjection;
2566
+ const resetCodexPromptInputs = () => {
2567
+ promptText = params.prompt;
2568
+ developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions);
2569
+ prePromptMessageCount = historyMessages.length;
2570
+ contextEngineProjection = void 0;
2571
+ };
2572
+ const applyActiveContextEngineProjection = async (decisionStartupBinding) => {
2573
+ if (!activeContextEngine) return;
2462
2574
  const assembled = await assembleHarnessContextEngine({
2463
2575
  contextEngine: activeContextEngine,
2464
- sessionId: params.sessionId,
2576
+ sessionId: activeSessionId,
2465
2577
  sessionKey: sandboxSessionKey,
2466
2578
  messages: historyMessages,
2467
2579
  tokenBudget: params.contextTokenBudget,
@@ -2471,6 +2583,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2471
2583
  prompt: params.prompt
2472
2584
  });
2473
2585
  if (!assembled) throw new Error("context engine assemble returned no result");
2586
+ contextEngineProjection = readContextEngineThreadBootstrapProjection(assembled.contextProjection);
2474
2587
  const projection = projectContextEngineAssemblyForCodex({
2475
2588
  assembledMessages: assembled.messages,
2476
2589
  originalHistoryMessages: historyMessages,
@@ -2479,11 +2592,41 @@ async function runCodexAppServerAttempt(params, options = {}) {
2479
2592
  maxRenderedContextChars: resolveCodexContextEngineProjectionMaxChars({
2480
2593
  contextTokenBudget: params.contextTokenBudget,
2481
2594
  reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
2482
- })
2595
+ }),
2596
+ toolPayloadMode: contextEngineProjection ? "preserve" : "elide"
2483
2597
  });
2484
- promptText = projection.promptText;
2598
+ const projectionDecision = contextEngineProjection ? resolveContextEngineBootstrapProjectionDecision({
2599
+ startupBinding: decisionStartupBinding,
2600
+ expectedBinding: buildContextEngineBinding(buildActiveRunAttemptParams(), contextEngineProjection),
2601
+ projection: contextEngineProjection,
2602
+ dynamicToolsFingerprint: codexDynamicToolsFingerprint(toolBridge.specs)
2603
+ }) : {
2604
+ project: true,
2605
+ reason: "per-turn-projection"
2606
+ };
2607
+ embeddedAgentLog.info("codex app-server context-engine projection decision", {
2608
+ sessionId: params.sessionId,
2609
+ sessionKey: sandboxSessionKey,
2610
+ engineId: activeContextEngine.info.id,
2611
+ mode: contextEngineProjection?.mode ?? assembled.contextProjection?.mode ?? "per_turn",
2612
+ epoch: contextEngineProjection?.epoch,
2613
+ fingerprint: contextEngineProjection?.fingerprint,
2614
+ previousThreadId: decisionStartupBinding?.threadId,
2615
+ previousEpoch: decisionStartupBinding?.contextEngine?.projection?.epoch,
2616
+ previousFingerprint: decisionStartupBinding?.contextEngine?.projection?.fingerprint,
2617
+ projected: projectionDecision.project,
2618
+ reason: projectionDecision.reason,
2619
+ assembledMessages: assembled.messages.length,
2620
+ originalHistoryMessages: historyMessages.length,
2621
+ projectedPromptChars: projection.promptText.length,
2622
+ developerInstructionAdditionChars: projection.developerInstructionAddition?.length ?? 0
2623
+ });
2624
+ promptText = projectionDecision.project ? projection.promptText : params.prompt;
2485
2625
  developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions, projection.developerInstructionAddition);
2486
2626
  prePromptMessageCount = projection.prePromptMessageCount;
2627
+ };
2628
+ if (activeContextEngine) try {
2629
+ await applyActiveContextEngineProjection(startupBinding);
2487
2630
  } catch (assembleErr) {
2488
2631
  embeddedAgentLog.warn("context engine assemble failed; using Codex baseline prompt", { error: formatErrorMessage(assembleErr) });
2489
2632
  }
@@ -2500,13 +2643,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
2500
2643
  promptText = projection.promptText;
2501
2644
  prePromptMessageCount = projection.prePromptMessageCount;
2502
2645
  }
2503
- promptText = prependCurrentTurnContext(promptText, params.currentTurnContext);
2504
- const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({
2505
- prompt: promptText,
2646
+ const buildPromptFromCurrentInputs = () => resolveAgentHarnessBeforePromptBuildResult({
2647
+ prompt: prependCurrentTurnContext(promptText, params.currentTurnContext),
2506
2648
  developerInstructions,
2507
2649
  messages: historyMessages,
2508
2650
  ctx: hookContext
2509
2651
  });
2652
+ let promptBuild = await buildPromptFromCurrentInputs();
2510
2653
  const systemPromptReport = buildCodexSystemPromptReport({
2511
2654
  attempt: params,
2512
2655
  sessionKey: sandboxSessionKey,
@@ -2588,9 +2731,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
2588
2731
  timeoutMs: appServer.requestTimeoutMs,
2589
2732
  signal: runAbortController.signal
2590
2733
  });
2591
- const threadLifecycleParams = {
2734
+ const buildThreadLifecycleParams = () => ({
2592
2735
  client: startupClient,
2593
- params: runtimeParams,
2736
+ params: buildActiveRunAttemptParams(),
2594
2737
  agentId: sessionAgentId,
2595
2738
  cwd: effectiveWorkspace,
2596
2739
  dynamicTools: toolBridge.specs,
@@ -2599,6 +2742,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2599
2742
  config: threadConfig,
2600
2743
  mcpServersFingerprint: bundleMcpThreadConfig.fingerprint,
2601
2744
  mcpServersFingerprintEvaluated: bundleMcpThreadConfig.evaluated,
2745
+ contextEngineProjection,
2602
2746
  pluginThreadConfig: pluginThreadConfigEnabled ? {
2603
2747
  enabled: true,
2604
2748
  inputFingerprint: pluginThreadConfigInputFingerprint,
@@ -2613,11 +2757,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
2613
2757
  appCacheKey: pluginAppCacheKey
2614
2758
  })
2615
2759
  } : void 0
2616
- };
2617
- restartContextEngineCodexThread = () => startOrResumeThread(threadLifecycleParams);
2760
+ });
2761
+ restartContextEngineCodexThread = () => startOrResumeThread(buildThreadLifecycleParams());
2618
2762
  return {
2619
2763
  client: startupClient,
2620
- thread: await startOrResumeThread(threadLifecycleParams)
2764
+ thread: await startOrResumeThread(buildThreadLifecycleParams())
2621
2765
  };
2622
2766
  };
2623
2767
  for (let attempt = 1; attempt <= CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS; attempt += 1) try {
@@ -2702,14 +2846,22 @@ async function runCodexAppServerAttempt(params, options = {}) {
2702
2846
  let turnAssistantCompletionIdleWatchArmed = false;
2703
2847
  let turnAssistantCompletionLastActivityAt = Date.now();
2704
2848
  let turnAssistantCompletionLastActivityDetails;
2849
+ const turnAttemptIdleTimeoutMs = Math.max(100, Math.floor(params.timeoutMs));
2850
+ let turnAttemptIdleTimer;
2851
+ let turnAttemptIdleWatchArmed = false;
2705
2852
  let turnTerminalIdleTimer;
2706
2853
  let turnTerminalIdleWatchArmed = false;
2707
2854
  let turnCompletionLastActivityAt = Date.now();
2708
2855
  let turnCompletionLastActivityReason = "startup";
2709
2856
  let turnCompletionLastActivityDetails;
2857
+ let turnAttemptLastProgressAt = Date.now();
2858
+ let turnAttemptLastProgressReason = "startup";
2859
+ let turnAttemptLastProgressDetails;
2860
+ let nativeHookRelayLastRenewedAt = 0;
2710
2861
  let activeAppServerTurnRequests = 0;
2711
2862
  const activeOpenClawDynamicToolCallIds = /* @__PURE__ */ new Set();
2712
2863
  const activeTurnItemIds = /* @__PURE__ */ new Set();
2864
+ let turnCrossedToolHandoff = false;
2713
2865
  const clearTurnCompletionIdleTimer = () => {
2714
2866
  if (turnCompletionIdleTimer) {
2715
2867
  clearTimeout(turnCompletionIdleTimer);
@@ -2728,6 +2880,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
2728
2880
  turnAssistantCompletionIdleTimer = void 0;
2729
2881
  }
2730
2882
  };
2883
+ const clearTurnAttemptIdleTimer = () => {
2884
+ if (turnAttemptIdleTimer) {
2885
+ clearTimeout(turnAttemptIdleTimer);
2886
+ turnAttemptIdleTimer = void 0;
2887
+ }
2888
+ };
2731
2889
  const fireTurnAssistantCompletionIdleRelease = () => {
2732
2890
  if (completed || runAbortController.signal.aborted || !turnAssistantCompletionIdleWatchArmed) return;
2733
2891
  if (activeAppServerTurnRequests > 0 || activeTurnItemIds.size > 0) {
@@ -2764,6 +2922,35 @@ async function runCodexAppServerAttempt(params, options = {}) {
2764
2922
  completed = true;
2765
2923
  resolveCompletion?.();
2766
2924
  };
2925
+ const fireTurnAttemptIdleTimeout = () => {
2926
+ if (completed || runAbortController.signal.aborted || !turnAttemptIdleWatchArmed) return;
2927
+ const idleMs = Math.max(0, Date.now() - turnAttemptLastProgressAt);
2928
+ if (idleMs < turnAttemptIdleTimeoutMs) {
2929
+ scheduleTurnAttemptIdleWatch();
2930
+ return;
2931
+ }
2932
+ timedOut = true;
2933
+ turnCompletionIdleTimedOut = true;
2934
+ turnCompletionIdleTimeoutMessage = "codex app-server turn idle timed out waiting for turn/completed";
2935
+ projector?.markTimedOut();
2936
+ trajectoryRecorder?.recordEvent("turn.progress_idle_timeout", {
2937
+ threadId: thread.threadId,
2938
+ turnId,
2939
+ idleMs,
2940
+ timeoutMs: turnAttemptIdleTimeoutMs,
2941
+ lastActivityReason: turnAttemptLastProgressReason,
2942
+ ...turnAttemptLastProgressDetails
2943
+ });
2944
+ embeddedAgentLog.warn("codex app-server turn idle timed out waiting for progress", {
2945
+ threadId: thread.threadId,
2946
+ turnId,
2947
+ idleMs,
2948
+ timeoutMs: turnAttemptIdleTimeoutMs,
2949
+ lastActivityReason: turnAttemptLastProgressReason,
2950
+ ...turnAttemptLastProgressDetails
2951
+ });
2952
+ runAbortController.abort("turn_progress_idle_timeout");
2953
+ };
2767
2954
  const fireTurnCompletionIdleTimeout = () => {
2768
2955
  if (completed || runAbortController.signal.aborted || !turnCompletionIdleWatchArmed || activeAppServerTurnRequests > 0) return;
2769
2956
  const idleMs = Math.max(0, Date.now() - turnCompletionLastActivityAt);
@@ -2838,6 +3025,14 @@ async function runCodexAppServerAttempt(params, options = {}) {
2838
3025
  turnAssistantCompletionIdleTimer = setTimeout(fireTurnAssistantCompletionIdleRelease, delayMs);
2839
3026
  turnAssistantCompletionIdleTimer.unref?.();
2840
3027
  }
3028
+ function scheduleTurnAttemptIdleWatch() {
3029
+ clearTurnAttemptIdleTimer();
3030
+ if (completed || runAbortController.signal.aborted || !turnAttemptIdleWatchArmed) return;
3031
+ const elapsedMs = Math.max(0, Date.now() - turnAttemptLastProgressAt);
3032
+ const delayMs = Math.max(1, turnAttemptIdleTimeoutMs - elapsedMs);
3033
+ turnAttemptIdleTimer = setTimeout(fireTurnAttemptIdleTimeout, delayMs);
3034
+ turnAttemptIdleTimer.unref?.();
3035
+ }
2841
3036
  function scheduleTurnTerminalIdleWatch() {
2842
3037
  clearTurnTerminalIdleTimer();
2843
3038
  if (completed || runAbortController.signal.aborted || !turnTerminalIdleWatchArmed || activeAppServerTurnRequests > 0) return;
@@ -2846,10 +3041,41 @@ async function runCodexAppServerAttempt(params, options = {}) {
2846
3041
  turnTerminalIdleTimer = setTimeout(fireTurnTerminalIdleTimeout, delayMs);
2847
3042
  turnTerminalIdleTimer.unref?.();
2848
3043
  }
3044
+ function scheduleTurnProgressWatches() {
3045
+ scheduleTurnAttemptIdleWatch();
3046
+ scheduleTurnCompletionIdleWatch();
3047
+ scheduleTurnTerminalIdleWatch();
3048
+ }
3049
+ const renewNativeHookRelayForTurnProgress = () => {
3050
+ if (!nativeHookRelay || options.nativeHookRelay?.ttlMs !== void 0) return;
3051
+ const now = Date.now();
3052
+ const renewsRecently = now - nativeHookRelayLastRenewedAt < CODEX_NATIVE_HOOK_RELAY_RENEW_INTERVAL_MS;
3053
+ const expiresSoon = now >= nativeHookRelay.expiresAtMs - CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
3054
+ if (renewsRecently && !expiresSoon) return;
3055
+ nativeHookRelayLastRenewedAt = now;
3056
+ nativeHookRelay.renew(resolveCodexNativeHookRelayTtlMs({
3057
+ explicitTtlMs: void 0,
3058
+ attemptTimeoutMs: turnAttemptIdleTimeoutMs,
3059
+ startupTimeoutMs,
3060
+ turnStartTimeoutMs: params.timeoutMs
3061
+ }));
3062
+ };
2849
3063
  const touchTurnCompletionActivity = (reason, options) => {
2850
3064
  turnCompletionLastActivityAt = Date.now();
2851
3065
  turnCompletionLastActivityReason = reason;
2852
3066
  turnCompletionLastActivityDetails = options?.details;
3067
+ if (options?.attemptProgress) {
3068
+ turnAttemptLastProgressAt = turnCompletionLastActivityAt;
3069
+ turnAttemptLastProgressReason = reason;
3070
+ turnAttemptLastProgressDetails = options.details;
3071
+ renewNativeHookRelayForTurnProgress();
3072
+ params.onRunProgress?.({
3073
+ reason,
3074
+ provider: params.provider,
3075
+ model: params.modelId,
3076
+ backend: "codex-app-server"
3077
+ });
3078
+ }
2853
3079
  emitTrustedDiagnosticEvent({
2854
3080
  type: "run.progress",
2855
3081
  runId: params.runId,
@@ -2861,8 +3087,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2861
3087
  turnCompletionIdleWatchArmed = true;
2862
3088
  turnCompletionIdleWatchPinnedByTerminalError = false;
2863
3089
  }
2864
- scheduleTurnCompletionIdleWatch();
2865
- scheduleTurnTerminalIdleWatch();
3090
+ scheduleTurnProgressWatches();
2866
3091
  };
2867
3092
  const disarmTurnCompletionIdleWatch = () => {
2868
3093
  turnCompletionIdleWatchArmed = false;
@@ -2946,26 +3171,34 @@ async function runCodexAppServerAttempt(params, options = {}) {
2946
3171
  const isCurrentTurnNotification = isTurnNotification(notification.params, thread.threadId, turnId);
2947
3172
  const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
2948
3173
  if (isCurrentTurnNotification) {
2949
- touchTurnCompletionActivity(`notification:${notification.method}`, { details: describeNotificationActivity(notification) });
3174
+ touchTurnCompletionActivity(`notification:${notification.method}`, {
3175
+ details: describeNotificationActivity(notification),
3176
+ attemptProgress: true
3177
+ });
2950
3178
  reportCodexExecutionNotification(notification);
2951
3179
  }
2952
3180
  if (isCurrentTurnNotification) updateActiveTurnItemIds(notification, activeTurnItemIds);
2953
3181
  const unblockedAssistantCompletionRelease = isCurrentTurnNotification && turnAssistantCompletionIdleWatchArmed && notification.method === "item/completed" && activeTurnItemIds.size === 0;
2954
3182
  const trackedDynamicToolCompletion = isTrackedOpenClawDynamicToolCompletionNotification(notification, activeOpenClawDynamicToolCallIds);
2955
- const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem = isCurrentTurnNotification && notification.method === "item/completed" && activeTurnItemIds.size === 0 && !trackedDynamicToolCompletion && !isCompletedAssistantNotification(notification);
3183
+ const rawToolOutputCompletion = isRawToolOutputCompletionNotification(notification);
3184
+ if (isCurrentTurnNotification && (rawToolOutputCompletion || isNativeToolProgressNotification(notification))) turnCrossedToolHandoff = true;
3185
+ const assistantCompletionCanRelease = isAssistantCompletionReleaseNotification(notification, turnCrossedToolHandoff);
3186
+ const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem = isCurrentTurnNotification && notification.method === "item/completed" && activeTurnItemIds.size === 0 && !trackedDynamicToolCompletion && !assistantCompletionCanRelease;
2956
3187
  if (isCurrentTurnNotification && notification.method === "error") {
2957
3188
  if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
2958
3189
  else armTurnCompletionIdleWatch({ pinnedByTerminalError: true });
2959
3190
  disarmTurnAssistantCompletionIdleWatch();
2960
3191
  } else if (isTurnCompletion) disarmTurnAssistantCompletionIdleWatch();
2961
- else if (isCurrentTurnNotification && isCompletedAssistantNotification(notification)) armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
3192
+ else if (isCurrentTurnNotification && assistantCompletionCanRelease) armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
2962
3193
  else if (unblockedAssistantCompletionRelease) armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
2963
3194
  else if (shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) armTurnCompletionIdleWatch();
3195
+ else if (isCurrentTurnNotification && rawToolOutputCompletion) armTurnCompletionIdleWatch();
2964
3196
  else if (isCurrentTurnNotification && shouldDisarmAssistantCompletionIdleWatch(notification)) disarmTurnAssistantCompletionIdleWatch();
2965
- if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) disarmTurnCompletionIdleWatch();
3197
+ if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) disarmTurnCompletionIdleWatch();
2966
3198
  const isTurnAbortMarker = isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
2967
3199
  const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
2968
3200
  try {
3201
+ await waitForCodexNotificationDispatchTurn();
2969
3202
  await projector.handleNotification(notification);
2970
3203
  } catch (error) {
2971
3204
  embeddedAgentLog.debug("codex app-server projector notification threw", {
@@ -2985,16 +3218,25 @@ async function runCodexAppServerAttempt(params, options = {}) {
2985
3218
  }
2986
3219
  };
2987
3220
  const enqueueNotification = (notification) => {
3221
+ if (!projector || !turnId) {
3222
+ userInputBridge?.handleNotification(notification);
3223
+ pendingNotifications.push(notification);
3224
+ return Promise.resolve();
3225
+ }
2988
3226
  notificationQueue = notificationQueue.then(() => handleNotification(notification), () => handleNotification(notification));
2989
3227
  return notificationQueue;
2990
3228
  };
2991
3229
  const notificationCleanup = client.addNotificationHandler(enqueueNotification);
2992
3230
  const requestCleanup = client.addRequestHandler(async (request) => {
2993
- activeAppServerTurnRequests += 1;
2994
- clearTurnCompletionIdleTimer();
2995
- disarmTurnAssistantCompletionIdleWatch();
2996
- touchTurnCompletionActivity(`request:${request.method}`);
2997
3231
  let armCompletionWatchOnResponse = false;
3232
+ let requestCountsAsTurnActivity = false;
3233
+ const markCurrentTurnRequestProgress = () => {
3234
+ activeAppServerTurnRequests += 1;
3235
+ clearTurnCompletionIdleTimer();
3236
+ disarmTurnAssistantCompletionIdleWatch();
3237
+ requestCountsAsTurnActivity = true;
3238
+ touchTurnCompletionActivity(`request:${request.method}:start`, { attemptProgress: true });
3239
+ };
2998
3240
  try {
2999
3241
  if (request.method === "account/chatgptAuthTokens/refresh") return refreshCodexAppServerAuthTokens({
3000
3242
  agentDir,
@@ -3003,7 +3245,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
3003
3245
  });
3004
3246
  if (!turnId) return;
3005
3247
  if (request.method === "mcpServer/elicitation/request") {
3006
- armCompletionWatchOnResponse = true;
3248
+ if (isCurrentThreadOptionalTurnRequestParams(request.params, thread.threadId, turnId)) {
3249
+ armCompletionWatchOnResponse = true;
3250
+ markCurrentTurnRequestProgress();
3251
+ }
3007
3252
  return handleCodexAppServerElicitationRequest({
3008
3253
  requestParams: request.params,
3009
3254
  paramsForRun: params,
@@ -3014,7 +3259,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
3014
3259
  });
3015
3260
  }
3016
3261
  if (request.method === "item/tool/requestUserInput") {
3017
- armCompletionWatchOnResponse = true;
3262
+ if (isCurrentThreadTurnRequestParams(request.params, thread.threadId, turnId)) {
3263
+ armCompletionWatchOnResponse = true;
3264
+ markCurrentTurnRequestProgress();
3265
+ }
3018
3266
  return userInputBridge?.handleRequest({
3019
3267
  id: request.id,
3020
3268
  params: request.params
@@ -3022,7 +3270,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
3022
3270
  }
3023
3271
  if (request.method !== "item/tool/call") {
3024
3272
  if (isCodexAppServerApprovalRequest(request.method)) {
3025
- armCompletionWatchOnResponse = true;
3273
+ if (isCurrentApprovalTurnRequestParams(request.params, thread.threadId, turnId)) {
3274
+ armCompletionWatchOnResponse = true;
3275
+ markCurrentTurnRequestProgress();
3276
+ }
3026
3277
  return handleApprovalRequest({
3027
3278
  method: request.method,
3028
3279
  params: request.params,
@@ -3037,6 +3288,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
3037
3288
  const call = readDynamicToolCallParams(request.params);
3038
3289
  if (!call || call.threadId !== thread.threadId || call.turnId !== turnId) return;
3039
3290
  armCompletionWatchOnResponse = true;
3291
+ markCurrentTurnRequestProgress();
3292
+ turnCrossedToolHandoff = true;
3040
3293
  activeOpenClawDynamicToolCallIds.add(call.callId);
3041
3294
  trajectoryRecorder?.recordEvent("tool.call", {
3042
3295
  threadId: call.threadId,
@@ -3113,11 +3366,85 @@ async function runCodexAppServerAttempt(params, options = {}) {
3113
3366
  });
3114
3367
  return response;
3115
3368
  } finally {
3116
- activeAppServerTurnRequests = Math.max(0, activeAppServerTurnRequests - 1);
3117
- touchTurnCompletionActivity(`request:${request.method}:response`, { arm: armCompletionWatchOnResponse });
3369
+ if (requestCountsAsTurnActivity) {
3370
+ activeAppServerTurnRequests = Math.max(0, activeAppServerTurnRequests - 1);
3371
+ touchTurnCompletionActivity(`request:${request.method}:response`, {
3372
+ arm: armCompletionWatchOnResponse,
3373
+ attemptProgress: true
3374
+ });
3375
+ } else scheduleTurnProgressWatches();
3118
3376
  }
3119
3377
  });
3120
- const llmInputEvent = {
3378
+ const forceContextEngineCompactionForCodexOverflow = async (error) => {
3379
+ if (!activeContextEngine?.info.ownsCompaction) return false;
3380
+ embeddedAgentLog.warn("codex app-server context-engine turn overflowed; forcing context-engine compaction", {
3381
+ sessionId: activeSessionId,
3382
+ sessionKey: sandboxSessionKey,
3383
+ threadId: thread.threadId,
3384
+ engineId: activeContextEngine.info.id,
3385
+ tokenBudget: params.contextTokenBudget,
3386
+ error: formatErrorMessage(error)
3387
+ });
3388
+ try {
3389
+ const runtimeContext = buildActiveContextEngineRuntimeContext();
3390
+ const overflowTokenCount = params.contextTokenBudget ?? params.contextWindowInfo?.tokens;
3391
+ const compactResult = await activeContextEngine.compact({
3392
+ sessionId: activeSessionId,
3393
+ sessionKey: sandboxSessionKey,
3394
+ sessionFile: activeSessionFile,
3395
+ tokenBudget: params.contextTokenBudget,
3396
+ force: true,
3397
+ ...overflowTokenCount ? { currentTokenCount: overflowTokenCount } : {},
3398
+ compactionTarget: "threshold",
3399
+ runtimeContext: overflowTokenCount ? {
3400
+ ...runtimeContext,
3401
+ currentTokenCount: overflowTokenCount
3402
+ } : runtimeContext
3403
+ });
3404
+ embeddedAgentLog.info("codex app-server context-engine forced compaction result", {
3405
+ sessionId: activeSessionId,
3406
+ sessionKey: sandboxSessionKey,
3407
+ engineId: activeContextEngine.info.id,
3408
+ ok: compactResult.ok,
3409
+ compacted: compactResult.compacted,
3410
+ reason: compactResult.reason,
3411
+ tokensBefore: compactResult.result?.tokensBefore,
3412
+ tokensAfter: compactResult.result?.tokensAfter
3413
+ });
3414
+ if (!compactResult.ok || !compactResult.compacted) return false;
3415
+ adoptContextEngineCompactionTranscript(compactResult);
3416
+ const maintenanceRuntimeContext = buildActiveContextEngineRuntimeContext();
3417
+ await runHarnessContextEngineMaintenance({
3418
+ contextEngine: activeContextEngine,
3419
+ sessionId: activeSessionId,
3420
+ sessionKey: sandboxSessionKey,
3421
+ sessionFile: activeSessionFile,
3422
+ reason: "compaction",
3423
+ runtimeContext: maintenanceRuntimeContext,
3424
+ config: params.config
3425
+ });
3426
+ return true;
3427
+ } catch (compactErr) {
3428
+ embeddedAgentLog.warn("codex app-server context-engine forced compaction failed", {
3429
+ sessionId: params.sessionId,
3430
+ sessionKey: sandboxSessionKey,
3431
+ engineId: activeContextEngine.info.id,
3432
+ error: formatErrorMessage(compactErr)
3433
+ });
3434
+ return false;
3435
+ }
3436
+ };
3437
+ const rebuildPromptAfterContextEngineCompaction = async () => {
3438
+ historyMessages = await readMirroredSessionHistoryMessages(activeSessionFile) ?? historyMessages;
3439
+ resetCodexPromptInputs();
3440
+ try {
3441
+ await applyActiveContextEngineProjection(void 0);
3442
+ } catch (assembleErr) {
3443
+ embeddedAgentLog.warn("context engine assemble failed after forced compaction; using Codex baseline prompt", { error: formatErrorMessage(assembleErr) });
3444
+ }
3445
+ promptBuild = await buildPromptFromCurrentInputs();
3446
+ };
3447
+ const buildLlmInputEvent = () => ({
3121
3448
  runId: params.runId,
3122
3449
  sessionId: params.sessionId,
3123
3450
  provider: params.provider,
@@ -3126,8 +3453,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
3126
3453
  prompt: promptBuild.prompt,
3127
3454
  historyMessages,
3128
3455
  imagesCount: params.images?.length ?? 0
3129
- };
3130
- const turnStartFailureMessages = [...historyMessages, buildCodexUserPromptMessage({
3456
+ });
3457
+ const buildTurnStartFailureMessages = () => [...historyMessages, buildCodexUserPromptMessage({
3131
3458
  ...params,
3132
3459
  prompt: promptBuild.prompt
3133
3460
  })];
@@ -3143,7 +3470,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3143
3470
  }));
3144
3471
  try {
3145
3472
  runAgentHarnessLlmInputHook({
3146
- event: llmInputEvent,
3473
+ event: buildLlmInputEvent(),
3147
3474
  ctx: hookContext
3148
3475
  });
3149
3476
  emitCodexAppServerEvent(params, {
@@ -3165,7 +3492,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
3165
3492
  threadId: thread.threadId,
3166
3493
  error: formatErrorMessage(turnStartError)
3167
3494
  });
3168
- await clearCodexAppServerBinding(params.sessionFile);
3495
+ const preRetrySessionFile = activeSessionFile;
3496
+ const compactedForRetry = await forceContextEngineCompactionForCodexOverflow(turnStartError);
3497
+ await clearCodexAppServerBinding(preRetrySessionFile);
3498
+ if (activeSessionFile !== preRetrySessionFile) await clearCodexAppServerBinding(activeSessionFile);
3499
+ if (compactedForRetry) await rebuildPromptAfterContextEngineCompaction();
3169
3500
  thread = await restartContextEngineCodexThread();
3170
3501
  emitCodexAppServerEvent(params, {
3171
3502
  stream: "codex_app_server.lifecycle",
@@ -3219,7 +3550,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3219
3550
  });
3220
3551
  runAgentHarnessAgentEndHook({
3221
3552
  event: {
3222
- messages: turnStartFailureMessages,
3553
+ messages: buildTurnStartFailureMessages(),
3223
3554
  success: false,
3224
3555
  error: turnStartErrorMessage,
3225
3556
  durationMs: Date.now() - attemptStartedAt
@@ -3248,7 +3579,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3248
3579
  return buildCodexTurnStartFailureResult({
3249
3580
  params,
3250
3581
  message: usageLimitError.message,
3251
- messagesSnapshot: turnStartFailureMessages,
3582
+ messagesSnapshot: buildTurnStartFailureMessages(),
3252
3583
  systemPromptReport
3253
3584
  });
3254
3585
  }
@@ -3302,11 +3633,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
3302
3633
  abort: () => runAbortController.abort("aborted")
3303
3634
  };
3304
3635
  setActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
3305
- const timeout = setTimeout(() => {
3306
- timedOut = true;
3307
- projector?.markTimedOut();
3308
- runAbortController.abort("timeout");
3309
- }, Math.max(100, params.timeoutMs));
3636
+ turnAttemptIdleWatchArmed = true;
3637
+ turnTerminalIdleWatchArmed = true;
3638
+ touchTurnCompletionActivity("turn:start", { attemptProgress: true });
3310
3639
  const abortListener = () => {
3311
3640
  const shouldRetireClient = timedOut;
3312
3641
  interruptCodexTurnBestEffort(client, {
@@ -3380,20 +3709,20 @@ async function runCodexAppServerAttempt(params, options = {}) {
3380
3709
  });
3381
3710
  if (activeContextEngine) {
3382
3711
  const activeContextEnginePluginId = resolveContextEngineOwnerPluginId(activeContextEngine);
3383
- const finalMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? historyMessages.concat(result.messagesSnapshot);
3712
+ const finalMessages = await readMirroredSessionHistoryMessages(activeSessionFile) ?? historyMessages.concat(result.messagesSnapshot);
3384
3713
  await finalizeHarnessContextEngineTurn({
3385
3714
  contextEngine: activeContextEngine,
3386
3715
  promptError: Boolean(finalPromptError),
3387
3716
  aborted: finalAborted,
3388
3717
  yieldAborted: Boolean(result.yieldDetected),
3389
- sessionIdUsed: params.sessionId,
3718
+ sessionIdUsed: activeSessionId,
3390
3719
  sessionKey: sandboxSessionKey,
3391
- sessionFile: params.sessionFile,
3720
+ sessionFile: activeSessionFile,
3392
3721
  messagesSnapshot: finalMessages,
3393
3722
  prePromptMessageCount,
3394
3723
  tokenBudget: params.contextTokenBudget,
3395
3724
  runtimeContext: buildHarnessContextEngineRuntimeContextFromUsage({
3396
- attempt: runtimeParams,
3725
+ attempt: buildActiveRunAttemptParams(),
3397
3726
  workspaceDir: effectiveWorkspace,
3398
3727
  agentDir,
3399
3728
  activeAgentId: sessionAgentId,
@@ -3462,7 +3791,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3462
3791
  });
3463
3792
  if (!timedOut && !runAbortController.signal.aborted) await steeringQueue?.flushPending();
3464
3793
  userInputBridge?.cancelPending();
3465
- clearTimeout(timeout);
3794
+ clearTurnAttemptIdleTimer();
3466
3795
  clearTurnCompletionIdleTimer();
3467
3796
  clearTurnAssistantCompletionIdleTimer();
3468
3797
  clearTurnTerminalIdleTimer();
@@ -3804,6 +4133,45 @@ function shouldProjectMirroredHistoryForCodexStart(params) {
3804
4133
  next: params.dynamicToolsFingerprint
3805
4134
  });
3806
4135
  }
4136
+ function readContextEngineThreadBootstrapProjection(projection) {
4137
+ if (projection?.mode !== "thread_bootstrap") return;
4138
+ const epoch = projection.epoch?.trim();
4139
+ if (!epoch) {
4140
+ embeddedAgentLog.warn("context engine requested Codex thread-bootstrap projection without an epoch; using per-turn projection");
4141
+ return;
4142
+ }
4143
+ const fingerprint = projection.fingerprint?.trim();
4144
+ return {
4145
+ mode: "thread_bootstrap",
4146
+ epoch,
4147
+ ...fingerprint ? { fingerprint } : {}
4148
+ };
4149
+ }
4150
+ function resolveContextEngineBootstrapProjectionDecision(params) {
4151
+ const bindingProjection = params.startupBinding?.contextEngine?.projection;
4152
+ if (!params.startupBinding?.threadId || !bindingProjection) return {
4153
+ project: true,
4154
+ reason: !params.startupBinding?.threadId ? "missing-thread-binding" : "missing-projection-binding"
4155
+ };
4156
+ if (!params.expectedBinding || !isContextEngineBindingCompatible(params.startupBinding.contextEngine, params.expectedBinding)) return {
4157
+ project: true,
4158
+ reason: "context-engine-binding-mismatch"
4159
+ };
4160
+ if (!areCodexDynamicToolFingerprintsCompatible({
4161
+ previous: params.startupBinding.dynamicToolsFingerprint,
4162
+ next: params.dynamicToolsFingerprint
4163
+ })) return {
4164
+ project: true,
4165
+ reason: "dynamic-tools-mismatch"
4166
+ };
4167
+ return bindingProjection.mode !== "thread_bootstrap" || bindingProjection.epoch !== params.projection.epoch || bindingProjection.fingerprint !== params.projection.fingerprint ? {
4168
+ project: true,
4169
+ reason: "projection-mismatch"
4170
+ } : {
4171
+ project: false,
4172
+ reason: "matching-thread-bootstrap-binding"
4173
+ };
4174
+ }
3807
4175
  async function withCodexStartupTimeout(params) {
3808
4176
  if (params.signal.aborted) throw new Error("codex app-server startup aborted");
3809
4177
  let timeout;
@@ -3985,6 +4353,10 @@ function isCompletedAssistantNotification(notification) {
3985
4353
  const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
3986
4354
  return Boolean(item && readString(item, "type") === "agentMessage" && readString(item, "phase") !== "commentary");
3987
4355
  }
4356
+ function isAssistantCompletionReleaseNotification(notification, turnCrossedToolHandoff) {
4357
+ if (isCompletedAssistantNotification(notification)) return true;
4358
+ return !turnCrossedToolHandoff && isRawAssistantCompletionNotification(notification);
4359
+ }
3988
4360
  function shouldDisarmAssistantCompletionIdleWatch(notification) {
3989
4361
  if (!isJsonObject(notification.params)) return false;
3990
4362
  if (notification.method === "item/started") return true;
@@ -4004,6 +4376,28 @@ function isTrackedOpenClawDynamicToolCompletionNotification(notification, active
4004
4376
  const itemType = item ? readString(item, "type") : void 0;
4005
4377
  return itemType === void 0 || itemType === "dynamicToolCall";
4006
4378
  }
4379
+ function isRawToolOutputCompletionNotification(notification) {
4380
+ if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) return false;
4381
+ const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
4382
+ return item ? readString(item, "type") === "custom_tool_call_output" : false;
4383
+ }
4384
+ function isNativeToolProgressNotification(notification) {
4385
+ if (notification.method !== "item/started" && notification.method !== "item/completed" && notification.method !== "item/updated") return false;
4386
+ if (!isJsonObject(notification.params)) return false;
4387
+ const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
4388
+ switch (item ? readString(item, "type") : void 0) {
4389
+ case "commandExecution":
4390
+ case "fileChange":
4391
+ case "mcpToolCall":
4392
+ case "webSearch": return true;
4393
+ default: return false;
4394
+ }
4395
+ }
4396
+ function isRawAssistantCompletionNotification(notification) {
4397
+ if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) return false;
4398
+ const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
4399
+ return Boolean(item && readString(item, "type") === "message" && readString(item, "role") === "assistant" && readString(item, "phase") !== "commentary" && readRawAssistantTextPreview(item));
4400
+ }
4007
4401
  function readRawAssistantTextPreview(item) {
4008
4402
  if (readString(item, "role") !== "assistant" || !Array.isArray(item.content)) return;
4009
4403
  const text = item.content.flatMap((content) => {
@@ -4018,6 +4412,19 @@ function isTurnNotification(value, threadId, turnId) {
4018
4412
  if (!isJsonObject(value)) return false;
4019
4413
  return readString(value, "threadId") === threadId && readNotificationTurnId(value) === turnId;
4020
4414
  }
4415
+ function isCurrentThreadTurnRequestParams(value, threadId, turnId) {
4416
+ if (!isJsonObject(value)) return false;
4417
+ return readString(value, "threadId") === threadId && readString(value, "turnId") === turnId;
4418
+ }
4419
+ function isCurrentApprovalTurnRequestParams(value, threadId, turnId) {
4420
+ if (!isJsonObject(value)) return false;
4421
+ return (readString(value, "threadId") ?? readString(value, "conversationId")) === threadId && readString(value, "turnId") === turnId;
4422
+ }
4423
+ function isCurrentThreadOptionalTurnRequestParams(value, threadId, turnId) {
4424
+ if (!isJsonObject(value) || readString(value, "threadId") !== threadId) return false;
4425
+ const requestTurnId = value.turnId;
4426
+ return requestTurnId === null || requestTurnId === void 0 || requestTurnId === turnId;
4427
+ }
4021
4428
  function isRetryableErrorNotification(value) {
4022
4429
  if (!isJsonObject(value)) return false;
4023
4430
  return readBoolean(value, "willRetry") === true || readBoolean(value, "will_retry") === true;
@@ -4289,6 +4696,11 @@ function prependCurrentTurnContext(prompt, context) {
4289
4696
  const text = context?.text.trim();
4290
4697
  return text ? [text, prompt].filter(Boolean).join("\n\n") : prompt;
4291
4698
  }
4699
+ function waitForCodexNotificationDispatchTurn() {
4700
+ return new Promise((resolve) => {
4701
+ setImmediate(resolve);
4702
+ });
4703
+ }
4292
4704
  function handleApprovalRequest(params) {
4293
4705
  return handleCodexAppServerApprovalRequest({
4294
4706
  method: params.method,