synabun 2.0.5 → 2.0.9
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 +78 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/post-plan.mjs +6 -30
- package/hooks/claude-code/session-start.mjs +1 -4
- package/hooks/claude-code/shared.mjs +112 -75
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/package-processes.js +670 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/runtime-dependencies.js +225 -0
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -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/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- 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/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.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/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +11 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +35 -14
- 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 +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- 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 +18 -5
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +3 -3
- package/mcp-server/package.json +3 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/browser-bench.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/categories.ts +77 -32
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/desktop-bench.ts +1 -1
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +34 -15
- 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 +51 -0
- 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 +24 -10
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +645 -57
- package/neural-interface/i18n/pt-BR.json +644 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
- package/neural-interface/lib/agent-sandbox-setting.js +57 -0
- package/neural-interface/lib/assistant-api.js +274 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +312 -48
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/codex-account-auth.js +180 -0
- package/neural-interface/lib/codex-accounts.js +502 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +122 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/exec-loop-command.js +3 -1
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-read.js +34 -2
- package/neural-interface/lib/install-details.js +163 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +22 -4
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +96 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/session-indexer.js +3 -1
- package/neural-interface/lib/session-title-generator.js +6 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +542 -0
- package/neural-interface/public/shared/account-usage.js +79 -0
- package/neural-interface/public/shared/account-view.js +336 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +39 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
- package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +49 -108
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
- package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
- package/neural-interface/public/shared/pwa-install-page.js +3 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +490 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +281 -32
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +262 -217
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-explorer.js +2 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +8 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +598 -7
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/scripts/rebuild-pty.js +14 -2
- package/neural-interface/server.js +1384 -679
- package/npm-shrinkwrap.json +4424 -0
- package/package.json +57 -2
- package/postinstall.js +61 -47
- package/preuninstall.js +8 -35
- package/setup.js +80 -47
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
- package/updater.cmd +2 -2
- package/updater.mjs +97 -3
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynaBun — stop SynaBun's own processes before npm replaces its folder (Windows).
|
|
3
|
+
*
|
|
4
|
+
* `npm i -g synabun@<version>` renames the installed package folder away and
|
|
5
|
+
* extracts the new one. Windows refuses to rename or remove a folder while a
|
|
6
|
+
* process runs from it or holds one of its native modules loaded
|
|
7
|
+
* (onnxruntime_binding.node, onnxruntime.dll, DirectML.dll, sharp's libvips),
|
|
8
|
+
* and npm then fails with EPERM, EBUSY or ENOTEMPTY. The SynaBun server stops
|
|
9
|
+
* its own children before an update, but the MCP servers an AI client started
|
|
10
|
+
* (Claude Code, Codex, Cursor, OpenCode, ...) and their embedding workers are
|
|
11
|
+
* not its children: they keep running from the folder.
|
|
12
|
+
*
|
|
13
|
+
* What counts as one of SynaBun's processes (findPackageProcesses):
|
|
14
|
+
* - any process whose executable is inside the package root;
|
|
15
|
+
* - a node process whose entrypoint is inside it: the script Node runs (the
|
|
16
|
+
* first argument after node's own options: `node <root>\mcp-server\run.mjs`,
|
|
17
|
+
* the embedding worker it forks, a hook) or a module it preloads (-r,
|
|
18
|
+
* --require, --import, --loader). No other argument decides: a config file,
|
|
19
|
+
* a data folder, a --prefix or a working folder inside the package does not
|
|
20
|
+
* make `node C:\tools\client\cli.js --mcp-config <root>\.mcp.json` (an AI
|
|
21
|
+
* client) or `npm --prefix <root> ...` one of SynaBun's processes.
|
|
22
|
+
* Paths compare in every form they have: as written (`\\?\` and `\\?\UNC\`
|
|
23
|
+
* taken off, either slash, any case) and as the real path (junctions, symbolic
|
|
24
|
+
* links and 8.3 short names resolved), for the root and for each process. Only
|
|
25
|
+
* on a whole folder name: `...\synabun-foo`, `...\synabun - Copy`, a checkout
|
|
26
|
+
* and another install are someone else's. Never the process that asks, the
|
|
27
|
+
* processes it descends from (its terminal among them) or the ones it started.
|
|
28
|
+
*
|
|
29
|
+
* The process list comes from one fixed PowerShell command (Get-CimInstance
|
|
30
|
+
* Win32_Process; wmic is gone from current Windows 11) that takes no input at
|
|
31
|
+
* all: the root and the PIDs never reach PowerShell, the matching happens here.
|
|
32
|
+
* Before each kill the list is read again, and a process whose creation time
|
|
33
|
+
* changed (Windows gave its PID to another one) is left alone.
|
|
34
|
+
*
|
|
35
|
+
* Used by updater.mjs (staged next to it, in lib/, by server.js
|
|
36
|
+
* stageSynabunUpdater) and preuninstall.js. Only node: modules and
|
|
37
|
+
* ./runtime-dependencies.js (which has only node: modules too): the staged copy
|
|
38
|
+
* has nothing else next to it.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { spawnSync } from 'node:child_process';
|
|
42
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
43
|
+
import { win32 } from 'node:path';
|
|
44
|
+
import { fileURLToPath } from 'node:url';
|
|
45
|
+
import { newestDebugLog } from './runtime-dependencies.js';
|
|
46
|
+
|
|
47
|
+
/** How npm says a file or folder is in use on Windows: the codes, and the errno npm exits with for each. */
|
|
48
|
+
export const WINDOWS_LOCK_ERRNO = Object.freeze({ EPERM: -4048, EBUSY: -4082, ENOTEMPTY: -4051 });
|
|
49
|
+
|
|
50
|
+
/** How many times npm runs again after a lock failure (each time after another stop). */
|
|
51
|
+
export const LOCK_RETRIES = 2;
|
|
52
|
+
|
|
53
|
+
/** How long the stop waits for the processes it ended to be gone. */
|
|
54
|
+
export const STOP_WAIT_MS = 10_000;
|
|
55
|
+
|
|
56
|
+
/** The longest the process list may take. */
|
|
57
|
+
export const LIST_TIMEOUT_MS = 30_000;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The most the whole stop step may take: every list (each up to LIST_TIMEOUT_MS), kill (up to 15 s) and wait of
|
|
61
|
+
* every pass the updater makes. Generous (a pass takes seconds), but never endless: past it nothing more is
|
|
62
|
+
* stopped, and the updater ends with what to close by hand.
|
|
63
|
+
*/
|
|
64
|
+
export const STOP_DEADLINE_MS = 180_000;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The stop step's time limit, counted only while a stop pass runs: `run(pass)`
|
|
68
|
+
* hands the pass an absolute deadline made of what the earlier passes left,
|
|
69
|
+
* then takes the time the pass took from it. The time npm takes between passes
|
|
70
|
+
* is never counted: a slow install leaves the next pass its full share.
|
|
71
|
+
*/
|
|
72
|
+
export function createStopBudget({ totalMs = STOP_DEADLINE_MS, now = Date.now } = {}) {
|
|
73
|
+
let left = totalMs;
|
|
74
|
+
return {
|
|
75
|
+
get leftMs() { return left; },
|
|
76
|
+
async run(pass) {
|
|
77
|
+
const started = now();
|
|
78
|
+
try {
|
|
79
|
+
return await pass(started + Math.max(0, left));
|
|
80
|
+
} finally {
|
|
81
|
+
left -= Math.max(0, now() - started);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Every process as one JSON array, written as base64 of its UTF-8: what reaches
|
|
88
|
+
// the pipe is ASCII whatever the console code page is (a user folder named
|
|
89
|
+
// "João"). One line, single quotes only: nothing in it needs quoting on a
|
|
90
|
+
// command line, and nothing in it comes from the caller.
|
|
91
|
+
export const PROCESS_LIST_SCRIPT = [
|
|
92
|
+
"$ErrorActionPreference = 'Stop'",
|
|
93
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
94
|
+
'$rows = @(Get-CimInstance -ClassName Win32_Process | ForEach-Object { $created = $null; '
|
|
95
|
+
+ "if ($_.CreationDate) { $created = $_.CreationDate.ToUniversalTime().ToString('o') }; "
|
|
96
|
+
+ '[pscustomobject]@{ pid = [int]$_.ProcessId; ppid = [int]$_.ParentProcessId; name = [string]$_.Name; '
|
|
97
|
+
+ 'exe = [string]$_.ExecutablePath; cmd = [string]$_.CommandLine; created = $created } })',
|
|
98
|
+
'$json = ConvertTo-Json -InputObject $rows -Compress -Depth 2',
|
|
99
|
+
'[Console]::Out.Write([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($json)))',
|
|
100
|
+
].join('; ');
|
|
101
|
+
|
|
102
|
+
const systemFolder = (env) => win32.join(String(env.SystemRoot || env.SYSTEMROOT || env.windir || 'C:\\Windows'), 'System32');
|
|
103
|
+
|
|
104
|
+
/** What to hand spawnSync to list the processes: the same constant script whatever the caller's input, never a shell. */
|
|
105
|
+
export function processListCommand({ env = process.env } = {}) {
|
|
106
|
+
return {
|
|
107
|
+
file: win32.join(systemFolder(env), 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
108
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', PROCESS_LIST_SCRIPT],
|
|
109
|
+
options: { env, encoding: 'utf8', windowsHide: true, shell: false, timeout: LIST_TIMEOUT_MS, maxBuffer: 64 * 1024 * 1024 },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The processes in PowerShell's answer: [{ pid, ppid, name, exe, cmd, created }], or null when it is not one. */
|
|
114
|
+
export function parseProcessList(stdout) {
|
|
115
|
+
try {
|
|
116
|
+
const text = Buffer.from(String(stdout || '').trim(), 'base64').toString('utf8').replace(/^\uFEFF/, '');
|
|
117
|
+
const parsed = JSON.parse(text);
|
|
118
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
119
|
+
return rows
|
|
120
|
+
.filter(row => Number.isInteger(row?.pid) && row.pid > 0)
|
|
121
|
+
.map(row => ({
|
|
122
|
+
pid: row.pid,
|
|
123
|
+
ppid: Number.isInteger(row.ppid) ? row.ppid : 0,
|
|
124
|
+
name: String(row.name || ''),
|
|
125
|
+
exe: String(row.exe || ''),
|
|
126
|
+
cmd: String(row.cmd || ''),
|
|
127
|
+
created: Number.isFinite(Date.parse(row.created)) ? Date.parse(row.created) : null,
|
|
128
|
+
}));
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Every process on this Windows machine, or null when they cannot be listed. */
|
|
135
|
+
export function listWindowsProcesses({ env = process.env, spawn = spawnSync } = {}) {
|
|
136
|
+
const command = processListCommand({ env });
|
|
137
|
+
const result = spawn(command.file, command.args, command.options);
|
|
138
|
+
if (result.error || result.status !== 0) return null;
|
|
139
|
+
return parseProcessList(result.stdout);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** What to hand spawnSync to end one process and everything it started: taskkill /PID <pid> /T /F, never a shell. */
|
|
143
|
+
export function taskkillCommand(pid, { env = process.env } = {}) {
|
|
144
|
+
return {
|
|
145
|
+
file: win32.join(systemFolder(env), 'taskkill.exe'),
|
|
146
|
+
args: ['/PID', String(pid), '/T', '/F'],
|
|
147
|
+
options: { env, windowsHide: true, shell: false, stdio: 'ignore', timeout: 15_000 },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A command line split into its arguments the way a Windows program reads it (CommandLineToArgvW). */
|
|
152
|
+
export function splitWindowsCommandLine(line) {
|
|
153
|
+
const text = String(line || '');
|
|
154
|
+
const args = [];
|
|
155
|
+
let current = '';
|
|
156
|
+
let started = false;
|
|
157
|
+
let quoted = false;
|
|
158
|
+
for (let i = 0; i < text.length;) {
|
|
159
|
+
const c = text[i];
|
|
160
|
+
if (c === '\\') {
|
|
161
|
+
let n = 0;
|
|
162
|
+
while (text[i] === '\\') { n++; i++; }
|
|
163
|
+
if (text[i] === '"') {
|
|
164
|
+
current += '\\'.repeat(n >> 1);
|
|
165
|
+
if (n % 2) { current += '"'; i++; }
|
|
166
|
+
} else {
|
|
167
|
+
current += '\\'.repeat(n);
|
|
168
|
+
}
|
|
169
|
+
started = true;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (c === '"') {
|
|
173
|
+
if (quoted && text[i + 1] === '"') { current += '"'; i += 2; started = true; continue; }
|
|
174
|
+
quoted = !quoted;
|
|
175
|
+
started = true;
|
|
176
|
+
i++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!quoted && (c === ' ' || c === '\t')) {
|
|
180
|
+
if (started) { args.push(current); current = ''; started = false; }
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
current += c;
|
|
185
|
+
started = true;
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
if (started) args.push(current);
|
|
189
|
+
return args;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A Windows path as written, made plain: a `file:` URL read as a path, forward
|
|
194
|
+
* slashes turned back, the `\\?\`, `\\.\` and `\??\` prefixes taken off
|
|
195
|
+
* (`\\?\UNC\srv\share` is `\\srv\share`), `.`, `..` and doubled separators
|
|
196
|
+
* resolved, no trailing separator. Null for anything that is not an absolute
|
|
197
|
+
* drive or share path: a relative one would need the process's working folder,
|
|
198
|
+
* which the process list does not give.
|
|
199
|
+
*/
|
|
200
|
+
export function lexicalPath(value) {
|
|
201
|
+
let text = String(value ?? '').trim();
|
|
202
|
+
if (!text) return null;
|
|
203
|
+
if (/^file:/i.test(text)) {
|
|
204
|
+
try { text = fileURLToPath(text, { windows: true }); } catch { return null; }
|
|
205
|
+
}
|
|
206
|
+
text = text.replace(/\//g, '\\');
|
|
207
|
+
const prefix = /^(?:\\\\[?.]\\|\\\?\?\\)/.exec(text);
|
|
208
|
+
if (prefix) {
|
|
209
|
+
const rest = text.slice(prefix[0].length);
|
|
210
|
+
text = /^unc\\/i.test(rest) ? `\\\\${rest.slice(4)}` : rest;
|
|
211
|
+
}
|
|
212
|
+
if (!/^[A-Za-z]:\\/.test(text) && !/^\\\\(?![?.]\\)[^\\]+\\[^\\]+/.test(text)) return null;
|
|
213
|
+
const normal = win32.normalize(text);
|
|
214
|
+
return normal.length > win32.parse(normal).root.length ? normal.replace(/\\+$/, '') : normal;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const folderDepth = (path) => path.slice(win32.parse(path).root.length).split('\\').filter(Boolean).length;
|
|
218
|
+
|
|
219
|
+
// A folder npm installed: its parent is node_modules (as installKind in runtime-dependencies.js reads it).
|
|
220
|
+
const npmInstalled = (path) => win32.basename(win32.dirname(path)).toLowerCase() === 'node_modules';
|
|
221
|
+
|
|
222
|
+
// What paths are compared by: forward slashes, lower case.
|
|
223
|
+
const keyOf = (path) => path.replace(/\\/g, '/').toLowerCase();
|
|
224
|
+
|
|
225
|
+
// The real path of a file or folder on this machine: junctions, symbolic links
|
|
226
|
+
// and 8.3 short names resolved to the long names (GetFinalPathNameByHandle).
|
|
227
|
+
// Only on Windows: anywhere else the paths are another machine's and stay as written.
|
|
228
|
+
const nativeRealpath = (path) => {
|
|
229
|
+
if (process.platform !== 'win32') throw new Error('Windows paths resolve only on Windows');
|
|
230
|
+
return realpathSync.native(path);
|
|
231
|
+
};
|
|
232
|
+
// Whether a path is a folder, the same way: asked only on Windows.
|
|
233
|
+
const nativeIsDirectory = (path) => process.platform === 'win32' && statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The canonical form of an absolute Windows path: its real path (`realpath`,
|
|
237
|
+
* fs.realpathSync.native by default), through its nearest ancestor that
|
|
238
|
+
* resolves when the path itself does not (a script removed since it started),
|
|
239
|
+
* or lexicalPath when nothing on the way resolves. Null when it is not absolute.
|
|
240
|
+
*/
|
|
241
|
+
export function canonicalPath(value, { realpath = nativeRealpath } = {}) {
|
|
242
|
+
return resolvePath(value, realpath)?.real ?? null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// { lexical, real, resolved }: canonicalPath's answer, and whether `realpath` answered for the path or an ancestor
|
|
246
|
+
// of it (false: `real` is only the path as written).
|
|
247
|
+
function resolvePath(value, realpath) {
|
|
248
|
+
const lexical = lexicalPath(value);
|
|
249
|
+
if (!lexical) return null;
|
|
250
|
+
const tail = [];
|
|
251
|
+
for (let current = lexical; ;) {
|
|
252
|
+
let real = null;
|
|
253
|
+
try { real = lexicalPath(realpath(current)); } catch {}
|
|
254
|
+
if (real) return { lexical, real: tail.length ? win32.join(real, ...tail.reverse()) : real, resolved: true };
|
|
255
|
+
const parent = win32.dirname(current);
|
|
256
|
+
if (parent === current) return { lexical, real: lexical, resolved: false };
|
|
257
|
+
tail.push(win32.basename(current));
|
|
258
|
+
current = parent;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* A package root as it is compared: lexicalPath with forward slashes. Null for
|
|
264
|
+
* anything that is not an absolute folder at least `minDepth` levels below a
|
|
265
|
+
* drive or share: a root like `C:\` would take in every process.
|
|
266
|
+
*/
|
|
267
|
+
export function normalizeRoot(root, { minDepth = 2 } = {}) {
|
|
268
|
+
const lexical = lexicalPath(root);
|
|
269
|
+
if (!lexical || folderDepth(lexical) < Math.max(1, minDepth)) return null;
|
|
270
|
+
return lexical.replace(/\\/g, '/');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The forms of one package root that count: [as given, its real path] (one
|
|
275
|
+
* when they are the same), or [] when it is not a folder npm installed. Whether
|
|
276
|
+
* npm installed it (its parent is node_modules, as installKind in
|
|
277
|
+
* runtime-dependencies.js reads it) is read on the real path whenever that
|
|
278
|
+
* resolves: a short name (`NODE_M~1`) is node_modules, and an `npm link` alias
|
|
279
|
+
* of a checkout is the checkout, in every spelling: `npm i -g` replaces only
|
|
280
|
+
* the link, and the checkout's processes are a developer's. On the path as
|
|
281
|
+
* given only when nothing resolves.
|
|
282
|
+
*/
|
|
283
|
+
export function rootForms(value, { realpath = nativeRealpath } = {}) {
|
|
284
|
+
if (typeof value !== 'string' || !normalizeRoot(value)) return [];
|
|
285
|
+
const forms = resolvePath(value, realpath);
|
|
286
|
+
if (!forms || !npmInstalled(forms.resolved ? forms.real : forms.lexical)) return [];
|
|
287
|
+
return keyOf(forms.real) === keyOf(forms.lexical) ? [forms.lexical] : [forms.lexical, forms.real];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Decides whether a path is inside one of `rootList` (only the roots npm
|
|
292
|
+
* installed, rootForms), comparing every form of both: each root as given and
|
|
293
|
+
* as its real path, each path as written and, when that does not match, as its
|
|
294
|
+
* canonical path (one resolution per path). `inside(path)` is what follows the
|
|
295
|
+
* root (`\mcp-server\run.mjs`; '' for the root itself), or null;
|
|
296
|
+
* `isDirectory(path)` asks the file system (injectable, like `realpath`).
|
|
297
|
+
*/
|
|
298
|
+
export function createPathMatcher(rootList, { realpath = nativeRealpath, isDirectory = nativeIsDirectory } = {}) {
|
|
299
|
+
const keys = new Set();
|
|
300
|
+
for (const value of Array.isArray(rootList) ? rootList : [rootList]) {
|
|
301
|
+
for (const form of rootForms(value, { realpath })) keys.add(keyOf(form));
|
|
302
|
+
}
|
|
303
|
+
const rootKeys = [...keys];
|
|
304
|
+
const resolved = new Map();
|
|
305
|
+
const below = (path) => {
|
|
306
|
+
const key = keyOf(path);
|
|
307
|
+
const root = rootKeys.find(candidate => key === candidate || key.startsWith(`${candidate}/`));
|
|
308
|
+
if (root == null) return null;
|
|
309
|
+
// What follows the root, in the path's own case when lower-casing kept its length.
|
|
310
|
+
return (key.length === path.length ? path.slice(root.length) : key.slice(root.length)).replace(/\//g, '\\');
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
roots: rootKeys,
|
|
314
|
+
isDirectory(value) {
|
|
315
|
+
const lexical = lexicalPath(value);
|
|
316
|
+
try { return !!lexical && isDirectory(lexical) === true; } catch { return false; }
|
|
317
|
+
},
|
|
318
|
+
inside(value) {
|
|
319
|
+
const lexical = rootKeys.length ? lexicalPath(value) : null;
|
|
320
|
+
if (!lexical) return null;
|
|
321
|
+
const direct = below(lexical);
|
|
322
|
+
if (direct != null) return direct;
|
|
323
|
+
const cacheKey = keyOf(lexical);
|
|
324
|
+
if (!resolved.has(cacheKey)) resolved.set(cacheKey, canonicalPath(lexical, { realpath }));
|
|
325
|
+
const real = resolved.get(cacheKey);
|
|
326
|
+
return real ? below(real) : null;
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The folders whose processes are stopped before `npm i -g` replaces the
|
|
333
|
+
* package: each form of it the caller knows (the configured one, the one it
|
|
334
|
+
* resolved) and, when it differs, its real path (a junction, a short 8.3
|
|
335
|
+
* name). Only a folder npm installed (rootForms, with Windows path rules on any
|
|
336
|
+
* OS): a checkout, or the checkout an `npm link` points to, is never what
|
|
337
|
+
* `npm i -g` replaces.
|
|
338
|
+
*/
|
|
339
|
+
export function packageRootsToStop(packageRoots, { realpath = nativeRealpath } = {}) {
|
|
340
|
+
const out = [];
|
|
341
|
+
const seen = new Set();
|
|
342
|
+
for (const value of Array.isArray(packageRoots) ? packageRoots : [packageRoots]) {
|
|
343
|
+
for (const form of rootForms(value, { realpath })) {
|
|
344
|
+
if (seen.has(keyOf(form))) continue;
|
|
345
|
+
seen.add(keyOf(form));
|
|
346
|
+
out.push(form);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Node's options that take the next argument as their value when it is not
|
|
353
|
+
// given with `=` (`--require x`). Any other option is a flag, or a V8 option
|
|
354
|
+
// whose value is a number (`--max-old-space-size 4096`).
|
|
355
|
+
const NODE_VALUE_OPTIONS = new Set([
|
|
356
|
+
'-r', '--require', '--import', '--loader', '--experimental-loader', '-C', '--conditions',
|
|
357
|
+
'--env-file', '--env-file-if-exists', '--input-type', '--title', '--inspect-port', '--debug-port', '--inspect-publish-uid',
|
|
358
|
+
'--redirect-warnings', '--disable-warning', '--disable-proto', '--dns-result-order', '--unhandled-rejections',
|
|
359
|
+
'--experimental-default-type', '--experimental-policy', '--policy-integrity', '--experimental-sea-config',
|
|
360
|
+
'--experimental-config-file', '--experimental-specifier-resolution', '--es-module-specifier-resolution',
|
|
361
|
+
'--build-snapshot-config', '--snapshot-blob', '--localstorage-file', '--icu-data-dir', '--openssl-config',
|
|
362
|
+
'--tls-cipher-list', '--tls-keylog', '--secure-heap', '--secure-heap-min', '--security-revert', '--use-largepages',
|
|
363
|
+
'--v8-pool-size', '--max-http-header-size', '--network-family-autoselection-attempt-timeout',
|
|
364
|
+
'--allow-fs-read', '--allow-fs-write', '--diagnostic-dir', '--report-dir', '--report-directory', '--report-filename',
|
|
365
|
+
'--report-signal', '--heapsnapshot-signal', '--heapsnapshot-near-heap-limit', '--cpu-prof-dir', '--cpu-prof-name',
|
|
366
|
+
'--cpu-prof-interval', '--heap-prof-dir', '--heap-prof-name', '--heap-prof-interval', '--trace-event-categories',
|
|
367
|
+
'--trace-event-file-pattern', '--trace-require-module', '--watch-path', '--watch-kill-signal',
|
|
368
|
+
'--test-concurrency', '--test-coverage-branches', '--test-coverage-functions', '--test-coverage-lines',
|
|
369
|
+
'--test-coverage-include', '--test-coverage-exclude', '--test-global-setup', '--test-isolation', '--test-name-pattern',
|
|
370
|
+
'--test-reporter', '--test-reporter-destination', '--test-shard', '--test-skip-pattern', '--test-timeout',
|
|
371
|
+
'--max-old-space-size', '--max-semi-space-size', '--stack-size', '--stack-trace-limit',
|
|
372
|
+
'--logfile', '--trace-file',
|
|
373
|
+
]);
|
|
374
|
+
// The options whose value is code Node loads before the entrypoint.
|
|
375
|
+
const NODE_PRELOAD_OPTIONS = new Set(['-r', '--require', '--import', '--loader', '--experimental-loader']);
|
|
376
|
+
// After these Node runs no script: the code is their value (-e, -p) or a package.json script (--run).
|
|
377
|
+
const NODE_NO_SCRIPT_OPTIONS = new Set(['-e', '--eval', '-p', '--print', '-pe', '-ep', '--run']);
|
|
378
|
+
// An entrypoint or a preload of SynaBun's is a code file below the root: .js, .mjs, .cjs or .ts (every one SynaBun
|
|
379
|
+
// starts has its extension). The root itself, a folder, a name without an extension, a config or a data file means
|
|
380
|
+
// node's options were read wrong, and never makes a process SynaBun's.
|
|
381
|
+
const CODE_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/i;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* One of node's own arguments as Node reads it: { name, value }. A long
|
|
385
|
+
* option's name ends at the first `=` (`--require=x` carries its value) and
|
|
386
|
+
* its underscores are dashes (`--diagnostic_dir` is `--diagnostic-dir`; V8's
|
|
387
|
+
* options take either spelling too). A short option (`-r`) is its own name.
|
|
388
|
+
*/
|
|
389
|
+
export function nodeOption(arg) {
|
|
390
|
+
if (!arg.startsWith('--')) return { name: arg, value: null };
|
|
391
|
+
const eq = arg.indexOf('=');
|
|
392
|
+
const name = (eq > 0 ? arg.slice(0, eq) : arg).replace(/_/g, '-');
|
|
393
|
+
return { name, value: eq > 0 ? arg.slice(eq + 1) : null };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* What a node command line runs: { script, preloads }. `script` is the first
|
|
398
|
+
* argument after node's own options (null when there is none: -e, -p, --run, a
|
|
399
|
+
* REPL); `preloads` are the values of -r / --require / --import / --loader.
|
|
400
|
+
* Everything after the script is the script's own arguments and never read.
|
|
401
|
+
*/
|
|
402
|
+
export function nodeEntrypoint(commandLine) {
|
|
403
|
+
const args = splitWindowsCommandLine(commandLine).slice(1);
|
|
404
|
+
const out = { script: null, preloads: [] };
|
|
405
|
+
for (let i = 0; i < args.length; i++) {
|
|
406
|
+
const arg = args[i];
|
|
407
|
+
if (arg === '--') { out.script = args[i + 1] ?? null; break; }
|
|
408
|
+
if (arg === '-') break;
|
|
409
|
+
if (!arg.startsWith('-')) { out.script = arg; break; }
|
|
410
|
+
const { name, value: inline } = nodeOption(arg);
|
|
411
|
+
if (NODE_NO_SCRIPT_OPTIONS.has(name)) break;
|
|
412
|
+
let value = inline;
|
|
413
|
+
if (value == null && NODE_VALUE_OPTIONS.has(name)) value = args[++i] ?? null;
|
|
414
|
+
else if (value == null && /^\d+(?:\.\d+)?$/.test(args[i + 1] ?? '')) i++;
|
|
415
|
+
if (value != null && NODE_PRELOAD_OPTIONS.has(name)) out.preloads.push(value);
|
|
416
|
+
}
|
|
417
|
+
return out;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Why a node command line is SynaBun's: { arg, rest, via } (`via` 'script' or
|
|
422
|
+
* 'preload', `rest` what follows the root), or null. Only the entrypoint and
|
|
423
|
+
* the preloads are read (nodeEntrypoint), and only a code file below the root
|
|
424
|
+
* counts (never the root itself or a folder); `rootList` is a list of roots or
|
|
425
|
+
* a createPathMatcher.
|
|
426
|
+
*/
|
|
427
|
+
export function commandLineReference(commandLine, rootList, { realpath = nativeRealpath, isDirectory = nativeIsDirectory } = {}) {
|
|
428
|
+
const matcher = typeof rootList?.inside === 'function' ? rootList : createPathMatcher(rootList, { realpath, isDirectory });
|
|
429
|
+
if (!matcher.roots.length) return null;
|
|
430
|
+
const { script, preloads } = nodeEntrypoint(commandLine);
|
|
431
|
+
const checks = [...(script ? [[script, 'script']] : []), ...preloads.map(value => [value, 'preload'])];
|
|
432
|
+
for (const [arg, via] of checks) {
|
|
433
|
+
const rest = matcher.inside(arg);
|
|
434
|
+
if (!rest || !CODE_FILE.test(win32.basename(rest)) || matcher.isDirectory(arg)) continue;
|
|
435
|
+
return { arg, rest, via };
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const isNode = (proc) => win32.basename(proc.exe || '').toLowerCase() === 'node.exe' || String(proc.name || '').toLowerCase() === 'node.exe';
|
|
441
|
+
|
|
442
|
+
// A parent is older than its child: a PID Windows gave again to a newer process is not a parent.
|
|
443
|
+
const olderOrUnknown = (parent, child) => parent.created == null || child.created == null || parent.created <= child.created;
|
|
444
|
+
|
|
445
|
+
/** The PIDs `pid` descends from in `processes`, nearest first. */
|
|
446
|
+
function ancestorsOf(pid, byPid) {
|
|
447
|
+
const out = [];
|
|
448
|
+
for (let child = byPid.get(pid); child;) {
|
|
449
|
+
const parent = byPid.get(child.ppid);
|
|
450
|
+
if (!parent || parent.pid === pid || out.includes(parent.pid) || !olderOrUnknown(parent, child)) break;
|
|
451
|
+
out.push(parent.pid);
|
|
452
|
+
child = parent;
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* The PIDs never to stop: `selfPid`, the processes it descends from (each
|
|
459
|
+
* parent older than its child: a PID Windows gave again to a newer process is
|
|
460
|
+
* not a parent) and the ones it started.
|
|
461
|
+
*/
|
|
462
|
+
export function protectedPids(processes, selfPid) {
|
|
463
|
+
const byPid = new Map(processes.map(proc => [proc.pid, proc]));
|
|
464
|
+
const out = new Set([selfPid, 0, 4, ...ancestorsOf(selfPid, byPid)]);
|
|
465
|
+
const self = byPid.get(selfPid);
|
|
466
|
+
const descendants = new Set([selfPid]);
|
|
467
|
+
for (let grew = true; grew;) {
|
|
468
|
+
grew = false;
|
|
469
|
+
for (const proc of processes) {
|
|
470
|
+
if (descendants.has(proc.pid) || !descendants.has(proc.ppid)) continue;
|
|
471
|
+
const parent = byPid.get(proc.ppid) || self;
|
|
472
|
+
if (parent && !olderOrUnknown(parent, proc)) continue;
|
|
473
|
+
descendants.add(proc.pid);
|
|
474
|
+
out.add(proc.pid);
|
|
475
|
+
grew = true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** What is shown for a process: its program and the path inside the package, the root written as <SynaBun>. */
|
|
482
|
+
export function describeProcess(proc, reference = null) {
|
|
483
|
+
const program = proc.name || win32.basename(proc.exe || '') || 'process';
|
|
484
|
+
if (reference) return `${program}${reference.via === 'preload' ? ' (preloads)' : ''} <SynaBun>${reference.rest}`.slice(0, 160);
|
|
485
|
+
return `${program} (runs from <SynaBun>)`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* The processes to stop, from a process list: [{ pid, ppid, name, created, reason, summary }].
|
|
490
|
+
* `reason` is 'executable' (its program file is inside a root), 'script' (node
|
|
491
|
+
* runs a script inside a root) or 'preload' (node preloads a module inside a root).
|
|
492
|
+
*/
|
|
493
|
+
export function findPackageProcesses({ processes, roots: rootList, selfPid = process.pid, realpath = nativeRealpath, isDirectory = nativeIsDirectory }) {
|
|
494
|
+
if (!Array.isArray(processes)) return [];
|
|
495
|
+
const matcher = createPathMatcher(rootList, { realpath, isDirectory });
|
|
496
|
+
if (!matcher.roots.length) return [];
|
|
497
|
+
const keep = protectedPids(processes, selfPid);
|
|
498
|
+
const targets = [];
|
|
499
|
+
const target = (proc, reason, reference = null) => ({
|
|
500
|
+
pid: proc.pid, ppid: proc.ppid, name: proc.name, created: proc.created, reason, summary: describeProcess(proc, reference),
|
|
501
|
+
});
|
|
502
|
+
for (const proc of processes) {
|
|
503
|
+
if (keep.has(proc.pid)) continue;
|
|
504
|
+
if (proc.exe && matcher.inside(proc.exe) != null) {
|
|
505
|
+
targets.push(target(proc, 'executable'));
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (!isNode(proc)) continue;
|
|
509
|
+
const reference = commandLineReference(proc.cmd, matcher);
|
|
510
|
+
if (reference) targets.push(target(proc, reference.via, reference));
|
|
511
|
+
}
|
|
512
|
+
return targets;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const pidAlive = (pid) => {
|
|
516
|
+
try { process.kill(pid, 0); return true; } catch (error) { return error?.code === 'EPERM'; }
|
|
517
|
+
};
|
|
518
|
+
const pause = (ms) => new Promise(done => setTimeout(done, ms));
|
|
519
|
+
|
|
520
|
+
// The same process as the target: its PID, with the creation time it had when it was matched.
|
|
521
|
+
const sameProcess = (target, list) => {
|
|
522
|
+
if (target.created == null || !Array.isArray(list)) return false;
|
|
523
|
+
return list.some(proc => proc.pid === target.pid && proc.created === target.created);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Why a target is left alone at the kill: sameProcess said no.
|
|
527
|
+
const whySkipped = (target, list) => {
|
|
528
|
+
if (!Array.isArray(list)) return 'could not be checked again';
|
|
529
|
+
if (target.created == null) return 'has no creation time to check';
|
|
530
|
+
return list.some(proc => proc.pid === target.pid) ? 'now belongs to another process' : 'has already ended';
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Stop every process of findPackageProcesses and wait (bounded) until they are gone.
|
|
535
|
+
*
|
|
536
|
+
* There is no cheap graceful stop for these from outside: an MCP stdio server
|
|
537
|
+
* ends when its client closes its input, and `taskkill` without /F only posts
|
|
538
|
+
* WM_CLOSE to windows, which a console process has none of. So each one is
|
|
539
|
+
* ended with `taskkill /PID <pid> /T /F` (its own children with it), the
|
|
540
|
+
* outermost first.
|
|
541
|
+
*
|
|
542
|
+
* Right before each kill the processes are listed again (the same fixed
|
|
543
|
+
* script), and a target that has ended, or whose PID now belongs to a process
|
|
544
|
+
* created at another time, is left alone. A target below one that was just
|
|
545
|
+
* ended went with it (/T) and is not killed again: its PID may be someone
|
|
546
|
+
* else's by then.
|
|
547
|
+
*
|
|
548
|
+
* Everything happens before `deadline` (an absolute time; by default
|
|
549
|
+
* STOP_DEADLINE_MS from now): past it no list is read and nothing is killed,
|
|
550
|
+
* and the targets not reached count as remaining while they run.
|
|
551
|
+
*
|
|
552
|
+
* Returns { listed, stopped: [target], remaining: [target], skipped: [target], timedOut }:
|
|
553
|
+
* `skipped` were left alone at the kill (ended, PID reused, or the list could
|
|
554
|
+
* not be read again); `listed` false means the processes could not be listed
|
|
555
|
+
* and nothing was done; `timedOut` that the deadline cut the step short.
|
|
556
|
+
*/
|
|
557
|
+
export async function stopPackageProcesses({
|
|
558
|
+
roots: rootList, selfPid = process.pid, list = listWindowsProcesses, kill = (pid) => {
|
|
559
|
+
const command = taskkillCommand(pid);
|
|
560
|
+
spawnSync(command.file, command.args, command.options);
|
|
561
|
+
},
|
|
562
|
+
isAlive = pidAlive, sleep = pause, now = Date.now, log = () => {}, waitMs = STOP_WAIT_MS, deadline = null,
|
|
563
|
+
realpath = nativeRealpath, isDirectory = nativeIsDirectory,
|
|
564
|
+
} = {}) {
|
|
565
|
+
const until = Number.isFinite(deadline) ? deadline : now() + STOP_DEADLINE_MS;
|
|
566
|
+
const late = () => now() >= until;
|
|
567
|
+
if (late()) return { listed: false, stopped: [], remaining: [], skipped: [], timedOut: true };
|
|
568
|
+
const processes = list();
|
|
569
|
+
if (!Array.isArray(processes)) return { listed: false, stopped: [], remaining: [], skipped: [], timedOut: false };
|
|
570
|
+
const targets = findPackageProcesses({ processes, roots: rootList, selfPid, realpath, isDirectory });
|
|
571
|
+
if (!targets.length) return { listed: true, stopped: [], remaining: [], skipped: [], timedOut: false };
|
|
572
|
+
const byPid = new Map(processes.map(proc => [proc.pid, proc]));
|
|
573
|
+
const ancestry = new Map(targets.map(target => [target.pid, ancestorsOf(target.pid, byPid)]));
|
|
574
|
+
const targetPids = new Set(targets.map(target => target.pid));
|
|
575
|
+
const depth = (target) => ancestry.get(target.pid).filter(pid => targetPids.has(pid)).length;
|
|
576
|
+
const killed = new Set();
|
|
577
|
+
const skipped = [];
|
|
578
|
+
// The list each kill is checked against; read again after every kill.
|
|
579
|
+
let current = null;
|
|
580
|
+
let timedOut = false;
|
|
581
|
+
for (const target of [...targets].sort((a, b) => depth(a) - depth(b))) {
|
|
582
|
+
if (ancestry.get(target.pid).some(pid => killed.has(pid))) continue;
|
|
583
|
+
if (late()) { timedOut = true; break; }
|
|
584
|
+
if (!current) current = list();
|
|
585
|
+
if (!sameProcess(target, current)) {
|
|
586
|
+
skipped.push(target);
|
|
587
|
+
log(` PID ${target.pid} ${whySkipped(target, current)}, left alone: ${target.summary}`);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
log(` Stopping PID ${target.pid}: ${target.summary}`);
|
|
591
|
+
kill(target.pid);
|
|
592
|
+
killed.add(target.pid);
|
|
593
|
+
current = null;
|
|
594
|
+
}
|
|
595
|
+
const watched = targets.filter(target => !skipped.includes(target));
|
|
596
|
+
const waitUntil = Math.min(now() + waitMs, until);
|
|
597
|
+
let remaining = watched.filter(target => isAlive(target.pid));
|
|
598
|
+
while (remaining.length && now() < waitUntil) {
|
|
599
|
+
await sleep(250);
|
|
600
|
+
remaining = remaining.filter(target => isAlive(target.pid));
|
|
601
|
+
}
|
|
602
|
+
if (remaining.length && late()) timedOut = true;
|
|
603
|
+
// A PID still in use may be another process's by now: only the same process counts as remaining.
|
|
604
|
+
if (remaining.length && !late()) {
|
|
605
|
+
const after = list();
|
|
606
|
+
if (Array.isArray(after)) remaining = remaining.filter(target => sameProcess(target, after));
|
|
607
|
+
}
|
|
608
|
+
return { listed: true, stopped: watched.filter(target => !remaining.includes(target)), remaining, skipped, timedOut };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* The lock code (EPERM, EBUSY, ENOTEMPTY) a failed npm reported, or null.
|
|
613
|
+
* npm's last `error code X` in its debug log decides when it is there, whatever
|
|
614
|
+
* X is (a lifecycle script's `error code 1` is no lock, even with an EPERM
|
|
615
|
+
* cleanup warning before it). Without one: the stack of the error npm failed
|
|
616
|
+
* with (`verbose stack Error: EBUSY: resource busy or locked, rename`), then
|
|
617
|
+
* its exit status (npm exits with the errno: -4082 for EBUSY, read back as
|
|
618
|
+
* 4294963214 by a 32-bit unsigned exit code).
|
|
619
|
+
*/
|
|
620
|
+
export function npmLockCode({ status = null, logText = '' } = {}) {
|
|
621
|
+
const codes = Object.keys(WINDOWS_LOCK_ERRNO);
|
|
622
|
+
const text = String(logText || '');
|
|
623
|
+
const verdicts = [...text.matchAll(/^[ \t]*(?:\d+ )?(?:npm )?(?:error|ERR!) code (\S+)/gm)];
|
|
624
|
+
if (verdicts.length) {
|
|
625
|
+
const verdict = verdicts.at(-1)[1];
|
|
626
|
+
return codes.includes(verdict) ? verdict : null;
|
|
627
|
+
}
|
|
628
|
+
const stack = new RegExp(`^[ \\t]*(?:\\d+ )?(?:npm )?(?:verbose stack (?:[\\w.]*Error: )?|error |ERR! )(${codes.join('|')}): `, 'm').exec(text);
|
|
629
|
+
if (stack) return stack[1];
|
|
630
|
+
if (Number.isFinite(status)) {
|
|
631
|
+
const signed = status > 0x7fffffff ? status - 0x100000000 : status;
|
|
632
|
+
for (const [code, errno] of Object.entries(WINDOWS_LOCK_ERRNO)) if (signed === errno) return code;
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** An npm run, as the retry loop sees it: { ok, status, signal, error, logFile, lockCode }. */
|
|
638
|
+
export function npmOutcome(result, { logsDir = null, startedAt = 0, readFile = readFileSync } = {}) {
|
|
639
|
+
const ok = !result.error && result.status === 0;
|
|
640
|
+
const logFile = ok ? null : newestDebugLog(logsDir, startedAt);
|
|
641
|
+
let logText = '';
|
|
642
|
+
if (logFile) { try { logText = readFile(logFile, 'utf8'); } catch {} }
|
|
643
|
+
return {
|
|
644
|
+
ok,
|
|
645
|
+
status: result.status ?? null,
|
|
646
|
+
signal: result.signal || null,
|
|
647
|
+
error: result.error ? (result.error.code || result.error.message) : null,
|
|
648
|
+
logFile,
|
|
649
|
+
lockCode: ok || result.error ? null : npmLockCode({ status: result.status, logText }),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Run npm; when it fails because a file in the folder is in use, stop
|
|
655
|
+
* SynaBun's processes again (an AI client may have started its MCP server
|
|
656
|
+
* again) and run it again, at most `retries` more times. A stop that reached
|
|
657
|
+
* its deadline (`{ timedOut: true }`) ends the retries there.
|
|
658
|
+
* Returns the last outcome plus { attempts, locked, timedOut } (locked: still a lock failure at the end).
|
|
659
|
+
*/
|
|
660
|
+
export async function installWithLockRetries({ install, stop, retries = LOCK_RETRIES, log = () => {} }) {
|
|
661
|
+
for (let attempt = 1; ; attempt++) {
|
|
662
|
+
const outcome = await install(attempt);
|
|
663
|
+
if (outcome.ok || !outcome.lockCode || attempt > retries) {
|
|
664
|
+
return { ...outcome, attempts: attempt, locked: !outcome.ok && !!outcome.lockCode, timedOut: false };
|
|
665
|
+
}
|
|
666
|
+
log(`\nnpm could not replace the SynaBun folder (${outcome.lockCode}): something still runs from it. Stopping SynaBun's processes again, then retry ${attempt} of ${retries}.`);
|
|
667
|
+
const stopped = await stop(attempt);
|
|
668
|
+
if (stopped?.timedOut) return { ...outcome, attempts: attempt, locked: true, timedOut: true };
|
|
669
|
+
}
|
|
670
|
+
}
|