@nextclaw/ui 0.12.23 → 0.12.25

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 (215) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/assets/api-DGD9_Bg4.js +15 -0
  3. package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
  4. package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
  5. package/dist/assets/channels-list-page-FJDuPwU6.js +8 -0
  6. package/dist/assets/chat-page-D1fMNBrT.js +1 -0
  7. package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
  8. package/dist/assets/cpu-DPPwMzoC.js +3 -0
  9. package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
  10. package/dist/assets/desktop-kk7qvZ-v.js +3 -0
  11. package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
  12. package/dist/assets/{dialog-dxsKz7jJ.js → dialog-BKo0RItd.js} +1 -1
  13. package/dist/assets/{dist-DsYTOyq7.js → dist-CFiwgaLs.js} +1 -1
  14. package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
  15. package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
  16. package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
  17. package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
  18. package/dist/assets/download-CMM8po31.js +1 -0
  19. package/dist/assets/{es2015-V75WQJ2s.js → es2015-BhznEEyJ.js} +1 -1
  20. package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
  21. package/dist/assets/i18n-D1144VAA.js +1 -0
  22. package/dist/assets/index-D-AAMKCt.js +103 -0
  23. package/dist/assets/index-DnBeV2Xm.css +1 -0
  24. package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
  25. package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
  26. package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
  27. package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
  28. package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
  29. package/dist/assets/model-config-Bc6VVnxy.js +1 -0
  30. package/dist/assets/{notice-card-D1RNsTn_.js → notice-card-Dr6xCwva.js} +1 -1
  31. package/dist/assets/play-AqrNslHI.js +1 -0
  32. package/dist/assets/plus-B-YHtTNC.js +1 -0
  33. package/dist/assets/{popover-BMyiifTA.js → popover-BDFNiLlg.js} +1 -1
  34. package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
  35. package/dist/assets/providers-list-DN0tvISH.js +1 -0
  36. package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
  37. package/dist/assets/remote-Dr3jcfWP.js +1 -0
  38. package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
  39. package/dist/assets/runtime-config-page-CRWOwBbl.js +1 -0
  40. package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
  41. package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
  42. package/dist/assets/search-config-C4c1yZSP.js +1 -0
  43. package/dist/assets/secrets-config-zAF30YfO.js +3 -0
  44. package/dist/assets/{select-DTdzR8j8.js → select-BUTwE_lC.js} +1 -1
  45. package/dist/assets/{setting-row-CvKngoNI.js → setting-row-BavcnXw1.js} +1 -1
  46. package/dist/assets/settings-MWL2SMyk.js +1 -0
  47. package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
  48. package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
  49. package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
  50. package/dist/assets/{tag-chip-BywQeHJj.js → tag-chip-Dm2Lqnpu.js} +1 -1
  51. package/dist/assets/use-config-Cyv5IuSt.js +1 -0
  52. package/dist/assets/use-infinite-scroll-loader-Cvz8ZteY.js +1 -0
  53. package/dist/assets/x-BeyYA_h6.js +1 -0
  54. package/dist/index.html +29 -40
  55. package/package.json +9 -9
  56. package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
  57. package/src/app/components/theme-provider.tsx +1 -0
  58. package/src/app/configs/app-navigation.config.ts +0 -6
  59. package/src/app/index.tsx +4 -7
  60. package/src/features/agents/components/agents-page.test.tsx +25 -15
  61. package/src/features/agents/components/agents-page.tsx +133 -172
  62. package/src/features/channels/components/config/channel-form.test.tsx +1 -0
  63. package/src/features/channels/components/config/channel-form.tsx +4 -3
  64. package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
  65. package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
  66. package/src/features/channels/index.ts +1 -1
  67. package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
  68. package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
  69. package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
  70. package/src/features/chat/components/chat-session-workspace-panel.tsx +34 -2
  71. package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
  72. package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
  73. package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
  74. package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
  75. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +56 -25
  76. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
  77. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +26 -5
  78. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
  79. package/src/features/chat/components/layout/chat-sidebar.test.tsx +119 -8
  80. package/src/features/chat/components/layout/chat-sidebar.tsx +57 -75
  81. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  82. package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
  83. package/src/features/chat/hooks/use-hydrated-ncp-agent.test.tsx +6 -0
  84. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +172 -69
  85. package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -2
  86. package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
  87. package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -7
  88. package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
  89. package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +10 -0
  90. package/src/features/chat/hooks/use-ncp-session-conversation.ts +2 -1
  91. package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
  92. package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +2 -4
  93. package/src/features/chat/managers/chat-session-list.manager.test.ts +21 -20
  94. package/src/features/chat/managers/chat-session-list.manager.ts +15 -24
  95. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +22 -13
  96. package/src/features/chat/managers/ncp-chat-input.manager.ts +4 -2
  97. package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -0
  98. package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
  99. package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
  100. package/src/features/chat/pages/ncp-chat-page.tsx +28 -17
  101. package/src/features/chat/stores/chat-session-list.store.ts +0 -3
  102. package/src/features/chat/stores/chat-thread.store.ts +4 -0
  103. package/src/features/chat/types/chat-stream.types.ts +1 -1
  104. package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
  105. package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
  106. package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
  107. package/src/features/chat/utils/ncp-session-adapter.utils.ts +33 -1
  108. package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
  109. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
  110. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
  111. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
  112. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
  113. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
  114. package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
  115. package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
  116. package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
  117. package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
  118. package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
  119. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  120. package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
  121. package/src/features/marketplace/components/marketplace-page.tsx +154 -132
  122. package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
  123. package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
  124. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
  125. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
  126. package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
  127. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
  128. package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
  129. package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
  130. package/src/features/system-status/components/runtime-control-card.tsx +7 -6
  131. package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
  132. package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
  133. package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
  134. package/src/features/system-status/utils/system-status.utils.ts +31 -6
  135. package/src/index.css +8 -0
  136. package/src/platforms/desktop/components/desktop-app-shell.test.tsx +67 -0
  137. package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
  138. package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
  139. package/src/platforms/desktop/index.ts +6 -0
  140. package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
  141. package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
  142. package/src/shared/components/common/brand-header.tsx +36 -16
  143. package/src/shared/components/config/provider-form-support.ts +2 -22
  144. package/src/shared/components/cron-config.tsx +12 -58
  145. package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
  146. package/src/shared/components/ui/select.tsx +19 -7
  147. package/src/shared/lib/api/channel-auth.types.ts +1 -0
  148. package/src/shared/lib/api/ncp-session-query-cache.test.ts +26 -1
  149. package/src/shared/lib/api/ncp-session-query-cache.ts +5 -1
  150. package/src/shared/lib/api/ncp-session.types.ts +9 -0
  151. package/src/shared/lib/api/types.ts +12 -1
  152. package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
  153. package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
  154. package/src/shared/lib/cron/index.ts +1 -0
  155. package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
  156. package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
  157. package/src/shared/lib/i18n/index.ts +20 -59
  158. package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
  159. package/src/shared/lib/provider-models/index.test.ts +39 -0
  160. package/src/shared/lib/provider-models/index.ts +1 -3
  161. package/src/shared/lib/ui-document-title/index.ts +0 -1
  162. package/tsconfig.json +1 -0
  163. package/vite.config.ts +1 -1
  164. package/vitest.config.ts +1 -1
  165. package/dist/assets/api-BGd3rgv_.js +0 -15
  166. package/dist/assets/app-manager-provider-BuJ_U9eC.js +0 -1
  167. package/dist/assets/app-navigation.config-BTdUuqXS.js +0 -1
  168. package/dist/assets/channels-list-page-BrwymXPe.js +0 -8
  169. package/dist/assets/chat-DGM6K3Qs.js +0 -61
  170. package/dist/assets/chat-page-DpmXMWNS.js +0 -1
  171. package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
  172. package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
  173. package/dist/assets/desktop-update-config-BGKiqc6q.js +0 -1
  174. package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
  175. package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
  176. package/dist/assets/doc-browser-p82AdNO-.js +0 -1
  177. package/dist/assets/folder-CeJKPx5P.js +0 -1
  178. package/dist/assets/hash-BqxRTZW5.js +0 -1
  179. package/dist/assets/i18n-DnTGDIRw.js +0 -1
  180. package/dist/assets/index-BrEdR78s.js +0 -2
  181. package/dist/assets/index-D8MKmXtO.css +0 -1
  182. package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
  183. package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
  184. package/dist/assets/logos-C4sYP1Vl.js +0 -1
  185. package/dist/assets/marketplace-page-B2Pm2RDJ.js +0 -1
  186. package/dist/assets/marketplace-page-CPHxlYL8.js +0 -49
  187. package/dist/assets/mcp-marketplace-page-BcjVmw36.js +0 -1
  188. package/dist/assets/mcp-marketplace-page-CswPXSjf.js +0 -40
  189. package/dist/assets/message-square-z_osm9c0.js +0 -1
  190. package/dist/assets/model-config-Cmruiqdx.js +0 -1
  191. package/dist/assets/play-Dv6Nr1Ew.js +0 -1
  192. package/dist/assets/plus-D8eKFY7h.js +0 -1
  193. package/dist/assets/provider-scoped-model-input-D7ACiMAO.js +0 -1
  194. package/dist/assets/providers-list-gg7LrfuB.js +0 -1
  195. package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
  196. package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
  197. package/dist/assets/remote-Db2M39Cv.js +0 -1
  198. package/dist/assets/runtime-config-page-BT_VV41p.js +0 -1
  199. package/dist/assets/search-config-0VTPpz-w.js +0 -1
  200. package/dist/assets/secrets-config-DwQbLLEy.js +0 -3
  201. package/dist/assets/sessions-config-page-CAG7Zevv.js +0 -2
  202. package/dist/assets/settings-drbWqzA4.js +0 -1
  203. package/dist/assets/skeleton-BK1SOSRA.js +0 -1
  204. package/dist/assets/theme-provider-COAwWFv8.js +0 -2
  205. package/dist/assets/tooltip-BOYp8Ue7.js +0 -1
  206. package/dist/assets/trash-2-CBsHCfqq.js +0 -1
  207. package/dist/assets/use-config-DTwhNDQE.js +0 -1
  208. package/dist/assets/use-confirm-dialog-oeSqhmrx.js +0 -1
  209. package/dist/assets/use-infinite-scroll-loader-X3KGuME8.js +0 -1
  210. package/dist/assets/use-viewport-layout-C0NJAVXs.js +0 -1
  211. package/dist/assets/x-CM-XDMpk.js +0 -1
  212. package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
  213. package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
  214. package/src/features/chat/pages/sessions-config-page.tsx +0 -192
  215. /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
