@nextclaw/ui 0.27.4 → 0.27.5-beta.1

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 (60) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/.vite/manifest.json +95 -95
  3. package/dist/assets/{account-panel-Cfx3ocPY.js → account-panel-C83GDFuy.js} +1 -1
  4. package/dist/assets/agents-CJA15zzv.js +1 -0
  5. package/dist/assets/{agents-BdeFtIOV.js → agents-Dr2KkEp2.js} +1 -1
  6. package/dist/assets/{app-presenter-provider-BB3Xl8b0.js → app-presenter-provider-Cx9MwMtb.js} +17 -16
  7. package/dist/assets/{appearance-settings-page-DiSRSDN3.js → appearance-settings-page-3IfIzjic.js} +1 -1
  8. package/dist/assets/apps-CvIlb9Rn.js +1 -0
  9. package/dist/assets/{confirm-dialog-jz2cQKId.js → confirm-dialog-D30HJBXN.js} +1 -1
  10. package/dist/assets/{context-menu-C0qywo73.js → context-menu-C6m-WNpl.js} +65 -65
  11. package/dist/assets/cron-BWZWu80t.js +1 -0
  12. package/dist/assets/{dialog-DMKYZDFu.js → dialog-DB_l9LK0.js} +1 -1
  13. package/dist/assets/doc-browser-DQDsmRlr.js +1 -0
  14. package/dist/assets/inbox-CIsD65lW.js +1 -0
  15. package/dist/assets/{keyboard-shortcuts-settings-page-BFBXVf20.js → keyboard-shortcuts-settings-page-L4kLGuB8.js} +1 -1
  16. package/dist/assets/{main-Bp1iozNB.js → main-BNWRrWc_.js} +2 -2
  17. package/dist/assets/{marketplace-BTDWnHP7.js → marketplace-BH1Sq8dL.js} +1 -1
  18. package/dist/assets/mcp-marketplace-page-BA-InAbJ.js +1 -0
  19. package/dist/assets/{model-config-page-CeVvZ29o.js → model-config-page-zqVWyIT0.js} +1 -1
  20. package/dist/assets/panel-apps-CMX4iWwQ.js +1 -0
  21. package/dist/assets/{panelStandalone-DomappXm.js → panelStandalone-C38o5B4Q.js} +1 -1
  22. package/dist/assets/{projects-fPTOn2Wz.js → projects-DNe43T2d.js} +1 -1
  23. package/dist/assets/{provider-models-D8WOdsdu.js → provider-models-zGsDUBHG.js} +1 -1
  24. package/dist/assets/{provider-scoped-model-input-BOPkUOtD.js → provider-scoped-model-input-DpEyP1h8.js} +1 -1
  25. package/dist/assets/{providers-config-page-BilPL0kF.js → providers-config-page-J-1auWPu.js} +1 -1
  26. package/dist/assets/{pwa-Cp7pPvsZ.js → pwa-jmuWbrqH.js} +1 -1
  27. package/dist/assets/remote-C7H2o46-.js +1 -0
  28. package/dist/assets/{runtime-config-page-P5RUq3YK.js → runtime-config-page-CJhqeDcu.js} +1 -1
  29. package/dist/assets/{service-apps-panel-BReYr_ni.js → service-apps-panel-Dj9okUgp.js} +1 -1
  30. package/dist/assets/{theme-provider-BsJ6HxeX.js → theme-provider-D4rd-tCP.js} +1 -1
  31. package/dist/index.html +10 -10
  32. package/dist/panel-standalone.html +4 -4
  33. package/package.json +7 -7
  34. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +16 -0
  35. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +17 -0
  36. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +3 -3
  37. package/src/features/chat/features/message/utils/chat-message.utils.ts +11 -4
  38. package/src/features/chat/features/message/utils/chat-user-question-timeline.utils.ts +42 -0
  39. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +2 -0
  40. package/src/features/panel-apps/components/panel-app-runtime-surface.tsx +3 -1
  41. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +25 -10
  42. package/src/features/right-panel-resources/components/__tests__/system-object-resource.test.tsx +25 -3
  43. package/src/features/right-panel-resources/components/native-object-resource.tsx +3 -1
  44. package/src/features/right-panel-resources/components/system-object-resource.tsx +15 -1
  45. package/src/features/right-panel-resources/pages/resource-page.tsx +29 -2
  46. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -13
  47. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +3 -0
  48. package/src/shared/components/doc-browser/doc-browser.tsx +7 -1
  49. package/src/shared/components/ui/context-menu/context-menu-items.tsx +1 -0
  50. package/src/shared/components/ui/context-menu/context-menu.tsx +9 -0
  51. package/dist/assets/agents-p_wcGhC3.js +0 -1
  52. package/dist/assets/apps-CKFrWb00.js +0 -1
  53. package/dist/assets/cron-UqsFT41c.js +0 -1
  54. package/dist/assets/doc-browser-2VjSkax1.js +0 -1
  55. package/dist/assets/inbox-D62apR2j.js +0 -1
  56. package/dist/assets/mcp-marketplace-page-oHCbBkS-.js +0 -1
  57. package/dist/assets/panel-apps-CniMi7QW.js +0 -1
  58. package/dist/assets/remote-CsJMF8OR.js +0 -1
  59. package/src/features/panel-apps/components/__tests__/panel-app-toolbar.test.tsx +0 -108
  60. package/src/features/panel-apps/components/panel-app-toolbar.tsx +0 -89
