@gkorepanov/ccodex 0.4.20 → 0.4.24

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 (264) hide show
  1. package/README.md +4 -2
  2. package/agents/codex-wrapper.md +1 -0
  3. package/config/compatibility.json +5 -5
  4. package/dist/claude/inputMapper.d.ts +5 -0
  5. package/dist/claude/inputMapper.js +9 -0
  6. package/dist/claude/inputMapper.js.map +1 -1
  7. package/dist/claude/rateLimits.d.ts +2 -1
  8. package/dist/claude/rateLimits.js +12 -16
  9. package/dist/claude/rateLimits.js.map +1 -1
  10. package/dist/claude/service.d.ts +41 -1
  11. package/dist/claude/service.js +166 -69
  12. package/dist/claude/service.js.map +1 -1
  13. package/dist/claude/session/commands.d.ts +36 -0
  14. package/dist/claude/session/goalState.d.ts +5 -0
  15. package/dist/claude/session/goalState.js +22 -0
  16. package/dist/claude/session/goalState.js.map +1 -1
  17. package/dist/claude/session/providerRuntimeFactory.js +15 -7
  18. package/dist/claude/session/providerRuntimeFactory.js.map +1 -1
  19. package/dist/claude/session/repository.d.ts +3 -0
  20. package/dist/claude/session/repository.js +2 -0
  21. package/dist/claude/session/repository.js.map +1 -1
  22. package/dist/claude/session/runtime.d.ts +10 -0
  23. package/dist/claude/session/runtime.js +45 -3
  24. package/dist/claude/session/runtime.js.map +1 -1
  25. package/dist/claude/session/scopeState.js +1 -0
  26. package/dist/claude/session/scopeState.js.map +1 -1
  27. package/dist/claude/session/session.d.ts +1 -0
  28. package/dist/claude/session/session.js +162 -55
  29. package/dist/claude/session/session.js.map +1 -1
  30. package/dist/claude/skillCatalog.js +1 -0
  31. package/dist/claude/skillCatalog.js.map +1 -1
  32. package/dist/claude/statusCommand.js +1 -2
  33. package/dist/claude/statusCommand.js.map +1 -1
  34. package/dist/codex/generated/AuthMode.d.ts +1 -1
  35. package/dist/codex/generated/ClientRequest.d.ts +25 -0
  36. package/dist/codex/generated/InternalSessionSource.d.ts +1 -1
  37. package/dist/codex/generated/ResponseItem.d.ts +3 -1
  38. package/dist/codex/generated/ResponseItemRuntimeSchema.js +2 -2
  39. package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -1
  40. package/dist/codex/generated/ServerNotification.d.ts +23 -0
  41. package/dist/codex/generated/ServerNotificationEnvelope.d.ts +23 -0
  42. package/dist/codex/generated/v2/AllowDenyRequirement.d.ts +1 -0
  43. package/dist/codex/generated/v2/AllowDenyRequirement.js +3 -0
  44. package/dist/codex/generated/v2/AllowDenyRequirement.js.map +1 -0
  45. package/dist/codex/generated/v2/AppLinksConfig.d.ts +11 -0
  46. package/dist/codex/generated/v2/AppLinksConfig.js +3 -0
  47. package/dist/codex/generated/v2/AppLinksConfig.js.map +1 -0
  48. package/dist/codex/generated/v2/AppsConfig.d.ts +5 -0
  49. package/dist/codex/generated/v2/AsyncUserInputQuestion.d.ts +4 -0
  50. package/dist/codex/generated/v2/AsyncUserInputQuestion.js +3 -0
  51. package/dist/codex/generated/v2/AsyncUserInputQuestion.js.map +1 -0
  52. package/dist/codex/generated/v2/AuthRecoveryNotification.d.ts +6 -0
  53. package/dist/codex/generated/v2/AuthRecoveryNotification.js +3 -0
  54. package/dist/codex/generated/v2/AuthRecoveryNotification.js.map +1 -0
  55. package/dist/codex/generated/v2/BedrockSetupParams.d.ts +0 -8
  56. package/dist/codex/generated/v2/BrowserUseAccessApprovalLifetime.d.ts +1 -0
  57. package/dist/codex/generated/v2/BrowserUseAccessApprovalLifetime.js +3 -0
  58. package/dist/codex/generated/v2/BrowserUseAccessApprovalLifetime.js.map +1 -0
  59. package/dist/codex/generated/v2/BrowserUseConfig.d.ts +8 -0
  60. package/dist/codex/generated/v2/BrowserUseConfig.js +3 -0
  61. package/dist/codex/generated/v2/BrowserUseConfig.js.map +1 -0
  62. package/dist/codex/generated/v2/BrowserUseOriginPolicy.d.ts +11 -0
  63. package/dist/codex/generated/v2/BrowserUseOriginPolicy.js +3 -0
  64. package/dist/codex/generated/v2/BrowserUseOriginPolicy.js.map +1 -0
  65. package/dist/codex/generated/v2/BrowserUseOriginPolicyConfig.d.ts +7 -0
  66. package/dist/codex/generated/v2/BrowserUseOriginPolicyConfig.js +3 -0
  67. package/dist/codex/generated/v2/BrowserUseOriginPolicyConfig.js.map +1 -0
  68. package/dist/codex/generated/v2/BrowserUseRequirements.d.ts +7 -0
  69. package/dist/codex/generated/v2/CodexErrorInfo.d.ts +1 -1
  70. package/dist/codex/generated/v2/CollabAgentTool.d.ts +1 -1
  71. package/dist/codex/generated/v2/CollabAgentToolCallStatus.d.ts +1 -1
  72. package/dist/codex/generated/v2/CommandExecutionApprovalKind.d.ts +4 -0
  73. package/dist/codex/generated/v2/CommandExecutionApprovalKind.js +3 -0
  74. package/dist/codex/generated/v2/CommandExecutionApprovalKind.js.map +1 -0
  75. package/dist/codex/generated/v2/CommandExecutionRequestApprovalParams.d.ts +6 -0
  76. package/dist/codex/generated/v2/ComputerUseConfig.d.ts +8 -0
  77. package/dist/codex/generated/v2/ComputerUseConfig.js +3 -0
  78. package/dist/codex/generated/v2/ComputerUseConfig.js.map +1 -0
  79. package/dist/codex/generated/v2/ComputerUseMacosConfig.d.ts +6 -0
  80. package/dist/codex/generated/v2/ComputerUseMacosConfig.js +3 -0
  81. package/dist/codex/generated/v2/ComputerUseMacosConfig.js.map +1 -0
  82. package/dist/codex/generated/v2/ComputerUseMacosRequirements.d.ts +6 -0
  83. package/dist/codex/generated/v2/ComputerUseMacosRequirements.js +3 -0
  84. package/dist/codex/generated/v2/ComputerUseMacosRequirements.js.map +1 -0
  85. package/dist/codex/generated/v2/ComputerUseRequirements.d.ts +7 -0
  86. package/dist/codex/generated/v2/ComputerUseWindowsConfig.d.ts +8 -0
  87. package/dist/codex/generated/v2/ComputerUseWindowsConfig.js +3 -0
  88. package/dist/codex/generated/v2/ComputerUseWindowsConfig.js.map +1 -0
  89. package/dist/codex/generated/v2/ComputerUseWindowsExeConfig.d.ts +7 -0
  90. package/dist/codex/generated/v2/ComputerUseWindowsExeConfig.js +3 -0
  91. package/dist/codex/generated/v2/ComputerUseWindowsExeConfig.js.map +1 -0
  92. package/dist/codex/generated/v2/ComputerUseWindowsExeRequirement.d.ts +7 -0
  93. package/dist/codex/generated/v2/ComputerUseWindowsExeRequirement.js +3 -0
  94. package/dist/codex/generated/v2/ComputerUseWindowsExeRequirement.js.map +1 -0
  95. package/dist/codex/generated/v2/ComputerUseWindowsRequirements.d.ts +8 -0
  96. package/dist/codex/generated/v2/ComputerUseWindowsRequirements.js +3 -0
  97. package/dist/codex/generated/v2/ComputerUseWindowsRequirements.js.map +1 -0
  98. package/dist/codex/generated/v2/Config.d.ts +4 -0
  99. package/dist/codex/generated/v2/ConfigRequirements.d.ts +4 -0
  100. package/dist/codex/generated/v2/CyberAccessProgram.d.ts +5 -0
  101. package/dist/codex/generated/v2/CyberAccessProgram.js +3 -0
  102. package/dist/codex/generated/v2/CyberAccessProgram.js.map +1 -0
  103. package/dist/codex/generated/v2/GetAccountRateLimitsResponse.d.ts +10 -0
  104. package/dist/codex/generated/v2/GuardianApprovalReviewAction.d.ts +7 -0
  105. package/dist/codex/generated/v2/HookEventName.d.ts +1 -1
  106. package/dist/codex/generated/v2/InAppBrowserRequirements.d.ts +3 -0
  107. package/dist/codex/generated/v2/InAppBrowserRequirements.js +3 -0
  108. package/dist/codex/generated/v2/InAppBrowserRequirements.js.map +1 -0
  109. package/dist/codex/generated/v2/ItemGuardianApprovalReviewCompletedNotification.d.ts +2 -0
  110. package/dist/codex/generated/v2/ItemGuardianApprovalReviewStartedNotification.d.ts +2 -0
  111. package/dist/codex/generated/v2/LoginAccountParams.d.ts +6 -0
  112. package/dist/codex/generated/v2/ManagedHooksRequirements.d.ts +1 -0
  113. package/dist/codex/generated/v2/McpServerConnectionStatus.d.ts +1 -0
  114. package/dist/codex/generated/v2/McpServerConnectionStatus.js +3 -0
  115. package/dist/codex/generated/v2/McpServerConnectionStatus.js.map +1 -0
  116. package/dist/codex/generated/v2/McpServerElicitationRequestParams.d.ts +5 -0
  117. package/dist/codex/generated/v2/McpServerEventNotification.d.ts +5 -0
  118. package/dist/codex/generated/v2/McpServerEventNotification.js +3 -0
  119. package/dist/codex/generated/v2/McpServerEventNotification.js.map +1 -0
  120. package/dist/codex/generated/v2/McpServerEventStreamNotification.d.ts +5 -0
  121. package/dist/codex/generated/v2/McpServerEventStreamNotification.js +3 -0
  122. package/dist/codex/generated/v2/McpServerEventStreamNotification.js.map +1 -0
  123. package/dist/codex/generated/v2/McpServerEventStreamStartParams.d.ts +9 -0
  124. package/dist/codex/generated/v2/McpServerEventStreamStartParams.js +3 -0
  125. package/dist/codex/generated/v2/McpServerEventStreamStartParams.js.map +1 -0
  126. package/dist/codex/generated/v2/McpServerEventStreamStartResponse.d.ts +1 -0
  127. package/dist/codex/generated/v2/McpServerEventStreamStartResponse.js +3 -0
  128. package/dist/codex/generated/v2/McpServerEventStreamStartResponse.js.map +1 -0
  129. package/dist/codex/generated/v2/McpServerEventStreamStopParams.d.ts +3 -0
  130. package/dist/codex/generated/v2/McpServerEventStreamStopParams.js +3 -0
  131. package/dist/codex/generated/v2/McpServerEventStreamStopParams.js.map +1 -0
  132. package/dist/codex/generated/v2/McpServerEventStreamStopResponse.d.ts +1 -0
  133. package/dist/codex/generated/v2/McpServerEventStreamStopResponse.js +3 -0
  134. package/dist/codex/generated/v2/McpServerEventStreamStopResponse.js.map +1 -0
  135. package/dist/codex/generated/v2/McpServerStatus.d.ts +5 -0
  136. package/dist/codex/generated/v2/MisalignmentErrorDetails.d.ts +15 -0
  137. package/dist/codex/generated/v2/MisalignmentErrorDetails.js +3 -0
  138. package/dist/codex/generated/v2/MisalignmentErrorDetails.js.map +1 -0
  139. package/dist/codex/generated/v2/MisalignmentSteer.d.ts +3 -0
  140. package/dist/codex/generated/v2/MisalignmentSteer.js +3 -0
  141. package/dist/codex/generated/v2/MisalignmentSteer.js.map +1 -0
  142. package/dist/codex/generated/v2/PluginReconcileChangedPlugin.d.ts +19 -0
  143. package/dist/codex/generated/v2/PluginReconcileChangedPlugin.js +3 -0
  144. package/dist/codex/generated/v2/PluginReconcileChangedPlugin.js.map +1 -0
  145. package/dist/codex/generated/v2/PluginReconcileParams.d.ts +6 -0
  146. package/dist/codex/generated/v2/PluginReconcileParams.js +3 -0
  147. package/dist/codex/generated/v2/PluginReconcileParams.js.map +1 -0
  148. package/dist/codex/generated/v2/PluginReconcileResponse.d.ts +23 -0
  149. package/dist/codex/generated/v2/PluginReconcileResponse.js +3 -0
  150. package/dist/codex/generated/v2/PluginReconcileResponse.js.map +1 -0
  151. package/dist/codex/generated/v2/Project.d.ts +4 -0
  152. package/dist/codex/generated/v2/ProjectListParams.d.ts +10 -0
  153. package/dist/codex/generated/v2/ProjectSortKey.d.ts +1 -0
  154. package/dist/codex/generated/v2/ProjectSortKey.js +3 -0
  155. package/dist/codex/generated/v2/ProjectSortKey.js.map +1 -0
  156. package/dist/codex/generated/v2/RawResponseCompletedNotification.d.ts +2 -0
  157. package/dist/codex/generated/v2/ResponseUsageMetadata.d.ts +8 -0
  158. package/dist/codex/generated/v2/ResponseUsageMetadata.js +3 -0
  159. package/dist/codex/generated/v2/ResponseUsageMetadata.js.map +1 -0
  160. package/dist/codex/generated/v2/SkillMetadata.d.ts +4 -0
  161. package/dist/codex/generated/v2/SubAgentActivityKind.d.ts +1 -1
  162. package/dist/codex/generated/v2/Thread.d.ts +11 -0
  163. package/dist/codex/generated/v2/ThreadForkParams.d.ts +3 -1
  164. package/dist/codex/generated/v2/ThreadItem.d.ts +9 -0
  165. package/dist/codex/generated/v2/ThreadReadParams.d.ts +3 -0
  166. package/dist/codex/generated/v2/ThreadRealtimeBemItemPresentation.d.ts +11 -0
  167. package/dist/codex/generated/v2/ThreadRealtimeBemItemPresentation.js +3 -0
  168. package/dist/codex/generated/v2/ThreadRealtimeBemItemPresentation.js.map +1 -0
  169. package/dist/codex/generated/v2/ThreadRealtimeItem.d.ts +24 -0
  170. package/dist/codex/generated/v2/ThreadRealtimeItem.js +3 -0
  171. package/dist/codex/generated/v2/ThreadRealtimeItem.js.map +1 -0
  172. package/dist/codex/generated/v2/ThreadRealtimeItemCompletedNotification.d.ts +8 -0
  173. package/dist/codex/generated/v2/ThreadRealtimeItemCompletedNotification.js +3 -0
  174. package/dist/codex/generated/v2/ThreadRealtimeItemCompletedNotification.js.map +1 -0
  175. package/dist/codex/generated/v2/ThreadRealtimeItemStartedNotification.d.ts +8 -0
  176. package/dist/codex/generated/v2/ThreadRealtimeItemStartedNotification.js +3 -0
  177. package/dist/codex/generated/v2/ThreadRealtimeItemStartedNotification.js.map +1 -0
  178. package/dist/codex/generated/v2/ThreadRealtimeItemTranscriptDeltaNotification.d.ts +8 -0
  179. package/dist/codex/generated/v2/ThreadRealtimeItemTranscriptDeltaNotification.js +3 -0
  180. package/dist/codex/generated/v2/ThreadRealtimeItemTranscriptDeltaNotification.js.map +1 -0
  181. package/dist/codex/generated/v2/ThreadRealtimeSessionOutcome.d.ts +1 -0
  182. package/dist/codex/generated/v2/ThreadRealtimeSessionOutcome.js +3 -0
  183. package/dist/codex/generated/v2/ThreadRealtimeSessionOutcome.js.map +1 -0
  184. package/dist/codex/generated/v2/ThreadRealtimeStartTransport.d.ts +6 -0
  185. package/dist/codex/generated/v2/ThreadRealtimeTranscriptRole.d.ts +1 -0
  186. package/dist/codex/generated/v2/ThreadRealtimeTranscriptRole.js +3 -0
  187. package/dist/codex/generated/v2/ThreadRealtimeTranscriptRole.js.map +1 -0
  188. package/dist/codex/generated/v2/ThreadResumeParams.d.ts +3 -1
  189. package/dist/codex/generated/v2/ThreadShellCommandParams.d.ts +6 -0
  190. package/dist/codex/generated/v2/ThreadTimelineEntry.d.ts +31 -0
  191. package/dist/codex/generated/v2/ThreadTimelineEntry.js +3 -0
  192. package/dist/codex/generated/v2/ThreadTimelineEntry.js.map +1 -0
  193. package/dist/codex/generated/v2/ThreadTimelineListParams.d.ts +8 -0
  194. package/dist/codex/generated/v2/ThreadTimelineListParams.js +3 -0
  195. package/dist/codex/generated/v2/ThreadTimelineListParams.js.map +1 -0
  196. package/dist/codex/generated/v2/ThreadTimelineListResponse.d.ts +9 -0
  197. package/dist/codex/generated/v2/ThreadTimelineListResponse.js +3 -0
  198. package/dist/codex/generated/v2/ThreadTimelineListResponse.js.map +1 -0
  199. package/dist/codex/generated/v2/TurnError.d.ts +5 -0
  200. package/dist/codex/generated/v2/TurnSettingsUpdateParams.d.ts +34 -0
  201. package/dist/codex/generated/v2/TurnSettingsUpdateParams.js +3 -0
  202. package/dist/codex/generated/v2/TurnSettingsUpdateParams.js.map +1 -0
  203. package/dist/codex/generated/v2/TurnSettingsUpdateResponse.d.ts +4 -0
  204. package/dist/codex/generated/v2/TurnSettingsUpdateResponse.js +3 -0
  205. package/dist/codex/generated/v2/TurnSettingsUpdateResponse.js.map +1 -0
  206. package/dist/codex/generated/v2/TurnSettingsUpdateStatus.d.ts +1 -0
  207. package/dist/codex/generated/v2/TurnSettingsUpdateStatus.js +3 -0
  208. package/dist/codex/generated/v2/TurnSettingsUpdateStatus.js.map +1 -0
  209. package/dist/codex/generated/v2/TurnStartParams.d.ts +19 -0
  210. package/dist/codex/generated/v2/TurnToolOutput.d.ts +6 -0
  211. package/dist/codex/generated/v2/TurnToolOutput.js +3 -0
  212. package/dist/codex/generated/v2/TurnToolOutput.js.map +1 -0
  213. package/dist/codex/generated/v2/index.d.ts +46 -0
  214. package/dist/daemon/ownership.js +9 -1
  215. package/dist/daemon/ownership.js.map +1 -1
  216. package/dist/daemon/probe.js +1 -1
  217. package/dist/gateway/clientConnection.js +105 -19
  218. package/dist/gateway/clientConnection.js.map +1 -1
  219. package/dist/gateway/optimisticSideThreads.d.ts +11 -5
  220. package/dist/gateway/optimisticSideThreads.js +103 -47
  221. package/dist/gateway/optimisticSideThreads.js.map +1 -1
  222. package/dist/gateway/remoteRelay.js +2 -2
  223. package/dist/gateway/remoteRelay.js.map +1 -1
  224. package/dist/gateway/server.js +31 -1
  225. package/dist/gateway/server.js.map +1 -1
  226. package/dist/gateway/stockSideThreads.d.ts +3 -6
  227. package/dist/gateway/stockSideThreads.js +38 -43
  228. package/dist/gateway/stockSideThreads.js.map +1 -1
  229. package/dist/gateway/threadList.d.ts +9 -3
  230. package/dist/gateway/threadList.js +66 -15
  231. package/dist/gateway/threadList.js.map +1 -1
  232. package/dist/gateway/transientNotice.js +4 -4
  233. package/dist/gateway/transientNotice.js.map +1 -1
  234. package/dist/handoff/service.d.ts +10 -2
  235. package/dist/handoff/service.js +164 -106
  236. package/dist/handoff/service.js.map +1 -1
  237. package/dist/handoff/store.d.ts +21 -0
  238. package/dist/handoff/store.js +15 -0
  239. package/dist/handoff/store.js.map +1 -1
  240. package/dist/handoff/titleGeneration.d.ts +1 -0
  241. package/dist/handoff/titleGeneration.js.map +1 -1
  242. package/dist/management/setup.d.ts +6 -0
  243. package/dist/management/setup.js +11 -3
  244. package/dist/management/setup.js.map +1 -1
  245. package/dist/protocol/search.d.ts +24 -0
  246. package/dist/protocol/search.js +105 -0
  247. package/dist/protocol/search.js.map +1 -0
  248. package/dist/protocol/turnPagination.d.ts +16 -0
  249. package/dist/protocol/turnPagination.js +75 -35
  250. package/dist/protocol/turnPagination.js.map +1 -1
  251. package/dist/runtime/dependencies.js +1 -1
  252. package/dist/store/HybridStore.d.ts +4 -0
  253. package/dist/store/HybridStore.js +1 -1
  254. package/dist/store/HybridStore.js.map +1 -1
  255. package/dist/store/memoryStore.d.ts +6 -0
  256. package/dist/store/memoryStore.js +16 -0
  257. package/dist/store/memoryStore.js.map +1 -1
  258. package/dist/store/sqliteStore.d.ts +3 -0
  259. package/dist/store/sqliteStore.js +57 -0
  260. package/dist/store/sqliteStore.js.map +1 -1
  261. package/legal/THIRD_PARTY_NOTICES.md +160 -140
  262. package/package.json +6 -6
  263. package/scripts/install-claude-stack.sh +27 -4
  264. package/vendor/codex/continuation.md +5 -0
