yepanywhere 0.5.2 → 0.6.1

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 (944) hide show
  1. package/README.md +21 -5
  2. package/bundled/@yep-anywhere/shared/dist/agent-context.d.ts +9 -0
  3. package/bundled/@yep-anywhere/shared/dist/agent-context.d.ts.map +1 -0
  4. package/bundled/@yep-anywhere/shared/dist/agent-context.js +18 -0
  5. package/bundled/@yep-anywhere/shared/dist/agent-context.js.map +1 -0
  6. package/bundled/@yep-anywhere/shared/dist/app-types.d.ts +107 -1
  7. package/bundled/@yep-anywhere/shared/dist/app-types.d.ts.map +1 -1
  8. package/bundled/@yep-anywhere/shared/dist/app-types.js +4 -0
  9. package/bundled/@yep-anywhere/shared/dist/app-types.js.map +1 -1
  10. package/bundled/@yep-anywhere/shared/dist/binary-framing.d.ts +4 -1
  11. package/bundled/@yep-anywhere/shared/dist/binary-framing.d.ts.map +1 -1
  12. package/bundled/@yep-anywhere/shared/dist/binary-framing.js +8 -3
  13. package/bundled/@yep-anywhere/shared/dist/binary-framing.js.map +1 -1
  14. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/AssistantEntrySchema.d.ts +4 -0
  15. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/AssistantEntrySchema.d.ts.map +1 -1
  16. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.d.ts +4 -0
  17. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.d.ts.map +1 -1
  18. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.js +7 -0
  19. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.js.map +1 -1
  20. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/ProgressEntrySchema.d.ts +4 -0
  21. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/ProgressEntrySchema.d.ts.map +1 -1
  22. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.d.ts +64 -1
  23. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.d.ts.map +1 -1
  24. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.js +9 -0
  25. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.js.map +1 -1
  26. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/UserEntrySchema.d.ts +4 -0
  27. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/UserEntrySchema.d.ts.map +1 -1
  28. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.d.ts.map +1 -1
  29. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.js +45 -1
  30. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.js.map +1 -1
  31. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/index.d.ts +144 -2
  32. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/index.d.ts.map +1 -1
  33. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.d.ts +6 -4
  34. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.d.ts.map +1 -1
  35. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.js +8 -1
  36. package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.js.map +1 -1
  37. package/bundled/@yep-anywhere/shared/dist/codex-schema/index.d.ts +3 -2
  38. package/bundled/@yep-anywhere/shared/dist/codex-schema/index.d.ts.map +1 -1
  39. package/bundled/@yep-anywhere/shared/dist/codex-schema/index.js +3 -2
  40. package/bundled/@yep-anywhere/shared/dist/codex-schema/index.js.map +1 -1
  41. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts +266 -17
  42. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts.map +1 -1
  43. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js +128 -1
  44. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js.map +1 -1
  45. package/bundled/@yep-anywhere/shared/dist/dag.d.ts +31 -11
  46. package/bundled/@yep-anywhere/shared/dist/dag.d.ts.map +1 -1
  47. package/bundled/@yep-anywhere/shared/dist/dag.js +77 -33
  48. package/bundled/@yep-anywhere/shared/dist/dag.js.map +1 -1
  49. package/bundled/@yep-anywhere/shared/dist/git-status.d.ts +93 -1
  50. package/bundled/@yep-anywhere/shared/dist/git-status.d.ts.map +1 -1
  51. package/bundled/@yep-anywhere/shared/dist/git-status.js +1 -1
  52. package/bundled/@yep-anywhere/shared/dist/git-status.js.map +1 -1
  53. package/bundled/@yep-anywhere/shared/dist/ideMetadata.js +1 -1
  54. package/bundled/@yep-anywhere/shared/dist/ideMetadata.js.map +1 -1
  55. package/bundled/@yep-anywhere/shared/dist/index.d.ts +22 -11
  56. package/bundled/@yep-anywhere/shared/dist/index.d.ts.map +1 -1
  57. package/bundled/@yep-anywhere/shared/dist/index.js +9 -3
  58. package/bundled/@yep-anywhere/shared/dist/index.js.map +1 -1
  59. package/bundled/@yep-anywhere/shared/dist/local-resource.d.ts +1 -0
  60. package/bundled/@yep-anywhere/shared/dist/local-resource.d.ts.map +1 -1
  61. package/bundled/@yep-anywhere/shared/dist/local-resource.js +4 -0
  62. package/bundled/@yep-anywhere/shared/dist/local-resource.js.map +1 -1
  63. package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.d.ts +136 -0
  64. package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.d.ts.map +1 -1
  65. package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.js +65 -3
  66. package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.js.map +1 -1
  67. package/bundled/@yep-anywhere/shared/dist/opencode-schema/types.d.ts +1 -1
  68. package/bundled/@yep-anywhere/shared/dist/opencode-schema/types.d.ts.map +1 -1
  69. package/bundled/@yep-anywhere/shared/dist/project-queue.d.ts +153 -0
  70. package/bundled/@yep-anywhere/shared/dist/project-queue.d.ts.map +1 -0
  71. package/bundled/@yep-anywhere/shared/dist/project-queue.js +8 -0
  72. package/bundled/@yep-anywhere/shared/dist/project-queue.js.map +1 -0
  73. package/bundled/@yep-anywhere/shared/dist/relay-protocol.d.ts +26 -2
  74. package/bundled/@yep-anywhere/shared/dist/relay-protocol.d.ts.map +1 -1
  75. package/bundled/@yep-anywhere/shared/dist/relay-protocol.js +33 -0
  76. package/bundled/@yep-anywhere/shared/dist/relay-protocol.js.map +1 -1
  77. package/bundled/@yep-anywhere/shared/dist/relay.d.ts +45 -5
  78. package/bundled/@yep-anywhere/shared/dist/relay.d.ts.map +1 -1
  79. package/bundled/@yep-anywhere/shared/dist/relay.js.map +1 -1
  80. package/bundled/@yep-anywhere/shared/dist/safe-restart.d.ts +25 -0
  81. package/bundled/@yep-anywhere/shared/dist/safe-restart.d.ts.map +1 -0
  82. package/bundled/@yep-anywhere/shared/dist/safe-restart.js +2 -0
  83. package/bundled/@yep-anywhere/shared/dist/safe-restart.js.map +1 -0
  84. package/bundled/@yep-anywhere/shared/dist/server-capabilities.d.ts +248 -0
  85. package/bundled/@yep-anywhere/shared/dist/server-capabilities.d.ts.map +1 -0
  86. package/bundled/@yep-anywhere/shared/dist/server-capabilities.js +251 -0
  87. package/bundled/@yep-anywhere/shared/dist/server-capabilities.js.map +1 -0
  88. package/bundled/@yep-anywhere/shared/dist/session/SessionView.d.ts +2 -2
  89. package/bundled/@yep-anywhere/shared/dist/session/SessionView.js +3 -3
  90. package/bundled/@yep-anywhere/shared/dist/session/SessionView.js.map +1 -1
  91. package/bundled/@yep-anywhere/shared/dist/session/UnifiedSession.d.ts +10 -0
  92. package/bundled/@yep-anywhere/shared/dist/session/UnifiedSession.d.ts.map +1 -1
  93. package/bundled/@yep-anywhere/shared/dist/session-liveness.d.ts +18 -0
  94. package/bundled/@yep-anywhere/shared/dist/session-liveness.d.ts.map +1 -1
  95. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.d.ts +26 -0
  96. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.d.ts.map +1 -0
  97. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js +9 -0
  98. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js.map +1 -0
  99. package/bundled/@yep-anywhere/shared/dist/types.d.ts +253 -8
  100. package/bundled/@yep-anywhere/shared/dist/types.d.ts.map +1 -1
  101. package/bundled/@yep-anywhere/shared/dist/types.js +48 -5
  102. package/bundled/@yep-anywhere/shared/dist/types.js.map +1 -1
  103. package/bundled/@yep-anywhere/shared/dist/upload.d.ts +34 -2
  104. package/bundled/@yep-anywhere/shared/dist/upload.d.ts.map +1 -1
  105. package/bundled/@yep-anywhere/shared/dist/user-message-metadata.d.ts +47 -0
  106. package/bundled/@yep-anywhere/shared/dist/user-message-metadata.d.ts.map +1 -1
  107. package/bundled/@yep-anywhere/shared/dist/user-message-metadata.js +59 -1
  108. package/bundled/@yep-anywhere/shared/dist/user-message-metadata.js.map +1 -1
  109. package/bundled/@yep-anywhere/shared/dist/workstreams.d.ts +54 -0
  110. package/bundled/@yep-anywhere/shared/dist/workstreams.d.ts.map +1 -0
  111. package/bundled/@yep-anywhere/shared/dist/workstreams.js +8 -0
  112. package/bundled/@yep-anywhere/shared/dist/workstreams.js.map +1 -0
  113. package/bundled/@yep-anywhere/shared/package.json +1 -1
  114. package/client-dist/assets/alegreya-sans-latin-400-italic-BQIUBrX-.woff2 +0 -0
  115. package/client-dist/assets/alegreya-sans-latin-400-normal-BxeZdXas.woff2 +0 -0
  116. package/client-dist/assets/alegreya-sans-latin-500-normal-uodOEoAl.woff2 +0 -0
  117. package/client-dist/assets/alegreya-sans-latin-700-normal-DFYOGfC8.woff2 +0 -0
  118. package/client-dist/assets/de-BNhVdIq4.js +6 -0
  119. package/client-dist/assets/es-Csn9n24J.js +6 -0
  120. package/client-dist/assets/fira-mono-latin-400-normal-DVTTRLHv.woff2 +0 -0
  121. package/client-dist/assets/fira-mono-latin-ext-400-normal-B2gPvaNr.woff2 +0 -0
  122. package/client-dist/assets/fira-mono-symbols2-400-normal-C6JptOil.woff2 +0 -0
  123. package/client-dist/assets/fr-DKx4tSpo.js +6 -0
  124. package/client-dist/assets/index-C41clQ66.js +561 -0
  125. package/client-dist/assets/index-LbeFv8ym.css +1 -0
  126. package/client-dist/assets/inter-latin-400-italic-D1l0FflY.woff2 +0 -0
  127. package/client-dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  128. package/client-dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  129. package/client-dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  130. package/client-dist/assets/ja-DAkhkwQZ.js +6 -0
  131. package/client-dist/assets/zh-CN-B7lY0cc6.js +6 -0
  132. package/client-dist/index.html +3 -3
  133. package/client-dist/sw.js +96 -9
  134. package/dist/app.d.ts +25 -0
  135. package/dist/app.d.ts.map +1 -1
  136. package/dist/app.js +350 -35
  137. package/dist/app.js.map +1 -1
  138. package/dist/augments/augment-generator.d.ts +1 -1
  139. package/dist/augments/augment-generator.d.ts.map +1 -1
  140. package/dist/augments/augment-generator.js +2 -2
  141. package/dist/augments/augment-generator.js.map +1 -1
  142. package/dist/augments/markdown-augments.d.ts +1 -1
  143. package/dist/augments/markdown-augments.d.ts.map +1 -1
  144. package/dist/augments/markdown-augments.js +5 -5
  145. package/dist/augments/markdown-augments.js.map +1 -1
  146. package/dist/augments/safe-markdown.d.ts +10 -0
  147. package/dist/augments/safe-markdown.d.ts.map +1 -1
  148. package/dist/augments/safe-markdown.js +146 -3
  149. package/dist/augments/safe-markdown.js.map +1 -1
  150. package/dist/augments/stream-augmenter.d.ts +3 -0
  151. package/dist/augments/stream-augmenter.d.ts.map +1 -1
  152. package/dist/augments/stream-augmenter.js +6 -3
  153. package/dist/augments/stream-augmenter.js.map +1 -1
  154. package/dist/augments/stream-coordinator.d.ts +5 -1
  155. package/dist/augments/stream-coordinator.d.ts.map +1 -1
  156. package/dist/augments/stream-coordinator.js +6 -4
  157. package/dist/augments/stream-coordinator.js.map +1 -1
  158. package/dist/augments/task-list-augments.d.ts +27 -0
  159. package/dist/augments/task-list-augments.d.ts.map +1 -0
  160. package/dist/augments/task-list-augments.js +360 -0
  161. package/dist/augments/task-list-augments.js.map +1 -0
  162. package/dist/cli.d.ts +1 -17
  163. package/dist/cli.d.ts.map +1 -1
  164. package/dist/cli.js +22 -8
  165. package/dist/cli.js.map +1 -1
  166. package/dist/codex/codeModeExec.d.ts +27 -0
  167. package/dist/codex/codeModeExec.d.ts.map +1 -0
  168. package/dist/codex/codeModeExec.js +264 -0
  169. package/dist/codex/codeModeExec.js.map +1 -0
  170. package/dist/codex/correlationDebugLogger.d.ts.map +1 -1
  171. package/dist/codex/correlationDebugLogger.js +36 -8
  172. package/dist/codex/correlationDebugLogger.js.map +1 -1
  173. package/dist/codex/displayActions.d.ts +48 -0
  174. package/dist/codex/displayActions.d.ts.map +1 -0
  175. package/dist/codex/displayActions.js +674 -0
  176. package/dist/codex/displayActions.js.map +1 -0
  177. package/dist/codex/normalization.d.ts +10 -6
  178. package/dist/codex/normalization.d.ts.map +1 -1
  179. package/dist/codex/normalization.js +94 -261
  180. package/dist/codex/normalization.js.map +1 -1
  181. package/dist/config.d.ts +49 -6
  182. package/dist/config.d.ts.map +1 -1
  183. package/dist/config.js +55 -14
  184. package/dist/config.js.map +1 -1
  185. package/dist/defaults.d.ts +3 -0
  186. package/dist/defaults.d.ts.map +1 -0
  187. package/dist/defaults.js +3 -0
  188. package/dist/defaults.js.map +1 -0
  189. package/dist/device/DeviceBridgeService.js +1 -1
  190. package/dist/device/DeviceBridgeService.js.map +1 -1
  191. package/dist/envSettings.d.ts +64 -0
  192. package/dist/envSettings.d.ts.map +1 -0
  193. package/dist/envSettings.js +553 -0
  194. package/dist/envSettings.js.map +1 -0
  195. package/dist/frontend/static.d.ts.map +1 -1
  196. package/dist/frontend/static.js +3 -1
  197. package/dist/frontend/static.js.map +1 -1
  198. package/dist/index.d.ts +1 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +190 -14
  201. package/dist/index.js.map +1 -1
  202. package/dist/indexes/SessionDiscoveryIndex.d.ts +78 -0
  203. package/dist/indexes/SessionDiscoveryIndex.d.ts.map +1 -0
  204. package/dist/indexes/SessionDiscoveryIndex.js +223 -0
  205. package/dist/indexes/SessionDiscoveryIndex.js.map +1 -0
  206. package/dist/indexes/SessionIndexService.d.ts +114 -1
  207. package/dist/indexes/SessionIndexService.d.ts.map +1 -1
  208. package/dist/indexes/SessionIndexService.js +875 -105
  209. package/dist/indexes/SessionIndexService.js.map +1 -1
  210. package/dist/indexes/index.d.ts +2 -1
  211. package/dist/indexes/index.d.ts.map +1 -1
  212. package/dist/indexes/index.js +1 -0
  213. package/dist/indexes/index.js.map +1 -1
  214. package/dist/indexes/types.d.ts +26 -0
  215. package/dist/indexes/types.d.ts.map +1 -1
  216. package/dist/logging/logger.d.ts.map +1 -1
  217. package/dist/logging/logger.js +2 -3
  218. package/dist/logging/logger.js.map +1 -1
  219. package/dist/maintenance/server.d.ts.map +1 -1
  220. package/dist/maintenance/server.js +8 -11
  221. package/dist/maintenance/server.js.map +1 -1
  222. package/dist/metadata/ProjectMetadataService.d.ts +7 -0
  223. package/dist/metadata/ProjectMetadataService.d.ts.map +1 -1
  224. package/dist/metadata/ProjectMetadataService.js +63 -19
  225. package/dist/metadata/ProjectMetadataService.js.map +1 -1
  226. package/dist/metadata/SessionMetadataService.d.ts +82 -3
  227. package/dist/metadata/SessionMetadataService.d.ts.map +1 -1
  228. package/dist/metadata/SessionMetadataService.js +233 -17
  229. package/dist/metadata/SessionMetadataService.js.map +1 -1
  230. package/dist/middleware/file-access.d.ts +66 -0
  231. package/dist/middleware/file-access.d.ts.map +1 -0
  232. package/dist/middleware/file-access.js +112 -0
  233. package/dist/middleware/file-access.js.map +1 -0
  234. package/dist/projects/codex-scanner.d.ts +28 -1
  235. package/dist/projects/codex-scanner.d.ts.map +1 -1
  236. package/dist/projects/codex-scanner.js +149 -39
  237. package/dist/projects/codex-scanner.js.map +1 -1
  238. package/dist/projects/gemini-scanner.d.ts.map +1 -1
  239. package/dist/projects/gemini-scanner.js +59 -6
  240. package/dist/projects/gemini-scanner.js.map +1 -1
  241. package/dist/projects/paths.d.ts +15 -1
  242. package/dist/projects/paths.d.ts.map +1 -1
  243. package/dist/projects/paths.js +69 -23
  244. package/dist/projects/paths.js.map +1 -1
  245. package/dist/projects/scanner.d.ts +5 -0
  246. package/dist/projects/scanner.d.ts.map +1 -1
  247. package/dist/projects/scanner.js +136 -28
  248. package/dist/projects/scanner.js.map +1 -1
  249. package/dist/push/InactivityPushNotifier.d.ts +61 -0
  250. package/dist/push/InactivityPushNotifier.d.ts.map +1 -0
  251. package/dist/push/InactivityPushNotifier.js +305 -0
  252. package/dist/push/InactivityPushNotifier.js.map +1 -0
  253. package/dist/push/PushNotifier.d.ts.map +1 -1
  254. package/dist/push/PushNotifier.js.map +1 -1
  255. package/dist/push/PushService.d.ts +7 -4
  256. package/dist/push/PushService.d.ts.map +1 -1
  257. package/dist/push/PushService.js +27 -7
  258. package/dist/push/PushService.js.map +1 -1
  259. package/dist/push/index.d.ts +2 -1
  260. package/dist/push/index.d.ts.map +1 -1
  261. package/dist/push/index.js +1 -0
  262. package/dist/push/index.js.map +1 -1
  263. package/dist/push/routes.d.ts.map +1 -1
  264. package/dist/push/routes.js +28 -1
  265. package/dist/push/routes.js.map +1 -1
  266. package/dist/push/types.d.ts +21 -3
  267. package/dist/push/types.d.ts.map +1 -1
  268. package/dist/push/types.js +2 -0
  269. package/dist/push/types.js.map +1 -1
  270. package/dist/routes/dev.d.ts +2 -0
  271. package/dist/routes/dev.d.ts.map +1 -1
  272. package/dist/routes/dev.js +25 -0
  273. package/dist/routes/dev.js.map +1 -1
  274. package/dist/routes/env-settings.d.ts +20 -0
  275. package/dist/routes/env-settings.d.ts.map +1 -0
  276. package/dist/routes/env-settings.js +25 -0
  277. package/dist/routes/env-settings.js.map +1 -0
  278. package/dist/routes/files.d.ts +9 -0
  279. package/dist/routes/files.d.ts.map +1 -1
  280. package/dist/routes/files.js +98 -10
  281. package/dist/routes/files.js.map +1 -1
  282. package/dist/routes/git-status.d.ts.map +1 -1
  283. package/dist/routes/git-status.js +691 -23
  284. package/dist/routes/git-status.js.map +1 -1
  285. package/dist/routes/global-sessions.d.ts +11 -1
  286. package/dist/routes/global-sessions.d.ts.map +1 -1
  287. package/dist/routes/global-sessions.js +53 -33
  288. package/dist/routes/global-sessions.js.map +1 -1
  289. package/dist/routes/inbox.d.ts +10 -1
  290. package/dist/routes/inbox.d.ts.map +1 -1
  291. package/dist/routes/inbox.js +30 -3
  292. package/dist/routes/inbox.js.map +1 -1
  293. package/dist/routes/local-file.d.ts +6 -2
  294. package/dist/routes/local-file.d.ts.map +1 -1
  295. package/dist/routes/local-file.js +13 -4
  296. package/dist/routes/local-file.js.map +1 -1
  297. package/dist/routes/local-image.d.ts +2 -1
  298. package/dist/routes/local-image.d.ts.map +1 -1
  299. package/dist/routes/local-image.js.map +1 -1
  300. package/dist/routes/local-resource-policy.d.ts +7 -1
  301. package/dist/routes/local-resource-policy.d.ts.map +1 -1
  302. package/dist/routes/local-resource-policy.js +0 -0
  303. package/dist/routes/local-resource-policy.js.map +1 -1
  304. package/dist/routes/processes.d.ts.map +1 -1
  305. package/dist/routes/processes.js +49 -11
  306. package/dist/routes/processes.js.map +1 -1
  307. package/dist/routes/project-queue-response.d.ts +26 -0
  308. package/dist/routes/project-queue-response.d.ts.map +1 -0
  309. package/dist/routes/project-queue-response.js +188 -0
  310. package/dist/routes/project-queue-response.js.map +1 -0
  311. package/dist/routes/project-queue.d.ts +5 -0
  312. package/dist/routes/project-queue.d.ts.map +1 -0
  313. package/dist/routes/project-queue.js +219 -0
  314. package/dist/routes/project-queue.js.map +1 -0
  315. package/dist/routes/projects.d.ts +6 -0
  316. package/dist/routes/projects.d.ts.map +1 -1
  317. package/dist/routes/projects.js +116 -28
  318. package/dist/routes/projects.js.map +1 -1
  319. package/dist/routes/providers.d.ts +5 -0
  320. package/dist/routes/providers.d.ts.map +1 -1
  321. package/dist/routes/providers.js +52 -33
  322. package/dist/routes/providers.js.map +1 -1
  323. package/dist/routes/public-shares.d.ts.map +1 -1
  324. package/dist/routes/public-shares.js +46 -17
  325. package/dist/routes/public-shares.js.map +1 -1
  326. package/dist/routes/recents.d.ts +3 -0
  327. package/dist/routes/recents.d.ts.map +1 -1
  328. package/dist/routes/recents.js +3 -1
  329. package/dist/routes/recents.js.map +1 -1
  330. package/dist/routes/server-admin.d.ts +9 -0
  331. package/dist/routes/server-admin.d.ts.map +1 -1
  332. package/dist/routes/server-admin.js +45 -7
  333. package/dist/routes/server-admin.js.map +1 -1
  334. package/dist/routes/session-claude-resume-guard.d.ts +16 -0
  335. package/dist/routes/session-claude-resume-guard.d.ts.map +1 -0
  336. package/dist/routes/session-claude-resume-guard.js +50 -0
  337. package/dist/routes/session-claude-resume-guard.js.map +1 -0
  338. package/dist/routes/session-compact-thresholds.d.ts +22 -0
  339. package/dist/routes/session-compact-thresholds.d.ts.map +1 -0
  340. package/dist/routes/session-compact-thresholds.js +35 -0
  341. package/dist/routes/session-compact-thresholds.js.map +1 -0
  342. package/dist/routes/session-index.d.ts +7 -0
  343. package/dist/routes/session-index.d.ts.map +1 -0
  344. package/dist/routes/session-index.js +9 -0
  345. package/dist/routes/session-index.js.map +1 -0
  346. package/dist/routes/session-list-options.d.ts +1 -1
  347. package/dist/routes/session-list-options.d.ts.map +1 -1
  348. package/dist/routes/session-list-options.js +1 -1
  349. package/dist/routes/session-list-options.js.map +1 -1
  350. package/dist/routes/session-metadata-patch.d.ts +24 -0
  351. package/dist/routes/session-metadata-patch.d.ts.map +1 -0
  352. package/dist/routes/session-metadata-patch.js +119 -0
  353. package/dist/routes/session-metadata-patch.js.map +1 -0
  354. package/dist/routes/session-project-routing.d.ts +25 -0
  355. package/dist/routes/session-project-routing.d.ts.map +1 -0
  356. package/dist/routes/session-project-routing.js +32 -0
  357. package/dist/routes/session-project-routing.js.map +1 -0
  358. package/dist/routes/session-provider-resolution.d.ts +28 -0
  359. package/dist/routes/session-provider-resolution.d.ts.map +1 -0
  360. package/dist/routes/session-provider-resolution.js +22 -0
  361. package/dist/routes/session-provider-resolution.js.map +1 -0
  362. package/dist/routes/session-queue-summaries.d.ts +21 -0
  363. package/dist/routes/session-queue-summaries.d.ts.map +1 -0
  364. package/dist/routes/session-queue-summaries.js +79 -0
  365. package/dist/routes/session-queue-summaries.js.map +1 -0
  366. package/dist/routes/session-recovered-queue.d.ts +43 -0
  367. package/dist/routes/session-recovered-queue.d.ts.map +1 -0
  368. package/dist/routes/session-recovered-queue.js +132 -0
  369. package/dist/routes/session-recovered-queue.js.map +1 -0
  370. package/dist/routes/session-request-helpers.d.ts +31 -0
  371. package/dist/routes/session-request-helpers.d.ts.map +1 -0
  372. package/dist/routes/session-request-helpers.js +212 -0
  373. package/dist/routes/session-request-helpers.js.map +1 -0
  374. package/dist/routes/session-thinking-options.d.ts +12 -0
  375. package/dist/routes/session-thinking-options.d.ts.map +1 -0
  376. package/dist/routes/session-thinking-options.js +7 -0
  377. package/dist/routes/session-thinking-options.js.map +1 -0
  378. package/dist/routes/sessions.d.ts +14 -1
  379. package/dist/routes/sessions.d.ts.map +1 -1
  380. package/dist/routes/sessions.js +1965 -679
  381. package/dist/routes/sessions.js.map +1 -1
  382. package/dist/routes/settings-parsers.d.ts +37 -0
  383. package/dist/routes/settings-parsers.d.ts.map +1 -0
  384. package/dist/routes/settings-parsers.js +896 -0
  385. package/dist/routes/settings-parsers.js.map +1 -0
  386. package/dist/routes/settings.d.ts +7 -1
  387. package/dist/routes/settings.d.ts.map +1 -1
  388. package/dist/routes/settings.js +114 -258
  389. package/dist/routes/settings.js.map +1 -1
  390. package/dist/routes/speech.d.ts +24 -2
  391. package/dist/routes/speech.d.ts.map +1 -1
  392. package/dist/routes/speech.js +539 -10
  393. package/dist/routes/speech.js.map +1 -1
  394. package/dist/routes/supervisor-queue.d.ts +6 -0
  395. package/dist/routes/supervisor-queue.d.ts.map +1 -0
  396. package/dist/routes/supervisor-queue.js +35 -0
  397. package/dist/routes/supervisor-queue.js.map +1 -0
  398. package/dist/routes/upload.d.ts +2 -0
  399. package/dist/routes/upload.d.ts.map +1 -1
  400. package/dist/routes/upload.js +190 -38
  401. package/dist/routes/upload.js.map +1 -1
  402. package/dist/routes/version.d.ts +16 -1
  403. package/dist/routes/version.d.ts.map +1 -1
  404. package/dist/routes/version.js +27 -8
  405. package/dist/routes/version.js.map +1 -1
  406. package/dist/routes/workstreams.d.ts +11 -0
  407. package/dist/routes/workstreams.d.ts.map +1 -0
  408. package/dist/routes/workstreams.js +144 -0
  409. package/dist/routes/workstreams.js.map +1 -0
  410. package/dist/routes/ws-message-router.d.ts +10 -1
  411. package/dist/routes/ws-message-router.d.ts.map +1 -1
  412. package/dist/routes/ws-message-router.js +43 -4
  413. package/dist/routes/ws-message-router.js.map +1 -1
  414. package/dist/routes/ws-relay-handlers.d.ts +27 -5
  415. package/dist/routes/ws-relay-handlers.d.ts.map +1 -1
  416. package/dist/routes/ws-relay-handlers.js +177 -19
  417. package/dist/routes/ws-relay-handlers.js.map +1 -1
  418. package/dist/routes/ws-relay.d.ts +19 -0
  419. package/dist/routes/ws-relay.d.ts.map +1 -1
  420. package/dist/routes/ws-relay.js +21 -7
  421. package/dist/routes/ws-relay.js.map +1 -1
  422. package/dist/sdk/cli-detection.d.ts +1 -1
  423. package/dist/sdk/cli-detection.d.ts.map +1 -1
  424. package/dist/sdk/cli-detection.js +47 -7
  425. package/dist/sdk/cli-detection.js.map +1 -1
  426. package/dist/sdk/messageLogger.d.ts.map +1 -1
  427. package/dist/sdk/messageLogger.js +38 -10
  428. package/dist/sdk/messageLogger.js.map +1 -1
  429. package/dist/sdk/messageQueue.d.ts +5 -0
  430. package/dist/sdk/messageQueue.d.ts.map +1 -1
  431. package/dist/sdk/messageQueue.js +47 -0
  432. package/dist/sdk/messageQueue.js.map +1 -1
  433. package/dist/sdk/providers/__mocks__/grok.d.ts +1 -1
  434. package/dist/sdk/providers/__mocks__/grok.d.ts.map +1 -1
  435. package/dist/sdk/providers/__mocks__/grok.js +1 -1
  436. package/dist/sdk/providers/__mocks__/grok.js.map +1 -1
  437. package/dist/sdk/providers/agentctl-session-env.d.ts +8 -0
  438. package/dist/sdk/providers/agentctl-session-env.d.ts.map +1 -0
  439. package/dist/sdk/providers/agentctl-session-env.js +52 -0
  440. package/dist/sdk/providers/agentctl-session-env.js.map +1 -0
  441. package/dist/sdk/providers/claude-ollama.d.ts +2 -1
  442. package/dist/sdk/providers/claude-ollama.d.ts.map +1 -1
  443. package/dist/sdk/providers/claude-ollama.js +1 -0
  444. package/dist/sdk/providers/claude-ollama.js.map +1 -1
  445. package/dist/sdk/providers/claude-retention.d.ts +19 -0
  446. package/dist/sdk/providers/claude-retention.d.ts.map +1 -0
  447. package/dist/sdk/providers/claude-retention.js +169 -0
  448. package/dist/sdk/providers/claude-retention.js.map +1 -0
  449. package/dist/sdk/providers/claude.d.ts +52 -5
  450. package/dist/sdk/providers/claude.d.ts.map +1 -1
  451. package/dist/sdk/providers/claude.js +593 -71
  452. package/dist/sdk/providers/claude.js.map +1 -1
  453. package/dist/sdk/providers/codex-model-catalog.d.ts +34 -0
  454. package/dist/sdk/providers/codex-model-catalog.d.ts.map +1 -0
  455. package/dist/sdk/providers/codex-model-catalog.js +378 -0
  456. package/dist/sdk/providers/codex-model-catalog.js.map +1 -0
  457. package/dist/sdk/providers/codex-notification-guards.d.ts +15 -0
  458. package/dist/sdk/providers/codex-notification-guards.d.ts.map +1 -0
  459. package/dist/sdk/providers/codex-notification-guards.js +159 -0
  460. package/dist/sdk/providers/codex-notification-guards.js.map +1 -0
  461. package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.d.ts +8 -0
  462. package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.d.ts.map +1 -0
  463. package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.js +3 -0
  464. package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.js.map +1 -0
  465. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.d.ts +9 -0
  466. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.d.ts.map +1 -0
  467. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.js +3 -0
  468. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.js.map +1 -0
  469. package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.d.ts +10 -0
  470. package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.d.ts.map +1 -0
  471. package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.js +3 -0
  472. package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.js.map +1 -0
  473. package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.d.ts +23 -0
  474. package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.d.ts.map +1 -0
  475. package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.js +3 -0
  476. package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.js.map +1 -0
  477. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts +8 -0
  478. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts.map +1 -0
  479. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.js +3 -0
  480. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.js.map +1 -0
  481. package/dist/sdk/providers/codex-protocol/generated/ReasoningEffort.d.ts +1 -1
  482. package/dist/sdk/providers/codex-protocol/generated/ReasoningEffort.d.ts.map +1 -1
  483. package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts +39 -1
  484. package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts.map +1 -1
  485. package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts +5 -0
  486. package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts.map +1 -1
  487. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts +7 -0
  488. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts.map +1 -0
  489. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js +3 -0
  490. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js.map +1 -0
  491. package/dist/sdk/providers/codex-protocol/generated/v2/AdditionalFileSystemPermissions.d.ts +3 -3
  492. package/dist/sdk/providers/codex-protocol/generated/v2/AdditionalFileSystemPermissions.d.ts.map +1 -1
  493. package/dist/sdk/providers/codex-protocol/generated/v2/AskForApproval.d.ts +1 -1
  494. package/dist/sdk/providers/codex-protocol/generated/v2/AskForApproval.d.ts.map +1 -1
  495. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts +12 -0
  496. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts.map +1 -0
  497. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.js +3 -0
  498. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.js.map +1 -0
  499. package/dist/sdk/providers/codex-protocol/generated/v2/CodexErrorInfo.d.ts +1 -1
  500. package/dist/sdk/providers/codex-protocol/generated/v2/CodexErrorInfo.d.ts.map +1 -1
  501. package/dist/sdk/providers/codex-protocol/generated/v2/CommandExecutionRequestApprovalParams.d.ts +6 -2
  502. package/dist/sdk/providers/codex-protocol/generated/v2/CommandExecutionRequestApprovalParams.d.ts.map +1 -1
  503. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.d.ts +8 -0
  504. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.d.ts.map +1 -0
  505. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.js +3 -0
  506. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.js.map +1 -0
  507. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.d.ts +7 -0
  508. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.d.ts.map +1 -0
  509. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.js +3 -0
  510. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.js.map +1 -0
  511. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.d.ts +5 -0
  512. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.d.ts.map +1 -0
  513. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.js +3 -0
  514. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.js.map +1 -0
  515. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolSpec.d.ts +6 -7
  516. package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolSpec.d.ts.map +1 -1
  517. package/dist/sdk/providers/codex-protocol/generated/v2/FileSystemPath.d.ts +2 -2
  518. package/dist/sdk/providers/codex-protocol/generated/v2/FileSystemPath.d.ts.map +1 -1
  519. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts +9 -0
  520. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts.map +1 -0
  521. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.js +3 -0
  522. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.js.map +1 -0
  523. package/dist/sdk/providers/codex-protocol/generated/v2/PermissionsRequestApprovalParams.d.ts +1 -0
  524. package/dist/sdk/providers/codex-protocol/generated/v2/PermissionsRequestApprovalParams.d.ts.map +1 -1
  525. package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.d.ts +15 -0
  526. package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.d.ts.map +1 -0
  527. package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.js +3 -0
  528. package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.js.map +1 -0
  529. package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.d.ts +2 -0
  530. package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.d.ts.map +1 -0
  531. package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.js +3 -0
  532. package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.js.map +1 -0
  533. package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.d.ts +2 -0
  534. package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.d.ts.map +1 -0
  535. package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.js +3 -0
  536. package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.js.map +1 -0
  537. package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts +21 -0
  538. package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts.map +1 -1
  539. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.d.ts +4 -0
  540. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.d.ts.map +1 -0
  541. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.js +3 -0
  542. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.js.map +1 -0
  543. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.d.ts +2 -0
  544. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.d.ts.map +1 -0
  545. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.js +3 -0
  546. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.js.map +1 -0
  547. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.d.ts +5 -0
  548. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.d.ts.map +1 -0
  549. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js +3 -0
  550. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js.map +1 -0
  551. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts +71 -0
  552. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts.map +1 -0
  553. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.js +3 -0
  554. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.js.map +1 -0
  555. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.d.ts +46 -0
  556. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.d.ts.map +1 -0
  557. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.js +3 -0
  558. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.js.map +1 -0
  559. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.d.ts +2 -0
  560. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.d.ts.map +1 -0
  561. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js +3 -0
  562. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js.map +1 -0
  563. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts +22 -12
  564. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts.map +1 -1
  565. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.d.ts +5 -0
  566. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.d.ts.map +1 -0
  567. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.js +3 -0
  568. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.js.map +1 -0
  569. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.d.ts +17 -0
  570. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.d.ts.map +1 -0
  571. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.js +3 -0
  572. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.js.map +1 -0
  573. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeParams.d.ts +7 -7
  574. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeParams.d.ts.map +1 -1
  575. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeResponse.d.ts +13 -2
  576. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeResponse.d.ts.map +1 -1
  577. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts +14 -0
  578. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts.map +1 -0
  579. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.js +3 -0
  580. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.js.map +1 -0
  581. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.d.ts +12 -0
  582. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.d.ts.map +1 -0
  583. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.js +3 -0
  584. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.js.map +1 -0
  585. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadSource.d.ts +1 -1
  586. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadSource.d.ts.map +1 -1
  587. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts +23 -9
  588. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts.map +1 -1
  589. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartResponse.d.ts +8 -2
  590. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartResponse.d.ts.map +1 -1
  591. package/dist/sdk/providers/codex-protocol/generated/v2/ToolRequestUserInputParams.d.ts +1 -0
  592. package/dist/sdk/providers/codex-protocol/generated/v2/ToolRequestUserInputParams.d.ts.map +1 -1
  593. package/dist/sdk/providers/codex-protocol/generated/v2/Turn.d.ts +3 -0
  594. package/dist/sdk/providers/codex-protocol/generated/v2/Turn.d.ts.map +1 -1
  595. package/dist/sdk/providers/codex-protocol/generated/v2/TurnEnvironmentParams.d.ts +2 -2
  596. package/dist/sdk/providers/codex-protocol/generated/v2/TurnEnvironmentParams.d.ts.map +1 -1
  597. package/dist/sdk/providers/codex-protocol/generated/v2/TurnStartParams.d.ts +17 -5
  598. package/dist/sdk/providers/codex-protocol/generated/v2/TurnStartParams.d.ts.map +1 -1
  599. package/dist/sdk/providers/codex-protocol/generated/v2/TurnSteerParams.d.ts +8 -1
  600. package/dist/sdk/providers/codex-protocol/generated/v2/TurnSteerParams.d.ts.map +1 -1
  601. package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.d.ts +7 -0
  602. package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.d.ts.map +1 -0
  603. package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.js +3 -0
  604. package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.js.map +1 -0
  605. package/dist/sdk/providers/codex-protocol/index.d.ts +7 -0
  606. package/dist/sdk/providers/codex-protocol/index.d.ts.map +1 -1
  607. package/dist/sdk/providers/codex-summary-helpers.d.ts +41 -0
  608. package/dist/sdk/providers/codex-summary-helpers.d.ts.map +1 -0
  609. package/dist/sdk/providers/codex-summary-helpers.js +207 -0
  610. package/dist/sdk/providers/codex-summary-helpers.js.map +1 -0
  611. package/dist/sdk/providers/codex.d.ts +23 -26
  612. package/dist/sdk/providers/codex.d.ts.map +1 -1
  613. package/dist/sdk/providers/codex.js +429 -623
  614. package/dist/sdk/providers/codex.js.map +1 -1
  615. package/dist/sdk/providers/env-filter.d.ts.map +1 -1
  616. package/dist/sdk/providers/env-filter.js +19 -1
  617. package/dist/sdk/providers/env-filter.js.map +1 -1
  618. package/dist/sdk/providers/gemini-tools.d.ts +28 -0
  619. package/dist/sdk/providers/gemini-tools.d.ts.map +1 -0
  620. package/dist/sdk/providers/gemini-tools.js +57 -0
  621. package/dist/sdk/providers/gemini-tools.js.map +1 -0
  622. package/dist/sdk/providers/gemini.d.ts.map +1 -1
  623. package/dist/sdk/providers/gemini.js +4 -2
  624. package/dist/sdk/providers/gemini.js.map +1 -1
  625. package/dist/sdk/providers/grok-acp.d.ts +1 -1
  626. package/dist/sdk/providers/grok-acp.d.ts.map +1 -1
  627. package/dist/sdk/providers/grok-acp.js +1 -1
  628. package/dist/sdk/providers/grok-acp.js.map +1 -1
  629. package/dist/sdk/providers/index.d.ts +1 -0
  630. package/dist/sdk/providers/index.d.ts.map +1 -1
  631. package/dist/sdk/providers/index.js +6 -0
  632. package/dist/sdk/providers/index.js.map +1 -1
  633. package/dist/sdk/providers/opencode-models.d.ts +17 -0
  634. package/dist/sdk/providers/opencode-models.d.ts.map +1 -0
  635. package/dist/sdk/providers/opencode-models.js +83 -0
  636. package/dist/sdk/providers/opencode-models.js.map +1 -0
  637. package/dist/sdk/providers/opencode-tools.d.ts +36 -0
  638. package/dist/sdk/providers/opencode-tools.d.ts.map +1 -0
  639. package/dist/sdk/providers/opencode-tools.js +78 -0
  640. package/dist/sdk/providers/opencode-tools.js.map +1 -0
  641. package/dist/sdk/providers/opencode.d.ts +44 -4
  642. package/dist/sdk/providers/opencode.d.ts.map +1 -1
  643. package/dist/sdk/providers/opencode.js +329 -102
  644. package/dist/sdk/providers/opencode.js.map +1 -1
  645. package/dist/sdk/providers/pi-rpc-client.d.ts +90 -0
  646. package/dist/sdk/providers/pi-rpc-client.d.ts.map +1 -0
  647. package/dist/sdk/providers/pi-rpc-client.js +168 -0
  648. package/dist/sdk/providers/pi-rpc-client.js.map +1 -0
  649. package/dist/sdk/providers/pi-tools.d.ts +38 -0
  650. package/dist/sdk/providers/pi-tools.d.ts.map +1 -0
  651. package/dist/sdk/providers/pi-tools.js +214 -0
  652. package/dist/sdk/providers/pi-tools.js.map +1 -0
  653. package/dist/sdk/providers/pi.d.ts +84 -0
  654. package/dist/sdk/providers/pi.d.ts.map +1 -0
  655. package/dist/sdk/providers/pi.js +686 -0
  656. package/dist/sdk/providers/pi.js.map +1 -0
  657. package/dist/sdk/providers/staticSlashCommands.d.ts +4 -0
  658. package/dist/sdk/providers/staticSlashCommands.d.ts.map +1 -0
  659. package/dist/sdk/providers/staticSlashCommands.js +14 -0
  660. package/dist/sdk/providers/staticSlashCommands.js.map +1 -0
  661. package/dist/sdk/providers/types.d.ts +144 -17
  662. package/dist/sdk/providers/types.d.ts.map +1 -1
  663. package/dist/sdk/remote-spawn.d.ts.map +1 -1
  664. package/dist/sdk/remote-spawn.js.map +1 -1
  665. package/dist/sdk/slashCommandEmulation.d.ts +9 -0
  666. package/dist/sdk/slashCommandEmulation.d.ts.map +1 -1
  667. package/dist/sdk/slashCommandEmulation.js +15 -0
  668. package/dist/sdk/slashCommandEmulation.js.map +1 -1
  669. package/dist/sdk/types.d.ts +43 -1
  670. package/dist/sdk/types.d.ts.map +1 -1
  671. package/dist/security/approvalAuditLog.d.ts +3 -2
  672. package/dist/security/approvalAuditLog.d.ts.map +1 -1
  673. package/dist/security/approvalAuditLog.js +62 -2
  674. package/dist/security/approvalAuditLog.js.map +1 -1
  675. package/dist/services/BrowserProfileService.d.ts +21 -0
  676. package/dist/services/BrowserProfileService.d.ts.map +1 -1
  677. package/dist/services/BrowserProfileService.js +74 -1
  678. package/dist/services/BrowserProfileService.js.map +1 -1
  679. package/dist/services/CacheMissBillingMonitor.d.ts +25 -0
  680. package/dist/services/CacheMissBillingMonitor.d.ts.map +1 -0
  681. package/dist/services/CacheMissBillingMonitor.js +179 -0
  682. package/dist/services/CacheMissBillingMonitor.js.map +1 -0
  683. package/dist/services/CodexUpdateChecker.d.ts.map +1 -1
  684. package/dist/services/CodexUpdateChecker.js +3 -2
  685. package/dist/services/CodexUpdateChecker.js.map +1 -1
  686. package/dist/services/ModelInfoService.d.ts +58 -9
  687. package/dist/services/ModelInfoService.d.ts.map +1 -1
  688. package/dist/services/ModelInfoService.js +127 -14
  689. package/dist/services/ModelInfoService.js.map +1 -1
  690. package/dist/services/ProjectQueueScheduler.d.ts +91 -0
  691. package/dist/services/ProjectQueueScheduler.d.ts.map +1 -0
  692. package/dist/services/ProjectQueueScheduler.js +404 -0
  693. package/dist/services/ProjectQueueScheduler.js.map +1 -0
  694. package/dist/services/ProjectQueueService.d.ts +60 -0
  695. package/dist/services/ProjectQueueService.d.ts.map +1 -0
  696. package/dist/services/ProjectQueueService.js +806 -0
  697. package/dist/services/ProjectQueueService.js.map +1 -0
  698. package/dist/services/RelayClientService.d.ts +5 -0
  699. package/dist/services/RelayClientService.d.ts.map +1 -1
  700. package/dist/services/RelayClientService.js +26 -12
  701. package/dist/services/RelayClientService.js.map +1 -1
  702. package/dist/services/SafeRestartService.d.ts +48 -0
  703. package/dist/services/SafeRestartService.d.ts.map +1 -0
  704. package/dist/services/SafeRestartService.js +173 -0
  705. package/dist/services/SafeRestartService.js.map +1 -0
  706. package/dist/services/ServerSettingsService.d.ts +41 -1
  707. package/dist/services/ServerSettingsService.d.ts.map +1 -1
  708. package/dist/services/ServerSettingsService.js +110 -1
  709. package/dist/services/ServerSettingsService.js.map +1 -1
  710. package/dist/services/SessionQueuePersistenceService.d.ts +55 -0
  711. package/dist/services/SessionQueuePersistenceService.d.ts.map +1 -0
  712. package/dist/services/SessionQueuePersistenceService.js +264 -0
  713. package/dist/services/SessionQueuePersistenceService.js.map +1 -0
  714. package/dist/services/WorkstreamService.d.ts +92 -0
  715. package/dist/services/WorkstreamService.d.ts.map +1 -0
  716. package/dist/services/WorkstreamService.js +627 -0
  717. package/dist/services/WorkstreamService.js.map +1 -0
  718. package/dist/services/index.d.ts +4 -0
  719. package/dist/services/index.d.ts.map +1 -1
  720. package/dist/services/index.js +4 -0
  721. package/dist/services/index.js.map +1 -1
  722. package/dist/services/projectWorkIdle.d.ts +42 -0
  723. package/dist/services/projectWorkIdle.d.ts.map +1 -0
  724. package/dist/services/projectWorkIdle.js +50 -0
  725. package/dist/services/projectWorkIdle.js.map +1 -0
  726. package/dist/services/voice/SpeechBackend.d.ts +18 -1
  727. package/dist/services/voice/SpeechBackend.d.ts.map +1 -1
  728. package/dist/services/voice/SpeechBackend.js +3 -0
  729. package/dist/services/voice/SpeechBackend.js.map +1 -1
  730. package/dist/services/voice/audioRetention.d.ts +20 -0
  731. package/dist/services/voice/audioRetention.d.ts.map +1 -1
  732. package/dist/services/voice/audioRetention.js +2 -0
  733. package/dist/services/voice/audioRetention.js.map +1 -1
  734. package/dist/services/voice/deepgramBackend.d.ts.map +1 -1
  735. package/dist/services/voice/deepgramBackend.js +4 -1
  736. package/dist/services/voice/deepgramBackend.js.map +1 -1
  737. package/dist/services/voice/localNemoBackend.d.ts +31 -0
  738. package/dist/services/voice/localNemoBackend.d.ts.map +1 -0
  739. package/dist/services/voice/localNemoBackend.js +181 -0
  740. package/dist/services/voice/localNemoBackend.js.map +1 -0
  741. package/dist/services/voice/localParakeetBackend.d.ts +31 -0
  742. package/dist/services/voice/localParakeetBackend.d.ts.map +1 -0
  743. package/dist/services/voice/localParakeetBackend.js +180 -0
  744. package/dist/services/voice/localParakeetBackend.js.map +1 -0
  745. package/dist/services/voice/localSttRuntime.d.ts +21 -0
  746. package/dist/services/voice/localSttRuntime.d.ts.map +1 -0
  747. package/dist/services/voice/localSttRuntime.js +87 -0
  748. package/dist/services/voice/localSttRuntime.js.map +1 -0
  749. package/dist/services/voice/localWhisperBackend.d.ts +2 -1
  750. package/dist/services/voice/localWhisperBackend.d.ts.map +1 -1
  751. package/dist/services/voice/localWhisperBackend.js +47 -34
  752. package/dist/services/voice/localWhisperBackend.js.map +1 -1
  753. package/dist/services/voice/nemo_worker.py +232 -0
  754. package/dist/services/voice/parakeet_worker.py +151 -0
  755. package/dist/services/voice/registry.d.ts +19 -4
  756. package/dist/services/voice/registry.d.ts.map +1 -1
  757. package/dist/services/voice/registry.js +72 -22
  758. package/dist/services/voice/registry.js.map +1 -1
  759. package/dist/services/voice/serialQueue.d.ts +17 -0
  760. package/dist/services/voice/serialQueue.d.ts.map +1 -0
  761. package/dist/services/voice/serialQueue.js +24 -0
  762. package/dist/services/voice/serialQueue.js.map +1 -0
  763. package/dist/services/voice/whisper_worker.py +94 -0
  764. package/dist/services/voice/xaiSttBackend.d.ts +1 -1
  765. package/dist/services/voice/xaiSttBackend.d.ts.map +1 -1
  766. package/dist/services/voice/xaiSttBackend.js +15 -3
  767. package/dist/services/voice/xaiSttBackend.js.map +1 -1
  768. package/dist/sessions/agent-excerpt.d.ts +29 -0
  769. package/dist/sessions/agent-excerpt.d.ts.map +1 -0
  770. package/dist/sessions/agent-excerpt.js +128 -0
  771. package/dist/sessions/agent-excerpt.js.map +1 -0
  772. package/dist/sessions/claude-messages.d.ts.map +1 -1
  773. package/dist/sessions/claude-messages.js +75 -4
  774. package/dist/sessions/claude-messages.js.map +1 -1
  775. package/dist/sessions/claude-summary.d.ts +33 -0
  776. package/dist/sessions/claude-summary.d.ts.map +1 -0
  777. package/dist/sessions/claude-summary.js +461 -0
  778. package/dist/sessions/claude-summary.js.map +1 -0
  779. package/dist/sessions/codex-discovery.d.ts +46 -0
  780. package/dist/sessions/codex-discovery.d.ts.map +1 -0
  781. package/dist/sessions/codex-discovery.js +269 -0
  782. package/dist/sessions/codex-discovery.js.map +1 -0
  783. package/dist/sessions/codex-reader.d.ts +96 -13
  784. package/dist/sessions/codex-reader.d.ts.map +1 -1
  785. package/dist/sessions/codex-reader.js +1070 -142
  786. package/dist/sessions/codex-reader.js.map +1 -1
  787. package/dist/sessions/dag.d.ts +25 -4
  788. package/dist/sessions/dag.d.ts.map +1 -1
  789. package/dist/sessions/dag.js +37 -4
  790. package/dist/sessions/dag.js.map +1 -1
  791. package/dist/sessions/gemini-reader.d.ts +3 -1
  792. package/dist/sessions/gemini-reader.d.ts.map +1 -1
  793. package/dist/sessions/gemini-reader.js +17 -8
  794. package/dist/sessions/gemini-reader.js.map +1 -1
  795. package/dist/sessions/grok-reader.d.ts +1 -0
  796. package/dist/sessions/grok-reader.d.ts.map +1 -1
  797. package/dist/sessions/grok-reader.js +11 -5
  798. package/dist/sessions/grok-reader.js.map +1 -1
  799. package/dist/sessions/normalization.d.ts +4 -1
  800. package/dist/sessions/normalization.d.ts.map +1 -1
  801. package/dist/sessions/normalization.js +109 -21
  802. package/dist/sessions/normalization.js.map +1 -1
  803. package/dist/sessions/null-reader.d.ts +31 -0
  804. package/dist/sessions/null-reader.d.ts.map +1 -0
  805. package/dist/sessions/null-reader.js +30 -0
  806. package/dist/sessions/null-reader.js.map +1 -0
  807. package/dist/sessions/opencode-db-reader.d.ts +66 -0
  808. package/dist/sessions/opencode-db-reader.d.ts.map +1 -0
  809. package/dist/sessions/opencode-db-reader.js +333 -0
  810. package/dist/sessions/opencode-db-reader.js.map +1 -0
  811. package/dist/sessions/opencode-reader.d.ts +50 -0
  812. package/dist/sessions/opencode-reader.d.ts.map +1 -1
  813. package/dist/sessions/opencode-reader.js +375 -71
  814. package/dist/sessions/opencode-reader.js.map +1 -1
  815. package/dist/sessions/pagination.d.ts +6 -0
  816. package/dist/sessions/pagination.d.ts.map +1 -1
  817. package/dist/sessions/pagination.js +158 -5
  818. package/dist/sessions/pagination.js.map +1 -1
  819. package/dist/sessions/persisted-augments.d.ts +2 -1
  820. package/dist/sessions/persisted-augments.d.ts.map +1 -1
  821. package/dist/sessions/persisted-augments.js +2 -2
  822. package/dist/sessions/persisted-augments.js.map +1 -1
  823. package/dist/sessions/pi-fork.d.ts +12 -0
  824. package/dist/sessions/pi-fork.d.ts.map +1 -0
  825. package/dist/sessions/pi-fork.js +125 -0
  826. package/dist/sessions/pi-fork.js.map +1 -0
  827. package/dist/sessions/pi-reader.d.ts +81 -0
  828. package/dist/sessions/pi-reader.d.ts.map +1 -0
  829. package/dist/sessions/pi-reader.js +413 -0
  830. package/dist/sessions/pi-reader.js.map +1 -0
  831. package/dist/sessions/provider-resolution.d.ts +10 -3
  832. package/dist/sessions/provider-resolution.d.ts.map +1 -1
  833. package/dist/sessions/provider-resolution.js +62 -6
  834. package/dist/sessions/provider-resolution.js.map +1 -1
  835. package/dist/sessions/reader.d.ts +17 -24
  836. package/dist/sessions/reader.d.ts.map +1 -1
  837. package/dist/sessions/reader.js +86 -157
  838. package/dist/sessions/reader.js.map +1 -1
  839. package/dist/sessions/recap-overlays.d.ts +38 -0
  840. package/dist/sessions/recap-overlays.d.ts.map +1 -0
  841. package/dist/sessions/recap-overlays.js +176 -0
  842. package/dist/sessions/recap-overlays.js.map +1 -0
  843. package/dist/sessions/summary-parser-worker-client.d.ts +80 -0
  844. package/dist/sessions/summary-parser-worker-client.d.ts.map +1 -0
  845. package/dist/sessions/summary-parser-worker-client.js +539 -0
  846. package/dist/sessions/summary-parser-worker-client.js.map +1 -0
  847. package/dist/sessions/summary-parser-worker-entry.d.ts +2 -0
  848. package/dist/sessions/summary-parser-worker-entry.d.ts.map +1 -0
  849. package/dist/sessions/summary-parser-worker-entry.js +29 -0
  850. package/dist/sessions/summary-parser-worker-entry.js.map +1 -0
  851. package/dist/sessions/summary-parser-worker-protocol.d.ts +105 -0
  852. package/dist/sessions/summary-parser-worker-protocol.d.ts.map +1 -0
  853. package/dist/sessions/summary-parser-worker-protocol.js +35 -0
  854. package/dist/sessions/summary-parser-worker-protocol.js.map +1 -0
  855. package/dist/sessions/summary-parser-worker-runner.d.ts +3 -0
  856. package/dist/sessions/summary-parser-worker-runner.d.ts.map +1 -0
  857. package/dist/sessions/summary-parser-worker-runner.js +158 -0
  858. package/dist/sessions/summary-parser-worker-runner.js.map +1 -0
  859. package/dist/sessions/types.d.ts +30 -1
  860. package/dist/sessions/types.d.ts.map +1 -1
  861. package/dist/startupEnv.d.ts +2 -0
  862. package/dist/startupEnv.d.ts.map +1 -0
  863. package/dist/startupEnv.js +97 -0
  864. package/dist/startupEnv.js.map +1 -0
  865. package/dist/subscriptions.d.ts.map +1 -1
  866. package/dist/subscriptions.js +28 -18
  867. package/dist/subscriptions.js.map +1 -1
  868. package/dist/supervisor/ExternalSessionTracker.d.ts +5 -1
  869. package/dist/supervisor/ExternalSessionTracker.d.ts.map +1 -1
  870. package/dist/supervisor/ExternalSessionTracker.js +98 -6
  871. package/dist/supervisor/ExternalSessionTracker.js.map +1 -1
  872. package/dist/supervisor/Process.d.ts +259 -30
  873. package/dist/supervisor/Process.d.ts.map +1 -1
  874. package/dist/supervisor/Process.js +1282 -229
  875. package/dist/supervisor/Process.js.map +1 -1
  876. package/dist/supervisor/Supervisor.d.ts +201 -5
  877. package/dist/supervisor/Supervisor.d.ts.map +1 -1
  878. package/dist/supervisor/Supervisor.js +1074 -55
  879. package/dist/supervisor/Supervisor.js.map +1 -1
  880. package/dist/supervisor/WorkerQueue.d.ts +3 -1
  881. package/dist/supervisor/WorkerQueue.d.ts.map +1 -1
  882. package/dist/supervisor/WorkerQueue.js +1 -0
  883. package/dist/supervisor/WorkerQueue.js.map +1 -1
  884. package/dist/supervisor/composeTimeAnchor.d.ts +36 -0
  885. package/dist/supervisor/composeTimeAnchor.d.ts.map +1 -0
  886. package/dist/supervisor/composeTimeAnchor.js +51 -0
  887. package/dist/supervisor/composeTimeAnchor.js.map +1 -0
  888. package/dist/supervisor/deferredDeliverySettings.d.ts +23 -0
  889. package/dist/supervisor/deferredDeliverySettings.d.ts.map +1 -0
  890. package/dist/supervisor/deferredDeliverySettings.js +40 -0
  891. package/dist/supervisor/deferredDeliverySettings.js.map +1 -0
  892. package/dist/supervisor/liveness.d.ts +5 -2
  893. package/dist/supervisor/liveness.d.ts.map +1 -1
  894. package/dist/supervisor/liveness.js +40 -2
  895. package/dist/supervisor/liveness.js.map +1 -1
  896. package/dist/supervisor/types.d.ts +56 -5
  897. package/dist/supervisor/types.d.ts.map +1 -1
  898. package/dist/supervisor/types.js +0 -2
  899. package/dist/supervisor/types.js.map +1 -1
  900. package/dist/uploads/AttachmentStagingService.d.ts +94 -0
  901. package/dist/uploads/AttachmentStagingService.d.ts.map +1 -0
  902. package/dist/uploads/AttachmentStagingService.js +673 -0
  903. package/dist/uploads/AttachmentStagingService.js.map +1 -0
  904. package/dist/uploads/index.d.ts +2 -0
  905. package/dist/uploads/index.d.ts.map +1 -1
  906. package/dist/uploads/index.js +1 -0
  907. package/dist/uploads/index.js.map +1 -1
  908. package/dist/uploads/manager.d.ts +1 -0
  909. package/dist/uploads/manager.d.ts.map +1 -1
  910. package/dist/uploads/manager.js +72 -12
  911. package/dist/uploads/manager.js.map +1 -1
  912. package/dist/utils/codexRolloutFiles.d.ts +16 -0
  913. package/dist/utils/codexRolloutFiles.d.ts.map +1 -0
  914. package/dist/utils/codexRolloutFiles.js +45 -0
  915. package/dist/utils/codexRolloutFiles.js.map +1 -0
  916. package/dist/utils/jsonl.d.ts +8 -0
  917. package/dist/utils/jsonl.d.ts.map +1 -1
  918. package/dist/utils/jsonl.js +158 -5
  919. package/dist/utils/jsonl.js.map +1 -1
  920. package/dist/utils/publicShareViewerUrl.d.ts.map +1 -1
  921. package/dist/utils/publicShareViewerUrl.js +2 -15
  922. package/dist/utils/publicShareViewerUrl.js.map +1 -1
  923. package/dist/watcher/EventBus.d.ts +42 -4
  924. package/dist/watcher/EventBus.d.ts.map +1 -1
  925. package/dist/watcher/EventBus.js.map +1 -1
  926. package/dist/watcher/FileWatcher.d.ts +47 -0
  927. package/dist/watcher/FileWatcher.d.ts.map +1 -1
  928. package/dist/watcher/FileWatcher.js +185 -14
  929. package/dist/watcher/FileWatcher.js.map +1 -1
  930. package/dist/watcher/index.d.ts +2 -2
  931. package/dist/watcher/index.d.ts.map +1 -1
  932. package/dist/watcher/index.js.map +1 -1
  933. package/dist/yaModuleEnv.d.ts +6 -6
  934. package/dist/yaModuleEnv.d.ts.map +1 -1
  935. package/dist/yaModuleEnv.js +12 -12
  936. package/dist/yaModuleEnv.js.map +1 -1
  937. package/package.json +5 -6
  938. package/client-dist/assets/de-BuEb60Yc.js +0 -6
  939. package/client-dist/assets/es-CRlmPKnE.js +0 -6
  940. package/client-dist/assets/fr-Cglgjc2X.js +0 -6
  941. package/client-dist/assets/index-BNj7ibmq.js +0 -524
  942. package/client-dist/assets/index-CAmsUazi.css +0 -1
  943. package/client-dist/assets/ja-DMb9mFPv.js +0 -6
  944. package/client-dist/assets/zh-CN-D1tbfamz.js +0 -6
