@jskit-ai/assistant-core 0.2.7 → 0.2.8

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 (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -0,0 +1,672 @@
1
+ import path from "node:path";
2
+ import { isPlainRecord, normalizeNullableText } from "./normalize.js";
3
+ import { codexAppServerTextHasControlCharacters } from "./codexConfiguration.js";
4
+
5
+ const DISABLED_FEATURES = Object.freeze([
6
+ "apps",
7
+ "artifact",
8
+ "browser_use",
9
+ "browser_use_external",
10
+ "browser_use_full_cdp_access",
11
+ "code_mode",
12
+ "code_mode_host",
13
+ "computer_use",
14
+ "default_mode_request_user_input",
15
+ "deferred_executor",
16
+ "goals",
17
+ "hooks",
18
+ "image_generation",
19
+ "in_app_browser",
20
+ "memories",
21
+ "multi_agent",
22
+ "multi_agent_v2",
23
+ "plugins",
24
+ "psp",
25
+ "recommended_plugins",
26
+ "request_permissions_tool",
27
+ "shell_tool",
28
+ "skill_mcp_dependency_install",
29
+ "skill_search",
30
+ "tool_call_mcp_elicitation",
31
+ "tool_suggest",
32
+ "unified_exec",
33
+ "unified_exec_zsh_fork",
34
+ "view_image"
35
+ ]);
36
+
37
+ function deepFreezeCodexAppServerHelperConfig(value) {
38
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
39
+ return value;
40
+ }
41
+ for (const nested of Object.values(value)) {
42
+ deepFreezeCodexAppServerHelperConfig(nested);
43
+ }
44
+ return Object.freeze(value);
45
+ }
46
+
47
+ function codexAppServerIsolationPolicyError(message = "", details = {}) {
48
+ const error = new Error(normalizeNullableText(message) || "Codex cannot prove the helper execution policy.");
49
+ error.code = "codex_policy_unenforceable";
50
+ error.details = Object.freeze({ ...details });
51
+ for (const [name, value] of Object.entries(error.details)) {
52
+ if (!["code", "details", "message", "name"].includes(name)) {
53
+ error[name] = value;
54
+ }
55
+ }
56
+ return error;
57
+ }
58
+
59
+ /**
60
+ * Native helper isolation verification; hosts supply their profile policy.
61
+ * @param {object} [policy]
62
+ * @param {string} [policy.clientName]
63
+ * @param {string} [policy.minimumVersion]
64
+ * @param {readonly string[]} [policy.disabledFeatures]
65
+ * @param {Record<string, number>} [policy.limits]
66
+ * @param {(message?: string, details?: Record<string, unknown>) => Error} [policy.createError]
67
+ */
68
+ export function createCodexAppServerIsolation({
69
+ clientName = "jskit",
70
+ minimumVersion = "0.151.0",
71
+ disabledFeatures = DISABLED_FEATURES,
72
+ limits: {
73
+ userAgentMaxLength = 512,
74
+ mcpServerMaxCount = 128,
75
+ mcpServerNameMaxLength = 256,
76
+ configResponseMaxBytes = 256 * 1024,
77
+ hookMaxCount = 256,
78
+ hookErrorMaxCount = 256,
79
+ hookFieldMaxLength = 2048,
80
+ hookFingerprintMaxLength = 256 * 1024,
81
+ hookResponseMaxBytes = 512 * 1024
82
+ } = {},
83
+ createError = codexAppServerIsolationPolicyError
84
+ } = {}) {
85
+ const codexAppServerHelperIsolationConfigs = new WeakSet();
86
+
87
+ function assertCodexAppServerHelperResponseBounded(value, maxBytes, label) {
88
+ let bytes = 0;
89
+ try {
90
+ bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
91
+ } catch {
92
+ throw createError(
93
+ `Codex helper execution received an invalid ${label}.`
94
+ );
95
+ }
96
+ if (bytes > maxBytes) {
97
+ throw createError(
98
+ `Codex helper execution received an oversized ${label}.`
99
+ );
100
+ }
101
+ }
102
+
103
+ function codexAppServerHelperConnectionGeneration(provider) {
104
+ // readable-check: allow method-probe - safety invariant: a provider without a connection generation would let helper policy run on an unverified connection
105
+ if (typeof provider?.currentConnectionGeneration !== "function") {
106
+ throw createError(
107
+ "Codex helper execution cannot verify the app-server connection generation."
108
+ );
109
+ }
110
+ const generation = provider.currentConnectionGeneration();
111
+ if (!Number.isSafeInteger(generation) || generation <= 0) {
112
+ throw createError(
113
+ "Codex helper execution found no active app-server connection."
114
+ );
115
+ }
116
+ return generation;
117
+ }
118
+
119
+ async function codexAppServerHelperExecutionContext(provider) {
120
+ // readable-check: allow method-probe - safety invariant: only the dedicated helper provider reports an isolated execution context
121
+ if (typeof provider?.currentHelperExecutionContext !== "function") {
122
+ throw createError(
123
+ "Codex helper execution requires its dedicated isolated provider."
124
+ );
125
+ }
126
+ const context = await provider.currentHelperExecutionContext();
127
+ const cwd = normalizeNullableText(context?.cwd);
128
+ const accountIdentitySignature = normalizeNullableText(context?.accountIdentitySignature);
129
+ if (
130
+ context?.executionMode !== "helper" ||
131
+ !cwd ||
132
+ !path.isAbsolute(cwd) ||
133
+ !/^sha256:[a-f0-9]{64}$/u.test(accountIdentitySignature)
134
+ ) {
135
+ throw createError(
136
+ "Codex helper runtime isolation could not be verified."
137
+ );
138
+ }
139
+ return Object.freeze({
140
+ accountIdentitySignature,
141
+ cwd,
142
+ executionMode: "helper"
143
+ });
144
+ }
145
+
146
+ function codexAppServerSemanticVersionParts(value = "") {
147
+ const match = normalizeNullableText(value).match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u);
148
+ if (!match) {
149
+ return null;
150
+ }
151
+ const parts = match.slice(1).map((part) => Number.parseInt(part, 10));
152
+ return parts.every(Number.isSafeInteger) ? parts : null;
153
+ }
154
+
155
+ function codexAppServerUserAgentVersionParts(value = "") {
156
+ const userAgent = normalizeNullableText(value);
157
+ if (
158
+ !userAgent ||
159
+ userAgent.length > userAgentMaxLength ||
160
+ codexAppServerTextHasControlCharacters(userAgent)
161
+ ) {
162
+ return null;
163
+ }
164
+ const prefix = `${clientName}/`;
165
+ if (!userAgent.startsWith(prefix)) {
166
+ return null;
167
+ }
168
+ const match = userAgent.slice(prefix.length).match(/^((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))(?:$|[ \t].*$)/u);
169
+ return match ? codexAppServerSemanticVersionParts(match[1]) : null;
170
+ }
171
+
172
+ function assertCodexAppServerHelperCompatibility(provider) {
173
+ // readable-check: allow method-probe - safety invariant: a provider that cannot report its app-server version would run helpers on an unchecked version
174
+ if (typeof provider?.currentServerInfo !== "function") {
175
+ throw createError(
176
+ "Codex helper execution cannot verify the app-server version. Update Codex and retry.",
177
+ { minimumVersion }
178
+ );
179
+ }
180
+ const userAgent = normalizeNullableText(provider.currentServerInfo()?.userAgent);
181
+ const actualParts = codexAppServerUserAgentVersionParts(userAgent);
182
+ if (!actualParts) {
183
+ throw createError(
184
+ "Codex helper execution received an unrecognised app-server version. Update Codex and retry.",
185
+ {
186
+ minimumVersion
187
+ }
188
+ );
189
+ }
190
+ const actualVersion = actualParts.join(".");
191
+ const minimumParts = codexAppServerSemanticVersionParts(minimumVersion);
192
+ const differingPart = actualParts.findIndex((part, index) => part !== minimumParts[index]);
193
+ if (differingPart !== -1 && actualParts[differingPart] < minimumParts[differingPart]) {
194
+ throw createError(
195
+ `Codex helper execution requires app-server ${minimumVersion} or newer; current version is ${actualVersion}. Update Codex and retry.`,
196
+ {
197
+ actualVersion,
198
+ minimumVersion
199
+ }
200
+ );
201
+ }
202
+ return Object.freeze({
203
+ minimumVersion,
204
+ version: actualVersion
205
+ });
206
+ }
207
+
208
+ function codexAppServerHelperMcpServerNames(configResult = null) {
209
+ if (!isPlainRecord(configResult?.config)) {
210
+ throw createError(
211
+ "Codex helper execution could not read the effective app-server configuration."
212
+ );
213
+ }
214
+ const servers = configResult.config.mcp_servers;
215
+ if (servers === undefined || servers === null) {
216
+ return [];
217
+ }
218
+ // Only the MCP inventory participates in this isolation check. The native
219
+ // response can also contain large model catalogues and configuration layers.
220
+ assertCodexAppServerHelperResponseBounded(
221
+ servers,
222
+ configResponseMaxBytes,
223
+ "MCP configuration"
224
+ );
225
+ if (!isPlainRecord(servers)) {
226
+ throw createError(
227
+ "Codex helper execution received an invalid MCP server configuration."
228
+ );
229
+ }
230
+ const names = Object.keys(servers);
231
+ if (
232
+ names.length > mcpServerMaxCount ||
233
+ names.some((name) => (
234
+ !name ||
235
+ name.length > mcpServerNameMaxLength ||
236
+ codexAppServerTextHasControlCharacters(name)
237
+ ))
238
+ ) {
239
+ throw createError(
240
+ "Codex helper execution received an oversized or invalid MCP server inventory."
241
+ );
242
+ }
243
+ return names.sort();
244
+ }
245
+
246
+ function codexAppServerHelperHookState(result = null, cwd = "") {
247
+ assertCodexAppServerHelperResponseBounded(
248
+ result,
249
+ hookResponseMaxBytes,
250
+ "hook inventory"
251
+ );
252
+ if (!Array.isArray(result?.data)) {
253
+ throw createError(
254
+ "Codex helper execution could not enumerate app-server hooks."
255
+ );
256
+ }
257
+ if (result.data.length !== 1) {
258
+ throw createError(
259
+ "Codex helper execution received an unexpected hook inventory."
260
+ );
261
+ }
262
+ const record = result.data[0];
263
+ if (
264
+ normalizeNullableText(record?.cwd) !== cwd ||
265
+ normalizeNullableText(record?.cwd).length > hookFieldMaxLength ||
266
+ !Array.isArray(record.hooks) ||
267
+ !Array.isArray(record.errors) ||
268
+ record.hooks.length > hookMaxCount ||
269
+ record.errors.length > hookErrorMaxCount
270
+ ) {
271
+ throw createError(
272
+ "Codex helper execution received an incomplete hook inventory."
273
+ );
274
+ }
275
+ if (record.errors.length > 0) {
276
+ throw createError(
277
+ "Codex helper execution cannot continue while hook discovery has errors.",
278
+ { hookErrorCount: record.errors.length }
279
+ );
280
+ }
281
+ const hooks = record.hooks.map((hook) => {
282
+ const key = normalizeNullableText(hook?.key);
283
+ const currentHash = normalizeNullableText(hook?.currentHash);
284
+ const handlerType = normalizeNullableText(hook?.handlerType);
285
+ const sourcePath = normalizeNullableText(hook?.sourcePath);
286
+ if (
287
+ !key ||
288
+ [key, currentHash, handlerType, sourcePath].some((field) => (
289
+ field.length > hookFieldMaxLength ||
290
+ codexAppServerTextHasControlCharacters(field)
291
+ ))
292
+ ) {
293
+ throw createError(
294
+ "Codex helper execution found an invalid hook inventory entry."
295
+ );
296
+ }
297
+ if (hook?.isManaged === true && hook?.enabled === true) {
298
+ throw createError(
299
+ "Codex helper execution cannot disable a managed hook."
300
+ );
301
+ }
302
+ return {
303
+ currentHash,
304
+ enabled: hook?.enabled === true,
305
+ handlerType,
306
+ isManaged: hook?.isManaged === true,
307
+ key,
308
+ sourcePath
309
+ };
310
+ }).sort((left, right) => left.key.localeCompare(right.key));
311
+ const fingerprint = JSON.stringify(hooks);
312
+ if (fingerprint.length > hookFingerprintMaxLength) {
313
+ throw createError(
314
+ "Codex helper execution received an oversized hook inventory."
315
+ );
316
+ }
317
+ return {
318
+ fingerprint,
319
+ hookKeys: hooks.filter((hook) => !hook.isManaged).map((hook) => hook.key)
320
+ };
321
+ }
322
+
323
+ function codexAppServerHelperIsolationConfig({
324
+ effort = "",
325
+ summary = "",
326
+ hookKeys = [],
327
+ mcpServerNames = []
328
+ } = {}) {
329
+ const config = {
330
+ features: Object.fromEntries(
331
+ disabledFeatures.map((feature) => [feature, false])
332
+ ),
333
+ hooks: {
334
+ state: Object.fromEntries(hookKeys.map((key) => [key, { enabled: false }]))
335
+ },
336
+ include_apps_instructions: false,
337
+ include_collaboration_mode_instructions: false,
338
+ include_environment_context: false,
339
+ include_permissions_instructions: false,
340
+ ...(effort ? { model_reasoning_effort: effort } : {}),
341
+ ...(summary ? { model_reasoning_summary: summary } : {}),
342
+ mcp_servers: Object.fromEntries(
343
+ mcpServerNames.map((name) => [name, { enabled: false }])
344
+ ),
345
+ memories: {
346
+ dedicated_tools: false,
347
+ generate_memories: false,
348
+ use_memories: false
349
+ },
350
+ notify: [],
351
+ orchestrator: {
352
+ mcp: {
353
+ enabled: false
354
+ },
355
+ skills: {
356
+ enabled: false
357
+ }
358
+ },
359
+ project_doc_max_bytes: 0,
360
+ shell_environment_policy: {
361
+ inherit: "none",
362
+ set: {}
363
+ },
364
+ skills: {
365
+ include_instructions: false
366
+ },
367
+ tools: {
368
+ experimental_request_user_input: {
369
+ enabled: false
370
+ },
371
+ update_plan: {
372
+ enabled: false
373
+ }
374
+ },
375
+ web_search: "disabled"
376
+ };
377
+ const frozenConfig = deepFreezeCodexAppServerHelperConfig(config);
378
+ codexAppServerHelperIsolationConfigs.add(frozenConfig);
379
+ return frozenConfig;
380
+ }
381
+
382
+ function isolationConfigFrom(configResult, hookResult, cwd, { effort = "", summary = "" } = {}) {
383
+ const mcpServerNames = codexAppServerHelperMcpServerNames(configResult);
384
+ const hookState = codexAppServerHelperHookState(hookResult, cwd);
385
+ return {
386
+ mcpServerNames,
387
+ hookState,
388
+ config: codexAppServerHelperIsolationConfig({
389
+ effort,
390
+ summary,
391
+ hookKeys: hookState.hookKeys,
392
+ mcpServerNames
393
+ })
394
+ };
395
+ }
396
+
397
+ async function codexAppServerHelperIsolationState(provider, { effort = "", summary = "" } = {}) {
398
+ assertCodexAppServerHelperCompatibility(provider);
399
+ // readable-check: allow method-probe - safety invariant: without configuration and hook inventories the helper surface cannot be proved tool-free
400
+ if (typeof provider?.readConfig !== "function" || typeof provider?.listHooks !== "function") {
401
+ throw createError(
402
+ "Codex helper execution cannot inventory configuration and hooks."
403
+ );
404
+ }
405
+ const executionContext = await codexAppServerHelperExecutionContext(provider);
406
+ const generation = codexAppServerHelperConnectionGeneration(provider);
407
+ const configResult = await provider.readConfig({
408
+ cwd: executionContext.cwd,
409
+ includeLayers: false
410
+ });
411
+ if (codexAppServerHelperConnectionGeneration(provider) !== generation) {
412
+ throw createError(
413
+ "Codex app-server reconnected during helper policy verification."
414
+ );
415
+ }
416
+ const hookResult = await provider.listHooks([executionContext.cwd]);
417
+ if (codexAppServerHelperConnectionGeneration(provider) !== generation) {
418
+ throw createError(
419
+ "Codex app-server reconnected during helper policy verification."
420
+ );
421
+ }
422
+ const { mcpServerNames, hookState, config } = isolationConfigFrom(configResult, hookResult, executionContext.cwd, {
423
+ effort,
424
+ summary
425
+ });
426
+ return Object.freeze({
427
+ accountIdentitySignature: executionContext.accountIdentitySignature,
428
+ config,
429
+ connectionGeneration: generation,
430
+ executionCwd: executionContext.cwd,
431
+ hookFingerprint: hookState.fingerprint,
432
+ hookKeys: Object.freeze([...hookState.hookKeys]),
433
+ mcpServerNames: Object.freeze([...mcpServerNames])
434
+ });
435
+ }
436
+
437
+ function codexAppServerHelperIsolationMatches(left = null, right = null) {
438
+ return left?.accountIdentitySignature === right?.accountIdentitySignature &&
439
+ left?.connectionGeneration === right?.connectionGeneration &&
440
+ left?.executionCwd === right?.executionCwd &&
441
+ left?.hookFingerprint === right?.hookFingerprint &&
442
+ JSON.stringify(left?.mcpServerNames || []) === JSON.stringify(right?.mcpServerNames || []);
443
+ }
444
+
445
+ function codexAppServerHelperVerificationFailure(error, cleanupError, threadId = "") {
446
+ const normalizedThreadId = normalizeNullableText(threadId);
447
+ if (!cleanupError) {
448
+ error.codexAppServerHelperThreadId = normalizedThreadId;
449
+ error.codexAppServerHelperThreadRetired = true;
450
+ return error;
451
+ }
452
+ const failure = createError(
453
+ "Codex could not retire a helper thread after policy verification failed.",
454
+ {
455
+ cleanupFailed: true,
456
+ threadId: normalizedThreadId
457
+ }
458
+ );
459
+ failure.codexAppServerHelperThreadCleanupRequired = true;
460
+ failure.codexAppServerHelperThreadId = normalizedThreadId;
461
+ return failure;
462
+ }
463
+
464
+ async function throwAfterCodexAppServerHelperVerificationFailure({
465
+ error = null,
466
+ provider = null,
467
+ threadId = ""
468
+ } = {}) {
469
+ let cleanupError = null;
470
+ try {
471
+ await provider.deleteThread(threadId);
472
+ } catch (caught) {
473
+ cleanupError = caught;
474
+ }
475
+ throw codexAppServerHelperVerificationFailure(error, cleanupError, threadId);
476
+ }
477
+
478
+ // Re-inspect after the native thread exists; a changed surface retires it.
479
+ async function verifyHelperThread(provider, enforcement, thread, threadId, phase) {
480
+ try {
481
+ const verified = await codexAppServerHelperIsolationState(
482
+ provider,
483
+ {
484
+ effort: enforcement.config.model_reasoning_effort,
485
+ summary: enforcement.config.model_reasoning_summary
486
+ }
487
+ );
488
+ if (!codexAppServerHelperIsolationMatches(enforcement, verified)) {
489
+ throw createError(
490
+ `Codex execution surfaces changed while the helper thread was ${phase}.`
491
+ );
492
+ }
493
+ return Object.freeze({
494
+ enforcement: verified,
495
+ thread,
496
+ threadId
497
+ });
498
+ } catch (error) {
499
+ await throwAfterCodexAppServerHelperVerificationFailure({
500
+ error,
501
+ provider,
502
+ threadId
503
+ });
504
+ }
505
+ }
506
+
507
+ async function startCodexAppServerHelperThread(provider, prepare) {
508
+ // readable-check: allow method-probe - safety invariant: a helper thread that cannot be deleted would outlive a failed policy verification
509
+ if (typeof provider?.startThread !== "function" || typeof provider?.deleteThread !== "function") {
510
+ throw createError(
511
+ "Codex helper execution cannot own and clean up its app-server thread."
512
+ );
513
+ }
514
+ const prepared = await prepare();
515
+ const thread = await provider.startThread(prepared.settings);
516
+ const threadId = normalizeNullableText(thread?.id);
517
+ if (!threadId) {
518
+ throw createError(
519
+ "Codex app-server did not return a helper thread id."
520
+ );
521
+ }
522
+ return verifyHelperThread(provider, prepared.enforcement, thread, threadId, "starting");
523
+ }
524
+
525
+ async function resumeCodexAppServerHelperThread(provider, threadId, prepare) {
526
+ // readable-check: allow method-probe - safety invariant: a resumed helper thread that cannot be deleted would outlive a failed policy verification
527
+ if (typeof provider?.resumeThread !== "function" || typeof provider?.deleteThread !== "function") {
528
+ throw createError(
529
+ "Codex helper execution cannot safely resume and clean up its app-server thread."
530
+ );
531
+ }
532
+ const normalizedThreadId = normalizeNullableText(threadId);
533
+ if (!normalizedThreadId) {
534
+ throw createError(
535
+ "Codex helper resume requires a controller-owned thread id."
536
+ );
537
+ }
538
+ const prepared = await prepare();
539
+ const enforcement = prepared.enforcement;
540
+ const thread = await provider.resumeThread(
541
+ normalizedThreadId,
542
+ prepared.settings
543
+ );
544
+ return verifyHelperThread(provider, enforcement, thread, normalizedThreadId, "resuming");
545
+ }
546
+
547
+ function hasConfiguration(config) {
548
+ return codexAppServerHelperIsolationConfigs.has(config);
549
+ }
550
+
551
+ function configuration({ configResult, hookResult, workdir, effort = "", summary = "" }) {
552
+ return isolationConfigFrom(configResult, hookResult, workdir, { effort, summary }).config;
553
+ }
554
+
555
+ // Native Helper requests retain their verified configuration object and the
556
+ // distinct Start/Resume field sets. Profile policy is validated by the host.
557
+ function codexAppServerHelperThreadSettings({
558
+ approvalPolicy,
559
+ baseInstructions,
560
+ config,
561
+ cwd,
562
+ effectiveSettings,
563
+ sandbox,
564
+ systemPrompt
565
+ }) {
566
+ return {
567
+ allowProviderModelFallback: false,
568
+ approvalPolicy,
569
+ baseInstructions,
570
+ config,
571
+ cwd,
572
+ systemPrompt: systemPrompt || null,
573
+ dynamicTools: [],
574
+ environments: [],
575
+ model: effectiveSettings.model,
576
+ runtimeWorkspaceRoots: [],
577
+ sandbox,
578
+ selectedCapabilityRoots: []
579
+ };
580
+ }
581
+
582
+ function codexAppServerHelperThreadStartSettings(settings, threadSource) {
583
+ return {
584
+ ...settings,
585
+ sessionStartSource: "startup",
586
+ threadSource
587
+ };
588
+ }
589
+
590
+ function codexAppServerHelperThreadResumeSettings(settings) {
591
+ return {
592
+ approvalPolicy: settings.approvalPolicy,
593
+ baseInstructions: settings.baseInstructions,
594
+ config: settings.config,
595
+ cwd: settings.cwd,
596
+ systemPrompt: settings.systemPrompt,
597
+ model: settings.model,
598
+ runtimeWorkspaceRoots: settings.runtimeWorkspaceRoots,
599
+ sandbox: settings.sandbox
600
+ };
601
+ }
602
+
603
+ function codexAppServerHelperTurnSettings({
604
+ approvalPolicy,
605
+ cwd,
606
+ effectiveSettings,
607
+ outputSchema
608
+ }) {
609
+ const settings = {
610
+ approvalPolicy,
611
+ cwd,
612
+ environments: [],
613
+ model: effectiveSettings.model,
614
+ outputSchema,
615
+ runtimeWorkspaceRoots: [],
616
+ sandboxPolicy: {
617
+ networkAccess: false,
618
+ type: "readOnly"
619
+ },
620
+ summary: "none"
621
+ };
622
+ if (effectiveSettings.request.reasoning) {
623
+ settings.effort = effectiveSettings.thinking;
624
+ }
625
+ return settings;
626
+ }
627
+
628
+ return Object.freeze({
629
+ assertCompatibility: assertCodexAppServerHelperCompatibility,
630
+ executionContext: codexAppServerHelperExecutionContext,
631
+ inspect: codexAppServerHelperIsolationState,
632
+ configuration,
633
+ hasConfiguration,
634
+ threadSettings: codexAppServerHelperThreadSettings,
635
+ threadStartSettings: codexAppServerHelperThreadStartSettings,
636
+ threadResumeSettings: codexAppServerHelperThreadResumeSettings,
637
+ turnSettings: codexAppServerHelperTurnSettings,
638
+ start: startCodexAppServerHelperThread,
639
+ resume: resumeCodexAppServerHelperThread
640
+ });
641
+ }
642
+
643
+ const codexConversationIsolation = createCodexAppServerIsolation();
644
+
645
+ /** Disable ambient tools while allowing the caller to add its authorized tools. */
646
+ export function codexToolFreeConfiguration(options) {
647
+ return structuredClone(codexConversationIsolation.configuration(options));
648
+ }
649
+
650
+ export async function readCodexToolFreeConfiguration(
651
+ provider = null,
652
+ workdir = "",
653
+ assertCompatibility = codexConversationIsolation.assertCompatibility
654
+ ) {
655
+ assertCompatibility(provider);
656
+ // readable-check: allow method-probe - safety invariant: without these inventories an ephemeral conversation cannot be proved tool-free
657
+ if (typeof provider?.readConfig !== "function" || typeof provider?.listHooks !== "function" || typeof provider?.currentConnectionGeneration !== "function") {
658
+ throw new Error("Codex cannot verify tool isolation for this ephemeral conversation.");
659
+ }
660
+ const connectionGeneration = provider.currentConnectionGeneration();
661
+ if (!Number.isSafeInteger(connectionGeneration) || connectionGeneration <= 0) {
662
+ throw new Error("Codex has no active connection for this ephemeral conversation.");
663
+ }
664
+ const [configResult, hookResult] = await Promise.all([
665
+ provider.readConfig({ cwd: workdir, includeLayers: false }),
666
+ provider.listHooks([workdir])
667
+ ]);
668
+ if (provider.currentConnectionGeneration() !== connectionGeneration) {
669
+ throw new Error("Codex reconnected while verifying this ephemeral conversation.");
670
+ }
671
+ return codexToolFreeConfiguration({ configResult, hookResult, workdir });
672
+ }