@nextclaw/ui 0.12.22 → 0.12.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/assets/{api-lwyw9j7i.js → api-D2xRKmZd.js} +5 -5
  3. package/dist/assets/app-manager-provider-CNaZboG4.js +1 -0
  4. package/dist/assets/{app-navigation.config-DgiR0c5_.js → app-navigation.config-Ihhrrt--.js} +1 -1
  5. package/dist/assets/{book-open-DgLqYpNY.js → book-open-DDlN5MvX.js} +1 -1
  6. package/dist/assets/{channels-list-page-Dl839n02.js → channels-list-page-p26lgxLk.js} +2 -2
  7. package/dist/assets/{chat-DwUf7AKR.js → chat-Dkh2qtuz.js} +8 -8
  8. package/dist/assets/{chat-page-B-FvPmA7.js → chat-page-DoTmE2wx.js} +1 -1
  9. package/dist/assets/{chunk-JZWAC4HX-u4uYphxM.js → chunk-JZWAC4HX-Kydj4yEz.js} +1 -1
  10. package/dist/assets/{config-split-page-BMRGuCJQ.js → config-split-page-DIOCjj2Q.js} +1 -1
  11. package/dist/assets/{createLucideIcon-BZkY6emz.js → createLucideIcon-BLMK3QUd.js} +1 -1
  12. package/dist/assets/{desktop-update-config-D5g_gPak.js → desktop-update-config-DlpzDfKM.js} +1 -1
  13. package/dist/assets/{dialog-CdtCU2xX.js → dialog-C3D7Be0p.js} +1 -1
  14. package/dist/assets/{dist-CuqvE--P.js → dist-CPlbUgwU.js} +1 -1
  15. package/dist/assets/{doc-browser-BUlCkZo2.js → doc-browser-C8FM5fC0.js} +1 -1
  16. package/dist/assets/doc-browser-RJUOL_GO.js +1 -0
  17. package/dist/assets/{doc-browser-context-DfLHAWbG.js → doc-browser-context-BJuMaI3o.js} +1 -1
  18. package/dist/assets/{doc-browser-CzCV73NJ.js → doc-browser-p82AdNO-.js} +1 -1
  19. package/dist/assets/{es2015-yYU5Ad5w.js → es2015-xqN1slyW.js} +1 -1
  20. package/dist/assets/{external-link-Sw3ah_JD.js → external-link-DwfSfTLB.js} +1 -1
  21. package/dist/assets/{folder-D7-VTnkz.js → folder-CeJKPx5P.js} +1 -1
  22. package/dist/assets/{hash-zajSTDXZ.js → hash-BqxRTZW5.js} +1 -1
  23. package/dist/assets/i18n-DnTGDIRw.js +1 -0
  24. package/dist/assets/{index-Doxyk7L2.js → index-pBvbJ5Mt.js} +2 -2
  25. package/dist/assets/{key-round-CnI1mc9F.js → key-round-CJ5gDAAG.js} +1 -1
  26. package/dist/assets/loader-circle-fd-vQKtW.js +1 -0
  27. package/dist/assets/{logo-badge-BQgKnVtz.js → logo-badge-KAe-7d8c.js} +1 -1
  28. package/dist/assets/{logos-CqVm0q0W.js → logos-C4sYP1Vl.js} +1 -1
  29. package/dist/assets/marketplace-page-Cql0kDi-.js +1 -0
  30. package/dist/assets/{marketplace-page-CawcdL6Y.js → marketplace-page-m4P5g_Ht.js} +1 -1
  31. package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +1 -0
  32. package/dist/assets/{mcp-marketplace-page-DEGfJ_70.js → mcp-marketplace-page-ByzBQZcx.js} +1 -1
  33. package/dist/assets/message-square-z_osm9c0.js +1 -0
  34. package/dist/assets/{model-config-r-1RPSrZ.js → model-config-Dbr_0APb.js} +1 -1
  35. package/dist/assets/{notice-card-BPtCVEKW.js → notice-card-BFDbKQDA.js} +1 -1
  36. package/dist/assets/play-Dv6Nr1Ew.js +1 -0
  37. package/dist/assets/plus-D8eKFY7h.js +1 -0
  38. package/dist/assets/{popover-jbfQhYQh.js → popover-B86Dbfhf.js} +1 -1
  39. package/dist/assets/{provider-scoped-model-input-gdk2lmRi.js → provider-scoped-model-input-DFm6N2f7.js} +1 -1
  40. package/dist/assets/{providers-list-DpISIr3M.js → providers-list-BJcLOjun.js} +1 -1
  41. package/dist/assets/{refresh-ccw-Bii4w8aB.js → refresh-ccw-ByVwmnN_.js} +1 -1
  42. package/dist/assets/{refresh-cw-BxojR62w.js → refresh-cw-PcqoYB3K.js} +1 -1
  43. package/dist/assets/remote-BOxo9iwd.js +1 -0
  44. package/dist/assets/{rotate-cw-1Xqa7LZ8.js → rotate-cw-BZ2JObNs.js} +1 -1
  45. package/dist/assets/runtime-config-page-CjLhnbSl.js +1 -0
  46. package/dist/assets/{save--BVI5wZX.js → save-euRxl8pI.js} +1 -1
  47. package/dist/assets/{search-vChioOoe.js → search-CLd7m0M7.js} +1 -1
  48. package/dist/assets/{search-config-BWqz8nqY.js → search-config-J4Htco-P.js} +1 -1
  49. package/dist/assets/{secrets-config-CjzSNg0Y.js → secrets-config-CUdERjco.js} +1 -1
  50. package/dist/assets/{select-Cw5Zkb1w.js → select-CJ0wbo3D.js} +1 -1
  51. package/dist/assets/{sessions-config-page-beoDPtII.js → sessions-config-page-DpK991fs.js} +2 -2
  52. package/dist/assets/{setting-row-Cjl2d40s.js → setting-row-D1Yygqp7.js} +1 -1
  53. package/dist/assets/{settings-CiRChctQ.js → settings-drbWqzA4.js} +1 -1
  54. package/dist/assets/skeleton-BK1SOSRA.js +1 -0
  55. package/dist/assets/{sparkles-D1ZKWdm4.js → sparkles-DVfeSVJQ.js} +1 -1
  56. package/dist/assets/{status-dot-Dv_hiUVa.js → status-dot-ChvPCib9.js} +1 -1
  57. package/dist/assets/{tabs-custom-CsACkVji.js → tabs-custom-Hia_ong0.js} +1 -1
  58. package/dist/assets/{tag-chip-CoWHxYJj.js → tag-chip-FrkmkT8r.js} +1 -1
  59. package/dist/assets/theme-provider-0hxjiPc_.js +2 -0
  60. package/dist/assets/{tooltip-GYzH-Hfq.js → tooltip-Cj4yA0gH.js} +1 -1
  61. package/dist/assets/{trash-2-rY9ZteZX.js → trash-2-CBsHCfqq.js} +1 -1
  62. package/dist/assets/{use-config-BhJHD3-G.js → use-config-38Ur-89i.js} +1 -1
  63. package/dist/assets/{use-confirm-dialog-Bqgy3Gi-.js → use-confirm-dialog-DPQThaeU.js} +1 -1
  64. package/dist/assets/{use-infinite-scroll-loader-BfexitoF.js → use-infinite-scroll-loader-5Gf1xQi7.js} +1 -1
  65. package/dist/assets/{use-viewport-layout-D33zVbr5.js → use-viewport-layout-D1XzKeip.js} +1 -1
  66. package/dist/assets/x-CM-XDMpk.js +1 -0
  67. package/dist/index.html +39 -39
  68. package/package.json +9 -9
  69. package/src/features/account/hooks/use-auth.test.ts +7 -5
  70. package/src/features/account/hooks/use-auth.ts +23 -20
  71. package/src/features/chat/components/chat-sidebar-session-item.tsx +1 -1
  72. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +2 -2
  73. package/src/features/chat/components/layout/chat-sidebar.test.tsx +74 -0
  74. package/src/features/chat/components/layout/chat-sidebar.tsx +28 -29
  75. package/src/features/chat/hooks/use-hydrated-ncp-agent.test.tsx +6 -0
  76. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +158 -69
  77. package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -2
  78. package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -7
  79. package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +10 -0
  80. package/src/features/chat/hooks/use-ncp-session-conversation.ts +2 -1
  81. package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +2 -4
  82. package/src/features/chat/managers/chat-session-list.manager.test.ts +19 -16
  83. package/src/features/chat/managers/chat-session-list.manager.ts +20 -24
  84. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +23 -12
  85. package/src/features/chat/managers/ncp-chat-input.manager.ts +4 -2
  86. package/src/features/chat/pages/ncp-chat-page.tsx +23 -13
  87. package/src/features/chat/stores/chat-session-list.store.ts +2 -3
  88. package/src/features/chat/types/chat-stream.types.ts +1 -1
  89. package/src/features/chat/utils/ncp-session-adapter.utils.ts +1 -1
  90. package/src/features/system-status/hooks/use-system-status.ts +6 -28
  91. package/src/features/system-status/index.ts +2 -1
  92. package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +14 -4
  93. package/src/features/system-status/managers/system-status.manager.test.ts +2 -8
  94. package/src/features/system-status/managers/system-status.manager.ts +20 -30
  95. package/src/shared/components/common/brand-header.test.tsx +84 -3
  96. package/src/shared/components/common/brand-header.tsx +37 -39
  97. package/src/shared/lib/api/managers/client.manager.ts +30 -2
  98. package/src/shared/lib/api/ncp-session-query-cache.test.ts +26 -1
  99. package/src/shared/lib/api/ncp-session-query-cache.ts +5 -1
  100. package/src/shared/lib/api/utils/config.utils.ts +6 -4
  101. package/src/shared/lib/i18n/desktop-update-labels.utils.ts +3 -1
  102. package/src/shared/lib/transport/index.ts +1 -0
  103. package/src/shared/lib/transport/transport.types.ts +20 -0
  104. package/dist/assets/app-manager-provider-C0ONQxUg.js +0 -1
  105. package/dist/assets/doc-browser-Doh2541x.js +0 -1
  106. package/dist/assets/i18n-C5Mibli1.js +0 -1
  107. package/dist/assets/loader-circle-B5i8oMMY.js +0 -1
  108. package/dist/assets/marketplace-page-BRHkZaO5.js +0 -1
  109. package/dist/assets/mcp-marketplace-page-CL7BF4dD.js +0 -1
  110. package/dist/assets/message-square-D6Z4NwpG.js +0 -1
  111. package/dist/assets/play-D8WJLnJe.js +0 -1
  112. package/dist/assets/plus-Di0KAkiO.js +0 -1
  113. package/dist/assets/remote-BnRNqMlb.js +0 -1
  114. package/dist/assets/runtime-config-page-DQ8YY8Lc.js +0 -1
  115. package/dist/assets/skeleton-CFQRIUzt.js +0 -1
  116. package/dist/assets/theme-provider-B5XReW_-.js +0 -1
  117. package/dist/assets/x-DpTzXQcX.js +0 -1
