@openhands/agent-canvas 1.0.0-alpha.8 → 1.0.0-alpha.9

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 (108) hide show
  1. package/README.md +17 -6
  2. package/build/assets/{add-backend-modal-KMmPQNZU.js → add-backend-modal-FsnpTTgO.js} +1 -1
  3. package/build/assets/{agent-server-conversation-service.api-DSl9G5UR.js → agent-server-conversation-service.api-BZmUqtiO.js} +1 -1
  4. package/build/assets/{automation-detail-g5-RZ0da.js → automation-detail-R-99FUce.js} +1 -1
  5. package/build/assets/{automations-list-DHoq_0MM.js → automations-list-Dfu2c-_D.js} +1 -1
  6. package/build/assets/backend-form-modal-DxYjqqAK.js +1 -0
  7. package/build/assets/browser-HrYc5Gce.js +5 -0
  8. package/build/assets/conversation--ldUK72N.js +19 -0
  9. package/build/assets/conversation-eNrhH94O.js +1 -0
  10. package/build/assets/conversation-panel-B49Jpqpb.js +1 -0
  11. package/build/assets/{conversation-service.api-C8pYCyV6.js → conversation-service.api--f8WglOC.js} +1 -1
  12. package/build/assets/conversation-websocket-context-BW68-J8o.js +3 -0
  13. package/build/assets/{entry.client-D9uR9Blz.js → entry.client-CqqXOSvd.js} +2 -2
  14. package/build/assets/{files-tab-B3A1NDlZ.js → files-tab-CQHdWpQt.js} +1 -1
  15. package/build/assets/git-control-bar-branch-button-C8u5rzjc.js +27 -0
  16. package/build/assets/{git-provider-icon-DYE9n7fs.js → git-provider-icon-D-a-rcLm.js} +1 -1
  17. package/build/assets/{home-dIzxi5Dd.js → home-DD0GroCu.js} +1 -1
  18. package/build/assets/{launch-hZ0ifhcV.js → launch-B2mbfOSm.js} +1 -1
  19. package/build/assets/llm-settings-BEyqixPI.js +1 -0
  20. package/build/assets/{llm-settings-CcHqGOYL.js → llm-settings-BdiaGFbg.js} +1 -1
  21. package/build/assets/{manage-backends-modal-rYeyGx7j.js → manage-backends-modal-s22zCdEW.js} +1 -1
  22. package/build/assets/{manifest-97e839da.js → manifest-9d1c34fb.js} +1 -1
  23. package/build/assets/{messages-T2ewVkbp.js → messages-6aOyUu3r.js} +1 -1
  24. package/build/assets/{path-utils-CqJboYxo.js → path-utils-BVbe598W.js} +1 -1
  25. package/build/assets/{planner-tab-BrntFmb1.js → planner-tab-bN6r1G-1.js} +1 -1
  26. package/build/assets/{recommended-automations-launcher-BI9NhG8Y.js → recommended-automations-launcher-mJhK6Atl.js} +1 -1
  27. package/build/assets/{root-BS1Td78t.js → root-3t9rxEpE.js} +2 -2
  28. package/build/assets/{root-layout-BLjAEgle.js → root-layout-BjVwHmta.js} +2 -2
  29. package/build/assets/{shared-conversation-a0QV8o99.js → shared-conversation-EZV0FRIf.js} +1 -1
  30. package/build/assets/{sidebar-mobile-menu-toggle-DTUNI1WQ.js → sidebar-mobile-menu-toggle-BnbzzpQl.js} +1 -1
  31. package/build/assets/{skills-settings-DOnMn9q1.js → skills-settings-CG2hu34D.js} +1 -1
  32. package/build/assets/{task-list-tab-Day9nhRT.js → task-list-tab-465DDju0.js} +1 -1
  33. package/build/assets/{terminal-ro4SNjUU.js → terminal-CcgBEVnC.js} +1 -1
  34. package/build/assets/{use-active-conversation-D15D9GgR.js → use-active-conversation-DS5j9R4q.js} +1 -1
  35. package/build/assets/{use-agent-state-DE5dlEXJ.js → use-agent-state-D2C9SeGw.js} +1 -1
  36. package/build/assets/{use-create-conversation-DW7AGgLA.js → use-create-conversation-BEZg__Vv.js} +1 -1
  37. package/build/assets/{use-event-store-CQZCcVz-.js → use-event-store-BT_gV3ut.js} +1 -1
  38. package/build/assets/{use-handle-plan-click-DpgEQDAV.js → use-handle-plan-click-uOpew2LO.js} +1 -1
  39. package/build/assets/{use-runtime-is-ready-XFbT16BD.js → use-runtime-is-ready-pGSbPddC.js} +1 -1
  40. package/build/assets/{use-skills-Xe0vjPMt.js → use-skills-BIvlWblA.js} +1 -1
  41. package/build/assets/{use-task-list-Bs90uF2N.js → use-task-list-DDeNHprj.js} +1 -1
  42. package/build/assets/{use-unified-vscode-url-BOsIOd-b.js → use-unified-vscode-url-wAMzv8Sn.js} +1 -1
  43. package/build/assets/{use-user-conversation-Mc0mQgkl.js → use-user-conversation-B_zDoSeh.js} +1 -1
  44. package/build/assets/{vscode-tab-C0ShhiSU.js → vscode-tab-B0vdh9gU.js} +1 -1
  45. package/build/index.html +4 -4
  46. package/dist/api/agent-server-adapter.cjs +1 -1
  47. package/dist/api/agent-server-adapter.cjs.map +1 -1
  48. package/dist/api/agent-server-adapter.js +1 -0
  49. package/dist/api/agent-server-adapter.js.map +1 -1
  50. package/dist/api/skills-service.cjs +1 -1
  51. package/dist/api/skills-service.cjs.map +1 -1
  52. package/dist/api/skills-service.d.ts +1 -1
  53. package/dist/api/skills-service.js +2 -2
  54. package/dist/api/skills-service.js.map +1 -1
  55. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  56. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  57. package/dist/components/features/backends/backend-form-modal.js +149 -142
  58. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  59. package/dist/components/features/conversation-panel/skills-modal.cjs +1 -1
  60. package/dist/components/features/conversation-panel/skills-modal.cjs.map +1 -1
  61. package/dist/components/features/conversation-panel/skills-modal.js +1 -1
  62. package/dist/components/features/conversation-panel/skills-modal.js.map +1 -1
  63. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  64. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  65. package/dist/contexts/conversation-websocket-context.js +97 -89
  66. package/dist/contexts/conversation-websocket-context.js.map +1 -1
  67. package/dist/hooks/chat/use-slash-command.cjs +1 -1
  68. package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
  69. package/dist/hooks/chat/use-slash-command.js +1 -1
  70. package/dist/hooks/chat/use-slash-command.js.map +1 -1
  71. package/dist/hooks/query/use-conversation-skills.cjs +2 -0
  72. package/dist/hooks/query/use-conversation-skills.cjs.map +1 -0
  73. package/dist/hooks/query/use-conversation-skills.d.ts +7 -0
  74. package/dist/hooks/query/use-conversation-skills.js +8 -0
  75. package/dist/hooks/query/use-conversation-skills.js.map +1 -0
  76. package/dist/hooks/query/use-skills.cjs +1 -1
  77. package/dist/hooks/query/use-skills.cjs.map +1 -1
  78. package/dist/hooks/query/use-skills.d.ts +6 -1
  79. package/dist/hooks/query/use-skills.js +3 -3
  80. package/dist/hooks/query/use-skills.js.map +1 -1
  81. package/dist/package.cjs +1 -1
  82. package/dist/package.cjs.map +1 -1
  83. package/dist/package.js +3 -3
  84. package/dist/package.js.map +1 -1
  85. package/dist/routes/conversation.cjs +1 -1
  86. package/dist/routes/conversation.cjs.map +1 -1
  87. package/dist/routes/conversation.js +61 -63
  88. package/dist/routes/conversation.js.map +1 -1
  89. package/dist/stores/use-event-store.cjs +1 -1
  90. package/dist/stores/use-event-store.cjs.map +1 -1
  91. package/dist/stores/use-event-store.d.ts +22 -0
  92. package/dist/stores/use-event-store.js +9 -1
  93. package/dist/stores/use-event-store.js.map +1 -1
  94. package/dist/ui/context-menu.d.ts +1 -1
  95. package/dist/ui/help-link.d.ts +1 -1
  96. package/package.json +3 -3
  97. package/scripts/dev-safe.mjs +35 -17
  98. package/scripts/dev-with-automation.mjs +24 -49
  99. package/build/assets/backend-form-modal-K6IMCr3p.js +0 -1
  100. package/build/assets/browser-DKG63inJ.js +0 -5
  101. package/build/assets/conversation-BD5WemJI.js +0 -19
  102. package/build/assets/conversation-C47K62n8.js +0 -1
  103. package/build/assets/conversation-panel-Dn-S56Gk.js +0 -1
  104. package/build/assets/conversation-websocket-context-Ywrxd_9p.js +0 -3
  105. package/build/assets/git-control-bar-branch-button-CcIpmyfM.js +0 -27
  106. package/build/assets/llm-settings-2036m7Wt.js +0 -1
  107. /package/build/assets/{link-external-Df8J52xI.js → link-external-C9d6Fo3x.js} +0 -0
  108. /package/build/assets/{use-llm-profiles-D3-KXwQ0.js → use-llm-profiles-O4a9V6RC.js} +0 -0
