@jskit-ai/assistant-core 0.1.239 → 0.1.241

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 (103) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +5 -5
  3. package/src/client/conversation/AssistantTranscript.vue +26 -29
  4. package/src/client/conversation/LongTextPreviewBlocks.vue +11 -0
  5. package/src/client/conversation/messageDelivery.js +8 -2
  6. package/src/client/conversation/useAssistantAttachments.js +1 -1
  7. package/src/server/conversation/agentRun.js +12 -198
  8. package/src/server/conversation/attachments.js +6 -5
  9. package/src/server/conversation/boundedToolLoop.js +160 -0
  10. package/src/server/conversation/claudeHistory.js +20 -16
  11. package/src/server/conversation/claudeProcess.js +2 -2
  12. package/src/server/conversation/claudeTurn.js +51 -40
  13. package/src/server/conversation/codexAuthPreflight.js +104 -0
  14. package/src/server/conversation/codexClient.js +11 -16
  15. package/src/server/conversation/codexConfiguration.js +112 -98
  16. package/src/server/conversation/codexConversationStore.js +189 -0
  17. package/src/server/conversation/codexConversations.js +47 -44
  18. package/src/server/conversation/codexDetachedTurn.js +12 -11
  19. package/src/server/conversation/codexEvents.js +95 -99
  20. package/src/server/conversation/codexGoalCommands.js +1 -1
  21. package/src/server/conversation/codexHelperExecution.js +3 -13
  22. package/src/server/conversation/codexHelperThreadLedger.js +7 -13
  23. package/src/server/conversation/codexHelperThreadRecovery.js +2 -16
  24. package/src/server/conversation/codexHelperThreads.js +5 -6
  25. package/src/server/conversation/codexHistoryAdapter.js +32 -26
  26. package/src/server/conversation/codexMessageCommands.js +22 -22
  27. package/src/server/conversation/codexMessageDelivery.js +12 -7
  28. package/src/server/conversation/codexModelCatalog.js +97 -0
  29. package/src/server/conversation/codexNotificationObserver.js +24 -40
  30. package/src/server/conversation/codexNotificationQueue.js +2 -3
  31. package/src/server/conversation/codexOutput.js +20 -27
  32. package/src/server/conversation/codexProcess.js +123 -127
  33. package/src/server/conversation/codexProcessLeader.js +3 -3
  34. package/src/server/conversation/codexProvider.js +31 -614
  35. package/src/server/conversation/codexProviderOwner.js +2 -2
  36. package/src/server/conversation/codexProviderSelection.js +38 -40
  37. package/src/server/conversation/codexRenewalCommands.js +13 -10
  38. package/src/server/conversation/codexRenewalThreads.js +412 -0
  39. package/src/server/conversation/codexRunJournal.js +24 -42
  40. package/src/server/conversation/codexRuntimeLifecycle.js +19 -15
  41. package/src/server/conversation/codexSessionCleanup.js +11 -10
  42. package/src/server/conversation/codexSessionReconciliation.js +6 -6
  43. package/src/server/conversation/codexThreadReadiness.js +19 -33
  44. package/src/server/conversation/codexThreadStatus.js +3 -3
  45. package/src/server/conversation/codexTurn.js +124 -297
  46. package/src/server/conversation/codexTurnRecovery.js +49 -28
  47. package/src/server/conversation/codexTurnState.js +34 -13
  48. package/src/server/conversation/commandWrapper.js +3 -0
  49. package/src/server/conversation/deliveryTiming.js +14 -5
  50. package/src/server/conversation/index.js +1 -0
  51. package/src/server/conversation/localExecution.js +13 -17
  52. package/src/server/conversation/normalize.js +8 -3
  53. package/src/server/conversation/openCodeClient.js +9 -10
  54. package/src/server/conversation/openCodeProcess.js +9 -5
  55. package/src/server/conversation/openCodeRuntime.js +10 -11
  56. package/src/server/conversation/openCodeTurn.js +43 -29
  57. package/src/server/conversation/providers/claudeDriver.js +10 -16
  58. package/src/server/conversation/providers/codexDriver.js +27 -43
  59. package/src/server/conversation/providers/opencodeDriver.js +17 -42
  60. package/src/server/conversation/providers/scoped.js +48 -4
  61. package/src/server/conversation/runtime.js +50 -49
  62. package/src/server/conversation/runtimeStateUpgrade.js +34 -22
  63. package/src/server/conversation/transcript.js +10 -10
  64. package/src/server/hosts/codexConfiguration.js +1 -0
  65. package/src/server/hosts/codexProvider.js +8 -6
  66. package/src/server/index.js +2 -1
  67. package/src/server/lib/assistantToolLoop.js +1 -153
  68. package/src/server/lib/serviceToolCatalog.js +17 -42
  69. package/src/shared/assistantResource.js +4 -2
  70. package/src/shared/conversation/attachments.js +4 -0
  71. package/src/shared/conversation/longTextBlocks.js +54 -19
  72. package/src/shared/conversation/numberedQuestionSugar.js +17 -10
  73. package/src/shared/conversation/pagination.js +4 -6
  74. package/src/shared/conversation/patches.js +1 -4
  75. package/src/shared/conversation/streaming.js +3 -11
  76. package/src/shared/conversation/turns.js +11 -0
  77. package/src/shared/index.js +1 -0
  78. package/test/assistantBoundedToolLoop.test.js +2 -2
  79. package/test/assistantResource.test.js +4 -4
  80. package/test/assistantScroll.browser.test.js +29 -6
  81. package/test/assistantSettingsResource.test.js +2 -2
  82. package/test/claudeConversationSession.test.js +20 -3
  83. package/test/claudeTurn.test.js +27 -7
  84. package/test/codexClient.test.js +20 -0
  85. package/test/codexConversationSession.test.js +5 -3
  86. package/test/codexConversationsCheckpoint.test.js +51 -0
  87. package/test/codexModelCatalog.test.js +1 -1
  88. package/test/codexProviderHome.test.js +51 -0
  89. package/test/codexProviderSelection.test.js +71 -0
  90. package/test/codexTurn.test.js +222 -6
  91. package/test/conversationAgentRun.test.js +2 -1
  92. package/test/conversationAttachments.test.js +27 -2
  93. package/test/conversationDeliveryTiming.test.js +21 -0
  94. package/test/conversationNormalize.test.js +11 -0
  95. package/test/fixtures/nestedListQuestionMessage.md +37 -0
  96. package/test/frameworkActions.test.js +2 -2
  97. package/test/longTextBlocks.test.js +102 -0
  98. package/test/messageDelivery.test.js +7 -1
  99. package/test/openCodeClient.test.js +20 -0
  100. package/test/openCodeRuntime.test.js +1 -1
  101. package/test/openCodeTurn.test.js +90 -0
  102. package/test/opencodeConversationSession.test.js +1 -1
  103. package/test/serviceToolCatalog.test.js +27 -0
