@baique/pi-web-sky 0.1.55 → 0.1.57

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 (396) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -4
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +2 -2
  5. package/.next/react-loadable-manifest.json +2 -2
  6. package/.next/required-server-files.js +3 -2
  7. package/.next/required-server-files.json +3 -2
  8. package/.next/routes-manifest.json +28 -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 +4 -3
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -3
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +4 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -2
  23. package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  25. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/agent/new/route.js +1 -1
  29. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  30. package/.next/server/app/api/agent/running/route.js +1 -1
  31. package/.next/server/app/api/agent/running/route.js.nft.json +1 -1
  32. package/.next/server/app/api/app-update/route.js +1 -1
  33. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  34. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  35. package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
  36. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  37. package/.next/server/app/api/auth/providers/route.js +1 -1
  38. package/.next/server/app/api/board-assets/[...path]/route.js +2 -2
  39. package/.next/server/app/api/board-assets/route.js +2 -2
  40. package/.next/server/app/api/boards/[id]/add-session/route.js +1 -0
  41. package/.next/server/app/api/boards/[id]/add-session/route.js.nft.json +1 -0
  42. package/.next/server/app/api/boards/[id]/add-session/route_client-reference-manifest.js +1 -0
  43. package/.next/server/app/api/boards/[id]/reconcile/route.js +1 -1
  44. package/.next/server/app/api/boards/[id]/route.js +1 -1
  45. package/.next/server/app/api/boards/reorder/route.js +2 -2
  46. package/.next/server/app/api/boards/route.js +1 -1
  47. package/.next/server/app/api/cwd/browse/route.js +1 -1
  48. package/.next/server/app/api/cwd/validate/route.js +2 -2
  49. package/.next/server/app/api/default-cwd/route.js +2 -2
  50. package/.next/server/app/api/file-index/route.js +2 -2
  51. package/.next/server/app/api/files/[...path]/route.js +3 -3
  52. package/.next/server/app/api/git/diff/route.js +1 -1
  53. package/.next/server/app/api/git/status/route.js +1 -1
  54. package/.next/server/app/api/home/route.js +1 -1
  55. package/.next/server/app/api/mcp/route.js +2 -2
  56. package/.next/server/app/api/models/route.js +2 -2
  57. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  58. package/.next/server/app/api/models-config/discover/route.js +1 -1
  59. package/.next/server/app/api/models-config/route.js +1 -1
  60. package/.next/server/app/api/models-config/test/route.js +1 -1
  61. package/.next/server/app/api/plugins/route.js +2 -2
  62. package/.next/server/app/api/project-trust/route.js +1 -1
  63. package/.next/server/app/api/project-trust/route.js.nft.json +1 -1
  64. package/.next/server/app/api/quota/route.js +1 -1
  65. package/.next/server/app/api/search/route.js +3 -3
  66. package/.next/server/app/api/sessions/[id]/auto-name/route.js +2 -2
  67. package/.next/server/app/api/sessions/[id]/auto-name/route.js.nft.json +1 -1
  68. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  69. package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +1 -1
  70. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
  71. package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
  72. package/.next/server/app/api/sessions/[id]/minimap/route.js +1 -1
  73. package/.next/server/app/api/sessions/[id]/minimap/route.js.nft.json +1 -1
  74. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  75. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  76. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  77. package/.next/server/app/api/sessions/[id]/state/route.js.nft.json +1 -1
  78. package/.next/server/app/api/sessions/[id]/todos/route.js +1 -1
  79. package/.next/server/app/api/sessions/[id]/todos/route.js.nft.json +1 -1
  80. package/.next/server/app/api/sessions/route.js +1 -1
  81. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  82. package/.next/server/app/api/sessions/summary/route.js +2 -2
  83. package/.next/server/app/api/skills/check/route.js +1 -1
  84. package/.next/server/app/api/skills/install/route.js +2 -2
  85. package/.next/server/app/api/skills/route.js +1 -1
  86. package/.next/server/app/api/skills/search/route.js +1 -1
  87. package/.next/server/app/api/skills/update/route.js +1 -1
  88. package/.next/server/app/api/subagents/[id]/route.js +1 -0
  89. package/.next/server/app/api/subagents/[id]/route.js.nft.json +1 -0
  90. package/.next/server/app/api/subagents/[id]/route_client-reference-manifest.js +1 -0
  91. package/.next/server/app/api/subagents/profiles/route.js +30 -0
  92. package/.next/server/app/api/subagents/profiles/route.js.nft.json +1 -0
  93. package/.next/server/app/api/subagents/profiles/route_client-reference-manifest.js +1 -0
  94. package/.next/server/app/api/subagents/settings/route.js +1 -0
  95. package/.next/server/app/api/subagents/settings/route.js.nft.json +1 -0
  96. package/.next/server/app/api/subagents/settings/route_client-reference-manifest.js +1 -0
  97. package/.next/server/app/api/task-card-questions/[id]/answer/route.js +2 -2
  98. package/.next/server/app/api/task-card-questions/route.js +2 -2
  99. package/.next/server/app/api/task-cards/[id]/route.js +1 -1
  100. package/.next/server/app/api/task-cards/route.js +1 -1
  101. package/.next/server/app/api/task-cards/unbind/route.js +3 -3
  102. package/.next/server/app/api/task-scheduler/status/route.js +1 -1
  103. package/.next/server/app/api/task-scheduler/status/route.js.nft.json +1 -1
  104. package/.next/server/app/api/tasks/[id]/assign-session/route.js +2 -12
  105. package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
  106. package/.next/server/app/api/tasks/[id]/route.js +1 -1
  107. package/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -1
  108. package/.next/server/app/api/tasks/reorder/route.js +2 -12
  109. package/.next/server/app/api/tasks/route.js +2 -12
  110. package/.next/server/app/api/terminal/[id]/events/route.js +2 -2
  111. package/.next/server/app/api/terminal/[id]/route.js +2 -2
  112. package/.next/server/app/api/terminal/route.js +2 -2
  113. package/.next/server/app/api/worktrees/route.js +2 -2
  114. package/.next/server/app/api/yjs-version/route.js +1 -1
  115. package/.next/server/app/favicon.ico/route.js +1 -1
  116. package/.next/server/app/index.html +1 -1
  117. package/.next/server/app/index.rsc +6 -5
  118. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -5
  119. package/.next/server/app/index.segments/_full.segment.rsc +6 -5
  120. package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
  121. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  122. package/.next/server/app/page.js +175 -8
  123. package/.next/server/app/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app-paths-manifest.json +8 -4
  125. package/.next/server/chunks/1937.js +14 -13
  126. package/.next/server/chunks/2524.js +2 -2
  127. package/.next/server/chunks/3379.js +1 -1
  128. package/.next/server/chunks/3500.js +1 -1
  129. package/.next/server/chunks/402.js +28 -7
  130. package/.next/server/chunks/426.js +14 -0
  131. package/.next/server/chunks/4367.js +1 -1
  132. package/.next/server/chunks/4457.js +3 -13
  133. package/.next/server/chunks/4980.js +1 -1
  134. package/.next/server/chunks/542.js +23 -4
  135. package/.next/server/chunks/5794.js +2 -2
  136. package/.next/server/chunks/5940.js +1 -1
  137. package/.next/server/chunks/6337.js +1 -1
  138. package/.next/server/chunks/6429.js +15 -2
  139. package/.next/server/chunks/6525.js +25 -4
  140. package/.next/server/chunks/6583.js +12 -11
  141. package/.next/server/chunks/7048.js +6 -0
  142. package/.next/server/instrumentation.js +1 -1
  143. package/.next/server/middleware-build-manifest.js +1 -1
  144. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  145. package/.next/server/middleware.js +3 -3
  146. package/.next/server/pages/404.html +1 -1
  147. package/.next/server/pages/500.html +1 -1
  148. package/.next/static/MPBMmQqmDG8zo8tM_bkDy/_buildManifest.js +1 -0
  149. package/.next/static/chunks/752.bafff304e6b00987.js +1 -0
  150. package/.next/static/chunks/8088.39866dc5f1d15868.js +1 -0
  151. package/.next/static/chunks/app/_global-error/page-ad6e695deb9de65c.js +1 -0
  152. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-ad6e695deb9de65c.js +1 -0
  153. package/.next/static/chunks/app/api/agent/[id]/events/route-ad6e695deb9de65c.js +1 -0
  154. package/.next/static/chunks/app/api/agent/[id]/route-ad6e695deb9de65c.js +1 -0
  155. package/.next/static/chunks/app/api/agent/new/route-ad6e695deb9de65c.js +1 -0
  156. package/.next/static/chunks/app/api/agent/running/route-ad6e695deb9de65c.js +1 -0
  157. package/.next/static/chunks/app/api/app-update/route-ad6e695deb9de65c.js +1 -0
  158. package/.next/static/chunks/app/api/auth/all-providers/route-ad6e695deb9de65c.js +1 -0
  159. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-ad6e695deb9de65c.js +1 -0
  160. package/.next/static/chunks/app/api/auth/login/[provider]/route-ad6e695deb9de65c.js +1 -0
  161. package/.next/static/chunks/app/api/auth/logout/[provider]/route-ad6e695deb9de65c.js +1 -0
  162. package/.next/static/chunks/app/api/auth/providers/route-ad6e695deb9de65c.js +1 -0
  163. package/.next/static/chunks/app/api/board-assets/[...path]/route-ad6e695deb9de65c.js +1 -0
  164. package/.next/static/chunks/app/api/board-assets/route-ad6e695deb9de65c.js +1 -0
  165. package/.next/static/chunks/app/api/boards/[id]/add-session/route-ad6e695deb9de65c.js +1 -0
  166. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-ad6e695deb9de65c.js +1 -0
  167. package/.next/static/chunks/app/api/boards/[id]/route-ad6e695deb9de65c.js +1 -0
  168. package/.next/static/chunks/app/api/boards/reorder/route-ad6e695deb9de65c.js +1 -0
  169. package/.next/static/chunks/app/api/boards/route-ad6e695deb9de65c.js +1 -0
  170. package/.next/static/chunks/app/api/cwd/browse/route-ad6e695deb9de65c.js +1 -0
  171. package/.next/static/chunks/app/api/cwd/validate/route-ad6e695deb9de65c.js +1 -0
  172. package/.next/static/chunks/app/api/default-cwd/route-ad6e695deb9de65c.js +1 -0
  173. package/.next/static/chunks/app/api/file-index/route-ad6e695deb9de65c.js +1 -0
  174. package/.next/static/chunks/app/api/files/[...path]/route-ad6e695deb9de65c.js +1 -0
  175. package/.next/static/chunks/app/api/git/diff/route-ad6e695deb9de65c.js +1 -0
  176. package/.next/static/chunks/app/api/git/status/route-ad6e695deb9de65c.js +1 -0
  177. package/.next/static/chunks/app/api/home/route-ad6e695deb9de65c.js +1 -0
  178. package/.next/static/chunks/app/api/mcp/route-ad6e695deb9de65c.js +1 -0
  179. package/.next/static/chunks/app/api/models/route-ad6e695deb9de65c.js +1 -0
  180. package/.next/static/chunks/app/api/models-config/catalog/route-ad6e695deb9de65c.js +1 -0
  181. package/.next/static/chunks/app/api/models-config/discover/route-ad6e695deb9de65c.js +1 -0
  182. package/.next/static/chunks/app/api/models-config/route-ad6e695deb9de65c.js +1 -0
  183. package/.next/static/chunks/app/api/models-config/test/route-ad6e695deb9de65c.js +1 -0
  184. package/.next/static/chunks/app/api/plugins/route-ad6e695deb9de65c.js +1 -0
  185. package/.next/static/chunks/app/api/project-trust/route-ad6e695deb9de65c.js +1 -0
  186. package/.next/static/chunks/app/api/quota/route-ad6e695deb9de65c.js +1 -0
  187. package/.next/static/chunks/app/api/search/route-ad6e695deb9de65c.js +1 -0
  188. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-ad6e695deb9de65c.js +1 -0
  189. package/.next/static/chunks/app/api/sessions/[id]/context/route-ad6e695deb9de65c.js +1 -0
  190. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-ad6e695deb9de65c.js +1 -0
  191. package/.next/static/chunks/app/api/sessions/[id]/export/route-ad6e695deb9de65c.js +1 -0
  192. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-ad6e695deb9de65c.js +1 -0
  193. package/.next/static/chunks/app/api/sessions/[id]/route-ad6e695deb9de65c.js +1 -0
  194. package/.next/static/chunks/app/api/sessions/[id]/state/route-ad6e695deb9de65c.js +1 -0
  195. package/.next/static/chunks/app/api/sessions/[id]/todos/route-ad6e695deb9de65c.js +1 -0
  196. package/.next/static/chunks/app/api/sessions/route-ad6e695deb9de65c.js +1 -0
  197. package/.next/static/chunks/app/api/sessions/summary/route-ad6e695deb9de65c.js +1 -0
  198. package/.next/static/chunks/app/api/skills/check/route-ad6e695deb9de65c.js +1 -0
  199. package/.next/static/chunks/app/api/skills/install/route-ad6e695deb9de65c.js +1 -0
  200. package/.next/static/chunks/app/api/skills/route-ad6e695deb9de65c.js +1 -0
  201. package/.next/static/chunks/app/api/skills/search/route-ad6e695deb9de65c.js +1 -0
  202. package/.next/static/chunks/app/api/skills/update/route-ad6e695deb9de65c.js +1 -0
  203. package/.next/static/chunks/app/api/subagents/[id]/route-ad6e695deb9de65c.js +1 -0
  204. package/.next/static/chunks/app/api/subagents/profiles/route-ad6e695deb9de65c.js +1 -0
  205. package/.next/static/chunks/app/api/subagents/settings/route-ad6e695deb9de65c.js +1 -0
  206. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-ad6e695deb9de65c.js +1 -0
  207. package/.next/static/chunks/app/api/task-card-questions/route-ad6e695deb9de65c.js +1 -0
  208. package/.next/static/chunks/app/api/task-cards/[id]/route-ad6e695deb9de65c.js +1 -0
  209. package/.next/static/chunks/app/api/task-cards/route-ad6e695deb9de65c.js +1 -0
  210. package/.next/static/chunks/app/api/task-cards/unbind/route-ad6e695deb9de65c.js +1 -0
  211. package/.next/static/chunks/app/api/task-scheduler/status/route-ad6e695deb9de65c.js +1 -0
  212. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-ad6e695deb9de65c.js +1 -0
  213. package/.next/static/chunks/app/api/tasks/[id]/board/route-ad6e695deb9de65c.js +1 -0
  214. package/.next/static/chunks/app/api/tasks/[id]/route-ad6e695deb9de65c.js +1 -0
  215. package/.next/static/chunks/app/api/tasks/reorder/route-ad6e695deb9de65c.js +1 -0
  216. package/.next/static/chunks/app/api/tasks/route-ad6e695deb9de65c.js +1 -0
  217. package/.next/static/chunks/app/api/terminal/[id]/events/route-ad6e695deb9de65c.js +1 -0
  218. package/.next/static/chunks/app/api/terminal/[id]/route-ad6e695deb9de65c.js +1 -0
  219. package/.next/static/chunks/app/api/terminal/route-ad6e695deb9de65c.js +1 -0
  220. package/.next/static/chunks/app/api/worktrees/route-ad6e695deb9de65c.js +1 -0
  221. package/.next/static/chunks/app/api/yjs-version/route-ad6e695deb9de65c.js +1 -0
  222. package/.next/static/chunks/app/layout-fae2b226b3c78448.js +1 -0
  223. package/.next/static/chunks/app/manifest.webmanifest/route-ad6e695deb9de65c.js +1 -0
  224. package/.next/static/chunks/app/page-d5af3bd535589daf.js +256 -0
  225. package/.next/static/chunks/next/dist/client/components/builtin/app-error-ad6e695deb9de65c.js +1 -0
  226. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-ad6e695deb9de65c.js +1 -0
  227. package/.next/static/chunks/next/dist/client/components/builtin/not-found-ad6e695deb9de65c.js +1 -0
  228. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-ad6e695deb9de65c.js +1 -0
  229. package/.next/static/chunks/{webpack-b0effde51c1651b8.js → webpack-1ce35e1249cfd931.js} +1 -1
  230. package/.next/static/css/bb9361c7f832a89d.css +1 -0
  231. package/.next/static/css/c7d127e07b1a786c.css +1 -0
  232. package/.next/trace +8 -7
  233. package/.next/trace-build +1 -1
  234. package/.next/types/app/api/boards/[id]/add-session/route.ts +353 -0
  235. package/.next/types/app/api/subagents/[id]/route.ts +353 -0
  236. package/.next/types/app/api/subagents/profiles/route.ts +353 -0
  237. package/.next/types/app/api/subagents/settings/route.ts +353 -0
  238. package/.next/types/routes.d.ts +5 -1
  239. package/.next/types/validator.ts +36 -0
  240. package/lib/api-types.ts +10 -0
  241. package/lib/board-reconcile.test.mjs +46 -8
  242. package/lib/board-reconcile.ts +57 -1
  243. package/lib/board-store.test.mjs +2 -2
  244. package/lib/chat-lazy-load.test.mjs +0 -62
  245. package/lib/chat-lazy-load.ts +0 -13
  246. package/lib/chat-only.ts +23 -0
  247. package/lib/draft-stash.ts +4 -5
  248. package/lib/file-types.ts +16 -1
  249. package/lib/i18n/messages/en.ts +91 -0
  250. package/lib/i18n/messages/zh-CN.ts +91 -0
  251. package/lib/id.test.mjs +38 -0
  252. package/lib/id.ts +17 -0
  253. package/lib/ime-guard.test.mjs +52 -0
  254. package/lib/ime-guard.ts +54 -0
  255. package/lib/message-text.ts +24 -0
  256. package/lib/models-cache.ts +1 -1
  257. package/lib/pi-types.ts +19 -1
  258. package/lib/pin-store.ts +85 -0
  259. package/lib/powershell-settings.ts +106 -0
  260. package/lib/quoted-selection.ts +4 -0
  261. package/lib/rpc-manager.activity.test.mjs +223 -0
  262. package/lib/rpc-manager.test.mjs +22 -0
  263. package/lib/rpc-manager.title.test.mjs +152 -0
  264. package/lib/rpc-manager.ts +301 -28
  265. package/lib/session-activity.test.mjs +123 -0
  266. package/lib/session-activity.ts +77 -0
  267. package/lib/session-delete.test.mjs +45 -41
  268. package/lib/session-delete.ts +8 -34
  269. package/lib/session-family.test.mjs +49 -0
  270. package/lib/session-family.ts +80 -0
  271. package/lib/session-index-scanner.test.mjs +364 -2
  272. package/lib/session-index-scanner.ts +199 -55
  273. package/lib/session-reader.noscan.test.mjs +371 -0
  274. package/lib/session-reader.project.test.mjs +30 -52
  275. package/lib/session-reader.test.mjs +2 -65
  276. package/lib/session-reader.ts +177 -213
  277. package/lib/session-scanner.test.mjs +151 -38
  278. package/lib/session-scanner.ts +140 -61
  279. package/lib/session-search.ts +6 -5
  280. package/lib/session-time-group.test.mjs +38 -0
  281. package/lib/session-time-group.ts +55 -0
  282. package/lib/settings-navigation.ts +124 -0
  283. package/lib/sqlite-db.test.mjs +26 -0
  284. package/lib/sqlite-db.ts +10 -1
  285. package/lib/subagent-extension.test.mjs +424 -0
  286. package/lib/subagent-extension.ts +309 -0
  287. package/lib/subagent-input.test.mjs +63 -0
  288. package/lib/subagent-input.ts +71 -0
  289. package/lib/subagent-profile-precedence.ts +20 -0
  290. package/lib/subagent-prompt.test.mjs +64 -0
  291. package/lib/subagent-prompt.ts +31 -0
  292. package/lib/subagent-queue.test.mjs +35 -0
  293. package/lib/subagent-queue.ts +74 -0
  294. package/lib/subagent-runtime.test.mjs +150 -0
  295. package/lib/subagent-runtime.ts +629 -0
  296. package/lib/subagent-session-title.test.mjs +120 -0
  297. package/lib/subagent-settings.test.mjs +50 -0
  298. package/lib/subagent-settings.ts +93 -0
  299. package/lib/subagents.test.mjs +601 -0
  300. package/lib/subagents.ts +602 -0
  301. package/lib/task-scheduler.test.mjs +27 -0
  302. package/lib/task-scheduler.ts +8 -1
  303. package/lib/task-store.activity.test.mjs +141 -0
  304. package/lib/task-store.meta-row.test.mjs +25 -5
  305. package/lib/task-store.partial-row.test.mjs +198 -0
  306. package/lib/task-store.subtree.test.mjs +172 -0
  307. package/lib/task-store.test.mjs +46 -20
  308. package/lib/task-store.ts +344 -68
  309. package/lib/tool-presets.ts +4 -0
  310. package/lib/types.ts +19 -0
  311. package/lib/worktree-isolation.test.mjs +51 -0
  312. package/next.config.ts +3 -1
  313. package/package.json +1 -1
  314. package/.next/static/bsW3fla0-BlBHVMKtAM5I/_buildManifest.js +0 -1
  315. package/.next/static/chunks/752.0ad92735d5f38143.js +0 -1
  316. package/.next/static/chunks/8088.53d9cdaf15634d4b.js +0 -1
  317. package/.next/static/chunks/app/_global-error/page-2d5f6296c7e693c6.js +0 -1
  318. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-2d5f6296c7e693c6.js +0 -1
  319. package/.next/static/chunks/app/api/agent/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  320. package/.next/static/chunks/app/api/agent/[id]/route-2d5f6296c7e693c6.js +0 -1
  321. package/.next/static/chunks/app/api/agent/new/route-2d5f6296c7e693c6.js +0 -1
  322. package/.next/static/chunks/app/api/agent/running/route-2d5f6296c7e693c6.js +0 -1
  323. package/.next/static/chunks/app/api/app-update/route-2d5f6296c7e693c6.js +0 -1
  324. package/.next/static/chunks/app/api/auth/all-providers/route-2d5f6296c7e693c6.js +0 -1
  325. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-2d5f6296c7e693c6.js +0 -1
  326. package/.next/static/chunks/app/api/auth/login/[provider]/route-2d5f6296c7e693c6.js +0 -1
  327. package/.next/static/chunks/app/api/auth/logout/[provider]/route-2d5f6296c7e693c6.js +0 -1
  328. package/.next/static/chunks/app/api/auth/providers/route-2d5f6296c7e693c6.js +0 -1
  329. package/.next/static/chunks/app/api/board-assets/[...path]/route-2d5f6296c7e693c6.js +0 -1
  330. package/.next/static/chunks/app/api/board-assets/route-2d5f6296c7e693c6.js +0 -1
  331. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-2d5f6296c7e693c6.js +0 -1
  332. package/.next/static/chunks/app/api/boards/[id]/route-2d5f6296c7e693c6.js +0 -1
  333. package/.next/static/chunks/app/api/boards/reorder/route-2d5f6296c7e693c6.js +0 -1
  334. package/.next/static/chunks/app/api/boards/route-2d5f6296c7e693c6.js +0 -1
  335. package/.next/static/chunks/app/api/cwd/browse/route-2d5f6296c7e693c6.js +0 -1
  336. package/.next/static/chunks/app/api/cwd/validate/route-2d5f6296c7e693c6.js +0 -1
  337. package/.next/static/chunks/app/api/default-cwd/route-2d5f6296c7e693c6.js +0 -1
  338. package/.next/static/chunks/app/api/file-index/route-2d5f6296c7e693c6.js +0 -1
  339. package/.next/static/chunks/app/api/files/[...path]/route-2d5f6296c7e693c6.js +0 -1
  340. package/.next/static/chunks/app/api/git/diff/route-2d5f6296c7e693c6.js +0 -1
  341. package/.next/static/chunks/app/api/git/status/route-2d5f6296c7e693c6.js +0 -1
  342. package/.next/static/chunks/app/api/home/route-2d5f6296c7e693c6.js +0 -1
  343. package/.next/static/chunks/app/api/mcp/route-2d5f6296c7e693c6.js +0 -1
  344. package/.next/static/chunks/app/api/models/route-2d5f6296c7e693c6.js +0 -1
  345. package/.next/static/chunks/app/api/models-config/catalog/route-2d5f6296c7e693c6.js +0 -1
  346. package/.next/static/chunks/app/api/models-config/discover/route-2d5f6296c7e693c6.js +0 -1
  347. package/.next/static/chunks/app/api/models-config/route-2d5f6296c7e693c6.js +0 -1
  348. package/.next/static/chunks/app/api/models-config/test/route-2d5f6296c7e693c6.js +0 -1
  349. package/.next/static/chunks/app/api/plugins/route-2d5f6296c7e693c6.js +0 -1
  350. package/.next/static/chunks/app/api/project-trust/route-2d5f6296c7e693c6.js +0 -1
  351. package/.next/static/chunks/app/api/quota/route-2d5f6296c7e693c6.js +0 -1
  352. package/.next/static/chunks/app/api/search/route-2d5f6296c7e693c6.js +0 -1
  353. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-2d5f6296c7e693c6.js +0 -1
  354. package/.next/static/chunks/app/api/sessions/[id]/context/route-2d5f6296c7e693c6.js +0 -1
  355. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-2d5f6296c7e693c6.js +0 -1
  356. package/.next/static/chunks/app/api/sessions/[id]/export/route-2d5f6296c7e693c6.js +0 -1
  357. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-2d5f6296c7e693c6.js +0 -1
  358. package/.next/static/chunks/app/api/sessions/[id]/route-2d5f6296c7e693c6.js +0 -1
  359. package/.next/static/chunks/app/api/sessions/[id]/state/route-2d5f6296c7e693c6.js +0 -1
  360. package/.next/static/chunks/app/api/sessions/[id]/todos/route-2d5f6296c7e693c6.js +0 -1
  361. package/.next/static/chunks/app/api/sessions/route-2d5f6296c7e693c6.js +0 -1
  362. package/.next/static/chunks/app/api/sessions/summary/route-2d5f6296c7e693c6.js +0 -1
  363. package/.next/static/chunks/app/api/skills/check/route-2d5f6296c7e693c6.js +0 -1
  364. package/.next/static/chunks/app/api/skills/install/route-2d5f6296c7e693c6.js +0 -1
  365. package/.next/static/chunks/app/api/skills/route-2d5f6296c7e693c6.js +0 -1
  366. package/.next/static/chunks/app/api/skills/search/route-2d5f6296c7e693c6.js +0 -1
  367. package/.next/static/chunks/app/api/skills/update/route-2d5f6296c7e693c6.js +0 -1
  368. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-2d5f6296c7e693c6.js +0 -1
  369. package/.next/static/chunks/app/api/task-card-questions/route-2d5f6296c7e693c6.js +0 -1
  370. package/.next/static/chunks/app/api/task-cards/[id]/route-2d5f6296c7e693c6.js +0 -1
  371. package/.next/static/chunks/app/api/task-cards/route-2d5f6296c7e693c6.js +0 -1
  372. package/.next/static/chunks/app/api/task-cards/unbind/route-2d5f6296c7e693c6.js +0 -1
  373. package/.next/static/chunks/app/api/task-scheduler/status/route-2d5f6296c7e693c6.js +0 -1
  374. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-2d5f6296c7e693c6.js +0 -1
  375. package/.next/static/chunks/app/api/tasks/[id]/board/route-2d5f6296c7e693c6.js +0 -1
  376. package/.next/static/chunks/app/api/tasks/[id]/route-2d5f6296c7e693c6.js +0 -1
  377. package/.next/static/chunks/app/api/tasks/reorder/route-2d5f6296c7e693c6.js +0 -1
  378. package/.next/static/chunks/app/api/tasks/route-2d5f6296c7e693c6.js +0 -1
  379. package/.next/static/chunks/app/api/terminal/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  380. package/.next/static/chunks/app/api/terminal/[id]/route-2d5f6296c7e693c6.js +0 -1
  381. package/.next/static/chunks/app/api/terminal/route-2d5f6296c7e693c6.js +0 -1
  382. package/.next/static/chunks/app/api/worktrees/route-2d5f6296c7e693c6.js +0 -1
  383. package/.next/static/chunks/app/api/yjs-version/route-2d5f6296c7e693c6.js +0 -1
  384. package/.next/static/chunks/app/layout-dace09825b2d3857.js +0 -1
  385. package/.next/static/chunks/app/manifest.webmanifest/route-2d5f6296c7e693c6.js +0 -1
  386. package/.next/static/chunks/app/page-64b9f4b3567a2f73.js +0 -89
  387. package/.next/static/chunks/next/dist/client/components/builtin/app-error-2d5f6296c7e693c6.js +0 -1
  388. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-2d5f6296c7e693c6.js +0 -1
  389. package/.next/static/chunks/next/dist/client/components/builtin/not-found-2d5f6296c7e693c6.js +0 -1
  390. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-2d5f6296c7e693c6.js +0 -1
  391. package/.next/static/css/fd25673802257262.css +0 -1
  392. package/lib/extension-command.test.mjs +0 -63
  393. package/lib/extension-command.ts +0 -81
  394. package/lib/subagent-widget.test.mjs +0 -87
  395. package/lib/subagent-widget.ts +0 -109
  396. /package/.next/static/{bsW3fla0-BlBHVMKtAM5I → MPBMmQqmDG8zo8tM_bkDy}/_ssgManifest.js +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import { closeSync, existsSync, openSync, readSync } from "fs";