package/dist/index.html CHANGED
@@ -78,45 +78,45 @@
78
78
  })();
79
79
  </script>
80
80
  <title>NextClaw</title>
81
- <script type="module" crossorigin src="/assets/index-Doxyk7L2.js"></script>
82
- <link rel="modulepreload" crossorigin href="/assets/i18n-C5Mibli1.js">
83
- <link rel="modulepreload" crossorigin href="/assets/chunk-JZWAC4HX-u4uYphxM.js">
84
- <link rel="modulepreload" crossorigin href="/assets/api-lwyw9j7i.js">
85
- <link rel="modulepreload" crossorigin href="/assets/es2015-yYU5Ad5w.js">
86
- <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BZkY6emz.js">
87
- <link rel="modulepreload" crossorigin href="/assets/select-Cw5Zkb1w.js">
88
- <link rel="modulepreload" crossorigin href="/assets/dist-CuqvE--P.js">
89
- <link rel="modulepreload" crossorigin href="/assets/x-DpTzXQcX.js">
90
- <link rel="modulepreload" crossorigin href="/assets/dialog-CdtCU2xX.js">
91
- <link rel="modulepreload" crossorigin href="/assets/popover-jbfQhYQh.js">
92
- <link rel="modulepreload" crossorigin href="/assets/tooltip-GYzH-Hfq.js">
93
- <link rel="modulepreload" crossorigin href="/assets/refresh-cw-BxojR62w.js">
94
- <link rel="modulepreload" crossorigin href="/assets/use-config-BhJHD3-G.js">
95
- <link rel="modulepreload" crossorigin href="/assets/theme-provider-B5XReW_-.js">
96
- <link rel="modulepreload" crossorigin href="/assets/search-vChioOoe.js">
97
- <link rel="modulepreload" crossorigin href="/assets/book-open-DgLqYpNY.js">
98
- <link rel="modulepreload" crossorigin href="/assets/external-link-Sw3ah_JD.js">
99
- <link rel="modulepreload" crossorigin href="/assets/folder-D7-VTnkz.js">
100
- <link rel="modulepreload" crossorigin href="/assets/logos-CqVm0q0W.js">
101
- <link rel="modulepreload" crossorigin href="/assets/loader-circle-B5i8oMMY.js">
102
- <link rel="modulepreload" crossorigin href="/assets/plus-Di0KAkiO.js">
103
- <link rel="modulepreload" crossorigin href="/assets/refresh-ccw-Bii4w8aB.js">
104
- <link rel="modulepreload" crossorigin href="/assets/settings-CiRChctQ.js">
105
- <link rel="modulepreload" crossorigin href="/assets/sparkles-D1ZKWdm4.js">
106
- <link rel="modulepreload" crossorigin href="/assets/trash-2-rY9ZteZX.js">
107
- <link rel="modulepreload" crossorigin href="/assets/doc-browser-context-DfLHAWbG.js">
108
- <link rel="modulepreload" crossorigin href="/assets/doc-browser-CzCV73NJ.js">
109
- <link rel="modulepreload" crossorigin href="/assets/doc-browser-BUlCkZo2.js">
110
- <link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-D33zVbr5.js">
111
- <link rel="modulepreload" crossorigin href="/assets/logo-badge-BQgKnVtz.js">
112
- <link rel="modulepreload" crossorigin href="/assets/skeleton-CFQRIUzt.js">
113
- <link rel="modulepreload" crossorigin href="/assets/chat-DwUf7AKR.js">
114
- <link rel="modulepreload" crossorigin href="/assets/key-round-CnI1mc9F.js">
115
- <link rel="modulepreload" crossorigin href="/assets/message-square-D6Z4NwpG.js">
116
- <link rel="modulepreload" crossorigin href="/assets/app-navigation.config-DgiR0c5_.js">
117
- <link rel="modulepreload" crossorigin href="/assets/notice-card-BPtCVEKW.js">
118
- <link rel="modulepreload" crossorigin href="/assets/status-dot-Dv_hiUVa.js">
119
- <link rel="modulepreload" crossorigin href="/assets/app-manager-provider-C0ONQxUg.js">
81
+ <script type="module" crossorigin src="/assets/index-pBvbJ5Mt.js"></script>
82
+ <link rel="modulepreload" crossorigin href="/assets/i18n-DnTGDIRw.js">
83
+ <link rel="modulepreload" crossorigin href="/assets/chunk-JZWAC4HX-Kydj4yEz.js">
84
+ <link rel="modulepreload" crossorigin href="/assets/api-D2xRKmZd.js">
85
+ <link rel="modulepreload" crossorigin href="/assets/es2015-xqN1slyW.js">
86
+ <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BLMK3QUd.js">
87
+ <link rel="modulepreload" crossorigin href="/assets/select-CJ0wbo3D.js">
88
+ <link rel="modulepreload" crossorigin href="/assets/dist-CPlbUgwU.js">
89
+ <link rel="modulepreload" crossorigin href="/assets/x-CM-XDMpk.js">
90
+ <link rel="modulepreload" crossorigin href="/assets/dialog-C3D7Be0p.js">
91
+ <link rel="modulepreload" crossorigin href="/assets/popover-B86Dbfhf.js">
92
+ <link rel="modulepreload" crossorigin href="/assets/tooltip-Cj4yA0gH.js">
93
+ <link rel="modulepreload" crossorigin href="/assets/refresh-cw-PcqoYB3K.js">
94
+ <link rel="modulepreload" crossorigin href="/assets/use-config-38Ur-89i.js">
95
+ <link rel="modulepreload" crossorigin href="/assets/theme-provider-0hxjiPc_.js">
96
+ <link rel="modulepreload" crossorigin href="/assets/search-CLd7m0M7.js">
97
+ <link rel="modulepreload" crossorigin href="/assets/book-open-DDlN5MvX.js">
98
+ <link rel="modulepreload" crossorigin href="/assets/external-link-DwfSfTLB.js">
99
+ <link rel="modulepreload" crossorigin href="/assets/folder-CeJKPx5P.js">
100
+ <link rel="modulepreload" crossorigin href="/assets/logos-C4sYP1Vl.js">
101
+ <link rel="modulepreload" crossorigin href="/assets/loader-circle-fd-vQKtW.js">
102
+ <link rel="modulepreload" crossorigin href="/assets/plus-D8eKFY7h.js">
103
+ <link rel="modulepreload" crossorigin href="/assets/refresh-ccw-ByVwmnN_.js">
104
+ <link rel="modulepreload" crossorigin href="/assets/settings-drbWqzA4.js">
105
+ <link rel="modulepreload" crossorigin href="/assets/sparkles-DVfeSVJQ.js">
106
+ <link rel="modulepreload" crossorigin href="/assets/trash-2-CBsHCfqq.js">
107
+ <link rel="modulepreload" crossorigin href="/assets/doc-browser-context-BJuMaI3o.js">
108
+ <link rel="modulepreload" crossorigin href="/assets/doc-browser-p82AdNO-.js">
109
+ <link rel="modulepreload" crossorigin href="/assets/doc-browser-C8FM5fC0.js">
110
+ <link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-D1XzKeip.js">
111
+ <link rel="modulepreload" crossorigin href="/assets/logo-badge-KAe-7d8c.js">
112
+ <link rel="modulepreload" crossorigin href="/assets/skeleton-BK1SOSRA.js">
113
+ <link rel="modulepreload" crossorigin href="/assets/chat-Dkh2qtuz.js">
114
+ <link rel="modulepreload" crossorigin href="/assets/key-round-CJ5gDAAG.js">
115
+ <link rel="modulepreload" crossorigin href="/assets/message-square-z_osm9c0.js">
116
+ <link rel="modulepreload" crossorigin href="/assets/app-navigation.config-Ihhrrt--.js">
117
+ <link rel="modulepreload" crossorigin href="/assets/notice-card-BFDbKQDA.js">
118
+ <link rel="modulepreload" crossorigin href="/assets/status-dot-ChvPCib9.js">
119
+ <link rel="modulepreload" crossorigin href="/assets/app-manager-provider-CNaZboG4.js">
120
120
  <link rel="stylesheet" crossorigin href="/assets/index-D8MKmXtO.css">