@@ -1,2 +1,2 @@
1
- require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../utils/constants.cjs`),t=require(`../../contexts/active-backend-context.cjs`),n=require(`../query/use-skills.cjs`),r=require(`../query/use-llm-profiles.cjs`);let i=require(`react`);function a(e){let t=window.getSelection();if(!t||t.rangeCount===0)return-1;let n=t.getRangeAt(0),r=n.cloneRange();return r.selectNodeContents(e),r.setEnd(n.startContainer,n.startOffset),r.toString().length}var o=o=>{let{data:s,isLoading:c}=n.useSkills(),l=t.useActiveBackend().backend.kind===`cloud`,{data:u,isLoading:d}=r.useLlmProfiles({enabled:!l}),[f,p]=(0,i.useState)(!1),[m,h]=(0,i.useState)(``),[g,_]=(0,i.useState)(`command`),[v,y]=(0,i.useState)(0),b=(0,i.useMemo)(()=>{let t=e.BUILT_IN_COMMANDS.filter(e=>e.command===`/new`?l:e.command===`/model`?!l:!0);return c||!s||s.forEach(e=>{let n=(e.triggers||[]).filter(e=>e.startsWith(`/`));n.length>0?n.forEach(n=>{t.push({skill:e,command:n})}):e.type===`agentskills`&&t.push({skill:e,command:`/${e.name}`})}),t},[s,c,l]),x=(0,i.useMemo)(()=>l?[]:(u?.profiles??[]).map(t=>{let n=`${e.MODEL_COMMAND} ${t.name}`;return{command:n,skill:{name:t.name,type:`agentskills`,content:t.model?`Switch to ${t.model}`:`Switch to this LLM profile`,triggers:[n]}}}),[u?.profiles,l]),S=(0,i.useMemo)(()=>{let e=g===`model-profile`?x:b;if(!m)return e;let t=m.toLowerCase();return e.filter(e=>e.command.toLowerCase().includes(t)||e.skill.name.toLowerCase().includes(t)||e.skill.content?.toLowerCase().includes(t))},[g,x,b,m]),C=(0,i.useRef)(f);C.current=f;let w=(0,i.useRef)(S);w.current=S;let T=(0,i.useRef)(v);T.current=v,(0,i.useEffect)(()=>{y(0)},[m]);let E=(0,i.useRef)(null),D=(0,i.useCallback)(()=>{let e=o.current;if(!e)return null;let t=(e.innerText||``).replace(/[\n\r]+$/,``),n=a(e);if(n<0)return null;let r=t.slice(0,n),i=r.match(/(^|\s)(\/model(?:\s+\S*)?)$/);if(i){let e=i[2],a=r.length-e.length,o=t.slice(n).match(/^\S*/),s=n+(o?o[0].length:0);return{kind:`model-profile`,text:e.replace(/^\/model(?:\s+)?/,``),start:a,end:s}}let s=r.match(/(^|\s)(\/\S*)$/);if(!s)return null;let c=s[2],l=r.length-c.length,u=t.slice(n).match(/^\S*/),d=n+(u?u[0].length:0);return{kind:`command`,text:c.slice(1),start:l,end:d}},[o]),O=(0,i.useCallback)(()=>{let e=D(),t=e?.kind===`model-profile`?x.length>0||d:b.length>0;e!==null&&t?(_(e.kind),h(e.text),E.current={start:e.start,end:e.end},p(!0)):(p(!1),h(``),_(`command`),E.current=null)},[D,d,x.length,b.length]),k=(0,i.useCallback)(e=>{let t=o.current;if(!t)return;let n=E.current,r=(t.innerText||``).replace(/[\n\r]+$/,``),i=`${e.command} `;if(n){t.textContent=r.slice(0,n.start)+i+r.slice(n.end);let e=n.start+i.length,a=t.firstChild;if(a){let t=document.createRange(),n=window.getSelection(),r=Math.min(e,a.textContent.length);t.setStart(a,r),t.collapse(!0),n?.removeAllRanges(),n?.addRange(t)}}else{t.textContent=i;let e=document.createRange(),n=window.getSelection();e.selectNodeContents(t),e.collapse(!1),n?.removeAllRanges(),n?.addRange(e)}p(!1),h(``),_(`command`),y(0),E.current=null,t.dispatchEvent(new InputEvent(`input`,{bubbles:!0})),t.focus()},[o]);return{isMenuOpen:f,filteredItems:S,selectedIndex:v,updateSlashMenu:O,selectItem:k,handleSlashKeyDown:(0,i.useCallback)(e=>{let t=w.current;if(!C.current||t.length===0)return!1;switch(e.key){case`ArrowDown`:return e.preventDefault(),y(e=>e<t.length-1?e+1:0),!0;case`ArrowUp`:return e.preventDefault(),y(e=>e>0?e-1:t.length-1),!0;case`Enter`:case`Tab`:{let n=t[T.current];return n?(e.preventDefault(),k(n),!0):!1}case`Escape`:return e.preventDefault(),p(!1),!0;case`ArrowLeft`:case`ArrowRight`:case`Home`:case`End`:return p(!1),!1;default:return!1}},[k]),closeMenu:(0,i.useCallback)(()=>p(!1),[])}};exports.useSlashCommand=o;
1
+ require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../utils/constants.cjs`),t=require(`../../contexts/active-backend-context.cjs`),n=require(`../query/use-conversation-skills.cjs`),r=require(`../query/use-llm-profiles.cjs`);let i=require(`react`);function a(e){let t=window.getSelection();if(!t||t.rangeCount===0)return-1;let n=t.getRangeAt(0),r=n.cloneRange();return r.selectNodeContents(e),r.setEnd(n.startContainer,n.startOffset),r.toString().length}var o=o=>{let{data:s,isLoading:c}=n.useConversationSkills(),l=t.useActiveBackend().backend.kind===`cloud`,{data:u,isLoading:d}=r.useLlmProfiles({enabled:!l}),[f,p]=(0,i.useState)(!1),[m,h]=(0,i.useState)(``),[g,_]=(0,i.useState)(`command`),[v,y]=(0,i.useState)(0),b=(0,i.useMemo)(()=>{let t=e.BUILT_IN_COMMANDS.filter(e=>e.command===`/new`?l:e.command===`/model`?!l:!0);return c||!s||s.forEach(e=>{let n=(e.triggers||[]).filter(e=>e.startsWith(`/`));n.length>0?n.forEach(n=>{t.push({skill:e,command:n})}):e.type===`agentskills`&&t.push({skill:e,command:`/${e.name}`})}),t},[s,c,l]),x=(0,i.useMemo)(()=>l?[]:(u?.profiles??[]).map(t=>{let n=`${e.MODEL_COMMAND} ${t.name}`;return{command:n,skill:{name:t.name,type:`agentskills`,content:t.model?`Switch to ${t.model}`:`Switch to this LLM profile`,triggers:[n]}}}),[u?.profiles,l]),S=(0,i.useMemo)(()=>{let e=g===`model-profile`?x:b;if(!m)return e;let t=m.toLowerCase();return e.filter(e=>e.command.toLowerCase().includes(t)||e.skill.name.toLowerCase().includes(t)||e.skill.content?.toLowerCase().includes(t))},[g,x,b,m]),C=(0,i.useRef)(f);C.current=f;let w=(0,i.useRef)(S);w.current=S;let T=(0,i.useRef)(v);T.current=v,(0,i.useEffect)(()=>{y(0)},[m]);let E=(0,i.useRef)(null),D=(0,i.useCallback)(()=>{let e=o.current;if(!e)return null;let t=(e.innerText||``).replace(/[\n\r]+$/,``),n=a(e);if(n<0)return null;let r=t.slice(0,n),i=r.match(/(^|\s)(\/model(?:\s+\S*)?)$/);if(i){let e=i[2],a=r.length-e.length,o=t.slice(n).match(/^\S*/),s=n+(o?o[0].length:0);return{kind:`model-profile`,text:e.replace(/^\/model(?:\s+)?/,``),start:a,end:s}}let s=r.match(/(^|\s)(\/\S*)$/);if(!s)return null;let c=s[2],l=r.length-c.length,u=t.slice(n).match(/^\S*/),d=n+(u?u[0].length:0);return{kind:`command`,text:c.slice(1),start:l,end:d}},[o]),O=(0,i.useCallback)(()=>{let e=D(),t=e?.kind===`model-profile`?x.length>0||d:b.length>0;e!==null&&t?(_(e.kind),h(e.text),E.current={start:e.start,end:e.end},p(!0)):(p(!1),h(``),_(`command`),E.current=null)},[D,d,x.length,b.length]),k=(0,i.useCallback)(e=>{let t=o.current;if(!t)return;let n=E.current,r=(t.innerText||``).replace(/[\n\r]+$/,``),i=`${e.command} `;if(n){t.textContent=r.slice(0,n.start)+i+r.slice(n.end);let e=n.start+i.length,a=t.firstChild;if(a){let t=document.createRange(),n=window.getSelection(),r=Math.min(e,a.textContent.length);t.setStart(a,r),t.collapse(!0),n?.removeAllRanges(),n?.addRange(t)}}else{t.textContent=i;let e=document.createRange(),n=window.getSelection();e.selectNodeContents(t),e.collapse(!1),n?.removeAllRanges(),n?.addRange(e)}p(!1),h(``),_(`command`),y(0),E.current=null,t.dispatchEvent(new InputEvent(`input`,{bubbles:!0})),t.focus()},[o]);return{isMenuOpen:f,filteredItems:S,selectedIndex:v,updateSlashMenu:O,selectItem:k,handleSlashKeyDown:(0,i.useCallback)(e=>{let t=w.current;if(!C.current||t.length===0)return!1;switch(e.key){case`ArrowDown`:return e.preventDefault(),y(e=>e<t.length-1?e+1:0),!0;case`ArrowUp`:return e.preventDefault(),y(e=>e>0?e-1:t.length-1),!0;case`Enter`:case`Tab`:{let n=t[T.current];return n?(e.preventDefault(),k(n),!0):!1}case`Escape`:return e.preventDefault(),p(!1),!0;case`ArrowLeft`:case`ArrowRight`:case`Home`:case`End`:return p(!1),!1;default:return!1}},[k]),closeMenu:(0,i.useCallback)(()=>p(!1),[])}};exports.useSlashCommand=o;
2
2
  //# sourceMappingURL=use-slash-command.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-slash-command.cjs","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useSkills } from \"#/hooks/query/use-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { Microagent } from \"#/api/open-hands.types\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\n\nexport type SlashCommandSkill = SkillInfo | Microagent;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n const { data: skills, isLoading: isSkillsLoading } = useSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles({\n enabled: !isCloud,\n });\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model is local-only — cloud backends don't have profiles\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n if (cmd.command === MODEL_COMMAND) return !isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n if (isCloud) return [];\n\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n content: profile.model\n ? `Switch to ${profile.model}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles, isCloud]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-slash-command.cjs","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useConversationSkills } from \"#/hooks/query/use-conversation-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { Microagent } from \"#/api/open-hands.types\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\n\nexport type SlashCommandSkill = SkillInfo | Microagent;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n // Scope the skill catalog to this conversation's attached workspace so the\n // slash menu lists the same project skills that were loaded into it.\n const { data: skills, isLoading: isSkillsLoading } = useConversationSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles({\n enabled: !isCloud,\n });\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model is local-only — cloud backends don't have profiles\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n if (cmd.command === MODEL_COMMAND) return !isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n if (isCloud) return [];\n\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n content: profile.model\n ? `Switch to ${profile.model}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles, isCloud]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { BUILT_IN_COMMANDS as e, MODEL_COMMAND as t } from "../../utils/constants.js";
2
2
  import { useActiveBackend as n } from "../../contexts/active-backend-context.js";
3
- import { useSkills as r } from "../query/use-skills.js";
3
+ import { useConversationSkills as r } from "../query/use-conversation-skills.js";
4
4
  import { useLlmProfiles as i } from "../query/use-llm-profiles.js";
5
5
  import { useCallback as a, useEffect as o, useMemo as s, useRef as c, useState as l } from "react";
6
6
  //#region src/hooks/chat/use-slash-command.ts
@@ -1 +1 @@
1
- {"version":3,"file":"use-slash-command.js","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useSkills } from \"#/hooks/query/use-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { Microagent } from \"#/api/open-hands.types\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\n\nexport type SlashCommandSkill = SkillInfo | Microagent;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n const { data: skills, isLoading: isSkillsLoading } = useSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles({\n enabled: !isCloud,\n });\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model is local-only — cloud backends don't have profiles\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n if (cmd.command === MODEL_COMMAND) return !isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n if (isCloud) return [];\n\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n content: profile.model\n ? `Switch to ${profile.model}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles, isCloud]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-slash-command.js","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useConversationSkills } from \"#/hooks/query/use-conversation-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { Microagent } from \"#/api/open-hands.types\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\n\nexport type SlashCommandSkill = SkillInfo | Microagent;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n // Scope the skill catalog to this conversation's attached workspace so the\n // slash menu lists the same project skills that were loaded into it.\n const { data: skills, isLoading: isSkillsLoading } = useConversationSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles({\n enabled: !isCloud,\n });\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model is local-only — cloud backends don't have profiles\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n if (cmd.command === MODEL_COMMAND) return !isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n if (isCloud) return [];\n\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n content: profile.model\n ? `Switch to ${profile.model}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles, isCloud]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`./use-active-conversation.cjs`),t=require(`./use-skills.cjs`);var n=()=>t.useSkills(e.useActiveConversation().data?.selected_workspace??void 0);exports.useConversationSkills=n;
2
+ //# sourceMappingURL=use-conversation-skills.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-conversation-skills.cjs","names":[],"sources":["../../../src/hooks/query/use-conversation-skills.ts"],"sourcesContent":["import { useActiveConversation } from \"./use-active-conversation\";\nimport { useSkills } from \"./use-skills\";\n\n/**\n * Skills catalog scoped to the active conversation's attached workspace, so\n * the slash-command menu and skills modal list the same project skills that\n * were loaded into the conversation. Falls back to the global workspace dir\n * for \"No workspace\" conversations (``selected_workspace`` is null).\n */\nexport const useConversationSkills = () => {\n const conversation = useActiveConversation();\n return useSkills(conversation.data?.selected_workspace ?? undefined);\n};\n"],"mappings":"+HASA,IAAa,MAEJ,EAAA,UADc,EAAA,uBACJ,CAAa,MAAM,oBAAsB,IAAA,GAAU"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Skills catalog scoped to the active conversation's attached workspace, so
3
+ * the slash-command menu and skills modal list the same project skills that
4
+ * were loaded into the conversation. Falls back to the global workspace dir
5
+ * for "No workspace" conversations (``selected_workspace`` is null).
6
+ */
7
+ export declare const useConversationSkills: () => import("@tanstack/react-query").UseQueryResult<import("../../types/settings").SkillInfo[], import("axios").AxiosError<unknown, any>>;
@@ -0,0 +1,8 @@
1
+ import { useActiveConversation as e } from "./use-active-conversation.js";
2
+ import { useSkills as t } from "./use-skills.js";
3
+ //#region src/hooks/query/use-conversation-skills.ts
4
+ var n = () => t(e().data?.selected_workspace ?? void 0);
5
+ //#endregion
6
+ export { n as useConversationSkills };
7
+
8
+ //# sourceMappingURL=use-conversation-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-conversation-skills.js","names":[],"sources":["../../../src/hooks/query/use-conversation-skills.ts"],"sourcesContent":["import { useActiveConversation } from \"./use-active-conversation\";\nimport { useSkills } from \"./use-skills\";\n\n/**\n * Skills catalog scoped to the active conversation's attached workspace, so\n * the slash-command menu and skills modal list the same project skills that\n * were loaded into the conversation. Falls back to the global workspace dir\n * for \"No workspace\" conversations (``selected_workspace`` is null).\n */\nexport const useConversationSkills = () => {\n const conversation = useActiveConversation();\n return useSkills(conversation.data?.selected_workspace ?? undefined);\n};\n"],"mappings":";;;AASA,IAAa,UAEJ,EADc,GACJ,CAAa,MAAM,sBAAsB,KAAA,EAAU"}
@@ -1,2 +1,2 @@
1
- require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs`),t=require(`../../api/skills-service.cjs`);var n=()=>e.useQuery({queryKey:[`skills`],queryFn:t.default.getSkills,staleTime:1e3*60*10,refetchOnWindowFocus:!1});exports.useSkills=n;
1
+ require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs`),t=require(`../../api/skills-service.cjs`);var n=n=>e.useQuery({queryKey:[`skills`,n??null],queryFn:()=>t.default.getSkills(n),staleTime:1e3*60*10,refetchOnWindowFocus:!1});exports.useSkills=n;
2
2
  //# sourceMappingURL=use-skills.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-skills.cjs","names":[],"sources":["../../../src/hooks/query/use-skills.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport SkillsService from \"#/api/skills-service\";\nimport { SkillInfo } from \"#/types/settings\";\n\nexport const useSkills = () =>\n useQuery<SkillInfo[]>({\n queryKey: [\"skills\"],\n queryFn: SkillsService.getSkills,\n staleTime: 1000 * 60 * 10, // 10 minutes – skill list rarely changes\n refetchOnWindowFocus: false,\n });\n"],"mappings":"gLAIA,IAAa,MACX,EAAA,SAAsB,CACpB,SAAU,CAAC,SAAS,CACpB,QAAS,EAAA,QAAc,UACvB,UAAW,IAAO,GAAK,GACvB,qBAAsB,GACvB,CAAC"}
