@nextclaw/ui 0.27.5-beta.0 → 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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/.vite/manifest.json +99 -99
  3. package/dist/assets/{account-panel-DgqicWMm.js → account-panel-C83GDFuy.js} +1 -1
  4. package/dist/assets/agents-CJA15zzv.js +1 -0
  5. package/dist/assets/{agents-CTSR4ZKb.js → agents-Dr2KkEp2.js} +1 -1
  6. package/dist/assets/{app-presenter-provider-DdD2Q7jZ.js → app-presenter-provider-Cx9MwMtb.js} +12 -12
  7. package/dist/assets/{appearance-settings-page-CSWYLrsR.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-BCxB9m69.js → keyboard-shortcuts-settings-page-L4kLGuB8.js} +1 -1
  16. package/dist/assets/{main-C6VGoYNO.js → main-BNWRrWc_.js} +2 -2
  17. package/dist/assets/{marketplace-CXCLbyho.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-BWM-oEo0.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-Dx6lWn3_.js → runtime-config-page-CJhqeDcu.js} +1 -1
  29. package/dist/assets/{service-apps-panel-9khaUSFI.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/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +2 -0
  35. package/src/features/panel-apps/components/panel-app-runtime-surface.tsx +3 -1
  36. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +25 -10
  37. package/src/features/right-panel-resources/components/__tests__/system-object-resource.test.tsx +25 -3
  38. package/src/features/right-panel-resources/components/native-object-resource.tsx +3 -1
  39. package/src/features/right-panel-resources/components/system-object-resource.tsx +15 -1
  40. package/src/features/right-panel-resources/pages/resource-page.tsx +29 -2
  41. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -13
  42. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +3 -0
  43. package/src/shared/components/doc-browser/doc-browser.tsx +7 -1
  44. package/src/shared/components/ui/context-menu/context-menu-items.tsx +1 -0
  45. package/src/shared/components/ui/context-menu/context-menu.tsx +9 -0
  46. package/dist/assets/agents-i7167DcF.js +0 -1
  47. package/dist/assets/apps-DUP3aPpu.js +0 -1
  48. package/dist/assets/cron-DPBUHn7j.js +0 -1
  49. package/dist/assets/doc-browser-C7kvXGc_.js +0 -1
  50. package/dist/assets/inbox-C09MQO9k.js +0 -1
  51. package/dist/assets/mcp-marketplace-page-ZAm380SB.js +0 -1
  52. package/dist/assets/panel-apps-BQlyeAId.js +0 -1
  53. package/dist/assets/remote-DngM8iep.js +0 -1
  54. package/src/features/panel-apps/components/__tests__/panel-app-toolbar.test.tsx +0 -108
  55. 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-C6VGoYNO.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-DdD2Q7jZ.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-CTSR4ZKb.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.5-beta.0",
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/client-sdk": "0.12.14-beta.0",
38
- "@nextclaw/shared": "0.8.4",
37
+ "@nextclaw/agent-chat-ui": "0.12.4-beta.0",
38
+ "@nextclaw/ncp-http-agent-client": "0.4.27",
39
39
  "@nextclaw/ncp": "0.11.3",
40
+ "@nextclaw/client-sdk": "0.12.14-beta.1",
41
+ "@nextclaw/shared": "0.8.4",
40
42
  "@nextclaw/ncp-toolkit": "0.6.28",
41
- "@nextclaw/agent-chat-ui": "0.12.3",
42
- "@nextclaw/ncp-http-agent-client": "0.4.27",
43
- "@nextclaw/agent-chat": "0.3.14",
44
- "@nextclaw/ncp-react": "0.5.31"
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",
@@ -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
  };
@@ -14,6 +14,16 @@ import { pageResourceTab } from "@/features/right-panel-resources/managers/page-
14
14
  import { pageResourceFromTarget } from "@/features/right-panel-resources/utils/page-resource-identity.utils";
15
15
  import type { PageResource } from "@/features/right-panel-resources/types/page-resource.types";
16
16
  import { t } from "@/shared/lib/i18n";
17
+ import { parseSystemObjectReferenceUri } from "@nextclaw/shared";
18
+ import { RefreshCw } from "lucide-react";
19
+ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
20
+ import { usePanelApps } from "@/features/panel-apps/hooks/use-panel-apps";
21
+
22
+ function PanelAppResourceTitle({ appId, fallback }: { appId: string; fallback: string }) {
23
+ const panelApps = usePanelApps();
24
+ const title = panelApps.data?.entries.find((entry) => entry.appId === appId)?.title ?? fallback;
25
+ return <span className="min-w-0 flex-1 truncate text-sm">{title}</span>;
26
+ }
17
27
 