121
121
  </head>
122
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.12.22",
3
+ "version": "0.12.24",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,14 +28,14 @@
28
28
  "tailwind-merge": "^2.5.4",
29
29
  "zod": "^3.23.8",
30
30
  "zustand": "^5.0.2",
31
- "@nextclaw/agent-chat": "0.1.13",
32
- "@nextclaw/agent-chat-ui": "0.3.15",
33
- "@nextclaw/client-sdk": "0.1.3",
34
- "@nextclaw/ncp": "0.5.8",
35
- "@nextclaw/ncp-react": "0.4.28",
36
- "@nextclaw/server": "0.12.15",
37
- "@nextclaw/shared": "0.1.2",
38
- "@nextclaw/ncp-http-agent-client": "0.3.20"
31
+ "@nextclaw/agent-chat": "0.1.14",
32
+ "@nextclaw/agent-chat-ui": "0.3.16",
33
+ "@nextclaw/client-sdk": "0.1.4",
34
+ "@nextclaw/ncp": "0.5.9",
35
+ "@nextclaw/ncp-http-agent-client": "0.3.21",
36
+ "@nextclaw/ncp-react": "0.4.29",
37
+ "@nextclaw/server": "0.12.16",
38
+ "@nextclaw/shared": "0.1.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@testing-library/react": "^16.3.0",
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import {
3
- AUTH_STATUS_BOOTSTRAP_RETRY_DELAY_MS,
4
3
  isTransientAuthStatusBootstrapError,
4
+ resolveAuthStatusBootstrapRetryDelay,
5
5
  shouldRetryAuthStatusBootstrap
6
6
  } from './use-auth';
