@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
@@ -1,44 +1,37 @@
1
- import { deleteCodexAppServerThread, codexAppServerRuntimeStopWasVerified } from "./codexProvider.js";
2
- import { directoryExists } from "./codexProviderOwner.js";
3
- import { CODEX_HELPER_THREAD_LIFECYCLES } from "./codexHelperThreadLedger.js";
4
- import { normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
1
+ import { deleteCodexAppServerThread } from "./codexProviderThreads.js";
2
+ import { codexAppServerRuntimeStopWasVerified, directoryExists } from "./codexRuntimeInventory.js";
3
+ import { CODEX_HELPER_THREAD_LIFECYCLES } from "./codexHelperThreadRecord.js";
4
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
5
 
6
- // Recovery operations of the same Helper lifecycle. All maps and record mutations
7
- // belong to the supplied lifecycle; this module creates no owner or registry.
6
+ /**
7
+ * Recovery operations of the same helper lifecycle. All maps and record mutations belong to the supplied lifecycle;
8
+ * this module creates no owner or registry.
9
+ * @param {import("./codexHelperThreads.js").CodexHelperThreadLifecycle} lifecycle
10
+ * @param {object} dependencies
11
+ * @param {import("./codexProviderOwner.js").CodexAppServerProviderOwner} dependencies.providerOwner
12
+ * @param {string} dependencies.applicationName
13
+ * @param {string} dependencies.errorPrefix
14
+ */
8
15
  export function createCodexHelperThreadRecovery(lifecycle, {
9
16
  providerOwner,
10
17
  applicationName,
11
18
  errorPrefix
12
19
  }) {
13
20
  const codexAppServerProviders = providerOwner.providers;
14
- const {
15
- threads: codexAppServerHelperThreads,
16
- mutations: codexAppServerHelperThreadMutations,
17
- restores: codexAppServerHelperThreadRestores,
18
- key: codexAppServerHelperThreadKey,
19
- ledger: codexAppServerHelperThreadLedger,
20
- providerFingerprint: codexAppServerProviderKeyFingerprint,
21
- attach: attachCodexAppServerHelperThread,
22
- remove: removeCodexAppServerHelperThread,
23
- retire: retireCodexAppServerHelperThread,
24
- ownershipError: codexAppServerHelperOwnershipError,
25
- ownershipMatches: codexAppServerHelperThreadOwnershipMatches,
26
- failure: codexAppServerHelperFailure
27
- } = lifecycle;
28
21
 
29
22
  async function restoreCodexAppServerHelperThread(record = {}, context = {}) {
30
23
  const { ledger = null } = context;
31
- const key = codexAppServerHelperThreadKey(record);
32
- const pendingMutation = codexAppServerHelperThreadMutations.get(key);
24
+ const key = lifecycle.key(record);
25
+ const pendingMutation = lifecycle.mutations.get(key);
33
26
  if (pendingMutation) {
34
27
  await pendingMutation.catch(() => null);
35
28
  }
36
- const existing = codexAppServerHelperThreads.get(key);
29
+ const existing = lifecycle.threads.get(key);
37
30
  if (existing && (
38
31
  existing.ownershipId !== record.ownershipId ||
39
32
  existing.revision < record.revision
40
33
  )) {
41
- throw codexAppServerHelperOwnershipError(
34
+ throw lifecycle.ownershipError(
42
35
  "Persisted Codex helper ownership changed while the controller was running.",
43
36
  { sessionId: record.sessionId, threadId: record.threadId }
44
37
  );
@@ -48,12 +41,12 @@ export function createCodexHelperThreadRecovery(lifecycle, {
48
41
  if (!context.retiring) {
49
42
  return { record: existing, retiredThreadId: "" };
50
43
  }
51
- await retireCodexAppServerHelperThread(existing);
44
+ await lifecycle.retire(existing);
52
45
  return { record: null, retiredThreadId: existing.threadId };
53
46
  }
54
47
  if (!await directoryExists(record.identity.runtime.runtimeDir)) {
55
48
  if (existing) {
56
- await retireCodexAppServerHelperThread(existing);
49
+ await lifecycle.retire(existing);
57
50
  } else {
58
51
  await ledger.remove(record);
59
52
  }
@@ -62,7 +55,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
62
55
  const prepared = await context.prepareProvider();
63
56
  const { providerOptions, providerKey } = prepared;
64
57
  const contextChanged = !existing &&
65
- codexAppServerProviderKeyFingerprint(providerKey) !== record.identity.providerKeyFingerprint;
58
+ lifecycle.providerFingerprint(providerKey) !== record.identity.providerKeyFingerprint;
66
59
  if (contextChanged) {
67
60
  const staleProvider = providerOwner.createNativeProvider({
68
61
  ...providerOptions,
@@ -73,7 +66,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
73
66
  const accountChanged = record.identity.runtime.accountIdentitySignature !==
74
67
  normalizeText(currentRuntime.accountIdentitySignature);
75
68
  if (accountChanged && !cleanupRequired) {
76
- throw codexAppServerHelperOwnershipError(
69
+ throw lifecycle.ownershipError(
77
70
  "Reconnect the original Codex account to resume or delete this helper thread.",
78
71
  { retryable: false, sessionId: record.sessionId, threadId: record.threadId }
79
72
  );
@@ -87,21 +80,21 @@ export function createCodexHelperThreadRecovery(lifecycle, {
87
80
  ? stopped?.ownershipSuperseded === true || stopped?.processExitVerified === true
88
81
  : codexAppServerRuntimeStopWasVerified(stopped);
89
82
  if (!retired) {
90
- throw codexAppServerHelperOwnershipError(
83
+ throw lifecycle.ownershipError(
91
84
  "The earlier Codex runtime could not be verified as retired; its cleanup record has been preserved.",
92
85
  { sessionId: record.sessionId, threadId: record.threadId }
93
86
  );
94
87
  }
95
88
  await ledger.remove(record);
96
- codexAppServerHelperThreads.delete(key);
89
+ lifecycle.threads.delete(key);
97
90
  return { record: null, retiredThreadId: record.threadId };
98
91
  }
99
92
  } finally {
100
- staleProvider.close?.();
93
+ staleProvider.close();
101
94
  }
102
95
  }
103
96
  if (existing) {
104
- await retireCodexAppServerHelperThread(existing);
97
+ await lifecycle.retire(existing);
105
98
  return { record: null, retiredThreadId: existing.threadId };
106
99
  }
107
100
  let provider = codexAppServerProviders.get(providerKey);
@@ -113,17 +106,8 @@ export function createCodexHelperThreadRecovery(lifecycle, {
113
106
  create: (onObservationLost) => providerOwner.createNativeProvider({ ...providerOptions, onObservationLost })
114
107
  });
115
108
  }
116
- if (typeof provider.currentRuntimeInfo !== "function") {
117
- throw codexAppServerHelperOwnershipError(
118
- "The Codex provider cannot prove persisted runtime ownership.",
119
- {
120
- sessionId: record.sessionId,
121
- threadId: record.threadId
122
- }
123
- );
124
- }
125
109
  const expectedRuntime = await provider.currentRuntimeInfo();
126
- if (!codexAppServerHelperThreadOwnershipMatches(record, {
110
+ if (!lifecycle.ownershipMatches(record, {
127
111
  providerKey,
128
112
  runtime: expectedRuntime
129
113
  }, {
@@ -131,7 +115,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
131
115
  requireAccount: !cleanupRequired,
132
116
  requireEndpoint: false
133
117
  })) {
134
- throw codexAppServerHelperOwnershipError(
118
+ throw lifecycle.ownershipError(
135
119
  "The current Codex runtime/auth identity does not match persisted helper ownership.",
136
120
  {
137
121
  retryable: record.identity.runtime.accountIdentitySignature === normalizeText(expectedRuntime.accountIdentitySignature),
@@ -141,14 +125,14 @@ export function createCodexHelperThreadRecovery(lifecycle, {
141
125
  );
142
126
  }
143
127
  await providerOwner.acquireRuntime({
144
- operation: () => provider.ensureAvailable?.(),
128
+ operation: () => provider.ensureAvailable(),
145
129
  provider,
146
130
  providerKey,
147
131
  providerOptions
148
132
  });
149
133
  const currentRuntime = await provider.currentRuntimeInfo();
150
- const currentServer = provider.currentServerInfo?.();
151
- if (!codexAppServerHelperThreadOwnershipMatches(record, {
134
+ const currentServer = provider.currentServerInfo();
135
+ if (!lifecycle.ownershipMatches(record, {
152
136
  providerKey,
153
137
  runtime: currentRuntime,
154
138
  server: currentServer
@@ -157,7 +141,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
157
141
  requireEndpoint: true,
158
142
  requireServer: !cleanupRequired
159
143
  })) {
160
- throw codexAppServerHelperOwnershipError(
144
+ throw lifecycle.ownershipError(
161
145
  "The connected Codex server identity does not match persisted helper ownership.",
162
146
  {
163
147
  sessionId: record.sessionId,
@@ -167,7 +151,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
167
151
  }
168
152
  const currentLedger = await ledger.readAll();
169
153
  if (currentLedger.failures.length > 0) {
170
- throw codexAppServerHelperOwnershipError(
154
+ throw lifecycle.ownershipError(
171
155
  "Persisted Codex helper ownership could not be revalidated before restore.",
172
156
  {
173
157
  failed: currentLedger.failures,
@@ -177,7 +161,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
177
161
  );
178
162
  }
179
163
  const currentDurable = currentLedger.records.find((candidate) => (
180
- codexAppServerHelperThreadKey(candidate) === key
164
+ lifecycle.key(candidate) === key
181
165
  ));
182
166
  if (!currentDurable) {
183
167
  return { record: null, retiredThreadId: record.threadId };
@@ -186,7 +170,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
186
170
  currentDurable.ownershipId !== record.ownershipId ||
187
171
  currentDurable.revision !== record.revision
188
172
  ) {
189
- throw codexAppServerHelperOwnershipError(
173
+ throw lifecycle.ownershipError(
190
174
  "Persisted Codex helper ownership changed before it could be restored.",
191
175
  {
192
176
  sessionId: record.sessionId,
@@ -194,7 +178,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
194
178
  }
195
179
  );
196
180
  }
197
- const attached = attachCodexAppServerHelperThread({
181
+ const attached = lifecycle.attach({
198
182
  durable: currentDurable,
199
183
  ledger,
200
184
  provider
@@ -203,25 +187,25 @@ export function createCodexHelperThreadRecovery(lifecycle, {
203
187
  attached.lifecycle !== CODEX_HELPER_THREAD_LIFECYCLES.READY ||
204
188
  context.retiring
205
189
  ) {
206
- await retireCodexAppServerHelperThread(attached);
190
+ await lifecycle.retire(attached);
207
191
  return { record: null, retiredThreadId: attached.threadId };
208
192
  }
209
193
  return { record: attached, retiredThreadId: "" };
210
194
  }
211
195
 
212
196
  async function restoreCodexAppServerHelperThreads(projectRuntimeRoot = "", context = {}) {
213
- const previous = codexAppServerHelperThreadRestores.get(projectRuntimeRoot) || Promise.resolve();
197
+ const previous = lifecycle.restores.get(projectRuntimeRoot) || Promise.resolve();
214
198
  // Every failure, including an unreadable ledger or a refused host notice,
215
199
  // is reported in the result. Callers that require restoration assert it;
216
200
  // cleanup and reconciliation continue with the records that did restore.
217
201
  const restore = previous.catch(() => null).then(async () => {
218
- const ledger = codexAppServerHelperThreadLedger(projectRuntimeRoot);
202
+ const ledger = lifecycle.ledger(projectRuntimeRoot);
219
203
  const failed = [];
220
204
  let listed = { failures: [], records: [] };
221
205
  try {
222
206
  listed = await ledger.readAll();
223
207
  } catch (error) {
224
- failed.push(codexAppServerHelperFailure({ projectRuntimeRoot }, error));
208
+ failed.push(lifecycle.failure({ projectRuntimeRoot }, error));
225
209
  }
226
210
  failed.push(...listed.failures.map((failure) => ({
227
211
  ...failure,
@@ -244,13 +228,13 @@ export function createCodexHelperThreadRecovery(lifecycle, {
244
228
  retiredBySession.set(record.sessionId, retired);
245
229
  }
246
230
  } catch (error) {
247
- failed.push(codexAppServerHelperFailure(record, error));
231
+ failed.push(lifecycle.failure(record, error));
248
232
  }
249
233
  }
250
234
  try {
251
235
  await context.publish({ records, failed, retiredBySession, normalizedSessionId });
252
236
  } catch (error) {
253
- failed.push(codexAppServerHelperFailure({ projectRuntimeRoot }, error));
237
+ failed.push(lifecycle.failure({ projectRuntimeRoot }, error));
254
238
  }
255
239
  return {
256
240
  failed,
@@ -260,12 +244,12 @@ export function createCodexHelperThreadRecovery(lifecycle, {
260
244
  retiredThreadIds: [...new Set(retiredThreadIds)]
261
245
  };
262
246
  });
263
- codexAppServerHelperThreadRestores.set(projectRuntimeRoot, restore);
247
+ lifecycle.restores.set(projectRuntimeRoot, restore);
264
248
  try {
265
249
  return await restore;
266
250
  } finally {
267
- if (codexAppServerHelperThreadRestores.get(projectRuntimeRoot) === restore) {
268
- codexAppServerHelperThreadRestores.delete(projectRuntimeRoot);
251
+ if (lifecycle.restores.get(projectRuntimeRoot) === restore) {
252
+ lifecycle.restores.delete(projectRuntimeRoot);
269
253
  }
270
254
  }
271
255
  }
@@ -275,10 +259,10 @@ export function createCodexHelperThreadRecovery(lifecycle, {
275
259
  projectRuntimeRoot = "",
276
260
  prepareProvider
277
261
  } = {}) {
278
- const ledger = codexAppServerHelperThreadLedger(projectRuntimeRoot);
262
+ const ledger = lifecycle.ledger(projectRuntimeRoot);
279
263
  const listed = await ledger.readAll();
280
264
  if (listed.failures.length > 0) {
281
- throw codexAppServerHelperOwnershipError(
265
+ throw lifecycle.ownershipError(
282
266
  `${applicationName} cannot inventory helper threads while durable ownership is malformed.`,
283
267
  { failed: listed.failures, sessionId }
284
268
  );
@@ -296,11 +280,11 @@ export function createCodexHelperThreadRecovery(lifecycle, {
296
280
  ) {
297
281
  continue;
298
282
  }
299
- const record = codexAppServerHelperThreads.get(
300
- codexAppServerHelperThreadKey(durable)
283
+ const record = lifecycle.threads.get(
284
+ lifecycle.key(durable)
301
285
  );
302
286
  if (!record || record.ownershipId !== durable.ownershipId) {
303
- throw codexAppServerHelperOwnershipError(
287
+ throw lifecycle.ownershipError(
304
288
  `${applicationName} cannot retire missing helper ownership without its verified controller record.`,
305
289
  { sessionId, threadId: durable.threadId }
306
290
  );
@@ -312,7 +296,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
312
296
  continue;
313
297
  }
314
298
  try {
315
- await removeCodexAppServerHelperThread(record);
299
+ await lifecycle.remove(record);
316
300
  } catch (error) {
317
301
  if (error?.code === `${errorPrefix}codex_helper_thread_unavailable`) {
318
302
  continue;
@@ -343,15 +327,9 @@ export function createCodexHelperThreadRecovery(lifecycle, {
343
327
  };
344
328
  }
345
329
  provider ||= await providerOwner.ensureSession(prepared.sessionContext);
346
- if (typeof provider.listHelperThreads !== "function") {
347
- throw codexAppServerHelperOwnershipError(
348
- "The Codex helper provider cannot authoritatively inventory its threads.",
349
- { sessionId }
350
- );
351
- }
352
330
  const inventory = await provider.listHelperThreads();
353
331
  if (!Array.isArray(inventory?.threadIds)) {
354
- throw codexAppServerHelperOwnershipError(
332
+ throw lifecycle.ownershipError(
355
333
  "The Codex helper provider returned an invalid thread inventory.",
356
334
  { sessionId }
357
335
  );
@@ -366,7 +344,7 @@ export function createCodexHelperThreadRecovery(lifecycle, {
366
344
  await deleteCodexAppServerThread({ provider, threadId });
367
345
  } catch (error) {
368
346
  if (error?.code !== "codex_helper_thread_delete_unconfirmed") throw error;
369
- throw codexAppServerHelperOwnershipError(
347
+ throw lifecycle.ownershipError(
370
348
  "Codex did not confirm deletion of an unowned helper thread.",
371
349
  { sessionId, threadId }
372
350
  );
@@ -386,9 +364,9 @@ export function createCodexHelperThreadRecovery(lifecycle, {
386
364
  };
387
365
  }
388
366
 
389
- return {
367
+ return Object.freeze({
390
368
  restoreThread: restoreCodexAppServerHelperThread,
391
369
  restoreThreads: restoreCodexAppServerHelperThreads,
392
370
  reconcileRuntimeUnlocked: reconcileCodexAppServerHelperRuntimeUnlocked
393
- };
371
+ });
394
372
  }
@@ -1,28 +1,86 @@
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
1
2
  import crypto from "node:crypto";
2
- import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
4
  import { createCodexHelperOwnershipError, prepareDetachedCodexHelperThread } from "./codexHelperExecution.js";
4
- import { deleteCodexAppServerHelperThread } from "./codexProvider.js";
5
- import { directoryExists } from "./codexProviderOwner.js";
5
+ import { deleteCodexAppServerHelperThread } from "./codexProviderThreads.js";
6
+ import { directoryExists } from "./codexRuntimeInventory.js";
6
7
  import { createCodexHelperThreadRecovery } from "./codexHelperThreadRecovery.js";
7
- import { codexAppServerErrorMessage as errorMessage, normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
8
+ import { codexAppServerErrorMessage as errorMessage } from "./codexTurnState.js";
8
9
  import {
9
10
  CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
10
11
  CODEX_HELPER_THREAD_LIFECYCLES
11
- } from "./codexHelperThreadLedger.js";
12
+ } from "./codexHelperThreadRecord.js";
12
13
 
13
14
  const CODEX_APP_SERVER_PROVIDER_ID = "codex_app_server";
14
15
 
15
- // Internal facet of the original run owner. Public supplies policy and authorized
16
- // scope inputs; native ownership, pending work and retirement have one owner.
16
+ function codexAppServerHelperThreadKey({
17
+ projectRuntimeRoot = "",
18
+ sessionId = "",
19
+ threadId = ""
20
+ } = {}) {
21
+ return [
22
+ normalizeText(projectRuntimeRoot),
23
+ normalizeText(sessionId),
24
+ normalizeText(threadId)
25
+ ].join("\u001f");
26
+ }
27
+
28
+ function codexAppServerProviderKeyFingerprint(providerKey = "") {
29
+ return `sha256:${crypto.createHash("sha256")
30
+ .update(normalizeText(providerKey))
31
+ .digest("hex")}`;
32
+ }
33
+
34
+ function codexAppServerRuntimeIdentityMatches(expected = {}, actual = {}, {
35
+ requireAccount = true,
36
+ requireEndpoint = true
37
+ } = {}) {
38
+ return (!requireAccount || expected.accountIdentitySignature === normalizeText(actual.accountIdentitySignature)) &&
39
+ (!requireEndpoint || expected.endpoint === normalizeText(actual.endpoint)) &&
40
+ expected.executionMode === normalizeText(actual.executionMode) &&
41
+ expected.executionContextHash === normalizeText(actual.executionContextHash) &&
42
+ expected.provider === normalizeText(actual.provider) &&
43
+ expected.runtimeDir === normalizeText(actual.runtimeDir) &&
44
+ expected.runtimesHash === normalizeText(actual.runtimesHash) &&
45
+ expected.terminalEnvHash === normalizeText(actual.terminalEnvHash) &&
46
+ expected.toolHomeSource === normalizeText(actual.toolHomeSource) &&
47
+ expected.transport === normalizeText(actual.transport);
48
+ }
49
+
50
+ function codexAppServerHelperThreadOwnershipMatches(record = {}, {
51
+ providerKey = "",
52
+ runtime = {},
53
+ server = null
54
+ } = {}, options = {}) {
55
+ return record.identity?.providerId === normalizeText(record.executionProfile?.providerId) &&
56
+ record.identity?.providerKeyFingerprint === codexAppServerProviderKeyFingerprint(providerKey) &&
57
+ record.identity?.transportId === CODEX_APP_SERVER_PROVIDER_ID &&
58
+ codexAppServerRuntimeIdentityMatches(record.identity?.runtime, runtime, options) &&
59
+ (
60
+ options.requireServer !== true ||
61
+ record.identity?.server?.userAgent === normalizeText(server?.userAgent)
62
+ );
63
+ }
64
+
65
+ function ignoreHelperThreadRetired() {}
66
+
67
+ /**
68
+ * Internal facet of the run owner. The application supplies policy and authorized scope inputs; native ownership,
69
+ * pending work and retirement have one owner.
70
+ * @param {object} dependencies
71
+ * @param {import("./codexProviderOwner.js").CodexAppServerProviderOwner} dependencies.providerOwner
72
+ * @param {ReturnType<typeof import("./codexHelperThreadLedger.js").createCodexHelperThreadLedgerOwner>} dependencies.ledgerOwner
73
+ * @param {string} [dependencies.applicationName]
74
+ * @param {string} [dependencies.errorPrefix]
75
+ */
17
76
  export function createCodexHelperThreadLifecycle({
18
77
  providerOwner,
19
78
  ledgerOwner,
20
79
  applicationName = "Application",
21
80
  errorPrefix = ""
22
- } = {}) {
81
+ }) {
23
82
  const codexAppServerProviders = providerOwner.providers;
24
83
  const {
25
- createCodexHelperThreadLedger: codexHelperThreadLedgerFactory,
26
84
  defineCodexHelperThreadRecord
27
85
  } = ledgerOwner;
28
86
  const codexAppServerHelperThreads = new Map();
@@ -33,18 +91,6 @@ export function createCodexHelperThreadLifecycle({
33
91
  const codexAppServerHelperTurnStarts = new Map();
34
92
  const codexAppServerHelperThreadRestores = new Map();
35
93
 
36
- function codexAppServerHelperThreadKey({
37
- projectRuntimeRoot = "",
38
- sessionId = "",
39
- threadId = ""
40
- } = {}) {
41
- return [
42
- normalizeText(projectRuntimeRoot),
43
- normalizeText(sessionId),
44
- normalizeText(threadId)
45
- ].join("\u001f");
46
- }
47
-
48
94
  async function withCodexAppServerHelperProjectOperation(
49
95
  projectRuntimeRoot = "",
50
96
  operation
@@ -68,13 +114,10 @@ export function createCodexHelperThreadLifecycle({
68
114
  }
69
115
 
70
116
  function codexAppServerHelperThreadUnavailableError(threadId = "") {
71
- const error = new Error(
72
- "This low-cost assistant thread is no longer available. Start the background task again instead of reusing another assistant conversation."
117
+ const error = new AppError(409,
118
+ "This low-cost assistant thread is no longer available. Start the background task again instead of reusing another assistant conversation.",
119
+ { code: `${errorPrefix}codex_helper_thread_unavailable`, messageCode: "assistant.codex.helperThreadUnavailable" }
73
120
  );
74
- error.code = `${errorPrefix}codex_helper_thread_unavailable`;
75
- error.statusCode = 409;
76
- error.messageCode = "assistant.codex.helperThreadUnavailable";
77
- error.messageParams = {};
78
121
  error.threadId = normalizeText(threadId);
79
122
  return error;
80
123
  }
@@ -148,7 +191,7 @@ export function createCodexHelperThreadLifecycle({
148
191
  const normalizedProjectRuntimeRoot = normalizeText(projectRuntimeRoot);
149
192
  let ledger = codexAppServerHelperThreadLedgers.get(normalizedProjectRuntimeRoot);
150
193
  if (!ledger) {
151
- ledger = codexHelperThreadLedgerFactory({
194
+ ledger = ledgerOwner.createCodexHelperThreadLedger({
152
195
  projectRuntimeRoot: normalizedProjectRuntimeRoot
153
196
  });
154
197
  codexAppServerHelperThreadLedgers.set(normalizedProjectRuntimeRoot, ledger);
@@ -165,20 +208,11 @@ export function createCodexHelperThreadLifecycle({
165
208
  return "";
166
209
  }
167
210
 
168
- function codexAppServerProviderKeyFingerprint(providerKey = "") {
169
- return `sha256:${crypto.createHash("sha256")
170
- .update(normalizeText(providerKey))
171
- .digest("hex")}`;
172
- }
173
-
174
211
  async function codexAppServerHelperThreadIdentity({
175
212
  executionProfile = null,
176
213
  provider = null
177
214
  } = {}) {
178
- if (
179
- typeof provider?.currentRuntimeInfo !== "function" ||
180
- typeof provider?.currentServerInfo !== "function"
181
- ) {
215
+ if (!provider) {
182
216
  throw new Error("Codex provider cannot prove durable helper thread ownership.");
183
217
  }
184
218
  const runtime = await provider.currentRuntimeInfo();
@@ -209,7 +243,7 @@ export function createCodexHelperThreadLifecycle({
209
243
  ...durable,
210
244
  durable,
211
245
  ledger,
212
- onRetired,
246
+ onRetired: onRetired || ignoreHelperThreadRetired,
213
247
  provider
214
248
  });
215
249
  codexAppServerHelperThreads.set(key, record);
@@ -313,7 +347,7 @@ export function createCodexHelperThreadLifecycle({
313
347
  }
314
348
  codexAppServerHelperThreadCleanups.delete(key);
315
349
  codexAppServerHelperThreads.delete(key);
316
- record.onRetired?.({ threadId: record.threadId });
350
+ record.onRetired({ threadId: record.threadId });
317
351
  return true;
318
352
  }
319
353
 
@@ -351,22 +385,23 @@ export function createCodexHelperThreadLifecycle({
351
385
  }
352
386
 
353
387
  function codexAppServerHelperThreadCleanupError(record = {}, error = null, interruptError = null) {
354
- const failure = new Error(
355
- `${applicationName} could not retire a low-cost assistant thread. Retry cleanup before shutting down its Codex provider.`
388
+ const failure = new AppError(503,
389
+ `${applicationName} could not retire a low-cost assistant thread. Retry cleanup before shutting down its Codex provider.`,
390
+ {
391
+ code: `${errorPrefix}codex_helper_thread_cleanup_failed`,
392
+ details: {
393
+ cleanupError: errorMessage(error, "Codex helper thread deletion failed."),
394
+ ...(interruptError
395
+ ? { interruptError: errorMessage(interruptError, "Codex helper turn interruption failed.") }
396
+ : {}),
397
+ retryable: true,
398
+ sessionId: normalizeText(record.sessionId),
399
+ threadId: normalizeText(record.threadId),
400
+ turnId: normalizeText(record.turnId)
401
+ }
402
+ }
356
403
  );
357
- failure.code = `${errorPrefix}codex_helper_thread_cleanup_failed`;
358
- failure.statusCode = 503;
359
404
  failure.retryable = true;
360
- failure.details = {
361
- cleanupError: errorMessage(error, "Codex helper thread deletion failed."),
362
- ...(interruptError
363
- ? { interruptError: errorMessage(interruptError, "Codex helper turn interruption failed.") }
364
- : {}),
365
- retryable: true,
366
- sessionId: normalizeText(record.sessionId),
367
- threadId: normalizeText(record.threadId),
368
- turnId: normalizeText(record.turnId)
369
- };
370
405
  return failure;
371
406
  }
372
407
 
@@ -473,52 +508,16 @@ export function createCodexHelperThreadLifecycle({
473
508
  if (result.ok !== false) {
474
509
  return result;
475
510
  }
476
- const failure = new Error(
477
- `${applicationName} could not retire every low-cost assistant thread. Retry cleanup before shutting down Codex.`
511
+ const failure = new AppError(503,
512
+ `${applicationName} could not retire every low-cost assistant thread. Retry cleanup before shutting down Codex.`,
513
+ { code: `${errorPrefix}codex_helper_thread_cleanup_failed`, details: { failed: result.failed, retryable: true } }
478
514
  );
479
- failure.code = `${errorPrefix}codex_helper_thread_cleanup_failed`;
480
- failure.statusCode = 503;
481
515
  failure.retryable = true;
482
- failure.details = {
483
- failed: result.failed,
484
- retryable: true
485
- };
486
516
  throw failure;
487
517
  }
488
518
 
489
519
  const codexAppServerHelperOwnershipError = createCodexHelperOwnershipError({ applicationName, errorPrefix });
490
520
 
491
- function codexAppServerRuntimeIdentityMatches(expected = {}, actual = {}, {
492
- requireAccount = true,
493
- requireEndpoint = true
494
- } = {}) {
495
- return (!requireAccount || expected.accountIdentitySignature === normalizeText(actual.accountIdentitySignature)) &&
496
- (!requireEndpoint || expected.endpoint === normalizeText(actual.endpoint)) &&
497
- expected.executionMode === normalizeText(actual.executionMode) &&
498
- expected.executionContextHash === normalizeText(actual.executionContextHash) &&
499
- expected.provider === normalizeText(actual.provider) &&
500
- expected.runtimeDir === normalizeText(actual.runtimeDir) &&
501
- expected.runtimesHash === normalizeText(actual.runtimesHash) &&
502
- expected.terminalEnvHash === normalizeText(actual.terminalEnvHash) &&
503
- expected.toolHomeSource === normalizeText(actual.toolHomeSource) &&
504
- expected.transport === normalizeText(actual.transport);
505
- }
506
-
507
- function codexAppServerHelperThreadOwnershipMatches(record = {}, {
508
- providerKey = "",
509
- runtime = {},
510
- server = null
511
- } = {}, options = {}) {
512
- return record.identity?.providerId === normalizeText(record.executionProfile?.providerId) &&
513
- record.identity?.providerKeyFingerprint === codexAppServerProviderKeyFingerprint(providerKey) &&
514
- record.identity?.transportId === CODEX_APP_SERVER_PROVIDER_ID &&
515
- codexAppServerRuntimeIdentityMatches(record.identity?.runtime, runtime, options) &&
516
- (
517
- options.requireServer !== true ||
518
- record.identity?.server?.userAgent === normalizeText(server?.userAgent)
519
- );
520
- }
521
-
522
521
  function codexAppServerHelperFailure(record = null, error = null) {
523
522
  return {
524
523
  code: normalizeText(error?.code) || `${errorPrefix}codex_helper_ownership_blocked`,
@@ -646,3 +645,5 @@ export function createCodexHelperThreadLifecycle({
646
645
  });
647
646
  return Object.freeze(lifecycle);
648
647
  }
648
+
649
+ /** @typedef {ReturnType<typeof createCodexHelperThreadLifecycle>} CodexHelperThreadLifecycle */