@rexvane/piweb 0.3.25 → 0.3.26

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 (345) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +6 -5
  3. package/.next/build-manifest.json +4 -4
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +4 -4
  6. package/.next/react-loadable-manifest.json +14 -26
  7. package/.next/required-server-files.js +26 -26
  8. package/.next/required-server-files.json +26 -26
  9. package/.next/routes-manifest.json +6 -0
  10. package/.next/server/app/_global-error/page.js +2 -2
  11. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  12. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_global-error.html +1 -1
  14. package/.next/server/app/_global-error.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found/page.js +1 -1
  19. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/_not-found.html +1 -1
  22. package/.next/server/app/_not-found.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  24. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  27. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  28. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/agent/[id]/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  33. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  34. package/.next/server/app/api/agent/[id]/tree/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/agent/new/route.js +1 -1
  36. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  37. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/agent/resources/route.js +1 -0
  39. package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
  40. package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
  41. package/.next/server/app/api/config/route.js +1 -1
  42. package/.next/server/app/api/config/route.js.nft.json +1 -1
  43. package/.next/server/app/api/config/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/files/route.js +1 -1
  45. package/.next/server/app/api/files/route.js.nft.json +1 -1
  46. package/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/fs/route.js +1 -1
  48. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  49. package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/git/route.js +4 -4
  51. package/.next/server/app/api/git/route.js.nft.json +1 -1
  52. package/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/github/route.js +4 -4
  54. package/.next/server/app/api/github/route.js.nft.json +1 -1
  55. package/.next/server/app/api/github/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/growth/route.js +1 -1
  57. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  58. package/.next/server/app/api/growth/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/health/route.js +1 -1
  60. package/.next/server/app/api/health/route.js.nft.json +1 -1
  61. package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/models/defaults/route.js +1 -1
  63. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
  64. package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/models/route.js +1 -1
  66. package/.next/server/app/api/models/route.js.nft.json +1 -1
  67. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/pi-settings/route.js +1 -1
  69. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  70. package/.next/server/app/api/pi-settings/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/plugins/route.js +1 -1
  72. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  73. package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/prompts/route.js +1 -1
  75. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  76. package/.next/server/app/api/prompts/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/remote/route.js +2 -2
  78. package/.next/server/app/api/remote/route.js.nft.json +1 -1
  79. package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/security/route.js +1 -1
  81. package/.next/server/app/api/security/route.js.nft.json +1 -1
  82. package/.next/server/app/api/security/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  84. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  85. package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  87. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  88. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/sessions/import/route.js +2 -2
  90. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  91. package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/sessions/route.js +1 -1
  93. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  94. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/skills/route.js +2 -2
  96. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  97. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/update/route.js +2 -2
  99. package/.next/server/app/api/update/route.js.nft.json +1 -1
  100. package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/version/route.js +1 -1
  102. package/.next/server/app/api/version/route.js.nft.json +1 -1
  103. package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/web-auth/route.js +2 -2
  105. package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
  106. package/.next/server/app/api/web-auth/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/workspaces/route.js +1 -1
  108. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  109. package/.next/server/app/api/workspaces/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/icon.svg/route.js +1 -1
  111. package/.next/server/app/icon.svg/route.js.nft.json +1 -1
  112. package/.next/server/app/index.html +1 -1
  113. package/.next/server/app/index.rsc +5 -5
  114. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  115. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  116. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  117. package/.next/server/app/login/page.js +1 -1
  118. package/.next/server/app/login/page.js.nft.json +1 -1
  119. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/login.html +1 -1
  121. package/.next/server/app/login.rsc +4 -4
  122. package/.next/server/app/login.segments/_full.segment.rsc +4 -4
  123. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  124. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  125. package/.next/server/app/page.js +1 -1
  126. package/.next/server/app/page.js.nft.json +1 -1
  127. package/.next/server/app/page_client-reference-manifest.js +1 -1
  128. package/.next/server/app-paths-manifest.json +6 -5
  129. package/.next/server/chunks/{813.js → 1813.js} +1 -1
  130. package/.next/server/chunks/{430.js → 2430.js} +5 -5
  131. package/.next/server/chunks/{970.js → 2970.js} +1 -1
  132. package/.next/server/chunks/3924.js +6 -0
  133. package/.next/server/chunks/4807.js +1 -0
  134. package/.next/server/chunks/5393.js +1 -0
  135. package/.next/server/chunks/5802.js +2 -0
  136. package/.next/server/chunks/620.js +1 -1
  137. package/.next/server/chunks/6229.js +1 -0
  138. package/.next/server/chunks/7496.js +1 -0
  139. package/.next/server/chunks/7662.js +2 -0
  140. package/.next/server/chunks/8686.js +1 -0
  141. package/.next/server/chunks/{593.js → 974.js} +1 -1
  142. package/.next/server/chunks/{920.js → 9920.js} +1 -1
  143. package/.next/server/instrumentation.js +1 -1
  144. package/.next/server/instrumentation.js.nft.json +1 -1
  145. package/.next/server/middleware-build-manifest.js +1 -1
  146. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  147. package/.next/server/middleware.js +5 -5
  148. package/.next/server/pages/404.html +1 -1
  149. package/.next/server/pages/500.html +1 -1
  150. package/.next/server/preview-props.json +3 -3
  151. package/.next/server/server-reference-manifest.json +1 -1
  152. package/.next/server/webpack-runtime.js +1 -1
  153. package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
  154. package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
  155. package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
  156. package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
  157. package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
  158. package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
  159. package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
  160. package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
  161. package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
  162. package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
  163. package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
  164. package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
  165. package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
  166. package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
  167. package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
  168. package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
  169. package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
  170. package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
  171. package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
  172. package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
  173. package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
  174. package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
  175. package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
  176. package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
  177. package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
  178. package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
  179. package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
  180. package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
  181. package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
  182. package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
  183. package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
  184. package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
  185. package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
  186. package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
  187. package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
  188. package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
  189. package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
  190. package/.next/static/chunks/app/{layout-7f64fa67ea6ce980.js → layout-50d06e2078d6ce76.js} +1 -1
  191. package/.next/static/chunks/app/login/{page-00dbd2f395b364d5.js → page-5e276a1eeb8867df.js} +1 -1
  192. package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
  193. package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
  194. package/.next/static/chunks/{main-app-56863ace24eb152f.js → main-app-e9b53f0e1c6591b2.js} +1 -1
  195. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
  196. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
  197. package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
  198. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
  199. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
  200. package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
  201. package/.next/static/css/7e3caf797e4cb19a.css +1 -0
  202. package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
  203. package/.next/types/app/api/agent/[id]/route.ts +1 -1
  204. package/.next/types/app/api/agent/[id]/tree/route.ts +1 -1
  205. package/.next/types/app/api/agent/new/route.ts +1 -1
  206. package/.next/types/app/api/agent/resources/route.ts +355 -0
  207. package/.next/types/app/api/config/route.ts +1 -1
  208. package/.next/types/app/api/files/route.ts +1 -1
  209. package/.next/types/app/api/fs/route.ts +1 -1
  210. package/.next/types/app/api/git/route.ts +1 -1
  211. package/.next/types/app/api/github/route.ts +1 -1
  212. package/.next/types/app/api/growth/route.ts +1 -1
  213. package/.next/types/app/api/health/route.ts +1 -1
  214. package/.next/types/app/api/models/defaults/route.ts +1 -1
  215. package/.next/types/app/api/models/route.ts +1 -1
  216. package/.next/types/app/api/pi-settings/route.ts +1 -1
  217. package/.next/types/app/api/plugins/route.ts +1 -1
  218. package/.next/types/app/api/prompts/route.ts +1 -1
  219. package/.next/types/app/api/remote/route.ts +1 -1
  220. package/.next/types/app/api/security/route.ts +1 -1
  221. package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
  222. package/.next/types/app/api/sessions/[id]/route.ts +1 -1
  223. package/.next/types/app/api/sessions/import/route.ts +1 -1
  224. package/.next/types/app/api/sessions/route.ts +1 -1
  225. package/.next/types/app/api/skills/route.ts +1 -1
  226. package/.next/types/app/api/update/route.ts +1 -1
  227. package/.next/types/app/api/version/route.ts +1 -1
  228. package/.next/types/app/api/web-auth/route.ts +1 -1
  229. package/.next/types/app/api/workspaces/route.ts +1 -1
  230. package/.next/types/app/layout.ts +1 -1
  231. package/.next/types/app/login/page.ts +1 -1
  232. package/.next/types/app/page.ts +1 -1
  233. package/.next/types/routes.d.ts +2 -1
  234. package/.next/types/validator.ts +10 -0
  235. package/LICENSE +1 -1
  236. package/README.md +1 -1
  237. package/bin/pi.js +0 -0
  238. package/bin/piweb.js +0 -0
  239. package/package.json +1 -1
  240. package/scripts/build-output.mjs +0 -0
  241. package/scripts/dev.mjs +0 -0
  242. package/scripts/entrypoint.mjs +0 -0
  243. package/scripts/install-build.mjs +0 -0
  244. package/scripts/launcher.mjs +0 -0
  245. package/scripts/pick-folder.ps1 +0 -0
  246. package/scripts/postinstall.mjs +0 -0
  247. package/scripts/process-runner.mjs +0 -0
  248. package/scripts/release.mjs +0 -0
  249. package/scripts/sync-file-icons.mjs +0 -0
  250. package/scripts/sync-provider-icons.mjs +0 -0
  251. package/src/app/api/agent/resources/route.ts +18 -0
  252. package/src/app/api/remote/route.ts +5 -7
  253. package/src/app/api/sessions/[id]/export/route.ts +3 -1
  254. package/src/app/globals.css +16 -0
  255. package/src/components/AppShell.tsx +68 -21
  256. package/src/components/ChatInput.tsx +98 -27
  257. package/src/components/ChatWindow.tsx +68 -11
  258. package/src/components/ContextMeter.tsx +24 -4
  259. package/src/components/ExtensionUI.tsx +9 -3
  260. package/src/components/FileViewer.tsx +24 -21
  261. package/src/components/GitPanel.tsx +449 -449
  262. package/src/components/GrowthTree.tsx +3 -1
  263. package/src/components/ImageViewer.tsx +72 -0
  264. package/src/components/MarkdownBlock.tsx +6 -3
  265. package/src/components/PreviewImage.tsx +33 -0
  266. package/src/components/ProjectPanel.tsx +5 -3
  267. package/src/components/PromptPanel.tsx +322 -313
  268. package/src/components/RemoteSection.tsx +2 -1
  269. package/src/components/SessionSidebar.tsx +22 -4
  270. package/src/components/SettingsPanel.tsx +2654 -2639
  271. package/src/components/composer/ComposerImages.tsx +1 -79
  272. package/src/hooks/usePiWeb.ts +47 -2
  273. package/src/i18n.tsx +24 -0
  274. package/src/lib/agent-manager.ts +275 -18
  275. package/src/lib/chat/skill-expansion.ts +10 -0
  276. package/src/lib/extension-ui.ts +25 -1
  277. package/src/lib/git-service.ts +291 -289
  278. package/src/lib/github-identity.ts +245 -245
  279. package/src/lib/growth-turns.ts +3 -1
  280. package/src/lib/session-reader.ts +25 -13
  281. package/src/lib/session-retention.ts +11 -8
  282. package/src/lib/slash-command-token.ts +14 -0
  283. package/src/lib/trajectory.ts +17 -6
  284. package/src/lib/types.ts +24 -2
  285. package/src/lib/workspace-store.ts +36 -1
  286. package/.next/diagnostics/build-diagnostics.json +0 -6
  287. package/.next/diagnostics/framework.json +0 -1
  288. package/.next/server/chunks/229.js +0 -1
  289. package/.next/server/chunks/393.js +0 -1
  290. package/.next/server/chunks/612.js +0 -1
  291. package/.next/server/chunks/662.js +0 -2
  292. package/.next/server/chunks/686.js +0 -1
  293. package/.next/server/chunks/802.js +0 -2
  294. package/.next/server/chunks/807.js +0 -1
  295. package/.next/server/chunks/924.js +0 -6
  296. package/.next/static/4snUelZTEIiQYCkB5nTW7/_buildManifest.js +0 -1
  297. package/.next/static/chunks/1966.1560d0f43ac9a41a.js +0 -1
  298. package/.next/static/chunks/2577.eebf2cf291a811ad.js +0 -1
  299. package/.next/static/chunks/2716.a5228b0eeedbe4c8.js +0 -1
  300. package/.next/static/chunks/2906.546a64377e12dfbc.js +0 -1
  301. package/.next/static/chunks/3899.55a0943bf5e7fec7.js +0 -1
  302. package/.next/static/chunks/4673.8eff281cb63e2799.js +0 -1
  303. package/.next/static/chunks/4686-f03275819a719fd8.js +0 -1
  304. package/.next/static/chunks/4778.2dae0c6a9e396326.js +0 -1
  305. package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +0 -1
  306. package/.next/static/chunks/app/_not-found/page-851dc3883a1937aa.js +0 -1
  307. package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +0 -1
  308. package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +0 -1
  309. package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +0 -1
  310. package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +0 -1
  311. package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +0 -1
  312. package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +0 -1
  313. package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +0 -1
  314. package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +0 -1
  315. package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +0 -1
  316. package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +0 -1
  317. package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +0 -1
  318. package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +0 -1
  319. package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +0 -1
  320. package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +0 -1
  321. package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +0 -1
  322. package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +0 -1
  323. package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +0 -1
  324. package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +0 -1
  325. package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +0 -1
  326. package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +0 -1
  327. package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +0 -1
  328. package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +0 -1
  329. package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +0 -1
  330. package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +0 -1
  331. package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +0 -1
  332. package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +0 -1
  333. package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +0 -1
  334. package/.next/static/chunks/app/page-0159987118376f42.js +0 -1
  335. package/.next/static/chunks/main-22ccebc2da4d291e.js +0 -5
  336. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +0 -1
  337. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +0 -1
  338. package/.next/static/chunks/next/dist/client/components/builtin/global-error-c37b7707077f50ce.js +0 -1
  339. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +0 -1
  340. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +0 -1
  341. package/.next/static/chunks/webpack-c1b43c269867b64f.js +0 -1
  342. package/.next/static/css/93ed3daa6f3fbc35.css +0 -1
  343. package/.next/trace +0 -58
  344. package/.next/trace-build +0 -1
  345. /package/.next/static/{4snUelZTEIiQYCkB5nTW7 → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
@@ -1,245 +1,245 @@
1
- /**
2
- * GitHub 身份:Git 面板头部显示「头像 + 账号」。
3
- *
4
- * 只读,不碰任何写操作,也不把 token 交给浏览器。三档降级:
5
- *
6
- * 1. `gh api user` —— 本机 gh 已登录时拿到的是**当前登录账号**(最准);
7
- * 2. 仓库 `origin` remote 的 owner —— 没装 gh / 没登录也能用,头像是免登录的
8
- * `github.com/<owner>.png`;但拿到的可能是「仓库归属者」而不是登录账号;
9
- * 3. `git config user.name` —— 纯本地身份,没有头像,只作文字兜底。
10
- *
11
- * 两条网络路径实测:`gh api user` 约 1.1s、头像约 2.2s,所以结果缓存 12 小时
12
- * (内存 + 落盘,PiWeb 重启后仍然即时)。头像取到后转成 data URL 随身份一起返回,
13
- * 前端不用再发第二次请求,断网也能显示。
14
- *
15
- * 任何一步失败都只降级,不抛错:Git 面板不该因为 GitHub 不通而少东西。
16
- */
17
- import { execFile } from "node:child_process";
18
- import fs from "node:fs/promises";
19
- import path from "node:path";
20
- import { promisify } from "node:util";
21
- import { resolveGitBin } from "./growth-service";
22
- import { getAgentDir } from "./pi";
23
-
24
- const exec = promisify(execFile);
25
-
26
- export interface GithubIdentity {
27
- login: string;
28
- /** gh 里的显示名(如「心安」),没有就省略 */
29
- name?: string;
30
- /** data URL;取不到头像时省略,UI 退回首字母 */
31
- avatar?: string;
32
- /** 账号主页,点击可跳转 */
33
- url?: string;
34
- /** `owner/repo`(来自 origin remote),没配 remote 就没有 */
35
- repo?: string;
36
- }
37
-
38
- const TTL_MS = 12 * 60 * 60 * 1_000;
39
- const GH_TIMEOUT_MS = 8_000;
40
- const AVATAR_TIMEOUT_MS = 8_000;
41
- const AVATAR_SIZE = 64;
42
- const AVATAR_MAX_BYTES = 256 * 1_024;
43
- const CACHE_FILENAME = "piweb-github-identity.json";
44
- // 身份字段一变就提版本:旧的缓存条目直接作废重取,避免返回已经删掉的字段
45
- const CACHE_VERSION = 2;
46
-
47
- interface CacheFile {
48
- version: number;
49
- entries: Record<string, { at: number; value: GithubIdentity | null }>;
50
- }
51
-
52
- const memory = new Map<string, { at: number; value: GithubIdentity | null }>();
53
- const inflight = new Map<string, Promise<GithubIdentity | null>>();
54
- let diskLoaded = false;
55
- let diskWrite: Promise<void> | null = null;
56
-
57
- function cachePath(): string {
58
- return path.join(getAgentDir(), CACHE_FILENAME);
59
- }
60
-
61
- /** 离线模式(SDK 也认这个变量):不发任何网络请求,只走本地能算出来的那一档 */
62
- function offline(): boolean {
63
- const value = process.env.PI_OFFLINE;
64
- if (!value || value === "0") return false;
65
- return value.toLowerCase() !== "false";
66
- }
67
-
68
- async function loadDisk(): Promise<void> {
69
- if (diskLoaded) return;
70
- diskLoaded = true;
71
- try {
72
- const raw = JSON.parse(await fs.readFile(cachePath(), "utf8")) as CacheFile;
73
- if (raw?.version !== CACHE_VERSION || !raw.entries || typeof raw.entries !== "object") return;
74
- const now = Date.now();
75
- for (const [key, entry] of Object.entries(raw.entries)) {
76
- if (!entry || typeof entry.at !== "number" || now - entry.at >= TTL_MS) continue;
77
- if (!memory.has(key)) memory.set(key, entry);
78
- }
79
- } catch {
80
- /* 没有缓存 / 读不了都当第一次 */
81
- }
82
- }
83
-
84
- function persist(): void {
85
- if (diskWrite) return;
86
- diskWrite = (async () => {
87
- try {
88
- const entries: CacheFile["entries"] = {};
89
- for (const [key, entry] of memory) entries[key] = entry;
90
- await fs.writeFile(cachePath(), JSON.stringify({ version: CACHE_VERSION, entries }), "utf8");
91
- } catch {
92
- /* 写不了就只在内存里缓存 */
93
- } finally {
94
- diskWrite = null;
95
- }
96
- })();
97
- }
98
-
99
- /** 解析 gh 可执行文件(PATH 找不到时退回常见安装位置);结果缓存 */
100
- let ghBin: string | null | undefined;
101
- async function resolveGhBin(): Promise<string | null> {
102
- if (ghBin !== undefined) return ghBin;
103
- const candidates = process.platform === "win32"
104
- ? ["gh", path.join(process.env.ProgramFiles ?? "C:\\Program Files", "GitHub CLI", "gh.exe"), path.join(process.env.LOCALAPPDATA ?? "", "Programs", "GitHub CLI", "gh.exe")]
105
- : ["gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"];
106
- for (const candidate of candidates) {
107
- if (!candidate) continue;
108
- const ok = await exec(candidate, ["--version"], { timeout: 5_000, windowsHide: true }).then(() => true, () => false);
109
- if (ok) {
110
- ghBin = candidate;
111
- return ghBin;
112
- }
113
- }
114
- ghBin = null;
115
- return ghBin;
116
- }
117
-
118
- async function git(cwd: string, args: string[]): Promise<string> {
119
- const out = await exec(await resolveGitBin(), args, {
120
- cwd,
121
- timeout: 15_000,
122
- maxBuffer: 1024 * 1_024,
123
- windowsHide: true,
124
- env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
125
- });
126
- return out.stdout;
127
- }
128
-
129
- /** 从 remote URL 里取 owner 与仓库名:支持 https / ssh / git:// 三种写法 */
130
- export function parseGithubRemote(remote: string | undefined | null): { owner: string; repo?: string } | null {
131
- if (!remote) return null;
132
- const trimmed = remote.trim().replace(/\.git$/, "");
133
- const m = trimmed.match(/github\.com[:/]+([^/]+)(?:\/([^/]+))?/i);
134
- if (!m?.[1]) return null;
135
- return { owner: m[1], repo: m[2] ? `${m[1]}/${m[2]}` : undefined };
136
- }
137
-
138
- async function ghAccount(cwd: string): Promise<{ login: string; name?: string; url?: string } | null> {
139
- if (offline()) return null;
140
- const bin = await resolveGhBin();
141
- if (!bin) return null;
142
- try {
143
- const { stdout } = await exec(bin, ["api", "user"], {
144
- cwd,
145
- timeout: GH_TIMEOUT_MS,
146
- maxBuffer: 1024 * 1_024,
147
- windowsHide: true,
148
- env: { ...process.env, GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" },
149
- });
150
- const parsed = JSON.parse(stdout) as { login?: unknown; name?: unknown; html_url?: unknown };
151
- if (typeof parsed.login !== "string" || !parsed.login) return null;
152
- return {
153
- login: parsed.login,
154
- name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : undefined,
155
- url: typeof parsed.html_url === "string" ? parsed.html_url : undefined,
156
- };
157
- } catch {
158
- // 没网 / 没登录 / token 失效:交给下一档
159
- return null;
160
- }
161
- }
162
-
163
- async function avatarDataUrl(owner: string): Promise<string | undefined> {
164
- if (offline()) return undefined;
165
- try {
166
- const res = await fetch(`https://github.com/${encodeURIComponent(owner)}.png?size=${AVATAR_SIZE}`, {
167
- signal: AbortSignal.timeout(AVATAR_TIMEOUT_MS),
168
- headers: { Accept: "image/*" },
169
- });
170
- if (!res.ok) return undefined;
171
- const type = res.headers.get("content-type") ?? "image/png";
172
- if (!type.startsWith("image/")) return undefined;
173
- const buffer = Buffer.from(await res.arrayBuffer());
174
- if (!buffer.length || buffer.length > AVATAR_MAX_BYTES) return undefined;
175
- return `data:${type};base64,${buffer.toString("base64")}`;
176
- } catch {
177
- return undefined;
178
- }
179
- }
180
-
181
- async function compute(root: string): Promise<GithubIdentity | null> {
182
- let remote: { owner: string; repo?: string } | null = null;
183
- try {
184
- remote = parseGithubRemote(await git(root, ["remote", "get-url", "origin"]));
185
- } catch {
186
- /* 没有 origin 就往下走 */
187
- }
188
- const account = await ghAccount(root);
189
- if (account) {
190
- return {
191
- login: account.login,
192
- name: account.name,
193
- url: account.url ?? `https://github.com/${account.login}`,
194
- avatar: await avatarDataUrl(account.login),
195
- repo: remote?.repo,
196
- };
197
- }
198
- if (remote) {
199
- return { login: remote.owner, url: `https://github.com/${remote.owner}`, avatar: await avatarDataUrl(remote.owner), repo: remote.repo };
200
- }
201
- try {
202
- const name = (await git(root, ["config", "user.name"])).trim();
203
- if (name) return { login: name };
204
- } catch {
205
- /* 什么都没配 */
206
- }
207
- return null;
208
- }
209
-
210
- function start(root: string): Promise<GithubIdentity | null> {
211
- const existing = inflight.get(root);
212
- if (existing) return existing;
213
- const task = compute(root)
214
- .then((value) => {
215
- memory.set(root, { at: Date.now(), value });
216
- persist();
217
- return value;
218
- })
219
- .catch(() => null)
220
- .finally(() => inflight.delete(root)) as Promise<GithubIdentity | null>;
221
- inflight.set(root, task);
222
- return task;
223
- }
224
-
225
- /**
226
- * 取仓库的 GitHub 身份。命中缓存立刻返回;否则**后台**刷新并返回已过期的旧值(或 null),
227
- * 不会让调用方等网络。要拿首次结果用 `awaitGithubIdentity`。
228
- */
229
- export function githubIdentity(root: string | null): GithubIdentity | null {
230
- if (!root) return null;
231
- void loadDisk();
232
- const hit = memory.get(root);
233
- if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
234
- void start(root);
235
- return hit?.value ?? null;
236
- }
237
-
238
- /** 等首次解析完成(面板刚打开、缓存还冷时用;调用方自己控制超时) */
239
- export async function awaitGithubIdentity(root: string | null): Promise<GithubIdentity | null> {
240
- if (!root) return null;
241
- await loadDisk();
242
- const hit = memory.get(root);
243
- if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
244
- return start(root);
245
- }
1
+ /**
2
+ * GitHub 身份:Git 面板头部显示「头像 + 账号」。
3
+ *
4
+ * 只读,不碰任何写操作,也不把 token 交给浏览器。三档降级:
5
+ *
6
+ * 1. `gh api user` —— 本机 gh 已登录时拿到的是**当前登录账号**(最准);
7
+ * 2. 仓库 `origin` remote 的 owner —— 没装 gh / 没登录也能用,头像是免登录的
8
+ * `github.com/<owner>.png`;但拿到的可能是「仓库归属者」而不是登录账号;
9
+ * 3. `git config user.name` —— 纯本地身份,没有头像,只作文字兜底。
10
+ *
11
+ * 两条网络路径实测:`gh api user` 约 1.1s、头像约 2.2s,所以结果缓存 12 小时
12
+ * (内存 + 落盘,PiWeb 重启后仍然即时)。头像取到后转成 data URL 随身份一起返回,
13
+ * 前端不用再发第二次请求,断网也能显示。
14
+ *
15
+ * 任何一步失败都只降级,不抛错:Git 面板不该因为 GitHub 不通而少东西。
16
+ */
17
+ import { execFile } from "node:child_process";
18
+ import fs from "node:fs/promises";
19
+ import path from "node:path";
20
+ import { promisify } from "node:util";
21
+ import { resolveGitBin } from "./growth-service";
22
+ import { getAgentDir } from "./pi";
23
+
24
+ const exec = promisify(execFile);
25
+
26
+ export interface GithubIdentity {
27
+ login: string;
28
+ /** gh 里的显示名(如「心安」),没有就省略 */
29
+ name?: string;
30
+ /** data URL;取不到头像时省略,UI 退回首字母 */
31
+ avatar?: string;
32
+ /** 账号主页,点击可跳转 */
33
+ url?: string;
34
+ /** `owner/repo`(来自 origin remote),没配 remote 就没有 */
35
+ repo?: string;
36
+ }
37
+
38
+ const TTL_MS = 12 * 60 * 60 * 1_000;
39
+ const GH_TIMEOUT_MS = 8_000;
40
+ const AVATAR_TIMEOUT_MS = 8_000;
41
+ const AVATAR_SIZE = 64;
42
+ const AVATAR_MAX_BYTES = 256 * 1_024;
43
+ const CACHE_FILENAME = "piweb-github-identity.json";
44
+ // 身份字段一变就提版本:旧的缓存条目直接作废重取,避免返回已经删掉的字段
45
+ const CACHE_VERSION = 2;
46
+
47
+ interface CacheFile {
48
+ version: number;
49
+ entries: Record<string, { at: number; value: GithubIdentity | null }>;
50
+ }
51
+
52
+ const memory = new Map<string, { at: number; value: GithubIdentity | null }>();
53
+ const inflight = new Map<string, Promise<GithubIdentity | null>>();
54
+ let diskLoaded = false;
55
+ let diskWrite: Promise<void> | null = null;
56
+
57
+ function cachePath(): string {
58
+ return path.join(getAgentDir(), CACHE_FILENAME);
59
+ }
60
+
61
+ /** 离线模式(SDK 也认这个变量):不发任何网络请求,只走本地能算出来的那一档 */
62
+ function offline(): boolean {
63
+ const value = process.env.PI_OFFLINE;
64
+ if (!value || value === "0") return false;
65
+ return value.toLowerCase() !== "false";
66
+ }
67
+
68
+ async function loadDisk(): Promise<void> {
69
+ if (diskLoaded) return;
70
+ diskLoaded = true;
71
+ try {
72
+ const raw = JSON.parse(await fs.readFile(cachePath(), "utf8")) as CacheFile;
73
+ if (raw?.version !== CACHE_VERSION || !raw.entries || typeof raw.entries !== "object") return;
74
+ const now = Date.now();
75
+ for (const [key, entry] of Object.entries(raw.entries)) {
76
+ if (!entry || typeof entry.at !== "number" || now - entry.at >= TTL_MS) continue;
77
+ if (!memory.has(key)) memory.set(key, entry);
78
+ }
79
+ } catch {
80
+ /* 没有缓存 / 读不了都当第一次 */
81
+ }
82
+ }
83
+
84
+ function persist(): void {
85
+ if (diskWrite) return;
86
+ diskWrite = (async () => {
87
+ try {
88
+ const entries: CacheFile["entries"] = {};
89
+ for (const [key, entry] of memory) entries[key] = entry;
90
+ await fs.writeFile(cachePath(), JSON.stringify({ version: CACHE_VERSION, entries }), "utf8");
91
+ } catch {
92
+ /* 写不了就只在内存里缓存 */
93
+ } finally {
94
+ diskWrite = null;
95
+ }
96
+ })();
97
+ }
98
+
99
+ /** 解析 gh 可执行文件(PATH 找不到时退回常见安装位置);结果缓存 */
100
+ let ghBin: string | null | undefined;
101
+ async function resolveGhBin(): Promise<string | null> {
102
+ if (ghBin !== undefined) return ghBin;
103
+ const candidates = process.platform === "win32"
104
+ ? ["gh", path.join(process.env.ProgramFiles ?? "C:\\Program Files", "GitHub CLI", "gh.exe"), path.join(process.env.LOCALAPPDATA ?? "", "Programs", "GitHub CLI", "gh.exe")]
105
+ : ["gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"];
106
+ for (const candidate of candidates) {
107
+ if (!candidate) continue;
108
+ const ok = await exec(candidate, ["--version"], { timeout: 5_000, windowsHide: true }).then(() => true, () => false);
109
+ if (ok) {
110
+ ghBin = candidate;
111
+ return ghBin;
112
+ }
113
+ }
114
+ ghBin = null;
115
+ return ghBin;
116
+ }
117
+
118
+ async function git(cwd: string, args: string[]): Promise<string> {
119
+ const out = await exec(await resolveGitBin(), args, {
120
+ cwd,
121
+ timeout: 15_000,
122
+ maxBuffer: 1024 * 1_024,
123
+ windowsHide: true,
124
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
125
+ });
126
+ return out.stdout;
127
+ }
128
+
129
+ /** 从 remote URL 里取 owner 与仓库名:支持 https / ssh / git:// 三种写法 */
130
+ export function parseGithubRemote(remote: string | undefined | null): { owner: string; repo?: string } | null {
131
+ if (!remote) return null;
132
+ const trimmed = remote.trim().replace(/\.git$/, "");
133
+ const m = trimmed.match(/github\.com[:/]+([^/]+)(?:\/([^/]+))?/i);
134
+ if (!m?.[1]) return null;
135
+ return { owner: m[1], repo: m[2] ? `${m[1]}/${m[2]}` : undefined };
136
+ }
137
+
138
+ async function ghAccount(cwd: string): Promise<{ login: string; name?: string; url?: string } | null> {
139
+ if (offline()) return null;
140
+ const bin = await resolveGhBin();
141
+ if (!bin) return null;
142
+ try {
143
+ const { stdout } = await exec(bin, ["api", "user"], {
144
+ cwd,
145
+ timeout: GH_TIMEOUT_MS,
146
+ maxBuffer: 1024 * 1_024,
147
+ windowsHide: true,
148
+ env: { ...process.env, GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" },
149
+ });
150
+ const parsed = JSON.parse(stdout) as { login?: unknown; name?: unknown; html_url?: unknown };
151
+ if (typeof parsed.login !== "string" || !parsed.login) return null;
152
+ return {
153
+ login: parsed.login,
154
+ name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : undefined,
155
+ url: typeof parsed.html_url === "string" ? parsed.html_url : undefined,
156
+ };
157
+ } catch {
158
+ // 没网 / 没登录 / token 失效:交给下一档
159
+ return null;
160
+ }
161
+ }
162
+
163
+ async function avatarDataUrl(owner: string): Promise<string | undefined> {
164
+ if (offline()) return undefined;
165
+ try {
166
+ const res = await fetch(`https://github.com/${encodeURIComponent(owner)}.png?size=${AVATAR_SIZE}`, {
167
+ signal: AbortSignal.timeout(AVATAR_TIMEOUT_MS),
168
+ headers: { Accept: "image/*" },
169
+ });
170
+ if (!res.ok) return undefined;
171
+ const type = res.headers.get("content-type") ?? "image/png";
172
+ if (!type.startsWith("image/")) return undefined;
173
+ const buffer = Buffer.from(await res.arrayBuffer());
174
+ if (!buffer.length || buffer.length > AVATAR_MAX_BYTES) return undefined;
175
+ return `data:${type};base64,${buffer.toString("base64")}`;
176
+ } catch {
177
+ return undefined;
178
+ }
179
+ }
180
+
181
+ async function compute(root: string): Promise<GithubIdentity | null> {
182
+ let remote: { owner: string; repo?: string } | null = null;
183
+ try {
184
+ remote = parseGithubRemote(await git(root, ["remote", "get-url", "origin"]));
185
+ } catch {
186
+ /* 没有 origin 就往下走 */
187
+ }
188
+ const account = await ghAccount(root);
189
+ if (account) {
190
+ return {
191
+ login: account.login,
192
+ name: account.name,
193
+ url: account.url ?? `https://github.com/${account.login}`,
194
+ avatar: await avatarDataUrl(account.login),
195
+ repo: remote?.repo,
196
+ };
197
+ }
198
+ if (remote) {
199
+ return { login: remote.owner, url: `https://github.com/${remote.owner}`, avatar: await avatarDataUrl(remote.owner), repo: remote.repo };
200
+ }
201
+ try {
202
+ const name = (await git(root, ["config", "user.name"])).trim();
203
+ if (name) return { login: name };
204
+ } catch {
205
+ /* 什么都没配 */
206
+ }
207
+ return null;
208
+ }
209
+
210
+ function start(root: string): Promise<GithubIdentity | null> {
211
+ const existing = inflight.get(root);
212
+ if (existing) return existing;
213
+ const task = compute(root)
214
+ .then((value) => {
215
+ memory.set(root, { at: Date.now(), value });
216
+ persist();
217
+ return value;
218
+ })
219
+ .catch(() => null)
220
+ .finally(() => inflight.delete(root)) as Promise<GithubIdentity | null>;
221
+ inflight.set(root, task);
222
+ return task;
223
+ }
224
+
225
+ /**
226
+ * 取仓库的 GitHub 身份。命中缓存立刻返回;否则**后台**刷新并返回已过期的旧值(或 null),
227
+ * 不会让调用方等网络。要拿首次结果用 `awaitGithubIdentity`。
228
+ */
229
+ export function githubIdentity(root: string | null): GithubIdentity | null {
230
+ if (!root) return null;
231
+ void loadDisk();
232
+ const hit = memory.get(root);
233
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
234
+ void start(root);
235
+ return hit?.value ?? null;
236
+ }
237
+
238
+ /** 等首次解析完成(面板刚打开、缓存还冷时用;调用方自己控制超时) */
239
+ export async function awaitGithubIdentity(root: string | null): Promise<GithubIdentity | null> {
240
+ if (!root) return null;
241
+ await loadDisk();
242
+ const hit = memory.get(root);
243
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
244
+ return start(root);
245
+ }
@@ -1,3 +1,5 @@
1
+ import { promptTitleText } from "./chat/skill-expansion";
2
+
1
3
  export interface SessionTurn {
2
4
  id: string;
3
5
  ts: number;
@@ -47,7 +49,7 @@ export function promptsFromEntries(entries: readonly SessionEntry[], fromIndex =
47
49
  for (const entry of entries.slice(Math.max(0, fromIndex))) {
48
50
  if (entry.type !== "message" || entry.message?.role !== "user" || !entry.id) continue;
49
51
  ids.push(entry.id);
50
- if (!title) title = textOf(entry.message.content).split("\n").map((line) => line.trim()).find(Boolean) ?? "";
52
+ if (!title) title = promptTitleText(textOf(entry.message.content)).split("\n").map((line) => line.trim()).find(Boolean) ?? "";
51
53
  }
52
54
  return { ids, title };
53
55
  }
@@ -7,9 +7,10 @@ import path from "node:path";
7
7
  import { createInterface } from "node:readline";
8
8
  import { SessionManager } from "@earendil-works/pi-coding-agent";
9
9
  import type { SessionSummary, WebMessage } from "./types";
10
- import { toWebMessage } from "./agent-manager";
10
+ import { sessionDisplayMessages, syncSessionsFromDisk } from "./agent-manager";
11
11
  import { getAgentDir } from "./pi";
12
12
  import { BoundaryError, isPathInside } from "./path-security";
13
+ import { promptTitleText } from "./chat/skill-expansion";
13
14
 
14
15
  function toSummary(info: {
15
16
  path: string;
@@ -31,6 +32,11 @@ function toSummary(info: {
31
32
  };
32
33
  }
33
34
 
35
+ /** Date 能表示的毫秒数(±8.64e15):超出范围的 toISOString 会抛 RangeError */
36
+ function isValidTime(ms: number): boolean {
37
+ return Number.isFinite(ms) && Math.abs(ms) <= 8.64e15;
38
+ }
39
+
34
40
  /** firstMessage 只当侧栏标题与搜索用:整段贴进来的长提问不该随每 3 秒一次的列表轮询反复下发 */
35
41
  export const FIRST_MESSAGE_MAX_CHARS = 300;
36
42
 
@@ -76,22 +82,25 @@ async function summarize(filePath: string, stat: { mtime: Date }): Promise<Sessi
76
82
  if (!message || typeof message.role !== "string" || !("content" in message)) continue;
77
83
  if (message.role !== "user" && message.role !== "assistant") continue;
78
84
  const at = typeof message.timestamp === "number" ? message.timestamp : new Date(entry.timestamp).getTime();
79
- if (Number.isFinite(at)) lastActivity = Math.max(lastActivity ?? 0, at);
85
+ if (isValidTime(at)) lastActivity = Math.max(lastActivity ?? 0, at);
80
86
  if (!firstMessage && message.role === "user") {
81
87
  const content = message.content;
82
- firstMessage = typeof content === "string"
88
+ firstMessage = promptTitleText(typeof content === "string"
83
89
  ? content
84
- : Array.isArray(content) ? content.filter((b: any) => b?.type === "text").map((b: any) => b.text).join(" ") : "";
90
+ : Array.isArray(content) ? content.filter((b: any) => b?.type === "text").map((b: any) => b.text).join(" ") : "");
85
91
  }
86
92
  }
87
93
  if (!header) return null;
88
94
  const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
89
- const modified = lastActivity && lastActivity > 0 ? new Date(lastActivity) : !Number.isNaN(headerTime) ? new Date(headerTime) : stat.mtime;
95
+ const created = isValidTime(headerTime) ? new Date(headerTime) : stat.mtime;
96
+ const modified = lastActivity && lastActivity > 0 ? new Date(lastActivity) : created;
90
97
  return toSummary({
91
98
  path: filePath,
92
99
  name,
93
100
  cwd: typeof header.cwd === "string" ? header.cwd : "",
94
- created: new Date(String(header.timestamp)),
101
+ // 头里缺 timestamp 或写坏了:回落 mtime。以前直接 new Date(String(...)),Invalid Date 的 toISOString 一抛,
102
+ // 整个会话就被 catch 吞成 null,从侧栏里悄悄消失
103
+ created,
95
104
  modified,
96
105
  messageCount,
97
106
  firstMessage: clipFirstMessage(firstMessage) || "(no messages)",
@@ -118,8 +127,12 @@ interface CachedSummary {
118
127
  * 按 mtime + 大小校验,命中就沿用摘要——侧栏首次渲染从"全量解析"变成"读一个小 JSON"。
119
128
  * 索引只是加速:读不到、版本不符、写不进去都退化成全量扫描,绝不影响正确性。
120
129
  */
121
- /** 2:firstMessage 开始截断,旧索引里的整段原文要重新摘要 */
122
- const INDEX_VERSION = 2;
130
+ /**
131
+ * 2:firstMessage 开始截断,旧索引里的整段原文要重新摘要
132
+ * 3:技能展开的首条消息还原成 `/skill:name 参数`,旧索引里存的是 `<skill name=…>` 加 SKILL.md 正文
133
+ * 4:头时间戳坏掉的会话不再摘要成 null,旧索引里缓存的 null 要重读才能回到侧栏
134
+ */
135
+ const INDEX_VERSION = 4;
123
136
  const INDEX_FILENAME = "piweb-session-index.json";
124
137
  const INDEX_MAX_ENTRIES = 5000;
125
138
  const INDEX_SAVE_DELAY_MS = 1500;
@@ -246,6 +259,8 @@ export async function listSessions(query?: string): Promise<SessionSummary[]> {
246
259
  scanning ??= scanSessions().finally(() => { scanning = null; });
247
260
  list = await scanning;
248
261
  listCache = { at: Date.now(), list };
262
+ // 顺带看看开着的会话有没有被终端 pi 写过(见 agent-manager syncWithDisk)
263
+ syncSessionsFromDisk();
249
264
  }
250
265
  if (query?.trim()) {
251
266
  const q = query.trim().toLowerCase();
@@ -281,7 +296,7 @@ export async function deleteSession(sessionPath: string): Promise<void> {
281
296
  }
282
297
  }
283
298
 
284
- /** 冷读取:还原上下文消息 + 会话名(不启动 agent) */
299
+ /** 冷读取:还原消息(含被压缩掉的历史)+ 会话名(不启动 agent) */
285
300
  export async function readSession(sessionPath: string): Promise<{
286
301
  cwd: string;
287
302
  name: string;
@@ -290,10 +305,7 @@ export async function readSession(sessionPath: string): Promise<{
290
305
  const sm = SessionManager.open(sessionPath);
291
306
  let messages: WebMessage[] = [];
292
307
  try {
293
- const entries = sm.buildContextEntries();
294
- messages = entries
295
- .filter((e: any) => e.type === "message" && e.message)
296
- .map((e: any) => toWebMessage(e.message, e.id));
308
+ messages = sessionDisplayMessages(sm);
297
309
  } catch {
298
310
  messages = [];
299
311
  }
@@ -1,5 +1,6 @@
1
1
  /**
2
- * 归档会话保留期:归档后超过 N 天没有活动(以会话文件 mtime 为准)就自动删除。
2
+ * 归档会话保留期:从归档那一刻起算满 N 天就自动删除;归档后又用过的,从最后一次活动(文件 mtime)起算。
3
+ * 以前只看 mtime,于是把一个很久没动的旧会话归档,几小时内就会被直接删掉。
3
4
  *
4
5
  * 只会碰「在归档列表里」的会话:没归档的会话即使很久没动也不会被动。
5
6
  * 正在被本进程管理的会话(打开中/运行中)一律跳过——交给它自己空闲回收。
@@ -7,15 +8,16 @@
7
8
  */
8
9
  import fs from "node:fs/promises";
9
10
  import path from "node:path";
10
- import { forgetSession, getWorkspaceRegistry } from "./workspace-store";
11
+ import { forgetSession, getArchivedEntries } from "./workspace-store";
11
12
 
12
- /** 归档会话多久没有活动就删除(天) */
13
+ /** 归档会话保留多久(天);侧栏「已归档」的提示文案(i18n archiveRetentionHint)写的是同一个数 */
13
14
  export const ARCHIVED_RETENTION_DAYS = 30;
14
15
  /** 两次清理之间的间隔 */
15
16
  export const SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1000;
16
17
 
17
18
  export interface RetentionReport {
18
19
  scanned: number;
20
+ /** idleDays:距归档或最后一次活动(取较晚者)的天数 */
19
21
  deleted: { path: string; idleDays: number }[];
20
22
  /** 文件已不在磁盘上:只清掉归档列表里的记录 */
21
23
  missing: string[];
@@ -44,9 +46,9 @@ export async function sweepArchivedSessions({
44
46
  const report: RetentionReport = { scanned: 0, deleted: [], missing: [], skippedActive: [], kept: 0 };
45
47
  if (!Number.isFinite(retentionDays) || retentionDays <= 0) return report;
46
48
  const cutoff = now - retentionDays * 24 * 60 * 60 * 1000;
47
- const { archivedSessions } = await getWorkspaceRegistry();
49
+ const entries = await getArchivedEntries({ now, persist: !dryRun });
48
50
 
49
- for (const sessionPath of archivedSessions) {
51
+ for (const { path: sessionPath, archivedAt } of entries) {
50
52
  report.scanned += 1;
51
53
  const target = path.resolve(sessionPath);
52
54
  if (isActive(target)) {
@@ -66,18 +68,19 @@ export async function sweepArchivedSessions({
66
68
  if (!dryRun) await forgetSession(target);
67
69
  continue;
68
70
  }
69
- if (mtimeMs > cutoff) {
71
+ const since = Math.max(archivedAt, mtimeMs);
72
+ if (since > cutoff) {
70
73
  report.kept += 1;
71
74
  continue;
72
75
  }
73
- const idleDays = Math.floor((now - mtimeMs) / (24 * 60 * 60 * 1000));
76
+ const idleDays = Math.floor((now - since) / (24 * 60 * 60 * 1000));
74
77
  report.deleted.push({ path: target, idleDays });
75
78
  if (dryRun) continue;
76
79
  // 运行时再取:session-reader → agent-manager → 本模块 构成环,静态导入会在初始化期踩空
77
80
  const { deleteSession } = await import("./session-reader");
78
81
  await deleteSession(target);
79
82
  await forgetSession(target);
80
- log(`[piweb] 归档会话已超过 ${retentionDays} 天没有活动(${idleDays} 天),已删除:${target}`);
83
+ log(`[piweb] 归档会话已满 ${retentionDays} 天(归档后 ${idleDays} 天没有活动),已删除:${target}`);
81
84
  }
82
85
  return report;
83
86
  }