@@ -1,32 +1,51 @@
1
- import { PROMPT_SUGGESTION_MODES, HELPER_SIDE_MODEL_CHEAPEST, HELPER_SIDE_MODEL_SAME_AS_MAIN, RECAP_MODES, getModelContextWindow, isUrlProjectId, thinkingOptionToConfig, truncateSessionTitle, } from "../../bundled/@yep-anywhere/shared/dist/index.js";
1
+ import { ALL_PERMISSION_MODES, buildEffectiveAgentContext, getModelContextWindow, isUrlProjectId, isWorkstreamId, mainWorkstreamId, truncateSessionTitle, } from "../../bundled/@yep-anywhere/shared/dist/index.js";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { mkdir } from "node:fs/promises";
3
4
  import { performance } from "node:perf_hooks";
4
5
  import { Hono } from "hono";
5
6
  import { augmentTextBlocks } from "../augments/markdown-augments.js";
6
7
  import { getLogger } from "../logging/logger.js";
7
8
  import { DETACHED_PROJECT_PATH, encodeProjectId } from "../projects/paths.js";
9
+ import { resolveCanonicalProjectRedirect } from "./session-project-routing.js";
8
10
  import { ensureRemoteDirectory } from "../sdk/remote-spawn.js";
11
+ import { parseSlashCommandSubmission } from "../sdk/slashCommandEmulation.js";
9
12
  import { getProjectDirFromCwd, syncSessions } from "../sdk/session-sync.js";