@@ -1 +1 @@
1
- import{i as e}from"./chunk-DF37yw5_.js";import{Lt as t,Qn as n,Sn as r,Xn as i,Yt as a,Zn as o,fn as s,hn as c,lr as l,ur as ee,xr as u}from"./api-CXWxExzS.js";import{t as d}from"./createLucideIcon-C3LSyx-e.js";import{J as te}from"./context-menu-C0qywo73.js";import{d as ne,f as re,l as f,p,s as m}from"./i18n-ByVUrt9w.js";import{a as ie,i as ae,n as oe,o as h,r as se,t as ce}from"./dialog-DMKYZDFu.js";import{n as g}from"./navigation-history-CCnxsAKR.js";var _=`nextclaw.ui.theme`,v=`default`,le=`plain-paper`;function y(e){return e===`default`?le:e}var b=[{value:`default`,labelKey:`themeDefault`,appearance:`light`},{value:`plain-paper`,labelKey:`themePlainPaper`,appearance:`light`},{value:`paper-ink`,labelKey:`themePaperInk`,appearance:`light`},{value:`work`,labelKey:`themeWork`,appearance:`light`},{value:`night`,labelKey:`themeNight`,appearance:`dark`},{value:`charcoal`,labelKey:`themeCharcoal`,appearance:`dark`},{value:`island`,labelKey:`themeIsland`,appearance:`light`},{value:`natural`,labelKey:`themeNatural`,appearance:`light`},{value:`minimal`,labelKey:`themeMinimal`,appearance:`light`},{value:`warm`,labelKey:`themeWarm`,appearance:`light`},{value:`cool`,labelKey:`themeCool`,appearance:`light`},{value:`dawn`,labelKey:`themeDawn`,appearance:`light`},{value:`graphite`,labelKey:`themeGraphite`,appearance:`light`},{value:`probe`,labelKey:`themeProbe`,appearance:`light`}],ue=b.map(({value:e})=>e),de=b.map(({value:e,labelKey:t})=>({value:e,labelKey:t}));function x(e){return typeof e==`string`?ue.includes(e)?e:e===`leaf`?`warm`:null:null}function S(e){return b.find(t=>t.value===y(e))?.appearance??`light`}var C=new class{constructor(){this.activeTheme=v,this.initialized=!1,this.listeners=new Set,this.resolveInitialTheme=()=>{if(typeof window>`u`)return v;try{let e=x(window.localStorage.getItem(_));if(e)return e}catch{}return v},this.initializeTheme=()=>(this.initialized||=(this.activeTheme=this.resolveInitialTheme(),this.applyThemeAttribute(this.activeTheme),!0),this.activeTheme),this.getTheme=()=>this.initialized?this.activeTheme:this.initializeTheme(),this.setTheme=e=>{this.initializeTheme(),e!==this.activeTheme&&(this.activeTheme=e,this.applyThemeAttribute(this.activeTheme),this.saveTheme(this.activeTheme),this.listeners.forEach(e=>e(this.activeTheme)))},this.subscribeThemeChange=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)}),this.applyThemeAttribute=e=>{if(typeof document>`u`)return;let t=S(e);document.documentElement.setAttribute(`data-theme`,y(e)),document.documentElement.setAttribute(`data-theme-appearance`,t),document.documentElement.style.colorScheme=t},this.saveTheme=e=>{if(!(typeof window>`u`))try{window.localStorage.setItem(_,e)}catch{}}}};function w(){return C.initializeTheme()}function fe(){return C.getTheme()}function pe(e){C.setTheme(e)}function me(e){return C.subscribeThemeChange(e)}var T=d(`Boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),E=d(`ShieldAlert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),D=class{constructor(e){this.authorizationManager=e,this.handleIframeMessage=({event:e,iframe:t})=>{this.isBridgeRequest(e.data)&&(!t?.contentWindow||e.source!==t.contentWindow||this.handleBridgeRequest({event:e,iframe:t},e.data))},this.handleBridgeRequest=async(e,t)=>{try{let n=this.getBridgeContext(t),r=await this.dispatchRequest(n,t);this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!0,data:r})}catch(n){this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!1,error:this.toBridgeError(n)})}},this.dispatchRequest=async(e,n)=>{switch(n.method){case`list`:return await t.serviceApps.listServiceActions({bridgeSessionToken:e.token});case`requestGrant`:return await this.confirmAndGrant(e,this.requireActionId(n));case`revokeGrant`:return await t.serviceApps.revokeServiceAction(this.requireActionId(n),{bridgeSessionToken:e.token});case`invoke`:return await this.invokeWithAuthorization(e,n);case`agent.send`:return await this.sendAgentMessageWithAuthorization(e,n);case`agent.generateObject`:return await this.generateAgentObjectWithAuthorization(e,n);case`verification.list`:return await t.serviceApps.listVerificationRecords({acceptanceId:n.payload?.acceptanceId,appId:n.payload?.appId,limit:n.payload?.limit,bridgeSessionToken:e.token});case`acceptance.status`:return await t.serviceApps.getPortableRuntimeAcceptanceStatus({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});case`acceptance.export`:return await t.serviceApps.exportPortableRuntimeAcceptance({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});default:throw Error(`Unsupported panel bridge method: ${String(n.method)}`)}},this.invokeWithAuthorization=async(e,n)=>{let r=this.requireActionId(n);try{return await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}catch(i){if(!(i instanceof c)||i.code!==`AUTHORIZATION_REQUIRED`)throw i;return await this.confirmAndGrant(e,r,this.readOptionalRecord(n.payload?.input)),await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}},this.sendAgentMessageWithAuthorization=async(e,n)=>{try{return await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:send`),await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}},this.generateAgentObjectWithAuthorization=async(e,n)=>{try{return await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:generateObject`),await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}},this.confirmAndGrantAgentCapability=async(e,n)=>{if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:[{actionId:n,actionTitle:n===`agent:send`?`Send agent message`:`Generate object`,actionDescription:n===`agent:send`?`Send a message to a NextClaw Agent session.`:`Send context to a NextClaw Agent session and receive a structured object.`,risk:`write`}]}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});return await t.panelApps.grantAgentCapability(n,{bridgeSessionToken:e.token})},this.confirmAndGrant=async(e,n,r)=>{let i=await this.listGrantCandidateActions(e,n);if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:i.map(e=>({actionId:e.id,actionTitle:e.title,actionDescription:e.description,risk:e.risk})),inputPreview:this.createInputPreview(r)}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});let a=await t.serviceApps.grantServiceActions(i.map(e=>e.id),{bridgeSessionToken:e.token}),o=a.grants.find(e=>e.actionId===n)??a.grants[0];if(!o)throw Error(`No permission grant returned for ${n}.`);return o},this.listGrantCandidateActions=async(e,n)=>{let r=await t.serviceApps.listServiceActions({bridgeSessionToken:e.token}),i=r.actions.find(e=>e.id===n);if(i&&i.grantState!==`not-granted`)return[i];let a=r.actions.filter(e=>e.grantState===`not-granted`),o=[...a.filter(e=>e.id===n),...a.filter(e=>e.id!==n)];return o.length>0?o:[{appId:i?.appId??n.split(`.`)[0]??n,description:i?.description,id:n,name:i?.name??n,risk:i?.risk??`dangerous`,title:i?.title}]},this.createInputPreview=e=>{if(!(!e||Object.keys(e).length===0))try{let t=JSON.stringify(e,null,2);return t.length>500?`${t.slice(0,500)}...`:t}catch{return`[unserializable input]`}},this.getBridgeContext=e=>({appId:e.appId,token:e.runtimeToken}),this.requireActionId=e=>{let t=e.payload?.actionId?.trim();if(!t)throw Error(`Panel bridge actionId is required.`);return t},this.readOptionalRecord=e=>{if(e!==void 0)return this.requireRecord(e,`input`)},this.readAgentSendRequest=e=>({payload:this.requireRecord(e.payload?.request,`agent.send request`)}),this.readGenerateObjectRequest=e=>({input:this.requireRecord(e.payload?.input,`generateObject input`)}),this.requireRecord=(e,t)=>{if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`${t} is required.`);return e},this.postResponse=(e,t)=>{e.iframe?.contentWindow?.postMessage(t,`*`)},this.toBridgeError=e=>e instanceof c?{code:e.code,message:e.message,details:e.details}:{message:e instanceof Error?e.message:String(e)},this.isBridgeRequest=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return t.type===`nextclaw:panel-app-service-actions:request`&&typeof t.requestId==`string`&&typeof t.runtimeToken==`string`&&typeof t.appId==`string`&&(t.method===`invoke`||t.method===`agent.send`||t.method===`agent.generateObject`||t.method===`acceptance.export`||t.method===`acceptance.status`||t.method===`list`||t.method===`requestGrant`||t.method===`revokeGrant`||t.method===`verification.list`)}}};function he(){return`service-action-auth-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}var O=s((e,t)=>({pending:null,requestAuthorization:async n=>await new Promise(r=>{t().pending?.resolve(!1),e({pending:{id:he(),...n,resolve:r}})}),resolveAuthorization:n=>{let{pending:r}=t();r&&(r.resolve(n),e({pending:null}))}})),ge=class{constructor(){this.requestAuthorization=async e=>await O.getState().requestAuthorization(e)}},k=class{constructor(){this.serviceActionAuthorizationManager=new ge,this.panelAppBridgeManager=new D(this.serviceActionAuthorizationManager)}},A=e(u(),1),j=ee(),M=globalThis,N=M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__??(0,A.createContext)(null);M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__=N;function P({children:e,presenter:t}){let n=(0,A.useMemo)(()=>t??new k,[t]);return(0,j.jsx)(N.Provider,{value:n,children:e})}function F(){let e=(0,A.useContext)(N);if(!e)throw Error(`usePanelAppHostPresenter must be used inside PanelAppHostProvider`);return e}var I=[`panel-apps`],L=[`panel-app`];function R(e,t){return e.id===t||e.appId===t}function z(e,t,n){return e&&{...e,entries:e.entries.map(e=>R(e,t)?n(e):e)}}function B(e,t){return z(e,t.id,()=>t)}function V(e,t){let n=e?.entries.reduce((e,t)=>Math.max(e,t.mainSidebarOrder??-1),-1)??-1;return z(e,t.id,e=>({...e,...typeof t.preferences.favorite==`boolean`?{favorite:t.preferences.favorite}:{},...typeof t.preferences.mainSidebar==`boolean`?t.preferences.mainSidebar?{mainSidebar:!0,mainSidebarOrder:e.mainSidebar?e.mainSidebarOrder:n+1}:{mainSidebar:!1,mainSidebarOrder:void 0}:{}}))}function _e(e={}){return n({queryKey:I,queryFn:()=>t.panelApps.listPanelApps(),enabled:e.enabled??!0,staleTime:0})}function H(e,r={}){return n({queryKey:[...L,e],queryFn:()=>t.panelApps.getPanelApp(e),enabled:!!e&&(r.enabled??!0),staleTime:5e3})}function ve(){let e=l();return o({mutationFn:({id:e,preferences:n})=>t.panelApps.updatePanelAppPreferences(e,n),onMutate:async t=>{await e.cancelQueries({queryKey:I});let n=e.getQueryData(I)?.entries.find(e=>R(e,t.id));return e.setQueryData(I,e=>V(e,t)),{previousEntry:n}},onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)},onError:(t,n,r)=>{let a=r?.previousEntry;a&&e.setQueryData(I,e=>z(e,n.id,()=>a));let o=t instanceof Error?t.message:String(t);i.error(`${m(`panelAppsPreferenceUpdateFailed`)}: ${o}`)}})}function ye(){let e=l();return o({mutationFn:e=>t.panelApps.recordPanelAppOpened(e),onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)}})}function U(){let e=l();return o({mutationFn:e=>t.panelApps.grantClient(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function be(){let e=l();return o({mutationFn:e=>t.panelApps.deletePanelApp(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function W(){let e=F(),{isPending:t,mutateAsync:n}=U(),r=(0,A.useRef)(new Map);return{ensurePanelAppClientGrant:(0,A.useCallback)(async t=>{if(!t.clientDeclared||t.clientGranted)return!0;let i=r.current.get(t.appId);if(i)return await i;let a=(async()=>await e.serviceActionAuthorizationManager.requestAuthorization({panelAppId:t.appId,actions:[{actionId:`nextclaw.client`,actionTitle:m(`panelAppsClientGrantTitle`),actionDescription:m(`panelAppsClientGrantDescription`),risk:`dangerous`}]})?(await n(t.appId),!0):!1)();r.current.set(t.appId,a);try{return await a}finally{r.current.delete(t.appId)}},[n,e]),isPending:t}}function xe(e){let r=l(),i=(0,A.useId)(),{ensurePanelAppClientGrant:a}=W(),o=!!(e?.clientDeclared&&!e.clientGranted),s=n({queryKey:[`panel-app-client-grant`,e?.appId??`none`,e?.clientGranted??!1],queryFn:async()=>await a(e),enabled:o,gcTime:0,retry:!1,staleTime:1/0}),c=e?o?s.isError?`error`:s.isPending?`checking`:s.data?`allowed`:`denied`:`allowed`:`idle`;return n({queryKey:[`panel-app-open`,i,e?.appId??`none`],queryFn:async()=>{let n=await t.panelApps.recordPanelAppOpened(e.id);return r.setQueryData(I,e=>B(e,n)),r.setQueryData([...L,n.appId],n),n},enabled:!!(e&&c===`allowed`),retry:!1,staleTime:1/0}),{retryClientGrant:s.refetch,state:c}}var G=`allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads allow-pointer-lock allow-presentation`;function K(e){e?.focus({preventScroll:!0}),e?.contentWindow?.focus()}function q(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.kind===`document`?!0:t.kind===`element`&&Array.isArray(t.path)&&t.path.length>0&&t.path.length<=30&&t.path.every(e=>typeof e?.index==`number`&&Number.isInteger(e.index)&&e.index>=0&&e.index<=1e3&&typeof e.tagName==`string`&&e.tagName.length>0&&e.tagName.length<=32)}function Se(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.currentUrl==`string`&&q(t.target)}function Ce(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.type===r.scrollMessageType&&t.version===r.version&&q(t.target)&&typeof t.x==`number`&&Number.isFinite(t.x)&&t.x>=0&&typeof t.y==`number`&&Number.isFinite(t.y)&&t.y>=0}function J({currentUrl:e,iframeRef:t,isEnabled:n,restorationKey:i}){let a=i&&e?JSON.stringify([i,e]):null;return(0,A.useEffect)(()=>{if(!e||!n||!a)return;let r=a,i=n=>{n.source!==t.current?.contentWindow||!Ce(n.data)||g.save(r,{x:n.data.x,y:n.data.y,payload:{currentUrl:e,target:n.data.target}})};return window.addEventListener(`message`,i),()=>window.removeEventListener(`message`,i)},[e,t,n,a]),(0,A.useCallback)(()=>{if(!e||!n||!a)return;let i=g.read(a);!i||!Se(i.payload)||i.payload.currentUrl!==e||t.current?.contentWindow?.postMessage({type:r.restoreScrollMessageType,version:r.version,target:i.payload.target,x:i.x,y:i.y},`*`)},[e,t,n,a])}function Y({appId:e,restorationScope:t,unavailableAction:n}){let r=F(),i=H(e),a=(0,A.useRef)(null),o=(0,A.useMemo)(()=>i.data,[i.data]),s=xe(o),c=J({currentUrl:o?.contentPath??null,iframeRef:a,isEnabled:!!o,restorationKey:o?`panel-app:${t}:${o.appId}`:null});return(0,A.useEffect)(()=>{let e=e=>{r.panelAppBridgeManager.handleIframeMessage({event:e,iframe:a.current})};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[r]),i.isLoading?(0,j.jsx)(X,{message:m(`panelAppsLoading`)}):i.isError?(0,j.jsx)(X,{message:i.error instanceof Error?i.error.message:m(`panelAppsLoadFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void i.refetch()}):o?s.state===`checking`||s.state===`idle`?(0,j.jsx)(X,{message:m(`panelAppsCheckingPermission`)}):s.state===`denied`||s.state===`error`?(0,j.jsx)(X,{message:s.state===`denied`?m(`panelAppsPermissionDenied`):m(`panelAppsPermissionFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void s.retryClientGrant()}):(0,j.jsx)(`div`,{className:`h-full min-h-0 bg-background`,children:(0,j.jsx)(`iframe`,{ref:a,src:o.contentPath,title:o.title,sandbox:G,className:`block h-full w-full border-0 bg-background`,onLoad:c,onPointerOver:e=>K(e.currentTarget)},o.appId)}):(0,j.jsx)(X,{message:m(`panelAppsMainUnavailable`),actionLabel:n?.label,onAction:n?.onAction})}function X({actionLabel:e,message:t,onAction:n}){return(0,j.jsx)(`div`,{className:`flex h-full min-h-0 items-center justify-center bg-background px-6 text-center`,children:(0,j.jsxs)(`div`,{className:`max-w-sm`,children:[(0,j.jsx)(`div`,{className:`mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground`,children:n?(0,j.jsx)(te,{className:`h-5 w-5`}):(0,j.jsx)(T,{className:`h-5 w-5`})}),(0,j.jsx)(`p`,{className:`mt-3 text-sm leading-6 text-muted-foreground`,children:t}),e&&n?(0,j.jsx)(`button`,{type:`button`,className:`mt-4 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border`,onClick:n,children:e}):null]})})}var Z=(0,A.createContext)(null);function we({children:e}){let[t,n]=(0,A.useState)(()=>ne());(0,A.useEffect)(()=>p(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{re(e),n(f())},[]),i=(0,A.useCallback)(()=>{r(t===`en`?`zh`:`en`)},[t,r]),a=(0,A.useMemo)(()=>({language:t,setLanguage:r,toggleLanguage:i}),[t,r,i]);return(0,j.jsx)(Z.Provider,{value:a,children:e})}function Te(){let e=(0,A.useContext)(Z);if(!e)throw Error(`useI18n must be used within I18nProvider`);return{...e,t:m}}function Ee({appId:e}){let t=H(e),n=(0,A.useMemo)(()=>t.data,[t.data]);return(0,A.useEffect)(()=>{let e=document.title;return document.title=`${n?.title??m(`panelAppsTitle`)} · NextClaw`,()=>{document.title=e}},[n?.title]),(0,j.jsx)(`main`,{className:`h-dvh min-h-0 w-full overflow-hidden bg-background`,children:(0,j.jsx)(Y,{appId:e,restorationScope:`standalone`})})}function De(){let e=O(e=>e.pending),t=O(e=>e.resolveAuthorization);return(0,j.jsx)(ce,{open:!!e,onOpenChange:e=>{e||t(!1)},children:(0,j.jsxs)(oe,{children:[(0,j.jsxs)(ie,{showClose:!1,children:[(0,j.jsxs)(h,{className:`flex items-center gap-2`,children:[(0,j.jsx)(E,{className:`h-4 w-4 text-amber-600`}),m(`serviceActionAuthorizationTitle`)]}),(0,j.jsx)(se,{children:m(`serviceActionAuthorizationDescription`)})]}),e?(0,j.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-muted/60 p-3 text-sm`,children:[(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationSource`),value:e.panelAppId}),(0,j.jsxs)(`div`,{className:`space-y-2`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:m(`serviceActionAuthorizationActions`)}),(0,j.jsx)(`div`,{className:`max-h-64 space-y-2 overflow-auto`,children:e.actions.map(e=>(0,j.jsx)(Oe,{action:e},e.actionId))})]}),e.inputPreview?(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationInput`),value:e.inputPreview}):null]}):null,(0,j.jsxs)(ae,{className:`gap-2 sm:gap-0`,children:[(0,j.jsx)(a,{type:`button`,variant:`outline`,onClick:()=>t(!1),children:m(`serviceActionAuthorizationReject`)}),(0,j.jsx)(a,{type:`button`,onClick:()=>t(!0),children:m(`serviceActionAuthorizationAllow`)})]})]})})}function Oe({action:e}){return(0,j.jsxs)(`div`,{className:`space-y-1 rounded border border-border bg-card px-3 py-2`,children:[(0,j.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs font-medium text-foreground`,children:e.actionTitle??e.actionId}),(0,j.jsx)(`div`,{className:`shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-700`,children:e.risk??`dangerous`})]}),(0,j.jsx)(`div`,{className:`break-words text-[11px] text-muted-foreground`,children:e.actionId}),e.actionDescription?(0,j.jsx)(`div`,{className:`break-words text-xs text-muted-foreground`,children:e.actionDescription}):null]})}function Q({label:e,value:t}){return(0,j.jsxs)(`div`,{className:`grid grid-cols-[5rem_minmax(0,1fr)] gap-3`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:e}),(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs text-foreground`,children:t})]})}var $=(0,A.createContext)(null);function ke({children:e}){let[t,n]=(0,A.useState)(()=>w());(0,A.useEffect)(()=>me(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{pe(e),n(fe())},[]),i=(0,A.useMemo)(()=>({theme:t,setTheme:r}),[t,r]);return(0,j.jsx)($.Provider,{value:i,children:e})}function Ae(){let e=(0,A.useContext)($);if(!e)throw Error(`useTheme must be used within ThemeProvider`);return e}export{v as C,x as D,w as E,y as O,T as S,S as T,ve as _,we as a,D as b,J as c,W as d,be as f,ye as g,_e as h,Ee as i,G as l,H as m,Ae as n,Te as o,U as p,De as r,Y as s,ke as t,K as u,P as v,de as w,E as x,k as y};
1
+ import{i as e}from"./chunk-DF37yw5_.js";import{Lt as t,Qn as n,Sn as r,Xn as i,Yt as a,Zn as o,fn as s,hn as c,lr as l,ur as ee,xr as u}from"./api-CXWxExzS.js";import{t as d}from"./createLucideIcon-C3LSyx-e.js";import{J as te}from"./context-menu-C6m-WNpl.js";import{d as ne,f as re,l as f,p,s as m}from"./i18n-ByVUrt9w.js";import{a as ie,i as ae,n as oe,o as h,r as se,t as ce}from"./dialog-DB_l9LK0.js";import{n as g}from"./navigation-history-CCnxsAKR.js";var _=`nextclaw.ui.theme`,v=`default`,le=`plain-paper`;function y(e){return e===`default`?le:e}var b=[{value:`default`,labelKey:`themeDefault`,appearance:`light`},{value:`plain-paper`,labelKey:`themePlainPaper`,appearance:`light`},{value:`paper-ink`,labelKey:`themePaperInk`,appearance:`light`},{value:`work`,labelKey:`themeWork`,appearance:`light`},{value:`night`,labelKey:`themeNight`,appearance:`dark`},{value:`charcoal`,labelKey:`themeCharcoal`,appearance:`dark`},{value:`island`,labelKey:`themeIsland`,appearance:`light`},{value:`natural`,labelKey:`themeNatural`,appearance:`light`},{value:`minimal`,labelKey:`themeMinimal`,appearance:`light`},{value:`warm`,labelKey:`themeWarm`,appearance:`light`},{value:`cool`,labelKey:`themeCool`,appearance:`light`},{value:`dawn`,labelKey:`themeDawn`,appearance:`light`},{value:`graphite`,labelKey:`themeGraphite`,appearance:`light`},{value:`probe`,labelKey:`themeProbe`,appearance:`light`}],ue=b.map(({value:e})=>e),de=b.map(({value:e,labelKey:t})=>({value:e,labelKey:t}));function x(e){return typeof e==`string`?ue.includes(e)?e:e===`leaf`?`warm`:null:null}function S(e){return b.find(t=>t.value===y(e))?.appearance??`light`}var C=new class{constructor(){this.activeTheme=v,this.initialized=!1,this.listeners=new Set,this.resolveInitialTheme=()=>{if(typeof window>`u`)return v;try{let e=x(window.localStorage.getItem(_));if(e)return e}catch{}return v},this.initializeTheme=()=>(this.initialized||=(this.activeTheme=this.resolveInitialTheme(),this.applyThemeAttribute(this.activeTheme),!0),this.activeTheme),this.getTheme=()=>this.initialized?this.activeTheme:this.initializeTheme(),this.setTheme=e=>{this.initializeTheme(),e!==this.activeTheme&&(this.activeTheme=e,this.applyThemeAttribute(this.activeTheme),this.saveTheme(this.activeTheme),this.listeners.forEach(e=>e(this.activeTheme)))},this.subscribeThemeChange=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)}),this.applyThemeAttribute=e=>{if(typeof document>`u`)return;let t=S(e);document.documentElement.setAttribute(`data-theme`,y(e)),document.documentElement.setAttribute(`data-theme-appearance`,t),document.documentElement.style.colorScheme=t},this.saveTheme=e=>{if(!(typeof window>`u`))try{window.localStorage.setItem(_,e)}catch{}}}};function w(){return C.initializeTheme()}function fe(){return C.getTheme()}function pe(e){C.setTheme(e)}function me(e){return C.subscribeThemeChange(e)}var T=d(`Boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),E=d(`ShieldAlert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),D=class{constructor(e){this.authorizationManager=e,this.handleIframeMessage=({event:e,iframe:t})=>{this.isBridgeRequest(e.data)&&(!t?.contentWindow||e.source!==t.contentWindow||this.handleBridgeRequest({event:e,iframe:t},e.data))},this.handleBridgeRequest=async(e,t)=>{try{let n=this.getBridgeContext(t),r=await this.dispatchRequest(n,t);this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!0,data:r})}catch(n){this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!1,error:this.toBridgeError(n)})}},this.dispatchRequest=async(e,n)=>{switch(n.method){case`list`:return await t.serviceApps.listServiceActions({bridgeSessionToken:e.token});case`requestGrant`:return await this.confirmAndGrant(e,this.requireActionId(n));case`revokeGrant`:return await t.serviceApps.revokeServiceAction(this.requireActionId(n),{bridgeSessionToken:e.token});case`invoke`:return await this.invokeWithAuthorization(e,n);case`agent.send`:return await this.sendAgentMessageWithAuthorization(e,n);case`agent.generateObject`:return await this.generateAgentObjectWithAuthorization(e,n);case`verification.list`:return await t.serviceApps.listVerificationRecords({acceptanceId:n.payload?.acceptanceId,appId:n.payload?.appId,limit:n.payload?.limit,bridgeSessionToken:e.token});case`acceptance.status`:return await t.serviceApps.getPortableRuntimeAcceptanceStatus({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});case`acceptance.export`:return await t.serviceApps.exportPortableRuntimeAcceptance({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});default:throw Error(`Unsupported panel bridge method: ${String(n.method)}`)}},this.invokeWithAuthorization=async(e,n)=>{let r=this.requireActionId(n);try{return await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}catch(i){if(!(i instanceof c)||i.code!==`AUTHORIZATION_REQUIRED`)throw i;return await this.confirmAndGrant(e,r,this.readOptionalRecord(n.payload?.input)),await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}},this.sendAgentMessageWithAuthorization=async(e,n)=>{try{return await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:send`),await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}},this.generateAgentObjectWithAuthorization=async(e,n)=>{try{return await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:generateObject`),await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}},this.confirmAndGrantAgentCapability=async(e,n)=>{if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:[{actionId:n,actionTitle:n===`agent:send`?`Send agent message`:`Generate object`,actionDescription:n===`agent:send`?`Send a message to a NextClaw Agent session.`:`Send context to a NextClaw Agent session and receive a structured object.`,risk:`write`}]}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});return await t.panelApps.grantAgentCapability(n,{bridgeSessionToken:e.token})},this.confirmAndGrant=async(e,n,r)=>{let i=await this.listGrantCandidateActions(e,n);if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:i.map(e=>({actionId:e.id,actionTitle:e.title,actionDescription:e.description,risk:e.risk})),inputPreview:this.createInputPreview(r)}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});let a=await t.serviceApps.grantServiceActions(i.map(e=>e.id),{bridgeSessionToken:e.token}),o=a.grants.find(e=>e.actionId===n)??a.grants[0];if(!o)throw Error(`No permission grant returned for ${n}.`);return o},this.listGrantCandidateActions=async(e,n)=>{let r=await t.serviceApps.listServiceActions({bridgeSessionToken:e.token}),i=r.actions.find(e=>e.id===n);if(i&&i.grantState!==`not-granted`)return[i];let a=r.actions.filter(e=>e.grantState===`not-granted`),o=[...a.filter(e=>e.id===n),...a.filter(e=>e.id!==n)];return o.length>0?o:[{appId:i?.appId??n.split(`.`)[0]??n,description:i?.description,id:n,name:i?.name??n,risk:i?.risk??`dangerous`,title:i?.title}]},this.createInputPreview=e=>{if(!(!e||Object.keys(e).length===0))try{let t=JSON.stringify(e,null,2);return t.length>500?`${t.slice(0,500)}...`:t}catch{return`[unserializable input]`}},this.getBridgeContext=e=>({appId:e.appId,token:e.runtimeToken}),this.requireActionId=e=>{let t=e.payload?.actionId?.trim();if(!t)throw Error(`Panel bridge actionId is required.`);return t},this.readOptionalRecord=e=>{if(e!==void 0)return this.requireRecord(e,`input`)},this.readAgentSendRequest=e=>({payload:this.requireRecord(e.payload?.request,`agent.send request`)}),this.readGenerateObjectRequest=e=>({input:this.requireRecord(e.payload?.input,`generateObject input`)}),this.requireRecord=(e,t)=>{if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`${t} is required.`);return e},this.postResponse=(e,t)=>{e.iframe?.contentWindow?.postMessage(t,`*`)},this.toBridgeError=e=>e instanceof c?{code:e.code,message:e.message,details:e.details}:{message:e instanceof Error?e.message:String(e)},this.isBridgeRequest=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return t.type===`nextclaw:panel-app-service-actions:request`&&typeof t.requestId==`string`&&typeof t.runtimeToken==`string`&&typeof t.appId==`string`&&(t.method===`invoke`||t.method===`agent.send`||t.method===`agent.generateObject`||t.method===`acceptance.export`||t.method===`acceptance.status`||t.method===`list`||t.method===`requestGrant`||t.method===`revokeGrant`||t.method===`verification.list`)}}};function he(){return`service-action-auth-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}var O=s((e,t)=>({pending:null,requestAuthorization:async n=>await new Promise(r=>{t().pending?.resolve(!1),e({pending:{id:he(),...n,resolve:r}})}),resolveAuthorization:n=>{let{pending:r}=t();r&&(r.resolve(n),e({pending:null}))}})),ge=class{constructor(){this.requestAuthorization=async e=>await O.getState().requestAuthorization(e)}},k=class{constructor(){this.serviceActionAuthorizationManager=new ge,this.panelAppBridgeManager=new D(this.serviceActionAuthorizationManager)}},A=e(u(),1),j=ee(),M=globalThis,N=M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__??(0,A.createContext)(null);M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__=N;function P({children:e,presenter:t}){let n=(0,A.useMemo)(()=>t??new k,[t]);return(0,j.jsx)(N.Provider,{value:n,children:e})}function F(){let e=(0,A.useContext)(N);if(!e)throw Error(`usePanelAppHostPresenter must be used inside PanelAppHostProvider`);return e}var I=[`panel-apps`],L=[`panel-app`];function R(e,t){return e.id===t||e.appId===t}function z(e,t,n){return e&&{...e,entries:e.entries.map(e=>R(e,t)?n(e):e)}}function B(e,t){return z(e,t.id,()=>t)}function V(e,t){let n=e?.entries.reduce((e,t)=>Math.max(e,t.mainSidebarOrder??-1),-1)??-1;return z(e,t.id,e=>({...e,...typeof t.preferences.favorite==`boolean`?{favorite:t.preferences.favorite}:{},...typeof t.preferences.mainSidebar==`boolean`?t.preferences.mainSidebar?{mainSidebar:!0,mainSidebarOrder:e.mainSidebar?e.mainSidebarOrder:n+1}:{mainSidebar:!1,mainSidebarOrder:void 0}:{}}))}function _e(e={}){return n({queryKey:I,queryFn:()=>t.panelApps.listPanelApps(),enabled:e.enabled??!0,staleTime:0})}function H(e,r={}){return n({queryKey:[...L,e],queryFn:()=>t.panelApps.getPanelApp(e),enabled:!!e&&(r.enabled??!0),staleTime:5e3})}function ve(){let e=l();return o({mutationFn:({id:e,preferences:n})=>t.panelApps.updatePanelAppPreferences(e,n),onMutate:async t=>{await e.cancelQueries({queryKey:I});let n=e.getQueryData(I)?.entries.find(e=>R(e,t.id));return e.setQueryData(I,e=>V(e,t)),{previousEntry:n}},onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)},onError:(t,n,r)=>{let a=r?.previousEntry;a&&e.setQueryData(I,e=>z(e,n.id,()=>a));let o=t instanceof Error?t.message:String(t);i.error(`${m(`panelAppsPreferenceUpdateFailed`)}: ${o}`)}})}function ye(){let e=l();return o({mutationFn:e=>t.panelApps.recordPanelAppOpened(e),onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)}})}function U(){let e=l();return o({mutationFn:e=>t.panelApps.grantClient(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function be(){let e=l();return o({mutationFn:e=>t.panelApps.deletePanelApp(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function W(){let e=F(),{isPending:t,mutateAsync:n}=U(),r=(0,A.useRef)(new Map);return{ensurePanelAppClientGrant:(0,A.useCallback)(async t=>{if(!t.clientDeclared||t.clientGranted)return!0;let i=r.current.get(t.appId);if(i)return await i;let a=(async()=>await e.serviceActionAuthorizationManager.requestAuthorization({panelAppId:t.appId,actions:[{actionId:`nextclaw.client`,actionTitle:m(`panelAppsClientGrantTitle`),actionDescription:m(`panelAppsClientGrantDescription`),risk:`dangerous`}]})?(await n(t.appId),!0):!1)();r.current.set(t.appId,a);try{return await a}finally{r.current.delete(t.appId)}},[n,e]),isPending:t}}function xe(e){let r=l(),i=(0,A.useId)(),{ensurePanelAppClientGrant:a}=W(),o=!!(e?.clientDeclared&&!e.clientGranted),s=n({queryKey:[`panel-app-client-grant`,e?.appId??`none`,e?.clientGranted??!1],queryFn:async()=>await a(e),enabled:o,gcTime:0,retry:!1,staleTime:1/0}),c=e?o?s.isError?`error`:s.isPending?`checking`:s.data?`allowed`:`denied`:`allowed`:`idle`;return n({queryKey:[`panel-app-open`,i,e?.appId??`none`],queryFn:async()=>{let n=await t.panelApps.recordPanelAppOpened(e.id);return r.setQueryData(I,e=>B(e,n)),r.setQueryData([...L,n.appId],n),n},enabled:!!(e&&c===`allowed`),retry:!1,staleTime:1/0}),{retryClientGrant:s.refetch,state:c}}var G=`allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads allow-pointer-lock allow-presentation`;function K(e){e?.focus({preventScroll:!0}),e?.contentWindow?.focus()}function q(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.kind===`document`?!0:t.kind===`element`&&Array.isArray(t.path)&&t.path.length>0&&t.path.length<=30&&t.path.every(e=>typeof e?.index==`number`&&Number.isInteger(e.index)&&e.index>=0&&e.index<=1e3&&typeof e.tagName==`string`&&e.tagName.length>0&&e.tagName.length<=32)}function Se(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.currentUrl==`string`&&q(t.target)}function Ce(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.type===r.scrollMessageType&&t.version===r.version&&q(t.target)&&typeof t.x==`number`&&Number.isFinite(t.x)&&t.x>=0&&typeof t.y==`number`&&Number.isFinite(t.y)&&t.y>=0}function J({currentUrl:e,iframeRef:t,isEnabled:n,restorationKey:i}){let a=i&&e?JSON.stringify([i,e]):null;return(0,A.useEffect)(()=>{if(!e||!n||!a)return;let r=a,i=n=>{n.source!==t.current?.contentWindow||!Ce(n.data)||g.save(r,{x:n.data.x,y:n.data.y,payload:{currentUrl:e,target:n.data.target}})};return window.addEventListener(`message`,i),()=>window.removeEventListener(`message`,i)},[e,t,n,a]),(0,A.useCallback)(()=>{if(!e||!n||!a)return;let i=g.read(a);!i||!Se(i.payload)||i.payload.currentUrl!==e||t.current?.contentWindow?.postMessage({type:r.restoreScrollMessageType,version:r.version,target:i.payload.target,x:i.x,y:i.y},`*`)},[e,t,n,a])}function Y({appId:e,restorationScope:t,refreshVersion:n=0,unavailableAction:r}){let i=F(),a=H(e),o=(0,A.useRef)(null),s=(0,A.useMemo)(()=>a.data,[a.data]),c=xe(s),l=J({currentUrl:s?.contentPath??null,iframeRef:o,isEnabled:!!s,restorationKey:s?`panel-app:${t}:${s.appId}`:null});return(0,A.useEffect)(()=>{let e=e=>{i.panelAppBridgeManager.handleIframeMessage({event:e,iframe:o.current})};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[i]),a.isLoading?(0,j.jsx)(X,{message:m(`panelAppsLoading`)}):a.isError?(0,j.jsx)(X,{message:a.error instanceof Error?a.error.message:m(`panelAppsLoadFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void a.refetch()}):s?c.state===`checking`||c.state===`idle`?(0,j.jsx)(X,{message:m(`panelAppsCheckingPermission`)}):c.state===`denied`||c.state===`error`?(0,j.jsx)(X,{message:c.state===`denied`?m(`panelAppsPermissionDenied`):m(`panelAppsPermissionFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void c.retryClientGrant()}):(0,j.jsx)(`div`,{className:`h-full min-h-0 bg-background`,children:(0,j.jsx)(`iframe`,{ref:o,src:s.contentPath,title:s.title,sandbox:G,className:`block h-full w-full border-0 bg-background`,onLoad:l,onPointerOver:e=>K(e.currentTarget)},`${s.appId}:${n}`)}):(0,j.jsx)(X,{message:m(`panelAppsMainUnavailable`),actionLabel:r?.label,onAction:r?.onAction})}function X({actionLabel:e,message:t,onAction:n}){return(0,j.jsx)(`div`,{className:`flex h-full min-h-0 items-center justify-center bg-background px-6 text-center`,children:(0,j.jsxs)(`div`,{className:`max-w-sm`,children:[(0,j.jsx)(`div`,{className:`mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground`,children:n?(0,j.jsx)(te,{className:`h-5 w-5`}):(0,j.jsx)(T,{className:`h-5 w-5`})}),(0,j.jsx)(`p`,{className:`mt-3 text-sm leading-6 text-muted-foreground`,children:t}),e&&n?(0,j.jsx)(`button`,{type:`button`,className:`mt-4 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border`,onClick:n,children:e}):null]})})}var Z=(0,A.createContext)(null);function we({children:e}){let[t,n]=(0,A.useState)(()=>ne());(0,A.useEffect)(()=>p(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{re(e),n(f())},[]),i=(0,A.useCallback)(()=>{r(t===`en`?`zh`:`en`)},[t,r]),a=(0,A.useMemo)(()=>({language:t,setLanguage:r,toggleLanguage:i}),[t,r,i]);return(0,j.jsx)(Z.Provider,{value:a,children:e})}function Te(){let e=(0,A.useContext)(Z);if(!e)throw Error(`useI18n must be used within I18nProvider`);return{...e,t:m}}function Ee({appId:e}){let t=H(e),n=(0,A.useMemo)(()=>t.data,[t.data]);return(0,A.useEffect)(()=>{let e=document.title;return document.title=`${n?.title??m(`panelAppsTitle`)} · NextClaw`,()=>{document.title=e}},[n?.title]),(0,j.jsx)(`main`,{className:`h-dvh min-h-0 w-full overflow-hidden bg-background`,children:(0,j.jsx)(Y,{appId:e,restorationScope:`standalone`})})}function De(){let e=O(e=>e.pending),t=O(e=>e.resolveAuthorization);return(0,j.jsx)(ce,{open:!!e,onOpenChange:e=>{e||t(!1)},children:(0,j.jsxs)(oe,{children:[(0,j.jsxs)(ie,{showClose:!1,children:[(0,j.jsxs)(h,{className:`flex items-center gap-2`,children:[(0,j.jsx)(E,{className:`h-4 w-4 text-amber-600`}),m(`serviceActionAuthorizationTitle`)]}),(0,j.jsx)(se,{children:m(`serviceActionAuthorizationDescription`)})]}),e?(0,j.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-muted/60 p-3 text-sm`,children:[(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationSource`),value:e.panelAppId}),(0,j.jsxs)(`div`,{className:`space-y-2`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:m(`serviceActionAuthorizationActions`)}),(0,j.jsx)(`div`,{className:`max-h-64 space-y-2 overflow-auto`,children:e.actions.map(e=>(0,j.jsx)(Oe,{action:e},e.actionId))})]}),e.inputPreview?(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationInput`),value:e.inputPreview}):null]}):null,(0,j.jsxs)(ae,{className:`gap-2 sm:gap-0`,children:[(0,j.jsx)(a,{type:`button`,variant:`outline`,onClick:()=>t(!1),children:m(`serviceActionAuthorizationReject`)}),(0,j.jsx)(a,{type:`button`,onClick:()=>t(!0),children:m(`serviceActionAuthorizationAllow`)})]})]})})}function Oe({action:e}){return(0,j.jsxs)(`div`,{className:`space-y-1 rounded border border-border bg-card px-3 py-2`,children:[(0,j.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs font-medium text-foreground`,children:e.actionTitle??e.actionId}),(0,j.jsx)(`div`,{className:`shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-700`,children:e.risk??`dangerous`})]}),(0,j.jsx)(`div`,{className:`break-words text-[11px] text-muted-foreground`,children:e.actionId}),e.actionDescription?(0,j.jsx)(`div`,{className:`break-words text-xs text-muted-foreground`,children:e.actionDescription}):null]})}function Q({label:e,value:t}){return(0,j.jsxs)(`div`,{className:`grid grid-cols-[5rem_minmax(0,1fr)] gap-3`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:e}),(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs text-foreground`,children:t})]})}var $=(0,A.createContext)(null);function ke({children:e}){let[t,n]=(0,A.useState)(()=>w());(0,A.useEffect)(()=>me(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{pe(e),n(fe())},[]),i=(0,A.useMemo)(()=>({theme:t,setTheme:r}),[t,r]);return(0,j.jsx)($.Provider,{value:i,children:e})}function Ae(){let e=(0,A.useContext)($);if(!e)throw Error(`useTheme must be used within ThemeProvider`);return e}export{v as C,x as D,w as E,y as O,T as S,S as T,ve as _,we as a,D as b,J as c,W as d,be as f,ye as g,_e as h,Ee as i,G as l,H as m,Ae as n,Te as o,U as p,De as r,Y as s,ke as t,K as u,P as v,de as w,E as x,k as y};
package/dist/index.html CHANGED
@@ -192,7 +192,7 @@
192
192
  })();