8
8
  import { readdir } from "fs/promises";
9
- import { join as joinPath, normalize as normalizePath } from "path";
9
+ import { join as joinPath, basename as basenamePath, normalize as normalizePath } from "path";
10
10
  import type { AgentMessage, SessionEntry, SessionHeader, SessionInfo, SessionContext } from "./types";
11
11
  import { parseTodoSnapshot, TODO_STATE_CUSTOM_TYPE, type Todo } from "./todo-store";
12
12
  import type { SessionEntry as PiSessionEntry } from "@earendil-works/pi-coding-agent";
@@ -15,7 +15,7 @@ import { normalizeToolCalls } from "./normalize";
15
15
  import { projectIdentityKey } from "./project-identity";
16
16
  import { sessionPathKey } from "./session-path";
17
17
  import { resolveProject, type ProjectInfo } from "./worktree";
18
- import { scanSessionFileMeta, scanOneSessionFile, scanOneSessionHead, sessionScanner } from "./session-scanner";
18
+ import { scanOneSessionFile } from "./session-scanner";
19
19
  import { ensureSessionIndexReady } from "./session-index-scanner";
20
20
  import type { TurnIndexItem } from "./api-types";
21
21
 
@@ -23,7 +23,13 @@ export { getAgentDir };
23
23
 