10
13
  import { appendApprovalAuditLog } from "../security/approvalAuditLog.js";
11
14
  import { CodexSessionReader } from "../sessions/codex-reader.js";
12
15
  import { cloneClaudeSession, cloneCodexSession } from "../sessions/fork.js";
13
- import { GeminiSessionReader } from "../sessions/gemini-reader.js";
14
- import { buildDag } from "../sessions/dag.js";
15
16
  import { GrokSessionReader } from "../sessions/grok-reader.js";
17
+ import { extractLastAgentExcerpt } from "../sessions/agent-excerpt.js";
16
18
  import { normalizeSession } from "../sessions/normalization.js";
17
- import { sliceAfterMessageIdWithMatch, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
19
+ import { applyRecapOverlayToSession, applyRecapOverlayToSummary, hasEquivalentRecapMessage, hasUnreadProviderContent, latestRecapMessage, mergeRecapMessages, } from "../sessions/recap-overlays.js";
20
+ import { sliceAfterMessageIdWithMatch, sliceAtCompactAndUserTurnBoundaries, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
21
+ import { augmentTaskListSnapshots, pruneTaskListSnapshotsToLatest, } from "../augments/task-list-augments.js";
18
22
  import { augmentEditToolUses, augmentPersistedSessionMessages, } from "../sessions/persisted-augments.js";
19
- import { findSessionSummaryAcrossProviders } from "../sessions/provider-resolution.js";
20
- import { isValidSshHostAlias, normalizeSshHostAlias, } from "../utils/sshHostAlias.js";
23
+ import { findSessionSummaryAcrossProviders, getSessionSources, } from "../sessions/provider-resolution.js";
24
+ import { getProvider } from "../sdk/providers/index.js";
25
+ import { getStaticSlashCommandsForProvider } from "../sdk/providers/staticSlashCommands.js";
26
+ import { ResumeCompactionError } from "../supervisor/Supervisor.js";
27
+ import { buildUserMessageMetadata, normalizeOptionalServiceTier, parseHelperSettings, parseOptionalExecutor, parseOptionalResumeMode, } from "./session-request-helpers.js";
28
+ import { resolveCompactPercent, resolveCompactWindow, } from "./session-compact-thresholds.js";
29
+ import { getClaudeResumeBlockerFromReader, } from "./session-claude-resume-guard.js";
30
+ import { isClaudeSdkProviderName, isCodexProviderName, providerResolutionDeps, } from "./session-provider-resolution.js";
31
+ import { parseSessionMetadataPatch } from "./session-metadata-patch.js";
32
+ import { recoveredPatientQueueSummaries, sessionQueueSummaries, } from "./session-queue-summaries.js";
33
+ import { reportableProcessState, resolveRecoveredGroupForDelivery, resumeRecoveredGroup, } from "./session-recovered-queue.js";
34
+ import { buildThinkingOptions } from "./session-thinking-options.js";
21
35
  const SESSION_DETAIL_SLOW_LOG_MS = 250;
22
- const CLAUDE_RESUME_API_ERROR_RECOVERY = "handoff-required";
23
- const CLAUDE_RESUME_API_ERROR_MESSAGE = "Claude session cannot be safely resumed because the Claude SDK recorded an API-error response as the latest assistant message. Start a handoff session instead.";
36
+ const DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS = 2;
37
+ const LARGE_FULL_HISTORY_MESSAGE_THRESHOLD = 1000;
38
+ async function getSessionSlashCommands(process, provider) {
39
+ if (process?.supportsDynamicCommands) {
40
+ const commands = await process.supportedCommands();
41
+ if (commands)
42
+ return commands;
43
+ }
44
+ return getStaticSlashCommandsForProvider(provider);
45
+ }
24
46
  function roundedMs(value) {
25
47
  return Math.round(value * 10) / 10;
26
48
  }
27
- function isClaudeSdkProviderName(provider) {
28
- return provider === "claude" || provider === "claude-ollama";
29
- }
30
49
  /**
31
50
  * Type guard to check if a result is a QueuedResponse
32
51
  */
@@ -39,241 +58,19 @@ function isQueuedResponse(result) {
39
58
  function isQueueFullResponse(result) {
40
59
  return "error" in result && result.error === "queue_full";
41
60
  }
42
- function getClaudeResumeApiErrorBlocker(messages) {
43
- const { activeBranch } = buildDag(messages);
44
- for (let i = activeBranch.length - 1; i >= 0; i--) {
45
- const raw = activeBranch[i]?.raw;
46
- if (raw?.type !== "assistant") {
47
- continue;
48
- }
49
- if (raw.isApiErrorMessage !== true) {
50
- return null;
51
- }
52
- const apiError = raw;
53
- return {
54
- error: CLAUDE_RESUME_API_ERROR_MESSAGE,
55
- recovery: CLAUDE_RESUME_API_ERROR_RECOVERY,
56
- messageId: raw.message.id,
57
- apiErrorStatus: apiError.apiErrorStatus,
58
- };
59
- }
60
- return null;
61
- }
62
- async function getClaudeResumeBlockerFromReader(reader, sessionId, projectId) {
63
- const session = await reader.getSession(sessionId, projectId);
64
- if (!session) {
65
- return null;
66
- }
67
- if (session.data.provider !== "claude" &&
68
- session.data.provider !== "claude-ollama") {
69
- return null;
70
- }
71
- return getClaudeResumeApiErrorBlocker(session.data.session.messages);
72
- }
73
- function parseOptionalExecutor(rawExecutor) {
74
- if (rawExecutor === undefined || rawExecutor === null) {
75
- return { executor: undefined };
76
- }
77
- if (typeof rawExecutor !== "string") {
78
- return { executor: undefined, error: "executor must be a string" };
79
- }
80
- const executor = normalizeSshHostAlias(rawExecutor);
81
- if (!executor) {
82
- return { executor: undefined };
83
- }
84
- if (!isValidSshHostAlias(executor)) {
85
- return {
86
- executor: undefined,
87
- error: "executor must be a valid SSH host alias",
88
- };
89
- }
90
- return { executor };
91
- }
92
- function normalizeOptionalServiceTier(rawServiceTier) {
93
- if (typeof rawServiceTier !== "string") {
94
- return undefined;
95
- }
96
- const serviceTier = rawServiceTier.trim();
97
- return /^[A-Za-z0-9_-]{1,64}$/.test(serviceTier)
98
- ? serviceTier
99
- : undefined;
100
- }
101
- function parseOptionalRecapMode(rawMode) {
102
- if (rawMode === undefined || rawMode === null || rawMode === "") {
103
- return { recapMode: undefined };
104
- }
105
- if (typeof rawMode !== "string" ||
106
- !RECAP_MODES.includes(rawMode)) {
107
- return {
108
- recapMode: undefined,
109
- error: "recapMode must be one of: off, native, side-session",
110
- };
111
- }
112
- return { recapMode: rawMode };
113
- }
114
- function parseOptionalPromptSuggestionMode(rawMode) {
115
- if (rawMode === undefined || rawMode === null || rawMode === "") {
116
- return { promptSuggestionMode: undefined };
117
- }
118
- if (typeof rawMode !== "string" ||
119
- !PROMPT_SUGGESTION_MODES.includes(rawMode)) {
120
- return {
121
- promptSuggestionMode: undefined,
122
- error: "promptSuggestionMode must be one of: off, native",
123
- };
124
- }
125
- return { promptSuggestionMode: rawMode };
126
- }
127
- function parseOptionalHelperSideModel(rawModel) {
128
- if (rawModel === undefined || rawModel === null || rawModel === "") {
129
- return { helperSideModel: undefined };
130
- }
131
- if (typeof rawModel !== "string") {
132
- return { helperSideModel: undefined, error: "helperSideModel must be a string" };
133
- }
134
- const trimmed = rawModel.trim();
135
- if (!trimmed) {
136
- return { helperSideModel: undefined };
137
- }
138
- return {
139
- helperSideModel: trimmed === HELPER_SIDE_MODEL_SAME_AS_MAIN
140
- ? HELPER_SIDE_MODEL_SAME_AS_MAIN
141
- : trimmed === HELPER_SIDE_MODEL_CHEAPEST
142
- ? HELPER_SIDE_MODEL_CHEAPEST
143
- : trimmed.slice(0, 200),
144
- };
145
- }
146
- function parseHelperSettings(body) {
147
- const recap = parseOptionalRecapMode(body.recapMode);
148
- if (recap.error) {
149
- return {
150
- recapMode: undefined,
151
- promptSuggestionMode: undefined,
152
- helperSideModel: undefined,
153
- error: recap.error,
154
- };
155
- }
156
- const promptSuggestion = parseOptionalPromptSuggestionMode(body.promptSuggestionMode);
157
- if (promptSuggestion.error) {
158
- return {
159
- recapMode: undefined,
160
- promptSuggestionMode: undefined,
161
- helperSideModel: undefined,
162
- error: promptSuggestion.error,
163
- };
164
- }
165
- const helperModel = parseOptionalHelperSideModel(body.helperSideModel);
166
- if (helperModel.error) {
167
- return {
168
- recapMode: undefined,
169
- promptSuggestionMode: undefined,
170
- helperSideModel: undefined,
171
- error: helperModel.error,
172
- };
173
- }
174
- return {
175
- recapMode: recap.recapMode,
176
- promptSuggestionMode: promptSuggestion.promptSuggestionMode,
177
- helperSideModel: helperModel.helperSideModel,
178
- };
179
- }
180
- function isCodexProviderName(provider) {
181
- return provider === "codex" || provider === "codex-oss";
182
- }
183
- function parseDeferredPlacement(body) {
184
- const beforeTempId = typeof body.insertBeforeTempId === "string" &&
185
- body.insertBeforeTempId.trim()
186
- ? body.insertBeforeTempId.trim()
187
- : undefined;
188
- const afterTempId = typeof body.insertAfterTempId === "string" && body.insertAfterTempId.trim()
189
- ? body.insertAfterTempId.trim()
190
- : undefined;
191
- const replaceTempId = typeof body.replaceDeferredTempId === "string" &&
192
- body.replaceDeferredTempId.trim()
193
- ? body.replaceDeferredTempId.trim()
194
- : undefined;
195
- if (!beforeTempId && !afterTempId && !replaceTempId) {
196
- return undefined;
197
- }
198
- return {
199
- ...(afterTempId ? { afterTempId } : {}),
200
- ...(beforeTempId ? { beforeTempId } : {}),
201
- ...(replaceTempId ? { replaceTempId } : {}),
202
- };
203
- }
204
- const USER_MESSAGE_DELIVERY_INTENTS = new Set(["direct", "steer", "deferred", "patient"]);
205
61
  const AUTO_COMPACT_CONTEXT_PERCENT_THRESHOLD = 85;
206
62
  const AUTO_COMPACT_MODEL_PREFIXES = ["gpt-5.3-codex-spark"];
207
63
  const AUTO_COMPACT_TARGET_PROVIDERS = new Set([
208
64
  "codex",
209
65
  "codex-oss",
210
66
  ]);
211
- function isRecord(value) {
212
- return typeof value === "object" && value !== null && !Array.isArray(value);
213
- }
214
- function parseIsoTimestamp(value) {
215
- if (typeof value !== "string" || !value.trim())
216
- return undefined;
217
- return Number.isNaN(Date.parse(value)) ? undefined : value;
218
- }
219
- function parseDeliveryIntent(value) {
220
- return typeof value === "string" &&
221
- USER_MESSAGE_DELIVERY_INTENTS.has(value)
222
- ? value
223
- : undefined;
224
- }
225
- function parseShortString(value, maxLength) {
226
- if (typeof value !== "string")
227
- return undefined;
228
- const trimmed = value.trim();
229
- return trimmed ? trimmed.slice(0, maxLength) : undefined;
230
- }
231
- function parseStringList(value, maxItems, maxLength) {
232
- if (!Array.isArray(value))
233
- return undefined;
234
- const parsed = value
235
- .map((entry) => parseShortString(entry, maxLength))
236
- .filter((entry) => entry !== undefined)
237
- .slice(0, maxItems);
238
- return parsed.length > 0 ? parsed : undefined;
67
+ function isApprovalAuditLogEnabled(deps) {
68
+ return (deps.serverSettingsService?.getSetting("approvalAuditLogEnabled") === true);
239
69
  }
240
- function buildUserMessageMetadata(body, serverTimestamp, fallbackIntent) {
241
- const rawMetadata = isRecord(body.messageMetadata) ? body.messageMetadata : {};
242
- const rawComposition = isRecord(rawMetadata.composition)
243
- ? rawMetadata.composition
244
- : {};
245
- const composition = {
246
- typingStartedAt: parseIsoTimestamp(rawComposition.typingStartedAt),
247
- typingEndedAt: parseIsoTimestamp(rawComposition.typingEndedAt),
248
- lastEditedAt: parseIsoTimestamp(rawComposition.lastEditedAt),
249
- submittedAt: parseIsoTimestamp(rawComposition.submittedAt),
250
- };
251
- const cleanComposition = Object.fromEntries(Object.entries(composition).filter(([, value]) => value !== undefined));
252
- const clientTimestamp = typeof body.clientTimestamp === "number" && Number.isFinite(body.clientTimestamp)
253
- ? body.clientTimestamp
254
- : undefined;
255
- const rawSpeech = isRecord(rawMetadata.speech) ? rawMetadata.speech : {};
256
- const speechClientTurnId = parseShortString(rawSpeech.clientTurnId, 120);
257
- const speechTranscriptionIds = parseStringList(rawSpeech.transcriptionIds, 20, 120);
258
- const speech = speechClientTurnId || speechTranscriptionIds
259
- ? {
260
- ...(speechClientTurnId
261
- ? { clientTurnId: speechClientTurnId }
262
- : {}),
263
- ...(speechTranscriptionIds
264
- ? { transcriptionIds: speechTranscriptionIds }
265
- : {}),
266
- }
267
- : undefined;
268
- return {
269
- deliveryIntent: parseDeliveryIntent(rawMetadata.deliveryIntent) ?? fallbackIntent,
270
- ...(Object.keys(cleanComposition).length > 0
271
- ? { composition: cleanComposition }
272
- : {}),
273
- ...(speech ? { speech } : {}),
274
- ...(clientTimestamp !== undefined ? { clientTimestamp } : {}),
275
- serverReceivedAt: new Date(serverTimestamp).toISOString(),
276
- };
70
+ async function resolveSessionReaderForAgentContent({ deps, project, sessionId, projectId, }) {
71
+ const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
72
+ const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
73
+ return resolved?.source.reader ?? deps.readerFactory(project);
277
74
  }
278
75
  const RESTART_HANDOFF_MAX_CHARS = 40_000;
279
76
  const RESTART_HANDOFF_JSON_MAX_CHARS = 2_000;
@@ -336,7 +133,9 @@ function renderRestartContent(content) {
336
133
  case "text":
337
134
  return typed.text ?? "";
338
135
  case "thinking":
339
- return typed.thinking ? `[thinking]\n${typed.thinking}` : "[thinking]";
136
+ return typed.thinking
137
+ ? `[thinking]\n${typed.thinking}`
138
+ : "[thinking]";
340
139
  case "tool_use":
341
140
  return `[tool_use ${typed.name ?? "unknown"}]\n${stringifyForRestart(typed.input, RESTART_HANDOFF_JSON_MAX_CHARS)}`;
342
141
  case "tool_result":
@@ -372,6 +171,33 @@ function messageContent(message) {
372
171
  const nested = message.message;
373
172
  return nested?.content ?? message.content;
374
173
  }
174
+ function messageId(message) {
175
+ if (!message) {
176
+ return undefined;
177
+ }
178
+ return ((typeof message.uuid === "string" && message.uuid) ||
179
+ (typeof message.id === "string" && message.id) ||
180
+ undefined);
181
+ }
182
+ function findDurableRecapCursor(afterMessageId, recaps) {
183
+ if (!afterMessageId) {
184
+ return undefined;
185
+ }
186
+ return recaps.find((recap) => recap.uuid === afterMessageId || recap.id === afterMessageId);
187
+ }
188
+ function sliceAfterDurableRecapCursor(params) {
189
+ const index = params.messages.findIndex((message) => {
190
+ const id = messageId(message);
191
+ return (id === params.cursorId ||
192
+ id === params.recap.uuid ||
193
+ id === params.recap.id ||
194
+ hasEquivalentRecapMessage([message], params.recap));
195
+ });
196
+ if (index < 0) {
197
+ return { messages: params.messages, found: false };
198
+ }
199
+ return { messages: params.messages.slice(index + 1), found: true };
200
+ }
375
201
  function messageHasToolResult(message) {
376
202
  if (message.toolUseResult !== undefined) {
377
203
  return true;
@@ -382,6 +208,32 @@ function messageHasToolResult(message) {
382
208
  typeof block === "object" &&
383
209
  block.type === "tool_result"));
384
210
  }
211
+ function messageTextContent(message) {
212
+ const content = messageContent(message);
213
+ if (typeof content === "string") {
214
+ return content;
215
+ }
216
+ if (!Array.isArray(content)) {
217
+ return undefined;
218
+ }
219
+ const textBlocks = content
220
+ .map((block) => block &&
221
+ typeof block === "object" &&
222
+ block.type === "text" &&
223
+ typeof block.text === "string"
224
+ ? block.text
225
+ : "")
226
+ .filter(Boolean);
227
+ return textBlocks.length > 0 ? textBlocks.join("\n") : undefined;
228
+ }
229
+ function isSlashCommandSkillBodyUserMessage(message) {
230
+ if (message.isMeta !== true) {
231
+ return false;
232
+ }
233
+ return (messageTextContent(message)
234
+ ?.trimStart()
235
+ .startsWith("Base directory for this skill:") === true);
236
+ }
385
237
  function isRestartInternalCompactCommand(message) {
386
238
  if (messageRole(message) !== "user" || messageHasToolResult(message)) {
387
239
  return false;
@@ -751,8 +603,111 @@ function isHumanUserMessage(message) {
751
603
  message.type;
752
604
  return role === "user" && !messageHasToolResult(message);
753
605
  }
606
+ function isCompactSummaryUserMessage(message) {
607
+ return message.isCompactSummary === true;
608
+ }
609
+ function isUserAuthoredRequest(message) {
610
+ return (isHumanUserMessage(message) &&
611
+ !isCompactSummaryUserMessage(message) &&
612
+ !isSlashCommandSkillBodyUserMessage(message));
613
+ }
614
+ function resolveForkAfterBoundary(messages, sourceMessageId, sourceIsBusy) {
615
+ const sourceIndex = messages.findIndex((message) => messageId(message) === sourceMessageId);
616
+ const sourceMessage = messages[sourceIndex];
617
+ if (sourceIndex < 0 || !sourceMessage) {
618
+ return { error: "Selected source message was not found", status: 404 };
619
+ }
620
+ if (!isUserAuthoredRequest(sourceMessage)) {
621
+ return {
622
+ error: "sourceMessageId must identify a user-authored request",
623
+ status: 400,
624
+ };
625
+ }
626
+ let nextUserIndex = -1;
627
+ for (let index = sourceIndex + 1; index < messages.length; index += 1) {
628
+ const candidate = messages[index];
629
+ if (candidate && isUserAuthoredRequest(candidate)) {
630
+ nextUserIndex = index;
631
+ break;
632
+ }
633
+ }
634
+ if (nextUserIndex < 0 && sourceIsBusy) {
635
+ return {
636
+ error: "The selected turn is still in progress",
637
+ status: 409,
638
+ };
639
+ }
640
+ const searchEnd = nextUserIndex >= 0 ? nextUserIndex - 1 : messages.length - 1;
641
+ let hasAssistantResponse = false;
642
+ for (let index = sourceIndex + 1; index <= searchEnd; index += 1) {
643
+ const candidate = messages[index];
644
+ if (candidate && messageRole(candidate) === "assistant") {
645
+ hasAssistantResponse = true;
646
+ break;
647
+ }
648
+ }
649
+ if (!hasAssistantResponse) {
650
+ return {
651
+ error: "The selected request has no completed assistant response",
652
+ status: 409,
653
+ };
654
+ }
655
+ let boundary;
656
+ for (let index = searchEnd; index > sourceIndex; index -= 1) {
657
+ const candidate = messages[index];
658
+ const role = candidate ? messageRole(candidate) : undefined;
659
+ if (candidate &&
660
+ messageId(candidate) &&
661
+ (role === "user" || role === "assistant")) {
662
+ boundary = candidate;
663
+ break;
664
+ }
665
+ }
666
+ const retainedThroughMessageId = messageId(boundary);
667
+ if (!boundary || !retainedThroughMessageId) {
668
+ return { error: "Completed turn boundary has no message id", status: 409 };
669
+ }
670
+ const placementAfterMessageId = [...messages].reverse().map(messageId).find(Boolean) ??
671
+ retainedThroughMessageId;
672
+ const rendered = renderRestartContent(messageContent(boundary)).trim();
673
+ return {
674
+ placementAfterMessageId,
675
+ retainedThroughMessageId,
676
+ retainedThroughContext: rendered
677
+ ? truncateForRestart(rendered, 1200)
678
+ : undefined,
679
+ };
680
+ }
681
+ function forkSummaryTitle(summary, fallback) {
682
+ const firstLine = summary
683
+ .split("\n")
684
+ .map((line) => line.trim())
685
+ .find(Boolean);
686
+ const candidate = firstLine
687
+ ?.replace(/^#{1,6}\s*/u, "")
688
+ .replace(/^title:\s*/iu, "")
689
+ .trim()
690
+ .replace(/[.!?]+$/u, "");
691
+ return truncateSessionTitle(candidate || fallback || "Forked session");
692
+ }
693
+ function generatedRetitleCandidate(title) {
694
+ const firstLine = title
695
+ .split("\n")
696
+ .map((line) => line.trim())
697
+ .find(Boolean);
698
+ if (!firstLine) {
699
+ return undefined;
700
+ }
701
+ const candidate = normalizeRestartTitleCandidate(firstLine
702
+ .replace(/^title:\s*/iu, "")
703
+ .replace(/^["'“”‘’]+|["'“”‘’]+$/gu, "")
704
+ .replace(/[.!?]+$/u, "")
705
+ .trim());
706
+ return candidate ? truncateSessionTitle(candidate) : undefined;
707
+ }
754
708
  function messageTitleCandidate(message) {
755
- if (!isHumanUserMessage(message) || isRestartInternalCompactCommand(message)) {
709
+ if (!isHumanUserMessage(message) ||
710
+ isRestartInternalCompactCommand(message)) {
756
711
  return undefined;
757
712
  }
758
713
  const nested = message.message;
@@ -793,6 +748,18 @@ function deriveRestartTitle(params) {
793
748
  const title = /^Handoff:/i.test(base) ? base : `Handoff: ${base}`;
794
749
  return truncateRestartTitle(title);
795
750
  }
751
+ function deriveForkTitle(params) {
752
+ const candidates = [
753
+ params.preferredTitle,
754
+ params.sourceSession.customTitle,
755
+ params.sourceSession.title,
756
+ latestUserTitleCandidate(params.sourceSession.messages),
757
+ ];
758
+ const base = candidates.map(normalizeRestartTitleCandidate).find(Boolean) ??
759
+ "forked session";
760
+ const title = /^Fork:/i.test(base) ? base : `Fork: ${base}`;
761
+ return truncateRestartTitle(title);
762
+ }
796
763
  function buildRestartHandoff(params) {
797
764
  const { handoffTitle, sourceSession, sourceProvider, sourceModel, sourceProcess, compactAttempt, projectPath, reason, omittedCount, transcript, } = params;
798
765
  const oldProcessLine = sourceProcess
@@ -847,12 +814,14 @@ function isRestartReplacementActivity(message) {
847
814
  function sdkMessagesToClientMessages(sdkMessages) {
848
815
  const messages = [];
849
816
  for (const msg of sdkMessages) {
817
+ const rawFields = msg;
850
818
  if (isCompactBoundaryMessage(msg)) {
851
819
  const content = (typeof msg.message?.content === "string"
852
820
  ? msg.message.content
853
821
  : undefined) ??
854
822
  (typeof msg.content === "string" ? msg.content : "Context compacted");
855
823
  messages.push({
824
+ ...rawFields,
856
825
  id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
857
826
  type: "system",
858
827
  role: "system",
@@ -889,6 +858,7 @@ function sdkMessagesToClientMessages(sdkMessages) {
889
858
  continue;
890
859
  }
891
860
  messages.push({
861
+ ...rawFields,
892
862
  id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
893
863
  type: msg.type,
894
864
  role: msg.type,
@@ -1005,6 +975,31 @@ function extractContextUsageFromSDKMessages(sdkMessages, model, provider, resolv
1005
975
  }
1006
976
  export function createSessionsRoutes(deps) {
1007
977
  const routes = new Hono();
978
+ const activeForkSummaryJobs = new Map();
979
+ const emitTranscriptDisplayObjects = (sessionId) => {
980
+ deps.eventBus?.emit({
981
+ type: "session-metadata-changed",
982
+ sessionId,
983
+ transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
984
+ [],
985
+ timestamp: new Date().toISOString(),
986
+ });
987
+ };
988
+ const updateForkSummaryChildMetadata = async (childSessionId, parentSessionId, title, archived) => {
989
+ await deps.sessionMetadataService?.updateMetadata(childSessionId, {
990
+ title,
991
+ archived,
992
+ parentSessionId,
993
+ });
994
+ deps.eventBus?.emit({
995
+ type: "session-metadata-changed",
996
+ sessionId: childSessionId,
997
+ title,
998
+ archived,
999
+ parentSessionId,
1000
+ timestamp: new Date().toISOString(),
1001
+ });
1002
+ };
1008
1003
  const getCodexReader = (projectPath) => deps.codexReaderFactory?.(projectPath) ??
1009
1004
  (deps.codexSessionsDir
1010
1005
  ? new CodexSessionReader({
@@ -1012,13 +1007,6 @@ export function createSessionsRoutes(deps) {
1012
1007
  projectPath,
1013
1008
  })
1014
1009
  : null);
1015
- const getGrokReader = (projectPath) => deps.grokReaderFactory?.(projectPath) ??
1016
- (deps.grokSessionsDir
1017
- ? new GrokSessionReader({
1018
- sessionsDir: deps.grokSessionsDir,
1019
- projectPath,
1020
- })
1021
- : null);
1022
1010
  let unscopedGrokReader;
1023
1011
  const getUnscopedGrokReader = () => {
1024
1012
  if (unscopedGrokReader !== undefined) {
@@ -1045,8 +1033,91 @@ export function createSessionsRoutes(deps) {
1045
1033
  const search = new URL(requestUrl).search;
1046
1034
  return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
1047
1035
  };
1048
- const getGlobalInstructions = () => deps.serverSettingsService?.getSetting("globalInstructions") || undefined;
1049
- const persistLaunchMetadata = async (sessionId, provider, executor, initialPrompt) => {
1036
+ const buildSessionProjectRedirectPath = (canonicalProjectId, sessionId, suffix, requestUrl) => {
1037
+ const search = new URL(requestUrl).search;
1038
+ return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
1039
+ };
1040
+ const resolveSessionProjectRouting = async (requestProjectId, sessionId) => {
1041
+ const workingProjectId = deps.sessionMetadataService?.getMetadata(sessionId)?.workingProjectId;
1042
+ const activeProcess = deps.supervisor.getProcessForSession(sessionId);
1043
+ const redirectProjectId = resolveCanonicalProjectRedirect({
1044
+ requestProjectId,
1045
+ workingProjectId,
1046
+ activeProcessProjectId: typeof activeProcess?.projectId === "string"
1047
+ ? activeProcess.projectId
1048
+ : undefined,
1049
+ });
1050
+ if (redirectProjectId) {
1051
+ return { redirectProjectId };
1052
+ }
1053
+ const workingProject = await deps.scanner.getOrCreateProject(requestProjectId);
1054
+ if (!workingProject) {
1055
+ return { error: "Project not found", status: 404 };
1056
+ }
1057
+ const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
1058
+ const overlayTranscriptProjectId = metadata?.workingProjectId === requestProjectId &&
1059
+ metadata.transcriptProjectId &&
1060
+ metadata.transcriptProjectId !== requestProjectId
1061
+ ? metadata.transcriptProjectId
1062
+ : undefined;
1063
+ // Default: the transcript lives under the working (request) project.
1064
+ let transcriptProjectId = requestProjectId;
1065
+ let transcriptProject = workingProject;
1066
+ // Overlay case (session "moved" to a different working project): the
1067
+ // transcript may live under a distinct project. Trust the recorded pointer
1068
+ // only if the transcript is actually findable there — a stale/wrong pointer
1069
+ // must not permanently brick viewing, so fall back to the working project.
1070
+ if (overlayTranscriptProjectId) {
1071
+ const overlayProject = await deps.scanner.getOrCreateProject(overlayTranscriptProjectId);
1072
+ const overlayHasTranscript = overlayProject
1073
+ ? Boolean(await findSessionSummaryAcrossProviders(overlayProject, sessionId, overlayTranscriptProjectId, providerResolutionDeps(deps), deps.sessionMetadataService?.getProvider(sessionId), { readMode: "head" }))
1074
+ : false;
1075
+ if (overlayProject && overlayHasTranscript) {
1076
+ transcriptProjectId = overlayTranscriptProjectId;
1077
+ transcriptProject = overlayProject;
1078
+ }
1079
+ }
1080
+ return {
1081
+ transcriptProject,
1082
+ transcriptProjectId,
1083
+ workingProject,
1084
+ workingProjectId: requestProjectId,
1085
+ };
1086
+ };
1087
+ const getGlobalInstructions = () => buildEffectiveAgentContext({
1088
+ globalInstructions: deps.serverSettingsService?.getSetting("globalInstructions"),
1089
+ hints: deps.serverSettingsService?.getSetting("agentContextHints"),
1090
+ });
1091
+ const resolveLaunchWorkstreamTarget = (project, rawWorkstreamId) => {
1092
+ if (rawWorkstreamId === undefined) {
1093
+ return { projectPath: project.path };
1094
+ }
1095
+ if (!isWorkstreamId(rawWorkstreamId)) {
1096
+ return { error: "Invalid workstream ID format", status: 400 };
1097
+ }
1098
+ if (deps.serverSettingsService?.getSetting("workstreamsEnabled") !== true) {
1099
+ return { error: "Workstreams are disabled", status: 404 };
1100
+ }
1101
+ if (!deps.workstreamService) {
1102
+ return { error: "Workstreams unavailable", status: 503 };
1103
+ }
1104
+ const workstreamId = rawWorkstreamId;
1105
+ if (workstreamId === mainWorkstreamId(project.id)) {
1106
+ return { projectPath: project.path };
1107
+ }
1108
+ if (!deps.sessionMetadataService) {
1109
+ return { error: "Session metadata unavailable", status: 503 };
1110
+ }
1111
+ const workstream = deps.workstreamService.getWorkstream(project.id, workstreamId);
1112
+ if (workstream?.kind !== "checkout") {
1113
+ return { error: "Workstream not found", status: 404 };
1114
+ }
1115
+ if (workstream.status !== "active") {
1116
+ return { error: "Workstream is not active", status: 409 };
1117
+ }
1118
+ return { projectPath: workstream.path, workstreamId };
1119
+ };
1120
+ const persistLaunchMetadata = async (sessionId, provider, executor, initialPrompt, requestedModel, promptSuggestionMode, recapAfterSeconds, workstreamId) => {
1050
1121
  if (!deps.sessionMetadataService) {
1051
1122
  return;
1052
1123
  }
@@ -1059,18 +1130,40 @@ export function createSessionsRoutes(deps) {
1059
1130
  if (initialPrompt?.trim()) {
1060
1131
  await deps.sessionMetadataService.setInitialPrompt(sessionId, initialPrompt);
1061
1132
  }
1133
+ // Persist the requested YA model id (the launch alias, incl. "default") so
1134
+ // per-model settings still key by it after a server restart, instead of
1135
+ // falling back to the reported model. See topics/provider-abstraction.md.
1136
+ if (requestedModel) {
1137
+ await deps.sessionMetadataService.setRequestedModel(sessionId, requestedModel);
1138
+ }
1139
+ // Persist the resolved prompt-suggestion mode so a later resume (which may
1140
+ // omit it from the request body) recovers the per-session preference
1141
+ // instead of falling back to the provider's native default.
1142
+ if (promptSuggestionMode !== undefined) {
1143
+ await deps.sessionMetadataService.updateMetadata(sessionId, {
1144
+ promptSuggestionMode,
1145
+ });
1146
+ }
1147
+ if (recapAfterSeconds !== undefined) {
1148
+ await deps.sessionMetadataService.updateMetadata(sessionId, {
1149
+ recapAfterSeconds,
1150
+ });
1151
+ }
1152
+ if (workstreamId !== undefined) {
1153
+ await deps.sessionMetadataService.setWorkstream(sessionId, workstreamId);
1154
+ }
1155
+ };
1156
+ const loadProviderSession = async (project, sessionId, projectId, preferredProvider, afterMessageId, options) => {
1157
+ for (const source of getSessionSources(project, providerResolutionDeps(deps), preferredProvider)) {
1158
+ const loaded = await source.reader.getSession(sessionId, projectId, afterMessageId, options);
1159
+ if (loaded) {
1160
+ return loaded;
1161
+ }
1162
+ }
1163
+ return null;
1062
1164
  };
1063
1165
  const loadRestartSourceSession = async (project, sessionId, projectId, preferredProvider, process) => {
1064
- const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, {
1065
- readerFactory: deps.readerFactory,
1066
- codexSessionsDir: deps.codexSessionsDir,
1067
- codexReaderFactory: deps.codexReaderFactory,
1068
- geminiSessionsDir: deps.geminiSessionsDir,
1069
- geminiReaderFactory: deps.geminiReaderFactory,
1070
- geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
1071
- grokSessionsDir: deps.grokSessionsDir,
1072
- grokReaderFactory: deps.grokReaderFactory,
1073
- }, preferredProvider);
1166
+ const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), preferredProvider, { readMode: "head" });
1074
1167
  if (resolved) {
1075
1168
  const loaded = await resolved.source.reader.getSession(sessionId, projectId, undefined, { includeOrphans: false });
1076
1169
  if (loaded) {
@@ -1092,6 +1185,7 @@ export function createSessionsRoutes(deps) {
1092
1185
  processId: process.id,
1093
1186
  permissionMode: process.permissionMode,
1094
1187
  modeVersion: process.modeVersion,
1188
+ recapAfterSeconds: process.recapAfterSeconds,
1095
1189
  },
1096
1190
  provider: process.provider,
1097
1191
  model: process.resolvedModel ?? process.model,
@@ -1127,7 +1221,8 @@ export function createSessionsRoutes(deps) {
1127
1221
  unsubscribe = null;
1128
1222
  };
1129
1223
  unsubscribe = replacement.subscribe((event) => {
1130
- if (event.type === "message" && isRestartReplacementActivity(event.message)) {
1224
+ if (event.type === "message" &&
1225
+ isRestartReplacementActivity(event.message)) {
1131
1226
  cleanup();
1132
1227
  void deps.supervisor.abortProcess(oldProcess.id).catch((error) => {
1133
1228
  console.warn(`[restart] Failed to abort old process ${oldProcess.id}:`, error);
@@ -1157,11 +1252,19 @@ export function createSessionsRoutes(deps) {
1157
1252
  if (!isUrlProjectId(projectId)) {
1158
1253
  return c.json({ error: "Invalid project ID format" }, 400);
1159
1254
  }
1160
- const project = await deps.scanner.getOrCreateProject(projectId);
1161
- if (!project) {
1162
- return c.json({ error: "Project not found" }, 404);
1255
+ const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
1256
+ if ("redirectProjectId" in routing) {
1257
+ return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
1258
+ }
1259
+ if ("error" in routing) {
1260
+ return c.json({ error: routing.error }, routing.status);
1163
1261
  }
1164
- const reader = deps.readerFactory(project);
1262
+ const reader = await resolveSessionReaderForAgentContent({
1263
+ deps,
1264
+ project: routing.transcriptProject,
1265
+ sessionId: c.req.param("sessionId"),
1266
+ projectId: routing.transcriptProjectId,
1267
+ });
1165
1268
  const mappings = await reader.getAgentMappings();
1166
1269
  return c.json({ mappings });
1167
1270
  });
@@ -1174,17 +1277,30 @@ export function createSessionsRoutes(deps) {
1174
1277
  if (!isUrlProjectId(projectId)) {
1175
1278
  return c.json({ error: "Invalid project ID format" }, 400);
1176
1279
  }
1177
- const project = await deps.scanner.getOrCreateProject(projectId);
1178
- if (!project) {
1179
- return c.json({ error: "Project not found" }, 404);
1280
+ const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
1281
+ if ("redirectProjectId" in routing) {
1282
+ return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
1180
1283
  }
1181
- const reader = deps.readerFactory(project);
1284
+ if ("error" in routing) {
1285
+ return c.json({ error: routing.error }, routing.status);
1286
+ }
1287
+ const reader = await resolveSessionReaderForAgentContent({
1288
+ deps,
1289
+ project: routing.transcriptProject,
1290
+ sessionId: c.req.param("sessionId"),
1291
+ projectId: routing.transcriptProjectId,
1292
+ });
1182
1293
  const agentSession = await reader.getAgentSession(agentId);
1183
1294
  if (!agentSession) {
1184
1295
  return c.json({ error: "Agent session not found" }, 404);
1185
1296
  }
1186
1297
  // Add server-rendered HTML to text blocks for markdown display
1187
- await augmentTextBlocks(agentSession.messages);
1298
+ await augmentTextBlocks(agentSession.messages, {
1299
+ projectFileLinks: {
1300
+ projectId: routing.workingProjectId,
1301
+ projectPath: routing.workingProject.path,
1302
+ },
1303
+ });
1188
1304
  return c.json(agentSession);
1189
1305
  });
1190
1306
  // GET /api/projects/:projectId/sessions/:sessionId/metadata - Get session metadata only (no messages)
@@ -1196,10 +1312,16 @@ export function createSessionsRoutes(deps) {
1196
1312
  if (!isUrlProjectId(projectId)) {
1197
1313
  return c.json({ error: "Invalid project ID format" }, 400);
1198
1314
  }
1199
- const project = await deps.scanner.getOrCreateProject(projectId);
1200
- if (!project) {
1201
- return c.json({ error: "Project not found" }, 404);
1315
+ const routing = await resolveSessionProjectRouting(projectId, sessionId);
1316
+ if ("redirectProjectId" in routing) {
1317
+ return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/metadata", c.req.url), 307);
1318
+ }
1319
+ if ("error" in routing) {
1320
+ return c.json({ error: routing.error }, routing.status);
1202
1321
  }
1322
+ const project = routing.workingProject;
1323
+ const transcriptProject = routing.transcriptProject;
1324
+ const transcriptProjectId = routing.transcriptProjectId;
1203
1325
  // Check if session is actively owned by a process
1204
1326
  const process = deps.supervisor.getProcessForSession(sessionId);
1205
1327
  // Check if session is being controlled by an external program
@@ -1211,6 +1333,7 @@ export function createSessionsRoutes(deps) {
1211
1333
  processId: process.id,
1212
1334
  permissionMode: process.permissionMode,
1213
1335
  modeVersion: process.modeVersion,
1336
+ recapAfterSeconds: process.recapAfterSeconds,
1214
1337
  }
1215
1338
  : isExternal
1216
1339
  ? { owner: "external" }
@@ -1222,23 +1345,18 @@ export function createSessionsRoutes(deps) {
1222
1345
  const lastSeenAt = lastSeenEntry?.timestamp;
1223
1346
  // Get pending input request from active process
1224
1347
  const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
1225
- // Get available slash commands from active process
1226
- const slashCommands = process?.supportsDynamicCommands
1227
- ? await process.supportedCommands()
1228
- : null;
1348
+ const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
1229
1349
  // Read minimal session info from disk (just for title/timestamps, no messages)
1230
- const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
1231
- const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, {
1232
- readerFactory: deps.readerFactory,
1233
- codexSessionsDir: deps.codexSessionsDir,
1234
- codexReaderFactory: deps.codexReaderFactory,
1235
- geminiSessionsDir: deps.geminiSessionsDir,
1236
- geminiReaderFactory: deps.geminiReaderFactory,
1237
- geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
1238
- grokSessionsDir: deps.grokSessionsDir,
1239
- grokReaderFactory: deps.grokReaderFactory,
1240
- }, metadataProvider ?? process?.provider);
1241
- const sessionSummary = sessionSummaryResult?.summary ?? null;
1350
+ const metadataProvider = metadata?.provider ??
1351
+ deps.sessionMetadataService?.getProvider(sessionId);
1352
+ const slashCommands = await getSessionSlashCommands(process, process?.provider ?? metadataProvider ?? project.provider);
1353
+ const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
1354
+ const sessionSummaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider ?? process?.provider);
1355
+ const rawSessionSummary = sessionSummaryResult?.summary ?? null;
1356
+ const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
1357
+ const sessionSummary = rawSessionSummary
1358
+ ? applyRecapOverlayToSummary(rawSessionSummary, recapMessages)
1359
+ : null;
1242
1360
  if (!sessionSummary && !process) {
1243
1361
  const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
1244
1362
  if (canonicalProjectId) {
@@ -1246,14 +1364,13 @@ export function createSessionsRoutes(deps) {
1246
1364
  }
1247
1365
  return c.json({ error: "Session not found" }, 404);
1248
1366
  }
1249
- // Calculate hasUnread if we have session summary
1250
- const hasUnread = deps.notificationService && sessionSummary
1251
- ? deps.notificationService.hasUnread(sessionId, sessionSummary.updatedAt)
1367
+ const hasUnread = rawSessionSummary
1368
+ ? hasUnreadProviderContent(deps.notificationService, sessionId, rawSessionSummary.updatedAt)
1252
1369
  : undefined;
1253
1370
  const response = {
1254
1371
  session: {
1255
1372
  id: sessionId,
1256
- projectId: projectId,
1373
+ projectId: routing.workingProjectId,
1257
1374
  title: sessionSummary?.title ?? null,
1258
1375
  fullTitle: sessionSummary?.fullTitle ?? null,
1259
1376
  createdAt: sessionSummary?.createdAt ?? new Date().toISOString(),
@@ -1279,29 +1396,158 @@ export function createSessionsRoutes(deps) {
1279
1396
  heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
1280
1397
  heartbeatTurnText: metadata?.heartbeatTurnText,
1281
1398
  heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes ?? undefined,
1399
+ promptSuggestionMode: metadata?.promptSuggestionMode,
1400
+ recapAfterSeconds: metadata?.recapAfterSeconds,
1401
+ workingProjectId: metadata?.workingProjectId,
1402
+ transcriptProjectId: metadata?.transcriptProjectId,
1403
+ workstreamId: metadata?.workstreamId,
1404
+ transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
1282
1405
  lastSeenAt,
1283
1406
  hasUnread,
1284
1407
  },
1285
1408
  ownership,
1286
1409
  processState: process?.state.type ?? null,
1410
+ providerRuntimeStatus,
1287
1411
  pendingInputRequest,
1288
1412
  slashCommands,
1413
+ ...(deferredMessages.length > 0 ? { deferredMessages } : {}),
1289
1414
  };
1290
1415
  return c.json(response);
1291
1416
  });
1417
+ // PUT /api/projects/:projectId/sessions/:sessionId/project
1418
+ // Reclassify a session under a different YA project without sending any
1419
+ // provider-visible turn. The provider transcript remains under
1420
+ // transcriptProjectId; relative file links and UI routing use projectId.
1421
+ routes.put("/projects/:projectId/sessions/:sessionId/project", async (c) => {
1422
+ const projectId = c.req.param("projectId");
1423
+ const sessionId = c.req.param("sessionId");
1424
+ if (!isUrlProjectId(projectId)) {
1425
+ return c.json({ error: "Invalid project ID format" }, 400);
1426
+ }
1427
+ if (!deps.sessionMetadataService) {
1428
+ return c.json({ error: "Session metadata service unavailable" }, 503);
1429
+ }
1430
+ let body;
1431
+ try {
1432
+ body = await c.req.json();
1433
+ }
1434
+ catch {
1435
+ return c.json({ error: "Invalid JSON body" }, 400);
1436
+ }
1437
+ const targetProjectId = body.projectId;
1438
+ if (typeof targetProjectId !== "string" ||
1439
+ !isUrlProjectId(targetProjectId)) {
1440
+ return c.json({ error: "Invalid target project ID format" }, 400);
1441
+ }
1442
+ const targetProject = await deps.scanner.getOrCreateProject(targetProjectId);
1443
+ if (!targetProject) {
1444
+ return c.json({ error: "Target project not found" }, 404);
1445
+ }
1446
+ const metadata = deps.sessionMetadataService.getMetadata(sessionId);
1447
+ const process = deps.supervisor.getProcessForSession(sessionId);
1448
+ const transcriptProjectId = metadata?.transcriptProjectId ??
1449
+ process?.projectId ??
1450
+ projectId;
1451
+ const transcriptProject = transcriptProjectId === targetProjectId
1452
+ ? targetProject
1453
+ : await deps.scanner.getOrCreateProject(transcriptProjectId);
1454
+ if (!transcriptProject) {
1455
+ return c.json({ error: "Transcript project not found" }, 404);
1456
+ }
1457
+ if (!process) {
1458
+ const metadataProvider = metadata?.provider ??
1459
+ deps.sessionMetadataService.getProvider(sessionId);
1460
+ const summary = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
1461
+ if (!summary) {
1462
+ return c.json({ error: "Session not found" }, 404);
1463
+ }
1464
+ }
1465
+ const storedWorkingProjectId = targetProjectId === transcriptProjectId ? undefined : targetProjectId;
1466
+ const storedTranscriptProjectId = targetProjectId === transcriptProjectId ? undefined : transcriptProjectId;
1467
+ await deps.sessionMetadataService.setWorkingProject(sessionId, storedWorkingProjectId, storedTranscriptProjectId);
1468
+ deps.eventBus?.emit({
1469
+ type: "session-metadata-changed",
1470
+ sessionId,
1471
+ projectId: targetProjectId,
1472
+ transcriptProjectId: storedTranscriptProjectId ?? null,
1473
+ timestamp: new Date().toISOString(),
1474
+ });
1475
+ return c.json({
1476
+ updated: true,
1477
+ projectId: targetProjectId,
1478
+ transcriptProjectId: storedTranscriptProjectId ?? null,
1479
+ });
1480
+ });
1481
+ // POST /api/projects/:projectId/sessions/:sessionId/refresh-preview
1482
+ // On-demand recompute of the hover-card recent-activity excerpt for a
1483
+ // non-running session (its live state is otherwise only refreshed when the
1484
+ // session is resumed). Fast reverse-scan of the JSONL, then push the result
1485
+ // to lists/hovers via a session-updated event so the preview updates in
1486
+ // place without flicker. See topics/session-hovercard-recent-activity.md.
1487
+ routes.post("/projects/:projectId/sessions/:sessionId/refresh-preview", async (c) => {
1488
+ const projectId = c.req.param("projectId");
1489
+ const sessionId = c.req.param("sessionId");
1490
+ if (!isUrlProjectId(projectId)) {
1491
+ return c.json({ error: "Invalid project ID format" }, 400);
1492
+ }
1493
+ const routing = await resolveSessionProjectRouting(projectId, sessionId);
1494
+ if ("redirectProjectId" in routing) {
1495
+ return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/refresh-preview", c.req.url), 307);
1496
+ }
1497
+ if ("error" in routing) {
1498
+ return c.json({ error: routing.error }, routing.status);
1499
+ }
1500
+ const transcriptProject = routing.transcriptProject;
1501
+ const transcriptProjectId = routing.transcriptProjectId;
1502
+ const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
1503
+ const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
1504
+ let lastAgentText;
1505
+ if (recapMessages.length > 0) {
1506
+ const summaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider);
1507
+ if (summaryResult?.summary) {
1508
+ lastAgentText = applyRecapOverlayToSummary(summaryResult.summary, recapMessages).lastAgentText;
1509
+ }
1510
+ }
1511
+ // Claude uses the fast reverse-scan when there is no durable overlay to
1512
+ // compare; every other provider goes through the cross-provider load +
1513
+ // normalize, then a shared extractor on the uniform message form.
1514
+ const reader = deps.readerFactory(transcriptProject);
1515
+ lastAgentText ??= reader.getLastAgentExcerpt
1516
+ ? await reader.getLastAgentExcerpt(sessionId)
1517
+ : undefined;
1518
+ if (lastAgentText === undefined) {
1519
+ const normalized = await loadRestartSourceSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider);
1520
+ if (normalized) {
1521
+ lastAgentText = extractLastAgentExcerpt(normalized.messages);
1522
+ }
1523
+ }
1524
+ if (lastAgentText !== undefined) {
1525
+ deps.eventBus?.emit({
1526
+ type: "session-updated",
1527
+ sessionId,
1528
+ projectId: routing.workingProjectId,
1529
+ lastAgentText,
1530
+ timestamp: new Date().toISOString(),
1531
+ });
1532
+ }
1533
+ return c.json({ lastAgentText: lastAgentText ?? null });
1534
+ });
1292
1535
  // GET /api/projects/:projectId/sessions/:sessionId - Get session detail
1293
1536
  // Optional query params:
1294
1537
  // ?afterMessageId=<id> - incremental forward-fetch (append new messages)
1295
1538
  // ?tailCompactions=<n> - return only last N compact boundaries worth of messages
1296
1539
  // ?beforeMessageId=<id> - cursor for loading older chunks (used with tailCompactions)
1297
- // ?tailTurns=<n> - aggressive opt-in client memory cap by recent user turns
1298
- // ?tailFrom=<id> - aggressive opt-in client memory cap from a user message id
1540
+ // ?tailTurns=<n> - recent-turn selector within the authorized history scope
1541
+ // ?tailFrom=<id> - start selector within the authorized history scope
1542
+ // ?fullHistory=1 - explicitly authorize selectors to inspect full history
1299
1543
  routes.get("/projects/:projectId/sessions/:sessionId", async (c) => {
1300
1544
  const requestStartMs = performance.now();
1301
1545
  const projectId = c.req.param("projectId");
1302
1546
  const sessionId = c.req.param("sessionId");
1303
1547
  const afterMessageId = c.req.query("afterMessageId");
1304
1548
  const publicShare = c.req.query("publicShare") === "1";
1549
+ const fullHistory = c.req.query("fullHistory") === "1";
1550
+ const fullHistoryReason = c.req.query("fullHistoryReason");
1305
1551
  const tailCompactionsParam = c.req.query("tailCompactions");
1306
1552
  const beforeMessageId = c.req.query("beforeMessageId");
1307
1553
  const tailTurnsParam = c.req.query("tailTurns");
@@ -1312,15 +1558,39 @@ export function createSessionsRoutes(deps) {
1312
1558
  const tailTurns = tailTurnsParam !== undefined
1313
1559
  ? Number.parseInt(tailTurnsParam, 10)
1314
1560
  : undefined;
1561
+ const requestedTailCompactions = tailCompactions !== undefined &&
1562
+ !Number.isNaN(tailCompactions) &&
1563
+ tailCompactions > 0
1564
+ ? tailCompactions
1565
+ : undefined;
1566
+ const requestedTailTurns = tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0
1567
+ ? tailTurns
1568
+ : undefined;
1569
+ const defaultedToCompactTail = !fullHistory &&
1570
+ !afterMessageId &&
1571
+ requestedTailCompactions === undefined;
1572
+ const unboundedRequestDefaultedToCompactTail = defaultedToCompactTail &&
1573
+ !tailFrom &&
1574
+ requestedTailTurns === undefined;
1575
+ const effectiveTailCompactions = requestedTailCompactions ??
1576
+ (defaultedToCompactTail
1577
+ ? DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS
1578
+ : undefined);
1315
1579
  // Validate projectId format at API boundary
1316
1580
  if (!isUrlProjectId(projectId)) {
1317
1581
  return c.json({ error: "Invalid project ID format" }, 400);
1318
1582
  }
1319
- // Use getOrCreateProject to support Codex projects that may not be in the scan cache yet
1320
- const project = await deps.scanner.getOrCreateProject(projectId);
1321
- if (!project) {
1322
- return c.json({ error: "Project not found" }, 404);
1583
+ const routing = await resolveSessionProjectRouting(projectId, sessionId);
1584
+ if ("redirectProjectId" in routing) {
1585
+ return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "", c.req.url), 307);
1323
1586
  }
1587
+ if ("error" in routing) {
1588
+ return c.json({ error: routing.error }, routing.status);
1589
+ }
1590
+ const project = routing.workingProject;
1591
+ const effectiveProjectId = routing.workingProjectId;
1592
+ const transcriptProject = routing.transcriptProject;
1593
+ const transcriptProjectId = routing.transcriptProjectId;
1324
1594
  const projectResolvedMs = performance.now();
1325
1595
  // Check if session is actively owned by a process
1326
1596
  const process = deps.supervisor.getProcessForSession(sessionId);
@@ -1330,70 +1600,30 @@ export function createSessionsRoutes(deps) {
1330
1600
  // Only mark tools as "aborted" if we owned the session and know it terminated
1331
1601
  const wasEverOwned = deps.supervisor.wasEverOwned(sessionId);
1332
1602
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
1333
- // Always try to read from disk first (even for owned sessions)
1334
- const reader = deps.readerFactory(project);
1335
- let loadedSession = await reader.getSession(sessionId, project.id, afterMessageId, {
1603
+ const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
1604
+ const recapCursor = findDurableRecapCursor(afterMessageId, recapMessages);
1605
+ const providerAfterMessageId = recapCursor ? undefined : afterMessageId;
1606
+ const primaryReaderAfterMessageId = isClaudeSdkProviderName(project.provider) ||
1607
+ isClaudeSdkProviderName(metadataProvider)
1608
+ ? undefined
1609
+ : providerAfterMessageId;
1610
+ const loadedSession = await loadProviderSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider ?? process?.provider, primaryReaderAfterMessageId, {
1336
1611
  // Only include orphaned tool info if:
1337
1612
  // 1. We previously owned this session (not external)
1338
1613
  // 2. No active process (tools aren't potentially in progress)
1339
1614
  // When we own the session, tools without results might be pending approval
1340
1615
  includeOrphans: wasEverOwned && !process,
1341
1616
  });
1342
- // For Claude projects, also check for Codex sessions if primary reader didn't find it
1343
- // This handles mixed projects that have sessions from multiple providers
1344
- if (!loadedSession &&
1345
- project.provider === "claude" &&
1346
- (deps.codexReaderFactory || deps.codexSessionsDir)) {
1347
- const codexReader = deps.codexReaderFactory?.(project.path) ??
1348
- (deps.codexSessionsDir
1349
- ? new CodexSessionReader({
1350
- sessionsDir: deps.codexSessionsDir,
1351
- projectPath: project.path,
1352
- })
1353
- : null);
1354
- if (codexReader) {
1355
- loadedSession = await codexReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
1356
- }
1357
- }
1358
- // For Claude/Codex projects, also check for Gemini sessions if still not found
1359
- // This handles mixed projects that have sessions from multiple providers
1360
- if (!loadedSession &&
1361
- (project.provider === "claude" || project.provider === "codex") &&
1362
- (deps.geminiReaderFactory || deps.geminiSessionsDir)) {
1363
- const geminiReader = deps.geminiReaderFactory?.(project.path) ??
1364
- (deps.geminiSessionsDir
1365
- ? new GeminiSessionReader({
1366
- sessionsDir: deps.geminiSessionsDir,
1367
- projectPath: project.path,
1368
- hashToCwd: deps.geminiScanner?.getHashToCwd(),
1369
- })
1370
- : null);
1371
- if (geminiReader) {
1372
- loadedSession = await geminiReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
1373
- }
1374
- }
1375
- // For mixed-provider projects, a restarted YA process may need session
1376
- // metadata to rediscover an OpenCode-native ses_* transcript.
1377
- if (!loadedSession && metadataProvider === "opencode") {
1378
- const opencodeReader = deps.readerFactory({
1379
- ...project,
1380
- provider: "opencode",
1381
- });
1382
- loadedSession = await opencodeReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
1383
- }
1384
- if (!loadedSession) {
1385
- const grokReader = getGrokReader(project.path);
1386
- if (grokReader) {
1387
- loadedSession = await grokReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
1388
- }
1389
- }
1390
1617
  const readEndMs = performance.now();
1391
1618
  let session = loadedSession ? normalizeSession(loadedSession) : null;
1392
1619
  const normalizedMessageCount = session?.messages.length ?? 0;
1393
1620
  const normalizeEndMs = performance.now();
1621
+ if (session && isClaudeSdkProviderName(session.provider)) {
1622
+ augmentTaskListSnapshots(session.messages);
1623
+ }
1394
1624
  let incrementalAnchorFound = false;
1395
- if (session && afterMessageId) {
1396
- const sliced = sliceAfterMessageIdWithMatch(session.messages, afterMessageId);
1625
+ if (session && providerAfterMessageId) {
1626
+ const sliced = sliceAfterMessageIdWithMatch(session.messages, providerAfterMessageId);
1397
1627
  session = {
1398
1628
  ...session,
1399
1629
  messages: sliced.messages,
@@ -1407,6 +1637,7 @@ export function createSessionsRoutes(deps) {
1407
1637
  processId: process.id,
1408
1638
  permissionMode: process.permissionMode,
1409
1639
  modeVersion: process.modeVersion,
1640
+ recapAfterSeconds: process.recapAfterSeconds,
1410
1641
  }
1411
1642
  : isExternal
1412
1643
  ? { owner: "external" }
@@ -1414,12 +1645,16 @@ export function createSessionsRoutes(deps) {
1414
1645
  // Get pending input request from active process (for tool approval prompts)
1415
1646
  // This ensures clients get pending requests immediately without waiting for SSE
1416
1647
  const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
1417
- // Get available slash commands from active process (for "/" button in toolbar)
1648
+ const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
1649
+ // Get available slash commands (for "/" button and typed slash menu)
1418
1650
  // The init message that normally carries these gets discarded from the SSE buffer
1419
- // after ~30s, so we attach them to the REST response for reliable delivery.
1420
- const slashCommands = process?.supportsDynamicCommands
1421
- ? await process.supportedCommands()
1422
- : null;
1651
+ // after ~30s, so we attach them to the REST response. Providers with known
1652
+ // native built-ins, such as Codex, can expose those while stopped.
1653
+ const slashCommands = await getSessionSlashCommands(process, process?.provider ??
1654
+ session?.provider ??
1655
+ metadataProvider ??
1656
+ project.provider);
1657
+ const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
1423
1658
  if (!session) {
1424
1659
  // Session file doesn't exist yet - only valid if we own the process
1425
1660
  if (process) {
@@ -1436,22 +1671,23 @@ export function createSessionsRoutes(deps) {
1436
1671
  : mis
1437
1672
  ? (m, p) => mis.getContextWindow(m, p)
1438
1673
  : undefined);
1439
- // Cache SDK-reported context window for future JSONL reads
1440
- if (mis && sdkContextWindow && process.resolvedModel) {
1441
- mis.recordContextWindow(process.resolvedModel, sdkContextWindow, process.provider);
1442
- }
1674
+ // (Durable recording happens at the observation point in Process via
1675
+ // onContextWindowObserved, not as a side effect of this GET.)
1443
1676
  // Get metadata even for new sessions (in case it was set before file was written)
1444
1677
  const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
1678
+ const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
1679
+ const visibleProcessMessages = mergeRecapMessages(processMessages, recapMessages);
1445
1680
  // Get notification data for new sessions too
1446
1681
  const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
1447
- const newSessionUpdatedAt = new Date().toISOString();
1682
+ const latestRecap = latestRecapMessage(recapMessages);
1683
+ const newSessionUpdatedAt = latestRecap?.timestamp ?? new Date().toISOString();
1448
1684
  const hasUnread = deps.notificationService
1449
1685
  ? deps.notificationService.hasUnread(sessionId, newSessionUpdatedAt)
1450
1686
  : undefined;
1451
1687
  return c.json({
1452
1688
  session: {
1453
1689
  id: sessionId,
1454
- projectId,
1690
+ projectId: effectiveProjectId,
1455
1691
  title: null,
1456
1692
  createdAt: new Date().toISOString(),
1457
1693
  updatedAt: newSessionUpdatedAt,
@@ -1466,16 +1702,24 @@ export function createSessionsRoutes(deps) {
1466
1702
  heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
1467
1703
  heartbeatTurnText: metadata?.heartbeatTurnText,
1468
1704
  heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
1705
+ promptSuggestionMode: metadata?.promptSuggestionMode,
1706
+ recapAfterSeconds: metadata?.recapAfterSeconds,
1707
+ workingProjectId: metadata?.workingProjectId,
1708
+ transcriptProjectId: metadata?.transcriptProjectId,
1709
+ workstreamId: metadata?.workstreamId,
1710
+ transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
1469
1711
  lastSeenAt: lastSeenEntry?.timestamp,
1470
1712
  hasUnread,
1471
1713
  provider: process.provider,
1472
1714
  model: process.resolvedModel,
1473
1715
  contextUsage,
1474
1716
  },
1475
- messages: processMessages,
1717
+ messages: visibleProcessMessages,
1476
1718
  ownership,
1719
+ providerRuntimeStatus,
1477
1720
  pendingInputRequest,
1478
1721
  slashCommands,
1722
+ ...(deferredMessages.length > 0 ? { deferredMessages } : {}),
1479
1723
  });
1480
1724
  }
1481
1725
  const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
@@ -1489,37 +1733,41 @@ export function createSessionsRoutes(deps) {
1489
1733
  // Get notification data (lastSeenAt, hasUnread)
1490
1734
  const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
1491
1735
  const lastSeenAt = lastSeenEntry?.timestamp;
1492
- const hasUnread = deps.notificationService
1493
- ? deps.notificationService.hasUnread(sessionId, session.updatedAt)
1494
- : undefined;
1495
- // Apply pagination if requested (BEFORE expensive augmentation). tailTurns
1496
- // is an opt-in stronger client memory cap, so it wins over compact tails.
1497
- // Skip both when afterMessageId is present since that's an incremental
1498
- // forward-fetch use case.
1736
+ // Apply pagination BEFORE expensive augmentation. Compact boundaries set
1737
+ // the authorized history scope by default; turn selectors may narrow that
1738
+ // scope but cannot broaden it unless fullHistory=1 removed the default.
1739
+ // Skip initial-window slicing for incremental forward fetches.
1499
1740
  let paginationInfo;
1500
1741
  if (!afterMessageId &&
1501
- (tailFrom ||
1502
- (tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0))) {
1503
- const sliced = sliceAtUserTurnBoundary(session.messages, tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0
1504
- ? tailTurns
1505
- : 20, tailFrom);
1742
+ effectiveTailCompactions !== undefined &&
1743
+ !beforeMessageId &&
1744
+ (tailFrom || requestedTailTurns !== undefined)) {
1745
+ const sliced = sliceAtCompactAndUserTurnBoundaries(session.messages, effectiveTailCompactions, requestedTailTurns ?? 20, tailFrom);
1506
1746
  session = { ...session, messages: sliced.messages };
1507
1747
  paginationInfo = sliced.pagination;
1508
1748
  }
1509
- else if (tailCompactions !== undefined &&
1510
- !Number.isNaN(tailCompactions) &&
1511
- tailCompactions > 0 &&
1749
+ else if (!afterMessageId &&
1750
+ effectiveTailCompactions === undefined &&
1751
+ (tailFrom || requestedTailTurns !== undefined)) {
1752
+ const sliced = sliceAtUserTurnBoundary(session.messages, requestedTailTurns ?? 20, tailFrom);
1753
+ session = { ...session, messages: sliced.messages };
1754
+ paginationInfo = sliced.pagination;
1755
+ }
1756
+ else if (effectiveTailCompactions !== undefined &&
1512
1757
  !afterMessageId) {
1513
- const sliced = sliceAtCompactBoundaries(session.messages, tailCompactions, beforeMessageId);
1758
+ const sliced = sliceAtCompactBoundaries(session.messages, effectiveTailCompactions, beforeMessageId);
1514
1759
  session = { ...session, messages: sliced.messages };
1515
1760
  paginationInfo = sliced.pagination;
1516
1761
  }
1762
+ if (isClaudeSdkProviderName(session.provider)) {
1763
+ pruneTaskListSnapshotsToLatest(session.messages);
1764
+ }
1517
1765
  const sliceEndMs = performance.now();
1518
1766
  // Codex normalized IDs can drift between stream and JSONL. If an
1519
1767
  // incremental request misses its anchor, never return the full historical
1520
1768
  // session into a compact-tail client; bound the fallback to the same tail
1521
1769
  // window used for initial loads.
1522
- if (afterMessageId && !incrementalAnchorFound) {
1770
+ if (providerAfterMessageId && !incrementalAnchorFound) {
1523
1771
  const sliced = sliceAtCompactBoundaries(session.messages, 2);
1524
1772
  session = { ...session, messages: sliced.messages };
1525
1773
  paginationInfo = sliced.pagination;
@@ -1529,9 +1777,33 @@ export function createSessionsRoutes(deps) {
1529
1777
  await augmentEditToolUses(session.messages);
1530
1778
  }
1531
1779
  else {
1532
- await augmentPersistedSessionMessages(session.messages);
1780
+ await augmentPersistedSessionMessages(session.messages, {
1781
+ projectFileLinks: {
1782
+ projectId: effectiveProjectId,
1783
+ projectPath: project.path,
1784
+ },
1785
+ });
1786
+ }
1787
+ // The overlay reassigns `session` below; hasUnreadProviderContent needs
1788
+ // the pre-overlay timestamp.
1789
+ const preRecapUpdatedAt = session.updatedAt;
1790
+ if (!beforeMessageId) {
1791
+ session = applyRecapOverlayToSession(session, recapMessages);
1792
+ if (recapCursor && afterMessageId) {
1793
+ const sliced = sliceAfterDurableRecapCursor({
1794
+ messages: session.messages,
1795
+ recap: recapCursor,
1796
+ cursorId: afterMessageId,
1797
+ });
1798
+ session = { ...session, messages: sliced.messages };
1799
+ incrementalAnchorFound = sliced.found;
1800
+ }
1801
+ }
1802
+ else {
1803
+ session = applyRecapOverlayToSummary(session, recapMessages);
1533
1804
  }
1534
1805
  const augmentEndMs = performance.now();
1806
+ const hasUnread = hasUnreadProviderContent(deps.notificationService, sessionId, preRecapUpdatedAt);
1535
1807
  // Override context usage with SDK-reported context window from live process
1536
1808
  // The reader uses hardcoded defaults; the process captures the real value at runtime
1537
1809
  let { contextUsage } = session;
@@ -1542,29 +1814,80 @@ export function createSessionsRoutes(deps) {
1542
1814
  percentage: Math.round((contextUsage.inputTokens / cw) * 100),
1543
1815
  contextWindow: cw,
1544
1816
  };
1545
- // Cache for future reads without a live process
1546
- deps.modelInfoService?.recordContextWindow(process.resolvedModel ?? session.model ?? "", cw, process.provider);
1817
+ // Durable recording happens at the observation point in Process via
1818
+ // onContextWindowObserved; this block only overrides the displayed value.
1547
1819
  }
1548
1820
  const { messages: _messages, ...sessionMetadata } = session;
1549
1821
  const totalMs = performance.now() - requestStartMs;
1822
+ if (unboundedRequestDefaultedToCompactTail &&
1823
+ normalizedMessageCount >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
1824
+ getLogger().warn({
1825
+ beforeMessageId: beforeMessageId ?? null,
1826
+ defaultTailCompactions: DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS,
1827
+ event: "session_detail_unbounded_defaulted_to_tail",
1828
+ normalizedMessageCount,
1829
+ projectId: effectiveProjectId,
1830
+ transcriptProjectId,
1831
+ provider: session.provider,
1832
+ publicShare,
1833
+ returnedMessageCount: session.messages.length,
1834
+ sessionId,
1835
+ timings: {
1836
+ augmentMs: roundedMs(augmentEndMs - sliceEndMs),
1837
+ normalizeMs: roundedMs(normalizeEndMs - readEndMs),
1838
+ projectMs: roundedMs(projectResolvedMs - requestStartMs),
1839
+ readMs: roundedMs(readEndMs - projectResolvedMs),
1840
+ routeMs: roundedMs(sliceEndMs - normalizeEndMs),
1841
+ totalMs: roundedMs(totalMs),
1842
+ },
1843
+ totalMessageCount: session.messageCount,
1844
+ }, "SESSION_DETAIL: defaulted unbounded request to compact tail");
1845
+ }
1846
+ if (fullHistory &&
1847
+ session.messages.length >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
1848
+ getLogger().warn({
1849
+ event: "session_detail_full_history_large",
1850
+ fullHistoryReason: fullHistoryReason ?? null,
1851
+ normalizedMessageCount,
1852
+ projectId: effectiveProjectId,
1853
+ transcriptProjectId,
1854
+ provider: session.provider,
1855
+ publicShare,
1856
+ returnedMessageCount: session.messages.length,
1857
+ sessionId,
1858
+ timings: {
1859
+ augmentMs: roundedMs(augmentEndMs - sliceEndMs),
1860
+ normalizeMs: roundedMs(normalizeEndMs - readEndMs),
1861
+ projectMs: roundedMs(projectResolvedMs - requestStartMs),
1862
+ readMs: roundedMs(readEndMs - projectResolvedMs),
1863
+ routeMs: roundedMs(sliceEndMs - normalizeEndMs),
1864
+ totalMs: roundedMs(totalMs),
1865
+ },
1866
+ totalMessageCount: session.messageCount,
1867
+ }, "SESSION_DETAIL: large explicit full-history request");
1868
+ }
1550
1869
  if (totalMs >= SESSION_DETAIL_SLOW_LOG_MS) {
1551
1870
  getLogger().warn({
1552
1871
  afterMessageId: afterMessageId ?? null,
1553
1872
  beforeMessageId: beforeMessageId ?? null,
1554
1873
  event: "session_detail_slow",
1874
+ defaultedToCompactTail,
1875
+ fullHistory,
1876
+ fullHistoryReason: fullHistoryReason ?? null,
1555
1877
  incrementalAnchorFound: afterMessageId
1556
1878
  ? incrementalAnchorFound
1557
1879
  : null,
1558
1880
  normalizedMessageCount,
1559
1881
  owned: Boolean(process),
1560
1882
  processState: process?.state.type ?? null,
1561
- projectId,
1883
+ projectId: effectiveProjectId,
1884
+ transcriptProjectId,
1562
1885
  provider: session.provider,
1563
1886
  publicShare,
1564
1887
  returnedMessageCount: session.messages.length,
1565
1888
  sessionId,
1566
- tailCompactions: tailCompactions ?? null,
1567
- tailTurns: tailTurns ?? null,
1889
+ tailCompactions: effectiveTailCompactions ?? null,
1890
+ tailTurns: requestedTailTurns ?? null,
1568
1891
  timings: {
1569
1892
  augmentMs: roundedMs(augmentEndMs - sliceEndMs),
1570
1893
  normalizeMs: roundedMs(normalizeEndMs - readEndMs),
@@ -1579,6 +1902,7 @@ export function createSessionsRoutes(deps) {
1579
1902
  return c.json({
1580
1903
  session: {
1581
1904
  ...sessionMetadata,
1905
+ projectId: effectiveProjectId,
1582
1906
  ownership,
1583
1907
  contextUsage,
1584
1908
  customTitle: metadata?.customTitle,
@@ -1590,6 +1914,12 @@ export function createSessionsRoutes(deps) {
1590
1914
  heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
1591
1915
  heartbeatTurnText: metadata?.heartbeatTurnText,
1592
1916
  heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
1917
+ promptSuggestionMode: metadata?.promptSuggestionMode,
1918
+ recapAfterSeconds: metadata?.recapAfterSeconds,
1919
+ workingProjectId: metadata?.workingProjectId,
1920
+ transcriptProjectId: metadata?.transcriptProjectId,
1921
+ workstreamId: metadata?.workstreamId,
1922
+ transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
1593
1923
  // Model comes from the session reader (extracted from JSONL)
1594
1924
  model: session.model,
1595
1925
  lastSeenAt,
@@ -1597,9 +1927,11 @@ export function createSessionsRoutes(deps) {
1597
1927
  },
1598
1928
  messages: session.messages,
1599
1929
  ownership,
1930
+ providerRuntimeStatus,
1600
1931
  pendingInputRequest,
1601
1932
  slashCommands,
1602
1933
  ...(paginationInfo && { pagination: paginationInfo }),
1934
+ ...(deferredMessages.length > 0 ? { deferredMessages } : {}),
1603
1935
  });
1604
1936
  });
1605
1937
  // POST /api/projects/:projectId/sessions - Start new session
@@ -1632,6 +1964,10 @@ export function createSessionsRoutes(deps) {
1632
1964
  if (helperSettings.error) {
1633
1965
  return c.json({ error: helperSettings.error }, 400);
1634
1966
  }
1967
+ const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
1968
+ if ("error" in workstreamTarget) {
1969
+ return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
1970
+ }
1635
1971
  const serverTimestamp = Date.now();
1636
1972
  const userMessage = {
1637
1973
  text: body.message,
@@ -1642,10 +1978,7 @@ export function createSessionsRoutes(deps) {
1642
1978
  tempId: body.tempId,
1643
1979
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
1644
1980
  };
1645
- // Convert thinking option to SDK config
1646
- const { thinking, effort } = body.thinking
1647
- ? thinkingOptionToConfig(body.thinking)
1648
- : { thinking: undefined, effort: undefined };
1981
+ const { thinking, effort } = buildThinkingOptions(body);
1649
1982
  // Convert model option (undefined or "default" means use CLI default)
1650
1983
  const model = body.model && body.model !== "default" ? body.model : undefined;
1651
1984
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
@@ -1656,7 +1989,7 @@ export function createSessionsRoutes(deps) {
1656
1989
  model: body.model,
1657
1990
  serviceTier,
1658
1991
  });
1659
- const result = await deps.supervisor.startSession(project.path, userMessage, body.mode, {
1992
+ const result = await deps.supervisor.startSession(workstreamTarget.projectPath, userMessage, body.mode, {
1660
1993
  model,
1661
1994
  serviceTier,
1662
1995
  thinking,
@@ -1666,8 +1999,12 @@ export function createSessionsRoutes(deps) {
1666
1999
  globalInstructions: getGlobalInstructions(),
1667
2000
  permissions: body.permissions,
1668
2001
  recapMode: helperSettings.recapMode,
2002
+ recapAfterSeconds: helperSettings.recapAfterSeconds,
1669
2003
  promptSuggestionMode: helperSettings.promptSuggestionMode,
1670
2004
  helperSideModel: helperSettings.helperSideModel,
2005
+ }, {
2006
+ projectId: project.id,
2007
+ workstreamId: workstreamTarget.workstreamId,
1671
2008
  });
1672
2009
  // Check if queue is full
1673
2010
  if (isQueueFullResponse(result)) {
@@ -1677,13 +2014,14 @@ export function createSessionsRoutes(deps) {
1677
2014
  if (isQueuedResponse(result)) {
1678
2015
  return c.json({ ...result, serverTimestamp }, 202); // 202 Accepted - queued for processing
1679
2016
  }
1680
- await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
2017
+ await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
1681
2018
  return c.json({
1682
2019
  sessionId: result.sessionId,
1683
2020
  processId: result.id,
1684
2021
  projectId: result.projectId,
1685
2022
  permissionMode: result.permissionMode,
1686
2023
  modeVersion: result.modeVersion,
2024
+ recapAfterSeconds: result.recapAfterSeconds,
1687
2025
  serverTimestamp,
1688
2026
  });
1689
2027
  });
@@ -1715,14 +2053,15 @@ export function createSessionsRoutes(deps) {
1715
2053
  if (helperSettings.error) {
1716
2054
  return c.json({ error: helperSettings.error }, 400);
1717
2055
  }
1718
- // Convert thinking option to SDK config
1719
- const { thinking, effort } = body.thinking
1720
- ? thinkingOptionToConfig(body.thinking)
1721
- : { thinking: undefined, effort: undefined };
2056
+ const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
2057
+ if ("error" in workstreamTarget) {
2058
+ return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
2059
+ }
2060
+ const { thinking, effort } = buildThinkingOptions(body);
1722
2061
  // Convert model option (undefined or "default" means use CLI default)
1723
2062
  const model = body.model && body.model !== "default" ? body.model : undefined;
1724
2063
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
1725
- const result = await deps.supervisor.createSession(project.path, body.mode, {
2064
+ const result = await deps.supervisor.createSession(workstreamTarget.projectPath, body.mode, {
1726
2065
  model,
1727
2066
  serviceTier,
1728
2067
  thinking,
@@ -1732,8 +2071,12 @@ export function createSessionsRoutes(deps) {
1732
2071
  globalInstructions: getGlobalInstructions(),
1733
2072
  permissions: body.permissions,
1734
2073
  recapMode: helperSettings.recapMode,
2074
+ recapAfterSeconds: helperSettings.recapAfterSeconds,
1735
2075
  promptSuggestionMode: helperSettings.promptSuggestionMode,
1736
2076
  helperSideModel: helperSettings.helperSideModel,
2077
+ }, {
2078
+ projectId: project.id,
2079
+ workstreamId: workstreamTarget.workstreamId,
1737
2080
  });
1738
2081
  // Check if queue is full
1739
2082
  if (isQueueFullResponse(result)) {
@@ -1743,13 +2086,14 @@ export function createSessionsRoutes(deps) {
1743
2086
  if (isQueuedResponse(result)) {
1744
2087
  return c.json({ ...result, serverTimestamp: Date.now() }, 202); // 202 Accepted - queued for processing
1745
2088
  }
1746
- await persistLaunchMetadata(result.sessionId, body.provider, executor);
2089
+ await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
1747
2090
  return c.json({
1748
2091
  sessionId: result.sessionId,
1749
2092
  processId: result.id,
1750
2093
  projectId: result.projectId,
1751
2094
  permissionMode: result.permissionMode,
1752
2095
  modeVersion: result.modeVersion,
2096
+ recapAfterSeconds: result.recapAfterSeconds,
1753
2097
  serverTimestamp: Date.now(),
1754
2098
  });
1755
2099
  });
@@ -1784,9 +2128,7 @@ export function createSessionsRoutes(deps) {
1784
2128
  tempId: body.tempId,
1785
2129
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
1786
2130
  };
1787
- const { thinking, effort } = body.thinking
1788
- ? thinkingOptionToConfig(body.thinking)
1789
- : { thinking: undefined, effort: undefined };
2131
+ const { thinking, effort } = buildThinkingOptions(body);
1790
2132
  const model = body.model && body.model !== "default" ? body.model : undefined;
1791
2133
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
1792
2134
  const result = await deps.supervisor.startSession(projectPath, userMessage, body.mode, {
@@ -1799,6 +2141,7 @@ export function createSessionsRoutes(deps) {
1799
2141
  globalInstructions: getGlobalInstructions(),
1800
2142
  permissions: body.permissions,
1801
2143
  recapMode: helperSettings.recapMode,
2144
+ recapAfterSeconds: helperSettings.recapAfterSeconds,
1802
2145
  promptSuggestionMode: helperSettings.promptSuggestionMode,
1803
2146
  helperSideModel: helperSettings.helperSideModel,
1804
2147
  });
@@ -1808,13 +2151,14 @@ export function createSessionsRoutes(deps) {
1808
2151
  if (isQueuedResponse(result)) {
1809
2152
  return c.json({ ...result, serverTimestamp: Date.now() }, 202);
1810
2153
  }
1811
- await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
2154
+ await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
1812
2155
  return c.json({
1813
2156
  sessionId: result.sessionId,
1814
2157
  processId: result.id,
1815
2158
  projectId: result.projectId,
1816
2159
  permissionMode: result.permissionMode,
1817
2160
  modeVersion: result.modeVersion,
2161
+ recapAfterSeconds: result.recapAfterSeconds,
1818
2162
  serverTimestamp,
1819
2163
  });
1820
2164
  });
@@ -1836,9 +2180,7 @@ export function createSessionsRoutes(deps) {
1836
2180
  return c.json({ error: helperSettings.error }, 400);
1837
2181
  }
1838
2182
  const projectPath = await ensureDetachedProjectPath(executor);
1839
- const { thinking, effort } = body.thinking
1840
- ? thinkingOptionToConfig(body.thinking)
1841
- : { thinking: undefined, effort: undefined };
2183
+ const { thinking, effort } = buildThinkingOptions(body);
1842
2184
  const model = body.model && body.model !== "default" ? body.model : undefined;
1843
2185
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
1844
2186
  const result = await deps.supervisor.createSession(projectPath, body.mode, {
@@ -1851,6 +2193,7 @@ export function createSessionsRoutes(deps) {
1851
2193
  globalInstructions: getGlobalInstructions(),
1852
2194
  permissions: body.permissions,
1853
2195
  recapMode: helperSettings.recapMode,
2196
+ recapAfterSeconds: helperSettings.recapAfterSeconds,
1854
2197
  promptSuggestionMode: helperSettings.promptSuggestionMode,
1855
2198
  helperSideModel: helperSettings.helperSideModel,
1856
2199
  });
@@ -1860,13 +2203,14 @@ export function createSessionsRoutes(deps) {
1860
2203
  if (isQueuedResponse(result)) {
1861
2204
  return c.json({ ...result, serverTimestamp: Date.now() }, 202);
1862
2205
  }
1863
- await persistLaunchMetadata(result.sessionId, body.provider, executor);
2206
+ await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
1864
2207
  return c.json({
1865
2208
  sessionId: result.sessionId,
1866
2209
  processId: result.id,
1867
2210
  projectId: result.projectId,
1868
2211
  permissionMode: result.permissionMode,
1869
2212
  modeVersion: result.modeVersion,
2213
+ recapAfterSeconds: result.recapAfterSeconds,
1870
2214
  serverTimestamp: Date.now(),
1871
2215
  });
1872
2216
  });
@@ -1901,6 +2245,11 @@ export function createSessionsRoutes(deps) {
1901
2245
  if (helperSettings.error) {
1902
2246
  return c.json({ error: helperSettings.error }, 400);
1903
2247
  }
2248
+ const parsedResumeMode = parseOptionalResumeMode(body.resumeMode);
2249
+ if (parsedResumeMode.error) {
2250
+ return c.json({ error: parsedResumeMode.error }, 400);
2251
+ }
2252
+ const resumeMode = parsedResumeMode.resumeMode ?? "full";
1904
2253
  const serverTimestamp = Date.now();
1905
2254
  const userMessage = {
1906
2255
  text: body.message,
@@ -1911,12 +2260,15 @@ export function createSessionsRoutes(deps) {
1911
2260
  tempId: body.tempId,
1912
2261
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
1913
2262
  };
1914
- // Convert thinking option to SDK config
1915
- const { thinking, effort } = body.thinking
1916
- ? thinkingOptionToConfig(body.thinking)
1917
- : { thinking: undefined, effort: undefined };
1918
- // Convert model option (undefined or "default" means use CLI default)
1919
- const model = body.model && body.model !== "default" ? body.model : undefined;
2263
+ const { thinking, effort } = buildThinkingOptions(body);
2264
+ // Convert model option (undefined or "default" means use CLI default). When
2265
+ // the client sends no model (e.g. resume after a server restart), recover the
2266
+ // YA model id persisted at launch so the process keeps its requested alias
2267
+ // and per-model settings stay keyed by it. See topics/provider-abstraction.md.
2268
+ const requestedModel = body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId);
2269
+ const model = requestedModel && requestedModel !== "default"
2270
+ ? requestedModel
2271
+ : undefined;
1920
2272
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
1921
2273
  // Use client-provided executor, falling back to saved executor from metadata.
1922
2274
  let executor = parsedBodyExecutor.executor;
@@ -1945,23 +2297,14 @@ export function createSessionsRoutes(deps) {
1945
2297
  await deps.sessionMetadataService.setExecutor(sessionId, executor);
1946
2298
  }
1947
2299
  }
1948
- const globalInstructions = deps.serverSettingsService?.getSetting("globalInstructions") || undefined;
2300
+ const globalInstructions = getGlobalInstructions();
1949
2301
  // Look up the session's original provider so we resume with the correct one
1950
2302
  // (e.g., claude-ollama sessions need the Ollama provider, not default Claude).
1951
2303
  // Check metadata first (explicitly saved on creation), then fall back to reader.
1952
2304
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
1953
2305
  let providerName = metadataProvider ?? body.provider;
1954
2306
  if (!providerName) {
1955
- const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, {
1956
- readerFactory: deps.readerFactory,
1957
- codexSessionsDir: deps.codexSessionsDir,
1958
- codexReaderFactory: deps.codexReaderFactory,
1959
- geminiSessionsDir: deps.geminiSessionsDir,
1960
- geminiReaderFactory: deps.geminiReaderFactory,
1961
- geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
1962
- grokSessionsDir: deps.grokSessionsDir,
1963
- grokReaderFactory: deps.grokReaderFactory,
1964
- }, metadataProvider ?? body.provider);
2307
+ const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider ?? body.provider, { readMode: "head" });
1965
2308
  const sessionSummary = sessionSummaryResult?.summary ?? null;
1966
2309
  providerName =
1967
2310
  sessionSummary?.provider ??
@@ -1969,6 +2312,20 @@ export function createSessionsRoutes(deps) {
1969
2312
  body.provider ??
1970
2313
  project.provider;
1971
2314
  }
2315
+ const previousProcess = deps.supervisor.getProcessForSession?.(sessionId);
2316
+ const resumeDiagnostics = {
2317
+ requestedMode: resumeMode,
2318
+ provider: providerName,
2319
+ previousProcess: previousProcess
2320
+ ? {
2321
+ processId: previousProcess.id,
2322
+ state: previousProcess.state.type,
2323
+ provider: previousProcess.provider,
2324
+ supportsDynamicCommands: previousProcess.supportsDynamicCommands,
2325
+ }
2326
+ : null,
2327
+ };
2328
+ let resumeSessionAt;
1972
2329
  if (isClaudeSdkProviderName(providerName)) {
1973
2330
  let blocker = null;
1974
2331
  try {
@@ -1983,7 +2340,21 @@ export function createSessionsRoutes(deps) {
1983
2340
  error: error instanceof Error ? error.message : String(error),
1984
2341
  }, "Failed to check Claude session for resume-blocking API error");
1985
2342
  }
1986
- if (blocker) {
2343
+ if (blocker?.resumeAtMessageId) {
2344
+ // Recoverable: resume up to the last good assistant message,
2345
+ // dropping the API-error tail, instead of forcing a handoff.
2346
+ resumeSessionAt = blocker.resumeAtMessageId;
2347
+ getLogger().info({
2348
+ event: "claude_resume_truncated_after_api_error",
2349
+ sessionId,
2350
+ projectId,
2351
+ providerName,
2352
+ messageId: blocker.messageId,
2353
+ apiErrorStatus: blocker.apiErrorStatus,
2354
+ resumeSessionAt,
2355
+ }, "Resuming Claude session before SDK API-error tail");
2356
+ }
2357
+ else if (blocker) {
1987
2358
  getLogger().warn({
1988
2359
  event: "claude_resume_blocked_after_api_error",
1989
2360
  sessionId,
@@ -1995,36 +2366,241 @@ export function createSessionsRoutes(deps) {
1995
2366
  return c.json({
1996
2367
  error: blocker.error,
1997
2368
  recovery: blocker.recovery,
2369
+ resume: {
2370
+ ...resumeDiagnostics,
2371
+ blockedReason: "claude-api-error-tail",
2372
+ },
1998
2373
  }, 409);
1999
2374
  }
2000
2375
  }
2001
- const result = await deps.supervisor.resumeSession(sessionId, project.path, userMessage, body.mode, {
2002
- model,
2003
- serviceTier,
2004
- thinking,
2005
- effort,
2006
- providerName,
2007
- executor,
2008
- globalInstructions,
2009
- permissions: body.permissions,
2010
- recapMode: helperSettings.recapMode,
2011
- promptSuggestionMode: helperSettings.promptSuggestionMode,
2012
- helperSideModel: helperSettings.helperSideModel,
2013
- });
2376
+ let result;
2377
+ try {
2378
+ result = await deps.supervisor.resumeSession(sessionId, project.path, userMessage, body.mode, {
2379
+ model,
2380
+ serviceTier,
2381
+ thinking,
2382
+ effort,
2383
+ providerName,
2384
+ executor,
2385
+ globalInstructions,
2386
+ permissions: body.permissions,
2387
+ recapMode: helperSettings.recapMode,
2388
+ recapAfterSeconds: helperSettings.recapAfterSeconds ??
2389
+ deps.sessionMetadataService?.getRecapAfterSeconds?.(sessionId),
2390
+ // Body value wins; otherwise recover the per-session preference from
2391
+ // metadata so a body-less resume does not default back to native.
2392
+ promptSuggestionMode: helperSettings.promptSuggestionMode ??
2393
+ deps.sessionMetadataService?.getPromptSuggestionMode?.(sessionId),
2394
+ helperSideModel: helperSettings.helperSideModel,
2395
+ resumeMode,
2396
+ resumeSessionAt,
2397
+ });
2398
+ }
2399
+ catch (error) {
2400
+ if (error instanceof ResumeCompactionError) {
2401
+ getLogger().warn({
2402
+ event: "resume_compaction_failed",
2403
+ sessionId,
2404
+ projectId,
2405
+ providerName,
2406
+ attempt: error.attempt,
2407
+ }, "Compact-first resume failed");
2408
+ return c.json({
2409
+ error: error.message,
2410
+ recovery: error.recovery,
2411
+ resume: {
2412
+ ...resumeDiagnostics,
2413
+ compaction: error.attempt,
2414
+ },
2415
+ }, 409);
2416
+ }
2417
+ throw error;
2418
+ }
2014
2419
  // Check if queue is full
2015
2420
  if (isQueueFullResponse(result)) {
2016
2421
  return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
2017
2422
  }
2018
2423
  // Check if request was queued
2019
2424
  if (isQueuedResponse(result)) {
2020
- return c.json({ ...result, serverTimestamp: Date.now() }, 202); // 202 Accepted - queued for processing
2425
+ return c.json({
2426
+ ...result,
2427
+ serverTimestamp: Date.now(),
2428
+ resume: { ...resumeDiagnostics, outcome: "queued" },
2429
+ }, 202); // 202 Accepted - queued for processing
2021
2430
  }
2022
2431
  return c.json({
2023
2432
  processId: result.id,
2024
2433
  permissionMode: result.permissionMode,
2025
2434
  modeVersion: result.modeVersion,
2435
+ recapAfterSeconds: result.recapAfterSeconds,
2026
2436
  serverTimestamp,
2437
+ resume: {
2438
+ ...resumeDiagnostics,
2439
+ outcome: "started",
2440
+ compaction: resumeMode === "compact-first" ? { status: "completed" } : undefined,
2441
+ },
2442
+ });
2443
+ });
2444
+ // POST /api/projects/:projectId/sessions/:sessionId/reactivate
2445
+ // Spawn a live harness process bound to the session WITHOUT delivering a turn,
2446
+ // so the client can read live process state (model options) before messaging.
2447
+ // Idempotent: returns the existing process if the session is already owned.
2448
+ routes.post("/projects/:projectId/sessions/:sessionId/reactivate", async (c) => {
2449
+ const projectId = c.req.param("projectId");
2450
+ const sessionId = c.req.param("sessionId");
2451
+ if (!isUrlProjectId(projectId)) {
2452
+ return c.json({ error: "Invalid project ID format" }, 400);
2453
+ }
2454
+ const project = await deps.scanner.getOrCreateProject(projectId);
2455
+ if (!project) {
2456
+ return c.json({ error: "Project not found or path does not exist" }, 404);
2457
+ }
2458
+ // Already owned by a live process - return it (idempotent).
2459
+ const existing = deps.supervisor.getProcessForSession(sessionId);
2460
+ if (existing) {
2461
+ return c.json({
2462
+ processId: existing.id,
2463
+ permissionMode: existing.permissionMode,
2464
+ modeVersion: existing.modeVersion,
2465
+ recapAfterSeconds: existing.recapAfterSeconds,
2466
+ serverTimestamp: Date.now(),
2467
+ });
2468
+ }
2469
+ // Body is optional - allows overriding mode/model/executor.
2470
+ let body = {};
2471
+ try {
2472
+ body = await c.req.json();
2473
+ }
2474
+ catch {
2475
+ // No body - resume with the session's saved settings.
2476
+ }
2477
+ const parsedBodyExecutor = parseOptionalExecutor(body.executor);
2478
+ if (parsedBodyExecutor.error) {
2479
+ return c.json({ error: parsedBodyExecutor.error }, 400);
2480
+ }
2481
+ const helperSettings = parseHelperSettings(body);
2482
+ if (helperSettings.error) {
2483
+ return c.json({ error: helperSettings.error }, 400);
2484
+ }
2485
+ // Resolve provider/model/executor from the YA launch record (persisted on
2486
+ // launch) so reactivation resumes with the correct backend and model.
2487
+ const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
2488
+ const providerName = metadata?.provider ??
2489
+ body.provider ??
2490
+ project.provider;
2491
+ const executor = parsedBodyExecutor.executor ?? metadata?.executor;
2492
+ // Prefer an explicit override, else the YA id the session was launched
2493
+ // with; "default" means let the backend pick (pass undefined).
2494
+ const rawModel = body.model && body.model !== "default"
2495
+ ? body.model
2496
+ : metadata?.requestedModel;
2497
+ const model = rawModel && rawModel !== "default" ? rawModel : undefined;
2498
+ const { thinking, effort } = buildThinkingOptions(body);
2499
+ let process;
2500
+ try {
2501
+ process = await deps.supervisor.reactivateSession(project.path, sessionId, body.mode, {
2502
+ model,
2503
+ thinking,
2504
+ effort,
2505
+ providerName,
2506
+ executor,
2507
+ globalInstructions: getGlobalInstructions(),
2508
+ recapAfterSeconds: helperSettings.recapAfterSeconds ?? metadata?.recapAfterSeconds,
2509
+ recapMode: helperSettings.recapMode ?? metadata?.recapMode,
2510
+ });
2511
+ }
2512
+ catch (error) {
2513
+ return c.json({
2514
+ error: error instanceof Error
2515
+ ? error.message
2516
+ : "Failed to reactivate session",
2517
+ }, 503);
2518
+ }
2519
+ // Keep the launch record current (provider/executor) for this session.
2520
+ await persistLaunchMetadata(sessionId, providerName, executor);
2521
+ return c.json({
2522
+ processId: process.id,
2523
+ permissionMode: process.permissionMode,
2524
+ modeVersion: process.modeVersion,
2525
+ recapAfterSeconds: process.recapAfterSeconds,
2526
+ serverTimestamp: Date.now(),
2527
+ });
2528
+ });
2529
+ // POST /api/projects/:projectId/sessions/:sessionId/recap
2530
+ // Away-recap trigger, keyed by session (not process) so it survives a server
2531
+ // restart that killed the process. A live process recaps directly in its own
2532
+ // mode; a cold fork-mode session is revived — without ever preempting a live
2533
+ // worker — and recapped from its on-disk transcript. See topics/fork-recap.md.
2534
+ routes.post("/projects/:projectId/sessions/:sessionId/recap", async (c) => {
2535
+ const projectId = c.req.param("projectId");
2536
+ const sessionId = c.req.param("sessionId");
2537
+ if (!isUrlProjectId(projectId)) {
2538
+ return c.json({ error: "Invalid project ID format" }, 400);
2539
+ }
2540
+ let sinceMs = null;
2541
+ try {
2542
+ const body = await c.req.json();
2543
+ if (typeof body.hiddenSinceMs === "number" &&
2544
+ Number.isFinite(body.hiddenSinceMs)) {
2545
+ sinceMs = body.hiddenSinceMs;
2546
+ }
2547
+ }
2548
+ catch {
2549
+ // Empty body is accepted.
2550
+ }
2551
+ // Live process: recap directly in whatever mode it runs.
2552
+ const live = deps.supervisor.getProcessForSession(sessionId);
2553
+ if (live && !live.isTerminated) {
2554
+ const result = await deps.supervisor.requestRecap(live.id, { sinceMs });
2555
+ return c.json(result);
2556
+ }
2557
+ // Cold session: only fork mode can recap from the on-disk transcript —
2558
+ // side-session/native recaps need in-memory recent text a revived process
2559
+ // lacks. Other modes simply skip until the session is live again.
2560
+ const recapMode = deps.sessionMetadataService?.getRecapMode(sessionId);
2561
+ if (recapMode !== "fork") {
2562
+ return c.json({
2563
+ supported: true,
2564
+ emitted: false,
2565
+ reason: "recap requires a live process for this recap mode",
2566
+ });
2567
+ }
2568
+ const project = await deps.scanner.getOrCreateProject(projectId);
2569
+ if (!project) {
2570
+ return c.json({ error: "Project not found or path does not exist" }, 404);
2571
+ }
2572
+ const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
2573
+ const providerName = metadata?.provider ?? project.provider;
2574
+ const rawModel = metadata?.requestedModel;
2575
+ const model = rawModel && rawModel !== "default" ? rawModel : undefined;
2576
+ let process;
2577
+ try {
2578
+ process = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
2579
+ model,
2580
+ providerName,
2581
+ executor: metadata?.executor,
2582
+ globalInstructions: getGlobalInstructions(),
2583
+ recapAfterSeconds: metadata?.recapAfterSeconds,
2584
+ recapMode: "fork",
2585
+ }, { preempt: false });
2586
+ }
2587
+ catch (error) {
2588
+ // At capacity with no idle worker to drop (background recaps never
2589
+ // preempt), or reactivation failed — skip the recap rather than error.
2590
+ const atCapacity = error instanceof Error && error.message.includes("worker capacity");
2591
+ return c.json({
2592
+ supported: true,
2593
+ emitted: false,
2594
+ reason: atCapacity
2595
+ ? "recap skipped: at worker capacity"
2596
+ : "recap skipped: session could not be revived",
2597
+ });
2598
+ }
2599
+ const result = await deps.supervisor.requestRecap(process.id, {
2600
+ sinceMs,
2601
+ revived: true,
2027
2602
  });
2603
+ return c.json(result);
2028
2604
  });
2029
2605
  // POST /api/projects/:projectId/sessions/:sessionId/restart
2030
2606
  // Start a fresh session from a bounded handoff, then terminate the old YA-owned process.
@@ -2061,10 +2637,18 @@ export function createSessionsRoutes(deps) {
2061
2637
  }
2062
2638
  executor = parsedSavedExecutor.executor;
2063
2639
  }
2640
+ if (body.restartMode !== undefined &&
2641
+ body.restartMode !== "handoff" &&
2642
+ body.restartMode !== "fork") {
2643
+ return c.json({ error: 'restartMode must be "handoff" or "fork"' }, 400);
2644
+ }
2645
+ const restartMode = body.restartMode ?? "handoff";
2064
2646
  const oldProcess = deps.supervisor.getProcessForSession(sessionId);
2065
2647
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
2066
2648
  const preferredSourceProvider = metadataProvider ?? oldProcess?.provider ?? project.provider;
2067
- const compactAttempt = await tryRestartCompact(oldProcess);
2649
+ // Fork copies the transcript as-is; pre-restart compaction is a
2650
+ // handoff-only mitigation.
2651
+ const compactAttempt = restartMode === "fork" ? undefined : await tryRestartCompact(oldProcess);
2068
2652
  const oldProcessInterrupted = await interruptOldProcessForHandoff(oldProcess);
2069
2653
  const sourceSession = await loadRestartSourceSession(project, sessionId, projectId, preferredSourceProvider, oldProcess);
2070
2654
  if (!sourceSession) {
@@ -2073,6 +2657,103 @@ export function createSessionsRoutes(deps) {
2073
2657
  const sourceProvider = sourceSession.provider ?? preferredSourceProvider;
2074
2658
  const providerName = body.provider ?? sourceProvider;
2075
2659
  const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
2660
+ const { thinking, effort } = buildThinkingOptions(body);
2661
+ const model = body.model && body.model !== "default" ? body.model : undefined;
2662
+ const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
2663
+ if (restartMode === "fork") {
2664
+ if (body.provider && body.provider !== sourceProvider) {
2665
+ return c.json({
2666
+ error: "Fork keeps the source provider; omit provider or match the source session",
2667
+ }, 400);
2668
+ }
2669
+ if (!deps.supervisor.supportsForkSession(sourceProvider)) {
2670
+ return c.json({ error: `${sourceProvider} does not support transcript fork` }, 400);
2671
+ }
2672
+ const forkTitle = deriveForkTitle({
2673
+ preferredTitle: originalMetadata?.customTitle,
2674
+ sourceSession,
2675
+ });
2676
+ let fork;
2677
+ try {
2678
+ fork = await deps.supervisor.forkSession({
2679
+ sessionId,
2680
+ projectPath: project.path,
2681
+ providerName: sourceProvider,
2682
+ upToMessageId: body.forkUpToMessageId,
2683
+ title: forkTitle,
2684
+ });
2685
+ }
2686
+ catch (error) {
2687
+ getLogger().warn({
2688
+ event: "restart_fork_failed",
2689
+ sessionId,
2690
+ projectId,
2691
+ sourceProvider,
2692
+ upToMessageId: body.forkUpToMessageId,
2693
+ error: error instanceof Error ? error.message : String(error),
2694
+ }, "Transcript fork failed");
2695
+ return c.json({
2696
+ error: error instanceof Error ? error.message : "Transcript fork failed",
2697
+ }, 500);
2698
+ }
2699
+ const forkMessage = body.reason?.trim() || "Continue from this fork point.";
2700
+ const result = await deps.supervisor.resumeSession(fork.sessionId, project.path, { text: forkMessage, mode: body.mode }, body.mode, {
2701
+ model,
2702
+ serviceTier,
2703
+ thinking,
2704
+ effort,
2705
+ providerName: sourceProvider,
2706
+ executor,
2707
+ globalInstructions: getGlobalInstructions(),
2708
+ permissions: body.permissions,
2709
+ recapMode: helperSettings.recapMode,
2710
+ recapAfterSeconds: helperSettings.recapAfterSeconds ??
2711
+ originalMetadata?.recapAfterSeconds,
2712
+ // Inherit the source session's preference unless the body overrides.
2713
+ promptSuggestionMode: helperSettings.promptSuggestionMode ??
2714
+ originalMetadata?.promptSuggestionMode,
2715
+ helperSideModel: helperSettings.helperSideModel,
2716
+ });
2717
+ if (isQueueFullResponse(result)) {
2718
+ return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
2719
+ }
2720
+ if (isQueuedResponse(result)) {
2721
+ deps.supervisor.cancelQueuedRequest(result.queueId);
2722
+ return c.json({
2723
+ error: "Fork could not start immediately; old process was left running",
2724
+ }, 503);
2725
+ }
2726
+ await persistLaunchMetadata(result.sessionId, sourceProvider, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
2727
+ if (deps.sessionMetadataService) {
2728
+ await deps.sessionMetadataService.updateMetadata(result.sessionId, {
2729
+ title: forkTitle,
2730
+ });
2731
+ deps.eventBus?.emit({
2732
+ type: "session-metadata-changed",
2733
+ sessionId: result.sessionId,
2734
+ title: forkTitle,
2735
+ timestamp: new Date().toISOString(),
2736
+ });
2737
+ }
2738
+ const oldProcessAbortDeferred = abortOldProcessAfterReplacementActivity(oldProcess, result);
2739
+ return c.json({
2740
+ sessionId: result.sessionId,
2741
+ processId: result.id,
2742
+ projectId: result.projectId,
2743
+ provider: result.provider,
2744
+ model: result.resolvedModel ?? result.model,
2745
+ title: forkTitle,
2746
+ permissionMode: result.permissionMode,
2747
+ modeVersion: result.modeVersion,
2748
+ recapAfterSeconds: result.recapAfterSeconds,
2749
+ restartedFrom: sessionId,
2750
+ forkUpToMessageId: body.forkUpToMessageId,
2751
+ oldProcessId: oldProcess?.id,
2752
+ oldProcessInterrupted,
2753
+ oldProcessAbortDeferred,
2754
+ oldProcessAborted: false,
2755
+ });
2756
+ }
2076
2757
  const handoffTitle = deriveRestartTitle({
2077
2758
  preferredTitle: originalMetadata?.customTitle,
2078
2759
  sourceSession,
@@ -2090,11 +2771,6 @@ export function createSessionsRoutes(deps) {
2090
2771
  omittedCount,
2091
2772
  transcript,
2092
2773
  });
2093
- const { thinking, effort } = body.thinking
2094
- ? thinkingOptionToConfig(body.thinking)
2095
- : { thinking: undefined, effort: undefined };
2096
- const model = body.model && body.model !== "default" ? body.model : undefined;
2097
- const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
2098
2774
  const result = await deps.supervisor.startSession(project.path, {
2099
2775
  text: handoff,
2100
2776
  mode: body.mode,
@@ -2109,7 +2785,11 @@ export function createSessionsRoutes(deps) {
2109
2785
  globalInstructions: getGlobalInstructions(),
2110
2786
  permissions: body.permissions,
2111
2787
  recapMode: helperSettings.recapMode,
2112
- promptSuggestionMode: helperSettings.promptSuggestionMode,
2788
+ recapAfterSeconds: helperSettings.recapAfterSeconds ??
2789
+ originalMetadata?.recapAfterSeconds,
2790
+ // Inherit the source session's preference unless the body overrides.
2791
+ promptSuggestionMode: helperSettings.promptSuggestionMode ??
2792
+ originalMetadata?.promptSuggestionMode,
2113
2793
  helperSideModel: helperSettings.helperSideModel,
2114
2794
  });
2115
2795
  if (isQueueFullResponse(result)) {
@@ -2121,7 +2801,7 @@ export function createSessionsRoutes(deps) {
2121
2801
  error: "Restart could not start immediately; old process was left running",
2122
2802
  }, 503);
2123
2803
  }
2124
- await persistLaunchMetadata(result.sessionId, providerName, executor);
2804
+ await persistLaunchMetadata(result.sessionId, providerName, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
2125
2805
  if (deps.sessionMetadataService) {
2126
2806
  await deps.sessionMetadataService.updateMetadata(result.sessionId, {
2127
2807
  title: handoffTitle,
@@ -2143,6 +2823,7 @@ export function createSessionsRoutes(deps) {
2143
2823
  title: handoffTitle,
2144
2824
  permissionMode: result.permissionMode,
2145
2825
  modeVersion: result.modeVersion,
2826
+ recapAfterSeconds: result.recapAfterSeconds,
2146
2827
  restartedFrom: sessionId,
2147
2828
  oldProcessId: oldProcess?.id,
2148
2829
  oldProcessInterrupted,
@@ -2150,54 +2831,628 @@ export function createSessionsRoutes(deps) {
2150
2831
  oldProcessAborted: false,
2151
2832
  });
2152
2833
  });
2153
- // POST /api/sessions/:sessionId/messages - Queue message
2154
- routes.post("/sessions/:sessionId/messages", async (c) => {
2834
+ // POST /api/projects/:projectId/sessions/:sessionId/fork
2835
+ // Fork the provider transcript into a new resumable session without
2836
+ // starting a process or sending any message ("fork from here" / rewind).
2837
+ // The forked session opens cold; the next user send resumes it normally.
2838
+ routes.post("/projects/:projectId/sessions/:sessionId/fork", async (c) => {
2839
+ const projectId = c.req.param("projectId");
2155
2840
  const sessionId = c.req.param("sessionId");
2156
- const process = deps.supervisor.getProcessForSession(sessionId);
2157
- if (!process) {
2158
- return c.json({ error: "No active process for session" }, 404);
2841
+ if (!isUrlProjectId(projectId)) {
2842
+ return c.json({ error: "Invalid project ID format" }, 400);
2159
2843
  }
2160
- let body;
2844
+ const project = await deps.scanner.getOrCreateProject(projectId);
2845
+ if (!project) {
2846
+ return c.json({ error: "Project not found or path does not exist" }, 404);
2847
+ }
2848
+ let body = {};
2161
2849
  try {
2162
2850
  body = await c.req.json();
2163
2851
  }
2164
2852
  catch {
2165
- return c.json({ error: "Invalid JSON body" }, 400);
2853
+ // Body is optional; full-transcript fork.
2166
2854
  }
2167
- if (!body.message) {
2168
- return c.json({ error: "Message is required" }, 400);
2855
+ const upToMessageId = typeof body.upToMessageId === "string" && body.upToMessageId
2856
+ ? body.upToMessageId
2857
+ : undefined;
2858
+ const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
2859
+ const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
2860
+ const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), sourceProcess?.provider ?? metadataProvider, { readMode: "head" });
2861
+ const sessionSummary = sessionSummaryResult?.summary ?? null;
2862
+ const providerName = sourceProcess?.provider ??
2863
+ metadataProvider ??
2864
+ sessionSummary?.provider ??
2865
+ project.provider;
2866
+ if (!deps.supervisor.supportsForkSession(providerName)) {
2867
+ return c.json({ error: `${providerName} does not support transcript fork` }, 400);
2169
2868
  }
2170
- const serverTimestamp = Date.now();
2171
- const userMessage = {
2172
- text: body.message,
2173
- images: body.images,
2174
- documents: body.documents,
2175
- attachments: body.attachments,
2176
- mode: body.mode,
2177
- tempId: body.tempId,
2178
- metadata: buildUserMessageMetadata(body, serverTimestamp, body.deferred ? "deferred" : "direct"),
2179
- };
2180
- // Check if process is terminated
2181
- if (process.isTerminated) {
2182
- return c.json({
2183
- error: "Process terminated",
2184
- reason: process.terminationReason,
2185
- }, 410); // 410 Gone
2869
+ const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
2870
+ let baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle);
2871
+ if (!baseTitle) {
2872
+ try {
2873
+ const summary = await deps
2874
+ .readerFactory(project)
2875
+ .getSessionSummary(sessionId, projectId, { readMode: "head" });
2876
+ baseTitle = normalizeRestartTitleCandidate(summary?.title);
2877
+ }
2878
+ catch {
2879
+ // Title is cosmetic; fall through to the provider default.
2880
+ }
2186
2881
  }
2187
- const resolvedModel = body.model && body.model !== "default"
2188
- ? body.model
2189
- : process.resolvedModel ?? process.model;
2190
- const modelInfoService = deps.modelInfoService;
2191
- const resolveContextWindow = modelInfoService
2192
- ? (model, provider) => modelInfoService.getContextWindow(model, provider)
2193
- : getModelContextWindow;
2194
- let compactQueued = false;
2195
- if (!body.deferred && !body.thinking && body.model === undefined) {
2196
- const compactAttempt = await tryQueueTargetedAutoCompact({
2197
- process,
2198
- model: resolvedModel,
2199
- message: body.message,
2200
- resolveContextWindow,
2882
+ const forkTitle = baseTitle
2883
+ ? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
2884
+ : undefined;
2885
+ let fork;
2886
+ try {
2887
+ fork = await deps.supervisor.forkSession({
2888
+ sessionId,
2889
+ projectPath: project.path,
2890
+ providerName,
2891
+ upToMessageId,
2892
+ title: forkTitle,
2893
+ });
2894
+ }
2895
+ catch (error) {
2896
+ getLogger().warn({
2897
+ event: "session_fork_failed",
2898
+ sessionId,
2899
+ projectId,
2900
+ providerName,
2901
+ upToMessageId,
2902
+ error: error instanceof Error ? error.message : String(error),
2903
+ }, "Transcript fork failed");
2904
+ return c.json({
2905
+ error: error instanceof Error ? error.message : "Transcript fork failed",
2906
+ }, 500);
2907
+ }
2908
+ const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId)).executor;
2909
+ await persistLaunchMetadata(fork.sessionId, providerName, savedExecutor, undefined, deps.sessionMetadataService?.getRequestedModel(sessionId), originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
2910
+ if (deps.sessionMetadataService) {
2911
+ await deps.sessionMetadataService.updateMetadata(fork.sessionId, {
2912
+ title: forkTitle,
2913
+ parentSessionId: sessionId,
2914
+ });
2915
+ deps.eventBus?.emit({
2916
+ type: "session-metadata-changed",
2917
+ sessionId: fork.sessionId,
2918
+ title: forkTitle,
2919
+ parentSessionId: sessionId,
2920
+ timestamp: new Date().toISOString(),
2921
+ });
2922
+ }
2923
+ return c.json({
2924
+ sessionId: fork.sessionId,
2925
+ projectId,
2926
+ provider: providerName,
2927
+ title: forkTitle,
2928
+ forkedFrom: sessionId,
2929
+ upToMessageId,
2930
+ });
2931
+ });
2932
+ // POST /api/projects/:projectId/sessions/:sessionId/retitle
2933
+ // Generate a proposed session title through an archived helper fork. This
2934
+ // never updates the source session title; the client must explicitly accept
2935
+ // the returned proposal through the ordinary metadata update route.
2936
+ routes.post("/projects/:projectId/sessions/:sessionId/retitle", async (c) => {
2937
+ const projectId = c.req.param("projectId");
2938
+ const sessionId = c.req.param("sessionId");
2939
+ if (!isUrlProjectId(projectId)) {
2940
+ return c.json({ error: "Invalid project ID format" }, 400);
2941
+ }
2942
+ const project = await deps.scanner.getOrCreateProject(projectId);
2943
+ if (!project) {
2944
+ return c.json({ error: "Project not found or path does not exist" }, 404);
2945
+ }
2946
+ if (!deps.sessionMetadataService) {
2947
+ return c.json({ error: "Session metadata service not available" }, 503);
2948
+ }
2949
+ let body = {};
2950
+ try {
2951
+ body = await c.req.json();
2952
+ }
2953
+ catch {
2954
+ // Body is optional.
2955
+ }
2956
+ const currentTitle = typeof body.currentTitle === "string"
2957
+ ? body.currentTitle.trim().slice(0, 200)
2958
+ : undefined;
2959
+ let lengthTarget;
2960
+ if (body.lengthTarget !== undefined) {
2961
+ if (typeof body.lengthTarget !== "number" ||
2962
+ !Number.isInteger(body.lengthTarget) ||
2963
+ body.lengthTarget < 20 ||
2964
+ body.lengthTarget > 132) {
2965
+ return c.json({ error: "lengthTarget must be an integer between 20 and 132" }, 400);
2966
+ }
2967
+ lengthTarget = body.lengthTarget;
2968
+ }
2969
+ const metadataProvider = deps.sessionMetadataService.getProvider(sessionId);
2970
+ let sourceProcess = deps.supervisor.getProcessForSession(sessionId);
2971
+ const liveSourceProcess = sourceProcess && !sourceProcess.isTerminated ? sourceProcess : undefined;
2972
+ const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), liveSourceProcess?.provider ?? metadataProvider, { readMode: "head" });
2973
+ const sessionSummary = sessionSummaryResult?.summary ?? null;
2974
+ const providerName = liveSourceProcess?.provider ??
2975
+ metadataProvider ??
2976
+ sessionSummary?.provider ??
2977
+ sourceProcess?.provider ??
2978
+ project.provider;
2979
+ if (!deps.supervisor.supportsForkSession(providerName)) {
2980
+ return c.json({ error: `${providerName} does not support transcript fork` }, 400);
2981
+ }
2982
+ const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
2983
+ let requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
2984
+ liveSourceProcess?.model;
2985
+ const sourceMetadata = deps.sessionMetadataService.getMetadata(sessionId);
2986
+ const promptSuggestionMode = sourceMetadata?.promptSuggestionMode;
2987
+ const recapAfterSeconds = sourceMetadata?.recapAfterSeconds;
2988
+ const abortController = new AbortController();
2989
+ const abortFromRequest = () => abortController.abort();
2990
+ if (c.req.raw.signal.aborted) {
2991
+ abortController.abort();
2992
+ }
2993
+ else {
2994
+ c.req.raw.signal.addEventListener("abort", abortFromRequest, {
2995
+ once: true,
2996
+ });
2997
+ }
2998
+ let generatorSessionId;
2999
+ try {
3000
+ if (!liveSourceProcess) {
3001
+ sourceProcess = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
3002
+ model: requestedModel && requestedModel !== "default"
3003
+ ? requestedModel
3004
+ : undefined,
3005
+ providerName,
3006
+ executor: savedExecutor,
3007
+ globalInstructions: getGlobalInstructions(),
3008
+ promptSuggestionMode,
3009
+ recapAfterSeconds,
3010
+ });
3011
+ requestedModel = requestedModel ?? sourceProcess.model;
3012
+ }
3013
+ const generator = await deps.supervisor.forkSession({
3014
+ sessionId,
3015
+ projectPath: project.path,
3016
+ providerName,
3017
+ title: "Retitle generator",
3018
+ });
3019
+ generatorSessionId = generator.sessionId;
3020
+ await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Retitle generator", true);
3021
+ await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, promptSuggestionMode, recapAfterSeconds);
3022
+ if (abortController.signal.aborted) {
3023
+ throw new DOMException("Retitle cancelled", "AbortError");
3024
+ }
3025
+ const generated = await deps.supervisor.generateSummary(providerName, {
3026
+ purpose: "session-retitle",
3027
+ strategy: "fork",
3028
+ generatorSessionId: generator.sessionId,
3029
+ cwd: project.path,
3030
+ currentTitle,
3031
+ lengthTarget,
3032
+ signal: abortController.signal,
3033
+ });
3034
+ const title = generatedRetitleCandidate(generated.text);
3035
+ if (!title) {
3036
+ throw new Error("Retitle generation returned empty title");
3037
+ }
3038
+ return c.json({ title, generatorSessionId: generator.sessionId });
3039
+ }
3040
+ catch (error) {
3041
+ const cancelled = abortController.signal.aborted ||
3042
+ (error instanceof DOMException && error.name === "AbortError");
3043
+ const message = error instanceof Error ? error.message : "Retitle generation failed";
3044
+ getLogger().warn({
3045
+ event: cancelled
3046
+ ? "session_retitle_cancelled"
3047
+ : "session_retitle_failed",
3048
+ sessionId,
3049
+ projectId,
3050
+ providerName,
3051
+ generatorSessionId,
3052
+ error: message,
3053
+ }, cancelled ? "Session retitle cancelled" : "Session retitle failed");
3054
+ return c.json({ error: message }, cancelled ? 400 : 500);
3055
+ }
3056
+ finally {
3057
+ c.req.raw.signal.removeEventListener("abort", abortFromRequest);
3058
+ abortController.abort();
3059
+ }
3060
+ });
3061
+ // POST /api/projects/:projectId/sessions/:sessionId/fork-summary
3062
+ // Start a server-owned whole-context fork-after-summary job. The response
3063
+ // returns after durable job creation; generation continues independently of
3064
+ // the requesting client connection.
3065
+ routes.post("/projects/:projectId/sessions/:sessionId/fork-summary", async (c) => {
3066
+ const projectId = c.req.param("projectId");
3067
+ const sessionId = c.req.param("sessionId");
3068
+ if (!isUrlProjectId(projectId)) {
3069
+ return c.json({ error: "Invalid project ID format" }, 400);
3070
+ }
3071
+ const project = await deps.scanner.getOrCreateProject(projectId);
3072
+ if (!project) {
3073
+ return c.json({ error: "Project not found or path does not exist" }, 404);
3074
+ }
3075
+ let body;
3076
+ try {
3077
+ body = await c.req.json();
3078
+ }
3079
+ catch {
3080
+ return c.json({ error: "Invalid JSON body" }, 400);
3081
+ }
3082
+ const sourceMessageId = typeof body.sourceMessageId === "string" && body.sourceMessageId.trim()
3083
+ ? body.sourceMessageId.trim()
3084
+ : undefined;
3085
+ if (!sourceMessageId) {
3086
+ return c.json({ error: "sourceMessageId is required" }, 400);
3087
+ }
3088
+ if (body.mode !== undefined &&
3089
+ (typeof body.mode !== "string" ||
3090
+ !ALL_PERMISSION_MODES.includes(body.mode))) {
3091
+ return c.json({ error: "Invalid permission mode" }, 400);
3092
+ }
3093
+ if (body.autoOpenWhenReady !== undefined &&
3094
+ typeof body.autoOpenWhenReady !== "boolean") {
3095
+ return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
3096
+ }
3097
+ const mode = body.mode;
3098
+ const instructions = typeof body.instructions === "string" ? body.instructions : undefined;
3099
+ const autoOpenWhenReady = body.autoOpenWhenReady === true;
3100
+ const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
3101
+ const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
3102
+ const providerName = metadataProvider ?? sourceProcess?.provider ?? project.provider;
3103
+ if (!deps.supervisor.supportsForkSession(providerName)) {
3104
+ return c.json({ error: `${providerName} does not support transcript fork` }, 400);
3105
+ }
3106
+ if (!deps.sessionMetadataService) {
3107
+ return c.json({ error: "Session metadata service not available" }, 503);
3108
+ }
3109
+ if (activeForkSummaryJobs.has(sessionId)) {
3110
+ return c.json({ error: "A fork summary is already generating for this session" }, 409);
3111
+ }
3112
+ const sourceSession = sourceProcess
3113
+ ? {
3114
+ messages: sdkMessagesToClientMessages(sourceProcess.getMessageHistory()),
3115
+ }
3116
+ : await loadRestartSourceSession(project, sessionId, projectId, providerName);
3117
+ if (!sourceSession) {
3118
+ return c.json({ error: "Source session not found" }, 404);
3119
+ }
3120
+ const boundary = resolveForkAfterBoundary(sourceSession.messages, sourceMessageId, sourceProcess?.state.type === "in-turn" ||
3121
+ sourceProcess?.state.type === "waiting-input");
3122
+ if ("error" in boundary) {
3123
+ return c.json({ error: boundary.error }, boundary.status);
3124
+ }
3125
+ const displayObject = {
3126
+ id: randomUUID(),
3127
+ kind: "fork-summary",
3128
+ createdAt: new Date().toISOString(),
3129
+ placementAfterMessageId: boundary.placementAfterMessageId,
3130
+ sourceMessageId,
3131
+ retainedThroughMessageId: boundary.retainedThroughMessageId,
3132
+ status: "generating",
3133
+ autoOpenWhenReady: autoOpenWhenReady || undefined,
3134
+ };
3135
+ const abortController = new AbortController();
3136
+ activeForkSummaryJobs.set(sessionId, {
3137
+ objectId: displayObject.id,
3138
+ abortController,
3139
+ });
3140
+ try {
3141
+ await deps.sessionMetadataService.addTranscriptDisplayObject(sessionId, displayObject);
3142
+ }
3143
+ catch (error) {
3144
+ activeForkSummaryJobs.delete(sessionId);
3145
+ throw error;
3146
+ }
3147
+ emitTranscriptDisplayObjects(sessionId);
3148
+ const originalMetadata = deps.sessionMetadataService.getMetadata(sessionId);
3149
+ const baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle ?? sourceSession.title);
3150
+ const fallbackTitle = baseTitle
3151
+ ? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
3152
+ : undefined;
3153
+ const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
3154
+ const requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
3155
+ sourceProcess?.model;
3156
+ void (async () => {
3157
+ let generatorSessionId;
3158
+ let targetSessionId;
3159
+ let targetTitle;
3160
+ let targetProcessId;
3161
+ let completed = false;
3162
+ try {
3163
+ const generator = await deps.supervisor.forkSession({
3164
+ sessionId,
3165
+ projectPath: project.path,
3166
+ providerName,
3167
+ title: "Fork summary generator",
3168
+ });
3169
+ generatorSessionId = generator.sessionId;
3170
+ await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Fork summary generator", true);
3171
+ await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
3172
+ if (abortController.signal.aborted) {
3173
+ throw new DOMException("Fork summary cancelled", "AbortError");
3174
+ }
3175
+ const generated = await deps.supervisor.generateSummary(providerName, {
3176
+ purpose: "fork-after-summary",
3177
+ strategy: "fork",
3178
+ generatorSessionId: generator.sessionId,
3179
+ cwd: project.path,
3180
+ afterTurnMessageId: boundary.retainedThroughMessageId,
3181
+ afterTurnContext: boundary.retainedThroughContext,
3182
+ instructions,
3183
+ signal: abortController.signal,
3184
+ });
3185
+ if (abortController.signal.aborted) {
3186
+ throw new DOMException("Fork summary cancelled", "AbortError");
3187
+ }
3188
+ const title = forkSummaryTitle(generated.text, fallbackTitle);
3189
+ targetTitle = title;
3190
+ const target = await deps.supervisor.forkSession({
3191
+ sessionId,
3192
+ projectPath: project.path,
3193
+ providerName,
3194
+ upToMessageId: boundary.retainedThroughMessageId,
3195
+ title,
3196
+ });
3197
+ targetSessionId = target.sessionId;
3198
+ await updateForkSummaryChildMetadata(target.sessionId, sessionId, title, true);
3199
+ await persistLaunchMetadata(target.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
3200
+ if (abortController.signal.aborted) {
3201
+ throw new DOMException("Fork summary cancelled", "AbortError");
3202
+ }
3203
+ const result = await deps.supervisor.resumeSession(target.sessionId, project.path, { text: generated.text, mode }, mode, {
3204
+ providerName,
3205
+ executor: savedExecutor,
3206
+ globalInstructions: getGlobalInstructions(),
3207
+ model: requestedModel,
3208
+ promptSuggestionMode: originalMetadata?.promptSuggestionMode,
3209
+ recapAfterSeconds: originalMetadata?.recapAfterSeconds,
3210
+ });
3211
+ if (isQueueFullResponse(result)) {
3212
+ throw new Error("Queue is full");
3213
+ }
3214
+ if (isQueuedResponse(result)) {
3215
+ deps.supervisor.cancelQueuedRequest(result.queueId);
3216
+ throw new Error("Fork summary could not start immediately");
3217
+ }
3218
+ targetProcessId = result.id;
3219
+ if (abortController.signal.aborted) {
3220
+ await deps.supervisor.abortProcess(result.id);
3221
+ targetProcessId = undefined;
3222
+ throw new DOMException("Fork summary cancelled", "AbortError");
3223
+ }
3224
+ await persistLaunchMetadata(result.sessionId, providerName, savedExecutor, undefined, requestedModel, result.promptSuggestionMode, result.recapAfterSeconds);
3225
+ await updateForkSummaryChildMetadata(result.sessionId, sessionId, title, false);
3226
+ await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
3227
+ ...object,
3228
+ status: "ready",
3229
+ targetSessionId: result.sessionId,
3230
+ title,
3231
+ error: undefined,
3232
+ }));
3233
+ emitTranscriptDisplayObjects(sessionId);
3234
+ completed = true;
3235
+ }
3236
+ catch (error) {
3237
+ const cancelled = abortController.signal.aborted ||
3238
+ (error instanceof DOMException && error.name === "AbortError");
3239
+ const logCleanupFailure = (stage, cleanupError) => {
3240
+ getLogger().warn({
3241
+ event: "fork_after_summary_cleanup_failed",
3242
+ sessionId,
3243
+ projectId,
3244
+ providerName,
3245
+ stage,
3246
+ error: cleanupError instanceof Error
3247
+ ? cleanupError.message
3248
+ : String(cleanupError),
3249
+ }, "Fork-after-summary cleanup failed");
3250
+ };
3251
+ if (generatorSessionId) {
3252
+ try {
3253
+ await updateForkSummaryChildMetadata(generatorSessionId, sessionId, "Fork summary generator", true);
3254
+ }
3255
+ catch (cleanupError) {
3256
+ logCleanupFailure("archive-generator", cleanupError);
3257
+ }
3258
+ }
3259
+ if (targetSessionId && targetTitle) {
3260
+ try {
3261
+ await updateForkSummaryChildMetadata(targetSessionId, sessionId, targetTitle, true);
3262
+ }
3263
+ catch (cleanupError) {
3264
+ logCleanupFailure("archive-target", cleanupError);
3265
+ }
3266
+ }
3267
+ if (!completed && targetProcessId) {
3268
+ try {
3269
+ await deps.supervisor.abortProcess(targetProcessId);
3270
+ }
3271
+ catch (cleanupError) {
3272
+ logCleanupFailure("abort-target-process", cleanupError);
3273
+ }
3274
+ }
3275
+ if (!cancelled) {
3276
+ const message = error instanceof Error
3277
+ ? error.message
3278
+ : "Summary generation failed";
3279
+ getLogger().warn({
3280
+ event: "fork_after_summary_failed",
3281
+ sessionId,
3282
+ projectId,
3283
+ providerName,
3284
+ sourceMessageId,
3285
+ error: message,
3286
+ }, "Fork-after-summary job failed");
3287
+ try {
3288
+ await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
3289
+ ...object,
3290
+ status: "error",
3291
+ error: message,
3292
+ }));
3293
+ emitTranscriptDisplayObjects(sessionId);
3294
+ }
3295
+ catch (cleanupError) {
3296
+ logCleanupFailure("persist-error-state", cleanupError);
3297
+ }
3298
+ }
3299
+ }
3300
+ finally {
3301
+ const active = activeForkSummaryJobs.get(sessionId);
3302
+ if (active?.objectId === displayObject.id) {
3303
+ activeForkSummaryJobs.delete(sessionId);
3304
+ }
3305
+ }
3306
+ })().catch((error) => {
3307
+ getLogger().error({
3308
+ event: "fork_after_summary_unhandled_failure",
3309
+ sessionId,
3310
+ projectId,
3311
+ providerName,
3312
+ sourceMessageId,
3313
+ error: error instanceof Error ? error.message : String(error),
3314
+ }, "Fork-after-summary job escaped its guarded workflow");
3315
+ });
3316
+ return c.json({ displayObject }, 202);
3317
+ });
3318
+ routes.post("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId/cancel", async (c) => {
3319
+ const projectId = c.req.param("projectId");
3320
+ const sessionId = c.req.param("sessionId");
3321
+ const objectId = c.req.param("objectId");
3322
+ if (!isUrlProjectId(projectId)) {
3323
+ return c.json({ error: "Invalid project ID format" }, 400);
3324
+ }
3325
+ const active = activeForkSummaryJobs.get(sessionId);
3326
+ const object = deps.sessionMetadataService
3327
+ ?.getTranscriptDisplayObjects(sessionId)
3328
+ .find((candidate) => candidate.id === objectId);
3329
+ if (!object) {
3330
+ return c.json({ error: "Transcript display object not found" }, 404);
3331
+ }
3332
+ if (active?.objectId === objectId && object.status === "generating") {
3333
+ active.abortController.abort();
3334
+ }
3335
+ else if (object.status !== "error") {
3336
+ return c.json({ error: "Fork summary job is not active" }, 409);
3337
+ }
3338
+ await deps.sessionMetadataService?.removeTranscriptDisplayObject(sessionId, objectId);
3339
+ emitTranscriptDisplayObjects(sessionId);
3340
+ return c.json({
3341
+ transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
3342
+ [],
3343
+ });
3344
+ });
3345
+ routes.patch("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId", async (c) => {
3346
+ const projectId = c.req.param("projectId");
3347
+ const sessionId = c.req.param("sessionId");
3348
+ const objectId = c.req.param("objectId");
3349
+ if (!isUrlProjectId(projectId)) {
3350
+ return c.json({ error: "Invalid project ID format" }, 400);
3351
+ }
3352
+ if (!deps.sessionMetadataService) {
3353
+ return c.json({ error: "Session metadata service not available" }, 503);
3354
+ }
3355
+ let body;
3356
+ try {
3357
+ body = await c.req.json();
3358
+ }
3359
+ catch {
3360
+ return c.json({ error: "Invalid JSON body" }, 400);
3361
+ }
3362
+ const action = body.action === "opened" || body.action === "clicked"
3363
+ ? body.action
3364
+ : undefined;
3365
+ if (body.autoOpenWhenReady !== undefined &&
3366
+ typeof body.autoOpenWhenReady !== "boolean") {
3367
+ return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
3368
+ }
3369
+ if (body.action !== undefined && !action) {
3370
+ return c.json({ error: "action must be opened or clicked" }, 400);
3371
+ }
3372
+ const now = new Date().toISOString();
3373
+ const updated = await deps.sessionMetadataService.updateTranscriptDisplayObject(sessionId, objectId, (object) => ({
3374
+ ...object,
3375
+ ...(typeof body.autoOpenWhenReady === "boolean"
3376
+ ? { autoOpenWhenReady: body.autoOpenWhenReady || undefined }
3377
+ : {}),
3378
+ ...(action === "opened" ? { openedAt: now } : {}),
3379
+ ...(action === "clicked" ? { clickedAt: now } : {}),
3380
+ }));
3381
+ if (!updated) {
3382
+ return c.json({ error: "Transcript display object not found" }, 404);
3383
+ }
3384
+ emitTranscriptDisplayObjects(sessionId);
3385
+ return c.json({
3386
+ displayObject: updated,
3387
+ transcriptDisplayObjects: deps.sessionMetadataService.getTranscriptDisplayObjects(sessionId),
3388
+ });
3389
+ });
3390
+ // POST /api/sessions/:sessionId/messages - Queue message
3391
+ routes.post("/sessions/:sessionId/messages", async (c) => {
3392
+ const sessionId = c.req.param("sessionId");
3393
+ const process = deps.supervisor.getProcessForSession(sessionId);
3394
+ if (!process) {
3395
+ return c.json({ error: "No active process for session" }, 404);
3396
+ }
3397
+ let body;
3398
+ try {
3399
+ body = await c.req.json();
3400
+ }
3401
+ catch {
3402
+ return c.json({ error: "Invalid JSON body" }, 400);
3403
+ }
3404
+ if (!body.message) {
3405
+ return c.json({ error: "Message is required" }, 400);
3406
+ }
3407
+ const serverTimestamp = Date.now();
3408
+ const userMessage = {
3409
+ text: body.message,
3410
+ images: body.images,
3411
+ documents: body.documents,
3412
+ attachments: body.attachments,
3413
+ mode: body.mode,
3414
+ tempId: body.tempId,
3415
+ metadata: buildUserMessageMetadata(body, serverTimestamp, body.deferred ? "deferred" : "direct"),
3416
+ };
3417
+ // Check if process is terminated
3418
+ if (process.isTerminated) {
3419
+ return c.json({
3420
+ error: "Process terminated",
3421
+ reason: process.terminationReason,
3422
+ }, 410); // 410 Gone
3423
+ }
3424
+ // Provider-native slash commands (e.g. Codex `/compact`) are dispatched
3425
+ // through the provider's own protocol rather than delivered as turn text the
3426
+ // model would never interpret. Claude's `/compact` reports handled:false and
3427
+ // falls through to normal delivery so the SDK handles it as a regular turn
3428
+ // (and any trailing focus instructions reach the SDK verbatim). A deferred
3429
+ // submission keeps normal queue semantics.
3430
+ if (!body.deferred) {
3431
+ const parsed = parseSlashCommandSubmission(body.message);
3432
+ if (parsed) {
3433
+ const providerResult = await process.runProviderCommand(parsed.name, parsed.argument);
3434
+ if (providerResult.handled) {
3435
+ if (providerResult.error) {
3436
+ return c.json({ error: "Failed to run command", reason: providerResult.error }, 409);
3437
+ }
3438
+ return c.json({ queued: true, serverTimestamp });
3439
+ }
3440
+ }
3441
+ }
3442
+ const resolvedModel = body.model && body.model !== "default"
3443
+ ? body.model
3444
+ : (process.resolvedModel ?? process.model);
3445
+ const modelInfoService = deps.modelInfoService;
3446
+ const resolveContextWindow = modelInfoService
3447
+ ? (model, provider) => modelInfoService.getContextWindow(model, provider)
3448
+ : getModelContextWindow;
3449
+ let compactQueued = false;
3450
+ if (!body.deferred && !body.thinking && body.model === undefined) {
3451
+ const compactAttempt = await tryQueueTargetedAutoCompact({
3452
+ process,
3453
+ model: resolvedModel,
3454
+ message: body.message,
3455
+ resolveContextWindow,
2201
3456
  });
2202
3457
  compactQueued = compactAttempt.queued;
2203
3458
  }
@@ -2210,7 +3465,6 @@ export function createSessionsRoutes(deps) {
2210
3465
  await process.primeSupportedCommandsForMessage(userMessage);
2211
3466
  const deferredResult = process.deferMessage(userMessage, {
2212
3467
  promoteIfReady: true,
2213
- placement: parseDeferredPlacement(body),
2214
3468
  });
2215
3469
  if (!deferredResult.success) {
2216
3470
  return c.json({
@@ -2218,6 +3472,7 @@ export function createSessionsRoutes(deps) {
2218
3472
  reason: deferredResult.error,
2219
3473
  }, 410);
2220
3474
  }
3475
+ await process.waitForPatientQueuePersistenceIdle();
2221
3476
  return c.json({
2222
3477
  queued: true,
2223
3478
  deferred: deferredResult.deferred,
@@ -2227,10 +3482,7 @@ export function createSessionsRoutes(deps) {
2227
3482
  serverTimestamp,
2228
3483
  });
2229
3484
  }
2230
- // Convert thinking option to SDK config
2231
- const { thinking, effort } = body.thinking
2232
- ? thinkingOptionToConfig(body.thinking)
2233
- : { thinking: undefined, effort: undefined };
3485
+ const { thinking, effort } = buildThinkingOptions(body);
2234
3486
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
2235
3487
  const metadataExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId));
2236
3488
  if (metadataExecutor.error) {
@@ -2244,9 +3496,26 @@ export function createSessionsRoutes(deps) {
2244
3496
  ? body.model
2245
3497
  : (process.resolvedModel ?? process.model);
2246
3498
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
3499
+ // Per-model preemptive-compaction threshold (task 029). The route holds the
3500
+ // settings; the Supervisor stays settings-agnostic. Key strictly by the YA
3501
+ // model id: the alias the user picked this turn, else the live requested
3502
+ // alias, else the alias persisted at launch (survives restart), else the
3503
+ // provider's reported→YA-id helper for sessions YA didn't start. No family
3504
+ // fallback. See topics/provider-abstraction.md § Per-model settings keying.
3505
+ const yaModelId = body.model ??
3506
+ process.requestedModel ??
3507
+ deps.sessionMetadataService?.getRequestedModel(sessionId) ??
3508
+ getProvider(process.provider)?.yaModelIdForReported?.(process.resolvedModel);
3509
+ const compactAtContextPercent = resolveCompactPercent(deps.serverSettingsService?.getSetting("clientDefaults")
3510
+ ?.compactAtContextPercent, yaModelId);
3511
+ // Resolve the effective window here too — process.contextWindow is
3512
+ // unreliable (often undefined) and the base resolver ignores always-1M.
3513
+ const compactAtContextWindow = compactAtContextPercent === undefined
3514
+ ? undefined
3515
+ : resolveCompactWindow(process.provider, [yaModelId, process.resolvedModel, process.model], resolveContextWindow);
2247
3516
  // Use queueMessageToSession which handles thinking mode changes
2248
3517
  // If thinking mode changed, it will restart the process automatically
2249
- const queueGlobalInstructions = deps.serverSettingsService?.getSetting("globalInstructions") || undefined;
3518
+ const queueGlobalInstructions = getGlobalInstructions();
2250
3519
  const result = await deps.supervisor.queueMessageToSession(sessionId, process.projectPath, userMessage, body.mode, {
2251
3520
  model,
2252
3521
  serviceTier,
@@ -2259,6 +3528,8 @@ export function createSessionsRoutes(deps) {
2259
3528
  undefined,
2260
3529
  globalInstructions: queueGlobalInstructions,
2261
3530
  permissions: body.permissions,
3531
+ compactAtContextPercent,
3532
+ compactAtContextWindow,
2262
3533
  });
2263
3534
  if (!result.success) {
2264
3535
  return c.json({
@@ -2274,8 +3545,114 @@ export function createSessionsRoutes(deps) {
2274
3545
  serverTimestamp,
2275
3546
  });
2276
3547
  });
3548
+ // DELETE /api/sessions/:sessionId/recovered-queue/:queueId - Delete a
3549
+ // restart-paused patient queue entry by durable queue id.
3550
+ routes.delete("/sessions/:sessionId/recovered-queue/:queueId", async (c) => {
3551
+ const sessionId = c.req.param("sessionId");
3552
+ const queueId = c.req.param("queueId");
3553
+ const service = deps.sessionQueuePersistenceService;
3554
+ if (!service) {
3555
+ return c.json({ error: "Session queue persistence unavailable" }, 503);
3556
+ }
3557
+ const item = service
3558
+ .listSession(sessionId)
3559
+ .find((candidate) => candidate.id === queueId);
3560
+ if (item?.kind !== "patient" || item.status !== "paused-after-restart") {
3561
+ return c.json({ error: "Recovered queued message not found" }, 404);
3562
+ }
3563
+ await service.deleteItem(queueId);
3564
+ return c.json({
3565
+ deleted: true,
3566
+ deferredMessages: recoveredPatientQueueSummaries(deps, sessionId),
3567
+ });
3568
+ });
3569
+ const recoveredQueueDeps = {
3570
+ sessionQueuePersistenceService: deps.sessionQueuePersistenceService,
3571
+ sessionMetadataService: deps.sessionMetadataService,
3572
+ supervisor: deps.supervisor,
3573
+ getGlobalInstructions,
3574
+ persistLaunchMetadata,
3575
+ };
3576
+ // POST /api/sessions/:sessionId/recovered-queue/:queueId/resume - Move
3577
+ // restart-paused patient queue entries back into the live patient queue,
3578
+ // through the requested entry: resuming a non-head entry also resumes every
3579
+ // recovered entry before it, so compose order is preserved.
3580
+ routes.post("/sessions/:sessionId/recovered-queue/:queueId/resume", async (c) => {
3581
+ const sessionId = c.req.param("sessionId");
3582
+ const queueId = c.req.param("queueId");
3583
+ const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before resuming recovered work.");
3584
+ if (!resolved.ok) {
3585
+ return c.json(resolved.body, resolved.status);
3586
+ }
3587
+ const { process, group } = resolved;
3588
+ const { last, error } = await resumeRecoveredGroup(process, group, {
3589
+ promoteIfReady: true,
3590
+ });
3591
+ if (error || !last) {
3592
+ return c.json({
3593
+ error: "Failed to resume recovered queued message",
3594
+ reason: error,
3595
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
3596
+ }, 409);
3597
+ }
3598
+ await process.waitForPatientQueuePersistenceIdle();
3599
+ return c.json({
3600
+ resumed: true,
3601
+ resumedCount: group.length,
3602
+ deferred: last.deferred,
3603
+ promoted: last.promoted,
3604
+ position: last.position,
3605
+ processId: process.id,
3606
+ processState: reportableProcessState(process),
3607
+ permissionMode: process.permissionMode,
3608
+ modeVersion: process.modeVersion,
3609
+ recapAfterSeconds: process.recapAfterSeconds,
3610
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
3611
+ serverTimestamp: Date.now(),
3612
+ });
3613
+ });
3614
+ // POST /api/sessions/:sessionId/recovered-queue/:queueId/steer - Steer a
3615
+ // restart-paused patient queue entry into the session now, without waiting
3616
+ // for verified quiet: the entry and every recovered or live patient entry
3617
+ // composed before it rejoin the live queue and steer in compose order.
3618
+ routes.post("/sessions/:sessionId/recovered-queue/:queueId/steer", async (c) => {
3619
+ const sessionId = c.req.param("sessionId");
3620
+ const queueId = c.req.param("queueId");
3621
+ const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before steering recovered work.");
3622
+ if (!resolved.ok) {
3623
+ return c.json(resolved.body, resolved.status);
3624
+ }
3625
+ const { process, group } = resolved;
3626
+ const { lastMessage, error } = await resumeRecoveredGroup(process, group, { promoteIfReady: false });
3627
+ if (error || !lastMessage?.tempId) {
3628
+ return c.json({
3629
+ error: "Failed to steer recovered queued message",
3630
+ reason: error,
3631
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
3632
+ }, 409);
3633
+ }
3634
+ const steerResult = process.steerPatientDeferredMessagesThrough(lastMessage.tempId);
3635
+ if (!steerResult.success) {
3636
+ return c.json({
3637
+ error: steerResult.error ?? "Failed to steer recovered queued message",
3638
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
3639
+ }, 409);
3640
+ }
3641
+ await process.waitForPatientQueuePersistenceIdle();
3642
+ return c.json({
3643
+ steered: true,
3644
+ count: steerResult.steered,
3645
+ processId: process.id,
3646
+ processState: reportableProcessState(process),
3647
+ permissionMode: process.permissionMode,
3648
+ modeVersion: process.modeVersion,
3649
+ recapAfterSeconds: process.recapAfterSeconds,
3650
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
3651
+ serverTimestamp: Date.now(),
3652
+ });
3653
+ });
2277
3654
  // DELETE /api/sessions/:sessionId/deferred/:tempId - Cancel a deferred message
2278
- routes.delete("/sessions/:sessionId/deferred/:tempId", (c) => {
3655
+ routes.delete("/sessions/:sessionId/deferred/:tempId", async (c) => {
2279
3656
  const sessionId = c.req.param("sessionId");
2280
3657
  const tempId = c.req.param("tempId");
2281
3658
  const process = deps.supervisor.getProcessForSession(sessionId);
@@ -2286,40 +3663,45 @@ export function createSessionsRoutes(deps) {
2286
3663
  if (!cancelled) {
2287
3664
  return c.json({ error: "Deferred message not found" }, 404);
2288
3665
  }
3666
+ await process.waitForPatientQueuePersistenceIdle();
2289
3667
  return c.json({ cancelled: true });
2290
3668
  });
2291
- // POST /api/sessions/:sessionId/deferred/:tempId/edit - Take a deferred message for editing
2292
- routes.post("/sessions/:sessionId/deferred/:tempId/edit", (c) => {
3669
+ // DELETE /api/sessions/:sessionId/steering/:tempId - Cancel a sent steering
3670
+ // message that has not yet been consumed by the provider.
3671
+ routes.delete("/sessions/:sessionId/steering/:tempId", async (c) => {
2293
3672
  const sessionId = c.req.param("sessionId");
2294
3673
  const tempId = c.req.param("tempId");
2295
3674
  const process = deps.supervisor.getProcessForSession(sessionId);
2296
3675
  if (!process) {
2297
3676
  return c.json({ error: "No active process for session" }, 404);
2298
3677
  }
2299
- const taken = process.takeDeferredMessage(tempId);
2300
- if (!taken) {
2301
- return c.json({ error: "Deferred message not found" }, 404);
3678
+ const cancelled = process.cancelUnconfirmedSteerMessage(tempId);
3679
+ if (!cancelled) {
3680
+ return c.json({ error: "Steering message already acted on or not found" }, 404);
2302
3681
  }
2303
- return c.json({
2304
- message: taken.message.text,
2305
- tempId: taken.message.tempId,
2306
- mode: taken.message.mode,
2307
- attachments: taken.message.attachments,
2308
- placement: taken.placement,
2309
- });
3682
+ return c.json({ cancelled: true });
2310
3683
  });
2311
- // POST /api/sessions/:sessionId/deferred/:tempId/edit/release - Release a queued edit barrier
2312
- routes.post("/sessions/:sessionId/deferred/:tempId/edit/release", (c) => {
3684
+ // POST /api/sessions/:sessionId/deferred/:tempId/steer - Steer a patient
3685
+ // queued message, and every patient entry ahead of it, into the session now.
3686
+ routes.post("/sessions/:sessionId/deferred/:tempId/steer", async (c) => {
2313
3687
  const sessionId = c.req.param("sessionId");
2314
3688
  const tempId = c.req.param("tempId");
2315
3689
  const process = deps.supervisor.getProcessForSession(sessionId);
2316
3690
  if (!process) {
2317
3691
  return c.json({ error: "No active process for session" }, 404);
2318
3692
  }
2319
- const released = process.releaseDeferredEditBarrier(tempId);
3693
+ const result = process.steerPatientDeferredMessagesThrough(tempId);
3694
+ if (!result.success) {
3695
+ const status = result.error === "Deferred message not found" ? 404 : 409;
3696
+ return c.json({ error: result.error }, status);
3697
+ }
3698
+ await process.waitForPatientQueuePersistenceIdle();
2320
3699
  return c.json({
2321
- released,
2322
- deferredMessages: process.getDeferredQueueSummary(),
3700
+ steered: true,
3701
+ count: result.steered,
3702
+ // Include any still-paused recovered entries so the client's list does
3703
+ // not lose their chips when a live entry is steered.
3704
+ deferredMessages: sessionQueueSummaries(deps, sessionId, process),
2323
3705
  });
2324
3706
  });
2325
3707
  // PUT /api/sessions/:sessionId/mode - Update permission mode without sending a message
@@ -2387,32 +3769,65 @@ export function createSessionsRoutes(deps) {
2387
3769
  const requestBefore = process.getPendingInputRequest();
2388
3770
  const permissionModeBefore = process.permissionMode;
2389
3771
  if (process.state.type !== "waiting-input") {
2390
- try {
2391
- await appendApprovalAuditLog(deps.dataDir, {
2392
- timestamp: new Date().toISOString(),
2393
- sessionId,
2394
- processId: process.id,
2395
- provider: process.provider,
2396
- requestId: body.requestId,
2397
- request: requestBefore,
2398
- response: body.response,
2399
- normalizedResponse,
2400
- answers: body.answers,
2401
- feedback: body.feedback,
2402
- accepted: false,
2403
- failure: "No pending input request",
2404
- permissionModeBefore,
2405
- permissionModeAfter: process.permissionMode,
2406
- });
2407
- }
2408
- catch (error) {
2409
- console.warn("[approval-audit] Failed to append audit log:", error);
3772
+ if (isApprovalAuditLogEnabled(deps)) {
3773
+ try {
3774
+ await appendApprovalAuditLog(deps.dataDir, {
3775
+ timestamp: new Date().toISOString(),
3776
+ sessionId,
3777
+ processId: process.id,
3778
+ provider: process.provider,
3779
+ requestId: body.requestId,
3780
+ request: requestBefore,
3781
+ response: body.response,
3782
+ normalizedResponse,
3783
+ answers: body.answers,
3784
+ feedback: body.feedback,
3785
+ accepted: false,
3786
+ failure: "No pending input request",
3787
+ permissionModeBefore,
3788
+ permissionModeAfter: process.permissionMode,
3789
+ });
3790
+ }
3791
+ catch (error) {
3792
+ console.warn("[approval-audit] Failed to append audit log:", error);
3793
+ }
2410
3794
  }
2411
3795
  return c.json({ error: "No pending input request" }, 400);
2412
3796
  }
2413
3797
  // Call respondToInput which resolves the SDK's canUseTool promise
2414
3798
  const accepted = process.respondToInput(body.requestId, normalizedResponse, body.answers, body.feedback);
2415
3799
  if (!accepted) {
3800
+ if (isApprovalAuditLogEnabled(deps)) {
3801
+ try {
3802
+ await appendApprovalAuditLog(deps.dataDir, {
3803
+ timestamp: new Date().toISOString(),
3804
+ sessionId,
3805
+ processId: process.id,
3806
+ provider: process.provider,
3807
+ requestId: body.requestId,
3808
+ request: requestBefore,
3809
+ response: body.response,
3810
+ normalizedResponse,
3811
+ answers: body.answers,
3812
+ feedback: body.feedback,
3813
+ accepted: false,
3814
+ failure: "Invalid request ID or no pending request",
3815
+ permissionModeBefore,
3816
+ permissionModeAfter: process.permissionMode,
3817
+ });
3818
+ }
3819
+ catch (error) {
3820
+ console.warn("[approval-audit] Failed to append audit log:", error);
3821
+ }
3822
+ }
3823
+ return c.json({ error: "Invalid request ID or no pending request" }, 400);
3824
+ }
3825
+ // If approve_accept_edits, switch the permission mode
3826
+ if (isApproveAcceptEdits) {
3827
+ process.setPermissionMode("acceptEdits");
3828
+ }
3829
+ const pendingInputRequest = process.getPendingInputRequest();
3830
+ if (isApprovalAuditLogEnabled(deps)) {
2416
3831
  try {
2417
3832
  await appendApprovalAuditLog(deps.dataDir, {
2418
3833
  timestamp: new Date().toISOString(),
@@ -2425,8 +3840,7 @@ export function createSessionsRoutes(deps) {
2425
3840
  normalizedResponse,
2426
3841
  answers: body.answers,
2427
3842
  feedback: body.feedback,
2428
- accepted: false,
2429
- failure: "Invalid request ID or no pending request",
3843
+ accepted: true,
2430
3844
  permissionModeBefore,
2431
3845
  permissionModeAfter: process.permissionMode,
2432
3846
  });
@@ -2434,32 +3848,6 @@ export function createSessionsRoutes(deps) {
2434
3848
  catch (error) {
2435
3849
  console.warn("[approval-audit] Failed to append audit log:", error);
2436
3850
  }
2437
- return c.json({ error: "Invalid request ID or no pending request" }, 400);
2438
- }
2439
- // If approve_accept_edits, switch the permission mode
2440
- if (isApproveAcceptEdits) {
2441
- process.setPermissionMode("acceptEdits");
2442
- }
2443
- const pendingInputRequest = process.getPendingInputRequest();
2444
- try {
2445
- await appendApprovalAuditLog(deps.dataDir, {
2446
- timestamp: new Date().toISOString(),
2447
- sessionId,
2448
- processId: process.id,
2449
- provider: process.provider,
2450
- requestId: body.requestId,
2451
- request: requestBefore,
2452
- response: body.response,
2453
- normalizedResponse,
2454
- answers: body.answers,
2455
- feedback: body.feedback,
2456
- accepted: true,
2457
- permissionModeBefore,
2458
- permissionModeAfter: process.permissionMode,
2459
- });
2460
- }
2461
- catch (error) {
2462
- console.warn("[approval-audit] Failed to append audit log:", error);
2463
3851
  }
2464
3852
  return c.json({ accepted: true, pendingInputRequest });
2465
3853
  });
@@ -2534,101 +3922,27 @@ export function createSessionsRoutes(deps) {
2534
3922
  catch {
2535
3923
  return c.json({ error: "Invalid JSON body" }, 400);
2536
3924
  }
2537
- // At least one field must be provided
2538
- if (body.title === undefined &&
2539
- body.archived === undefined &&
2540
- body.starred === undefined &&
2541
- body.parentSessionId === undefined &&
2542
- body.heartbeatTurnsEnabled === undefined &&
2543
- body.heartbeatTurnsAfterMinutes === undefined &&
2544
- body.heartbeatTurnText === undefined &&
2545
- body.heartbeatForceAfterMinutes === undefined) {
2546
- return c.json({
2547
- error: "At least one session metadata field must be provided",
2548
- }, 400);
2549
- }
2550
- let heartbeatForceAfterMinutes;
2551
- if (body.heartbeatForceAfterMinutes !== undefined) {
2552
- if (body.heartbeatForceAfterMinutes === null ||
2553
- body.heartbeatForceAfterMinutes === 0) {
2554
- heartbeatForceAfterMinutes = null;
2555
- }
2556
- else if (typeof body.heartbeatForceAfterMinutes === "number" &&
2557
- Number.isInteger(body.heartbeatForceAfterMinutes) &&
2558
- body.heartbeatForceAfterMinutes >= 1 &&
2559
- body.heartbeatForceAfterMinutes <= 1440) {
2560
- heartbeatForceAfterMinutes = body.heartbeatForceAfterMinutes;
2561
- }
2562
- else {
2563
- return c.json({
2564
- error: "heartbeatForceAfterMinutes must be null or an integer between 1 and 1440",
2565
- }, 400);
2566
- }
2567
- }
2568
- let heartbeatTurnsAfterMinutes;
2569
- if (body.heartbeatTurnsAfterMinutes !== undefined) {
2570
- if (body.heartbeatTurnsAfterMinutes === null ||
2571
- body.heartbeatTurnsAfterMinutes === 0) {
2572
- heartbeatTurnsAfterMinutes = null;
2573
- }
2574
- else if (typeof body.heartbeatTurnsAfterMinutes === "number" &&
2575
- Number.isInteger(body.heartbeatTurnsAfterMinutes) &&
2576
- body.heartbeatTurnsAfterMinutes >= 1 &&
2577
- body.heartbeatTurnsAfterMinutes <= 1440) {
2578
- heartbeatTurnsAfterMinutes = body.heartbeatTurnsAfterMinutes;
2579
- }
2580
- else {
2581
- return c.json({
2582
- error: "heartbeatTurnsAfterMinutes must be null or an integer between 1 and 1440",
2583
- }, 400);
2584
- }
3925
+ const parsed = parseSessionMetadataPatch(body);
3926
+ if (!parsed.ok) {
3927
+ return c.json({ error: parsed.error }, parsed.status);
2585
3928
  }
2586
- const heartbeatTurnText = body.heartbeatTurnText === undefined
2587
- ? undefined
2588
- : body.heartbeatTurnText === null || body.heartbeatTurnText === ""
2589
- ? null
2590
- : typeof body.heartbeatTurnText === "string"
2591
- ? body.heartbeatTurnText.slice(0, 200)
2592
- : null;
2593
- if (body.heartbeatTurnText !== undefined &&
2594
- body.heartbeatTurnText !== null &&
2595
- body.heartbeatTurnText !== "" &&
2596
- typeof body.heartbeatTurnText !== "string") {
2597
- return c.json({ error: "heartbeatTurnText must be a string or null" }, 400);
2598
- }
2599
- if (body.parentSessionId !== undefined &&
2600
- body.parentSessionId !== null &&
2601
- typeof body.parentSessionId !== "string") {
2602
- return c.json({ error: "parentSessionId must be a string or null" }, 400);
2603
- }
2604
- const parentSessionId = body.parentSessionId === undefined
2605
- ? undefined
2606
- : typeof body.parentSessionId === "string"
2607
- ? body.parentSessionId.trim() || null
2608
- : null;
2609
- await deps.sessionMetadataService.updateMetadata(sessionId, {
2610
- title: body.title,
2611
- archived: body.archived,
2612
- starred: body.starred,
2613
- parentSessionId,
2614
- heartbeatTurnsEnabled: body.heartbeatTurnsEnabled,
2615
- heartbeatTurnsAfterMinutes,
2616
- heartbeatTurnText,
2617
- heartbeatForceAfterMinutes,
2618
- });
3929
+ const { patch } = parsed;
3930
+ await deps.sessionMetadataService.updateMetadata(sessionId, patch);
2619
3931
  // Emit SSE event so sidebar and other clients can update
2620
3932
  if (deps.eventBus) {
2621
3933
  deps.eventBus.emit({
2622
3934
  type: "session-metadata-changed",
2623
3935
  sessionId,
2624
- title: body.title,
2625
- archived: body.archived,
2626
- starred: body.starred,
2627
- parentSessionId,
2628
- heartbeatTurnsEnabled: body.heartbeatTurnsEnabled,
2629
- heartbeatTurnsAfterMinutes,
2630
- heartbeatTurnText,
2631
- heartbeatForceAfterMinutes,
3936
+ title: patch.title,
3937
+ archived: patch.archived,
3938
+ starred: patch.starred,
3939
+ parentSessionId: patch.parentSessionId,
3940
+ heartbeatTurnsEnabled: patch.heartbeatTurnsEnabled,
3941
+ heartbeatTurnsAfterMinutes: patch.heartbeatTurnsAfterMinutes,
3942
+ heartbeatTurnText: patch.heartbeatTurnText,
3943
+ heartbeatForceAfterMinutes: patch.heartbeatForceAfterMinutes,
3944
+ promptSuggestionMode: patch.promptSuggestionMode ?? undefined,
3945
+ recapAfterSeconds: patch.recapAfterSeconds ?? undefined,
2632
3946
  timestamp: new Date().toISOString(),
2633
3947
  });
2634
3948
  }
@@ -2674,7 +3988,7 @@ export function createSessionsRoutes(deps) {
2674
3988
  }
2675
3989
  // Get original session to extract title for the clone
2676
3990
  const reader = deps.readerFactory(project);
2677
- let originalSession = await reader.getSessionSummary(sessionId, projectId);
3991
+ let originalSession = await reader.getSessionSummary(sessionId, projectId, { readMode: "head" });
2678
3992
  let cloneProvider = project.provider;
2679
3993
  let result;
2680
3994
  const shouldCloneFromCodex = isCodexProviderName(body.provider) ||
@@ -2691,7 +4005,9 @@ export function createSessionsRoutes(deps) {
2691
4005
  }
2692
4006
  originalSession =
2693
4007
  originalSession ??
2694
- (await codexReader.getSessionSummary(sessionId, projectId)) ??
4008
+ (await codexReader.getSessionSummary(sessionId, projectId, {
4009
+ readMode: "head",
4010
+ })) ??
2695
4011
  null;
2696
4012
  cloneProvider =
2697
4013
  originalSession?.provider ??
@@ -2734,36 +4050,6 @@ export function createSessionsRoutes(deps) {
2734
4050
  return c.json({ error: message }, 500);
2735
4051
  }
2736
4052
  });
2737
- // ============ Worker Queue Endpoints ============
2738
- // GET /api/status/workers - Get worker activity for safe restart indicator
2739
- routes.get("/status/workers", (c) => {
2740
- const activity = deps.supervisor.getWorkerActivity();
2741
- return c.json(activity);
2742
- });
2743
- // GET /api/queue - Get all queued requests
2744
- routes.get("/queue", (c) => {
2745
- const queue = deps.supervisor.getQueueInfo();
2746
- const poolStatus = deps.supervisor.getWorkerPoolStatus();
2747
- return c.json({ queue, ...poolStatus });
2748
- });
2749
- // GET /api/queue/:queueId - Get specific queue entry position
2750
- routes.get("/queue/:queueId", (c) => {
2751
- const queueId = c.req.param("queueId");
2752
- const position = deps.supervisor.getQueuePosition(queueId);
2753
- if (position === undefined) {
2754
- return c.json({ error: "Queue entry not found" }, 404);
2755
- }
2756
- return c.json({ queueId, position });
2757
- });
2758
- // DELETE /api/queue/:queueId - Cancel a queued request
2759
- routes.delete("/queue/:queueId", (c) => {
2760
- const queueId = c.req.param("queueId");
2761
- const cancelled = deps.supervisor.cancelQueuedRequest(queueId);
2762
- if (!cancelled) {
2763
- return c.json({ error: "Queue entry not found or already processed" }, 404);
2764
- }
2765
- return c.json({ cancelled: true });
2766
- });
2767
4053
  return routes;
2768
4054
  }
2769
4055
  //# sourceMappingURL=sessions.js.map