@henlii/pidance 0.2.5 → 0.2.7

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.
Files changed (242) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +7 -6
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.json +4 -2
  6. package/.next/routes-manifest.json +6 -0
  7. package/.next/server/app/_global-error/page.js +3 -3
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/server/app/_not-found/page.js +2 -2
  18. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/server/app/_not-found.html +1 -1
  20. package/.next/server/app/_not-found.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  24. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  27. package/.next/server/app/api/about/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
  29. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  30. package/.next/server/app/api/agent/[id]/route.js +1 -1
  31. package/.next/server/app/api/agent/new/route.js +1 -1
  32. package/.next/server/app/api/agent/running/events/route.js +1 -1
  33. package/.next/server/app/api/agent/running/route.js +1 -1
  34. package/.next/server/app/api/agent-settings/route.js +2 -2
  35. package/.next/server/app/api/attachments/route.js +1 -1
  36. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  39. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  40. package/.next/server/app/api/auth/providers/route.js +1 -1
  41. package/.next/server/app/api/auth/ui-session/route.js +1 -1
  42. package/.next/server/app/api/cwd/browse/route.js +1 -1
  43. package/.next/server/app/api/cwd/validate/route.js +1 -1
  44. package/.next/server/app/api/default-cwd/route.js +1 -1
  45. package/.next/server/app/api/file-index/route.js +1 -1
  46. package/.next/server/app/api/files/[...path]/route.js +2 -2
  47. package/.next/server/app/api/git/diff/route.js +1 -1
  48. package/.next/server/app/api/git/status/route.js +1 -1
  49. package/.next/server/app/api/home/route.js +1 -1
  50. package/.next/server/app/api/models/route.js +1 -1
  51. package/.next/server/app/api/models-config/raw/route.js +1 -1
  52. package/.next/server/app/api/models-config/route.js +1 -1
  53. package/.next/server/app/api/models-config/test/route.js +1 -1
  54. package/.next/server/app/api/plugins/route.js +1 -1
  55. package/.next/server/app/api/plugins/search/route.js +1 -1
  56. package/.next/server/app/api/plugins/updates/route.js +1 -1
  57. package/.next/server/app/api/preferences/route.js +1 -1
  58. package/.next/server/app/api/project-assets/route.js +2 -2
  59. package/.next/server/app/api/prompts/route.js +1 -1
  60. package/.next/server/app/api/pty/route.js +3 -0
  61. package/.next/server/app/api/pty/route_client-reference-manifest.js +1 -0
  62. package/.next/server/app/api/sessions/[id]/archive/route.js +1 -1
  63. package/.next/server/app/api/sessions/[id]/auto-name/route.js +3 -3
  64. package/.next/server/app/api/sessions/[id]/command-entry/route.js +1 -1
  65. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  66. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  67. package/.next/server/app/api/sessions/[id]/export/route.js +3 -3
  68. package/.next/server/app/api/sessions/[id]/info/route.js +1 -1
  69. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  70. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  71. package/.next/server/app/api/sessions/[id]/tool-results/[toolCallId]/route.js +1 -1
  72. package/.next/server/app/api/sessions/route.js +1 -1
  73. package/.next/server/app/api/sessions/search/route.js +1 -1
  74. package/.next/server/app/api/settings/raw/route.js +2 -2
  75. package/.next/server/app/api/skills/check/route.js +1 -1
  76. package/.next/server/app/api/skills/install/route.js +1 -1
  77. package/.next/server/app/api/skills/route.js +2 -2
  78. package/.next/server/app/api/skills/search/route.js +1 -1
  79. package/.next/server/app/api/skills/update/route.js +1 -1
  80. package/.next/server/app/api/subagent-runs/route.js +1 -1
  81. package/.next/server/app/api/update/apply/route.js +3 -3
  82. package/.next/server/app/api/update/check/route.js +2 -2
  83. package/.next/server/app/api/worktrees/route.js +1 -1
  84. package/.next/server/app/icon.png/route.js +1 -1
  85. package/.next/server/app/index.html +1 -1
  86. package/.next/server/app/index.rsc +6 -5
  87. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -3
  88. package/.next/server/app/index.segments/_full.segment.rsc +6 -5
  89. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  91. package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
  92. package/.next/server/app/page.js +22 -6
  93. package/.next/server/app/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app-paths-manifest.json +7 -6
  95. package/.next/server/chunks/188.js +1 -1
  96. package/.next/server/chunks/5582.js +1 -1
  97. package/.next/server/chunks/7594.js +1 -1
  98. package/.next/server/middleware-build-manifest.js +1 -1
  99. package/.next/server/pages/404.html +1 -1
  100. package/.next/server/pages/500.html +1 -1
  101. package/.next/server/server-reference-manifest.json +1 -1
  102. package/.next/static/01veEEO0MbroyjY72tWyr/_buildManifest.js +1 -0
  103. package/.next/static/chunks/7874-4f48b670898adf75.js +1 -0
  104. package/.next/static/chunks/9780-96bbb56af7ed70f1.js +1 -0
  105. package/.next/static/chunks/app/_global-error/page-212bed9d7023fb3f.js +1 -0
  106. package/.next/static/chunks/app/api/about/route-212bed9d7023fb3f.js +1 -0
  107. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-212bed9d7023fb3f.js +1 -0
  108. package/.next/static/chunks/app/api/agent/[id]/events/route-212bed9d7023fb3f.js +1 -0
  109. package/.next/static/chunks/app/api/agent/[id]/route-212bed9d7023fb3f.js +1 -0
  110. package/.next/static/chunks/app/api/agent/new/route-212bed9d7023fb3f.js +1 -0
  111. package/.next/static/chunks/app/api/agent/running/events/route-212bed9d7023fb3f.js +1 -0
  112. package/.next/static/chunks/app/api/agent/running/route-212bed9d7023fb3f.js +1 -0
  113. package/.next/static/chunks/app/api/agent-settings/route-212bed9d7023fb3f.js +1 -0
  114. package/.next/static/chunks/app/api/attachments/route-212bed9d7023fb3f.js +1 -0
  115. package/.next/static/chunks/app/api/auth/all-providers/route-212bed9d7023fb3f.js +1 -0
  116. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-212bed9d7023fb3f.js +1 -0
  117. package/.next/static/chunks/app/api/auth/login/[provider]/route-212bed9d7023fb3f.js +1 -0
  118. package/.next/static/chunks/app/api/auth/logout/[provider]/route-212bed9d7023fb3f.js +1 -0
  119. package/.next/static/chunks/app/api/auth/providers/route-212bed9d7023fb3f.js +1 -0
  120. package/.next/static/chunks/app/api/auth/ui-session/route-212bed9d7023fb3f.js +1 -0
  121. package/.next/static/chunks/app/api/cwd/browse/route-212bed9d7023fb3f.js +1 -0
  122. package/.next/static/chunks/app/api/cwd/validate/route-212bed9d7023fb3f.js +1 -0
  123. package/.next/static/chunks/app/api/default-cwd/route-212bed9d7023fb3f.js +1 -0
  124. package/.next/static/chunks/app/api/file-index/route-212bed9d7023fb3f.js +1 -0
  125. package/.next/static/chunks/app/api/files/[...path]/route-212bed9d7023fb3f.js +1 -0
  126. package/.next/static/chunks/app/api/git/diff/route-212bed9d7023fb3f.js +1 -0
  127. package/.next/static/chunks/app/api/git/status/route-212bed9d7023fb3f.js +1 -0
  128. package/.next/static/chunks/app/api/home/route-212bed9d7023fb3f.js +1 -0
  129. package/.next/static/chunks/app/api/models/route-212bed9d7023fb3f.js +1 -0
  130. package/.next/static/chunks/app/api/models-config/raw/route-212bed9d7023fb3f.js +1 -0
  131. package/.next/static/chunks/app/api/models-config/route-212bed9d7023fb3f.js +1 -0
  132. package/.next/static/chunks/app/api/models-config/test/route-212bed9d7023fb3f.js +1 -0
  133. package/.next/static/chunks/app/api/plugins/route-212bed9d7023fb3f.js +1 -0
  134. package/.next/static/chunks/app/api/plugins/search/route-212bed9d7023fb3f.js +1 -0
  135. package/.next/static/chunks/app/api/plugins/updates/route-212bed9d7023fb3f.js +1 -0
  136. package/.next/static/chunks/app/api/preferences/route-212bed9d7023fb3f.js +1 -0
  137. package/.next/static/chunks/app/api/project-assets/route-212bed9d7023fb3f.js +1 -0
  138. package/.next/static/chunks/app/api/prompts/route-212bed9d7023fb3f.js +1 -0
  139. package/.next/static/chunks/app/api/pty/route-212bed9d7023fb3f.js +1 -0
  140. package/.next/static/chunks/app/api/sessions/[id]/archive/route-212bed9d7023fb3f.js +1 -0
  141. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-212bed9d7023fb3f.js +1 -0
  142. package/.next/static/chunks/app/api/sessions/[id]/command-entry/route-212bed9d7023fb3f.js +1 -0
  143. package/.next/static/chunks/app/api/sessions/[id]/context/route-212bed9d7023fb3f.js +1 -0
  144. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-212bed9d7023fb3f.js +1 -0
  145. package/.next/static/chunks/app/api/sessions/[id]/export/route-212bed9d7023fb3f.js +1 -0
  146. package/.next/static/chunks/app/api/sessions/[id]/info/route-212bed9d7023fb3f.js +1 -0
  147. package/.next/static/chunks/app/api/sessions/[id]/route-212bed9d7023fb3f.js +1 -0
  148. package/.next/static/chunks/app/api/sessions/[id]/state/route-212bed9d7023fb3f.js +1 -0
  149. package/.next/static/chunks/app/api/sessions/[id]/tool-results/[toolCallId]/route-212bed9d7023fb3f.js +1 -0
  150. package/.next/static/chunks/app/api/sessions/route-212bed9d7023fb3f.js +1 -0
  151. package/.next/static/chunks/app/api/sessions/search/route-212bed9d7023fb3f.js +1 -0
  152. package/.next/static/chunks/app/api/settings/raw/route-212bed9d7023fb3f.js +1 -0
  153. package/.next/static/chunks/app/api/skills/check/route-212bed9d7023fb3f.js +1 -0
  154. package/.next/static/chunks/app/api/skills/install/route-212bed9d7023fb3f.js +1 -0
  155. package/.next/static/chunks/app/api/skills/route-212bed9d7023fb3f.js +1 -0
  156. package/.next/static/chunks/app/api/skills/search/route-212bed9d7023fb3f.js +1 -0
  157. package/.next/static/chunks/app/api/skills/update/route-212bed9d7023fb3f.js +1 -0
  158. package/.next/static/chunks/app/api/subagent-runs/route-212bed9d7023fb3f.js +1 -0
  159. package/.next/static/chunks/app/api/update/apply/route-212bed9d7023fb3f.js +1 -0
  160. package/.next/static/chunks/app/api/update/check/route-212bed9d7023fb3f.js +1 -0
  161. package/.next/static/chunks/app/api/worktrees/route-212bed9d7023fb3f.js +1 -0
  162. package/.next/static/chunks/app/page-c76a709acf636b23.js +25 -0
  163. package/.next/static/chunks/e868780c-16e43c5968ccf2bf.js +18 -0
  164. package/.next/static/chunks/next/dist/client/components/builtin/app-error-212bed9d7023fb3f.js +1 -0
  165. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-212bed9d7023fb3f.js +1 -0
  166. package/.next/static/chunks/next/dist/client/components/builtin/not-found-212bed9d7023fb3f.js +1 -0
  167. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-212bed9d7023fb3f.js +1 -0
  168. package/.next/static/chunks/{webpack-9f5e68be126b5e81.js → webpack-d2379fd13c5c78c6.js} +1 -1
  169. package/.next/static/css/659eccb5db697b76.css +1 -0
  170. package/.next/static/css/{6faea79e75763a6a.css → b890757d9e0dd456.css} +1 -1
  171. package/README.md +1 -1
  172. package/bin/pidance-http-server.js +101 -0
  173. package/bin/pidance.js +45 -48
  174. package/bin/pty-manager.cjs +149 -0
  175. package/bin/pty-worker.js +48 -0
  176. package/bin/pty-ws.js +271 -0
  177. package/package.json +13 -3
  178. package/.next/static/chunks/1-a030cc9570b4428a.js +0 -1
  179. package/.next/static/chunks/9780-6931a654abc0ac16.js +0 -1
  180. package/.next/static/chunks/app/_global-error/page-c74291295bcd24a9.js +0 -1
  181. package/.next/static/chunks/app/api/about/route-c74291295bcd24a9.js +0 -1
  182. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-c74291295bcd24a9.js +0 -1
  183. package/.next/static/chunks/app/api/agent/[id]/events/route-c74291295bcd24a9.js +0 -1
  184. package/.next/static/chunks/app/api/agent/[id]/route-c74291295bcd24a9.js +0 -1
  185. package/.next/static/chunks/app/api/agent/new/route-c74291295bcd24a9.js +0 -1
  186. package/.next/static/chunks/app/api/agent/running/events/route-c74291295bcd24a9.js +0 -1
  187. package/.next/static/chunks/app/api/agent/running/route-c74291295bcd24a9.js +0 -1
  188. package/.next/static/chunks/app/api/agent-settings/route-c74291295bcd24a9.js +0 -1
  189. package/.next/static/chunks/app/api/attachments/route-c74291295bcd24a9.js +0 -1
  190. package/.next/static/chunks/app/api/auth/all-providers/route-c74291295bcd24a9.js +0 -1
  191. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-c74291295bcd24a9.js +0 -1
  192. package/.next/static/chunks/app/api/auth/login/[provider]/route-c74291295bcd24a9.js +0 -1
  193. package/.next/static/chunks/app/api/auth/logout/[provider]/route-c74291295bcd24a9.js +0 -1
  194. package/.next/static/chunks/app/api/auth/providers/route-c74291295bcd24a9.js +0 -1
  195. package/.next/static/chunks/app/api/auth/ui-session/route-c74291295bcd24a9.js +0 -1
  196. package/.next/static/chunks/app/api/cwd/browse/route-c74291295bcd24a9.js +0 -1
  197. package/.next/static/chunks/app/api/cwd/validate/route-c74291295bcd24a9.js +0 -1
  198. package/.next/static/chunks/app/api/default-cwd/route-c74291295bcd24a9.js +0 -1
  199. package/.next/static/chunks/app/api/file-index/route-c74291295bcd24a9.js +0 -1
  200. package/.next/static/chunks/app/api/files/[...path]/route-c74291295bcd24a9.js +0 -1
  201. package/.next/static/chunks/app/api/git/diff/route-c74291295bcd24a9.js +0 -1
  202. package/.next/static/chunks/app/api/git/status/route-c74291295bcd24a9.js +0 -1
  203. package/.next/static/chunks/app/api/home/route-c74291295bcd24a9.js +0 -1
  204. package/.next/static/chunks/app/api/models/route-c74291295bcd24a9.js +0 -1
  205. package/.next/static/chunks/app/api/models-config/raw/route-c74291295bcd24a9.js +0 -1
  206. package/.next/static/chunks/app/api/models-config/route-c74291295bcd24a9.js +0 -1
  207. package/.next/static/chunks/app/api/models-config/test/route-c74291295bcd24a9.js +0 -1
  208. package/.next/static/chunks/app/api/plugins/route-c74291295bcd24a9.js +0 -1
  209. package/.next/static/chunks/app/api/plugins/search/route-c74291295bcd24a9.js +0 -1
  210. package/.next/static/chunks/app/api/plugins/updates/route-c74291295bcd24a9.js +0 -1
  211. package/.next/static/chunks/app/api/preferences/route-c74291295bcd24a9.js +0 -1
  212. package/.next/static/chunks/app/api/project-assets/route-c74291295bcd24a9.js +0 -1
  213. package/.next/static/chunks/app/api/prompts/route-c74291295bcd24a9.js +0 -1
  214. package/.next/static/chunks/app/api/sessions/[id]/archive/route-c74291295bcd24a9.js +0 -1
  215. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-c74291295bcd24a9.js +0 -1
  216. package/.next/static/chunks/app/api/sessions/[id]/command-entry/route-c74291295bcd24a9.js +0 -1
  217. package/.next/static/chunks/app/api/sessions/[id]/context/route-c74291295bcd24a9.js +0 -1
  218. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-c74291295bcd24a9.js +0 -1
  219. package/.next/static/chunks/app/api/sessions/[id]/export/route-c74291295bcd24a9.js +0 -1
  220. package/.next/static/chunks/app/api/sessions/[id]/info/route-c74291295bcd24a9.js +0 -1
  221. package/.next/static/chunks/app/api/sessions/[id]/route-c74291295bcd24a9.js +0 -1
  222. package/.next/static/chunks/app/api/sessions/[id]/state/route-c74291295bcd24a9.js +0 -1
  223. package/.next/static/chunks/app/api/sessions/[id]/tool-results/[toolCallId]/route-c74291295bcd24a9.js +0 -1
  224. package/.next/static/chunks/app/api/sessions/route-c74291295bcd24a9.js +0 -1
  225. package/.next/static/chunks/app/api/sessions/search/route-c74291295bcd24a9.js +0 -1
  226. package/.next/static/chunks/app/api/settings/raw/route-c74291295bcd24a9.js +0 -1
  227. package/.next/static/chunks/app/api/skills/check/route-c74291295bcd24a9.js +0 -1
  228. package/.next/static/chunks/app/api/skills/install/route-c74291295bcd24a9.js +0 -1
  229. package/.next/static/chunks/app/api/skills/route-c74291295bcd24a9.js +0 -1
  230. package/.next/static/chunks/app/api/skills/search/route-c74291295bcd24a9.js +0 -1
  231. package/.next/static/chunks/app/api/skills/update/route-c74291295bcd24a9.js +0 -1
  232. package/.next/static/chunks/app/api/subagent-runs/route-c74291295bcd24a9.js +0 -1
  233. package/.next/static/chunks/app/api/update/apply/route-c74291295bcd24a9.js +0 -1
  234. package/.next/static/chunks/app/api/update/check/route-c74291295bcd24a9.js +0 -1
  235. package/.next/static/chunks/app/api/worktrees/route-c74291295bcd24a9.js +0 -1
  236. package/.next/static/chunks/app/page-c7126319d333426f.js +0 -25
  237. package/.next/static/chunks/next/dist/client/components/builtin/app-error-c74291295bcd24a9.js +0 -1
  238. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-c74291295bcd24a9.js +0 -1
  239. package/.next/static/chunks/next/dist/client/components/builtin/not-found-c74291295bcd24a9.js +0 -1
  240. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-c74291295bcd24a9.js +0 -1
  241. package/.next/static/eBSMREOce0GNNfdZMBj6n/_buildManifest.js +0 -1
  242. /package/.next/static/{eBSMREOce0GNNfdZMBj6n → 01veEEO0MbroyjY72tWyr}/_ssgManifest.js +0 -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henlii/pidance",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Pidance:面向 Pi coding agent 的开源 Web 客户端",