193
193
  </script>
194
194
  <title>NextClaw</title>
195
- <script type="module" crossorigin src="/assets/main-Bp1iozNB.js"></script>
195
+ <script type="module" crossorigin src="/assets/main-BNWRrWc_.js"></script>
196
196
  <link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
197
197
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-DWTEM3RW.js">
198
198
  <link rel="modulepreload" crossorigin href="/assets/i18n-ByVUrt9w.js">
@@ -206,12 +206,12 @@
206
206
  <link rel="modulepreload" crossorigin href="/assets/inbox-CI8ATevA.js">
207
207
  <link rel="modulepreload" crossorigin href="/assets/x-waP-WLuT.js">
208
208
  <link rel="modulepreload" crossorigin href="/assets/plus-C42o6xrD.js">
209
- <link rel="modulepreload" crossorigin href="/assets/context-menu-C0qywo73.js">
209
+ <link rel="modulepreload" crossorigin href="/assets/context-menu-C6m-WNpl.js">
210
210
  <link rel="modulepreload" crossorigin href="/assets/puzzle-DmKVREoh.js">
211
211
  <link rel="modulepreload" crossorigin href="/assets/book-open-B3Ac0voD.js">
212
- <link rel="modulepreload" crossorigin href="/assets/dialog-DMKYZDFu.js">
212
+ <link rel="modulepreload" crossorigin href="/assets/dialog-DB_l9LK0.js">
213
213
  <link rel="modulepreload" crossorigin href="/assets/navigation-history-CCnxsAKR.js">