24
24
  type SessionMetaRow = Record<string, unknown>;
25
25
 
26
- /** 单个 session_meta 行 → SessionInfo(聊天列表与任务列表共用的唯一映射)。 */
26
+ /** session_meta 行的读取列(列表 / 任务区 / 看板摘要共用一套映射)。 */
27
+ const META_SELECT_COLUMNS =
28
+ "session_id, path, cwd, title, first_message, parent_id, created, modified, pinned, last_reply";
29
+
30
+ /** 单个 session_meta 行 → SessionInfo(聊天列表与任务列表共用的唯一映射)。
31
+ * lastReply 直接取库(v12 列:事件链路写真实回复、扫描器一次性回填存量);
32
+ * NULL(未回填)与 ''(回填过但无回复)在 UI 上都表现为空串。 */
27
33
  function mapSessionMetaRow(r: SessionMetaRow): SessionInfo {
28
34
  return {
29
35
  path: (r.path as string) ?? "",
@@ -34,21 +40,22 @@ function mapSessionMetaRow(r: SessionMetaRow): SessionInfo {
34
40
  modified: new Date((r.modified as number) ?? 0).toISOString(),
35
41
  messageCount: 0,
36
42
  firstMessage: (r.first_message as string | null) ?? "(no messages)",
43
+ lastReply: (r.last_reply as string | null) ?? "",
37
44
  parentSessionId: (r.parent_id as string | null) ?? undefined,
38
45
  pinned: Boolean((r.pinned as number) ?? 0),
39
46
  };
40
47
  }
41
48
 
42
49
  /** 按 id 集合查 session_meta 映射 SessionInfo(返回顺序与入参一致,无行 id 跳过)。
43
- * 任务列表详情与聊天列表 loadProjectSessions 完全同源——标题/首条消息不再依赖
44
- * 读文件(旧 readSessionDetails → scanOneSessionFile),统一由 session_meta 供给。 */
50
+ * 任务列表详情与聊天列表 loadProjectSessions 完全同源——标题/首条消息/最后回复
51
+ * 全部由 session_meta 供给,读取路径不读任何文件。 */
45
52
  export async function loadSessionDetailsFromMeta(ids: string[]): Promise<SessionInfo[]> {
46
53
  if (ids.length === 0) return [];
47
54
  let rows: Array<Record<string, unknown>>;
48
55
  try {
49
56
  rows = getDb()
50
57
  .prepare(
51
- `SELECT session_id, path, cwd, title, first_message, parent_id, created, modified, pinned
58
+ `SELECT ${META_SELECT_COLUMNS}
52
59
  FROM session_meta WHERE session_id IN (${ids.map(() => "?").join(",")})`,
53
60
  )
54
61
  .all(...ids) as Array<Record<string, unknown>>;
@@ -61,42 +68,16 @@ export async function loadSessionDetailsFromMeta(ids: string[]): Promise<Session
61
68
  const row = byId.get(id);
62
69
  if (row) sessions.push(mapSessionMetaRow(row));
63
70
  }
64
- return fillFirstMessageFromFile(sessions);
65
- }
66
-
67
- /**
68
- * lazy 回填:列表读取时对 first_message 为空的会话读文件头补真实首条消息
69
- * (文件是内容事实源),并批量写回 session_meta(后台收敛,下次免读文件)。
70
- * 真·空会话(文件无消息)保持 NULL → 显示 no messages。
71
- * 列表(聊天/任务/全量)共用同一逻辑,保证各处标题一致。
72
- */
73
- function fillFirstMessageFromFile(sessions: SessionInfo[]): SessionInfo[] {
74
- const pending = sessions.filter((s) => s.firstMessage === "(no messages)" && s.path);
75
- if (pending.length === 0) return sessions;
76
- const updateStmt = getDb().prepare("UPDATE session_meta SET first_message = ? WHERE session_id = ?");
77
- return sessions.map((s) => {
78
- if (s.firstMessage !== "(no messages)" || !s.path) return s;
79
- let firstMessage = s.firstMessage;
80
- try {
81
- const head = scanOneSessionHead(s.path);
82
- if (head?.firstMessage) {
83
- firstMessage = head.firstMessage;
84
- updateStmt.run(firstMessage, s.id);
85
- }
86
- } catch {
87
- // 文件不可读(丢失/权限)→ 保持 no messages
88
- }
89
- return firstMessage === s.firstMessage ? s : { ...s, firstMessage };
90
- });
71
+ return sessions;
91
72
  }
92
73
 
93
74
  /**
94
75
  * 当前项目聊天区会话(列表重构 v2 主读取路径)。
95
76
  *
96
77
  * 纯查 session_meta:按 project_key 过滤 + 排除任务会话(task_id 非空交任务区管),
97
- * 置顶优先 + modified 降序。不读文件内容——title 用 meta.title(本应用改名写库),
98
- * 无自定义名回退 first_message;last_reply 不入库也不在此读(侧栏列表不消费,
99
- * 看板卡片走独立摘要轮询)。运行时/未落盘会话由调用方 union getRpcSessionInfos。
78
+ * 置顶优先 + modified 降序。**不读任何文件**(用户决策 4):title 用 meta.title
79
+ * (本应用改名写库),无自定义名回退 first_message,最后一条消息用 last_reply
80
+ * (事件链路写入 + 扫描器一次性回填)。运行时/未落盘会话由调用方 union getRpcSessionInfos。
100
81
  */
101
82
  export async function loadProjectSessions(projectKey: string): Promise<SessionInfo[]> {
102
83
  if (!projectKey) return [];
@@ -105,7 +86,7 @@ export async function loadProjectSessions(projectKey: string): Promise<SessionIn
105
86
  try {
106
87
  rows = getDb()
107
88
  .prepare(
108
- `SELECT session_id, path, cwd, title, first_message, parent_id, created, modified, pinned
89
+ `SELECT ${META_SELECT_COLUMNS}
109
90
  FROM session_meta
110
91
  WHERE project_key = ? AND task_id IS NULL
111
92
  ORDER BY pinned DESC, modified DESC`,
@@ -115,20 +96,18 @@ export async function loadProjectSessions(projectKey: string): Promise<SessionIn
115
96
  return [];
116
97
  }
117
98
 
118
- const sessions = fillFirstMessageFromFile(rows.map(mapSessionMetaRow));
119
- return attachSessionProjectInfo(sessions);
99
+ return attachSessionProjectInfo(rows.map(mapSessionMetaRow));
120
100
  }
121
101
 
122
102
  /** 全项目会话索引读取(无参 /api/sessions:跨项目统计/项目下拉/hydrate/红点清理用)。
123
- * 纯查 session_meta 全表(含任务会话、含各项目),不再整盘扫文件——
124
- * lastReply 消费方已改走摘要点查,侧栏 allSessions 不需要它。 */
103
+ * 纯查 session_meta 全表(含任务会话、含各项目),不整盘扫文件、也不读文件尾。 */
125
104
  export async function loadAllSessionIndex(): Promise<SessionInfo[]> {
126
105
  await ensureSessionIndexReady();
127
106
  let rows: Array<Record<string, unknown>>;
128
107
  try {
129
108
  rows = getDb()
130
109
  .prepare(
131
- `SELECT session_id, path, cwd, title, first_message, parent_id, created, modified, pinned
110
+ `SELECT ${META_SELECT_COLUMNS}
132
111
  FROM session_meta
133
112
  ORDER BY modified DESC`,
134
113
  )
@@ -137,8 +116,7 @@ export async function loadAllSessionIndex(): Promise<SessionInfo[]> {
137
116
  return [];
138
117
  }
139
118
 
140
- const sessions = fillFirstMessageFromFile(rows.map(mapSessionMetaRow));
141
- return attachSessionProjectInfo(sessions);
119
+ return attachSessionProjectInfo(rows.map(mapSessionMetaRow));
142
120
  }
143
121
 
144
122
  export async function attachSessionProjectInfo(sessions: SessionInfo[]): Promise<SessionInfo[]> {
@@ -185,93 +163,68 @@ export function mergeSessionLists(
185
163
  return [...byId.values()].sort((a, b) => b.modified.localeCompare(a.modified));
186
164
  }
187
165
 
188
- async function loadAllSessions(): Promise<SessionInfo[]> {
189
- // 轻量扫描:只读每个会话文件的头部(header + 首条用户消息)与尾部(最新
190
- // session_info 自定义名),最后活动时间用文件 mtime——列表只展示标题+时间,
191
- // 不读取消息正文(SDK 的 listAll 会全量读每个 jsonl 并拼接全部文本,在大会话
192
- // 文件上会把列表刷新拖到秒级甚至十几秒)。
193
- const scanned = await sessionScanner.scan();
194
- const pathToId = new Map<string, string>();
195
- for (const s of scanned) pathToId.set(sessionPathKey(s.path), s.id);
196
-
197
- const sessions = scanned.map((s) => {
198
- cacheSessionPath(s.id, s.path);
199
- return {
200
- path: s.path,
201
- id: s.id,
202
- cwd: s.cwd,
203
- name: s.name,
204
- created: s.created.toISOString(),
205
- modified: s.modified.toISOString(),
206
- // 列表不消费消息数(精确值需要全量读,已移除);保留字段以兼容类型。
207
- messageCount: 0,
208
- firstMessage: s.firstMessage || "(no messages)",
209
- lastReply: s.lastReply || "",
210
- parentSessionId: s.parentSessionPath ? pathToId.get(sessionPathKey(s.parentSessionPath)) : undefined,
211
- transient: false,
212
- };
213
- });
214
- return attachSessionProjectInfo(sessions);
215
- }
216
-
217
166
  /** 任务会话详情按需分页(侧栏任务区)。
218
167
  *
219
- * 服务端分流:任务下的会话详情由 /api/tasks 直接下发,前端不再用
220
- * /api/sessions 全量列表 join task.sessionIds 反查(旧设计已被服务端
221
- * 分流取代——前端零归属判断)。
168
+ * 成员与父子链全部来自 session_meta(库是唯一事实源):任务成员 = `task_id = 该任务`
169
+ * 的全部节点;**根** = `parent_id` 为空或父不在本任务成员集合内的节点。这里不 readdir、
170
+ * 不读 header(旧 buildTaskSessionIndex 每次请求全盘 readdir+读首行)。
222
171
  *
223
- * 每任务返回:置顶根会话全量 + 非置顶根从 offset 起的 limit 个(含各自
224
- * fork 子树),外加 rootTotal(根会话总数,加载更多游标)与
225
- * sessionTotal(含子树全部节点数,删除确认文案用)。
172
+ * 每任务返回:置顶节点全量 + 非置顶根从 offset 起的 limit 个(含各自 fork 子树),
173
+ * 外加 rootTotal(根数,前端「加载更多」游标)与 sessionTotal(任务下全部节点数,
174
+ * 删除确认文案用)。响应字段与分页语义与旧实现一致,前端契约不变。
226
175
  */
227
176
  export async function loadTaskSessionsPage(
228
177
  taskId: string,
229
178
  offset = 0,
230
179
  limit = 5,
231
180
  ): Promise<{ sessions: SessionInfo[]; rootTotal: number; sessionTotal: number; pinnedSessionIds: string[] }> {
232
- return loadTaskSessionsPageWithIndex(taskId, await buildTaskSessionIndex(), offset, limit);
233
- }
234
-
235
- /** 任务会话索引:全量 id+path+mtime(readdir/stat,不读内容)+ 父链(读 header 首行)。
236
- * 一次构建供多个任务复用——/api/tasks 列表对每个任务都调 loadTaskSessionsPage,
237
- * 若各自全量扫文件会随任务数线性变慢(N 任务 = N 次全量 readdir+header)。 */
238
- export async function buildTaskSessionIndex(): Promise<{
239
- metaById: Map<string, { path: string; id: string; modified: Date }>;
240
- childrenOf: Map<string, string[]>;
241
- }> {
242
- // 阶段一:全量 id+path+mtime(readdir+stat,不读内容)。
243
- const metas = await scanSessionFileMeta();
244
- const metaById = new Map(metas.map((m) => [m.id, m]));
245
- const metaByPath = new Map(metas.map((m) => [sessionPathKey(m.path), m]));
246
-
247
- // 父链索引(只读每个文件 header 首行——比 scanOneSessionFile 便宜,
248
- // 不需要尾部反向分块):childId -> parentId(childrenOf: parentId -> [childId])。
181
+ const { listPinnedTaskSessionIds } = await import("./task-store");
182
+ // 冷启动闸门(与同族 loadProjectSessions / loadAllSessionIndex 一致):任务成员来自
183
+ // 库,而库要等首轮扫描才把磁盘会话建行。少了这一句,冷启动时任务区可能读到
184
+ // 「任务在、会话 0」且之后无人重拉(30s 后才自愈)。
185
+ // 闸门抛错(扫描器故障 / 库异常)不能穿透成路由 500:库里有上一轮扫描的行,
186
+ // 照旧查库返回比空页 / 500 都好。这里的闸门只是「尽力补一轮」。
187
+ try {
188
+ await ensureSessionIndexReady();
189
+ } catch (error) {
190
+ // 文案保持中性:这里只知道「索引就绪检查失败」,库好不好是另一回事——库可用时下面
191
+ // 照常返回库内行,库不可用时下面的查库 catch 降级为空页。旧文案「(继续查库)」把
192
+ // 后一种情形说反了,会让排查者以为库没问题。
193
+ console.error(
194
+ "[pi-web] 任务区索引就绪检查失败(接着查库;库不可用则降级为空页):",
195
+ error instanceof Error ? error.message : String(error),
196
+ );
197
+ }
198
+ // 同族读取函数(loadProjectSessions / loadAllSessionIndex)在 db 不可用时返回空,
199
+ // 这里同样降级为空页(路由层不必因一次读不到库整个 /api/tasks 500)。
200
+ // ORDER BY:成员顺序确定,让 modified 相同的根在 slice(offset, offset+limit)
201
+ // 下分页稳定(rowid 升序=建行顺序;下面的 sort 是稳定排序,同键保持此顺序)。
202
+ let rows: Array<{ session_id: string; parent_id: string | null; modified: number | null }>;
203
+ let pinnedSessionIds: string[];
204
+ try {
205
+ rows = getDb()
206
+ .prepare("SELECT session_id, parent_id, modified FROM session_meta WHERE task_id = ? ORDER BY modified DESC, rowid")
207
+ .all(taskId) as Array<{ session_id: string; parent_id: string | null; modified: number | null }>;
208
+ pinnedSessionIds = listPinnedTaskSessionIds(taskId);
209
+ } catch {
210
+ return { sessions: [], rootTotal: 0, sessionTotal: 0, pinnedSessionIds: [] };
211
+ }
212
+
213
+ const memberIds = new Set(rows.map((r) => r.session_id));
214
+ const modifiedOf = new Map<string, number>();
249
215
  const childrenOf = new Map<string, string[]>();
250
- for (const m of metas) {
251
- let parentPath: string | undefined;
252
- try {
253
- parentPath = readSessionHeader(m.path)?.parentSession ?? undefined;
254
- } catch {
255
- // 首行不可读 → 视为根会话
216
+ const roots: string[] = [];
217
+ for (const row of rows) {
218
+ modifiedOf.set(row.session_id, row.modified ?? 0);
219
+ if (row.parent_id && memberIds.has(row.parent_id)) {
220
+ const siblings = childrenOf.get(row.parent_id) ?? [];
221
+ siblings.push(row.session_id);
222
+ childrenOf.set(row.parent_id, siblings);
223
+ } else {
224
+ roots.push(row.session_id);
256
225
  }
257
- if (!parentPath) continue;
258
- const parentMeta = metaByPath.get(sessionPathKey(parentPath));
259
- if (!parentMeta) continue;
260
- const arr = childrenOf.get(parentMeta.id) ?? [];
261
- arr.push(m.id);
262
- childrenOf.set(parentMeta.id, arr);
263
226
  }
264
- return { metaById, childrenOf };
265
- }
266
227
 
267
- /** 加载单个任务会话详情分页(复用外部已构建的任务索引,避免重复全量扫)。 */
268
- export async function loadTaskSessionsPageWithIndex(
269
- taskId: string,
270
- index: { metaById: Map<string, { path: string; id: string; modified: Date }>; childrenOf: Map<string, string[]> },
271
- offset = 0,
272
- limit = 5,
273
- ): Promise<{ sessions: SessionInfo[]; rootTotal: number; sessionTotal: number; pinnedSessionIds: string[] }> {
274
- const { metaById, childrenOf } = index;
275
228
  const collectSubtree = (rootId: string): string[] => {
276
229
  const out = [rootId];
277
230
  const queue = [rootId];
@@ -285,71 +238,24 @@ export async function loadTaskSessionsPageWithIndex(
285
238
  return out;
286
239
  };
287
240
 
288
- const { listTaskSessionIds, listPinnedTaskSessionIds } = await import("./task-store");
289
- const rootIds = listTaskSessionIds(taskId);
290
- const pinnedSessionIds = listPinnedTaskSessionIds(taskId);
241
+ // 排序键:modified(最后活动时间;session_meta.updated 只是归属/置顶时间,
242
+ // 用它切页会把活跃会话切到后页而前端够不着)。
243
+ const byModifiedDesc = (a: string, b: string) => (modifiedOf.get(b) ?? 0) - (modifiedOf.get(a) ?? 0);
291
244
  const pinnedSet = new Set(pinnedSessionIds);
245
+ const nonPinnedRoots = roots.filter((id) => !pinnedSet.has(id)).sort(byModifiedDesc);
292
246
 
293
- // 任务区根会话排序:按文件 mtime(最后写入时间)降序——活跃会话必然
294
- // 最新写入,排最前。session_meta.updated 只是归属/置顶时间,切页用它
295
- // 会把活跃会话切到后页(前端只能重排已加载页,够不着它)。
296
- const modifiedOf = (id: string): number => metaById.get(id)?.modified.getTime() ?? 0;
297
- const byMtimeDesc = (a: string, b: string) => modifiedOf(b) - modifiedOf(a);
298
- const pinnedRoots = [...pinnedSessionIds].sort(byMtimeDesc);
299
- const nonPinnedRoots = rootIds.filter((id) => !pinnedSet.has(id)).sort(byMtimeDesc);
300
-
301
- // 当前页根 = 置顶全量 + 非置顶 slice(offset, offset+limit);子树跟随根。
302
- const pageRootIds = [...pinnedRoots, ...nonPinnedRoots.slice(offset, offset + limit)];
247
+ // 当前页根 = 置顶节点全量(不受 offset 影响)+ 非置顶根 slice(offset, offset+limit);子树跟随根。
248
+ // 排序只作用于页序,返回的 pinnedSessionIds 保持库内原序(与旧实现一致)。
249
+ const pageRootIds = [...[...pinnedSessionIds].sort(byModifiedDesc), ...nonPinnedRoots.slice(offset, offset + limit)];
303
250
  const wantedIds = new Set<string>();
304
251
  for (const rid of pageRootIds) {
305
252
  for (const id of collectSubtree(rid)) wantedIds.add(id);
306
253
  }
307
- // 详情走 session_meta(与聊天列表 loadProjectSessions 同源)——标题/首条消息
308
- // 由索引供给,不再读文件;不存在/未入索引的 id 自然跳过。
254
+ // 详情走 session_meta(与聊天列表 loadProjectSessions 同源)——标题/最后回复由库供给,
255
+ // 不读文件;库内没有行的 id 自然跳过。
309
256
  const sessions = await attachSessionProjectInfo(await loadSessionDetailsFromMeta([...wantedIds]));
310
257
 
311
- // sessionTotal:任务下全部根 + 子树节点数(删除确认文案)。
312
- const allIds = new Set<string>();
313
- for (const rid of rootIds) for (const id of collectSubtree(rid)) allIds.add(id);
314
- return { sessions, rootTotal: rootIds.length, sessionTotal: allIds.size, pinnedSessionIds };
315
- }
316
-
317
- export async function listAllSessions(options: { force?: boolean } = {}): Promise<SessionInfo[]> {
318
- if (options.force) invalidateSessionListCache();
319
- const generation = globalThis.__piSessionListGeneration ?? 0;
320
-
321
- // Return cached result if still fresh (avoids re-scanning session files
322
- // and re-spawning git processes on every page load).
323
- if (globalThis.__piSessionListCache && Date.now() - globalThis.__piSessionListCache.ts < SESSION_LIST_CACHE_TTL_MS) {
324
- return globalThis.__piSessionListCache.data;
325
- }
326
-
327
- // Coalescing dedup: concurrent callers share the same in-flight promise
328
- // only while it belongs to the current cache generation.
329
- if (globalThis.__piSessionListPromise && globalThis.__piSessionListPromiseGeneration === generation) {
330
- return globalThis.__piSessionListPromise;
331
- }
332
-
333
- const loadPromise = loadAllSessions().then((data) => {
334
- // If a mutation invalidated this scan, make this caller join (or start) a
335
- // scan for the current generation. Returning the stale result here made a
336
- // refresh race indistinguishable from a successful refresh.
337
- if ((globalThis.__piSessionListGeneration ?? 0) !== generation) {
338
- return listAllSessions();
339
- }
340
- globalThis.__piSessionListCache = { data, ts: Date.now() };
341
- return data;
342
- });
343
- const trackedPromise = loadPromise.finally(() => {
344
- if (globalThis.__piSessionListPromise === trackedPromise) {
345
- globalThis.__piSessionListPromise = undefined;
346
- globalThis.__piSessionListPromiseGeneration = undefined;
347
- }
348
- });
349
-
350
- globalThis.__piSessionListPromise = trackedPromise;
351
- globalThis.__piSessionListPromiseGeneration = generation;
352
- return trackedPromise;
258
+ return { sessions, rootTotal: roots.length, sessionTotal: rows.length, pinnedSessionIds };
353
259
  }
354
260
 
355
261
  // ============================================================================
@@ -358,17 +264,17 @@ export async function listAllSessions(options: { force?: boolean } = {}): Promis
358
264
  declare global {
359
265
  var __piSessionPathCache: Map<string, string> | undefined;
360
266
  var __piPathToSessionIdCache: Map<string, string> | undefined;
361
- var __piSessionListPromise: Promise<SessionInfo[]> | undefined;
362
- var __piSessionListPromiseGeneration: number | undefined;
363
267
  var __piSessionListGeneration: number | undefined;
364
- var __piSessionListCache: { data: SessionInfo[]; ts: number } | undefined;
365
268
  }
366
269
 
367
- const SESSION_LIST_CACHE_TTL_MS = 300_000;
368
-
270
+ /** 写路径的「数据变了」信号:归属/pin/改名/新建/删除/事件都会调它。
271
+ *
272
+ * 列表已无缓存(读取 = 每次一次单表查询),所以目前**没有生产读者**:这里只保留
273
+ * generation 自增的既有行为(T1–T4b 的写路径与 auto-name / patch-write-failure 的
274
+ * 断言都依赖调用本身)。若要连函数一起删,需要同时改掉全部调用方与那两处断言——
275
+ * 属待清理项,本次不动。 */
369
276
  export function invalidateSessionListCache(): void {
370
277
  globalThis.__piSessionListGeneration = (globalThis.__piSessionListGeneration ?? 0) + 1;
371
- globalThis.__piSessionListCache = undefined;
372
278
  }
373
279
 
374
280
  function getPathCache(): Map<string, string> {
@@ -382,13 +288,20 @@ function getPathToIdCache(): Map<string, string> {
382
288
  }
383
289
 
384
290
  /**
385
- * Find one session's file without parsing the catalogue.
291
+ * Find one session's file by name, as a bounded fallback for sessions that are
292
+ * not in session_meta yet (a session an external tool just created).
386
293
  *
387
294
  * Session files are written as `<timestamp>_<id>.jsonl` under a per-project
388
295
  * directory, so the id can be located by reading directory entries alone. The
389
296
  * header is then parsed — bounded, first line only — to confirm the match
390
- * rather than trusting the name. Returns null when nothing matches, leaving the
391
- * caller on the full scan.
297
+ * rather than trusting the name. Returns null when nothing matches; callers
298
+ * must not fall back to a full catalogue scan (that read path is gone).
299
+ *
300
+ * 决策 4 的代价(说得明白,免后人误以为是 bug):这里只平铺 readdir 每个项目目录的
301
+ * **第一层**,并依赖 `_<id>.jsonl` 命名约定。子目录里的会话(`<session>/forks/…`、
302
+ * `<session>/<runId>/run-0/session.jsonl`)与文件名不合约定的行因此解析不到——它们要等
303
+ * 扫描器把 path 写进 session_meta(≤30s)才能被 resolveSessionPath 命中。换来的是读
304
+ * 取路径不发全量扫盘。
392
305
  *
393
306
  * `sessionId` is only ever compared against names that came back from
394
307
  * `readdir`, never joined into a path itself, so a separator or `..` inside it
@@ -424,7 +337,7 @@ async function findSessionPathByName(sessionId: string): Promise<string | null>
424
337
  try {
425
338
  if (readSessionHeader(candidate)?.id === sessionId) return candidate;
426
339
  } catch {
427
- // Unreadable or truncated: let the full scan decide.
340
+ // 读不出 header(不可读/截断)→ 不认这个候选;不回落全量扫盘(读取路径已无此退路)。
428
341
  }
429
342
  }
430
343
  return null;
@@ -434,18 +347,28 @@ export async function resolveSessionPath(sessionId: string): Promise<string | nu
434
347
  const cached = getPathCache().get(sessionId);
435
348
  if (cached) return cached;
436
349
 
437
- // Opening one session should not wait for the whole catalogue to be parsed.
438
- // The name carries the id, so this costs a directory listing per project plus
439
- // one header read, and only a miss falls through to the full scan.
350
+ // 库是路径的事实源(扫描器已把每个磁盘会话的 path 写入 session_meta):
351
+ // 命中即用,不读目录、不扫盘。
352
+ try {
353
+ const row = getDb()
354
+ .prepare("SELECT path FROM session_meta WHERE session_id = ?")
355
+ .get(sessionId) as { path: string | null } | undefined;
356
+ if (row?.path && existsSync(row.path)) {
357
+ cacheSessionPath(sessionId, row.path);
358
+ return row.path;
359
+ }
360
+ } catch {
361
+ // db 不可用 → 走名字兜底
362
+ }
363
+
364
+ // 库内无行/路径已失效(外部工具刚建的会话)→ 按名字匹配兜底:每项目目录一次
365
+ // readdir + 一次 header 读,不再退化成全量头尾扫。
440
366
  const direct = await findSessionPathByName(sessionId);
441
367
  if (direct) {
442
368
  cacheSessionPath(sessionId, direct);
443
369
  return direct;
444
370
  }
445
-
446
- // Cache miss: scan all sessions to populate cache, then retry
447
- await listAllSessions();
448
- return getPathCache().get(sessionId) ?? null;
371
+ return null;
449
372
  }
450
373
 
451
374
  export async function resolveSessionIdByPath(filePath: string): Promise<string | undefined> {
@@ -453,8 +376,34 @@ export async function resolveSessionIdByPath(filePath: string): Promise<string |
453
376
  const cached = getPathToIdCache().get(pathKey);
454
377
  if (cached) return cached;
455
378
 
456
- await listAllSessions();
457
- return getPathToIdCache().get(pathKey);
379
+ try {
380
+ const db = getDb();
381
+ // 精确命中(绝大多数调用:header.parentSession 与建行时写入的 path 同源)。
382
+ const exact = db
383
+ .prepare("SELECT session_id, path FROM session_meta WHERE path = ?")
384
+ .get(filePath) as { session_id: string; path: string } | undefined;
385
+ if (exact?.path) {
386
+ cacheSessionPath(exact.session_id, exact.path);
387
+ return exact.session_id;
388
+ }
389
+ // 归一化差异兜底(`/a/./b`、Windows 大小写):按文件名后缀粗筛,再用
390
+ // sessionPathKey 精确比较。`_` 在 LIKE 里是单字符通配符 → 只会多筛不会漏筛。
391
+ const base = basenamePath(filePath);
392
+ if (base) {
393
+ const candidates = db
394
+ .prepare("SELECT session_id, path FROM session_meta WHERE path LIKE ?")
395
+ .all(`%${base}`) as Array<{ session_id: string; path: string | null }>;
396
+ for (const row of candidates) {
397
+ if (row.path && sessionPathKey(row.path) === pathKey) {
398
+ cacheSessionPath(row.session_id, row.path);
399
+ return row.session_id;
400
+ }
401
+ }
402
+ }
403
+ } catch {
404
+ // db 不可用 → 查不到(读取路径不再扫盘兜底)
405
+ }
406
+ return undefined;
458
407
  }
459
408
 
460
409
  export function cacheSessionPath(sessionId: string, filePath: string): void {
@@ -848,49 +797,64 @@ export function extractTurnIndex(entries: SessionEntry[], leafId: string | null)
848
797
  /**
849
798
  * 按 id 批量点查会话摘要(看板卡片轮询用,替代全量列表自筛)。
850
799
  *
851
- * 画布上有几张会话卡就查几个 id——先查 session_meta 拿 path(扫描器已全量
852
- * 建索引,避免 resolveSessionPath 对 miss 触发全量扫盘),再 scanOneSessionFile
853
- * 读头尾(name=自定义名/firstMessage/lastReply/mtime),最后 attachSessionProjectInfo
854
- * 补 projectRoot/branch 等 UI 字段。查不到(id 不存在)跳过。
800
+ * 库优先(用户决策 4):title / last_reply / modified 全部取 session_meta,
801
+ * 一次点查不读任何文件(旧实现逐卡 scanOneSessionFile 读头尾)。只有
802
+ * **库里完全没有行**的 id(外部工具刚建、尚未入索引的会话)才回退到
803
+ * resolveSessionPath + 单文件详情读。查不到(id 不存在)跳过。
855
804
  */
856
805
  export async function loadSessionSummariesByIds(ids: string[]): Promise<SessionInfo[]> {
857
806
  const unique = [...new Set(ids.filter(Boolean))];
858
807
  if (unique.length === 0) return [];
859
808
 
860
- // meta 索引拿 path + title(扫描器维护;未索引的新会话走 resolveSessionPath 兜底)
861
- // title 优先用 meta(与列表同源:改名写 session_meta.title),文件尾 session_info 兜底——
862
- // 避免改名后继续聊超 tail 上限(16MB)时看板卡片标题与侧栏不一致。
863
- const rows = new Map<string, { path: string | null; title: string | null }>();
809
+ const rowById = new Map<string, SessionMetaRow>();
864
810
  try {
865
811
  const found = getDb()
866
- .prepare("SELECT session_id, path, title FROM session_meta WHERE session_id IN (" + unique.map(() => "?").join(",") + ")")
867
- .all(...unique) as Array<{ session_id: string; path: string | null; title: string | null }>;
868
- for (const r of found) rows.set(r.session_id, { path: r.path, title: r.title });
812
+ .prepare(`SELECT ${META_SELECT_COLUMNS} FROM session_meta WHERE session_id IN (${unique.map(() => "?").join(",")})`)
813
+ .all(...unique) as Array<Record<string, unknown>>;
814
+ for (const r of found) rowById.set(r.session_id as string, r);
869
815
  } catch {
870
- // db 不可用 → 全部走 resolveSessionPath 兜底
816
+ // db 不可用 → 全部走文件兜底
871
817
  }
872
818
 
873
- const sessions: SessionInfo[] = [];
819
+ const byId = new Map<string, SessionInfo>();
820
+ const missing: string[] = [];
874
821
  for (const id of unique) {
875
- const metaRow = rows.get(id);
876
- let filePath = metaRow?.path ?? null;
877
- if (!filePath) filePath = await resolveSessionPath(id);
822
+ const row = rowById.get(id);
823
+ if (!row) {
824
+ missing.push(id);
825
+ continue;
826
+ }
827
+ if (row.path) cacheSessionPath(id, row.path as string);
828
+ byId.set(id, mapSessionMetaRow(row));
829
+ }
830
+
831
+ // 兜底:库内无行的 id(外部工具刚建的会话)—— 一次路径解析 + 文件头尾读。
832
+ for (const id of missing) {
833
+ const filePath = await resolveSessionPath(id);
878
834
  if (!filePath) continue;
879
835
  const scanned = scanOneSessionFile(filePath);
880
836
  if (!scanned) continue;
881
837
  cacheSessionPath(id, scanned.path);
882
- sessions.push({
838
+ // parentSessionId 与主路径同字段(会话 id,不是路径):磁盘上只有父路径,
839
+ // 经库反查一次;父也还没行就留 undefined(与主路径的 NULL 行为一致)。
840
+ const parentSessionId = scanned.parentSessionPath
841
+ ? await resolveSessionIdByPath(scanned.parentSessionPath)
842
+ : undefined;
843
+ byId.set(id, {
883
844
  path: scanned.path,
884
845
  id: scanned.id,
885
846
  cwd: scanned.cwd,
886
- name: metaRow?.title ?? scanned.name,
847
+ name: scanned.name,
887
848
  created: scanned.created.toISOString(),
888
849
  modified: scanned.modified.toISOString(),
889
850
  messageCount: 0,
890
851
  firstMessage: scanned.firstMessage || "(no messages)",
891
852
  lastReply: scanned.lastReply || "",
853
+ parentSessionId,
892
854
  transient: false,
893
855
  });
894
856
  }
895
- return attachSessionProjectInfo(sessions);
857
+
858
+ // 返回顺序与入参一致(命中集)。
859
+ return attachSessionProjectInfo(unique.map((id) => byId.get(id)).filter((s): s is SessionInfo => Boolean(s)));
896
860
  }