7
7
 
@@ -23,11 +23,13 @@ describe('auth status bootstrap retry policy', () => {
23
23
  });
24
24
 
25
25
  it('stops retrying after the bootstrap retry budget is exhausted', () => {
26
- expect(shouldRetryAuthStatusBootstrap(39, new Error('Failed to fetch'))).toBe(true);
27
- expect(shouldRetryAuthStatusBootstrap(40, new Error('Failed to fetch'))).toBe(false);
26
+ expect(shouldRetryAuthStatusBootstrap(7, new Error('Failed to fetch'))).toBe(true);
27
+ expect(shouldRetryAuthStatusBootstrap(8, new Error('Failed to fetch'))).toBe(false);
28
28
  });
29
29
 
30
- it('keeps the retry delay short and predictable', () => {
31
- expect(AUTH_STATUS_BOOTSTRAP_RETRY_DELAY_MS).toBe(250);
30
+ it('backs off retry delay without becoming sluggish', () => {
31
+ expect(resolveAuthStatusBootstrapRetryDelay(1)).toBe(500);
32
+ expect(resolveAuthStatusBootstrapRetryDelay(2)).toBe(1000);
33
+ expect(resolveAuthStatusBootstrapRetryDelay(4)).toBe(3000);
32
34
  });
33
35
  });