@@ -7,10 +7,14 @@ const Select = SelectPrimitive.Root
7
7
  const SelectGroup = SelectPrimitive.Group
8
8
  const SelectValue = SelectPrimitive.Value
9
9
 
10
+ type SelectTriggerProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
11
+ indicator?: React.ReactNode
12
+ }
13
+
10
14
  const SelectTrigger = React.forwardRef<
11
15
  React.ElementRef<typeof SelectPrimitive.Trigger>,
12
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
13
- >(({ className, children, ...props }, ref) => (
16
+ SelectTriggerProps
17
+ >(({ className, children, indicator, ...props }, ref) => (
14
18
  <SelectPrimitive.Trigger
15
19
  ref={ref}
16
20
  className={cn(
@@ -21,7 +25,7 @@ const SelectTrigger = React.forwardRef<
21
25
  >
22
26
  {children}
23
27
  <SelectPrimitive.Icon asChild>
24
- <ChevronDown className="h-4 w-4 opacity-50" />
28
+ {indicator ?? <ChevronDown className="h-4 w-4 opacity-50" />}
25
29
  </SelectPrimitive.Icon>
26
30
  </SelectPrimitive.Trigger>
27
31
  ))
@@ -55,15 +59,19 @@ const SelectScrollDownButton = React.forwardRef<
55
59
  ))
56
60
  SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
57
61
 
62
+ type SelectContentProps = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & {
63
+ viewportClassName?: string
64
+ }
65
+
58
66
  const SelectContent = React.forwardRef<
59
67
  React.ElementRef<typeof SelectPrimitive.Content>,
60
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
61
- >(({ className, children, position = "popper", ...props }, ref) => (
68
+ SelectContentProps
69
+ >(({ className, children, position = "popper", viewportClassName, ...props }, ref) => (
62
70
  <SelectPrimitive.Portal>
63
71
  <SelectPrimitive.Content
64
72
  ref={ref}
65
73
  className={cn(
66
- "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-white",
74
+ "relative z-[var(--z-popover,50)] max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-white",
67
75
  position === "popper" &&
68
76
  "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
69
77
  className
@@ -73,7 +81,11 @@ const SelectContent = React.forwardRef<
73
81
  >
74
82
  <SelectScrollUpButton />
75
83
  <SelectPrimitive.Viewport
76
- className={cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}
84
+ className={cn(
85
+ "p-1",
86
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
87
+ viewportClassName
88
+ )}
77
89
  >
78
90
  {children}
79
91
  </SelectPrimitive.Viewport>
@@ -1,6 +1,7 @@
1
1
  export type ChannelAuthStartRequest = {
2
2
  accountId?: string;
3
3
  baseUrl?: string;
4
+ domain?: string;
4
5
  };
5
6
 
6
7
  export type ChannelAuthStartResult = {
@@ -14,14 +14,18 @@ function createSessionsList(): NcpSessionsListView {
14
14
  {
15
15
  sessionId: 'session-1',
16
16
  messageCount: 1,
17
+ createdAt: '2026-03-29T08:00:00.000Z',
17
18
  updatedAt: '2026-03-29T10:00:00.000Z',
19
+ lastMessageAt: '2026-03-29T10:00:00.000Z',
18
20
  status: 'idle',
19
21
  metadata: {}
20
22
  },
21
23
  {
22
24
  sessionId: 'session-2',
23
25
  messageCount: 2,
26
+ createdAt: '2026-03-29T07:00:00.000Z',
24
27
  updatedAt: '2026-03-29T09:00:00.000Z',
28
+ lastMessageAt: '2026-03-29T09:00:00.000Z',
25
29
  status: 'idle',
26
30
  metadata: {}
27
31
  }
@@ -31,11 +35,13 @@ function createSessionsList(): NcpSessionsListView {
31
35
  }
32
36
 
33
37
  describe('ncp-session-query-cache', () => {
34
- it('upserts summaries and keeps the list sorted by updatedAt descending', () => {
38
+ it('upserts summaries and keeps the list sorted by last message time descending', () => {
35
39
  const updated = upsertNcpSessionSummaryList(createSessionsList(), {
36
40
  sessionId: 'session-2',
37
41
  messageCount: 3,
42
+ createdAt: '2026-03-29T07:00:00.000Z',
38
43
  updatedAt: '2026-03-29T11:00:00.000Z',
44
+ lastMessageAt: '2026-03-29T11:00:00.000Z',
39
45
  status: 'running',
40
46
  metadata: { label: 'Latest' }
41
47
  });
@@ -48,6 +54,23 @@ describe('ncp-session-query-cache', () => {
48
54
  });
49
55
  });
50
56
 
57
+ it('does not reorder when only session metadata updatedAt changes', () => {
58
+ const updated = upsertNcpSessionSummaryList(createSessionsList(), {
59
+ sessionId: 'session-2',
60
+ messageCount: 2,
61
+ createdAt: '2026-03-29T07:00:00.000Z',
62
+ updatedAt: '2026-03-29T12:00:00.000Z',
63
+ lastMessageAt: '2026-03-29T09:00:00.000Z',
64
+ status: 'idle',
65
+ metadata: { ui_last_read_at: '2026-03-29T09:00:00.000Z' }
66
+ });
67
+
68
+ expect(updated?.sessions.map((session) => session.sessionId)).toEqual(['session-1', 'session-2']);
69
+ expect(updated?.sessions[1]?.metadata).toEqual({
70
+ ui_last_read_at: '2026-03-29T09:00:00.000Z'
71
+ });
72
+ });
73
+
51
74
  it('ignores stale summaries that would move a session back to an older running state', () => {
52
75
  const updated = upsertNcpSessionSummaryList(createSessionsList(), {
53
76
  sessionId: 'session-1',
@@ -128,7 +151,9 @@ describe('ncp-session-query-cache', () => {
128
151
  summary: {
129
152
  sessionId: 'session-3',
130
153
  messageCount: 1,
154
+ createdAt: '2026-03-29T12:00:00.000Z',
131
155
  updatedAt: '2026-03-29T12:00:00.000Z',
156
+ lastMessageAt: '2026-03-29T12:00:00.000Z',
132
157
  status: 'running',
133
158
  metadata: {}
134
159
  }
@@ -1,8 +1,12 @@
1
1
  import type { QueryClient } from '@tanstack/react-query';
2
2
  import type { NcpSessionSummaryView, NcpSessionsListView, WsEvent } from '@/shared/lib/api';
3
3
 
4
+ function readSessionActivityAt(summary: NcpSessionSummaryView): string {
5
+ return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
6
+ }
7
+
4
8
  function sortSessionSummaries(summaries: readonly NcpSessionSummaryView[]): NcpSessionSummaryView[] {
5
- return [...summaries].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
9
+ return [...summaries].sort((left, right) => readSessionActivityAt(right).localeCompare(readSessionActivityAt(left)));
6
10
  }
7
11
 
8
12
  function shouldReplaceSessionSummary(
@@ -31,6 +31,13 @@ export type SessionContextWindowView = {
31
31
  updatedAt: string;
32
32
  };
33
33
 
34
+ export type SessionActivityPreviewView = {
35
+ state: "running" | "completed" | "failed" | "idle";
36
+ timestamp: string;
37
+ statusText?: string;
38
+ replyText?: string;
39
+ };
40
+
34
41
  export type SessionEntryView = {
35
42
  key: string;
36
43
  createdAt: string;
@@ -52,9 +59,11 @@ export type SessionEntryView = {
52
59
  parentSessionId?: string | null;
53
60
  spawnedByRequestId?: string | null;
54
61
  contextWindow?: SessionContextWindowView | null;
62
+ activityPreview?: SessionActivityPreviewView;
55
63
  messageCount: number;
56
64
  lastRole?: string;
57
65
  lastTimestamp?: string;
66
+ status?: NcpSessionStatus;
58
67
  };
59
68
 
60
69
  export type SessionMessageView = {
@@ -1,5 +1,5 @@
1
1
  import type { NcpSessionStatus } from '@nextclaw/ncp';
2
- import type { SessionEntryView, RuntimeEntryView, SessionTypeIconView, SessionMessageView, SessionEventView, NcpSessionSummaryView } from './ncp-session.types';
2
+ import type { RuntimeEntryView, NcpSessionSummaryView } from './ncp-session.types';
3
3
  export type { SessionEntryView, RuntimeEntryView, SessionTypeIconView, SessionMessageView, SessionEventView, NcpSessionSummaryView, NcpSessionsListView, NcpMessageView, NcpSessionMessagesView, SessionContextWindowView } from './ncp-session.types';
4
4
 
5
5
  // API Types - matching backend response format
@@ -776,6 +776,17 @@ export type MarketplaceRecommendationView = {
776
776
  items: MarketplaceItemSummary[];
777
777
  };
778
778
 
779
+ export type MarketplaceSceneView = {
780
+ scene: string;
781
+ title: string;
782
+ description?: string;
783
+ count?: number;
784
+ };
785
+
786
+ export type MarketplaceScenesView = {
787
+ scenes: MarketplaceSceneView[];
788
+ };
789
+
779
790
  export type MarketplaceInstalledRecord = {
780
791
  type: MarketplaceItemType;
781
792
  id?: string;
@@ -1,4 +1,4 @@
1
- import { nextclawClient } from '../managers/client.manager';
1
+ import { nextclawClient } from '@/shared/lib/api/managers/client.manager';
2
2
  import type {
3
3
  MarketplaceInstallRequest,
4
4
  MarketplaceInstallResult,
@@ -11,6 +11,7 @@ import type {
11
11
  MarketplaceItemView,
12
12
  MarketplaceListView,
13
13
  MarketplaceRecommendationView,
14
+ MarketplaceScenesView,
14
15
  MarketplaceSort
15
16
  } from '@/shared/lib/api/types';
16
17
 
@@ -18,6 +19,7 @@ export type MarketplaceListParams = {
18
19
  type: MarketplaceItemType;
19
20
  q?: string;
20
21
  tag?: string;
22
+ scene?: string;
21
23
  sort?: MarketplaceSort;
22
24
  page?: number;
23
25
  pageSize?: number;
@@ -49,6 +51,10 @@ export async function fetchMarketplaceRecommendations(
49
51
  return await nextclawClient.marketplace.fetchRecommendations(type, params);
50
52
  }
51
53
 
54
+ export async function fetchMarketplaceSkillScenes(): Promise<MarketplaceScenesView> {
55
+ return await nextclawClient.marketplace.fetchSkillScenes();
56
+ }
57
+
52
58
  export async function installMarketplaceItem(request: MarketplaceInstallRequest): Promise<MarketplaceInstallResult> {
53
59
  return await nextclawClient.marketplace.install(
54
60
  request as Parameters<typeof nextclawClient.marketplace.install>[0]
@@ -0,0 +1,59 @@
1
+ import type { CronJobView } from "@/shared/lib/api";
2
+ import { formatDateTime } from "@/shared/lib/i18n";
3
+
4
+ export function formatCronDate(value?: string | null): string {
5
+ return formatDateTime(value ?? undefined);
6
+ }
7
+
8
+ function formatDateFromMs(value?: number | null): string {
9
+ if (typeof value !== "number" || !Number.isFinite(value)) {
10
+ return "-";
11
+ }
12
+ return formatDateTime(new Date(value));
13
+ }
14
+
15
+ function formatEveryDuration(ms?: number | null): string {
16
+ if (typeof ms !== "number" || !Number.isFinite(ms)) {
17
+ return "-";
18
+ }
19
+ const seconds = Math.round(ms / 1000);
20
+ if (seconds < 60) return `${seconds}s`;
21
+ const minutes = Math.round(seconds / 60);
22
+ if (minutes < 60) return `${minutes}m`;
23
+ const hours = Math.round(minutes / 60);
24
+ if (hours < 24) return `${hours}h`;
25
+ const days = Math.round(hours / 24);
26
+ return `${days}d`;
27
+ }
28
+
29
+ export function describeCronSchedule(job: CronJobView): string {
30
+ const { schedule } = job;
31
+ if (schedule.kind === "cron") {
32
+ return schedule.expr ? `cron ${schedule.expr}` : "cron";
33
+ }
34
+ if (schedule.kind === "every") {
35
+ return `every ${formatEveryDuration(schedule.everyMs)}`;
36
+ }
37
+ if (schedule.kind === "at") {
38
+ return `at ${formatDateFromMs(schedule.atMs)}`;
39
+ }
40
+ return "-";
41
+ }
42
+
43
+ export function describeCronDelivery(job: CronJobView): string {
44
+ if (!job.payload.deliver) {
45
+ return "-";
46
+ }
47
+ const channel = job.payload.channel ?? "-";
48
+ const target = job.payload.to ?? "-";
49
+ return `${channel}:${target}`;
50
+ }
51
+
52
+ export function describeCronSession(job: CronJobView): string {
53
+ return job.payload.sessionId?.trim() || `cron:${job.id}`;
54
+ }
55
+
56
+ export function isCronJobForSession(job: CronJobView, sessionKey: string | null | undefined): boolean {
57
+ const normalizedSessionKey = sessionKey?.trim();
58
+ return Boolean(normalizedSessionKey && job.payload.sessionId?.trim() === normalizedSessionKey);
59
+ }
@@ -0,0 +1 @@
1
+ export * from "./cron-job-view.utils";
@@ -38,5 +38,36 @@ export const CHANNEL_AUTH_LABELS: Record<string, { zh: string; en: string }> = {
38
38
  weixinAuthAdvancedDescription: {
39
39
  zh: '仅在你需要自定义接口地址、账号映射或白名单时再展开这些字段。',
40
40
  en: 'Expand these fields only when you need to customize the API base URL, account mapping, or allowlist.'
41
+ },
42
+ feishuAuthTitle: { zh: '扫码连接飞书', en: 'Connect Feishu by QR' },
43
+ feishuAuthDescription: { zh: '飞书渠道使用扫码创建自建应用,不再要求手动复制应用凭证。', en: 'Feishu uses QR-based app registration, so you no longer need to copy app credentials manually.' },
44
+ feishuAuthHint: {
45
+ zh: '点击按钮后用飞书扫码确认,连接成功后会自动保存应用凭证并启用 WebSocket 收发。',
46
+ en: 'Start the flow, scan with Feishu, and the app credentials will be saved automatically for WebSocket messaging.'
47
+ },
48
+ feishuAuthCapabilityHint: {
49
+ zh: '连接成功后,Agent 可以通过飞书自建应用收发私聊和群聊消息。',
50
+ en: 'After connecting, the agent can send and receive Feishu direct and group messages through the registered app.'
51
+ },
52
+ feishuAuthDisabledHint: {
53
+ zh: '当前飞书应用已完成扫码连接,但渠道处于未启用状态。打开 Enabled 后才会开始收发消息。',
54
+ en: 'This Feishu app is connected, but the channel is inactive. Turn on Enabled before it can send or receive messages.'
55
+ },
56
+ feishuAuthConnect: { zh: '扫码连接飞书', en: 'Scan QR to connect Feishu' },
57
+ feishuAuthQrAlt: { zh: '飞书应用注册二维码', en: 'Feishu app registration QR code' },
58
+ feishuAuthScanPrompt: { zh: '请用飞书扫码,并按页面提示完成应用创建授权。', en: 'Scan with Feishu and follow the prompts to finish app registration.' },
59
+ feishuAuthReadyTitle: { zh: '准备连接飞书', en: 'Ready to connect Feishu' },
60
+ feishuAuthReadyDescription: {
61
+ zh: '点击左侧按钮后,这里会显示应用注册二维码。首配流程默认不需要进入开放平台手动建应用。',
62
+ en: 'After you start the flow, the app registration QR code will appear here. First-time setup does not require manually creating an app in the developer console.'
63
+ },
64
+ feishuAuthAdvancedTitle: { zh: '高级设置', en: 'Advanced settings' },
65
+ feishuAuthAdvancedDescription: {
66
+ zh: '仅在你需要切换 Feishu/Lark 域名、指定默认账号、白名单或群聊策略时再展开这些字段。',
67
+ en: 'Expand these fields only when you need to switch Feishu/Lark domains, choose a default account, or adjust allowlist and group policies.'
68
+ },
69
+ feishuAuthDomain: {
70
+ zh: '飞书域名',
71
+ en: 'Feishu domain'
41
72
  }
42
73
  };
@@ -38,11 +38,10 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
38
38
  },
39
39
  chatSessionLabel: { zh: '当前会话', en: 'Current Session' },
40
40
  chatNoSession: { zh: '未选择会话', en: 'No session selected' },
41
- chatNoSessionHint: { zh: '创建一个会话并发送第一条消息。', en: 'Create a session and send your first message.' },
42
41
  chatHistoryLoading: { zh: '加载会话历史中...', en: 'Loading session history...' },
43
- chatNoMessages: { zh: '暂无消息,发送一条开始对话。', en: 'No messages yet. Send one to start.' },
44
42
  chatBackToParent: { zh: '返回父会话', en: 'Back to parent' },
45
43
  chatSessionOpenChildSessions: { zh: '查看子会话', en: 'View child sessions' },
44
+ chatSessionOpenCronJobs: { zh: '查看本会话定时任务', en: 'View session cron jobs' },
46
45
  chatChildSessionLoading: { zh: '正在加载子会话…', en: 'Loading child session…' },
47
46
  chatChildSessionEmpty: { zh: '子会话还没有消息。', en: 'No child session messages yet.' },
48
47
  chatWorkspaceClosePanel: { zh: '关闭工作区侧栏', en: 'Close workspace panel' },
@@ -56,6 +55,8 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
56
55
  chatWorkspacePreviewEmpty: { zh: '当前没有可显示的文件内容。', en: 'No file content is available for preview.' },
57
56
  chatWorkspaceDiffEmpty: { zh: '当前没有可显示的 diff 内容。', en: 'No diff content is available.' },
58
57
  chatWorkspacePreviewTruncated: { zh: '内容已截断', en: 'Preview truncated' },
58
+ chatWorkspaceSessionCronJobs: { zh: '本会话定时任务', en: 'Session cron jobs' },
59
+ chatWorkspaceCronJobEmpty: { zh: '这个会话暂无定时任务。', en: 'This session has no cron jobs.' },
59
60
  chatSessionUnread: { zh: '会话有未读更新', en: 'Session has unread updates' },
60
61
  chatTyping: { zh: 'Agent 正在思考...', en: 'Agent is thinking...' },
61
62
  chatRuntimeInitializing: {
@@ -1,6 +1,6 @@
1
1
  import { AGENT_LABELS } from './agents';
2
2
  import { CHANNEL_LABELS } from './channels';
3
- import { CHANNEL_AUTH_LABELS } from './channel-auth';
3
+ import { CHANNEL_AUTH_LABELS } from './channel-auth.constants';
4
4
  import { CHAT_LABELS } from './chat-labels.utils';
5
5
  import {
6
6
  getLanguage,
@@ -17,7 +17,7 @@ import { MARKETPLACE_LABELS } from './marketplace';
17
17
  import { PATH_PICKER_LABELS } from './runtime/i18n.path-picker';
18
18
  import { PWA_LABELS } from './pwa';
19
19
  import { REMOTE_LABELS } from './remote';
20
- import { RUNTIME_CONTROL_LABELS } from './runtime-control';
20
+ import { RUNTIME_CONTROL_LABELS } from './runtime-control-labels.utils';
21
21
  import { SEARCH_LABELS } from './search';
22
22
  export type { I18nLanguage };
23
23
  export { getLanguage, getLocale, initializeI18n, LANGUAGE_OPTIONS, resolveInitialLanguage, setLanguage, subscribeLanguageChange };
@@ -113,7 +113,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
113
113
  en: 'If the model is not listed, type a custom model ID directly.'
114
114
  },
115
115
  modelIdentifierHelp: {
116
- zh: 'Agent 默认模型标识,使用带 provider 前缀的格式。例如:openai/gpt-5.1、anthropic/claude-opus-4-1、deepseek/deepseek-chat、minimax/MiniMax-M2.5、openrouter/openai/gpt-5.3-codex。',
116
+ zh: '智能体默认模型标识,使用带提供商前缀的格式。例如:openai/gpt-5.1、anthropic/claude-opus-4-1、deepseek/deepseek-chat、minimax/MiniMax-M2.5、openrouter/openai/gpt-5.3-codex。',
117
117
  en: 'Default model identifier used by the agent. Use provider-prefixed format. Examples: openai/gpt-5.1 · anthropic/claude-opus-4-1 · deepseek/deepseek-chat · minimax/MiniMax-M2.5 · openrouter/openai/gpt-5.3-codex.'
118
118
  },
119
119
  maxToolIterations: { zh: '最大工具迭代次数', en: 'Max Tool Iterations' },
@@ -132,7 +132,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
132
132
  providersSelectPlaceholder: { zh: '选择提供商', en: 'Select Provider' },
133
133
  providersSelectTitle: { zh: '选择左侧提供商开始配置', en: 'Select a provider from the left to configure' },
134
134
  providersSelectDescription: { zh: '你可以连续切换多个提供商并逐个保存配置。', en: 'Switch between providers continuously and save each configuration.' },
135
- providersDefaultDescription: { zh: '为你的 Agent 配置 AI 服务', en: 'Configure AI services for your agents' },
135
+ providersDefaultDescription: { zh: '为你的智能体配置 AI 服务', en: 'Configure AI services for your agents' },
136
136
  providersEmptyTitle: { zh: '尚未配置提供商', en: 'No providers configured' },
137
137
  providersEmptyDescription: { zh: '添加一个 AI 提供商后即可开始使用。', en: 'Add an AI provider to start using the platform.' },
138
138
  apiKey: { zh: 'API 密钥', en: 'API Key' },
@@ -346,32 +346,32 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
346
346
  authSessionMemoryNotice: { zh: '当前版本的会话只保存在服务端内存里。NextClaw UI 进程重启后,需要重新登录。', en: 'Sessions are stored only in server memory for now. You will need to sign in again after the NextClaw UI process restarts.' },
347
347
  dmScope: { zh: '私聊范围', en: 'DM Scope' },
348
348
  dmScopeHelp: { zh: '控制私聊会话如何隔离。', en: 'Control how direct-message sessions are isolated.' },
349
- defaultContextTokens: { zh: '默认上下文 Token', en: 'Default Context Tokens' },
350
- defaultContextTokensHelp: { zh: '当 Agent 未设置单独值时使用该上下文预算。', en: 'Input context budget for agents when no per-agent override is set.' },
349
+ defaultContextTokens: { zh: '默认上下文额度', en: 'Default Context Tokens' },
350
+ defaultContextTokensHelp: { zh: '当智能体未设置单独值时使用该上下文预算。', en: 'Input context budget for agents when no per-agent override is set.' },
351
351
  runtimeCompanionEnabled: { zh: '桌宠 Companion', en: 'Companion' },
352
352
  runtimeCompanionEnabledHelp: { zh: '开启后会自动拉起悬浮 Companion;关闭后会立即停止,并在下次启动时保持关闭。', en: 'When enabled, NextClaw auto-starts the floating companion. When disabled, it stops immediately and stays off after restart.' },
353
- defaultEngine: { zh: '默认 Runtime', en: 'Default Runtime' },
354
- defaultEngineHelp: { zh: '默认使用的 Agent Runtime,例如 native、codex 或 claude。', en: 'Default agent runtime, for example native, codex, or claude.' },
355
- agentList: { zh: 'Agent 列表', en: 'Agent List' },
356
- agentListHelp: { zh: '在同一个网关进程中运行多个固定角色 Agent。', en: 'Run multiple fixed-role agents in one gateway process.' },
353
+ defaultEngine: { zh: '默认运行时', en: 'Default Runtime' },
354
+ defaultEngineHelp: { zh: '默认使用的智能体运行时,例如 native、codex 或 claude。', en: 'Default agent runtime, for example native, codex, or claude.' },
355
+ agentList: { zh: '智能体列表', en: 'Agent List' },
356
+ agentListHelp: { zh: '在同一个网关进程中运行多个固定角色智能体。', en: 'Run multiple fixed-role agents in one gateway process.' },
357
357
  bindings: { zh: '绑定规则', en: 'Bindings' },
358
- bindingsHelp: { zh: '根据渠道 + 账号 + 对端将入站消息路由到目标 Agent。', en: 'Route inbound message by channel + account + peer to target agent.' },
358
+ bindingsHelp: { zh: '根据渠道 + 账号 + 对端将入站消息路由到目标智能体。', en: 'Route inbound message by channel + account + peer to target agent.' },
359
359
  agentIdRequiredError: { zh: 'agents.list[{index}].id 必填', en: 'agents.list[{index}].id is required' },
360
- duplicateAgentId: { zh: '重复的 agent id', en: 'Duplicate agent id' },
360
+ duplicateAgentId: { zh: '重复的智能体 ID', en: 'Duplicate agent id' },
361
361
  bindingAgentIdRequired: { zh: 'bindings[{index}].agentId 必填', en: 'bindings[{index}].agentId is required' },
362
362
  bindingAgentIdNotFound: { zh: 'bindings[{index}].agentId 未在 agents.list/main 中找到', en: "bindings[{index}].agentId not found in agents.list/main" },
363
363
  bindingChannelRequired: { zh: 'bindings[{index}].match.channel 必填', en: 'bindings[{index}].match.channel is required' },
364
364
  bindingPeerIdRequired: { zh: '设置 peer.kind 时,bindings[{index}].match.peer.id 必填', en: 'bindings[{index}].match.peer.id is required when peer.kind is set' },
365
- agentIdPlaceholder: { zh: 'Agent ID(例如 engineer)', en: 'Agent ID (e.g. engineer)' },
365
+ agentIdPlaceholder: { zh: '智能体 ID(例如 engineer)', en: 'Agent ID (e.g. engineer)' },
366
366
  workspaceOverridePlaceholder: { zh: '工作空间覆盖(可选)', en: 'Workspace override (optional)' },
367
367
  modelOverridePlaceholder: { zh: '模型覆盖(可选)', en: 'Model override (optional)' },
368
- defaultEnginePlaceholder: { zh: '默认 Runtime(如 native 或 codex)', en: 'Default runtime (e.g. native or codex)' },
369
- engineOverridePlaceholder: { zh: 'Runtime 覆盖(可选)', en: 'Runtime override (optional)' },
370
- contextTokensPlaceholder: { zh: '上下文 tokens', en: 'Context tokens' },
368
+ defaultEnginePlaceholder: { zh: '默认运行时(如 native 或 codex)', en: 'Default runtime (e.g. native or codex)' },
369
+ engineOverridePlaceholder: { zh: '运行时覆盖(可选)', en: 'Runtime override (optional)' },
370
+ contextTokensPlaceholder: { zh: '上下文额度', en: 'Context tokens' },
371
371
  maxToolsPlaceholder: { zh: '最大工具次数', en: 'Max tools' },
372
- defaultAgent: { zh: '默认 Agent', en: 'Default agent' },
373
- addAgent: { zh: '添加 Agent', en: 'Add Agent' },
374
- targetAgentIdPlaceholder: { zh: '目标 Agent ID', en: 'Target agent ID' },
372
+ defaultAgent: { zh: '默认智能体', en: 'Default agent' },
373
+ addAgent: { zh: '添加智能体', en: 'Add Agent' },
374
+ targetAgentIdPlaceholder: { zh: '目标智能体 ID', en: 'Target agent ID' },
375
375
  channelPlaceholder: { zh: '渠道(例如 discord)', en: 'Channel (e.g. discord)' },
376
376
  accountIdOptionalPlaceholder: { zh: '账号 ID(可选)', en: 'Account ID (optional)' },
377
377
  peerKindOptional: { zh: '对端类型(可选)', en: 'Peer kind (optional)' },
@@ -415,51 +415,12 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
415
415
  secretProviderAlias: { zh: 'Provider 别名', en: 'Provider Alias' },
416
416
  addSecretRef: { zh: '添加 Ref', en: 'Add Ref' },
417
417
 
418
- // Sessions
419
- sessionsPageTitle: { zh: '会话管理', en: 'Sessions' },
420
- sessionsPageDescription: {
421
- zh: '管理会话:筛选、按渠道分组、查看历史、改标签/偏好模型、清空和删除。',
422
- en: 'Manage sessions: filter, group by channel, inspect history, edit metadata, clear, and delete.'
423
- },
424
- sessionsFiltersTitle: { zh: '筛选', en: 'Filters' },
425
- sessionsFiltersDescription: { zh: '按关键词、活跃窗口和分组方式筛选会话。', en: 'Filter sessions by query, activity window, and grouping mode.' },
426
- sessionsSearchPlaceholder: { zh: '搜索 key 或标签', en: 'Search session key or label' },
427
- sessionsActiveMinutesPlaceholder: { zh: '活跃分钟(0=不限)', en: 'Active minutes (0 = no limit)' },
428
- sessionsLimitPlaceholder: { zh: '展示上限', en: 'Limit' },
429
- sessionsGroupModeLabel: { zh: '分组方式', en: 'Grouping' },
430
- sessionsGroupModeAll: { zh: '不分组 / 全部', en: 'All (No grouping)' },
431
- sessionsGroupModeByChannel: { zh: '按渠道分组', en: 'Group by channel' },
432
- sessionsListTitle: { zh: '会话列表', en: 'Session list' },
433
- sessionsTotalLabel: { zh: '总数', en: 'Total' },
434
- sessionsCurrentLabel: { zh: '当前展示', en: 'Showing' },
418
+ // Session labels shared by the main chat workspace.
435
419
  sessionsLoading: { zh: '加载会话中...', en: 'Loading sessions...' },
436
420
  sessionsEmpty: { zh: '暂无会话。', en: 'No sessions yet.' },
437
- sessionsKeyLabel: { zh: '键', en: 'Key' },
438
- sessionsChannelLabel: { zh: '渠道', en: 'Channel' },
439
- sessionsMessagesLabel: { zh: '消息数', en: 'Messages' },
440
- sessionsUpdatedLabel: { zh: '更新时间', en: 'Updated' },
441
- sessionsLastRoleLabel: { zh: '最后角色', en: 'Last Role' },
442
421
  sessionsLabelPlaceholder: { zh: '会话标签(可选)', en: 'Session label (optional)' },
443
- sessionsModelPlaceholder: { zh: '偏好模型(可选)', en: 'Preferred model (optional)' },
444
- sessionsShowHistory: { zh: '查看历史', en: 'View history' },
445
- sessionsHideHistory: { zh: '隐藏历史', en: 'Hide history' },
446
- sessionsSaveMeta: { zh: '保存元信息', en: 'Save metadata' },
447
- sessionsClearHistory: { zh: '清空历史', en: 'Clear history' },
448
- sessionsDeleteConfirm: { zh: '确认删除会话', en: 'Delete session' },
449
- sessionsHistoryTitle: { zh: '历史', en: 'History' },
450
- sessionsHistoryDescription: { zh: '最近 200 条消息(展示窗口)。', en: 'Latest 200 messages (display window).' },
451
- sessionsHistoryLoading: { zh: '加载历史中...', en: 'Loading history...' },
452
- sessionsApplyingChanges: { zh: '正在应用会话变更...', en: 'Applying session changes...' },
453
- sessionsUnknownChannel: { zh: '未知渠道', en: 'Unknown channel' },
454
- sessionsAllChannels: { zh: '全部渠道', en: 'All Channels' },
455
422
  sessionsRunStatusRunning: { zh: '运行中', en: 'Running' },
456
423
  sessionsRunStatusQueued: { zh: '排队中', en: 'Queued' },
457
- sessionsMetadata: { zh: '元信息', en: 'Metadata' },
458
- sessionsNoSelectionTitle: { zh: '未选择会话', en: 'No Session Selected' },
459
- sessionsNoSelectionDescription: {
460
- zh: '从左侧列表选择一个会话以查看聊天历史并配置其元信息。',
461
- en: 'Select a session from the list on the left to view its chat history and configure its metadata.'
462
- },
463
424
  // Chat
464
425
  ...AGENT_LABELS,
465
426
  ...CHAT_LABELS,
@@ -1,9 +1,27 @@
1
1
  export const RUNTIME_CONTROL_LABELS: Record<string, { zh: string; en: string }> = {
2
2
  runtimePageTitle: { zh: '路由与运行时', en: 'Routing & Runtime' },
3
3
  runtimePageDescription: {
4
- zh: '对齐 OpenClaw 的多 Agent 路由:绑定规则、Agent 池、私聊范围。',
4
+ zh: '对齐 OpenClaw 的多智能体路由:绑定规则、智能体池、私聊范围。',
5
5
  en: 'Align multi-agent routing with OpenClaw: bindings, agent pool, and DM scope.'
6
6
  },
7
+ dmScopeMain: { zh: '统一到主会话', en: 'Main session' },
8
+ dmScopePerPeer: { zh: '按对端隔离', en: 'Per peer' },
9
+ dmScopePerChannelPeer: { zh: '按渠道和对端隔离', en: 'Per channel and peer' },
10
+ dmScopePerAccountChannelPeer: { zh: '按账号、渠道和对端隔离', en: 'Per account, channel, and peer' },
11
+ runtimeEntries: { zh: '运行时入口', en: 'Runtime Entries' },
12
+ runtimeEntriesHelp: { zh: '统一管理可见的运行时入口与其配置。', en: 'Manage visible runtime entries and their configuration in one place.' },
13
+ runtimeEntryIdPlaceholder: { zh: '入口 ID,例如 hermes', en: 'Entry ID, for example hermes' },
14
+ runtimeEntryLabelPlaceholder: { zh: '展示名称,例如 Hermes', en: 'Display name, for example Hermes' },
15
+ runtimeEntryTypePlaceholder: { zh: '运行时类型,例如 narp-stdio', en: 'Runtime type, for example narp-stdio' },
16
+ runtimeEntryConfigJson: { zh: '配置 JSON', en: 'Config JSON' },
17
+ addRuntimeEntry: { zh: '添加运行时入口', en: 'Add Runtime Entry' },
18
+ runtimeEntryIdRequired: { zh: '运行时入口 ID 必填,位置:{index}', en: 'Runtime entry id is required at index {index}.' },
19
+ runtimeEntryTypeRequired: { zh: '运行时入口 "{id}" 的类型必填', en: 'Runtime entry type is required for "{id}".' },
20
+ runtimeEntryDuplicate: { zh: '重复的运行时入口 ID', en: 'Duplicate runtime entry id' },
21
+ runtimeEntryConfigObjectRequired: { zh: '运行时入口 "{id}" 的配置必须是 JSON 对象。', en: 'Runtime entry config for "{id}" must be a JSON object.' },
22
+ peerKindDirect: { zh: '私聊', en: 'Direct' },
23
+ peerKindGroup: { zh: '群组', en: 'Group' },
24
+ peerKindChannel: { zh: '频道', en: 'Channel' },
7
25
  runtimeLoading: { zh: '加载运行时配置中...', en: 'Loading runtime settings...' },
8
26
  runtimeControlTitle: { zh: '服务管理', en: 'Service Management' },
9
27
  runtimeControlDescription: {
@@ -53,6 +71,17 @@ export const RUNTIME_CONTROL_LABELS: Record<string, { zh: string; en: string }>
53
71
  },
54
72
  runtimeControlLoading: { zh: '读取运行时控制能力中...', en: 'Loading runtime control capabilities...' },
55
73
  runtimeControlLoadFailed: { zh: '读取运行时控制状态失败', en: 'Failed to load runtime control state' },
74
+ runtimeControlManagedLocalMessage: { zh: '使用此页面管理本地 NextClaw 服务。关闭浏览器不会停止服务。', en: 'Use this page to manage the local NextClaw service. Closing the browser does not stop the service.' },
75
+ runtimeControlManagedLocalHint: { zh: '此页面由正在运行的本地服务提供。关闭浏览器不会停止它。', en: 'This page is served by the running local service. Closing the browser does not stop it.' },
76
+ runtimeControlManagedLocalHintShort: { zh: '此页面由正在运行的本地服务提供。', en: 'This page is served by the running local service.' },
77
+ runtimeControlStartUnavailableHosted: { zh: '当前页面已经由运行中的本地服务托管。', en: 'This page is already hosted by the running local service.' },
78
+ runtimeControlRestartAppDesktopOnly: { zh: '只有桌面壳环境支持重启整个应用。', en: 'App restart is only available in the desktop shell.' },
79
+ runtimeControlServiceNotRunning: { zh: '本地服务未运行。', en: 'The local service is not running.' },
80
+ runtimeControlServiceAlreadyStopped: { zh: '本地服务已经停止。', en: 'The local service is already stopped.' },
81
+ runtimeControlManagedServiceStarted: { zh: '本地服务已启动。', en: 'Managed service started.' },
82
+ runtimeControlManagedServiceStartScheduled: { zh: '本地服务启动已安排。', en: 'Managed service start scheduled.' },
83
+ runtimeControlRestartScheduled: { zh: '重启已安排。此页面可能会短暂断开。', en: 'Restart scheduled. This page may disconnect for a few seconds.' },
84
+ runtimeControlStopScheduled: { zh: '停止已安排。此页面即将断开。', en: 'Stop scheduled. This page will disconnect shortly.' },
56
85
  runtimeControlServiceRunning: { zh: '服务运行中', en: 'Service running' },
57
86
  runtimeControlServiceStopped: { zh: '服务已停止', en: 'Service stopped' },
58
87
  runtimeControlServiceStarting: { zh: '正在启动服务', en: 'Starting service' },
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildProviderModelCatalog } from './index';
3
+
4
+ describe('buildProviderModelCatalog', () => {
5
+ it('does not resurrect an OpenRouter model removed from config models', () => {
6
+ const catalog = buildProviderModelCatalog({
7
+ onlyConfigured: true,
8
+ meta: {
9
+ providers: [
10
+ {
11
+ name: 'openrouter',
12
+ displayName: 'OpenRouter',
13
+ modelPrefix: 'openrouter',
14
+ defaultModels: [
15
+ 'openrouter/deepseek/deepseek-v3.2',
16
+ 'openrouter/openai/gpt-5.3-codex'
17
+ ],
18
+ keywords: [],
19
+ envKey: 'OPENROUTER_API_KEY'
20
+ }
21
+ ],
22
+ search: [],
23
+ channels: []
24
+ },
25
+ config: {
26
+ providers: {
27
+ openrouter: {
28
+ enabled: true,
29
+ apiKeySet: true,
30
+ models: ['openai/gpt-5.3-codex']
31
+ }
32
+ }
33
+ } as never
34
+ });
35
+
36
+ expect(catalog).toHaveLength(1);
37
+ expect(catalog[0]?.models).toEqual(['openai/gpt-5.3-codex']);
38
+ });
39
+ });
@@ -189,11 +189,9 @@ export function buildProviderModelCatalog(params: {
189
189
  const providerConfig = config?.providers?.[spec.name];
190
190
  const prefix = (spec.modelPrefix || spec.name || '').trim();
191
191
  const aliases = normalizeStringList([spec.modelPrefix || '', spec.name || '']);
192
- const defaultModels = normalizeStringList((spec.defaultModels ?? []).map((model) => toProviderLocalModel(model, aliases)));
193
- const customModels = normalizeStringList(
192
+ const models = normalizeStringList(
194
193
  (providerConfig?.models ?? []).map((model) => toProviderLocalModel(model, aliases))
195
194
  );
196
- const models = normalizeStringList([...defaultModels, ...customModels]);
197
195
  const modelThinking = normalizeModelThinkingMap(providerConfig?.modelThinking, aliases);
198
196
  const configDisplayName = providerConfig?.displayName?.trim();
199
197
  const configured = isProviderConfigured(providerConfig);
@@ -19,7 +19,6 @@ const ROUTE_TITLE_KEYS: Array<{ prefix: string; key: string }> = [
19
19
  { prefix: '/updates', key: 'runtimeUpdatesPageTitle' },
20
20
  { prefix: '/remote', key: 'remotePageTitle' },
21
21
  { prefix: '/security', key: 'authSecurityTitle' },
22
- { prefix: '/sessions', key: 'sessionsPageTitle' },
23
22
  { prefix: '/secrets', key: 'secretsPageTitle' }
24
23
  ];
25
24
 
package/tsconfig.json CHANGED
@@ -34,6 +34,7 @@
34
34
  "@/*": ["./src/*"],
35
35
  "@nextclaw/agent-chat": ["../nextclaw-agent-chat/src/index.ts"],
36
36
  "@nextclaw/agent-chat-ui": ["../nextclaw-agent-chat-ui/src/index.ts"],
37
+ "@agent-chat-ui/*": ["../nextclaw-agent-chat-ui/src/*"],
37
38
  "@nextclaw/client-sdk": ["../nextclaw-client-sdk/src/index.ts"],
38
39
  "@nextclaw/shared": ["../nextclaw-shared/src/index.ts"],
39
40
  "@nextclaw/ncp": ["../ncp-packages/nextclaw-ncp/src/index.ts"],