@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
@@ -0,0 +1,85 @@
1
+ "use client";
2
+
3
+ import { useSyncExternalStore } from "react";
4
+
5
+ export interface PinnedMessageItem {
6
+ id: string;
7
+ /** 快照内容(markdown 文本)——钉卡只渲染内容,与消息气泡/会话解耦 */
8
+ content: string;
9
+ x: number;
10
+ y: number;
11
+ w: number;
12
+ /** 高度:undefined = 初始随内容自适应;缩放/resize 后变为固定值 */
13
+ h?: number;
14
+ }
15
+
16
+ type Listener = () => void;
17
+
18
+ /**
19
+ * 全局钉 store:pins 存活在模块级(与任何会话组件无关)。
20
+ * 跨会话/看板切换保留;刷新页面不持久化(内存态,符合设计)。
21
+ */
22
+ let pins: PinnedMessageItem[] = [];
23
+ const listeners = new Set<Listener>();
24
+
25
+ function emit() {
26
+ for (const l of listeners) l();
27
+ }
28
+
29
+ function subscribe(listener: Listener) {
30
+ listeners.add(listener);
31
+ return () => {
32
+ listeners.delete(listener);
33
+ };
34
+ }
35
+
36
+ function getSnapshot() {
37
+ return pins;
38
+ }
39
+
40
+ let pinCount = 0;
41
+
42
+ /** 钉住内容:卡片出现在鼠标下方,多张级联偏移,clamp 视口内 */
43
+ export function pin(content: string, clientX: number, clientY: number) {
44
+ const vw = window.innerWidth;
45
+ const vh = window.innerHeight;
46
+ const w = Math.min(340, Math.max(240, vw - 40));
47
+ const offset = (pinCount % 8) * 22;
48
+ pinCount += 1;
49
+ const item: PinnedMessageItem = {
50
+ id: `pin-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
51
+ content,
52
+ x: Math.min(Math.max(0, clientX + offset), Math.max(0, vw - w - 4)),
53
+ y: Math.min(Math.max(0, clientY + 14 + offset), Math.max(0, vh - 48)),
54
+ w,
55
+ };
56
+ pins = [...pins, item];
57
+ emit();
58
+ }
59
+
60
+ export function closePin(id: string) {
61
+ pins = pins.filter((p) => p.id !== id);
62
+ emit();
63
+ }
64
+
65
+ export function movePin(id: string, patch: Partial<Pick<PinnedMessageItem, "x" | "y" | "w" | "h">>) {
66
+ pins = pins.map((p) => (p.id === id ? { ...p, ...patch } : p));
67
+ emit();
68
+ }
69
+
70
+ /** 激活置顶:把卡片移到数组末尾(zIndex 最高) */
71
+ export function activatePin(id: string) {
72
+ if (pins.length <= 1) return;
73
+ const idx = pins.findIndex((p) => p.id === id);
74
+ if (idx === -1 || idx === pins.length - 1) return;
75
+ const next = [...pins];
76
+ const [item] = next.splice(idx, 1);
77
+ next.push(item);
78
+ pins = next;
79
+ emit();
80
+ }
81
+
82
+ /** 订阅 pins 快照(组件用) */
83
+ export function usePins(): PinnedMessageItem[] {
84
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
85
+ }
@@ -0,0 +1,106 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+ import lockfile from "proper-lockfile";
5
+
6
+ const DEFAULT_TOOLS = ["read", "bash", "edit", "write"];
7
+ const SHELL_TOOLS = new Set(["bash", "powershell"]);
8
+
9
+ function isRecord(value: unknown): value is Record<string, unknown> {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+
13
+ export function isPowerShellToolEnabled(
14
+ defaultTools: readonly string[] | undefined,
15
+ platform: NodeJS.Platform = process.platform,
16
+ ): boolean {
17
+ return platform === "win32"
18
+ && defaultTools?.includes("powershell") === true
19
+ && !defaultTools.includes("bash");
20
+ }
21
+
22
+ export function replaceShellTool(
23
+ toolNames: readonly string[],
24
+ usePowerShell: boolean,
25
+ ): string[] {
26
+ const shell = usePowerShell ? "powershell" : "bash";
27
+ const result: string[] = [];
28
+ for (const name of toolNames) {
29
+ const next = SHELL_TOOLS.has(name) ? shell : name;
30
+ if (!result.includes(next)) result.push(next);
31
+ }
32
+ return result;
33
+ }
34
+
35
+ export function resolveShellTools(
36
+ toolNames: readonly string[],
37
+ defaultTools: readonly string[] | undefined,
38
+ platform: NodeJS.Platform = process.platform,
39
+ ): string[] {
40
+ return replaceShellTool(toolNames, isPowerShellToolEnabled(defaultTools, platform));
41
+ }
42
+
43
+ export function getPowerShellSettingsPath(agentDir = getAgentDir()): string {
44
+ return join(agentDir, "settings.json");
45
+ }
46
+
47
+ function parseSettings(path: string): Record<string, unknown> {
48
+ if (!existsSync(path)) return {};
49
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
50
+ if (!isRecord(parsed)) throw new Error("Invalid settings.json: expected an object");
51
+ return parsed;
52
+ }
53
+
54
+ function configuredTools(settings: Record<string, unknown>): string[] | undefined {
55
+ if (settings.defaultTools === undefined) return undefined;
56
+ if (
57
+ !Array.isArray(settings.defaultTools)
58
+ || settings.defaultTools.some((name) => typeof name !== "string")
59
+ ) {
60
+ throw new Error("Invalid settings.json: defaultTools must be an array of strings");
61
+ }
62
+ return settings.defaultTools as string[];
63
+ }
64
+
65
+ export async function readPowerShellToolEnabled(
66
+ settingsPath = getPowerShellSettingsPath(),
67
+ platform: NodeJS.Platform = process.platform,
68
+ ): Promise<boolean> {
69
+ if (!existsSync(settingsPath)) return false;
70
+ const release = await lockfile.lock(settingsPath, { realpath: false, retries: 10 });
71
+ try {
72
+ return isPowerShellToolEnabled(configuredTools(parseSettings(settingsPath)), platform);
73
+ } finally {
74
+ await release();
75
+ }
76
+ }
77
+
78
+ export async function writePowerShellToolEnabled(
79
+ enabled: boolean,
80
+ settingsPath = getPowerShellSettingsPath(),
81
+ platform: NodeJS.Platform = process.platform,
82
+ ): Promise<boolean> {
83
+ if (platform !== "win32") throw new Error("PowerShell tool settings are only available on Windows");
84
+
85
+ mkdirSync(dirname(settingsPath), { recursive: true });
86
+ try {
87
+ writeFileSync(settingsPath, "{}", { flag: "wx", mode: 0o600 });
88
+ } catch (error) {
89
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
90
+ }
91
+ const release = await lockfile.lock(settingsPath, { realpath: false, retries: 10 });
92
+ try {
93
+ const settings = parseSettings(settingsPath);
94
+ const currentTools = configuredTools(settings) ?? DEFAULT_TOOLS;
95
+ const nextTools = replaceShellTool(currentTools, enabled);
96
+ if (!currentTools.some((name) => SHELL_TOOLS.has(name))) {
97
+ nextTools.push(enabled ? "powershell" : "bash");
98
+ }
99
+ settings.defaultTools = nextTools;
100
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
101
+ chmodSync(settingsPath, 0o600);
102
+ } finally {
103
+ await release();
104
+ }
105
+ return enabled;
106
+ }
@@ -0,0 +1,4 @@
1
+ export function buildQuotedSelection(text: string, intro: string, question: string): string {
2
+ const quote = text.trim().split("\n").map((line) => `> ${line}`).join("\n");
3
+ return `${intro}\n\n${quote}\n\n${question}`;
4
+ }
@@ -0,0 +1,223 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { createJiti } from "jiti";
5
+
6
+ const jiti = createJiti(import.meta.url, {
7
+ interopDefault: true,
8
+ moduleCache: false,
9
+ alias: { "@": process.cwd() },
10
+ });
11
+ const { setDbForTesting } = await jiti.import("./sqlite-db.ts");
12
+ const { AgentSessionWrapper } = await jiti.import("./rpc-manager.ts");
13
+
14
+ /** 事件链路的行为测试:真 wrapper + 真库(:memory:)+ 假 SDK 会话。 */
15
+ function freshDb() {
16
+ const db = new DatabaseSync(":memory:");
17
+ setDbForTesting(db);
18
+ return db;
19
+ }
20
+
21
+ function seedSession(db, sessionId) {
22
+ db.prepare(
23
+ `INSERT INTO session_meta
24
+ (session_id, task_id, updated, pinned, path, cwd, project_key, title, first_message, parent_id, created, modified, last_reply)
25
+ VALUES (?, NULL, 111, 0, ?, '/p', 'proj', NULL, NULL, NULL, 100, 200, NULL)`,
26
+ ).run(sessionId, `/s/${sessionId}.jsonl`);
27
+ }
28
+
29
+ const rowOf = (db, sessionId) =>
30
+ db.prepare("SELECT * FROM session_meta WHERE session_id = ?").get(sessionId);
31
+
32
+ const assistant = (text, ts, stopReason = "stop") => ({
33
+ type: "message_end",
34
+ message: { role: "assistant", timestamp: ts, stopReason, content: [{ type: "text", text }] },
35
+ });
36
+
37
+ /** 假 AgentSessionLike:只实现事件订阅 + wrapper 读到的几处。 */
38
+ function makeWrapper(sessionId, entries = []) {
39
+ let emit = null;
40
+ const inner = {
41
+ sessionId,
42
+ isBashRunning: false,
43
+ isStreaming: false,
44
+ isCompacting: false,
45
+ extensionRunner: {},
46
+ sessionManager: { getCwd: () => "/tmp", getEntries: () => entries },
47
+ agent: { state: {} },
48
+ subscribe(listener) {
49
+ emit = listener;
50
+ return () => {
51
+ emit = null;
52
+ };
53
+ },
54
+ dispose() {},
55
+ };
56
+ const wrapper = new AgentSessionWrapper(inner);
57
+ wrapper.start();
58
+ return { wrapper, fire: (event) => emit(event) };
59
+ }
60
+
61
+ const userEntry = (text) => ({ type: "message", message: { role: "user", content: [{ type: "text", text }] } });
62
+
63
+ test("agent_start → 刷 modified(活跃时间立刻前移),不碰 last_reply", (t) => {
64
+ const db = freshDb();
65
+ seedSession(db, "sess-live");
66
+ const { wrapper, fire } = makeWrapper("sess-live");
67
+ t.after(() => wrapper.destroy());
68
+
69
+ const before = Date.now();
70
+ fire({ type: "agent_start" });
71
+
72
+ const row = rowOf(db, "sess-live");
73
+ assert.ok(row.modified >= before, "agent_start 立刻把活跃时间前移到当前");
74
+ assert.equal(row.last_reply, null);
75
+ assert.equal(row.updated, 111);
76
+ assert.ok(wrapper.lastAgentStartAt() >= before, "既有 agent_start 逻辑(缓存起始时间)保持原样");
77
+ });
78
+
79
+ test("一轮循环结束:message_end 缓存、agent_settled 落 last_reply + modified", (t) => {
80
+ const db = freshDb();
81
+ seedSession(db, "sess-live");
82
+ const { wrapper, fire } = makeWrapper("sess-live");
83
+ t.after(() => wrapper.destroy());
84
+
85
+ fire({ type: "agent_start" });
86
+ fire(assistant("中间回复", 1000));
87
+ assert.equal(rowOf(db, "sess-live").last_reply, null, "单条 message_end 不落库(不是循环最后一条)");
88
+ fire(assistant("最后一条回复", 2000));
89
+ fire({ type: "agent_settled" });
90
+
91
+ const row = rowOf(db, "sess-live");
92
+ assert.equal(row.last_reply, "最后一条回复");
93
+ assert.equal(row.modified, 2000);
94
+ });
95
+
96
+ test("用户取消(stopReason=aborted)走同一条路径落库", (t) => {
97
+ const db = freshDb();
98
+ seedSession(db, "sess-live");
99
+ const { wrapper, fire } = makeWrapper("sess-live");
100
+ t.after(() => wrapper.destroy());
101
+
102
+ fire(assistant("被打断的半截回复", 3000, "aborted"));
103
+ fire({ type: "agent_settled" });
104
+
105
+ assert.equal(rowOf(db, "sess-live").last_reply, "被打断的半截回复");
106
+ });
107
+
108
+ test("本轮没采到文本 → 保留库里的 last_reply,只刷 modified", (t) => {
109
+ const db = freshDb();
110
+ seedSession(db, "sess-live");
111
+ db.prepare("UPDATE session_meta SET last_reply = '上一条回复' WHERE session_id = 'sess-live'").run();
112
+ const { wrapper, fire } = makeWrapper("sess-live");
113
+ t.after(() => wrapper.destroy());
114
+
115
+ fire({ type: "agent_start" });
116
+ fire({ type: "agent_settled" });
117
+
118
+ const row = rowOf(db, "sess-live");
119
+ assert.equal(row.last_reply, "上一条回复", "空文本不清库");
120
+ assert.ok(row.modified > 200);
121
+ });
122
+
123
+ test("首条用户消息在落库时回填(内存 entries,不读文件),只补一次", (t) => {
124
+ const db = freshDb();
125
+ seedSession(db, "sess-live");
126
+ const entries = [userEntry("帮我写一个 e2e 测试"), { type: "message", message: { role: "assistant", content: [] } }];
127
+ const { wrapper, fire } = makeWrapper("sess-live", entries);
128
+ t.after(() => wrapper.destroy());
129
+
130
+ fire(assistant("好的", 1000));
131
+ fire({ type: "agent_settled" });
132
+ assert.equal(rowOf(db, "sess-live").first_message, "帮我写一个 e2e 测试");
133
+
134
+ entries[0] = userEntry("换了个说法");
135
+ fire(assistant("第二次回复", 2000));
136
+ fire({ type: "agent_settled" });
137
+ assert.equal(rowOf(db, "sess-live").first_message, "帮我写一个 e2e 测试", "首条消息不被后续覆盖");
138
+ });
139
+
140
+ test("首条用户消息截断到 300 字符(与扫描器同一上限)", (t) => {
141
+ const db = freshDb();
142
+ seedSession(db, "sess-live");
143
+ const long = "长".repeat(500);
144
+ const { wrapper, fire } = makeWrapper("sess-live", [userEntry(long)]);
145
+ t.after(() => wrapper.destroy());
146
+
147
+ fire(assistant("回复", 1000));
148
+ fire({ type: "agent_settled" });
149
+
150
+ assert.equal(rowOf(db, "sess-live").first_message.length, 300);
151
+ });
152
+
153
+ test("并发会话各自记账,事件不串台", (t) => {
154
+ const db = freshDb();
155
+ seedSession(db, "sess-a");
156
+ seedSession(db, "sess-b");
157
+ const a = makeWrapper("sess-a");
158
+ const b = makeWrapper("sess-b");
159
+ t.after(() => {
160
+ a.wrapper.destroy();
161
+ b.wrapper.destroy();
162
+ });
163
+
164
+ a.fire(assistant("A 的回复", 1000));
165
+ b.fire(assistant("B 的回复", 2000));
166
+ b.fire({ type: "agent_settled" });
167
+ a.fire({ type: "agent_settled" });
168
+
169
+ assert.deepEqual(
170
+ [rowOf(db, "sess-a").last_reply, rowOf(db, "sess-b").last_reply],
171
+ ["A 的回复", "B 的回复"],
172
+ );
173
+ });
174
+
175
+ test("库里没有行时事件写库静默 no-op,且不影响既有事件流", (t) => {
176
+ const db = freshDb();
177
+ const { wrapper, fire } = makeWrapper("sess-unknown");
178
+ t.after(() => wrapper.destroy());
179
+ const events = [];
180
+ wrapper.onEvent((event) => events.push(event.type));
181
+
182
+ fire({ type: "agent_start" });
183
+ fire(assistant("回复", 1000));
184
+ fire({ type: "agent_settled" });
185
+
186
+ assert.equal(db.prepare("SELECT COUNT(*) AS c FROM session_meta").get().c, 0, "事件不建行");
187
+ assert.deepEqual(events, ["agent_start", "message_end", "agent_settled"]);
188
+ });
189
+
190
+ test("写库失败必须可见且不打断既有事件流", (t) => {
191
+ const db = freshDb();
192
+ seedSession(db, "sess-live");
193
+ const { wrapper, fire } = makeWrapper("sess-live");
194
+ t.after(() => {
195
+ wrapper.destroy();
196
+ setDbForTesting(db);
197
+ });
198
+ const errors = [];
199
+ const original = console.error;
200
+ console.error = (...args) => errors.push(args.map(String).join(" "));
201
+ t.after(() => {
202
+ console.error = original;
203
+ });
204
+ const events = [];
205
+ wrapper.onEvent((event) => events.push(event.type));
206
+
207
+ // 让库写入抛错:stub 掉全局库句柄(真库仍在 db 变量里,未被触碰)
208
+ globalThis.__piWebDb = {
209
+ prepare() {
210
+ throw new Error("database is locked");
211
+ },
212
+ };
213
+
214
+ fire({ type: "agent_start" });
215
+ fire(assistant("回复", 1000));
216
+ fire({ type: "agent_settled" });
217
+
218
+ assert.equal(errors.length, 2, "agent_start / agent_settled 各报一次写库失败");
219
+ assert.match(errors.join("\n"), /会话活跃写库失败/);
220
+ assert.match(errors.join("\n"), /database is locked/);
221
+ assert.deepEqual(events, ["agent_start", "message_end", "agent_settled"], "写库失败不打断事件流");
222
+ assert.ok(wrapper.lastAgentStartAt() > 0, "既有回调逻辑(agent_start 缓存)不受影响");
223
+ });
@@ -137,3 +137,25 @@ test("reloading a session invalidates the models cache", async () => {
137
137
  assert.match(reloadSource, /await this\.inner\.reload\(\)/);
138
138
  assert.match(reloadSource, /this\.applyForcedEmptySystemPrompt\(\);\s*invalidateModelsCache\(\)/);
139
139
  });
140
+
141
+ test("fork 建行失败不再静默(可见报错,不依赖扫描器兜底)", async () => {
142
+ const source = await readFile(new URL("./rpc-manager.ts", import.meta.url), "utf8");
143
+ const forkSource = source.slice(source.indexOf('case "fork"'), source.indexOf('case "navigate_tree"'));
144
+
145
+ assert.match(forkSource, /console\.error\(\s*`\[pi-web\] fork 建行失败 session=\$\{newSessionId\}/);
146
+ // 入板失败单独一条日志:不能被误报成「建行失败」。
147
+ assert.match(forkSource, /console\.error\(\s*`\[pi-web\] fork 入板失败 source=\$\{sourceSessionId\}/);
148
+ // 静默 catch 会让子会话以「无归属临时会话」出现在聊天区(V1 病灶):fork 分支里不得再有裸 catch。
149
+ assert.doesNotMatch(forkSource, /catch \{/);
150
+ });
151
+
152
+ test("新会话建行失败也不再静默(失败可见,不依赖扫描器兜底)", async () => {
153
+ const source = await readFile(new URL("./rpc-manager.ts", import.meta.url), "utf8");
154
+ const persistSource = source.slice(
155
+ source.indexOf("async function persistNewSessionFile"),
156
+ source.indexOf("export function getRpcSession"),
157
+ );
158
+
159
+ assert.match(persistSource, /console\.error\(\s*`\[pi-web\] 新会话建行失败 session=\$\{sessionId\}/);
160
+ assert.doesNotMatch(persistSource, /catch \{/);
161
+ });
@@ -0,0 +1,152 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import test from "node:test";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { createJiti } from "jiti";
6
+
7
+ const jiti = createJiti(import.meta.url, {
8
+ interopDefault: true,
9
+ moduleCache: false,
10
+ alias: { "@": process.cwd() },
11
+ });
12
+ const { setDbForTesting } = await jiti.import("./sqlite-db.ts");
13
+ const { AgentSessionWrapper } = await jiti.import("./rpc-manager.ts");
14
+
15
+ /** set_session_name 命令(前端改名/看板卡改名)必须与路由层同语义落库:
16
+ * pi 侧成功 → 同一次 send 内写 session_meta.title → 缓存失效;
17
+ * 写库失败 console.error 可见,但不抛断命令响应(RPC 已改文件名,抛错无可恢复动作)。
18
+ * 真 wrapper + 真库(:memory:)+ 假 SDK 会话(参照 rpc-manager.activity.test.mjs)。 */
19
+
20
+ function freshDb() {
21
+ const db = new DatabaseSync(":memory:");
22
+ setDbForTesting(db);
23
+ return db;
24
+ }
25
+
26
+ /** 全列行(title 为 NULL:模拟还没改过名的老会话)。 */
27
+ function seedSession(db, sessionId) {
28
+ db.prepare(
29
+ `INSERT INTO session_meta
30
+ (session_id, task_id, updated, pinned, path, cwd, project_key, title, first_message, parent_id, created, modified, last_reply)
31
+ VALUES (?, NULL, 111, 0, ?, '/p', 'proj', NULL, NULL, NULL, 100, 200, NULL)`,
32
+ ).run(sessionId, `/s/${sessionId}.jsonl`);
33
+ }
34
+
35
+ const rowOf = (db, sessionId) =>
36
+ db.prepare("SELECT * FROM session_meta WHERE session_id = ?").get(sessionId);
37
+
38
+ /** 取 `case "<name>"` 到下一个 case 之间的源码片段。
39
+ * 右边界用「下一个 case」的形状(不依赖某个具体 case 名存在)——别人把
40
+ * get_session_stats 改名/删除也不会让切片静默退化成整文件;找不到边界直接报错。 */
41
+ function caseSourceOf(source, name) {
42
+ const marker = `case "${name}"`;
43
+ const start = source.indexOf(marker);
44
+ assert.ok(start >= 0, `找不到 ${marker}`);
45
+ const rest = source.slice(start + marker.length);
46
+ const next = rest.slice(1).search(/\n case "/);
47
+ assert.ok(next >= 0, `${marker} 之后找不到下一个 case(切片无法闭合)`);
48
+ return rest.slice(0, next + 1);
49
+ }
50
+
51
+ /** 假 AgentSessionLike:只实现 wrapper 用到的几处 + 记录 pi 侧改名调用。 */
52
+ function makeWrapper(sessionId) {
53
+ const nameCalls = [];
54
+ const inner = {
55
+ sessionId,
56
+ isBashRunning: false,
57
+ isStreaming: false,
58
+ isCompacting: false,
59
+ extensionRunner: {},
60
+ sessionManager: { getCwd: () => "/tmp", getEntries: () => [] },
61
+ agent: { state: {} },
62
+ setSessionName(name) {
63
+ nameCalls.push(name);
64
+ },
65
+ subscribe() {
66
+ return () => {};
67
+ },
68
+ dispose() {},
69
+ };
70
+ const wrapper = new AgentSessionWrapper(inner);
71
+ wrapper.start();
72
+ return { wrapper, nameCalls };
73
+ }
74
+
75
+ /** 源码断言:命令分支确实接了库写,且失败处理形状正确。 */
76
+ test("set_session_name 分支接线:pi 成功 → await setSessionTitle → 失败只记日志不抛", async () => {
77
+ const source = await readFile(new URL("./rpc-manager.ts", import.meta.url), "utf8");
78
+ const caseSource = caseSourceOf(source, "set_session_name");
79
+
80
+ assert.match(source, /import \{[^}]*\bsetSessionTitle\b[^}]*\} from "\.\/task-store";/s);
81
+ assert.ok(caseSource.includes("await setSessionTitle("), "库写必须 await(命令响应返回时库已落地)");
82
+ assert.match(caseSource, /setSessionTitle\(this\.inner\.sessionId, name\)/);
83
+ assert.ok(
84
+ caseSource.indexOf("this.inner.setSessionName(name)") < caseSource.indexOf("await setSessionTitle("),
85
+ "先 pi 侧改名成功、再写库(pi 失败时不该写库)",
86
+ );
87
+ assert.match(caseSource, /catch \(error\) \{[\s\S]*?console\.error\([\s\S]*?会话标题写库失败/);
88
+ assert.doesNotMatch(caseSource, /catch[\s\S]{0,400}?\bthrow\b/, "写库失败不得抛断命令响应/事件流");
89
+ assert.match(caseSource, /invalidateSessionListCache\(\);/);
90
+ assert.match(caseSource, /return null;/);
91
+ });
92
+
93
+ test("改名成功:同一次 send 内把 title 写库(不依赖扫描器/懒更新)", async (t) => {
94
+ const db = freshDb();
95
+ seedSession(db, "sess-name");
96
+ const { wrapper, nameCalls } = makeWrapper("sess-name");
97
+ t.after(() => wrapper.destroy());
98
+
99
+ const before = Date.now();
100
+ const result = await wrapper.send({ type: "set_session_name", name: " 新标题 " });
101
+
102
+ assert.deepEqual(nameCalls, ["新标题"], "pi 侧收到 trim 后的名字(校验行为不变)");
103
+ assert.equal(result, null, "返回值保持原样");
104
+ const row = rowOf(db, "sess-name");
105
+ assert.equal(row.title, "新标题", "刷新侧栏/看板卡不再退回旧标题");
106
+ assert.ok(row.updated >= before, "写库顺带前移 updated");
107
+ assert.equal(row.task_id, null, "改名不碰归属");
108
+ assert.equal(row.path, "/s/sess-name.jsonl", "改名不碰索引列(只 UPDATE title/updated)");
109
+ });
110
+
111
+ test("写库失败:console.error 可见、不抛(命令响应与事件流都不被打断)", async (t) => {
112
+ const db = freshDb();
113
+ seedSession(db, "sess-name");
114
+ const { wrapper, nameCalls } = makeWrapper("sess-name");
115
+ const errors = [];
116
+ const original = console.error;
117
+ t.after(() => {
118
+ wrapper.destroy();
119
+ console.error = original;
120
+ globalThis.__piWebDb = db;
121
+ });
122
+ console.error = (...args) => errors.push(args.map(String).join(" "));
123
+
124
+ // 让库写入抛错:stub 掉全局库句柄(真库仍在 db 变量里,未被触碰)
125
+ globalThis.__piWebDb = {
126
+ prepare() {
127
+ throw new Error("database is locked");
128
+ },
129
+ };
130
+
131
+ const result = await wrapper.send({ type: "set_session_name", name: "新标题" });
132
+
133
+ assert.equal(result, null, "RPC 命令照常返回成功响应(与路由层 500 语义不同:这里无可恢复动作)");
134
+ assert.deepEqual(nameCalls, ["新标题"], "pi 侧改名已生效");
135
+ assert.equal(errors.length, 1, "写库失败必须可见");
136
+ assert.match(errors[0], /\[pi-web\] 会话标题写库失败/);
137
+ assert.match(errors[0], /database is locked/);
138
+ });
139
+
140
+ test("空名字仍然报错(既有校验不变),不碰 pi 也不碰库", async (t) => {
141
+ const db = freshDb();
142
+ seedSession(db, "sess-name");
143
+ const { wrapper, nameCalls } = makeWrapper("sess-name");
144
+ t.after(() => wrapper.destroy());
145
+
146
+ await assert.rejects(
147
+ () => wrapper.send({ type: "set_session_name", name: " " }),
148
+ /Session name cannot be empty/,
149
+ );
150
+ assert.deepEqual(nameCalls, []);
151
+ assert.equal(rowOf(db, "sess-name").title, null);
152
+ });