@openchambery/web 1.19.7-beta.8 → 1.19.7

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 (201) hide show
  1. package/dist/assets/AgentAvatar-CX0a1rKE.js +1 -0
  2. package/dist/assets/AssistantView-nVF0kdFh.js +2 -0
  3. package/dist/assets/DiagramView-B2UtQ8mw.js +1 -0
  4. package/dist/assets/FadeInOnReveal-CK4asqVR.js +1 -0
  5. package/dist/assets/Icon-BYM02QyU.js +1 -0
  6. package/dist/assets/MarkdownLoadingSkeleton-DWs4Jg7b.js +3 -0
  7. package/dist/assets/MarkdownRenderer-DT_GN1Ex.js +2 -0
  8. package/dist/assets/MarkdownRendererImpl-Cslh4XN_.js +7 -0
  9. package/dist/assets/MarkstreamRendererImpl-CsNwy1T5.css +1 -0
  10. package/dist/assets/MarkstreamRendererImpl-Ymm12x6J.js +1 -0
  11. package/dist/assets/MobileDetailNavigation-DD29ENgU.js +1 -0
  12. package/dist/assets/MobileShareBridge-CrTe_QND.js +4 -0
  13. package/dist/assets/MobileSurface-DjxchpUC.js +2 -0
  14. package/dist/assets/{MultiRunWindow-D_jo8cqz.js → MultiRunWindow-jG5dCEMH.js} +1 -1
  15. package/dist/assets/OnboardingScreen-C3-wzeSX.js +1 -0
  16. package/dist/assets/OverlayScrollbar-KUpvYHdv.js +2 -0
  17. package/dist/assets/SettingsView-UKlEewdq.js +25 -0
  18. package/dist/assets/{SettingsWindow-CzMoz2fi.js → SettingsWindow-DO1aAplT.js} +1 -1
  19. package/dist/assets/TerminalView-BG1UPLsz.js +8 -0
  20. package/dist/assets/ToolOutputDialog-CWr5eVTK.js +10 -0
  21. package/dist/assets/appThemeRegistry-BI5drVpC.js +1 -0
  22. package/dist/assets/{button-z0GOBtWi.js → button-C0cth9tl.js} +1 -1
  23. package/dist/assets/clipboard-DJ4nQEuY.js +1 -0
  24. package/dist/assets/{collapsible-D9zJRpeB.js → collapsible-DFT9cE80.js} +1 -1
  25. package/dist/assets/context-menu-CAp74NfV.js +1 -0
  26. package/dist/assets/desktopHosts-CWxex1c3.js +1 -0
  27. package/dist/assets/desktopRelayRestore-9EbBYQ9s.js +1 -0
  28. package/dist/assets/{diagram-CoEqmReY.js → diagram-BryzMuta.js} +1 -1
  29. package/dist/assets/{dialog-x2hirWBd.js → dialog-CgbV5ljg.js} +1 -1
  30. package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +1 -0
  31. package/dist/assets/{es-C8Sqjsia.js → es-BHqnSjoZ.js} +1 -1
  32. package/dist/assets/fileQueries-BY-9OWzp.js +1 -0
  33. package/dist/assets/fileReferenceActions-BJJtX_S5.js +1 -0
  34. package/dist/assets/{fr-p-Zwm_j2.js → fr-BfrlJY9i.js} +1 -1
  35. package/dist/assets/{gitApi-BE0vA4zm.js → gitApi-D9hFlE3T.js} +3 -3
  36. package/dist/assets/{input-BG3VWTIF.js → input-DFLpqjGR.js} +1 -1
  37. package/dist/assets/insertionBoundaries-D2L7D0Oe.js +4 -0
  38. package/dist/assets/{ja-B6REu1qP.js → ja-DP-7qt-x.js} +1 -1
  39. package/dist/assets/{ko-CJBNH9IZ.js → ko-E2B5vGQP.js} +1 -1
  40. package/dist/assets/main-BRedOs_V.js +169 -0
  41. package/dist/assets/main-DlXpJ0Vw.js +2 -0
  42. package/dist/assets/markdown-worker-BuveaH3O.js +1 -0
  43. package/dist/assets/markdownTheme-CzZm3riB.js +2 -0
  44. package/dist/assets/miniChat-CBLbFpfy.js +2 -0
  45. package/dist/assets/mobile-CoZsWdWk.js +2 -0
  46. package/dist/assets/multirun-BF-I0r2_.js +4 -0
  47. package/dist/assets/outsideFileGrants-DnplRxr-.js +1 -0
  48. package/dist/assets/path-utils-jGozTg9d.js +1 -0
  49. package/dist/assets/persistence-DdUac14I.js +3 -0
  50. package/dist/assets/{pl-B2cBI_7K.js → pl-r8X5gRM5.js} +1 -1
  51. package/dist/assets/platform-B4KKLnZ5.js +2 -0
  52. package/dist/assets/projectMeta-2t4L5_4a.js +3 -0
  53. package/dist/assets/{pt-BR-CrCc9hmt.js → pt-BR-DcpdXCf5.js} +1 -1
  54. package/dist/assets/renderElectronMiniChatApp-fl7O3iHy.js +2 -0
  55. package/dist/assets/renderMobileApp-CKYDwf4U.js +28 -0
  56. package/dist/assets/runtimeConfig-B4eb4ZiU.js +2 -0
  57. package/dist/assets/runtimeEndpointReset-BHuTYyzc.js +147 -0
  58. package/dist/assets/sessionChangeSummary-DOq8EN_H.js +1 -0
  59. package/dist/assets/sessionLookup-DNc0ZeR3.js +1 -0
  60. package/dist/assets/sortable-tabs-strip-C8_9kHqu.js +1 -0
  61. package/dist/assets/src-DasvfkEg.css +2 -0
  62. package/dist/assets/{store-CL3WP48T.js → store-BDFdnk6l.js} +2 -2
  63. package/dist/assets/terminalApi-947l7Ra3.js +1 -0
  64. package/dist/assets/textarea-Cgo_nWUU.js +1 -0
  65. package/dist/assets/toolHelpers-DEvVP9U0.js +2 -0
  66. package/dist/assets/{tooltip-CtH61mQh.js → tooltip-BifLJ8o0.js} +1 -1
  67. package/dist/assets/{uk-C8YKAz0x.js → uk-Di0vl9V-.js} +1 -1
  68. package/dist/assets/url-Pk6tQ1EZ.js +1 -0
  69. package/dist/assets/{ChatContainer-D3onaUkL.js → useAppFontEffects-Dle8xs5h.js} +44 -39
  70. package/dist/assets/useAssistantUIStore-me6s63b2.js +1 -0
  71. package/dist/assets/useDesktopWindowControlsLayout-_QAwKcQn.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-BSyuAgz6.js +1 -0
  73. package/dist/assets/useFontPreferences-BCOBlaAd.js +1 -0
  74. package/dist/assets/useMobileNavigationStore-f8xHJk7C.js +413 -0
  75. package/dist/assets/useSessionAutoCleanup-BHiXaW0J.js +1 -0
  76. package/dist/assets/useUIStore-CuaA8-Hp.js +1 -0
  77. package/dist/assets/useWorkerHighlightedLines-DuvXTSm_.js +7 -0
  78. package/dist/assets/utils-BiK3tBGM.js +1 -0
  79. package/dist/assets/{vendor-markstream-react-CwvbEg9R.js → vendor-markstream-react-C3K8YH0d.js} +4 -4
  80. package/dist/assets/{zh-CN-CCYwUnmB.js → zh-CN-coFCfeQE.js} +1 -1
  81. package/dist/assets/{zh-TW-D8Y6py08.js → zh-TW-oDRZ1-yb.js} +1 -1
  82. package/dist/index.html +9 -11
  83. package/dist/mini-chat.html +9 -11
  84. package/dist/mobile.html +15 -17
  85. package/package.json +2 -1
  86. package/server/index.js +2 -0
  87. package/server/lib/assistant-delivery-parts.js +3 -1
  88. package/server/lib/assistant-delivery-parts.test.js +11 -0
  89. package/server/lib/assistants/DOCUMENTATION.md +76 -3
  90. package/server/lib/assistants/assign.js +225 -0
  91. package/server/lib/assistants/assign.test.js +118 -0
  92. package/server/lib/assistants/bubbles.js +45 -0
  93. package/server/lib/assistants/bubbles.test.js +36 -0
  94. package/server/lib/assistants/cards.js +148 -0
  95. package/server/lib/assistants/cards.test.js +116 -0
  96. package/server/lib/assistants/contact-store.js +284 -0
  97. package/server/lib/assistants/contact-store.test.js +131 -0
  98. package/server/lib/assistants/contact-tools.js +761 -0
  99. package/server/lib/assistants/contact-tools.test.js +328 -0
  100. package/server/lib/assistants/contracts.js +20 -1
  101. package/server/lib/assistants/harness.js +551 -0
  102. package/server/lib/assistants/harness.test.js +607 -0
  103. package/server/lib/assistants/routes.js +44 -4
  104. package/server/lib/assistants/service.js +930 -17
  105. package/server/lib/assistants/service.test.js +1163 -45
  106. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  107. package/server/lib/event-stream/directory-ws-bridge.js +11 -2
  108. package/server/lib/event-stream/global-ws-bridge.js +19 -3
  109. package/server/lib/event-stream/protocol.js +11 -1
  110. package/server/lib/event-stream/reasoning-projection.js +425 -0
  111. package/server/lib/event-stream/reasoning-projection.test.js +300 -0
  112. package/server/lib/event-stream/runtime.js +10 -0
  113. package/server/lib/event-stream/runtime.test.js +53 -0
  114. package/server/lib/llm/DOCUMENTATION.md +84 -0
  115. package/server/lib/llm/catalog.js +100 -0
  116. package/server/lib/llm/catalog.test.js +78 -0
  117. package/server/lib/llm/completions.js +151 -0
  118. package/server/lib/llm/completions.test.js +127 -0
  119. package/server/lib/llm/generate.js +570 -0
  120. package/server/lib/llm/generate.test.js +459 -0
  121. package/server/lib/llm/routes.js +47 -0
  122. package/server/lib/llm/temp-directory.js +37 -0
  123. package/server/lib/notifications/APNS.md +19 -13
  124. package/server/lib/notifications/DOCUMENTATION.md +4 -3
  125. package/server/lib/notifications/runtime.js +153 -38
  126. package/server/lib/notifications/runtime.test.js +122 -0
  127. package/server/lib/opencode/DOCUMENTATION.md +2 -1
  128. package/server/lib/opencode/feature-routes-runtime.js +30 -0
  129. package/server/lib/opencode/feature-routes-runtime.test.js +20 -0
  130. package/server/lib/opencode/proxy.js +103 -9
  131. package/server/lib/opencode/proxy.test.js +177 -1
  132. package/server/lib/opencode/settings-runtime.js +2 -2
  133. package/server/lib/relay/reasoning-projection.e2e.test.ts +529 -0
  134. package/server/lib/session-goal/DOCUMENTATION.md +8 -1
  135. package/server/lib/session-goal/runtime.js +20 -0
  136. package/server/lib/session-index/DOCUMENTATION.md +3 -1
  137. package/server/lib/session-index/service.js +10 -4
  138. package/server/lib/session-index/service.test.js +37 -0
  139. package/server/lib/session-title/runtime.js +1 -0
  140. package/server/lib/session-title/runtime.test.js +11 -0
  141. package/server/lib/session-turn-pages/DOCUMENTATION.md +2 -1
  142. package/server/lib/session-turn-pages/routes.js +22 -3
  143. package/server/lib/session-turn-pages/routes.test.js +43 -0
  144. package/server/lib/transcript-cache/DOCUMENTATION.md +2 -2
  145. package/server/lib/transcript-cache/routes.js +9 -2
  146. package/server/lib/transcript-cache/routes.test.js +32 -0
  147. package/dist/assets/AgentAvatar-Di8F4QJh.js +0 -1
  148. package/dist/assets/AssistantView-DYdXsGDh.js +0 -1
  149. package/dist/assets/ContextPanel-Cj6RVUAe.js +0 -285
  150. package/dist/assets/DiagramView-BoRTG67Z.js +0 -1
  151. package/dist/assets/FadeInOnReveal-CV2s6YN8.js +0 -1
  152. package/dist/assets/Icon-CLpn1mIz.js +0 -1
  153. package/dist/assets/MarkdownRendererImpl-DwUKY9k4.js +0 -7
  154. package/dist/assets/MarkstreamRendererImpl-BRvprELo.js +0 -1
  155. package/dist/assets/MarkstreamRendererImpl-Ca7RATbs.css +0 -1
  156. package/dist/assets/MobileDetailNavigation-C0Z8beRL.js +0 -1
  157. package/dist/assets/MobileShareBridge-DTvmm9vz.js +0 -4
  158. package/dist/assets/MobileSurface-CfrI-5uf.js +0 -2
  159. package/dist/assets/OnboardingScreen-D6rtE7rj.js +0 -1
  160. package/dist/assets/OverlayScrollbar-DlNZho3q.js +0 -2
  161. package/dist/assets/SettingsView-BOT158gD.js +0 -25
  162. package/dist/assets/TerminalView-sf_LfP6Z.js +0 -8
  163. package/dist/assets/ToolOutputDialog-BI5vwuaT.js +0 -10
  164. package/dist/assets/appThemeRegistry-G_zwgqAH.js +0 -2
  165. package/dist/assets/clipboard-D35XSwID.js +0 -1
  166. package/dist/assets/desktop-CfLlmafK.js +0 -1
  167. package/dist/assets/desktopHosts-C1Ns3DUu.js +0 -1
  168. package/dist/assets/desktopRelayRestore-8pvI_dSc.js +0 -1
  169. package/dist/assets/fileQueries-DGHZqZHc.js +0 -1
  170. package/dist/assets/insertionBoundaries-CJksyugt.js +0 -4
  171. package/dist/assets/main-BTeHOAU_.js +0 -29
  172. package/dist/assets/main-BUBx-y1E.js +0 -2
  173. package/dist/assets/markdown-worker-y3ewaqAK.js +0 -4
  174. package/dist/assets/markdownTheme-SPe4jOwX.js +0 -2
  175. package/dist/assets/miniChat-Btnf42TH.js +0 -2
  176. package/dist/assets/mobile-CAjwV6ly.js +0 -2
  177. package/dist/assets/multirun-Rpz1_-fz.js +0 -4
  178. package/dist/assets/persistence-w-AiX2i4.js +0 -3
  179. package/dist/assets/platform-4eCmsdol.js +0 -2
  180. package/dist/assets/projectMeta-CvUuy-j5.js +0 -3
  181. package/dist/assets/radio-D_a28OlM.js +0 -1
  182. package/dist/assets/renderElectronMiniChatApp-DCzBQk2w.js +0 -2
  183. package/dist/assets/renderMobileApp-U5sclQRp.js +0 -28
  184. package/dist/assets/runtime-switch-BkpTTbEi.js +0 -1
  185. package/dist/assets/runtimeConfig-6inIl99b.js +0 -2
  186. package/dist/assets/runtimeEndpointReset-B_rUFDT5.js +0 -4
  187. package/dist/assets/shikiHighlight-CMpSAuTl.js +0 -1
  188. package/dist/assets/sortable-tabs-strip-CNDgyX0B.js +0 -1
  189. package/dist/assets/src-DZt1nWf5.css +0 -2
  190. package/dist/assets/terminalApi-CGtOtc8I.js +0 -1
  191. package/dist/assets/url-KeihbE0d.js +0 -1
  192. package/dist/assets/useAppFontEffects-3xHRVJcf.js +0 -7
  193. package/dist/assets/useAssistantUIStore-I0VWrB5V.js +0 -1
  194. package/dist/assets/useDesktopWindowControlsLayout-QJlddPRn.js +0 -1
  195. package/dist/assets/useEffectiveDirectory-Bcj1wP95.js +0 -1
  196. package/dist/assets/useFontPreferences-BLZ3y6mN.js +0 -1
  197. package/dist/assets/useMobileNavigationStore-CD0GRjaV.js +0 -412
  198. package/dist/assets/useSessionAutoCleanup-CN_6ABvx.js +0 -1
  199. package/dist/assets/useUIStore-CNLmCoPU.js +0 -1
  200. package/dist/assets/useWorkerHighlightedLines-DX9znDoW.js +0 -7
  201. package/dist/assets/utils-CTL16t2f.js +0 -1
