@ai-sdk/harness-codex 1.0.93 → 1.0.95

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.
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@openai/codex-sdk": "0.144.5",
7
+ "@openai/codex-sdk": "0.149.1",
8
8
  "ws": "8.21.0"
9
9
  }
10
10
  }
@@ -9,55 +9,55 @@ importers:
9
9
  .:
10
10
  dependencies:
11
11
  '@openai/codex-sdk':
12
- specifier: 0.144.5
13
- version: 0.144.5
12
+ specifier: 0.149.1
13
+ version: 0.149.1
14
14
  ws:
15
15
  specifier: 8.21.0
16
16
  version: 8.21.0
17
17
 
18
18
  packages:
19
19
 
20
- '@openai/codex-sdk@0.144.5':
21
- resolution: {integrity: sha512-90wHPEGyk74On6gwQPNtw+wzuDJ2zYpiVADDxw43S1cWn3QbBh/21zFS2xlAWWCrdY0gE90yXfmmrzwdUDlBGw==}
20
+ '@openai/codex-sdk@0.149.1':
21
+ resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==}
22
22
  engines: {node: '>=18'}
23
23
 
24
- '@openai/codex@0.144.5':
25
- resolution: {integrity: sha512-jjB+K+OMv572mKhS+2QuLxWXDJNdpwbPenf+V+8bdq7wg4Scqt3cn6WEekD8wPqDVZqck0HSX17K9rD9kbDJQA==}
24
+ '@openai/codex@0.149.1':
25
+ resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==}
26
26
  engines: {node: '>=16'}
27
27
  hasBin: true
28
28
 
29
- '@openai/codex@0.144.5-darwin-arm64':
30
- resolution: {integrity: sha512-zcT6NfBCqLFt+BReNSETTZW6v6PdbH0dzNtm9j7l7mDGqwPbKZDGJdnpkBao2389I0ZacyIKgSZoI0vez1d4Dw==}
29
+ '@openai/codex@0.149.1-darwin-arm64':
30
+ resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==}
31
31
  engines: {node: '>=16'}
32
32
  cpu: [arm64]
33
33
  os: [darwin]
34
34
 
35
- '@openai/codex@0.144.5-darwin-x64':
36
- resolution: {integrity: sha512-//Mo0m1MwaoT6psu5xsmofXpKx4/0irIkeq10xJvk59+886EG355ibjA+ZmlRcKhE3bLjsKD7p81nTbAdRL/bw==}
35
+ '@openai/codex@0.149.1-darwin-x64':
36
+ resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==}
37
37
  engines: {node: '>=16'}
38
38
  cpu: [x64]
39
39
  os: [darwin]
40
40
 
41
- '@openai/codex@0.144.5-linux-arm64':
42
- resolution: {integrity: sha512-zAHggxVwR2TBxKmybXY7ZMiB0G8DMonY2YPdwNNjwXcf+LOIqNGgswwNCDMbP/HEe6r8j+R9ZX/yYoo8f+n/RQ==}
41
+ '@openai/codex@0.149.1-linux-arm64':
42
+ resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==}
43
43
  engines: {node: '>=16'}
44
44
  cpu: [arm64]
45
45
  os: [linux]
46
46
 
47
- '@openai/codex@0.144.5-linux-x64':
48
- resolution: {integrity: sha512-FalLJlBQGFdK8Gc3kj9sa/ekNdgkHhUawLaKkvy5CtB18JaP2YxtTP/Pe1pD2iBiq8mMUliRnafpF6AdBdQMbg==}
47
+ '@openai/codex@0.149.1-linux-x64':
48
+ resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==}
49
49
  engines: {node: '>=16'}
50
50
  cpu: [x64]
51
51
  os: [linux]
52
52
 
53
- '@openai/codex@0.144.5-win32-arm64':
54
- resolution: {integrity: sha512-0Pj7iqjEOEvPQPO3kFfCy9vGX4BTu76ChFFZHr2eNNIfVc3FOENAv/X98u4L+iIUtDOK9DbqmfUudW3DPapshg==}
53
+ '@openai/codex@0.149.1-win32-arm64':
54
+ resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==}
55
55
  engines: {node: '>=16'}
56
56
  cpu: [arm64]
57
57
  os: [win32]
58
58
 