5
5
  "homepage": "https://github.com/henlii/pidance#readme",
6
6
  "repository": {
@@ -44,24 +44,30 @@
44
44
  "scripts": {
45
45
  "dev": "next dev -p 31415",
46
46
  "build": "next build --webpack",
47
- "start": "next start -p 31415",
47
+ "start": "node bin/pidance.js",
48
48
  "lint": "eslint .",
49
49
  "typecheck": "tsc --noEmit",
50
50
  "test": "node --test lib/*.test.mjs components/*.test.mjs hooks/*.test.mjs bin/*.test.mjs",
51
51
  "verify:render-bridge": "node scripts/verify-subagent-render-bridge.mjs",
52
+ "verify:render-bridge:installed": "node scripts/verify-installed-render-bridge.mjs",
52
53
  "test:browser": "node --test scripts/browser-regression.test.mjs",
53
54
  "check": "npm run typecheck && npm run lint && npm run test",
54
55
  "release:audit": "node scripts/audit-release-package.mjs --pre",
55
56
  "release:audit:tgz": "node scripts/audit-release-package.mjs --tgz",
57
+ "package:candidate": "node scripts/make-candidate-package.mjs",
56
58
  "release:check": "npm run check && npm run build && npm run release:audit"
57
59
  },
58
60
  "dependencies": {
59
61
  "@earendil-works/pi-coding-agent": "0.84.1",
62
+ "@xterm/addon-fit": "^0.11.0",
63
+ "@xterm/xterm": "^6.0.0",
64
+ "jiti": "^2.7.0",
60
65
  "lucide-react": "^0.562.0",
61
66
  "next": "16.2.11",
62
67
  "react": "^19.2.4",
63
68
  "react-dom": "^19.2.4",
64
- "undici": "8.5.0"
69
+ "undici": "8.5.0",
70
+ "ws": "^8.21.3"
65
71
  },
66
72
  "devDependencies": {
67
73
  "@lobehub/icons": "^5.6.0",
@@ -70,6 +76,7 @@
70
76
  "@types/react": "^19",
71
77
  "@types/react-dom": "^19",
72
78
  "@types/react-syntax-highlighter": "^15.5.13",
79
+ "@types/ws": "^8.18.1",
73
80
  "eslint": "^9",
74
81
  "eslint-config-next": "16.2.11",
75
82
  "jiti": "^2.7.0",
@@ -86,5 +93,8 @@
86
93
  "remark-math": "^6.0.0",
87
94
  "tailwindcss": "^4.2.2",
88
95
  "typescript": "^5"
96
+ },
97
+ "optionalDependencies": {
98
+ "node-pty": "^1.1.0"
89
99
  }
90
100
  }