18
28
  function ResourcePageContent({ page }: { page: PageResource }) {
19
29
  const app = useAppPresenter();
@@ -22,6 +32,10 @@ function ResourcePageContent({ page }: { page: PageResource }) {
22
32
  const [revision, setRevision] = useState(0);
23
33
  const tab = useMemo(() => pageResourceTab(page), [page]);
24
34
  const renderer = PAGE_RESOURCE_RENDERERS[tab.kind];
35
+ const object = tab.kind === "system-object"
36
+ ? parseSystemObjectReferenceUri(page.uri)
37
+ : null;
38
+ const panelAppId = object?.objectType === "panel-app" ? object.objectId : null;
25
39
  const iframeInstanceId = `main:${page.uri}:${revision}`;
26
40
  const restore = useDocBrowserScrollRestoration({
27
41
  currentTab: tab,
@@ -55,6 +69,7 @@ function ResourcePageContent({ page }: { page: PageResource }) {
55
69
  tab,
56
70
  currentUrl: tab.currentUrl,
57
71
  refreshIframe: () => setRevision((value) => value + 1),
72
+ refreshVersion: revision,
58
73
  open: (uri?: string) => {
59
74
  const next = uri ? app.pageResourceManager.resolve(uri) : null;
60
75
  if (next) app.pageResourceManager.open(next, "main", navigate);
@@ -73,10 +88,22 @@ function ResourcePageContent({ page }: { page: PageResource }) {
73
88
  >
74
89
  <header className="flex min-h-10 shrink-0 items-center gap-2 border-b border-border/60 px-3">
75
90
  <PageResourceIcon uri={page.uri} icon={page.target.dockIcon} />
76
- <span className="min-w-0 flex-1 truncate text-sm">{page.title}</span>
91
+ {panelAppId ? (
92
+ <PanelAppResourceTitle appId={panelAppId} fallback={page.title} />
93
+ ) : (
94
+ <span className="min-w-0 flex-1 truncate text-sm">{page.title}</span>
95
+ )}
96
+ {panelAppId ? (
97
+ <IconActionButton
98
+ icon={<RefreshCw className="h-3.5 w-3.5" />}
99
+ label={t("panelAppsRefreshCurrent")}
100
+ onClick={params.refreshIframe}
101
+ size="sm"
102
+ />
103
+ ) : null}
77
104
  <PageResourceActionsMenu page={page} />
78
105
  </header>
79
- {renderer?.renderToolbar?.(params)}
106
+ {!panelAppId ? renderer?.renderToolbar?.(params) : null}
80
107
  <DocBrowserFrameContent
81
108
  currentTab={tab}
82
109
  currentUrl={tab.currentUrl}
@@ -18,6 +18,7 @@ import type {
18
18
  DocBrowserTab,
19
19
  } from "@/shared/components/doc-browser/doc-browser-context";
20
20
  import { PANEL_APPS_DOC_BROWSER_RENDERERS } from "@/features/panel-apps";
21
+ import { pageResourceFromTab, usePageResourceActions } from "@/features/right-panel-resources";
21
22
  import { PANEL_APP_SCROLL_RESTORATION_CONTRACT } from "@nextclaw/shared";
22
23
 
23
24
  const { navigateMock } = vi.hoisted(() => ({
@@ -28,6 +29,12 @@ const panelAppHooks = vi.hoisted(() => ({
28
29
  mutate: vi.fn(),
29
30
  }));
30
31
 
32
+ function DocBrowserWithPageActions() {
33
+ const actions = usePageResourceActions();
34
+ return <DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS}
35
+ getTabMenuGroups={(tab) => actions(pageResourceFromTab(tab), 'global')} />;
36
+ }
37
+
31
38
  vi.mock("react-router-dom", async () => ({
32
39
  ...((await vi.importActual("react-router-dom")) as object),
33
40
  useNavigate: () => navigateMock,
@@ -536,21 +543,12 @@ describe("DocBrowser panel app navigation", () => {
536
543
  docBrowserState.activeHistoryIndex = 1;
537
544
  docBrowserState.currentTab = panelAppTab;
538
545
 
539
- render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
546
+ render(<DocBrowserWithPageActions />);
540
547
 
541
548
  expect(screen.getAllByRole("button", { name: "Back" })).toHaveLength(1);
542
549
  expect(screen.queryByRole("button", { name: "Apps" })).toBeNull();
543
550
  await user.click(screen.getByRole("button", { name: "Back" }));
544
551
  expect(docBrowserState.goBack).toHaveBeenCalledTimes(1);
545
-
546
- expect(screen.queryByRole("button", { name: "Pin to left sidebar" })).toBeNull();
547
- await user.click(screen.getByRole("button", { name: "More panel app actions" }));
548
- await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
549
- await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
550
- expect(panelAppHooks.mutate).toHaveBeenCalledWith({
551
- id: "piano",
552
- preferences: { mainSidebar: true },
553
- });
554
552
  });
555
553
 
556
554
  it("keeps placement actions when a restored shortcut only has a stable current URL", async () => {
@@ -569,9 +567,9 @@ describe("DocBrowser panel app navigation", () => {
569
567
  docBrowserState.activeTabId = panelAppTab.id;
570
568
  docBrowserState.currentTab = panelAppTab;
571
569
 
572
- render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
570
+ render(<DocBrowserWithPageActions />);
573
571
 
574
- await user.click(screen.getByRole("button", { name: "More panel app actions" }));
572
+ await user.click(screen.getByRole("button", { name: "More tab actions" }));
575
573
  await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
576
574
  await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
577
575
  expect(panelAppHooks.mutate).toHaveBeenCalledWith({
@@ -623,7 +621,12 @@ describe("DocBrowser scroll restoration", () => {
623
621
  source: initialWindow,
624
622
  }));
625
623
 
626
- fireEvent.click(screen.getByRole("button", { name: "Refresh current panel app" }));
624
+ expect(screen.queryByRole("button", { name: "Refresh current panel app" })).toBeNull();
625
+ fireEvent.click(screen.getByRole("button", { name: "More tab actions" }));
626
+ const standalone = screen.getByRole("menuitem", { name: "Open in New Tab" });
627
+ expect(standalone.getAttribute("href")).toBe("/apps/panel/piano/standalone");
628
+ expect(standalone.getAttribute("target")).toBe("_blank");
629
+ fireEvent.click(screen.getByRole("menuitem", { name: "Refresh current panel app" }));
627
630
 
628
631
  const refreshedIframe = container.querySelector('iframe[title="Piano"]') as HTMLIFrameElement;
629
632
  const refreshedPostMessage = vi.fn();
@@ -1,11 +1,13 @@
1
1
  import type { PointerEvent, ReactNode } from 'react';
2
2
  import type { DocBrowserContextValue, DocBrowserTab } from './doc-browser-context';
3
+ import type { ContextMenuGroup } from '@/shared/components/ui/context-menu/context-menu';
3
4
 
4
5
  export type DocBrowserCustomTabRenderParams = {
5
6
  currentUrl: string;
6
7
  open: DocBrowserContextValue['open'];
7
8
  openTarget: DocBrowserContextValue['openTarget'];
8
9
  refreshIframe: () => void;
10
+ refreshVersion: number;
9
11
  tab: DocBrowserTab;
10
12
  };
11
13
 
@@ -17,6 +19,7 @@ export type DocBrowserIframeMessageParams = {
17
19
  };
18
20
 
19
21
  export type DocBrowserCustomTabRenderer = {
22
+ getTabMenuGroups?: (params: DocBrowserCustomTabRenderParams) => readonly ContextMenuGroup[];
20
23
  getIframeSandbox?: (tab: DocBrowserTab) => string | undefined;
21
24
  getTitle?: (tab: DocBrowserTab) => string;
22
25
  onIframeMessage?: (params: DocBrowserIframeMessageParams) => void;
@@ -265,6 +265,7 @@ export function DocBrowser({
265
265
  open,
266
266
  openTarget,
267
267
  refreshIframe,
268
+ refreshVersion: iframeReloadVersion,
268
269
  tab: currentTab,
269
270
  } : undefined;
270
271
  const customToolbar = customRenderParams ? customRenderer?.renderToolbar?.(customRenderParams) : null;
@@ -325,7 +326,12 @@ export function DocBrowser({
325
326
  onToggleDock={handleToggleDock}
326
327
  onSetActiveTab={setActiveTab}
327
328
  onCloseTab={closeTab}
328
- getTabMenuGroups={getTabMenuGroups}
329
+ getTabMenuGroups={(tab) => [
330
+ ...(getTabMenuGroups?.(tab) ?? []),
331
+ ...(tab.id === activeTabId && customRenderParams
332
+ ? customRenderer?.getTabMenuGroups?.(customRenderParams) ?? []
333
+ : []),
334
+ ]}
329
335
  />
330
336
  }>
331
337
  {panelContent}
@@ -164,6 +164,7 @@ export function ContextMenuItems({ groups, onClose }: {
164
164
  onClose={onClose} className={className}>{content}</ContextSubmenu>;
165
165
  const select = () => { item.onSelect?.(); onClose(item.href ? false : item.restoreFocus !== false); };
166
166
  return item.href ? <a key={item.key} role="menuitem" className={className} href={item.href} download={item.download}
167
+ target={item.target} rel={item.rel} referrerPolicy={item.referrerPolicy}
167
168
  onPointerEnter={() => setOpenKey(null)} onClick={(event) => { event.stopPropagation(); select(); }}>{content}</a>
168
169
  : <button key={item.key} type="button" role="menuitem" disabled={item.disabled} aria-pressed={item.pressed}
169
170
  data-menu-key={item.key} className={className} onPointerEnter={() => setOpenKey(null)}