@openchambery/web 1.18.2-beta.1 → 1.18.2-beta.11

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 (126) hide show
  1. package/dist/assets/{AgentAvatar-B2kZjgaf.js → AgentAvatar-BzRb9sP6.js} +1 -1
  2. package/dist/assets/AssistantView-BDxTxI4C.js +1 -0
  3. package/dist/assets/{ChatContainer-Bd_gGZ0w.js → ChatContainer-C26Rw1h2.js} +38 -38
  4. package/dist/assets/{DiagramView-IUHmsP8a.js → DiagramView-siSsyD6n.js} +1 -1
  5. package/dist/assets/{FadeInOnReveal-aeWlaqVB.js → FadeInOnReveal-oeyB_nIi.js} +1 -1
  6. package/dist/assets/{Icon-AXqa-fYB.js → Icon-DHaR0peD.js} +1 -1
  7. package/dist/assets/{MarkdownRendererImpl-DCYzkDAx.js → MarkdownRendererImpl-Crm21KO-.js} +1 -1
  8. package/dist/assets/{MobileDetailNavigation-C_nolgxy.js → MobileDetailNavigation-m2VCvJuL.js} +1 -1
  9. package/dist/assets/{MobileShareBridge-DdVLVS8t.js → MobileShareBridge-B4X9T8I9.js} +1 -1
  10. package/dist/assets/MobileSurface-DUhwRQFz.js +2 -0
  11. package/dist/assets/{MultiRunWindow-CHcsJbf-.js → MultiRunWindow-DHm99eU5.js} +1 -1
  12. package/dist/assets/{OnboardingScreen-CUsgwt4y.js → OnboardingScreen-BZHN7C2r.js} +1 -1
  13. package/dist/assets/{OverlayScrollbar-BIik9dVI.js → OverlayScrollbar-ivKSIGWh.js} +1 -1
  14. package/dist/assets/SettingsView-CpM0iBkp.js +25 -0
  15. package/dist/assets/{SettingsWindow-Cmg8gTUn.js → SettingsWindow-DW1pl40e.js} +1 -1
  16. package/dist/assets/{TerminalView-K1b_ur_R.js → TerminalView-z4ek-zEW.js} +1 -1
  17. package/dist/assets/{ToolOutputDialog-U6Y6cvHH.js → ToolOutputDialog-BZPEjn9H.js} +1 -1
  18. package/dist/assets/appThemeRegistry-BUHyoCKY.js +2 -0
  19. package/dist/assets/{button-CDvMVaiO.js → button-DCUMIXDC.js} +1 -1
  20. package/dist/assets/{clipboard-BW7nk_Cx.js → clipboard-DF5tFLDb.js} +1 -1
  21. package/dist/assets/{collapsible-C2aamDvD.js → collapsible-DNg5bLKV.js} +1 -1
  22. package/dist/assets/{context-menu-ISe_NsrI.js → context-menu-uy-nsYLa.js} +1 -1
  23. package/dist/assets/{desktopHosts-CBoBpJ1Z.js → desktopHosts-DSgGxDpI.js} +1 -1
  24. package/dist/assets/{desktopRelayRestore-l2R9muux.js → desktopRelayRestore-DVIx7jsz.js} +1 -1
  25. package/dist/assets/{diagram-CEon0BQs.js → diagram-cjvFqVa8.js} +1 -1
  26. package/dist/assets/{dialog-DjYfj1Sn.js → dialog-DaSIYgrj.js} +1 -1
  27. package/dist/assets/{es-5Lhudh0n.js → es-DCoGZljP.js} +1 -1
  28. package/dist/assets/{fileQueries-CE_DaZWQ.js → fileQueries-DXvhIL_U.js} +1 -1
  29. package/dist/assets/{fr-Q_UhFmKT.js → fr-dkEoZpPz.js} +2 -2
  30. package/dist/assets/{gitApi-ijVhBI00.js → gitApi-BukyCDHf.js} +1 -1
  31. package/dist/assets/gitApiHttp-SLrnr8a5.js +2 -0
  32. package/dist/assets/{input-k9DGGyZ6.js → input-QS3IWgY5.js} +1 -1
  33. package/dist/assets/{insertionBoundaries-C6Fu8UI7.js → insertionBoundaries-Cn--FUpW.js} +1 -1
  34. package/dist/assets/{ja-CzMPz7bT.js → ja-bQwSsvRr.js} +1 -1
  35. package/dist/assets/{ko-B-EslUsR.js → ko-Kq5C3hsv.js} +1 -1
  36. package/dist/assets/{main-BNVRs5Qb.js → main-B9LVDYC3.js} +2 -2
  37. package/dist/assets/{main-BSmiVacc.js → main-DBMzIIq8.js} +12 -12
  38. package/dist/assets/{markdown-worker-DkeWiER6.js → markdown-worker-D-OR2e_G.js} +1 -1
  39. package/dist/assets/{markdownTheme-DNT93i5L.js → markdownTheme-y_Rf6mLa.js} +1 -1
  40. package/dist/assets/{miniChat-CojzHV-6.js → miniChat-nHXgh499.js} +2 -2
  41. package/dist/assets/mobile-DheffaWK.js +2 -0
  42. package/dist/assets/{multirun-BvW4wk6I.js → multirun-BkftDz-w.js} +1 -1
  43. package/dist/assets/{persistence-DPNMIbF5.js → persistence-oSFWyKgV.js} +1 -1
  44. package/dist/assets/{pl-Dnea6Ppc.js → pl-CYpMAgVn.js} +1 -1
  45. package/dist/assets/projectMeta-Cn8JzItU.js +3 -0
  46. package/dist/assets/{pt-BR-Ca6QTem7.js → pt-BR-MSPTLfTm.js} +1 -1
  47. package/dist/assets/{radio-BKXS9qG7.js → radio-C9csxweA.js} +1 -1
  48. package/dist/assets/{renderElectronMiniChatApp-ts1ghAvW.js → renderElectronMiniChatApp-DLaYpuy5.js} +2 -2
  49. package/dist/assets/{renderMobileApp-DgXcVgpJ.js → renderMobileApp-CxDgYCxR.js} +5 -5
  50. package/dist/assets/{runtimeConfig-BtWi74Pm.js → runtimeConfig-n4QAk1lU.js} +1 -1
  51. package/dist/assets/runtimeEndpointReset-CCpti76q.js +145 -0
  52. package/dist/assets/{sortable-tabs-strip-MaeKZFdt.js → sortable-tabs-strip-sSkepr7C.js} +1 -1
  53. package/dist/assets/src-BcPciZGI.css +2 -0
  54. package/dist/assets/{store-wa1ersRf.js → store-OURVnLSN.js} +2 -2
  55. package/dist/assets/{terminalApi-tB3qLcfa.js → terminalApi-_NnfkxgP.js} +1 -1
  56. package/dist/assets/{tooltip-DMNR5rN0.js → tooltip-xEnco9xY.js} +1 -1
  57. package/dist/assets/{uk-xGc9Pzge.js → uk-cSkRAYFY.js} +1 -1
  58. package/dist/assets/{url-7MqoLs9X.js → url-DIZgk80M.js} +1 -1
  59. package/dist/assets/{useAppFontEffects-CkgySgdO.js → useAppFontEffects-BSolc5HW.js} +5 -5
  60. package/dist/assets/{useDesktopWindowControlsLayout-CxI6wePK.js → useDesktopWindowControlsLayout-CXQ-Ublz.js} +1 -1
  61. package/dist/assets/{useEffectiveDirectory-4a-0hzcq.js → useEffectiveDirectory-Dm7djJX8.js} +1 -1
  62. package/dist/assets/{useFontPreferences-CIurCeNv.js → useFontPreferences-B-QHyR3C.js} +1 -1
  63. package/dist/assets/useMobileNavigationStore-a9a3gOnd.js +476 -0
  64. package/dist/assets/useSessionAutoCleanup-CH6_9K8S.js +1 -0
  65. package/dist/assets/{useWorkerHighlightedLines-6fZw2Zn0.js → useWorkerHighlightedLines-Qg2o83Ps.js} +1 -1
  66. package/dist/assets/{utils-Co19EJEL.js → utils-Dxsi3J3l.js} +1 -1
  67. package/dist/assets/{zh-CN-sL4ktGB3.js → zh-CN-BKqeT_uz.js} +1 -1
  68. package/dist/assets/{zh-TW-Cfxps4gq.js → zh-TW-BaNjJ8Y4.js} +1 -1
  69. package/dist/index.html +8 -8
  70. package/dist/mini-chat.html +8 -8
  71. package/dist/mobile.html +8 -8
  72. package/package.json +1 -1
  73. package/server/index.d.ts +6 -0
  74. package/server/index.js +6 -0
  75. package/server/lib/config-sync/DOCUMENTATION.md +70 -0
  76. package/server/lib/config-sync/config-sync.test.js +197 -0
  77. package/server/lib/config-sync/constants.js +39 -0
  78. package/server/lib/config-sync/contract.js +130 -0
  79. package/server/lib/config-sync/credential-auth.js +39 -0
  80. package/server/lib/config-sync/engine.js +108 -0
  81. package/server/lib/config-sync/index.js +67 -0
  82. package/server/lib/config-sync/local-backup.js +171 -0
  83. package/server/lib/config-sync/plan.js +318 -0
  84. package/server/lib/config-sync/receiver.js +330 -0
  85. package/server/lib/config-sync/routes.js +141 -0
  86. package/server/lib/config-sync/routes.test.js +154 -0
  87. package/server/lib/config-sync/scripts.js +264 -0
  88. package/server/lib/config-sync/selections.js +112 -0
  89. package/server/lib/config-sync/tar.js +35 -0
  90. package/server/lib/config-sync/target-id.js +24 -0
  91. package/server/lib/event-stream/DOCUMENTATION.md +2 -2
  92. package/server/lib/event-stream/diff-summary.js +35 -16
  93. package/server/lib/event-stream/diff-summary.test.js +46 -6
  94. package/server/lib/event-stream/protocol.test.js +7 -7
  95. package/server/lib/git/DOCUMENTATION.md +2 -1
  96. package/server/lib/git/service.js +24 -5
  97. package/server/lib/git/service.test.js +51 -0
  98. package/server/lib/opencode/DOCUMENTATION.md +4 -1
  99. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  100. package/server/lib/opencode/core-routes.js +1 -0
  101. package/server/lib/opencode/feature-routes-runtime.test.js +10 -1
  102. package/server/lib/opencode/openchamber-routes.js +7 -0
  103. package/server/lib/opencode/settings-runtime.js +82 -51
  104. package/server/lib/opencode/settings-runtime.test.js +42 -0
  105. package/server/lib/relay/DOCUMENTATION.md +1 -1
  106. package/server/lib/session-turn-pages/DOCUMENTATION.md +26 -6
  107. package/server/lib/session-turn-pages/changes.service.js +156 -0
  108. package/server/lib/session-turn-pages/changes.service.test.js +188 -0
  109. package/server/lib/session-turn-pages/reconcile.routes.test.js +48 -0
  110. package/server/lib/session-turn-pages/reconcile.service.js +3 -1
  111. package/server/lib/session-turn-pages/reconcile.service.test.js +40 -0
  112. package/server/lib/session-turn-pages/routes.js +273 -12
  113. package/server/lib/session-turn-pages/routes.test.js +359 -0
  114. package/server/lib/session-turn-pages/service.js +29 -7
  115. package/server/lib/session-turn-pages/service.test.js +30 -0
  116. package/dist/assets/AssistantView-BmqAg8Cw.js +0 -1
  117. package/dist/assets/MobileSurface-BlBKmKB6.js +0 -2
  118. package/dist/assets/SettingsView-CCvrqxZK.js +0 -25
  119. package/dist/assets/appThemeRegistry-Cj8Pu23O.js +0 -2
  120. package/dist/assets/gitApiHttp-D8j1qRH4.js +0 -2
  121. package/dist/assets/mobile-BuH1uYzc.js +0 -2
  122. package/dist/assets/projectMeta-DihLz2v_.js +0 -3
  123. package/dist/assets/runtimeEndpointReset-Cb15HWwd.js +0 -145
  124. package/dist/assets/src-oHrYIqHF.css +0 -2
  125. package/dist/assets/useMobileNavigationStore-OlMvat05.js +0 -475
  126. package/dist/assets/useSessionAutoCleanup-DjtbOIMV.js +0 -1