@@ -7,8 +7,9 @@ import { SqliteHybridStore } from "../store/sqliteStore.js";
7
7
  import { LayeredHybridStore } from "../store/memoryStore.js";
8
8
  import { createClaudeQuery } from "./queryFactory.js";
9
9
  import { claudeEnvironment } from "./environment.js";
10
- import { invalidParams } from "../protocol/errors.js";
11
- import { paginateTurns, turnCursor } from "../protocol/turnPagination.js";
10
+ import { invalidParams, invalidRequest } from "../protocol/errors.js";
11
+ import { historyCursors, paginateItems, paginateTurns, startedTurn } from "../protocol/turnPagination.js";
12
+ import { searchTurnOccurrences, threadSearchSnippet } from "../protocol/search.js";
12
13
  import { MetricsRegistry } from "../observability/metrics.js";
13
14
  import { SdkTranscriptBrancher, } from "./transcriptBrancher.js";
14
15
  import { ClaudeRateLimitCoordinator, } from "./rateLimits.js";
@@ -257,6 +258,8 @@ export class ClaudeService {
257
258
  nativeSessionCatalog;
258
259
  ephemeralReleases = new Map();
259
260
  ephemeralReleaseTimers = new Map();
261
+ /** Root threads whose last turn completed or failed and whose queue drains once the session is quiescent. */
262
+ queueDrainPending = new Set();
260
263
  removalRetries = new Map();
261
264
  terminalAdmins = new Map();
262
265
  settingsUpdates = new Map();
@@ -292,7 +295,7 @@ export class ClaudeService {
292
295
  const sessionRepository = new ClaudeSessionRepository(this.store);
293
296
  this.sessionOutput = new ClaudeOutputAdapter(hub);
294
297
  this.rateLimits = new ClaudeRateLimitCoordinator(logger);
295
- this.sessions = new ClaudeSessionRegistry((threadId) => new ClaudeSession(threadId, sessionRepository, this.sessionOutput, undefined, metrics, undefined, (childThreadId) => this.sessions.registerChild(childThreadId, threadId), (childThreadId) => this.sessions.unregisterChild(childThreadId), shellRunner, undefined, {
298
+ this.sessions = new ClaudeSessionRegistry((threadId) => new ClaudeSession(threadId, sessionRepository, this.sessionOutput, undefined, metrics, (update) => this.onSessionLifecycle(threadId, update), (childThreadId) => this.sessions.registerChild(childThreadId, threadId), (childThreadId) => this.sessions.unregisterChild(childThreadId), shellRunner, undefined, {
296
299
  claudeBinary: this.config.claudeBinary,
297
300
  logger: this.logger,
298
301
  queryFactory: this.queryFactory,
@@ -301,7 +304,7 @@ export class ClaudeService {
301
304
  invalidateModelCatalog: () => this.modelCatalog?.invalidate?.(),
302
305
  ...(this.skillsChanged ? { skillsChanged: this.skillsChanged } : {}),
303
306
  isClosing: () => this.closing,
304
- persistUserSideSessions: this.config.features?.sideChatPromotion ?? true,
307
+ persistUserSideSessions: true,
305
308
  interactiveQuestions: this.config.features?.interactiveQuestions ?? true,
306
309
  resolveChildModel: (model) => {
307
310
  const value = normalizeClaudeModelIdentifier(model);
@@ -548,10 +551,10 @@ export class ClaudeService {
548
551
  throw invalidParams(`Unknown Claude thread '${threadId}'.`);
549
552
  if (record.thread.parentThreadId) {
550
553
  record = this.withCatalogModel(record);
554
+ record = { ...record, thread: this.withNativeMetadata(record) };
551
555
  return {
552
556
  ...threadResponse(record, !resume.excludeTurns),
553
- turnsBackwardsCursor: record.thread.turns.length ? turnCursor(record.thread.turns.at(-1).id, true) : null,
554
- itemsBackwardsCursor: record.thread.turns.some((turn) => turn.items.length) ? "hyb-item:0" : null,
557
+ ...historyCursors(record.thread.turns),
555
558
  initialTurnsPage: resume.initialTurnsPage
556
559
  ? this.turnsPage({
557
560
  threadId,
@@ -575,10 +578,12 @@ export class ClaudeService {
575
578
  await (await this.sessions.getOrCreate(threadId)).materializeRuntime();
576
579
  record = await this.sessions.submit(threadId, { type: "readThread", includeTurns: true });
577
580
  record = this.withCatalogModel(record);
581
+ record = { ...record, thread: this.withNativeMetadata(record) };
582
+ if (this.store.listQueuedSubmissions(threadId).length)
583
+ this.scheduleQueueDrain(threadId);
578
584
  return {
579
585
  ...threadResponse(record, !resume.excludeTurns),
580
- turnsBackwardsCursor: record.thread.turns.length ? turnCursor(record.thread.turns.at(-1).id, true) : null,
581
- itemsBackwardsCursor: record.thread.turns.some((turn) => turn.items.length) ? "hyb-item:0" : null,
586
+ ...historyCursors(record.thread.turns),
582
587
  initialTurnsPage: resume.initialTurnsPage
583
588
  ? this.turnsPage({
584
589
  threadId,
@@ -629,6 +634,10 @@ export class ClaudeService {
629
634
  }
630
635
  async prepareTurn(params, review) {
631
636
  this.requireIndependentThread(params.threadId, "start a turn in");
637
+ if (params.toolOutput)
638
+ throw invalidParams("Claude threads do not support toolOutput.");
639
+ if (params.serviceTierForTurn != null)
640
+ this.logger.warn("claude.turn.service-tier-for-turn.ignored", { threadId: params.threadId, serviceTierForTurn: params.serviceTierForTurn });
632
641
  const availability = await this.availabilityProbe();
633
642
  if (availability.state !== "ready")
634
643
  throw invalidParams(providerUnavailableMessage(availability));
@@ -655,7 +664,8 @@ export class ClaudeService {
655
664
  let announcement;
656
665
  const startAndWait = () => announcement ? announcement.then(startRuntime) : startRuntime();
657
666
  return {
658
- response: { turn: prepared.turn },
667
+ response: { turn: startedTurn(prepared.turn) },
668
+ turn: prepared.turn,
659
669
  announce: () => announcement ??= this.sessions.submit(params.threadId, { type: "announceTurn", turnId: prepared.turn.id }),
660
670
  start: () => {
661
671
  void startAndWait().catch((error) => this.logger.error("claude.turn.start-failed", { threadId: params.threadId, error: String(error) }));
@@ -891,7 +901,10 @@ export class ClaudeService {
891
901
  }
892
902
  async shellCommand(params) {
893
903
  this.requireIndependentThread(params.threadId, "run a shell command in");
894
- await this.sessions.submit(params.threadId, { type: "runShell", command: params.command });
904
+ const timeoutMs = params.timeoutMs ?? 3_600_000;
905
+ if (timeoutMs < 0)
906
+ throw invalidParams(`thread/shellCommand timeoutMs must be non-negative, got ${timeoutMs}`);
907
+ await this.sessions.submit(params.threadId, { type: "runShell", command: params.command, timeoutMs });
895
908
  return {};
896
909
  }
897
910
  async prepareReview(params) {
@@ -914,6 +927,8 @@ export class ClaudeService {
914
927
  let reviewThreadId = params.threadId;
915
928
  let forkedThread;
916
929
  if (params.delivery === "detached") {
930
+ if (this.requireRecord(params.threadId, false).thread.historyMode === "paginated")
931
+ throw invalidRequest("paginated threads do not support detached review");
917
932
  const fork = await this.forkThread({ threadId: params.threadId, excludeTurns: false });
918
933
  reviewThreadId = fork.thread.id;
919
934
  forkedThread = fork.thread;
@@ -923,7 +938,7 @@ export class ClaudeService {
923
938
  threadId: reviewThreadId,
924
939
  input: [{ type: "text", text: prompt, text_elements: [] }],
925
940
  }, { label: review, display: review });
926
- const userItem = prepared.response.turn.items.find((item) => item.type === "userMessage");
941
+ const userItem = prepared.turn.items.find((item) => item.type === "userMessage");
927
942
  return {
928
943
  response: {
929
944
  turn: {
@@ -982,6 +997,15 @@ export class ClaudeService {
982
997
  this.nativeMetadataRead = read;
983
998
  return read;
984
999
  }
1000
+ async setGeneratedThreadName(params, userPrompt) {
1001
+ const record = this.requireRecord(params.threadId, false);
1002
+ if (record.thread.parentThreadId || this.nativeMetadata.get(record.claudeSessionId)?.customTitle)
1003
+ return;
1004
+ await this.sessions.submit(params.threadId, {
1005
+ type: "threadAdmin",
1006
+ command: { kind: "generatedName", name: params.name, userPrompt },
1007
+ });
1008
+ }
985
1009
  async setThreadName(params) {
986
1010
  const record = this.requireRecord(params.threadId, false);
987
1011
  if (record.thread.parentThreadId) {
@@ -1086,7 +1110,7 @@ export class ClaudeService {
1086
1110
  if (this.closing || this.ephemeralReleaseTimers.has(threadId))
1087
1111
  return;
1088
1112
  const record = this.store.getThreadRecord(threadId, false);
1089
- if (!record?.thread.ephemeral || record.thread.parentThreadId)
1113
+ if (!record?.thread.ephemeral || record.thread.parentThreadId || record.thread.threadSource === "user")
1090
1114
  return;
1091
1115
  const timer = setTimeout(() => {
1092
1116
  this.ephemeralReleaseTimers.delete(threadId);
@@ -1125,63 +1149,28 @@ export class ClaudeService {
1125
1149
  }
1126
1150
  listItems(params) {
1127
1151
  this.assertThreadAvailable(params.threadId);
1128
- const items = this.store.listTurns(params.threadId)
1129
- .flatMap((turn) => params.turnId && turn.id !== params.turnId
1130
- ? []
1131
- : turn.items.map((item) => ({ turnId: turn.id, item })));
1132
- const ordered = params.sortDirection === "desc" ? [...items].reverse() : items;
1133
- if (params.cursor && !params.cursor.startsWith("hyb-item:"))
1134
- throw invalidParams("Invalid Claude item cursor.");
1135
- const offset = params.cursor?.startsWith("hyb-item:") ? Number(params.cursor.slice("hyb-item:".length)) : 0;
1136
- if (!Number.isInteger(offset) || offset < 0)
1137
- throw invalidParams("Invalid Claude item cursor.");
1138
- const limit = Math.max(1, Math.min(params.limit ?? 50, 100));
1139
- const data = ordered.slice(offset, offset + limit);
1140
- return {
1141
- data,
1142
- nextCursor: offset + data.length < ordered.length ? `hyb-item:${offset + data.length}` : null,
1143
- backwardsCursor: data.length > 0 ? `hyb-item:${Math.max(0, offset - limit)}` : null,
1144
- };
1152
+ return paginateItems(this.store.listTurns(params.threadId), params, ["hyb-item:"]);
1145
1153
  }
1146
1154
  searchOccurrences(params) {
1147
1155
  this.assertThreadAvailable(params.threadId);
1156
+ return searchTurnOccurrences(params.threadId, this.store.listTurns(params.threadId), params);
1157
+ }
1158
+ /** `thread/search` over durable Claude threads: one result per thread with the first matching snippet. */
1159
+ searchThreads(params) {
1148
1160
  const searchTerm = params.searchTerm.trim();
1149
- if (!searchTerm)
1150
- throw invalidParams("thread/searchOccurrences requires a non-empty searchTerm.");
1151
- if (params.cursor && !params.cursor.startsWith("hyb-search:"))
1152
- throw invalidParams("Invalid Claude search cursor.");
1153
- const offset = params.cursor ? Number(params.cursor.slice("hyb-search:".length)) : 0;
1154
- if (!Number.isInteger(offset) || offset < 0)
1155
- throw invalidParams("Invalid Claude search cursor.");
1156
- const turns = this.store.listTurns(params.threadId);
1157
- const needle = searchTerm.toLocaleLowerCase();
1158
- const occurrences = turns.flatMap((turn) => turn.items.flatMap((item) => {
1159
- const text = item.type === "userMessage"
1160
- ? item.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n")
1161
- : item.type === "agentMessage" && item.phase !== "commentary" ? item.text : undefined;
1162
- if (text === undefined)
1161
+ return this.listThreads({ archived: params.archived ?? false }).flatMap((thread) => {
1162
+ if (thread.ephemeral)
1163
1163
  return [];
1164
- const haystack = text.toLocaleLowerCase();
1165
- const matches = [];
1166
- for (let start = haystack.indexOf(needle); start >= 0; start = haystack.indexOf(needle, start + needle.length)) {
1167
- matches.push({
1168
- turnId: turn.id,
1169
- itemId: item.id,
1170
- snippet: text,
1171
- snippetMatchRange: { start, end: start + needle.length },
1172
- turnCursor: turnCursor(turn.id, true),
1173
- });
1174
- }
1175
- return matches;
1176
- }));
1177
- const limit = Math.max(1, Math.min(params.limit ?? 50, 100));
1178
- const data = occurrences.slice(offset, offset + limit);
1179
- return {
1180
- data,
1181
- nextCursor: offset + data.length < occurrences.length ? `hyb-search:${offset + data.length}` : null,
1182
- };
1164
+ const snippet = threadSearchSnippet(this.store.listTurns(thread.id), searchTerm);
1165
+ return snippet === undefined ? [] : [{ thread: { ...thread, turns: [] }, snippet }];
1166
+ });
1183
1167
  }
1184
- async forkThread(params, visibleForkedFromId = params.threadId) {
1168
+ async forkThread(params, visibleForkedFromId = params.threadId, reservedSideId) {
1169
+ if (reservedSideId && this.store.hasThread(reservedSideId)) {
1170
+ const session = await this.sessions.getOrCreate(reservedSideId);
1171
+ await session.ensureEphemeralRuntime();
1172
+ return threadResponse(this.requireRecord(reservedSideId, false), false);
1173
+ }
1185
1174
  if (params.path)
1186
1175
  throw invalidParams("Claude thread forks must use threadId, not a Codex rollout path.");
1187
1176
  this.requireIndependentThread(params.threadId, "fork");
@@ -1194,6 +1183,8 @@ export class ClaudeService {
1194
1183
  : undefined;
1195
1184
  const source = await this.sessions.submit(params.threadId, { type: "snapshotBranch" });
1196
1185
  const sourceRecord = source.record;
1186
+ if (sourceRecord.thread.historyMode === "paginated" && params.ephemeral && !params.excludeTurns)
1187
+ throw invalidRequest("ephemeral paginated thread/fork requires `excludeTurns: true`");
1197
1188
  const sidePromotion = Boolean((this.config.features?.sideChatPromotion ?? true)
1198
1189
  && sourceRecord.thread.ephemeral
1199
1190
  && sourceRecord.thread.threadSource === "user"
@@ -1271,12 +1262,13 @@ export class ClaudeService {
1271
1262
  cwd: sourceRecord.thread.cwd,
1272
1263
  runtimeWorkspaceRoots: storedWorkspaceRoots(sourceRecord),
1273
1264
  });
1274
- const threadId = uuidv7();
1265
+ const threadId = reservedSideId ?? uuidv7();
1275
1266
  const thread = {
1276
1267
  ...sourceRecord.thread, id: threadId, ephemeral: params.ephemeral ?? false,
1277
1268
  section: null, sectionEnteredAt: null, projectId: null,
1278
1269
  sessionId: threadId, forkedFromId: visibleForkedFromId,
1279
- cwd, modelProvider: "claude", createdAt, updatedAt: createdAt, recencyAt: createdAt,
1270
+ cwd, modelProvider: "claude", model: modelPickerId, reasoningEffort,
1271
+ createdAt, updatedAt: createdAt, recencyAt: createdAt,
1280
1272
  status: params.ephemeral ? { type: "idle" } : { type: "notLoaded" },
1281
1273
  canAcceptDirectInput: true,
1282
1274
  name: params.ephemeral
@@ -1336,6 +1328,8 @@ export class ClaudeService {
1336
1328
  }
1337
1329
  async rollbackThread(params) {
1338
1330
  const source = await this.truncationSource(params.threadId, "roll back");
1331
+ if (source.record.thread.historyMode === "paginated")
1332
+ throw invalidRequest("paginated threads do not support thread/rollback");
1339
1333
  if (!Number.isInteger(params.numTurns) || params.numTurns < 1)
1340
1334
  throw invalidParams("numTurns must be at least 1.");
1341
1335
  if (params.numTurns > source.record.thread.turns.length)
@@ -1352,8 +1346,7 @@ export class ClaudeService {
1352
1346
  const retained = (await this.truncateThread(source, keepCount)).thread;
1353
1347
  return {
1354
1348
  thread: { ...retained, turns: [] },
1355
- turnsBackwardsCursor: retained.turns.length ? turnCursor(retained.turns.at(-1).id, true) : null,
1356
- itemsBackwardsCursor: retained.turns.some((turn) => turn.items.length) ? "hyb-item:0" : null,
1349
+ ...historyCursors(retained.turns),
1357
1350
  };
1358
1351
  }
1359
1352
  async truncationSource(threadId, verb) {
@@ -1399,6 +1392,106 @@ export class ClaudeService {
1399
1392
  this.requireIndependentThread(params.threadId, "set a goal on");
1400
1393
  return this.sessions.submit(params.threadId, { type: "goal", command: { kind: "prepareSet", params } }).then((mutation) => this.goalHandle(params.threadId, mutation));
1401
1394
  }
1395
+ async addQueuedSubmission(params) {
1396
+ this.requireIndependentThread(params.threadId, "queue a submission in");
1397
+ const queuedSubmission = await this.sessions.submit(params.threadId, {
1398
+ type: "queue",
1399
+ command: { kind: "add", input: params.input, clientUserMessageId: params.clientUserMessageId },
1400
+ });
1401
+ return { response: { queuedSubmission }, after: () => this.drainQueue(params.threadId) };
1402
+ }
1403
+ /** Offset pagination over the durable per-thread queue; the cursor is the base-10 offset, as in stock. */
1404
+ listQueue(params) {
1405
+ this.requireRecord(params.threadId, false);
1406
+ const cursor = params.cursor ?? null;
1407
+ if (cursor !== null && !/^\d+$/u.test(cursor))
1408
+ throw invalidRequest(`invalid queue pagination cursor: ${cursor}`);
1409
+ const offset = cursor === null ? 0 : Number(cursor);
1410
+ const limit = Math.min(Math.max(params.limit ?? 25, 1), 100);
1411
+ const queue = this.store.listQueuedSubmissions(params.threadId);
1412
+ return {
1413
+ data: queue.slice(offset, offset + limit),
1414
+ nextCursor: offset + limit < queue.length ? String(offset + limit) : null,
1415
+ };
1416
+ }
1417
+ async updateQueuedSubmission(params) {
1418
+ this.requireIndependentThread(params.threadId, "update a queued submission in");
1419
+ const queuedSubmission = await this.sessions.submit(params.threadId, {
1420
+ type: "queue",
1421
+ command: { kind: "update", queuedSubmissionId: params.queuedSubmissionId, input: params.input },
1422
+ });
1423
+ return { queuedSubmission };
1424
+ }
1425
+ async deleteQueuedSubmission(params) {
1426
+ this.requireIndependentThread(params.threadId, "delete a queued submission from");
1427
+ const deleted = await this.sessions.submit(params.threadId, {
1428
+ type: "queue", command: { kind: "delete", queuedSubmissionId: params.queuedSubmissionId },
1429
+ });
1430
+ return { deleted };
1431
+ }
1432
+ async reorderQueue(params) {
1433
+ this.requireIndependentThread(params.threadId, "reorder the queue of");
1434
+ await this.sessions.submit(params.threadId, {
1435
+ type: "queue", command: { kind: "reorder", queuedSubmissionIds: params.queuedSubmissionIds },
1436
+ });
1437
+ return {};
1438
+ }
1439
+ async prepareQueueStart(params) {
1440
+ this.requireIndependentThread(params.threadId, "start a queued submission in");
1441
+ const entry = await this.sessions.submit(params.threadId, {
1442
+ type: "queue",
1443
+ command: {
1444
+ kind: "take",
1445
+ manual: true,
1446
+ ...(params.queuedSubmissionId == null ? {} : { queuedSubmissionId: params.queuedSubmissionId }),
1447
+ },
1448
+ });
1449
+ return this.startQueuedSubmission(params.threadId, entry);
1450
+ }
1451
+ async startQueuedSubmission(threadId, entry) {
1452
+ try {
1453
+ return await this.prepareTurn({ threadId, input: entry.input, clientUserMessageId: entry.clientUserMessageId });
1454
+ }
1455
+ catch (error) {
1456
+ await this.sessions.submit(threadId, { type: "queue", command: { kind: "restore", entry } });
1457
+ throw error;
1458
+ }
1459
+ }
1460
+ /** Starts the head of the queue when the thread is idle and its last turn was not interrupted. */
1461
+ async drainQueue(threadId) {
1462
+ if (this.closing || this.store.listQueuedSubmissions(threadId).length === 0)
1463
+ return;
1464
+ const entry = await this.sessions.submit(threadId, {
1465
+ type: "queue", command: { kind: "take", manual: false },
1466
+ });
1467
+ if (!entry)
1468
+ return;
1469
+ let prepared;
1470
+ try {
1471
+ prepared = await this.startQueuedSubmission(threadId, entry);
1472
+ }
1473
+ catch (error) {
1474
+ this.logger[error instanceof ActiveClaudeTurnError ? "info" : "warn"]("claude.queue.drain-skipped", {
1475
+ threadId, queuedSubmissionId: entry.id, error: String(error),
1476
+ });
1477
+ return;
1478
+ }
1479
+ await prepared.announce();
1480
+ prepared.start();
1481
+ }
1482
+ scheduleQueueDrain(threadId) {
1483
+ queueMicrotask(() => {
1484
+ void this.drainQueue(threadId).catch((error) => {
1485
+ this.logger.warn("claude.queue.drain-failed", { threadId, error: String(error) });
1486
+ });
1487
+ });
1488
+ }
1489
+ onSessionLifecycle(threadId, update) {
1490
+ if (update.completed && update.completed.turn.status !== "interrupted")
1491
+ this.queueDrainPending.add(threadId);
1492
+ if (update.quiescent && this.queueDrainPending.delete(threadId))
1493
+ this.scheduleQueueDrain(threadId);
1494
+ }
1402
1495
  async getGoal(threadId) {
1403
1496
  if (this.requireRecord(threadId, false).thread.parentThreadId)
1404
1497
  return { goal: null };
@@ -1708,6 +1801,10 @@ export class ClaudeService {
1708
1801
  return record;
1709
1802
  }
1710
1803
  withNativeMetadata(record) {
1804
+ if (record.thread.ephemeral && record.thread.threadSource === "user"
1805
+ && !record.thread.parentThreadId && record.thread.status.type === "notLoaded") {
1806
+ record = { ...record, thread: { ...record.thread, status: { type: "idle" } } };
1807
+ }
1711
1808
  const native = this.nativeMetadata.get(record.claudeSessionId);
1712
1809
  if (!native)
1713
1810
  return {
@@ -1807,8 +1904,8 @@ export class ClaudeService {
1807
1904
  section: null,
1808
1905
  sectionEnteredAt: null,
1809
1906
  projectId: null,
1810
- historyMode: params.historyMode ?? "legacy",
1811
- modelProvider: "claude",
1907
+ historyMode: params.historyMode ?? (params.ephemeral ? "legacy" : "paginated"),
1908
+ modelProvider: "claude", model: modelPickerId, reasoningEffort,
1812
1909
  createdAt,
1813
1910
  updatedAt: createdAt,
1814
1911
  recencyAt: createdAt,