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
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — start at login, for an npm or Git install (Settings > App)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The server starts in the background when the user logs in, with no window
|
|
6
|
+
// and no terminal, through the same launcher the Start Server button uses
|
|
7
|
+
// (launcher.mjs → lib/start-launcher.js runLauncher, in its background mode:
|
|
8
|
+
// nothing happens when a server already answers or a start is under way, and
|
|
9
|
+
// the server gets the login shell's environment, so an nvm or Homebrew Node
|
|
10
|
+
// and the user's CLIs are found as in a terminal). Every path in the entry is
|
|
11
|
+
// absolute: the PATH a login starts programs with is not the user's.
|
|
12
|
+
//
|
|
13
|
+
// macOS ~/Library/LaunchAgents/ai.synabun.server.plist (RunAtLoad; the
|
|
14
|
+
// launcher's process group is left alone when it exits)
|
|
15
|
+
// Windows HKCU\...\Run "SynaBun Server": wscript.exe runs a small script of
|
|
16
|
+
// SynaBun's (DATA_HOME/runtime/login/synabun-hidden.vbs) that starts
|
|
17
|
+
// node with no window. A console program started from Run would
|
|
18
|
+
// show a console. A packaged entry (a GUI program) runs directly
|
|
19
|
+
// Linux ~/.config/autostart/synabun-server.desktop (XDG autostart)
|
|
20
|
+
//
|
|
21
|
+
// The entry itself is the setting: on while it is there, off when it is gone.
|
|
22
|
+
// It is read back from the system for every answer (inspect), and put back in
|
|
23
|
+
// place on every start of the server while it is on (heal: another Node, a
|
|
24
|
+
// moved install). One the user turned off in the system (disabled in Startup
|
|
25
|
+
// apps, in Login Items, or in the desktop's autostart settings) stays off and
|
|
26
|
+
// is reported as off.
|
|
27
|
+
//
|
|
28
|
+
// The SynaBun desktop app on the same data folder opens at login on its own
|
|
29
|
+
// (packaging/desktop/src/login-item.mjs, which names both apps' entries the
|
|
30
|
+
// same way: NPM_LOGIN, DESKTOP_LOGIN; a test keeps the two equal). Two starts
|
|
31
|
+
// at login would start two servers on one data folder: while the app's entry is
|
|
32
|
+
// there and on for this data folder (read from the system, desktopLoginOwner),
|
|
33
|
+
// this one is refused ('desktop-app'); turning the app's on removes this one.
|
|
34
|
+
//
|
|
35
|
+
// Pure: the file calls and the programs it runs are handed in (tests/login-start.test.mjs).
|
|
36
|
+
|
|
37
|
+
import { execFileSync } from 'node:child_process';
|
|
38
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
39
|
+
import { homedir, tmpdir } from 'node:os';
|
|
40
|
+
import { dirname, join as nativeJoin, posix, win32 } from 'node:path';
|
|
41
|
+
import { desktopExecArg, launcherOverrides, winQuote } from '../../lib/start-launcher.js';
|
|
42
|
+
|
|
43
|
+
/** The names of the entries (packaging/desktop/src/login-item.mjs NPM_LOGIN must be equal). */
|
|
44
|
+
export const NPM_LOGIN = Object.freeze({
|
|
45
|
+
macLabel: 'ai.synabun.server',
|
|
46
|
+
winName: 'SynaBun Server',
|
|
47
|
+
linuxFile: 'synabun-server.desktop',
|
|
48
|
+
});
|
|
49
|
+
/** What the launcher is started with at login (lib/start-launcher.js parseLauncherArgs reads them). */
|
|
50
|
+
export const LOGIN_ARGS = Object.freeze(['--via=login', '--background']);
|
|
51
|
+
/**
|
|
52
|
+
* The desktop app's own login entries (packaging/desktop/src/login-item.mjs DESKTOP_LOGIN must be equal): read from
|
|
53
|
+
* the system to know whether the app opens at login on this data folder, never from the app's settings file.
|
|
54
|
+
*/
|
|
55
|
+
export const DESKTOP_LOGIN = Object.freeze({
|
|
56
|
+
macLabel: 'ai.synabun.app.login',
|
|
57
|
+
winName: 'ai.synabun.app',
|
|
58
|
+
linuxFile: 'synabun-app.desktop',
|
|
59
|
+
env: 'SYNABUN_DESKTOP_LOGIN',
|
|
60
|
+
flag: '--synabun-login',
|
|
61
|
+
dataFlag: '--synabun-data-home=',
|
|
62
|
+
portFlag: '--synabun-port=',
|
|
63
|
+
});
|
|
64
|
+
export const HIDDEN_SCRIPT = 'synabun-hidden.vbs';
|
|
65
|
+
export const LINUX_MARKER = 'X-SynaBun-Login=server';
|
|
66
|
+
|
|
67
|
+
const RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
|
|
68
|
+
const APPROVED_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Runs its arguments as one command line with no window. ASCII only, no path
|
|
72
|
+
* in it: what it starts comes in as its arguments, which the registry holds as
|
|
73
|
+
* Unicode (Windows Script Host reads the file itself in the ANSI code page).
|
|
74
|
+
*/
|
|
75
|
+
export const HIDDEN_SCRIPT_SOURCE = [
|
|
76
|
+
"' SynaBun: runs its arguments as one command, with no window (start at login).",
|
|
77
|
+
"' Generated by SynaBun. Do not edit.",
|
|
78
|
+
'Option Explicit',
|
|
79
|
+
'Dim shell, command, i, a, n',
|
|
80
|
+
'command = ""',
|
|
81
|
+
'For i = 0 To WScript.Arguments.Count - 1',
|
|
82
|
+
' a = WScript.Arguments(i)',
|
|
83
|
+
" ' Backslashes before the closing quote are doubled, or the last one would escape it.",
|
|
84
|
+
' n = 0',
|
|
85
|
+
' Do While n < Len(a)',
|
|
86
|
+
' If Mid(a, Len(a) - n, 1) <> "\\" Then Exit Do',
|
|
87
|
+
' n = n + 1',
|
|
88
|
+
' Loop',
|
|
89
|
+
' command = command & " """ & a & String(n, "\\") & """"',
|
|
90
|
+
'Next',
|
|
91
|
+
'Set shell = CreateObject("WScript.Shell")',
|
|
92
|
+
'shell.Run Trim(command), 0, False',
|
|
93
|
+
'',
|
|
94
|
+
].join('\r\n');
|
|
95
|
+
|
|
96
|
+
function defaultRun(file, args, { timeout = 5000 } = {}) {
|
|
97
|
+
return execFileSync(file, args, { encoding: 'utf8', timeout, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const DEFAULT_FS = { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync };
|
|
101
|
+
|
|
102
|
+
const failure = (code, message) => Object.assign(new Error(message || code), { code });
|
|
103
|
+
|
|
104
|
+
function xmlEscape(value) {
|
|
105
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function systemTool(env, name) {
|
|
109
|
+
const root = env.SystemRoot || env.SYSTEMROOT || env.windir;
|
|
110
|
+
return root ? win32.join(root, 'System32', name) : name;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* One REG_SZ value of a `reg export` file, or null. `reg query` prints in the console's code page, which turns a
|
|
115
|
+
* non-ASCII folder name (C:\Users\João) into other characters; the export (UTF-16) keeps it.
|
|
116
|
+
*/
|
|
117
|
+
export function parseRegExport(text, name) {
|
|
118
|
+
const quoted = `"${String(name).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"=`;
|
|
119
|
+
for (const line of String(text || '').replace(/^\uFEFF/, '').split(/\r?\n/)) {
|
|
120
|
+
if (!line.toLowerCase().startsWith(quoted.toLowerCase())) continue;
|
|
121
|
+
const rest = line.slice(quoted.length);
|
|
122
|
+
if (!rest.startsWith('"') || !rest.endsWith('"')) return null;
|
|
123
|
+
return rest.slice(1, -1).replace(/\\(["\\])/g, '$1');
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function decodeRegFile(buffer) {
|
|
129
|
+
const bytes = Buffer.isBuffer(buffer) ? buffer : Buffer.from(String(buffer ?? ''), 'utf8');
|
|
130
|
+
return bytes[0] === 0xff && bytes[1] === 0xfe ? bytes.subarray(2).toString('utf16le') : bytes.toString('utf8');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** One value of a registry key, read through `reg export` (Unicode-safe), or null. */
|
|
134
|
+
export function readRegValue(key, name, { run = defaultRun, fs = DEFAULT_FS, env = process.env, tmp = tmpdir() } = {}) {
|
|
135
|
+
const file = nativeJoin(tmp, `synabun-login-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.reg`);
|
|
136
|
+
try {
|
|
137
|
+
run(systemTool(env, 'reg.exe'), ['export', key, file, '/y']);
|
|
138
|
+
return parseRegExport(decodeRegFile(fs.readFileSync(file)), name);
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
} finally {
|
|
142
|
+
try { fs.rmSync(file, { force: true }); } catch {}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The data of one value in `reg query` output, or null. */
|
|
147
|
+
export function parseRegQuery(output, name) {
|
|
148
|
+
for (const line of String(output || '').split(/\r?\n/)) {
|
|
149
|
+
const match = /^\s{2,}(.+?)\s{2,}(REG_[A-Z_]+)\s{2,}(.*)$/.exec(line) || /^\s{2,}(.+?)\s{2,}(REG_[A-Z_]+)\s*$/.exec(line);
|
|
150
|
+
if (match && match[1].toLowerCase() === String(name).toLowerCase()) return (match[3] ?? '').trim();
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Windows' Startup apps switch, from its REG_BINARY: an odd first byte is "disabled". null when unknown. */
|
|
156
|
+
export function startupApproved(hex) {
|
|
157
|
+
const first = /^[0-9a-f]{2}/i.exec(String(hex || '').trim());
|
|
158
|
+
if (!first) return null;
|
|
159
|
+
return (parseInt(first[0], 16) & 1) === 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The [Desktop Entry] keys of a .desktop file (the first value of a key counts). */
|
|
163
|
+
function desktopEntryKeys(text) {
|
|
164
|
+
const out = {};
|
|
165
|
+
let inGroup = false;
|
|
166
|
+
for (const raw of String(text || '').split(/\r?\n/)) {
|
|
167
|
+
const line = raw.trim();
|
|
168
|
+
if (!line || line.startsWith('#')) continue;
|
|
169
|
+
if (line.startsWith('[')) { inGroup = line === '[Desktop Entry]'; continue; }
|
|
170
|
+
const at = line.indexOf('=');
|
|
171
|
+
if (inGroup && at > 0 && !(line.slice(0, at).trim() in out)) out[line.slice(0, at).trim()] = line.slice(at + 1).trim();
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The entry this install would write now, as data: no side effects.
|
|
178
|
+
* { kind: 'launch-agent' | 'run-value' | 'autostart', path?, content?, key?, name?, value?, helper? }
|
|
179
|
+
* `entry` is a packaged build's entry executable (it brings its own Node): `<entry> launcher …`.
|
|
180
|
+
*/
|
|
181
|
+
export function buildLoginPlan({
|
|
182
|
+
platform = process.platform, nodePath = process.execPath, packageRoot, home = homedir(), env = process.env, dataHome, entry = null,
|
|
183
|
+
} = {}) {
|
|
184
|
+
if (!packageRoot) throw new Error('packageRoot is required');
|
|
185
|
+
const overrides = launcherOverrides({ env, home, platform });
|
|
186
|
+
const args = [...LOGIN_ARGS, ...(overrides.dataHome ? ['--data-home', overrides.dataHome] : []), ...(overrides.port ? ['--port', String(overrides.port)] : [])];
|
|
187
|
+
|
|
188
|
+
if (platform === 'win32') {
|
|
189
|
+
const launcher = entry || win32.join(packageRoot, 'launcher.mjs');
|
|
190
|
+
const program = entry ? [entry, 'launcher', ...args] : [nodePath, launcher, ...args];
|
|
191
|
+
const mark = winQuote(launcher);
|
|
192
|
+
if (entry) return { kind: 'run-value', key: RUN_KEY, name: NPM_LOGIN.winName, value: program.map(winQuote).join(' '), helper: null, mark, refuse: null };
|
|
193
|
+
const helperPath = win32.join(dataHome, 'runtime', 'login', HIDDEN_SCRIPT);
|
|
194
|
+
// wscript's own options stay bare (//B: no message boxes); everything else is one quoted argument each.
|
|
195
|
+
const value = [winQuote(systemTool(env, 'wscript.exe')), '//B', '//NoLogo', ...[helperPath, ...program].map(winQuote)].join(' ');
|
|
196
|
+
// WshShell.Run expands %NAME% in what it runs, and nothing escapes it: such a path cannot be started this way.
|
|
197
|
+
const refuse = [helperPath, ...program].some(word => word.includes('%')) ? 'bad-path' : null;
|
|
198
|
+
return { kind: 'run-value', key: RUN_KEY, name: NPM_LOGIN.winName, value, helper: { path: helperPath, content: HIDDEN_SCRIPT_SOURCE }, mark, refuse };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const launcher = entry || posix.join(packageRoot, 'launcher.mjs');
|
|
202
|
+
const program = entry ? [entry, 'launcher', ...args] : [nodePath, launcher, ...args];
|
|
203
|
+
if (platform === 'darwin') {
|
|
204
|
+
const shell = /^\/[\w./-]+$/.test(String(env.SHELL || '')) ? env.SHELL : null;
|
|
205
|
+
const content = [
|
|
206
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
207
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
208
|
+
'<plist version="1.0">',
|
|
209
|
+
'<dict>',
|
|
210
|
+
` <key>Label</key><string>${NPM_LOGIN.macLabel}</string>`,
|
|
211
|
+
' <key>ProgramArguments</key>',
|
|
212
|
+
' <array>',
|
|
213
|
+
...program.map(word => ` <string>${xmlEscape(word)}</string>`),
|
|
214
|
+
' </array>',
|
|
215
|
+
` <key>WorkingDirectory</key><string>${xmlEscape(packageRoot)}</string>`,
|
|
216
|
+
// The launcher reads the login shell's environment (PATH, nvm): it is told which shell that is.
|
|
217
|
+
...(shell ? [' <key>EnvironmentVariables</key>', ' <dict>', ` <key>SHELL</key><string>${xmlEscape(shell)}</string>`, ' </dict>'] : []),
|
|
218
|
+
' <key>RunAtLoad</key><true/>',
|
|
219
|
+
// The server outlives the launcher: launchd must not end it with the launcher's process group.
|
|
220
|
+
' <key>AbandonProcessGroup</key><true/>',
|
|
221
|
+
' <key>ProcessType</key><string>Interactive</string>',
|
|
222
|
+
' <key>StandardOutPath</key><string>/dev/null</string>',
|
|
223
|
+
' <key>StandardErrorPath</key><string>/dev/null</string>',
|
|
224
|
+
'</dict>',
|
|
225
|
+
'</plist>',
|
|
226
|
+
'',
|
|
227
|
+
].join('\n');
|
|
228
|
+
return { kind: 'launch-agent', path: posix.join(home, 'Library', 'LaunchAgents', `${NPM_LOGIN.macLabel}.plist`), content, mark: `<string>${xmlEscape(launcher)}</string>` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const config = String(env.XDG_CONFIG_HOME || '');
|
|
232
|
+
const dir = posix.join(config.startsWith('/') ? config : posix.join(home, '.config'), 'autostart');
|
|
233
|
+
const content = [
|
|
234
|
+
'[Desktop Entry]',
|
|
235
|
+
'Type=Application',
|
|
236
|
+
'Name=SynaBun server',
|
|
237
|
+
'Comment=Starts the SynaBun server in the background when you log in',
|
|
238
|
+
`Exec=${program.map(desktopExecArg).join(' ')}`,
|
|
239
|
+
'Terminal=false',
|
|
240
|
+
'NoDisplay=true',
|
|
241
|
+
'X-GNOME-Autostart-enabled=true',
|
|
242
|
+
LINUX_MARKER,
|
|
243
|
+
'',
|
|
244
|
+
].join('\n');
|
|
245
|
+
return { kind: 'autostart', path: posix.join(dir, NPM_LOGIN.linuxFile), content, mark: desktopExecArg(launcher) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The entry as the system has it: 'on' (in place), 'stale' (there, but it
|
|
250
|
+
* starts something else: another Node, a moved install), 'disabled' (turned
|
|
251
|
+
* off in the system) or 'off' (not there).
|
|
252
|
+
*/
|
|
253
|
+
export function inspectLoginEntry(plan, { env = process.env, fs = DEFAULT_FS, run = defaultRun, uid = typeof process.getuid === 'function' ? process.getuid() : null } = {}) {
|
|
254
|
+
if (plan.kind === 'run-value') {
|
|
255
|
+
const reg = systemTool(env, 'reg.exe');
|
|
256
|
+
const value = readRegValue(plan.key, plan.name, { run, fs, env });
|
|
257
|
+
if (value == null) return 'off';
|
|
258
|
+
let approved = null;
|
|
259
|
+
try { approved = startupApproved(parseRegQuery(run(reg, ['query', APPROVED_KEY, '/v', plan.name]), plan.name)); } catch {}
|
|
260
|
+
if (approved === false) return 'disabled';
|
|
261
|
+
const helperOk = !plan.helper || (() => { try { return fs.readFileSync(plan.helper.path, 'utf8') === plan.helper.content; } catch { return false; } })();
|
|
262
|
+
return value === plan.value && helperOk ? 'on' : 'stale';
|
|
263
|
+
}
|
|
264
|
+
let text = null;
|
|
265
|
+
try { text = fs.readFileSync(plan.path, 'utf8'); } catch { return 'off'; }
|
|
266
|
+
if (plan.kind === 'autostart') {
|
|
267
|
+
const keys = desktopEntryKeys(text);
|
|
268
|
+
if (/^true$/i.test(keys.Hidden || '') || /^false$/i.test(keys['X-GNOME-Autostart-enabled'] || 'true')) return 'disabled';
|
|
269
|
+
}
|
|
270
|
+
if (plan.kind === 'launch-agent' && uid != null) {
|
|
271
|
+
// Login Items > Allow in the Background, switched off, disables the job.
|
|
272
|
+
try {
|
|
273
|
+
const out = run('/bin/launchctl', ['print-disabled', `gui/${uid}`]);
|
|
274
|
+
if (new RegExp(`"${NPM_LOGIN.macLabel.replace(/\./g, '\\.')}"\\s*=>\\s*(?:true|disabled)`).test(String(out))) return 'disabled';
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
return text === plan.content ? 'on' : 'stale';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function writeAtomic(fs, path, content, mode) {
|
|
281
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
282
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
283
|
+
fs.writeFileSync(temp, content, mode ? { mode } : undefined);
|
|
284
|
+
fs.renameSync(temp, path);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Put the entry in place. Throws { code: 'write-failed' | 'no-wscript' }. With `enable` (the user turned it on
|
|
289
|
+
* in Settings) a switch the system holds for it is turned on too: Windows' Startup apps, launchd's disabled list.
|
|
290
|
+
*/
|
|
291
|
+
export function applyLoginEntry(plan, { env = process.env, fs = DEFAULT_FS, run = defaultRun, uid = typeof process.getuid === 'function' ? process.getuid() : null, enable = false } = {}) {
|
|
292
|
+
if (plan.refuse) throw failure(plan.refuse, 'a path with % in it cannot be started at login on Windows');
|
|
293
|
+
try {
|
|
294
|
+
if (plan.kind === 'run-value') {
|
|
295
|
+
const wscript = systemTool(env, 'wscript.exe');
|
|
296
|
+
if (plan.helper && win32.isAbsolute(wscript) && !fs.existsSync(wscript)) throw failure('no-wscript', `${wscript} is missing`);
|
|
297
|
+
if (plan.helper) writeAtomic(fs, plan.helper.path, plan.helper.content);
|
|
298
|
+
run(systemTool(env, 'reg.exe'), ['add', plan.key, '/v', plan.name, '/t', 'REG_SZ', '/d', plan.value, '/f']);
|
|
299
|
+
// No Startup apps record: enabled.
|
|
300
|
+
if (enable) { try { run(systemTool(env, 'reg.exe'), ['delete', APPROVED_KEY, '/v', plan.name, '/f']); } catch {} }
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
writeAtomic(fs, plan.path, plan.content, 0o644);
|
|
304
|
+
if (enable && plan.kind === 'launch-agent' && uid != null) { try { run('/bin/launchctl', ['enable', `gui/${uid}/${NPM_LOGIN.macLabel}`]); } catch {} }
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (error.code === 'no-wscript') throw error;
|
|
307
|
+
throw failure('write-failed', error.message);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** The entry as it is written now (the file, or the Run value), or null. */
|
|
312
|
+
export function readLoginEntry(plan, { env = process.env, fs = DEFAULT_FS, run = defaultRun } = {}) {
|
|
313
|
+
if (plan.kind === 'run-value') return readRegValue(plan.key, plan.name, { run, fs, env });
|
|
314
|
+
try { return fs.readFileSync(plan.path, 'utf8'); } catch { return null; }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Take the entry away. Throws { code: 'write-failed' } when it stays. */
|
|
318
|
+
export function removeLoginEntry(plan, { env = process.env, fs = DEFAULT_FS, run = defaultRun, uid = typeof process.getuid === 'function' ? process.getuid() : null } = {}) {
|
|
319
|
+
try {
|
|
320
|
+
if (plan.kind === 'run-value') {
|
|
321
|
+
try { run(systemTool(env, 'reg.exe'), ['delete', plan.key, '/v', plan.name, '/f']); } catch (error) {
|
|
322
|
+
if (inspectLoginEntry(plan, { env, fs, run }) !== 'off') throw error;
|
|
323
|
+
}
|
|
324
|
+
if (plan.helper) fs.rmSync(plan.helper.path, { force: true });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (plan.kind === 'launch-agent' && uid != null) {
|
|
328
|
+
// Loaded since the last login (it ran once then): unload it too.
|
|
329
|
+
try { run('/bin/launchctl', ['bootout', `gui/${uid}/${NPM_LOGIN.macLabel}`]); } catch {}
|
|
330
|
+
}
|
|
331
|
+
fs.rmSync(plan.path, { force: true });
|
|
332
|
+
} catch (error) {
|
|
333
|
+
throw failure('write-failed', error.message);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── The desktop app's entry, as the system has it ──
|
|
338
|
+
|
|
339
|
+
/** The strings of a plist's ProgramArguments array. */
|
|
340
|
+
function plistArguments(text) {
|
|
341
|
+
const block = /<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(String(text || ''));
|
|
342
|
+
const unxml = (v) => v.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
|
|
343
|
+
return block ? [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map(m => unxml(m[1])) : [];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The words of a .desktop Exec value (its quoting and escapes undone). */
|
|
347
|
+
function desktopExecWords(value) {
|
|
348
|
+
const text = String(value || '').replace(/\\\\/g, '\\').replace(/%%/g, '%');
|
|
349
|
+
const words = [];
|
|
350
|
+
let word = '';
|
|
351
|
+
let quoted = false;
|
|
352
|
+
let any = false;
|
|
353
|
+
for (let i = 0; i < text.length; i++) {
|
|
354
|
+
const ch = text[i];
|
|
355
|
+
if (quoted) {
|
|
356
|
+
if (ch === '\\' && i + 1 < text.length) { word += text[++i]; continue; }
|
|
357
|
+
if (ch === '"') { quoted = false; continue; }
|
|
358
|
+
word += ch;
|
|
359
|
+
} else if (ch === '"') { quoted = true; any = true; } else if (/\s/.test(ch)) {
|
|
360
|
+
if (word || any) words.push(word);
|
|
361
|
+
word = ''; any = false;
|
|
362
|
+
} else word += ch;
|
|
363
|
+
}
|
|
364
|
+
if (word || any) words.push(word);
|
|
365
|
+
return words;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** The words of a Windows command line (CommandLineToArgvW's quoting). */
|
|
369
|
+
function windowsWords(line) {
|
|
370
|
+
const text = String(line || '');
|
|
371
|
+
const words = [];
|
|
372
|
+
let word = '';
|
|
373
|
+
let quoted = false;
|
|
374
|
+
let any = false;
|
|
375
|
+
let i = 0;
|
|
376
|
+
while (i < text.length) {
|
|
377
|
+
const ch = text[i];
|
|
378
|
+
if (ch === '\\') {
|
|
379
|
+
let slashes = 0;
|
|
380
|
+
while (text[i] === '\\') { slashes++; i++; }
|
|
381
|
+
if (text[i] === '"') {
|
|
382
|
+
word += '\\'.repeat(Math.floor(slashes / 2));
|
|
383
|
+
if (slashes % 2) { word += '"'; any = true; i++; }
|
|
384
|
+
} else word += '\\'.repeat(slashes);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
i++;
|
|
388
|
+
if (ch === '"') { quoted = !quoted; any = true; continue; }
|
|
389
|
+
if (!quoted && /\s/.test(ch)) { if (word || any) words.push(word); word = ''; any = false; continue; }
|
|
390
|
+
word += ch;
|
|
391
|
+
}
|
|
392
|
+
if (word || any) words.push(word);
|
|
393
|
+
return words;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const samePath = (a, b, platform) => {
|
|
397
|
+
if (!a || !b) return false;
|
|
398
|
+
const norm = (value) => (platform === 'win32' ? win32.resolve(value) : posix.resolve(value)).replace(/[\\/]+$/, '');
|
|
399
|
+
return platform === 'linux' ? norm(a) === norm(b) : norm(a).toLowerCase() === norm(b).toLowerCase();
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The desktop app's program when its login entry, as the system has it now (there, not switched off, its program
|
|
404
|
+
* still there), opens it on `dataHome`; otherwise null. The app's own settings file is not asked: an entry the user
|
|
405
|
+
* removed or switched off while the app was closed never blocks this install.
|
|
406
|
+
*/
|
|
407
|
+
export function desktopLoginOwner(dataHome, {
|
|
408
|
+
platform = process.platform, home = homedir(), env = process.env, fs = DEFAULT_FS, run = defaultRun,
|
|
409
|
+
uid = typeof process.getuid === 'function' ? process.getuid() : null,
|
|
410
|
+
} = {}) {
|
|
411
|
+
const usual = platform === 'win32' ? (env.APPDATA ? win32.resolve(env.APPDATA, 'synabun') : null) : posix.join(home, '.synabun');
|
|
412
|
+
const owns = (program, folder) => (program && fs.existsSync(program) && samePath(folder || usual, dataHome, platform) ? program : null);
|
|
413
|
+
try {
|
|
414
|
+
if (platform === 'darwin') {
|
|
415
|
+
const text = fs.readFileSync(posix.join(home, 'Library', 'LaunchAgents', `${DESKTOP_LOGIN.macLabel}.plist`), 'utf8');
|
|
416
|
+
if (uid != null) {
|
|
417
|
+
try {
|
|
418
|
+
const out = run('/bin/launchctl', ['print-disabled', `gui/${uid}`]);
|
|
419
|
+
if (new RegExp(`"${DESKTOP_LOGIN.macLabel.replace(/\./g, '\\.')}"\\s*=>\\s*(?:true|disabled)`).test(String(out))) return null;
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
// `open -a <app> --env …`, or (a Mac whose `open` has no --env) the app's own program with its environment.
|
|
423
|
+
const words = plistArguments(text);
|
|
424
|
+
const unxml = (v) => v.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
|
|
425
|
+
const direct = /^(.+\.app)\/Contents\/MacOS\/[^/]+$/.exec(words[0] || '');
|
|
426
|
+
const bundle = words.indexOf('-a') >= 0 ? words[words.indexOf('-a') + 1] : direct ? direct[1] : null;
|
|
427
|
+
const folder = words.find(w => w.startsWith('SYNABUN_DATA_HOME='))?.slice('SYNABUN_DATA_HOME='.length)
|
|
428
|
+
?? (/<key>SYNABUN_DATA_HOME<\/key>\s*<string>([\s\S]*?)<\/string>/.exec(text)?.[1] ? unxml(/<key>SYNABUN_DATA_HOME<\/key>\s*<string>([\s\S]*?)<\/string>/.exec(text)[1]) : undefined);
|
|
429
|
+
return owns(bundle, folder);
|
|
430
|
+
}
|
|
431
|
+
if (platform === 'win32') {
|
|
432
|
+
const value = readRegValue(RUN_KEY, DESKTOP_LOGIN.winName, { run, fs, env });
|
|
433
|
+
if (value == null) return null;
|
|
434
|
+
let approved = null;
|
|
435
|
+
try { approved = startupApproved(parseRegQuery(run(systemTool(env, 'reg.exe'), ['query', APPROVED_KEY, '/v', DESKTOP_LOGIN.winName]), DESKTOP_LOGIN.winName)); } catch {}
|
|
436
|
+
if (approved === false) return null;
|
|
437
|
+
const words = windowsWords(value);
|
|
438
|
+
const data = words.find(w => w.startsWith(DESKTOP_LOGIN.dataFlag));
|
|
439
|
+
let folder = null;
|
|
440
|
+
if (data) { try { folder = Buffer.from(data.slice(DESKTOP_LOGIN.dataFlag.length), 'base64url').toString('utf8') || null; } catch {} }
|
|
441
|
+
return words.includes(DESKTOP_LOGIN.flag) ? owns(words[0], folder) : null;
|
|
442
|
+
}
|
|
443
|
+
const config = String(env.XDG_CONFIG_HOME || '');
|
|
444
|
+
const file = posix.join(config.startsWith('/') ? config : posix.join(home, '.config'), 'autostart', DESKTOP_LOGIN.linuxFile);
|
|
445
|
+
const keys = desktopEntryKeys(fs.readFileSync(file, 'utf8'));
|
|
446
|
+
if (/^true$/i.test(keys.Hidden || '') || /^false$/i.test(keys['X-GNOME-Autostart-enabled'] || 'true')) return null;
|
|
447
|
+
const words = desktopExecWords(keys.Exec);
|
|
448
|
+
const vars = words[0] === 'env' ? words.slice(1).filter(w => /^[A-Za-z_][A-Za-z0-9_]*=/.test(w)) : [];
|
|
449
|
+
const program = words[0] === 'env' ? words.slice(1).find(w => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(w)) : words[0];
|
|
450
|
+
const folder = vars.find(w => w.startsWith('SYNABUN_DATA_HOME='))?.slice('SYNABUN_DATA_HOME='.length);
|
|
451
|
+
return owns(program, folder);
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* The start at login of this install.
|
|
459
|
+
*
|
|
460
|
+
* status() { supported, reason, enabled, state, owner }: reason 'desktop' (the desktop
|
|
461
|
+
* app has its own: Settings reads it from the app), 'not-here' (a test run or a
|
|
462
|
+
* throwaway data folder) or 'unsupported'; owner is the desktop app's program
|
|
463
|
+
* when it opens at login on this data folder
|
|
464
|
+
* set(enabled) turn it on or off; resolves to status(). Throws { code }: 'desktop-app', 'not-here',
|
|
465
|
+
* 'write-failed', 'no-wscript', 'refused'
|
|
466
|
+
* heal() on a server start: this install's own entry, when stale, is put back in place; an entry that
|
|
467
|
+
* starts another install, or one the user turned off, is left as it is
|
|
468
|
+
*/
|
|
469
|
+
export function createLoginStart({
|
|
470
|
+
platform = process.platform, nodePath = process.execPath, packageRoot, home = homedir(), env = process.env, dataHome,
|
|
471
|
+
entry = null, desktopShell = false, allowed = () => true, fs = DEFAULT_FS, run = defaultRun, uid,
|
|
472
|
+
log = () => {},
|
|
473
|
+
} = {}) {
|
|
474
|
+
const ops = { env, fs, run, ...(uid !== undefined ? { uid } : {}) };
|
|
475
|
+
const plan = () => buildLoginPlan({ platform, nodePath, packageRoot, home, env, dataHome, entry });
|
|
476
|
+
const support = () => {
|
|
477
|
+
if (desktopShell) return { supported: false, reason: 'desktop' };
|
|
478
|
+
if (!['darwin', 'win32', 'linux'].includes(platform)) return { supported: false, reason: 'unsupported' };
|
|
479
|
+
if (!allowed()) return { supported: false, reason: 'not-here' };
|
|
480
|
+
return { supported: true, reason: null };
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
function status() {
|
|
484
|
+
const { supported, reason } = support();
|
|
485
|
+
const owner = desktopLoginOwner(dataHome, { platform, home, env, ...ops });
|
|
486
|
+
if (!supported) return { supported, reason, enabled: false, state: 'off', owner };
|
|
487
|
+
let state = 'off';
|
|
488
|
+
try { state = inspectLoginEntry(plan(), ops); } catch {}
|
|
489
|
+
return { supported, reason, enabled: state === 'on' || state === 'stale', state, owner };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function set(enabled) {
|
|
493
|
+
const { supported, reason } = support();
|
|
494
|
+
if (!supported) throw failure(reason, `start at login is not available here (${reason})`);
|
|
495
|
+
const target = plan();
|
|
496
|
+
if (enabled) {
|
|
497
|
+
const owner = desktopLoginOwner(dataHome, { platform, home, env, ...ops });
|
|
498
|
+
if (owner) throw failure('desktop-app', `the SynaBun app opens at login on this data folder (${owner})`);
|
|
499
|
+
applyLoginEntry(target, { ...ops, enable: true });
|
|
500
|
+
} else {
|
|
501
|
+
removeLoginEntry(target, ops);
|
|
502
|
+
}
|
|
503
|
+
const after = status();
|
|
504
|
+
log(`start at login ${enabled ? 'on' : 'off'}: ${after.state}`);
|
|
505
|
+
if (enabled ? after.state !== 'on' : after.enabled) throw failure('refused', after.state);
|
|
506
|
+
return after;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function heal() {
|
|
510
|
+
const { supported } = support();
|
|
511
|
+
if (!supported) return { changed: false, state: 'off' };
|
|
512
|
+
const target = plan();
|
|
513
|
+
const state = inspectLoginEntry(target, ops);
|
|
514
|
+
if (state !== 'stale') return { changed: false, state };
|
|
515
|
+
// Only this install's own entry (another Node, other pins): one that starts another install on this data folder
|
|
516
|
+
// (an npm install, a Git checkout run by hand) is that install's, and stays as it is.
|
|
517
|
+
if (!String(readLoginEntry(target, ops) || '').includes(target.mark)) return { changed: false, state };
|
|
518
|
+
applyLoginEntry(target, ops);
|
|
519
|
+
log('start at login: the entry now starts this install');
|
|
520
|
+
return { changed: true, state: inspectLoginEntry(target, ops) };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return { status, set, heal, plan };
|
|
524
|
+
}
|
|
@@ -756,10 +756,12 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
756
756
|
const eventSource = client.onEvent ? client : client.event;
|
|
757
757
|
let sessionId = null;
|
|
758
758
|
// Assistant text assembled from the SSE stream (message.updated gives roles,
|
|
759
|
-
// message.part.updated gives text parts keyed by message id
|
|
759
|
+
// message.part.updated gives text parts keyed by message id, message.part.delta
|
|
760
|
+
// the text a part grows by between them).
|
|
760
761
|
const messageRoles = new Map();
|
|
761
762
|
const messageOrder = [];
|
|
762
763
|
const textParts = new Map(); // messageID → Map(partID → text)
|
|
764
|
+
const textPartMessages = new Map(); // partID of a text part → its messageID (where a delta goes)
|
|
763
765
|
const rememberMessage = (info) => {
|
|
764
766
|
if (!info?.id) return;
|
|
765
767
|
if (!messageRoles.has(info.id)) messageOrder.push(info.id);
|
|
@@ -780,8 +782,16 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
780
782
|
if (!messageRoles.has(part.messageID)) rememberMessage({ id: part.messageID, role: null });
|
|
781
783
|
let parts = textParts.get(part.messageID);
|
|
782
784
|
if (!parts) { parts = new Map(); textParts.set(part.messageID, parts); }
|
|
785
|
+
if (part.id) textPartMessages.set(part.id, part.messageID);
|
|
783
786
|
parts.set(part.id || `${parts.size}`, part.text);
|
|
784
787
|
};
|
|
788
|
+
// A delta of an announced text part extends it (OpenCode 2 streams text only this way until the part ends).
|
|
789
|
+
const rememberDelta = (event) => {
|
|
790
|
+
if (!event?.partID || (event.field && event.field !== 'text') || typeof event.delta !== 'string' || !event.delta) return;
|
|
791
|
+
const messageID = textPartMessages.get(event.partID);
|
|
792
|
+
if (!messageID || (event.messageID && event.messageID !== messageID)) return;
|
|
793
|
+
rememberPart({ id: event.partID, messageID, type: 'text', text: `${textParts.get(messageID)?.get(event.partID) || ''}${event.delta}` });
|
|
794
|
+
};
|
|
785
795
|
/** { media } with the generated files not returned yet (none: {}). */
|
|
786
796
|
const turnMedia = (extraParts = []) => {
|
|
787
797
|
for (const part of extraParts) rememberPart(part);
|
|
@@ -858,6 +868,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
858
868
|
}
|
|
859
869
|
if (/^message[.:]updated$/i.test(eventType)) rememberMessage(event.info || event.message?.info || null);
|
|
860
870
|
if (/^message[.:]part[.:]updated$/i.test(eventType)) rememberPart(event.part || null);
|
|
871
|
+
if (/^message[.:]part[.:]delta$/i.test(eventType)) rememberDelta(event);
|
|
861
872
|
if (/question\.asked/i.test(eventType)) {
|
|
862
873
|
const requestID = event.id || event.requestID;
|
|
863
874
|
if (requestID && askMode && client.question?.reply) {
|
|
@@ -916,9 +927,13 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
916
927
|
// What the serve stored, for the usage meter's reconcile at a turn's end (assistant-usage.js).
|
|
917
928
|
// A call that fails, times out or is missing on an older serve throws: the caller then knows
|
|
918
929
|
// the turn was not reconciled.
|
|
930
|
+
// The clients SynaBun builds (lib/opencode-v2-client.js, either major) keep `session` as it was and carry
|
|
931
|
+
// `children` under `extra.session`.
|
|
919
932
|
const storedRows = async (method, id) => {
|
|
920
|
-
|
|
921
|
-
|
|
933
|
+
const group = typeof client.session?.[method] === 'function' ? client.session
|
|
934
|
+
: typeof client.extra?.session?.[method] === 'function' ? client.extra.session : null;
|
|
935
|
+
if (!group) throw new Error(`OpenCode session.${method} is not available`);
|
|
936
|
+
const response = await settleWithin(group[method]({ sessionID: id, directory: cwd || undefined }), 5000);
|
|
922
937
|
if (!response || response.error) throw new Error(providerErrorMessage(response?.error, `OpenCode session.${method} did not answer`));
|
|
923
938
|
const rows = Array.isArray(response.data) ? response.data : Array.isArray(response) ? response : null;
|
|
924
939
|
if (!rows) throw new Error(`OpenCode session.${method} returned no list`);
|
|
@@ -49,6 +49,9 @@ function cleanDescriptor(record) {
|
|
|
49
49
|
// existing consumers (router, sidepanels, schedules) are unaffected.
|
|
50
50
|
runMode: record.runMode || 'loop',
|
|
51
51
|
turnState: record.turnState || null,
|
|
52
|
+
// What the last turn's result said (task mode: done, blocked or needs_input; the dispatcher's
|
|
53
|
+
// lastResult.status): an idle warm worker whose turn ended asking waits on the user.
|
|
54
|
+
outcome: record.outcome || null,
|
|
52
55
|
assistantSessionId: record.assistantSessionId || null,
|
|
53
56
|
};
|
|
54
57
|
}
|
|
@@ -161,6 +164,21 @@ export class NativeLoopRuntime {
|
|
|
161
164
|
return this._emit('sidepanel:run-updated', record, { reason: 'turn-state' });
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Task-mode helper: records what the wrapper's last turn reported ('done' |
|
|
169
|
+
* 'blocked' | 'needs_input', the dispatcher's lastResult.status) and
|
|
170
|
+
* broadcasts it, so a panel can tell a warm worker that rests from one whose
|
|
171
|
+
* turn ended asking the user. No-op for unknown runs.
|
|
172
|
+
*/
|
|
173
|
+
setOutcome(runId, outcome) {
|
|
174
|
+
const record = this.records.get(runId);
|
|
175
|
+
if (!record) return null;
|
|
176
|
+
const next = outcome ? String(outcome) : null;
|
|
177
|
+
if ((record.outcome || null) === next) return cleanDescriptor(record);
|
|
178
|
+
record.outcome = next;
|
|
179
|
+
return this._emit('sidepanel:run-updated', record, { reason: 'outcome' });
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
_loadLedger() {
|
|
165
183
|
const rows = readJson(this.ledgerPath, []);
|
|
166
184
|
if (!Array.isArray(rows)) return;
|
|
@@ -461,6 +479,7 @@ export class NativeLoopRuntime {
|
|
|
461
479
|
runId,
|
|
462
480
|
emit: (event) => this._emitProviderEvent(record, event),
|
|
463
481
|
setTurnState: (turnState) => this.setTurnState(runId, turnState),
|
|
482
|
+
setOutcome: (outcome) => this.setOutcome(runId, outcome),
|
|
464
483
|
readState: () => this._readState(runId),
|
|
465
484
|
})
|
|
466
485
|
: adapter;
|