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,361 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Which process a pid is.
|
|
3
|
+
//
|
|
4
|
+
// The OS gives a pid to a new process once the old one is gone: a server
|
|
5
|
+
// SynaBun started and recorded by its pid may have exited, and that number now
|
|
6
|
+
// belong to another program. A pid together with the time its process started
|
|
7
|
+
// and its command line, as the OS reports them, names one process and no other.
|
|
8
|
+
// SynaBun records all three when it starts a server (readProcessIdentity) and
|
|
9
|
+
// signals that pid only while the OS still reports the same three
|
|
10
|
+
// (sameProcess), checked again right before each signal. A process whose
|
|
11
|
+
// identity cannot be read is never taken for SynaBun's.
|
|
12
|
+
//
|
|
13
|
+
// A start time to the second can still be shared by two processes, and a pid,
|
|
14
|
+
// a start time and a command line mean something only on the machine, for the
|
|
15
|
+
// user and in the boot they were read in. So a serve SynaBun spawns also
|
|
16
|
+
// carries a random token in its environment (SERVE_TOKEN_ENV, newServeToken),
|
|
17
|
+
// which the OS gives back for the user's own processes (readEnvironmentHas:
|
|
18
|
+
// `ps -E` on macOS, /proc/<pid>/environ on Linux; Windows cannot read another
|
|
19
|
+
// process's environment, and its start times are to the fraction of a second),
|
|
20
|
+
// and every record says where it was written (readHostScope: the machine's id,
|
|
21
|
+
// the user, the boot). The parent chain (readProcessParentAsync) and the
|
|
22
|
+
// listeners of a port (listPortListenersAsync) tell whether what answers on a
|
|
23
|
+
// port is a process SynaBun spawned, or one that process started.
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
27
|
+
import { randomBytes } from 'node:crypto';
|
|
28
|
+
import { readFileSync } from 'node:fs';
|
|
29
|
+
|
|
30
|
+
/** The environment variable that carries the token of a serve SynaBun spawned. */
|
|
31
|
+
export const SERVE_TOKEN_ENV = 'SYNABUN_SERVE_TOKEN';
|
|
32
|
+
|
|
33
|
+
/** A fresh token for one spawn: 128 random bits, hex. */
|
|
34
|
+
export function newServeToken() {
|
|
35
|
+
return randomBytes(16).toString('hex');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// `ps -o uid= -o lstart=` in the C locale and UTC: " 501 Tue Oct 21 04:12:50 2026" (the day may be padded with a
|
|
39
|
+
// space). The uid is optional here: a test may hand in what an older query printed.
|
|
40
|
+
const PS_LINE = /^(?:(\d+)\s+)?(\S+\s+\S+\s+\d{1,2}\s+\d{1,2}:\d{2}:\d{2}\s+\d{4})\s+(\S.*)$/;
|
|
41
|
+
const PS_PARENT = /^(\d+)\s+(\S+\s+\S+\s+\d{1,2}\s+\d{1,2}:\d{2}:\d{2}\s+\d{4})$/;
|
|
42
|
+
// One locale and one time zone, so a record reads the same after either changes.
|
|
43
|
+
const C_ENV = () => ({ ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' });
|
|
44
|
+
|
|
45
|
+
const pidOf = (pid) => {
|
|
46
|
+
const n = Number(pid);
|
|
47
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const firstLine = (out) => String(out ?? '').split('\n').map((l) => l.trim()).find(Boolean) || '';
|
|
51
|
+
|
|
52
|
+
function powershell(script, timeout = 8000) {
|
|
53
|
+
return { file: 'powershell.exe', args: ['-NoProfile', '-NonInteractive', '-Command', script], options: { encoding: 'utf8', timeout, windowsHide: true } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// What asks the OS about one pid: `ps -ww -o uid= -o lstart= -o command=` (the user, the start time to the second,
|
|
57
|
+
// the whole command line) on macOS and Linux; the process's CreationDate and CommandLine (CIM, through PowerShell)
|
|
58
|
+
// on Windows.
|
|
59
|
+
function identityQuery(n, platform) {
|
|
60
|
+
if (platform === 'win32') {
|
|
61
|
+
return powershell(`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${n}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString('o'); $p.CommandLine }`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
file: 'ps',
|
|
65
|
+
args: ['-ww', '-o', 'uid=', '-o', 'lstart=', '-o', 'command=', '-p', String(n)],
|
|
66
|
+
options: { encoding: 'utf8', timeout: 5000, env: C_ENV() },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseIdentity(n, platform, out) {
|
|
71
|
+
const text = String(out ?? '');
|
|
72
|
+
if (platform === 'win32') {
|
|
73
|
+
const [first = '', ...rest] = text.split(/\r?\n/);
|
|
74
|
+
const startedAt = first.trim();
|
|
75
|
+
const command = rest.join('\n').trim();
|
|
76
|
+
return startedAt && command ? { pid: n, startedAt, command } : null;
|
|
77
|
+
}
|
|
78
|
+
const match = PS_LINE.exec(firstLine(text));
|
|
79
|
+
if (!match) return null;
|
|
80
|
+
const identity = { pid: n, startedAt: match[2].replace(/\s+/g, ' '), command: match[3].trim() };
|
|
81
|
+
if (match[1] != null) identity.uid = match[1];
|
|
82
|
+
return identity;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* `{ pid, startedAt, command }` of a running process (`uid` too on macOS and Linux), or null: gone, or the OS could
|
|
87
|
+
* not be asked. macOS and Linux: `ps -ww -o uid= -o lstart= -o command=` (the start time to the second, the whole
|
|
88
|
+
* command line); Windows: the process's CreationDate (to the fraction of a second) and CommandLine (CIM, through
|
|
89
|
+
* PowerShell).
|
|
90
|
+
* @param {number} pid
|
|
91
|
+
* @param {{ platform?: string, run?: typeof execFileSync }} [o] `run`: tests hand in what the OS would answer
|
|
92
|
+
*/
|
|
93
|
+
export function readProcessIdentity(pid, { platform = process.platform, run = execFileSync } = {}) {
|
|
94
|
+
const n = pidOf(pid);
|
|
95
|
+
if (!n) return null;
|
|
96
|
+
const query = identityQuery(n, platform);
|
|
97
|
+
try {
|
|
98
|
+
return parseIdentity(n, platform, run(query.file, query.args, { ...query.options, stdio: ['ignore', 'pipe', 'ignore'] }));
|
|
99
|
+
} catch {
|
|
100
|
+
// ps exits 1 for a pid nothing runs under; a missing ps or PowerShell is "could not be asked".
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* readProcessIdentity without holding the event loop while the OS answers (PowerShell takes about a second):
|
|
107
|
+
* for a look that may read several pids, such as the sweep of SynaBun's leftover serves.
|
|
108
|
+
* @param {number} pid
|
|
109
|
+
* @param {{ platform?: string, run?: (file: string, args: string[], options: object) => Promise<string> }} [o]
|
|
110
|
+
*/
|
|
111
|
+
export async function readProcessIdentityAsync(pid, { platform = process.platform, run = runFile } = {}) {
|
|
112
|
+
const n = pidOf(pid);
|
|
113
|
+
if (!n) return null;
|
|
114
|
+
const query = identityQuery(n, platform);
|
|
115
|
+
try {
|
|
116
|
+
return parseIdentity(n, platform, await run(query.file, query.args, query.options));
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function runFile(file, args, options) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
execFile(file, args, options, (error, stdout, stderr) => (error ? reject(Object.assign(error, { stdout, stderr })) : resolve(stdout)));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Is `current` (readProcessIdentity now) the process `recorded` was: the same pid, start time and command line? */
|
|
129
|
+
export function sameProcess(recorded, current) {
|
|
130
|
+
if (!recorded || !current || !recorded.startedAt || !recorded.command) return false;
|
|
131
|
+
return Number(recorded.pid) === Number(current.pid)
|
|
132
|
+
&& String(recorded.startedAt) === String(current.startedAt)
|
|
133
|
+
&& String(recorded.command) === String(current.command);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Does any process run under `pid`? true, false (none: the OS said so), or null (it could not be told). Signal 0
|
|
138
|
+
* sends nothing: the OS only checks the pid. A process of another user answers EPERM, which is a process too.
|
|
139
|
+
* @param {number} pid
|
|
140
|
+
* @param {{ kill?: (pid: number, signal: number | string) => void }} [o]
|
|
141
|
+
*/
|
|
142
|
+
export function pidExists(pid, { kill = (p, s) => process.kill(p, s) } = {}) {
|
|
143
|
+
const n = pidOf(pid);
|
|
144
|
+
if (!n) return false;
|
|
145
|
+
try {
|
|
146
|
+
kill(n, 0);
|
|
147
|
+
return true;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error?.code === 'ESRCH') return false;
|
|
150
|
+
if (error?.code === 'EPERM') return true;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── The environment a process started with ──────────────────────────────────
|
|
156
|
+
|
|
157
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
158
|
+
const ENV_VALUE = /^[A-Za-z0-9_.:-]+$/;
|
|
159
|
+
|
|
160
|
+
// macOS prints the environment after the command line (`ps -E`) only for a process it lets the user read: the
|
|
161
|
+
// user's own, and not a binary of the system's. The line must start with the command line read for the identity
|
|
162
|
+
// (else the pid is another process's by now), and hold more than that (else the environment was not shown).
|
|
163
|
+
function parseEnvironmentHas(out, command, name, value) {
|
|
164
|
+
const text = firstLine(out);
|
|
165
|
+
const plain = String(command ?? '').trim();
|
|
166
|
+
if (!plain || !text.startsWith(plain)) return null;
|
|
167
|
+
const environment = text.slice(plain.length);
|
|
168
|
+
if (!environment.trim()) return null;
|
|
169
|
+
return new RegExp(`(?:^|\\s)${name}=${value}(?=\\s|$)`).test(environment);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function linuxEnvironmentHas(n, name, value, readFile) {
|
|
173
|
+
let text;
|
|
174
|
+
try { text = readFile(`/proc/${n}/environ`, 'utf8'); } catch { return null; } // gone, or another user's
|
|
175
|
+
const vars = String(text).split('\0').filter(Boolean);
|
|
176
|
+
if (!vars.length) return null; // a process that has ended shows none
|
|
177
|
+
return vars.includes(`${name}=${value}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function environmentQuery(n) {
|
|
181
|
+
return { file: 'ps', args: ['-E', '-ww', '-o', 'command=', '-p', String(n)], options: { encoding: 'utf8', timeout: 5000, env: C_ENV() } };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Does the environment `pid` started with hold `name=value`? true, false (it was read, and does not), or null (it
|
|
186
|
+
* could not be read: gone, another user's, or the OS does not say). macOS: `ps -E`, given the command line just
|
|
187
|
+
* read for the process's identity; Linux: /proc/<pid>/environ; Windows and anything else: null.
|
|
188
|
+
* @param {number} pid
|
|
189
|
+
* @param {string} name
|
|
190
|
+
* @param {string} value
|
|
191
|
+
* @param {{ command?: string, platform?: string, run?: typeof execFileSync, readFile?: typeof readFileSync }} [o]
|
|
192
|
+
*/
|
|
193
|
+
export function readEnvironmentHas(pid, name, value, { command = '', platform = process.platform, run = execFileSync, readFile = readFileSync } = {}) {
|
|
194
|
+
const n = pidOf(pid);
|
|
195
|
+
if (!n || !ENV_NAME.test(String(name)) || !ENV_VALUE.test(String(value))) return null;
|
|
196
|
+
if (platform === 'linux') return linuxEnvironmentHas(n, name, value, readFile);
|
|
197
|
+
if (platform !== 'darwin') return null;
|
|
198
|
+
const query = environmentQuery(n);
|
|
199
|
+
try {
|
|
200
|
+
return parseEnvironmentHas(run(query.file, query.args, { ...query.options, stdio: ['ignore', 'pipe', 'ignore'] }), command, name, value);
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** readEnvironmentHas without holding the event loop while the OS answers. */
|
|
207
|
+
export async function readEnvironmentHasAsync(pid, name, value, { command = '', platform = process.platform, run = runFile, readFile = readFileSync } = {}) {
|
|
208
|
+
const n = pidOf(pid);
|
|
209
|
+
if (!n || !ENV_NAME.test(String(name)) || !ENV_VALUE.test(String(value))) return null;
|
|
210
|
+
if (platform === 'linux') return linuxEnvironmentHas(n, name, value, readFile);
|
|
211
|
+
if (platform !== 'darwin') return null;
|
|
212
|
+
const query = environmentQuery(n);
|
|
213
|
+
try {
|
|
214
|
+
return parseEnvironmentHas(await run(query.file, query.args, query.options), command, name, value);
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Where: the machine, the user, the boot ──────────────────────────────────
|
|
221
|
+
|
|
222
|
+
const WINDOWS_SCOPE = "(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid; "
|
|
223
|
+
+ "(Get-CimInstance Win32_OperatingSystem).LastBootUpTime.ToUniversalTime().ToString('o'); "
|
|
224
|
+
+ '[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value';
|
|
225
|
+
|
|
226
|
+
const clean = (v) => {
|
|
227
|
+
const text = String(v ?? '').trim();
|
|
228
|
+
return text || null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
function uidOf(getuid) {
|
|
232
|
+
try { const uid = getuid?.(); return Number.isInteger(uid) && uid >= 0 ? String(uid) : null; } catch { return null; }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function readFirst(readFile, paths) {
|
|
236
|
+
for (const path of paths) {
|
|
237
|
+
try { const text = clean(readFile(path, 'utf8')); if (text) return text; } catch { /* the next one */ }
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const machineFromIoreg = (out) => clean(/"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(String(out ?? ''))?.[1]);
|
|
243
|
+
const bootFromBoottime = (out) => {
|
|
244
|
+
const sec = /sec\s*=\s*(\d+)/.exec(String(out ?? ''))?.[1];
|
|
245
|
+
return sec ? `boottime:${sec}` : null;
|
|
246
|
+
};
|
|
247
|
+
function scopeFromPowershell(out) {
|
|
248
|
+
const [machine, boot, uid] = String(out ?? '').split(/\r?\n/).map(clean);
|
|
249
|
+
return { machine: machine || null, uid: uid || null, boot: boot || null };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Where a record is written, and for whom: `{ machine, uid, boot }`, each a string, or null where the OS could not
|
|
254
|
+
* tell. macOS: the platform UUID (ioreg), the user's uid, the boot session's UUID (kern.bootsessionuuid; the boot
|
|
255
|
+
* time when that is missing); Linux: /etc/machine-id, the uid, /proc/sys/kernel/random/boot_id; Windows: the
|
|
256
|
+
* MachineGuid, the user's SID and the last boot time (one PowerShell).
|
|
257
|
+
* @param {{ platform?: string, run?: typeof execFileSync, readFile?: typeof readFileSync, getuid?: () => number }} [o]
|
|
258
|
+
*/
|
|
259
|
+
export function readHostScope({ platform = process.platform, run = execFileSync, readFile = readFileSync, getuid = process.getuid } = {}) {
|
|
260
|
+
const ask = (file, args, timeout = 5000) => {
|
|
261
|
+
try { return run(file, args, { encoding: 'utf8', timeout, env: C_ENV(), windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }); } catch { return null; }
|
|
262
|
+
};
|
|
263
|
+
if (platform === 'darwin') {
|
|
264
|
+
return {
|
|
265
|
+
machine: machineFromIoreg(ask('ioreg', ['-rd1', '-c', 'IOPlatformExpertDevice'])),
|
|
266
|
+
uid: uidOf(getuid),
|
|
267
|
+
boot: clean(ask('sysctl', ['-n', 'kern.bootsessionuuid'])) || bootFromBoottime(ask('sysctl', ['-n', 'kern.boottime'])),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (platform === 'linux') {
|
|
271
|
+
return {
|
|
272
|
+
machine: readFirst(readFile, ['/etc/machine-id', '/var/lib/dbus/machine-id']),
|
|
273
|
+
uid: uidOf(getuid),
|
|
274
|
+
boot: readFirst(readFile, ['/proc/sys/kernel/random/boot_id']),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (platform === 'win32') {
|
|
278
|
+
const query = powershell(WINDOWS_SCOPE, 15000);
|
|
279
|
+
return scopeFromPowershell(ask(query.file, query.args, 15000));
|
|
280
|
+
}
|
|
281
|
+
return { machine: null, uid: uidOf(getuid), boot: null };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** readHostScope without holding the event loop while the OS answers (PowerShell takes about a second). */
|
|
285
|
+
export async function readHostScopeAsync({ platform = process.platform, run = runFile, readFile = readFileSync, getuid = process.getuid } = {}) {
|
|
286
|
+
const ask = async (file, args, timeout = 5000) => {
|
|
287
|
+
try { return await run(file, args, { encoding: 'utf8', timeout, env: C_ENV(), windowsHide: true }); } catch { return null; }
|
|
288
|
+
};
|
|
289
|
+
if (platform === 'darwin') {
|
|
290
|
+
const [ioreg, session] = await Promise.all([ask('ioreg', ['-rd1', '-c', 'IOPlatformExpertDevice']), ask('sysctl', ['-n', 'kern.bootsessionuuid'])]);
|
|
291
|
+
return {
|
|
292
|
+
machine: machineFromIoreg(ioreg),
|
|
293
|
+
uid: uidOf(getuid),
|
|
294
|
+
boot: clean(session) || bootFromBoottime(await ask('sysctl', ['-n', 'kern.boottime'])),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (platform === 'win32') {
|
|
298
|
+
const query = powershell(WINDOWS_SCOPE, 15000);
|
|
299
|
+
return scopeFromPowershell(await ask(query.file, query.args, 15000));
|
|
300
|
+
}
|
|
301
|
+
return readHostScope({ platform, readFile, getuid });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── The parent chain, and who listens on a port ─────────────────────────────
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The parent of a running process and when the process started: `{ pid, ppid, startedAt }`, or null (gone, or the
|
|
308
|
+
* OS could not be asked). macOS and Linux: `ps -o ppid= -o lstart=`; Windows: CIM's ParentProcessId and CreationDate
|
|
309
|
+
* (a parent pid Windows keeps after that parent ended: the caller compares start times).
|
|
310
|
+
* @param {number} pid
|
|
311
|
+
* @param {{ platform?: string, run?: (file: string, args: string[], options: object) => Promise<string> }} [o]
|
|
312
|
+
*/
|
|
313
|
+
export async function readProcessParentAsync(pid, { platform = process.platform, run = runFile } = {}) {
|
|
314
|
+
const n = pidOf(pid);
|
|
315
|
+
if (!n) return null;
|
|
316
|
+
try {
|
|
317
|
+
if (platform === 'win32') {
|
|
318
|
+
const query = powershell(`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${n}"; if ($p) { $p.ParentProcessId; $p.CreationDate.ToUniversalTime().ToString('o') }`);
|
|
319
|
+
const [ppid = '', startedAt = ''] = String(await run(query.file, query.args, query.options)).split(/\r?\n/).map((l) => l.trim());
|
|
320
|
+
return /^\d+$/.test(ppid) && startedAt ? { pid: n, ppid: Number(ppid), startedAt } : null;
|
|
321
|
+
}
|
|
322
|
+
const out = await run('ps', ['-o', 'ppid=', '-o', 'lstart=', '-p', String(n)], { encoding: 'utf8', timeout: 5000, env: C_ENV() });
|
|
323
|
+
const match = PS_PARENT.exec(firstLine(out));
|
|
324
|
+
return match ? { pid: n, ppid: Number(match[1]), startedAt: match[2].replace(/\s+/g, ' ') } : null;
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The pids that listen on a local TCP port: a list (empty: nothing listens that the user may see), or null when the
|
|
332
|
+
* OS could not be asked. macOS and Linux: lsof (exit 1 with nothing printed is "none"; no lsof is null); Windows:
|
|
333
|
+
* Get-NetTCPConnection, where an empty answer is null too (it lists every user's listeners: something answered on
|
|
334
|
+
* the port a moment ago).
|
|
335
|
+
* @param {number} port
|
|
336
|
+
* @param {{ platform?: string, run?: (file: string, args: string[], options: object) => Promise<string> }} [o]
|
|
337
|
+
*/
|
|
338
|
+
export async function listPortListenersAsync(port, { platform = process.platform, run = runFile, selfPid = process.pid } = {}) {
|
|
339
|
+
const p = Number(port);
|
|
340
|
+
if (!Number.isInteger(p) || p <= 0 || p > 65535) return null;
|
|
341
|
+
const pidsIn = (out) => [...new Set(String(out ?? '').split(/\r?\n/).map((l) => l.trim()).filter((l) => /^\d+$/.test(l)).map(Number))]
|
|
342
|
+
.filter((n) => n > 0 && n !== selfPid);
|
|
343
|
+
if (platform === 'win32') {
|
|
344
|
+
const query = powershell(`Get-NetTCPConnection -LocalPort ${p} -State Listen -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess }`);
|
|
345
|
+
try {
|
|
346
|
+
const pids = pidsIn(await run(query.file, query.args, query.options));
|
|
347
|
+
return pids.length ? pids : null;
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
return pidsIn(await run('lsof', ['-nP', `-tiTCP:${p}`, '-sTCP:LISTEN'], { encoding: 'utf8', timeout: 5000, env: C_ENV() }));
|
|
354
|
+
} catch (error) {
|
|
355
|
+
// lsof exits 1 when nothing matched, and for some errors too: nothing printed but a warning is "cannot be told".
|
|
356
|
+
if (error?.code !== 1) return null;
|
|
357
|
+
const pids = pidsIn(error.stdout);
|
|
358
|
+
if (pids.length) return pids;
|
|
359
|
+
return String(error.stderr ?? '').trim() ? null : [];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from './db.js';
|
|
25
25
|
import { parseLine } from './session-chunker.js';
|
|
26
26
|
import { listCodexSessionsFromAccounts } from './codex-session-catalog.js';
|
|
27
|
+
import { opencodeDbFlavor, sessionTablesFor, v2MessageText } from './opencode-db-schema.js';
|
|
27
28
|
|
|
28
29
|
const MAX_BODY = 256 * 1024;
|
|
29
30
|
const MAX_MSG_CHARS = 8 * 1024;
|
|
@@ -380,20 +381,26 @@ export function rebuildCodexCache({ project, projects, accounts, onProgress } =
|
|
|
380
381
|
|
|
381
382
|
// --- OpenCode ---
|
|
382
383
|
|
|
383
|
-
|
|
384
|
+
// `protocol`: the OpenCode major SynaBun talks to (lib/opencode-db-schema.js
|
|
385
|
+
// opencodeDbFlavor): OpenCode 2 keeps its sessions in session_v2 and their text
|
|
386
|
+
// in session_message rows, in the same database file as OpenCode 1's tables.
|
|
387
|
+
export function rebuildOpencodeCache({ getOpencodeDb, onProgress, protocol = null } = {}) {
|
|
384
388
|
const emit = onProgress || (() => {});
|
|
385
389
|
if (typeof getOpencodeDb !== 'function') return { sessions: 0, updated: 0 };
|
|
386
390
|
|
|
387
391
|
const ocdb = getOpencodeDb();
|
|
388
392
|
if (!ocdb) return { sessions: 0, updated: 0 };
|
|
393
|
+
const flavor = opencodeDbFlavor(ocdb, protocol);
|
|
394
|
+
if (!flavor) return { sessions: 0, updated: 0 };
|
|
395
|
+
const tables = sessionTablesFor(flavor);
|
|
389
396
|
|
|
390
397
|
let sessions;
|
|
391
398
|
try {
|
|
392
399
|
sessions = ocdb.prepare(`
|
|
393
400
|
SELECT s.id, s.title, s.directory, s.time_created, s.time_updated,
|
|
394
401
|
p.worktree AS project_worktree,
|
|
395
|
-
|
|
396
|
-
FROM session s
|
|
402
|
+
${tables.messageCount} AS message_count
|
|
403
|
+
FROM ${tables.session} s
|
|
397
404
|
LEFT JOIN project p ON s.project_id = p.id
|
|
398
405
|
WHERE s.time_archived IS NULL
|
|
399
406
|
`).all();
|
|
@@ -402,11 +409,25 @@ export function rebuildOpencodeCache({ getOpencodeDb, onProgress } = {}) {
|
|
|
402
409
|
let total = sessions.length;
|
|
403
410
|
let updated = 0;
|
|
404
411
|
|
|
405
|
-
// OpenCode schema: message.data (JSON w/ role+metadata) + part.data (JSON w/ type+text).
|
|
412
|
+
// OpenCode 1 schema: message.data (JSON w/ role+metadata) + part.data (JSON w/ type+text).
|
|
406
413
|
// Pull parts per message, decode JSON, grab text where type === 'text'.
|
|
414
|
+
// OpenCode 2: one session_message row per message, its text in `data`
|
|
415
|
+
// (v2MessageText); read as the same { part_id, part_created, pdata, mdata } rows.
|
|
407
416
|
let partStmt;
|
|
408
417
|
try {
|
|
409
|
-
partStmt =
|
|
418
|
+
partStmt = flavor === 'v2'
|
|
419
|
+
? (() => {
|
|
420
|
+
const rows = ocdb.prepare(`SELECT id, type, time_created, data FROM session_message
|
|
421
|
+
WHERE session_id = ? AND type IN ('user', 'assistant')
|
|
422
|
+
ORDER BY seq ASC`);
|
|
423
|
+
return {
|
|
424
|
+
all: (sessionId) => rows.all(sessionId).map((row) => {
|
|
425
|
+
const said = v2MessageText(row.type, row.data);
|
|
426
|
+
return said ? { part_id: row.id, part_created: row.time_created, pdata: JSON.stringify({ type: 'text', text: said.text }), mdata: JSON.stringify({ role: said.role }) } : null;
|
|
427
|
+
}).filter(Boolean),
|
|
428
|
+
};
|
|
429
|
+
})()
|
|
430
|
+
: ocdb.prepare(`SELECT p.id AS part_id, p.time_created AS part_created, p.data AS pdata, m.data AS mdata
|
|
410
431
|
FROM part p
|
|
411
432
|
LEFT JOIN message m ON m.id = p.message_id
|
|
412
433
|
WHERE p.session_id = ?
|
|
@@ -152,6 +152,23 @@ async function generateOpenCodeTitle(input, deps) {
|
|
|
152
152
|
const timer = setTimeout(() => controller.abort(new Error('Session title request timed out')), SESSION_TITLE_TIMEOUT_MS);
|
|
153
153
|
timer.unref?.();
|
|
154
154
|
let sessionId = null;
|
|
155
|
+
// OpenCode 2 (lib/opencode2-client.js): its one-shot generator when the model
|
|
156
|
+
// allows it (OpenCode's free tier answers it 503), else a temporary session
|
|
157
|
+
// whose permissions deny every tool: OpenCode 2 has no tool switch per prompt.
|
|
158
|
+
const opencode2 = deps.openCodeClient.getProtocol?.() === 2;
|
|
159
|
+
if (opencode2) {
|
|
160
|
+
const raw = deps.openCodeClient.getClient?.();
|
|
161
|
+
try {
|
|
162
|
+
const generated = await raw?.generate?.({
|
|
163
|
+
prompt: `${TITLE_SYSTEM_PROMPT}\n\n${titlePrompt(input)}`,
|
|
164
|
+
model: input.model || undefined,
|
|
165
|
+
directory: input.cwd || undefined,
|
|
166
|
+
signal: controller.signal,
|
|
167
|
+
});
|
|
168
|
+
const text = String(generated?.data?.text || '').trim();
|
|
169
|
+
if (!generated?.error && text) { clearTimeout(timer); return text; }
|
|
170
|
+
} catch { /* the session below */ }
|
|
171
|
+
}
|
|
155
172
|
try {
|
|
156
173
|
const toolIds = new Set([
|
|
157
174
|
'bash', 'edit', 'write', 'patch', 'apply_patch', 'multiedit', 'read',
|
|
@@ -170,6 +187,7 @@ async function generateOpenCodeTitle(input, deps) {
|
|
|
170
187
|
const created = await deps.openCodeClient.session.create({
|
|
171
188
|
directory: input.cwd || undefined,
|
|
172
189
|
title: 'SessionTitleDraft',
|
|
190
|
+
...(opencode2 ? { permission: [{ permission: '*', pattern: '*', action: 'deny' }] } : {}),
|
|
173
191
|
});
|
|
174
192
|
sessionId = created?.data?.id || created?.data?.sessionID || created?.id || created?.sessionID;
|
|
175
193
|
if (!sessionId) throw new Error('OpenCode did not create a temporary title session');
|
|
@@ -47,6 +47,7 @@ import { EXPORT_FORMATS, StyleGuideError, renderFormat } from './store.js';
|
|
|
47
47
|
import { estimateTokens, styleGuideSummaryForClass } from './summary.js';
|
|
48
48
|
import { resolvedTokens } from './tokens.js';
|
|
49
49
|
import { safeLogoFile } from './security.js';
|
|
50
|
+
import { setUntrustedFileHeaders } from '../untrusted-file-headers.js';
|
|
50
51
|
|
|
51
52
|
const LOGO_TYPES = Object.freeze({ 'image/png': '.png', 'image/svg+xml': '.svg', 'image/jpeg': '.jpg', 'image/webp': '.webp' });
|
|
52
53
|
const SAVE_SOURCE_RE = /^(ui|import:[a-z-]{1,24}|preset:[a-z0-9-]{1,40})$/;
|
|
@@ -367,9 +368,9 @@ export function registerStyleGuideRoutes(app, { store, fetchImpl = null, log = (
|
|
|
367
368
|
if (!full.startsWith(store.assetsDir + sep)) return res.status(403).json({ ok: false, error: 'Forbidden' });
|
|
368
369
|
if (!existsSync(full)) return res.status(404).json({ ok: false, error: 'Not found' });
|
|
369
370
|
store.assertPath(store.assetsDir, full);
|
|
370
|
-
// An uploaded SVG is served as a file to look at, never as a page that can run script on this origin
|
|
371
|
-
|
|
372
|
-
res
|
|
371
|
+
// An uploaded SVG is served as a file to look at, never as a page that can run script on this origin
|
|
372
|
+
// (and opened by its address, a download).
|
|
373
|
+
setUntrustedFileHeaders(res, file);
|
|
373
374
|
res.sendFile(full);
|
|
374
375
|
}));
|
|
375
376
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The headers of a file a user uploaded or an agent made, served back to the page: a pasted or generated image, a
|
|
2
|
+
// category logo, a custom file icon, a Skills Studio icon, a Style Guide logo, an Assistant run's media. An <img> or
|
|
3
|
+
// <video> in the app shows it as before (it ignores all three); opened by its address it runs nothing. An SVG is a
|
|
4
|
+
// document that can carry script, so it also comes as a download there. tests/untrusted-file-headers.test.mjs.
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
|
|
7
|
+
/** No script, no request, no plugin, no form; a unique origin (sandbox). Inline styles stay, so an SVG still draws. */
|
|
8
|
+
export const UNTRUSTED_FILE_CSP = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
|
|
9
|
+
|
|
10
|
+
/** Is this file an SVG, by its name or by the type it is sent as? */
|
|
11
|
+
export function isSvgFile(name, type = '') {
|
|
12
|
+
return /\.svg$/i.test(String(name || '')) || /^image\/svg\+xml\b/i.test(String(type || ''));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set the three headers on `res` (an Express response) for the file `name` (a path or a file name) sent as `type`.
|
|
17
|
+
* Call it before the body goes out; it does not touch Content-Type.
|
|
18
|
+
*/
|
|
19
|
+
export function setUntrustedFileHeaders(res, name, type = '') {
|
|
20
|
+
res.setHeader('Content-Security-Policy', UNTRUSTED_FILE_CSP);
|
|
21
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
22
|
+
if (isSvgFile(name, type)) {
|
|
23
|
+
const file = basename(String(name || '')) || 'image.svg';
|
|
24
|
+
const ascii = file.replace(/[^\x20-\x7e]|["\\]/g, '_');
|
|
25
|
+
res.setHeader('Content-Disposition', ascii === file ? `attachment; filename="${file}"` : `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(file)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|