214
- <link rel="modulepreload" crossorigin href="/assets/theme-provider-BsJ6HxeX.js">
214
+ <link rel="modulepreload" crossorigin href="/assets/theme-provider-D4rd-tCP.js">
215
215
  <link rel="modulepreload" crossorigin href="/assets/download-C2gYe8n1.js">
216
216
  <link rel="modulepreload" crossorigin href="/assets/middleware-B--6mAUM.js">
217
217
  <link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-Bnm5W5Qz.js">
@@ -224,15 +224,15 @@
224
224
  <link rel="modulepreload" crossorigin href="/assets/doc-browser-context-CoVMIiU8.js">
225
225
  <link rel="modulepreload" crossorigin href="/assets/tag-chip-DFzDSvcF.js">
226
226
  <link rel="modulepreload" crossorigin href="/assets/card-eFBmxYiI.js">
227
- <link rel="modulepreload" crossorigin href="/assets/confirm-dialog-jz2cQKId.js">
228
- <link rel="modulepreload" crossorigin href="/assets/provider-models-D8WOdsdu.js">
227
+ <link rel="modulepreload" crossorigin href="/assets/confirm-dialog-D30HJBXN.js">
228
+ <link rel="modulepreload" crossorigin href="/assets/provider-models-zGsDUBHG.js">
229
229
  <link rel="modulepreload" crossorigin href="/assets/select-BJ-EjmCz.js">
