synabun 2.0.7 → 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 +67 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -4
- package/hooks/claude-code/post-plan.mjs +6 -30
- package/hooks/claude-code/session-start.mjs +1 -4
- package/hooks/claude-code/shared.mjs +106 -72
- package/launcher.mjs +9 -3
- package/lib/package-processes.js +670 -0
- package/lib/runtime-dependencies.js +225 -0
- package/lib/start-launcher.js +6 -1
- package/mcp-server/dist/services/browser-bench.d.ts +1 -1
- package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/browser-bench.js +1 -1
- package/mcp-server/dist/services/browser-bench.js.map +1 -1
- package/mcp-server/dist/services/categories.d.ts +35 -2
- package/mcp-server/dist/services/categories.d.ts.map +1 -1
- package/mcp-server/dist/services/categories.js +77 -33
- package/mcp-server/dist/services/categories.js.map +1 -1
- package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
- package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/desktop-bench.js +1 -1
- package/mcp-server/dist/services/desktop-bench.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +1 -1
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +1 -1
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/bluesky.js +1 -1
- package/mcp-server/dist/tools/bluesky.js.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.js +1 -1
- package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-batch.js +1 -1
- package/mcp-server/dist/tools/browser-batch.js.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-interact.js +1 -1
- package/mcp-server/dist/tools/browser-interact.js.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.js +1 -1
- package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-observe.js +1 -1
- package/mcp-server/dist/tools/browser-observe.js.map +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/card-tools.js +1 -1
- package/mcp-server/dist/tools/card-tools.js.map +1 -1
- package/mcp-server/dist/tools/category.d.ts +1 -1
- package/mcp-server/dist/tools/category.d.ts.map +1 -1
- package/mcp-server/dist/tools/category.js +5 -1
- package/mcp-server/dist/tools/category.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +1 -1
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +1 -1
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/computer.d.ts +1 -1
- package/mcp-server/dist/tools/computer.d.ts.map +1 -1
- package/mcp-server/dist/tools/computer.js +1 -1
- package/mcp-server/dist/tools/computer.js.map +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-channel.js +1 -1
- package/mcp-server/dist/tools/discord-channel.js.map +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-guild.js +1 -1
- package/mcp-server/dist/tools/discord-guild.js.map +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-member.js +1 -1
- package/mcp-server/dist/tools/discord-member.js.map +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-message.js +1 -1
- package/mcp-server/dist/tools/discord-message.js.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-role.js +1 -1
- package/mcp-server/dist/tools/discord-role.js.map +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-thread.js +1 -1
- package/mcp-server/dist/tools/discord-thread.js.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.js +1 -1
- package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
- package/mcp-server/dist/tools/fb-groups.js +1 -1
- package/mcp-server/dist/tools/fb-groups.js.map +1 -1
- package/mcp-server/dist/tools/forget.d.ts +1 -1
- package/mcp-server/dist/tools/forget.d.ts.map +1 -1
- package/mcp-server/dist/tools/forget.js +1 -1
- package/mcp-server/dist/tools/forget.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +1 -1
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.js +1 -1
- package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.js +1 -1
- package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.js +1 -1
- package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/image-tools.js.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
- package/mcp-server/dist/tools/loop.d.ts +1 -1
- package/mcp-server/dist/tools/loop.d.ts.map +1 -1
- package/mcp-server/dist/tools/loop.js +1 -1
- package/mcp-server/dist/tools/loop.js.map +1 -1
- package/mcp-server/dist/tools/memories.d.ts +1 -1
- package/mcp-server/dist/tools/memories.d.ts.map +1 -1
- package/mcp-server/dist/tools/memories.js +1 -1
- package/mcp-server/dist/tools/memories.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +1 -1
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/profile.d.ts +1 -1
- package/mcp-server/dist/tools/profile.d.ts.map +1 -1
- package/mcp-server/dist/tools/profile.js +1 -1
- package/mcp-server/dist/tools/profile.js.map +1 -1
- package/mcp-server/dist/tools/recall.d.ts +1 -1
- package/mcp-server/dist/tools/recall.d.ts.map +1 -1
- package/mcp-server/dist/tools/recall.js +1 -1
- package/mcp-server/dist/tools/recall.js.map +1 -1
- package/mcp-server/dist/tools/reflect.d.ts +1 -1
- package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
- package/mcp-server/dist/tools/reflect.js +1 -1
- package/mcp-server/dist/tools/reflect.js.map +1 -1
- package/mcp-server/dist/tools/remember.d.ts +1 -1
- package/mcp-server/dist/tools/remember.d.ts.map +1 -1
- package/mcp-server/dist/tools/remember.js +13 -11
- package/mcp-server/dist/tools/remember.js.map +1 -1
- package/mcp-server/dist/tools/restore.d.ts +1 -1
- package/mcp-server/dist/tools/restore.d.ts.map +1 -1
- package/mcp-server/dist/tools/restore.js +1 -1
- package/mcp-server/dist/tools/restore.js.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
- package/mcp-server/dist/tools/sync.d.ts +1 -1
- package/mcp-server/dist/tools/sync.d.ts.map +1 -1
- package/mcp-server/dist/tools/sync.js +1 -1
- package/mcp-server/dist/tools/sync.js.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
- package/mcp-server/dist/tools/utils.d.ts +1 -1
- package/mcp-server/dist/tools/utils.d.ts.map +1 -1
- package/mcp-server/dist/tools/utils.js +1 -1
- package/mcp-server/dist/tools/utils.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.js +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/download.js +1 -1
- package/mcp-server/dist/tools/youtube/download.js.map +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/media.js +1 -1
- package/mcp-server/dist/tools/youtube/media.js.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/source.js +1 -1
- package/mcp-server/dist/tools/youtube/source.js.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.js +4 -9
- package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
- package/mcp-server/dist/tui.js +16 -4
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/package-lock.json +3 -3
- package/mcp-server/package.json +3 -3
- package/mcp-server/src/services/browser-bench.ts +1 -1
- package/mcp-server/src/services/categories.ts +77 -32
- package/mcp-server/src/services/desktop-bench.ts +1 -1
- package/mcp-server/src/tools/agents.ts +1 -1
- package/mcp-server/src/tools/bluesky.ts +1 -1
- package/mcp-server/src/tools/browser-advanced.ts +1 -1
- package/mcp-server/src/tools/browser-batch.ts +1 -1
- package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
- package/mcp-server/src/tools/browser-interact.ts +1 -1
- package/mcp-server/src/tools/browser-navigate.ts +1 -1
- package/mcp-server/src/tools/browser-observe.ts +1 -1
- package/mcp-server/src/tools/card-tools.ts +1 -1
- package/mcp-server/src/tools/category.ts +6 -1
- package/mcp-server/src/tools/choice.ts +1 -1
- package/mcp-server/src/tools/computer.ts +1 -1
- package/mcp-server/src/tools/discord-channel.ts +1 -1
- package/mcp-server/src/tools/discord-guild.ts +1 -1
- package/mcp-server/src/tools/discord-member.ts +1 -1
- package/mcp-server/src/tools/discord-message.ts +1 -1
- package/mcp-server/src/tools/discord-onboarding.ts +1 -1
- package/mcp-server/src/tools/discord-role.ts +1 -1
- package/mcp-server/src/tools/discord-thread.ts +1 -1
- package/mcp-server/src/tools/discord-webhook.ts +1 -1
- package/mcp-server/src/tools/fb-groups.ts +1 -1
- package/mcp-server/src/tools/forget.ts +1 -1
- package/mcp-server/src/tools/git-tools.ts +1 -1
- package/mcp-server/src/tools/gsc/experience.ts +1 -1
- package/mcp-server/src/tools/gsc/indexing.ts +1 -1
- package/mcp-server/src/tools/gsc/inspect.ts +1 -1
- package/mcp-server/src/tools/gsc/navigate.ts +1 -1
- package/mcp-server/src/tools/gsc/performance.ts +1 -1
- package/mcp-server/src/tools/gsc/settings.ts +1 -1
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
- package/mcp-server/src/tools/loop.ts +1 -1
- package/mcp-server/src/tools/memories.ts +1 -1
- package/mcp-server/src/tools/morelogin.ts +1 -1
- package/mcp-server/src/tools/profile.ts +1 -1
- package/mcp-server/src/tools/recall.ts +1 -1
- package/mcp-server/src/tools/reflect.ts +1 -1
- package/mcp-server/src/tools/remember.ts +13 -12
- package/mcp-server/src/tools/restore.ts +1 -1
- package/mcp-server/src/tools/style-guide-tools.ts +1 -1
- package/mcp-server/src/tools/sync.ts +1 -1
- package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
- package/mcp-server/src/tools/utils.ts +1 -1
- package/mcp-server/src/tools/whatsapp-send-file.ts +1 -1
- package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
- package/mcp-server/src/tools/youtube/analytics.ts +1 -1
- package/mcp-server/src/tools/youtube/download.ts +1 -1
- package/mcp-server/src/tools/youtube/media.ts +1 -1
- package/mcp-server/src/tools/youtube/metadata.ts +1 -1
- package/mcp-server/src/tools/youtube/navigate.ts +1 -1
- package/mcp-server/src/tools/youtube/playlist.ts +1 -1
- package/mcp-server/src/tools/youtube/schedule.ts +1 -1
- package/mcp-server/src/tools/youtube/source.ts +1 -1
- package/mcp-server/src/tools/youtube/upload.ts +4 -12
- package/mcp-server/src/tui.ts +22 -9
- package/neural-interface/i18n/en.json +236 -7
- package/neural-interface/i18n/pt-BR.json +238 -9
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
- package/neural-interface/lib/agent-sandbox-setting.js +57 -0
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +40 -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-route-gate-codex.js +3 -1
- package/neural-interface/lib/assistant-runtime.js +131 -8
- package/neural-interface/lib/claude-model-catalog.js +64 -14
- package/neural-interface/lib/codex-account-auth.js +4 -2
- package/neural-interface/lib/codex-accounts.js +14 -1
- package/neural-interface/lib/codex-settings-schema.js +111 -47
- package/neural-interface/lib/editor-completion-opencode.js +132 -35
- package/neural-interface/lib/exec-loop-command.js +11 -2
- package/neural-interface/lib/install-details.js +23 -5
- 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 +24 -4
- 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/provider-panel-terminal-auth.js +3 -2
- package/neural-interface/lib/session-cache-builder.js +26 -5
- package/neural-interface/lib/session-indexer.js +3 -1
- package/neural-interface/lib/session-title-generator.js +20 -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 +1342 -44
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/index.html +2 -2
- package/neural-interface/public/shared/account-menu.js +4 -1
- package/neural-interface/public/shared/account-usage.js +62 -2
- package/neural-interface/public/shared/account-view.js +2 -1
- package/neural-interface/public/shared/agent-runtime-options.js +55 -11
- package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
- package/neural-interface/public/shared/assistant/asst-api.js +18 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +424 -152
- 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 +275 -77
- package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +236 -35
- 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 +109 -17
- 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 +14 -2
- package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-styles.js +49 -5
- package/neural-interface/public/shared/cdx/cdx-tabs.js +152 -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 +11 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +97 -3
- 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 +83 -5
- 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/showcase-privacy.js +2 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
- package/neural-interface/public/shared/styles.css +543 -372
- 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 +126 -12
- 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-explorer.js +2 -0
- 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 +9 -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 +87 -16
- 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/scripts/rebuild-pty.js +14 -2
- package/neural-interface/server.js +1279 -372
- package/npm-shrinkwrap.json +4424 -0
- package/package.json +54 -2
- package/postinstall.js +61 -47
- package/preuninstall.js +8 -35
- package/setup.js +76 -44
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/schedule.md +2 -2
- package/updater.cmd +2 -2
- package/updater.mjs +97 -3
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// SynaBun's own OpenCode serves, on record, and what a crash left of them.
|
|
3
|
+
//
|
|
4
|
+
// Every `opencode serve` SynaBun starts is on record from its spawn until its
|
|
5
|
+
// process has ended: the shared serve (which the session title generator uses
|
|
6
|
+
// too), the isolated serves of panel sessions, loops and Assistant workers and
|
|
7
|
+
// brains, and the code editor's completion serve. One file per process,
|
|
8
|
+
// DATA_HOME/runtime/opencode-serves/<pid>.json (outside data/: process state
|
|
9
|
+
// of this machine, which no backup or restore carries):
|
|
10
|
+
// { v, kind, id, port, pid, startedAt, command, token, scope, owner, recordedAt }
|
|
11
|
+
// `pid`, `startedAt` and `command` are the process's identity as the OS reports
|
|
12
|
+
// it (lib/process-identity.js); `token` is the random token SynaBun put in the
|
|
13
|
+
// serve's environment when it spawned it (SERVE_TOKEN_ENV), which nothing else
|
|
14
|
+
// carries; `scope` is the machine, the user and the boot the record was written
|
|
15
|
+
// in; `owner` is the identity of the SynaBun server that started it. The
|
|
16
|
+
// spawned process is recorded at once (record()), and the process that answers
|
|
17
|
+
// on the port too when the spawned one started it, proved through the parent
|
|
18
|
+
// chain (answering()). No other process is ever recorded: another server that
|
|
19
|
+
// took the port is a start collision, left running and off record.
|
|
20
|
+
//
|
|
21
|
+
// SynaBun stops a recorded process only when it can prove that SynaBun spawned
|
|
22
|
+
// it, on this machine, for this user, in this boot, and that the SynaBun that
|
|
23
|
+
// started it has ended:
|
|
24
|
+
// • the record is whole (identity, port, token, scope, and a command line
|
|
25
|
+
// that serves on its port, read as the platform parses it) and was written
|
|
26
|
+
// on this machine, by this user, in this boot;
|
|
27
|
+
// • the OS still reports the very process recorded (verify()): the same pid
|
|
28
|
+
// and start time, a command line that serves on the port, the user's own,
|
|
29
|
+
// and the token in its environment (Windows, where no other process's
|
|
30
|
+
// environment can be read: the same command line, and a start time to the
|
|
31
|
+
// fraction of a second); read again right before each signal (SIGTERM,
|
|
32
|
+
// then SIGKILL when it stays);
|
|
33
|
+
// • its owner has positively ended: no process runs under the owner's pid,
|
|
34
|
+
// or one with another start time or command line. The running server
|
|
35
|
+
// stops its own serves itself (claimable()), never in the sweep.
|
|
36
|
+
// Anything unknown, unreadable or ambiguous is left running: an owner that was
|
|
37
|
+
// never recorded or cannot be read, a process whose identity, user or
|
|
38
|
+
// environment cannot be read, a record of another machine or user. A record is
|
|
39
|
+
// removed only once its process has provably ended (gone, its pid another
|
|
40
|
+
// process's, or an earlier boot); a record whose process cannot be read stays
|
|
41
|
+
// for the next look. Nothing else is looked for: the machine's processes are
|
|
42
|
+
// never listed and searched for OpenCode, and a process SynaBun did not record
|
|
43
|
+
// is never signalled.
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
47
|
+
import { join } from 'node:path';
|
|
48
|
+
import { spawn } from 'node:child_process';
|
|
49
|
+
import {
|
|
50
|
+
SERVE_TOKEN_ENV, listPortListenersAsync, newServeToken, pidExists, readEnvironmentHas, readEnvironmentHasAsync,
|
|
51
|
+
readHostScope, readHostScopeAsync, readProcessIdentity, readProcessIdentityAsync, readProcessParentAsync, sameProcess,
|
|
52
|
+
} from './process-identity.js';
|
|
53
|
+
|
|
54
|
+
/** The serves SynaBun starts. */
|
|
55
|
+
export const SERVE_KINDS = Object.freeze(['shared', 'isolated', 'completion']);
|
|
56
|
+
export const RECORD_VERSION = 2;
|
|
57
|
+
|
|
58
|
+
const RECORD_FILE = /^(\d+)\.json$/;
|
|
59
|
+
const TOKEN = /^[0-9a-f]{32}$/;
|
|
60
|
+
// A Windows start time to the fraction of a second (CIM's CreationDate, ISO 8601 with 7 digits).
|
|
61
|
+
const FINE_START = /T\d{2}:\d{2}:\d{2}\.\d{4,}/;
|
|
62
|
+
const isPid = (n) => Number.isInteger(n) && n > 0;
|
|
63
|
+
const isPort = (n) => Number.isInteger(n) && n > 0 && n <= 65535;
|
|
64
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
65
|
+
|
|
66
|
+
// ── Command lines, as the platform reads them ───────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A Windows command line split the way a program's C runtime splits it: whitespace parts the arguments, double
|
|
70
|
+
* quotes group them, backslashes are literal except before a quote, and "" inside quotes is a quote.
|
|
71
|
+
* @param {string} line
|
|
72
|
+
*/
|
|
73
|
+
export function windowsArgv(line) {
|
|
74
|
+
const text = String(line ?? '');
|
|
75
|
+
const n = text.length;
|
|
76
|
+
const args = [];
|
|
77
|
+
let i = 0;
|
|
78
|
+
while (i < n && (text[i] === ' ' || text[i] === '\t')) i++;
|
|
79
|
+
if (i >= n) return args;
|
|
80
|
+
// The program's name: up to the next quote when it starts with one, else up to whitespace; no escapes in it.
|
|
81
|
+
let program = '';
|
|
82
|
+
if (text[i] === '"') { i++; while (i < n && text[i] !== '"') program += text[i++]; i++; }
|
|
83
|
+
else { while (i < n && text[i] !== ' ' && text[i] !== '\t') program += text[i++]; }
|
|
84
|
+
args.push(program);
|
|
85
|
+
for (;;) {
|
|
86
|
+
while (i < n && (text[i] === ' ' || text[i] === '\t')) i++;
|
|
87
|
+
if (i >= n) break;
|
|
88
|
+
let arg = '';
|
|
89
|
+
let quoted = false;
|
|
90
|
+
while (i < n) {
|
|
91
|
+
const c = text[i];
|
|
92
|
+
if (!quoted && (c === ' ' || c === '\t')) break;
|
|
93
|
+
if (c === '\\') {
|
|
94
|
+
let k = i;
|
|
95
|
+
while (k < n && text[k] === '\\') k++;
|
|
96
|
+
const count = k - i;
|
|
97
|
+
if (text[k] === '"') {
|
|
98
|
+
arg += '\\'.repeat(Math.floor(count / 2));
|
|
99
|
+
if (count % 2) { arg += '"'; i = k + 1; } else i = k;
|
|
100
|
+
} else { arg += '\\'.repeat(count); i = k; }
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (c === '"') {
|
|
104
|
+
if (quoted && text[i + 1] === '"') { arg += '"'; i += 2; continue; }
|
|
105
|
+
quoted = !quoted;
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
arg += c;
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
args.push(arg);
|
|
113
|
+
}
|
|
114
|
+
return args;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// cmd.exe's reading of the line after /c: a caret takes the next character as it is (an escaped quote opens
|
|
118
|
+
// nothing), quotes group, unescaped whitespace outside quotes parts the words. The words keep their quotes.
|
|
119
|
+
function cmdWords(line) {
|
|
120
|
+
const text = String(line ?? '');
|
|
121
|
+
const words = [];
|
|
122
|
+
let word = '';
|
|
123
|
+
let quoted = false;
|
|
124
|
+
let has = false;
|
|
125
|
+
for (let i = 0; i < text.length; i++) {
|
|
126
|
+
const c = text[i];
|
|
127
|
+
if (!quoted && (c === ' ' || c === '\t')) { if (has) { words.push(word); word = ''; has = false; } continue; }
|
|
128
|
+
has = true;
|
|
129
|
+
if (c === '"') { quoted = !quoted; word += c; continue; }
|
|
130
|
+
if (c === '^' && !quoted && i + 1 < text.length) { word += text[++i]; continue; }
|
|
131
|
+
word += c;
|
|
132
|
+
}
|
|
133
|
+
if (has) words.push(word);
|
|
134
|
+
return words;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The text after cmd.exe's own switches up to /c (or /k), outer quotes taken off as /s does; null when there is none.
|
|
138
|
+
function cmdCommandText(line) {
|
|
139
|
+
const text = String(line ?? '');
|
|
140
|
+
let i = 0;
|
|
141
|
+
const n = text.length;
|
|
142
|
+
while (i < n && /\s/.test(text[i])) i++;
|
|
143
|
+
if (text[i] === '"') { i = text.indexOf('"', i + 1); if (i < 0) return null; i++; }
|
|
144
|
+
else while (i < n && !/\s/.test(text[i])) i++;
|
|
145
|
+
for (;;) {
|
|
146
|
+
while (i < n && /\s/.test(text[i])) i++;
|
|
147
|
+
if (text[i] !== '/') return null;
|
|
148
|
+
let j = i;
|
|
149
|
+
while (j < n && !/\s/.test(text[j])) j++;
|
|
150
|
+
const word = text.slice(i, j).toLowerCase();
|
|
151
|
+
i = j;
|
|
152
|
+
if (word === '/c' || word === '/k') break;
|
|
153
|
+
}
|
|
154
|
+
let rest = text.slice(i).trim();
|
|
155
|
+
if (rest.length >= 2 && rest.startsWith('"') && rest.endsWith('"')) rest = rest.slice(1, -1);
|
|
156
|
+
return rest;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const windowsBase = (path) => String(path ?? '').split(/[\\/]/).pop() || '';
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The arguments a command line holds, as the platform reads it. Windows: the C runtime's split; a cmd.exe that
|
|
163
|
+
* runs a command (`cmd.exe /d /s /c "…"`, how a command file is started: lib/cli-resolver.js spawnArgsFor) is read
|
|
164
|
+
* through to what it runs: its carets once, and once more for a command file (.cmd, .bat), which reads its own
|
|
165
|
+
* arguments again. Elsewhere: the words `ps` prints.
|
|
166
|
+
* @param {string} command
|
|
167
|
+
* @param {string} [platform]
|
|
168
|
+
*/
|
|
169
|
+
export function commandArgs(command, platform = process.platform) {
|
|
170
|
+
const text = str(command).trim();
|
|
171
|
+
if (!text) return [];
|
|
172
|
+
if (platform !== 'win32') return text.split(/\s+/);
|
|
173
|
+
const argv = windowsArgv(text);
|
|
174
|
+
if (!/^cmd(?:\.exe)?$/i.test(windowsBase(argv[0]))) return argv;
|
|
175
|
+
const inner = cmdCommandText(text);
|
|
176
|
+
if (inner == null) return argv;
|
|
177
|
+
const words = cmdWords(inner);
|
|
178
|
+
if (!words.length) return argv;
|
|
179
|
+
// The program cmd.exe runs: its first word, quotes only group it (an escaped space is part of it).
|
|
180
|
+
const target = words[0].replace(/"/g, '');
|
|
181
|
+
const rest = /\.(?:cmd|bat)$/i.test(target) ? words.slice(1).map((word) => cmdWords(word).join(' ')) : words.slice(1);
|
|
182
|
+
return [target, ...windowsArgv(`x ${rest.join(' ')}`).slice(1)];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Is `command` the command line of a serve on `port` (`… serve … --port <port>`), its arguments read as the platform reads them? */
|
|
186
|
+
export function servesOnPort(command, port, platform = process.platform) {
|
|
187
|
+
if (!isPort(Number(port))) return false;
|
|
188
|
+
const args = commandArgs(command, platform);
|
|
189
|
+
if (!args.includes('serve')) return false;
|
|
190
|
+
const p = String(Number(port));
|
|
191
|
+
return args.some((arg, i) => arg === `--port=${p}` || (arg === '--port' && args[i + 1] === p));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const wholeScope = (s) => !!s && typeof s === 'object' && !!str(s.machine) && !!str(s.uid) && !!str(s.boot);
|
|
195
|
+
const sameScope = (a, b) => wholeScope(a) && wholeScope(b) && a.machine === b.machine && a.uid === b.uid && a.boot === b.boot;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* A record that proves which process it names: its identity whole, its port, the token of its spawn, where it was
|
|
199
|
+
* written (machine, user, boot), and a command line that serves on its port.
|
|
200
|
+
*/
|
|
201
|
+
export function wholeRecord(record, platform = process.platform) {
|
|
202
|
+
if (!record || typeof record !== 'object') return false;
|
|
203
|
+
if (!SERVE_KINDS.includes(record.kind) || !isPid(record.pid) || !isPort(record.port)) return false;
|
|
204
|
+
if (!str(record.startedAt) || !str(record.command) || !TOKEN.test(str(record.token)) || !wholeScope(record.scope)) return false;
|
|
205
|
+
return servesOnPort(record.command, record.port, platform);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** An owner that can be told apart from any other process: its pid, start time and command line. */
|
|
209
|
+
const wholeOwner = (owner) => !!owner && isPid(owner.pid) && !!str(owner.startedAt) && !!str(owner.command);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {object} o
|
|
213
|
+
* @param {string} o.dir the folder of the records (DATA_HOME/runtime/opencode-serves)
|
|
214
|
+
* @param {() => object | null} [o.self] the identity of the running SynaBun server (read once, when first needed)
|
|
215
|
+
* @param {() => object} [o.scope] `{ machine, uid, boot }` here (lib/process-identity.js readHostScope)
|
|
216
|
+
* @param {() => Promise<object>} [o.scopeAsync] the same, without holding the event loop
|
|
217
|
+
* @param {(pid: number) => object | null} [o.readIdentity] read right before a signal (lib/process-identity.js)
|
|
218
|
+
* @param {(pid: number) => Promise<object | null>} [o.readIdentityAsync] every other read
|
|
219
|
+
* @param {(pid: number, token: string, command: string) => boolean | null} [o.readToken] is the token in its environment?
|
|
220
|
+
* @param {(pid: number, token: string, command: string) => Promise<boolean | null>} [o.readTokenAsync]
|
|
221
|
+
* @param {(port: number) => Promise<number[] | null>} [o.listeners] who listens on a port (null: cannot be told)
|
|
222
|
+
* @param {(pid: number) => Promise<{ pid: number, ppid: number, startedAt: string } | null>} [o.parentOf]
|
|
223
|
+
* @param {(pid: number) => boolean | null} [o.exists] does a process run under the pid (null: cannot be told)
|
|
224
|
+
* @param {(pid: number, signal: string) => void} [o.kill] the signal itself (Windows: taskkill /T /F, any signal)
|
|
225
|
+
* @param {number} [o.selfPid] process.pid
|
|
226
|
+
*/
|
|
227
|
+
export function createServeRecords({
|
|
228
|
+
dir,
|
|
229
|
+
selfPid = process.pid,
|
|
230
|
+
self = () => readProcessIdentity(selfPid),
|
|
231
|
+
scope = null,
|
|
232
|
+
scopeAsync = null,
|
|
233
|
+
readIdentity = readProcessIdentity,
|
|
234
|
+
readIdentityAsync = readProcessIdentityAsync,
|
|
235
|
+
readToken = (pid, token, command) => readEnvironmentHas(pid, SERVE_TOKEN_ENV, token, { command }),
|
|
236
|
+
readTokenAsync = (pid, token, command) => readEnvironmentHasAsync(pid, SERVE_TOKEN_ENV, token, { command }),
|
|
237
|
+
listeners = (port) => listPortListenersAsync(port),
|
|
238
|
+
parentOf = (pid) => readProcessParentAsync(pid),
|
|
239
|
+
exists = pidExists,
|
|
240
|
+
platform = process.platform,
|
|
241
|
+
kill = null,
|
|
242
|
+
newToken = newServeToken,
|
|
243
|
+
now = () => Date.now(),
|
|
244
|
+
sleep = (ms) => new Promise((done) => { const t = setTimeout(done, ms); t.unref?.(); }),
|
|
245
|
+
stopWaitMs = 6000,
|
|
246
|
+
killWaitMs = 3000,
|
|
247
|
+
pollMs = 100,
|
|
248
|
+
log = () => {},
|
|
249
|
+
} = {}) {
|
|
250
|
+
if (!dir) throw new Error('createServeRecords requires dir');
|
|
251
|
+
const readScope = scope || (() => readHostScope());
|
|
252
|
+
const readScopeAsync = scopeAsync || (scope ? async () => scope() : () => readHostScopeAsync());
|
|
253
|
+
const signal = kill || ((pid, sig) => {
|
|
254
|
+
if (platform === 'win32') {
|
|
255
|
+
// The serve and what it started: Windows has no signals to send.
|
|
256
|
+
spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }).on('error', () => {});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
process.kill(pid, sig);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Where this server runs: read once it is first needed, and again (at most once a minute) while the OS could not
|
|
263
|
+
// tell all three. Until then nothing is recorded and nothing is stopped.
|
|
264
|
+
let here = null;
|
|
265
|
+
let hereTriedAt = -Infinity;
|
|
266
|
+
const wholeOrNull = (s) => (wholeScope(s) ? { machine: s.machine, uid: s.uid, boot: s.boot } : null);
|
|
267
|
+
const asScope = (s) => wholeOrNull(s && { machine: s.machine == null ? '' : String(s.machine), uid: s.uid == null ? '' : String(s.uid), boot: s.boot == null ? '' : String(s.boot) });
|
|
268
|
+
function scopeNow() {
|
|
269
|
+
if (!here && now() - hereTriedAt >= 60_000) {
|
|
270
|
+
hereTriedAt = now();
|
|
271
|
+
try { here = asScope(readScope()); } catch { here = null; }
|
|
272
|
+
}
|
|
273
|
+
return here;
|
|
274
|
+
}
|
|
275
|
+
async function scopeLater() {
|
|
276
|
+
if (!here && now() - hereTriedAt >= 60_000) {
|
|
277
|
+
hereTriedAt = now();
|
|
278
|
+
try { here = asScope(await readScopeAsync()); } catch { here = null; }
|
|
279
|
+
}
|
|
280
|
+
return here;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The running server's own identity: read once it is first needed, and again (at most once a minute) while the OS
|
|
284
|
+
// could not be asked. Until then the owner is its pid alone, which a later run never takes for proof.
|
|
285
|
+
let selfIdentity = null;
|
|
286
|
+
let selfTriedAt = -Infinity;
|
|
287
|
+
function owner() {
|
|
288
|
+
if (!selfIdentity && now() - selfTriedAt >= 60_000) {
|
|
289
|
+
selfTriedAt = now();
|
|
290
|
+
try { selfIdentity = self() || null; } catch { selfIdentity = null; }
|
|
291
|
+
}
|
|
292
|
+
return selfIdentity && selfIdentity.pid === selfPid ? selfIdentity : { pid: selfPid };
|
|
293
|
+
}
|
|
294
|
+
// The tokens of the records this server wrote: those serves are its own.
|
|
295
|
+
const issued = new Set();
|
|
296
|
+
/** Is this record the running server's own: one it wrote, or one whose owner is this very process, here? */
|
|
297
|
+
function ownedHere(record) {
|
|
298
|
+
if (!record || typeof record !== 'object') return false;
|
|
299
|
+
if (str(record.token) && issued.has(record.token)) return true;
|
|
300
|
+
const o = record.owner;
|
|
301
|
+
const me = owner();
|
|
302
|
+
return wholeOwner(o) && wholeOwner(me) && o.pid === selfPid && sameProcess(o, me) && sameScope(record.scope, scopeNow());
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const fileOf = (pid) => join(dir, `${pid}.json`);
|
|
306
|
+
function readFile(name) {
|
|
307
|
+
try { return JSON.parse(readFileSync(join(dir, name), 'utf8')); } catch { return null; }
|
|
308
|
+
}
|
|
309
|
+
function unlink(name) {
|
|
310
|
+
try { unlinkSync(join(dir, name)); return true; } catch { return false; }
|
|
311
|
+
}
|
|
312
|
+
/** Every record on file, with its file name; `record` null for a file that is not a record. */
|
|
313
|
+
function entries() {
|
|
314
|
+
let names = [];
|
|
315
|
+
try { names = readdirSync(dir); } catch { return []; }
|
|
316
|
+
return names.filter((name) => RECORD_FILE.test(name)).map((name) => ({ name, record: readFile(name) }));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Is it still that process? ──
|
|
320
|
+
|
|
321
|
+
// The record against where this server runs: 'here', 'gone' (an earlier boot of this machine, for this user:
|
|
322
|
+
// every process of it has ended) or 'unknown' (another machine's or user's, or where cannot be told).
|
|
323
|
+
function whereVerdict(record) {
|
|
324
|
+
const at = scopeNow();
|
|
325
|
+
const there = record?.scope;
|
|
326
|
+
if (!at || !wholeScope(there)) return 'unknown';
|
|
327
|
+
if (there.machine !== at.machine || there.uid !== at.uid) return 'unknown';
|
|
328
|
+
return there.boot === at.boot ? 'here' : 'gone';
|
|
329
|
+
}
|
|
330
|
+
// What the OS reports for the pid now, against the record: 'same', 'gone', 'unknown', or 'token' (the token in its
|
|
331
|
+
// environment decides); null when nothing could be read for the pid.
|
|
332
|
+
function identityVerdict(record, current) {
|
|
333
|
+
if (!current) return null;
|
|
334
|
+
if (String(current.startedAt) !== String(record.startedAt)) return 'gone';
|
|
335
|
+
if (platform === 'win32') {
|
|
336
|
+
if (String(current.command) !== String(record.command)) return 'gone';
|
|
337
|
+
return FINE_START.test(String(record.startedAt)) ? 'same' : 'unknown';
|
|
338
|
+
}
|
|
339
|
+
if (current.uid == null || current.uid === '') return 'unknown';
|
|
340
|
+
if (String(current.uid) !== record.scope.uid) return 'gone';
|
|
341
|
+
if (!servesOnPort(current.command, record.port, platform)) return 'gone';
|
|
342
|
+
return 'token';
|
|
343
|
+
}
|
|
344
|
+
const tokenVerdict = (has) => (has === true ? 'same' : has === false ? 'gone' : 'unknown');
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Is the record's process still the very one recorded? 'same'; 'gone' (provably not: it has ended, its pid is
|
|
348
|
+
* another process's, or it was of an earlier boot); 'unknown' (it cannot be told: nothing is done on it, and its
|
|
349
|
+
* record stays). Read in full each time, right before a signal included.
|
|
350
|
+
*/
|
|
351
|
+
function verifySync(record) {
|
|
352
|
+
if (!wholeRecord(record, platform)) return 'unknown';
|
|
353
|
+
const where = whereVerdict(record);
|
|
354
|
+
if (where !== 'here') return where;
|
|
355
|
+
const current = readIdentity(record.pid);
|
|
356
|
+
const verdict = identityVerdict(record, current);
|
|
357
|
+
if (verdict === null) return exists(record.pid) === false ? 'gone' : 'unknown';
|
|
358
|
+
if (verdict !== 'token') return verdict;
|
|
359
|
+
return tokenVerdict(readToken(record.pid, record.token, current.command));
|
|
360
|
+
}
|
|
361
|
+
/** verifySync without holding the event loop while the OS answers. */
|
|
362
|
+
async function verify(record) {
|
|
363
|
+
if (!wholeRecord(record, platform)) return 'unknown';
|
|
364
|
+
await scopeLater();
|
|
365
|
+
const where = whereVerdict(record);
|
|
366
|
+
if (where !== 'here') return where;
|
|
367
|
+
const current = await readIdentityAsync(record.pid);
|
|
368
|
+
const verdict = identityVerdict(record, current);
|
|
369
|
+
if (verdict === null) return exists(record.pid) === false ? 'gone' : 'unknown';
|
|
370
|
+
if (verdict !== 'token') return verdict;
|
|
371
|
+
return tokenVerdict(await readTokenAsync(record.pid, record.token, current.command));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Has the record's owner ended? 'ended' (positively: no process under its pid, or the one there now has another
|
|
375
|
+
// start time or command line), 'running', or 'unknown' (no owner, a bare pid that runs, a read that failed).
|
|
376
|
+
function ownerVerdict(o, current, gone) {
|
|
377
|
+
if (current) return wholeOwner(o) ? (sameProcess(o, current) ? 'running' : 'ended') : 'unknown';
|
|
378
|
+
return gone ? 'ended' : 'unknown';
|
|
379
|
+
}
|
|
380
|
+
function ownerEndedSync(o) {
|
|
381
|
+
if (!o || !isPid(o.pid)) return 'unknown';
|
|
382
|
+
if (exists(o.pid) === false) return 'ended';
|
|
383
|
+
if (!wholeOwner(o)) return 'unknown';
|
|
384
|
+
const current = readIdentity(o.pid);
|
|
385
|
+
return ownerVerdict(o, current, !current && exists(o.pid) === false);
|
|
386
|
+
}
|
|
387
|
+
async function ownerEnded(o) {
|
|
388
|
+
if (!o || !isPid(o.pid)) return 'unknown';
|
|
389
|
+
if (exists(o.pid) === false) return 'ended';
|
|
390
|
+
if (!wholeOwner(o)) return 'unknown';
|
|
391
|
+
const current = await readIdentityAsync(o.pid);
|
|
392
|
+
return ownerVerdict(o, current, !current && exists(o.pid) === false);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* May the running server stop this record's process (once verify() says it is still that process)? Only a whole
|
|
397
|
+
* record written here (this machine, user and boot) that is its own, or whose SynaBun has positively ended. Never
|
|
398
|
+
* another live SynaBun's, one whose owner was never recorded or cannot be read, one of another machine or user.
|
|
399
|
+
*/
|
|
400
|
+
function claimable(record) {
|
|
401
|
+
if (!wholeRecord(record, platform) || whereVerdict(record) !== 'here') return false;
|
|
402
|
+
return ownedHere(record) || ownerEndedSync(record.owner) === 'ended';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── On record ──
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Record a process of a serve SynaBun started: the one it spawned, at once, and the one that answers on the port
|
|
409
|
+
* when the spawned one started it (answering()). Its identity is read now, so that a later run can tell it from
|
|
410
|
+
* whatever gets its pid afterwards. Nothing is recorded that could never be shown to be this start's: a process
|
|
411
|
+
* whose identity cannot be read, whose command line is not a serve on `port`, that is another user's, that does
|
|
412
|
+
* not carry this start's token (macOS, Linux), or whose start time is not to the fraction of a second (Windows);
|
|
413
|
+
* nor while the machine, the user or the boot cannot be told. A live record of another start under the same pid
|
|
414
|
+
* is never written over. Resolves the record or null.
|
|
415
|
+
*/
|
|
416
|
+
async function record({ kind, id = null, port, pid, token } = {}) {
|
|
417
|
+
const n = Number(pid);
|
|
418
|
+
if (!SERVE_KINDS.includes(kind) || !isPid(n) || !isPort(Number(port)) || !TOKEN.test(str(token))) return null;
|
|
419
|
+
const at = await scopeLater();
|
|
420
|
+
if (!at) { log(`no record for the ${kind} serve on port ${port}: the machine, the user or the boot could not be read`); return null; }
|
|
421
|
+
const identity = await readIdentityAsync(n);
|
|
422
|
+
if (!identity) { log(`no record for the ${kind} serve on port ${port}: the identity of process ${n} could not be read`); return null; }
|
|
423
|
+
const entry = {
|
|
424
|
+
v: RECORD_VERSION, kind, id: id == null ? null : String(id), port: Number(port),
|
|
425
|
+
pid: n, startedAt: identity.startedAt, command: identity.command, token,
|
|
426
|
+
scope: { ...at }, owner: owner(), recordedAt: new Date(now()).toISOString(),
|
|
427
|
+
};
|
|
428
|
+
if (!wholeRecord(entry, platform)) { log(`no record for the ${kind} serve on port ${port}: process ${n} is not a serve on that port (${identity.command})`); return null; }
|
|
429
|
+
if (platform === 'win32') {
|
|
430
|
+
if (!FINE_START.test(entry.startedAt)) { log(`no record for the ${kind} serve on port ${port}: the start time of process ${n} is not precise enough to tell it apart`); return null; }
|
|
431
|
+
} else {
|
|
432
|
+
if (String(identity.uid ?? '') !== at.uid) { log(`no record for the ${kind} serve on port ${port}: process ${n} is not this user's`); return null; }
|
|
433
|
+
if (await readTokenAsync(n, token, identity.command) !== true) { log(`no record for the ${kind} serve on port ${port}: process ${n} does not carry this start's token`); return null; }
|
|
434
|
+
}
|
|
435
|
+
const onFile = readFile(`${n}.json`);
|
|
436
|
+
if (onFile && str(onFile.token) && onFile.token !== token && !issued.has(onFile.token) && await verify(onFile) === 'same') {
|
|
437
|
+
log(`no record for the ${kind} serve on port ${port}: process ${n} is on record for another start`);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
if (!write(entry)) return null;
|
|
441
|
+
issued.add(token);
|
|
442
|
+
return entry;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function write(entry) {
|
|
446
|
+
try {
|
|
447
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
448
|
+
const path = fileOf(entry.pid);
|
|
449
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
450
|
+
writeFileSync(tmp, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
451
|
+
renameSync(tmp, path);
|
|
452
|
+
return true;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
log(`could not write the record of the ${entry.kind} serve on port ${entry.port}: ${error?.message || error}`);
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Remove a record: only that very one (its pid and its spawn's token), never another start's under the same pid. */
|
|
460
|
+
function remove(entry) {
|
|
461
|
+
if (!entry || !isPid(Number(entry.pid))) return false;
|
|
462
|
+
const name = `${Number(entry.pid)}.json`;
|
|
463
|
+
const onFile = readFile(name);
|
|
464
|
+
if (!onFile) return false;
|
|
465
|
+
const same = str(entry.token) ? onFile.token === entry.token : sameProcess(onFile, entry);
|
|
466
|
+
return same ? unlink(name) : false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** The whole records on file, the newest first. `kind` / `port` narrow them. */
|
|
470
|
+
function list({ kind = null, port = null } = {}) {
|
|
471
|
+
return entries()
|
|
472
|
+
.map(({ record: r }) => r)
|
|
473
|
+
.filter((r) => wholeRecord(r, platform) && (!kind || r.kind === kind) && (port == null || r.port === Number(port)))
|
|
474
|
+
.sort((a, b) => String(b.recordedAt || '').localeCompare(String(a.recordedAt || '')));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Did `ancestor` start `pid` (or a process that did)? true, false, or null (a link could not be read: nothing is
|
|
478
|
+
// proved). A parent never starts after its child: Windows keeps a parent's pid after that parent has ended, and
|
|
479
|
+
// the pid may be another process's by now.
|
|
480
|
+
async function descendsFrom(pid, ancestor) {
|
|
481
|
+
let at = await parentOf(pid);
|
|
482
|
+
for (let depth = 0; at && depth < 8; depth++) {
|
|
483
|
+
const ppid = Number(at.ppid);
|
|
484
|
+
if (!isPid(ppid) || ppid === 1) return false;
|
|
485
|
+
const up = await parentOf(ppid);
|
|
486
|
+
if (!up) return null;
|
|
487
|
+
if (platform === 'win32' && String(up.startedAt) > String(at.startedAt)) return false;
|
|
488
|
+
if (ppid === ancestor) return true;
|
|
489
|
+
at = up;
|
|
490
|
+
}
|
|
491
|
+
return at ? false : null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Who answers on `port` for a start whose spawned process is `pid`: `{ state: 'ours', pid }` (the spawned process,
|
|
496
|
+
* or one it started, proved through the parent chain: the pid to record), `{ state: 'foreign' }` (another process
|
|
497
|
+
* holds the port, or the spawned one has ended: a start collision) or `{ state: 'unknown' }` (the OS could not say
|
|
498
|
+
* who listens: only the spawned process stays on record).
|
|
499
|
+
* @param {{ port: number, pid: number, exited?: () => boolean }} o
|
|
500
|
+
*/
|
|
501
|
+
async function answering({ port, pid, exited = () => false } = {}) {
|
|
502
|
+
const n = Number(pid);
|
|
503
|
+
if (!isPid(n) || exited()) return { state: 'foreign' };
|
|
504
|
+
const pids = await listeners(Number(port));
|
|
505
|
+
if (exited()) return { state: 'foreign' };
|
|
506
|
+
if (!Array.isArray(pids)) return { state: 'unknown' };
|
|
507
|
+
if (!pids.length) return { state: 'foreign' };
|
|
508
|
+
let ours = null;
|
|
509
|
+
for (const listener of pids) {
|
|
510
|
+
if (listener !== n && await descendsFrom(listener, n) !== true) return { state: 'foreign', pids };
|
|
511
|
+
ours = listener === n ? n : (ours ?? listener);
|
|
512
|
+
}
|
|
513
|
+
return { state: 'ours', pid: ours };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ── What a crash left running ──
|
|
517
|
+
|
|
518
|
+
// The signal, only while the OS still reports the very process recorded: read once more right before. 'signalled',
|
|
519
|
+
// 'gone' (it is provably not that process now) or 'unknown' (the read or the signal failed: nothing is known).
|
|
520
|
+
function signalIfSame(entry, sig) {
|
|
521
|
+
const verdict = verifySync(entry);
|
|
522
|
+
if (verdict !== 'same') return verdict;
|
|
523
|
+
try { signal(entry.pid, sig); return 'signalled'; } catch (error) { return error?.code === 'ESRCH' ? 'gone' : 'unknown'; }
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function goneWithin(entry, ms) {
|
|
527
|
+
const deadline = now() + ms;
|
|
528
|
+
for (;;) {
|
|
529
|
+
if (await verify(entry) === 'gone') return true;
|
|
530
|
+
if (now() >= deadline) return false;
|
|
531
|
+
await sleep(pollMs);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Stop a leftover serve: SIGTERM, then SIGKILL when it stays. 'stopped', 'gone' (it was provably no longer that
|
|
536
|
+
// process when the signal was due), 'unknown' (it could not be read right before the signal: not signalled) or
|
|
537
|
+
// 'survived'.
|
|
538
|
+
async function stopLeftover(entry) {
|
|
539
|
+
const term = signalIfSame(entry, 'SIGTERM');
|
|
540
|
+
if (term !== 'signalled') return term;
|
|
541
|
+
if (await goneWithin(entry, stopWaitMs)) return 'stopped';
|
|
542
|
+
const hard = signalIfSame(entry, 'SIGKILL');
|
|
543
|
+
if (hard === 'gone') return 'stopped';
|
|
544
|
+
if (hard !== 'signalled') return 'survived';
|
|
545
|
+
return (await goneWithin(entry, killWaitMs)) ? 'stopped' : 'survived';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function sweepOnce() {
|
|
549
|
+
const result = { stopped: [], dropped: [], kept: [] };
|
|
550
|
+
const files = entries();
|
|
551
|
+
const at = await scopeLater();
|
|
552
|
+
if (!at) {
|
|
553
|
+
if (files.length) log('the machine, the user or the boot could not be read: no leftover serve is looked at now');
|
|
554
|
+
for (const { name } of files) result.kept.push({ pid: Number(RECORD_FILE.exec(name)?.[1]), why: 'scope-unknown' });
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
for (const { name, record: entry } of files) {
|
|
558
|
+
const pid = Number(RECORD_FILE.exec(name)?.[1]);
|
|
559
|
+
const about = (why) => ({ pid, kind: entry.kind, port: entry.port, why });
|
|
560
|
+
if (!entry || typeof entry !== 'object') { unlink(name); result.dropped.push({ pid, why: 'not-a-record' }); continue; }
|
|
561
|
+
// Another machine's or another user's (a data home copied or shared): not this one's to judge, nor to tidy.
|
|
562
|
+
const there = entry.scope;
|
|
563
|
+
if (wholeScope(there) && there.machine !== at.machine) { result.kept.push(about('other-machine')); continue; }
|
|
564
|
+
if (wholeScope(there) && there.uid !== at.uid) { result.kept.push(about('other-user')); continue; }
|
|
565
|
+
// The running server's own serves: its own stops end them, and their exits remove their records.
|
|
566
|
+
if (ownedHere(entry)) continue;
|
|
567
|
+
// A file that is no whole record proves nothing: it goes, and nothing is signalled.
|
|
568
|
+
if (!wholeRecord(entry, platform) || entry.pid !== pid) { unlink(name); result.dropped.push({ pid, why: 'not-a-record' }); continue; }
|
|
569
|
+
// An earlier boot ended every process it had: the record goes, and nothing is signalled.
|
|
570
|
+
if (there.boot !== at.boot) { remove(entry); result.dropped.push(about('earlier-boot')); continue; }
|
|
571
|
+
const serveNow = await verify(entry);
|
|
572
|
+
// Ended, or its pid is another program's by now: the record goes, and nothing is signalled.
|
|
573
|
+
if (serveNow === 'gone') { remove(entry); result.dropped.push(about('process-gone')); continue; }
|
|
574
|
+
if (serveNow !== 'same') { result.kept.push(about('identity-unknown')); continue; }
|
|
575
|
+
const ownerNow = await ownerEnded(entry.owner);
|
|
576
|
+
if (ownerNow !== 'ended') { result.kept.push(about(ownerNow === 'running' ? 'owner-running' : 'owner-unknown')); continue; }
|
|
577
|
+
// SynaBun's own serve, started by a SynaBun that has ended: nothing uses it any more.
|
|
578
|
+
log(`stopping a ${entry.kind} serve an earlier SynaBun left running on port ${entry.port} (pid ${entry.pid})`);
|
|
579
|
+
const outcome = await stopLeftover(entry);
|
|
580
|
+
if (outcome === 'stopped') { remove(entry); result.stopped.push(about('leftover')); continue; }
|
|
581
|
+
if (outcome === 'gone') { remove(entry); result.dropped.push(about('process-gone')); continue; }
|
|
582
|
+
// Not seen to end, or not readable right before the signal: it stays on record for the next look.
|
|
583
|
+
result.kept.push(about(outcome === 'survived' ? 'survived' : 'identity-unknown'));
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
let sweeping = null;
|
|
589
|
+
/** Look for the serves an earlier SynaBun left running, and stop them (see above). One look at a time. */
|
|
590
|
+
function sweep() {
|
|
591
|
+
if (!sweeping) {
|
|
592
|
+
sweeping = sweepOnce()
|
|
593
|
+
.catch((error) => { log(`the look for leftover serves failed: ${error?.message || error}`); return { stopped: [], dropped: [], kept: [] }; })
|
|
594
|
+
.finally(() => { sweeping = null; });
|
|
595
|
+
}
|
|
596
|
+
return sweeping;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
dir, record, remove, list, verify, verifySync, claimable, ownedHere, answering, sweep,
|
|
601
|
+
/** A fresh token for one spawn, to put in its environment as SERVE_TOKEN_ENV. */
|
|
602
|
+
newToken: () => newToken(),
|
|
603
|
+
/** Where this server runs: `{ machine, uid, boot }`, or null while the OS cannot tell all three. */
|
|
604
|
+
scope: scopeNow,
|
|
605
|
+
};
|
|
606
|
+
}
|