@nextclaw/ui 0.12.25 → 0.12.27

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 (48) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/assets/{channels-list-page-FJDuPwU6.js → channels-list-page-DkPvpAqc.js} +1 -1
  3. package/dist/assets/chat-page-b7Zf32fF.js +1 -0
  4. package/dist/assets/{desktop-kk7qvZ-v.js → desktop-DVUbOWbR.js} +1 -1
  5. package/dist/assets/index-DmWo8dX2.css +1 -0
  6. package/dist/assets/{index-D-AAMKCt.js → index-DqJ3CYwi.js} +34 -37
  7. package/dist/assets/marketplace-page-BVqFjnEB.js +105 -0
  8. package/dist/assets/marketplace-page-DkQ2hTs1.js +1 -0
  9. package/dist/assets/{mcp-marketplace-page-DIq_SpMe.js → mcp-marketplace-page-BOYJO0kp.js} +1 -1
  10. package/dist/assets/mcp-marketplace-page-DSML7NN0.js +1 -0
  11. package/dist/assets/{model-config-Bc6VVnxy.js → model-config-Bg2yycmn.js} +1 -1
  12. package/dist/assets/{providers-list-DN0tvISH.js → providers-list-DC1q3fvC.js} +1 -1
  13. package/dist/assets/{runtime-config-page-CRWOwBbl.js → runtime-config-page-q-nC0C5i.js} +1 -1
  14. package/dist/assets/{search-config-C4c1yZSP.js → search-config-CcKHif8O.js} +1 -1
  15. package/dist/assets/{secrets-config-zAF30YfO.js → secrets-config-DSg6O92a.js} +1 -1
  16. package/dist/assets/{use-infinite-scroll-loader-Cvz8ZteY.js → use-infinite-scroll-loader-DF2e6nQ2.js} +1 -1
  17. package/dist/index.html +3 -3
  18. package/package.json +9 -9
  19. package/src/features/agents/components/agents-page.test.tsx +1 -1
  20. package/src/features/agents/components/agents-page.tsx +1 -1
  21. package/src/features/chat/components/chat-session-workspace-panel.tsx +31 -45
  22. package/src/features/chat/components/chat-sidebar-session-item.tsx +7 -9
  23. package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +5 -2
  24. package/src/features/chat/components/conversation/chat-conversation-header.tsx +2 -2
  25. package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +106 -78
  26. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +172 -167
  27. package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
  28. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +2 -2
  29. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  30. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +147 -88
  31. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -0
  32. package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
  33. package/src/features/chat/managers/ncp-chat-presenter.manager.ts +1 -0
  34. package/src/features/chat/pages/ncp-chat-page.tsx +4 -1
  35. package/src/features/chat/stores/chat-input.store.ts +3 -1
  36. package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
  37. package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +54 -16
  38. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +96 -24
  39. package/src/features/marketplace/components/marketplace-page.test.tsx +4 -0
  40. package/src/features/marketplace/components/marketplace-page.tsx +16 -12
  41. package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +14 -5
  42. package/src/platforms/desktop/components/desktop-app-shell.test.tsx +1 -0
  43. package/src/platforms/desktop/components/desktop-app-shell.tsx +1 -1
  44. package/dist/assets/chat-page-D1fMNBrT.js +0 -1
  45. package/dist/assets/index-DnBeV2Xm.css +0 -1
  46. package/dist/assets/marketplace-page-BrCLRIc4.js +0 -105
  47. package/dist/assets/marketplace-page-odDpPYEs.js +0 -1
  48. package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +0 -1