230
230
  <link rel="modulepreload" crossorigin href="/assets/use-config-M1L4xn8c.js">
231
- <link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-BB3Xl8b0.js">
232
- <link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-BOPkUOtD.js">
231
+ <link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-Cx9MwMtb.js">
232
+ <link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-DpEyP1h8.js">
233
233
  <link rel="modulepreload" crossorigin href="/assets/settings-2-D3-m8a3v.js">
234
- <link rel="modulepreload" crossorigin href="/assets/agents-BdeFtIOV.js">
235
- <link rel="modulepreload" crossorigin href="/assets/pwa-Cp7pPvsZ.js">
234
+ <link rel="modulepreload" crossorigin href="/assets/agents-Dr2KkEp2.js">
235
+ <link rel="modulepreload" crossorigin href="/assets/pwa-jmuWbrqH.js">
236
236
  <link rel="modulepreload" crossorigin href="/assets/login-page--Njgbmpm.js">
237
237
  <link rel="stylesheet" crossorigin href="/assets/context-menu-DEcVZfaU.css">
238
238
  <link rel="stylesheet" crossorigin href="/assets/app-presenter-provider-D3BuuOyZ.css">
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#ffffff" />
7
7
  <title>Panel App · NextClaw</title>
8
- <script type="module" crossorigin src="/assets/panelStandalone-DomappXm.js"></script>
8
+ <script type="module" crossorigin src="/assets/panelStandalone-C38o5B4Q.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-DWTEM3RW.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/i18n-ByVUrt9w.js">
@@ -19,10 +19,10 @@
19
19
  <link rel="modulepreload" crossorigin href="/assets/inbox-CI8ATevA.js">