1
+ {"version":3,"file":"use-skills.cjs","names":[],"sources":["../../../src/hooks/query/use-skills.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport SkillsService from \"#/api/skills-service\";\nimport { SkillInfo } from \"#/types/settings\";\n\n/**\n * @param projectDir Workspace root to load project skills from. Conversation\n * views pass the conversation's own workspace so the catalog matches the\n * skills loaded into that conversation; the global Skills page omits it.\n */\nexport const useSkills = (projectDir?: string) =>\n useQuery<SkillInfo[]>({\n queryKey: [\"skills\", projectDir ?? null],\n queryFn: () => SkillsService.getSkills(projectDir),\n staleTime: 1000 * 60 * 10, // 10 minutes – skill list rarely changes\n refetchOnWindowFocus: false,\n });\n"],"mappings":"gLASA,IAAa,EAAa,GACxB,EAAA,SAAsB,CACpB,SAAU,CAAC,SAAU,GAAc,KAAK,CACxC,YAAe,EAAA,QAAc,UAAU,EAAW,CAClD,UAAW,IAAO,GAAK,GACvB,qBAAsB,GACvB,CAAC"}
@@ -1,2 +1,7 @@
1
1
  import { SkillInfo } from "#/types/settings";
2
- export declare const useSkills: () => import("@tanstack/react-query").UseQueryResult<SkillInfo[], import("axios").AxiosError<unknown, any>>;
2
+ /**
3
+ * @param projectDir Workspace root to load project skills from. Conversation
4
+ * views pass the conversation's own workspace so the catalog matches the
5
+ * skills loaded into that conversation; the global Skills page omits it.
6
+ */
7
+ export declare const useSkills: (projectDir?: string) => import("@tanstack/react-query").UseQueryResult<SkillInfo[], import("axios").AxiosError<unknown, any>>;
@@ -1,9 +1,9 @@
1
1
  import { useQuery as e } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
2
2
  import t from "../../api/skills-service.js";
3
3
  //#region src/hooks/query/use-skills.ts
