@jskit-ai/assistant-core 0.2.6 → 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 +18 -12
  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,324 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { normalizeNullableText } from "./normalize.js";
3
+ import {
4
+ CODEX_APP_SERVER_METADATA_SCHEMA_VERSION, CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS, CODEX_APP_SERVER_PROCESS_STATE,
5
+ CODEX_APP_SERVER_PROVIDER_ID, acquireCodexRuntimeLock, codexAppServerHelperHomeDir, codexAppServerHelperWorkspaceDir,
6
+ codexAppServerLogPath, codexAppServerMetadataPath, codexAppServerRuntimeCleanupCanSkip, codexAppServerRuntimeDirIsManaged,
7
+ codexAppServerRuntimeIdentity, codexAppServerSocketPath, readCodexAppServerMetadata, removeCodexAppServerMetadataTemps,
8
+ removeCodexAppServerRuntimeDir, stableHash, writeCodexAppServerMetadata
9
+ } from "./codexProcess.js";
10
+
11
+ /** @typedef {ReturnType<typeof createCodexAppServerRuntimeStop>} CodexRuntimeStop */
12
+
13
+ /**
14
+ * Stops a native runtime's owned process and removes what it left behind.
15
+ * @param {object} dependencies
16
+ * @param {import("./codexRuntime.js").CodexRuntimeExecution} dependencies.execution
17
+ * @param {import("./codexRuntime.js").CodexProcessIdentity} dependencies.codexProcessIdentity
18
+ * @param {string} dependencies.errorPrefix
19
+ */
20
+ export function createCodexAppServerRuntimeStop({ execution, codexProcessIdentity, errorPrefix }) {
21
+
22
+ async function acquireRuntimeLock(runtimeDir = "", options = {}) {
23
+ // readable-check: allow optional-call - optional operation: managed execution hosts add prepare; the local one has none
24
+ await execution.prepare?.();
25
+ return acquireCodexRuntimeLock(runtimeDir, { ...options, busyCode: `${errorPrefix}codex_app_server_runtime_busy` });
26
+ }
27
+
28
+ function codexAppServerProcessMetadataIsIdentifiable(metadata = {}, runtimeDir = "") {
29
+ const normalizedRuntimeDir = normalizeNullableText(runtimeDir);
30
+ return Boolean(
31
+ Number(metadata.schemaVersion) === CODEX_APP_SERVER_METADATA_SCHEMA_VERSION &&
32
+ metadata.provider === CODEX_APP_SERVER_PROVIDER_ID &&
33
+ Number.isSafeInteger(Number(metadata.pid)) && Number(metadata.pid) > 0 &&
34
+ (!normalizedRuntimeDir || metadata.runtimeDir === normalizedRuntimeDir) &&
35
+ codexProcessIdentity.isValid(metadata.processIdentity) &&
36
+ [
37
+ CODEX_APP_SERVER_PROCESS_STATE.RUNNING,
38
+ CODEX_APP_SERVER_PROCESS_STATE.STOPPED
39
+ ].includes(metadata.processState)
40
+ );
41
+ }
42
+
43
+ async function inspectIdentifiableCodexAppServerProcess(metadata = {}) {
44
+ if (!codexAppServerProcessMetadataIsIdentifiable(metadata)) {
45
+ return { processGroupIds: [], status: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID };
46
+ }
47
+ return codexProcessIdentity.inspect(metadata);
48
+ }
49
+
50
+ // A runtime caller may inspect the process identity its own way; every answer is normalized.
51
+ async function inspectCodexAppServerProcessIdentity(metadata = {}, options = {}) {
52
+ const { processIdentityInspector = inspectIdentifiableCodexAppServerProcess } = options;
53
+ return codexProcessIdentity.normalizeInspection(await processIdentityInspector(metadata));
54
+ }
55
+
56
+ async function stopCodexAppServerProcessGroup(metadata = {}, options = {}) {
57
+ if (!codexAppServerProcessMetadataIsIdentifiable(metadata)) {
58
+ return {
59
+ identityStatus: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID,
60
+ processExitVerified: false,
61
+ stopped: false
62
+ };
63
+ }
64
+ return codexProcessIdentity.stop(metadata, {
65
+ ...options,
66
+ processIdentityInspector: (current) => inspectCodexAppServerProcessIdentity(current, options)
67
+ });
68
+ }
69
+
70
+ async function stopOwnedCodexAppServerExecution(metadata = {}, options = {}) {
71
+ if (
72
+ metadata.processState === CODEX_APP_SERVER_PROCESS_STATE.STOPPED &&
73
+ metadata.processExitVerifiedAt
74
+ ) {
75
+ return stopCodexAppServerProcessGroup(metadata, options);
76
+ }
77
+ const executionId = normalizeNullableText(metadata.executionId);
78
+ if (executionId) {
79
+ const { stopExecution = execution.stop } = options;
80
+ let executionStop;
81
+ try {
82
+ executionStop = await stopExecution(executionId, {
83
+ allowMissingRecordScopeRecovery: true,
84
+ killTimeoutMs: options.killTimeoutMs,
85
+ reason: options.reason || "codex-app-server-stop",
86
+ termTimeoutMs: options.termTimeoutMs
87
+ });
88
+ } catch (error) {
89
+ return {
90
+ error: String(error?.message || error || "The owned execution could not be stopped."),
91
+ executionId,
92
+ processExitVerified: false,
93
+ scopeEmpty: false,
94
+ stopped: false
95
+ };
96
+ }
97
+ if (executionStop?.ok === true && executionStop?.scopeEmpty === true) {
98
+ return {
99
+ ...executionStop,
100
+ executionId,
101
+ processExitVerified: true,
102
+ stopped: executionStop.stopped === true
103
+ };
104
+ }
105
+ // A runtime caller's own execution stop is authoritative for its scope, as an authoritative facility is.
106
+ if (stopExecution !== execution.stop || execution.authoritative) {
107
+ return {
108
+ ...(executionStop && typeof executionStop === "object" ? executionStop : {}),
109
+ executionId,
110
+ processExitVerified: false,
111
+ scopeEmpty: false,
112
+ stopped: false
113
+ };
114
+ }
115
+ }
116
+ return stopCodexAppServerProcessGroup(metadata, options);
117
+ }
118
+
119
+ async function stopCodexAppServerProcess(runtimeDir = "", options = {}) {
120
+ const normalizedRuntimeDir = normalizeNullableText(runtimeDir);
121
+ if (!normalizedRuntimeDir) {
122
+ return {
123
+ processExitVerified: false,
124
+ stopped: false
125
+ };
126
+ }
127
+ const metadata = await readCodexAppServerMetadata(normalizedRuntimeDir);
128
+ if (!codexAppServerProcessMetadataIsIdentifiable(metadata, normalizedRuntimeDir)) {
129
+ return {
130
+ identityStatus: CODEX_APP_SERVER_PROCESS_IDENTITY_STATUS.INVALID,
131
+ processExitVerified: false,
132
+ stopped: false
133
+ };
134
+ }
135
+ return stopOwnedCodexAppServerExecution(metadata, options);
136
+ }
137
+
138
+ async function cleanupFailedCodexAppServerStart(runtimeDir = "", {
139
+ helper = false,
140
+ executionId = "",
141
+ neverStarted = false,
142
+ pid = null,
143
+ processIdentity = null
144
+ } = {}, options = {}) {
145
+ let processStop = {
146
+ processExitVerified: neverStarted === true,
147
+ scopeEmpty: neverStarted === true,
148
+ stopped: false
149
+ };
150
+ let processCleanupFailed = false;
151
+ if (!neverStarted) {
152
+ try {
153
+ processStop = await stopOwnedCodexAppServerExecution({
154
+ executionId: normalizeNullableText(executionId),
155
+ pid: Number(pid),
156
+ processIdentity,
157
+ processState: CODEX_APP_SERVER_PROCESS_STATE.RUNNING,
158
+ provider: CODEX_APP_SERVER_PROVIDER_ID,
159
+ runtimeDir: normalizeNullableText(runtimeDir),
160
+ schemaVersion: CODEX_APP_SERVER_METADATA_SCHEMA_VERSION
161
+ }, options);
162
+ } catch {
163
+ processCleanupFailed = true;
164
+ }
165
+ }
166
+ if (processCleanupFailed || processStop.processExitVerified !== true) {
167
+ return {
168
+ ...processStop,
169
+ cleanupFailed: true
170
+ };
171
+ }
172
+ const helperRemovals = helper
173
+ ? [
174
+ rm(codexAppServerHelperHomeDir(runtimeDir), { force: true, recursive: true }),
175
+ rm(codexAppServerHelperWorkspaceDir(runtimeDir), { force: true, recursive: true }),
176
+ rm(codexAppServerLogPath(runtimeDir), { force: true })
177
+ ]
178
+ : [];
179
+ const removals = await Promise.allSettled([
180
+ ...helperRemovals,
181
+ rm(codexAppServerSocketPath(runtimeDir), { force: true }),
182
+ rm(codexAppServerMetadataPath(runtimeDir), { force: true }),
183
+ removeCodexAppServerMetadataTemps(runtimeDir)
184
+ ]);
185
+ return {
186
+ ...processStop,
187
+ cleanupFailed: removals.some(({ status }) => status === "rejected")
188
+ };
189
+ }
190
+
191
+ async function stopCodexAppServerRuntime(options = {}) {
192
+ const runtimeDir = normalizeNullableText(options.runtimeDir);
193
+ const preserveProcessExitProof = options.preserveProcessExitProof === true;
194
+ if (!runtimeDir || !codexAppServerRuntimeDirIsManaged(runtimeDir)) {
195
+ return {
196
+ processExitVerified: false,
197
+ runtimeDirPreserved: false,
198
+ runtimeDirRemoved: false,
199
+ stopped: false
200
+ };
201
+ }
202
+ let releaseLock;
203
+ try {
204
+ releaseLock = await acquireRuntimeLock(runtimeDir, options);
205
+ } catch (error) {
206
+ if (!codexAppServerRuntimeCleanupCanSkip(error)) {
207
+ throw error;
208
+ }
209
+ return {
210
+ processExitVerified: false,
211
+ runtimeDirCleanupError: String(error?.message || error || ""),
212
+ runtimeDirCleanupSkipped: String(error?.code || "") !== "ENOENT",
213
+ runtimeDirPreserved: false,
214
+ runtimeDirRemoved: false,
215
+ stopped: false
216
+ };
217
+ }
218
+ let runtimeDirRemoved = false;
219
+ let runtimeDirCleanupSkipped = false;
220
+ let runtimeDirCleanupError = "";
221
+ let runtimeDirPreserved = false;
222
+ let processStop = {
223
+ processExitVerified: false,
224
+ stopped: false
225
+ };
226
+ try {
227
+ const existing = await readCodexAppServerMetadata(runtimeDir);
228
+ const expectedAccount = normalizeNullableText(options.expectedAccountIdentitySignature);
229
+ const ownedRuntime = options.ownedRuntime;
230
+ if (
231
+ ownedRuntime &&
232
+ codexAppServerProcessMetadataIsIdentifiable(ownedRuntime, runtimeDir) &&
233
+ (!expectedAccount || ownedRuntime.accountIdentitySignature === expectedAccount) &&
234
+ (!existing || codexAppServerRuntimeIdentity(existing) !== codexAppServerRuntimeIdentity(ownedRuntime))
235
+ ) {
236
+ // Another owner may already have removed or replaced the shared runtime.
237
+ // Prove this provider's exact execution stopped without touching its successor.
238
+ return {
239
+ ...await stopOwnedCodexAppServerExecution(ownedRuntime, options),
240
+ runtimeDirPreserved: Boolean(existing),
241
+ runtimeDirRemoved: false
242
+ };
243
+ }
244
+ if (!existing) {
245
+ // Changeover after a server restart may have no runtime.json left. The
246
+ // managed execution owner can still drain and prove its exact scope empty.
247
+ // A missing file alone is never process-exit proof, and account-specific
248
+ // cleanup must keep using its retained execution identity above.
249
+ const owned = options.verifyOwnerScope === true && !expectedAccount
250
+ ? await execution.stopOwned({
251
+ kind: "assistant",
252
+ operationId: "codex-app-server",
253
+ ownerId: normalizeNullableText(options.ownerId) || stableHash(runtimeDir)
254
+ }, { reason: "codex-app-server-changeover" })
255
+ : null;
256
+ const ownerExitVerified = owned?.supported === true && owned?.ok === true && owned?.scopeEmpty === true;
257
+ return {
258
+ processExitVerified: ownerExitVerified,
259
+ runtimeDirPreserved: false,
260
+ runtimeDirRemoved: false,
261
+ stopped: ownerExitVerified && Number(owned?.closed) > 0
262
+ };
263
+ }
264
+ if (expectedAccount) {
265
+ // A replacement runtime is installed only after the previous process has
266
+ // drained under this same lock. Never stop that replacement for stale
267
+ // ephemeral-thread cleanup.
268
+ if (!codexAppServerProcessMetadataIsIdentifiable(existing, runtimeDir) ||
269
+ !/^sha256:[a-f0-9]{64}$/u.test(existing.accountIdentitySignature)) {
270
+ return { processExitVerified: false, runtimeDirRemoved: false, stopped: false };
271
+ }
272
+ if (existing.accountIdentitySignature !== expectedAccount) {
273
+ return { ownershipSuperseded: true, processExitVerified: false, runtimeDirRemoved: false, stopped: false };
274
+ }
275
+ }
276
+ processStop = await stopCodexAppServerProcess(runtimeDir, options);
277
+ if (processStop.processExitVerified === true && preserveProcessExitProof) {
278
+ const metadata = await readCodexAppServerMetadata(runtimeDir);
279
+ if (!codexAppServerProcessMetadataIsIdentifiable(metadata, runtimeDir)) {
280
+ return {
281
+ ...processStop,
282
+ processExitVerified: false,
283
+ runtimeDirPreserved: false,
284
+ runtimeDirRemoved: false
285
+ };
286
+ }
287
+ await writeCodexAppServerMetadata(runtimeDir, {
288
+ ...metadata,
289
+ processExitVerifiedAt: metadata.processExitVerifiedAt || new Date().toISOString(),
290
+ processState: CODEX_APP_SERVER_PROCESS_STATE.STOPPED
291
+ });
292
+ runtimeDirPreserved = true;
293
+ } else if (processStop.processExitVerified === true) {
294
+ try {
295
+ runtimeDirRemoved = await removeCodexAppServerRuntimeDir(runtimeDir);
296
+ } catch (error) {
297
+ if (!codexAppServerRuntimeCleanupCanSkip(error)) {
298
+ throw error;
299
+ }
300
+ runtimeDirCleanupSkipped = true;
301
+ runtimeDirCleanupError = String(error?.message || error || "");
302
+ }
303
+ }
304
+ } finally {
305
+ await releaseLock();
306
+ }
307
+ return {
308
+ ...processStop,
309
+ runtimeDirCleanupError,
310
+ runtimeDirCleanupSkipped,
311
+ runtimeDirPreserved,
312
+ runtimeDirRemoved
313
+ };
314
+ }
315
+
316
+ return Object.freeze({
317
+ acquireRuntimeLock,
318
+ processMetadataIsIdentifiable: codexAppServerProcessMetadataIsIdentifiable,
319
+ inspectProcessIdentity: inspectCodexAppServerProcessIdentity,
320
+ stopProcess: stopCodexAppServerProcess,
321
+ cleanupFailedStart: cleanupFailedCodexAppServerStart,
322
+ stopRuntime: stopCodexAppServerRuntime
323
+ });
324
+ }
@@ -1,9 +1,21 @@
1
- import { normalizeCodexRunText } from "./codexTurnState.js";
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
2
 
