@baique/pi-web-sky 0.1.55 → 0.1.56

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 (361) 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 +1 -1
  7. package/.next/required-server-files.json +1 -1
  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 +2 -2
  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 -2
  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 -2
  109. package/.next/server/app/api/tasks/route.js +3 -3
  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 +1 -1
  126. package/.next/server/chunks/3379.js +1 -1
  127. package/.next/server/chunks/402.js +12 -2
  128. package/.next/server/chunks/426.js +14 -0
  129. package/.next/server/chunks/4367.js +1 -1
  130. package/.next/server/chunks/4457.js +2 -2
  131. package/.next/server/chunks/4980.js +1 -1
  132. package/.next/server/chunks/542.js +23 -4
  133. package/.next/server/chunks/5794.js +1 -1
  134. package/.next/server/chunks/6337.js +1 -1
  135. package/.next/server/chunks/6429.js +15 -2
  136. package/.next/server/chunks/6525.js +12 -2
  137. package/.next/server/chunks/7048.js +6 -0
  138. package/.next/server/instrumentation.js +1 -1
  139. package/.next/server/middleware-build-manifest.js +1 -1
  140. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  141. package/.next/server/middleware.js +3 -3
  142. package/.next/server/pages/404.html +1 -1
  143. package/.next/server/pages/500.html +1 -1
  144. package/.next/static/chunks/{752.0ad92735d5f38143.js → 752.adce8bc3ebbaf4b6.js} +1 -1
  145. package/.next/static/chunks/8088.0cad1ff2e09fd8ff.js +1 -0
  146. package/.next/static/chunks/app/_global-error/page-ad6e695deb9de65c.js +1 -0
  147. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-ad6e695deb9de65c.js +1 -0
  148. package/.next/static/chunks/app/api/agent/[id]/events/route-ad6e695deb9de65c.js +1 -0
  149. package/.next/static/chunks/app/api/agent/[id]/route-ad6e695deb9de65c.js +1 -0
  150. package/.next/static/chunks/app/api/agent/new/route-ad6e695deb9de65c.js +1 -0
  151. package/.next/static/chunks/app/api/agent/running/route-ad6e695deb9de65c.js +1 -0
  152. package/.next/static/chunks/app/api/app-update/route-ad6e695deb9de65c.js +1 -0
  153. package/.next/static/chunks/app/api/auth/all-providers/route-ad6e695deb9de65c.js +1 -0
  154. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-ad6e695deb9de65c.js +1 -0
  155. package/.next/static/chunks/app/api/auth/login/[provider]/route-ad6e695deb9de65c.js +1 -0
  156. package/.next/static/chunks/app/api/auth/logout/[provider]/route-ad6e695deb9de65c.js +1 -0
  157. package/.next/static/chunks/app/api/auth/providers/route-ad6e695deb9de65c.js +1 -0
  158. package/.next/static/chunks/app/api/board-assets/[...path]/route-ad6e695deb9de65c.js +1 -0
  159. package/.next/static/chunks/app/api/board-assets/route-ad6e695deb9de65c.js +1 -0
  160. package/.next/static/chunks/app/api/boards/[id]/add-session/route-ad6e695deb9de65c.js +1 -0
  161. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-ad6e695deb9de65c.js +1 -0
  162. package/.next/static/chunks/app/api/boards/[id]/route-ad6e695deb9de65c.js +1 -0
  163. package/.next/static/chunks/app/api/boards/reorder/route-ad6e695deb9de65c.js +1 -0
  164. package/.next/static/chunks/app/api/boards/route-ad6e695deb9de65c.js +1 -0
  165. package/.next/static/chunks/app/api/cwd/browse/route-ad6e695deb9de65c.js +1 -0
  166. package/.next/static/chunks/app/api/cwd/validate/route-ad6e695deb9de65c.js +1 -0
  167. package/.next/static/chunks/app/api/default-cwd/route-ad6e695deb9de65c.js +1 -0
  168. package/.next/static/chunks/app/api/file-index/route-ad6e695deb9de65c.js +1 -0
  169. package/.next/static/chunks/app/api/files/[...path]/route-ad6e695deb9de65c.js +1 -0
  170. package/.next/static/chunks/app/api/git/diff/route-ad6e695deb9de65c.js +1 -0
  171. package/.next/static/chunks/app/api/git/status/route-ad6e695deb9de65c.js +1 -0
  172. package/.next/static/chunks/app/api/home/route-ad6e695deb9de65c.js +1 -0
  173. package/.next/static/chunks/app/api/mcp/route-ad6e695deb9de65c.js +1 -0
  174. package/.next/static/chunks/app/api/models/route-ad6e695deb9de65c.js +1 -0
  175. package/.next/static/chunks/app/api/models-config/catalog/route-ad6e695deb9de65c.js +1 -0
  176. package/.next/static/chunks/app/api/models-config/discover/route-ad6e695deb9de65c.js +1 -0
  177. package/.next/static/chunks/app/api/models-config/route-ad6e695deb9de65c.js +1 -0
  178. package/.next/static/chunks/app/api/models-config/test/route-ad6e695deb9de65c.js +1 -0
  179. package/.next/static/chunks/app/api/plugins/route-ad6e695deb9de65c.js +1 -0
  180. package/.next/static/chunks/app/api/project-trust/route-ad6e695deb9de65c.js +1 -0
  181. package/.next/static/chunks/app/api/quota/route-ad6e695deb9de65c.js +1 -0
  182. package/.next/static/chunks/app/api/search/route-ad6e695deb9de65c.js +1 -0
  183. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-ad6e695deb9de65c.js +1 -0
  184. package/.next/static/chunks/app/api/sessions/[id]/context/route-ad6e695deb9de65c.js +1 -0
  185. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-ad6e695deb9de65c.js +1 -0
  186. package/.next/static/chunks/app/api/sessions/[id]/export/route-ad6e695deb9de65c.js +1 -0
  187. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-ad6e695deb9de65c.js +1 -0
  188. package/.next/static/chunks/app/api/sessions/[id]/route-ad6e695deb9de65c.js +1 -0
  189. package/.next/static/chunks/app/api/sessions/[id]/state/route-ad6e695deb9de65c.js +1 -0
  190. package/.next/static/chunks/app/api/sessions/[id]/todos/route-ad6e695deb9de65c.js +1 -0
  191. package/.next/static/chunks/app/api/sessions/route-ad6e695deb9de65c.js +1 -0
  192. package/.next/static/chunks/app/api/sessions/summary/route-ad6e695deb9de65c.js +1 -0
  193. package/.next/static/chunks/app/api/skills/check/route-ad6e695deb9de65c.js +1 -0
  194. package/.next/static/chunks/app/api/skills/install/route-ad6e695deb9de65c.js +1 -0
  195. package/.next/static/chunks/app/api/skills/route-ad6e695deb9de65c.js +1 -0
  196. package/.next/static/chunks/app/api/skills/search/route-ad6e695deb9de65c.js +1 -0
  197. package/.next/static/chunks/app/api/skills/update/route-ad6e695deb9de65c.js +1 -0
  198. package/.next/static/chunks/app/api/subagents/[id]/route-ad6e695deb9de65c.js +1 -0
  199. package/.next/static/chunks/app/api/subagents/profiles/route-ad6e695deb9de65c.js +1 -0
  200. package/.next/static/chunks/app/api/subagents/settings/route-ad6e695deb9de65c.js +1 -0
  201. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-ad6e695deb9de65c.js +1 -0
  202. package/.next/static/chunks/app/api/task-card-questions/route-ad6e695deb9de65c.js +1 -0
  203. package/.next/static/chunks/app/api/task-cards/[id]/route-ad6e695deb9de65c.js +1 -0
  204. package/.next/static/chunks/app/api/task-cards/route-ad6e695deb9de65c.js +1 -0
  205. package/.next/static/chunks/app/api/task-cards/unbind/route-ad6e695deb9de65c.js +1 -0
  206. package/.next/static/chunks/app/api/task-scheduler/status/route-ad6e695deb9de65c.js +1 -0
  207. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-ad6e695deb9de65c.js +1 -0
  208. package/.next/static/chunks/app/api/tasks/[id]/board/route-ad6e695deb9de65c.js +1 -0
  209. package/.next/static/chunks/app/api/tasks/[id]/route-ad6e695deb9de65c.js +1 -0
  210. package/.next/static/chunks/app/api/tasks/reorder/route-ad6e695deb9de65c.js +1 -0
  211. package/.next/static/chunks/app/api/tasks/route-ad6e695deb9de65c.js +1 -0
  212. package/.next/static/chunks/app/api/terminal/[id]/events/route-ad6e695deb9de65c.js +1 -0
  213. package/.next/static/chunks/app/api/terminal/[id]/route-ad6e695deb9de65c.js +1 -0
  214. package/.next/static/chunks/app/api/terminal/route-ad6e695deb9de65c.js +1 -0
  215. package/.next/static/chunks/app/api/worktrees/route-ad6e695deb9de65c.js +1 -0
  216. package/.next/static/chunks/app/api/yjs-version/route-ad6e695deb9de65c.js +1 -0
  217. package/.next/static/chunks/app/layout-87087b955e0c9507.js +1 -0
  218. package/.next/static/chunks/app/manifest.webmanifest/route-ad6e695deb9de65c.js +1 -0
  219. package/.next/static/chunks/app/page-efca6bf51a5f8ccb.js +256 -0
  220. package/.next/static/chunks/next/dist/client/components/builtin/app-error-ad6e695deb9de65c.js +1 -0
  221. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-ad6e695deb9de65c.js +1 -0
  222. package/.next/static/chunks/next/dist/client/components/builtin/not-found-ad6e695deb9de65c.js +1 -0
  223. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-ad6e695deb9de65c.js +1 -0
  224. package/.next/static/chunks/{webpack-b0effde51c1651b8.js → webpack-4de8e8dd9f2f3173.js} +1 -1
  225. package/.next/static/css/bb9361c7f832a89d.css +1 -0
  226. package/.next/static/css/c6d32b6031181be7.css +1 -0
  227. package/.next/static/k8ZFqwz7d-bnK0ZiUVgCc/_buildManifest.js +1 -0
  228. package/.next/trace +7 -7
  229. package/.next/trace-build +1 -1
  230. package/.next/types/app/api/boards/[id]/add-session/route.ts +353 -0
  231. package/.next/types/app/api/subagents/[id]/route.ts +353 -0
  232. package/.next/types/app/api/subagents/profiles/route.ts +353 -0
  233. package/.next/types/app/api/subagents/settings/route.ts +353 -0
  234. package/.next/types/routes.d.ts +5 -1
  235. package/.next/types/validator.ts +36 -0
  236. package/lib/api-types.ts +10 -0
  237. package/lib/board-reconcile.test.mjs +20 -0
  238. package/lib/board-reconcile.ts +54 -0
  239. package/lib/chat-lazy-load.test.mjs +0 -62
  240. package/lib/chat-lazy-load.ts +0 -13
  241. package/lib/chat-only.ts +23 -0
  242. package/lib/draft-stash.ts +4 -5
  243. package/lib/file-types.ts +16 -1
  244. package/lib/i18n/messages/en.ts +91 -0
  245. package/lib/i18n/messages/zh-CN.ts +91 -0
  246. package/lib/id.test.mjs +38 -0
  247. package/lib/id.ts +17 -0
  248. package/lib/message-text.ts +24 -0
  249. package/lib/models-cache.ts +1 -1
  250. package/lib/pi-types.ts +19 -1
  251. package/lib/pin-store.ts +85 -0
  252. package/lib/powershell-settings.ts +106 -0
  253. package/lib/quoted-selection.ts +4 -0
  254. package/lib/rpc-manager.ts +208 -22
  255. package/lib/session-family.test.mjs +49 -0
  256. package/lib/session-family.ts +80 -0
  257. package/lib/session-index-scanner.ts +39 -0
  258. package/lib/session-time-group.test.mjs +38 -0
  259. package/lib/session-time-group.ts +55 -0
  260. package/lib/settings-navigation.ts +124 -0
  261. package/lib/subagent-extension.test.mjs +424 -0
  262. package/lib/subagent-extension.ts +309 -0
  263. package/lib/subagent-input.test.mjs +63 -0
  264. package/lib/subagent-input.ts +71 -0
  265. package/lib/subagent-isolation.test.mjs +51 -0
  266. package/lib/subagent-profile-precedence.ts +20 -0
  267. package/lib/subagent-prompt.test.mjs +64 -0
  268. package/lib/subagent-prompt.ts +31 -0
  269. package/lib/subagent-queue.test.mjs +35 -0
  270. package/lib/subagent-queue.ts +74 -0
  271. package/lib/subagent-runtime.test.mjs +150 -0
  272. package/lib/subagent-runtime.ts +587 -0
  273. package/lib/subagent-settings.test.mjs +50 -0
  274. package/lib/subagent-settings.ts +93 -0
  275. package/lib/subagents.test.mjs +601 -0
  276. package/lib/subagents.ts +602 -0
  277. package/lib/tool-presets.ts +4 -0
  278. package/lib/types.ts +19 -0
  279. package/package.json +1 -1
  280. package/.next/static/bsW3fla0-BlBHVMKtAM5I/_buildManifest.js +0 -1
  281. package/.next/static/chunks/8088.53d9cdaf15634d4b.js +0 -1
  282. package/.next/static/chunks/app/_global-error/page-2d5f6296c7e693c6.js +0 -1
  283. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-2d5f6296c7e693c6.js +0 -1
  284. package/.next/static/chunks/app/api/agent/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  285. package/.next/static/chunks/app/api/agent/[id]/route-2d5f6296c7e693c6.js +0 -1
  286. package/.next/static/chunks/app/api/agent/new/route-2d5f6296c7e693c6.js +0 -1
  287. package/.next/static/chunks/app/api/agent/running/route-2d5f6296c7e693c6.js +0 -1
  288. package/.next/static/chunks/app/api/app-update/route-2d5f6296c7e693c6.js +0 -1
  289. package/.next/static/chunks/app/api/auth/all-providers/route-2d5f6296c7e693c6.js +0 -1
  290. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-2d5f6296c7e693c6.js +0 -1
  291. package/.next/static/chunks/app/api/auth/login/[provider]/route-2d5f6296c7e693c6.js +0 -1
  292. package/.next/static/chunks/app/api/auth/logout/[provider]/route-2d5f6296c7e693c6.js +0 -1
  293. package/.next/static/chunks/app/api/auth/providers/route-2d5f6296c7e693c6.js +0 -1
  294. package/.next/static/chunks/app/api/board-assets/[...path]/route-2d5f6296c7e693c6.js +0 -1
  295. package/.next/static/chunks/app/api/board-assets/route-2d5f6296c7e693c6.js +0 -1
  296. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-2d5f6296c7e693c6.js +0 -1
  297. package/.next/static/chunks/app/api/boards/[id]/route-2d5f6296c7e693c6.js +0 -1
  298. package/.next/static/chunks/app/api/boards/reorder/route-2d5f6296c7e693c6.js +0 -1
  299. package/.next/static/chunks/app/api/boards/route-2d5f6296c7e693c6.js +0 -1
  300. package/.next/static/chunks/app/api/cwd/browse/route-2d5f6296c7e693c6.js +0 -1
  301. package/.next/static/chunks/app/api/cwd/validate/route-2d5f6296c7e693c6.js +0 -1
  302. package/.next/static/chunks/app/api/default-cwd/route-2d5f6296c7e693c6.js +0 -1
  303. package/.next/static/chunks/app/api/file-index/route-2d5f6296c7e693c6.js +0 -1
  304. package/.next/static/chunks/app/api/files/[...path]/route-2d5f6296c7e693c6.js +0 -1
  305. package/.next/static/chunks/app/api/git/diff/route-2d5f6296c7e693c6.js +0 -1
  306. package/.next/static/chunks/app/api/git/status/route-2d5f6296c7e693c6.js +0 -1
  307. package/.next/static/chunks/app/api/home/route-2d5f6296c7e693c6.js +0 -1
  308. package/.next/static/chunks/app/api/mcp/route-2d5f6296c7e693c6.js +0 -1
  309. package/.next/static/chunks/app/api/models/route-2d5f6296c7e693c6.js +0 -1
  310. package/.next/static/chunks/app/api/models-config/catalog/route-2d5f6296c7e693c6.js +0 -1
  311. package/.next/static/chunks/app/api/models-config/discover/route-2d5f6296c7e693c6.js +0 -1
  312. package/.next/static/chunks/app/api/models-config/route-2d5f6296c7e693c6.js +0 -1
  313. package/.next/static/chunks/app/api/models-config/test/route-2d5f6296c7e693c6.js +0 -1
  314. package/.next/static/chunks/app/api/plugins/route-2d5f6296c7e693c6.js +0 -1
  315. package/.next/static/chunks/app/api/project-trust/route-2d5f6296c7e693c6.js +0 -1
  316. package/.next/static/chunks/app/api/quota/route-2d5f6296c7e693c6.js +0 -1
  317. package/.next/static/chunks/app/api/search/route-2d5f6296c7e693c6.js +0 -1
  318. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-2d5f6296c7e693c6.js +0 -1
  319. package/.next/static/chunks/app/api/sessions/[id]/context/route-2d5f6296c7e693c6.js +0 -1
  320. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-2d5f6296c7e693c6.js +0 -1
  321. package/.next/static/chunks/app/api/sessions/[id]/export/route-2d5f6296c7e693c6.js +0 -1
  322. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-2d5f6296c7e693c6.js +0 -1
  323. package/.next/static/chunks/app/api/sessions/[id]/route-2d5f6296c7e693c6.js +0 -1
  324. package/.next/static/chunks/app/api/sessions/[id]/state/route-2d5f6296c7e693c6.js +0 -1
  325. package/.next/static/chunks/app/api/sessions/[id]/todos/route-2d5f6296c7e693c6.js +0 -1
  326. package/.next/static/chunks/app/api/sessions/route-2d5f6296c7e693c6.js +0 -1
  327. package/.next/static/chunks/app/api/sessions/summary/route-2d5f6296c7e693c6.js +0 -1
  328. package/.next/static/chunks/app/api/skills/check/route-2d5f6296c7e693c6.js +0 -1
  329. package/.next/static/chunks/app/api/skills/install/route-2d5f6296c7e693c6.js +0 -1
  330. package/.next/static/chunks/app/api/skills/route-2d5f6296c7e693c6.js +0 -1
  331. package/.next/static/chunks/app/api/skills/search/route-2d5f6296c7e693c6.js +0 -1
  332. package/.next/static/chunks/app/api/skills/update/route-2d5f6296c7e693c6.js +0 -1
  333. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-2d5f6296c7e693c6.js +0 -1
  334. package/.next/static/chunks/app/api/task-card-questions/route-2d5f6296c7e693c6.js +0 -1
  335. package/.next/static/chunks/app/api/task-cards/[id]/route-2d5f6296c7e693c6.js +0 -1
  336. package/.next/static/chunks/app/api/task-cards/route-2d5f6296c7e693c6.js +0 -1
  337. package/.next/static/chunks/app/api/task-cards/unbind/route-2d5f6296c7e693c6.js +0 -1
  338. package/.next/static/chunks/app/api/task-scheduler/status/route-2d5f6296c7e693c6.js +0 -1
  339. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-2d5f6296c7e693c6.js +0 -1
  340. package/.next/static/chunks/app/api/tasks/[id]/board/route-2d5f6296c7e693c6.js +0 -1
  341. package/.next/static/chunks/app/api/tasks/[id]/route-2d5f6296c7e693c6.js +0 -1
  342. package/.next/static/chunks/app/api/tasks/reorder/route-2d5f6296c7e693c6.js +0 -1
  343. package/.next/static/chunks/app/api/tasks/route-2d5f6296c7e693c6.js +0 -1
  344. package/.next/static/chunks/app/api/terminal/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  345. package/.next/static/chunks/app/api/terminal/[id]/route-2d5f6296c7e693c6.js +0 -1
  346. package/.next/static/chunks/app/api/terminal/route-2d5f6296c7e693c6.js +0 -1
  347. package/.next/static/chunks/app/api/worktrees/route-2d5f6296c7e693c6.js +0 -1
  348. package/.next/static/chunks/app/api/yjs-version/route-2d5f6296c7e693c6.js +0 -1
  349. package/.next/static/chunks/app/layout-dace09825b2d3857.js +0 -1
  350. package/.next/static/chunks/app/manifest.webmanifest/route-2d5f6296c7e693c6.js +0 -1
  351. package/.next/static/chunks/app/page-64b9f4b3567a2f73.js +0 -89
  352. package/.next/static/chunks/next/dist/client/components/builtin/app-error-2d5f6296c7e693c6.js +0 -1
  353. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-2d5f6296c7e693c6.js +0 -1
  354. package/.next/static/chunks/next/dist/client/components/builtin/not-found-2d5f6296c7e693c6.js +0 -1
  355. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-2d5f6296c7e693c6.js +0 -1
  356. package/.next/static/css/fd25673802257262.css +0 -1
  357. package/lib/extension-command.test.mjs +0 -63
  358. package/lib/extension-command.ts +0 -81
  359. package/lib/subagent-widget.test.mjs +0 -87
  360. package/lib/subagent-widget.ts +0 -109
  361. /package/.next/static/{bsW3fla0-BlBHVMKtAM5I → k8ZFqwz7d-bnK0ZiUVgCc}/_ssgManifest.js +0 -0
