@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.
- package/CHANGELOG.md +103 -0
- package/dist/assets/{api-lwyw9j7i.js → api-D2xRKmZd.js} +5 -5
- package/dist/assets/app-manager-provider-CNaZboG4.js +1 -0
- package/dist/assets/{app-navigation.config-DgiR0c5_.js → app-navigation.config-Ihhrrt--.js} +1 -1
- package/dist/assets/{book-open-DgLqYpNY.js → book-open-DDlN5MvX.js} +1 -1
- package/dist/assets/{channels-list-page-Dl839n02.js → channels-list-page-p26lgxLk.js} +2 -2
- package/dist/assets/{chat-DwUf7AKR.js → chat-Dkh2qtuz.js} +8 -8
- package/dist/assets/{chat-page-B-FvPmA7.js → chat-page-DoTmE2wx.js} +1 -1
- package/dist/assets/{chunk-JZWAC4HX-u4uYphxM.js → chunk-JZWAC4HX-Kydj4yEz.js} +1 -1
- package/dist/assets/{config-split-page-BMRGuCJQ.js → config-split-page-DIOCjj2Q.js} +1 -1
- package/dist/assets/{createLucideIcon-BZkY6emz.js → createLucideIcon-BLMK3QUd.js} +1 -1
- package/dist/assets/{desktop-update-config-D5g_gPak.js → desktop-update-config-DlpzDfKM.js} +1 -1
- package/dist/assets/{dialog-CdtCU2xX.js → dialog-C3D7Be0p.js} +1 -1
- package/dist/assets/{dist-CuqvE--P.js → dist-CPlbUgwU.js} +1 -1
- package/dist/assets/{doc-browser-BUlCkZo2.js → doc-browser-C8FM5fC0.js} +1 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +1 -0
- package/dist/assets/{doc-browser-context-DfLHAWbG.js → doc-browser-context-BJuMaI3o.js} +1 -1
- package/dist/assets/{doc-browser-CzCV73NJ.js → doc-browser-p82AdNO-.js} +1 -1
- package/dist/assets/{es2015-yYU5Ad5w.js → es2015-xqN1slyW.js} +1 -1
- package/dist/assets/{external-link-Sw3ah_JD.js → external-link-DwfSfTLB.js} +1 -1
- package/dist/assets/{folder-D7-VTnkz.js → folder-CeJKPx5P.js} +1 -1
- package/dist/assets/{hash-zajSTDXZ.js → hash-BqxRTZW5.js} +1 -1
- package/dist/assets/i18n-DnTGDIRw.js +1 -0
- package/dist/assets/{index-Doxyk7L2.js → index-pBvbJ5Mt.js} +2 -2
- package/dist/assets/{key-round-CnI1mc9F.js → key-round-CJ5gDAAG.js} +1 -1
- package/dist/assets/loader-circle-fd-vQKtW.js +1 -0
- package/dist/assets/{logo-badge-BQgKnVtz.js → logo-badge-KAe-7d8c.js} +1 -1
- package/dist/assets/{logos-CqVm0q0W.js → logos-C4sYP1Vl.js} +1 -1
- package/dist/assets/marketplace-page-Cql0kDi-.js +1 -0
- package/dist/assets/{marketplace-page-CawcdL6Y.js → marketplace-page-m4P5g_Ht.js} +1 -1
- package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +1 -0
- package/dist/assets/{mcp-marketplace-page-DEGfJ_70.js → mcp-marketplace-page-ByzBQZcx.js} +1 -1
- package/dist/assets/message-square-z_osm9c0.js +1 -0
- package/dist/assets/{model-config-r-1RPSrZ.js → model-config-Dbr_0APb.js} +1 -1
- package/dist/assets/{notice-card-BPtCVEKW.js → notice-card-BFDbKQDA.js} +1 -1
- package/dist/assets/play-Dv6Nr1Ew.js +1 -0
- package/dist/assets/plus-D8eKFY7h.js +1 -0
- package/dist/assets/{popover-jbfQhYQh.js → popover-B86Dbfhf.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-gdk2lmRi.js → provider-scoped-model-input-DFm6N2f7.js} +1 -1
- package/dist/assets/{providers-list-DpISIr3M.js → providers-list-BJcLOjun.js} +1 -1
- package/dist/assets/{refresh-ccw-Bii4w8aB.js → refresh-ccw-ByVwmnN_.js} +1 -1
- package/dist/assets/{refresh-cw-BxojR62w.js → refresh-cw-PcqoYB3K.js} +1 -1
- package/dist/assets/remote-BOxo9iwd.js +1 -0
- package/dist/assets/{rotate-cw-1Xqa7LZ8.js → rotate-cw-BZ2JObNs.js} +1 -1
- package/dist/assets/runtime-config-page-CjLhnbSl.js +1 -0
- package/dist/assets/{save--BVI5wZX.js → save-euRxl8pI.js} +1 -1
- package/dist/assets/{search-vChioOoe.js → search-CLd7m0M7.js} +1 -1
- package/dist/assets/{search-config-BWqz8nqY.js → search-config-J4Htco-P.js} +1 -1
- package/dist/assets/{secrets-config-CjzSNg0Y.js → secrets-config-CUdERjco.js} +1 -1
- package/dist/assets/{select-Cw5Zkb1w.js → select-CJ0wbo3D.js} +1 -1
- package/dist/assets/{sessions-config-page-beoDPtII.js → sessions-config-page-DpK991fs.js} +2 -2
- package/dist/assets/{setting-row-Cjl2d40s.js → setting-row-D1Yygqp7.js} +1 -1
- package/dist/assets/{settings-CiRChctQ.js → settings-drbWqzA4.js} +1 -1
- package/dist/assets/skeleton-BK1SOSRA.js +1 -0
- package/dist/assets/{sparkles-D1ZKWdm4.js → sparkles-DVfeSVJQ.js} +1 -1
- package/dist/assets/{status-dot-Dv_hiUVa.js → status-dot-ChvPCib9.js} +1 -1
- package/dist/assets/{tabs-custom-CsACkVji.js → tabs-custom-Hia_ong0.js} +1 -1
- package/dist/assets/{tag-chip-CoWHxYJj.js → tag-chip-FrkmkT8r.js} +1 -1
- package/dist/assets/theme-provider-0hxjiPc_.js +2 -0
- package/dist/assets/{tooltip-GYzH-Hfq.js → tooltip-Cj4yA0gH.js} +1 -1
- package/dist/assets/{trash-2-rY9ZteZX.js → trash-2-CBsHCfqq.js} +1 -1
- package/dist/assets/{use-config-BhJHD3-G.js → use-config-38Ur-89i.js} +1 -1
- package/dist/assets/{use-confirm-dialog-Bqgy3Gi-.js → use-confirm-dialog-DPQThaeU.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-BfexitoF.js → use-infinite-scroll-loader-5Gf1xQi7.js} +1 -1
- package/dist/assets/{use-viewport-layout-D33zVbr5.js → use-viewport-layout-D1XzKeip.js} +1 -1
- package/dist/assets/x-CM-XDMpk.js +1 -0
- package/dist/index.html +39 -39
- package/package.json +9 -9
- package/src/features/account/hooks/use-auth.test.ts +7 -5
- package/src/features/account/hooks/use-auth.ts +23 -20
- package/src/features/chat/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +2 -2
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +74 -0
- package/src/features/chat/components/layout/chat-sidebar.tsx +28 -29
- package/src/features/chat/hooks/use-hydrated-ncp-agent.test.tsx +6 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +158 -69
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -2
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -7
- package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +10 -0
- package/src/features/chat/hooks/use-ncp-session-conversation.ts +2 -1
- package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +2 -4
- package/src/features/chat/managers/chat-session-list.manager.test.ts +19 -16
- package/src/features/chat/managers/chat-session-list.manager.ts +20 -24
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +23 -12
- package/src/features/chat/managers/ncp-chat-input.manager.ts +4 -2
- package/src/features/chat/pages/ncp-chat-page.tsx +23 -13
- package/src/features/chat/stores/chat-session-list.store.ts +2 -3
- package/src/features/chat/types/chat-stream.types.ts +1 -1
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +1 -1
- package/src/features/system-status/hooks/use-system-status.ts +6 -28
- package/src/features/system-status/index.ts +2 -1
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +14 -4
- package/src/features/system-status/managers/system-status.manager.test.ts +2 -8
- package/src/features/system-status/managers/system-status.manager.ts +20 -30
- package/src/shared/components/common/brand-header.test.tsx +84 -3
- package/src/shared/components/common/brand-header.tsx +37 -39
- package/src/shared/lib/api/managers/client.manager.ts +30 -2
- package/src/shared/lib/api/ncp-session-query-cache.test.ts +26 -1
- package/src/shared/lib/api/ncp-session-query-cache.ts +5 -1
- package/src/shared/lib/api/utils/config.utils.ts +6 -4
- package/src/shared/lib/i18n/desktop-update-labels.utils.ts +3 -1
- package/src/shared/lib/transport/index.ts +1 -0
- package/src/shared/lib/transport/transport.types.ts +20 -0
- package/dist/assets/app-manager-provider-C0ONQxUg.js +0 -1
- package/dist/assets/doc-browser-Doh2541x.js +0 -1
- package/dist/assets/i18n-C5Mibli1.js +0 -1
- package/dist/assets/loader-circle-B5i8oMMY.js +0 -1
- package/dist/assets/marketplace-page-BRHkZaO5.js +0 -1
- package/dist/assets/mcp-marketplace-page-CL7BF4dD.js +0 -1
- package/dist/assets/message-square-D6Z4NwpG.js +0 -1
- package/dist/assets/play-D8WJLnJe.js +0 -1
- package/dist/assets/plus-Di0KAkiO.js +0 -1
- package/dist/assets/remote-BnRNqMlb.js +0 -1
- package/dist/assets/runtime-config-page-DQ8YY8Lc.js +0 -1
- package/dist/assets/skeleton-CFQRIUzt.js +0 -1
- package/dist/assets/theme-provider-B5XReW_-.js +0 -1
- 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-
|
|
82
|
-
<link rel="modulepreload" crossorigin href="/assets/i18n-
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/chunk-JZWAC4HX-
|
|
84
|
-
<link rel="modulepreload" crossorigin href="/assets/api-
|
|
85
|
-
<link rel="modulepreload" crossorigin href="/assets/es2015-
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-
|
|
87
|
-
<link rel="modulepreload" crossorigin href="/assets/select-
|
|
88
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
89
|
-
<link rel="modulepreload" crossorigin href="/assets/x-
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
91
|
-
<link rel="modulepreload" crossorigin href="/assets/popover-
|
|
92
|
-
<link rel="modulepreload" crossorigin href="/assets/tooltip-
|
|
93
|
-
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-
|
|
94
|
-
<link rel="modulepreload" crossorigin href="/assets/use-config-
|
|
95
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-provider-
|
|
96
|
-
<link rel="modulepreload" crossorigin href="/assets/search-
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/book-open-
|
|
98
|
-
<link rel="modulepreload" crossorigin href="/assets/external-link-
|
|
99
|
-
<link rel="modulepreload" crossorigin href="/assets/folder-
|
|
100
|
-
<link rel="modulepreload" crossorigin href="/assets/logos-
|
|
101
|
-
<link rel="modulepreload" crossorigin href="/assets/loader-circle-
|
|
102
|
-
<link rel="modulepreload" crossorigin href="/assets/plus-
|
|
103
|
-
<link rel="modulepreload" crossorigin href="/assets/refresh-ccw-
|
|
104
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-
|
|
105
|
-
<link rel="modulepreload" crossorigin href="/assets/sparkles-
|
|
106
|
-
<link rel="modulepreload" crossorigin href="/assets/trash-2-
|
|
107
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-context-
|
|
108
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-
|
|
109
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-
|
|
110
|
-
<link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-
|
|
111
|
-
<link rel="modulepreload" crossorigin href="/assets/logo-badge-
|
|
112
|
-
<link rel="modulepreload" crossorigin href="/assets/skeleton-
|
|
113
|
-
<link rel="modulepreload" crossorigin href="/assets/chat-
|
|
114
|
-
<link rel="modulepreload" crossorigin href="/assets/key-round-
|
|
115
|
-
<link rel="modulepreload" crossorigin href="/assets/message-square-
|
|
116
|
-
<link rel="modulepreload" crossorigin href="/assets/app-navigation.config-
|
|
117
|
-
<link rel="modulepreload" crossorigin href="/assets/notice-card-
|
|
118
|
-
<link rel="modulepreload" crossorigin href="/assets/status-dot-
|
|
119
|
-
<link rel="modulepreload" crossorigin href="/assets/app-manager-provider-
|
|
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.
|
|
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.
|
|
32
|
-
"@nextclaw/agent-chat-ui": "0.3.
|
|
33
|
-
"@nextclaw/client-sdk": "0.1.
|
|
34
|
-
"@nextclaw/ncp": "0.5.
|
|
35
|
-
"@nextclaw/ncp-
|
|
36
|
-
"@nextclaw/
|
|
37
|
-
"@nextclaw/
|
|
38
|
-
"@nextclaw/
|
|
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(
|
|
27
|
-
expect(shouldRetryAuthStatusBootstrap(
|
|
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('
|
|
31
|
-
expect(
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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 >=
|
|
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({
|
|
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:
|
|
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.
|
|
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
|
-
|
|
277
|
-
if (layoutMode === "mobile") presenter.chatUiManager.
|
|
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
|
|
44
|
-
return new Date(item.session.
|
|
43
|
+
function getSessionActivityAtTimestamp(item: NcpSessionListItemView): number {
|
|
44
|
+
return new Date(item.session.lastMessageAt ?? item.session.createdAt).getTime();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
48
|
-
return [...items].sort((left, right) =>
|
|
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
|
|
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 =
|
|
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:
|
|
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(() =>
|
|
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
|
-
|
|
252
|
-
|
|
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();
|