3
- // Cleanup of the existing run/provider/Helper owners. The application keeps
4
- // its authority and storage projections; all native coordination stays here:
5
- // one closure per session key (shared with the runtime lifecycle, which drains
6
- // them on shutdown) and the private record of which closures are restricted.
3
+ /**
4
+ * Cleanup of the run, provider and Helper owners. The application keeps
5
+ * its authority and storage projections; all native coordination stays here:
6
+ * one closure per session key (shared with the runtime lifecycle, which drains
7
+ * them on shutdown) and the private record of which closures are restricted.
8
+ * @param {object} dependencies
9
+ * @param {(sessionId: string) => string} dependencies.namespace
10
+ * @param {import("./codexRuntimeLifecycle.js").CodexRunOwner} dependencies.runOwner
11
+ * @param {import("./codexProviderOwner.js").CodexProviderSessions | null} dependencies.providerSessions
12
+ * @param {Map<string, Promise<unknown>> | undefined} dependencies.sessionClosures
13
+ * @param {import("./codexRuntimeLifecycle.js").CodexHelperThreadLifecycle | null} dependencies.helperLifecycle
14
+ * @param {ReturnType<typeof import("./codexRunJournal.js").createCodexRunJournal>} dependencies.journal
15
+ * @param {object} dependencies.conversations
16
+ * @param {import("./codexRuntimeLifecycle.js").CodexDebugLog} dependencies.debugLog
17
+ * @param {(error: unknown) => unknown} dependencies.debugError
18
+ */
7
19
  export function createCodexSessionCleanup({ namespace, runOwner, providerSessions, sessionClosures, helperLifecycle,
8
20
  journal, conversations, debugLog, debugError }) {
9
21
  const providerOwner = providerSessions?.owner;
@@ -31,7 +43,7 @@ export function createCodexSessionCleanup({ namespace, runOwner, providerSession
31
43
  }
32
44
 
33
45
  async function closeSession(sessionId, cleanup) {
34
- const normalizedSessionId = normalizeCodexRunText(sessionId);
46
+ const normalizedSessionId = normalizeText(sessionId);
35
47
  const sessionKey = cleanup.sessionKey;
36
48
  let pending = sessionClosures.get(sessionKey);
37
49
  while (pending) {
@@ -113,8 +125,8 @@ export function createCodexSessionCleanup({ namespace, runOwner, providerSession
113
125
  await conversations.closeCodexAppServerConversations(sessionId);
114
126
  } catch (error) {
115
127
  conversationFailures.push({
116
- code: normalizeCodexRunText(error?.code),
117
- error: normalizeCodexRunText(error?.message || error),
128
+ code: normalizeText(error?.code),
129
+ error: normalizeText(error?.message || error),
118
130
  retryable: error?.retryable !== false,
119
131
  sessionId
120
132
  });
@@ -122,7 +134,7 @@ export function createCodexSessionCleanup({ namespace, runOwner, providerSession
122
134
  }
123
135
  const helperRestore = await restoreSavedHelpers(() => preparation.helpers,
124
136
  "appServerRuntime.closeProject.helperRestore.failed",
125
- { projectContextRoot: normalizeCodexRunText(input.projectContextRoot) });
137
+ { projectContextRoot: normalizeText(input.projectContextRoot) });
126
138
  // Every provider of the project's sessions stops, including one a
127
139
  // Temporary chat or a read borrowed and no Main thread names.
128
140
  const stopped = await providerOwner.stopProvidersForProject(input, {
@@ -140,5 +152,8 @@ export function createCodexSessionCleanup({ namespace, runOwner, providerSession
140
152
  };
141
153
  }
142
154
 
143
- return { closeSession, closeProject };
155
+ return Object.freeze({
156
+ closeSession,
157
+ closeProject
158
+ });
144
159
  }
@@ -1,8 +1,22 @@
1
- import { normalizeCodexRunText as normalizeText, codexAppServerErrorMessage as errorMessage } from "./codexTurnState.js";
2
- import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
1
+ import { codexAppServerErrorMessage as errorMessage } from "./codexTurnState.js";
2
+ import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
3
 
4
- // Original batch restoration, prune and startup unsubscribe. The run owner's
5
- // existing lifecycle and per-thread readiness retain all state and coordination.
4
+ /**
5
+ * Batch restoration, pruning and the startup unsubscribe. The run owner's
6
+ * lifecycle and per-thread readiness hold all state and coordination.
7
+ * @param {object} dependencies
8
+ * @param {object} dependencies.readiness
9
+ * @param {(sessionId: string, context: object) => Promise<object>} dependencies.recoverSessionObservationLoss
10
+ * @param {import("./codexProviderOwner.js").CodexProviderSessions} dependencies.providerSessions
11
+ * @param {import("./codexRuntimeLifecycle.js").CodexHelperThreadLifecycle | null} dependencies.helperLifecycle
12
+ * @param {import("./codexRuntimeLifecycle.js").CodexRuntimeLifecycle} dependencies.lifecycle
13
+ * @param {() => Promise<object>} dependencies.createRuntime
14
+ * @param {(sessionId: string) => string} dependencies.namespace
15
+ * @param {(session: object) => object} dependencies.turnState
16
+ * @param {string} dependencies.sessionIdRequiredError
17
+ * @param {import("./codexRuntimeLifecycle.js").CodexDebugLog} dependencies.debugLog
18
+ * @param {(error: unknown) => unknown} dependencies.debugError
19
+ */
6
20
  export function createCodexSessionReconciliation({
7
21
  readiness, recoverSessionObservationLoss, providerSessions, helperLifecycle, lifecycle, createRuntime,
8
22
  namespace, turnState, sessionIdRequiredError, debugLog, debugError
@@ -55,7 +69,7 @@ export function createCodexSessionReconciliation({
55
69
  );
56
70
  }
57
71
 
58
- function codexAppServerReconcileSessionId(value = {}) {
72
+ function codexAppServerReconcileSessionId(value) {
59
73
  if (typeof value === "string") {
60
74
  return normalizeText(value);
61
75
  }
@@ -290,9 +304,9 @@ export function createCodexSessionReconciliation({
290
304
  return unsubscribeCodexAppServerThreadsForSessions(sessions, preparation);
291
305
  }
292
306
 
293
- return {
307
+ return Object.freeze({
294
308
  reconcileSessions,
295
309
  unsubscribeSessions,
296
310
  retireProviderForSession: retireAndCloseCodexAppServerProviderForSession
297
- };
311
+ });
298
312
  }