@@ -320,6 +320,99 @@ describe('registerSessionTurnPageRoutes', () => {
320
320
  expect(part.state.output).toBeUndefined();
321
321
  });
322
322
 
323
+ it('projects message summary.diffs to L1 count/marker before serializing a turn-page response', async () => {
324
+ const patch = 'diff-body-'.repeat(20_000);
325
+ const loadPage = vi.fn(async () => ({
326
+ ok: true,
327
+ records: [{
328
+ info: {
329
+ id: 'msg_u1',
330
+ role: 'user',
331
+ summary: {
332
+ diffs: [{
333
+ file: 'src/large.ts',
334
+ status: 'modified',
335
+ additions: 9,
336
+ deletions: 3,
337
+ patch,
338
+ }],
339
+ },
340
+ },
341
+ parts: [],
342
+ }],
343
+ turnCount: 1,
344
+ cursor: null,
345
+ complete: true,
346
+ }));
347
+ const { app, route } = registry();
348
+ registerSessionTurnPageRoutes(app, { sessionTurnPageService: { loadPage } });
349
+ const res = response();
350
+
351
+ await route('GET', ROUTE)({
352
+ params: { sessionID: 'ses_1' },
353
+ query: { turns: '3' },
354
+ headers: {},
355
+ }, res);
356
+
357
+ expect(res.statusCode).toBe(200);
358
+ expect(res.body.records[0].info.summary).toEqual({
359
+ diffCount: 1,
360
+ hasDiffs: true,
361
+ });
362
+ expect(res.body.records[0].info.summary.diffs).toBeUndefined();
363
+ expect(JSON.stringify(res.body)).not.toContain(patch);
364
+ expect(JSON.stringify(res.body)).not.toContain('src/large.ts');
365
+ });
366
+
367
+ it('keeps L1 turn-page serialization under 64KiB for a 463-file / ~14MB patch summary', async () => {
368
+ const patchBody = 'P'.repeat(Math.floor((14 * 1024 * 1024) / 463));
369
+ const diffs = Array.from({ length: 463 }, (_, i) => ({
370
+ file: `src/generated/file-${i}.ts`,
371
+ status: 'modified',
372
+ additions: i % 7,
373
+ deletions: i % 3,
374
+ patch: `${patchBody}-${i}`,
375
+ before: `before-${i}`,
376
+ after: `after-${i}`,
377
+ }));
378
+ const loadPage = vi.fn(async () => ({
379
+ ok: true,
380
+ records: [{
381
+ info: {
382
+ id: 'msg_huge',
383
+ role: 'user',
384
+ summary: { title: 'huge', diffs },
385
+ },
386
+ parts: [{ id: 'prt_1', type: 'text', text: 'ok' }],
387
+ }],
388
+ turnCount: 1,
389
+ cursor: null,
390
+ complete: true,
391
+ }));
392
+ const { app, route } = registry();
393
+ registerSessionTurnPageRoutes(app, { sessionTurnPageService: { loadPage } });
394
+ const res = response();
395
+
396
+ await route('GET', ROUTE)({
397
+ params: { sessionID: 'ses_huge' },
398
+ query: { turns: '1' },
399
+ headers: {},
400
+ }, res);
401
+
402
+ expect(res.statusCode).toBe(200);
403
+ const serialized = JSON.stringify(res.body);
404
+ expect(res.body.records[0].info.summary).toEqual({
405
+ title: 'huge',
406
+ diffCount: 463,
407
+ hasDiffs: true,
408
+ });
409
+ expect(res.body.records[0].info.summary.diffs).toBeUndefined();
410
+ expect(serialized).not.toContain('"diffs"');
411
+ expect(serialized).not.toContain(patchBody.slice(0, 64));
412
+ expect(serialized).not.toContain('src/generated/file-0.ts');
413
+ expect(Buffer.byteLength(serialized, 'utf8')).toBeLessThan(64 * 1024);
414
+ });
415
+
323
416
  it('projects file parts on first packet and prepend the same way', async () => {
324
417
  const png = Buffer.concat([
325
418
  Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
@@ -479,3 +572,269 @@ describe('registerSessionTurnPageRoutes', () => {
479
572
  await pending;
480
573
  });
481
574
  });
575
+
576
+ const EXACT_MESSAGE_ROUTE = '/api/session/:sessionID/message/:messageID';
577
+ const CHANGES_ROUTE = '/api/openchamber/sessions/:sessionID/changes';
578
+
579
+ describe('registerSessionTurnPageRoutes — exact message GET', () => {
580
+ it('registers GET /api/session/:sessionID/message/:messageID', () => {
581
+ const { app, route } = registry();
582
+ registerSessionTurnPageRoutes(app, {
583
+ sessionTurnPageService: { loadPage: vi.fn() },
584
+ fetchExactMessage: vi.fn(),
585
+ });
586
+ expect(route('GET', EXACT_MESSAGE_ROUTE)).toEqual(expect.any(Function));
587
+ });
588
+
589
+ it('returns original message shape with L1 summary projection only', async () => {
590
+ const patch = '@@ huge @@\n' + 'x'.repeat(2000);
591
+ const fetchExactMessage = vi.fn(async () => ({
592
+ info: {
593
+ id: 'msg_1',
594
+ role: 'user',
595
+ summary: {
596
+ title: 'turn',
597
+ diffs: [{
598
+ file: 'a.ts',
599
+ status: 'modified',
600
+ additions: 2,
601
+ deletions: 1,
602
+ patch,
603
+ }],
604
+ },
605
+ },
606
+ parts: [{ id: 'prt_1', type: 'text', text: 'hello' }],
607
+ }));
608
+ const { app, route } = registry();
609
+ registerSessionTurnPageRoutes(app, {
610
+ sessionTurnPageService: { loadPage: vi.fn() },
611
+ fetchExactMessage,
612
+ });
613
+ const res = response();
614
+
615
+ await route('GET', EXACT_MESSAGE_ROUTE)({
616
+ params: { sessionID: 'ses_1', messageID: 'msg_1' },
617
+ query: { directory: '/repo' },
618
+ headers: {},
619
+ }, res);
620
+
621
+ expect(res.statusCode).toBe(200);
622
+ expect(fetchExactMessage).toHaveBeenCalledWith(expect.objectContaining({
623
+ sessionID: 'ses_1',
624
+ messageID: 'msg_1',
625
+ directory: '/repo',
626
+ }));
627
+ expect(res.body).toEqual({
628
+ info: {
629
+ id: 'msg_1',
630
+ role: 'user',
631
+ summary: {
632
+ title: 'turn',
633
+ diffCount: 1,
634
+ hasDiffs: true,
635
+ },
636
+ },
637
+ parts: [{ id: 'prt_1', type: 'text', text: 'hello' }],
638
+ });
639
+ expect(JSON.stringify(res.body)).not.toContain(patch);
640
+ expect(JSON.stringify(res.body)).not.toContain('a.ts');
641
+ });
642
+
643
+ it('rejects oversize messageID / sessionID with 400', async () => {
644
+ const fetchExactMessage = vi.fn();
645
+ const { app, route } = registry();
646
+ registerSessionTurnPageRoutes(app, {
647
+ sessionTurnPageService: { loadPage: vi.fn() },
648
+ fetchExactMessage,
649
+ });
650
+ const handler = route('GET', EXACT_MESSAGE_ROUTE);
651
+
652
+ const oversize = 'm'.repeat(513);
653
+ const res = response();
654
+ await handler({
655
+ params: { sessionID: 'ses_1', messageID: oversize },
656
+ query: {},
657
+ headers: {},
658
+ }, res);
659
+ expect(res.statusCode).toBe(400);
660
+ expect(res.body.code).toBe('invalid_message');
661
+ expect(fetchExactMessage).not.toHaveBeenCalled();
662
+ });
663
+
664
+ it('maps not_found to HTTP 404', async () => {
665
+ const fetchExactMessage = vi.fn(async () => {
666
+ const error = new Error('not_found');
667
+ error.code = 'not_found';
668
+ throw error;
669
+ });
670
+ const { app, route } = registry();
671
+ registerSessionTurnPageRoutes(app, {
672
+ sessionTurnPageService: { loadPage: vi.fn() },
673
+ fetchExactMessage,
674
+ });
675
+ const res = response();
676
+ await route('GET', EXACT_MESSAGE_ROUTE)({
677
+ params: { sessionID: 'ses_1', messageID: 'msg_missing' },
678
+ query: {},
679
+ headers: {},
680
+ }, res);
681
+ expect(res.statusCode).toBe(404);
682
+ expect(res.body.code).toBe('not_found');
683
+ });
684
+ });
685
+
686
+ describe('registerSessionTurnPageRoutes — changes API', () => {
687
+ it('registers GET /api/openchamber/sessions/:sessionID/changes', () => {
688
+ const { app, route } = registry();
689
+ registerSessionTurnPageRoutes(app, {
690
+ sessionTurnPageService: { loadPage: vi.fn() },
691
+ sessionChangesService: { loadChanges: vi.fn() },
692
+ });
693
+ expect(route('GET', CHANGES_ROUTE)).toEqual(expect.any(Function));
694
+ });
695
+
696
+ it('returns L2 file list without patch bodies when file is omitted', async () => {
697
+ const loadChanges = vi.fn(async () => ({
698
+ ok: true,
699
+ body: {
700
+ files: [{
701
+ file: 'src/a.ts',
702
+ status: 'modified',
703
+ additions: 3,
704
+ deletions: 1,
705
+ }],
706
+ },
707
+ }));
708
+ const { app, route } = registry();
709
+ registerSessionTurnPageRoutes(app, {
710
+ sessionTurnPageService: { loadPage: vi.fn() },
711
+ sessionChangesService: { loadChanges },
712
+ });
713
+ const res = response();
714
+
715
+ await route('GET', CHANGES_ROUTE)({
716
+ params: { sessionID: 'ses_1' },
717
+ query: { messageID: 'msg_1', directory: '/repo' },
718
+ headers: {},
719
+ }, res);
720
+
721
+ expect(res.statusCode).toBe(200);
722
+ expect(loadChanges).toHaveBeenCalledWith(expect.objectContaining({
723
+ sessionID: 'ses_1',
724
+ messageID: 'msg_1',
725
+ directory: '/repo',
726
+ file: undefined,
727
+ }));
728
+ expect(res.body).toEqual({
729
+ files: [{
730
+ file: 'src/a.ts',
731
+ status: 'modified',
732
+ additions: 3,
733
+ deletions: 1,
734
+ }],
735
+ });
736
+ expect(JSON.stringify(res.body)).not.toContain('patch');
737
+ expect(res.body.info).toBeUndefined();
738
+ expect(res.body.parts).toBeUndefined();
739
+ });
740
+
741
+ it('returns L3 single-file diff when file is provided', async () => {
742
+ const loadChanges = vi.fn(async () => ({
743
+ ok: true,
744
+ body: {
745
+ diff: {
746
+ file: 'src/a.ts',
747
+ status: 'modified',
748
+ additions: 3,
749
+ deletions: 1,
750
+ patch: '@@ -1 +1 @@\n+line',
751
+ },
752
+ },
753
+ }));
754
+ const { app, route } = registry();
755
+ registerSessionTurnPageRoutes(app, {
756
+ sessionTurnPageService: { loadPage: vi.fn() },
757
+ sessionChangesService: { loadChanges },
758
+ });
759
+ const res = response();
760
+
761
+ await route('GET', CHANGES_ROUTE)({
762
+ params: { sessionID: 'ses_1' },
763
+ query: { messageID: 'msg_1', file: 'src/a.ts' },
764
+ headers: {},
765
+ }, res);
766
+
767
+ expect(res.statusCode).toBe(200);
768
+ expect(loadChanges).toHaveBeenCalledWith(expect.objectContaining({
769
+ file: 'src/a.ts',
770
+ }));
771
+ expect(res.body.diff.file).toBe('src/a.ts');
772
+ expect(res.body.diff.patch).toContain('+line');
773
+ expect(res.body.files).toBeUndefined();
774
+ });
775
+
776
+ it('rejects missing messageID and control-character file with 400', async () => {
777
+ const loadChanges = vi.fn();
778
+ const { app, route } = registry();
779
+ registerSessionTurnPageRoutes(app, {
780
+ sessionTurnPageService: { loadPage: vi.fn() },
781
+ sessionChangesService: { loadChanges },
782
+ });
783
+ const handler = route('GET', CHANGES_ROUTE);
784
+
785
+ const missing = response();
786
+ await handler({
787
+ params: { sessionID: 'ses_1' },
788
+ query: {},
789
+ headers: {},
790
+ }, missing);
791
+ expect(missing.statusCode).toBe(400);
792
+ expect(missing.body.code).toBe('invalid_message');
793
+
794
+ const badFile = response();
795
+ await handler({
796
+ params: { sessionID: 'ses_1' },
797
+ query: { messageID: 'msg_1', file: 'src/\0evil.ts' },
798
+ headers: {},
799
+ }, badFile);
800
+ expect(badFile.statusCode).toBe(400);
801
+ expect(badFile.body.code).toBe('invalid_file');
802
+ expect(loadChanges).not.toHaveBeenCalled();
803
+ });
804
+
805
+ it('maps change_not_found to HTTP 404 with stable code', async () => {
806
+ const loadChanges = vi.fn(async () => ({ ok: false, error: 'change_not_found' }));
807
+ const { app, route } = registry();
808
+ registerSessionTurnPageRoutes(app, {
809
+ sessionTurnPageService: { loadPage: vi.fn() },
810
+ sessionChangesService: { loadChanges },
811
+ });
812
+ const res = response();
813
+ await route('GET', CHANGES_ROUTE)({
814
+ params: { sessionID: 'ses_1' },
815
+ query: { messageID: 'msg_1', file: 'missing.ts' },
816
+ headers: {},
817
+ }, res);
818
+ expect(res.statusCode).toBe(404);
819
+ expect(res.body).toEqual({
820
+ error: 'change file not found',
821
+ code: 'change_not_found',
822
+ });
823
+ });
824
+
825
+ it('maps aborted to HTTP 499', async () => {
826
+ const loadChanges = vi.fn(async () => ({ ok: false, error: 'aborted' }));
827
+ const { app, route } = registry();
828
+ registerSessionTurnPageRoutes(app, {
829
+ sessionTurnPageService: { loadPage: vi.fn() },
830
+ sessionChangesService: { loadChanges },
831
+ });
832
+ const res = response();
833
+ await route('GET', CHANGES_ROUTE)({
834
+ params: { sessionID: 'ses_1' },
835
+ query: { messageID: 'msg_1' },
836
+ headers: {},
837
+ }, res);
838
+ expect(res.statusCode).toBe(499);
839
+ });
840
+ });
@@ -16,6 +16,8 @@
16
16
  * (true only when nothing older was trimmed from the scanned window).
17
17
  */
18
18
 
19
+ import { projectMessageSummaryDiffCounts } from '../event-stream/diff-summary.js';
20
+
19
21
  const ASSISTANT_SESSION_DIVIDER_PREFIX = 'oc_asst_session_divider:';
20
22
 
21
23
  const DEFAULT_MAX_SCAN_PAGES = 50;
@@ -457,22 +459,42 @@ const isUserRecord = (record) => {
457
459
  };
458
460
 
459
461
  /**
460
- * Summarize tool/reasoning parts so a first packet does not have to carry the
461
- * bodies of a long tool-heavy turn.
462
+ * L1: project message `summary.diffs` arrays to `{ diffCount, hasDiffs }` and
463
+ * remove the file array. Preserves every other message / summary field.
464
+ */
465
+ export const projectMessageDiffSummaries = (records) => {
466
+ if (!Array.isArray(records) || records.length === 0) return records;
467
+
468
+ let changedAny = false;
469
+ const projected = records.map((record) => {
470
+ if (!record || typeof record !== 'object') return record;
471
+ const info = projectMessageSummaryDiffCounts(record.info);
472
+ if (info === record.info) return record;
473
+ changedAny = true;
474
+ return { ...record, info };
475
+ });
476
+
477
+ return changedAny ? projected : records;
478
+ };
479
+
480
+ /**
481
+ * Summarize message diff snapshots and tool/reasoning parts so a first packet
482
+ * does not have to carry bodies from a long tool-heavy turn.
462
483
  *
463
484
  * Runs after `selectTurnRecords`, so `turnCount`, `complete`, and cursor
464
- * encoding are all derived from unprojected records and cannot shift. User rows
465
- * and assistant text pass through untouched, and records that gain nothing from
485
+ * encoding are all derived from unprojected records and cannot shift. User and
486
+ * assistant text pass through untouched, and records that gain nothing from
466
487
  * projection keep their original object reference.
467
488
  *
468
489
  * @param {unknown[]} records
469
490
  * @returns {unknown[]}
470
491
  */
471
492
  export const projectSlimParts = (records) => {
472
- if (!Array.isArray(records) || records.length === 0) return records;
493
+ const summaryProjected = projectMessageDiffSummaries(records);
494
+ if (!Array.isArray(summaryProjected) || summaryProjected.length === 0) return summaryProjected;
473
495
 
474
496
  let changedAny = false;
475
- const projected = records.map((record) => {
497
+ const projected = summaryProjected.map((record) => {
476
498
  if (!record || typeof record !== 'object') return record;
477
499
  const userRow = isUserRecord(record);
478
500
 
@@ -503,7 +525,7 @@ export const projectSlimParts = (records) => {
503
525
  return { ...record, parts: nextParts };
504
526
  });
505
527
 
506
- return changedAny ? projected : records;
528
+ return changedAny ? projected : summaryProjected;
507
529
  };
508
530
 
509
531
  const countAuthoredBoundaries = (records) =>
@@ -172,6 +172,36 @@ describe('projectSlimParts', () => {
172
172
  },
173
173
  });
174
174
 
175
+ it('projects message summary.diffs to L1 count/marker even when parts are empty', () => {
176
+ const patch = 'diff-body-'.repeat(20_000);
177
+ const [projected] = projectSlimParts([{
178
+ info: {
179
+ id: 'msg_diff',
180
+ role: 'user',
181
+ summary: {
182
+ title: 'kept',
183
+ diffs: [{
184
+ file: 'src/large.ts',
185
+ status: 'modified',
186
+ additions: 12,
187
+ deletions: 4,
188
+ patch,
189
+ }],
190
+ },
191
+ },
192
+ parts: [],
193
+ }]);
194
+
195
+ expect(projected.info.summary).toEqual({
196
+ title: 'kept',
197
+ diffCount: 1,
198
+ hasDiffs: true,
199
+ });
200
+ expect(projected.info.summary.diffs).toBeUndefined();
201
+ expect(JSON.stringify(projected)).not.toContain(patch);
202
+ expect(JSON.stringify(projected)).not.toContain('src/large.ts');
203
+ });
204
+
175
205
  it('drops tool output and metadata but keeps identity and status', () => {
176
206
  const [record] = projectSlimParts([
177
207
  { info: { id: 'msg_a1', role: 'assistant' }, parts: [toolPart('prt_1', 'x'.repeat(5000))] },
@@ -1 +0,0 @@
1
- import{o as e,r as t}from"./rolldown-runtime-C0FnF6B9.js";import{D as n,T as r,j as i,t as a}from"./useUIStore-5agS8bsW.js";import{Ot as o,et as s,rt as c,yt as l}from"./persistence-DPNMIbF5.js";import{nt as u,rt as d}from"./vendor-base-ui-CnHfUmDN.js";import{n as f}from"./vendor-react-DLPElpoQ.js";import{r as p}from"./vendor-sonner-DVzioF3d.js";import{n as m}from"./vendor-reactuses-core-DDoDDyM9.js";import{n as h}from"./clipboard-BW7nk_Cx.js";import{Ao as g,Co as _,Gr as v,Ia as y,Qi as b,Yr as x,_i as S,an as C,co as w,en as T,fn as E,ki as D,lo as O,mn as k,o as A,pn as j,za as M}from"./useMobileNavigationStore-OlMvat05.js";import{a as N,n as P,s as F}from"./MobileShareBridge-DdVLVS8t.js";import{_ as I,a as L,c as R,d as ee,f as te,g as ne,l as re,n as ie,o as ae,p as oe,r as se,t as z,v as B,x as V,y as ce}from"./MobileDetailNavigation-C_nolgxy.js";import{i as le,r as ue}from"./useAssistantUIStore-Bq3cPj2x.js";import{t as de}from"./AgentAvatar-B2kZjgaf.js";import{t as fe}from"./utils-Co19EJEL.js";import{n as pe}from"./Icon-AXqa-fYB.js";import{a as H,i as me,n as he,o as U,r as W,t as G}from"./dialog-DjYfj1Sn.js";import{t as ge}from"./button-CDvMVaiO.js";import{i as _e}from"./OverlayScrollbar-BIik9dVI.js";import{I as ve,L as ye,M as be,N as K,q as xe,t as q}from"./ChatContainer-Bd_gGZ0w.js";import{a as Se,i as Ce,n as we,r as Te,t as Ee}from"./context-menu-ISe_NsrI.js";var De=f(),J=e(d(),1),Y=u(),Oe=e=>{let t=(0,De.c)(46),{assistant:n,sessionID:r,warning:i,surface:a,onRevertMessage:o,onEditMessage:s,pendingUserMessages:c,onPendingUserMessagesMaterialized:l}=e,u=n.effectiveWorkspacePath,d;t[0]!==n.sessionGeneration||t[1]!==r?(d={sessionID:r,sessionGeneration:n.sessionGeneration},t[0]=n.sessionGeneration,t[1]=r,t[2]=d):d=t[2];let f=B(n.id,d,a.active);f.data?.pages;let p;t[3]===f.data?.pages?p=t[4]:(p=f.data?.pages??[],t[3]=f.data?.pages,t[4]=p);let h;t[5]===p?h=t[6]:(h=S(p),t[5]=p,t[6]=h);let _=h,v;if(t[7]!==_){v=new Map;for(let e of _){let t=v.get(e.sessionID);v.set(e.sessionID,t===void 0||t===e.directory?e.directory:null)}t[7]=_,t[8]=v}else v=t[8];let y=v,b;t[9]===f?b=t[10]:(b=async()=>{(f.hasNextPage||f.isFetchNextPageError)&&await f.fetchNextPage()},t[9]=f,t[10]=b);let x=m(b),C=n.mode===`continuous`,w=be(),T;t[11]!==u||t[12]!==y||t[13]!==w||t[14]!==r?(T=(e,t)=>{let n=e===r?u:y.get(e);if(!n||n!==t){j(e,`missing-directory`);return}k(e,t,{phoneShell:!!(w&&!g()),switchToChat:!0})},t[11]=u,t[12]=y,t[13]=w,t[14]=r,t[15]=T):T=t[15];let E=m(T),D;t[16]===C?D=t[17]:(D={...K,forkSession:!1,navigateNestedSession:!1,mutateSession:C},t[16]=C,t[17]=D);let O;t[18]===s?O=t[19]:(O=s?{onEditMessage:s}:{},t[18]=s,t[19]=O);let A;t[20]!==u||t[21]!==o||t[22]!==E||t[23]!==r||t[24]!==a.active||t[25]!==a.surfaceID||t[26]!==D||t[27]!==O?(A={kind:`embedded`,surfaceId:a.surfaceID,sessionId:r,directory:u,active:a.active,capabilities:D,onRevertMessage:o,...O,openSourceSession:E},t[20]=u,t[21]=o,t[22]=E,t[23]=r,t[24]=a.active,t[25]=a.surfaceID,t[26]=D,t[27]=O,t[28]=A):A=t[28];let M=A,N=f.isError||f.isSuccess&&!f.hasNextPage,P=f.isLoading||f.isFetchingNextPage,F;t[29]!==x||t[30]!==N||t[31]!==_||t[32]!==P?(F={entries:_,complete:N,loading:P,fetchPrevious:x},t[29]=x,t[30]=N,t[31]=_,t[32]=P,t[33]=F):F=t[33];let I;t[34]!==u||t[35]!==l||t[36]!==o||t[37]!==c||t[38]!==r||t[39]!==M||t[40]!==a||t[41]!==F||t[42]!==i?(I={sessionId:r,directory:u,composerSurface:a,sessionSurface:M,warning:i,pendingUserMessages:c,onPendingUserMessagesMaterialized:l,assistantHistory:F,onRevertMessage:o},t[34]=u,t[35]=l,t[36]=o,t[37]=c,t[38]=r,t[39]=M,t[40]=a,t[41]=F,t[42]=i,t[43]=I):I=t[43];let L=I,R;return t[44]===L?R=t[45]:(R=(0,Y.jsx)(q,{autoOpenDraft:!1,host:L}),t[44]=L,t[45]=R),R},ke=class extends Error{constructor(){super(`assistant_selection_stale`)}},Ae=(e,t)=>e.assistantID===t.assistantID&&e.transportIdentity===t.transportIdentity&&e.runtimeGeneration===t.runtimeGeneration,je=class{onBusyChange;onError;flight=null;authoritativeIdentity=null;constructor(e,t){this.onBusyChange=e,this.onError=t}activate(e){this.authoritativeIdentity&&!Ae(this.authoritativeIdentity,e)&&this.flight&&this.retire(this.flight),this.authoritativeIdentity=e}enqueue(e,t,n){if(!this.isAuthoritative(e))return Promise.reject(new ke);let r=this.flight;r||(r={identity:e,controller:new AbortController,running:!1,desired:null,waiters:[],activeWaiters:[],idleWaiters:[],retired:!1},this.flight=r),r.desired=t;let i=new Promise((e,t)=>{r.waiters.push({resolve:e,reject:t})});return r.running||(r.running=!0,this.onBusyChange(!0),this.run(r,n)),i}async flush(e){if(!this.isAuthoritative(e))throw new ke;let t=this.flight;t?.running&&await new Promise((e,n)=>t.idleWaiters.push({resolve:e,reject:n}))}assertAuthoritative(e){if(!this.isAuthoritative(e))throw new ke}dispose(){this.flight&&this.retire(this.flight),this.authoritativeIdentity=null}async run(e,t){for(;!e.retired&&e.desired;){let n=e.desired;e.desired=null;let r=e.waiters.splice(0);e.activeWaiters=r;try{if(await t({identity:e.identity,selection:n,signal:e.controller.signal}),!e.retired)for(let e of r)e.resolve()}catch(t){if(!e.retired){this.onError(t);for(let e of r)e.reject(t)}}e.activeWaiters=[]}e.running=!1;for(let t of e.idleWaiters.splice(0))t.resolve();this.flight===e&&this.onBusyChange(!1)}retire(e){e.retired=!0,e.controller.abort();let t=new ke;for(let n of e.activeWaiters.splice(0))n.reject(t);for(let n of e.waiters.splice(0))n.reject(t);for(let n of e.idleWaiters.splice(0))n.reject(t);this.flight===e&&(this.flight=null,this.onBusyChange(!1))}isAuthoritative(e){return this.authoritativeIdentity!==null&&Ae(this.authoritativeIdentity,e)}},X=(e,t)=>{let n=t.providerID??e.providerID,r=t.modelID??e.modelID,i=n!==e.providerID||r!==e.modelID;return{enabled:e.enabled,name:e.name,defaultPrompt:e.defaultPrompt,workspacePath:e.workspacePath,providerID:n,modelID:r,agent:t.agent??null,variant:i?null:t.variant??null,mode:e.mode}},Me=async(e,t,n)=>{n.assertAuthoritative();let r=n.readSnapshot()?.assistants.find(t=>t.id===e.assistantID);if(!r)throw Error(`assistant_unavailable`);try{n.assertAuthoritative(),await n.updateAssistant(r,X(r,t)),n.assertAuthoritative()}catch(i){if(n.assertAuthoritative(),n.signal.aborted)throw new ke;if(!(i instanceof V)||i.code!==`revision_conflict`)throw i;if(n.assertAuthoritative(),r=(await n.ensureSnapshot(n.signal)).assistants.find(t=>t.id===e.assistantID),n.assertAuthoritative(),!r)throw new V(`revision_conflict`,409);n.assertAuthoritative(),await n.updateAssistant(r,X(r,t)),n.assertAuthoritative()}};async function Z(e){await L(e);let t=le.getState().defaultShareAssistant;if(t?.assistantID!==e.id)return;let n=await R();n.serverInstanceID&&t.serverInstanceID===n.serverInstanceID&&le.getState().setDefaultShareAssistant(null)}function Ne({assistant:e,open:t,onOpenChange:n,onDeleted:r}){let{t:i}=h(),[a,o]=J.useState(!1),s=e?N(e.name).displayName||e.name:``,c=m(e=>{a&&!e||n(e)}),l=m(()=>{a||n(!1)}),u=m(async()=>{if(!(!e||a)){o(!0);try{await Z(e),r?.(e.id),n(!1),p.success(i(`assistants.settings.toast.deleted`))}catch{p.error(i(`assistants.settings.toast.deleteFailed`))}finally{o(!1)}}});return(0,Y.jsx)(G,{open:t&&e!==null,onOpenChange:c,children:(0,Y.jsxs)(he,{className:`max-w-md`,children:[(0,Y.jsxs)(H,{children:[(0,Y.jsx)(U,{children:i(`assistants.settings.delete`)}),(0,Y.jsx)(W,{children:i(`assistants.settings.deleteConfirm`,{name:s})})]}),(0,Y.jsxs)(me,{children:[(0,Y.jsx)(ge,{size:`sm`,variant:`ghost`,onClick:l,disabled:a,children:i(`settings.common.actions.cancel`)}),(0,Y.jsx)(ge,{size:`sm`,variant:`destructive`,onClick:u,disabled:a,children:i(`settings.common.actions.delete`)})]})]})})}var Pe=[0,500,1500,4e3],Fe=(e,t,n)=>{let r=!1,i=e.map(e=>e.info.id!==t||e.info.sessionID===n?e:(r=!0,{...e,info:{...e.info,sessionID:n}}));return r?i:e},Ie=e=>{let t=e.refreshDelaysMs??Pe,n=e.sleep??(e=>new Promise(t=>setTimeout(t,e)));(async()=>{for(let r of t){if(r>0&&await n(r),!e.isCurrent())return;try{await e.refresh(e.binding);break}catch{}}})()},Le=e=>`${e.transport}\u0000${e.assistantID}`,Re=e=>({transport:e.transport,assistantID:e.assistantID}),ze=(e,t)=>e.assistantID===t.assistantID&&e.sessionID===t.sessionID&&e.directory===t.directory&&e.sessionGeneration===t.sessionGeneration&&e.transport===t.transport&&e.runtimeGeneration===t.runtimeGeneration,Be=(e,t)=>e.assistantID===t.assistantID&&e.sessionID===t.sessionID&&e.directory===t.directory&&e.sessionGeneration===t.sessionGeneration&&e.messageID===t.messageID&&e.transport===t.transport&&e.runtimeGeneration===t.runtimeGeneration,Ve=()=>{let e=new Map,t=new Map,n=1,r=e=>Le(e),i=async(e,n)=>{let i=r(e),a=t.get(i)??Promise.resolve(),o,s=new Promise(e=>{o=e}),c=a.catch(()=>void 0).then(()=>s);t.set(i,c),await a.catch(()=>void 0);try{return await n()}finally{o(),t.get(i)===c&&t.delete(i)}},a=(t,i)=>{let a=r(Re(t)),o=n++;return e.set(a,{identity:t,rollback:i,token:o}),o},o=t=>e.get(r(t))?.identity,s=t=>e.delete(r(t)),c=async e=>i(e,()=>s(e)),l=async t=>{let n=r(t),i=e.get(n);if(!i)return{kind:`none`};let a=i.token,o=i.identity,s;try{s=await i.rollback()}catch{return{kind:`blocked`,status:`failed`}}let c=e.get(n);return!c||c.token!==a||!Be(c.identity,o)?{kind:`superseded`}:s.status===`rolled-back`||s.status===`conflict`?(e.delete(n),{kind:`cleared`,status:s.status}):{kind:`blocked`,status:s.status}},u=async(t,n)=>{let i=r(t),a=e.get(i);if(!a)return{kind:`none`};if(ze(a.identity,n))return{kind:`match`};let o=a.token,s=a.identity,c=await a.rollback(),l=e.get(i);return!l||l.token!==o||!Be(l.identity,s)?{kind:`superseded`}:c.status===`rolled-back`||c.status===`conflict`?(e.delete(i),{kind:`cleared`,status:c.status}):{kind:`rollback_failed`,retained:!0,status:c.status}};return{register:a,read:o,clear:s,clearExclusive:c,retireCurrent:l,rollbackAndClearIfBindingMismatch:async(e,t)=>i(e,()=>u(e,t)),rollbackAllBestEffort:async t=>{let n=[...e.entries()];await Promise.all(n.map(async([,n])=>{if(t?.excludeTransport!==void 0&&n.identity.transport===t.excludeTransport)return;let a=Re(n.identity);await i(a,async()=>{let t=r(a),i=e.get(t);if(!i||i.token!==n.token)return;let o=i.token,s=i.identity;try{let n=await i.rollback(),r=e.get(t);if(!r||r.token!==o||!Be(r.identity,s))return;(n.status===`rolled-back`||n.status===`conflict`)&&e.delete(t)}catch{}})}))},stageExclusive:async(e,t)=>i(e,async()=>{let n=await l(e);if(n.kind===`blocked`)return{kind:`blocked`,status:n.status};let r=await t();if(!r)return{kind:`skipped`};let i=a(r.identity,r.rollback);return r.protectOnStale?{kind:`stale_protected`,token:i,identity:r.identity}:{kind:`registered`,token:i,identity:r.identity}})}},He=(e,t)=>!e?.attachments?.length||!t?[]:e.attachments.flatMap(e=>{let n=t[e.attachmentRefID];return n?[n]:[]}),Ue=(e,t)=>{let n=t??{};return e.map(e=>({partID:e.partID,text:e.text,...e.synthetic===!0?{synthetic:!0}:{},attachments:e.attachments.flatMap(e=>n[e.attachmentRefID]?[n[e.attachmentRefID]]:[])}))},We=(e,t)=>{if(t.length===0)return[...e];let n=new Map;for(let t of e)n.set(t.partID,t);for(let e of t)n.set(e.partID,e);let r=new Set,i=[];for(let t of e){let e=n.get(t.partID);e&&(i.push(e),r.add(t.partID))}for(let e of t)r.has(e.partID)||(i.push(e),r.add(e.partID));return i},Ge=t({AssistantView:()=>Qe}),Ke={},qe=[],Je=[],Ye=(e,t,n)=>[...e===void 0?[]:[{type:`text`,text:e}],...(t??[]).flatMap(e=>[{type:`text`,text:e.text,...e.synthetic===!0?{synthetic:!0}:{}},...(e.attachments??[]).map(e=>({type:`file`,mime:e.mimeType,url:e.dataUrl}))]),...(n??[]).map(e=>({type:`file`,mime:e.mimeType,url:e.dataUrl}))],Xe=e=>{let t=(0,De.c)(11),{assistant:n,onBack:r}=e,{t:i}=h(),a;t[0]===n?a=t[1]:(a=n?N(n.name):null,t[0]=n,t[1]=a);let o=a,s=n&&o?o.displayName||n.name:``,c;t[2]!==s||t[3]!==i?(c=s||i(`assistants.title`),t[2]=s,t[3]=i,t[4]=c):c=t[4];let l;t[5]===i?l=t[6]:(l=i(`assistants.actions.backToChat`),t[5]=i,t[6]=l);let u;return t[7]!==r||t[8]!==c||t[9]!==l?(u=(0,Y.jsx)(z,{title:c,backAriaLabel:l,onBack:r}),t[7]=r,t[8]=c,t[9]=l,t[10]=u):u=t[10],u},Ze=e=>{let t=(0,De.c)(45),{assistantID:n,displayName:r,avatarEmoji:i,modeLabel:a,selected:o,enabled:s,editLabel:c,deleteLabel:l,onSelect:u,onEdit:d,onDelete:f}=e,[p,h]=J.useState(!1),g;t[0]===u?g=t[1]:(g=()=>u(),t[0]=u,t[1]=g);let _=m(g),v;t[2]===d?v=t[3]:(v=()=>{h(!1),d()},t[2]=d,t[3]=v);let y=m(v),b;t[4]===f?b=t[5]:(b=()=>{h(!1),f()},t[4]=f,t[5]=b);let x=m(b),S;t[6]===Symbol.for(`react.memo_cache_sentinel`)?(S=e=>{e.preventDefault(),h(!0)},t[6]=S):S=t[6];let C=o?`border-border/50 bg-[var(--surface-elevated)]`:`border-transparent hover:bg-interactive-hover`,w=!s&&`opacity-65`,T;t[7]!==C||t[8]!==w?(T=fe(`flex w-full min-h-11 items-center gap-3 rounded-xl border px-3 py-3 text-left outline-none transition-[background-color,border-color,transform,opacity] duration-150 ease-out active:scale-[0.995] focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)] motion-reduce:transition-none`,C,w),t[7]=C,t[8]=w,t[9]=T):T=t[9];let E;t[10]!==_||t[11]!==o||t[12]!==T?(E=(0,Y.jsx)(`button`,{type:`button`,role:`option`,"aria-selected":o,onClick:_,onContextMenu:S,className:T}),t[10]=_,t[11]=o,t[12]=T,t[13]=E):E=t[13];let D;t[14]!==n||t[15]!==i||t[16]!==r?(D=(0,Y.jsx)(de,{name:n,emoji:i,size:24,label:r}),t[14]=n,t[15]=i,t[16]=r,t[17]=D):D=t[17];let O;t[18]===r?O=t[19]:(O=(0,Y.jsx)(`span`,{className:`block truncate typography-ui-label font-medium`,children:r}),t[18]=r,t[19]=O);let k;t[20]===a?k=t[21]:(k=(0,Y.jsx)(`span`,{className:`mt-0.5 block truncate typography-micro text-muted-foreground`,children:a}),t[20]=a,t[21]=k);let A;t[22]!==O||t[23]!==k?(A=(0,Y.jsxs)(`span`,{className:`min-w-0 flex-1`,children:[O,k]}),t[22]=O,t[23]=k,t[24]=A):A=t[24];let j;t[25]!==A||t[26]!==E||t[27]!==D?(j=(0,Y.jsxs)(Se,{render:E,children:[D,A]}),t[25]=A,t[26]=E,t[27]=D,t[28]=j):j=t[28];let M;t[29]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,Y.jsx)(pe,{name:`edit`,className:`size-4`}),t[29]=M):M=t[29];let N;t[30]!==c||t[31]!==y?(N=(0,Y.jsxs)(Te,{onClick:y,children:[M,c]}),t[30]=c,t[31]=y,t[32]=N):N=t[32];let P;t[33]===Symbol.for(`react.memo_cache_sentinel`)?(P=(0,Y.jsx)(Ce,{}),t[33]=P):P=t[33];let F;t[34]===Symbol.for(`react.memo_cache_sentinel`)?(F=(0,Y.jsx)(pe,{name:`delete-bin`,className:`size-4`}),t[34]=F):F=t[34];let I;t[35]!==l||t[36]!==x?(I=(0,Y.jsxs)(Te,{onClick:x,className:`text-[var(--status-error)] focus:text-[var(--status-error)] data-[highlighted]:text-[var(--status-error)] [&_svg]:text-[var(--status-error)]`,children:[F,l]}),t[35]=l,t[36]=x,t[37]=I):I=t[37];let L;t[38]!==N||t[39]!==I?(L=(0,Y.jsxs)(we,{className:`min-w-[10rem]`,children:[N,P,I]}),t[38]=N,t[39]=I,t[40]=L):L=t[40];let R;return t[41]!==p||t[42]!==j||t[43]!==L?(R=(0,Y.jsxs)(Ee,{open:p,onOpenChange:h,children:[j,L]}),t[41]=p,t[42]=j,t[43]=L,t[44]=R):R=t[44],R},Qe=({activeOverride:e,onMobileBack:t})=>{let{t:u}=h(),{isMobile:d}=_e(),f=be(),g=J.useSyncExternalStore(i,n,n),S=r(),k=a(e=>e.activeMainTab===`assistant`),j=e??k,L=a(e=>e.isMobile),R=d||L,z=I(),B=ce(),V=B.data,H=le(e=>e.assistantByTransport[g]??null),me=le(e=>e.selectAssistant),he=le(e=>e.requestCreate),U=V?.assistants.find(e=>e.id===H)??null,W=U?.id??``,G=U?.sessionID??``,K=U?.effectiveWorkspacePath??``,q=J.useMemo(()=>o({transportIdentity:g},`assistant:${W}`),[W,g]),Se=l(q),Ce=_(e=>e.draftAttachmentViews[Se]??Ke),we=_(e=>e.drafts[Se]),Te=J.useMemo(()=>{let e=He(we,Ce);return e.length===0?qe:e},[Ce,we]),Ee=we?.attachments,De=we?.syntheticParts,Ae=J.useMemo(()=>!Ee||!De?``:`${Ee.map(e=>e.attachmentRefID).join(``)}\u0004${De.map(e=>`${e.partID}\u0002${e.attachments.map(e=>e.attachmentRefID).join(``)}`).join(``)}`,[Ee,De]);J.useEffect(()=>{W&&_.getState().hydrateDraftAttachments(q)},[W,q,Ae]);let X=O(K||null,{enabled:!!K&&j}),Z=w(K||null,{enabled:!!K&&j}),Pe=T(G,K||void 0),Le=C(G,K||void 0),ze=E(G),Be=xe(),[Ge,Qe]=J.useState(()=>new Map),$e=J.useRef(0),et=J.useRef(Ve()),tt=Ge.get(W)??Je,[,nt]=J.useState(!1),rt=J.useRef(()=>{});rt.current=()=>{p.error(u(`assistants.composer.selectionSaveFailed`))};let it=J.useRef(null);it.current||=new je(nt,e=>rt.current(e));let at=J.useMemo(()=>({assistantID:W,transportIdentity:g,runtimeGeneration:S}),[W,S,g]),Q=it.current,ot=J.useMemo(()=>({transport:g,assistantID:W}),[W,g]),st=m(async(e,t=g)=>{await et.current.clearExclusive({transport:t,assistantID:e})}),ct=m(e=>{et.current.rollbackAllBestEffort(e).catch(()=>void 0)});J.useEffect(()=>{!H&&V?.assistants[0]&&me(V.assistants[0].id)},[me,H,V?.assistants]),J.useEffect(()=>{B.isSuccess&&H&&!U&&me(V?.assistants[0]?.id??null)},[U,me,H,V?.assistants,B.isSuccess]),J.useEffect(()=>{j&&W&&!G&&ae(W)},[j,W,G]),J.useEffect(()=>{$e.current++,Qe(new Map),ct({excludeTransport:g})},[g,S,ct]),J.useEffect(()=>()=>{$e.current++,ct()},[ct]),J.useEffect(()=>{Q.activate(at)},[Q,at]),J.useEffect(()=>()=>{Q.dispose()},[Q]),J.useEffect(()=>{W&&et.current.rollbackAndClearIfBindingMismatch(ot,{assistantID:W,sessionID:G,directory:K,sessionGeneration:U?.sessionGeneration??-1,transport:g,runtimeGeneration:S}).catch(()=>void 0)},[U?.sessionGeneration,W,K,S,G,ot,g]);let lt=m(e=>at.assistantID?Q.enqueue(at,e,async({identity:e,selection:t,signal:i})=>{await Me(e,t,{readSnapshot:()=>te(void 0,e.transportIdentity),ensureSnapshot:e=>re(e),updateAssistant:ne,signal:i,assertAuthoritative:()=>{if(Q.assertAuthoritative(e),n()!==e.transportIdentity||r()!==e.runtimeGeneration)throw new ke}})}):Promise.reject(Error(`assistant_unavailable`))),ut=J.useMemo(()=>ye(Z.data),[Z.data]),dt=m(e=>{if(!U)return;let t=y(ut,U.agent??void 0,e);if(t){let e=M({providerID:U.providerID,modelID:U.modelID,agent:U.agent},t,ut,X.data??[]),n=e.providerID===U.providerID&&e.modelID===U.modelID;lt({...e,agent:e.agent??void 0,variant:n?U.variant??void 0:void 0})}}),$=m(async(e,t)=>{e.sessionID&&await Be.ensureSessionRenderable(e.sessionID,{directory:e.directory,...t?.force?{force:!0}:{}})}),ft=U?.sessionGeneration;J.useEffect(()=>{!j||!W||!G||!K||ft===void 0||$({sessionID:G,directory:K,sessionGeneration:ft},{force:!0})},[j,W,K,$,ft,G]);let pt=m((e,t)=>{if(t.length===0)return;let n=new Set(t);Qe(t=>{let r=t.get(e);if(!r?.some(e=>n.has(e.info.id)))return t;let i=new Map(t),a=r.filter(e=>!n.has(e.info.id));return a.length>0?i.set(e,a):i.delete(e),i})}),mt=m((e,t,n)=>{Qe(r=>{let i=r.get(e);if(!i)return r;let a=Fe(i,t,n);if(a===i)return r;let o=new Map(r);return o.set(e,a),o})}),ht=m(e=>{pt(W,e)}),gt=m(async e=>{if(!G||!K)throw Error(`assistant_unavailable`);b(G,K).some(t=>t?.id===e)&&(await v(G,e,{directory:K,draftKey:q,restorePrimaryInput:!1}),await st(W))}),_t=m(async(e,t)=>{if(!U||U.mode!==`continuous`)return;if(!G||!K)throw Error(`assistant_unavailable`);if(!b(G,K).some(t=>t?.id===e))return;if(t.info.id!==e||t.info.sessionID!==G||t.info.role!==`user`)throw Error(`The selected user message is unavailable`);let i={assistantID:U.id,sessionID:G,directory:K,sessionGeneration:U.sessionGeneration,messageID:e,transport:g,runtimeGeneration:S},a=Re(i),o=await et.current.stageExclusive(a,async()=>{let a=await x(G,e,t,{directory:K,draftKey:q}),o=n()===i.transport&&r()===i.runtimeGeneration,s=o?te(void 0,i.transport)?.assistants.find(e=>e.id===i.assistantID):void 0,c=!!(s&&s.mode===`continuous`&&s.sessionID===i.sessionID&&s.sessionGeneration===i.sessionGeneration&&(s.effectiveWorkspacePath??``)===i.directory);if(o&&c)return{identity:i,rollback:()=>a.rollback()};let l=await a.rollback();return l.status===`failed`||l.status===`skipped`?{identity:i,rollback:()=>a.rollback(),protectOnStale:!0}:null});if(o.kind===`blocked`)throw Error(`assistant_stage_message_edit_prior_blocked`);if(o.kind===`stale_protected`)throw Error(`assistant_stage_message_edit_rollback_failed`)}),vt=J.useMemo(()=>{let e=D(q,()=>_.getState());return{busy:!1,attachments:Te,addAttachment:t=>e.addLocal(t),removeAttachment:t=>e.removeByAttachmentID(t),clearAttachments:()=>e.clearRootAttachments(),restoreAttachments:t=>e.restoreRootAttachments(t),pendingInput:null,consumePendingInput:()=>null,pendingPreset:null,consumePendingPreset:()=>null,consumeSyntheticParts:()=>{let e=_.getState(),t=e.draftAttachmentViews[l(q)]??Ke,n=e.consumeDraftSyntheticParts(q,F);return n?Ue(n,t):null},restoreSyntheticParts:e=>{(async()=>{let t=_.getState(),n=e.map(e=>({partID:e.partID??s(),text:e.text,attachments:[],...e.synthetic===!0?{synthetic:!0}:{}})),r=We(t.getDraft(q)?.syntheticParts??[],n);t.setDraftSyntheticParts(q,r);for(let r=0;r<n.length;r++){let i=n[r].partID;for(let n of e[r]?.attachments??[])n.source===`server`&&n.dataUrl?t.addDraftDurableAttachment(q,{attachmentID:n.id,filename:n.filename,mimeType:n.mimeType,size:n.size,source:`server`,url:n.dataUrl,serverPath:n.serverPath,partID:i}):await t.addDraftLocalAttachment(q,n.file,{attachmentID:n.id,filename:n.filename,source:n.source===`vscode`?`vscode`:`local`,vscodePath:n.vscodePath,vscodeSource:n.vscodeSource===`selection`?`selection`:void 0,partID:i})}})()},inlineDrafts:[],removeInlineDraft:()=>{},restoreInlineDrafts:()=>{},history:ze,captureRuntime:()=>_.getState().captureDraftRuntime(),getDraft:e=>_.getState().getDraft(e),abortPrompt:{sessionID:null,clear:()=>{}}}},[Te,q,ze]),yt=J.useMemo(()=>{let e=(X.data??[]).find(e=>e.id===U?.providerID)?.models?.find(e=>e.id===U?.modelID);return ve(e)},[U?.modelID,U?.providerID,X.data]),bt=Pe.length>0,xt=J.useMemo(()=>{if(!U||!G||!K)return null;let e={sessionID:G,directory:K,sessionGeneration:U.sessionGeneration};return{kind:`secondary`,surfaceID:`assistant:${U.id}`,active:j,sessionID:G,directory:K,draftKey:q,transportIdentity:g,runtimeGeneration:S,deliveryTarget:{kind:`assistant`,assistantID:U.id},resources:vt,...U.mode===`stateless`?{queueSessionID:`assistant:${U.id}`}:{},selection:{value:{providerID:U.providerID,modelID:U.modelID,agent:U.agent??void 0,variant:U.variant??void 0},catalog:{providers:X.data??[],agents:ut,variants:yt,variantsReady:X.isSuccess,ready:X.isSuccess&&Z.isSuccess,loading:X.isPending||Z.isPending,error:X.isError||Z.isError},change:lt,flush:()=>Q.flush(at)},activity:{phase:Le?.type===`busy`?`busy`:Le?.type===`retry`?`retry`:`idle`,canAbort:Le?.type===`busy`||Le?.type===`retry`},commands:{sessionID:G,hasMessages:bt,hasNewDraft:!1},commandPolicy:e=>e.name!==`fork`&&e.name!==`thread`,backend:{send:async t=>{let n;for(let t of b(e.sessionID,e.directory)){let e=typeof t?.id==`string`?t.id:``;/^msg_[0-9a-f]{12}[0-9A-Za-z]{14}$/.test(e)&&(!n||e>n)&&(n=e)}let r=c(`msg`,n);t.options?.commitStagedMessageEdit&&et.current.clear({transport:g,assistantID:U.id});let i=A({messageID:r,sessionID:e.sessionID,providerID:t.providerID??U.providerID,modelID:t.modelID??U.modelID,agent:t.agent??U.agent??void 0,text:t.text,attachments:t.attachments,additionalParts:t.parts});Qe(e=>{let t=new Map(e);return t.set(U.id,[...e.get(U.id)??[],i]),t});let a;try{a=await oe(U.id,e,r,Ye(t.text,t.parts,t.attachments))}catch(e){throw pt(U.id,[r]),e}if(z.data?.serverInstanceID){let e=N(U.name);P({serverInstanceID:z.data.serverInstanceID,assistantID:U.id,name:e.displayName||U.name,avatarSeed:U.id,...e.avatarEmoji?{avatarEmoji:e.avatarEmoji}:{}}).catch(()=>void 0)}a.binding.sessionID&&mt(U.id,r,a.binding.sessionID);let o=$e.current;Ie({binding:a.binding,refresh:t=>$(t,{force:t.sessionID!==e.sessionID||t.sessionGeneration!==e.sessionGeneration}),isCurrent:()=>$e.current===o})},sendQueued:async()=>{throw Error(`assistant-server-queue-required`)},create:async()=>{let e=await ee(U.id);await st(U.id),await $(e,{force:!0})},compact:async()=>{let t=await se(U.id,e);await st(U.id),await $(t.binding,{force:!0})},abort:async()=>{await ie(U.id,e)}},shortcuts:{cycle:dt,new:async()=>{let e=await ee(U.id);await st(U.id),await $(e,{force:!0})},abort:async()=>{await ie(U.id,e)},submit:()=>{}}}},[j,U,z.data?.serverInstanceID,lt,st,dt,K,q,bt,X.data,X.isError,X.isPending,X.isSuccess,Z.isError,Z.isPending,Z.isSuccess,$,mt,pt,vt,S,Q,at,G,Le,g,yt,ut]),St=m(()=>{if(he(),f){f.openSettings();return}let e=a.getState();e.setSettingsPage(`assistants`),e.setSettingsDialogOpen(!0)}),Ct=m(e=>{ue(e,f?{openMobileSettings:f.openSettings}:void 0)}),[wt,Tt]=J.useState(null),Et=m(e=>{Tt(e)}),Dt=m(e=>{e||Tt(null)}),Ot=m(()=>{a.getState().setActiveMainTab(`chat`)}),kt=m(()=>{if(t){t();return}Ot()}),At=(e,t,n,r)=>(0,Y.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col`,children:[R?(0,Y.jsx)(Xe,{assistant:U,onBack:kt}):null,(0,Y.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col items-center justify-center px-6 text-center`,children:[(0,Y.jsx)(pe,{name:e,className:`size-6 text-muted-foreground`}),(0,Y.jsx)(`h1`,{className:`mt-4 typography-ui-header font-semibold`,children:t}),n?(0,Y.jsx)(`p`,{className:`mt-2 max-w-md typography-ui text-muted-foreground`,children:n}):null,r?(0,Y.jsx)(`div`,{className:`mt-5`,children:r}):null]})]});if(z.isPending||B.isPending)return At(`ai-agent`,u(`assistants.state.unavailable`));if(z.isError||!z.data?.supported||!z.data.enabled||!V?.enabled)return At(`cloud-off`,u(`assistants.state.unavailable`));if(!V.assistants.length)return At(`ai-agent`,u(`assistants.onboarding.title`),u(`assistants.onboarding.description`),(0,Y.jsx)(ge,{onClick:St,children:u(`assistants.onboarding.action`)}));if(!U)return At(`ai-agent`,u(`assistants.state.unavailable`));if(!xt)return(0,Y.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col bg-background`,children:[R?(0,Y.jsx)(Xe,{assistant:U,onBack:kt}):null,(0,Y.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6`,"aria-busy":`true`,"aria-label":u(`assistants.state.unavailable`),children:[(0,Y.jsx)(`div`,{className:`size-10 animate-pulse rounded-xl bg-[var(--surface-muted)] motion-reduce:animate-none`}),(0,Y.jsx)(`div`,{className:`h-3.5 w-36 animate-pulse rounded-md bg-[var(--surface-muted)] motion-reduce:animate-none`})]})]});let jt=N(U.name),Mt=U.enabled?B.isError?u(`assistants.state.staleSnapshot`):null:u(`assistants.state.assistantDisabled`);return(0,Y.jsxs)(`div`,{className:`relative flex h-full min-h-0 overflow-hidden bg-background`,"data-presentation":`workspace`,children:[R?null:(0,Y.jsxs)(`section`,{className:`flex h-full min-h-0 w-[clamp(16rem,22vw,20rem)] shrink-0 flex-col overflow-hidden`,children:[(0,Y.jsx)(`header`,{className:`shrink-0 px-4 pb-3 pt-4 sm:px-5`,children:(0,Y.jsx)(`h1`,{className:`truncate typography-ui-label font-semibold text-foreground`,children:u(`assistants.title`)})}),(0,Y.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto px-3 pb-4 sm:px-4`,role:`listbox`,"aria-label":u(`assistants.listAria`),children:(0,Y.jsx)(`div`,{className:`flex flex-col gap-1 border-t border-border/40 pt-3`,children:V.assistants.map(e=>{let t=e.id===H,n=N(e.name);return(0,Y.jsx)(Ze,{assistantID:e.id,displayName:n.displayName,avatarEmoji:n.avatarEmoji??void 0,modeLabel:e.mode===`stateless`?u(`assistants.mode.stateless`):u(`assistants.mode.continuous`),selected:t,enabled:e.enabled,editLabel:u(`assistants.menu.edit`),deleteLabel:u(`assistants.settings.delete`),onSelect:()=>me(e.id),onEdit:()=>Ct(e.id),onDelete:()=>Et(e)},e.id)})})})]}),(0,Y.jsx)(Ne,{assistant:wt,open:wt!==null,onOpenChange:Dt}),(0,Y.jsxs)(`div`,{className:fe(`flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background`,!R&&`border-l border-border/60`),children:[R?(0,Y.jsx)(Xe,{assistant:U,onBack:kt}):(0,Y.jsxs)(`header`,{className:`flex h-14 shrink-0 items-center gap-3 border-b border-border/40 px-4 sm:px-5`,children:[(0,Y.jsx)(de,{name:U.id,emoji:jt.avatarEmoji,size:24,label:jt.displayName||U.name}),(0,Y.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,Y.jsx)(`div`,{className:`truncate typography-ui-label font-medium`,children:jt.displayName}),(0,Y.jsx)(`div`,{className:`mt-0.5 truncate typography-micro leading-none text-muted-foreground/70`,children:U.mode===`stateless`?u(`assistants.conversation.statelessHint`):u(`assistants.conversation.continuousHint`)})]})]}),(0,Y.jsx)(Oe,{onRevertMessage:gt,onEditMessage:U.mode===`continuous`?_t:void 0,assistant:U,sessionID:G,warning:Mt,surface:xt,pendingUserMessages:tt,onPendingUserMessagesMaterialized:ht})]})]})};export{Ge as n,Ne as r,Qe as t};