@henlii/pidance 0.2.6 → 0.2.8
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +12 -8
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +4 -2
- package/.next/routes-manifest.json +26 -0
- package/.next/server/app/_global-error/page.js +3 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/about/route.js +1 -1
- package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/events/route.js +2 -2
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/agent-settings/route.js +2 -2
- package/.next/server/app/api/attachments/route.js +1 -1
- package/.next/server/app/api/auth/all-providers/route.js +1 -1
- package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
- package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/providers/route.js +1 -1
- package/.next/server/app/api/auth/ui-session/route.js +1 -1
- package/.next/server/app/api/auth/ui-sessions/[id]/route.js +1 -0
- package/.next/server/app/api/auth/ui-sessions/[id]/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/auth/ui-sessions/route.js +1 -0
- package/.next/server/app/api/auth/ui-sessions/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/cwd/browse/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +2 -2
- package/.next/server/app/api/git/diff/route.js +1 -1
- package/.next/server/app/api/git/status/route.js +1 -1
- package/.next/server/app/api/home/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models-config/raw/route.js +1 -1
- package/.next/server/app/api/models-config/route.js +1 -1
- package/.next/server/app/api/models-config/test/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/search/route.js +1 -1
- package/.next/server/app/api/plugins/updates/route.js +1 -1
- package/.next/server/app/api/preferences/route.js +1 -1
- package/.next/server/app/api/project-assets/route.js +2 -2
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/pty/route.js +3 -0
- package/.next/server/app/api/pty/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sessions/[id]/archive/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/command-entry/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +3 -3
- package/.next/server/app/api/sessions/[id]/info/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/tool-results/[toolCallId]/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/search/route.js +1 -1
- package/.next/server/app/api/settings/raw/route.js +2 -2
- package/.next/server/app/api/settings/remote/route.js +1 -0
- package/.next/server/app/api/settings/remote/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/search/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/subagent-runs/route.js +1 -1
- package/.next/server/app/api/update/apply/route.js +3 -3
- package/.next/server/app/api/update/check/route.js +2 -2
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/icon.png/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +6 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -3
- package/.next/server/app/index.segments/_full.segment.rsc +6 -5
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
- package/.next/server/app/page.js +22 -6
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +12 -8
- package/.next/server/chunks/188.js +1 -1
- package/.next/server/chunks/329.js +1 -0
- package/.next/server/chunks/5582.js +1 -1
- package/.next/server/chunks/7594.js +1 -1
- package/.next/server/functions-config-manifest.json +3 -0
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/Sc36XikGMPPmKsuJFVoCS/_buildManifest.js +1 -0
- package/.next/static/chunks/7874-4f48b670898adf75.js +1 -0
- package/.next/static/chunks/9780-e3ccfd6965a6273f.js +1 -0
- package/.next/static/chunks/app/_global-error/page-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/about/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/bash-output/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/running/events/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent/running/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/agent-settings/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/attachments/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/all-providers/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/login/[provider]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/providers/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/ui-session/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/ui-sessions/[id]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/auth/ui-sessions/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/cwd/browse/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/cwd/validate/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/default-cwd/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/file-index/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/files/[...path]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/git/diff/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/git/status/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/home/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/models/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/models-config/raw/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/models-config/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/models-config/test/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/plugins/search/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/plugins/updates/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/preferences/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/project-assets/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/pty/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/archive/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/command-entry/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/context/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/info/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/state/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/tool-results/[toolCallId]/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/sessions/search/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/settings/raw/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/settings/remote/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/skills/check/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/skills/install/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/skills/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/skills/search/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/skills/update/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/subagent-runs/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/update/apply/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/update/check/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/api/worktrees/route-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/app/page-23c183e276008d31.js +25 -0
- package/.next/static/chunks/e868780c-16e43c5968ccf2bf.js +18 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-81c6f790083aee21.js +1 -0
- package/.next/static/chunks/{webpack-9f5e68be126b5e81.js → webpack-d2379fd13c5c78c6.js} +1 -1
- package/.next/static/css/659eccb5db697b76.css +1 -0
- package/.next/static/css/{6faea79e75763a6a.css → b890757d9e0dd456.css} +1 -1
- package/README.md +7 -4
- package/bin/pidance-auth-gate.js +10 -7
- package/bin/pidance-http-server.js +101 -0
- package/bin/pidance-options.js +3 -2
- package/bin/pidance-server-config.js +46 -0
- package/bin/pidance.js +62 -77
- package/bin/pty-manager.cjs +149 -0
- package/bin/pty-worker.js +48 -0
- package/bin/pty-ws.js +271 -0
- package/package.json +13 -3
- package/.next/static/HW-Jlwn4MNQKUjCkayKg3/_buildManifest.js +0 -1
- package/.next/static/chunks/1-a030cc9570b4428a.js +0 -1
- package/.next/static/chunks/9780-6931a654abc0ac16.js +0 -1
- package/.next/static/chunks/app/_global-error/page-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/about/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/bash-output/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/running/events/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent/running/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/agent-settings/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/attachments/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/all-providers/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/api-key/[provider]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/login/[provider]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/logout/[provider]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/providers/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/auth/ui-session/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/cwd/browse/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/cwd/validate/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/default-cwd/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/file-index/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/files/[...path]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/git/diff/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/git/status/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/home/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/models/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/models-config/raw/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/models-config/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/models-config/test/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/plugins/search/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/plugins/updates/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/preferences/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/project-assets/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/archive/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/command-entry/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/context/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/info/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/state/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/tool-results/[toolCallId]/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/sessions/search/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/settings/raw/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/skills/check/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/skills/install/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/skills/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/skills/search/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/skills/update/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/subagent-runs/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/update/apply/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/update/check/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/api/worktrees/route-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/app/page-dc174ab4b86d5192.js +0 -25
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-c74291295bcd24a9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-c74291295bcd24a9.js +0 -1
- /package/.next/static/{HW-Jlwn4MNQKUjCkayKg3 → Sc36XikGMPPmKsuJFVoCS}/_ssgManifest.js +0 -0
package/bin/pidance.js
CHANGED
|
@@ -10,10 +10,15 @@ const fs = require("fs");
|
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
11
|
const { parseLaunchOptions } = require("./pidance-options");
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
+
const { loadServerConfig } = require("./pidance-server-config");
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
15
|
const { shouldRequireAuth, resolvePassword, describeHost } = require("./pidance-auth-gate");
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
|
+
const { startPidanceHttpServer } = require("./pidance-http-server");
|
|
14
18
|
|
|
15
19
|
const pkgDir = path.join(__dirname, "..");
|
|
16
|
-
const
|
|
20
|
+
const distDirName = process.env.PIDANCE_DIST_DIR || ".next";
|
|
21
|
+
const nextDir = path.join(pkgDir, distDirName);
|
|
17
22
|
|
|
18
23
|
// Node engine 门禁(与 package.json engines.node 对齐):低于最低版本直接失败,
|
|
19
24
|
// 避免进入半运行状态。npm engines 默认只警告,这里 fail-closed。
|
|
@@ -32,27 +37,23 @@ if (!nodeMeetsMin(process.versions.node)) {
|
|
|
32
37
|
process.exit(1);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
nextBin = path.join(pkgDir, "node_modules", "next", "dist", "bin", "next");
|
|
47
|
-
}
|
|
40
|
+
const { port: explicitPort, hostname: explicitHostname, openBrowser } = parseLaunchOptions();
|
|
41
|
+
const serverConfig = loadServerConfig();
|
|
42
|
+
// 默认绑定:设置 → 通用 开启远程服务 → 0.0.0.0;否则仅本机 127.0.0.1(免密码)。
|
|
43
|
+
// 显式 --hostname / -H / HOSTNAME env 优先;显式 0.0.0.0 仍走密码门禁。
|
|
44
|
+
const hostname = explicitHostname ?? (serverConfig.remoteEnabled ? "0.0.0.0" : "127.0.0.1");
|
|
45
|
+
// 端口优先级:显式 -p / PORT env → 配置文件 port → 产品默认 31415。
|
|
46
|
+
const port = explicitPort ?? (serverConfig.port ? String(serverConfig.port) : "31415");
|
|
47
|
+
const listenPort = Number.parseInt(String(port), 10);
|
|
48
|
+
if (!Number.isInteger(listenPort) || listenPort <= 0) {
|
|
49
|
+
console.error(`[pidance] 拒绝启动:无效端口 ${port}`);
|
|
50
|
+
process.exit(1);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
const { port, hostname, openBrowser } = parseLaunchOptions();
|
|
51
|
-
|
|
52
53
|
// 启动认证门禁(fail-closed,P0):非回环监听地址(0.0.0.0 / :: / 局域网 IP / 非回环主机名)
|
|
53
54
|
// 且未设置认证密码时拒绝启动,防止局域网/公网匿名调用 Agent API(创建会话/发 prompt/调工具)。
|
|
54
|
-
//
|
|
55
|
-
if (shouldRequireAuth(hostname, resolvePassword(process.env))) {
|
|
55
|
+
// 默认仅绑定 127.0.0.1(本机),无需密码;远程服务在设置 → 通用 中开启(须先设置密码)。
|
|
56
|
+
if (shouldRequireAuth(hostname, resolvePassword(process.env), serverConfig)) {
|
|
56
57
|
console.error(
|
|
57
58
|
"[pidance] 拒绝启动:监听地址 " +
|
|
58
59
|
describeHost(hostname) +
|
|
@@ -63,7 +64,10 @@ if (shouldRequireAuth(hostname, resolvePassword(process.env))) {
|
|
|
63
64
|
"(兼容旧变量 PI_WEB_PASSWORD)。",
|
|
64
65
|
);
|
|
65
66
|
console.error(
|
|
66
|
-
"
|
|
67
|
+
"也可以在设置 → 通用 中设置密码并开启远程服务(重启后监听 0.0.0.0)。",
|
|
68
|
+
);
|
|
69
|
+
console.error(
|
|
70
|
+
"仅本机访问默认绑定 127.0.0.1,直接运行 pidance 即可(或 --hostname 127.0.0.1 / localhost)。",
|
|
67
71
|
);
|
|
68
72
|
process.exit(1);
|
|
69
73
|
}
|
|
@@ -73,66 +77,47 @@ if (!fs.existsSync(nextDir)) {
|
|
|
73
77
|
process.exit(1);
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// and path-with-spaces problems on Windows when shell: true is used.
|
|
81
|
-
const child = spawn(process.execPath, [nextBin, ...nextArgs], {
|
|
82
|
-
cwd: pkgDir,
|
|
83
|
-
stdio: ["inherit", "pipe", "inherit"],
|
|
84
|
-
env: { ...process.env },
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
let browserOpened = false;
|
|
88
|
-
const url = `http://${hostname ?? "localhost"}:${port}`;
|
|
80
|
+
const url = `http://${hostname}:${listenPort}`;
|
|
81
|
+
// 浏览器打开用回环地址(0.0.0.0 在多数浏览器不可直达)
|
|
82
|
+
const browserHost = hostname === "0.0.0.0" || hostname === "::" ? "localhost" : hostname;
|
|
83
|
+
const browserUrl = `http://${browserHost}:${listenPort}`;
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
process.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
function openBrowserOnce() {
|
|
86
|
+
const isWindows = process.platform === "win32";
|
|
87
|
+
const isMac = process.platform === "darwin";
|
|
88
|
+
const openCmd = isWindows ? "start" : isMac ? "open" : "xdg-open";
|
|
89
|
+
const opener = spawn(openCmd, [browserUrl], {
|
|
90
|
+
shell: isWindows,
|
|
91
|
+
stdio: "ignore",
|
|
92
|
+
detached: true,
|
|
93
|
+
});
|
|
94
|
+
opener.on("error", (error) => {
|
|
95
|
+
console.warn(`Could not open browser automatically: ${error.message}`);
|
|
96
|
+
});
|
|
97
|
+
opener.unref();
|
|
98
|
+
}
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
void startPidanceHttpServer({
|
|
101
|
+
dir: pkgDir,
|
|
102
|
+
hostname,
|
|
103
|
+
port: listenPort,
|
|
104
|
+
}).then(({ server }) => {
|
|
105
|
+
console.log(`Ready on ${url}`);
|
|
106
|
+
if (openBrowser) openBrowserOnce();
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
// 同一进程持有 HTTP:SIGTERM 关 listener,不再 spawn next-server。
|
|
109
|
+
const STOP_GRACE_MS = 8_000;
|
|
110
|
+
let stopping = false;
|
|
111
|
+
function requestStop() {
|
|
112
|
+
if (stopping) return;
|
|
113
|
+
stopping = true;
|
|
114
|
+
server.close(() => process.exit(0));
|
|
115
|
+
const force = setTimeout(() => process.exit(1), STOP_GRACE_MS);
|
|
116
|
+
if (typeof force.unref === "function") force.unref();
|
|
109
117
|
}
|
|
118
|
+
process.on("SIGTERM", requestStop);
|
|
119
|
+
process.on("SIGINT", requestStop);
|
|
120
|
+
}).catch((error) => {
|
|
121
|
+
console.error(`[pidance] 启动失败:${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
+
process.exit(1);
|
|
110
123
|
});
|
|
111
|
-
|
|
112
|
-
// systemd KillMode=control-group 会给包装进程和 next-server 同时发 SIGTERM。
|
|
113
|
-
// next-server 有活跃 SSE/会话时经常不退出,拖到 TimeoutStopSec=120s 才被 SIGKILL,
|
|
114
|
-
// 一键升级的 systemctl restart 会先超时误报失败。包装进程留下做限期回收。
|
|
115
|
-
const CHILD_STOP_GRACE_MS = 8_000;
|
|
116
|
-
let stopping = false;
|
|
117
|
-
function requestChildStop() {
|
|
118
|
-
if (stopping) return;
|
|
119
|
-
stopping = true;
|
|
120
|
-
try {
|
|
121
|
-
if (child.exitCode == null && !child.killed) child.kill("SIGTERM");
|
|
122
|
-
} catch {
|
|
123
|
-
/* already gone */
|
|
124
|
-
}
|
|
125
|
-
const force = setTimeout(() => {
|
|
126
|
-
try {
|
|
127
|
-
if (child.exitCode == null) child.kill("SIGKILL");
|
|
128
|
-
} catch {
|
|
129
|
-
/* ignore */
|
|
130
|
-
}
|
|
131
|
-
}, CHILD_STOP_GRACE_MS);
|
|
132
|
-
if (typeof force.unref === "function") force.unref();
|
|
133
|
-
child.once("exit", () => clearTimeout(force));
|
|
134
|
-
}
|
|
135
|
-
process.on("SIGTERM", requestChildStop);
|
|
136
|
-
process.on("SIGINT", requestChildStop);
|
|
137
|
-
|
|
138
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const { existsSync, realpathSync } = require("fs");
|
|
5
|
+
const { WebSocketServer } = require("ws");
|
|
6
|
+
const ptyWss = new WebSocketServer({ noServer: true });
|
|
7
|
+
|
|
8
|
+
function sanitizeEnv(env) {
|
|
9
|
+
const next = { ...env };
|
|
10
|
+
delete next.PI_WEB_PASSWORD;
|
|
11
|
+
delete next.PIDANCE_PASSWORD;
|
|
12
|
+
return next;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function tryLoadNodePty() {
|
|
16
|
+
try {
|
|
17
|
+
const loaded = require("node-pty");
|
|
18
|
+
if (typeof loaded.spawn !== "function") return null;
|
|
19
|
+
return loaded;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolvePtyCwd(cwd) {
|
|
26
|
+
if (!existsSync(cwd)) throw new Error("PTY cwd 不存在");
|
|
27
|
+
return realpathSync(cwd);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function startPtySession(options) {
|
|
31
|
+
const spawner = options.pty === undefined ? tryLoadNodePty() : options.pty;
|
|
32
|
+
if (!spawner) throw new Error("node-pty 不可用");
|
|
33
|
+
const cwd = resolvePtyCwd(options.cwd);
|
|
34
|
+
const cols = options.cols && options.cols > 0 ? options.cols : 80;
|
|
35
|
+
const rows = options.rows && options.rows > 0 ? options.rows : 24;
|
|
36
|
+
const shell = process.env.SHELL && process.env.SHELL.length > 0 ? process.env.SHELL : "/bin/bash";
|
|
37
|
+
const proc = spawner.spawn(shell, [], {
|
|
38
|
+
name: "xterm-256color",
|
|
39
|
+
cols,
|
|
40
|
+
rows,
|
|
41
|
+
cwd,
|
|
42
|
+
env: sanitizeEnv(options.env ?? process.env),
|
|
43
|
+
});
|
|
44
|
+
let disposed = false;
|
|
45
|
+
const onChunk = (data) => {
|
|
46
|
+
if (!disposed) {
|
|
47
|
+
const text = typeof data === "string" ? data : data.toString();
|
|
48
|
+
options.onData(text);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (typeof proc.on === "function") proc.on("data", onChunk);
|
|
52
|
+
else if (typeof proc.onData === "function") proc.onData(onChunk);
|
|
53
|
+
const onGone = (e) => {
|
|
54
|
+
if (!disposed) options.onExit(typeof e === "object" && e ? e.exitCode : 0);
|
|
55
|
+
};
|
|
56
|
+
if (typeof proc.on === "function") proc.on("exit", onGone);
|
|
57
|
+
else if (typeof proc.onExit === "function") proc.onExit(onGone);
|
|
58
|
+
return {
|
|
59
|
+
pid: proc.pid,
|
|
60
|
+
write: (data) => {
|
|
61
|
+
if (!disposed) proc.write(data);
|
|
62
|
+
},
|
|
63
|
+
resize: (nextCols, nextRows) => {
|
|
64
|
+
if (!disposed && nextCols > 0 && nextRows > 0) proc.resize(nextCols, nextRows);
|
|
65
|
+
},
|
|
66
|
+
dispose: () => {
|
|
67
|
+
if (disposed) return;
|
|
68
|
+
disposed = true;
|
|
69
|
+
try {
|
|
70
|
+
if (proc.pid > 0) process.kill(-proc.pid, "SIGTERM");
|
|
71
|
+
} catch {
|
|
72
|
+
/* 进程组可能已不在 */
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
proc.kill("SIGTERM");
|
|
76
|
+
} catch {
|
|
77
|
+
/* 已退出 */
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function attachPtyToWebSocket(ws, cwd) {
|
|
84
|
+
const { spawn } = require("child_process");
|
|
85
|
+
const path = require("path");
|
|
86
|
+
const workerPath = path.join(__dirname, "pty-worker.js");
|
|
87
|
+
const child = spawn(process.execPath, [workerPath, cwd, "80", "24"], {
|
|
88
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
89
|
+
env: process.env,
|
|
90
|
+
});
|
|
91
|
+
child.stderr.setEncoding("utf8");
|
|
92
|
+
child.stderr.on("data", (data) => console.warn("[pidance] pty-worker stderr", data));
|
|
93
|
+
child.on("error", (error) => console.warn("[pidance] pty-worker spawn", error.message));
|
|
94
|
+
const session = {
|
|
95
|
+
pid: child.pid,
|
|
96
|
+
write: (data) => child.stdin.write(`${JSON.stringify({ type: "in", d: data })}\n`),
|
|
97
|
+
resize: (cols, rows) => child.stdin.write(`${JSON.stringify({ type: "rs", cols, rows })}\n`),
|
|
98
|
+
dispose: () => {
|
|
99
|
+
try { child.kill("SIGTERM"); } catch { /* ignore */ }
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
child.stdout.setEncoding("utf8");
|
|
103
|
+
// pty-worker 输出按 JSON Lines 帧化:{type:"out",d:...},避免与终端原始输出混淆。
|
|
104
|
+
let stdoutBuf = "";
|
|
105
|
+
child.stdout.on("data", (data) => {
|
|
106
|
+
stdoutBuf += typeof data === "string" ? data : Buffer.from(data).toString("utf8");
|
|
107
|
+
let idx;
|
|
108
|
+
while ((idx = stdoutBuf.indexOf("\n")) >= 0) {
|
|
109
|
+
const line = stdoutBuf.slice(0, idx);
|
|
110
|
+
stdoutBuf = stdoutBuf.slice(idx + 1);
|
|
111
|
+
if (!line) continue;
|
|
112
|
+
try {
|
|
113
|
+
const msg = JSON.parse(line);
|
|
114
|
+
if (msg && msg.type === "out" && typeof msg.d === "string") {
|
|
115
|
+
// 统一用 JSON 帧发给浏览器,避免普通输出与控制消息混淆。
|
|
116
|
+
ws.send(JSON.stringify({ type: "out", d: msg.d }));
|
|
117
|
+
}
|
|
118
|
+
} catch { /* 非 JSON 的旧协议输出,尽量原样转发 */ try { ws.send(line); } catch { /* ignore */ } }
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
child.on("exit", (code) => {
|
|
122
|
+
try {
|
|
123
|
+
if (ws.readyState === 1) {
|
|
124
|
+
ws.send(JSON.stringify({ type: "exit", code: code ?? 0 }));
|
|
125
|
+
ws.close();
|
|
126
|
+
}
|
|
127
|
+
} catch { /* ignore */ }
|
|
128
|
+
});
|
|
129
|
+
ws.on("message", (raw) => {
|
|
130
|
+
const text = typeof raw === "string" ? raw : raw.toString();
|
|
131
|
+
let parsed;
|
|
132
|
+
try { parsed = JSON.parse(text); } catch { return; }
|
|
133
|
+
if (parsed && parsed.type === "in" && typeof parsed.d === "string") session.write(parsed.d);
|
|
134
|
+
if (parsed && parsed.type === "rs" && parsed.cols > 0 && parsed.rows > 0) session.resize(parsed.cols, parsed.rows);
|
|
135
|
+
});
|
|
136
|
+
ws.on("close", () => session.dispose());
|
|
137
|
+
ws.on("error", () => session.dispose());
|
|
138
|
+
try { ws.send(JSON.stringify({ type: "out", d: "\r\n[pidance] terminal ready\r\n" })); } catch { /* ignore */ }
|
|
139
|
+
return session;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function completePtyUpgrade(req, socket, head, cwd) {
|
|
143
|
+
ptyWss.handleUpgrade(req, socket, head, (ws) => {
|
|
144
|
+
ptyWss.emit("connection", ws, req);
|
|
145
|
+
attachPtyToWebSocket(ws, cwd);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { startPtySession, tryLoadNodePty, resolvePtyCwd, attachPtyToWebSocket, completePtyUpgrade };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const pty = require("node-pty");
|
|
5
|
+
|
|
6
|
+
const cwd = process.argv[2] || process.cwd();
|
|
7
|
+
const cols = Number(process.argv[3]) || 80;
|
|
8
|
+
const rows = Number(process.argv[4]) || 24;
|
|
9
|
+
const shell = process.env.SHELL && process.env.SHELL.length > 0 ? process.env.SHELL : "/bin/bash";
|
|
10
|
+
const env = { ...process.env };
|
|
11
|
+
delete env.PI_WEB_PASSWORD;
|
|
12
|
+
delete env.PIDANCE_PASSWORD;
|
|
13
|
+
|
|
14
|
+
const proc = pty.spawn(shell, [], {
|
|
15
|
+
name: "xterm-256color",
|
|
16
|
+
cols,
|
|
17
|
+
rows,
|
|
18
|
+
cwd,
|
|
19
|
+
env,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
proc.onData((data) => {
|
|
23
|
+
process.stdout.write(JSON.stringify({ type: "out", d: data }) + "\n");
|
|
24
|
+
});
|
|
25
|
+
proc.onExit((e) => {
|
|
26
|
+
process.exit(e.exitCode ?? 0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
process.stdin.setEncoding("utf8");
|
|
30
|
+
let buf = "";
|
|
31
|
+
process.stdin.on("data", (chunk) => {
|
|
32
|
+
buf += chunk;
|
|
33
|
+
let idx;
|
|
34
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
35
|
+
const line = buf.slice(0, idx);
|
|
36
|
+
buf = buf.slice(idx + 1);
|
|
37
|
+
if (!line) continue;
|
|
38
|
+
let msg;
|
|
39
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
40
|
+
if (msg.type === "in" && typeof msg.d === "string") proc.write(msg.d);
|
|
41
|
+
if (msg.type === "rs" && msg.cols > 0 && msg.rows > 0) proc.resize(msg.cols, msg.rows);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
process.on("SIGTERM", () => {
|
|
46
|
+
try { proc.kill("SIGTERM"); } catch { /* ignore */ }
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
package/bin/pty-ws.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Pidance PTY WebSocket upgrade 处理器(发布包自包含:不依赖 lib/ 源码树)。
|
|
4
|
+
// 守卫逻辑与 lib/request-guard.ts / lib/ui-session.ts 保持一致的精简内联版:
|
|
5
|
+
// - Host 白名单(DNS rebinding 防护)
|
|
6
|
+
// - 可选认证:PIDANCE_PASSWORD / PI_WEB_PASSWORD 启用时接受 UI 会话 Cookie(JWT)或 Basic
|
|
7
|
+
// - 兜底:未设置密码时仅放行回环请求(fail-closed)
|
|
8
|
+
// WebSocket 升级不做 CSRF 校验:部分手机浏览器 Origin / sec-fetch 不完整。
|
|
9
|
+
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
11
|
+
const { existsSync } = require("fs");
|
|
12
|
+
const { homedir } = require("os");
|
|
13
|
+
const { createHash, createHmac, randomBytes, timingSafeEqual } = require("crypto");
|
|
14
|
+
const { isIP } = require("net");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const { tryLoadNodePty, completePtyUpgrade } = require("./pty-manager.cjs");
|
|
18
|
+
|
|
19
|
+
// ── Host 白名单(对齐 lib/request-guard.ts)──────────────────────────────
|
|
20
|
+
|
|
21
|
+
function normalizeHostname(hostname) {
|
|
22
|
+
return (hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname)
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/\.$/, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hostnameFromHostHeader(host) {
|
|
28
|
+
if (!host || /[\s/@\\]/.test(host)) return null;
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(`http://${host}`);
|
|
31
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null;
|
|
32
|
+
return normalizeHostname(url.hostname);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function allowedHosts(env) {
|
|
39
|
+
return [
|
|
40
|
+
env.PI_WEB_HOSTNAME,
|
|
41
|
+
...(env.PI_WEB_ALLOWED_HOSTS ? String(env.PI_WEB_ALLOWED_HOSTS).split(",") : []),
|
|
42
|
+
]
|
|
43
|
+
.filter((h) => typeof h === "string" && h.trim().length > 0)
|
|
44
|
+
.map((h) => h.trim());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isTrustedHost(hostHeader, env) {
|
|
48
|
+
const hostname = hostHeader ? hostnameFromHostHeader(hostHeader) : null;
|
|
49
|
+
if (!hostname) return false;
|
|
50
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || isIP(hostname) !== 0) return true;
|
|
51
|
+
const names = allowedHosts(env)
|
|
52
|
+
.map((h) => hostnameFromHostHeader(h))
|
|
53
|
+
.filter((n) => n !== null);
|
|
54
|
+
return names.includes(hostname);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isLoopbackHost(hostHeader) {
|
|
58
|
+
const hostname = hostHeader ? hostnameFromHostHeader(hostHeader) : null;
|
|
59
|
+
if (!hostname) return false;
|
|
60
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
61
|
+
const ip = isIP(hostname);
|
|
62
|
+
if (ip === 4) return hostname.startsWith("127.");
|
|
63
|
+
if (ip === 6) return hostname === "::1" || hostname === "0:0:0:0:0:0:0:1";
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── 认证(对齐 lib/request-guard.ts + lib/ui-session.ts 精简版)────────────
|
|
68
|
+
|
|
69
|
+
const UI_SESSION_COOKIE_NAME = "pidance_ui_session";
|
|
70
|
+
|
|
71
|
+
function resolvePassword(env) {
|
|
72
|
+
const p =
|
|
73
|
+
env && env.PIDANCE_PASSWORD && env.PIDANCE_PASSWORD.length > 0
|
|
74
|
+
? env.PIDANCE_PASSWORD
|
|
75
|
+
: env && env.PI_WEB_PASSWORD;
|
|
76
|
+
return typeof p === "string" && p.length > 0 ? p : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function passwordEnabled(env) {
|
|
80
|
+
return resolvePassword(env) !== null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sha256(input) {
|
|
84
|
+
return createHash("sha256").update(input, "utf8").digest();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function safeEqual(a, b) {
|
|
88
|
+
return timingSafeEqual(sha256(a), sha256(b));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function checkBasicAuth(headers, env) {
|
|
92
|
+
const password = resolvePassword(env);
|
|
93
|
+
if (!passwordEnabled(env) || !password) return false;
|
|
94
|
+
const auth = headers.authorization;
|
|
95
|
+
if (!auth) return false;
|
|
96
|
+
const match = /^Basic\s+(\S+)$/i.exec(auth);
|
|
97
|
+
if (!match) return false;
|
|
98
|
+
let decoded;
|
|
99
|
+
try {
|
|
100
|
+
const buf = Buffer.from(match[1], "base64");
|
|
101
|
+
if (buf.toString("base64") !== match[1]) return false;
|
|
102
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const idx = decoded.indexOf(":");
|
|
107
|
+
if (idx === -1) return false;
|
|
108
|
+
return safeEqual(decoded.slice(0, idx), "pi") && safeEqual(decoded.slice(idx + 1), password);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseCookieValue(cookieHeader, name) {
|
|
112
|
+
if (!cookieHeader) return null;
|
|
113
|
+
for (const part of String(cookieHeader).split(";")) {
|
|
114
|
+
const idx = part.indexOf("=");
|
|
115
|
+
if (idx <= 0) continue;
|
|
116
|
+
const key = part.slice(0, idx).trim();
|
|
117
|
+
if (key !== name) continue;
|
|
118
|
+
const raw = part.slice(idx + 1).trim();
|
|
119
|
+
try {
|
|
120
|
+
return decodeURIComponent(raw);
|
|
121
|
+
} catch {
|
|
122
|
+
return raw;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function defaultSecretFilePath() {
|
|
129
|
+
const root = process.env.PI_CODING_AGENT_DIR || path.join(homedir(), ".pi", "agent");
|
|
130
|
+
return path.join(root, "pidance-ui-jwt-secret");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getOrCreateJwtSecret(env) {
|
|
134
|
+
const fromEnv = env.PIDANCE_UI_JWT_SECRET || env.OPENCODE_JWT_SECRET;
|
|
135
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv.trim();
|
|
136
|
+
const filePath = defaultSecretFilePath();
|
|
137
|
+
try {
|
|
138
|
+
if (fs.existsSync(filePath)) {
|
|
139
|
+
const existing = fs.readFileSync(filePath, "utf8").trim();
|
|
140
|
+
if (existing) return existing;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// 读失败则重建
|
|
144
|
+
}
|
|
145
|
+
const secret = randomBytes(32).toString("hex");
|
|
146
|
+
try {
|
|
147
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
148
|
+
fs.writeFileSync(filePath, secret, { mode: 0o600 });
|
|
149
|
+
} catch {
|
|
150
|
+
// 无法落盘时仍返回内存密钥(进程内有效)
|
|
151
|
+
}
|
|
152
|
+
return secret;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function verifyUiSessionJwt(token, secret, nowMs) {
|
|
156
|
+
if (!token || !secret) return false;
|
|
157
|
+
const parts = token.split(".");
|
|
158
|
+
if (parts.length !== 3) return false;
|
|
159
|
+
const [header, payload, sig] = parts;
|
|
160
|
+
const data = `${header}.${payload}`;
|
|
161
|
+
const expected = createHmac("sha256", secret).update(data).digest();
|
|
162
|
+
let actual;
|
|
163
|
+
try {
|
|
164
|
+
actual = Buffer.from(sig, "base64url");
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) return false;
|
|
169
|
+
try {
|
|
170
|
+
const body = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
171
|
+
if (body.type !== "ui-session") return false;
|
|
172
|
+
if (typeof body.exp !== "number" || body.exp * 1000 <= nowMs) return false;
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function checkUiSessionCookie(headers, env) {
|
|
180
|
+
if (!passwordEnabled(env)) return false;
|
|
181
|
+
const token = parseCookieValue(headers.cookie, UI_SESSION_COOKIE_NAME);
|
|
182
|
+
if (!token) return false;
|
|
183
|
+
return verifyUiSessionJwt(token, getOrCreateJwtSecret(env), Date.now());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function checkAuthenticated(headers, env) {
|
|
187
|
+
return checkUiSessionCookie(headers, env) || checkBasicAuth(headers, env);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── upgrade 处理(原 lib/pty-ws.ts)─────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
function header(req, name) {
|
|
193
|
+
const value = req.headers[name];
|
|
194
|
+
if (typeof value === "string") return value;
|
|
195
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function ptyGuardHeaders(req) {
|
|
200
|
+
const host = header(req, "host");
|
|
201
|
+
const rawUrl = req.url || "/api/pty";
|
|
202
|
+
const url = `http://${host || "127.0.0.1"}${rawUrl}`;
|
|
203
|
+
let pathname = "/api/pty";
|
|
204
|
+
try {
|
|
205
|
+
pathname = new URL(url).pathname;
|
|
206
|
+
} catch {
|
|
207
|
+
// 用默认
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
host,
|
|
211
|
+
origin: header(req, "origin"),
|
|
212
|
+
secFetchSite: header(req, "sec-fetch-site"),
|
|
213
|
+
secFetchMode: header(req, "sec-fetch-mode"),
|
|
214
|
+
secFetchDest: header(req, "sec-fetch-dest"),
|
|
215
|
+
secFetchUser: header(req, "sec-fetch-user"),
|
|
216
|
+
authorization: header(req, "authorization"),
|
|
217
|
+
cookie: header(req, "cookie"),
|
|
218
|
+
method: "GET",
|
|
219
|
+
url,
|
|
220
|
+
pathname,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function reject(socket, status, message) {
|
|
225
|
+
try {
|
|
226
|
+
socket.write(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
227
|
+
} catch {
|
|
228
|
+
// ignore
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
socket.destroy();
|
|
232
|
+
} catch {
|
|
233
|
+
// ignore
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function handlePtyUpgrade(req, socket, head) {
|
|
238
|
+
if (!tryLoadNodePty()) {
|
|
239
|
+
reject(socket, 503, "PTY Unavailable");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const guardHeaders = ptyGuardHeaders(req);
|
|
243
|
+
if (!guardHeaders.pathname.startsWith("/api/pty")) {
|
|
244
|
+
reject(socket, 404, "Not Found");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// WebSocket 升级:部分手机浏览器 Origin / sec-fetch 不完整,CSRF 会误杀。
|
|
248
|
+
// 仍校验 Host 白名单和登录(Cookie / Basic)。
|
|
249
|
+
if (!isTrustedHost(guardHeaders.host, process.env)) {
|
|
250
|
+
reject(socket, 403, "untrusted-host");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (passwordEnabled(process.env)) {
|
|
254
|
+
if (!checkAuthenticated(guardHeaders, process.env)) {
|
|
255
|
+
reject(socket, 401, "auth-required");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
} else if (!isLoopbackHost(guardHeaders.host)) {
|
|
259
|
+
// fail-closed 兜底:未设密码时仅放行回环请求(与 middleware 一致)。
|
|
260
|
+
reject(socket, 401, "auth-required");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const cwd = homedir();
|
|
264
|
+
if (!existsSync(cwd)) {
|
|
265
|
+
reject(socket, 500, "home not found");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
completePtyUpgrade(req, socket, head, cwd);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = { handlePtyUpgrade, ptyGuardHeaders };
|