20
20
  <link rel="modulepreload" crossorigin href="/assets/x-waP-WLuT.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/plus-C42o6xrD.js">
22
- <link rel="modulepreload" crossorigin href="/assets/context-menu-C0qywo73.js">
23
- <link rel="modulepreload" crossorigin href="/assets/dialog-DMKYZDFu.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/context-menu-C6m-WNpl.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/dialog-DB_l9LK0.js">
24
24
  <link rel="modulepreload" crossorigin href="/assets/navigation-history-CCnxsAKR.js">
25
- <link rel="modulepreload" crossorigin href="/assets/theme-provider-BsJ6HxeX.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/theme-provider-D4rd-tCP.js">
26
26
  <link rel="stylesheet" crossorigin href="/assets/context-menu-DEcVZfaU.css">
27
27
  <link rel="stylesheet" crossorigin href="/assets/panelStandalone-XRlWxbRS.css">
28
28
  </head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.27.4",
3
+ "version": "0.27.5-beta.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,14 +34,14 @@
34
34
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
35
35
  "zod": "^3.23.8",
36
36
  "zustand": "^5.0.2",
37
- "@nextclaw/agent-chat": "0.3.14",
38
- "@nextclaw/ncp": "0.11.3",
37
+ "@nextclaw/agent-chat-ui": "0.12.4-beta.0",
39
38
  "@nextclaw/ncp-http-agent-client": "0.4.27",
40
- "@nextclaw/ncp-react": "0.5.31",
41
- "@nextclaw/agent-chat-ui": "0.12.3",
42
- "@nextclaw/client-sdk": "0.12.13",
39
+ "@nextclaw/ncp": "0.11.3",
40
+ "@nextclaw/client-sdk": "0.12.14-beta.1",
41
+ "@nextclaw/shared": "0.8.4",
43
42
  "@nextclaw/ncp-toolkit": "0.6.28",
44
- "@nextclaw/shared": "0.8.4"
43
+ "@nextclaw/ncp-react": "0.5.31",
44
+ "@nextclaw/agent-chat": "0.3.14"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@testing-library/react": "^16.3.0",
@@ -20,6 +20,22 @@ const visibleMessage = {
20
20
  } satisfies NcpMessage;
21
21
 
