synabun 2.0.9 → 2.0.10
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.
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -4
- package/launcher.mjs +9 -3
- package/lib/start-launcher.js +6 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/neural-interface/i18n/en.json +218 -7
- package/neural-interface/i18n/pt-BR.json +220 -9
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +21 -7
- package/neural-interface/lib/assistant-brains/opencode.js +13 -1
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +44 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +57 -0
- package/neural-interface/lib/assistant-catalog.js +17 -2
- package/neural-interface/lib/assistant-dispatch.js +3 -0
- package/neural-interface/lib/assistant-envelope.js +19 -1
- package/neural-interface/lib/assistant-runtime.js +127 -8
- package/neural-interface/lib/claude-model-catalog.js +64 -14
- package/neural-interface/lib/codex-settings-schema.js +109 -47
- package/neural-interface/lib/editor-completion-opencode.js +132 -35
- package/neural-interface/lib/exec-loop-command.js +8 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- package/neural-interface/lib/native-loop-providers.js +18 -3
- package/neural-interface/lib/native-loop-runtime.js +19 -0
- package/neural-interface/lib/opencode-db-schema.js +75 -0
- package/neural-interface/lib/opencode-history.js +16 -1
- package/neural-interface/lib/opencode-protocol.js +173 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +10 -3
- package/neural-interface/lib/opencode-serve-records.js +606 -0
- package/neural-interface/lib/opencode-v2-client.js +33 -6
- package/neural-interface/lib/opencode-v2-ws-requests.js +19 -5
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode2-client.js +760 -0
- package/neural-interface/lib/opencode2-proxy.js +116 -0
- package/neural-interface/lib/opencode2-translate.js +1008 -0
- package/neural-interface/lib/process-identity.js +361 -0
- package/neural-interface/lib/session-cache-builder.js +26 -5
- package/neural-interface/lib/session-title-generator.js +18 -0
- package/neural-interface/lib/style-guide/api.js +4 -3
- package/neural-interface/lib/untrusted-file-headers.js +27 -0
- package/neural-interface/package-lock.json +1343 -45
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/index.html +2 -2
- package/neural-interface/public/shared/agent-runtime-options.js +55 -11
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +195 -80
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
- package/neural-interface/public/shared/assistant/asst-menu.js +77 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +197 -33
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +28 -10
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +382 -36
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +528 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- package/neural-interface/public/shared/assistant/asst-state.js +14 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +43 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +13 -1
- package/neural-interface/public/shared/cc-dropdown.js +480 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +13 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +149 -35
- package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +15 -5
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +74 -80
- package/neural-interface/public/shared/launch-prefetch.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +7 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +93 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +16 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +60 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -19
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +195 -19
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- package/neural-interface/public/shared/schedules-view.js +318 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-data.js +88 -0
- package/neural-interface/public/shared/settings/settings-ia.js +17 -0
- package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
- package/neural-interface/public/shared/styles.css +540 -371
- package/neural-interface/public/shared/ui-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +67 -27
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +76 -8
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- package/neural-interface/public/shared/ui-detail.js +6 -6
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +6 -5
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +232 -33
- package/neural-interface/public/shared/ui-native-loop-router.js +2 -0
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-schedules-studio.js +2099 -869
- package/neural-interface/public/shared/ui-sessions.js +15 -4
- package/neural-interface/public/shared/ui-settings.js +87 -36
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +55 -4
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-skills.js +70 -12
- package/neural-interface/public/shared/ui-sync.js +7 -0
- package/neural-interface/public/shared/ui-terminal.js +36 -20
- package/neural-interface/public/shared/ui-trash.js +8 -1
- package/neural-interface/public/shared/ui-tutorial-steps.js +7 -3
- package/neural-interface/public/shared/ui-tutorial.js +15 -5
- package/neural-interface/public/shared/ui-update.js +83 -14
- package/neural-interface/public/shared/ui-whiteboard.js +2 -1
- package/neural-interface/public/variant/2d/main.js +4 -1
- package/neural-interface/public/variant/3d/main.js +3 -0
- package/neural-interface/public/vendor/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
- package/neural-interface/scripts/build-vendor.mjs +84 -3
- package/neural-interface/server.js +1099 -286
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import { getHiddenModels, HIDDEN_MODELS_EVENT, hiddenModelMatch } from '../ocp-hidden-models.js';
|
|
30
30
|
import { getProviderMeta } from '../provider-icons.js';
|
|
31
31
|
import { on } from '../state.js';
|
|
32
|
+
import { registerLaunchPrefetch } from '../launch-prefetch.js';
|
|
32
33
|
import { fetchAssistantCatalog, fetchProjects, patchClaudeAccount, patchCodexAccount, setClaudeWorkerAccount, setCodexWorkerAccount } from './asst-api.js';
|
|
33
34
|
import { accountBadge, accountIdentity, accountName, accountText } from '../account-view.js';
|
|
34
35
|
import { autoAccountLabel } from '../account-usage.js';
|
|
@@ -54,6 +55,9 @@ import {
|
|
|
54
55
|
} from './asst-state.js';
|
|
55
56
|
|
|
56
57
|
let _projectsCache = null;
|
|
58
|
+
// Providers whose field menus have everything they read loaded once (models, catalog, accounts or agents,
|
|
59
|
+
// presets, projects): their menus open from it at once and read it again in the background.
|
|
60
|
+
const _warmProviders = new Set();
|
|
57
61
|
|
|
58
62
|
/** SynaBun's registered projects as the last load saw them ([{ path, label }]; [] before the first). */
|
|
59
63
|
export function cachedProjects() { return _projectsCache || []; }
|
|
@@ -321,11 +325,41 @@ export async function loadModelCatalog({ force = false } = {}) {
|
|
|
321
325
|
// while it loaded: the next open asks again.
|
|
322
326
|
_catalogAt = remote && epoch === _catalogEpoch ? Date.now() : 0;
|
|
323
327
|
_catalogPartial = !remote;
|
|
328
|
+
if (_catalogAt) keepCatalog(out, sets);
|
|
324
329
|
return out;
|
|
325
330
|
})().finally(() => { _catalogLoading = null; });
|
|
326
331
|
return _catalogLoading;
|
|
327
332
|
}
|
|
328
333
|
|
|
334
|
+
// The last complete catalog is also kept in sessionStorage (this tab only): after a reload the model menus
|
|
335
|
+
// paint from it at once. Read back, it is due: the first load asks the server again.
|
|
336
|
+
const CATALOG_STORE_KEY = 'synabun:assistant-model-catalog:v1';
|
|
337
|
+
function keepCatalog(catalog, sets) {
|
|
338
|
+
try {
|
|
339
|
+
if (typeof sessionStorage === 'undefined') return;
|
|
340
|
+
const hidden = {};
|
|
341
|
+
for (const provider of PROVIDERS) hidden[provider] = [...(sets?.[provider] || [])];
|
|
342
|
+
const rowOnly = {};
|
|
343
|
+
for (const provider of PROVIDERS) rowOnly[provider] = [...(sets?.rowOnly?.[provider] || [])];
|
|
344
|
+
sessionStorage.setItem(CATALOG_STORE_KEY, JSON.stringify({ catalog, hidden, rowOnly }));
|
|
345
|
+
} catch { /* full or unavailable */ }
|
|
346
|
+
}
|
|
347
|
+
(() => {
|
|
348
|
+
try {
|
|
349
|
+
if (typeof sessionStorage === 'undefined' || _catalog) return;
|
|
350
|
+
const kept = JSON.parse(sessionStorage.getItem(CATALOG_STORE_KEY) || 'null');
|
|
351
|
+
if (!kept?.catalog || typeof kept.catalog !== 'object') return;
|
|
352
|
+
_catalog = kept.catalog;
|
|
353
|
+
_catalogAt = 0;
|
|
354
|
+
const sets = { rowOnly: {} };
|
|
355
|
+
for (const provider of PROVIDERS) {
|
|
356
|
+
sets[provider] = new Set(Array.isArray(kept.hidden?.[provider]) ? kept.hidden[provider] : []);
|
|
357
|
+
sets.rowOnly[provider] = new Set(Array.isArray(kept.rowOnly?.[provider]) ? kept.rowOnly[provider] : []);
|
|
358
|
+
}
|
|
359
|
+
_hidden = sets;
|
|
360
|
+
} catch { /* unreadable: the first load builds it */ }
|
|
361
|
+
})();
|
|
362
|
+
|
|
329
363
|
/** The last loadModelCatalog() result, synchronously (null before the first load). */
|
|
330
364
|
export function cachedModelCatalog() { return _catalog; }
|
|
331
365
|
|
|
@@ -346,6 +380,8 @@ export function invalidateModelCatalog() {
|
|
|
346
380
|
_catalogAt = 0;
|
|
347
381
|
_catalogPartial = false;
|
|
348
382
|
_catalogEpoch += 1;
|
|
383
|
+
// A reload must not paint the lists this replaced.
|
|
384
|
+
try { if (typeof sessionStorage !== 'undefined') sessionStorage.removeItem(CATALOG_STORE_KEY); } catch { /* unavailable */ }
|
|
349
385
|
}
|
|
350
386
|
|
|
351
387
|
/**
|
|
@@ -366,6 +402,15 @@ function modelListsChanged() {
|
|
|
366
402
|
for (const listener of [..._catalogListeners]) {
|
|
367
403
|
try { listener(); } catch { /* a listener's failure is its own */ }
|
|
368
404
|
}
|
|
405
|
+
scheduleCatalogReload();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// In a page: the new lists are read right away, so the next menu opens from them (an open that comes
|
|
409
|
+
// first joins this read).
|
|
410
|
+
let _catalogReloadTimer = null;
|
|
411
|
+
function scheduleCatalogReload() {
|
|
412
|
+
if (typeof document === 'undefined' || _catalogReloadTimer) return;
|
|
413
|
+
_catalogReloadTimer = setTimeout(() => { _catalogReloadTimer = null; loadModelCatalog().catch(() => {}); }, 250);
|
|
369
414
|
}
|
|
370
415
|
|
|
371
416
|
/**
|
|
@@ -397,6 +442,12 @@ on('sync:opencode:hidden-models-changed', modelListsChanged);
|
|
|
397
442
|
on('sync:assistant:hidden-models-changed', modelListsChanged);
|
|
398
443
|
on('sync:assistant:catalog-changed', modelListsChanged);
|
|
399
444
|
|
|
445
|
+
// Launch: the menus' lists are read once the page has painted, so the first model menu opens from them.
|
|
446
|
+
if (typeof document !== 'undefined') {
|
|
447
|
+
registerLaunchPrefetch('assistant-model-catalog', () => loadModelCatalog(), { order: 10, owner: true });
|
|
448
|
+
registerLaunchPrefetch('assistant-accounts', () => Promise.allSettled([fetchClaudeAccounts(), fetchCodexAccounts(), fetchMcpProfilePresets(), fetchOpencodeAgents()]), { order: 14, owner: true });
|
|
449
|
+
}
|
|
450
|
+
|
|
400
451
|
function sameModel(selected, m) {
|
|
401
452
|
if (!selected) return false;
|
|
402
453
|
return selected === m.id || selected === m.selector;
|
|
@@ -435,74 +486,28 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
435
486
|
if (!anchor) return null;
|
|
436
487
|
const tr = tf(t);
|
|
437
488
|
const b = normalizeBrain(brain);
|
|
438
|
-
|
|
439
|
-
//
|
|
440
|
-
const
|
|
441
|
-
title, className: 'asst-model-menu asst-route-model-menu',
|
|
442
|
-
items: [{ kind: 'info', label: tr('assistant.routes.editorState.loadingModels', 'Loading models…') }],
|
|
443
|
-
width: 380, zIndex: opts.zIndex ?? null, repositionOnResize: true,
|
|
444
|
-
}) : null;
|
|
445
|
-
if (loading) loading.el.setAttribute('aria-busy', 'true');
|
|
446
|
-
if (loading) { loading.el.setAttribute('role', 'dialog'); anchor.setAttribute('aria-haspopup', 'dialog'); }
|
|
489
|
+
// The kept catalog paints the menu at once (it is read again in the background below, and an open menu
|
|
490
|
+
// follows it in place); only a first open, or a reload the user asked for, waits for the lists.
|
|
491
|
+
const kept = opts.forceReload === true ? null : cachedModelCatalog();
|
|
447
492
|
let catalog = {};
|
|
448
493
|
let failed = false;
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (!
|
|
464
|
-
listed += models.length;
|
|
465
|
-
const meta = getProviderMeta(provider);
|
|
466
|
-
const hiddenCount = metadata.hiddenCounts[provider] || 0;
|
|
467
|
-
const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
|
|
468
|
-
items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
|
|
469
|
-
for (const m of models) {
|
|
470
|
-
items.push({
|
|
471
|
-
kind: 'radio',
|
|
472
|
-
id: `${provider}:${m.id}`,
|
|
473
|
-
label: m.label,
|
|
474
|
-
desc: modelDesc(m, tr, opts.routingEditor),
|
|
475
|
-
title: `${m.label || m.id} · ${m.id}`,
|
|
476
|
-
tag: tierTag(m, tr),
|
|
477
|
-
keywords: `${meta.label} ${m.id}`,
|
|
478
|
-
selected: !!current && current.provider === provider && sameModel(current.model, m),
|
|
479
|
-
onSelect: () => onSelect?.({ provider, model: m.id, label: m.label, tier: m.tier || null, vision: m.vision ?? null, outputs: m.outputs || null, efforts: m.efforts || null, price: m.price || null }),
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
if (opts.routingEditor && !models.length) items.push({ kind: 'info', label: typeof accept === 'function' ? tr('assistant.routes.menu.noCompatible', 'No compatible models shown for this provider.') : tr('assistant.routes.menu.providerEmpty', 'No models shown for this provider.') });
|
|
483
|
-
}
|
|
484
|
-
// Nothing passes the filter (e.g. no model makes video yet): say so instead of an empty list.
|
|
485
|
-
if (!listed && typeof accept === 'function' && emptyLabel) items.push({ kind: 'info', label: emptyLabel });
|
|
486
|
-
if (opts.routingEditor && !listed && typeof accept !== 'function') items.push({ kind: 'info', label: tr('assistant.routes.editorState.modelsEmpty', 'No models are available. Check Models or connect a provider, then reload.') });
|
|
487
|
-
if (allowCustom) {
|
|
488
|
-
items.push({ kind: 'separator' });
|
|
489
|
-
items.push({
|
|
490
|
-
kind: 'input',
|
|
491
|
-
placeholder: tr('assistant.brain.modelCustom', 'Custom model id…'),
|
|
492
|
-
onSubmit: (v) => {
|
|
493
|
-
const provider = current?.provider || b.provider;
|
|
494
|
-
const disabled = hiddenModelFor(provider, v);
|
|
495
|
-
if (disabled) {
|
|
496
|
-
onToast?.(tr('assistant.brain.modelDisabled', "{model} is disabled in the Assistant's Models list.", { model: disabled }));
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
onSelect?.({ provider, model: v, label: modelShortName(v), custom: true });
|
|
500
|
-
},
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
if (!opts.routingEditor && typeof opts.onManageModels === 'function') {
|
|
504
|
-
items.push({ id: '__manage_models', label: tr('assistant.models.manage', 'Manage models…'), onSelect: () => opts.onManageModels() });
|
|
494
|
+
let loading = null;
|
|
495
|
+
if (kept) {
|
|
496
|
+
catalog = kept;
|
|
497
|
+
} else {
|
|
498
|
+
anchor.setAttribute('aria-busy', 'true');
|
|
499
|
+
// Open immediately: a slow provider still has a visible, cancellable state.
|
|
500
|
+
loading = opts.routingEditor ? openMenu(anchor, {
|
|
501
|
+
title, className: 'asst-model-menu asst-route-model-menu',
|
|
502
|
+
items: [{ kind: 'info', label: tr('assistant.routes.editorState.loadingModels', 'Loading models…') }],
|
|
503
|
+
width: 380, zIndex: opts.zIndex ?? null, repositionOnResize: true,
|
|
504
|
+
}) : null;
|
|
505
|
+
if (loading) loading.el.setAttribute('aria-busy', 'true');
|
|
506
|
+
if (loading) { loading.el.setAttribute('role', 'dialog'); anchor.setAttribute('aria-haspopup', 'dialog'); }
|
|
507
|
+
try { catalog = await loadModelCatalog({ force: opts.forceReload === true }); } catch { failed = true; } finally { anchor.removeAttribute('aria-busy'); }
|
|
508
|
+
if (!anchor.isConnected || (loading && currentMenu() !== loading.el)) return null;
|
|
505
509
|
}
|
|
510
|
+
const items = buildModelMenuItems(catalog);
|
|
506
511
|
const menu = openMenu(anchor, {
|
|
507
512
|
title: title || tr('assistant.brain.model', 'Model'),
|
|
508
513
|
items,
|
|
@@ -516,7 +521,79 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
516
521
|
repositionOnResize: true,
|
|
517
522
|
filterEmptyLabel: tr('assistant.routes.menu.noMatches', 'No matching models. Check Models for hidden choices.'),
|
|
518
523
|
});
|
|
519
|
-
if (opts.routingEditor && menu)
|
|
524
|
+
if (opts.routingEditor && menu) decorateRouteMenu(menu, failed);
|
|
525
|
+
// Painted from the kept catalog: read it again (fresh within its minute, else a background request) and
|
|
526
|
+
// redraw the open menu in place when the lists changed, leaving the filter, the scroll and the focus alone.
|
|
527
|
+
if (kept && menu && !opts.routingEditor) {
|
|
528
|
+
loadModelCatalog().then((next) => {
|
|
529
|
+
if (!next || next === kept || currentMenu() !== menu.el || !anchor.isConnected) return;
|
|
530
|
+
const nextItems = buildModelMenuItems(next);
|
|
531
|
+
if (menuItemsSignature(nextItems) !== menuItemsSignature(items)) menu.update({ items: nextItems, keepPlace: true });
|
|
532
|
+
}).catch(() => { /* the kept list stays */ });
|
|
533
|
+
}
|
|
534
|
+
return menu;
|
|
535
|
+
|
|
536
|
+
function buildModelMenuItems(catalog) {
|
|
537
|
+
const metadata = modelCatalogStatus();
|
|
538
|
+
const items = [];
|
|
539
|
+
if (Array.isArray(opts.accountItems) && opts.accountItems.length) {
|
|
540
|
+
items.push({ kind: 'header', label: opts.accountTitle || tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
|
|
541
|
+
}
|
|
542
|
+
if (includeHere) {
|
|
543
|
+
items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
|
|
544
|
+
}
|
|
545
|
+
const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : PROVIDERS;
|
|
546
|
+
let listed = 0;
|
|
547
|
+
for (const provider of list) {
|
|
548
|
+
const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
|
|
549
|
+
if (!models.length && !opts.routingEditor) continue;
|
|
550
|
+
listed += models.length;
|
|
551
|
+
const meta = getProviderMeta(provider);
|
|
552
|
+
const hiddenCount = metadata.hiddenCounts[provider] || 0;
|
|
553
|
+
const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
|
|
554
|
+
items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
|
|
555
|
+
for (const m of models) {
|
|
556
|
+
items.push({
|
|
557
|
+
kind: 'radio',
|
|
558
|
+
id: `${provider}:${m.id}`,
|
|
559
|
+
label: m.label,
|
|
560
|
+
desc: modelDesc(m, tr, opts.routingEditor),
|
|
561
|
+
title: `${m.label || m.id} · ${m.id}`,
|
|
562
|
+
tag: tierTag(m, tr),
|
|
563
|
+
keywords: `${meta.label} ${m.id}`,
|
|
564
|
+
selected: !!current && current.provider === provider && sameModel(current.model, m),
|
|
565
|
+
onSelect: () => onSelect?.({ provider, model: m.id, label: m.label, tier: m.tier || null, vision: m.vision ?? null, outputs: m.outputs || null, efforts: m.efforts || null, price: m.price || null }),
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
if (opts.routingEditor && !models.length) items.push({ kind: 'info', label: typeof accept === 'function' ? tr('assistant.routes.menu.noCompatible', 'No compatible models shown for this provider.') : tr('assistant.routes.menu.providerEmpty', 'No models shown for this provider.') });
|
|
569
|
+
}
|
|
570
|
+
// Nothing passes the filter (e.g. no model makes video yet): say so instead of an empty list.
|
|
571
|
+
if (!listed && typeof accept === 'function' && emptyLabel) items.push({ kind: 'info', label: emptyLabel });
|
|
572
|
+
if (opts.routingEditor && !listed && typeof accept !== 'function') items.push({ kind: 'info', label: tr('assistant.routes.editorState.modelsEmpty', 'No models are available. Check Models or connect a provider, then reload.') });
|
|
573
|
+
if (allowCustom) {
|
|
574
|
+
items.push({ kind: 'separator' });
|
|
575
|
+
items.push({
|
|
576
|
+
kind: 'input',
|
|
577
|
+
placeholder: tr('assistant.brain.modelCustom', 'Custom model id…'),
|
|
578
|
+
onSubmit: (v) => {
|
|
579
|
+
const provider = current?.provider || b.provider;
|
|
580
|
+
const disabled = hiddenModelFor(provider, v);
|
|
581
|
+
if (disabled) {
|
|
582
|
+
onToast?.(tr('assistant.brain.modelDisabled', "{model} is disabled in the Assistant's Models list.", { model: disabled }));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
onSelect?.({ provider, model: v, label: modelShortName(v), custom: true });
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
if (!opts.routingEditor && typeof opts.onManageModels === 'function') {
|
|
590
|
+
items.push({ id: '__manage_models', label: tr('assistant.models.manage', 'Manage models…'), onSelect: () => opts.onManageModels() });
|
|
591
|
+
}
|
|
592
|
+
return items;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function decorateRouteMenu(menu, failed) {
|
|
596
|
+
const metadata = modelCatalogStatus();
|
|
520
597
|
// Search and a custom-id input belong to a dialog; choices are a radio group.
|
|
521
598
|
// A menu role cannot contain these form fields or recovery buttons.
|
|
522
599
|
menu.el.setAttribute('role', 'dialog');
|
|
@@ -546,7 +623,11 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
546
623
|
}
|
|
547
624
|
menu.el.append(footer); menu.reposition();
|
|
548
625
|
}
|
|
549
|
-
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** What a model menu lists, for telling a fresher read from the one on screen. */
|
|
629
|
+
function menuItemsSignature(items) {
|
|
630
|
+
return JSON.stringify((items || []).map((item) => [item.kind || '', item.id ?? '', item.label || '', item.desc || '', item.tag || '', item.note || '', !!item.selected]));
|
|
550
631
|
}
|
|
551
632
|
|
|
552
633
|
// ── Picker ──────────────────────────────────────────────────────────────────
|
|
@@ -968,6 +1049,7 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
968
1049
|
}).catch(() => { _projectsCache = _projectsCache || []; }));
|
|
969
1050
|
}
|
|
970
1051
|
await Promise.allSettled(jobs);
|
|
1052
|
+
if (cachedModelCatalog()) _warmProviders.add(provider);
|
|
971
1053
|
}
|
|
972
1054
|
|
|
973
1055
|
async function refresh(force = false) {
|
|
@@ -999,7 +1081,11 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
999
1081
|
const anchor = anchorFor(field, anchorOverride);
|
|
1000
1082
|
const brain = currentBrain();
|
|
1001
1083
|
if (field === 'model') {
|
|
1002
|
-
|
|
1084
|
+
// The account rows come from the kept lists; they are read again in the background once loaded.
|
|
1085
|
+
if (ACCOUNT_PROVIDERS.has(brain.provider)) {
|
|
1086
|
+
if (_warmProviders.has(brain.provider)) ensureLoaded(brain.provider).catch(() => {});
|
|
1087
|
+
else await ensureLoaded(brain.provider);
|
|
1088
|
+
}
|
|
1003
1089
|
await openModelMenu(anchor, {
|
|
1004
1090
|
brain,
|
|
1005
1091
|
t: hooks.t,
|
|
@@ -1034,12 +1120,48 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1034
1120
|
});
|
|
1035
1121
|
return;
|
|
1036
1122
|
}
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1123
|
+
// The account menu reads the kept rows and asks again by itself (asst-account-menu.js).
|
|
1124
|
+
if (field === 'account') {
|
|
1125
|
+
if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
|
|
1126
|
+
if (!_warmProviders.has(brain.provider)) {
|
|
1127
|
+
anchor.setAttribute('aria-busy', 'true');
|
|
1128
|
+
try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
|
|
1129
|
+
if (destroyed || !anchor.isConnected) return;
|
|
1130
|
+
} else {
|
|
1131
|
+
ensureLoaded(brain.provider).catch(() => {});
|
|
1132
|
+
}
|
|
1133
|
+
// Kept inside the panel that holds the anchor (the sidepanel, or the tab's own root).
|
|
1134
|
+
openAccountMenu(anchor, accountMenu(brain, anchor.closest?.('.assistant-panel') || anchor.closest?.('.asst-root') || null));
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
// Everything else needs the provider catalog — load it lazily on first open. Once it is loaded the menu
|
|
1138
|
+
// opens from what is kept, the lists are read again in the background, and an open menu they change is
|
|
1139
|
+
// redrawn in place (the filter, the scroll and the focus stay).
|
|
1140
|
+
const warm = _warmProviders.has(brain.provider);
|
|
1141
|
+
if (!warm) {
|
|
1142
|
+
anchor.setAttribute('aria-busy', 'true');
|
|
1143
|
+
try { await ensureLoaded(brain.provider); } finally { anchor.removeAttribute('aria-busy'); }
|
|
1144
|
+
if (destroyed || !anchor.isConnected) return;
|
|
1145
|
+
}
|
|
1146
|
+
let shown = null;
|
|
1147
|
+
drawField(field, brain, (title, items, extra = {}) => {
|
|
1148
|
+
const opened = openMenu(anchor, { title, items, placement, role: 'menu', ...extra });
|
|
1149
|
+
shown = opened ? { opened, items } : null;
|
|
1150
|
+
return opened;
|
|
1151
|
+
}, { anchor, restoreFocus });
|
|
1152
|
+
if (warm && shown) {
|
|
1153
|
+
ensureLoaded(brain.provider).then(() => {
|
|
1154
|
+
if (destroyed || !shown || currentMenu() !== shown.opened.el || !anchor.isConnected) return;
|
|
1155
|
+
let fresh = null;
|
|
1156
|
+
drawField(field, currentBrain(), (_title, items) => { fresh = items; return null; }, { anchor, restoreFocus });
|
|
1157
|
+
if (fresh && menuItemsSignature(fresh) !== menuItemsSignature(shown.items)) shown.opened.update({ items: fresh, keepPlace: true });
|
|
1158
|
+
}).catch(() => {});
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1042
1161
|
|
|
1162
|
+
// The menu of one field, from the lists as they are kept now: `menu(title, items, extra)` opens it (or, for a
|
|
1163
|
+
// redraw, only takes the items).
|
|
1164
|
+
function drawField(field, brain, menu, { anchor, restoreFocus = false } = {}) {
|
|
1043
1165
|
if (field === 'effort') {
|
|
1044
1166
|
if (brain.provider === 'opencode') {
|
|
1045
1167
|
const agents = getCachedOpencodeAgents();
|
|
@@ -1070,13 +1192,6 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1070
1192
|
return;
|
|
1071
1193
|
}
|
|
1072
1194
|
|
|
1073
|
-
if (field === 'account') {
|
|
1074
|
-
if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
|
|
1075
|
-
// Kept inside the panel that holds the anchor (the sidepanel, or the tab's own root).
|
|
1076
|
-
openAccountMenu(anchor, accountMenu(brain, anchor.closest?.('.assistant-panel') || anchor.closest?.('.asst-root') || null));
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
1195
|
if (field === 'project') {
|
|
1081
1196
|
const actor = anchor === heroProject ? heroProjectMascot : null;
|
|
1082
1197
|
const pick = project => {
|
|
@@ -204,7 +204,7 @@ export function createComposer(hostEl, hooks = {}) {
|
|
|
204
204
|
function renderAttachments() {
|
|
205
205
|
const chips = [];
|
|
206
206
|
state.images.forEach((img, i) => {
|
|
207
|
-
chips.push(`<span class="asst-chip static" data-kind="image" data-idx="${i}"><img src="data:${esc(img.mediaType)};base64,${img.base64}" alt=""><span class="asst-chip-text">${esc(img.name || 'image')}</span><button type="button" class="asst-chip-x" aria-label="${esc(t('common.remove', 'Remove'))}">${ICON_X}</button></span>`);
|
|
207
|
+
chips.push(`<span class="asst-chip static" data-kind="image" data-idx="${i}"><img src="data:${esc(img.mediaType)};base64,${esc(img.base64)}" alt=""><span class="asst-chip-text">${esc(img.name || 'image')}</span><button type="button" class="asst-chip-x" aria-label="${esc(t('common.remove', 'Remove'))}">${ICON_X}</button></span>`);
|
|
208
208
|
});
|
|
209
209
|
state.files.forEach((f, i) => {
|
|
210
210
|
chips.push(`<span class="asst-chip static" data-kind="file" data-idx="${i}"><span class="asst-chip-text">${esc(f.name)}</span><span class="asst-chip-meta">${esc(Math.ceil(f.content.length / 1024))}k</span><button type="button" class="asst-chip-x" aria-label="${esc(t('common.remove', 'Remove'))}">${ICON_X}</button></span>`);
|
|
@@ -54,7 +54,7 @@ export function describeBrainFallback(fallback) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* deps: { api: { list(), get(id), create({brain,label,fallback}) },
|
|
57
|
+
* deps: { api: { list(), get(id), create({brain,label,fallback,draft}), remove?(id) },
|
|
58
58
|
* isBlocked() → bool, toast(text), storedBrain() → brain|null,
|
|
59
59
|
* fallbackText(session.fallback) → text, defaultHost: 'terminal' }
|
|
60
60
|
*/
|
|
@@ -149,7 +149,9 @@ export function createAssistantHosts({
|
|
|
149
149
|
try {
|
|
150
150
|
// A model disabled since it was picked (Assistant → Models) must not
|
|
151
151
|
// lock the Assistant out: the server starts on its enabled stand-in.
|
|
152
|
-
|
|
152
|
+
// A draft: the server stores and lists it from its first prompt on (a prompt
|
|
153
|
+
// given here is that prompt); closed or left unused, nothing of it remains.
|
|
154
|
+
res = await api.create({ brain: b, label: label || undefined, fallback: true, draft: true });
|
|
153
155
|
} catch (err) {
|
|
154
156
|
say(`Assistant unavailable: ${err?.message || err}`);
|
|
155
157
|
return null;
|
|
@@ -245,6 +247,48 @@ export function createAssistantHosts({
|
|
|
245
247
|
return true;
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
/**
|
|
251
|
+
* A session nobody chatted in that the user left (they picked another one in its Sessions menu):
|
|
252
|
+
* its tab closes the way the user closes one, and the server removes it (it holds no conversation).
|
|
253
|
+
*/
|
|
254
|
+
function discard(sessionId) {
|
|
255
|
+
const owner = ownerOf(sessionId);
|
|
256
|
+
if (last?.sessionId === sessionId) last = null;
|
|
257
|
+
if (!owner) return false;
|
|
258
|
+
adapters.get(owner).close(sessionId, { closeServer: true });
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The Sessions menu's Remove: the server stops the session's running turn and workers and purges it,
|
|
264
|
+
* then its tab closes wherever it is open (other windows: the session-ended broadcast). `fresh`: it is
|
|
265
|
+
* the session showing where the user asked, and the host that has it gets a new one first, as New does
|
|
266
|
+
* (a host is never left empty in between). → { ok: true, stoppedRuns } | { ok: false, error }
|
|
267
|
+
*/
|
|
268
|
+
async function remove(sessionId, { host = null, fresh = false, brain = null } = {}) {
|
|
269
|
+
if (!sessionId || !api.remove) return { ok: false, error: 'unavailable' };
|
|
270
|
+
if (isBlocked()) return { ok: false, error: 'blocked' };
|
|
271
|
+
if (fresh) { try { await open({ host: host || ownerOf(sessionId), brain }); } catch { /* the removal goes on */ } }
|
|
272
|
+
let res = null;
|
|
273
|
+
try { res = await api.remove(sessionId); }
|
|
274
|
+
catch (err) { return { ok: false, error: err?.message || String(err) }; }
|
|
275
|
+
ended(sessionId);
|
|
276
|
+
return { ok: true, stoppedRuns: Array.isArray(res?.stoppedRuns) ? res.stoppedRuns : [] };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The server no longer has a session a host shows (a draft it dropped, a restart before its first
|
|
281
|
+
* prompt, a removal this page missed): its tab closes, and with `fresh` (it was showing and had
|
|
282
|
+
* no conversation) a new session takes its place in the host that had it, so the user never sees a dead tab.
|
|
283
|
+
*/
|
|
284
|
+
async function gone(sessionId, { host = null, fresh = false, brain = null } = {}) {
|
|
285
|
+
if (!sessionId) return null;
|
|
286
|
+
let opened = null;
|
|
287
|
+
if (fresh && !isBlocked()) { try { opened = await open({ host: host || ownerOf(sessionId), brain }); } catch { opened = null; } }
|
|
288
|
+
ended(sessionId);
|
|
289
|
+
return opened;
|
|
290
|
+
}
|
|
291
|
+
|
|
248
292
|
return {
|
|
249
293
|
register,
|
|
250
294
|
ownerOf,
|
|
@@ -253,6 +297,9 @@ export function createAssistantHosts({
|
|
|
253
297
|
toggle,
|
|
254
298
|
show,
|
|
255
299
|
ended,
|
|
300
|
+
discard,
|
|
301
|
+
remove,
|
|
302
|
+
gone,
|
|
256
303
|
noteFocus,
|
|
257
304
|
fetchLive,
|
|
258
305
|
isLive: isAssistantSessionLive,
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
// tag { label } → non-interactive badge row
|
|
32
32
|
// input { placeholder, value, onSubmit } → inline text field
|
|
33
33
|
// buttons { label, buttons: [item…] } → one row of compact items (a group)
|
|
34
|
+
// `action: { icon, label, tooltip, id, onSelect }` on an item (or radio / check) adds one icon button at the
|
|
35
|
+
// row's end, shown on hover and keyboard focus (the Sessions menu's Remove). It is not a stop of ↑/↓: → from
|
|
36
|
+
// its row reaches it, ← goes back, Delete on the row presses it; choosing it never chooses the row and leaves
|
|
37
|
+
// the menu open (its onSelect usually redraws it with update()).
|
|
34
38
|
// `render(button, { checked })` on an item draws its content instead of label/desc/tag.
|
|
35
39
|
// `className` on an item or an info row adds its own class.
|
|
36
40
|
// `keepOpen` on an item leaves the menu open when it is chosen.
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
// Keys: ↑/↓ move, Home/End jump, Enter/Space activate, Esc closes (focus back
|
|
39
43
|
// to the anchor), Tab closes. The optional filter hides non-matching items and
|
|
40
44
|
// any group left empty. The handle's update({ title, items, footer, focus }) redraws
|
|
41
|
-
// an open menu in place (focus: the data-id to land on).
|
|
45
|
+
// an open menu in place (focus: the data-id to land on; the filter's text still applies).
|
|
42
46
|
|
|
43
47
|
let _open = null;
|
|
44
48
|
let _uid = 0;
|
|
@@ -101,6 +105,11 @@ export function currentMenu() {
|
|
|
101
105
|
return _open?.menu || null;
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
/** The element the open menu belongs to (null when none is open). */
|
|
109
|
+
export function currentMenuAnchor() {
|
|
110
|
+
return _open?.anchor || null;
|
|
111
|
+
}
|
|
112
|
+
|
|
104
113
|
function itemButton(item, kind, { role, list, onActivate }) {
|
|
105
114
|
const btn = el('button', `asst-dd-item${item.danger ? ' danger' : ''}${item.className ? ` ${item.className}` : ''}`);
|
|
106
115
|
btn.type = 'button';
|
|
@@ -145,12 +154,28 @@ function itemButton(item, kind, { role, list, onActivate }) {
|
|
|
145
154
|
return btn;
|
|
146
155
|
}
|
|
147
156
|
|
|
157
|
+
/** A row's own action (item.action): an icon button beside the row, outside the ↑/↓ cycle. */
|
|
158
|
+
function actionButton(action, item, { role }) {
|
|
159
|
+
const btn = el('button', `asst-iconbtn asst-dd-item-action${action.className ? ` ${action.className}` : ''}`);
|
|
160
|
+
btn.type = 'button';
|
|
161
|
+
btn.tabIndex = -1;
|
|
162
|
+
btn.setAttribute('role', role === 'listbox' ? 'option' : 'menuitem');
|
|
163
|
+
if (action.id != null) btn.dataset.id = String(action.id);
|
|
164
|
+
const label = String(action.label || '');
|
|
165
|
+
if (label) btn.setAttribute('aria-label', label);
|
|
166
|
+
if (action.tooltip || label) btn.dataset.tooltip = action.tooltip || label;
|
|
167
|
+
btn.innerHTML = action.icon || '';
|
|
168
|
+
btn.querySelector('svg')?.setAttribute('aria-hidden', 'true');
|
|
169
|
+
btn.addEventListener('click', (e) => { e.stopPropagation(); action.onSelect?.(item, btn); });
|
|
170
|
+
return btn;
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
function buildItems(list, items, { role, onActivate }) {
|
|
149
174
|
for (const item of items || []) {
|
|
150
175
|
const kind = itemKind(item);
|
|
151
176
|
if (kind === 'separator') { list.appendChild(el('div', 'asst-dd-sep')).setAttribute('role', 'separator'); continue; }
|
|
152
177
|
if (kind === 'buttons') {
|
|
153
|
-
const row = el('div',
|
|
178
|
+
const row = el('div', `asst-dd-btnrow${item.className ? ` ${item.className}` : ''}`);
|
|
154
179
|
row.setAttribute('role', 'group');
|
|
155
180
|
if (item.id != null) row.dataset.id = String(item.id);
|
|
156
181
|
if (item.ariaLabel || item.label) row.setAttribute('aria-label', item.ariaLabel || item.label);
|
|
@@ -188,6 +213,14 @@ function buildItems(list, items, { role, onActivate }) {
|
|
|
188
213
|
list.appendChild(input);
|
|
189
214
|
continue;
|
|
190
215
|
}
|
|
216
|
+
if (item.action && typeof item.action === 'object' && !item.disabled) {
|
|
217
|
+
const row = el('div', 'asst-dd-itemrow');
|
|
218
|
+
row.setAttribute('role', 'none');
|
|
219
|
+
row.appendChild(itemButton(item, kind, { role, list, onActivate }));
|
|
220
|
+
row.appendChild(actionButton(item.action, item, { role }));
|
|
221
|
+
list.appendChild(row);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
191
224
|
list.appendChild(itemButton(item, kind, { role, list, onActivate }));
|
|
192
225
|
}
|
|
193
226
|
if (!list.querySelector('.asst-dd-item, .asst-dd-input, .asst-dd-info, .asst-dd-tagrow')) list.appendChild(el('div', 'asst-dd-empty', '—'));
|
|
@@ -199,6 +232,8 @@ export function applyMenuFilter(list, query, emptyLabel = 'No matches') {
|
|
|
199
232
|
const rows = [...list.children];
|
|
200
233
|
for (const row of rows) {
|
|
201
234
|
if (row.classList.contains('asst-dd-item')) row.hidden = !!q && !row.dataset.search.includes(q);
|
|
235
|
+
// A row with its own action: shown while its item matches.
|
|
236
|
+
else if (row.classList.contains('asst-dd-itemrow')) row.hidden = !!q && !(row.querySelector('.asst-dd-item')?.dataset.search || '').includes(q);
|
|
202
237
|
// A button row stays whole: shown while one of its buttons matches.
|
|
203
238
|
else if (row.classList.contains('asst-dd-btnrow')) row.hidden = !!q && ![...row.querySelectorAll('.asst-dd-item')].some(b => b.dataset.search.includes(q));
|
|
204
239
|
else if (row.classList.contains('asst-dd-info') || row.classList.contains('asst-dd-tagrow')) row.hidden = !!q;
|
|
@@ -226,7 +261,7 @@ export function applyMenuFilter(list, query, emptyLabel = 'No matches') {
|
|
|
226
261
|
const last = visible[visible.length - 1];
|
|
227
262
|
if (last?.classList.contains('asst-dd-sep')) last.hidden = true;
|
|
228
263
|
let empty = list.querySelector('.asst-dd-empty.asst-dd-noresults');
|
|
229
|
-
const hasItem = rows.some(r => !r.hidden && (r.classList.contains('asst-dd-item') || r.classList.contains('asst-dd-btnrow') || r.classList.contains('asst-dd-input')));
|
|
264
|
+
const hasItem = rows.some(r => !r.hidden && (r.classList.contains('asst-dd-item') || r.classList.contains('asst-dd-itemrow') || r.classList.contains('asst-dd-btnrow') || r.classList.contains('asst-dd-input')));
|
|
230
265
|
if (q && !hasItem) {
|
|
231
266
|
if (!empty) { empty = el('div', 'asst-dd-empty asst-dd-noresults', emptyLabel); list.appendChild(empty); }
|
|
232
267
|
empty.hidden = false;
|
|
@@ -422,7 +457,9 @@ export function openMenu(anchor, opts = {}) {
|
|
|
422
457
|
const target = e.target;
|
|
423
458
|
const inInput = target?.classList?.contains('asst-dd-input');
|
|
424
459
|
const nodes = focusables(menu);
|
|
425
|
-
|
|
460
|
+
// On a row's action (item.action) the keys move from its row.
|
|
461
|
+
const onAction = document.activeElement?.classList?.contains('asst-dd-item-action') && menu.contains(document.activeElement) ? document.activeElement : null;
|
|
462
|
+
const idx = nodes.indexOf(onAction ? onAction.previousElementSibling : document.activeElement);
|
|
426
463
|
if (e.key === 'Escape') {
|
|
427
464
|
e.preventDefault(); e.stopPropagation();
|
|
428
465
|
if (typeof opts.onEscape === 'function' && opts.onEscape() === true) return;
|
|
@@ -447,6 +484,18 @@ export function openMenu(anchor, opts = {}) {
|
|
|
447
484
|
setFocused(menu, nodes[next]);
|
|
448
485
|
return;
|
|
449
486
|
}
|
|
487
|
+
if (!inInput && (e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.key === 'Delete')) {
|
|
488
|
+
const row = onAction ? null : document.activeElement?.closest?.('.asst-dd-item');
|
|
489
|
+
const action = onAction || (row?.parentElement?.classList.contains('asst-dd-itemrow') ? row.nextElementSibling : null);
|
|
490
|
+
if (action?.classList.contains('asst-dd-item-action')) {
|
|
491
|
+
e.preventDefault(); e.stopPropagation();
|
|
492
|
+
const main = action.previousElementSibling;
|
|
493
|
+
if (e.key === 'ArrowLeft') { if (onAction) setFocused(menu, main); return; }
|
|
494
|
+
if (!onAction) { setFocused(menu, main); try { action.focus({ preventScroll: true }); } catch { action.focus(); } }
|
|
495
|
+
if (e.key === 'Delete') action.click();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
450
499
|
if ((e.key === 'Home' || e.key === 'End') && !inInput) {
|
|
451
500
|
e.preventDefault(); e.stopPropagation();
|
|
452
501
|
if (nodes.length) setFocused(menu, nodes[e.key === 'Home' ? 0 : nodes.length - 1]);
|
|
@@ -464,6 +513,7 @@ export function openMenu(anchor, opts = {}) {
|
|
|
464
513
|
return;
|
|
465
514
|
}
|
|
466
515
|
if ((e.key === 'Enter' || e.key === ' ') && !inInput) {
|
|
516
|
+
if (onAction) { e.preventDefault(); e.stopPropagation(); onAction.click(); return; }
|
|
467
517
|
const current = document.activeElement?.closest?.('.asst-dd-item');
|
|
468
518
|
if (current && menu.contains(current)) { e.preventDefault(); e.stopPropagation(); current.click(); }
|
|
469
519
|
return;
|
|
@@ -499,14 +549,35 @@ export function openMenu(anchor, opts = {}) {
|
|
|
499
549
|
setFocused(menu, selected || first || null);
|
|
500
550
|
}
|
|
501
551
|
// Redraw in place (another view of the same menu): the anchor, the listeners and the focus stay in it.
|
|
552
|
+
// `keepPlace` (fresher data for the same view, read in the background): the scroll position, the row the
|
|
553
|
+
// keyboard is on and the filter's text and focus are left where the user had them.
|
|
502
554
|
const update = (next = {}) => {
|
|
503
555
|
if (!_open || _open.menu !== menu) return;
|
|
556
|
+
const keep = next.keepPlace === true;
|
|
557
|
+
const scrollBefore = list.scrollTop;
|
|
558
|
+
const active = document.activeElement;
|
|
559
|
+
const typing = !!active && active.classList?.contains('asst-dd-input') && menu.contains(active);
|
|
560
|
+
const onRowId = !typing ? (menu.querySelector('.asst-dd-item.focused')?.dataset?.id ?? null) : null;
|
|
504
561
|
if (typeof next.title === 'string' && heading) heading.textContent = next.title;
|
|
505
|
-
if (Array.isArray(next.items)) {
|
|
562
|
+
if (Array.isArray(next.items)) {
|
|
563
|
+
list.replaceChildren();
|
|
564
|
+
buildItems(list, next.items, { role, onActivate: activate });
|
|
565
|
+
if (filterInput?.value) applyMenuFilter(list, filterInput.value, opts.filterEmptyLabel);
|
|
566
|
+
list.scrollTop = keep ? scrollBefore : 0;
|
|
567
|
+
}
|
|
506
568
|
if ('footer' in next) fillFooter(next.footer);
|
|
507
569
|
place();
|
|
508
|
-
|
|
570
|
+
if (keep && typing) {
|
|
571
|
+
// The input keeps the caret; the row highlight follows the selection as it did when it opened.
|
|
572
|
+
menu.querySelectorAll('.asst-dd-item.focused').forEach(n => n.classList.remove('focused'));
|
|
573
|
+
list.querySelector('.asst-dd-item[data-checked="1"]:not([disabled])')?.classList.add('focused');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const keptRow = keep && onRowId != null ? [...menu.querySelectorAll('.asst-dd-item')].find(n => n.dataset.id === onRowId && !n.disabled) : null;
|
|
577
|
+
const wanted = keptRow || (next.focus != null ? [...menu.querySelectorAll('.asst-dd-item')].find(n => n.dataset.id === String(next.focus) && !n.disabled) : null);
|
|
578
|
+
if (keep && !keptRow && !menu.contains(document.activeElement)) return; // focus is elsewhere: not taken back
|
|
509
579
|
setFocused(menu, wanted || list.querySelector('.asst-dd-item[data-checked="1"]:not([disabled])') || focusables(menu)[0] || null);
|
|
580
|
+
if (keep) list.scrollTop = scrollBefore;
|
|
510
581
|
};
|
|
511
582
|
return { el: menu, list, close: closeMenu, reposition: place, update };
|
|
512
583
|
}
|