@@ -11,6 +11,15 @@ import {
11
11
  extractDirectoryFromRequest,
12
12
  isDirectoryTurnAdmissionPath,
13
13
  } from './instance-recovery-runtime.js';
14
+ import {
15
+ createReasoningOutboundFilter,
16
+ createSseBlockSplitter,
17
+ filterSseBlock,
18
+ projectMessagesPayloadForReasoning,
19
+ readIncludeReasoningFromUrl,
20
+ readIncludeReasoningQuery,
21
+ stripIncludeReasoningParam,
22
+ } from '../event-stream/reasoning-projection.js';
14
23
 
15
24
  export const createDirectoryQueryCanonicalizer = ({ realpath, ...cacheOptions } = {}) => {
16
25
  const realpathCache = createRealpathCache({ fallbackOnError: true, realpath, ...cacheOptions });
@@ -379,6 +388,14 @@ export const registerOpenCodeProxy = (app, deps) => {
379
388
  let connectTimedOut = false;
380
389
  let writeQueue = Promise.resolve(true);
381
390
  const sseBoundary = createSseBoundaryTracker();
391
+ const requestUrl = typeof req.originalUrl === 'string' && req.originalUrl.length > 0
392
+ ? req.originalUrl
393
+ : (typeof req.url === 'string' ? req.url : '');
394
+ // OpenChamber projection control — never forwarded to OpenCode.
395
+ const includeReasoning = readIncludeReasoningQuery(req.query)
396
+ && readIncludeReasoningFromUrl(requestUrl);
397
+ const reasoningFilter = includeReasoning ? null : createReasoningOutboundFilter();
398
+ const sseSplitter = includeReasoning ? null : createSseBlockSplitter();
382
399
 
383
400
  req.on('close', closeUpstream);
384
401
 
@@ -400,10 +417,9 @@ export const registerOpenCodeProxy = (app, deps) => {
400
417
  };
401
418
 
402
419
  try {
403
- const requestUrl = typeof req.originalUrl === 'string' && req.originalUrl.length > 0
404
- ? req.originalUrl
405
- : (typeof req.url === 'string' ? req.url : '');
406
- const upstreamPath = requestUrl.startsWith('/api') ? requestUrl.slice(4) || '/' : requestUrl;
420
+ // Strip includeReasoning before building the upstream OpenCode path.
421
+ const strippedUrl = stripIncludeReasoningParam(requestUrl);
422
+ const upstreamPath = strippedUrl.startsWith('/api') ? strippedUrl.slice(4) || '/' : strippedUrl;
407
423
  const headers = normalizeForwardedDirectoryHeaders(
408
424
  collectForwardProxyHeaders(req.headers, getOpenCodeAuthHeaders())
409
425
  );
@@ -495,10 +511,40 @@ export const registerOpenCodeProxy = (app, deps) => {
495
511
  }
496
512
  if (value && value.length > 0) {
497
513
  resetStallTimer();
498
- sseBoundary.observe(value);
499
- const canContinue = await enqueueSseWrite(value);
500
- if (!canContinue) {
501
- break;
514
+ if (includeReasoning) {
515
+ // Enabled path: byte-identical passthrough (existing behavior).
516
+ sseBoundary.observe(value);
517
+ const canContinue = await enqueueSseWrite(value);
518
+ if (!canContinue) {
519
+ break;
520
+ }
521
+ } else {
522
+ // Disabled path: parse SSE blocks, drop reasoning, re-serialize.
523
+ // Preserves id/event lines and GlobalEvent wrap when unchanged drop-only.
524
+ const blocks = sseSplitter.push(value);
525
+ let canContinue = true;
526
+ for (const block of blocks) {
527
+ const filtered = filterSseBlock(block, reasoningFilter);
528
+ if (!filtered) {
529
+ continue;
530
+ }
531
+ sseBoundary.observe(filtered);
532
+ canContinue = await enqueueSseWrite(filtered);
533
+ if (!canContinue) break;
534
+ }
535
+ if (!canContinue) {
536
+ break;
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ if (!includeReasoning && sseSplitter) {
543
+ for (const block of sseSplitter.finish()) {
544
+ const filtered = filterSseBlock(block, reasoningFilter);
545
+ if (filtered) {
546
+ sseBoundary.observe(filtered);
547
+ await enqueueSseWrite(filtered);
502
548
  }
503
549
  }
504
550
  }
@@ -524,6 +570,7 @@ export const registerOpenCodeProxy = (app, deps) => {
524
570
  } finally {
525
571
  clearConnectTimer();
526
572
  clearStallTimer();
573
+ reasoningFilter?.dispose?.();
527
574
  if (heartbeatTimer) {
528
575
  clearTimeout(heartbeatTimer);
529
576
  heartbeatTimer = null;
@@ -584,7 +631,7 @@ export const registerOpenCodeProxy = (app, deps) => {
584
631
 
585
632
  const forwardSanitizedSessionListRequest = async (req, res, next, logLabel) => {
586
633
  try {
587
- const upstreamPath = await getRequestUpstreamPath(req);
634
+ const upstreamPath = stripIncludeReasoningParam(await getRequestUpstreamPath(req));
588
635
  const result = await fetchSessionListPayload(upstreamPath, { req });
589
636
 
590
637
  res.status(result.upstream.status);
@@ -617,6 +664,47 @@ export const registerOpenCodeProxy = (app, deps) => {
617
664
  }
618
665
  };
619
666
 
667
+ /**
668
+ * Official session.messages list (`GET /api/session/:id/message`).
669
+ * When includeReasoning=false, strip reasoning parts from the JSON body.
670
+ * includeReasoning is never forwarded upstream.
671
+ */
672
+ const forwardSessionMessagesListRequest = async (req, res, next) => {
673
+ try {
674
+ const includeReasoning = readIncludeReasoningQuery(req.query);
675
+ const upstreamPath = stripIncludeReasoningParam(await getRequestUpstreamPath(req));
676
+ const result = await fetchSessionListPayload(upstreamPath, { req });
677
+
678
+ res.status(result.upstream.status);
679
+ applyForwardProxyResponseHeaders(result.upstream.headers, res);
680
+
681
+ if (!result.isJson) {
682
+ res.setHeader('content-type', result.contentType);
683
+ res.end(result.bodyText);
684
+ return;
685
+ }
686
+
687
+ if (result.parseError || result.payload == null) {
688
+ res.setHeader('content-type', result.contentType);
689
+ res.end(result.bodyText);
690
+ return;
691
+ }
692
+
693
+ res.setHeader('content-type', result.contentType);
694
+ res.json(projectMessagesPayloadForReasoning(result.payload, includeReasoning));
695
+ } catch (error) {
696
+ if (isAbortError(error)) {
697
+ return;
698
+ }
699
+ console.error('[proxy] OpenCode session.messages proxy error:', error?.message ?? error);
700
+ if (!res.headersSent) {
701
+ res.status(503).json({ error: 'OpenCode service unavailable' });
702
+ return;
703
+ }
704
+ next(error);
705
+ }
706
+ };
707
+
620
708
  // Ensure API prefix is detected before proxying
621
709
  app.use('/api', (_req, _res, next) => {
622
710
  ensureOpenCodeApiPrefix();
@@ -776,6 +864,12 @@ export const registerOpenCodeProxy = (app, deps) => {
776
864
  return forwardSanitizedSessionListRequest(req, res, next, 'session.list');
777
865
  });
778
866
 
867
+ // Official session.messages list (not exact message — that is owned by
868
+ // session-turn-pages and registered before this proxy).
869
+ app.get('/api/session/:sessionID/message', (req, res, next) => {
870
+ return forwardSessionMessagesListRequest(req, res, next);
871
+ });
872
+
779
873
  app.get('/api/global/event', forwardSseRequest);
780
874
  app.get('/api/event', forwardSseRequest);
781
875
 
@@ -1,12 +1,69 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import express from 'express';
2
+ import request from 'supertest';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
4
 
3
5
  import {
4
6
  createDirectoryQueryCanonicalizer,
5
7
  isInteractiveSessionRequest,
6
8
  normalizeForwardedDirectoryHeaders,
9
+ registerOpenCodeProxy,
7
10
  resolveSessionTurnAdmissionRequest,
8
11
  } from './proxy.js';
9
12
 
13
+ const originalFetch = globalThis.fetch;
14
+
15
+ afterEach(() => {
16
+ globalThis.fetch = originalFetch;
17
+ vi.restoreAllMocks();
18
+ });
19
+
20
+ const readyRuntime = () => ({
21
+ isOpenCodeReady: true,
22
+ openCodeNotReadySince: 0,
23
+ isRestartingOpenCode: false,
24
+ openCodePort: 4096,
25
+ openCodeBaseUrl: 'http://127.0.0.1:4096',
26
+ });
27
+
28
+ const mountProxy = () => {
29
+ const app = express();
30
+ // Fresh app each time — registerOpenCodeProxy guards on app setting.
31
+ registerOpenCodeProxy(app, {
32
+ fs: { promises: { realpath: async (v) => v } },
33
+ os: {},
34
+ path: {},
35
+ OPEN_CODE_READY_GRACE_MS: 1000,
36
+ LONG_REQUEST_TIMEOUT_MS: 60_000,
37
+ SSE_UPSTREAM_CONNECT_TIMEOUT_MS: 5_000,
38
+ SSE_UPSTREAM_STALL_TIMEOUT_MS: 5_000,
39
+ getRuntime: readyRuntime,
40
+ getOpenCodeAuthHeaders: () => ({}),
41
+ buildOpenCodeUrl: (path) => `http://127.0.0.1:4096${path}`,
42
+ ensureOpenCodeApiPrefix: () => {},
43
+ });
44
+ return app;
45
+ };
46
+
47
+ const sseBody = (blocks) => {
48
+ const encoder = new TextEncoder();
49
+ const text = blocks.join('');
50
+ let sent = false;
51
+ return {
52
+ getReader() {
53
+ return {
54
+ async read() {
55
+ if (sent) return { done: true, value: undefined };
56
+ sent = true;
57
+ return { done: false, value: encoder.encode(text) };
58
+ },
59
+ async cancel() {},
60
+ releaseLock() {},
61
+ };
62
+ },
63
+ async cancel() {},
64
+ };
65
+ };
66
+
10
67
  describe('createDirectoryQueryCanonicalizer', () => {
11
68
  it('canonicalizes directory query params and preserves other params', async () => {
12
69
  const canonicalize = createDirectoryQueryCanonicalizer({
@@ -124,3 +181,122 @@ describe('resolveSessionTurnAdmissionRequest', () => {
124
181
  expect(resolveSessionTurnAdmissionRequest({ method: 'POST', url: '/api/session/ses_1/message' })).toBeNull();
125
182
  });
126
183
  });
184
+
185
+ describe('registerOpenCodeProxy reasoning projection routes', () => {
186
+ it('strips includeReasoning from SSE upstream and drops reasoning events when false', async () => {
187
+ const upstreamUrls = [];
188
+ globalThis.fetch = vi.fn(async (url) => {
189
+ upstreamUrls.push(String(url));
190
+ return {
191
+ ok: true,
192
+ status: 200,
193
+ headers: new Headers({ 'content-type': 'text/event-stream' }),
194
+ body: sseBody([
195
+ 'id: 1\ndata: {"type":"message.part.updated","properties":{"part":{"id":"t","messageID":"m","type":"text","text":"hi"}}}\n\n',
196
+ 'id: 2\ndata: {"type":"session.next.reasoning.delta.1","properties":{"delta":"secret"}}\n\n',
197
+ 'id: 3\ndata: {"type":"message.part.delta","properties":{"messageID":"m","partID":"t","field":"text","delta":"!"}}\n\n',
198
+ ]),
199
+ };
200
+ });
201
+
202
+ const app = mountProxy();
203
+ const res = await request(app)
204
+ .get('/api/event?directory=%2Ftmp&includeReasoning=false')
205
+ .buffer(true)
206
+ .parse((response, callback) => {
207
+ const chunks = [];
208
+ response.on('data', (c) => chunks.push(Buffer.from(c)));
209
+ response.on('end', () => callback(null, Buffer.concat(chunks).toString('utf8')));
210
+ });
211
+
212
+ expect(res.status).toBe(200);
213
+ expect(upstreamUrls).toHaveLength(1);
214
+ expect(upstreamUrls[0]).not.toContain('includeReasoning');
215
+ expect(upstreamUrls[0]).toContain('directory=');
216
+ expect(res.body).toContain('"type":"message.part.updated"');
217
+ expect(res.body).toContain('"type":"message.part.delta"');
218
+ expect(res.body).not.toContain('reasoning');
219
+ expect(res.body).not.toContain('secret');
220
+ });
221
+
222
+ it('byte-passthrough SSE when includeReasoning is omitted', async () => {
223
+ const raw =
224
+ 'id: 1\ndata: {"type":"session.next.reasoning.delta","properties":{"delta":"keep"}}\n\n';
225
+ globalThis.fetch = vi.fn(async () => ({
226
+ ok: true,
227
+ status: 200,
228
+ headers: new Headers({ 'content-type': 'text/event-stream' }),
229
+ body: sseBody([raw]),
230
+ }));
231
+
232
+ const app = mountProxy();
233
+ const res = await request(app)
234
+ .get('/api/global/event')
235
+ .buffer(true)
236
+ .parse((response, callback) => {
237
+ const chunks = [];
238
+ response.on('data', (c) => chunks.push(Buffer.from(c)));
239
+ response.on('end', () => callback(null, Buffer.concat(chunks).toString('utf8')));
240
+ });
241
+
242
+ expect(res.status).toBe(200);
243
+ expect(res.body).toContain('session.next.reasoning.delta');
244
+ expect(res.body).toContain('keep');
245
+ });
246
+
247
+ it('filters session.messages list JSON and strips includeReasoning upstream', async () => {
248
+ const upstreamUrls = [];
249
+ globalThis.fetch = vi.fn(async (url) => {
250
+ upstreamUrls.push(String(url));
251
+ return {
252
+ ok: true,
253
+ status: 200,
254
+ headers: new Headers({ 'content-type': 'application/json' }),
255
+ async text() {
256
+ return JSON.stringify([
257
+ {
258
+ info: { id: 'msg_1', tokens: { reasoning: 12 } },
259
+ parts: [
260
+ { id: 'r', type: 'reasoning', text: 'hidden' },
261
+ { id: 't', type: 'text', text: 'visible' },
262
+ ],
263
+ },
264
+ ]);
265
+ },
266
+ };
267
+ });
268
+
269
+ const app = mountProxy();
270
+ const res = await request(app)
271
+ .get('/api/session/ses_1/message?limit=30&includeReasoning=false&directory=%2Frepo');
272
+
273
+ expect(res.status).toBe(200);
274
+ expect(upstreamUrls[0]).not.toContain('includeReasoning');
275
+ expect(upstreamUrls[0]).toContain('/session/ses_1/message');
276
+ expect(res.body[0].parts).toEqual([{ id: 't', type: 'text', text: 'visible' }]);
277
+ expect(res.body[0].info.tokens.reasoning).toBe(12);
278
+ expect(JSON.stringify(res.body)).not.toContain('hidden');
279
+ });
280
+
281
+ it('keeps reasoning parts on session.messages list when includeReasoning is omitted', async () => {
282
+ globalThis.fetch = vi.fn(async () => ({
283
+ ok: true,
284
+ status: 200,
285
+ headers: new Headers({ 'content-type': 'application/json' }),
286
+ async text() {
287
+ return JSON.stringify([
288
+ {
289
+ info: { id: 'msg_1' },
290
+ parts: [{ id: 'r', type: 'reasoning', text: 'keep-me' }],
291
+ },
292
+ ]);
293
+ },
294
+ }));
295
+
296
+ const app = mountProxy();
297
+ const res = await request(app).get('/api/session/ses_1/message');
298
+ expect(res.status).toBe(200);
299
+ expect(res.body[0].parts[0].type).toBe('reasoning');
300
+ expect(res.body[0].parts[0].text).toBe('keep-me');
301
+ });
302
+ });
@@ -666,8 +666,8 @@ export const createSettingsRuntime = (deps) => {
666
666
  return { settings, changed: false };
667
667
  }
668
668
 
669
- const defaultLight = 'flexoki-light';
670
- const defaultDark = 'flexoki-dark';
669
+ const defaultLight = 'carbonfox-light';
670
+ const defaultDark = 'vitesse-dark-dark';
671
671
 
672
672
  let nextLightThemeId = hasLight ? settings.lightThemeId : undefined;
673
673
  let nextDarkThemeId = hasDark ? settings.darkThemeId : undefined;