@@ -1 +1 @@
1
- import{_ as e,m as t,p as n,r}from"./i18n-D1144VAA.js";import{$t as i,Bt as a,Qt as o,Zt as s,en as c}from"./api-DGD9_Bg4.js";import{t as l}from"./createLucideIcon-DzY6wN61.js";import{a as u,i as d,n as f,r as p,t as m}from"./select-BUTwE_lC.js";import{_ as h}from"./index-D-AAMKCt.js";var g=class extends c{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:i()})}getOptimisticResult(e){return e.behavior=i(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:c,isRefetchError:l}=r,u=n.fetchMeta?.fetchMore?.direction,d=c&&u===`forward`,f=i&&u===`forward`,p=c&&u===`backward`,m=i&&u===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:s(t,n.data),hasPreviousPage:o(t,n.data),isFetchNextPageError:d,isFetchingNextPage:f,isFetchPreviousPageError:p,isFetchingPreviousPage:m,isRefetchError:l&&!d&&!p,isRefetching:a&&!f&&!m}}};function _(e,t){return a(e,g,t)}var v=l(`PackageSearch`,[[`path`,{d:`M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14`,key:`e7tb2h`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`line`,{x1:`12`,x2:`12`,y1:`22`,y2:`12`,key:`a4e8g8`}],[`circle`,{cx:`18.5`,cy:`15.5`,r:`2.5`,key:`b5zd12`}],[`path`,{d:`M20.27 17.27 22 19`,key:`1l4muz`}]]);function y(e){if(!e||e.pages.length===0)return;let t=e.pages.flatMap(e=>e.items);return{...e.pages[e.pages.length-1],items:t,pages:e.pages,loadedItems:t.length,loadedPages:e.pages.length}}var b=n();function x({scope:e,searchText:t,searchPlaceholder:n,sort:i,onSearchTextChange:a,onSortChange:o}){return(0,b.jsx)(`div`,{className:`mb-4`,children:(0,b.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,b.jsxs)(`div`,{className:`relative min-w-0 flex-1`,children:[(0,b.jsx)(v,{className:`absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400`}),(0,b.jsx)(`input`,{value:t,onChange:e=>a(e.target.value),placeholder:n,className:`h-9 w-full rounded-xl border border-gray-200/80 pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40`})]}),e===`all`&&(0,b.jsxs)(m,{value:i,onValueChange:e=>o(e),children:[(0,b.jsx)(d,{className:`h-9 w-[150px] shrink-0 rounded-lg`,children:(0,b.jsx)(u,{})}),(0,b.jsxs)(f,{children:[(0,b.jsx)(p,{value:`relevance`,children:r(`marketplaceSortRelevance`)}),(0,b.jsx)(p,{value:`updated`,children:r(`marketplaceSortUpdated`)})]})]})]})})}function S({count:e}){return(0,b.jsx)(b.Fragment,{children:Array.from({length:e},(e,t)=>(0,b.jsx)(`article`,{className:`h-full rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm`,children:(0,b.jsxs)(`div`,{className:`flex items-start justify-between gap-3.5`,children:[(0,b.jsxs)(`div`,{className:`flex min-w-0 flex-1 gap-3`,children:[(0,b.jsx)(h,{className:`h-10 w-10 shrink-0 rounded-xl`}),(0,b.jsxs)(`div`,{className:`min-w-0 flex-1 space-y-2 pt-0.5`,children:[(0,b.jsx)(h,{className:`h-4 w-32 max-w-[70%]`}),(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(h,{className:`h-3 w-12`}),(0,b.jsx)(h,{className:`h-3 w-24`})]}),(0,b.jsx)(h,{className:`h-3 w-full`})]})]}),(0,b.jsx)(h,{className:`h-8 w-20 shrink-0 rounded-xl`})]})},`marketplace-skeleton-${t}`))})}function C({hasMore:e,loading:t,sentinelRef:n}){return!e&&!t?null:(0,b.jsxs)(`div`,{className:`py-4`,children:[e&&(0,b.jsx)(`div`,{ref:n,className:`h-1 w-full`,"aria-hidden":`true`}),t&&(0,b.jsx)(`div`,{"data-testid":`marketplace-loading-more`,className:`pt-3 text-center text-xs text-gray-500`,children:r(`loading`)})]})}function w(e){let t=e.trim().toLowerCase().replace(/_/g,`-`),n=[t,t.split(`-`)[0],`en`];return Array.from(new Set(n.filter(Boolean)))}function T(e){return e.trim().toLowerCase().replace(/_/g,`-`)}function E(e,t,n){if(e){let t=Object.entries(e).map(([e,t])=>({locale:T(e),text:typeof t==`string`?t.trim():``})).filter(e=>e.text.length>0);if(t.length>0){let e=new Map(t.map(e=>[e.locale,e.text]));for(let t of n){let n=T(t),r=e.get(n);if(r)return r}for(let e of n){let n=T(e).split(`-`)[0];if(!n)continue;let r=t.find(e=>e.locale===n||e.locale.startsWith(`${n}-`));if(r)return r.text}return t[0]?.text??``}}return t?.trim()??``}function D(e,t){if(!e)return``;for(let n of t)if(T(n).split(`-`)[0]===`zh`&&e.descriptionZh?.trim())return e.descriptionZh.trim();return e.description?.trim()?e.description.trim():e.descriptionZh?.trim()?e.descriptionZh.trim():``}var O=e(t(),1),k=160;function A(e){let t=(0,O.useRef)(null),n=(0,O.useRef)(null),r=(0,O.useRef)(e.onLoadMore),i=(0,O.useRef)(!1);return(0,O.useEffect)(()=>{r.current=e.onLoadMore},[e.onLoadMore]),(0,O.useEffect)(()=>{e.disabled&&(i.current=!1)},[e.disabled]),(0,O.useEffect)(()=>{let a=t.current,o=n.current,s=e.thresholdPx??k;if(e.disabled||!a||!o)return;let c=()=>{i.current||e.disabled||(i.current=!0,Promise.resolve(r.current()).finally(()=>{i.current=!1}))},l=()=>{o.getBoundingClientRect().top-a.getBoundingClientRect().bottom<=s&&c()};if(typeof IntersectionObserver==`function`){let e=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&c()},{root:a,rootMargin:`0px 0px ${s}px 0px`});return e.observe(o),l(),()=>{e.disconnect()}}return a.addEventListener(`scroll`,l,{passive:!0}),l(),()=>{a.removeEventListener(`scroll`,l)}},[e.disabled,e.thresholdPx,e.watchValue]),{containerRef:t,sentinelRef:n}}export{x as a,y as c,E as i,_ as l,w as n,C as o,D as r,S as s,A as t};
1
+ import{_ as e,m as t,p as n,r}from"./i18n-D1144VAA.js";import{$t as i,Bt as a,Qt as o,Zt as s,en as c}from"./api-DGD9_Bg4.js";import{t as l}from"./createLucideIcon-DzY6wN61.js";import{a as u,i as d,n as f,r as p,t as m}from"./select-BUTwE_lC.js";import{u as h}from"./index-DqJ3CYwi.js";var g=class extends c{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:i()})}getOptimisticResult(e){return e.behavior=i(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:c,isRefetchError:l}=r,u=n.fetchMeta?.fetchMore?.direction,d=c&&u===`forward`,f=i&&u===`forward`,p=c&&u===`backward`,m=i&&u===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:s(t,n.data),hasPreviousPage:o(t,n.data),isFetchNextPageError:d,isFetchingNextPage:f,isFetchPreviousPageError:p,isFetchingPreviousPage:m,isRefetchError:l&&!d&&!p,isRefetching:a&&!f&&!m}}};function _(e,t){return a(e,g,t)}var v=l(`PackageSearch`,[[`path`,{d:`M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14`,key:`e7tb2h`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`line`,{x1:`12`,x2:`12`,y1:`22`,y2:`12`,key:`a4e8g8`}],[`circle`,{cx:`18.5`,cy:`15.5`,r:`2.5`,key:`b5zd12`}],[`path`,{d:`M20.27 17.27 22 19`,key:`1l4muz`}]]);function y(e){if(!e||e.pages.length===0)return;let t=e.pages.flatMap(e=>e.items);return{...e.pages[e.pages.length-1],items:t,pages:e.pages,loadedItems:t.length,loadedPages:e.pages.length}}var b=n();function x({scope:e,searchText:t,searchPlaceholder:n,sort:i,onSearchTextChange:a,onSortChange:o}){return(0,b.jsx)(`div`,{className:`mb-4`,children:(0,b.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,b.jsxs)(`div`,{className:`relative min-w-0 flex-1`,children:[(0,b.jsx)(v,{className:`absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400`}),(0,b.jsx)(`input`,{value:t,onChange:e=>a(e.target.value),placeholder:n,className:`h-9 w-full rounded-xl border border-gray-200/80 pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40`})]}),e===`all`&&(0,b.jsxs)(m,{value:i,onValueChange:e=>o(e),children:[(0,b.jsx)(d,{className:`h-9 w-[150px] shrink-0 rounded-lg`,children:(0,b.jsx)(u,{})}),(0,b.jsxs)(f,{children:[(0,b.jsx)(p,{value:`relevance`,children:r(`marketplaceSortRelevance`)}),(0,b.jsx)(p,{value:`updated`,children:r(`marketplaceSortUpdated`)})]})]})]})})}function S({count:e}){return(0,b.jsx)(b.Fragment,{children:Array.from({length:e},(e,t)=>(0,b.jsx)(`article`,{className:`h-full rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm`,children:(0,b.jsxs)(`div`,{className:`flex items-start justify-between gap-3.5`,children:[(0,b.jsxs)(`div`,{className:`flex min-w-0 flex-1 gap-3`,children:[(0,b.jsx)(h,{className:`h-10 w-10 shrink-0 rounded-xl`}),(0,b.jsxs)(`div`,{className:`min-w-0 flex-1 space-y-2 pt-0.5`,children:[(0,b.jsx)(h,{className:`h-4 w-32 max-w-[70%]`}),(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(h,{className:`h-3 w-12`}),(0,b.jsx)(h,{className:`h-3 w-24`})]}),(0,b.jsx)(h,{className:`h-3 w-full`})]})]}),(0,b.jsx)(h,{className:`h-8 w-20 shrink-0 rounded-xl`})]})},`marketplace-skeleton-${t}`))})}function C({hasMore:e,loading:t,sentinelRef:n}){return!e&&!t?null:(0,b.jsxs)(`div`,{className:`py-4`,children:[e&&(0,b.jsx)(`div`,{ref:n,className:`h-1 w-full`,"aria-hidden":`true`}),t&&(0,b.jsx)(`div`,{"data-testid":`marketplace-loading-more`,className:`pt-3 text-center text-xs text-gray-500`,children:r(`loading`)})]})}function w(e){let t=e.trim().toLowerCase().replace(/_/g,`-`),n=[t,t.split(`-`)[0],`en`];return Array.from(new Set(n.filter(Boolean)))}function T(e){return e.trim().toLowerCase().replace(/_/g,`-`)}function E(e,t,n){if(e){let t=Object.entries(e).map(([e,t])=>({locale:T(e),text:typeof t==`string`?t.trim():``})).filter(e=>e.text.length>0);if(t.length>0){let e=new Map(t.map(e=>[e.locale,e.text]));for(let t of n){let n=T(t),r=e.get(n);if(r)return r}for(let e of n){let n=T(e).split(`-`)[0];if(!n)continue;let r=t.find(e=>e.locale===n||e.locale.startsWith(`${n}-`));if(r)return r.text}return t[0]?.text??``}}return t?.trim()??``}function D(e,t){if(!e)return``;for(let n of t)if(T(n).split(`-`)[0]===`zh`&&e.descriptionZh?.trim())return e.descriptionZh.trim();return e.description?.trim()?e.description.trim():e.descriptionZh?.trim()?e.descriptionZh.trim():``}var O=e(t(),1),k=160;function A(e){let t=(0,O.useRef)(null),n=(0,O.useRef)(null),r=(0,O.useRef)(e.onLoadMore),i=(0,O.useRef)(!1);return(0,O.useEffect)(()=>{r.current=e.onLoadMore},[e.onLoadMore]),(0,O.useEffect)(()=>{e.disabled&&(i.current=!1)},[e.disabled]),(0,O.useEffect)(()=>{let a=t.current,o=n.current,s=e.thresholdPx??k;if(e.disabled||!a||!o)return;let c=()=>{i.current||e.disabled||(i.current=!0,Promise.resolve(r.current()).finally(()=>{i.current=!1}))},l=()=>{o.getBoundingClientRect().top-a.getBoundingClientRect().bottom<=s&&c()};if(typeof IntersectionObserver==`function`){let e=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&c()},{root:a,rootMargin:`0px 0px ${s}px 0px`});return e.observe(o),l(),()=>{e.disconnect()}}return a.addEventListener(`scroll`,l,{passive:!0}),l(),()=>{a.removeEventListener(`scroll`,l)}},[e.disabled,e.thresholdPx,e.watchValue]),{containerRef:t,sentinelRef:n}}export{x as a,y as c,E as i,_ as l,w as n,C as o,D as r,S as s,A as t};
package/dist/index.html CHANGED
@@ -78,7 +78,7 @@
78
78
  })();