@@ -11,46 +11,49 @@ import {
11
11
  import type { AuthStatusView } from '@/shared/lib/api';
12
12
  import { toast } from 'sonner';
13
13
  import { t } from '@/shared/lib/i18n';
14
+ import { isTransientRuntimeConnectionErrorMessage } from '@/shared/lib/transport';
14
15
 
15
- const AUTH_STATUS_BOOTSTRAP_MAX_RETRIES = 40;
16
- const AUTH_STATUS_BOOTSTRAP_TIMEOUT_MS = 400;
17
- export const AUTH_STATUS_BOOTSTRAP_RETRY_DELAY_MS = 250;
16
+ const AUTH_STATUS_BOOTSTRAP_PROBE_POLICY = {
17
+ maxRetries: 8,
18
+ startupTimeoutMs: 2_000,
19
+ settledTimeoutMs: 5_000,
20
+ retryBaseDelayMs: 500,
21
+ retryMaxDelayMs: 3_000,
22
+ } as const;
18
23
 
19
24
  export function isTransientAuthStatusBootstrapError(error: unknown): boolean {
20
25
  if (!(error instanceof Error)) {
21
26
  return false;
22
27
  }
23
- const message = error.message.trim().toLowerCase();
24
- if (!message) {
25
- return false;
26
- }
27
- return (
28
- message.includes('failed to fetch') ||
29
- message.includes('networkerror') ||
30
- message.includes('network request failed') ||
31
- message.includes('load failed') ||
32
- message.includes('request timed out') ||
33
- message.includes('timed out waiting for remote request response') ||
34
- message.includes('remote transport connection closed')
35
- );
28
+ return isTransientRuntimeConnectionErrorMessage(error.message);
36
29
  }
37
30
 
38
31
  export function shouldRetryAuthStatusBootstrap(failureCount: number, error: unknown): boolean {
39
- if (failureCount >= AUTH_STATUS_BOOTSTRAP_MAX_RETRIES) {
32
+ if (failureCount >= AUTH_STATUS_BOOTSTRAP_PROBE_POLICY.maxRetries) {
40
33
  return false;
41
34
  }
42
35
  return isTransientAuthStatusBootstrapError(error);
43
36
  }
44
37
 
38
+ export function resolveAuthStatusBootstrapRetryDelay(failureCount: number): number {
39
+ return Math.min(
40
+ AUTH_STATUS_BOOTSTRAP_PROBE_POLICY.retryMaxDelayMs,
41
+ AUTH_STATUS_BOOTSTRAP_PROBE_POLICY.retryBaseDelayMs * 2 ** Math.max(0, failureCount - 1)
42
+ );
43
+ }
44
+
45
45
  export function useAuthStatus() {
46
46
  const [bootstrapSettled, setBootstrapSettled] = useState(false);
47
47
  const query = useQuery<AuthStatusView>({
48
48
  queryKey: ['auth-status'],
49
- queryFn: () => fetchAuthStatus({ timeoutMs: bootstrapSettled ? 5_000 : AUTH_STATUS_BOOTSTRAP_TIMEOUT_MS }),
49
+ queryFn: () => fetchAuthStatus({
50
+ timeoutMs: bootstrapSettled
51
+ ? AUTH_STATUS_BOOTSTRAP_PROBE_POLICY.settledTimeoutMs
52
+ : AUTH_STATUS_BOOTSTRAP_PROBE_POLICY.startupTimeoutMs,
53
+ }),
50
54
  staleTime: 5_000,
51
55
  retry: shouldRetryAuthStatusBootstrap,
52
- retryDelay: AUTH_STATUS_BOOTSTRAP_RETRY_DELAY_MS,
53
- refetchOnWindowFocus: true
56
+ retryDelay: resolveAuthStatusBootstrapRetryDelay
54
57
  });
55
58
 
56
59
  useEffect(() => {
@@ -165,7 +165,7 @@ function ChatSidebarSessionDisplayView({
165
165
  className="ml-auto h-2 w-2 shrink-0 rounded-full bg-primary"
166
166
  />
167
167
  ) : (
168
- <span className="ml-auto shrink-0">{formatDateShort(session.updatedAt)}</span>
168
+ <span className="ml-auto shrink-0">{formatDateShort(session.lastMessageAt ?? session.createdAt)}</span>
169
169
  )}
170
170
  </div>
171
171
  </button>
@@ -273,8 +273,8 @@ export function ChatConversationPanel({
273
273
  resolveDraftAgent(snapshot.agentId ?? "main"),
274
274
  defaultSessionType,
275
275
  );
276
- const sessionKey = presenter.chatSessionListManager.createSession(sessionType);
277
- if (layoutMode === "mobile") presenter.chatUiManager.goToSession(sessionKey);
276
+ presenter.chatSessionListManager.createSession(sessionType);
277
+ if (layoutMode === "mobile") presenter.chatUiManager.goToChatRoot();
278
278
  };
279
279
  const selectDraftAgent = (agentId: string) => {
280
280
  presenter.chatSessionListManager.setSelectedAgentId(agentId);
@@ -341,6 +341,80 @@ describe('ChatSidebar create and list basics', () => {
341
341
  });
342
342
  });
343
343
 
344
+ describe('ChatSidebar activity ordering', () => {
345
+ beforeEach(resetSidebarTestState);
346
+
347
+ it('orders sessions by last message time and ignores metadata updatedAt changes', () => {
348
+ mocks.sessionItems = [
349
+ createSessionItem({
350
+ key: 'session:message-newer',
351
+ createdAt: '2026-03-19T08:00:00.000Z',
352
+ updatedAt: '2026-03-19T09:00:00.000Z',
353
+ lastMessageAt: '2026-03-19T09:00:00.000Z',
354
+ label: 'Message Newer',
355
+ sessionType: 'native',
356
+ sessionTypeMutable: false,
357
+ messageCount: 1
358
+ }),
359
+ createSessionItem({
360
+ key: 'session:metadata-newer',
361
+ createdAt: '2026-03-19T07:00:00.000Z',
362
+ updatedAt: '2026-03-19T12:00:00.000Z',
363
+ lastMessageAt: '2026-03-19T08:00:00.000Z',
364
+ label: 'Metadata Newer',
365
+ sessionType: 'native',
366
+ sessionTypeMutable: false,
367
+ messageCount: 1
368
+ })
369
+ ];
370
+
371
+ render(
372
+ <MemoryRouter>
373
+ <ChatSidebar />
374
+ </MemoryRouter>
375
+ );
376
+
377
+ const messageNewer = screen.getByText('Message Newer');
378
+ const metadataNewer = screen.getByText('Metadata Newer');
379
+
380
+ expect(messageNewer.compareDocumentPosition(metadataNewer) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
381
+ });
382
+
383
+ it('uses createdAt for sorting sessions without messages', () => {
384
+ mocks.sessionItems = [
385
+ createSessionItem({
386
+ key: 'session:created-older',
387
+ createdAt: '2026-03-19T08:00:00.000Z',
388
+ updatedAt: '2026-03-19T12:00:00.000Z',
389
+ label: 'Created Older',
390
+ sessionType: 'native',
391
+ sessionTypeMutable: false,
392
+ messageCount: 0
393
+ }),
394
+ createSessionItem({
395
+ key: 'session:created-newer',
396
+ createdAt: '2026-03-19T09:00:00.000Z',
397
+ updatedAt: '2026-03-19T10:00:00.000Z',
398
+ label: 'Created Newer',
399
+ sessionType: 'native',
400
+ sessionTypeMutable: false,
401
+ messageCount: 0
402
+ })
403
+ ];
404
+
405
+ render(
406
+ <MemoryRouter>
407
+ <ChatSidebar />
408
+ </MemoryRouter>
409
+ );
410
+
411
+ const createdNewer = screen.getByText('Created Newer');
412
+ const createdOlder = screen.getByText('Created Older');
413
+
414
+ expect(createdNewer.compareDocumentPosition(createdOlder) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
415
+ });
416
+ });
417
+
344
418
  describe('ChatSidebar project-first mode', () => {
345
419
  beforeEach(resetSidebarTestState);
346
420
 
@@ -40,12 +40,12 @@ type DateGroup = {
40
40
  items: NcpSessionListItemView[];
41
41
  };
42
42
 
43
- function getSessionUpdatedAtTimestamp(item: NcpSessionListItemView): number {
44
- return new Date(item.session.updatedAt).getTime();
43
+ function getSessionActivityAtTimestamp(item: NcpSessionListItemView): number {
44
+ return new Date(item.session.lastMessageAt ?? item.session.createdAt).getTime();
45
45
  }
46
46
 
47
- function sortSessionItemsByUpdatedAtDesc(items: NcpSessionListItemView[]): NcpSessionListItemView[] {
48
- return [...items].sort((left, right) => getSessionUpdatedAtTimestamp(right) - getSessionUpdatedAtTimestamp(left));
47
+ function sortSessionItemsByActivityAtDesc(items: NcpSessionListItemView[]): NcpSessionListItemView[] {
48
+ return [...items].sort((left, right) => getSessionActivityAtTimestamp(right) - getSessionActivityAtTimestamp(left));
49
49
  }
50
50
 
51
51
  function groupSessionsByDate(items: NcpSessionListItemView[]): DateGroup[] {
@@ -60,8 +60,7 @@ function groupSessionsByDate(items: NcpSessionListItemView[]): DateGroup[] {
60
60
  const older: NcpSessionListItemView[] = [];
61
61
 
62
62
  for (const item of items) {
63
- const { session } = item;
64
- const ts = new Date(session.updatedAt).getTime();
63
+ const ts = getSessionActivityAtTimestamp(item);
65
64
  if (ts >= todayStart) {
66
65
  today.push(item);
67
66
  } else if (ts >= yesterdayStart) {
@@ -90,7 +89,7 @@ function groupSessionsByProject(items: NcpSessionListItemView[]): ChatSidebarPro
90
89
  continue;
91
90
  }
92
91
  const existingGroup = grouped.get(projectRoot);
93
- const updatedAt = getSessionUpdatedAtTimestamp(item);
92
+ const updatedAt = getSessionActivityAtTimestamp(item);
94
93
  if (existingGroup) {
95
94
  existingGroup.items.push(item);
96
95
  existingGroup.latestUpdatedAt = Math.max(existingGroup.latestUpdatedAt, updatedAt);
@@ -107,11 +106,28 @@ function groupSessionsByProject(items: NcpSessionListItemView[]): ChatSidebarPro
107
106
  return [...grouped.values()]
108
107
  .map((group) => ({
109
108
  ...group,
110
- items: sortSessionItemsByUpdatedAtDesc(group.items)
109
+ items: sortSessionItemsByActivityAtDesc(group.items)
111
110
  }))
112
111
  .sort((left, right) => right.latestUpdatedAt - left.latestUpdatedAt);
113
112
  }
114
113
 
114
+ function groupChildSessionsByParentKey(items: NcpSessionListItemView[]): Map<string, NcpSessionListItemView[]> {
115
+ const grouped = new Map<string, NcpSessionListItemView[]>();
116
+ for (const item of items) {
117
+ const parentSessionKey = item.session.parentSessionId?.trim();
118
+ if (!parentSessionKey) {
119
+ continue;
120
+ }
121
+ const bucket = grouped.get(parentSessionKey) ?? [];
122
+ bucket.push(item);
123
+ grouped.set(parentSessionKey, bucket);
124
+ }
125
+ for (const bucket of grouped.values()) {
126
+ bucket.sort((left, right) => getSessionActivityAtTimestamp(right) - getSessionActivityAtTimestamp(left));
127
+ }
128
+ return grouped;
129
+ }
130
+
115
131
  function sessionTitle(session: SessionEntryView): string {
116
132
  if (session.label && session.label.trim()) {
117
133
  return session.label.trim();
@@ -183,23 +199,8 @@ export function ChatSidebar({
183
199
  () => new Map((agentsQuery.data?.agents ?? []).map((agent) => [agent.id, agent])),
184
200
  [agentsQuery.data?.agents]
185
201
  );
186
- const sortedItems = useMemo(() => sortSessionItemsByUpdatedAtDesc(items), [items]);
187
- const childSessionsByParentKey = useMemo(() => {
188
- const grouped = new Map<string, NcpSessionListItemView[]>();
189
- for (const item of items) {
190
- const parentSessionKey = item.session.parentSessionId?.trim();
191
- if (!parentSessionKey) {
192
- continue;
193
- }
194
- const bucket = grouped.get(parentSessionKey) ?? [];
195
- bucket.push(item);
196
- grouped.set(parentSessionKey, bucket);
197
- }
198
- for (const bucket of grouped.values()) {
199
- bucket.sort((left, right) => getSessionUpdatedAtTimestamp(right) - getSessionUpdatedAtTimestamp(left));
200
- }
201
- return grouped;
202
- }, [items]);
202
+ const sortedItems = useMemo(() => sortSessionItemsByActivityAtDesc(items), [items]);
203
+ const childSessionsByParentKey = useMemo(() => groupChildSessionsByParentKey(items), [items]);
203
204
  const groups = useMemo(() => groupSessionsByDate(sortedItems), [sortedItems]);
204
205
  const projectGroups = useMemo(() => groupSessionsByProject(sortedItems), [sortedItems]);
205
206
  const defaultSessionType = inputSnapshot.defaultSessionType || 'native';
@@ -248,10 +249,8 @@ export function ChatSidebar({
248
249
  />
249
250
  );
250
251
  const createSessionAndOpenIfNeeded = (sessionType: string, projectRoot?: string | null) => {
251
- const sessionKey = typeof projectRoot === "string"
252
- ? presenter.chatSessionListManager.createSession(sessionType, projectRoot)
253
- : presenter.chatSessionListManager.createSession(sessionType);
254
- if (isMobileVariant) presenter.chatUiManager.goToSession(sessionKey);
252
+ presenter.chatSessionListManager.createSession(sessionType, typeof projectRoot === "string" ? projectRoot : undefined);
253
+ if (isMobileVariant) presenter.chatUiManager.goToChatRoot();
255
254
  };
256
255
 
257
256
  return (
@@ -7,6 +7,11 @@ const mocks = vi.hoisted(() => ({
7
7
  manager: {
8
8
  reset: vi.fn(),
9
9
  hydrate: vi.fn(),
10
+ getSnapshot: vi.fn(() => ({
11
+ messages: [],
12
+ streamingMessage: null,
13
+ activeRun: null,
14
+ })),
10
15
  },
11
16
  runtime: {
12
17
  snapshot: {
@@ -37,6 +42,7 @@ describe("useHydratedNcpAgent", () => {
37
42
  beforeEach(() => {
38
43
  mocks.manager.reset.mockReset();
39
44
  mocks.manager.hydrate.mockReset();
45
+ mocks.manager.getSnapshot.mockClear();
40
46
  mocks.runtime.send.mockReset();
41
47
  mocks.runtime.abort.mockReset();
42
48
  mocks.runtime.streamRun.mockReset();