59
- '@openai/codex@0.144.5-win32-x64':
60
- resolution: {integrity: sha512-DnsSTlnnzleTxvLwIGnBitKInscxn2I7qASqosS8Fv+qysBygd+ZiBn/SQsRCgQ28PAlsNzmd3Gf3ZTecolAmg==}
59
+ '@openai/codex@0.149.1-win32-x64':
60
+ resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==}
61
61
  engines: {node: '>=16'}
62
62
  cpu: [x64]
63
63
  os: [win32]
@@ -76,35 +76,35 @@ packages:
76
76
 
77
77
  snapshots:
78
78
 
79
- '@openai/codex-sdk@0.144.5':
79
+ '@openai/codex-sdk@0.149.1':
80
80
  dependencies:
81
- '@openai/codex': 0.144.5
81
+ '@openai/codex': 0.149.1
82
82
 
83
- '@openai/codex@0.144.5':
83
+ '@openai/codex@0.149.1':
84
84
  optionalDependencies:
85
- '@openai/codex-darwin-arm64': '@openai/codex@0.144.5-darwin-arm64'
86
- '@openai/codex-darwin-x64': '@openai/codex@0.144.5-darwin-x64'
87
- '@openai/codex-linux-arm64': '@openai/codex@0.144.5-linux-arm64'
88
- '@openai/codex-linux-x64': '@openai/codex@0.144.5-linux-x64'
89
- '@openai/codex-win32-arm64': '@openai/codex@0.144.5-win32-arm64'
90
- '@openai/codex-win32-x64': '@openai/codex@0.144.5-win32-x64'
91
-
92
- '@openai/codex@0.144.5-darwin-arm64':
85
+ '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64'
86
+ '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64'
87
+ '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64'
88
+ '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64'
89
+ '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64'
90
+ '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64'
91
+
92
+ '@openai/codex@0.149.1-darwin-arm64':
93
93
  optional: true
94
94
 
95
- '@openai/codex@0.144.5-darwin-x64':
95
+ '@openai/codex@0.149.1-darwin-x64':
96
96
  optional: true
97
97
 
98
- '@openai/codex@0.144.5-linux-arm64':
98
+ '@openai/codex@0.149.1-linux-arm64':
99
99
  optional: true
100
100
 
101
- '@openai/codex@0.144.5-linux-x64':
101
+ '@openai/codex@0.149.1-linux-x64':
102
102
  optional: true
103
103
 
104
- '@openai/codex@0.144.5-win32-arm64':
104
+ '@openai/codex@0.149.1-win32-arm64':
105
105
  optional: true
106
106
 
107
- '@openai/codex@0.144.5-win32-x64':
107
+ '@openai/codex@0.149.1-win32-x64':
108
108
  optional: true
109
109
 
110
110
  ws@8.21.0: {}
package/dist/index.d.ts CHANGED
@@ -1,33 +1,10 @@
1
1
  import * as _ai_sdk_harness from '@ai-sdk/harness';