4
- var n = () => e({
5
- queryKey: ["skills"],
6
- queryFn: t.getSkills,
4
+ var n = (n) => e({
5
+ queryKey: ["skills", n ?? null],
6
+ queryFn: () => t.getSkills(n),
7
7
  staleTime: 1e3 * 60 * 10,
8
8
  refetchOnWindowFocus: !1
9
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"use-skills.js","names":[],"sources":["../../../src/hooks/query/use-skills.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport SkillsService from \"#/api/skills-service\";\nimport { SkillInfo } from \"#/types/settings\";\n\nexport const useSkills = () =>\n useQuery<SkillInfo[]>({\n queryKey: [\"skills\"],\n queryFn: SkillsService.getSkills,\n staleTime: 1000 * 60 * 10, // 10 minutes – skill list rarely changes\n refetchOnWindowFocus: false,\n });\n"],"mappings":";;;AAIA,IAAa,UACX,EAAsB;CACpB,UAAU,CAAC,SAAS;CACpB,SAAS,EAAc;CACvB,WAAW,MAAO,KAAK;CACvB,sBAAsB;CACvB,CAAC"}
1
+ {"version":3,"file":"use-skills.js","names":[],"sources":["../../../src/hooks/query/use-skills.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport SkillsService from \"#/api/skills-service\";\nimport { SkillInfo } from \"#/types/settings\";\n\n/**\n * @param projectDir Workspace root to load project skills from. Conversation\n * views pass the conversation's own workspace so the catalog matches the\n * skills loaded into that conversation; the global Skills page omits it.\n */\nexport const useSkills = (projectDir?: string) =>\n useQuery<SkillInfo[]>({\n queryKey: [\"skills\", projectDir ?? null],\n queryFn: () => SkillsService.getSkills(projectDir),\n staleTime: 1000 * 60 * 10, // 10 minutes – skill list rarely changes\n refetchOnWindowFocus: false,\n });\n"],"mappings":";;;AASA,IAAa,KAAa,MACxB,EAAsB;CACpB,UAAU,CAAC,UAAU,KAAc,KAAK;CACxC,eAAe,EAAc,UAAU,EAAW;CAClD,WAAW,MAAO,KAAK;CACvB,sBAAsB;CACvB,CAAC"}
package/dist/package.cjs CHANGED
@@ -1,2 +1,2 @@
1
- require(`./_virtual/_rolldown/runtime.cjs`);var e={name:`@openhands/agent-canvas`,version:`1.0.0-alpha.8`,description:`Agent Canvas UI for OpenHands - run AI coding agents with a visual interface`,license:`MIT`,private:!1,type:`module`,repository:{type:`git`,url:`https://github.com/OpenHands/agent-canvas`},homepage:`https://github.com/OpenHands/agent-canvas#readme`,bugs:{url:`https://github.com/OpenHands/agent-canvas/issues`},bin:{"agent-canvas":`bin/agent-canvas.mjs`},engines:{node:`>=22.12.0`},dependencies:{"@heroui/react":`2.8.10`,"@microlink/react-json-view":`1.31.20`,"@monaco-editor/react":`4.7.0`,"@openhands/extensions":`git+https://github.com/OpenHands/extensions.git#39711065f53166c52608462f60a4c8507253ce56`,"@openhands/typescript-client":`1.24.0`,"@react-router/node":`7.14.2`,"@react-router/serve":`7.14.2`,"@tailwindcss/vite":`4.2.4`,"@tanstack/react-query":`5.100.9`,"@types/shell-quote":`^1.7.5`,"@uidotdev/usehooks":`2.4.1`,"@xterm/addon-fit":`0.11.0`,"@xterm/xterm":`6.0.0`,axios:`1.16.0`,"class-variance-authority":`0.7.1`,clsx:`2.1.1`,downshift:`9.3.2`,"framer-motion":`12.38.0`,i18next:`26.0.8`,"i18next-browser-languagedetector":`8.2.1`,"i18next-http-backend":`4.0.0`,isbot:`5.1.39`,"lucide-react":`1.14.0`,"monaco-editor":`0.55.1`,"posthog-js":`1.372.6`,react:`19.2.5`,"react-dom":`19.2.5`,"react-hot-toast":`2.6.0`,"react-i18next":`17.0.6`,"react-icons":`5.6.0`,"react-markdown":`10.1.0`,"react-router":`7.14.2`,"react-syntax-highlighter":`16.1.1`,"rehype-raw":`7.0.0`,"rehype-sanitize":`6.0.0`,"remark-breaks":`4.0.0`,"remark-gfm":`4.0.1`,"shell-quote":`^1.8.3`,"sirv-cli":`3.0.1`,"socket.io-client":`4.8.3`,"tailwind-merge":`3.5.0`,"tailwind-scrollbar":`4.0.2`,"unist-util-visit":`5.1.0`,uuid:`14.0.0`,vite:`8.0.10`,zustand:`5.0.12`},scripts:{dev:`node --env-file-if-exists=.env scripts/dev-with-automation.mjs`,"dev:static":`node --env-file-if-exists=.env scripts/dev-static.mjs`,"dev:extra-backend":`node --env-file-if-exists=.env scripts/dev-extra-backend.mjs`,"dev:minimal":`node --env-file-if-exists=.env scripts/dev-safe.mjs`,"dev:frontend":`npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev`,"dev:mock":`npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev`,build:`npm run build:app`,"build:mock":`npm run make-i18n && cross-env VITE_MOCK_API=true react-router build`,start:`npx sirv-cli build/ --single`,test:`npm run make-i18n && vitest run`,"test:e2e":`playwright test --pass-with-no-tests`,"test:e2e:live":`node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs`,"test:e2e:mock-llm":`playwright test --config=playwright.mock-llm.config.ts`,"test:e2e:snapshots":`playwright test tests/e2e/snapshots --project=chromium --retries=0`,"test:e2e:snapshots:update":`playwright test tests/e2e/snapshots --project=chromium --update-snapshots`,"test:coverage":`npm run make-i18n && vitest run --coverage`,dev_wsl:`VITE_WATCH_USE_POLLING=true vite`,preview:`vite preview`,"make-i18n":`node scripts/make-i18n-translations.cjs`,prelint:`npm run make-i18n`,lint:`npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}`,"lint:fix":`eslint src --fix && prettier --write src/**/*.{ts,tsx}`,prepare:`husky`,typecheck:`react-router typegen && tsc`,"typecheck:staged":`react-router typegen && npx tsc --noEmit --skipLibCheck`,"check-translation-completeness":`node scripts/check-translation-completeness.cjs`,"build:app":`npm run make-i18n && react-router build`,"build:lib":`npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json`,"build:docker":`node scripts/docker-build.mjs`},"lint-staged":{"src/**/*.{ts,tsx,js}":[`eslint --fix`,`prettier --write`],"src/**/*.{ts,tsx}":[`bash -c 'npm run typecheck:staged'`],"src/**/*":[`npm run check-translation-completeness`]},devDependencies:{"@eslint/eslintrc":`3.3.1`,"@eslint/js":`9.39.4`,"@mswjs/socket.io-binding":`0.2.0`,"@playwright/test":`1.59.1`,"@react-router/dev":`7.14.2`,"@tailwindcss/typography":`0.5.19`,"@tanstack/eslint-plugin-query":`5.100.9`,"@testing-library/dom":`10.4.1`,"@testing-library/jest-dom":`6.9.1`,"@testing-library/react":`16.3.2`,"@testing-library/user-event":`14.6.1`,"@types/mdast":`4.0.4`,"@types/node":`25.6.0`,"@types/react":`19.2.14`,"@types/react-dom":`19.2.3`,"@types/react-syntax-highlighter":`15.5.13`,"@typescript-eslint/eslint-plugin":`8.59.2`,"@typescript-eslint/parser":`8.59.2`,"@vercel/react-router":`1.3.0`,"@vitest/coverage-v8":`4.1.5`,"cross-env":`10.1.0`,eslint:`9.39.4`,"eslint-config-prettier":`10.1.8`,"eslint-import-resolver-typescript":`4.4.4`,"eslint-plugin-i18next":`6.1.4`,"eslint-plugin-import-x":`4.16.2`,"eslint-plugin-jsx-a11y":`6.10.2`,"eslint-plugin-prettier":`5.5.5`,"eslint-plugin-react":`7.37.5`,"eslint-plugin-react-hooks":`7.1.1`,"eslint-plugin-unused-imports":`4.4.1`,globals:`16.5.0`,husky:`9.1.7`,jsdom:`29.1.1`,"lint-staged":`16.4.0`,msw:`2.14.2`,"postcss-prefix-selector":`2.1.1`,prettier:`3.8.3`,tailwindcss:`4.2.4`,typescript:`6.0.3`,"vite-plugin-svgr":`5.2.0`,vitest:`4.1.5`},packageManager:`npm@10.5.0`,volta:{node:`22.12.0`},msw:{workerDirectory:[`public`]},overrides:{dompurify:`3.3.2`},main:`./dist/index.cjs`,module:`./dist/index.js`,types:`./dist/index.d.ts`,files:[`dist`,`bin`,`build`,`config`,`scripts`,`tools`],exports:{".":{types:`./dist/index.d.ts`,import:`./dist/index.js`,require:`./dist/index.cjs`},"./browser":{types:`./dist/components/browser/index.d.ts`,import:`./dist/components/browser/index.js`,require:`./dist/components/browser/index.cjs`},"./conversation":{types:`./dist/components/conversation/index.d.ts`,import:`./dist/components/conversation/index.js`,require:`./dist/components/conversation/index.cjs`},"./files":{types:`./dist/components/files/index.d.ts`,import:`./dist/components/files/index.js`,require:`./dist/components/files/index.cjs`},"./settings":{types:`./dist/components/settings/index.d.ts`,import:`./dist/components/settings/index.js`,require:`./dist/components/settings/index.cjs`},"./sidebar":{types:`./dist/components/sidebar/index.d.ts`,import:`./dist/components/sidebar/index.js`,require:`./dist/components/sidebar/index.cjs`},"./terminal":{types:`./dist/components/terminal/index.d.ts`,import:`./dist/components/terminal/index.js`,require:`./dist/components/terminal/index.cjs`},"./i18n":{types:`./dist/i18n/index.d.ts`,import:`./dist/i18n/index.js`,require:`./dist/i18n/index.cjs`},"./package.json":`./package.json`},peerDependencies:{react:`19.2.5`,"react-dom":`19.2.5`,"react-router":`7.14.2`}};exports.default=e;
1
+ require(`./_virtual/_rolldown/runtime.cjs`);var e={name:`@openhands/agent-canvas`,version:`1.0.0-alpha.9`,description:`Agent Canvas UI for OpenHands - run AI coding agents with a visual interface`,license:`MIT`,private:!1,type:`module`,repository:{type:`git`,url:`https://github.com/OpenHands/agent-canvas`},homepage:`https://github.com/OpenHands/agent-canvas#readme`,bugs:{url:`https://github.com/OpenHands/agent-canvas/issues`},bin:{"agent-canvas":`bin/agent-canvas.mjs`},engines:{node:`>=22.12.0`},dependencies:{"@heroui/react":`2.8.10`,"@microlink/react-json-view":`1.31.20`,"@monaco-editor/react":`4.7.0`,"@openhands/extensions":`git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909`,"@openhands/typescript-client":`1.24.3`,"@react-router/node":`7.14.2`,"@react-router/serve":`7.14.2`,"@tailwindcss/vite":`4.2.4`,"@tanstack/react-query":`5.100.9`,"@types/shell-quote":`^1.7.5`,"@uidotdev/usehooks":`2.4.1`,"@xterm/addon-fit":`0.11.0`,"@xterm/xterm":`6.0.0`,axios:`1.16.0`,"class-variance-authority":`0.7.1`,clsx:`2.1.1`,downshift:`9.3.2`,"framer-motion":`12.38.0`,i18next:`26.0.8`,"i18next-browser-languagedetector":`8.2.1`,"i18next-http-backend":`4.0.0`,isbot:`5.1.39`,"lucide-react":`1.14.0`,"monaco-editor":`0.55.1`,"posthog-js":`1.372.6`,react:`19.2.5`,"react-dom":`19.2.5`,"react-hot-toast":`2.6.0`,"react-i18next":`17.0.6`,"react-icons":`5.6.0`,"react-markdown":`10.1.0`,"react-router":`7.14.2`,"react-syntax-highlighter":`16.1.1`,"rehype-raw":`7.0.0`,"rehype-sanitize":`6.0.0`,"remark-breaks":`4.0.0`,"remark-gfm":`4.0.1`,"shell-quote":`^1.8.3`,"sirv-cli":`3.0.1`,"socket.io-client":`4.8.3`,"tailwind-merge":`3.5.0`,"tailwind-scrollbar":`4.0.2`,"unist-util-visit":`5.1.0`,uuid:`14.0.0`,vite:`8.0.10`,zustand:`5.0.12`},scripts:{dev:`node --env-file-if-exists=.env scripts/dev-with-automation.mjs`,"dev:static":`node --env-file-if-exists=.env scripts/dev-static.mjs`,"dev:extra-backend":`node --env-file-if-exists=.env scripts/dev-extra-backend.mjs`,"dev:minimal":`node --env-file-if-exists=.env scripts/dev-safe.mjs`,"dev:frontend":`npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev`,"dev:mock":`npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev`,build:`npm run build:app`,"build:mock":`npm run make-i18n && cross-env VITE_MOCK_API=true react-router build`,start:`npx sirv-cli build/ --single`,test:`npm run make-i18n && vitest run`,"test:e2e":`playwright test --pass-with-no-tests`,"test:e2e:live":`node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs`,"test:e2e:mock-llm":`playwright test --config=playwright.mock-llm.config.ts`,"test:e2e:snapshots":`playwright test tests/e2e/snapshots --project=chromium --retries=0`,"test:e2e:snapshots:update":`playwright test tests/e2e/snapshots --project=chromium --update-snapshots`,"test:coverage":`npm run make-i18n && vitest run --coverage`,dev_wsl:`VITE_WATCH_USE_POLLING=true vite`,preview:`vite preview`,"make-i18n":`node scripts/make-i18n-translations.cjs`,prelint:`npm run make-i18n`,lint:`npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}`,"lint:fix":`eslint src --fix && prettier --write src/**/*.{ts,tsx}`,prepare:`husky`,typecheck:`react-router typegen && tsc`,"typecheck:staged":`react-router typegen && npx tsc --noEmit --skipLibCheck`,"check-translation-completeness":`node scripts/check-translation-completeness.cjs`,"build:app":`npm run make-i18n && react-router build`,"build:lib":`npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json`,"build:docker":`node scripts/docker-build.mjs`},"lint-staged":{"src/**/*.{ts,tsx,js}":[`eslint --fix`,`prettier --write`],"src/**/*.{ts,tsx}":[`bash -c 'npm run typecheck:staged'`],"src/**/*":[`npm run check-translation-completeness`]},devDependencies:{"@eslint/eslintrc":`3.3.1`,"@eslint/js":`9.39.4`,"@mswjs/socket.io-binding":`0.2.0`,"@playwright/test":`1.59.1`,"@react-router/dev":`7.14.2`,"@tailwindcss/typography":`0.5.19`,"@tanstack/eslint-plugin-query":`5.100.9`,"@testing-library/dom":`10.4.1`,"@testing-library/jest-dom":`6.9.1`,"@testing-library/react":`16.3.2`,"@testing-library/user-event":`14.6.1`,"@types/mdast":`4.0.4`,"@types/node":`25.6.0`,"@types/react":`19.2.14`,"@types/react-dom":`19.2.3`,"@types/react-syntax-highlighter":`15.5.13`,"@typescript-eslint/eslint-plugin":`8.59.2`,"@typescript-eslint/parser":`8.59.2`,"@vercel/react-router":`1.3.0`,"@vitest/coverage-v8":`4.1.5`,"cross-env":`10.1.0`,eslint:`9.39.4`,"eslint-config-prettier":`10.1.8`,"eslint-import-resolver-typescript":`4.4.4`,"eslint-plugin-i18next":`6.1.4`,"eslint-plugin-import-x":`4.16.2`,"eslint-plugin-jsx-a11y":`6.10.2`,"eslint-plugin-prettier":`5.5.5`,"eslint-plugin-react":`7.37.5`,"eslint-plugin-react-hooks":`7.1.1`,"eslint-plugin-unused-imports":`4.4.1`,globals:`16.5.0`,husky:`9.1.7`,jsdom:`29.1.1`,"lint-staged":`16.4.0`,msw:`2.14.2`,"postcss-prefix-selector":`2.1.1`,prettier:`3.8.3`,tailwindcss:`4.2.4`,typescript:`6.0.3`,"vite-plugin-svgr":`5.2.0`,vitest:`4.1.5`},packageManager:`npm@10.5.0`,volta:{node:`22.12.0`},msw:{workerDirectory:[`public`]},overrides:{dompurify:`3.3.2`},main:`./dist/index.cjs`,module:`./dist/index.js`,types:`./dist/index.d.ts`,files:[`dist`,`bin`,`build`,`config`,`scripts`,`tools`],exports:{".":{types:`./dist/index.d.ts`,import:`./dist/index.js`,require:`./dist/index.cjs`},"./browser":{types:`./dist/components/browser/index.d.ts`,import:`./dist/components/browser/index.js`,require:`./dist/components/browser/index.cjs`},"./conversation":{types:`./dist/components/conversation/index.d.ts`,import:`./dist/components/conversation/index.js`,require:`./dist/components/conversation/index.cjs`},"./files":{types:`./dist/components/files/index.d.ts`,import:`./dist/components/files/index.js`,require:`./dist/components/files/index.cjs`},"./settings":{types:`./dist/components/settings/index.d.ts`,import:`./dist/components/settings/index.js`,require:`./dist/components/settings/index.cjs`},"./sidebar":{types:`./dist/components/sidebar/index.d.ts`,import:`./dist/components/sidebar/index.js`,require:`./dist/components/sidebar/index.cjs`},"./terminal":{types:`./dist/components/terminal/index.d.ts`,import:`./dist/components/terminal/index.js`,require:`./dist/components/terminal/index.cjs`},"./i18n":{types:`./dist/i18n/index.d.ts`,import:`./dist/i18n/index.js`,require:`./dist/i18n/index.cjs`},"./package.json":`./package.json`},peerDependencies:{react:`19.2.5`,"react-dom":`19.2.5`,"react-router":`7.14.2`}};exports.default=e;
2
2
  //# sourceMappingURL=package.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"package.cjs","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@openhands/agent-canvas\",\n \"version\": \"1.0.0-alpha.8\",\n \"description\": \"Agent Canvas UI for OpenHands - run AI coding agents with a visual interface\",\n \"license\": \"MIT\",\n \"private\": false,\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/OpenHands/agent-canvas\"\n },\n \"homepage\": \"https://github.com/OpenHands/agent-canvas#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/OpenHands/agent-canvas/issues\"\n },\n \"bin\": {\n \"agent-canvas\": \"bin/agent-canvas.mjs\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"dependencies\": {\n \"@heroui/react\": \"2.8.10\",\n \"@microlink/react-json-view\": \"1.31.20\",\n \"@monaco-editor/react\": \"4.7.0\",\n \"@openhands/extensions\": \"git+https://github.com/OpenHands/extensions.git#39711065f53166c52608462f60a4c8507253ce56\",\n \"@openhands/typescript-client\": \"1.24.0\",\n \"@react-router/node\": \"7.14.2\",\n \"@react-router/serve\": \"7.14.2\",\n \"@tailwindcss/vite\": \"4.2.4\",\n \"@tanstack/react-query\": \"5.100.9\",\n \"@types/shell-quote\": \"^1.7.5\",\n \"@uidotdev/usehooks\": \"2.4.1\",\n \"@xterm/addon-fit\": \"0.11.0\",\n \"@xterm/xterm\": \"6.0.0\",\n \"axios\": \"1.16.0\",\n \"class-variance-authority\": \"0.7.1\",\n \"clsx\": \"2.1.1\",\n \"downshift\": \"9.3.2\",\n \"framer-motion\": \"12.38.0\",\n \"i18next\": \"26.0.8\",\n \"i18next-browser-languagedetector\": \"8.2.1\",\n \"i18next-http-backend\": \"4.0.0\",\n \"isbot\": \"5.1.39\",\n \"lucide-react\": \"1.14.0\",\n \"monaco-editor\": \"0.55.1\",\n \"posthog-js\": \"1.372.6\",\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-hot-toast\": \"2.6.0\",\n \"react-i18next\": \"17.0.6\",\n \"react-icons\": \"5.6.0\",\n \"react-markdown\": \"10.1.0\",\n \"react-router\": \"7.14.2\",\n \"react-syntax-highlighter\": \"16.1.1\",\n \"rehype-raw\": \"7.0.0\",\n \"rehype-sanitize\": \"6.0.0\",\n \"remark-breaks\": \"4.0.0\",\n \"remark-gfm\": \"4.0.1\",\n \"shell-quote\": \"^1.8.3\",\n \"sirv-cli\": \"3.0.1\",\n \"socket.io-client\": \"4.8.3\",\n \"tailwind-merge\": \"3.5.0\",\n \"tailwind-scrollbar\": \"4.0.2\",\n \"unist-util-visit\": \"5.1.0\",\n \"uuid\": \"14.0.0\",\n \"vite\": \"8.0.10\",\n \"zustand\": \"5.0.12\"\n },\n \"scripts\": {\n \"dev\": \"node --env-file-if-exists=.env scripts/dev-with-automation.mjs\",\n \"dev:static\": \"node --env-file-if-exists=.env scripts/dev-static.mjs\",\n \"dev:extra-backend\": \"node --env-file-if-exists=.env scripts/dev-extra-backend.mjs\",\n \"dev:minimal\": \"node --env-file-if-exists=.env scripts/dev-safe.mjs\",\n \"dev:frontend\": \"npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev\",\n \"dev:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev\",\n \"build\": \"npm run build:app\",\n \"build:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router build\",\n \"start\": \"npx sirv-cli build/ --single\",\n \"test\": \"npm run make-i18n && vitest run\",\n \"test:e2e\": \"playwright test --pass-with-no-tests\",\n \"test:e2e:live\": \"node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs\",\n \"test:e2e:mock-llm\": \"playwright test --config=playwright.mock-llm.config.ts\",\n \"test:e2e:snapshots\": \"playwright test tests/e2e/snapshots --project=chromium --retries=0\",\n \"test:e2e:snapshots:update\": \"playwright test tests/e2e/snapshots --project=chromium --update-snapshots\",\n \"test:coverage\": \"npm run make-i18n && vitest run --coverage\",\n \"dev_wsl\": \"VITE_WATCH_USE_POLLING=true vite\",\n \"preview\": \"vite preview\",\n \"make-i18n\": \"node scripts/make-i18n-translations.cjs\",\n \"prelint\": \"npm run make-i18n\",\n \"lint\": \"npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint src --fix && prettier --write src/**/*.{ts,tsx}\",\n \"prepare\": \"husky\",\n \"typecheck\": \"react-router typegen && tsc\",\n \"typecheck:staged\": \"react-router typegen && npx tsc --noEmit --skipLibCheck\",\n \"check-translation-completeness\": \"node scripts/check-translation-completeness.cjs\",\n \"build:app\": \"npm run make-i18n && react-router build\",\n \"build:lib\": \"npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json\",\n \"build:docker\": \"node scripts/docker-build.mjs\"\n },\n \"lint-staged\": {\n \"src/**/*.{ts,tsx,js}\": [\n \"eslint --fix\",\n \"prettier --write\"\n ],\n \"src/**/*.{ts,tsx}\": [\n \"bash -c 'npm run typecheck:staged'\"\n ],\n \"src/**/*\": [\n \"npm run check-translation-completeness\"\n ]\n },\n \"devDependencies\": {\n \"@eslint/eslintrc\": \"3.3.1\",\n \"@eslint/js\": \"9.39.4\",\n \"@mswjs/socket.io-binding\": \"0.2.0\",\n \"@playwright/test\": \"1.59.1\",\n \"@react-router/dev\": \"7.14.2\",\n \"@tailwindcss/typography\": \"0.5.19\",\n \"@tanstack/eslint-plugin-query\": \"5.100.9\",\n \"@testing-library/dom\": \"10.4.1\",\n \"@testing-library/jest-dom\": \"6.9.1\",\n \"@testing-library/react\": \"16.3.2\",\n \"@testing-library/user-event\": \"14.6.1\",\n \"@types/mdast\": \"4.0.4\",\n \"@types/node\": \"25.6.0\",\n \"@types/react\": \"19.2.14\",\n \"@types/react-dom\": \"19.2.3\",\n \"@types/react-syntax-highlighter\": \"15.5.13\",\n \"@typescript-eslint/eslint-plugin\": \"8.59.2\",\n \"@typescript-eslint/parser\": \"8.59.2\",\n \"@vercel/react-router\": \"1.3.0\",\n \"@vitest/coverage-v8\": \"4.1.5\",\n \"cross-env\": \"10.1.0\",\n \"eslint\": \"9.39.4\",\n \"eslint-config-prettier\": \"10.1.8\",\n \"eslint-import-resolver-typescript\": \"4.4.4\",\n \"eslint-plugin-i18next\": \"6.1.4\",\n \"eslint-plugin-import-x\": \"4.16.2\",\n \"eslint-plugin-jsx-a11y\": \"6.10.2\",\n \"eslint-plugin-prettier\": \"5.5.5\",\n \"eslint-plugin-react\": \"7.37.5\",\n \"eslint-plugin-react-hooks\": \"7.1.1\",\n \"eslint-plugin-unused-imports\": \"4.4.1\",\n \"globals\": \"16.5.0\",\n \"husky\": \"9.1.7\",\n \"jsdom\": \"29.1.1\",\n \"lint-staged\": \"16.4.0\",\n \"msw\": \"2.14.2\",\n \"postcss-prefix-selector\": \"2.1.1\",\n \"prettier\": \"3.8.3\",\n \"tailwindcss\": \"4.2.4\",\n \"typescript\": \"6.0.3\",\n \"vite-plugin-svgr\": \"5.2.0\",\n \"vitest\": \"4.1.5\"\n },\n \"packageManager\": \"npm@10.5.0\",\n \"volta\": {\n \"node\": \"22.12.0\"\n },\n \"msw\": {\n \"workerDirectory\": [\n \"public\"\n ]\n },\n \"overrides\": {\n \"dompurify\": \"3.3.2\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"bin\",\n \"build\",\n \"config\",\n \"scripts\",\n \"tools\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./browser\": {\n \"types\": \"./dist/components/browser/index.d.ts\",\n \"import\": \"./dist/components/browser/index.js\",\n \"require\": \"./dist/components/browser/index.cjs\"\n },\n \"./conversation\": {\n \"types\": \"./dist/components/conversation/index.d.ts\",\n \"import\": \"./dist/components/conversation/index.js\",\n \"require\": \"./dist/components/conversation/index.cjs\"\n },\n \"./files\": {\n \"types\": \"./dist/components/files/index.d.ts\",\n \"import\": \"./dist/components/files/index.js\",\n \"require\": \"./dist/components/files/index.cjs\"\n },\n \"./settings\": {\n \"types\": \"./dist/components/settings/index.d.ts\",\n \"import\": \"./dist/components/settings/index.js\",\n \"require\": \"./dist/components/settings/index.cjs\"\n },\n \"./sidebar\": {\n \"types\": \"./dist/components/sidebar/index.d.ts\",\n \"import\": \"./dist/components/sidebar/index.js\",\n \"require\": \"./dist/components/sidebar/index.cjs\"\n },\n \"./terminal\": {\n \"types\": \"./dist/components/terminal/index.d.ts\",\n \"import\": \"./dist/components/terminal/index.js\",\n \"require\": \"./dist/components/terminal/index.cjs\"\n },\n \"./i18n\": {\n \"types\": \"./dist/i18n/index.d.ts\",\n \"import\": \"./dist/i18n/index.js\",\n \"require\": \"./dist/i18n/index.cjs\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"peerDependencies\": {\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-router\": \"7.14.2\"\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"package.cjs","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@openhands/agent-canvas\",\n \"version\": \"1.0.0-alpha.9\",\n \"description\": \"Agent Canvas UI for OpenHands - run AI coding agents with a visual interface\",\n \"license\": \"MIT\",\n \"private\": false,\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/OpenHands/agent-canvas\"\n },\n \"homepage\": \"https://github.com/OpenHands/agent-canvas#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/OpenHands/agent-canvas/issues\"\n },\n \"bin\": {\n \"agent-canvas\": \"bin/agent-canvas.mjs\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"dependencies\": {\n \"@heroui/react\": \"2.8.10\",\n \"@microlink/react-json-view\": \"1.31.20\",\n \"@monaco-editor/react\": \"4.7.0\",\n \"@openhands/extensions\": \"git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909\",\n \"@openhands/typescript-client\": \"1.24.3\",\n \"@react-router/node\": \"7.14.2\",\n \"@react-router/serve\": \"7.14.2\",\n \"@tailwindcss/vite\": \"4.2.4\",\n \"@tanstack/react-query\": \"5.100.9\",\n \"@types/shell-quote\": \"^1.7.5\",\n \"@uidotdev/usehooks\": \"2.4.1\",\n \"@xterm/addon-fit\": \"0.11.0\",\n \"@xterm/xterm\": \"6.0.0\",\n \"axios\": \"1.16.0\",\n \"class-variance-authority\": \"0.7.1\",\n \"clsx\": \"2.1.1\",\n \"downshift\": \"9.3.2\",\n \"framer-motion\": \"12.38.0\",\n \"i18next\": \"26.0.8\",\n \"i18next-browser-languagedetector\": \"8.2.1\",\n \"i18next-http-backend\": \"4.0.0\",\n \"isbot\": \"5.1.39\",\n \"lucide-react\": \"1.14.0\",\n \"monaco-editor\": \"0.55.1\",\n \"posthog-js\": \"1.372.6\",\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-hot-toast\": \"2.6.0\",\n \"react-i18next\": \"17.0.6\",\n \"react-icons\": \"5.6.0\",\n \"react-markdown\": \"10.1.0\",\n \"react-router\": \"7.14.2\",\n \"react-syntax-highlighter\": \"16.1.1\",\n \"rehype-raw\": \"7.0.0\",\n \"rehype-sanitize\": \"6.0.0\",\n \"remark-breaks\": \"4.0.0\",\n \"remark-gfm\": \"4.0.1\",\n \"shell-quote\": \"^1.8.3\",\n \"sirv-cli\": \"3.0.1\",\n \"socket.io-client\": \"4.8.3\",\n \"tailwind-merge\": \"3.5.0\",\n \"tailwind-scrollbar\": \"4.0.2\",\n \"unist-util-visit\": \"5.1.0\",\n \"uuid\": \"14.0.0\",\n \"vite\": \"8.0.10\",\n \"zustand\": \"5.0.12\"\n },\n \"scripts\": {\n \"dev\": \"node --env-file-if-exists=.env scripts/dev-with-automation.mjs\",\n \"dev:static\": \"node --env-file-if-exists=.env scripts/dev-static.mjs\",\n \"dev:extra-backend\": \"node --env-file-if-exists=.env scripts/dev-extra-backend.mjs\",\n \"dev:minimal\": \"node --env-file-if-exists=.env scripts/dev-safe.mjs\",\n \"dev:frontend\": \"npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev\",\n \"dev:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev\",\n \"build\": \"npm run build:app\",\n \"build:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router build\",\n \"start\": \"npx sirv-cli build/ --single\",\n \"test\": \"npm run make-i18n && vitest run\",\n \"test:e2e\": \"playwright test --pass-with-no-tests\",\n \"test:e2e:live\": \"node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs\",\n \"test:e2e:mock-llm\": \"playwright test --config=playwright.mock-llm.config.ts\",\n \"test:e2e:snapshots\": \"playwright test tests/e2e/snapshots --project=chromium --retries=0\",\n \"test:e2e:snapshots:update\": \"playwright test tests/e2e/snapshots --project=chromium --update-snapshots\",\n \"test:coverage\": \"npm run make-i18n && vitest run --coverage\",\n \"dev_wsl\": \"VITE_WATCH_USE_POLLING=true vite\",\n \"preview\": \"vite preview\",\n \"make-i18n\": \"node scripts/make-i18n-translations.cjs\",\n \"prelint\": \"npm run make-i18n\",\n \"lint\": \"npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint src --fix && prettier --write src/**/*.{ts,tsx}\",\n \"prepare\": \"husky\",\n \"typecheck\": \"react-router typegen && tsc\",\n \"typecheck:staged\": \"react-router typegen && npx tsc --noEmit --skipLibCheck\",\n \"check-translation-completeness\": \"node scripts/check-translation-completeness.cjs\",\n \"build:app\": \"npm run make-i18n && react-router build\",\n \"build:lib\": \"npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json\",\n \"build:docker\": \"node scripts/docker-build.mjs\"\n },\n \"lint-staged\": {\n \"src/**/*.{ts,tsx,js}\": [\n \"eslint --fix\",\n \"prettier --write\"\n ],\n \"src/**/*.{ts,tsx}\": [\n \"bash -c 'npm run typecheck:staged'\"\n ],\n \"src/**/*\": [\n \"npm run check-translation-completeness\"\n ]\n },\n \"devDependencies\": {\n \"@eslint/eslintrc\": \"3.3.1\",\n \"@eslint/js\": \"9.39.4\",\n \"@mswjs/socket.io-binding\": \"0.2.0\",\n \"@playwright/test\": \"1.59.1\",\n \"@react-router/dev\": \"7.14.2\",\n \"@tailwindcss/typography\": \"0.5.19\",\n \"@tanstack/eslint-plugin-query\": \"5.100.9\",\n \"@testing-library/dom\": \"10.4.1\",\n \"@testing-library/jest-dom\": \"6.9.1\",\n \"@testing-library/react\": \"16.3.2\",\n \"@testing-library/user-event\": \"14.6.1\",\n \"@types/mdast\": \"4.0.4\",\n \"@types/node\": \"25.6.0\",\n \"@types/react\": \"19.2.14\",\n \"@types/react-dom\": \"19.2.3\",\n \"@types/react-syntax-highlighter\": \"15.5.13\",\n \"@typescript-eslint/eslint-plugin\": \"8.59.2\",\n \"@typescript-eslint/parser\": \"8.59.2\",\n \"@vercel/react-router\": \"1.3.0\",\n \"@vitest/coverage-v8\": \"4.1.5\",\n \"cross-env\": \"10.1.0\",\n \"eslint\": \"9.39.4\",\n \"eslint-config-prettier\": \"10.1.8\",\n \"eslint-import-resolver-typescript\": \"4.4.4\",\n \"eslint-plugin-i18next\": \"6.1.4\",\n \"eslint-plugin-import-x\": \"4.16.2\",\n \"eslint-plugin-jsx-a11y\": \"6.10.2\",\n \"eslint-plugin-prettier\": \"5.5.5\",\n \"eslint-plugin-react\": \"7.37.5\",\n \"eslint-plugin-react-hooks\": \"7.1.1\",\n \"eslint-plugin-unused-imports\": \"4.4.1\",\n \"globals\": \"16.5.0\",\n \"husky\": \"9.1.7\",\n \"jsdom\": \"29.1.1\",\n \"lint-staged\": \"16.4.0\",\n \"msw\": \"2.14.2\",\n \"postcss-prefix-selector\": \"2.1.1\",\n \"prettier\": \"3.8.3\",\n \"tailwindcss\": \"4.2.4\",\n \"typescript\": \"6.0.3\",\n \"vite-plugin-svgr\": \"5.2.0\",\n \"vitest\": \"4.1.5\"\n },\n \"packageManager\": \"npm@10.5.0\",\n \"volta\": {\n \"node\": \"22.12.0\"\n },\n \"msw\": {\n \"workerDirectory\": [\n \"public\"\n ]\n },\n \"overrides\": {\n \"dompurify\": \"3.3.2\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"bin\",\n \"build\",\n \"config\",\n \"scripts\",\n \"tools\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./browser\": {\n \"types\": \"./dist/components/browser/index.d.ts\",\n \"import\": \"./dist/components/browser/index.js\",\n \"require\": \"./dist/components/browser/index.cjs\"\n },\n \"./conversation\": {\n \"types\": \"./dist/components/conversation/index.d.ts\",\n \"import\": \"./dist/components/conversation/index.js\",\n \"require\": \"./dist/components/conversation/index.cjs\"\n },\n \"./files\": {\n \"types\": \"./dist/components/files/index.d.ts\",\n \"import\": \"./dist/components/files/index.js\",\n \"require\": \"./dist/components/files/index.cjs\"\n },\n \"./settings\": {\n \"types\": \"./dist/components/settings/index.d.ts\",\n \"import\": \"./dist/components/settings/index.js\",\n \"require\": \"./dist/components/settings/index.cjs\"\n },\n \"./sidebar\": {\n \"types\": \"./dist/components/sidebar/index.d.ts\",\n \"import\": \"./dist/components/sidebar/index.js\",\n \"require\": \"./dist/components/sidebar/index.cjs\"\n },\n \"./terminal\": {\n \"types\": \"./dist/components/terminal/index.d.ts\",\n \"import\": \"./dist/components/terminal/index.js\",\n \"require\": \"./dist/components/terminal/index.cjs\"\n },\n \"./i18n\": {\n \"types\": \"./dist/i18n/index.d.ts\",\n \"import\": \"./dist/i18n/index.js\",\n \"require\": \"./dist/i18n/index.cjs\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"peerDependencies\": {\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-router\": \"7.14.2\"\n }\n}\n"],"mappings":""}
package/dist/package.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var e = {
2
2
  name: "@openhands/agent-canvas",
3
- version: "1.0.0-alpha.8",
3
+ version: "1.0.0-alpha.9",
4
4
  description: "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
5
5
  license: "MIT",
6
6
  private: !1,
@@ -17,8 +17,8 @@ var e = {
17
17
  "@heroui/react": "2.8.10",
18
18
  "@microlink/react-json-view": "1.31.20",
19
19
  "@monaco-editor/react": "4.7.0",
20
- "@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#39711065f53166c52608462f60a4c8507253ce56",
21
- "@openhands/typescript-client": "1.24.0",
20
+ "@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909",
21
+ "@openhands/typescript-client": "1.24.3",
22
22
  "@react-router/node": "7.14.2",
23
23
  "@react-router/serve": "7.14.2",
24
24
  "@tailwindcss/vite": "4.2.4",
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@openhands/agent-canvas\",\n \"version\": \"1.0.0-alpha.8\",\n \"description\": \"Agent Canvas UI for OpenHands - run AI coding agents with a visual interface\",\n \"license\": \"MIT\",\n \"private\": false,\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/OpenHands/agent-canvas\"\n },\n \"homepage\": \"https://github.com/OpenHands/agent-canvas#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/OpenHands/agent-canvas/issues\"\n },\n \"bin\": {\n \"agent-canvas\": \"bin/agent-canvas.mjs\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"dependencies\": {\n \"@heroui/react\": \"2.8.10\",\n \"@microlink/react-json-view\": \"1.31.20\",\n \"@monaco-editor/react\": \"4.7.0\",\n \"@openhands/extensions\": \"git+https://github.com/OpenHands/extensions.git#39711065f53166c52608462f60a4c8507253ce56\",\n \"@openhands/typescript-client\": \"1.24.0\",\n \"@react-router/node\": \"7.14.2\",\n \"@react-router/serve\": \"7.14.2\",\n \"@tailwindcss/vite\": \"4.2.4\",\n \"@tanstack/react-query\": \"5.100.9\",\n \"@types/shell-quote\": \"^1.7.5\",\n \"@uidotdev/usehooks\": \"2.4.1\",\n \"@xterm/addon-fit\": \"0.11.0\",\n \"@xterm/xterm\": \"6.0.0\",\n \"axios\": \"1.16.0\",\n \"class-variance-authority\": \"0.7.1\",\n \"clsx\": \"2.1.1\",\n \"downshift\": \"9.3.2\",\n \"framer-motion\": \"12.38.0\",\n \"i18next\": \"26.0.8\",\n \"i18next-browser-languagedetector\": \"8.2.1\",\n \"i18next-http-backend\": \"4.0.0\",\n \"isbot\": \"5.1.39\",\n \"lucide-react\": \"1.14.0\",\n \"monaco-editor\": \"0.55.1\",\n \"posthog-js\": \"1.372.6\",\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-hot-toast\": \"2.6.0\",\n \"react-i18next\": \"17.0.6\",\n \"react-icons\": \"5.6.0\",\n \"react-markdown\": \"10.1.0\",\n \"react-router\": \"7.14.2\",\n \"react-syntax-highlighter\": \"16.1.1\",\n \"rehype-raw\": \"7.0.0\",\n \"rehype-sanitize\": \"6.0.0\",\n \"remark-breaks\": \"4.0.0\",\n \"remark-gfm\": \"4.0.1\",\n \"shell-quote\": \"^1.8.3\",\n \"sirv-cli\": \"3.0.1\",\n \"socket.io-client\": \"4.8.3\",\n \"tailwind-merge\": \"3.5.0\",\n \"tailwind-scrollbar\": \"4.0.2\",\n \"unist-util-visit\": \"5.1.0\",\n \"uuid\": \"14.0.0\",\n \"vite\": \"8.0.10\",\n \"zustand\": \"5.0.12\"\n },\n \"scripts\": {\n \"dev\": \"node --env-file-if-exists=.env scripts/dev-with-automation.mjs\",\n \"dev:static\": \"node --env-file-if-exists=.env scripts/dev-static.mjs\",\n \"dev:extra-backend\": \"node --env-file-if-exists=.env scripts/dev-extra-backend.mjs\",\n \"dev:minimal\": \"node --env-file-if-exists=.env scripts/dev-safe.mjs\",\n \"dev:frontend\": \"npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev\",\n \"dev:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev\",\n \"build\": \"npm run build:app\",\n \"build:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router build\",\n \"start\": \"npx sirv-cli build/ --single\",\n \"test\": \"npm run make-i18n && vitest run\",\n \"test:e2e\": \"playwright test --pass-with-no-tests\",\n \"test:e2e:live\": \"node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs\",\n \"test:e2e:mock-llm\": \"playwright test --config=playwright.mock-llm.config.ts\",\n \"test:e2e:snapshots\": \"playwright test tests/e2e/snapshots --project=chromium --retries=0\",\n \"test:e2e:snapshots:update\": \"playwright test tests/e2e/snapshots --project=chromium --update-snapshots\",\n \"test:coverage\": \"npm run make-i18n && vitest run --coverage\",\n \"dev_wsl\": \"VITE_WATCH_USE_POLLING=true vite\",\n \"preview\": \"vite preview\",\n \"make-i18n\": \"node scripts/make-i18n-translations.cjs\",\n \"prelint\": \"npm run make-i18n\",\n \"lint\": \"npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint src --fix && prettier --write src/**/*.{ts,tsx}\",\n \"prepare\": \"husky\",\n \"typecheck\": \"react-router typegen && tsc\",\n \"typecheck:staged\": \"react-router typegen && npx tsc --noEmit --skipLibCheck\",\n \"check-translation-completeness\": \"node scripts/check-translation-completeness.cjs\",\n \"build:app\": \"npm run make-i18n && react-router build\",\n \"build:lib\": \"npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json\",\n \"build:docker\": \"node scripts/docker-build.mjs\"\n },\n \"lint-staged\": {\n \"src/**/*.{ts,tsx,js}\": [\n \"eslint --fix\",\n \"prettier --write\"\n ],\n \"src/**/*.{ts,tsx}\": [\n \"bash -c 'npm run typecheck:staged'\"\n ],\n \"src/**/*\": [\n \"npm run check-translation-completeness\"\n ]\n },\n \"devDependencies\": {\n \"@eslint/eslintrc\": \"3.3.1\",\n \"@eslint/js\": \"9.39.4\",\n \"@mswjs/socket.io-binding\": \"0.2.0\",\n \"@playwright/test\": \"1.59.1\",\n \"@react-router/dev\": \"7.14.2\",\n \"@tailwindcss/typography\": \"0.5.19\",\n \"@tanstack/eslint-plugin-query\": \"5.100.9\",\n \"@testing-library/dom\": \"10.4.1\",\n \"@testing-library/jest-dom\": \"6.9.1\",\n \"@testing-library/react\": \"16.3.2\",\n \"@testing-library/user-event\": \"14.6.1\",\n \"@types/mdast\": \"4.0.4\",\n \"@types/node\": \"25.6.0\",\n \"@types/react\": \"19.2.14\",\n \"@types/react-dom\": \"19.2.3\",\n \"@types/react-syntax-highlighter\": \"15.5.13\",\n \"@typescript-eslint/eslint-plugin\": \"8.59.2\",\n \"@typescript-eslint/parser\": \"8.59.2\",\n \"@vercel/react-router\": \"1.3.0\",\n \"@vitest/coverage-v8\": \"4.1.5\",\n \"cross-env\": \"10.1.0\",\n \"eslint\": \"9.39.4\",\n \"eslint-config-prettier\": \"10.1.8\",\n \"eslint-import-resolver-typescript\": \"4.4.4\",\n \"eslint-plugin-i18next\": \"6.1.4\",\n \"eslint-plugin-import-x\": \"4.16.2\",\n \"eslint-plugin-jsx-a11y\": \"6.10.2\",\n \"eslint-plugin-prettier\": \"5.5.5\",\n \"eslint-plugin-react\": \"7.37.5\",\n \"eslint-plugin-react-hooks\": \"7.1.1\",\n \"eslint-plugin-unused-imports\": \"4.4.1\",\n \"globals\": \"16.5.0\",\n \"husky\": \"9.1.7\",\n \"jsdom\": \"29.1.1\",\n \"lint-staged\": \"16.4.0\",\n \"msw\": \"2.14.2\",\n \"postcss-prefix-selector\": \"2.1.1\",\n \"prettier\": \"3.8.3\",\n \"tailwindcss\": \"4.2.4\",\n \"typescript\": \"6.0.3\",\n \"vite-plugin-svgr\": \"5.2.0\",\n \"vitest\": \"4.1.5\"\n },\n \"packageManager\": \"npm@10.5.0\",\n \"volta\": {\n \"node\": \"22.12.0\"\n },\n \"msw\": {\n \"workerDirectory\": [\n \"public\"\n ]\n },\n \"overrides\": {\n \"dompurify\": \"3.3.2\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"bin\",\n \"build\",\n \"config\",\n \"scripts\",\n \"tools\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./browser\": {\n \"types\": \"./dist/components/browser/index.d.ts\",\n \"import\": \"./dist/components/browser/index.js\",\n \"require\": \"./dist/components/browser/index.cjs\"\n },\n \"./conversation\": {\n \"types\": \"./dist/components/conversation/index.d.ts\",\n \"import\": \"./dist/components/conversation/index.js\",\n \"require\": \"./dist/components/conversation/index.cjs\"\n },\n \"./files\": {\n \"types\": \"./dist/components/files/index.d.ts\",\n \"import\": \"./dist/components/files/index.js\",\n \"require\": \"./dist/components/files/index.cjs\"\n },\n \"./settings\": {\n \"types\": \"./dist/components/settings/index.d.ts\",\n \"import\": \"./dist/components/settings/index.js\",\n \"require\": \"./dist/components/settings/index.cjs\"\n },\n \"./sidebar\": {\n \"types\": \"./dist/components/sidebar/index.d.ts\",\n \"import\": \"./dist/components/sidebar/index.js\",\n \"require\": \"./dist/components/sidebar/index.cjs\"\n },\n \"./terminal\": {\n \"types\": \"./dist/components/terminal/index.d.ts\",\n \"import\": \"./dist/components/terminal/index.js\",\n \"require\": \"./dist/components/terminal/index.cjs\"\n },\n \"./i18n\": {\n \"types\": \"./dist/i18n/index.d.ts\",\n \"import\": \"./dist/i18n/index.js\",\n \"require\": \"./dist/i18n/index.cjs\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"peerDependencies\": {\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-router\": \"7.14.2\"\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@openhands/agent-canvas\",\n \"version\": \"1.0.0-alpha.9\",\n \"description\": \"Agent Canvas UI for OpenHands - run AI coding agents with a visual interface\",\n \"license\": \"MIT\",\n \"private\": false,\n \"type\": \"module\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/OpenHands/agent-canvas\"\n },\n \"homepage\": \"https://github.com/OpenHands/agent-canvas#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/OpenHands/agent-canvas/issues\"\n },\n \"bin\": {\n \"agent-canvas\": \"bin/agent-canvas.mjs\"\n },\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"dependencies\": {\n \"@heroui/react\": \"2.8.10\",\n \"@microlink/react-json-view\": \"1.31.20\",\n \"@monaco-editor/react\": \"4.7.0\",\n \"@openhands/extensions\": \"git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909\",\n \"@openhands/typescript-client\": \"1.24.3\",\n \"@react-router/node\": \"7.14.2\",\n \"@react-router/serve\": \"7.14.2\",\n \"@tailwindcss/vite\": \"4.2.4\",\n \"@tanstack/react-query\": \"5.100.9\",\n \"@types/shell-quote\": \"^1.7.5\",\n \"@uidotdev/usehooks\": \"2.4.1\",\n \"@xterm/addon-fit\": \"0.11.0\",\n \"@xterm/xterm\": \"6.0.0\",\n \"axios\": \"1.16.0\",\n \"class-variance-authority\": \"0.7.1\",\n \"clsx\": \"2.1.1\",\n \"downshift\": \"9.3.2\",\n \"framer-motion\": \"12.38.0\",\n \"i18next\": \"26.0.8\",\n \"i18next-browser-languagedetector\": \"8.2.1\",\n \"i18next-http-backend\": \"4.0.0\",\n \"isbot\": \"5.1.39\",\n \"lucide-react\": \"1.14.0\",\n \"monaco-editor\": \"0.55.1\",\n \"posthog-js\": \"1.372.6\",\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-hot-toast\": \"2.6.0\",\n \"react-i18next\": \"17.0.6\",\n \"react-icons\": \"5.6.0\",\n \"react-markdown\": \"10.1.0\",\n \"react-router\": \"7.14.2\",\n \"react-syntax-highlighter\": \"16.1.1\",\n \"rehype-raw\": \"7.0.0\",\n \"rehype-sanitize\": \"6.0.0\",\n \"remark-breaks\": \"4.0.0\",\n \"remark-gfm\": \"4.0.1\",\n \"shell-quote\": \"^1.8.3\",\n \"sirv-cli\": \"3.0.1\",\n \"socket.io-client\": \"4.8.3\",\n \"tailwind-merge\": \"3.5.0\",\n \"tailwind-scrollbar\": \"4.0.2\",\n \"unist-util-visit\": \"5.1.0\",\n \"uuid\": \"14.0.0\",\n \"vite\": \"8.0.10\",\n \"zustand\": \"5.0.12\"\n },\n \"scripts\": {\n \"dev\": \"node --env-file-if-exists=.env scripts/dev-with-automation.mjs\",\n \"dev:static\": \"node --env-file-if-exists=.env scripts/dev-static.mjs\",\n \"dev:extra-backend\": \"node --env-file-if-exists=.env scripts/dev-extra-backend.mjs\",\n \"dev:minimal\": \"node --env-file-if-exists=.env scripts/dev-safe.mjs\",\n \"dev:frontend\": \"npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev\",\n \"dev:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev\",\n \"build\": \"npm run build:app\",\n \"build:mock\": \"npm run make-i18n && cross-env VITE_MOCK_API=true react-router build\",\n \"start\": \"npx sirv-cli build/ --single\",\n \"test\": \"npm run make-i18n && vitest run\",\n \"test:e2e\": \"playwright test --pass-with-no-tests\",\n \"test:e2e:live\": \"node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs\",\n \"test:e2e:mock-llm\": \"playwright test --config=playwright.mock-llm.config.ts\",\n \"test:e2e:snapshots\": \"playwright test tests/e2e/snapshots --project=chromium --retries=0\",\n \"test:e2e:snapshots:update\": \"playwright test tests/e2e/snapshots --project=chromium --update-snapshots\",\n \"test:coverage\": \"npm run make-i18n && vitest run --coverage\",\n \"dev_wsl\": \"VITE_WATCH_USE_POLLING=true vite\",\n \"preview\": \"vite preview\",\n \"make-i18n\": \"node scripts/make-i18n-translations.cjs\",\n \"prelint\": \"npm run make-i18n\",\n \"lint\": \"npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint src --fix && prettier --write src/**/*.{ts,tsx}\",\n \"prepare\": \"husky\",\n \"typecheck\": \"react-router typegen && tsc\",\n \"typecheck:staged\": \"react-router typegen && npx tsc --noEmit --skipLibCheck\",\n \"check-translation-completeness\": \"node scripts/check-translation-completeness.cjs\",\n \"build:app\": \"npm run make-i18n && react-router build\",\n \"build:lib\": \"npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json\",\n \"build:docker\": \"node scripts/docker-build.mjs\"\n },\n \"lint-staged\": {\n \"src/**/*.{ts,tsx,js}\": [\n \"eslint --fix\",\n \"prettier --write\"\n ],\n \"src/**/*.{ts,tsx}\": [\n \"bash -c 'npm run typecheck:staged'\"\n ],\n \"src/**/*\": [\n \"npm run check-translation-completeness\"\n ]\n },\n \"devDependencies\": {\n \"@eslint/eslintrc\": \"3.3.1\",\n \"@eslint/js\": \"9.39.4\",\n \"@mswjs/socket.io-binding\": \"0.2.0\",\n \"@playwright/test\": \"1.59.1\",\n \"@react-router/dev\": \"7.14.2\",\n \"@tailwindcss/typography\": \"0.5.19\",\n \"@tanstack/eslint-plugin-query\": \"5.100.9\",\n \"@testing-library/dom\": \"10.4.1\",\n \"@testing-library/jest-dom\": \"6.9.1\",\n \"@testing-library/react\": \"16.3.2\",\n \"@testing-library/user-event\": \"14.6.1\",\n \"@types/mdast\": \"4.0.4\",\n \"@types/node\": \"25.6.0\",\n \"@types/react\": \"19.2.14\",\n \"@types/react-dom\": \"19.2.3\",\n \"@types/react-syntax-highlighter\": \"15.5.13\",\n \"@typescript-eslint/eslint-plugin\": \"8.59.2\",\n \"@typescript-eslint/parser\": \"8.59.2\",\n \"@vercel/react-router\": \"1.3.0\",\n \"@vitest/coverage-v8\": \"4.1.5\",\n \"cross-env\": \"10.1.0\",\n \"eslint\": \"9.39.4\",\n \"eslint-config-prettier\": \"10.1.8\",\n \"eslint-import-resolver-typescript\": \"4.4.4\",\n \"eslint-plugin-i18next\": \"6.1.4\",\n \"eslint-plugin-import-x\": \"4.16.2\",\n \"eslint-plugin-jsx-a11y\": \"6.10.2\",\n \"eslint-plugin-prettier\": \"5.5.5\",\n \"eslint-plugin-react\": \"7.37.5\",\n \"eslint-plugin-react-hooks\": \"7.1.1\",\n \"eslint-plugin-unused-imports\": \"4.4.1\",\n \"globals\": \"16.5.0\",\n \"husky\": \"9.1.7\",\n \"jsdom\": \"29.1.1\",\n \"lint-staged\": \"16.4.0\",\n \"msw\": \"2.14.2\",\n \"postcss-prefix-selector\": \"2.1.1\",\n \"prettier\": \"3.8.3\",\n \"tailwindcss\": \"4.2.4\",\n \"typescript\": \"6.0.3\",\n \"vite-plugin-svgr\": \"5.2.0\",\n \"vitest\": \"4.1.5\"\n },\n \"packageManager\": \"npm@10.5.0\",\n \"volta\": {\n \"node\": \"22.12.0\"\n },\n \"msw\": {\n \"workerDirectory\": [\n \"public\"\n ]\n },\n \"overrides\": {\n \"dompurify\": \"3.3.2\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"bin\",\n \"build\",\n \"config\",\n \"scripts\",\n \"tools\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./browser\": {\n \"types\": \"./dist/components/browser/index.d.ts\",\n \"import\": \"./dist/components/browser/index.js\",\n \"require\": \"./dist/components/browser/index.cjs\"\n },\n \"./conversation\": {\n \"types\": \"./dist/components/conversation/index.d.ts\",\n \"import\": \"./dist/components/conversation/index.js\",\n \"require\": \"./dist/components/conversation/index.cjs\"\n },\n \"./files\": {\n \"types\": \"./dist/components/files/index.d.ts\",\n \"import\": \"./dist/components/files/index.js\",\n \"require\": \"./dist/components/files/index.cjs\"\n },\n \"./settings\": {\n \"types\": \"./dist/components/settings/index.d.ts\",\n \"import\": \"./dist/components/settings/index.js\",\n \"require\": \"./dist/components/settings/index.cjs\"\n },\n \"./sidebar\": {\n \"types\": \"./dist/components/sidebar/index.d.ts\",\n \"import\": \"./dist/components/sidebar/index.js\",\n \"require\": \"./dist/components/sidebar/index.cjs\"\n },\n \"./terminal\": {\n \"types\": \"./dist/components/terminal/index.d.ts\",\n \"import\": \"./dist/components/terminal/index.js\",\n \"require\": \"./dist/components/terminal/index.cjs\"\n },\n \"./i18n\": {\n \"types\": \"./dist/i18n/index.d.ts\",\n \"import\": \"./dist/i18n/index.js\",\n \"require\": \"./dist/i18n/index.cjs\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"peerDependencies\": {\n \"react\": \"19.2.5\",\n \"react-dom\": \"19.2.5\",\n \"react-router\": \"7.14.2\"\n }\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../i18n/declaration.cjs`),r=require(`../types/agent-state.cjs`),i=require(`../hooks/use-conversation-id.cjs`),a=require(`../stores/command-store.cjs`),o=require(`../stores/conversation-store.cjs`),s=require(`../stores/agent-store.cjs`),c=require(`../stores/conversation-state-store.cjs`),l=require(`../contexts/active-backend-context.cjs`),u=require(`../api/backend-registry/last-conversation-store.cjs`),d=require(`../utils/custom-toast-handlers.cjs`),f=require(`../stores/use-event-store.cjs`),p=require(`../stores/error-message-store.cjs`),m=require(`../api/cloud/conversation-service.api.cjs`),h=require(`../hooks/query/use-active-conversation.cjs`),g=require(`../wrapper/event-handler.cjs`),_=require(`../hooks/query/use-task-polling.cjs`),v=require(`../hooks/query/use-is-authed.cjs`),y=require(`../components/features/conversation/conversation-main/conversation-main.cjs`),b=require(`../contexts/websocket-provider-wrapper.cjs`);let x=require(`react`);x=e.__toESM(x,1);let S=require(`react/jsx-runtime`),C=require(`react-router`);function w(){let{t:e}=t.useTranslation(`openhands`),{conversationId:w}=i.useConversationId(),T=(0,C.useMatch)(`/conversations/:conversationId/panel`),E=f.useEventStore(e=>e.clearEvents),{isTask:D,taskStatus:O,taskDetail:k}=_.useTaskPolling(),A=l.useActiveBackend(),j=x.default.useRef(A.backend.id),M=x.default.useRef(A.orgId),N=j.current!==A.backend.id||M.current!==A.orgId,{data:P,isFetched:F}=h.useActiveConversation(),{data:I}=v.useIsAuthed(),{resetConversationState:L}=o.useConversationStore(),R=(0,C.useNavigate)(),z=(0,C.useLocation)(),B=a.useCommandStore(e=>e.clearTerminal),V=c.useConversationStateStore(e=>e.reset),H=s.useAgentStore(e=>e.setCurrentAgentState),U=p.useErrorMessageStore(e=>e.removeErrorMessage);x.default.useEffect(()=>{B(),L(),V(),H(r.AgentState.LOADING),U(),E()},[w,B,L,V,H,U,E]),x.default.useEffect(()=>{if(D&&O===`ERROR`){d.displayErrorToast(k||e(n.I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));let t=z.state?.resumedFromConversationId;R(t?`/conversations/${t}`:`/conversations`,{replace:!0})}},[D,O,k,e,R,z.state]),x.default.useEffect(()=>{!F||!I||N||P||(u.clearLastConversationId(A.backend.id,A.orgId),d.displayErrorToast(e(n.I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION)),R(`/conversations`))},[P,F,I,R,e,N,A.backend.id,A.orgId]),x.default.useEffect(()=>{N||w&&(w.startsWith(`task-`)||u.setLastConversationId(A.backend.id,A.orgId,w))},[w,N,A.backend.id,A.orgId]);let W=x.default.useRef(null);return x.default.useEffect(()=>{!F||!P||A.backend.kind===`cloud`&&P.sandbox_status===`PAUSED`&&P.sandbox_id&&W.current!==P.id&&(W.current=P.id,m.resumeCloudSandbox(P.sandbox_id).catch(()=>{d.displayErrorToast(e(n.I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK))}))},[F,P?.id,P?.sandbox_status,P?.sandbox_id,A.backend.kind,e]),(0,S.jsx)(b.WebSocketProviderWrapper,{conversationId:w,children:(0,S.jsx)(g.EventHandler,{children:(0,S.jsx)(`div`,{"data-testid":`app-route`,className:`flex h-full flex-col`,children:T?(0,S.jsx)(y.ConversationMobilePanelPage,{onNavigateBack:()=>R(`/conversations/${w}`)}):(0,S.jsx)(y.ConversationMain,{})})})})}function T(){return(0,S.jsx)(w,{})}exports.default=T;
1
+ const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../i18n/declaration.cjs`),r=require(`../types/agent-state.cjs`),i=require(`../hooks/use-conversation-id.cjs`),a=require(`../stores/command-store.cjs`),o=require(`../stores/conversation-store.cjs`),s=require(`../stores/agent-store.cjs`),c=require(`../stores/conversation-state-store.cjs`),l=require(`../contexts/active-backend-context.cjs`),u=require(`../api/backend-registry/last-conversation-store.cjs`),d=require(`../utils/custom-toast-handlers.cjs`),f=require(`../stores/error-message-store.cjs`),p=require(`../api/cloud/conversation-service.api.cjs`),m=require(`../hooks/query/use-active-conversation.cjs`),h=require(`../wrapper/event-handler.cjs`),g=require(`../hooks/query/use-task-polling.cjs`),_=require(`../hooks/query/use-is-authed.cjs`),v=require(`../components/features/conversation/conversation-main/conversation-main.cjs`),y=require(`../contexts/websocket-provider-wrapper.cjs`);let b=require(`react`);b=e.__toESM(b,1);let x=require(`react/jsx-runtime`),S=require(`react-router`);function C(){let{t:e}=t.useTranslation(`openhands`),{conversationId:C}=i.useConversationId(),w=(0,S.useMatch)(`/conversations/:conversationId/panel`),{isTask:T,taskStatus:E,taskDetail:D}=g.useTaskPolling(),O=l.useActiveBackend(),k=b.default.useRef(O.backend.id),A=b.default.useRef(O.orgId),j=k.current!==O.backend.id||A.current!==O.orgId,{data:M,isFetched:N}=m.useActiveConversation(),{data:P}=_.useIsAuthed(),{resetConversationState:F}=o.useConversationStore(),I=(0,S.useNavigate)(),L=(0,S.useLocation)(),R=a.useCommandStore(e=>e.clearTerminal),z=c.useConversationStateStore(e=>e.reset),B=s.useAgentStore(e=>e.setCurrentAgentState),V=f.useErrorMessageStore(e=>e.removeErrorMessage);b.default.useEffect(()=>{R(),F(),z(),B(r.AgentState.LOADING),V()},[C,R,F,z,B,V]),b.default.useEffect(()=>{if(T&&E===`ERROR`){d.displayErrorToast(D||e(n.I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));let t=L.state?.resumedFromConversationId;I(t?`/conversations/${t}`:`/conversations`,{replace:!0})}},[T,E,D,e,I,L.state]),b.default.useEffect(()=>{!N||!P||j||M||(u.clearLastConversationId(O.backend.id,O.orgId),d.displayErrorToast(e(n.I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION)),I(`/conversations`))},[M,N,P,I,e,j,O.backend.id,O.orgId]),b.default.useEffect(()=>{j||C&&(C.startsWith(`task-`)||u.setLastConversationId(O.backend.id,O.orgId,C))},[C,j,O.backend.id,O.orgId]);let H=b.default.useRef(null);return b.default.useEffect(()=>{!N||!M||O.backend.kind===`cloud`&&M.sandbox_status===`PAUSED`&&M.sandbox_id&&H.current!==M.id&&(H.current=M.id,p.resumeCloudSandbox(M.sandbox_id).catch(()=>{d.displayErrorToast(e(n.I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK))}))},[N,M?.id,M?.sandbox_status,M?.sandbox_id,O.backend.kind,e]),(0,x.jsx)(y.WebSocketProviderWrapper,{conversationId:C,children:(0,x.jsx)(h.EventHandler,{children:(0,x.jsx)(`div`,{"data-testid":`app-route`,className:`flex h-full flex-col`,children:w?(0,x.jsx)(v.ConversationMobilePanelPage,{onNavigateBack:()=>I(`/conversations/${C}`)}):(0,x.jsx)(v.ConversationMain,{})})})})}function w(){return(0,x.jsx)(C,{})}exports.default=w;
2
2
  //# sourceMappingURL=conversation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.cjs","names":[],"sources":["../../src/routes/conversation.tsx"],"sourcesContent":["import React from \"react\";\nimport { useNavigate, useLocation, useMatch } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\n\nimport { useConversationId } from \"#/hooks/use-conversation-id\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { useAgentStore } from \"#/stores/agent-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n clearLastConversationId,\n setLastConversationId,\n} from \"#/api/backend-registry/last-conversation-store\";\nimport { AgentState } from \"#/types/agent-state\";\n\nimport { EventHandler } from \"../wrapper/event-handler\";\n\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useTaskPolling } from \"#/hooks/query/use-task-polling\";\n\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { useIsAuthed } from \"#/hooks/query/use-is-authed\";\nimport {\n ConversationMain,\n ConversationMobilePanelPage,\n} from \"#/components/features/conversation/conversation-main/conversation-main\";\n\nimport { WebSocketProviderWrapper } from \"#/contexts/websocket-provider-wrapper\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useEventStore } from \"#/stores/use-event-store\";\nimport { resumeCloudSandbox } from \"#/api/cloud/conversation-service.api\";\n\nfunction AppContent() {\n const { t } = useTranslation(\"openhands\");\n const { conversationId } = useConversationId();\n const panelViewMatch = useMatch(\"/conversations/:conversationId/panel\");\n const clearEvents = useEventStore((state) => state.clearEvents);\n\n const { isTask, taskStatus, taskDetail } = useTaskPolling();\n\n // The conversationId in the URL belongs to whichever backend was\n // active when the route first mounted. If the user switches backends\n // while this route is still mounted, the id is meaningless under the\n // new backend — disable the active-conversation fetch (and its 404\n // toast) so we don't fire a request that the BackendSelector's\n // redirect will immediately navigate away from anyway. Mirrors the\n // same guard in `routes/automation-detail.tsx`.\n const active = useActiveBackend();\n const mountedBackendId = React.useRef(active.backend.id);\n const mountedOrgId = React.useRef(active.orgId);\n const backendChanged =\n mountedBackendId.current !== active.backend.id ||\n mountedOrgId.current !== active.orgId;\n\n const { data: conversation, isFetched } = useActiveConversation();\n const { data: isAuthed } = useIsAuthed();\n const { resetConversationState } = useConversationStore();\n const navigate = useNavigate();\n const location = useLocation();\n const clearTerminal = useCommandStore((state) => state.clearTerminal);\n const resetConversationRuntimeState = useConversationStateStore(\n (state) => state.reset,\n );\n const setCurrentAgentState = useAgentStore(\n (state) => state.setCurrentAgentState,\n );\n const removeErrorMessage = useErrorMessageStore(\n (state) => state.removeErrorMessage,\n );\n\n React.useEffect(() => {\n clearTerminal();\n resetConversationState();\n resetConversationRuntimeState();\n setCurrentAgentState(AgentState.LOADING);\n removeErrorMessage();\n clearEvents();\n }, [\n conversationId,\n clearTerminal,\n resetConversationState,\n resetConversationRuntimeState,\n setCurrentAgentState,\n removeErrorMessage,\n clearEvents,\n ]);\n\n React.useEffect(() => {\n if (isTask && taskStatus === \"ERROR\") {\n displayErrorToast(\n taskDetail || t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK),\n );\n // Navigate back to the original conversation when a resume task fails so\n // the user isn't stranded at the dead task-{id} URL. The resume effect's\n // ref prevents it from immediately retrying once we land there.\n const resumedFrom = (location.state as Record<string, unknown> | null)\n ?.resumedFromConversationId as string | undefined;\n navigate(\n resumedFrom ? `/conversations/${resumedFrom}` : \"/conversations\",\n { replace: true },\n );\n }\n }, [isTask, taskStatus, taskDetail, t, navigate, location.state]);\n\n React.useEffect(() => {\n if (!isFetched || !isAuthed) return;\n // The BackendSelector is in the middle of redirecting us away from\n // this route — don't toast/navigate based on a 404 that's just\n // \"this id doesn't exist on the new backend\".\n if (backendChanged) return;\n\n if (!conversation) {\n // Clear the per-backend \"last selected\" slot so the next switch\n // to this backend doesn't try to revisit a stale id.\n clearLastConversationId(active.backend.id, active.orgId);\n displayErrorToast(t(I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION));\n navigate(\"/conversations\");\n }\n }, [\n conversation,\n isFetched,\n isAuthed,\n navigate,\n t,\n backendChanged,\n active.backend.id,\n active.orgId,\n ]);\n\n // Remember the most recently selected conversation for the current\n // (backend, org) so flipping back to this backend later restores the\n // user to where they left off. Skip while a backend switch is in\n // flight: the id in the URL is from the previous backend and would\n // otherwise overwrite the new backend's memory.\n React.useEffect(() => {\n if (backendChanged) return;\n if (!conversationId) return;\n if (conversationId.startsWith(\"task-\")) return;\n setLastConversationId(active.backend.id, active.orgId, conversationId);\n }, [conversationId, backendChanged, active.backend.id, active.orgId]);\n\n // Cloud conversation resume: mirrors OpenHands' useSandboxRecovery.\n //\n // When the cloud API reports sandbox_status === \"PAUSED\" the sandbox is\n // sleeping. The correct wake-up call is POST /api/v1/sandboxes/{id}/resume\n // (a lightweight unpause). The previous approach — creating a new start task\n // via POST /api/v1/app-conversations — was wrong: it tries to provision a\n // fresh conversation in the sandbox and is subject to a 120-second cold-start\n // timeout that can fail. The resume endpoint simply unpauses the existing one.\n //\n // After calling resume we stay on the current URL. The 3-second refetch\n // interval in useActiveConversation (active while conversation_url is null)\n // polls until conversation_url populates, then the WebSocket connects.\n //\n // A ref guards against duplicate triggers per unique conversation.id within\n // the same route-mount lifetime.\n const resumeTriggeredForRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!isFetched || !conversation) return;\n if (active.backend.kind !== \"cloud\") return;\n if (conversation.sandbox_status !== \"PAUSED\") return; // only resume PAUSED sandboxes\n if (!conversation.sandbox_id) return; // no sandbox to resume\n if (resumeTriggeredForRef.current === conversation.id) return; // already sent\n\n resumeTriggeredForRef.current = conversation.id;\n\n resumeCloudSandbox(conversation.sandbox_id).catch(() => {\n displayErrorToast(t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));\n });\n }, [\n isFetched,\n conversation?.id,\n conversation?.sandbox_status,\n conversation?.sandbox_id,\n active.backend.kind,\n t,\n ]);\n\n const content = (\n <EventHandler>\n <div data-testid=\"app-route\" className=\"flex h-full flex-col\">\n {panelViewMatch ? (\n <ConversationMobilePanelPage\n onNavigateBack={() => navigate(`/conversations/${conversationId}`)}\n />\n ) : (\n <ConversationMain />\n )}\n </div>\n </EventHandler>\n );\n\n return (\n <WebSocketProviderWrapper conversationId={conversationId}>\n {content}\n </WebSocketProviderWrapper>\n );\n}\n\nexport function ConversationView() {\n return <AppContent />;\n}\n\nexport default ConversationView;\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversation.cjs","names":[],"sources":["../../src/routes/conversation.tsx"],"sourcesContent":["import React from \"react\";\nimport { useNavigate, useLocation, useMatch } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\n\nimport { useConversationId } from \"#/hooks/use-conversation-id\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { useAgentStore } from \"#/stores/agent-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n clearLastConversationId,\n setLastConversationId,\n} from \"#/api/backend-registry/last-conversation-store\";\nimport { AgentState } from \"#/types/agent-state\";\n\nimport { EventHandler } from \"../wrapper/event-handler\";\n\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useTaskPolling } from \"#/hooks/query/use-task-polling\";\n\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { useIsAuthed } from \"#/hooks/query/use-is-authed\";\nimport {\n ConversationMain,\n ConversationMobilePanelPage,\n} from \"#/components/features/conversation/conversation-main/conversation-main\";\n\nimport { WebSocketProviderWrapper } from \"#/contexts/websocket-provider-wrapper\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { resumeCloudSandbox } from \"#/api/cloud/conversation-service.api\";\n\nfunction AppContent() {\n const { t } = useTranslation(\"openhands\");\n const { conversationId } = useConversationId();\n const panelViewMatch = useMatch(\"/conversations/:conversationId/panel\");\n\n const { isTask, taskStatus, taskDetail } = useTaskPolling();\n\n // The conversationId in the URL belongs to whichever backend was\n // active when the route first mounted. If the user switches backends\n // while this route is still mounted, the id is meaningless under the\n // new backend — disable the active-conversation fetch (and its 404\n // toast) so we don't fire a request that the BackendSelector's\n // redirect will immediately navigate away from anyway. Mirrors the\n // same guard in `routes/automation-detail.tsx`.\n const active = useActiveBackend();\n const mountedBackendId = React.useRef(active.backend.id);\n const mountedOrgId = React.useRef(active.orgId);\n const backendChanged =\n mountedBackendId.current !== active.backend.id ||\n mountedOrgId.current !== active.orgId;\n\n const { data: conversation, isFetched } = useActiveConversation();\n const { data: isAuthed } = useIsAuthed();\n const { resetConversationState } = useConversationStore();\n const navigate = useNavigate();\n const location = useLocation();\n const clearTerminal = useCommandStore((state) => state.clearTerminal);\n const resetConversationRuntimeState = useConversationStateStore(\n (state) => state.reset,\n );\n const setCurrentAgentState = useAgentStore(\n (state) => state.setCurrentAgentState,\n );\n const removeErrorMessage = useErrorMessageStore(\n (state) => state.removeErrorMessage,\n );\n\n // Per-conversation UI/runtime resets. The event store is cleared separately,\n // inside ConversationWebSocketProvider, so the clear is ordered *before* the\n // preloaded-history re-seed (see the note there) — clearing it here would run\n // too late and wipe the freshly seeded history on a conversation switch.\n React.useEffect(() => {\n clearTerminal();\n resetConversationState();\n resetConversationRuntimeState();\n setCurrentAgentState(AgentState.LOADING);\n removeErrorMessage();\n }, [\n conversationId,\n clearTerminal,\n resetConversationState,\n resetConversationRuntimeState,\n setCurrentAgentState,\n removeErrorMessage,\n ]);\n\n React.useEffect(() => {\n if (isTask && taskStatus === \"ERROR\") {\n displayErrorToast(\n taskDetail || t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK),\n );\n // Navigate back to the original conversation when a resume task fails so\n // the user isn't stranded at the dead task-{id} URL. The resume effect's\n // ref prevents it from immediately retrying once we land there.\n const resumedFrom = (location.state as Record<string, unknown> | null)\n ?.resumedFromConversationId as string | undefined;\n navigate(\n resumedFrom ? `/conversations/${resumedFrom}` : \"/conversations\",\n { replace: true },\n );\n }\n }, [isTask, taskStatus, taskDetail, t, navigate, location.state]);\n\n React.useEffect(() => {\n if (!isFetched || !isAuthed) return;\n // The BackendSelector is in the middle of redirecting us away from\n // this route — don't toast/navigate based on a 404 that's just\n // \"this id doesn't exist on the new backend\".\n if (backendChanged) return;\n\n if (!conversation) {\n // Clear the per-backend \"last selected\" slot so the next switch\n // to this backend doesn't try to revisit a stale id.\n clearLastConversationId(active.backend.id, active.orgId);\n displayErrorToast(t(I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION));\n navigate(\"/conversations\");\n }\n }, [\n conversation,\n isFetched,\n isAuthed,\n navigate,\n t,\n backendChanged,\n active.backend.id,\n active.orgId,\n ]);\n\n // Remember the most recently selected conversation for the current\n // (backend, org) so flipping back to this backend later restores the\n // user to where they left off. Skip while a backend switch is in\n // flight: the id in the URL is from the previous backend and would\n // otherwise overwrite the new backend's memory.\n React.useEffect(() => {\n if (backendChanged) return;\n if (!conversationId) return;\n if (conversationId.startsWith(\"task-\")) return;\n setLastConversationId(active.backend.id, active.orgId, conversationId);\n }, [conversationId, backendChanged, active.backend.id, active.orgId]);\n\n // Cloud conversation resume: mirrors OpenHands' useSandboxRecovery.\n //\n // When the cloud API reports sandbox_status === \"PAUSED\" the sandbox is\n // sleeping. The correct wake-up call is POST /api/v1/sandboxes/{id}/resume\n // (a lightweight unpause). The previous approach — creating a new start task\n // via POST /api/v1/app-conversations — was wrong: it tries to provision a\n // fresh conversation in the sandbox and is subject to a 120-second cold-start\n // timeout that can fail. The resume endpoint simply unpauses the existing one.\n //\n // After calling resume we stay on the current URL. The 3-second refetch\n // interval in useActiveConversation (active while conversation_url is null)\n // polls until conversation_url populates, then the WebSocket connects.\n //\n // A ref guards against duplicate triggers per unique conversation.id within\n // the same route-mount lifetime.\n const resumeTriggeredForRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!isFetched || !conversation) return;\n if (active.backend.kind !== \"cloud\") return;\n if (conversation.sandbox_status !== \"PAUSED\") return; // only resume PAUSED sandboxes\n if (!conversation.sandbox_id) return; // no sandbox to resume\n if (resumeTriggeredForRef.current === conversation.id) return; // already sent\n\n resumeTriggeredForRef.current = conversation.id;\n\n resumeCloudSandbox(conversation.sandbox_id).catch(() => {\n displayErrorToast(t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));\n });\n }, [\n isFetched,\n conversation?.id,\n conversation?.sandbox_status,\n conversation?.sandbox_id,\n active.backend.kind,\n t,\n ]);\n\n const content = (\n <EventHandler>\n <div data-testid=\"app-route\" className=\"flex h-full flex-col\">\n {panelViewMatch ? (\n <ConversationMobilePanelPage\n onNavigateBack={() => navigate(`/conversations/${conversationId}`)}\n />\n ) : (\n <ConversationMain />\n )}\n </div>\n </EventHandler>\n );\n\n return (\n <WebSocketProviderWrapper conversationId={conversationId}>\n {content}\n </WebSocketProviderWrapper>\n );\n}\n\nexport function ConversationView() {\n return <AppContent />;\n}\n\nexport default ConversationView;\n"],"mappings":"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"}