79
79
  </script>
80
80
  <title>NextClaw</title>
81
- <script type="module" crossorigin src="/assets/index-D-AAMKCt.js"></script>
81
+ <script type="module" crossorigin src="/assets/index-DqJ3CYwi.js"></script>
82
82
  <link rel="modulepreload" crossorigin href="/assets/i18n-D1144VAA.js">
83
83
  <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-DzY6wN61.js">
84
84
  <link rel="modulepreload" crossorigin href="/assets/cpu-DPPwMzoC.js">
@@ -105,8 +105,8 @@
105
105
  <link rel="modulepreload" crossorigin href="/assets/doc-browser-rZIQIjuw.js">
106
106
  <link rel="modulepreload" crossorigin href="/assets/doc-browser-CAhfnm0D.js">
107
107
  <link rel="modulepreload" crossorigin href="/assets/use-config-Cyv5IuSt.js">
108
- <link rel="modulepreload" crossorigin href="/assets/desktop-kk7qvZ-v.js">
109
- <link rel="stylesheet" crossorigin href="/assets/index-DnBeV2Xm.css">
108
+ <link rel="modulepreload" crossorigin href="/assets/desktop-DVUbOWbR.js">
109
+ <link rel="stylesheet" crossorigin href="/assets/index-DmWo8dX2.css">
110
110
  </head>