22
22
  describe("chat message timeline visibility", () => {
23
+ it("places an async question at its tool call between assistant text parts", () => {
24
+ const source = { ...visibleMessage, parts: [
25
+ { type: "text" as const, text: "先说明。" },
26
+ { type: "tool-invocation" as const, toolCallId: "call-1", toolName: "request_user_input_async", state: "result" as const,
27
+ args: "{}", result: { accepted: true, questionIds: ["q-1"] } },
28
+ { type: "text" as const, text: "稍后继续。" },
29
+ ] } satisfies NcpMessage;
30
+ const question = { ...visibleMessage, id: "assistant-question-1", parts: [
31
+ { type: "text" as const, text: "城市? (北京 / 上海)" },
32
+ { type: "extension" as const, extensionType: "nextclaw.user-question", data: { questions: [{ id: "q-1", title: "城市?", options: ["北京", "上海"] }] } },
33
+ ] } satisfies NcpMessage;
34
+ const projected = projectVisibleChatMessages([source, question]);
35
+ expect(projected).toHaveLength(1);
36
+ expect(projected[0]?.parts.map((part) => part.type)).toEqual(["text", "extension", "text"]);
37
+ expect(projected[0]?.parts[1]).toMatchObject({ data: { questions: [{ id: "q-1" }] } });
38
+ });
23
39
  it("places context compaction between surrounding visible messages", () => {
24
40
  const afterMessage = {
25
41
  ...visibleMessage,
@@ -118,6 +118,23 @@ it("maps context compaction extensions into stable process parts", () => {
118
118
  }]);
119
119
  });
120
120
 
121
+ it("keeps assistant prose on both sides of an unanchored question fallback", () => {
122
+ const data = { questions: [{ id: "question-1", title: "Which format?", options: ["PDF", "DOCX"] }] };
123
+ const adapted = adapt([{
124
+ id: "assistant-question-older",
125
+ role: "assistant",
126
+ parts: [
127
+ { type: "text", text: "Before." },
128
+ { type: "text", text: "Which format? (PDF / DOCX)" },
129
+ { type: "extension", extensionType: "nextclaw.user-question", data },
130
+ { type: "text", text: "After." },
131
+ ],
132
+ }]);
133
+ expect(adapted[0]?.parts.map((part) => part.type)).toEqual(["markdown", "custom", "markdown"]);
134
+ expect(adapted[0]?.parts[0]).toMatchObject({ text: "Before." });
135
+ expect(adapted[0]?.parts[2]).toMatchObject({ text: "After." });
136
+ });
137
+
121
138
  it("renders a structured user question at its message position without repeating fallback text", () => {
122
139
  const data = { questions: [{ id: "question-1", title: "Which format?", options: ["PDF", "DOCX"] }] };
123
140
  const adapted = adapt([{
@@ -13,10 +13,10 @@ import {
13
13
  readObservationEventPartData,
14
14
  type ObservationEventPartData,
15
15
  } from "@/features/chat/features/message/utils/chat-message-observation-event.utils";
16
+ import { placeUserQuestionsInAssistantBody } from "@/features/chat/features/message/utils/chat-user-question-timeline.utils";
16
17
 
17
18
  const INHERITED_FROM_SESSION_METADATA_KEY = "inherited_from_session_id";
18
- export const CONTEXT_COMPACTION_PART_EXTENSION_TYPE =
19
- "nextclaw.context-compaction";
19
+ export const CONTEXT_COMPACTION_PART_EXTENSION_TYPE = "nextclaw.context-compaction";
20
20
 
21
21
  export type ContextCompactionPartData = {
22
22
  id: string;
@@ -280,7 +280,7 @@ function projectVisibleChatMessageState(
280
280
  status: "pending",
281
281
  };
282
282
  }
283
- return { messages, inlineCompactionMessageIds, observationEvents };
283
+ return { ...placeUserQuestionsInAssistantBody(messages, observationEvents), inlineCompactionMessageIds };
284
284
  }
285
285
 
286
286
  export function projectVisibleChatMessages(
@@ -122,16 +122,23 @@ export function adaptChatMessage(
122
122
  texts: params.texts,
123
123
  }))
124
124
  .filter((part) => part !== null);
125
- const hasQuestionPart = parts.some(
126
- (part) => part.type === "custom" && part.customType === "nextclaw.user-question",
127
- );
128
125
  const reply = message.meta?.userQuestionReply;
129
126
  const displayParts: ChatMessageViewModel["parts"] = reply
130
127
  ? [
131
128
  { type: "custom", id: reply.questionId, customType: "nextclaw.user-question-reply", data: reply },
132
129
  ...(reply.answer ? [{ type: "markdown" as const, text: reply.answer }] : []),
133
130
  ]
134
- : hasQuestionPart ? parts.filter((part) => part.type !== "markdown") : parts;
131
+ : parts.filter((part, index) => {
132
+ const next = parts[index + 1];
133
+ if (part.type !== "markdown" || next?.type !== "custom" || next.customType !== "nextclaw.user-question") return true;
134
+ const entries = next.data && typeof next.data === "object" && "questions" in next.data ? next.data.questions : null;
135
+ if (!Array.isArray(entries)) return true;
136
+ const fallback = entries.map((entry) => {
137
+ if (!entry || typeof entry.title !== "string") return "";
138
+ return `${entry.title}${Array.isArray(entry.options) && entry.options.length ? ` (${entry.options.join(" / ")})` : ""}`;
139
+ }).join("\n");
140
+ return part.text !== fallback;
141
+ });
135
142
  return {
136
143
  id: message.id,
137
144
  role: resolveUiRole(message.role),
@@ -0,0 +1,42 @@
1
+ import type { NcpMessage } from "@nextclaw/ncp";
2
+
3
+ function questionIdsFromToolPart(part: NcpMessage["parts"][number]): string[] {
4
+ if (part.type !== "tool-invocation" || part.toolName !== "request_user_input_async" ||
5
+ !part.result || typeof part.result !== "object" || Array.isArray(part.result)) return [];
6
+ const { accepted, questionIds } = part.result as { accepted?: unknown; questionIds?: unknown };
7
+ return accepted === true && Array.isArray(questionIds) && questionIds.every((id) => typeof id === "string")
8
+ ? questionIds : [];
9
+ }
10
+
11
+ function questionEntries(part: NcpMessage["parts"][number]): Array<{ id: string }> {
12
+ if (part.type !== "extension" || part.extensionType !== "nextclaw.user-question") return [];
13
+ const entries = part.data && typeof part.data === "object" && "questions" in part.data ? part.data.questions : null;
14
+ return Array.isArray(entries) ? entries.filter((entry): entry is { id: string } => Boolean(entry && typeof entry.id === "string")) : [];
15
+ }
16
+
17
+ export function placeUserQuestionsInAssistantBody<T extends { boundaryIndex: number }>(
18
+ messages: NcpMessage[], observationEvents: T[],
19
+ ): { messages: NcpMessage[]; observationEvents: T[] } {
20
+ const entriesById = new Map(messages.flatMap((message) => message.parts.flatMap((part) => questionEntries(part).map((entry) => [entry.id, entry] as const))));
21
+ const anchoredIds = new Set(messages.flatMap((message) => message.parts.flatMap((part) =>
22
+ questionIdsFromToolPart(part).filter((id) => entriesById.has(id)))));
23
+ if (!anchoredIds.size) return { messages, observationEvents };
24
+ const removedIndices: number[] = [];
25
+ const projected = messages.flatMap((message, index) => {
26
+ const questionPart = message.parts.find((part) => questionEntries(part).length);
27
+ if (questionPart && message.id.startsWith("assistant-question-")) {
28
+ const remaining = questionEntries(questionPart).filter((entry) => !anchoredIds.has(entry.id));
29
+ if (!remaining.length) { removedIndices.push(index); return []; }
30
+ return [{ ...message, parts: [{ type: "extension" as const, extensionType: "nextclaw.user-question", data: { questions: remaining } }] }];
31
+ }
32
+ const parts = message.parts.flatMap((part): NcpMessage["parts"] => {
33
+ const questions = questionIdsFromToolPart(part).flatMap((id) => entriesById.get(id) ?? []);
34
+ return questions.length
35
+ ? [{ type: "extension", extensionType: "nextclaw.user-question", data: { questions } }]
36
+ : [part];
37
+ });
38
+ return [{ ...message, parts }];
39
+ });
40
+ return { messages: projected, observationEvents: observationEvents.map((entry) => ({ ...entry,
41
+ boundaryIndex: entry.boundaryIndex - removedIndices.filter((index) => index < entry.boundaryIndex).length })) };
42
+ }
@@ -37,6 +37,7 @@ function renderDetail(detailId: string) {
37
37
  open: vi.fn(),
38
38
  openTarget: vi.fn(),
39
39
  refreshIframe: vi.fn(),
40
+ refreshVersion: 0,
40
41
  tab: {
41
42
  id: "tab-1",
42
43
  kind: MARKETPLACE_DETAIL_TAB_KIND,
@@ -131,6 +132,7 @@ describe("Marketplace detail doc renderer", () => {
131
132
  open: vi.fn(),
132
133
  openTarget: vi.fn(),
133
134
  refreshIframe: vi.fn(),
135
+ refreshVersion: 0,
134
136
  tab: {
135
137
  id: "tab-1",
136
138
  kind: MARKETPLACE_DETAIL_TAB_KIND,
@@ -18,10 +18,12 @@ type PanelAppUnavailableAction = {
18
18
  export function PanelAppRuntimeSurface({
19
19
  appId,
20
20
  restorationScope,
21
+ refreshVersion = 0,
21
22
  unavailableAction,
22
23
  }: {
23
24
  appId: string;
24
25
  restorationScope: "main" | "standalone";
26
+ refreshVersion?: number;
25
27
  unavailableAction?: PanelAppUnavailableAction;
26
28
  }) {
27
29
  const presenter = usePanelAppHostPresenter();
@@ -97,7 +99,7 @@ export function PanelAppRuntimeSurface({
97
99
  return (
98
100
  <div className="h-full min-h-0 bg-background">
99
101
  <iframe
100
- key={entry.appId}
102
+ key={`${entry.appId}:${refreshVersion}`}
101
103
  ref={iframeRef}
102
104
  src={entry.contentPath}
103
105
  title={entry.title}
@@ -1,9 +1,8 @@
1
- import { AppWindow, Boxes } from 'lucide-react';
1
+ import { AppWindow, Boxes, ExternalLink, RefreshCw } from 'lucide-react';
2
2
  import type { DocBrowserContextValue } from '@/shared/components/doc-browser/doc-browser-context';
3
3
  import type { DocBrowserCustomTabRenderers } from '@/shared/components/doc-browser/doc-browser-renderer.types';
4
4
  import { getPresenter } from '@/app/presenters/app.presenter';
5
5
  import { AppsPanel } from '@/features/apps';
6
- import { PanelAppDocBrowserToolbar } from '@/features/panel-apps/components/panel-app-toolbar';
7
6
  import {
8
7
  createPanelAppRightPanelResourceTarget,
9
8
  RIGHT_PANEL_APPS_TAB_KIND,
@@ -12,6 +11,7 @@ import {
12
11
  RIGHT_PANEL_HOME_TAB_KIND,
13
12
  RIGHT_PANEL_PANEL_APP_TAB_KIND,
14
13
  RightPanelResourceHomePage,
14
+ readPanelAppIdFromTab,
15
15
  } from '@/features/right-panel-resources';
16
16
  import { t } from '@/shared/lib/i18n';
17
17
  import { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './panel-app-iframe.utils';
@@ -56,14 +56,29 @@ export const PANEL_APPS_DOC_BROWSER_RENDERERS: DocBrowserCustomTabRenderers = {
56
56
  onIframeMessage: (params) => getPresenter().panelAppBridgeManager.handleIframeMessage(params),
57
57
  onIframePointerOver: (event) => focusPanelAppIframe(event.currentTarget),
58
58
  renderIcon: () => <AppWindow className="w-4 h-4 text-primary shrink-0" />,
59
- renderToolbar: ({ refreshIframe, tab }) => (
60
- <PanelAppDocBrowserToolbar
61
- appTitle={tab.title || t('panelAppsTitle')}
62
- currentUrl={tab.currentUrl}
63
- onRefresh={refreshIframe}
64
- resourceUri={tab.resourceUri}
65
- />
66
- ),
59
+ getTabMenuGroups: ({ refreshIframe, tab }) => {
60
+ const appId = readPanelAppIdFromTab(tab);
61
+ return [{
62
+ key: 'panel-app-actions',
63
+ items: [
64
+ {
65
+ key: 'refresh',
66
+ icon: <RefreshCw className="h-4 w-4" />,
67
+ label: t('panelAppsRefreshCurrent'),
68
+ onSelect: refreshIframe,
69
+ },
70
+ ...(appId ? [{
71
+ key: 'open-standalone',
72
+ icon: <ExternalLink className="h-4 w-4" />,
73
+ label: t(typeof window !== 'undefined' && window.nextclawDesktop ? 'panelAppsOpenInBrowser' : 'panelAppsOpenInNewTab'),
74
+ href: `/apps/panel/${encodeURIComponent(appId)}/standalone`,
75
+ target: '_blank' as const,
76
+ rel: 'opener',
77
+ referrerPolicy: 'no-referrer' as const,
78
+ }] : []),
79
+ ],
80
+ }];
81
+ },
67
82
  supportsScrollRestoration: true,
68
83
  },
69
84
  };
@@ -17,7 +17,7 @@ vi.mock("@/features/inbox", () => ({ InboxPage: ({ resourceId }: { resourceId: s
17
17
  vi.mock("@/features/apps", () => ({ AppsPanel: ({ serviceResourceId }: { serviceResourceId: string }) => <div>service-app:{serviceResourceId}</div> }));
18
18
  vi.mock("@/features/marketplace", () => ({ McpMarketplacePage: ({ resourceId }: { resourceId: string }) => <div>mcp-server:{resourceId}</div> }));
19
19
  vi.mock("@/features/panel-apps", () => ({
20
- PanelAppRuntimeSurface: ({ appId }: { appId: string }) => <div>panel-app:{appId}</div>,
20
+ PanelAppRuntimeSurface: ({ appId, refreshVersion }: { appId: string; refreshVersion?: number }) => <div>panel-app:{appId}:{refreshVersion}</div>,
21
21
  PanelAppHostProvider: ({ children }: { children: ReactNode }) => children,
22
22
  }));
23
23
 
@@ -61,7 +61,7 @@ afterEach(cleanup);
61
61
  describe("system object reading identity", () => {
62
62
  it.each(["agent", "cron-job", "project", "inbox-delivery", "service-app", "mcp-server", "panel-app"])("opens %s with its native renderer and unchanged identity", async (objectType) => {
63
63
  render(<NativeObjectResource objectType={objectType} objectId="raw:id" openTarget={vi.fn()} />);
64
- expect(await screen.findByText(`${objectType}:raw:id`)).toBeTruthy();
64
+ expect(await screen.findByText(objectType === "panel-app" ? `${objectType}:raw:id:` : `${objectType}:raw:id`)).toBeTruthy();
65
65
  expect(screen.queryByTestId("preview")).toBeNull();
66
66
  });
67
67
 
@@ -70,6 +70,27 @@ describe("system object reading identity", () => {
70
70
  expect(await screen.findByText("project:project/work")).toBeTruthy();
71
71
  });
72
72
 
73
+ it("adds refresh to the host tab menu and passes its version to panel app content", async () => {
74
+ const uri = "nextclaw://objects/panel-app/raw%3Aid";
75
+ const refreshIframe = vi.fn();
76
+ const params = (refreshVersion: number) => ({
77
+ currentUrl: uri,
78
+ tab: { id: uri, kind: "system-object", title: "Panel App", currentUrl: uri, resourceUri: uri, history: [uri], historyIndex: 0, navVersion: 0 },
79
+ open: vi.fn(), openTarget: vi.fn(), refreshIframe, refreshVersion,
80
+ } satisfies DocBrowserCustomTabRenderParams);
81
+ const renderer = SYSTEM_OBJECT_RESOURCE_RENDERERS["system-object"];
82
+ expect(renderer.renderToolbar).toBeUndefined();
83
+ const refreshAction = renderer.getTabMenuGroups!(params(0))[0]?.items[0];
84
+ expect(refreshAction?.label).toBe("Refresh current panel app");
85
+ if (refreshAction && "onSelect" in refreshAction) refreshAction.onSelect?.();
86
+ expect(refreshIframe).toHaveBeenCalledTimes(1);
87
+ const view = render(renderer.renderContent!(params(0)));
88
+ expect(await screen.findByText("panel-app:raw:id:0")).toBeTruthy();
89
+ view.rerender(renderer.renderContent!(params(1)));
90
+ expect(await screen.findByText("panel-app:raw:id:1")).toBeTruthy();
91
+ expect(renderer.getTabMenuGroups!({ ...params(0), tab: { ...params(0).tab, currentUrl: "nextclaw://objects/agent/test", resourceUri: "nextclaw://objects/agent/test" } })).toEqual([]);
92
+ });
93
+
73
94
  it("retains a global file's source context when quoting into a different conversation", () => {
74
95
  const target = createWorkspaceFilePanelTarget({
75
96
  key: "source-file", path: "agent.md", label: "agent.md", parentSessionKey: "source-session", viewMode: "preview",
@@ -77,7 +98,7 @@ describe("system object reading identity", () => {
77
98
  render(WORKSPACE_FILE_PANEL_RENDERERS["workspace-file"].renderContent!({
78
99
  currentUrl: target.url,
79
100
  tab: { ...target, id: target.url, currentUrl: target.url, history: [target.url], historyIndex: 0, navVersion: 0 },
80
- open: vi.fn(), openTarget: vi.fn(), refreshIframe: vi.fn(),
101
+ open: vi.fn(), openTarget: vi.fn(), refreshIframe: vi.fn(), refreshVersion: 0,
81
102
  }));
82
103
  fireEvent.click(screen.getByRole("button", { name: "Add selection" }));
83
104
  expect(actions.addExcerptToChat).toHaveBeenLastCalledWith({
@@ -92,6 +113,7 @@ describe("system object reading identity", () => {
92
113
  open: vi.fn(),
93
114
  openTarget: vi.fn(),
94
115
  refreshIframe: vi.fn(),
116
+ refreshVersion: 0,
95
117
  } satisfies DocBrowserCustomTabRenderParams);
96
118
  const first = "nextclaw://objects/skill/first";
97
119
  const second = "nextclaw://objects/skill/second";
@@ -65,10 +65,12 @@ export function NativeObjectResource({
65
65
  objectType,
66
66
  objectId,
67
67
  openTarget,
68
+ refreshVersion,
68
69
  }: {
69
70
  objectType: string;
70
71
  objectId: string;
71
72
  openTarget: DocBrowserCustomTabRenderParams["openTarget"];
73
+ refreshVersion?: number;
72
74
  }) {
73
75
  let content;
74
76
  switch (objectType) {
@@ -111,7 +113,7 @@ export function NativeObjectResource({
111
113
  case "panel-app":
112
114
  content = (
113
115
  <PanelAppHostProvider>
114
- <PanelAppRuntimeSurface appId={objectId} restorationScope="main" />
116
+ <PanelAppRuntimeSurface appId={objectId} restorationScope="main" refreshVersion={refreshVersion} />
115
117
  </PanelAppHostProvider>
116
118
  );
117
119
  break;
@@ -13,12 +13,14 @@ import { useAppPresenter } from "@/app/components/app-presenter-provider";
13
13
  import { useLocation, useNavigate } from "react-router-dom";
14
14
  import { parseSystemObjectReferenceUri } from "@nextclaw/shared";
15
15
  import { NativeObjectResource } from "./native-object-resource";
16
+ import { RefreshCw } from "lucide-react";
16
17
  import { ChatTextSelectionAction } from "@nextclaw/agent-chat-ui";
17
18
  import { WORKSPACE_TEXT_EXCERPT_MAX_CHARACTERS } from "@/features/chat";
18
19
 
19
20
  function SystemObjectResource({
20
21
  tab,
21
22
  openTarget,
23
+ refreshVersion,
22
24
  }: DocBrowserCustomTabRenderParams) {
23
25
  const app = useAppPresenter();
24
26
  const { pathname } = useLocation();
@@ -52,7 +54,7 @@ function SystemObjectResource({
52
54
  )
53
55
  }
54
56
  >
55
- <NativeObjectResource key={uri} {...identity} openTarget={openTarget} />
57
+ <NativeObjectResource key={uri} {...identity} openTarget={openTarget} refreshVersion={refreshVersion} />
56
58
  </ChatTextSelectionAction>
57
59
  );
58
60
  }
@@ -160,6 +162,18 @@ function SystemObjectDocument({
160
162
  export const SYSTEM_OBJECT_RESOURCE_RENDERERS: DocBrowserCustomTabRenderers = {
161
163
  "system-object": {
162
164
  getTitle: (tab) => tab.title,
165
+ getTabMenuGroups: ({ refreshIframe, tab }) => {
166
+ const identity = parseSystemObjectReferenceUri(tab.resourceUri ?? tab.currentUrl);
167
+ return identity?.objectType === "panel-app" ? [{
168
+ key: "panel-app-refresh",
169
+ items: [{
170
+ key: "refresh",
171
+ label: t("panelAppsRefreshCurrent"),
172
+ icon: <RefreshCw className="h-4 w-4" />,
173
+ onSelect: refreshIframe,
174
+ }],
175
+ }] : [];
176
+ },
163
177
  renderContent: (params) => <SystemObjectResource {...params} />,
164
178
  },
165
179
  };