@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
@@ -13,10 +13,26 @@ import {
13
13
  preferUserBashExtension,
14
14
  } from "./project-command-env";
15
15
  import { createTodoExtension } from "./todo-extension";
16
- import { cacheSessionPath, invalidateSessionListCache } from "./session-reader";
16
+ import { createSubagentExtension, preferPiWebSubagentExtension } from "./subagent-extension";
17
+ import { listSubagentProfiles, readSubagentRun, readSubagentSessionResources, SUBAGENT_CONTROL_TOOL_NAMES } from "./subagents";
18
+ import { createSubagentController } from "./subagent-runtime";
19
+ import { isBuiltInSubagentsEnabled } from "./subagent-settings";
20
+ import { resolveShellTools } from "./powershell-settings";
21
+ import { CHAT_ONLY_RESOURCE_LOADER_OPTIONS } from "./chat-only";
22
+ import { cacheSessionPath, invalidateSessionListCache, resolveSessionPath } from "./session-reader";
23
+ import { indexSessionFileNow } from "./session-index-scanner";
24
+ import { createSessionActivityTracker, type AgentLikeMessage } from "./session-activity";
25
+ import { FIRST_MESSAGE_PREVIEW_LENGTH } from "./session-scanner";
17
26
  import { projectIdentityKey } from "./project-identity";
18
27
  import { resolveProject } from "./worktree";
19
- import { ensureSessionMetaRow } from "./task-store";
28
+ import {
29
+ ensureSessionMetaRow,
30
+ fillFirstMessageIfEmpty,
31
+ recordSessionOutcome,
32
+ setSessionTitle,
33
+ taskForSession,
34
+ touchSessionActivity,
35
+ } from "./task-store";
20
36
  import { reconcileForkBoard } from "./board-reconcile";
21
37
  import { getProjectTrustStatus, projectTrustReloadOptions } from "./project-trust";
22
38
  import { persistExplicitStartupPreferences } from "./startup-preferences";
@@ -27,6 +43,7 @@ import type {
27
43
  ExtensionUiRequest,
28
44
  ExtensionUiResponse,
29
45
  ExtensionWidgetItem,
46
+ SessionEntry,
30
47
  SessionInfo,
31
48
  SessionMessageEntry,
32
49
  } from "./types";
@@ -97,6 +114,11 @@ type ExtensionBindingOptions = {
97
114
  forceEmptySystemPrompt?: boolean;
98
115
  };
99
116
 