@@ -0,0 +1,587 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import {
3
+ createAgentSessionFromServices,
4
+ createAgentSessionServices,
5
+ getAgentDir,
6
+ initTheme,
7
+ SessionManager,
8
+ SettingsManager,
9
+ type ModelRuntime,
10
+ } from "@earendil-works/pi-coding-agent";
11
+ import type { AgentSessionLike } from "./pi-types";
12
+ import {
13
+ subagentFinalText,
14
+ subagentToolDetails,
15
+ type ResumeSubagentRequest,
16
+ type StartSubagentRequest,
17
+ type SubagentExecution,
18
+ type SubagentExtensionRuntime,
19
+ } from "./subagent-extension";
20
+ import {
21
+ readSubagentRun,
22
+ resolveSubagentProfile,
23
+ SUBAGENT_CONTROL_TOOL_NAMES,
24
+ SUBAGENT_META_TYPE,
25
+ SUBAGENT_STATUS_TYPE,
26
+ SUBAGENT_RESULT_TYPE,
27
+ selectSubagentExtensionTools,
28
+ withSubagentExtensionTools,
29
+ type SubagentMetadata,
30
+ type SubagentResultMetadata,
31
+ type SubagentRunInfo,
32
+ } from "./subagents";
33
+ import type { SessionEntry } from "./types";
34
+ import { buildSubagentPromptPlan } from "./subagent-prompt";
35
+ import { appendSubagentInputFiles, loadSubagentInputFiles } from "./subagent-input";
36
+ import { projectTrustReloadOptions } from "./project-trust";
37
+ import { resolveShellTools } from "./powershell-settings";
38
+ import { isBuiltInSubagentsEnabled, readSubagentSettings } from "./subagent-settings";
39
+ import { SubagentQueue } from "./subagent-queue";
40
+ import { addWorktree, removeWorktree } from "./worktree";
41
+ import { randomUUID } from "node:crypto";
42
+
43
+ interface HostSession {
44
+ readonly inner: AgentSessionLike;
45
+ readonly sessionFile: string;
46
+ readonly cwd: string;
47
+ isAlive(): boolean;
48
+ isRunning(): boolean;
49
+ waitUntilReady(): Promise<void>;
50
+ }
51
+
52
+ export interface SubagentRuntimeDependencies {
53
+ getSession(sessionId: string): HostSession | undefined;
54
+ registerSession(
55
+ inner: AgentSessionLike,
56
+ options?: { exactSystemPrompt?: string; chatOnly?: boolean },
57
+ ): void;
58
+ reopenSession(sessionId: string, sessionFile: string): Promise<HostSession>;
59
+ resolveSessionPath(sessionId: string): Promise<string | null>;
60
+ invalidateSessionList(): void;
61
+ isBuiltInSubagentsEnabled?(): boolean;
62
+ }
63
+
64
+ export interface SubagentController {
65
+ readonly extensionRuntime: SubagentExtensionRuntime;
66
+ get(sessionId: string): Promise<SubagentRunInfo | null>;
67
+ steer(sessionId: string, message: string): Promise<void>;
68
+ abort(sessionId: string): Promise<void>;
69
+ }
70
+
71
+ type StoredSubagentExecution = {
72
+ run: SubagentRunInfo;
73
+ completion: Promise<SubagentRunInfo>;
74
+ abortRequested: boolean;
75
+ cancelQueued?: () => boolean;
76
+ };
77
+
78
+ declare global {
79
+ var __piSubagentRuns: Map<string, StoredSubagentExecution> | undefined;
80
+ var __piSubagentQueue: SubagentQueue<SubagentRunInfo> | undefined;
81
+ }
82
+ const SUBAGENT_CONTEXT_LIMIT = 50_000;
83
+ const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
84
+
85
+ function getSubagentRuns(): Map<string, StoredSubagentExecution> {
86
+ if (!globalThis.__piSubagentRuns) globalThis.__piSubagentRuns = new Map();
87
+ return globalThis.__piSubagentRuns;
88
+ }
89
+
90
+ function getSubagentQueue(): SubagentQueue<SubagentRunInfo> {
91
+ if (!globalThis.__piSubagentQueue) globalThis.__piSubagentQueue = new SubagentQueue();
92
+ return globalThis.__piSubagentQueue;
93
+ }
94
+
95
+ function parseSubagentModel(runtime: ModelRuntime, value: string | undefined) {
96
+ if (!value?.trim()) return undefined;
97
+ const requested = value.trim();
98
+ const slash = requested.indexOf("/");
99
+ if (slash > 0) {
100
+ const provider = requested.slice(0, slash);
101
+ const modelId = requested.slice(slash + 1);
102
+ const model = runtime.getModel(provider, modelId);
103
+ if (!model) throw new Error(`Subagent model not found: ${requested}`);
104
+ return model;
105
+ }
106
+ const matches = runtime.getModels().filter((model) => model.id === requested);
107
+ if (matches.length === 1) return matches[0];
108
+ if (matches.length === 0) throw new Error(`Subagent model not found: ${requested}`);
109
+ throw new Error(`Subagent model is ambiguous; use provider/modelId: ${requested}`);
110
+ }
111
+
112
+ function parentContextText(parent: HostSession): string {
113
+ const messages = parent.inner.sessionManager.buildSessionContext().messages;
114
+ const serialized = JSON.stringify(messages);
115
+ if (serialized.length <= SUBAGENT_CONTEXT_LIMIT) return serialized;
116
+ return `${serialized.slice(0, SUBAGENT_CONTEXT_LIMIT)}\n[Parent context truncated]`;
117
+ }
118
+
119
+ async function cleanupWorktree(
120
+ parentCwd: string,
121
+ worktree: { path: string; branch: string } | undefined,
122
+ ): Promise<string | undefined> {
123
+ if (!worktree) return undefined;
124
+ try {
125
+ await removeWorktree(parentCwd, worktree.path);
126
+ return undefined;
127
+ } catch (error) {
128
+ return `Worktree retained at ${worktree.path}: ${error instanceof Error ? error.message : String(error)}`;
129
+ }
130
+ }
131
+
132
+ export function createSubagentController(
133
+ dependencies: SubagentRuntimeDependencies,
134
+ ): SubagentController {
135
+ async function start(request: StartSubagentRequest): Promise<SubagentExecution> {
136
+ const enabled = dependencies.isBuiltInSubagentsEnabled ?? isBuiltInSubagentsEnabled;
137
+ if (!enabled()) throw new Error("Pi Web built-in sub-agents are disabled");
138
+ const parentSessionId = request.parentContext.sessionManager.getSessionId();
139
+ const parent = dependencies.getSession(parentSessionId);
140
+ if (!parent?.isAlive()) throw new Error("Parent session is no longer available");
141
+ if (!parent.sessionFile) throw new Error("Parent session must be persisted before starting a subagent");
142
+
143
+ let isolatedWorktree: { path: string; branch: string } | undefined;
144
+ try {
145
+ const profile = resolveSubagentProfile(parent.cwd, request.profile);
146
+ if (!profile) throw new Error(`Unknown or disabled subagent profile: ${request.profile}`);
147
+
148
+ const runInBackground = request.runInBackground ?? profile.runInBackground;
149
+ const isolation = profile.isolation === "off" ? undefined : request.isolation ?? profile.isolation;
150
+ if (isolation === "worktree") {
151
+ isolatedWorktree = await addWorktree(parent.cwd, `pi-web-agent-${randomUUID()}`);
152
+ }
153
+ const childCwd = isolatedWorktree?.path ?? parent.cwd;
154
+ const inheritContext = request.inheritContext ?? profile.inheritContext;
155
+ const maxTurns = request.maxTurns ?? profile.maxTurns;
156
+ if (maxTurns !== undefined && (!Number.isFinite(maxTurns) || maxTurns < 0)) {
157
+ throw new Error("max_turns must be a non-negative number");
158
+ }
159
+ const turnLimit = maxTurns && maxTurns > 0 ? Math.floor(maxTurns) : undefined;
160
+ const thinking = request.thinking ?? profile.thinking ?? parent.inner.agent.state?.thinkingLevel;
161
+ if (thinking && !THINKING_LEVELS.has(thinking as ThinkingLevel)) {
162
+ throw new Error(`Invalid subagent thinking level: ${thinking}`);
163
+ }
164
+
165
+ const agentDir = getAgentDir();
166
+ const parentModelRuntime = (parent.inner as unknown as { modelRuntime: ModelRuntime }).modelRuntime;
167
+ const settingsManager = SettingsManager.create(childCwd, agentDir);
168
+ const inheritedParentContext = inheritContext
169
+ ? `The following is the active conversation context from the parent session. Use it only as background for the delegated task:\n${parentContextText(parent)}`
170
+ : undefined;
171
+ const inputFiles = loadSubagentInputFiles(parent.cwd, request.inputFiles ?? []);
172
+ const promptPlan = buildSubagentPromptPlan({
173
+ profileSystemPrompt: profile.systemPrompt,
174
+ tools: profile.tools,
175
+ loadSkills: profile.loadSkills,
176
+ loadExtensions: profile.loadExtensions,
177
+ promptMode: profile.promptMode,
178
+ task: appendSubagentInputFiles(request.task, inputFiles),
179
+ inheritedParentContext,
180
+ });
181
+ const { chatOnly, appendSystemPrompt, delegatedTask } = promptPlan;
182
+ if (!chatOnly) initTheme();
183
+ const services = await createAgentSessionServices({
184
+ cwd: childCwd,
185
+ agentDir,
186
+ modelRuntime: parentModelRuntime,
187
+ settingsManager,
188
+ resourceLoaderOptions: {
189
+ noExtensions: !profile.loadExtensions,
190
+ noSkills: !profile.loadSkills,
191
+ noPromptTemplates: true,
192
+ noThemes: true,
193
+ noContextFiles: true,
194
+ ...(chatOnly || promptPlan.exactSystemPrompt !== undefined
195
+ ? {
196
+ systemPrompt: " ",
197
+ systemPromptOverride: () => undefined,
198
+ }
199
+ : {}),
200
+ appendSystemPrompt,
201
+ },
202
+ ...((profile.loadExtensions || profile.loadSkills)
203
+ ? { resourceLoaderReloadOptions: projectTrustReloadOptions(childCwd, agentDir) }
204
+ : {}),
205
+ });
206
+
207
+ const extensionToolNames = profile.loadExtensions
208
+ ? profile.extensionTools?.length
209
+ ? selectSubagentExtensionTools(services.resourceLoader.getExtensions().extensions, profile.extensionTools)
210
+ : services.resourceLoader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()])
211
+ : [];
212
+ const activeTools = resolveShellTools(
213
+ withSubagentExtensionTools(profile.tools, extensionToolNames),
214
+ settingsManager.getDefaultTools(),
215
+ );
216
+
217
+ const sessionManager = isolatedWorktree
218
+ ? SessionManager.create(childCwd, undefined, { parentSession: parent.sessionFile })
219
+ : SessionManager.create(parent.cwd, undefined, { parentSession: parent.sessionFile });
220
+ const createdAt = new Date().toISOString();
221
+ const metadata: SubagentMetadata = {
222
+ version: 1,
223
+ parentSessionId,
224
+ parentSessionPath: parent.sessionFile,
225
+ parentToolCallId: request.parentToolCallId,
226
+ profile: profile.name,
227
+ description: request.description.trim() || profile.displayName,
228
+ task: request.task,
229
+ runInBackground,
230
+ createdAt,
231
+ resourceSnapshot: {
232
+ version: 1,
233
+ appendSystemPrompt: [...appendSystemPrompt],
234
+ tools: [...activeTools],
235
+ loadSkills: profile.loadSkills,
236
+ loadExtensions: profile.loadExtensions,
237
+ ...(promptPlan.exactSystemPrompt !== undefined ? { exactSystemPrompt: promptPlan.exactSystemPrompt } : {}),
238
+ },
239
+ ...(isolatedWorktree ? { worktreePath: isolatedWorktree.path, worktreeBranch: isolatedWorktree.branch } : {}),
240
+ };
241
+ sessionManager.appendCustomEntry(SUBAGENT_META_TYPE, metadata);
242
+ sessionManager.appendSessionInfo(metadata.description);
243
+
244
+ const requestedModel = parseSubagentModel(parentModelRuntime, request.model ?? profile.model);
245
+ const parentModel = parent.inner.model as ReturnType<ModelRuntime["getModel"]>;
246
+ const { session: inner } = await createAgentSessionFromServices({
247
+ services,
248
+ sessionManager,
249
+ model: requestedModel ?? parentModel,
250
+ ...(thinking ? { thinkingLevel: thinking as ThinkingLevel } : {}),
251
+ tools: activeTools,
252
+ excludeTools: [...SUBAGENT_CONTROL_TOOL_NAMES],
253
+ });
254
+ dependencies.registerSession(inner, {
255
+ ...(promptPlan.exactSystemPrompt !== undefined
256
+ ? { exactSystemPrompt: promptPlan.exactSystemPrompt }
257
+ : {}),
258
+ chatOnly,
259
+ });
260
+
261
+ const initialRun: SubagentRunInfo = {
262
+ sessionId: inner.sessionId,
263
+ sessionPath: inner.sessionFile ?? sessionManager.getSessionFile() ?? "",
264
+ parentSessionId,
265
+ parentToolCallId: request.parentToolCallId,
266
+ profile: profile.name,
267
+ description: metadata.description,
268
+ task: request.task,
269
+ runInBackground,
270
+ status: "queued",
271
+ createdAt,
272
+ ...(isolatedWorktree ? { worktreePath: isolatedWorktree.path, worktreeBranch: isolatedWorktree.branch } : {}),
273
+ };
274
+
275
+ let turnCount = 0;
276
+ let maxTurnsReached = false;
277
+ let softLimitReached = false;
278
+ const unsubscribeTurns = turnLimit
279
+ ? inner.subscribe((event) => {
280
+ if (event.type !== "turn_end") return;
281
+ turnCount += 1;
282
+ if (!softLimitReached && turnCount >= turnLimit) {
283
+ softLimitReached = true;
284
+ void inner.steer("You have reached your turn limit. Wrap up immediately and provide your final answer now.");
285
+ } else if (softLimitReached && turnCount >= turnLimit + 1) {
286
+ maxTurnsReached = true;
287
+ void inner.abort();
288
+ }
289
+ })
290
+ : () => {};
291
+ let resolveCompletion!: (run: SubagentRunInfo) => void;
292
+ const completion = new Promise<SubagentRunInfo>((resolve) => { resolveCompletion = resolve; });
293
+ const stored: StoredSubagentExecution = {
294
+ run: initialRun,
295
+ completion,
296
+ abortRequested: false,
297
+ };
298
+ getSubagentRuns().set(initialRun.sessionId, stored);
299
+ request.onUpdate?.(initialRun);
300
+ dependencies.invalidateSessionList();
301
+
302
+ const handleParentAbort = () => {
303
+ stored.abortRequested = true;
304
+ if (stored.run.status === "queued") stored.cancelQueued?.();
305
+ else void inner.abort();
306
+ };
307
+ if (!runInBackground) request.signal?.addEventListener("abort", handleParentAbort, { once: true });
308
+
309
+ const execute = async (): Promise<SubagentRunInfo> => {
310
+ if (stored.abortRequested) {
311
+ const result: SubagentRunInfo = { ...initialRun, status: "aborted", completedAt: new Date().toISOString() };
312
+ sessionManager.appendCustomEntry(SUBAGENT_RESULT_TYPE, { version: 1, status: "aborted", completedAt: result.completedAt });
313
+ await cleanupWorktree(parent.cwd, isolatedWorktree);
314
+ stored.run = result;
315
+ request.onUpdate?.(result);
316
+ getSubagentRuns().delete(initialRun.sessionId);
317
+ dependencies.invalidateSessionList();
318
+ return result;
319
+ }
320
+ stored.run = { ...stored.run, status: "running" };
321
+ sessionManager.appendCustomEntry(SUBAGENT_STATUS_TYPE, { version: 1, status: "running" });
322
+ request.onUpdate?.(stored.run);
323
+ dependencies.invalidateSessionList();
324
+ let result: SubagentRunInfo;
325
+ try {
326
+ await inner.prompt(delegatedTask, {
327
+ source: "rpc",
328
+ ...(chatOnly
329
+ ? {
330
+ preflightResult: (success: boolean) => {
331
+ if (success && inner.agent.state) {
332
+ inner.agent.state.systemPrompt = profile.systemPrompt;
333
+ }
334
+ },
335
+ }
336
+ : {}),
337
+ });
338
+ const text = inner.getLastAssistantText()?.trim();
339
+ const aborted = stored.abortRequested && !maxTurnsReached;
340
+ result = {
341
+ ...initialRun,
342
+ status: aborted ? "aborted" : "completed",
343
+ completedAt: new Date().toISOString(),
344
+ ...(text ? { result: text } : {}),
345
+ };
346
+ } catch (error) {
347
+ const text = inner.getLastAssistantText()?.trim();
348
+ const aborted = stored.abortRequested || request.signal?.aborted;
349
+ result = {
350
+ ...initialRun,
351
+ status: aborted ? "aborted" : maxTurnsReached ? "completed" : "failed",
352
+ completedAt: new Date().toISOString(),
353
+ ...(text ? { result: text } : {}),
354
+ ...(!aborted && !maxTurnsReached
355
+ ? { error: error instanceof Error ? error.message : String(error) }
356
+ : {}),
357
+ };
358
+ } finally {
359
+ unsubscribeTurns();
360
+ request.signal?.removeEventListener("abort", handleParentAbort);
361
+ }
362
+
363
+ const cleanupError = await cleanupWorktree(parent.cwd, isolatedWorktree);
364
+ if (cleanupError) result = { ...result, worktreeCleanupError: cleanupError };
365
+ const persisted: SubagentResultMetadata = {
366
+ version: 1,
367
+ status: result.status as SubagentResultMetadata["status"],
368
+ completedAt: result.completedAt!,
369
+ ...(result.result ? { result: result.result } : {}),
370
+ ...(result.error ? { error: result.error } : {}),
371
+ ...(result.worktreeCleanupError ? { worktreeCleanupError: result.worktreeCleanupError } : {}),
372
+ };
373
+ sessionManager.appendCustomEntry(SUBAGENT_RESULT_TYPE, persisted);
374
+ stored.run = result;
375
+ request.onUpdate?.(result);
376
+ getSubagentRuns().delete(initialRun.sessionId);
377
+ dependencies.invalidateSessionList();
378
+ return result;
379
+ };
380
+
381
+ const finishQueuedAbort = async () => {
382
+ if (stored.run.status !== "queued") return;
383
+ const result: SubagentRunInfo = { ...initialRun, status: "aborted", completedAt: new Date().toISOString() };
384
+ const cleanupError = await cleanupWorktree(parent.cwd, isolatedWorktree);
385
+ const finalResult = cleanupError ? { ...result, worktreeCleanupError: cleanupError } : result;
386
+ sessionManager.appendCustomEntry(SUBAGENT_RESULT_TYPE, { version: 1, status: "aborted", completedAt: finalResult.completedAt, ...(cleanupError ? { worktreeCleanupError: cleanupError } : {}) });
387
+ stored.run = finalResult;
388
+ request.onUpdate?.(finalResult);
389
+ getSubagentRuns().delete(initialRun.sessionId);
390
+ dependencies.invalidateSessionList();
391
+ resolveCompletion(finalResult);
392
+ };
393
+ const queued = getSubagentQueue().enqueue(
394
+ parentSessionId,
395
+ readSubagentSettings().maxConcurrent,
396
+ execute,
397
+ (state) => {
398
+ if (state === "queued") {
399
+ sessionManager.appendCustomEntry(SUBAGENT_STATUS_TYPE, { version: 1, status: "queued" });
400
+ }
401
+ request.onUpdate?.({ ...stored.run, status: state });
402
+ stored.run = { ...stored.run, status: state };
403
+ dependencies.invalidateSessionList();
404
+ },
405
+ finishQueuedAbort,
406
+ );
407
+ stored.cancelQueued = queued.cancel;
408
+ void queued.promise.then(resolveCompletion, (error) => {
409
+ resolveCompletion({ ...initialRun, status: "failed", completedAt: new Date().toISOString(), error: error instanceof Error ? error.message : String(error) });
410
+ });
411
+
412
+ return { run: stored.run, completion: stored.completion };
413
+ } catch (error) {
414
+ if (isolatedWorktree) {
415
+ try { await removeWorktree(parent.cwd, isolatedWorktree.path); } catch { /* preserve setup failure and avoid force deletion */ }
416
+ }
417
+ throw error;
418
+ }
419
+ }
420
+
421
+ async function resume(request: ResumeSubagentRequest): Promise<SubagentExecution> {
422
+ const enabled = dependencies.isBuiltInSubagentsEnabled ?? isBuiltInSubagentsEnabled;
423
+ if (!enabled()) throw new Error("Pi Web built-in sub-agents are disabled");
424
+ const parentSessionId = request.parentContext.sessionManager.getSessionId();
425
+ const existing = await get(request.sessionId);
426
+ if (!existing) throw new Error(`Subagent not found: ${request.sessionId}`);
427
+ if (existing.parentSessionId !== parentSessionId) throw new Error("Subagent does not belong to this parent session");
428
+ if (existing.status === "running" || existing.status === "queued") throw new Error("Subagent is already running");
429
+ const parent = dependencies.getSession(parentSessionId);
430
+ if (!parent?.isAlive()) throw new Error("Parent session is no longer available");
431
+ const sessionPath = existing.sessionPath || await dependencies.resolveSessionPath(request.sessionId);
432
+ if (!sessionPath) throw new Error(`Subagent session file not found: ${request.sessionId}`);
433
+ let wrapper = dependencies.getSession(request.sessionId);
434
+ if (!wrapper?.isAlive()) wrapper = await dependencies.reopenSession(request.sessionId, sessionPath);
435
+ if (!wrapper.isAlive()) throw new Error("Subagent session is no longer available");
436
+ if (wrapper.isRunning()) throw new Error("Subagent is already running");
437
+
438
+ const runInBackground = request.runInBackground ?? existing.runInBackground;
439
+ const initialRun: SubagentRunInfo = {
440
+ ...existing,
441
+ parentToolCallId: request.parentToolCallId,
442
+ task: request.task,
443
+ description: request.description.trim() || existing.description,
444
+ runInBackground,
445
+ status: "queued",
446
+ completedAt: undefined,
447
+ result: undefined,
448
+ error: undefined,
449
+ };
450
+ const manager = wrapper.inner.sessionManager;
451
+ let resolveCompletion!: (run: SubagentRunInfo) => void;
452
+ const completion = new Promise<SubagentRunInfo>((resolve) => { resolveCompletion = resolve; });
453
+ const stored: StoredSubagentExecution = { run: initialRun, completion, abortRequested: false };
454
+ getSubagentRuns().set(request.sessionId, stored);
455
+ request.onUpdate?.(initialRun);
456
+ dependencies.invalidateSessionList();
457
+ const handleParentAbort = () => {
458
+ stored.abortRequested = true;
459
+ if (stored.run.status === "queued") stored.cancelQueued?.();
460
+ else void wrapper!.inner.abort();
461
+ };
462
+ if (!runInBackground) request.signal?.addEventListener("abort", handleParentAbort, { once: true });
463
+
464
+ const execute = async (): Promise<SubagentRunInfo> => {
465
+ if (stored.abortRequested) {
466
+ const result: SubagentRunInfo = { ...initialRun, status: "aborted", completedAt: new Date().toISOString() };
467
+ manager.appendCustomEntry(SUBAGENT_RESULT_TYPE, { version: 1, status: "aborted", completedAt: result.completedAt });
468
+ stored.run = result;
469
+ getSubagentRuns().delete(request.sessionId);
470
+ resolveCompletion(result);
471
+ return result;
472
+ }
473
+ stored.run = { ...stored.run, status: "running" };
474
+ manager.appendCustomEntry(SUBAGENT_STATUS_TYPE, { version: 1, status: "running" });
475
+ request.onUpdate?.(stored.run);
476
+ let result: SubagentRunInfo;
477
+ try {
478
+ await wrapper!.inner.prompt(request.task, { source: "rpc" });
479
+ const text = wrapper!.inner.getLastAssistantText()?.trim();
480
+ result = { ...initialRun, status: stored.abortRequested ? "aborted" : "completed", completedAt: new Date().toISOString(), ...(text ? { result: text } : {}) };
481
+ } catch (error) {
482
+ result = {
483
+ ...initialRun,
484
+ status: stored.abortRequested || request.signal?.aborted ? "aborted" : "failed",
485
+ completedAt: new Date().toISOString(),
486
+ ...(!stored.abortRequested && !request.signal?.aborted ? { error: error instanceof Error ? error.message : String(error) } : {}),
487
+ };
488
+ } finally {
489
+ request.signal?.removeEventListener("abort", handleParentAbort);
490
+ }
491
+ manager.appendCustomEntry(SUBAGENT_RESULT_TYPE, {
492
+ version: 1,
493
+ status: result.status as "completed" | "failed" | "aborted",
494
+ completedAt: result.completedAt!,
495
+ ...(result.result ? { result: result.result } : {}),
496
+ ...(result.error ? { error: result.error } : {}),
497
+ });
498
+ stored.run = result;
499
+ request.onUpdate?.(result);
500
+ getSubagentRuns().delete(request.sessionId);
501
+ dependencies.invalidateSessionList();
502
+ return result;
503
+ };
504
+ const finishQueuedAbort = () => {
505
+ if (stored.run.status !== "queued") return;
506
+ const result: SubagentRunInfo = { ...initialRun, status: "aborted", completedAt: new Date().toISOString() };
507
+ manager.appendCustomEntry(SUBAGENT_RESULT_TYPE, { version: 1, status: "aborted", completedAt: result.completedAt });
508
+ stored.run = result;
509
+ request.onUpdate?.(result);
510
+ getSubagentRuns().delete(request.sessionId);
511
+ dependencies.invalidateSessionList();
512
+ resolveCompletion(result);
513
+ };
514
+ const queued = getSubagentQueue().enqueue(parentSessionId, readSubagentSettings().maxConcurrent, execute, (state) => {
515
+ if (state === "queued") manager.appendCustomEntry(SUBAGENT_STATUS_TYPE, { version: 1, status: "queued" });
516
+ stored.run = { ...stored.run, status: state };
517
+ request.onUpdate?.(stored.run);
518
+ dependencies.invalidateSessionList();
519
+ }, finishQueuedAbort);
520
+ stored.cancelQueued = queued.cancel;
521
+ void queued.promise.then(resolveCompletion, (error) => resolveCompletion({ ...initialRun, status: "failed", completedAt: new Date().toISOString(), error: error instanceof Error ? error.message : String(error) }));
522
+ return { run: stored.run, completion };
523
+ }
524
+
525
+ async function get(sessionId: string): Promise<SubagentRunInfo | null> {
526
+ const stored = getSubagentRuns().get(sessionId);
527
+ if (stored) return stored.run;
528
+ const wrapper = dependencies.getSession(sessionId);
529
+ if (wrapper?.isAlive()) {
530
+ const run = readSubagentRun(
531
+ wrapper.inner.sessionManager.getEntries() as unknown as SessionEntry[],
532
+ sessionId,
533
+ wrapper.sessionFile,
534
+ );
535
+ if (run && wrapper.isRunning()) return { ...run, status: "running" };
536
+ if (run) return run;
537
+ }
538
+ const sessionPath = await dependencies.resolveSessionPath(sessionId);
539
+ if (!sessionPath) return null;
540
+ const manager = SessionManager.open(sessionPath);
541
+ return readSubagentRun(manager.getEntries() as unknown as SessionEntry[], sessionId, sessionPath);
542
+ }
543
+
544
+ async function steer(sessionId: string, message: string): Promise<void> {
545
+ const wrapper = dependencies.getSession(sessionId);
546
+ if (!wrapper?.isAlive() || !wrapper.isRunning()) throw new Error("Subagent is not running");
547
+ if (!message.trim()) throw new Error("Steering message is required");
548
+ await wrapper.inner.steer(message.trim());
549
+ }
550
+
551
+ async function notifyParent(run: SubagentRunInfo): Promise<void> {
552
+ let parent = dependencies.getSession(run.parentSessionId);
553
+ if (!parent?.isAlive()) {
554
+ const sessionFile = await dependencies.resolveSessionPath(run.parentSessionId);
555
+ if (!sessionFile) throw new Error(`Parent session not found: ${run.parentSessionId}`);
556
+ parent = await dependencies.reopenSession(run.parentSessionId, sessionFile);
557
+ }
558
+ await parent.waitUntilReady();
559
+ if (!parent.isAlive()) throw new Error(`Parent session is no longer available: ${run.parentSessionId}`);
560
+ await parent.inner.sendCustomMessage({
561
+ customType: "pi-web:subagent-notification",
562
+ content: subagentFinalText(run),
563
+ display: true,
564
+ details: subagentToolDetails(run),
565
+ }, { deliverAs: "followUp", triggerTurn: true });
566
+ }
567
+
568
+ async function abort(sessionId: string): Promise<void> {
569
+ const wrapper = dependencies.getSession(sessionId);
570
+ const stored = getSubagentRuns().get(sessionId);
571
+ if (stored?.run.status === "queued") {
572
+ stored.abortRequested = true;
573
+ if (!stored.cancelQueued?.()) throw new Error("Subagent is no longer queued");
574
+ return;
575
+ }
576
+ if (!wrapper?.isAlive() || !wrapper.isRunning()) throw new Error("Subagent is not running");
577
+ if (stored) stored.abortRequested = true;
578
+ await wrapper.inner.abort();
579
+ }
580
+
581
+ return {
582
+ extensionRuntime: { start, resume, get, steer, notifyParent },
583
+ get,
584
+ steer,
585
+ abort,
586
+ };
587
+ }
@@ -0,0 +1,50 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { createJiti } from "jiti";
7
+
8
+ const {
9
+ isBuiltInSubagentsEnabled,
10
+ readSubagentSettings,
11
+ writeBuiltInSubagentsEnabled,
12
+ } = await createJiti(import.meta.url).import("./subagent-settings.ts");
13
+
14
+ test("subagent settings default the built-in extension to disabled", async (t) => {
15
+ const root = await mkdtemp(join(tmpdir(), "pi-web-subagent-settings-"));
16
+ t.after(() => rm(root, { recursive: true, force: true }));
17
+ const settingsPath = join(root, "agents", "settings.json");
18
+
19
+ assert.deepEqual(readSubagentSettings(settingsPath), { builtInEnabled: false });
20
+ assert.equal(isBuiltInSubagentsEnabled(settingsPath), false);
21
+ });
22
+
23
+ test("subagent settings persist both states and preserve unrelated fields", async (t) => {
24
+ const root = await mkdtemp(join(tmpdir(), "pi-web-subagent-settings-"));
25
+ t.after(() => rm(root, { recursive: true, force: true }));
26
+ const settingsPath = join(root, "agents", "settings.json");
27
+
28
+ writeBuiltInSubagentsEnabled(true, settingsPath);
29
+ assert.deepEqual(readSubagentSettings(settingsPath), { builtInEnabled: true });
30
+ assert.equal(isBuiltInSubagentsEnabled(settingsPath), true);
31
+ const first = JSON.parse(await readFile(settingsPath, "utf8"));
32
+ assert.deepEqual(first, { version: 1, builtInEnabled: true });
33
+
34
+ await writeFile(settingsPath, JSON.stringify({ ...first, futureSetting: 3 }));
35
+ writeBuiltInSubagentsEnabled(false, settingsPath);
36
+ const second = JSON.parse(await readFile(settingsPath, "utf8"));
37
+ assert.deepEqual(second, { version: 1, builtInEnabled: false, futureSetting: 3 });
38
+ });
39
+
40
+ test("damaged settings fail closed and are not overwritten", async (t) => {
41
+ const root = await mkdtemp(join(tmpdir(), "pi-web-subagent-settings-"));
42
+ t.after(() => rm(root, { recursive: true, force: true }));
43
+ const settingsPath = join(root, "settings.json");
44
+ await writeFile(settingsPath, "{");
45
+
46
+ assert.equal(isBuiltInSubagentsEnabled(settingsPath), false);
47
+ assert.throws(() => readSubagentSettings(settingsPath));
48
+ assert.throws(() => writeBuiltInSubagentsEnabled(true, settingsPath));
49
+ assert.equal(await readFile(settingsPath, "utf8"), "{");
50
+ });