111
111
 
112
112
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.12.25",
3
+ "version": "0.12.27",
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.15",
32
- "@nextclaw/agent-chat-ui": "0.3.17",
33
- "@nextclaw/client-sdk": "0.1.5",
34
- "@nextclaw/ncp": "0.5.10",
35
- "@nextclaw/ncp-http-agent-client": "0.3.22",
36
- "@nextclaw/ncp-react": "0.4.30",
37
- "@nextclaw/server": "0.12.17",
38
- "@nextclaw/shared": "0.1.4"
31
+ "@nextclaw/agent-chat": "0.1.16",
32
+ "@nextclaw/agent-chat-ui": "0.3.18",
33
+ "@nextclaw/ncp-http-agent-client": "0.3.23",
34
+ "@nextclaw/ncp": "0.5.11",
35
+ "@nextclaw/ncp-react": "0.4.31",
36
+ "@nextclaw/server": "0.12.18",
37
+ "@nextclaw/shared": "0.1.5",
38
+ "@nextclaw/client-sdk": "0.1.6"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@testing-library/react": "^16.3.0",
@@ -194,7 +194,7 @@ describe("AgentsPage", () => {
194
194
  expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
195
195
  expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
196
196
  expect(useChatInputStore.getState().snapshot.draft).toContain(
197
- "请帮我创建一个新的 Agent",
197
+ "请直接创建一个默认示例 Agent,不要问我问题",
198
198
  );
199
199
  expect(mocks.createAgent).not.toHaveBeenCalled();
200
200
  expect(screen.queryByText("创建新的 Agent 身份")).toBeNull();
@@ -44,7 +44,7 @@ import {
44
44
  } from "lucide-react";
45
45
 
46
46
  const AGENT_CREATION_PROMPT =
47
- "请帮我创建一个新的 Agent。先问我这个 Agent 的用途、名称、工作目录、默认 Runtime / 模型和需要的技能;信息足够后,请用 NextClaw 的 Agent 管理能力完成创建。";
47
+ "请直接创建一个默认示例 Agent,不要问我问题。创建完成后,简单告诉我它能做什么。";
48
48
 
49
49
  function AgentsHero(props: { agentCount: number; onCreate: () => void }) {
50
50
  const { agentCount, onCreate } = props;
@@ -34,6 +34,7 @@ import { t } from "@/shared/lib/i18n";
34
34
  import { cn } from "@/shared/lib/utils";
35
35
 
36
36
  type ChatSessionWorkspacePanelProps = {
37
+ sessionKey: string | null;
37
38
  childSessionTabs: readonly ChatChildSessionTab[];
38
39
  activeChildSessionKey: string | null;
39
40
  workspaceFileTabs: readonly ChatWorkspaceFileTab[];
@@ -42,14 +43,6 @@ type ChatSessionWorkspacePanelProps = {
42
43
  sessionCronJobs?: readonly CronJobView[];
43
44
  sessionProjectRoot: string | null;
44
45
  displayMode?: "docked" | "overlay";
45
- onSelectSession: (sessionKey: string) => void;
46
- onSelectFile: (fileKey: string) => void;
47
- onCloseFile: (fileKey: string) => void;
48
- onSelectCronJobs?: () => void;
49
- onClose: () => void;
50
- onBackToParent: () => void;
51
- onToolAction?: (action: ChatToolActionViewModel) => void;
52
- onFileOpen: (action: ChatFileOpenActionViewModel) => void;
53
46
  };
54
47
 
55
48
  function ChildSessionContent({
@@ -146,11 +139,7 @@ function ChildSessionMetaStrip({ tab }: { tab: ResolvedChildSessionTab }) {
146
139
  );
147
140
  }
148
141
 
149
- function WorkspaceActiveChildHeader({
150
- tab,
151
- }: {
152
- tab: ResolvedChildSessionTab;
153
- }) {
142
+ function WorkspaceActiveChildHeader({ tab }: { tab: ResolvedChildSessionTab }) {
154
143
  return (
155
144
  <div className="border-b border-gray-200/70 px-4 py-3">
156
145
  <div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
@@ -199,7 +188,7 @@ function buildWorkspaceTabsViewModel(params: {
199
188
  ? optimisticReadAt.localeCompare(tab.readAt) > 0
200
189
  ? optimisticReadAt
201
190
  : tab.readAt
202
- : optimisticReadAt ?? tab.readAt;
191
+ : (optimisticReadAt ?? tab.readAt);
203
192
  return {
204
193
  key: `child:${tab.sessionKey}`,
205
194
  kind: "child-session" as const,
@@ -228,28 +217,30 @@ function buildWorkspaceTabsViewModel(params: {
228
217
  tooltip: file.path,
229
218
  viewMode: file.viewMode,
230
219
  active:
231
- activeSelection?.kind === "file" &&
232
- activeSelection.file.key === file.key,
220
+ activeSelection?.kind === "file" && activeSelection.file.key === file.key,
233
221
  onSelect: () => onSelectFile(file.key),
234
222
  onClose: () => onCloseFile(file.key),
235
223
  }));
236
224
 
237
225
  const cronTab =
238
226
  sessionCronJobCount > 0
239
- ? [{
240
- key: "cron:session",
241
- kind: "cron" as const,
242
- title: t("chatWorkspaceSessionCronJobs"),
243
- tooltip: t("chatWorkspaceSessionCronJobs"),
244
- active: activeSelection?.kind === "cron",
245
- onSelect: onSelectCronJobs,
246
- }]
227
+ ? [
228
+ {
229
+ key: "cron:session",
230
+ kind: "cron" as const,
231
+ title: t("chatWorkspaceSessionCronJobs"),
232
+ tooltip: t("chatWorkspaceSessionCronJobs"),
233
+ active: activeSelection?.kind === "cron",
234
+ onSelect: onSelectCronJobs,
235
+ },
236
+ ]
247
237
  : [];
248
238
 
249
239
  return [...childTabs, ...fileTabs, ...cronTab];
250
240
  }
251
241
 
252
242
  export function ChatSessionWorkspacePanel({
243
+ sessionKey,
253
244
  childSessionTabs,
254
245
  activeChildSessionKey,
255
246
  workspaceFileTabs,
@@ -258,14 +249,6 @@ export function ChatSessionWorkspacePanel({
258
249
  sessionCronJobs = [],
259
250
  sessionProjectRoot,
260
251
  displayMode = "docked",
261
- onSelectSession,
262
- onSelectFile,
263
- onCloseFile,
264
- onSelectCronJobs = () => {},
265
- onClose,
266
- onBackToParent,
267
- onToolAction,
268
- onFileOpen,
269
252
  }: ChatSessionWorkspacePanelProps) {
270
253
  const presenter = usePresenter();
271
254
  const resolvedChildTabs = useNcpChildSessionTabsView(childSessionTabs);
@@ -307,19 +290,20 @@ export function ChatSessionWorkspacePanel({
307
290
  sessionCronJobCount: sessionCronJobs.length,
308
291
  activeSelection,
309
292
  optimisticReadAtBySessionKey,
310
- onSelectSession,
311
- onSelectFile,
312
- onCloseFile,
313
- onSelectCronJobs,
293
+ onSelectSession: presenter.chatThreadManager.selectChildSessionDetail,
294
+ onSelectFile: presenter.chatThreadManager.selectWorkspaceFile,
295
+ onCloseFile: presenter.chatThreadManager.closeWorkspaceFile,
296
+ onSelectCronJobs: () => {
297
+ if (sessionKey)
298
+ presenter.chatThreadManager.openSessionCronPanel(sessionKey);
299
+ },
314
300
  }),
315
301
  [
316
302
  activeSelection,
317
- onCloseFile,
318
- onSelectCronJobs,
319
- onSelectFile,
320
- onSelectSession,
321
303
  optimisticReadAtBySessionKey,
304
+ presenter.chatThreadManager,
322
305
  resolvedChildTabs,
306
+ sessionKey,
323
307
  workspaceFileTabs,
324
308
  sessionCronJobs.length,
325
309
  ],
@@ -341,7 +325,7 @@ export function ChatSessionWorkspacePanel({
341
325
  <div className="flex items-center justify-between gap-3 border-b border-gray-200/70 px-4 py-2.5">
342
326
  <button
343
327
  type="button"
344
- onClick={onBackToParent}
328
+ onClick={presenter.chatThreadManager.goToParentSession}
345
329
  className={cn(
346
330
  "inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900",
347
331
  !hasParentSession && "pointer-events-none opacity-0",
@@ -352,7 +336,7 @@ export function ChatSessionWorkspacePanel({
352
336
  </button>
353
337
  <button
354
338
  type="button"
355
- onClick={onClose}
339
+ onClick={presenter.chatThreadManager.closeWorkspacePanel}
356
340
  className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
357
341
  aria-label={t("chatWorkspaceClosePanel")}
358
342
  >
@@ -369,8 +353,10 @@ export function ChatSessionWorkspacePanel({
369
353
  <div className="flex-1 min-h-0">
370
354
  <ChildSessionContent
371
355
  sessionKey={activeSelection.tab.sessionKey}
372
- onToolAction={onToolAction}
373
- onFileOpen={onFileOpen}
356
+ onToolAction={
357
+ presenter.chatThreadManager.openSessionFromToolAction
358
+ }
359
+ onFileOpen={presenter.chatThreadManager.openFilePreview}
374
360
  />
375
361
  </div>
376
362
  </>
@@ -378,7 +364,7 @@ export function ChatSessionWorkspacePanel({
378
364
  <ChatSessionWorkspaceFilePreview
379
365
  file={activeSelection.file}
380
366
  sessionProjectRoot={sessionProjectRoot}
381
- onFileOpen={onFileOpen}
367
+ onFileOpen={presenter.chatThreadManager.openFilePreview}
382
368
  />
383
369
  ) : (
384
370
  <SessionCronJobContent jobs={sessionCronJobs} />
@@ -126,7 +126,7 @@ function ChatSidebarSessionDisplayView({
126
126
  return (
127
127
  <div className="group/session relative">
128
128
  <button type="button" onClick={onSelect} className="w-full text-left">
129
- <div className={cn('grid grid-cols-[minmax(0,1fr)_auto] items-start gap-2', trailingControlsClassName)}>
129
+ <div className={cn('flex min-w-0 items-start', trailingControlsClassName)}>
130
130
  <span className="flex min-w-0 items-center gap-1.5">
131
131
  {agentId?.trim() && agentId.trim().toLowerCase() !== 'main' ? (
132
132
  <AgentAvatar
@@ -155,11 +155,6 @@ function ChatSidebarSessionDisplayView({
155
155
  </span>
156
156
  ) : null}
157
157
  </span>
158
- {runStatus ? (
159
- <span className="inline-flex shrink-0 items-center justify-end gap-1.5 pt-0.5">
160
- <SessionRunBadge status={runStatus} />
161
- </span>
162
- ) : null}
163
158
  </div>
164
159
  <div className="mt-1 flex items-center gap-2 text-[11px] text-gray-400">
165
160
  <span className="min-w-0 truncate">
@@ -195,6 +190,11 @@ function ChatSidebarSessionDisplayView({
195
190
  <span>{childSessionCount}</span>
196
191
  </button>
197
192
  ) : null}
193
+ {runStatus ? (
194
+ <span className="absolute right-0 top-0 inline-flex h-5 w-5 items-center justify-center transition-opacity group-hover/session:opacity-0 group-focus-within/session:opacity-0">
195
+ <SessionRunBadge status={runStatus} />
196
+ </span>
197
+ ) : null}
198
198
  <button
199
199
  type="button"
200
200
  onClick={(event) => {
@@ -203,9 +203,7 @@ function ChatSidebarSessionDisplayView({
203
203
  }}
204
204
  className={cn(
205
205
  'absolute right-0 top-0 inline-flex h-5 w-5 items-center justify-center rounded-md text-gray-400 transition-all hover:bg-white hover:text-gray-900',
206
- active
207
- ? 'opacity-100'
208
- : 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
206
+ 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
209
207
  )}
210
208
  aria-label={t('edit')}
211
209
  >
@@ -48,12 +48,13 @@ function renderHeader(
48
48
  }
49
49
 
50
50
  describe("ChatConversationHeader", () => {
51
- it("does not reserve extra height for draft sessions", () => {
51
+ it("uses a stable desktop height before and after session materialization", () => {
52
52
  renderHeader({});
53
53
 
54
54
  const header = screen.getByText("New Task").closest(".border-b");
55
55
 
56
- expect(header?.className).not.toContain("min-h-");
56
+ expect(header?.className).toContain("h-[52px]");
57
+ expect(header?.className).not.toContain("transition-all");
57
58
  });
58
59
 
59
60
  it("uses the standard session-header action button density after the session is materialized", () => {
@@ -64,7 +65,9 @@ describe("ChatConversationHeader", () => {
64
65
  });
65
66
 
66
67
  const moreActions = screen.getByRole("button", { name: "More actions" });
68
+ const header = screen.getByText("First message").closest(".border-b");
67
69
 
70
+ expect(header?.className).toContain("h-[52px]");
68
71
  expect(moreActions.className).toContain("h-7");
69
72
  expect(moreActions.className).toContain("w-7");
70
73
  });
@@ -73,10 +73,10 @@ export function ChatConversationHeader({
73
73
  return (
74
74
  <div
75
75
  className={cn(
76
- "border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-all duration-200",
76
+ "border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-colors duration-200",
77
77
  isMobileLayout ? "px-3 sm:px-3" : "px-4 sm:px-5",
78
78
  shouldShowSessionHeader ? "opacity-100" : "h-0 py-0 opacity-0 border-b-0",
79
- shouldShowSessionHeader && (isMobileLayout ? "pb-2 pt-2" : "py-3"),
79
+ shouldShowSessionHeader && (isMobileLayout ? "min-h-12 pb-2 pt-2" : "h-[52px]"),
80
80
  )}
81
81
  style={
82
82
  isMobileLayout && shouldShowSessionHeader
@@ -48,33 +48,40 @@ vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
48
48
  };
49
49
  });
50
50
 
51
- vi.mock("@/features/chat/components/conversation/chat-input-bar.container", () => ({
52
- ChatInputBarContainer: () => <div data-testid="chat-input-bar" />,
53
- }));
51
+ vi.mock(
52
+ "@/features/chat/components/conversation/chat-input-bar.container",
53
+ () => ({
54
+ ChatInputBarContainer: () => <div data-testid="chat-input-bar" />,
55
+ }),
56
+ );
54
57
 
55
- vi.mock("@/features/chat/components/conversation/chat-message-list.container", () => ({
56
- ChatMessageListContainer: ({
57
- isSending,
58
- messages,
59
- }: {
60
- isSending: boolean;
61
- messages: readonly unknown[];
62
- }) => (
63
- <div
64
- data-testid="chat-message-list"
65
- data-message-count={String(messages.length)}
66
- data-sending={String(isSending)}
67
- />
68
- ),
69
- }));
58
+ vi.mock(
59
+ "@/features/chat/components/conversation/chat-message-list.container",
60
+ () => ({
61
+ ChatMessageListContainer: ({
62
+ isSending,
63
+ messages,
64
+ }: {
65
+ isSending: boolean;
66
+ messages: readonly unknown[];
67
+ }) => (
68
+ <div
69
+ data-testid="chat-message-list"
70
+ data-message-count={String(messages.length)}
71
+ data-sending={String(isSending)}
72
+ />
73
+ ),
74
+ }),
75
+ );
70
76
 
71
- vi.mock("@/features/chat/components/chat-session-workspace-file-preview", () => ({
72
- ChatSessionWorkspaceFilePreview: ({
73
- file,
74
- }: {
75
- file: { path: string };
76
- }) => <div data-testid="workspace-file-preview">{file.path}</div>,
77
- }));
77
+ vi.mock(
78
+ "@/features/chat/components/chat-session-workspace-file-preview",
79
+ () => ({
80
+ ChatSessionWorkspaceFilePreview: ({ file }: { file: { path: string } }) => (
81
+ <div data-testid="workspace-file-preview">{file.path}</div>
82
+ ),
83
+ }),
84
+ );
78
85
 
79
86
  vi.mock("@/features/chat/components/chat-welcome", () => ({
80
87
  ChatWelcome: ({
@@ -123,10 +130,9 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
123
130
  readAt: string | null | undefined,
124
131
  ) =>
125
132
  sessionKey
126
- ? useChatSessionListStore.getState().markSessionRead(
127
- sessionKey,
128
- readAt,
129
- )
133
+ ? useChatSessionListStore
134
+ .getState()
135
+ .markSessionRead(sessionKey, readAt)
130
136
  : undefined,
131
137
  },
132
138
  chatInputManager: {
@@ -136,42 +142,44 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
136
142
  }));
137
143
 
138
144
  vi.mock("@/shared/hooks/use-config", () => ({
139
- useCronJobs: () => ({ data: { jobs: mocks.cronJobs, total: mocks.cronJobs.length } }),
145
+ useCronJobs: () => ({
146
+ data: { jobs: mocks.cronJobs, total: mocks.cronJobs.length },
147
+ }),
140
148
  useDeleteCronJob: () => ({
141
149
  mutate: mocks.deleteCronJob,
142
150
  isPending: false,
143
151
  }),
144
152
  }));
145
153
 
146
- vi.mock("@/features/chat/components/conversation/session-header/chat-session-header-actions", () => ({
147
- ChatSessionHeaderActions: () => <button aria-label="More actions" />,
148
- }));
149
-
150
- vi.mock("@/features/chat/components/conversation/session-header/chat-session-project-badge", () => ({
151
- ChatSessionProjectBadge: ({ projectName }: { projectName: string }) => (
152
- <button>{projectName}</button>
153
- ),
154
- }));
155
-
156
154
  vi.mock(
157
- "@/features/chat/hooks/use-ncp-child-session-tabs-view",
155
+ "@/features/chat/components/conversation/session-header/chat-session-header-actions",
158
156
  () => ({
159
- useNcpChildSessionTabsView: () => mocks.resolvedChildTabs,
157
+ ChatSessionHeaderActions: () => <button aria-label="More actions" />,
160
158
  }),
161
159
  );
162
160
 
163
161
  vi.mock(
164
- "@/features/chat/hooks/use-ncp-session-conversation",
162
+ "@/features/chat/components/conversation/session-header/chat-session-project-badge",
165
163
  () => ({
166
- useNcpSessionConversation: () => ({
167
- visibleMessages: [],
168
- isHydrating: false,
169
- hydrateError: null,
170
- isRunning: false,
171
- }),
164
+ ChatSessionProjectBadge: ({ projectName }: { projectName: string }) => (
165
+ <button>{projectName}</button>
166
+ ),
172
167
  }),
173
168
  );
174
169
 
170
+ vi.mock("@/features/chat/hooks/use-ncp-child-session-tabs-view", () => ({
171
+ useNcpChildSessionTabsView: () => mocks.resolvedChildTabs,
172
+ }));
173
+
174
+ vi.mock("@/features/chat/hooks/use-ncp-session-conversation", () => ({
175
+ useNcpSessionConversation: () => ({
176
+ visibleMessages: [],
177
+ isHydrating: false,
178
+ hydrateError: null,
179
+ isRunning: false,
180
+ }),
181
+ }));
182
+
175
183
  vi.mock("@/shared/components/common/agent-avatar", () => ({
176
184
  AgentAvatar: ({ agentId }: { agentId: string }) => (
177
185
  <div data-testid="agent-avatar">{agentId}</div>
@@ -278,7 +286,9 @@ describe("ChatConversationPanel", () => {
278
286
 
279
287
  render(<ChatConversationPanel layoutMode="mobile" />);
280
288
 
281
- await user.click(screen.getByRole("button", { name: "create draft session" }));
289
+ await user.click(
290
+ screen.getByRole("button", { name: "create draft session" }),
291
+ );
282
292
 
283
293
  expect(mocks.createSession).toHaveBeenCalledWith("native");
284
294
  expect(mocks.goToChatRoot).toHaveBeenCalledTimes(1);
@@ -365,13 +375,47 @@ describe("ChatConversationPanel", () => {
365
375
  expect(
366
376
  screen.queryByRole("status", { name: "Loading session history..." }),
367
377
  ).toBeNull();
368
- expect(screen.queryByText("No messages yet. Send one to start.")).toBeNull();
378
+ expect(
379
+ screen.queryByText("No messages yet. Send one to start."),
380
+ ).toBeNull();
369
381
  });
370
382
 
371
383
  it("keeps the message list mounted while waiting for the first assistant token", () => {
372
384
  useChatThreadStore.setState({
373
385
  snapshot: {
374
386
  ...useChatThreadStore.getState().snapshot,
387
+ sessionKey: "session-1",
388
+ messages: [
389
+ {
390
+ id: "user-1",
391
+ sessionId: "session-1",
392
+ role: "user",
393
+ status: "final",
394
+ parts: [{ type: "text", text: "hello" }],
395
+ timestamp: "2026-05-19T00:00:00.000Z",
396
+ } as never,
397
+ ],
398
+ isSending: true,
399
+ isAwaitingAssistantOutput: true,
400
+ },
401
+ });
402
+
403
+ render(<ChatConversationPanel />);
404
+
405
+ expect(screen.getByTestId("chat-message-list").dataset).toMatchObject({
406
+ messageCount: "1",
407
+ sending: "true",
408
+ });
409
+ expect(
410
+ screen.queryByText("No messages yet. Send one to start."),
411
+ ).toBeNull();
412
+ });
413
+
414
+ it("does not show assistant waiting copy before the first message is visible", () => {
415
+ useChatThreadStore.setState({
416
+ snapshot: {
417
+ ...useChatThreadStore.getState().snapshot,
418
+ sessionKey: "session-1",
375
419
  messages: [],
376
420
  isSending: true,
377
421
  isAwaitingAssistantOutput: true,
@@ -380,8 +424,10 @@ describe("ChatConversationPanel", () => {
380
424
 
381
425
  render(<ChatConversationPanel />);
382
426
 
383
- expect(screen.getByTestId("chat-message-list").dataset).toMatchObject({ messageCount: "0", sending: "true" });
384
- expect(screen.queryByText("No messages yet. Send one to start.")).toBeNull();
427
+ expect(screen.queryByTestId("chat-message-list")).toBeNull();
428
+ expect(
429
+ screen.queryByText("No messages yet. Send one to start."),
430
+ ).toBeNull();
385
431
  });
386
432
 
387
433
  it("does not reopen the welcome panel after a root draft send fails", () => {
@@ -399,7 +445,9 @@ describe("ChatConversationPanel", () => {
399
445
  render(<ChatConversationPanel />);
400
446
 
401
447
  expect(screen.queryByTestId("chat-welcome")).toBeNull();
402
- expect(screen.queryByText("No messages yet. Send one to start.")).toBeNull();
448
+ expect(
449
+ screen.queryByText("No messages yet. Send one to start."),
450
+ ).toBeNull();
403
451
  });
404
452
 
405
453
  it("does not render runtime lifecycle copy in the conversation alert strip", () => {
@@ -491,6 +539,7 @@ describe("ChatSessionWorkspacePanel", () => {
491
539
 
492
540
  render(
493
541
  <ChatSessionWorkspacePanel
542
+ sessionKey="parent-session-1"
494
543
  childSessionTabs={[
495
544
  {
496
545
  sessionKey: "child-session-1",
@@ -503,12 +552,6 @@ describe("ChatSessionWorkspacePanel", () => {
503
552
  workspaceFileTabs={[]}
504
553
  activeWorkspaceFileKey={null}
505
554
  sessionProjectRoot="/Users/demo/project-alpha"
506
- onSelectSession={vi.fn()}
507
- onSelectFile={vi.fn()}
508
- onCloseFile={vi.fn()}
509
- onClose={vi.fn()}
510
- onBackToParent={vi.fn()}
511
- onFileOpen={vi.fn()}
512
555
  />,
513
556
  );
514
557
 
@@ -559,6 +602,7 @@ describe("ChatSessionWorkspacePanel", () => {
559
602
 
560
603
  render(
561
604
  <ChatSessionWorkspacePanel
605
+ sessionKey="parent-session-1"
562
606
  childSessionTabs={[
563
607
  {
564
608
  sessionKey: "child-session-1",
@@ -577,12 +621,6 @@ describe("ChatSessionWorkspacePanel", () => {
577
621
  workspaceFileTabs={[]}
578
622
  activeWorkspaceFileKey={null}
579
623
  sessionProjectRoot="/Users/demo/project-alpha"
580
- onSelectSession={vi.fn()}
581
- onSelectFile={vi.fn()}
582
- onCloseFile={vi.fn()}
583
- onClose={vi.fn()}
584
- onBackToParent={vi.fn()}
585
- onFileOpen={vi.fn()}
586
624
  />,
587
625
  );
588
626
 
@@ -592,6 +630,7 @@ describe("ChatSessionWorkspacePanel", () => {
592
630
  it("shows opened files as top tabs and renders the file preview pane", () => {
593
631
  render(
594
632
  <ChatSessionWorkspacePanel
633
+ sessionKey="parent-session-1"
595
634
  childSessionTabs={[]}
596
635
  activeChildSessionKey={null}
597
636
  workspaceFileTabs={[
@@ -605,12 +644,6 @@ describe("ChatSessionWorkspacePanel", () => {
605
644
  ]}
606
645
  activeWorkspaceFileKey="parent-session-1::preview::README.md"
607
646
  sessionProjectRoot="/Users/demo/project-alpha"
608
- onSelectSession={vi.fn()}
609
- onSelectFile={vi.fn()}
610
- onCloseFile={vi.fn()}
611
- onClose={vi.fn()}
612
- onBackToParent={vi.fn()}
613
- onFileOpen={vi.fn()}
614
647
  />,
615
648
  );
616
649
 
@@ -649,6 +682,7 @@ describe("ChatSessionWorkspacePanel", () => {
649
682
 
650
683
  render(
651
684
  <ChatSessionWorkspacePanel
685
+ sessionKey="parent-session-1"
652
686
  childSessionTabs={[]}
653
687
  activeChildSessionKey={null}
654
688
  workspaceFileTabs={[]}
@@ -656,12 +690,6 @@ describe("ChatSessionWorkspacePanel", () => {
656
690
  activePanelKind="cron"
657
691
  sessionCronJobs={[job]}
658
692
  sessionProjectRoot="/Users/demo/project-alpha"
659
- onSelectSession={vi.fn()}
660
- onSelectFile={vi.fn()}
661
- onCloseFile={vi.fn()}
662
- onClose={vi.fn()}
663
- onBackToParent={vi.fn()}
664
- onFileOpen={vi.fn()}
665
693
  />,
666
694
  );
667
695