117
+ type AgentSessionWrapperOptions = {
118
+ exactSystemPrompt?: () => string;
119
+ chatOnly?: boolean;
120
+ };
121
+
100
122
  const IDLE_RESET_EVENT_TYPES = new Set([
101
123
  "agent_end",
102
124
  "agent_settled",
@@ -104,6 +126,28 @@ const IDLE_RESET_EVENT_TYPES = new Set([
104
126
  "compaction_end",
105
127
  ]);
106
128
 
129
+ const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
130
+
131
+ /**
132
+ * Resolves the PI_WEB_IDLE_TIMEOUT_MS environment variable into a session idle
133
+ * timeout in milliseconds. An unset/blank value returns the 10-minute default,
134
+ * `0` disables idle shutdown, and positive values up to Node's timer limit
135
+ * (2147483647 ms) are used as-is. Invalid or out-of-range values fall back to
136
+ * the default with a console warning.
137
+ */
138
+ export function resolveSessionIdleTimeoutMs(
139
+ rawValue: string | undefined = process.env.PI_WEB_IDLE_TIMEOUT_MS,
140
+ ): number {
141
+ if (rawValue !== undefined && rawValue.trim() !== "") {
142
+ const parsed = Number(rawValue);
143
+ if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 2_147_483_647) return parsed;
144
+ console.warn(`[pi-web] invalid PI_WEB_IDLE_TIMEOUT_MS "${rawValue}", falling back to 10 minutes`);
145
+ }
146
+ return DEFAULT_SESSION_IDLE_TIMEOUT_MS;
147
+ }
148
+
149
+ const SESSION_IDLE_TIMEOUT_MS = resolveSessionIdleTimeoutMs();
150
+
107
151
  export interface RpcSessionStartOptions {
108
152
  toolNames?: string[];
109
153
  initialModel?: { provider: string; modelId: string };
@@ -180,8 +224,17 @@ export class AgentSessionWrapper {
180
224
  private _alive = true;
181
225
  /** 最近一次 agent_start 的时间(看板“运行时长”用);0 = 未知 */
182
226
  private lastAgentStart = 0;
183
-
184
- constructor(public readonly inner: AgentSessionLike) {}
227
+ /** 会话活跃事件 → 落库(last_reply / modified);按 wrapper 实例持有防并发串台 */
228
+ private readonly activityTracker = createSessionActivityTracker();
229
+ private readonly exactSystemPrompt?: () => string;
230
+ private readonly chatOnly: boolean;
231
+
232
+ constructor(public readonly inner: AgentSessionLike, options: AgentSessionWrapperOptions = {}) {
233
+ this.exactSystemPrompt = options.exactSystemPrompt;
234
+ this.chatOnly = options.chatOnly ?? false;
235
+ this.installExactSystemPromptContinuation();
236
+ this.applyExactSystemPrompt();
237
+ }
185
238
 
186
239
  /** 是否有等待用户响应的扩展 UI 请求(waiting_input 判定用) */
187
240
  hasPendingUiRequest(): boolean {
@@ -217,6 +270,10 @@ export class AgentSessionWrapper {
217
270
  return this._alive;
218
271
  }
219
272
 
273
+ isChatOnly(): boolean {
274
+ return this.chatOnly;
275
+ }
276
+
220
277
  isRunning(): boolean {
221
278
  return this._alive && (this.pendingPromptCount > 0 || this.inner.isStreaming || this.inner.isCompacting || this.inner.isBashRunning);
222
279
  }
@@ -230,11 +287,42 @@ export class AgentSessionWrapper {
230
287
  invalidateSessionListCache();
231
288
  }
232
289
  if (IDLE_RESET_EVENT_TYPES.has(event.type)) this.resetIdleTimer();
290
+ // 会话活跃落库(last_reply / modified)。包在 try/catch 里:库是旁路索引,
291
+ // 写失败必须可见(console.error),但绝不能打断上面几件事与事件分发。
292
+ try {
293
+ this.persistSessionActivity(event);
294
+ } catch (error) {
295
+ console.error(
296
+ `[pi-web] 会话活跃写库失败(${event.type}):`,
297
+ error instanceof Error ? error.message : String(error),
298
+ );
299
+ }
233
300
  this.emit(event);
234
301
  });
235
302
  this.resetIdleTimer();
236
303
  }
237
304
 
305
+ /**
306
+ * 事件 → session_meta 写入(只 UPDATE;行由创建/扫描链路建立):
307
+ * · agent_start → modified 前移(运行中会话浮顶)
308
+ * · message_end → 本轮最后一条 assistant 文本(缓存,不落库)
309
+ * · agent_settled → last_reply + modified(一轮循环真正结束;用户取消走同一路径)
310
+ * 用 agent_settled 而非 agent_end:后者一轮里可能多次(重试/compaction/queue 续跑)。
311
+ */
312
+ private persistSessionActivity(event: AgentEvent): void {
313
+ const effect = this.activityTracker.handle(event as { type: string; message?: AgentLikeMessage });
314
+ if (!effect) return;
315
+ const sessionId = this.inner.sessionId;
316
+ if (!sessionId) return;
317
+ if (effect.kind === "touch") {
318
+ touchSessionActivity(sessionId);
319
+ return;
320
+ }
321
+ recordSessionOutcome(sessionId, effect);
322
+ // 首条用户消息此刻已在内存 entries 里(不读文件,读取路径零扫盘)。
323
+ fillFirstMessageIfEmpty(sessionId, firstUserMessageOf(this.inner.sessionManager.getEntries()));
324
+ }
325
+
238
326
  setForceEmptySystemPrompt(force: boolean): void {
239
327
  this.forceEmptySystemPrompt = force;
240
328
  this.applyForcedEmptySystemPrompt();
@@ -250,10 +338,31 @@ export class AgentSessionWrapper {
250
338
  await this.waitForExtensionsBound();
251
339
  }
252
340
 
341
+ private applyExactSystemPrompt(): void {
342
+ if (!this.exactSystemPrompt || !this.inner.agent.state) return;
343
+ this.inner.agent.state.systemPrompt = this.exactSystemPrompt();
344
+ }
345
+
346
+ private installExactSystemPromptContinuation(): void {
347
+ if (!this.exactSystemPrompt) return;
348
+ const previous = this.inner.agent.prepareNextTurnWithContext;
349
+ this.inner.agent.prepareNextTurnWithContext = async (turn, signal) => {
350
+ const prepared = await previous?.(turn, signal);
351
+ return {
352
+ ...prepared,
353
+ context: {
354
+ ...(prepared?.context ?? turn.context),
355
+ systemPrompt: this.exactSystemPrompt!(),
356
+ },
357
+ };
358
+ };
359
+ }
360
+
253
361
  private ensureExtensionsBound(options: ExtensionBindingOptions = {}): Promise<void> {
254
362
  if (options.forceEmptySystemPrompt) this.forceEmptySystemPrompt = true;
255
363
  if (this.extensionsBound) {
256
364
  this.applyForcedEmptySystemPrompt();
365
+ this.applyExactSystemPrompt();
257
366
  return Promise.resolve();
258
367
  }
259
368
  if (this.extensionBindingPromise) return this.extensionBindingPromise;
@@ -293,6 +402,7 @@ export class AgentSessionWrapper {
293
402
  }
294
403
  this.extensionsBound = true;
295
404
  this.applyForcedEmptySystemPrompt();
405
+ this.applyExactSystemPrompt();
296
406
  console.log(`[pi-web] session_start dispatched to extensions for session ${this.inner.sessionId}`);
297
407
  })().catch((err) => {
298
408
  this.extensionBindingError = err;
@@ -361,6 +471,8 @@ export class AgentSessionWrapper {
361
471
  }
362
472
 
363
473
  private resetIdleTimer(): void {
474
+ // A resolved timeout of 0 disables idle shutdown entirely.
475
+ if (SESSION_IDLE_TIMEOUT_MS === 0) return;
364
476
  if (this.idleTimer) clearTimeout(this.idleTimer);
365
477
  this.idleTimer = setTimeout(() => {
366
478
  if (this.isRunning()) {
@@ -370,7 +482,7 @@ export class AgentSessionWrapper {
370
482
  void this.shutdown().catch((error) => {
371
483
  console.error("[pi-web] failed to shut down idle session:", error instanceof Error ? error.message : error);
372
484
  });
373
- }, 10 * 60 * 1000);
485
+ }, SESSION_IDLE_TIMEOUT_MS);
374
486
  }
375
487
 
376
488
  onEvent(listener: EventListener): () => void {
@@ -531,6 +643,42 @@ export class AgentSessionWrapper {
531
643
  return { id: model.id, provider: model.provider };
532
644
  }
533
645
 
646
+ case "fork_branch": {
647
+ if (this.isRunning()) {
648
+ throw new Error("Cannot fork while the session is running");
649
+ }
650
+ const entryId = command.entryId as string;
651
+ const sessionManager = this.inner.sessionManager;
652
+ const currentSessionFile = this.inner.sessionFile;
653
+ if (!sessionManager.isPersisted()) return { cancelled: true };
654
+ if (!currentSessionFile) throw new Error("Persisted session is missing a session file");
655
+ if (!sessionManager.getEntry(entryId)) throw new Error("Invalid entry ID for forking");
656
+
657
+ const sessionDir = sessionManager.getSessionDir();
658
+ const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
659
+ const forkedPath = sourceManager.createBranchedSession(entryId);
660
+ if (!forkedPath) throw new Error("Failed to create forked session");
661
+ // SDK 惰性落盘契约:fork 点之前无 assistant 消息时不写文件(flushed=false),
662
+ // 只返回路径字符串。不强制落盘则后续 open 不存在文件会 newSession 生成新 id,
663
+ // 索引/列表/看板全部读不到——必须和 fork case 一样手动写盘。
664
+ if (!existsSync(forkedPath)) {
665
+ const content = [sourceManager.getHeader(), ...sourceManager.getEntries()]
666
+ .map((entry) => JSON.stringify(entry))
667
+ .join("\n") + "\n";
668
+ writeFileSync(forkedPath, content, { encoding: "utf8", flag: "wx" });
669
+ (sourceManager as unknown as { flushed: boolean }).flushed = true;
670
+ }
671
+
672
+ const newSessionId = SessionManager.open(forkedPath, sessionDir).getSessionId();
673
+ cacheSessionPath(newSessionId, forkedPath);
674
+ invalidateSessionListCache();
675
+ // 立即进 session_meta 索引(否则要等 30s 后台扫描才出现在会话列表);
676
+ // 归属继承:引用分支与源会话同任务(否则只会在聊天区当孤儿根出现)。
677
+ const sourceSessionId = sessionManager.getSessionId();
678
+ await indexSessionFileNow(forkedPath, sourceSessionId, taskForSession(sourceSessionId));
679
+ return { cancelled: false, newSessionId };
680
+ }
681
+
534
682
  case "fork": {
535
683
  if (this.inner.isBashRunning) {
536
684
  throw new Error("Cannot fork while a shell command is running");
@@ -590,6 +738,7 @@ export class AgentSessionWrapper {
590
738
  // resolveProject 的 await 不能发生在 registry 还挂着已 fork wrapper 的窗口里;
591
739
  // 建行只用局部变量(cwd/newSessionFile/sourceSessionId),不依赖 wrapper。
592
740
  const cwd = sessionManager.getCwd();
741
+ const sourceTaskId = taskForSession(sourceSessionId); // 归属继承:fork 子会话与父同任务
593
742
  await this.shutdown();
594
743
  try {
595
744
  const project = await resolveProject(cwd ?? "");
@@ -598,13 +747,28 @@ export class AgentSessionWrapper {
598
747
  cwd: cwd ?? "",
599
748
  projectKey: projectIdentityKey(project?.projectRoot ?? cwd ?? ""),
600
749
  parentId: sourceSessionId,
750
+ taskId: sourceTaskId, // 建行即带归属,否则子会话先以临时会话落在聊天区(V1)
601
751
  });
752
+ } catch (error) {
753
+ // 不阻塞 fork(文件已经在了,pi 侧已成功),但必须可见:静默失败会让子会话
754
+ // 以「无归属临时会话」出现在聊天区(也是本次要修的 V1 病灶);扫描器的归属
755
+ // 收敛是第二道网,不能当成唯一防线。
756
+ console.error(
757
+ `[pi-web] fork 建行失败 session=${newSessionId}:`,
758
+ error instanceof Error ? error.message : String(error),
759
+ );
760
+ }
761
+ try {
602
762
  // fork 卡即时入板:源会话归属任务 → 精准 reconcile 该任务看板。
603
763
  // await 保证卡先落 yjs 再返回(前端响应到达时卡已存在,无并发写窗口);
604
- // 失败不阻塞 fork,10s 定时兜底。
764
+ // 失败不阻塞 fork,10s 定时兜底。单独一个 try:与建行失败分开报错,避免
765
+ // 入板失败被误报成「建行失败」。
605
766
  await reconcileForkBoard(sourceSessionId);
606
- } catch {
607
- // 建行失败不阻塞 fork:扫描器下一轮兜底补行。
767
+ } catch (error) {
768
+ console.error(
769
+ `[pi-web] fork 入板失败 source=${sourceSessionId}:`,
770
+ error instanceof Error ? error.message : String(error),
771
+ );
608
772
  }
609
773
  return { cancelled: false, newSessionId };
610
774
  }
@@ -644,6 +808,15 @@ export class AgentSessionWrapper {
644
808
  const name = (command.name as string | undefined)?.trim();
645
809
  if (!name) throw new Error("Session name cannot be empty");
646
810
  this.inner.setSessionName(name);
811
+ // 列表索引同步:pi 侧改名成功即写 session_meta.title(前端侧栏/看板卡
812
+ // 改名走这条 RPC,不落库则刷新后退回旧标题)。失败处理与路由层同源但
813
+ // 语义有别:这里只记日志、不抛——RPC 已经改了文件名,抛错对调用方没有
814
+ // 可恢复动作,还会断掉命令响应/事件流。
815
+ try {
816
+ await setSessionTitle(this.inner.sessionId, name);
817
+ } catch (error) {
818
+ console.error("[pi-web] 会话标题写库失败:", error);
819
+ }
647
820
  invalidateSessionListCache();
648
821
  return null;
649
822
  }
@@ -685,9 +858,10 @@ export class AgentSessionWrapper {
685
858
  case "get_tools": {
686
859
  const all: ToolInfo[] = this.inner.getAllTools();
687
860
  const active = new Set<string>(this.inner.getActiveToolNames());
861
+ // 对齐上游:展开整个 ToolInfo(含 parameters / promptGuidelines),
862
+ // 之前只挑 name/description/active 会把参数 schema 丢掉 → 面板参数全是“无”。
688
863
  return all.map((t) => ({
689
- name: t.name,
690
- description: t.description,
864
+ ...t,
691
865
  active: active.has(t.name),
692
866
  }));
693
867
  }
@@ -723,6 +897,9 @@ export class AgentSessionWrapper {
723
897
 
724
898
  case "set_tools": {
725
899
  const toolNames = command.toolNames as string[];
900
+ if (readSubagentSessionResources(this.inner.sessionManager.getEntries() as unknown as SessionEntry[])) {
901
+ throw new Error("Subagent tool selection is fixed by its profile");
902
+ }
726
903
  this.setForceEmptySystemPrompt(toolNames.length === 0);
727
904
  this.inner.setActiveToolsByName(withExtensionTools(this.inner, toolNames));
728
905
  this.applyForcedEmptySystemPrompt();
@@ -1468,8 +1645,13 @@ async function persistNewSessionFile(manager: SessionManager, sessionId: string)
1468
1645
  // 与扫描器同源归一化(Windows 大小写折叠等),防新建会话 project_key 与列表查询键不一致
1469
1646
  projectKey: projectIdentityKey(project?.projectRoot ?? cwd ?? ""),
1470
1647
  });
1471
- } catch {
1472
- // 建行失败不阻塞开会话:扫描器下一轮兜底补行。
1648
+ } catch (error) {
1649
+ // 建行失败不阻塞开会话(文件已落盘,运行时列表由 runtime union 覆盖),但必须可见:
1650
+ // 静默会让该会话最多 30s 不在列表/任务区(扫描器会补行,但不能当成唯一防线)。
1651
+ console.error(
1652
+ `[pi-web] 新会话建行失败 session=${sessionId}:`,
1653
+ error instanceof Error ? error.message : String(error),
1654
+ );
1473
1655
  }
1474
1656
  }
1475
1657
 
@@ -1487,6 +1669,19 @@ function runtimeMessageText(entry: SessionMessageEntry): string {
1487
1669
  .join(" ");
1488
1670
  }
1489
1671
 
1672
+ /**
1673
+ * 内存 entries 里首条用户消息的简述(截断与扫描器同一上限)。
1674
+ * 会话活跃事件落库时用它回填 first_message——读取路径不再读文件。
1675
+ */
1676
+ function firstUserMessageOf(entries: unknown[]): string {
1677
+ for (const entry of entries as SessionMessageEntry[]) {
1678
+ if (entry?.type !== "message" || entry.message?.role !== "user") continue;
1679
+ const text = runtimeMessageText(entry).trim();
1680
+ if (text) return text.slice(0, FIRST_MESSAGE_PREVIEW_LENGTH);
1681
+ }
1682
+ return "";
1683
+ }
1684
+
1490
1685
  function runtimeMessageActivityMs(entry: SessionMessageEntry): number | undefined {
1491
1686
  if (entry.message.role !== "user" && entry.message.role !== "assistant") return undefined;
1492
1687
  if (typeof entry.message.timestamp === "number") return entry.message.timestamp;
@@ -1518,6 +1713,8 @@ export function getRpcSessionInfos(): SessionInfo[] {
1518
1713
  // loads commands. Do not leak it into history before a prompt is accepted.
1519
1714
  if (!persisted && (!session.isRunning() || !firstUserMessage)) continue;
1520
1715
 
1716
+ const subagent = readSubagentRun(entries as unknown as SessionEntry[], header?.id ?? session.sessionId, sessionFile ?? "");
1717
+
1521
1718
  const created = header?.timestamp
1522
1719
  ?? entries[0]?.timestamp
1523
1720
  ?? new Date().toISOString();
@@ -1537,6 +1734,16 @@ export function getRpcSessionInfos(): SessionInfo[] {
1537
1734
  modified: new Date(lastActivityMs).toISOString(),
1538
1735
  messageCount: messages.length,
1539
1736
  firstMessage: firstUserMessage ? runtimeMessageText(firstUserMessage) || "(no messages)" : "(no messages)",
1737
+ ...(subagent ? {
1738
+ parentSessionId: subagent.parentSessionId,
1739
+ relation: {
1740
+ kind: "subagent" as const,
1741
+ parentSessionId: subagent.parentSessionId,
1742
+ profile: subagent.profile,
1743
+ description: subagent.description,
1744
+ status: session.isRunning() ? "running" as const : subagent.status,
1745
+ },
1746
+ } : {}),
1540
1747
  transient: !persisted,
1541
1748
  });
1542
1749
  }
@@ -1646,14 +1853,25 @@ export async function startRpcSession(
1646
1853
  const sessionCwd = sessionManager.getCwd();
1647
1854
  const finishStartingSession = trackStartingSession(sessionCwd);
1648
1855
  const starting = (async () => {
1856
+ // subagent 会话(含恢复)按 profile 的资源/工具加载选项重建;普通会话不命中。
1857
+ const subagentResources = sessionFile
1858
+ ? readSubagentSessionResources(sessionManager.getEntries() as unknown as SessionEntry[])
1859
+ : null;
1860
+ const subagentLoadsResources = Boolean(subagentResources?.loadExtensions || subagentResources?.loadSkills);
1861
+ // chatOnly 仅当“显式空工具列表”时为真:subagent 会话按 profile 的 tools 判定,
1862
+ // 普通会话按调用方传入的 toolNames(undefined = 默认全量,[] = 全关)。
1863
+ const chatOnly = subagentResources
1864
+ ? subagentResources.tools.length === 0 && !subagentLoadsResources
1865
+ : toolNames?.length === 0;
1866
+
1649
1867
  // Some extensions access the SDK's global theme even outside the terminal UI.
1650
- initTheme();
1868
+ if (!chatOnly) initTheme();
1651
1869
  const agentDir = getAgentDir();
1652
1870
 
1653
1871
  // Determine which tools to pass based on requested toolNames.
1654
1872
  // Since v0.68.0, session creation expects string[] tool names instead of Tool[] instances.
1655
- let toolsOption: string[] | undefined;
1656
- if (toolNames !== undefined) {
1873
+ let toolsOption: string[] | undefined = subagentResources?.tools;
1874
+ if (!subagentResources && toolNames !== undefined) {
1657
1875
  // toolNames === [] -> "all off" (an empty allow-list disables every tool).
1658
1876
  // Otherwise DO NOT pass a builtin-only allow-list: passing CODING_TOOL_NAMES
1659
1877
  // set allowedToolNames to coding builtins only, which filtered every
@@ -1674,18 +1892,37 @@ export async function startRpcSession(
1674
1892
  cwd: sessionCwd,
1675
1893
  agentDir,
1676
1894
  settingsManager,
1677
- resourceLoaderOptions: {
1678
- extensionFactories: [
1679
- createProjectCommandBashExtension({
1680
- cwd: sessionCwd,
1681
- settings: settingsManager,
1682
- }),
1683
- // 内建会话 TODO(工具 + pi-todo.state 快照),装 pi-web-sky 即自带,
1684
- // 无需用户安装任何 pi 包。工具重名时用户扩展优先(见 pi-todo 文档)。
1685
- createTodoExtension(),
1686
- ],
1687
- extensionsOverride: preferUserBashExtension,
1688
- },
1895
+ resourceLoaderOptions: subagentResources
1896
+ ? {
1897
+ noExtensions: !subagentResources.loadExtensions,
1898
+ noSkills: !subagentResources.loadSkills,
1899
+ noPromptTemplates: true,
1900
+ noThemes: true,
1901
+ noContextFiles: true,
1902
+ systemPrompt: " ",
1903
+ appendSystemPrompt: subagentResources.appendSystemPrompt,
1904
+ }
1905
+ : chatOnly
1906
+ ? CHAT_ONLY_RESOURCE_LOADER_OPTIONS
1907
+ : {
1908
+ extensionFactories: [
1909
+ createProjectCommandBashExtension({
1910
+ cwd: sessionCwd,
1911
+ settings: settingsManager,
1912
+ }),
1913
+ // 内建会话 TODO(工具 + pi-todo.state 快照),装 pi-web-sky 即自带,
1914
+ // 无需用户安装任何 pi 包。工具重名时用户扩展优先(见 pi-todo 文档)。
1915
+ createTodoExtension(),
1916
+ // 内置 subagent(Agent/steer_subagent/get_subagent_result 工具),
1917
+ // 开关见 agents 设置(isBuiltInSubagentsEnabled)。
1918
+ createSubagentExtension(
1919
+ SUBAGENT_CONTROLLER.extensionRuntime,
1920
+ () => listSubagentProfiles(sessionCwd),
1921
+ isBuiltInSubagentsEnabled,
1922
+ ),
1923
+ ],
1924
+ extensionsOverride: (base) => preferUserBashExtension(preferPiWebSubagentExtension(base)),
1925
+ },
1689
1926
  ...(trustReloadOptions ? { resourceLoaderReloadOptions: trustReloadOptions } : {}),
1690
1927
  });
1691
1928
  const scope = await resolveVisibleModels(
@@ -1751,7 +1988,7 @@ export async function startRpcSession(
1751
1988
 
1752
1989
  wrapper.onDestroy(() => registry.delete(realSessionId));
1753
1990
  registry.set(realSessionId, wrapper);
1754
- wrapper.beginExtensionBinding({ forceEmptySystemPrompt: toolNames?.length === 0 });
1991
+ if (!chatOnly) wrapper.beginExtensionBinding({ forceEmptySystemPrompt: toolNames?.length === 0 });
1755
1992
 
1756
1993
  return { session: wrapper, realSessionId };
1757
1994
  })().finally(() => {
@@ -1762,3 +1999,39 @@ export async function startRpcSession(
1762
1999
  locks.set(sessionId, starting);
1763
2000
  return starting;
1764
2001
  }
2002
+
2003
+ const SUBAGENT_CONTROLLER = createSubagentController({
2004
+ getSession: (sessionId) => getRegistry().get(sessionId),
2005
+ registerSession: (inner, options) => {
2006
+ const wrapper = new AgentSessionWrapper(inner, {
2007
+ ...(options?.exactSystemPrompt !== undefined
2008
+ ? { exactSystemPrompt: () => options.exactSystemPrompt! }
2009
+ : {}),
2010
+ chatOnly: options?.chatOnly,
2011
+ });
2012
+ const realSessionId = inner.sessionId as string;
2013
+ const realSessionFile = inner.sessionFile as string | undefined;
2014
+ if (realSessionFile) cacheSessionPath(realSessionId, realSessionFile);
2015
+ wrapper.onDestroy(() => getRegistry().delete(realSessionId));
2016
+ getRegistry().set(realSessionId, wrapper);
2017
+ wrapper.start();
2018
+ if (!wrapper.isChatOnly()) wrapper.beginExtensionBinding();
2019
+ },
2020
+ reopenSession: async (sessionId, sessionFile) =>
2021
+ (await startRpcSession(sessionId, sessionFile, undefined)).session,
2022
+ resolveSessionPath,
2023
+ invalidateSessionList: invalidateSessionListCache,
2024
+ isBuiltInSubagentsEnabled,
2025
+ });
2026
+
2027
+ export function getSubagentRun(sessionId: string) {
2028
+ return SUBAGENT_CONTROLLER.get(sessionId);
2029
+ }
2030
+
2031
+ export function steerSubagent(sessionId: string, message: string) {
2032
+ return SUBAGENT_CONTROLLER.steer(sessionId, message);
2033
+ }
2034
+
2035
+ export function abortSubagent(sessionId: string) {
2036
+ return SUBAGENT_CONTROLLER.abort(sessionId);
2037
+ }
@@ -0,0 +1,123 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createJiti } from "jiti";
4
+
5
+ const jiti = createJiti(import.meta.url, {
6
+ interopDefault: true,
7
+ moduleCache: false,
8
+ alias: { "@": process.cwd() },
9
+ });
10
+ const { createSessionActivityTracker, lastAssistantText } = await jiti.import("./session-activity.ts");
11
+
12
+ const assistant = (text, ts, stopReason = "stop") => ({
13
+ role: "assistant",
14
+ timestamp: ts,
15
+ stopReason,
16
+ content: [{ type: "text", text }],
17
+ });
18
+
19
+ /** 依次喂事件,返回所有非 null 效果(真实调用方的用法:一轮事件流按序处理)。 */
20
+ function run(events) {
21
+ const tracker = createSessionActivityTracker();
22
+ const effects = [];
23
+ for (const event of events) {
24
+ const effect = tracker.handle(event);
25
+ if (effect) effects.push(effect);
26
+ }
27
+ return effects;
28
+ }
29
+
30
+ test("agent_start → touch(只刷活跃时间,浮动到列表顶部)", () => {
31
+ assert.deepEqual(run([{ type: "agent_start" }]), [{ kind: "touch" }]);
32
+ });
33
+
34
+ test("单条 message_end 不落 outcome(不是循环最后一条)", () => {
35
+ assert.deepEqual(run([{ type: "message_end", message: assistant("中间回复", 1000) }]), []);
36
+ });
37
+
38
+ test("agent_settled → outcome(带最后一条 assistant 文本与时间戳)", () => {
39
+ const effects = run([
40
+ { type: "message_end", message: assistant("第一条", 1000) },
41
+ { type: "message_end", message: { role: "user", content: "再问" } },
42
+ { type: "message_end", message: assistant("最后一条回复", 2000) },
43
+ { type: "agent_settled" },
44
+ ]);
45
+ assert.deepEqual(effects, [{ kind: "outcome", lastReply: "最后一条回复", at: 2000 }]);
46
+ });
47
+
48
+ test("用户取消(stopReason=aborted)同样落 outcome", () => {
49
+ const effects = run([
50
+ { type: "message_end", message: assistant("被打断的半截回复", 3000, "aborted") },
51
+ { type: "agent_settled" },
52
+ ]);
53
+ assert.deepEqual(effects, [{ kind: "outcome", lastReply: "被打断的半截回复", at: 3000 }]);
54
+ });
55
+
56
+ test("本轮没采到任何文本 → 只 touch(空文本不清库)", () => {
57
+ // 刚发出就被取消 / 整轮只有工具调用:agent_settled 不得产出 lastReply:"",
58
+ // 否则调用方会把已存的最后一条回复清空。
59
+ assert.deepEqual(run([{ type: "agent_start" }, { type: "agent_settled" }]), [
60
+ { kind: "touch" },
61
+ { kind: "touch" },
62
+ ]);
63
+ assert.deepEqual(
64
+ run([
65
+ { type: "message_end", message: { role: "assistant", timestamp: 1, content: [{ type: "toolCall", name: "bash" }] } },
66
+ { type: "message_end", message: { role: "assistant", timestamp: 2, content: [{ type: "thinking", thinking: "想一想" }] } },
67
+ { type: "message_end", message: { role: "assistant", timestamp: 3, content: [{ type: "text", text: " " }] } },
68
+ { type: "agent_settled" },
69
+ ]),
70
+ [{ kind: "touch" }],
71
+ );
72
+ });
73
+
74
+ test("settle 后 pending 归零:第二段 settle(无新消息)只 touch", () => {
75
+ const tracker = createSessionActivityTracker();
76
+ tracker.handle({ type: "message_end", message: assistant("回复", 1000) });
77
+ assert.deepEqual(tracker.handle({ type: "agent_settled" }), {
78
+ kind: "outcome",
79
+ lastReply: "回复",
80
+ at: 1000,
81
+ });
82
+ assert.deepEqual(tracker.handle({ type: "agent_settled" }), { kind: "touch" });
83
+ });
84
+
85
+ test("tracker 按实例持有状态:并发会话不串台", () => {
86
+ const a = createSessionActivityTracker();
87
+ const b = createSessionActivityTracker();
88
+ a.handle({ type: "message_end", message: assistant("A 的回复", 1000) });
89
+ b.handle({ type: "message_end", message: assistant("B 的回复", 2000) });
90
+ assert.deepEqual(b.handle({ type: "agent_settled" }), {
91
+ kind: "outcome",
92
+ lastReply: "B 的回复",
93
+ at: 2000,
94
+ });
95
+ assert.deepEqual(a.handle({ type: "agent_settled" }), {
96
+ kind: "outcome",
97
+ lastReply: "A 的回复",
98
+ at: 1000,
99
+ });
100
+ });
101
+
102
+ test("无关事件(tool_execution_end 等)不产生效果,也不破坏 pending", () => {
103
+ const effects = run([
104
+ { type: "message_end", message: assistant("回复", 1000) },
105
+ { type: "tool_execution_start", toolName: "bash" },
106
+ { type: "queue_update", steering: [] },
107
+ { type: "agent_settled" },
108
+ ]);
109
+ assert.deepEqual(effects, [{ kind: "outcome", lastReply: "回复", at: 1000 }]);
110
+ });
111
+
112
+ test("lastAssistantText:只取 text 块,thinking/toolCall 忽略", () => {
113
+ assert.equal(
114
+ lastAssistantText({
115
+ role: "assistant",
116
+ content: [{ type: "thinking", thinking: "x" }, { type: "text", text: "a" }, { type: "text", text: "b" }],
117
+ }),
118
+ "a\nb",
119
+ );
120
+ assert.equal(lastAssistantText({ role: "assistant", content: "plain" }), "plain");
121
+ assert.equal(lastAssistantText({ role: "assistant" }), "");
122
+ assert.equal(lastAssistantText({ role: "assistant", content: [{ type: "text", text: " " }] }), "");
123
+ });