@rexvane/piweb 0.3.18 → 0.3.20

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 (261) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -6
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +4 -4
  5. package/.next/react-loadable-manifest.json +23 -9
  6. package/.next/required-server-files.js +2 -1
  7. package/.next/required-server-files.json +2 -1
  8. package/.next/routes-manifest.json +12 -0
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/events/route.js +3 -3
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/config/route.js +1 -1
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/files/route.js +1 -1
  34. package/.next/server/app/api/files/route.js.nft.json +1 -1
  35. package/.next/server/app/api/fs/route.js +1 -0
  36. package/.next/server/app/api/fs/route.js.nft.json +1 -0
  37. package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
  38. package/.next/server/app/api/git/route.js +2 -2
  39. package/.next/server/app/api/git/route.js.nft.json +1 -1
  40. package/.next/server/app/api/growth/route.js +1 -1
  41. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  42. package/.next/server/app/api/health/route.js +1 -1
  43. package/.next/server/app/api/models/route.js +1 -1
  44. package/.next/server/app/api/models/route.js.nft.json +1 -1
  45. package/.next/server/app/api/pi-settings/route.js +1 -1
  46. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  47. package/.next/server/app/api/plugins/route.js +1 -1
  48. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  49. package/.next/server/app/api/prompts/route.js +1 -1
  50. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  51. package/.next/server/app/api/remote/route.js +8 -0
  52. package/.next/server/app/api/remote/route.js.nft.json +1 -0
  53. package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -0
  54. package/.next/server/app/api/security/route.js +1 -1
  55. package/.next/server/app/api/security/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  57. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  58. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  59. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  60. package/.next/server/app/api/sessions/import/route.js +2 -2
  61. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/route.js +1 -1
  63. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  64. package/.next/server/app/api/skills/route.js +1 -1
  65. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  66. package/.next/server/app/api/update/route.js +2 -2
  67. package/.next/server/app/api/update/route.js.nft.json +1 -1
  68. package/.next/server/app/api/version/route.js +1 -1
  69. package/.next/server/app/api/version/route.js.nft.json +1 -1
  70. package/.next/server/app/api/web-auth/route.js +3 -1
  71. package/.next/server/app/api/workspaces/route.js +1 -1
  72. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  73. package/.next/server/app/icon.svg/route.js +1 -1
  74. package/.next/server/app/index.html +1 -1
  75. package/.next/server/app/index.rsc +4 -4
  76. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  77. package/.next/server/app/index.segments/_full.segment.rsc +4 -4
  78. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/login/page.js +1 -1
  80. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/login.html +1 -1
  82. package/.next/server/app/login.rsc +3 -3
  83. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  84. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  85. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  86. package/.next/server/app/page.js +1 -1
  87. package/.next/server/app/page.js.nft.json +1 -1
  88. package/.next/server/app/page_client-reference-manifest.js +1 -1
  89. package/.next/server/app-paths-manifest.json +8 -6
  90. package/.next/server/chunks/229.js +1 -1
  91. package/.next/server/chunks/243.js +1 -0
  92. package/.next/server/chunks/393.js +1 -0
  93. package/.next/server/chunks/496.js +1 -1
  94. package/.next/server/chunks/538.js +1 -0
  95. package/.next/server/chunks/686.js +1 -4
  96. package/.next/server/chunks/802.js +2 -0
  97. package/.next/server/chunks/807.js +1 -0
  98. package/.next/server/chunks/920.js +4 -0
  99. package/.next/server/chunks/924.js +6 -0
  100. package/.next/server/instrumentation.js +1 -0
  101. package/.next/server/instrumentation.js.nft.json +1 -0
  102. package/.next/server/middleware-build-manifest.js +1 -1
  103. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  104. package/.next/server/middleware.js +4 -3
  105. package/.next/server/pages/404.html +1 -1
  106. package/.next/server/pages/500.html +1 -1
  107. package/.next/server/server-reference-manifest.json +1 -1
  108. package/.next/static/_Y3Lq0lI5RJ1tFSOnDi0L/_buildManifest.js +1 -0
  109. package/.next/static/chunks/2716.7ad073038e94a802.js +1 -0
  110. package/.next/static/chunks/316-5ea443ee6b512aa9.js +1 -0
  111. package/.next/static/chunks/4673.8a5a9e6855350e34.js +1 -0
  112. package/.next/static/chunks/4686-b1e9fa876d0ea671.js +1 -0
  113. package/.next/static/chunks/4778.e4c9ed093a413bc4.js +1 -0
  114. package/.next/static/chunks/5173.91ddb803785009f8.js +1 -0
  115. package/.next/static/chunks/839.d3fe337a9117c325.js +1 -0
  116. package/.next/static/chunks/8552.6bfe3e101250b20f.js +1 -0
  117. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +1 -0
  118. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +1 -0
  119. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +1 -0
  120. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +1 -0
  121. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +1 -0
  122. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +1 -0
  123. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +1 -0
  124. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +1 -0
  125. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +1 -0
  126. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +1 -0
  127. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +1 -0
  128. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +1 -0
  129. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +1 -0
  130. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +1 -0
  131. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +1 -0
  132. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +1 -0
  133. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +1 -0
  134. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +1 -0
  135. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +1 -0
  136. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +1 -0
  137. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +1 -0
  138. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +1 -0
  139. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +1 -0
  140. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +1 -0
  141. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +1 -0
  142. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +1 -0
  143. package/.next/static/chunks/app/login/page-5e276a1eeb8867df.js +1 -0
  144. package/.next/static/chunks/app/page-2e425ef83fba0782.js +1 -0
  145. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +1 -0
  146. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +1 -0
  147. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +1 -0
  149. package/.next/static/chunks/webpack-3614a8337843456b.js +1 -0
  150. package/.next/static/css/7391c42e1ae617f9.css +1 -0
  151. package/.next/types/app/api/fs/route.ts +353 -0
  152. package/.next/types/app/api/remote/route.ts +353 -0
  153. package/.next/types/routes.d.ts +3 -1
  154. package/.next/types/validator.ts +18 -0
  155. package/README.md +56 -10
  156. package/next.config.ts +3 -2
  157. package/package.json +3 -3
  158. package/scripts/launcher.mjs +194 -13
  159. package/scripts/release.mjs +0 -2
  160. package/src/app/api/agent/[id]/events/route.ts +11 -6
  161. package/src/app/api/fs/route.ts +119 -0
  162. package/src/app/api/growth/route.ts +11 -6
  163. package/src/app/api/models/route.ts +21 -9
  164. package/src/app/api/remote/route.ts +77 -0
  165. package/src/app/api/sessions/[id]/export/route.ts +11 -1
  166. package/src/app/api/sessions/route.ts +3 -1
  167. package/src/app/api/web-auth/route.ts +45 -49
  168. package/src/app/api/workspaces/route.ts +16 -0
  169. package/src/app/globals.css +6 -0
  170. package/src/app/login/page.tsx +8 -60
  171. package/src/components/AppShell.tsx +202 -155
  172. package/src/components/ChatInput.tsx +61 -6
  173. package/src/components/ChatWindow.tsx +274 -132
  174. package/src/components/DirectoryPicker.tsx +189 -0
  175. package/src/components/DrawerPanel.tsx +164 -0
  176. package/src/components/FileViewer.tsx +2 -2
  177. package/src/components/GrowthTree.tsx +6 -5
  178. package/src/components/MarkdownBlock.tsx +134 -0
  179. package/src/components/OutlineRail.tsx +22 -1
  180. package/src/components/ProjectPanel.tsx +197 -14
  181. package/src/components/RemoteSection.tsx +265 -0
  182. package/src/components/SessionImportSection.tsx +60 -38
  183. package/src/components/SettingsPanel.tsx +104 -57
  184. package/src/components/icons.tsx +23 -0
  185. package/src/hooks/useGrowth.ts +47 -25
  186. package/src/hooks/usePiWeb.ts +100 -14
  187. package/src/i18n.tsx +117 -28
  188. package/src/instrumentation-node.ts +35 -0
  189. package/src/instrumentation.ts +18 -0
  190. package/src/lib/agent-config.ts +134 -0
  191. package/src/lib/agent-manager.ts +243 -61
  192. package/src/lib/auth-throttle.ts +102 -0
  193. package/src/lib/auth.ts +35 -27
  194. package/src/lib/files-service.ts +61 -1
  195. package/src/lib/font.ts +84 -0
  196. package/src/lib/git-service.ts +5 -3
  197. package/src/lib/growth-service.ts +78 -30
  198. package/src/lib/growth-tracker.ts +91 -9
  199. package/src/lib/models-service.ts +36 -11
  200. package/src/lib/pi-settings.ts +10 -12
  201. package/src/lib/pi.ts +145 -18
  202. package/src/lib/plugins-service.ts +67 -35
  203. package/src/lib/remote/assets.json +4 -0
  204. package/src/lib/remote/mobile-proxy.ts +155 -0
  205. package/src/lib/remote/supervisor.ts +492 -0
  206. package/src/lib/security-service.ts +6 -11
  207. package/src/lib/session-import/claude.ts +45 -21
  208. package/src/lib/session-reader.ts +106 -3
  209. package/src/lib/skills-service.ts +34 -12
  210. package/src/lib/text-sanitize.ts +12 -0
  211. package/src/lib/theme.ts +3 -0
  212. package/src/lib/types.ts +16 -2
  213. package/src/lib/update-service.ts +0 -1
  214. package/src/lib/web-auth-shared.ts +5 -1
  215. package/src/lib/web-auth.ts +139 -22
  216. package/src/lib/web-launch-token.ts +55 -0
  217. package/src/lib/web-secret.ts +182 -0
  218. package/src/lib/workspace-store.ts +91 -7
  219. package/src/proxy.ts +46 -12
  220. package/.next/server/chunks/532.js +0 -1
  221. package/.next/static/chunks/2155.2f27fe7098987699.js +0 -1
  222. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
  223. package/.next/static/chunks/2716.93be8ad5c970c516.js +0 -1
  224. package/.next/static/chunks/4632.112d2e893d48d188.js +0 -1
  225. package/.next/static/chunks/4673.c1fb1580efc6ace7.js +0 -1
  226. package/.next/static/chunks/4686-f4a037d609cea3f2.js +0 -1
  227. package/.next/static/chunks/692-7992db0ae9cc3021.js +0 -1
  228. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
  229. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
  230. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
  231. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
  232. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
  233. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
  234. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
  235. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
  236. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
  237. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
  238. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
  239. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
  240. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
  241. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
  242. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
  243. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
  244. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
  245. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
  246. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
  247. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
  248. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
  249. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
  250. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
  251. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
  252. package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +0 -1
  253. package/.next/static/chunks/app/page-b51087831582ee37.js +0 -1
  254. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
  255. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
  256. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
  257. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
  258. package/.next/static/chunks/webpack-c31263fd83d75ce6.js +0 -1
  259. package/.next/static/css/0cd968b9b62035b5.css +0 -1
  260. package/.next/static/en03hnPiuro0rnf8QkFEe/_buildManifest.js +0 -1
  261. /package/.next/static/{en03hnPiuro0rnf8QkFEe → _Y3Lq0lI5RJ1tFSOnDi0L}/_ssgManifest.js +0 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * 浏览器会话的签名密钥(服务端专用,含 node:fs / node:crypto)。