2
- import { HarnessV1CredentialForwarding, HarnessV1PortEndpoint, HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
2
+ import { HarnessV1Authentication, HarnessV1CredentialForwarding, HarnessV1PortEndpoint, HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
3
3
 
4
- type CodexResolvedAuthenticationMode = 'direct' | 'ai-gateway';
5
- type CodexAuthenticationMode = CodexResolvedAuthenticationMode | 'auto';
6
- /**
7
- * @deprecated Passing an object to auth options is deprecated. Use a `CodexAuthenticationMode` string value ("auto" | "direct" | "ai-gateway") instead, and pass credentials via environment variables.
8
- */
9
- type LegacyCodexAuthOptions = {
10
- readonly openaiCompatible?: {
11
- readonly apiKey?: string;
12
- readonly baseUrl?: string;
13
- readonly modelProviderName?: string;
14
- readonly queryParamsJson?: string;
15
- };
16
- readonly openai?: {
17
- readonly apiKey?: string;
18
- readonly baseUrl?: string;
19
- readonly organization?: string;
20
- readonly project?: string;
21
- };
22
- readonly gateway?: {
23
- readonly apiKey?: string;
24
- readonly baseUrl?: string;
25
- };
26
- };
27
- type CodexAuthOptions = CodexAuthenticationMode | LegacyCodexAuthOptions;
4
+ type CodexAuthenticationMode = HarnessV1Authentication;
28
5
 
29
6
  type CodexHarnessSettings = {
30
- readonly auth?: CodexAuthOptions;
7
+ readonly auth?: CodexAuthenticationMode;
31
8
  /**
32
9
  * Customizes each credential value before it is forwarded into a sandbox
33
10
  * process. This does not restrict which credentials the harness adapter can
@@ -48,6 +25,8 @@ type CodexHarnessSettings = {
48
25
  /**
49
26
  * OpenAI model id the underlying `codex` CLI should use. Leaving this unset
50
27
  * pins the adapter default (`DEFAULT_CODEX_MODEL`).
28
+ *
29
+ * @deprecated Use `model` on `HarnessAgent` instead.
51
30
  */
52
31
  readonly model?: string;
53
32
  /**
@@ -105,4 +84,4 @@ declare const codex: _ai_sdk_harness.HarnessV1<{
105
84
  }, unknown>;
106
85
  }>;
107
86
 
108
- export { type CodexAuthOptions, type CodexAuthenticationMode, type CodexHarnessSettings, VERSION, codex, createCodex };
87
+ export { type CodexAuthenticationMode, type CodexHarnessSettings, VERSION, codex, createCodex };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/codex-harness.ts
2
- import { randomBytes } from "crypto";
2
+ import { createHash, randomBytes } from "crypto";
3
3
  import path from "path";
4
4
  import {
5
5
  commonTool,
@@ -76,7 +76,8 @@ async function readBridgeAsset(name) {
76
76
  // src/codex-auth.ts
77
77
  import {
78
78
  createCredentialRequestTransformation,
79
- getAiGatewayAuthFromEnv
79
+ getAiGatewayAuthFromEnv,
80
+ isHarnessAuthenticationEnvironment
80
81
  } from "@ai-sdk/harness/utils";
81
82
  var DEFAULT_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1";
82
83
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
@@ -105,95 +106,50 @@ function createCodexRequestTransformations({
105
106
  ];
106
107
  }
107
108
  function resolveCodexEnv(auth, processEnv = process.env) {
108
- const normalizedAuth = normalizeCodexAuthToLegacyAuth(auth);
109
- if (normalizedAuth?.openaiCompatible) {
110
- return pickOpenAICompatible(normalizedAuth.openaiCompatible, processEnv);
111
- }
112
- if (normalizedAuth?.openai) {
113
- return pickOpenAI({ explicit: normalizedAuth.openai, processEnv });
109
+ const suppliedEnvironment = isHarnessAuthenticationEnvironment(auth);
110
+ const authenticationEnvironment = suppliedEnvironment ? auth : processEnv;
111
+ if (auth === "direct") {
112
+ return pickOpenAI({ processEnv: authenticationEnvironment });
114
113
  }
115
114
  const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
116
- env: processEnv
115
+ env: authenticationEnvironment
117
116
  });
118
- if (normalizedAuth?.gateway) {
119
- return pickGateway({
120
- explicit: normalizedAuth.gateway,
121
- gatewayAuthFromEnv
122
- });
123
- }
124
- if (gatewayAuthFromEnv.apiKey) {
125
- return pickGateway({
126
- explicit: {},
127
- gatewayAuthFromEnv
128
- });
117
+ if (auth === "ai-gateway" || gatewayAuthFromEnv.apiKey) {
118
+ return pickGateway({ gatewayAuthFromEnv });
129
119
  }
130
- return pickOpenAI({ processEnv });
120
+ return pickOpenAI({ processEnv: authenticationEnvironment });
131
121
  }
132
122
  function resolveCodexAuthenticationMode(auth, processEnv = process.env) {
133
- if (typeof auth !== "string" && auth?.openaiCompatible) {
134
- return "direct";
123
+ if (isHarnessAuthenticationEnvironment(auth)) {
124
+ return getAiGatewayAuthFromEnv({ env: auth }).apiKey ? "ai-gateway" : "direct";
135
125
  }
136
- if (auth === "direct" || typeof auth !== "string" && auth?.openai) {
126
+ if (auth === "direct") {
137
127
  return "direct";
138
128
  }
139
- if (auth === "ai-gateway" || typeof auth !== "string" && auth?.gateway) {
129
+ if (auth === "ai-gateway") {
140
130
  return "ai-gateway";
141
131
  }
142
132
  return getAiGatewayAuthFromEnv({ env: processEnv }).apiKey ? "ai-gateway" : "direct";
143
133
  }
144
- function normalizeCodexAuthToLegacyAuth(auth) {
145
- if (auth == null || auth === "auto") {
146
- return void 0;
147
- }
148
- if (typeof auth === "string") {
149
- switch (auth) {
150
- case "direct":
151
- return { openai: {} };
152
- case "ai-gateway":
153
- return { gateway: {} };
154
- default:
155
- return void 0;
156
- }
157
- }
158
- console.warn(
159
- '[codex] Passing an object to auth options is deprecated. Use a string mode ("auto" | "direct" | "ai-gateway") instead, and pass credentials via environment variables.'
160
- );
161
- return auth;
162
- }
163
- function pickOpenAICompatible(explicit, processEnv) {
164
- const env = {};
165
- const apiKey = explicit.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;
166
- if (apiKey) env.CODEX_API_KEY = apiKey;
167
- if (explicit.baseUrl) env.OPENAI_BASE_URL = explicit.baseUrl;
168
- if (explicit.modelProviderName)
169
- env.CODEX_MODEL_PROVIDER_NAME = explicit.modelProviderName;
170
- if (explicit.queryParamsJson)
171
- env.OPENAI_QUERY_PARAMS_JSON = explicit.queryParamsJson;
172
- return env;
173
- }
174
134
  function pickOpenAI({
175
- explicit,
176
135
  processEnv
177
136
  }) {
178
137
  const env = {};
179
- const apiKey = explicit?.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;
138
+ const apiKey = processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;
180
139
  if (apiKey) env.CODEX_API_KEY = apiKey;
181
- const baseUrl = explicit?.baseUrl ?? processEnv.OPENAI_BASE_URL;
140
+ const baseUrl = processEnv.OPENAI_BASE_URL;
182
141
  if (baseUrl) env.OPENAI_BASE_URL = baseUrl;
183
- const organization = explicit?.organization ?? processEnv.OPENAI_ORGANIZATION;
142
+ const organization = processEnv.OPENAI_ORGANIZATION;
184
143
  if (organization) env.OPENAI_ORGANIZATION = organization;
185
- const project = explicit?.project ?? processEnv.OPENAI_PROJECT;
144
+ const project = processEnv.OPENAI_PROJECT;
186
145
  if (project) env.OPENAI_PROJECT = project;
187
146
  return env;
188
147
  }
189
148
  function pickGateway({
190
- explicit,
191
149
  gatewayAuthFromEnv
192
150
  }) {
193
- const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;
194
- const baseUrl = toCodexGatewayBaseUrl(
195
- explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl
196
- );
151
+ const apiKey = gatewayAuthFromEnv.apiKey;
152
+ const baseUrl = toCodexGatewayBaseUrl(gatewayAuthFromEnv.baseUrl);
197
153
  const env = {};
198
154
  if (apiKey) {
199
155
  env.AI_GATEWAY_API_KEY = apiKey;
@@ -227,7 +183,8 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
227
183
  // `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.
228
184
  // The host sources the id from lifecycle state `data` cached from a prior
229
185
  // `agent.detach`.
230
- resumeThreadId: z.string().optional()
186
+ resumeThreadId: z.string().optional(),
187
+ restartThread: z.boolean().optional()
231
188
  });
232
189
  var inboundMessageSchema = z.discriminatedUnion("type", [
233
190
  startMessageSchema,
@@ -238,7 +195,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
238
195
  var CLI_SHIM_FILENAME = "harness-tool.mjs";
239
196
 
240
197
  // src/version.ts
241
- var VERSION = true ? "1.0.93" : "0.0.0-test";
198
+ var VERSION = true ? "1.0.95" : "0.0.0-test";
242
199
 
243
200
  // src/codex-harness.ts
244
201
  var DEFAULT_CODEX_MODEL = "gpt-5.5";
@@ -265,6 +222,7 @@ var codexBridgeCoordsSchema = z2.object({
265
222
  });
266
223
  var codexResumeStateSchema = z2.object({
267
224
  threadId: z2.string().optional(),
225
+ turnConfigurationFingerprint: z2.string().optional(),
268
226
  bridge: codexBridgeCoordsSchema.optional(),
269
227
  sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional()
270
228
  });
@@ -277,6 +235,7 @@ function createCodex(settings = {}) {
277
235
  lifecycleStateSchema: codexResumeStateSchema,
278
236
  getBootstrap: getCodexBootstrap,
279
237
  doStart: async (startOpts) => {
238
+ const model = startOpts.model ?? settings.model ?? DEFAULT_CODEX_MODEL;
280
239
  if (startOpts.builtinToolFiltering != null) {
281
240
  throw new HarnessCapabilityUnsupportedError({
282
241
  message: "Harness 'codex' does not support built-in tool filtering controls.",
@@ -313,6 +272,7 @@ function createCodex(settings = {}) {
313
272
  const resumeData = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data : void 0;
314
273
  const resumeThreadId = resumeData?.threadId;
315
274
  const resumeThreadIdString = typeof resumeThreadId === "string" && resumeThreadId.length > 0 ? resumeThreadId : void 0;
275
+ const turnConfigurationFingerprint = typeof resumeData?.turnConfigurationFingerprint === "string" ? resumeData.turnConfigurationFingerprint : void 0;
316
276
  const coords = resumeData?.bridge;
317
277
  const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
318
278
  const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
@@ -351,6 +311,10 @@ function createCodex(settings = {}) {
351
311
  CODEX_BOOTSTRAP_DIR
352
312
  );
353
313
  const workDir = startOpts.sessionWorkDir;
314
+ const sandboxHomeDir = await resolveSandboxHomeDir({
315
+ sandbox: toolSafeSandboxSession,
316
+ abortSignal: startOpts.abortSignal
317
+ });
354
318
  const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
355
319
  const bridgeStateDir = `${sessionDataDir}/bridge`;
356
320
  const cliShimDir = `${sessionDataDir}/codex`;
@@ -392,7 +356,7 @@ function createCodex(settings = {}) {
392
356
  cliShimPath,
393
357
  // The live bridge was spawned by another process; no process handle.
394
358
  proc: void 0,
395
- model: settings.model ?? DEFAULT_CODEX_MODEL,
359
+ model,
396
360
  reasoningEffort: settings.reasoningEffort,
397
361
  webSearch: settings.webSearch,
398
362
  codexConfig: settings.codexConfig,
@@ -406,7 +370,10 @@ function createCodex(settings = {}) {
406
370
  sandboxId,
407
371
  sandboxCredentialEnvironment,
408
372
  debug: startOpts.observability?.debug,
409
- permissionMode: startOpts.permissionMode
373
+ permissionMode: startOpts.permissionMode,
374
+ sandbox: toolSafeSandboxSession,
375
+ sandboxHomeDir,
376
+ turnConfigurationFingerprint
410
377
  });
411
378
  } catch {
412
379
  }
@@ -433,20 +400,11 @@ function createCodex(settings = {}) {
433
400
  credentialEnvironmentVariables: CODEX_CREDENTIAL_ENVIRONMENT_VARIABLES,
434
401
  credentialForwarding: settings.credentialForwarding
435
402
  });
436
- const codexSkillSetup = startOpts.skills && startOpts.skills.length > 0 ? await writeCodexSkills({
437
- sandbox: toolSafeSandboxSession,
438
- skills: startOpts.skills,
439
- abortSignal: startOpts.abortSignal
440
- }) : void 0;
441
403
  const env = {
442
404
  ...forwardedAuthEnvironment,
443
405
  AI_SDK_HARNESS_CLIENT_APP: CODEX_CLIENT_APP,
444
406
  BRIDGE_CHANNEL_TOKEN: token,
445
407
  BRIDGE_WS_PORT: String(port),
446
- ...codexSkillSetup ? {
447
- HOME: codexSkillSetup.homeDir,
448
- CODEX_HOME: codexSkillSetup.codexHomeDir
449
- } : {},
450
408
  ...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
451
409
  };
452
410
  if (respawnStrategy === void 0) {
@@ -520,7 +478,7 @@ function createCodex(settings = {}) {
520
478
  channel,
521
479
  cliShimPath,
522
480
  proc,
523
- model: settings.model ?? DEFAULT_CODEX_MODEL,
481
+ model,
524
482
  reasoningEffort: settings.reasoningEffort,
525
483
  webSearch: settings.webSearch,
526
484
  codexConfig: settings.codexConfig,
@@ -534,7 +492,10 @@ function createCodex(settings = {}) {
534
492
  sandboxId,
535
493
  sandboxCredentialEnvironment,
536
494
  debug: startOpts.observability?.debug,
537
- permissionMode: startOpts.permissionMode
495
+ permissionMode: startOpts.permissionMode,
496
+ sandbox: toolSafeSandboxSession,
497
+ sandboxHomeDir,
498
+ turnConfigurationFingerprint
538
499
  });
539
500
  }
540
501
  };
@@ -587,17 +548,12 @@ async function resolveBridgeEndpoint({
587
548
  }
588
549
  async function writeCodexSkills({
589
550
  sandbox,
551
+ sandboxHomeDir,
590
552
  skills,
591
553
  abortSignal
592
554
  }) {
593
- const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });
594
- const codexHomeDir = path.posix.join(homeDir, ".codex");
595
- await sandbox.run({
596
- command: `mkdir -p ${shellQuote(codexHomeDir)}`,
597
- abortSignal
598
- });
599
- const rootDir = path.posix.join(homeDir, ".agents", "skills");
600
- await writeHarnessSkills({
555
+ const rootDir = path.posix.join(sandboxHomeDir, ".agents", "skills");
556
+ return writeHarnessSkills({
601
557
  sandbox,
602
558
  rootDir,
603
559
  skills,
@@ -605,10 +561,6 @@ async function writeCodexSkills({
605
561
  invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,
606
562
  invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid Codex skill file path for ${skillName}: ${filePath}`
607
563
  });
608
- return {
609
- homeDir,
610
- codexHomeDir
611
- };
612
564
  }
613
565
  function openWebSocket({
614
566
  url,
@@ -657,16 +609,44 @@ function createSession({
657
609
  sandboxId,
658
610
  sandboxCredentialEnvironment,
659
611
  debug,
660
- permissionMode
612
+ permissionMode,
613
+ sandbox,
614
+ sandboxHomeDir,
615
+ turnConfigurationFingerprint
661
616
  }) {
662
617
  let stopped = false;
663
618
  let stopPromise;
664
619
  let pendingResumeThreadId = seedResumeThreadOnFirstPrompt ? resumeThreadId : void 0;
665
620
  let initialPromptGuidanceApplied = isResume;
666
621
  let latestThreadId = resumeThreadId;
622
+ let latestTurnConfigurationFingerprint = turnConfigurationFingerprint;
667
623
  channel.on("bridge-thread", (msg) => {
668
624
  latestThreadId = msg.threadId;
669
625
  });
626
+ const synchronizeTurnConfiguration = async ({
627
+ skills,
628
+ instructions,
629
+ tools,
630
+ abortSignal
631
+ }) => {
632
+ const skillsResult = await writeCodexSkills({
633
+ sandbox,
634
+ sandboxHomeDir,
635
+ skills,
636
+ abortSignal
637
+ });
638
+ const nextFingerprint = fingerprintCodexTurnConfiguration({
639
+ instructions,
640
+ tools
641
+ });
642
+ const restartThread = latestThreadId != null && (skillsResult.changed || latestTurnConfigurationFingerprint != null && latestTurnConfigurationFingerprint !== nextFingerprint);
643
+ latestTurnConfigurationFingerprint = nextFingerprint;
644
+ if (restartThread) {
645
+ initialPromptGuidanceApplied = false;
646
+ pendingResumeThreadId = void 0;
647
+ }
648
+ return { restartThread };
649
+ };
670
650
  const wireTurn = (turnOpts) => {
671
651
  let pendingResolve;
672
652
  let pendingReject;
@@ -801,15 +781,21 @@ function createSession({
801
781
  harnessId: "codex"
802
782
  });
803
783
  }
804
- const turn = wireTurn({
805
- emit: promptOpts.emit,
806
- abortSignal: promptOpts.abortSignal
807
- });
808
784
  const tools = (promptOpts.tools ?? []).map((t) => ({
809
785
  name: t.name,
810
786
  description: t.description,
811
787
  inputSchema: t.inputSchema
812
788
  }));
789
+ const { restartThread } = await synchronizeTurnConfiguration({
790
+ skills: promptOpts.skills,
791
+ instructions: promptOpts.instructions,
792
+ tools,
793
+ abortSignal: promptOpts.abortSignal
794
+ });
795
+ const turn = wireTurn({
796
+ emit: promptOpts.emit,
797
+ abortSignal: promptOpts.abortSignal
798
+ });
813
799
  let promptText = extractUserText(promptOpts.prompt);
814
800
  if (!initialPromptGuidanceApplied) {
815
801
  promptText = frameInitialPromptGuidance({
@@ -834,6 +820,7 @@ function createSession({
834
820
  ...mcpServers == null ? {} : { mcpServers },
835
821
  ...permissionMode ? { permissionMode } : {},
836
822
  ...pendingResumeThreadId ? { resumeThreadId: pendingResumeThreadId } : {},
823
+ ...restartThread ? { restartThread: true } : {},
837
824
  ...debug ? { debug } : {}
838
825
  };
839
826
  pendingResumeThreadId = void 0;
@@ -847,6 +834,17 @@ function createSession({
847
834
  harnessId: "codex"
848
835
  });
849
836
  }
837
+ const tools = (continueOpts.tools ?? []).map((t) => ({
838
+ name: t.name,
839
+ description: t.description,
840
+ inputSchema: t.inputSchema
841
+ }));
842
+ const { restartThread } = await synchronizeTurnConfiguration({
843
+ skills: continueOpts.skills,
844
+ instructions: continueOpts.instructions,
845
+ tools,
846
+ abortSignal: continueOpts.abortSignal
847
+ });
850
848
  const turn = wireTurn({
851
849
  emit: continueOpts.emit,
852
850
  abortSignal: continueOpts.abortSignal
@@ -865,11 +863,7 @@ function createSession({
865
863
  * empty text block trips the Anthropic API's `cache_control` rule).
866
864
  */
867
865
  prompt: "Continue.",
868
- tools: (continueOpts.tools ?? []).map((t) => ({
869
- name: t.name,
870
- description: t.description,
871
- inputSchema: t.inputSchema
872
- })),
866
+ tools,
873
867
  ...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
874
868
  ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
875
869
  model,
@@ -879,6 +873,7 @@ function createSession({
879
873
  ...mcpServers == null ? {} : { mcpServers },
880
874
  ...permissionMode ? { permissionMode } : {},
881
875
  ...threadId ? { resumeThreadId: threadId } : {},
876
+ ...restartThread ? { restartThread: true } : {},
882
877
  ...debug ? { debug } : {}
883
878
  })
884
879
  );
@@ -905,6 +900,9 @@ function createSession({
905
900
  specificationVersion: "harness-v1",
906
901
  data: {
907
902
  ...latestThreadId ? { threadId: latestThreadId } : {},
903
+ ...latestTurnConfigurationFingerprint ? {
904
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
905
+ } : {},
908
906
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
909
907
  bridge: {
910
908
  port: bridgePort,
@@ -1006,6 +1004,9 @@ function createSession({
1006
1004
  specificationVersion: "harness-v1",
1007
1005
  data: {
1008
1006
  ...lifecycleData,
1007
+ ...latestTurnConfigurationFingerprint ? {
1008
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
1009
+ } : {},
1009
1010
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
1010
1011
  }
1011
1012
  };
@@ -1025,6 +1026,9 @@ function createSession({
1025
1026
  specificationVersion: "harness-v1",
1026
1027
  data: {
1027
1028
  ...latestThreadId ? { threadId: latestThreadId } : {},
1029
+ ...latestTurnConfigurationFingerprint ? {
1030
+ turnConfigurationFingerprint: latestTurnConfigurationFingerprint
1031
+ } : {},
1028
1032
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment },
1029
1033
  bridge: {
1030
1034
  port: bridgePort,
@@ -1038,6 +1042,12 @@ function createSession({
1038
1042
  }
1039
1043
  };
1040
1044
  }
1045
+ function fingerprintCodexTurnConfiguration({
1046
+ instructions,
1047
+ tools
1048
+ }) {
1049
+ return createHash("sha256").update(JSON.stringify({ instructions: instructions ?? null, tools })).digest("hex");
1050
+ }
1041
1051
  function frameInitialPromptGuidance({
1042
1052
  toolUsageBlock,
1043
1053
  userText