@@ -9,14 +9,15 @@ import {
9
9
  codexAppServerAgentRunPatch,
10
10
  codexAppServerRunIdentityForPatch,
11
11
  codexAppServerRunPatchIsStaleAfterTerminalState,
12
+ codexAppServerRunPatchSettlesOtherActiveTurn,
12
13
  CODEX_APP_SERVER_AGENT_RUN_ID,
13
14
  CODEX_AGENT_PROVIDER,
14
15
  CODEX_APP_SERVER_RUN_STATE,
15
- CODEX_APP_SERVER_ACTIVE_RUN_STATES,
16
- codexAppServerRunInputSource
16
+ codexAppServerRunInputSource,
17
+ codexAppServerTurnStateFromAgentRun
17
18
  } from "./codexTurnState.js";
18
19
  import crypto from "node:crypto";
19
- import { isPlainObject } from "./normalize.js";
20
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
20
21
 
21
22
  const CODEX_APP_SERVER_PROMPT_CLAIM_GRACE_MS = 15000;
22
23
 
@@ -207,12 +208,18 @@ export function createCodexRunJournal({
207
208
  currentTurn.threadId === runPatch.providerThreadId &&
208
209
  currentTurn.turnId === runPatch.providerTurnId &&
209
210
  runPatch.state === CODEX_APP_SERVER_RUN_STATE.ACTIVE;
211
+ const observationLostBlocks = currentTurn.status === "observation_lost" && status !== "observation_lost";
212
+ const phaseTargetsOtherTurn = phase !== undefined && (!normalizeCodexRunText(turnId) ||
213
+ !codexAppServerTurnCanReceiveProviderActivity(currentTurn, threadId, turnId));
214
+ const observationExpired = Boolean(observedRun) && !canRecoverStoppedTurn;
215
+ const terminalAlreadyRecorded = !canRecoverStoppedTurn && runPatchIsStaleAfterTerminalState(currentTurn, runPatch);
216
+ const settlesOtherActiveTurn = codexAppServerRunPatchSettlesOtherActiveTurn(currentTurn, runPatch);
210
217
  if (
211
- (currentTurn.status === "observation_lost" && status !== "observation_lost") ||
212
- (phase !== undefined && (!normalizeCodexRunText(turnId) ||
213
- !codexAppServerTurnCanReceiveProviderActivity(currentTurn, threadId, turnId))) ||
214
- (observedRun && !canRecoverStoppedTurn) ||
215
- (!canRecoverStoppedTurn && runPatchIsStaleAfterTerminalState(currentTurn, runPatch))
218
+ observationLostBlocks ||
219
+ phaseTargetsOtherTurn ||
220
+ observationExpired ||
221
+ terminalAlreadyRecorded ||
222
+ settlesOtherActiveTurn
216
223
  ) {
217
224
  stale = {
218
225
  currentState: currentTurn.state,
@@ -260,9 +267,9 @@ export function createCodexRunJournal({
260
267
 
261
268
  function clearCodexAppServerFinalizingTimer(sessionId = "", threadId = "", turnId = "") {
262
269
  const key = codexAppServerResultFinalizationKey(sessionId, threadId, turnId);
263
- const timer = codexAppServerFinalizingTimers.get(key);
264
- if (timer) {
265
- clearTimeout(timer);
270
+ const entry = codexAppServerFinalizingTimers.get(key);
271
+ if (entry) {
272
+ clearTimeout(entry.timer);
266
273
  codexAppServerFinalizingTimers.delete(key);
267
274
  }
268
275
  }
@@ -271,9 +278,9 @@ export function createCodexRunJournal({
271
278
  const normalizedSessionId = normalizeCodexRunText(sessionId);
272
279
  clearCodexAppServerActiveTimer(normalizedSessionId);
273
280
  const finalizingKeyPrefix = `${namespace(normalizedSessionId)}:`;
274
- for (const [key, timer] of codexAppServerFinalizingTimers.entries()) {
281
+ for (const [key, entry] of codexAppServerFinalizingTimers.entries()) {
275
282
  if (key.startsWith(finalizingKeyPrefix)) {
276
- clearTimeout(timer);
283
+ clearTimeout(entry.timer);
277
284
  codexAppServerFinalizingTimers.delete(key);
278
285
  }
279
286
  }
@@ -291,34 +298,10 @@ export function createCodexRunJournal({
291
298
  }
292
299
 
293
300
  function codexAppServerAgentRunRealtimePayload(runPatch = {}) {
294
- const runState = normalizeRunState(runPatch.state);
295
- const active = codexAppServerRunStateIsActive(runState);
296
- const state = runState === CODEX_APP_SERVER_RUN_STATE.FINALIZING
297
- ? "finalizing"
298
- : runState === CODEX_APP_SERVER_RUN_STATE.STARTING
299
- ? "starting"
300
- : active
301
- ? "active"
302
- : "idle";
303
- const turn = {
304
- active,
305
- completedAt: normalizeCodexRunText(runPatch.finishedAt),
306
- error: normalizeCodexRunText(runPatch.error),
307
- inputSource: normalizeCodexRunText(runPatch.inputSource),
308
- outerTurnId: normalizeCodexRunText(runPatch.outerTurnId),
309
- phase: active && runPatch.providerStatus !== "observation_lost" ? normalizeCodexRunText(runPatch.providerPhase) : "",
310
- runId: CODEX_APP_SERVER_AGENT_RUN_ID,
311
- runState,
312
- startedAt: normalizeCodexRunText(runPatch.startedAt),
313
- state,
314
- status: normalizeCodexRunText(runPatch.providerStatus || runState),
315
- threadId: normalizeCodexRunText(runPatch.providerThreadId),
316
- turnId: normalizeCodexRunText(runPatch.providerTurnId),
317
- updatedAt: normalizeCodexRunText(runPatch.updatedAt)
318
- };
301
+ const turn = codexAppServerTurnStateFromAgentRun({ ...runPatch, id: CODEX_APP_SERVER_AGENT_RUN_ID }, normalizeRunState);
319
302
  return {
320
303
  agentRun: {
321
- active,
304
+ active: turn.active,
322
305
  id: CODEX_APP_SERVER_AGENT_RUN_ID,
323
306
  inputSource: turn.inputSource,
324
307
  outerTurnId: turn.outerTurnId,
@@ -327,7 +310,7 @@ export function createCodexRunJournal({
327
310
  providerStatus: turn.status,
328
311
  providerThreadId: turn.threadId,
329
312
  providerTurnId: turn.turnId,
330
- state: runState,
313
+ state: turn.runState,
331
314
  updatedAt: turn.updatedAt
332
315
  },
333
316
  agentSession: {
@@ -390,7 +373,7 @@ export function createCodexRunJournal({
390
373
  payload: {
391
374
  ...codexAppServerAgentRunRealtimePayload(runPatch),
392
375
  ...(conversationStream ? { conversationStream } : {}),
393
- ...(isPlainObject(publishPayload) ? publishPayload : {})
376
+ ...(isRecord(publishPayload) ? publishPayload : {})
394
377
  },
395
378
  reason: publishReason || "codex-app-server-turn-state",
396
379
  session: updatedSession
@@ -584,7 +567,6 @@ export function createCodexRunJournal({
584
567
  nowMs - runUpdatedMs >= CODEX_APP_SERVER_PROMPT_CLAIM_GRACE_MS
585
568
  );
586
569
  return (
587
- (run.active === true || CODEX_APP_SERVER_ACTIVE_RUN_STATES.has(normalizeRunState(run.state))) &&
588
570
  normalizeCodexRunText(run.state) === CODEX_APP_SERVER_RUN_STATE.STARTING &&
589
571
  !normalizeCodexRunText(run.providerThreadId) &&
590
572
  !normalizeCodexRunText(run.providerTurnId) &&
@@ -1,9 +1,13 @@
1
1
  import { normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
2
2
 
3
- // The original shared runtime shutdown and wellbeing lifecycle. This facet
4
- // uses the run/provider owners' existing collections; it does not own servers.
3
+ // Shutdown, wellbeing timers and managed-session records for shared Codex
4
+ // runtimes. It works on the run/provider owners' collections; it owns no servers.
5
+ // `pendingWork` names the run owner's in-flight task collections that shutdown
6
+ // drains; `observer` owns the event subscriptions a released provider ends.
5
7
  export function createCodexRuntimeLifecycle({
6
- runOwner,
8
+ pendingWork,
9
+ observer,
10
+ checkManagedConnection,
7
11
  providerSessions,
8
12
  helperLifecycle,
9
13
  captureContext,
@@ -58,16 +62,16 @@ export function createCodexRuntimeLifecycle({
58
62
  function codexAppServerProviderResources(providerKey) {
59
63
  const normalizedProviderKey = normalizeText(providerKey);
60
64
  return {
61
- retire: runOwner.helperThreads.retireProvider,
62
- reconcileRetirement: runOwner.helperThreads.reconcileProviderRetirement,
65
+ retire: helperLifecycle.retireProvider,
66
+ reconcileRetirement: helperLifecycle.reconcileProviderRetirement,
63
67
  release(provider) {
64
- runOwner.helperThreads.assertProviderReleased(provider);
68
+ helperLifecycle.assertProviderReleased(provider);
65
69
  stopCodexAppServerWellbeing(normalizedProviderKey);
66
70
  providerSessions.managed.delete(normalizedProviderKey);
67
71
  const subscriptionPrefix = `${normalizedProviderKey}:`;
68
- for (const key of [...runOwner.eventSubscriptions.keys()]) {
72
+ for (const key of [...observer.eventSubscriptions.keys()]) {
69
73
  if (key.startsWith(subscriptionPrefix)) {
70
- runOwner.unsubscribeEventSubscription(key);
74
+ observer.unsubscribeEventSubscription(key);
71
75
  }
72
76
  }
73
77
  }
@@ -156,21 +160,21 @@ export function createCodexRuntimeLifecycle({
156
160
  async function drainCodexAppServerControllerTasks() {
157
161
  while (true) {
158
162
  const pending = new Set([
159
- ...runOwner.conversationTurnStarts.values(),
163
+ ...pendingWork.conversationTurnStarts.values(),
160
164
  ...helperLifecycle.projectOperations.values(),
161
165
  ...helperLifecycle.cleanups.values(),
162
166
  ...helperLifecycle.mutations.values(),
163
167
  ...helperLifecycle.restores.values(),
164
168
  ...helperLifecycle.turnStarts.values(),
165
- ...runOwner.messageDeliveries.values(),
166
- ...runOwner.reasoningPersistQueues.values(),
167
- ...runOwner.resultFinalizations.values(),
169
+ ...pendingWork.messageDeliveries.values(),
170
+ ...pendingWork.reasoningPersistQueues.values(),
171
+ ...pendingWork.resultFinalizations.values(),
168
172
  ...providerSessions.owner.lifecycleTasks,
169
173
  ...providerSessions.owner.runtimeAcquisitions,
170
174
  ...codexAppServerReconcileTasks,
171
175
  ...codexAppServerSessionClosures.values(),
172
- ...runOwner.threadReconciliations.values(),
173
- ...runOwner.notificationQueue.tasks.values()
176
+ ...pendingWork.threadReconciliations.values(),
177
+ ...pendingWork.notificationTasks.values()
174
178
  ]);
175
179
  if (pending.size === 0) {
176
180
  return;
@@ -194,7 +198,7 @@ export function createCodexRuntimeLifecycle({
194
198
  return;
195
199
  }
196
200
  await runInContext(current.projectContext, async () => {
197
- await runOwner.checkManagedConnection(normalizedProviderKey);
201
+ await checkManagedConnection(normalizedProviderKey);
198
202
  });
199
203
  })()
200
204
  .catch((error) => {
@@ -1,22 +1,23 @@
1
1
  import { normalizeCodexRunText } from "./codexTurnState.js";
2
2
 
3
3
  // Cleanup of the existing run/provider/Helper owners. The application keeps
4
- // its authority and storage projections; all native coordination stays here.
5
- export function createCodexSessionCleanup({ namespace, runOwner, providerSessions, helperLifecycle, journal,
6
- conversations, debugLog, debugError }) {
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.
7
+ export function createCodexSessionCleanup({ namespace, runOwner, providerSessions, sessionClosures, helperLifecycle,
8
+ journal, conversations, debugLog, debugError }) {
7
9
  const providerOwner = providerSessions?.owner;
10
+ const restrictedClosures = new WeakSet();
8
11
 
9
12
  async function closeSession(sessionId, cleanup) {
10
13
  const normalizedSessionId = normalizeCodexRunText(sessionId);
11
14
  const sessionKey = cleanup.sessionKey;
12
- const pendingClosures = cleanup.pending;
13
- const restrictedClosures = cleanup.restrictedPending;
14
- let pending = pendingClosures.get(sessionKey);
15
+ let pending = sessionClosures.get(sessionKey);
15
16
  while (pending) {
16
17
  const pendingIsRestricted = restrictedClosures.has(pending);
17
18
  if (cleanup.restricted && !pendingIsRestricted) {
18
19
  await pending.catch(() => null);
19
- pending = pendingClosures.get(sessionKey);
20
+ pending = sessionClosures.get(sessionKey);
20
21
  continue;
21
22
  }
22
23
  if (!cleanup.restricted && pendingIsRestricted) {
@@ -58,15 +59,15 @@ export function createCodexSessionCleanup({ namespace, runOwner, providerSession
58
59
  });
59
60
  await prepared.complete(exitFailure);
60
61
  })();
61
- pendingClosures.set(sessionKey, closing);
62
+ sessionClosures.set(sessionKey, closing);
62
63
  if (cleanup.restricted) {
63
64
  restrictedClosures.add(closing);
64
65
  }
65
66
  try {
66
67
  return await closing;
67
68
  } finally {
68
- if (pendingClosures.get(sessionKey) === closing) {
69
- pendingClosures.delete(sessionKey);
69
+ if (sessionClosures.get(sessionKey) === closing) {
70
+ sessionClosures.delete(sessionKey);
70
71
  }
71
72
  }
72
73
  }
@@ -1,10 +1,10 @@
1
1
  import { normalizeCodexRunText as normalizeText, codexAppServerErrorMessage as errorMessage } from "./codexTurnState.js";
2
- import { isPlainObject as isRecord } from "./normalize.js";
2
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
3
3
 
4
4
  // Original batch restoration, prune and startup unsubscribe. The run owner's
5
5
  // existing lifecycle and per-thread readiness retain all state and coordination.
6
6
  export function createCodexSessionReconciliation({
7
- runOwner, providerSessions, helperLifecycle, lifecycle, createRuntime,
7
+ readiness, recoverSessionObservationLoss, providerSessions, helperLifecycle, lifecycle, createRuntime,
8
8
  namespace, turnState, sessionIdRequiredError, debugLog, debugError
9
9
  }) {
10
10
  async function pruneCodexAppServerManagedSessions({
@@ -72,7 +72,7 @@ export function createCodexSessionReconciliation({
72
72
  const failed = [];
73
73
  const seenSessionIds = new Set();
74
74
  for (const session of Array.isArray(sessions) ? sessions : []) {
75
- const sessionId = normalizeText(session?.sessionId || session?.id || session);
75
+ const sessionId = codexAppServerReconcileSessionId(session);
76
76
  if (!sessionId || seenSessionIds.has(sessionId)) {
77
77
  continue;
78
78
  }
@@ -131,7 +131,7 @@ export function createCodexSessionReconciliation({
131
131
  if (prepared.result) return prepared.result;
132
132
  const { context } = prepared;
133
133
  if (turnState(context.session).status === "observation_lost") {
134
- const recovered = await prepared.recover(runOwner.recoverSessionObservationLoss);
134
+ const recovered = await prepared.recover(recoverSessionObservationLoss);
135
135
  const turn = turnState(recovered);
136
136
  return prepared.stopped({
137
137
  ok: !turn.active,
@@ -140,7 +140,7 @@ export function createCodexSessionReconciliation({
140
140
  threadId: turn.threadId
141
141
  }, recovered);
142
142
  }
143
- return runOwner.reconcileThread(normalizedSessionId, context, prepared.thread);
143
+ return readiness.reconcileThread(normalizedSessionId, context, prepared.thread);
144
144
  }
145
145
 
146
146
  async function reconcileCodexAppServerThreads(sessions = [], {
@@ -227,7 +227,7 @@ export function createCodexSessionReconciliation({
227
227
  }
228
228
  }
229
229
  if (reconcileGeneration === lifecycle.reconcileGeneration) {
230
- await runOwner.waitForThreadReconciliations({
230
+ await readiness.waitForThreadReconciliations({
231
231
  keepProviderKeys,
232
232
  projectContextRoot
233
233
  });
@@ -7,8 +7,8 @@ import {
7
7
  import { codexAppServerThreadIsMissing, ensureCodexAppServerThread } from "./codexProvider.js";
8
8
  import { codexAppServerThreadRawValue, codexAppServerThreadTurnId } from "./codexEvents.js";
9
9
 
10
- // Original native readiness, loaded-thread restoration and reconciliation drain.
11
- // One Map retains the original run-owner lifetime and is shared with shutdown.
10
+ // Native thread readiness, loaded-thread restoration and reconciliation drain.
11
+ // Its one Map lives as long as the run owner and is shared with shutdown.
12
12
  export function createCodexThreadReadiness({
13
13
  providerSessions,
14
14
  selection,
@@ -23,7 +23,9 @@ export function createCodexThreadReadiness({
23
23
  const {
24
24
  ensureCodexAppServerProviderForActiveTurn,
25
25
  codexAppServerSessionObserverOptions,
26
- rememberCodexAppServerPreparedThread
26
+ sessionConnection,
27
+ ensureSessionProvider,
28
+ rememberManagedSession
27
29
  } = selection;
28
30
  const {
29
31
  eventSubscriptionKey: codexAppServerEventSubscriptionKey,
@@ -47,10 +49,8 @@ export function createCodexThreadReadiness({
47
49
  if (!providerOptions) {
48
50
  providerOptions = await preparation.providerOptions(session);
49
51
  }
50
- const provider = activeProvider?.provider || await providerSessions.owner.ensureSession(
51
- providerSessions.context(normalizeCodexRunText(sessionId), providerOptions),
52
- preparation.threadId(session)
53
- );
52
+ const provider = activeProvider?.provider ||
53
+ await ensureSessionProvider(sessionId, providerOptions, preparation.threadId(session));
54
54
  const { currentSession: preparedSession, thread } = await preparation.gate(async currentSession => {
55
55
  const thread = await ensureCodexAppServerThread({
56
56
  ...preparation.preparation(currentSession),
@@ -63,7 +63,9 @@ export function createCodexThreadReadiness({
63
63
  });
64
64
  subscribeCodexAppServerEvents(sessionId, provider, thread.threadId,
65
65
  codexAppServerSessionObserverOptions(sessionId, providerOptions));
66
- rememberCodexAppServerPreparedThread(sessionId, providerOptions, context, thread);
66
+ rememberManagedSession(sessionId, providerOptions, {
67
+ executionRoot: context.executionRoot, workdir: context.workdir, threadId: thread.threadId
68
+ });
67
69
  await reconcileCodexAppServerThreadStatus(sessionId, provider, thread.threadId, {
68
70
  failUnconfirmedTrackedTurn: true,
69
71
  observeLatestTurn: true,
@@ -87,14 +89,9 @@ export function createCodexThreadReadiness({
87
89
  const threadId = preparation.threadId(session);
88
90
  if (!threadId) {
89
91
  const providerOptions = await preparation.providerOptions(session);
90
- const providerKey = providerSessions.context(normalizedSessionId, providerOptions).providerKey;
91
- await providerSessions.owner.ensureSession(providerSessions.context(normalizedSessionId, providerOptions));
92
- providerSessions.context(normalizedSessionId, providerOptions).runOwner.runtimeLifecycle.rememberManagedSession(providerKey, {
93
- providerOptions,
94
- sessionId: normalizedSessionId,
95
- executionRoot,
96
- workdir
97
- });
92
+ const { providerKey } = sessionConnection(normalizedSessionId, providerOptions);
93
+ await ensureSessionProvider(normalizedSessionId, providerOptions);
94
+ rememberManagedSession(normalizedSessionId, providerOptions, { executionRoot, workdir });
98
95
  return {
99
96
  ok: true,
100
97
  providerKey,
@@ -105,32 +102,23 @@ export function createCodexThreadReadiness({
105
102
  }
106
103
  const activeProvider = await ensureCodexAppServerProviderForActiveTurn(session, preparation.managedIdentity);
107
104
  const providerOptions = activeProvider?.providerOptions || await preparation.providerOptions(session);
108
- const providerKey = activeProvider?.providerKey || providerSessions.context(normalizedSessionId, providerOptions).providerKey;
105
+ const providerKey = activeProvider?.providerKey || sessionConnection(normalizedSessionId, providerOptions).providerKey;
109
106
  const existing = codexAppServerThreadReconciliations.get(providerKey);
110
107
  if (existing) {
111
108
  return existing;
112
109
  }
113
110
  const reconciliation = (async () => {
114
- const provider = activeProvider?.provider || await providerSessions.owner.ensureSession(
115
- providerSessions.context(normalizedSessionId, providerOptions), threadId
116
- );
111
+ const provider = activeProvider?.provider ||
112
+ await ensureSessionProvider(normalizedSessionId, providerOptions, threadId);
117
113
  try {
118
114
  const restored = await restoreCodexAppServerLoadedThread(normalizedSessionId, {
119
115
  runtime, provider, providerKey, threadId,
120
- subscriptionOptions: {
121
- get providerKey() { return providerSessions.context(normalizeCodexRunText(normalizedSessionId), providerOptions).providerKey; }
122
- },
116
+ subscriptionOptions: codexAppServerSessionObserverOptions(normalizedSessionId, providerOptions),
123
117
  get resumeOptions() {
124
118
  return preparation.resumeOptions;
125
119
  },
126
120
  remember() {
127
- providerSessions.context(normalizedSessionId, providerOptions).runOwner.runtimeLifecycle.rememberManagedSession(providerKey, {
128
- providerOptions,
129
- sessionId: normalizedSessionId,
130
- executionRoot,
131
- threadId,
132
- workdir
133
- });
121
+ rememberManagedSession(normalizedSessionId, providerOptions, { executionRoot, threadId, workdir });
134
122
  },
135
123
  ready() { return preparation.ready(); }
136
124
  });
@@ -207,9 +195,7 @@ export function createCodexThreadReadiness({
207
195
  return maintainCodexAppServerManagedConnection(sessionId, {
208
196
  provider, providerKey: normalizedProviderKey, threadId, reconnect,
209
197
  get workdir() { return managed.workdir; },
210
- subscriptionOptions: {
211
- get providerKey() { return providerSessions.context(normalizeCodexRunText(sessionId), managed.providerOptions || {}).providerKey; }
212
- }
198
+ subscriptionOptions: codexAppServerSessionObserverOptions(sessionId, managed.providerOptions || {})
213
199
  }, current);
214
200
  }
215
201
 
@@ -11,7 +11,7 @@ import {
11
11
  codexAppServerTurnCanAdoptSuccessor,
12
12
  codexAppServerErrorMessage
13
13
  } from "./codexTurnState.js";
14
- import { isPlainObject } from "./normalize.js";
14
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
15
15
  import {
16
16
  codexAppServerStatusFromValue,
17
17
  codexAppServerThreadRawValue,
@@ -19,7 +19,7 @@ import {
19
19
  codexAppServerThreadError
20
20
  } from "./codexEvents.js";
21
21
 
22
- // Original native snapshot reconciliation; existing owners retain all state.
22
+ // Native thread snapshot reconciliation; it holds no state of its own.
23
23
  export function createCodexThreadStatus({ journal, settlement, recovery, output, createRuntime, turnState, turnOutcomes, debugLog, debugError }) {
24
24
  const { turnResultWasProcessed: codexAppServerTurnResultWasProcessed } = journal;
25
25
  const {
@@ -278,7 +278,7 @@ export function createCodexThreadStatus({ journal, settlement, recovery, output,
278
278
  }
279
279
  if (
280
280
  codexAppServerTurnStatusIsComplete(status) &&
281
- isPlainObject(thread?.observedTurn) &&
281
+ isRecord(thread?.observedTurn) &&
282
282
  turnId
283
283
  ) {
284
284
  const currentSession = await runtime.getSession(normalizedSessionId);