3
+ *
4
+ * 与「密钥就是密码」的做法不同:这里是一把独立的 32 字节随机值,
5
+ * Cookie 泄露后无法离线爆破出任何用户凭据;删掉这个文件即撤销全部会话。
6
+ * 位置与其它 PiWeb 状态一致(web-workspaces.json / web-uploads 同目录),
7
+ * 放在 pi 的 agent 目录下,用 web- 前缀。
8
+ *
9
+ * 这里手写一份 agent 目录解析,而不是 import ./pi:
10
+ * proxy(Next 16 的 Node runtime 中间件)会引用本模块,不该把整个 pi SDK 打进包里。
11
+ * 规则与 @earendil-works/pi-coding-agent 的 getAgentDir() 一致。
12
+ */
13
+ import { randomBytes } from "node:crypto";
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+
18
+ export const SECRET_BYTES = 32;
19
+ /** 存盘的文件名,位于 agent 目录下 */
20
+ export const SECRET_FILENAME = "web-secret";
21
+ const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
22
+
23
+ function expandTilde(value: string): string {
24
+ if (value === "~") return os.homedir();
25
+ if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
26
+ return value;
27
+ }
28
+
29
+ /** pi 的 agent 目录:PI_CODING_AGENT_DIR 优先,默认 ~/.pi/agent */
30
+ export function agentDir(): string {
31
+ const envDir = process.env.PI_CODING_AGENT_DIR;
32
+ if (envDir) return expandTilde(envDir);
33
+ return path.join(os.homedir(), ".pi", "agent");
34
+ }
35
+
36
+ /** 密钥文件路径:PI_WEB_SECRET_FILE 优先,默认 <agentDir>/web-secret */
37
+ export function secretFilePath(): string {
38
+ const override = process.env.PI_WEB_SECRET_FILE;
39
+ return override ? expandTilde(override) : path.join(agentDir(), SECRET_FILENAME);
40
+ }
41
+
42
+ export function encodeSecret(secret: Buffer): string {
43
+ return secret.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
44
+ }
45
+
46
+ /** 只接受 32 字节的 base64url,其它一律当作无效 */
47
+ export function decodeSecret(value: string): Buffer | undefined {
48
+ const trimmed = value.trim();
49
+ if (!BASE64URL_PATTERN.test(trimmed) || trimmed.length % 4 === 1) return undefined;
50
+ const padding = "=".repeat((4 - (trimmed.length % 4)) % 4);
51
+ const decoded = Buffer.from(trimmed.replace(/-/g, "+").replace(/_/g, "/") + padding, "base64");
52
+ return decoded.byteLength === SECRET_BYTES ? decoded : undefined;
53
+ }
54
+
55
+ /** 同一个路径只解析一次;测试改环境变量后按路径自动失效 */
56
+ const cache = new Map<string, Buffer>();
57
+
58
+ function readSecretFile(file: string): Buffer | undefined {
59
+ try {
60
+ return decodeSecret(fs.readFileSync(file, "utf8"));
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ function createSecretFile(file: string): Buffer {
67
+ const secret = randomBytes(SECRET_BYTES);
68
+ fs.mkdirSync(path.dirname(file), { recursive: true });
69
+ try {
70
+ // wx:并发启动时只有一个进程能创建,其它进程读到它写下的值
71
+ fs.writeFileSync(file, `${encodeSecret(secret)}\n`, { flag: "wx", mode: 0o600 });
72
+ return secret;
73
+ } catch (error) {
74
+ const code = (error as NodeJS.ErrnoException)?.code;
75
+ if (code !== "EEXIST") throw error;
76
+ const existing = readSecretFile(file);
77
+ if (!existing) throw new Error(`web-secret file exists but is not a valid ${SECRET_BYTES}-byte key: ${file}`);
78
+ return existing;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 取会话签名密钥,必要时生成。
84
+ * PI_WEB_SECRET 直接给出 base64url 值时优先用它(容器/多实例不想挂卷的场合)。
85
+ * 密钥无效(长度不对)时按文件缺失处理并重新生成,避免服务直接不可用。
86
+ */
87
+ export function loadWebSecret(): Buffer {
88
+ const inline = process.env.PI_WEB_SECRET;
89
+ if (inline) {
90
+ const decoded = decodeSecret(inline);
91
+ if (!decoded) throw new Error(`PI_WEB_SECRET must be ${SECRET_BYTES} base64url-encoded bytes`);
92
+ return decoded;
93
+ }
94
+ const file = secretFilePath();
95
+ const hit = cache.get(file);
96
+ if (hit) return hit;
97
+ const secret = readSecretFile(file) ?? createSecretFile(file);
98
+ cache.set(file, secret);
99
+ return secret;
100
+ }
101
+
102
+ /** 仅供测试:清掉进程内缓存 */
103
+ export function resetWebSecretCache(): void {
104
+ cache.clear();
105
+ revocationCache = null;
106
+ }
107
+
108
+ // ---------- 会话吊销 ----------
109
+ //
110
+ // Cookie 是无状态的,单靠清浏览器里的 Cookie 撤不回已经被拷走的那一份。
111
+ // 这里记两样东西(<agentDir>/web-revocations.json):
112
+ // notBefore —— 这之前签发的 Cookie 全部作废(「让所有设备重新登录」);
113
+ // revoked —— 单张 Cookie 的指纹 → 它本来的过期时间(「退出登录」只撤这一张,过期后自动清理)。
114
+ // proxy 每个请求都要查,所以按文件的 mtime/size 缓存解析结果,只多一次 stat。
115
+
116
+ export const REVOCATIONS_FILENAME = "web-revocations.json";
117
+
118
+ export interface SessionRevocations {
119
+ notBefore: number;
120
+ revoked: ReadonlyMap<string, number>;
121
+ }
122
+
123
+ const NO_REVOCATIONS: SessionRevocations = { notBefore: 0, revoked: new Map() };
124
+ let revocationCache: { file: string; stamp: string; value: SessionRevocations } | null = null;
125
+
126
+ function revocationsPath(): string {
127
+ return path.join(agentDir(), REVOCATIONS_FILENAME);
128
+ }
129
+
130
+ /** 读不懂的文件按「没有吊销」处理:它只会由下面的原子写产生,坏掉时不能把所有人锁在门外(启动令牌也换不进来) */
131
+ export function loadSessionRevocations(): SessionRevocations {
132
+ const file = revocationsPath();
133
+ let stamp: string;
134
+ try {
135
+ const stat = fs.statSync(file);
136
+ stamp = `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;
137
+ } catch {
138
+ return NO_REVOCATIONS;
139
+ }
140
+ if (revocationCache?.file === file && revocationCache.stamp === stamp) return revocationCache.value;
141
+ let value = NO_REVOCATIONS;
142
+ try {
143
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8")) as { notBefore?: unknown; revoked?: unknown };
144
+ const revoked = new Map<string, number>();
145
+ if (parsed.revoked && typeof parsed.revoked === "object") {
146
+ for (const [id, expiresAt] of Object.entries(parsed.revoked)) {
147
+ if (typeof expiresAt === "number" && Number.isFinite(expiresAt)) revoked.set(id, expiresAt);
148
+ }
149
+ }
150
+ value = { notBefore: typeof parsed.notBefore === "number" && Number.isFinite(parsed.notBefore) ? parsed.notBefore : 0, revoked };
151
+ } catch {
152
+ /* 见函数说明 */
153
+ }
154
+ revocationCache = { file, stamp, value };
155
+ return value;
156
+ }
157
+
158
+ function writeSessionRevocations(value: SessionRevocations): void {
159
+ const file = revocationsPath();
160
+ fs.mkdirSync(path.dirname(file), { recursive: true });
161
+ const temporary = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
162
+ try {
163
+ fs.writeFileSync(temporary, `${JSON.stringify({ notBefore: value.notBefore, revoked: Object.fromEntries(value.revoked) }, null, 2)}\n`, { flag: "wx", mode: 0o600 });
164
+ fs.renameSync(temporary, file);
165
+ } finally {
166
+ fs.rmSync(temporary, { force: true });
167
+ revocationCache = null;
168
+ }
169
+ }
170
+
171
+ /** 撤销一张 Cookie(id 见 web-auth 的 sessionCookieId),记到它本来过期为止;顺手清掉已经过期的记录 */
172
+ export function revokeSessionCookie(id: string, expiresAt: number, now = Date.now()): void {
173
+ const current = loadSessionRevocations();
174
+ const revoked = new Map([...current.revoked].filter(([, until]) => until > now));
175
+ revoked.set(id, expiresAt);
176
+ writeSessionRevocations({ notBefore: current.notBefore, revoked });
177
+ }
178
+
179
+ /** 让此刻之前签发的所有 Cookie 失效(所有设备都要重新用启动令牌登录);单张记录随之没用了,一并清空 */
180
+ export function revokeAllSessions(now = Date.now()): void {
181
+ writeSessionRevocations({ notBefore: Math.max(now, loadSessionRevocations().notBefore), revoked: new Map() });
182
+ }
@@ -17,6 +17,8 @@ interface WorkspaceFile {
17
17
  aliases?: Record<string, string>;
18
18
  archivedSessions?: string[];
19
19
  removedWorkspaces?: string[];
20
+ /** 项目生长能否在该目录 `git init`(目录还不是仓库根目录时问过用户):pathKey → 决定 */
21
+ growthInit?: Record<string, boolean>;
20
22
  }
21
23
 
22
24
  export interface WorkspaceRegistrySnapshot {
@@ -38,11 +40,16 @@ async function readFile(target = file()): Promise<WorkspaceFile> {
38
40
  Object.entries(typeof parsed.aliases === "object" && parsed.aliases !== null ? parsed.aliases : {})
39
41
  .filter((entry): entry is [string, string] => typeof entry[1] === "string"),
40
42
  );
43
+ const growthInit = Object.fromEntries(
44
+ Object.entries(typeof parsed.growthInit === "object" && parsed.growthInit !== null ? parsed.growthInit : {})
45
+ .filter((entry): entry is [string, boolean] => typeof entry[1] === "boolean"),
46
+ );
41
47
  return {
42
48
  workspaces: Array.isArray(parsed.workspaces) ? parsed.workspaces.filter((w) => typeof w === "string") : [],
43
49
  aliases,
44
50
  archivedSessions: Array.isArray(parsed.archivedSessions) ? parsed.archivedSessions.filter((s) => typeof s === "string") : [],
45
51
  removedWorkspaces: Array.isArray(parsed.removedWorkspaces) ? parsed.removedWorkspaces.filter((s) => typeof s === "string") : [],
52
+ ...(Object.keys(growthInit).length ? { growthInit } : {}),
46
53
  };
47
54
  } catch (error) {
48
55
  if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
@@ -66,14 +73,62 @@ async function write(data: WorkspaceFile, target: string): Promise<void> {
66
73
  }
67
74
  }
68
75
 
76
+ function emptyRegistry(): WorkspaceFile {
77
+ return { workspaces: [], aliases: {}, archivedSessions: [], removedWorkspaces: [] };
78
+ }
79
+
80
+ interface Snapshot {
81
+ path: string;
82
+ stamp: string;
83
+ data: WorkspaceFile;
84
+ }
85
+
86
+ let snapshot: Snapshot | null = null;
87
+
88
+ /** 校验戳:mtime + ctime + 大小 + inode 任一变化就回源(写到一半、被别的进程改写、原子替换都能看出来) */
89
+ function stampOf(stat: { mtimeMs: number; ctimeMs: number; size: number; ino: number }): string {
90
+ return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}:${stat.ino}`;
91
+ }
92
+
93
+ /**
94
+ * 读注册表:按校验戳复用的进程内快照。
95
+ *
96
+ * 读不需要排他锁——这里以前走 withSettingsWriteLock + withExternalSettingsLock,
97
+ * 于是侧栏每 3 秒的一次轮询都要在 ~/.pi/agent 里建出再删掉一个 `.lock` 目录;
98
+ * 只读家目录、杀软扫描、或另一个 pi 进程留着锁时,整个会话列表会直接 500 并每次重试 2 秒。
99
+ * 写路径仍然是「锁 + 原子替换」,所以并发读只会看到旧版或新版整体。
100
+ * 返回值只读:要改数据走 mutate()。
101
+ */
102
+ async function readCached(target: string): Promise<WorkspaceFile> {
103
+ let stat;
104
+ try {
105
+ stat = await fs.stat(target);
106
+ } catch (error) {
107
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
108
+ snapshot = null;
109
+ return emptyRegistry();
110
+ }
111
+ const stamp = stampOf(stat);
112
+ if (snapshot && snapshot.path === target && snapshot.stamp === stamp) return snapshot.data;
113
+ const data = await readFile(target);
114
+ snapshot = { path: target, stamp, data };
115
+ return data;
116
+ }
117
+
69
118
  async function read(): Promise<WorkspaceFile> {
70
- const target = file();
71
- return withSettingsWriteLock(target, () => readFile(target));
119
+ return readCached(file());
72
120
  }
73
121
 
74
122
  async function mutate<T>(change: (data: WorkspaceFile, target: string) => Promise<T> | T): Promise<T> {
75
123
  const target = file();
76
- return withSettingsWriteLock(target, () => withExternalSettingsLock(target, async () => change(await readFile(target), target)));
124
+ return withSettingsWriteLock(target, () => withExternalSettingsLock(target, async () => {
125
+ try {
126
+ return await change(await readFile(target), target);
127
+ } finally {
128
+ // 写完(或失败)都让下一次读重新取戳,避免把内存快照留在旧内容上
129
+ snapshot = null;
130
+ }
131
+ }));
77
132
  }
78
133
 
79
134
  export async function listAdded(): Promise<string[]> {
@@ -106,6 +161,19 @@ export async function setAlias(dir: string, name: string): Promise<Record<string
106
161
  });
107
162
  }
108
163
 
164
+ /** 项目生长能否在该目录建仓库:true 同意 / false 拒绝 / null 还没问过 */
165
+ export async function getGrowthInitConsent(dir: string): Promise<boolean | null> {
166
+ return (await read()).growthInit?.[pathKey(dir)] ?? null;
167
+ }
168
+
169
+ export async function setGrowthInitConsent(dir: string, allow: boolean): Promise<void> {
170
+ const key = pathKey(dir);
171
+ await mutate(async (data, target) => {
172
+ data.growthInit = { ...data.growthInit, [key]: allow };
173
+ await write(data, target);
174
+ });
175
+ }
176
+
109
177
  export async function getArchivedSessions(): Promise<string[]> {
110
178
  return (await read()).archivedSessions ?? [];
111
179
  }
@@ -167,22 +235,38 @@ export async function addWorkspace(dir: string): Promise<{ workspaces: string[];
167
235
  }
168
236
 
169
237
  export async function registerCwds(cwds: string[]): Promise<string[]> {
170
- return mutate(async (data, target) => {
238
+ const target = file();
239
+ // 快路径:侧栏每 3 秒轮询一次,绝大多数时候没有任何新 cwd。
240
+ // 先在内存快照上判断,没有新增就不取锁、不写盘、不读文件。
241
+ const current = await readCached(target);
242
+ const removedKeys = new Set((current.removedWorkspaces ?? []).map(pathKey));
243
+ const knownKeys = new Set(current.workspaces.map(pathKey));
244
+ let missing = false;
245
+ for (const cwd of cwds) {
246
+ if (!cwd || typeof cwd !== "string") continue;
247
+ const key = pathKey(path.resolve(cwd));
248
+ if (removedKeys.has(key) || knownKeys.has(key)) continue;
249
+ knownKeys.add(key);
250
+ missing = true;
251
+ }
252
+ if (!missing) return current.workspaces;
253
+
254
+ return mutate(async (data, writeTarget) => {
171
255
  let changed = false;
172
- const removedKeys = new Set((data.removedWorkspaces ?? []).map(pathKey));
256
+ const removed = new Set((data.removedWorkspaces ?? []).map(pathKey));
173
257
  const existingKeys = new Set(data.workspaces.map(pathKey));
174
258
 
175
259
  for (const cwd of cwds) {
176
260
  if (!cwd || typeof cwd !== "string") continue;
177
261
  const norm = path.resolve(cwd);
178
262
  const key = pathKey(norm);
179
- if (!removedKeys.has(key) && !existingKeys.has(key)) {
263
+ if (!removed.has(key) && !existingKeys.has(key)) {
180
264
  data.workspaces.push(norm);
181
265
  existingKeys.add(key);
182
266
  changed = true;
183
267
  }
184
268
  }
185
- if (changed) await write(data, target);
269
+ if (changed) await write(data, writeTarget);
186
270
  return data.workspaces;
187
271
  });
188
272
  }
package/src/proxy.ts CHANGED
@@ -1,43 +1,66 @@
1
1
  import { NextResponse, type NextRequest } from "next/server";
2
- import { isSafeHost, isSafeOrigin, validBasicAuthorization } from "@/lib/auth";
3
- import { SESSION_COOKIE, readCookieHeader, validSessionToken } from "@/lib/web-auth";
2
+ import { configuredTrustedHosts, isSafeHost, isSafeOrigin } from "@/lib/auth";
3
+ import { authEnabled, createSessionCookie, requestAuthority, sessionCookieOptions, validSessionCookie } from "@/lib/web-auth";
4
+ import { TOKEN_QUERY, validLaunchToken } from "@/lib/web-launch-token";
5
+ import { loadSessionRevocations, loadWebSecret } from "@/lib/web-secret";
4
6
 
5
7
  const READ_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
6
8
  /** 未认证也可达:登录页自身与认证接口 */
7
9
  const PUBLIC_PATHS = new Set(["/login", "/api/web-auth"]);
8
- /** 静态资源不参与认证(dev 的 assetPrefix 是 /_piweb-dev/<id>,生产是 /_next) */
10
+ /** 静态资源不参与认证(dev 的 assetPrefix 是 /_piweb-dev/<id>,生产是 _next) */
9
11
  const ASSET_PREFIXES = ["/_next/", "/_piweb-dev/"];
10
12
  const ASSET_PATHS = new Set(["/icon.svg", "/favicon.ico"]);
11
13
 
14
+ /**
15
+ * 认证网关。三件事,顺序不能换:
16
+ * 1. Host 校验 —— 无论有没有开认证都要做(DNS rebinding 防护靠它);
17
+ * 2. 同源校验 —— 非只读方法必须同源;
18
+ * 3. 凭据校验 —— 启动令牌换 Cookie / 已有 Cookie。
19
+ */
12
20
  export function proxy(request: NextRequest) {
13
- const password = process.env.PI_WEB_PASSWORD;
14
- if (!isSafeHost(request, Boolean(password))) {
21
+ if (!isSafeHost(request, configuredTrustedHosts())) {
15
22
  return NextResponse.json({ success: false, error: "untrusted host" }, { status: 403 });
16
23
  }
17
24
  if (!READ_METHODS.has(request.method) && !isSafeOrigin(request)) {
18
25
  return NextResponse.json({ success: false, error: "cross-origin request rejected" }, { status: 403 });
19
26
  }
20
27
 
21
- if (!password) return NextResponse.next();
28
+ if (!authEnabled()) return NextResponse.next();
22
29
 
23
30
  const pathname = request.nextUrl.pathname;
24
31
  if (pathname === "/api/health" && (request.method === "GET" || request.method === "HEAD")) return NextResponse.next();
25
32
  if (ASSET_PREFIXES.some((prefix) => pathname.startsWith(prefix)) || ASSET_PATHS.has(pathname)) return NextResponse.next();
26
33
 
27
- const authenticated =
28
- validBasicAuthorization(request.headers.get("authorization"), password) ||
29
- validSessionToken(readCookieHeader(request.headers.get("cookie"), SESSION_COOKIE), password);
30
- if (authenticated) return NextResponse.next();
34
+ const secret = loadWebSecret();
35
+ const revocations = loadSessionRevocations();
36
+
37
+ // 启动令牌换 Cookie:只认根路径、只认一个 token 参数、只认 GET。
38
+ // 令牌不接受出现在 API 路径上,也不接受走 Authorization 头。
39
+ if (request.method === "GET" && pathname === "/") {
40
+ const tokens = request.nextUrl.searchParams.getAll(TOKEN_QUERY);
41
+ if (tokens.length > 0) {
42
+ const authority = requestAuthority(request);
43
+ if (authority && tokens.length === 1 && validLaunchToken(tokens[0], process.env.PI_WEB_LAUNCH_TOKEN)) {
44
+ const cookie = createSessionCookie(authority, secret);
45
+ const response = redirectToCleanRoot(request);
46
+ response.cookies.set(cookie.name, cookie.value, sessionCookieOptions(request));
47
+ return response;
48
+ }
49
+ // 令牌无效但 Cookie 还有效:把地址栏里的令牌清掉,别让它留在浏览器历史里
50
+ if (validSessionCookie(request, secret, Date.now(), revocations)) return redirectToCleanRoot(request);
51
+ }
52
+ }
31
53
 
54
+ const authenticated = validSessionCookie(request, secret, Date.now(), revocations);
55
+ if (authenticated) return NextResponse.next();
32
56
  if (PUBLIC_PATHS.has(pathname)) return NextResponse.next();
33
57
 
34
- // 页面请求 → 登录页(带返回地址);API → 401,保留脚本客户端的 Basic 挑战。
58
+ // 页面请求 → 登录页(带返回地址);API → 401(没有 Basic,不发挑战头)。
35
59
  if (pathname.startsWith("/api/")) {
36
60
  return new NextResponse(JSON.stringify({ success: false, error: "authentication required" }), {
37
61
  status: 401,
38
62
  headers: {
39
63
  "Content-Type": "application/json; charset=utf-8",
40
- "WWW-Authenticate": 'Basic realm="PiWeb", charset="UTF-8"',
41
64
  "Cache-Control": "no-store",
42
65
  },
43
66
  });
@@ -48,6 +71,17 @@ export function proxy(request: NextRequest) {
48
71
  return NextResponse.redirect(login, 307);
49
72
  }
50
73
 
74
+ /** 去掉查询串的根地址:令牌不能留在地址栏和浏览器历史里 */
75
+ function redirectToCleanRoot(request: NextRequest): NextResponse {
76
+ const url = request.nextUrl.clone();
77
+ url.pathname = "/";
78
+ url.search = "";
79
+ const response = NextResponse.redirect(url, 303);
80
+ response.headers.set("cache-control", "no-store");
81
+ response.headers.set("referrer-policy", "no-referrer");
82
+ return response;
83
+ }
84
+
51
85
  export const config = {
52
86
  matcher: ["/((?!_next/|_piweb-dev/|icon\\.svg|favicon\\.ico).*)"],
53
87
  };
@@ -1 +0,0 @@
1
- exports.id=532,exports.ids=[532],exports.modules={6896:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{Iz:()=>B,JZ:()=>A,PY:()=>x,Tp:()=>y,XG:()=>D,X_:()=>G,qh:()=>E,r7:()=>C,vQ:()=>z});var e=c(51455),f=c.n(e),g=c(48161),h=c.n(g),i=c(76760),j=c.n(i),k=c(92584),l=c(31421),m=c(46193),n=c(87982),o=c(77598),p=c(49904),q=a([n]);function r(){return j().join((0,n.$M)(),"web-workspaces.json")}async function s(a=r()){try{let b=JSON.parse(await f().readFile(a,"utf8"));if(!b||"object"!=typeof b||Array.isArray(b))throw Error("invalid workspace registry");let c=Object.fromEntries(Object.entries("object"==typeof b.aliases&&null!==b.aliases?b.aliases:{}).filter(a=>"string"==typeof a[1]));return{workspaces:Array.isArray(b.workspaces)?b.workspaces.filter(a=>"string"==typeof a):[],aliases:c,archivedSessions:Array.isArray(b.archivedSessions)?b.archivedSessions.filter(a=>"string"==typeof a):[],removedWorkspaces:Array.isArray(b.removedWorkspaces)?b.removedWorkspaces.filter(a=>"string"==typeof a):[]}}catch(a){if("ENOENT"!==a.code)throw a}return{workspaces:[],aliases:{},archivedSessions:[],removedWorkspaces:[]}}function t(a){let b=j().resolve(a);return"win32"===process.platform?b.toLowerCase():b}async function u(a,b){await f().mkdir(j().dirname(b),{recursive:!0});let c=`${b}.${(0,o.randomUUID)()}.tmp`;try{await f().writeFile(c,JSON.stringify(a,null,2),{encoding:"utf8",flag:"wx",mode:384}),await f().rename(c,b)}finally{await f().unlink(c).catch(()=>void 0)}}async function v(){let a=r();return(0,p.Ax)(a,()=>s(a))}async function w(a){let b=r();return(0,p.Ax)(b,()=>(0,p._i)(b,async()=>a(await s(b),b)))}async function x(){return(await v()).removedWorkspaces??[]}async function y(a,b){let c=t(j().resolve(a));return w(async(a,d)=>{for(let b of(a.aliases||(a.aliases={}),Object.keys(a.aliases)))b!==c&&t(b)===c&&delete a.aliases[b];return b.trim()?a.aliases[c]=b.trim():delete a.aliases[c],await u(a,d),a.aliases})}async function z(){let a=await v();return{workspaces:a.workspaces,aliases:a.aliases??{},archivedSessions:a.archivedSessions??[],removedWorkspaces:a.removedWorkspaces??[]}}async function A(a){let b=j().resolve(a),c=t(b);return w(async(a,d)=>(a.archivedSessions||(a.archivedSessions=[]),a.archivedSessions.some(a=>t(a)===c)||(a.archivedSessions.push(b),await u(a,d)),a.archivedSessions))}async function B(a){let b=j().resolve(a),c=t(b);return w(async(a,b)=>{let d=a.archivedSessions??[],e=d.filter(a=>t(a)!==c);return e.length!==d.length&&(a.archivedSessions=e,await u(a,b)),e})}async function C(a){let b=j().resolve(a),c=t(b);return w(async(a,d)=>{let e=!1;if(a.workspaces.some(a=>t(a)===c)||(a.workspaces.push(b),e=!0),a.removedWorkspaces){let b=a.removedWorkspaces.filter(a=>t(a)!==c);e||=b.length!==a.removedWorkspaces.length,a.removedWorkspaces=b}return e&&await u(a,d),{workspaces:a.workspaces,removedWorkspaces:a.removedWorkspaces??[]}})}async function D(a){return w(async(b,c)=>{let d=!1,e=new Set((b.removedWorkspaces??[]).map(t)),f=new Set(b.workspaces.map(t));for(let c of a){if(!c||"string"!=typeof c)continue;let a=j().resolve(c),g=t(a);e.has(g)||f.has(g)||(b.workspaces.push(a),f.add(g),d=!0)}return d&&await u(b,c),b.workspaces})}async function E(a){let b=j().resolve(a),c=t(b);return w(async(a,d)=>{if(a.workspaces=a.workspaces.filter(a=>t(a)!==c),a.removedWorkspaces||(a.removedWorkspaces=[]),a.removedWorkspaces.some(a=>t(a)===c)||a.removedWorkspaces.push(b),a.aliases)for(let b of Object.keys(a.aliases))t(b)===c&&delete a.aliases[b];return await u(a,d),{workspaces:a.workspaces,removedWorkspaces:a.removedWorkspaces,aliases:a.aliases??{}}})}n=(q.then?(await q)():q)[0];let H=!1;function F(a,b={}){let{timeoutMs:c=3e5,windowsHide:d=!1}=b;return new Promise((b,e)=>{let f,g=(0,l.spawn)(a[0],a.slice(1),{stdio:["ignore","pipe","pipe"],windowsHide:d}),h=new m.StringDecoder("utf8"),i="",j="",k=!1;g.stdout.on("data",a=>{i+=h.write(a)}),g.stderr.on("data",a=>{j=(j+a.toString("utf8")).slice(-1e3)});let n=!1,o=a=>{n||(n=!0,clearTimeout(f),k||(k=!0,i+=h.end()),a?e(a):b(i.trim()))};g.on("error",a=>o(a)),g.on("close",a=>o(0===a?void 0:Error(j||`folder picker exited with code ${a}`))),f=setTimeout(()=>{try{g.kill()}catch{}o(Error("folder picker timed out"))},c)})}async function G(){if(H)return{path:null,canceled:!0};H=!0;try{if("win32"===process.platform){let a=j().join(k.V,"scripts","pick-folder.ps1"),b=await F(["powershell.exe","-NoProfile","-STA","-ExecutionPolicy","Bypass","-File",a,"选择工作区文件夹"],{windowsHide:!0});return b?{path:b,canceled:!1}:{path:null,canceled:!0}}if("darwin"===process.platform){let a=await F(["osascript","-e",'POSIX path of (choose folder with prompt "选择工作区文件夹")']),b=function(a){let b=a.trim();if((b=b.replace(/^alias\s+/i,"").replace(/^"(.*)"$/s,"$1")).startsWith("/"))return b.replace(/\\(.)/g,"$1");if(/^[A-Za-z][A-Za-z0-9 ]*:(.+)$/.test(b)){let a=b.slice(b.indexOf(":")+1);return`/${a.split(":").filter(Boolean).join("/")}`}return b}(a);return b?{path:b,canceled:!1}:{path:null,canceled:!0}}for(let a of[["zenity","--file-selection","--directory","--title=选择工作区文件夹"],["kdialog","--getexistingdirectory",`${h().homedir()}`,"--title","选择工作区文件夹"]])try{let b=await F(a);return b?{path:b,canceled:!1}:{path:null,canceled:!0}}catch(a){if(a instanceof Error&&/ENOENT|not found|not recognized/i.test(a.message))continue;if(/exited with code (1|5)$/.test(a instanceof Error?a.message:String(a)))return{path:null,canceled:!0};throw a}throw Error("no folder picker available: install zenity or kdialog, or type the path manually")}finally{H=!1}}d()}catch(a){d(a)}})},49904:(a,b,c)=>{"use strict";c.d(b,{Ax:()=>k,Jt:()=>m,_i:()=>l});var d=c(51455),e=c.n(d),f=c(76760),g=c.n(f),h=c(80974),i=c.n(h);let j=new Map;function k(a,b){let c=g().resolve(a),d="win32"===process.platform?c.toLowerCase():c,e=(j.get(d)??Promise.resolve()).then(b,b),f=e.then(()=>void 0,()=>void 0);return j.set(d,f),f.then(()=>{j.get(d)===f&&j.delete(d)}),e}async function l(a,b){await e().mkdir(g().dirname(a),{recursive:!0});let c=await i().lock(a,{realpath:!1,retries:{retries:20,minTimeout:20,maxTimeout:100}});try{return await b()}finally{await c()}}async function m(a){await a.flush();let b=a.drainErrors();if(b.length)throw Error(`Pi settings failed: ${b.map(({scope:a,error:b})=>`${a}: ${b.message}`).join("; ")}`,{cause:b[0].error})}},78335:()=>{},87982:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{$M:()=>i.getAgentDir,Ax:()=>Q,BW:()=>D,Cv:()=>i.SessionManager,Dc:()=>z,Df:()=>x,Ec:()=>w,G1:()=>J,Gg:()=>H,QR:()=>F,Sd:()=>I,X1:()=>E,YW:()=>r,Yb:()=>t,af:()=>n,az:()=>i.createAgentSession,fm:()=>p,jz:()=>A,rC:()=>l,uU:()=>k,v9:()=>B,vT:()=>q,wK:()=>o,wj:()=>G,xD:()=>C,ye:()=>u});var e=c(76760),f=c.n(e),g=c(73024),h=c.n(g),i=c(75430),j=a([i]);i=(j.then?(await j)():j)[0],process.env.PYTHONUTF8??="1",process.env.PYTHONIOENCODING??="utf-8";let K=null;function k(){return K||(K=i.ModelRuntime.create().catch(a=>{throw K=null,a})),K}function l(){K=null}let L=new Map,M=null;function m(){return M||(M=new i.ProjectTrustStore((0,i.getAgentDir)())),M}function n(a){let b=f().resolve(a),c=!1;try{c=(0,i.hasTrustRequiringProjectResources)(b)}catch{c=!1}if(!c)return{required:!1,trusted:!0,source:"not-required"};let d=m().get(b);if(null!==d)return{required:!0,trusted:d,source:"remembered"};let e=function(){try{let a=JSON.parse(h().readFileSync(f().join((0,i.getAgentDir)(),"settings.json"),"utf8"));return"always"===a.defaultProjectTrust||"never"===a.defaultProjectTrust?a.defaultProjectTrust:"ask"}catch{return"ask"}}();return"always"===e?{required:!0,trusted:!0,source:"default-always"}:"never"===e?{required:!0,trusted:!1,source:"default-never"}:{required:!0,trusted:!1,source:"undecided"}}function o(a,b){let c=f().resolve(a);m().set(c,b);let d=n(c),e="win32"===process.platform?c.toLowerCase():c,g=L.get(e);return g&&g.setProjectTrusted(d.trusted),d}function p(){for(let[a,b]of(M=null,L))try{b.setProjectTrusted(n(a).trusted)}catch{}}function q(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=L.get(c);return d||(d=i.SettingsManager.create(b,(0,i.getAgentDir)(),{projectTrusted:n(b).trusted}),L.set(c,d)),d}let N=new Map,O=Promise.resolve();function r(a){let b=O.then(a,a);return O=b.then(()=>void 0,()=>void 0),b}function s(a){let b=f().resolve(a);return"win32"===process.platform?b.toLowerCase():b}async function t(a){let b=q(a);b.setProjectTrusted(n(a).trusted);let c=new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:b}),d=c.reload.bind(c);c.reload=async c=>{b.setProjectTrusted(n(a).trusted),await d(c)};try{return await r(()=>c.reload()),c}catch(a){throw c.getExtensions().runtime.invalidate(),a}}function u(a){let b=s(a),c=N.get(b);return c||(c={loader:new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:q(a)}),ready:Promise.resolve(),version:0,fingerprint:""},N.set(b,c),v(a,c)),c.loader}function v(a,b){let c=r(async()=>{q(a).setProjectTrusted(n(a).trusted),b.loader.getExtensions().runtime.invalidate();let c=y(a);await b.loader.reload(),b.fingerprint=c,b.version+=1});return b.ready=c,c.catch(()=>void 0),c}function w(a){return N.get(s(a))?.version??0}async function x(a){u(a),await N.get(s(a)).ready}function y(a){let b,c,d=[String(n(a).trusted)],e=(a,b)=>{try{let c=h().statSync(a);if(d.push(`${a}:${c.mtimeMs}:${c.ctimeMs}:${c.size}`),c.isDirectory()&&b<2)for(let c of h().readdirSync(a,{withFileTypes:!0}).sort((a,b)=>a.name.localeCompare(b.name)))"node_modules"!==c.name&&e(f().join(a,c.name),b+1)}catch{d.push(`${a}:missing`)}};for(let d of(b=(0,i.getAgentDir)(),c=f().join(f().resolve(a),".pi"),[f().join(b,"prompts"),f().join(b,"skills"),f().join(b,"extensions"),f().join(c,"prompts"),f().join(c,"skills"),f().join(c,"extensions")]))e(d,0);for(let b of[f().join((0,i.getAgentDir)(),"settings.json"),f().join(a,".pi","settings.json")])e(b,2);return d.join("\n")}function z(a){let b=N.get(s(a));return!b||y(a)!==b.fingerprint}async function A(a){await x(a);let b=N.get(s(a));return!!b&&y(a)!==b.fingerprint&&(await C(a),!0)}function B(a){return N.get(s(a))?.loader}async function C(a){let b=N.get(s(a));b?await v(a,b):await x(a)}async function D(){for(let a of L.values())try{await a.reload()}catch{}}async function E(){for(let a of L.values())try{await a.reload()}catch{}P.clear();let a=[];for(let[b,c]of N)try{await v(b,c),a.push(b)}catch{}return a}let P=new Map;function F(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=P.get(c);return d||(d=new i.DefaultPackageManager({cwd:b,agentDir:(0,i.getAgentDir)(),settingsManager:q(b)}),P.set(c,d)),d}function G(a){return Buffer.from(a,"utf8").toString("base64url")}function H(a){return Buffer.from(a,"base64url").toString("utf8")}function I(a){return/^[A-Za-z0-9_-]+$/.test(a)&&a.length>8}function J(a){return i.SessionManager.open(a)}let Q={readonly:["read","grep","find","ls"],standard:["read","grep","find","ls","bash","edit","write"],full:[]};d()}catch(a){d(a)}})},92584:(a,b,c)=>{"use strict";c.d(b,{V:()=>e});var d=c(76760);let e=c.n(d)().resolve(process.env.PI_WEB_ROOT??process.cwd())},96487:()=>{}};
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2155],{2155:(e,o,r)=>{r.d(o,{Q:()=>a});var l=r(5155),t=r(2115),s=r(9992),d=r(1859),n=r(2253),i=r(1135);let a=(0,t.memo)(function({tree:e,expanded:o,fresh:r,filter:s="",onlyChanges:n=!1,selectedPath:a,onToggleDir:f,onExpandLazy:p,onOpenFile:m,onReference:u,onOpenEditor:g,revealPath:h,revealKey:v,emptyText:b}){let{t:k}=(0,d.s)(),y=(0,t.useMemo)(()=>(0,i.dG)(e,o,{onlyChanges:n,filter:s}),[e,o,n,s]),j=(0,t.useRef)(null),[x,w]=(0,t.useState)({top:0,height:600}),[z,_]=(0,t.useState)(null);(0,t.useEffect)(()=>{let e=j.current;if(!e)return;let o=()=>w({top:e.scrollTop,height:e.clientHeight});o(),e.addEventListener("scroll",o,{passive:!0});let r=new ResizeObserver(o);return r.observe(e),()=>{e.removeEventListener("scroll",o),r.disconnect()}},[]),(0,t.useEffect)(()=>{if(!h)return;let e=y.findIndex(e=>e.node.path===h);if(e<0)return;_((0,i.SG)(y[e].node));let o=j.current;if(!o)return;let r=22*e;(r<o.scrollTop||r+22>o.scrollTop+o.clientHeight)&&o.scrollTo({top:Math.max(0,r-o.clientHeight/2)})},[h,v,y]);let q=(0,t.useCallback)(e=>{let o=e.node;"dir"===o.kind?(o.lazy&&!e.expanded&&p&&p(o.path),f(o.path)):m(o)},[p,f,m]),N=Math.max(0,Math.floor(x.top/22)-12),D=Math.min(y.length,Math.ceil((x.top+x.height)/22)+12),M=y.slice(N,D);return(0,l.jsx)("div",{ref:j,className:"pw-tree min-h-0 flex-1 overflow-auto outline-none",tabIndex:0,role:"tree",onKeyDown:e=>{if(!y.length)return;let o=Math.max(0,y.findIndex(e=>(0,i.SG)(e.node)===z)),r=y[o],l=e=>{let o=y[Math.max(0,Math.min(y.length-1,e))];_((0,i.SG)(o.node));let r=j.current;if(!r)return;let l=22*Math.max(0,Math.min(y.length-1,e));l<r.scrollTop?r.scrollTop=l:l+22>r.scrollTop+r.clientHeight&&(r.scrollTop=l+22-r.clientHeight)};if("ArrowDown"===e.key)e.preventDefault(),l(null===z?0:o+1);else if("ArrowUp"===e.key)e.preventDefault(),l(o-1);else if("ArrowRight"===e.key&&r)e.preventDefault(),"dir"!==r.node.kind||r.expanded?l(o+1):q(r);else if("ArrowLeft"===e.key&&r)if(e.preventDefault(),"dir"===r.node.kind&&r.expanded)f(r.node.path);else{let e=r.node.path.includes("/")?r.node.path.slice(0,r.node.path.lastIndexOf("/")):"",o=y.findIndex(o=>"dir"===o.node.kind&&o.node.path===e);o>=0&&l(o)}else("Enter"===e.key||" "===e.key)&&r?(e.preventDefault(),q(r)):"Home"===e.key?(e.preventDefault(),l(0)):"End"===e.key&&(e.preventDefault(),l(y.length-1))},onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||_(null)},children:0===y.length?(0,l.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"10px 12px"},children:b??k.filesEmpty}):(0,l.jsx)("div",{style:{height:22*y.length,position:"relative"},children:M.map((e,o)=>(0,l.jsx)(c,{row:e,top:(N+o)*22,fresh:"file"===e.node.kind&&!!r?.has(e.node.path),selected:"file"===e.node.kind&&a===e.node.path,focused:z===(0,i.SG)(e.node),onActivate:q,onFocusRow:_,onReference:u,onOpenEditor:g,pendingLabel:k.growthPending,lazyLabel:k.growthLazyDir,deletedLabel:k.growthStatD,referenceLabel:k.fileReference,editorLabel:k.fileOpenEditor},(0,i.SG)(e.node)))})})}),c=(0,t.memo)(function({row:e,top:o,fresh:r,selected:t,focused:d,onActivate:a,onFocusRow:c,onReference:f,onOpenEditor:p,pendingLabel:m,lazyLabel:u,deletedLabel:g,referenceLabel:h,editorLabel:v}){var b;let{node:k,depth:y,expanded:j}=e,x="dir"===k.kind,w="A"===(b=k.status)?"var(--dsw-success)":"M"===b?"var(--dsw-warn)":"D"===b?"var(--dsw-danger)":"R"===b?"var(--dsw-accent)":void 0,z=x?function(e){let o=e.counts;if(!o)return null;let r=o.added+o.modified+o.deleted+o.renamed;if(o.pending&&!r)return{text:"…",color:"var(--dsw-label-caption)"};if(!r)return null;let l=o.deleted>=o.added&&o.deleted>=o.modified?"var(--dsw-danger)":o.added>=o.modified?"var(--dsw-success)":"var(--dsw-warn)";return{text:String(r),color:l}}(k):null,_=!!(k.lazy&&!k.counts),q=[x?`${k.path}/`:k.path,"D"===k.status?g:"","R"===k.status&&k.from?`← ${k.from}`:"",k.pending?m:"",_?u:""].filter(Boolean).join("\n");return(0,l.jsxs)("div",{className:"pw-tree-row group/row",role:"treeitem","aria-expanded":x?j:void 0,"aria-selected":t,"data-selected":t||void 0,"data-focused":d||void 0,"data-fresh":r?k.status??"M":void 0,"data-status":k.status,"data-pending":k.pending||void 0,"data-lazy":_?"1":void 0,style:{top:o,paddingLeft:12*y+6},title:q,onClick:()=>{c((0,i.SG)(k)),a(e)},children:[(0,l.jsx)("span",{className:"pw-tree-chevron","aria-hidden":!0,children:x&&(0,l.jsx)(s.Yq,{size:10,style:{transform:j?"rotate(90deg)":void 0,transition:"transform var(--ds-duration-fast)"}})}),(0,l.jsx)("img",{className:"pw-tree-icon","data-kind":k.kind,src:x?(0,n.Q)(k.name,j):(0,n.s)(k.name),alt:"",draggable:!1}),(0,l.jsxs)("span",{className:"pw-tree-name",style:{color:w,textDecoration:"D"===k.status?"line-through":void 0},children:[k.name,x?"/":"","D"===k.status&&(0,l.jsxs)("span",{className:"pw-tree-from",children:["(",g,")"]}),"R"===k.status&&k.from&&(0,l.jsxs)("span",{className:"pw-tree-from",children:["← ",k.from.split("/").pop()]})]}),k.pending&&(0,l.jsx)("span",{className:"pw-tree-pulse","aria-label":m}),!x&&(f||p)&&!k.lazy&&(0,l.jsxs)("span",{className:"pw-tree-actions",children:[f&&(0,l.jsx)("button",{type:"button",className:"icon-btn",style:{width:18,height:18,borderRadius:5},title:h,onClick:e=>{e.stopPropagation(),f(k.path)},children:(0,l.jsx)(s.PT,{size:11})}),p&&(0,l.jsx)("button",{type:"button",className:"icon-btn",style:{width:18,height:18,borderRadius:5},title:v,onClick:e=>{e.stopPropagation(),p(k.path)},children:(0,l.jsx)(s.lx,{size:11})})]}),!x&&k.status&&(k.add||k.del)?(0,l.jsxs)("span",{className:"pw-tree-stat",children:[k.add?(0,l.jsxs)("span",{style:{color:"var(--dsw-success)"},children:["+",k.add]}):null,k.del?(0,l.jsxs)("span",{style:{color:"var(--dsw-danger)"},children:["−",k.del]}):null]}):null,!x&&k.status&&(0,l.jsx)("span",{className:"pw-tree-badge",style:{color:w,borderColor:w},children:k.status}),z&&(0,l.jsx)("span",{className:"pw-tree-badge",style:{color:z.color,borderColor:z.color},children:z.text})]})})},2253:(e,o,r)=>{r.d(o,{s:()=>s,Q:()=>d});let l=JSON.parse('{"defaults":{"file":"file","folder":"folder","folderOpen":"folder-open"},"ext":{"7z":"zip","adoc":"asciidoc","ai":"adobe-illustrator","asc":"key","asm":"assembly","astro":"astro","avi":"video","avif":"image","bash":"console","bat":"console","bib":"bibliography","bicep":"bicep","blend":"blender","bmp":"image","bz2":"zip","c":"c","cc":"cpp","cer":"certificate","cfg":"settings","cjs":"javascript","clj":"clojure","cmake":"cmake","cmd":"console","conf":"settings","cpp":"cpp","crt":"certificate","cs":"csharp","cshtml":"razor","css":"css","csv":"table","d.ts":"typescript-def","dart":"dart","db":"database","diff":"diff","doc":"word","docx":"word","ejs":"ejs","elm":"elm","env":"tune","eot":"font","erl":"erlang","ex":"elixir","exs":"elixir","fbx":"3d","fish":"console","flac":"audio","fs":"fsharp","gif":"image","glsl":"shader","gltf":"3d","go":"go","gpg":"key","gql":"graphql","gradle":"gradle","graphql":"graphql","groovy":"groovy","gz":"zip","h":"h","hbs":"handlebars","hcl":"hcl","hlsl":"shader","hpp":"hpp","hs":"haskell","htm":"html","html":"html","ico":"image","ics":"email","ini":"settings","ipynb":"jupyter","java":"java","jl":"julia","jpeg":"image","jpg":"image","js":"javascript","json":"json","json5":"json","jsonc":"json","jsonl":"json","jsx":"react","key":"key","kt":"kotlin","kts":"kotlin","less":"less","liquid":"liquid","lock":"lock","log":"log","lua":"lua","m":"objective-c","markdown":"markdown","md":"markdown","mdx":"mdx","mjs":"javascript","mk":"makefile","mkv":"video","ml":"ocaml","mm":"objective-cpp","mo":"i18n","mov":"video","mp3":"audio","mp4":"video","nim":"nim","nix":"nix","obj":"3d","ogg":"video","otf":"font","owl":"xml","patch":"git","pdf":"pdf","pem":"key","php":"php","pl":"prolog","plist":"xml","png":"image","po":"i18n","pot":"i18n","ppt":"powerpoint","pptx":"powerpoint","prisma":"prisma","properties":"settings","proto":"proto","ps1":"powershell","psd":"adobe-photoshop","psm1":"powershell","pub":"key","pug":"pug","py":"python","pyc":"python-misc","pyi":"python","r":"r","rar":"zip","razor":"razor","rb":"ruby","rdf":"xml","rs":"rust","rst":"markdown","rtf":"word","s":"assembly","sass":"sass","scala":"scala","scss":"sass","sh":"console","shader":"shader","sig":"sml","sol":"solidity","spec.js":"test-js","spec.ts":"test-ts","sql":"database","sqlite":"database","sqlite3":"database","stl":"3d","styl":"stylus","svelte":"svelte","svg":"svg","swift":"swift","tar":"zip","test.js":"test-js","test.ts":"test-ts","test.tsx":"test-jsx","tex":"tex","tf":"terraform","tfvars":"terraform","tgz":"zip","toml":"toml","ts":"typescript","tsv":"table","tsx":"react_ts","ttf":"font","txt":"document","unity":"unity","v":"vlang","vala":"vala","vim":"vim","vue":"vue","wasm":"webassembly","wat":"webassembly","wav":"audio","webm":"video","webp":"image","woff":"font","woff2":"font","xls":"table","xlsx":"table","xml":"xml","xz":"zip","yaml":"yaml","yml":"yaml","zig":"zig","zip":"zip","zsh":"console"},"name":{".babelrc":"babel",".cursorrules":"cursor",".editorconfig":"editorconfig",".env.development":"tune",".env.example":"tune",".env.local":"tune",".env.production":"tune",".env.test":"tune",".eslintrc":"eslint",".eslintrc.js":"eslint",".eslintrc.json":"eslint",".gitattributes":"git",".gitignore":"git",".gitkeep":"git",".gitmodules":"git",".huskyrc":"husky",".lintstagedrc":"lintstaged",".npmrc":"npm",".nvmrc":"nodejs",".prettierignore":"prettier",".prettierrc":"prettier",".releaserc":"semantic-release",".travis.yml":"travis","agents.md":"agent","authors":"authors","babel.config.js":"babel","bun.lock":"bun","bun.lockb":"bun","changelog":"changelog","changelog.md":"changelog","cmakelists.txt":"cmake","code_of_conduct.md":"conduct","codeowners":"codeowners","commitlint.config.js":"commitlint","compose.yaml":"docker","compose.yml":"docker","composer.lock":"json","contributing.md":"contributing","deno.json":"deno","deno.jsonc":"deno","docker-compose.yaml":"docker","docker-compose.yml":"docker","dockerfile":"docker","ecosystem.config.js":"pm2-ecosystem","eslint.config.js":"eslint","eslint.config.mjs":"eslint","favicon.ico":"favicon","gemfile":"gemfile","go.mod":"go-mod","go.sum":"go-mod","jenkinsfile":"jenkins","jest.config.js":"jest","jest.config.ts":"jest","jsconfig.json":"jsconfig","justfile":"just","lerna.json":"lerna","license":"license","license.md":"license","license.txt":"license","makefile":"makefile","netlify.toml":"netlify","next.config.js":"next","next.config.mjs":"next","next.config.ts":"next","nodemon.json":"nodemon","now.json":"vercel","nx.json":"nx","package-lock.json":"nodejs","package.json":"nodejs","pipfile":"python-misc","pnpm-lock.yaml":"pnpm","poetry.lock":"poetry","pom.xml":"maven","postcss.config.js":"postcss","postcss.config.mjs":"postcss","procfile":"heroku","pyproject.toml":"python-misc","rakefile":"ruby","readme":"readme","readme.md":"readme","renovate.json":"renovate","requirements.txt":"python-misc","robots.txt":"robots","rollup.config.js":"rollup","rollup.config.mjs":"rollup","security.md":"lock","tailwind.config.js":"tailwindcss","tailwind.config.ts":"tailwindcss","taskfile.yml":"taskfile","tsconfig.base.json":"tsconfig","tsconfig.json":"tsconfig","turbo.json":"turborepo","uv.lock":"uv","vercel.json":"vercel","vite.config.js":"vite","vite.config.ts":"vite","vitest.config.mts":"vitest","vitest.config.ts":"vitest","webpack.config.js":"webpack","yarn.lock":"yarn"},"folder":{"__mocks__":"folder-mock","__pycache__":"folder-python","__tests__":"folder-test",".cache":"folder-temp",".circleci":"folder-circleci",".git":"folder-git",".github":"folder-github",".gitlab":"folder-gitlab",".husky":"folder-husky",".idea":"folder-intellij",".next":"folder-next",".nuxt":"folder-nuxt",".pytest_cache":"folder-python",".svelte-kit":"folder-svelte",".turbo":"folder-turborepo",".venv":"folder-environment",".vscode":"folder-vscode","@types":"folder-typescript","admin":"folder-admin","agent":"folder-robot","agents":"folder-robot","android":"folder-android","api":"folder-api","apis":"folder-api","app":"folder-app","apps":"folder-app","archive":"folder-archive","archives":"folder-archive","assets":"folder-resource","atoms":"folder-atom","audio":"folder-audio","auth":"folder-secure","aws":"folder-aws","azure":"folder-aws","backend":"folder-server","backup":"folder-backup","backups":"folder-backup","benchmark":"folder-benchmark","benchmarks":"folder-benchmark","bin":"folder-dist","blog":"folder-docs","build":"folder-dist","cache":"folder-temp","cart":"folder-cart","certificates":"folder-secure","certs":"folder-secure","chat":"folder-messages","ci":"folder-ci","cli":"folder-command","client":"folder-client","command":"folder-command","commands":"folder-command","common":"folder-shared","components":"folder-components","config":"folder-config","configs":"folder-config","configuration":"folder-config","console":"folder-console","content":"folder-content","context":"folder-context","contexts":"folder-context","contract":"folder-contract","contracts":"folder-contract","controller":"folder-controller","controllers":"folder-controller","core":"folder-core","coverage":"folder-coverage","css":"folder-css","data":"folder-database","database":"folder-database","db":"folder-database","debug":"folder-debug","demo":"folder-examples","demos":"folder-examples","deprecated":"folder-deprecated","design":"folder-theme","designs":"folder-theme","desktop":"folder-desktop","dist":"folder-dist","doc":"folder-docs","docker":"folder-docker","docs":"folder-docs","documentation":"folder-docs","draft":"folder-mock","drafts":"folder-mock","e2e":"folder-coverage","element":"folder-element","elements":"folder-element","env":"folder-environment","environment":"folder-environment","environments":"folder-environment","event":"folder-event","events":"folder-event","example":"folder-examples","examples":"folder-examples","extension":"folder-plugin","extensions":"folder-plugin","feature":"folder-features","features":"folder-features","fixture":"folder-mock","fixtures":"folder-mock","font":"folder-font","fonts":"folder-font","frontend":"folder-client","functions":"folder-functions","gcp":"folder-aws","gen":"folder-generator","generated":"folder-generator","gql":"folder-graphql","graphql":"folder-graphql","helper":"folder-helper","helpers":"folder-helper","history":"folder-backup","hooks":"folder-hook","i18n":"folder-i18n","icon":"folder-images","icons":"folder-images","image":"folder-images","images":"folder-images","img":"folder-images","include":"folder-include","includes":"folder-include","infra":"folder-server","infrastructure":"folder-server","interfaces":"folder-interface","ios":"folder-ios","java":"folder-java","job":"folder-job","jobs":"folder-job","k8s":"folder-kubernetes","keys":"folder-keys","kubernetes":"folder-kubernetes","lambda":"folder-functions","lang":"folder-i18n","langs":"folder-i18n","layout":"folder-layout","layouts":"folder-layout","less":"folder-less","lib":"folder-lib","libraries":"folder-lib","library":"folder-lib","libs":"folder-lib","locale":"folder-i18n","locales":"folder-i18n","log":"folder-log","logs":"folder-log","media":"folder-video","messages":"folder-messages","meta":"folder-meta","metadata":"folder-meta","middleware":"folder-middleware","middlewares":"folder-middleware","migration":"folder-migrations","migrations":"folder-migrations","mobile":"folder-mobile","mock":"folder-mock","mocks":"folder-mock","model":"folder-class","models":"folder-class","module":"folder-plugin","modules":"folder-plugin","molecules":"folder-molecule","node_modules":"folder-node","notebook":"folder-jupyter","notebooks":"folder-jupyter","organisms":"folder-organism","out":"folder-dist","output":"folder-dist","package":"folder-packages","packages":"folder-packages","pages":"folder-views","plugin":"folder-plugin","plugins":"folder-plugin","posts":"folder-docs","prisma":"folder-prisma","prompt":"folder-prompts","prompts":"folder-prompts","proto":"folder-proto","provider":"folder-controller","providers":"folder-controller","public":"folder-public","python":"folder-python","queue":"folder-queue","release":"folder-dist","res":"folder-resource","resource":"folder-resource","resources":"folder-resource","review":"folder-review","reviews":"folder-review","router":"folder-routes","routers":"folder-routes","routes":"folder-routes","rust":"folder-rust","sample":"folder-examples","samples":"folder-examples","sass":"folder-sass","schema":"folder-class","schemas":"folder-class","script":"folder-scripts","scripts":"folder-scripts","scss":"folder-sass","secrets":"folder-keys","seed":"folder-seeders","seeds":"folder-seeders","server":"folder-server","service":"folder-controller","services":"folder-controller","settings":"folder-config","shared":"folder-shared","site":"folder-public","skill":"folder-skills","skills":"folder-skills","sounds":"folder-audio","source":"folder-src","spec":"folder-test","specs":"folder-test","src":"folder-src","static":"folder-resource","store":"folder-store","stores":"folder-store","stories":"folder-storybook","style":"folder-css","styles":"folder-css","supabase":"folder-supabase","target":"folder-target","tasks":"folder-tasks","temp":"folder-temp","template":"folder-template","templates":"folder-template","terraform":"folder-terraform","test":"folder-test","tests":"folder-test","theme":"folder-theme","themes":"folder-theme","third-party":"folder-lib","tmp":"folder-temp","tooling":"folder-tools","tools":"folder-tools","translations":"folder-i18n","types":"folder-typescript","typings":"folder-typescript","ui":"folder-ui","util":"folder-utils","utils":"folder-utils","vendor":"folder-lib","vendors":"folder-lib","venv":"folder-environment","video":"folder-video","videos":"folder-video","view":"folder-views","views":"folder-views","web":"folder-public","website":"folder-public","widget":"folder-components","widgets":"folder-components"}}');function t(e){return`/file-icons/${e}.svg`}function s(e){let o=e.toLowerCase(),r=l.name[o];if(r)return t(r);let s=o.split(".");for(let e=1;e<s.length;e+=1){let o=s.slice(e).join("."),r=l.ext[o];if(r)return t(r)}return t(l.defaults.file)}function d(e,o){let r=l.folder[e.toLowerCase()];return r?t(o?`${r}-open`:r):t(o?l.defaults.folderOpen:l.defaults.folder)}}}]);
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2608],{2608:(e,t,l)=>{l.r(t),l.d(t,{GitPanel:()=>d});var n=l(5155),a=l(2115),s=l(8476),i=l(9665),r=l(9992),o=l(1859);let c={modified:"var(--dsw-warn)",added:"var(--dsw-success)",deleted:"var(--dsw-danger)",renamed:"var(--dsw-accent)",untracked:"var(--dsw-label-caption)",conflict:"var(--dsw-danger)"};function d({cwd:e,onClose:t,refreshKey:l=0,onAskCommit:f,onOpenFile:h}){let{t:p}=(0,o.s)(),[u,x]=(0,a.useState)(null),[g,m]=(0,a.useState)(!1),[v,b]=(0,a.useState)(""),[w,y]=(0,a.useState)(""),[j,k]=(0,a.useState)(null),[S,N]=(0,a.useState)(null),[z,C]=(0,a.useState)(!1),E=(0,a.useRef)({version:0}),R=(0,a.useRef)({version:0}),U=(0,a.useCallback)(async()=>{E.current.controller?.abort();let t=new AbortController,l=++E.current.version;E.current.controller=t;let n=()=>!t.signal.aborted&&E.current.version===l;if(e){m(!0),b("");try{let l=await fetch(`/api/git?cwd=${encodeURIComponent(e)}`,{signal:t.signal}),a=await l.json();if(!l.ok||!a.success)throw Error(a.error||"failed to load git status");n()&&x(a.data)}catch(e){n()&&b(e instanceof Error?e.message:"failed to load git status")}finally{n()&&m(!1)}}},[e]);(0,a.useEffect)(()=>(x(null),k(null),N(null),y(""),b(""),m(!1),()=>{E.current.controller?.abort(),E.current.version+=1,R.current.controller?.abort(),R.current.version+=1}),[e]),(0,a.useEffect)(()=>{U()},[U,l]);let $=(0,a.useCallback)(async t=>{R.current.controller?.abort();let l=new AbortController,n=++R.current.version;R.current.controller=l;let a=()=>!l.signal.aborted&&R.current.version===n;C(!0),N(null),y("");try{let n="file"===t.kind?`/api/git?cwd=${encodeURIComponent(e)}&diff=${encodeURIComponent(t.path)}&mode=${t.mode}`:`/api/git?cwd=${encodeURIComponent(e)}&show=${encodeURIComponent(t.hash)}`,s=await fetch(n,{signal:l.signal}),i=await s.json();if(!s.ok||!i.success)throw Error(i.error||"failed to load diff");a()&&N(i.data)}catch(e){a()&&y(e instanceof Error?e.message:"failed to load diff")}finally{a()&&C(!1)}},[e]);(0,a.useEffect)(()=>(N(null),y(""),C(!1),j&&$(j),()=>{R.current.controller?.abort(),R.current.version+=1}),[j,$,l]);let W=e=>{R.current.controller?.abort(),R.current.version+=1,N(null),y(""),k(e)},M=j?w:v||u?.statusError||"",A=(0,a.useMemo)(()=>S?.patch?(0,s.rj)(S.patch):null,[S]),D=A?(0,s.hM)(A):null,I=u?.files.filter(e=>"conflict"===e.kind)??[],L=u?.files.filter(e=>"conflict"!==e.kind&&"untracked"!==e.kind&&" "!==e.indexStatus)??[],T=u?.files.filter(e=>"conflict"!==e.kind&&"untracked"!==e.kind&&" "!==e.workStatus)??[],B=u?.files.filter(e=>"untracked"===e.kind)??[],F=j?"file"===j.kind?j.path.split("/").pop()??j.path:S?.title?.split("\n")[0]??j.hash:p.gitPanel;return(0,n.jsxs)("div",{className:"flex h-full min-h-0 flex-col p-4",children:[(0,n.jsxs)("div",{className:"mb-3 flex items-center gap-2",children:[j?(0,n.jsx)("button",{className:"icon-btn",style:{width:22,height:22},title:p.gitBack,onClick:()=>W(null),children:(0,n.jsx)(r.bQ,{size:13})}):(0,n.jsx)(r.Je,{size:15,style:{flex:"none"}}),(0,n.jsx)("span",{className:"min-w-0 flex-1 truncate",style:{fontSize:13,fontWeight:600},title:j?.kind==="file"?j.path:void 0,children:F}),j?.kind==="file"&&h&&u?.root&&(0,n.jsx)("button",{className:"icon-btn",style:{width:22,height:22},title:p.fileOpenEditor,onClick:()=>h(`${u.root}/${j.path}`),children:(0,n.jsx)(r.lx,{size:13})}),(0,n.jsx)("button",{className:"icon-btn",style:{width:22,height:22},title:p.panelRefresh,onClick:()=>j?void $(j):void U(),children:(0,n.jsx)(r.Ct,{size:13})}),(0,n.jsx)("button",{className:"icon-btn",style:{width:22,height:22},title:p.close,onClick:t,children:(0,n.jsx)(r.Bh,{size:13})})]}),M&&(0,n.jsxs)("div",{className:"mb-2 rounded-xl px-3 py-2",style:{fontSize:12.5,background:"var(--dsw-danger)",color:"white"},role:"alert",children:[!j&&(0,n.jsx)("div",{children:p.gitStatusUnknown}),M]}),(0,n.jsxs)("div",{className:"min-h-0 flex-1 overflow-y-auto",children:[j&&(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",style:{fontSize:11.5,color:"var(--dsw-label-caption)"},children:["file"===j.kind?(0,n.jsx)("span",{children:j.label}):S?.title?.split("\n")[1]?(0,n.jsx)("span",{children:S.title.split("\n")[1]}):null,D&&(0,n.jsxs)("span",{style:{fontFamily:"var(--font-mono)"},children:[(0,n.jsxs)("span",{style:{color:"var(--dsw-success)"},children:["+",D.add]})," ",(0,n.jsxs)("span",{style:{color:"var(--dsw-danger)"},children:["−",D.del]})]}),S?.truncated&&(0,n.jsx)("span",{style:{color:"var(--dsw-warn)"},children:p.gitDiffTruncated})]}),z&&!S&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"4px 2px"},children:p.panelLoading}),S?.binary&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"4px 2px"},children:p.gitBinary}),S&&!S.binary&&!A&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"4px 2px"},children:p.gitDiffEmpty}),A&&(0,n.jsx)("div",{className:"overflow-x-auto rounded-xl px-2 py-1.5",style:{background:"var(--dsw-hover)"},children:(0,n.jsx)(s.xZ,{lines:A,language:j?.kind==="file"?(0,i.f3)(j.path):void 0})})]}),!j&&g&&!u&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"4px 2px"},children:p.panelLoading}),!j&&!g&&u&&!u.available&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-danger)",padding:"8px 2px",lineHeight:1.6},children:p.gitUnavailable}),!j&&!g&&u&&u.available&&!u.isRepo&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"8px 2px",lineHeight:1.6},children:p.gitNotRepo}),!j&&u?.isRepo&&(0,n.jsxs)("div",{className:"flex flex-col gap-4",children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-2",style:{fontSize:12.5},children:[(0,n.jsxs)("span",{className:"flex items-center gap-1.5 rounded-full px-2.5 py-1",style:{background:"var(--dsw-accent-soft)",color:"var(--dsw-accent)",fontWeight:500},children:[(0,n.jsx)(r.Je,{size:12}),u.detached?p.gitDetached:u.branch??"—"]}),u.ahead>0&&(0,n.jsxs)("span",{style:{color:"var(--dsw-label-secondary)"},children:["↑ ",p.gitAhead.replace("{n}",String(u.ahead))]}),u.behind>0&&(0,n.jsxs)("span",{style:{color:"var(--dsw-label-secondary)"},children:["↓ ",p.gitBehind.replace("{n}",String(u.behind))]}),(0,n.jsx)("span",{className:"min-w-0 truncate",style:{fontSize:11.5,color:"var(--dsw-label-caption)"},title:u.upstream??void 0,children:u.upstream?p.gitUpstream.replace("{name}",u.upstream):u.detached?"":p.gitNoUpstream})]}),!M&&u.files.length>0&&f&&(0,n.jsx)("button",{type:"button",className:"btn-outline self-start",style:{height:28,padding:"0 12px",fontSize:12},onClick:f,title:p.gitAskCommitPrompt,children:p.gitAskCommit}),M?null:0===u.files.length?(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:p.gitNoChanges}):[{label:p.gitConflict,list:I,mode:"worktree"},{label:p.gitStaged,list:L,mode:"staged"},{label:p.gitUnstaged,list:T,mode:"worktree"},{label:p.gitUntracked,list:B,mode:"untracked"}].map(e=>e.list.length>0&&(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{className:"mb-1 flex items-center gap-2",style:{fontSize:11.5,color:"worktree"===e.mode&&e.label===p.gitConflict?"var(--dsw-danger)":"var(--dsw-label-caption)"},children:[(0,n.jsx)("span",{style:{fontWeight:600},children:e.label}),(0,n.jsx)("span",{children:e.list.length})]}),(0,n.jsx)("div",{className:"flex flex-col",children:e.list.map(t=>(0,n.jsxs)("button",{type:"button",className:"flex w-full items-center gap-2 rounded-lg px-2 py-1 text-left transition-colors",title:t.path,onMouseEnter:e=>e.currentTarget.style.background="var(--dsw-hover)",onMouseLeave:e=>e.currentTarget.style.background="transparent",onClick:()=>{let l,n;return l=e.mode,n="staged"===e.mode?p.gitStagedDiff:"untracked"===e.mode?p.gitUntracked:p.gitWorktreeDiff,W({kind:"file",path:t.path,mode:l,label:n})},children:[(0,n.jsx)("span",{className:"flex-none rounded px-1 text-center",style:{minWidth:16,fontSize:10.5,fontFamily:"var(--font-mono)",fontWeight:600,color:c[t.kind],background:"var(--dsw-hover)"},children:"untracked"===t.kind?"?":"conflict"===t.kind?"!":"staged"===e.mode?t.indexStatus:" "!==t.workStatus?t.workStatus:t.indexStatus}),(0,n.jsx)("span",{className:"min-w-0 flex-1 truncate",style:{fontSize:12,fontFamily:"var(--font-mono)",color:"var(--dsw-label-secondary)"},children:t.path})]},`${e.label}:${t.path}`))})]},e.label)),(0,n.jsxs)("div",{className:"hairline-t pt-3",children:[(0,n.jsx)("div",{className:"mb-1",style:{fontSize:11.5,fontWeight:600,color:"var(--dsw-label-caption)"},children:p.gitRecentCommits}),0===u.commits.length?(0,n.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"4px 2px"},children:p.gitNoCommits}):(0,n.jsx)("div",{className:"flex flex-col",children:u.commits.map(e=>(0,n.jsxs)("button",{type:"button",className:"flex w-full flex-col items-stretch rounded-lg px-2 py-1 text-left transition-colors",onMouseEnter:e=>e.currentTarget.style.background="var(--dsw-hover)",onMouseLeave:e=>e.currentTarget.style.background="transparent",onClick:()=>W({kind:"commit",hash:e.hash}),children:[(0,n.jsxs)("span",{className:"flex items-baseline gap-2",children:[(0,n.jsx)("span",{className:"flex-none",style:{fontSize:11,fontFamily:"var(--font-mono)",color:"var(--dsw-accent)"},children:e.hash}),(0,n.jsx)("span",{className:"min-w-0 flex-1 truncate",style:{fontSize:12,color:"var(--dsw-label-secondary)"},title:e.subject,children:e.subject})]}),(0,n.jsxs)("span",{className:"truncate",style:{fontSize:11,color:"var(--dsw-label-caption)",paddingLeft:0},children:[e.author," \xb7 ",e.relative]})]},e.hash))})]})]})]})]})}}}]);
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2716],{2716:(e,t,n)=>{n.r(t),n.d(t,{FileViewer:()=>y});var i=n(5155),l=n(2115),r=n(8426),a=n(3437),s=n(7890),o=n(8476),c=n(1859),d=n(9665);let h=(0,l.memo)(function({lines:e,language:t,folded:n,handle:r,initialChange:a}){let{t:s}=(0,c.s)(),[o,h]=(0,l.useState)(()=>new Set),u=(0,l.useRef)(null),[p,f]=(0,l.useState)({top:0,height:800}),[m,w]=(0,l.useState)(-1),x=(0,l.useRef)(new Map);(0,l.useEffect)(()=>h(new Set),[e]);let{rows:v,blocks:g}=(0,l.useMemo)(()=>(function(e,t,n){let i=[],l=[],r=e=>"add"===e.kind||"del"===e.kind,a=-1,s=-1;for(let t=0;t<e.length;t+=1)r(e[t])&&(a<0&&(a=t),s=t);let o=0,c=!1;for(;o<e.length;){let d=e[o];if("hunk"===d.kind||"file"===d.kind){o+=1;continue}if(r(d)){c||l.push(i.length),i.push({kind:"line",line:d,index:o}),c=!0,o+=1;continue}c=!1;let h=o;for(;h<e.length&&"ctx"===e[h].kind;)h+=1;let u=h-o,p=a<0||a>=o?0:3,f=s<h?0:3;if(!t||n.has(o)||u<=p+f+2)for(let t=o;t<h;t+=1)i.push({kind:"line",line:e[t],index:t});else{for(let t=o;t<o+p;t+=1)i.push({kind:"line",line:e[t],index:t});i.push({kind:"fold",from:o,count:u-p-f});for(let t=h-f;t<h;t+=1)i.push({kind:"line",line:e[t],index:t})}o=h}return{rows:i,blocks:l}})(e,n,o),[e,n,o]),y=(0,l.useMemo)(()=>String(function(e){let t=1;for(let n of e){let e=n.new??n.old??0;e>t&&(t=e)}return t}(e)).length,[e]),j=!!t&&v.length<=6e3;(0,l.useEffect)(()=>{let e=u.current;if(!e)return;let t=()=>f({top:e.scrollTop,height:e.clientHeight});t(),e.addEventListener("scroll",t,{passive:!0});let n=new ResizeObserver(t);return n.observe(e),()=>{e.removeEventListener("scroll",t),n.disconnect()}},[]);let b=(0,l.useCallback)(e=>{let t=u.current,n=g[e];t&&void 0!==n&&(w(e),t.scrollTo({top:Math.max(0,20*n-t.clientHeight/3),behavior:"smooth"}))},[g]);(0,l.useImperativeHandle)(r,()=>({nextChange:()=>b(Math.min(g.length-1,m+1)),prevChange:()=>b(Math.max(0,m-1))}),[g.length,m,b]);let k=(0,l.useRef)(!1);(0,l.useEffect)(()=>{k.current=!1},[e]),(0,l.useEffect)(()=>{if(k.current||!a||!g.length)return;k.current=!0;let e=u.current;e&&(e.scrollTop=Math.max(0,20*g[0]-e.clientHeight/3),w(0))},[g,a,v]);let N=Math.max(0,Math.floor(p.top/20)-20),C=Math.min(v.length,Math.ceil((p.top+p.height)/20)+20);return(0,i.jsxs)("div",{className:"pw-fdiff relative min-h-0 flex-1",children:[(0,i.jsx)("div",{ref:u,className:"pw-fdiff-scroll h-full overflow-auto",children:(0,i.jsx)("div",{style:{height:20*v.length,position:"relative",minWidth:"100%"},children:v.slice(N,C).map((e,n)=>{let l=(N+n)*20;if("fold"===e.kind)return(0,i.jsx)("button",{type:"button",className:"pw-fdiff-fold",style:{top:l},onClick:()=>h(t=>new Set(t).add(e.from)),children:s.viewerFoldedLines.replace("{n}",String(e.count))},`f${e.from}`);let r=e.line,a=(e=>{if(!j||!e.trim())return null;let n=x.current.get(e);if(void 0!==n)return n;let i=(0,d.NK)(e,t);return x.current.size>5e3&&x.current.clear(),x.current.set(e,i),i})(r.text);return(0,i.jsxs)("div",{className:"pw-fdiff-row","data-kind":r.kind,style:{top:l},children:[(0,i.jsxs)("span",{className:"pw-fdiff-gutter",style:{width:`${2*y+3}ch`},children:[(0,i.jsx)("span",{children:"add"===r.kind?"":String(r.old??"").padStart(y)}),(0,i.jsx)("span",{children:"del"===r.kind?"":String(r.new??"").padStart(y)})]}),(0,i.jsx)("span",{className:"pw-fdiff-sign",children:"add"===r.kind?"+":"del"===r.kind?"−":" "}),(0,i.jsx)("span",{className:"pw-fdiff-text hljs",children:a?(0,i.jsx)("span",{dangerouslySetInnerHTML:{__html:a}}):r.text})]},e.index)})})}),v.length>0&&g.length>0&&(0,i.jsx)("div",{className:"pw-fdiff-ticks","aria-hidden":!0,children:g.map((e,t)=>{let n=v[e],l=n?.kind==="line"?n.line.kind:"add";return(0,i.jsx)("span",{className:"pw-fdiff-tick","data-kind":l,"data-current":t===m||void 0,style:{top:`${e/v.length*100}%`},onClick:()=>b(t)},t)})})]})});var u=n(2155),p=n(9992),f=n(2253),m=n(1135);let w=new Map;async function x(e){let t=await fetch(e),n=await t.json();if(!t.ok||!n.success){let e=Error(n.error||`request failed (${t.status})`);throw e.status=t.status,e}return n.data}function v(e){let t=w.get(e);return!t&&((t=x(e)).catch(()=>w.delete(e)),w.set(e,t),w.size>200&&w.delete(w.keys().next().value)),t}function g({content:e,name:t}){let n=(0,l.useMemo)(()=>(0,d.wY)(e,(0,d.f3)(t)),[e,t]),r=e.split("\n").length,a=`${String(r).length+1}ch`;return(0,i.jsxs)("div",{className:"flex min-h-0 flex-1 overflow-auto",style:{fontFamily:"var(--font-mono)",fontSize:12,lineHeight:"20px"},children:[(0,i.jsx)("pre",{"aria-hidden":!0,className:"flex-none select-none py-2 pl-3 pr-2 text-right",style:{width:a,minWidth:a,color:"var(--dsw-label-caption)",margin:0,position:"sticky",left:0,background:"var(--dsw-glass-modal)"},children:Array.from({length:r},(e,t)=>t+1).join("\n")}),null!==n?(0,i.jsx)("pre",{className:"hljs min-w-0 flex-1 py-2 pl-3 pr-4",style:{margin:0,background:"transparent",color:"var(--dsw-label-secondary)"},children:(0,i.jsx)("code",{dangerouslySetInnerHTML:{__html:n}})}):(0,i.jsx)("pre",{className:"min-w-0 flex-1 py-2 pl-3 pr-4",style:{margin:0,color:"var(--dsw-label-secondary)"},children:e})]})}function y({state:e,growth:t,cwd:n,onClose:w,onSelectTab:j,onCloseTab:b,onOpen:k,onReference:N,onOpenEditor:C}){let{t:$}=(0,c.s)(),[S,M]=(0,l.useState)(!1),[z,E]=(0,l.useState)("diff"),[T,R]=(0,l.useState)(!1),[L,D]=(0,l.useState)(!0),[F,P]=(0,l.useState)(!1),[I,A]=(0,l.useState)(""),[H,U]=(0,l.useState)(0),[_,O]=(0,l.useState)(""),[W,K]=(0,l.useState)(null),[q,B]=(0,l.useState)(null),Q=(0,l.useRef)(null),V=(0,l.useRef)(null),G=(0,l.useRef)(null),X=e.tabs.find(t=>t.path===e.active)??null,Y=(0,l.useMemo)(()=>X?t.changes.find(e=>e.path===X.path)??null:null,[X,t.changes]),J=!!(X&&/\.(md|mdx|markdown)$/i.test(X.path)),Z=t.range;(0,l.useEffect)(()=>{R(!1)},[e.active]),(0,l.useEffect)(()=>{!T&&X&&E(Y&&!X.lazy?"diff":J?"render":"content")},[X,Y,J,T]),(0,l.useEffect)(()=>{if(!X)return void B(null);let e=`${X.path}|${z}|${Z?.from??""}|${Z?.to??""}|${Y?.status??""}`,i=!0,l=t=>{i&&B({key:e,diff:null,content:null,binary:!1,truncated:!1,notice:null,error:null,...t})};if(void 0!==X.staticContent)return l({content:X.staticContent}),()=>{i=!1};if(!n)return void B(null);let r=`cwd=${encodeURIComponent(n)}`,a=async e=>{try{let t=await x(`/api/files?read=1&${r}&path=${encodeURIComponent(X.path)}`);l({content:t.content,binary:t.binary,truncated:t.truncated,notice:e})}catch(e){if(400!==e.status)throw e;l({notice:"missing"})}},s=async(e,n)=>{try{let t=await v(`/api/growth?${r}&tree=${e}&path=${encodeURIComponent(X.path)}&content=1`);l({content:t.content,binary:t.binary,truncated:t.truncated,notice:n})}catch(n){if(404===n.status){let n=t.selected?t.rounds.findIndex(e=>e.commit===t.selected.commit):t.rounds.length-1,i=t.rounds.slice(0,n+1).reverse().find(e=>e.changes.some(e=>e.path===X.path||e.from===X.path)),s=i?.changes.some(e=>"D"===e.status&&e.path===X.path||"R"===e.status&&e.from===X.path)?i.parentTree:i?.tree;if(s&&s!==e)try{let e=await v(`/api/growth?${r}&tree=${s}&path=${encodeURIComponent(X.path)}&content=1`);l({content:e.content,binary:e.binary,truncated:e.truncated,notice:"historical"});return}catch(e){if(404!==e.status)throw e}await a("disk");return}throw n}};return(async()=>{try{if(X.lazy||!Z)return void await a(X.lazy&&t.rounds.length>0?"disk":null);if("diff"===z){if(!Y)return void await s(Z.to,"nochange");let e=await v(`/api/growth?${r}&from=${Z.from}&to=${Z.to}&path=${encodeURIComponent(X.path)}${X.from??Y.from?`&renamedFrom=${encodeURIComponent(X.from??Y.from??"")}`:""}`);if(e.binary)return void l({binary:!0});let t=(0,o.rj)(e.patch);if(!t)return void await s(Z.to,"nochange");l({diff:t,truncated:e.truncated,notice:"D"===Y.status?"deleted":null});return}await s(Z.to,null)}catch(e){l({error:e instanceof Error?e.message:$.viewerLoadFailed})}})(),()=>{i=!1}},[X,n,z,Z,Y,t.rounds,t.selected,$.viewerLoadFailed]),(0,l.useEffect)(()=>{if(!e.open)return;let t=t=>{let n=t.target,i=n&&("INPUT"===n.tagName||"TEXTAREA"===n.tagName||n.isContentEditable);if("Escape"===t.key){t.preventDefault(),F?P(!1):w();return}let l=t.ctrlKey||t.metaKey;if(l&&"w"===t.key.toLowerCase()){t.preventDefault(),e.active?b(e.active):w();return}if(l&&"p"===t.key.toLowerCase()){t.preventDefault(),P(!0);return}i||l||"diff"!==z||("]"===t.key?Q.current?.nextChange():"["===t.key&&Q.current?.prevChange())};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[e.open,e.active,F,z,w,b]),(0,l.useEffect)(()=>{F&&(A(""),U(0),setTimeout(()=>G.current?.focus(),0))},[F]),(0,l.useEffect)(()=>{e.open&&setTimeout(()=>V.current?.focus(),0)},[e.open]);let ee=(0,l.useMemo)(()=>{if(!F)return[];let e=I.trim();return t.filePaths.map(t=>({p:t,s:(0,m.dt)(e,t)})).filter(e=>e.s>=0).sort((e,t)=>t.s-e.s||e.p.length-t.p.length).slice(0,50).map(e=>e.p)},[F,I,t.filePaths]);if(!e.open)return null;let et=X?X.path.split("/"):[],en=t.selected?t.selected.n?$.growthRoundLabel.replace("{n}",String(t.selected.n)):$.growthKindUser:"",ei=en?`${en}${Y?` \xb7 +${Y.add??0} −${Y.del??0}`:""}`:"",el="diff"===z&&!!q?.diff,er=q?.notice==="deleted"?$.viewerDeleted:q?.notice==="historical"?$.viewerHistorical:q?.notice==="missing"?$.viewerMissing:q?.notice==="disk"?$.viewerNotInSnapshot:q?.notice==="nochange"?$.viewerNoChanges:"";return(0,i.jsx)("div",{className:"pw-viewer-mask modal-mask fixed inset-0 z-[105] flex items-center justify-center",onMouseDown:e=>e.target===e.currentTarget&&w(),children:(0,i.jsxs)("div",{ref:V,className:"pw-viewer glass-modal flex flex-col overflow-hidden outline-none","data-fullscreen":S||void 0,role:"dialog","aria-modal":"true",tabIndex:-1,"data-testid":"file-viewer",children:[(0,i.jsxs)("div",{className:"pw-viewer-tabs hairline-b",onDoubleClick:e=>e.target===e.currentTarget&&M(e=>!e),children:[(0,i.jsxs)("button",{type:"button",className:"pw-viewer-tab","data-active":null===e.active,onClick:()=>j(null),children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:"/file-icons/folder-open.svg",alt:""}),(0,i.jsx)("span",{children:$.projectPanel})]}),(0,i.jsx)("div",{className:"pw-viewer-tabstrip",children:e.tabs.map(t=>(0,i.jsxs)("div",{className:"pw-viewer-tab","data-active":e.active===t.path,onAuxClick:e=>{1===e.button&&b(t.path)},children:[(0,i.jsxs)("button",{type:"button",className:"pw-viewer-tab-main",title:t.path,onClick:()=>j(t.path),children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:(0,f.s)(t.path.split("/").pop()??t.path),alt:""}),(0,i.jsx)("span",{className:"truncate",children:t.path.split("/").pop()})]}),(0,i.jsx)("button",{type:"button",className:"pw-viewer-tab-close",title:$.viewerCloseTab,"aria-label":`${$.viewerCloseTab}: ${t.path}`,onClick:()=>b(t.path),children:(0,i.jsx)(p.Bh,{size:10})})]},t.path))}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:`${$.viewerQuickOpen} (Ctrl/⌘+P)`,onClick:()=>P(!0),children:(0,i.jsx)(p.Xh,{size:13})}),(0,i.jsx)("div",{className:"flex-1"}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:S?$.viewerExitFullscreen:$.viewerFullscreen,onClick:()=>M(e=>!e),"data-testid":"viewer-fullscreen",children:(0,i.jsx)("svg",{width:"14",height:"14",viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:S?(0,i.jsx)("path",{d:"M6 1v5H1M10 1v5h5M6 15v-5H1M10 15v-5h5",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"}):(0,i.jsx)("path",{d:"M1 6V1h5M15 6V1h-5M1 10v5h5M15 10v5h-5",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"})})}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:`${$.close} (Esc)`,onClick:w,"data-testid":"viewer-close",children:(0,i.jsx)(p.Bh,{size:13})})]}),X?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)("div",{className:"pw-viewer-toolbar hairline-b",children:[(0,i.jsx)("div",{className:"pw-crumb min-w-0 flex-1",children:et.map((e,n)=>(0,i.jsxs)("span",{className:"flex items-center",children:[n>0&&(0,i.jsx)("span",{className:"pw-crumb-sep",children:"/"}),n<et.length-1?(0,i.jsx)("button",{type:"button",onClick:()=>{let e;return e=et.slice(0,n+1).join("/"),void(t.revealPath(e),K({path:e,key:Date.now()}),j(null))},children:e}):(0,i.jsx)("span",{style:{color:"var(--dsw-label-primary)",fontWeight:500},children:e})]},n))}),ei&&!X.lazy&&(0,i.jsx)("span",{className:"flex-none truncate",style:{fontSize:11.5,color:"var(--dsw-label-caption)",maxWidth:220},children:ei}),!X.lazy&&(0,i.jsx)("span",{className:"pw-seg",role:"radiogroup",children:["diff","content",...J?["render"]:[]].map(e=>(0,i.jsx)("button",{type:"button",role:"radio","aria-checked":z===e,"data-active":z===e,onClick:()=>{R(!0),E(e)},children:"diff"===e?$.viewerModeDiff:"content"===e?$.viewerModeContent:$.viewerModeRender},e))}),X.lazy&&J&&(0,i.jsx)("span",{className:"pw-seg",role:"radiogroup",children:["content","render"].map(e=>(0,i.jsx)("button",{type:"button",role:"radio","aria-checked":z===e,"data-active":z===e,onClick:()=>{R(!0),E(e)},children:"content"===e?$.viewerModeContent:$.viewerModeRender},e))}),el&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("button",{type:"button",className:"pw-chip","data-active":L,onClick:()=>D(e=>!e),children:L?$.viewerFold:$.viewerUnfold}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:`${$.viewerPrevChange} ([)`,onClick:()=>Q.current?.prevChange(),children:(0,i.jsx)(p.tr,{size:12})}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:`${$.viewerNextChange} (])`,onClick:()=>Q.current?.nextChange(),children:(0,i.jsx)(p.Cd,{size:12})})]}),N&&(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:$.fileReference,onClick:()=>N(X.path),children:(0,i.jsx)(p.PT,{size:13})}),C&&(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:$.fileOpenEditor,onClick:()=>C(X.path),children:(0,i.jsx)(p.lx,{size:13})})]}),er&&(0,i.jsx)("div",{className:"pw-viewer-notice","data-kind":q?.notice,children:er}),q?.truncated&&(0,i.jsx)("div",{className:"pw-viewer-notice","data-kind":"warn",children:"diff"===z?$.viewerPatchTruncated:$.filePreviewTruncated}),q&&q.key===`${X.path}|${z}|${Z?.from??""}|${Z?.to??""}|${Y?.status??""}`?q.error?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-danger)"},children:q.error}):"missing"===q.notice?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.viewerMissing}):q.binary?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.fileBinary}):q.diff?(0,i.jsx)(h,{lines:q.diff,language:(0,d.f3)(X.path),folded:L,handle:Q,initialChange:!0}):"render"===z&&null!==q.content?(0,i.jsx)("div",{className:"md min-h-0 flex-1 overflow-auto px-6 py-4",style:{fontSize:14},children:(0,i.jsx)(r.oz,{remarkPlugins:[s.A],rehypePlugins:[[a.A,{detect:!1,ignoreMissing:!0}]],children:q.content})}):null!==q.content?(0,i.jsx)(g,{content:q.content,name:X.path}):null:(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.panelLoading})]}):(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)("div",{className:"pw-viewer-toolbar hairline-b",children:[(0,i.jsxs)("div",{className:"flex min-w-0 flex-1 items-center gap-1.5 rounded-lg px-2",style:{background:"var(--dsw-hover)",height:26,maxWidth:360},children:[(0,i.jsx)(p.FG,{size:12,style:{flex:"none",color:"var(--dsw-label-caption)"}}),(0,i.jsx)("input",{value:_,onChange:e=>O(e.target.value),placeholder:$.growthFilter,className:"min-w-0 flex-1 bg-transparent",style:{fontSize:12}})]}),(0,i.jsx)("span",{style:{fontSize:11.5,color:"var(--dsw-label-caption)"},children:en}),(0,i.jsx)("button",{type:"button",className:"pw-chip",onClick:t.expandAll,children:$.growthExpandAll}),(0,i.jsx)("button",{type:"button",className:"pw-chip",onClick:t.collapseAll,children:$.growthCollapseAll})]}),(0,i.jsx)(u.Q,{tree:t.tree,expanded:t.expanded,fresh:t.fresh,filter:_,selectedPath:null,onToggleDir:t.toggleDir,onExpandLazy:t.expandLazy,onOpenFile:e=>k(e.path,{from:e.from,lazy:e.lazy}),onReference:N,onOpenEditor:C,revealPath:W?.path??null,revealKey:W?.key,emptyText:$.viewerEmpty})]}),F&&(0,i.jsx)("div",{className:"pw-quick",onMouseDown:e=>e.target===e.currentTarget&&P(!1),children:(0,i.jsxs)("div",{className:"pw-quick-box popover",children:[(0,i.jsxs)("div",{className:"flex items-center gap-2 px-2",style:{height:34},children:[(0,i.jsx)(p.FG,{size:14,style:{flex:"none",color:"var(--dsw-label-caption)"}}),(0,i.jsx)("input",{ref:G,value:I,onChange:e=>{A(e.target.value),U(0)},placeholder:$.viewerQuickOpen,className:"min-w-0 flex-1 bg-transparent",style:{fontSize:13},onKeyDown:e=>{if("ArrowDown"===e.key)e.preventDefault(),U(e=>Math.min(ee.length-1,e+1));else if("ArrowUp"===e.key)e.preventDefault(),U(e=>Math.max(0,e-1));else if("Enter"===e.key){e.preventDefault();let t=ee[H];t&&(k(t),P(!1))}}})]}),(0,i.jsx)("div",{className:"pw-quick-list",children:0===ee.length?(0,i.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"8px 10px"},children:$.viewerQuickOpenEmpty}):ee.map((e,t)=>(0,i.jsxs)("button",{type:"button",className:"pw-quick-item","data-active":t===H,onMouseEnter:()=>U(t),onClick:()=>{k(e),P(!1)},children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:(0,f.s)(e.split("/").pop()??e),alt:""}),(0,i.jsx)("span",{className:"truncate",children:e.split("/").pop()}),(0,i.jsx)("span",{className:"min-w-0 truncate",style:{color:"var(--dsw-label-caption)",fontSize:11.5},children:e})]},e))})]})})]})})}}}]);