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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
import { isBindingToken } from './ocp-v2-binding.js';
|
|
6
|
+
import { knownDescendant, requestOwner } from './ocp-v2-rehydrate.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* The compose box shows the outgoing prompt at once as a local message. The
|
|
@@ -115,3 +116,403 @@ export function settleFailedSend(turn, parked, { item, error = '' } = {}) {
|
|
|
115
116
|
const kept = !!(turn.sessionId && item && parked?.park(turn.sessionId, { item, error }));
|
|
116
117
|
return { here: false, parked: kept };
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
// ── How long a turn may take ────────────────────────────────────────────────
|
|
121
|
+
// A turn can wait on the user for as long as they take (a permission, a
|
|
122
|
+
// question): the clock that gives up on a turn nobody hears from again counts
|
|
123
|
+
// only the time it works.
|
|
124
|
+
|
|
125
|
+
/** The working time a turn's send waits for its answer (message:send, command:run, an async turn's idle). */
|
|
126
|
+
export const TURN_LIMIT_MS = 30 * 60 * 1000;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Runs `onExpire` once `ms` of unpaused time went by. `paused()` says whether
|
|
130
|
+
* the turn waits on the user now; `subscribe(fn)` calls `fn` whenever that may
|
|
131
|
+
* have changed and returns an unsubscribe. `cancel()` stops it for good.
|
|
132
|
+
*/
|
|
133
|
+
export function createTurnClock({
|
|
134
|
+
ms, paused = () => false, subscribe = () => () => {}, onExpire = () => {},
|
|
135
|
+
now = Date.now, setTimer = setTimeout, clearTimer = clearTimeout,
|
|
136
|
+
} = {}) {
|
|
137
|
+
let left = Math.max(0, Number(ms) || 0);
|
|
138
|
+
let since = 0;
|
|
139
|
+
let timer = null;
|
|
140
|
+
let done = false;
|
|
141
|
+
let unsubscribe = () => {};
|
|
142
|
+
const finish = () => {
|
|
143
|
+
done = true;
|
|
144
|
+
timer = null;
|
|
145
|
+
try { unsubscribe(); } catch { /* gone */ }
|
|
146
|
+
};
|
|
147
|
+
const run = () => {
|
|
148
|
+
if (done || timer) return;
|
|
149
|
+
since = now();
|
|
150
|
+
timer = setTimer(() => { finish(); onExpire(); }, left);
|
|
151
|
+
};
|
|
152
|
+
const hold = () => {
|
|
153
|
+
if (!timer) return;
|
|
154
|
+
clearTimer(timer);
|
|
155
|
+
timer = null;
|
|
156
|
+
left = Math.max(0, left - (now() - since));
|
|
157
|
+
};
|
|
158
|
+
const sync = () => {
|
|
159
|
+
if (done) return;
|
|
160
|
+
if (paused()) hold(); else run();
|
|
161
|
+
};
|
|
162
|
+
unsubscribe = subscribe(sync) || (() => {});
|
|
163
|
+
sync();
|
|
164
|
+
return {
|
|
165
|
+
cancel() {
|
|
166
|
+
if (done) return;
|
|
167
|
+
if (timer) clearTimer(timer);
|
|
168
|
+
finish();
|
|
169
|
+
},
|
|
170
|
+
/** The working time left (ms). */
|
|
171
|
+
left: () => (timer ? Math.max(0, left - (now() - since)) : left),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const ASKED = new Set(['permission.asked', 'question.asked']);
|
|
176
|
+
const ANSWERED = new Set(['permission.replied', 'question.replied', 'question.rejected']);
|
|
177
|
+
const askIdOf = (ev) => String(ev?.requestID || ev?.id || ev?.permissionID || '');
|
|
178
|
+
const askKindOf = (eventType) => (String(eventType).startsWith('permission.') ? 'permission' : 'question');
|
|
179
|
+
|
|
180
|
+
/** The two lists a session's open requests are read from (permission:list, question:list). */
|
|
181
|
+
export const ASK_KINDS = Object.freeze(['permission', 'question']);
|
|
182
|
+
/** How often a list is read before the panel gives up on it, the first read included. */
|
|
183
|
+
export const ASK_READ_ATTEMPTS = 3;
|
|
184
|
+
/** The waits before the second and the third read of a list. */
|
|
185
|
+
export const ASK_RETRY_MS = Object.freeze([1000, 4000]);
|
|
186
|
+
const LIST_STATES = new Set(['ok', 'failed', 'unsupported', 'unresolved']);
|
|
187
|
+
|
|
188
|
+
// One event applied to the open requests (request id → { owner, kind, stale }); true when what waits changed.
|
|
189
|
+
function applyAskEvent(open, eventType, ev) {
|
|
190
|
+
if (ASKED.has(eventType)) {
|
|
191
|
+
const id = askIdOf(ev);
|
|
192
|
+
const owner = requestOwner(ev);
|
|
193
|
+
if (!id || !owner) return false;
|
|
194
|
+
const known = open.get(id);
|
|
195
|
+
open.set(id, { owner, kind: askKindOf(eventType), stale: false });
|
|
196
|
+
return known?.owner !== owner;
|
|
197
|
+
}
|
|
198
|
+
if (ANSWERED.has(eventType)) return open.delete(askIdOf(ev));
|
|
199
|
+
if (eventType === 'session.idle' || eventType === 'session.error') {
|
|
200
|
+
const sid = String(ev?.sessionID || ev?.sessionId || '');
|
|
201
|
+
let gone = false;
|
|
202
|
+
for (const [id, entry] of open) if (entry.owner === sid) { open.delete(id); gone = true; }
|
|
203
|
+
return gone;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The requests that wait on the user, by the session that raised them, from
|
|
210
|
+
* the panel's event stream (every session's events reach it): asked by
|
|
211
|
+
* permission.asked / question.asked, gone with their answer, and gone with the
|
|
212
|
+
* turn (session.idle, session.error). `waits(sessionId)` is true while that
|
|
213
|
+
* session, or a sub-agent below it (`parentOf`: child → parent), has one.
|
|
214
|
+
*
|
|
215
|
+
* What the stream cannot tell (what was asked before the page loaded, or while
|
|
216
|
+
* the socket was down) is read from the server, list by list: each session's
|
|
217
|
+
* permissions and its questions (readOpenAsks). `read(want, { attempt, partial })`
|
|
218
|
+
* resolves `{ lists }`, each `{ sessionId, kind, state, requests }`; `want` is
|
|
219
|
+
* null for every session a panel is bound to, else `[{ sessionId, kinds }]`.
|
|
220
|
+
* • A list that was read (state ok, or unresolved: read, but a request in it
|
|
221
|
+
* is of a session whose place could not be established) replaces what was
|
|
222
|
+
* known of its kind for its session and the sub-agents known below it, also
|
|
223
|
+
* when it came back empty. Events that arrived while it was read are applied
|
|
224
|
+
* again on top.
|
|
225
|
+
* • A session whose list failed or is unresolved keeps waiting (no clock of it
|
|
226
|
+
* counts) and has that list read again, ASK_READ_ATTEMPTS times in all. Then,
|
|
227
|
+
* or at once for a list the server cannot give (unsupported), the panel gives
|
|
228
|
+
* up on it: what was known of it from before the outage goes, its clocks
|
|
229
|
+
* count again, and the rebuild resolves `{ gaveUp: [{ sessionId, kinds }] }`
|
|
230
|
+
* so that the panel says so. Nothing could be read at all: `{ unread: true }`.
|
|
231
|
+
* `hold()` (the socket closed): every session counts as waiting until the first
|
|
232
|
+
* read of the `rebuild(read)` that follows, and what was known turns into
|
|
233
|
+
* unconfirmed, to be confirmed or dropped by the lists. A newer `hold()` makes
|
|
234
|
+
* an older rebuild void (it resolves null).
|
|
235
|
+
* `expect(sessionIds)` + `rebuild(read, { sessions })` read only those sessions
|
|
236
|
+
* (a page load, a panel bound to another session): they wait from `expect` until
|
|
237
|
+
* their lists are in, and no other session is held.
|
|
238
|
+
*/
|
|
239
|
+
export function createAskTracker({ parentOf = new Map(), attempts = ASK_READ_ATTEMPTS, retryMs = ASK_RETRY_MS, setTimer = setTimeout } = {}) {
|
|
240
|
+
let open = new Map(); // request id → { owner, kind, stale }
|
|
241
|
+
let held = false; // the socket is down, or the first read after it is out: everything waits
|
|
242
|
+
const pending = new Map(); // session → the kinds whose list is still to be read (it waits meanwhile)
|
|
243
|
+
let generation = 0;
|
|
244
|
+
let replay = null; // the events noted while a read is out
|
|
245
|
+
const listeners = new Set();
|
|
246
|
+
const changed = () => {
|
|
247
|
+
for (const fn of [...listeners]) {
|
|
248
|
+
try { fn(); } catch (e) { console.warn('[ocp-v2-send] ask listener failed', e); }
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const under = (owner, root) => owner === root || knownDescendant(owner, root, parentOf);
|
|
252
|
+
const expect = (sessionId, kinds) => {
|
|
253
|
+
let set = pending.get(sessionId);
|
|
254
|
+
if (!set) pending.set(sessionId, (set = new Set()));
|
|
255
|
+
for (const kind of kinds) set.add(kind);
|
|
256
|
+
};
|
|
257
|
+
const settle = (sessionId, kind) => {
|
|
258
|
+
const set = pending.get(sessionId);
|
|
259
|
+
if (!set) return;
|
|
260
|
+
set.delete(kind);
|
|
261
|
+
if (!set.size) pending.delete(sessionId);
|
|
262
|
+
};
|
|
263
|
+
// An entry still to be confirmed by a list that will be read again.
|
|
264
|
+
const awaited = (entry) => {
|
|
265
|
+
for (const [root, kinds] of pending) if (kinds.has(entry.kind) && under(entry.owner, root)) return true;
|
|
266
|
+
return false;
|
|
267
|
+
};
|
|
268
|
+
const sleep = (ms) => new Promise((done) => { setTimer(done, Math.max(0, Number(ms) || 0)); });
|
|
269
|
+
const delayAfter = (attempt) => (Array.isArray(retryMs) ? retryMs[Math.min(attempt, retryMs.length) - 1] : retryMs) || 0;
|
|
270
|
+
const wantedOf = (sessions) => (Array.isArray(sessions)
|
|
271
|
+
? [...new Set(sessions.map((id) => String(id || '')).filter(Boolean))].map((sessionId) => ({ sessionId, kinds: [...ASK_KINDS] }))
|
|
272
|
+
: null);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
note(eventType, ev) {
|
|
276
|
+
replay?.push([eventType, ev]);
|
|
277
|
+
if (applyAskEvent(open, eventType, ev) && !held) changed();
|
|
278
|
+
},
|
|
279
|
+
waits(sessionId) {
|
|
280
|
+
const sid = String(sessionId || '');
|
|
281
|
+
if (!sid) return false;
|
|
282
|
+
if (held) return true;
|
|
283
|
+
for (const [root, kinds] of pending) if (kinds.size && under(sid, root)) return true;
|
|
284
|
+
for (const { owner } of open.values()) if (owner === sid || knownDescendant(owner, sid, parentOf)) return true;
|
|
285
|
+
return false;
|
|
286
|
+
},
|
|
287
|
+
hold() {
|
|
288
|
+
generation += 1;
|
|
289
|
+
replay = null;
|
|
290
|
+
pending.clear();
|
|
291
|
+
for (const entry of open.values()) entry.stale = true;
|
|
292
|
+
if (held) return;
|
|
293
|
+
held = true;
|
|
294
|
+
changed();
|
|
295
|
+
},
|
|
296
|
+
/** These sessions wait from now until their lists are read (rebuild with `sessions`). */
|
|
297
|
+
expect(sessionIds) {
|
|
298
|
+
const wanted = wantedOf(sessionIds) || [];
|
|
299
|
+
for (const { sessionId, kinds } of wanted) expect(sessionId, kinds);
|
|
300
|
+
if (wanted.length && !held) changed();
|
|
301
|
+
},
|
|
302
|
+
/** These sessions wait for no read of their own (their parent's lists hold their requests). */
|
|
303
|
+
release(sessionIds) {
|
|
304
|
+
let gone = false;
|
|
305
|
+
for (const { sessionId } of wantedOf(sessionIds) || []) gone = pending.delete(sessionId) || gone;
|
|
306
|
+
if (gone && !held) changed();
|
|
307
|
+
},
|
|
308
|
+
async rebuild(read, { sessions = null } = {}) {
|
|
309
|
+
const mine = generation;
|
|
310
|
+
const partial = Array.isArray(sessions);
|
|
311
|
+
let want = wantedOf(sessions);
|
|
312
|
+
if (partial) {
|
|
313
|
+
if (!want.length) return { gaveUp: [] };
|
|
314
|
+
for (const { sessionId, kinds } of want) expect(sessionId, kinds);
|
|
315
|
+
}
|
|
316
|
+
const gaveUp = new Map(); // session → kinds given up on
|
|
317
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
318
|
+
replay = [];
|
|
319
|
+
let snapshot = null;
|
|
320
|
+
try { snapshot = await read(want, { attempt, partial }); } catch { snapshot = null; }
|
|
321
|
+
if (mine !== generation) return null;
|
|
322
|
+
const events = replay || [];
|
|
323
|
+
replay = null;
|
|
324
|
+
const last = attempt >= attempts;
|
|
325
|
+
const lists = Array.isArray(snapshot?.lists) ? snapshot.lists : null;
|
|
326
|
+
if (!lists && !want) {
|
|
327
|
+
// Nothing came back, and nothing says which sessions were read.
|
|
328
|
+
if (!last) {
|
|
329
|
+
await sleep(delayAfter(attempt));
|
|
330
|
+
if (mine !== generation) return null;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
for (const [id, entry] of open) if (entry.stale) open.delete(id);
|
|
334
|
+
held = false;
|
|
335
|
+
changed();
|
|
336
|
+
return { gaveUp: [], unread: true };
|
|
337
|
+
}
|
|
338
|
+
const results = [];
|
|
339
|
+
for (const list of lists || []) {
|
|
340
|
+
const sessionId = String(list?.sessionId || '');
|
|
341
|
+
if (!sessionId || !ASK_KINDS.includes(list?.kind)) continue;
|
|
342
|
+
results.push({
|
|
343
|
+
sessionId,
|
|
344
|
+
kind: list.kind,
|
|
345
|
+
state: LIST_STATES.has(list.state) ? list.state : 'failed',
|
|
346
|
+
requests: Array.isArray(list.requests) ? list.requests : [],
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
for (const { sessionId, kinds } of want || []) {
|
|
350
|
+
for (const kind of kinds) {
|
|
351
|
+
if (results.some((list) => list.sessionId === sessionId && list.kind === kind)) continue;
|
|
352
|
+
// Not in the answer: nothing came back (failed), or no panel is bound to it any more (not asked).
|
|
353
|
+
if (lists) settle(sessionId, kind);
|
|
354
|
+
else results.push({ sessionId, kind, state: 'failed', requests: [] });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const next = new Map(open);
|
|
358
|
+
for (const list of results) {
|
|
359
|
+
if (list.state !== 'ok' && list.state !== 'unresolved') continue;
|
|
360
|
+
const listed = new Map();
|
|
361
|
+
for (const req of list.requests) {
|
|
362
|
+
const id = askIdOf(req);
|
|
363
|
+
const owner = requestOwner(req);
|
|
364
|
+
if (id && owner) listed.set(id, owner);
|
|
365
|
+
}
|
|
366
|
+
// What the list does not hold is no longer open, for its session and the sub-agents known below it.
|
|
367
|
+
for (const [id, entry] of next) {
|
|
368
|
+
if (entry.kind === list.kind && under(entry.owner, list.sessionId) && !listed.has(id)) next.delete(id);
|
|
369
|
+
}
|
|
370
|
+
for (const [id, owner] of listed) next.set(id, { owner, kind: list.kind, stale: false });
|
|
371
|
+
}
|
|
372
|
+
const again = new Map(); // session → kinds to read again
|
|
373
|
+
for (const list of results) {
|
|
374
|
+
if (list.state === 'ok') { settle(list.sessionId, list.kind); continue; }
|
|
375
|
+
if (list.state === 'unsupported' || last) {
|
|
376
|
+
settle(list.sessionId, list.kind);
|
|
377
|
+
if (!gaveUp.has(list.sessionId)) gaveUp.set(list.sessionId, new Set());
|
|
378
|
+
gaveUp.get(list.sessionId).add(list.kind);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
expect(list.sessionId, [list.kind]);
|
|
382
|
+
if (!again.has(list.sessionId)) again.set(list.sessionId, new Set());
|
|
383
|
+
again.get(list.sessionId).add(list.kind);
|
|
384
|
+
}
|
|
385
|
+
for (const [eventType, ev] of events) applyAskEvent(next, eventType, ev);
|
|
386
|
+
// Known from before the outage and confirmed by no list that is still to come: it goes.
|
|
387
|
+
for (const [id, entry] of next) if (entry.stale && !awaited(entry)) next.delete(id);
|
|
388
|
+
open = next;
|
|
389
|
+
if (!partial) held = false;
|
|
390
|
+
changed();
|
|
391
|
+
if (!again.size) {
|
|
392
|
+
return { gaveUp: [...gaveUp].map(([sessionId, kinds]) => ({ sessionId, kinds: ASK_KINDS.filter((kind) => kinds.has(kind)) })) };
|
|
393
|
+
}
|
|
394
|
+
await sleep(delayAfter(attempt));
|
|
395
|
+
if (mine !== generation) return null;
|
|
396
|
+
want = [...again].map(([sessionId, kinds]) => ({ sessionId, kinds: ASK_KINDS.filter((kind) => kinds.has(kind)) }));
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
clear() {
|
|
400
|
+
if (!open.size) return;
|
|
401
|
+
open.clear();
|
|
402
|
+
changed();
|
|
403
|
+
},
|
|
404
|
+
subscribe(fn) {
|
|
405
|
+
listeners.add(fn);
|
|
406
|
+
return () => listeners.delete(fn);
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* The sentence a panel shows when the open requests of its session could not
|
|
413
|
+
* be read (the tracker gave up on `kinds`): its turn's clock counts again.
|
|
414
|
+
*/
|
|
415
|
+
export function unreadAsksNotice(kinds = ASK_KINDS) {
|
|
416
|
+
const set = new Set(Array.isArray(kinds) && kinds.length ? kinds : ASK_KINDS);
|
|
417
|
+
const what = set.has('permission') && set.has('question') ? 'permissions and questions'
|
|
418
|
+
: set.has('permission') ? 'permissions' : 'questions';
|
|
419
|
+
return `Could not read this session's open ${what} from OpenCode: its 30-minute turn limit runs as if none were waiting.`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* What waits on the user in the sessions panels are bound to, read from the
|
|
424
|
+
* server: for each `{ sessionId, cwd, kinds }` (both kinds unless it says), the
|
|
425
|
+
* open permissions (`api.permissionList`) and questions (`api.questionList`) of
|
|
426
|
+
* the serve that runs that session, with its sub-agents' (they run on the same
|
|
427
|
+
* serve). Each list comes back with its `state`: ok; failed; unsupported (the
|
|
428
|
+
* server has no such request type); or unresolved: read, but it holds a request
|
|
429
|
+
* of a session whose place under this one could not be established.
|
|
430
|
+
* A request of a session that is neither one of these nor known below one has
|
|
431
|
+
* its place read (`ancestry(owner, sessionId)`: true, false, or null when that
|
|
432
|
+
* could not be read; it remembers what it learns), so that a sub-agent spawned
|
|
433
|
+
* while nobody watched holds its parent's clock too: at most `maxLookups`
|
|
434
|
+
* lookups within `lookupMs`, each given only the time that is left (a lookup
|
|
435
|
+
* still waiting on the server then counts as not read). `isDescendant`, a
|
|
436
|
+
* yes / no answer, is accepted in its place.
|
|
437
|
+
* Resolves `{ lists, requests, complete }`: `complete` when every list is ok.
|
|
438
|
+
*/
|
|
439
|
+
export async function readOpenAsks(api, sessions, {
|
|
440
|
+
ancestry = null, isDescendant = null, known = () => false,
|
|
441
|
+
maxLookups = 20, lookupMs = 5000, now = Date.now, setTimer = setTimeout, clearTimer = clearTimeout,
|
|
442
|
+
} = {}) {
|
|
443
|
+
const listOf = (res) => {
|
|
444
|
+
const data = res?.data;
|
|
445
|
+
if (Array.isArray(data)) return data;
|
|
446
|
+
if (Array.isArray(data?.questions)) return data.questions;
|
|
447
|
+
if (Array.isArray(data?.data)) return data.data;
|
|
448
|
+
return null;
|
|
449
|
+
};
|
|
450
|
+
const failed = (res) => !res || res.ok === false || res.error != null || (typeof res.status === 'number' && res.status >= 400);
|
|
451
|
+
const readList = async (call) => {
|
|
452
|
+
let res;
|
|
453
|
+
try { res = await call(); } catch { res = null; }
|
|
454
|
+
if (res?.unsupported) return { state: 'unsupported', requests: [] };
|
|
455
|
+
const list = failed(res) ? null : listOf(res);
|
|
456
|
+
if (!list) return { state: 'failed', requests: [] };
|
|
457
|
+
return { state: 'ok', requests: list.filter((req) => askIdOf(req) && requestOwner(req)) };
|
|
458
|
+
};
|
|
459
|
+
const lists = [];
|
|
460
|
+
for (const { sessionId, cwd, kinds } of Array.isArray(sessions) ? sessions : []) {
|
|
461
|
+
if (!sessionId) continue;
|
|
462
|
+
const root = String(sessionId);
|
|
463
|
+
const wanted = ASK_KINDS.filter((kind) => !Array.isArray(kinds) || kinds.includes(kind));
|
|
464
|
+
const read = await Promise.all(wanted.map((kind) => readList(() => (kind === 'permission'
|
|
465
|
+
? api.permissionList({ sessionId, cwd })
|
|
466
|
+
: api.questionList({ sessionId, cwd })))));
|
|
467
|
+
wanted.forEach((kind, i) => lists.push({ sessionId: root, kind, ...read[i] }));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Sub-agents spawned while nobody watched: their place under a session that was read, a few at most and for
|
|
471
|
+
// a few seconds at most (the clocks stay held meanwhile).
|
|
472
|
+
const lookup = typeof ancestry === 'function' ? ancestry
|
|
473
|
+
: typeof isDescendant === 'function' ? async (owner, root) => (await isDescendant(owner, root)) === true
|
|
474
|
+
: null;
|
|
475
|
+
const roots = [...new Set(lists.map((list) => list.sessionId))];
|
|
476
|
+
const until = now() + lookupMs;
|
|
477
|
+
let lookups = 0;
|
|
478
|
+
const place = async (owner, root) => {
|
|
479
|
+
if (!lookup) return false;
|
|
480
|
+
const left = until - now();
|
|
481
|
+
if (lookups >= maxLookups || left <= 0) return null;
|
|
482
|
+
lookups += 1;
|
|
483
|
+
let timer = null;
|
|
484
|
+
const late = new Promise((done) => { timer = setTimer(() => done(null), left); });
|
|
485
|
+
try {
|
|
486
|
+
const answer = await Promise.race([Promise.resolve().then(() => lookup(owner, root)).catch(() => null), late]);
|
|
487
|
+
return answer === true ? true : answer === false ? false : null;
|
|
488
|
+
} finally {
|
|
489
|
+
clearTimer(timer);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
const unplaced = new Map(); // owner → the sessions whose lists hold a request of it
|
|
493
|
+
for (const list of lists) {
|
|
494
|
+
if (list.state !== 'ok') continue;
|
|
495
|
+
for (const req of list.requests) {
|
|
496
|
+
const owner = requestOwner(req);
|
|
497
|
+
if (roots.includes(owner) || roots.some((root) => known(owner, root))) continue;
|
|
498
|
+
if (!unplaced.has(owner)) unplaced.set(owner, new Set());
|
|
499
|
+
unplaced.get(owner).add(list.sessionId);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
for (const [owner, under] of unplaced) {
|
|
503
|
+
const unknown = [];
|
|
504
|
+
let placed = false;
|
|
505
|
+
for (const root of under) {
|
|
506
|
+
const answer = await place(owner, root);
|
|
507
|
+
if (answer === true) { placed = true; break; }
|
|
508
|
+
if (answer === null) unknown.push(root);
|
|
509
|
+
}
|
|
510
|
+
if (placed) continue;
|
|
511
|
+
for (const list of lists) {
|
|
512
|
+
if (list.state === 'ok' && unknown.includes(list.sessionId) && list.requests.some((req) => requestOwner(req) === owner)) list.state = 'unresolved';
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const requests = new Map();
|
|
516
|
+
for (const list of lists) for (const req of list.requests) requests.set(askIdOf(req), req);
|
|
517
|
+
return { lists, requests: [...requests.values()], complete: lists.every((list) => list.state === 'ok') };
|
|
518
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// instance for back-compat with shared bindings.
|
|
7
7
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
|
-
import { api, supports, capabilities, onEvent } from './ocp-v2-ws.js';
|
|
9
|
+
import { api, supports, capabilities, onEvent, sessionWaitsOnUser, onAsksChanged } from './ocp-v2-ws.js';
|
|
10
10
|
import { getDefaultStore, agentForMode } from './ocp-v2-state.js';
|
|
11
11
|
import { mountModelPicker } from './ocp-v2-modelpicker.js';
|
|
12
12
|
import { mountVariantPicker } from './ocp-v2-variantpicker.js';
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './ocp-v2-plan.js';
|
|
31
31
|
import {
|
|
32
32
|
trackOptimisticMessage, beginTurn, currentTurn, endTurn, settleFailedSend, TRANSCRIPT_SAFETY_POLL_MS,
|
|
33
|
+
createTurnClock, TURN_LIMIT_MS,
|
|
33
34
|
} from './ocp-v2-send-logic.js';
|
|
34
35
|
import { hydrateTranscript } from './ocp-v2-rehydrate.js';
|
|
35
36
|
import { captureBinding, trackBinding, isBindingToken, searchOnBinding } from './ocp-v2-binding.js';
|
|
@@ -1512,7 +1513,7 @@ export function mountCompose(rootEl, store = getDefaultStore(), opts = {}) {
|
|
|
1512
1513
|
async function waitForAsyncTurn({ stopPolling, drainOnce, isPlanTurn, planTurn, turn, run }) {
|
|
1513
1514
|
let done = false;
|
|
1514
1515
|
let cleanup = null;
|
|
1515
|
-
let
|
|
1516
|
+
let clock = null;
|
|
1516
1517
|
|
|
1517
1518
|
await new Promise((resolve) => {
|
|
1518
1519
|
// The composer was unmounted: nobody waits for the end of this turn any
|
|
@@ -1521,7 +1522,7 @@ export function mountCompose(rootEl, store = getDefaultStore(), opts = {}) {
|
|
|
1521
1522
|
if (done) return;
|
|
1522
1523
|
done = true;
|
|
1523
1524
|
_turnWatches.delete(abandon);
|
|
1524
|
-
|
|
1525
|
+
clock?.cancel();
|
|
1525
1526
|
stopPolling();
|
|
1526
1527
|
if (unsubInner) unsubInner();
|
|
1527
1528
|
resolve();
|
|
@@ -1530,7 +1531,7 @@ export function mountCompose(rootEl, store = getDefaultStore(), opts = {}) {
|
|
|
1530
1531
|
if (done) return;
|
|
1531
1532
|
done = true;
|
|
1532
1533
|
_turnWatches.delete(abandon);
|
|
1533
|
-
|
|
1534
|
+
clock?.cancel();
|
|
1534
1535
|
stopPolling();
|
|
1535
1536
|
if (unsubInner) unsubInner();
|
|
1536
1537
|
await drainOnce(true);
|
|
@@ -1544,11 +1545,17 @@ export function mountCompose(rootEl, store = getDefaultStore(), opts = {}) {
|
|
|
1544
1545
|
const unsubInner = store.subscribe((event, state) => {
|
|
1545
1546
|
if ((event?.type === 'running:set' && !state.running) || !turn.isCurrent()) cleanup();
|
|
1546
1547
|
});
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1548
|
+
// Working time only: the clock stops while the session waits on the user (a permission, a question).
|
|
1549
|
+
clock = createTurnClock({
|
|
1550
|
+
ms: TURN_LIMIT_MS,
|
|
1551
|
+
paused: () => sessionWaitsOnUser(turn.sessionId),
|
|
1552
|
+
subscribe: onAsksChanged,
|
|
1553
|
+
onExpire: () => {
|
|
1554
|
+
if (run.isCurrent()) store.pushError({ message: 'OpenCode turn timed out waiting for session.idle.' });
|
|
1555
|
+
endTurn(store, run);
|
|
1556
|
+
cleanup();
|
|
1557
|
+
},
|
|
1558
|
+
});
|
|
1552
1559
|
_turnWatches.add(abandon);
|
|
1553
1560
|
if (!store.getState().running) cleanup();
|
|
1554
1561
|
});
|
|
@@ -287,6 +287,66 @@ export function createStoppedShares({ load = () => null, save = () => {}, max =
|
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
// ── Tabs that are not on screen ─────────────────────────────────────────────
|
|
291
|
+
// The panel shows one session; its other tabs go on running their turns in
|
|
292
|
+
// OpenCode. What each of them does comes from OpenCode's own run events (every
|
|
293
|
+
// session's reach every window) and from asking it (session:status) after a
|
|
294
|
+
// reload or a reconnect. Their tray pills read it; the session on screen reads
|
|
295
|
+
// its store.
|
|
296
|
+
|
|
297
|
+
/** `{ sessionId, busy }` for an event about a session's run, else null. */
|
|
298
|
+
export function sessionBusyFromEvent(eventType, ev) {
|
|
299
|
+
const sessionId = String(ev?.sessionID || ev?.sessionId || '');
|
|
300
|
+
if (!sessionId) return null;
|
|
301
|
+
if (eventType === 'session.status') {
|
|
302
|
+
const type = ev?.status?.type;
|
|
303
|
+
return { sessionId, busy: type === 'busy' || type === 'retry' };
|
|
304
|
+
}
|
|
305
|
+
if (eventType === 'session.idle' || eventType === 'session.error') return { sessionId, busy: false };
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** busy (true) / idle (false) from a session:status reply; null when it did not say. */
|
|
310
|
+
export function sessionBusyFromStatus(res) {
|
|
311
|
+
const type = res?.data?.status?.type;
|
|
312
|
+
return typeof type === 'string' && type ? (type === 'busy' || type === 'retry') : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* What an automation run's tab shows of its turn: 'working' while a turn runs,
|
|
317
|
+
* 'waiting' while the run waits on the user (a permission, a question: the
|
|
318
|
+
* dispatcher's awaiting_permission; or a turn that ended asking: an idle warm
|
|
319
|
+
* worker whose outcome, the result its turn reported, is needs_input), 'idle'
|
|
320
|
+
* between turns (a warm worker done with its task waits for a follow-up) and
|
|
321
|
+
* null once the run ended (its tab reads the session like any other). A run
|
|
322
|
+
* that reports no turn phase (a classic loop) works for as long as it runs:
|
|
323
|
+
* each iteration starts the next.
|
|
324
|
+
*/
|
|
325
|
+
export function automationTurnPhase(run) {
|
|
326
|
+
if (run?.status !== 'starting' && run?.status !== 'running') return null;
|
|
327
|
+
switch (run.turnState) {
|
|
328
|
+
case 'awaiting_permission': case 'needs_input': return 'waiting';
|
|
329
|
+
case 'idle': return (run.outcome || run.lastResult?.status) === 'needs_input' ? 'waiting' : 'idle';
|
|
330
|
+
case 'terminal': return 'idle';
|
|
331
|
+
default: return 'working';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* A tab's tray pill: 'waiting' while its session waits on the user, 'working'
|
|
337
|
+
* while a turn of it runs, else 'idle'. A live automation run's own phase
|
|
338
|
+
* (`automation`, automationTurnPhase) decides: an idle warm worker rests,
|
|
339
|
+
* whatever its run (and the store of the session on screen, which follows the
|
|
340
|
+
* run) says; nobody else can start a turn in it meanwhile (canSend). Otherwise
|
|
341
|
+
* the session on screen reads its store (`running`) and another tab what
|
|
342
|
+
* OpenCode said of it (`busy`).
|
|
343
|
+
*/
|
|
344
|
+
export function tabPillState({ sessionId, activeSessionId, running = false, busy = false, automation = null } = {}) {
|
|
345
|
+
if (automation === 'working' || automation === 'waiting' || automation === 'idle') return automation;
|
|
346
|
+
const turn = sessionId && sessionId === activeSessionId ? !!running : !!busy;
|
|
347
|
+
return turn ? 'working' : 'idle';
|
|
348
|
+
}
|
|
349
|
+
|
|
290
350
|
// ── Deleting a message ──────────────────────────────────────────────────────
|
|
291
351
|
|
|
292
352
|
/** The question before a message is deleted: which one, and that it is final. */
|
|
@@ -2737,11 +2737,11 @@ const CHILD_CSS = `
|
|
|
2737
2737
|
}
|
|
2738
2738
|
.ocpv2-panel-child.sp-floating > .ocpv2-header { font-size: var(--fw-header-control-size); }
|
|
2739
2739
|
/* The sub-agent's name keeps a readable 88px (or its own width if shorter):
|
|
2740
|
-
the
|
|
2740
|
+
the parents between the way back and the nearest parent give way first. */
|
|
2741
2741
|
.ocpv2-panel-child .ocpv2-session-btn { flex: 1 0 88px; }
|
|
2742
|
-
/* The chain of parents: at most 40% of the row
|
|
2743
|
-
|
|
2744
|
-
|
|
2742
|
+
/* The chain of parents: at most 40% of the row. Its first crumb is the way
|
|
2743
|
+
back to the main panel (.ocpv2-child-parent-link) and is on screen at every
|
|
2744
|
+
width; the crumbs after it give way before it does. */
|
|
2745
2745
|
.ocpv2-panel-child .ocpv2-child-crumbs {
|
|
2746
2746
|
display: inline-flex;
|
|
2747
2747
|
align-items: center;
|
|
@@ -2754,28 +2754,53 @@ const CHILD_CSS = `
|
|
|
2754
2754
|
margin-right: 6px;
|
|
2755
2755
|
}
|
|
2756
2756
|
.ocpv2-panel-child .ocpv2-child-crumbs:empty { display: none; }
|
|
2757
|
+
/* The nearest parent keeps its own width (at most 140px); the ones between
|
|
2758
|
+
it and the way back shrink first. */
|
|
2757
2759
|
.ocpv2-panel-child .ocpv2-child-crumbs > .ocpv2-child-crumb:last-child { flex-shrink: 0; }
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2760
|
+
.ocpv2-panel-child .ocpv2-child-crumbs > .ocpv2-child-crumb:not(:first-child):not(:last-child) { flex-shrink: 100; min-width: 0; }
|
|
2761
|
+
.ocpv2-panel-child .ocpv2-child-crumbs > .ocpv2-child-parent-link { flex-shrink: 1; }
|
|
2762
|
+
/* Narrow headers, in order: with less than 140px for the chain, the way back
|
|
2763
|
+
and the nearest parent show, truncated; with less than 64px, the way back is
|
|
2764
|
+
its arrow alone (a header control, named by its tooltip) and the name takes
|
|
2765
|
+
the rest; detached, when the name would get less than its 32px, Slide (which
|
|
2766
|
+
does what Minimize does here) and then "New session in main panel" give way.
|
|
2767
|
+
Room counted in controls (em) plus px: docked, the pencil and six buttons are
|
|
2768
|
+
7em with 18px of gaps; detached, the move handle makes 8em and the window's
|
|
2769
|
+
6px gaps 42px. Thresholds: the name's 88px + the chain's 6px margin + 140px
|
|
2770
|
+
or 64px + that row. Detached with the arrow alone, the move handle, the
|
|
2771
|
+
arrow, the pencil and six buttons are 9em with 48px of gaps and margins:
|
|
2772
|
+
+ 32px for the name, and one button (1em + 3px) less once Slide is gone. */
|
|
2764
2773
|
@container ocpv2-child-header (width < calc(7em + 252px)) {
|
|
2765
|
-
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs > :not(:last-child) { display: none; }
|
|
2766
|
-
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs > .ocpv2-child-crumb:last-child { flex-shrink: 1; min-width: 0; }
|
|
2774
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs > :not(:first-child):not(:last-child):not(:nth-last-child(2)) { display: none; }
|
|
2775
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs > .ocpv2-child-crumb:last-child:not(.ocpv2-child-parent-link) { flex-shrink: 1; min-width: 0; }
|
|
2767
2776
|
}
|
|
2768
2777
|
@container ocpv2-child-header (width < calc(7em + 176px)) {
|
|
2769
|
-
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs { display: none; }
|
|
2778
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs > :not(:first-child) { display: none; }
|
|
2779
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-crumbs { flex-shrink: 0; max-width: none; }
|
|
2770
2780
|
.ocpv2-panel-child:not(.sp-floating) .ocpv2-session-btn { flex: 1 1 0; }
|
|
2781
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-parent-label { display: none; }
|
|
2782
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-parent-link { width: 24px; height: 24px; min-width: 0; padding: 0; }
|
|
2783
|
+
.ocpv2-panel-child:not(.sp-floating) .ocpv2-child-parent-arrow { width: auto; height: auto; font-size: 13px; line-height: 1; }
|
|
2771
2784
|
}
|
|
2772
2785
|
@container ocpv2-child-header (width < calc(8em + 276px)) {
|
|
2773
|
-
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs > :not(:last-child) { display: none; }
|
|
2774
|
-
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs > .ocpv2-child-crumb:last-child { flex-shrink: 1; min-width: 0; }
|
|
2786
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs > :not(:first-child):not(:last-child):not(:nth-last-child(2)) { display: none; }
|
|
2787
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs > .ocpv2-child-crumb:last-child:not(.ocpv2-child-parent-link) { flex-shrink: 1; min-width: 0; }
|
|
2775
2788
|
}
|
|
2776
2789
|
@container ocpv2-child-header (width < calc(8em + 200px)) {
|
|
2777
|
-
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs { display: none; }
|
|
2790
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs > :not(:first-child) { display: none; }
|
|
2791
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-crumbs { flex-shrink: 0; max-width: none; }
|
|
2778
2792
|
.ocpv2-panel-child.sp-floating .ocpv2-session-btn { flex: 1 1 0; }
|
|
2793
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-parent-label { display: none; }
|
|
2794
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-parent-link {
|
|
2795
|
+
width: var(--fw-header-control-size); height: var(--fw-header-control-size); min-width: 0; padding: 0;
|
|
2796
|
+
}
|
|
2797
|
+
.ocpv2-panel-child.sp-floating .ocpv2-child-parent-arrow { width: auto; height: auto; font-size: 13px; line-height: 1; }
|
|
2798
|
+
}
|
|
2799
|
+
@container ocpv2-child-header (width < calc(9em + 80px)) {
|
|
2800
|
+
.ocpv2-panel-child.sp-floating .ocpv2-btn-slide { display: none; }
|
|
2801
|
+
}
|
|
2802
|
+
@container ocpv2-child-header (width < calc(8em + 77px)) {
|
|
2803
|
+
.ocpv2-panel-child.sp-floating .ocpv2-btn-new { display: none; }
|
|
2779
2804
|
}
|
|
2780
2805
|
.ocpv2-panel-child .ocpv2-child-crumb-sep {
|
|
2781
2806
|
flex-shrink: 0;
|
|
@@ -2785,6 +2810,7 @@ const CHILD_CSS = `
|
|
|
2785
2810
|
.ocpv2-panel-child .ocpv2-child-crumb {
|
|
2786
2811
|
display: inline-flex;
|
|
2787
2812
|
align-items: center;
|
|
2813
|
+
justify-content: center;
|
|
2788
2814
|
gap: 4px;
|
|
2789
2815
|
background: transparent;
|
|
2790
2816
|
border: 1px solid rgba(255,255,255,0.06);
|
|
@@ -2890,12 +2916,15 @@ const CHILD_CSS = `
|
|
|
2890
2916
|
color: #ffd591;
|
|
2891
2917
|
border-color: rgba(245, 165, 36, 0.55);
|
|
2892
2918
|
}
|
|
2893
|
-
/* Waiting for an answer: the one state that needs the user.
|
|
2894
|
-
.
|
|
2919
|
+
/* Waiting for an answer: the one state that needs the user. A sub-agent's pill, and a session tab's (an
|
|
2920
|
+
automation whose turn waits on a permission or a question). */
|
|
2921
|
+
.ocpv2-session-pill-child.ocpv2-pill-waiting,
|
|
2922
|
+
.ocpv2-session-pill.ocpv2-pill-waiting {
|
|
2895
2923
|
border-color: rgba(245, 165, 36, 0.85) !important;
|
|
2896
2924
|
animation: ocpv2-pill-waiting 1.6s ease-in-out infinite;
|
|
2897
2925
|
}
|
|
2898
|
-
.ocpv2-session-pill-child.ocpv2-pill-waiting .term-minimized-pill-icon
|
|
2926
|
+
.ocpv2-session-pill-child.ocpv2-pill-waiting .term-minimized-pill-icon,
|
|
2927
|
+
.ocpv2-session-pill.ocpv2-pill-waiting .term-minimized-pill-icon { color: #f5a524; }
|
|
2899
2928
|
@keyframes ocpv2-pill-waiting {
|
|
2900
2929
|
0%, 100% { box-shadow: 0 0 0 1px rgba(245, 165, 36, 0.25), 0 0 8px rgba(245, 165, 36, 0.2); }
|
|
2901
2930
|
50% { box-shadow: 0 0 0 1px rgba(245, 165, 36, 0.5), 0 0 16px rgba(245, 165, 36, 0.5); }
|