@baique/pi-web-sky 0.1.55 → 0.1.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -4
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +2 -2
  5. package/.next/react-loadable-manifest.json +2 -2
  6. package/.next/required-server-files.js +3 -2
  7. package/.next/required-server-files.json +3 -2
  8. package/.next/routes-manifest.json +28 -0
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +4 -3
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -3
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +4 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -2
  23. package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  25. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/agent/new/route.js +1 -1
  29. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  30. package/.next/server/app/api/agent/running/route.js +1 -1
  31. package/.next/server/app/api/agent/running/route.js.nft.json +1 -1
  32. package/.next/server/app/api/app-update/route.js +1 -1
  33. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  34. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  35. package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
  36. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  37. package/.next/server/app/api/auth/providers/route.js +1 -1
  38. package/.next/server/app/api/board-assets/[...path]/route.js +2 -2
  39. package/.next/server/app/api/board-assets/route.js +2 -2
  40. package/.next/server/app/api/boards/[id]/add-session/route.js +1 -0
  41. package/.next/server/app/api/boards/[id]/add-session/route.js.nft.json +1 -0
  42. package/.next/server/app/api/boards/[id]/add-session/route_client-reference-manifest.js +1 -0
  43. package/.next/server/app/api/boards/[id]/reconcile/route.js +1 -1
  44. package/.next/server/app/api/boards/[id]/route.js +1 -1
  45. package/.next/server/app/api/boards/reorder/route.js +2 -2
  46. package/.next/server/app/api/boards/route.js +1 -1
  47. package/.next/server/app/api/cwd/browse/route.js +1 -1
  48. package/.next/server/app/api/cwd/validate/route.js +2 -2
  49. package/.next/server/app/api/default-cwd/route.js +2 -2
  50. package/.next/server/app/api/file-index/route.js +2 -2
  51. package/.next/server/app/api/files/[...path]/route.js +3 -3
  52. package/.next/server/app/api/git/diff/route.js +1 -1
  53. package/.next/server/app/api/git/status/route.js +1 -1
  54. package/.next/server/app/api/home/route.js +1 -1
  55. package/.next/server/app/api/mcp/route.js +2 -2
  56. package/.next/server/app/api/models/route.js +2 -2
  57. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  58. package/.next/server/app/api/models-config/discover/route.js +1 -1
  59. package/.next/server/app/api/models-config/route.js +1 -1
  60. package/.next/server/app/api/models-config/test/route.js +1 -1
  61. package/.next/server/app/api/plugins/route.js +2 -2
  62. package/.next/server/app/api/project-trust/route.js +1 -1
  63. package/.next/server/app/api/project-trust/route.js.nft.json +1 -1
  64. package/.next/server/app/api/quota/route.js +1 -1
  65. package/.next/server/app/api/search/route.js +3 -3
  66. package/.next/server/app/api/sessions/[id]/auto-name/route.js +2 -2
  67. package/.next/server/app/api/sessions/[id]/auto-name/route.js.nft.json +1 -1
  68. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  69. package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +1 -1
  70. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
  71. package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
  72. package/.next/server/app/api/sessions/[id]/minimap/route.js +1 -1
  73. package/.next/server/app/api/sessions/[id]/minimap/route.js.nft.json +1 -1
  74. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  75. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  76. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  77. package/.next/server/app/api/sessions/[id]/state/route.js.nft.json +1 -1
  78. package/.next/server/app/api/sessions/[id]/todos/route.js +1 -1
  79. package/.next/server/app/api/sessions/[id]/todos/route.js.nft.json +1 -1
  80. package/.next/server/app/api/sessions/route.js +1 -1
  81. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  82. package/.next/server/app/api/sessions/summary/route.js +2 -2
  83. package/.next/server/app/api/skills/check/route.js +1 -1
  84. package/.next/server/app/api/skills/install/route.js +2 -2
  85. package/.next/server/app/api/skills/route.js +1 -1
  86. package/.next/server/app/api/skills/search/route.js +1 -1
  87. package/.next/server/app/api/skills/update/route.js +1 -1
  88. package/.next/server/app/api/subagents/[id]/route.js +1 -0
  89. package/.next/server/app/api/subagents/[id]/route.js.nft.json +1 -0
  90. package/.next/server/app/api/subagents/[id]/route_client-reference-manifest.js +1 -0
  91. package/.next/server/app/api/subagents/profiles/route.js +30 -0
  92. package/.next/server/app/api/subagents/profiles/route.js.nft.json +1 -0
  93. package/.next/server/app/api/subagents/profiles/route_client-reference-manifest.js +1 -0
  94. package/.next/server/app/api/subagents/settings/route.js +1 -0
  95. package/.next/server/app/api/subagents/settings/route.js.nft.json +1 -0
  96. package/.next/server/app/api/subagents/settings/route_client-reference-manifest.js +1 -0
  97. package/.next/server/app/api/task-card-questions/[id]/answer/route.js +2 -2
  98. package/.next/server/app/api/task-card-questions/route.js +2 -2
  99. package/.next/server/app/api/task-cards/[id]/route.js +1 -1
  100. package/.next/server/app/api/task-cards/route.js +1 -1
  101. package/.next/server/app/api/task-cards/unbind/route.js +3 -3
  102. package/.next/server/app/api/task-scheduler/status/route.js +1 -1
  103. package/.next/server/app/api/task-scheduler/status/route.js.nft.json +1 -1
  104. package/.next/server/app/api/tasks/[id]/assign-session/route.js +2 -12
  105. package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
  106. package/.next/server/app/api/tasks/[id]/route.js +1 -1
  107. package/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -1
  108. package/.next/server/app/api/tasks/reorder/route.js +2 -12
  109. package/.next/server/app/api/tasks/route.js +2 -12
  110. package/.next/server/app/api/terminal/[id]/events/route.js +2 -2
  111. package/.next/server/app/api/terminal/[id]/route.js +2 -2
  112. package/.next/server/app/api/terminal/route.js +2 -2
  113. package/.next/server/app/api/worktrees/route.js +2 -2
  114. package/.next/server/app/api/yjs-version/route.js +1 -1
  115. package/.next/server/app/favicon.ico/route.js +1 -1
  116. package/.next/server/app/index.html +1 -1
  117. package/.next/server/app/index.rsc +6 -5
  118. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -5
  119. package/.next/server/app/index.segments/_full.segment.rsc +6 -5
  120. package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
  121. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  122. package/.next/server/app/page.js +175 -8
  123. package/.next/server/app/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app-paths-manifest.json +8 -4
  125. package/.next/server/chunks/1937.js +14 -13
  126. package/.next/server/chunks/2524.js +2 -2
  127. package/.next/server/chunks/3379.js +1 -1
  128. package/.next/server/chunks/3500.js +1 -1
  129. package/.next/server/chunks/402.js +28 -7
  130. package/.next/server/chunks/426.js +14 -0
  131. package/.next/server/chunks/4367.js +1 -1
  132. package/.next/server/chunks/4457.js +3 -13
  133. package/.next/server/chunks/4980.js +1 -1
  134. package/.next/server/chunks/542.js +23 -4
  135. package/.next/server/chunks/5794.js +2 -2
  136. package/.next/server/chunks/5940.js +1 -1
  137. package/.next/server/chunks/6337.js +1 -1
  138. package/.next/server/chunks/6429.js +15 -2
  139. package/.next/server/chunks/6525.js +25 -4
  140. package/.next/server/chunks/6583.js +12 -11
  141. package/.next/server/chunks/7048.js +6 -0
  142. package/.next/server/instrumentation.js +1 -1
  143. package/.next/server/middleware-build-manifest.js +1 -1
  144. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  145. package/.next/server/middleware.js +3 -3
  146. package/.next/server/pages/404.html +1 -1
  147. package/.next/server/pages/500.html +1 -1
  148. package/.next/static/MPBMmQqmDG8zo8tM_bkDy/_buildManifest.js +1 -0
  149. package/.next/static/chunks/752.bafff304e6b00987.js +1 -0
  150. package/.next/static/chunks/8088.39866dc5f1d15868.js +1 -0
  151. package/.next/static/chunks/app/_global-error/page-ad6e695deb9de65c.js +1 -0
  152. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-ad6e695deb9de65c.js +1 -0
  153. package/.next/static/chunks/app/api/agent/[id]/events/route-ad6e695deb9de65c.js +1 -0
  154. package/.next/static/chunks/app/api/agent/[id]/route-ad6e695deb9de65c.js +1 -0
  155. package/.next/static/chunks/app/api/agent/new/route-ad6e695deb9de65c.js +1 -0
  156. package/.next/static/chunks/app/api/agent/running/route-ad6e695deb9de65c.js +1 -0
  157. package/.next/static/chunks/app/api/app-update/route-ad6e695deb9de65c.js +1 -0
  158. package/.next/static/chunks/app/api/auth/all-providers/route-ad6e695deb9de65c.js +1 -0
  159. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-ad6e695deb9de65c.js +1 -0
  160. package/.next/static/chunks/app/api/auth/login/[provider]/route-ad6e695deb9de65c.js +1 -0
  161. package/.next/static/chunks/app/api/auth/logout/[provider]/route-ad6e695deb9de65c.js +1 -0
  162. package/.next/static/chunks/app/api/auth/providers/route-ad6e695deb9de65c.js +1 -0
  163. package/.next/static/chunks/app/api/board-assets/[...path]/route-ad6e695deb9de65c.js +1 -0
  164. package/.next/static/chunks/app/api/board-assets/route-ad6e695deb9de65c.js +1 -0
  165. package/.next/static/chunks/app/api/boards/[id]/add-session/route-ad6e695deb9de65c.js +1 -0
  166. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-ad6e695deb9de65c.js +1 -0
  167. package/.next/static/chunks/app/api/boards/[id]/route-ad6e695deb9de65c.js +1 -0
  168. package/.next/static/chunks/app/api/boards/reorder/route-ad6e695deb9de65c.js +1 -0
  169. package/.next/static/chunks/app/api/boards/route-ad6e695deb9de65c.js +1 -0
  170. package/.next/static/chunks/app/api/cwd/browse/route-ad6e695deb9de65c.js +1 -0
  171. package/.next/static/chunks/app/api/cwd/validate/route-ad6e695deb9de65c.js +1 -0
  172. package/.next/static/chunks/app/api/default-cwd/route-ad6e695deb9de65c.js +1 -0
  173. package/.next/static/chunks/app/api/file-index/route-ad6e695deb9de65c.js +1 -0
  174. package/.next/static/chunks/app/api/files/[...path]/route-ad6e695deb9de65c.js +1 -0
  175. package/.next/static/chunks/app/api/git/diff/route-ad6e695deb9de65c.js +1 -0
  176. package/.next/static/chunks/app/api/git/status/route-ad6e695deb9de65c.js +1 -0
  177. package/.next/static/chunks/app/api/home/route-ad6e695deb9de65c.js +1 -0
  178. package/.next/static/chunks/app/api/mcp/route-ad6e695deb9de65c.js +1 -0
  179. package/.next/static/chunks/app/api/models/route-ad6e695deb9de65c.js +1 -0
  180. package/.next/static/chunks/app/api/models-config/catalog/route-ad6e695deb9de65c.js +1 -0
  181. package/.next/static/chunks/app/api/models-config/discover/route-ad6e695deb9de65c.js +1 -0
  182. package/.next/static/chunks/app/api/models-config/route-ad6e695deb9de65c.js +1 -0
  183. package/.next/static/chunks/app/api/models-config/test/route-ad6e695deb9de65c.js +1 -0
  184. package/.next/static/chunks/app/api/plugins/route-ad6e695deb9de65c.js +1 -0
  185. package/.next/static/chunks/app/api/project-trust/route-ad6e695deb9de65c.js +1 -0
  186. package/.next/static/chunks/app/api/quota/route-ad6e695deb9de65c.js +1 -0
  187. package/.next/static/chunks/app/api/search/route-ad6e695deb9de65c.js +1 -0
  188. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-ad6e695deb9de65c.js +1 -0
  189. package/.next/static/chunks/app/api/sessions/[id]/context/route-ad6e695deb9de65c.js +1 -0
  190. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-ad6e695deb9de65c.js +1 -0
  191. package/.next/static/chunks/app/api/sessions/[id]/export/route-ad6e695deb9de65c.js +1 -0
  192. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-ad6e695deb9de65c.js +1 -0
  193. package/.next/static/chunks/app/api/sessions/[id]/route-ad6e695deb9de65c.js +1 -0
  194. package/.next/static/chunks/app/api/sessions/[id]/state/route-ad6e695deb9de65c.js +1 -0
  195. package/.next/static/chunks/app/api/sessions/[id]/todos/route-ad6e695deb9de65c.js +1 -0
  196. package/.next/static/chunks/app/api/sessions/route-ad6e695deb9de65c.js +1 -0
  197. package/.next/static/chunks/app/api/sessions/summary/route-ad6e695deb9de65c.js +1 -0
  198. package/.next/static/chunks/app/api/skills/check/route-ad6e695deb9de65c.js +1 -0
  199. package/.next/static/chunks/app/api/skills/install/route-ad6e695deb9de65c.js +1 -0
  200. package/.next/static/chunks/app/api/skills/route-ad6e695deb9de65c.js +1 -0
  201. package/.next/static/chunks/app/api/skills/search/route-ad6e695deb9de65c.js +1 -0
  202. package/.next/static/chunks/app/api/skills/update/route-ad6e695deb9de65c.js +1 -0
  203. package/.next/static/chunks/app/api/subagents/[id]/route-ad6e695deb9de65c.js +1 -0
  204. package/.next/static/chunks/app/api/subagents/profiles/route-ad6e695deb9de65c.js +1 -0
  205. package/.next/static/chunks/app/api/subagents/settings/route-ad6e695deb9de65c.js +1 -0
  206. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-ad6e695deb9de65c.js +1 -0
  207. package/.next/static/chunks/app/api/task-card-questions/route-ad6e695deb9de65c.js +1 -0
  208. package/.next/static/chunks/app/api/task-cards/[id]/route-ad6e695deb9de65c.js +1 -0
  209. package/.next/static/chunks/app/api/task-cards/route-ad6e695deb9de65c.js +1 -0
  210. package/.next/static/chunks/app/api/task-cards/unbind/route-ad6e695deb9de65c.js +1 -0
  211. package/.next/static/chunks/app/api/task-scheduler/status/route-ad6e695deb9de65c.js +1 -0
  212. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-ad6e695deb9de65c.js +1 -0
  213. package/.next/static/chunks/app/api/tasks/[id]/board/route-ad6e695deb9de65c.js +1 -0
  214. package/.next/static/chunks/app/api/tasks/[id]/route-ad6e695deb9de65c.js +1 -0
  215. package/.next/static/chunks/app/api/tasks/reorder/route-ad6e695deb9de65c.js +1 -0
  216. package/.next/static/chunks/app/api/tasks/route-ad6e695deb9de65c.js +1 -0
  217. package/.next/static/chunks/app/api/terminal/[id]/events/route-ad6e695deb9de65c.js +1 -0
  218. package/.next/static/chunks/app/api/terminal/[id]/route-ad6e695deb9de65c.js +1 -0
  219. package/.next/static/chunks/app/api/terminal/route-ad6e695deb9de65c.js +1 -0
  220. package/.next/static/chunks/app/api/worktrees/route-ad6e695deb9de65c.js +1 -0
  221. package/.next/static/chunks/app/api/yjs-version/route-ad6e695deb9de65c.js +1 -0
  222. package/.next/static/chunks/app/layout-fae2b226b3c78448.js +1 -0
  223. package/.next/static/chunks/app/manifest.webmanifest/route-ad6e695deb9de65c.js +1 -0
  224. package/.next/static/chunks/app/page-d5af3bd535589daf.js +256 -0
  225. package/.next/static/chunks/next/dist/client/components/builtin/app-error-ad6e695deb9de65c.js +1 -0
  226. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-ad6e695deb9de65c.js +1 -0
  227. package/.next/static/chunks/next/dist/client/components/builtin/not-found-ad6e695deb9de65c.js +1 -0
  228. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-ad6e695deb9de65c.js +1 -0
  229. package/.next/static/chunks/{webpack-b0effde51c1651b8.js → webpack-1ce35e1249cfd931.js} +1 -1
  230. package/.next/static/css/bb9361c7f832a89d.css +1 -0
  231. package/.next/static/css/c7d127e07b1a786c.css +1 -0
  232. package/.next/trace +8 -7
  233. package/.next/trace-build +1 -1
  234. package/.next/types/app/api/boards/[id]/add-session/route.ts +353 -0
  235. package/.next/types/app/api/subagents/[id]/route.ts +353 -0
  236. package/.next/types/app/api/subagents/profiles/route.ts +353 -0
  237. package/.next/types/app/api/subagents/settings/route.ts +353 -0
  238. package/.next/types/routes.d.ts +5 -1
  239. package/.next/types/validator.ts +36 -0
  240. package/lib/api-types.ts +10 -0
  241. package/lib/board-reconcile.test.mjs +46 -8
  242. package/lib/board-reconcile.ts +57 -1
  243. package/lib/board-store.test.mjs +2 -2
  244. package/lib/chat-lazy-load.test.mjs +0 -62
  245. package/lib/chat-lazy-load.ts +0 -13
  246. package/lib/chat-only.ts +23 -0
  247. package/lib/draft-stash.ts +4 -5
  248. package/lib/file-types.ts +16 -1
  249. package/lib/i18n/messages/en.ts +91 -0
  250. package/lib/i18n/messages/zh-CN.ts +91 -0
  251. package/lib/id.test.mjs +38 -0
  252. package/lib/id.ts +17 -0
  253. package/lib/ime-guard.test.mjs +52 -0
  254. package/lib/ime-guard.ts +54 -0
  255. package/lib/message-text.ts +24 -0
  256. package/lib/models-cache.ts +1 -1
  257. package/lib/pi-types.ts +19 -1
  258. package/lib/pin-store.ts +85 -0
  259. package/lib/powershell-settings.ts +106 -0
  260. package/lib/quoted-selection.ts +4 -0
  261. package/lib/rpc-manager.activity.test.mjs +223 -0
  262. package/lib/rpc-manager.test.mjs +22 -0
  263. package/lib/rpc-manager.title.test.mjs +152 -0
  264. package/lib/rpc-manager.ts +301 -28
  265. package/lib/session-activity.test.mjs +123 -0
  266. package/lib/session-activity.ts +77 -0
  267. package/lib/session-delete.test.mjs +45 -41
  268. package/lib/session-delete.ts +8 -34
  269. package/lib/session-family.test.mjs +49 -0
  270. package/lib/session-family.ts +80 -0
  271. package/lib/session-index-scanner.test.mjs +364 -2
  272. package/lib/session-index-scanner.ts +199 -55
  273. package/lib/session-reader.noscan.test.mjs +371 -0
  274. package/lib/session-reader.project.test.mjs +30 -52
  275. package/lib/session-reader.test.mjs +2 -65
  276. package/lib/session-reader.ts +177 -213
  277. package/lib/session-scanner.test.mjs +151 -38
  278. package/lib/session-scanner.ts +140 -61
  279. package/lib/session-search.ts +6 -5
  280. package/lib/session-time-group.test.mjs +38 -0
  281. package/lib/session-time-group.ts +55 -0
  282. package/lib/settings-navigation.ts +124 -0
  283. package/lib/sqlite-db.test.mjs +26 -0
  284. package/lib/sqlite-db.ts +10 -1
  285. package/lib/subagent-extension.test.mjs +424 -0
  286. package/lib/subagent-extension.ts +309 -0
  287. package/lib/subagent-input.test.mjs +63 -0
  288. package/lib/subagent-input.ts +71 -0
  289. package/lib/subagent-profile-precedence.ts +20 -0
  290. package/lib/subagent-prompt.test.mjs +64 -0
  291. package/lib/subagent-prompt.ts +31 -0
  292. package/lib/subagent-queue.test.mjs +35 -0
  293. package/lib/subagent-queue.ts +74 -0
  294. package/lib/subagent-runtime.test.mjs +150 -0
  295. package/lib/subagent-runtime.ts +629 -0
  296. package/lib/subagent-session-title.test.mjs +120 -0
  297. package/lib/subagent-settings.test.mjs +50 -0
  298. package/lib/subagent-settings.ts +93 -0
  299. package/lib/subagents.test.mjs +601 -0
  300. package/lib/subagents.ts +602 -0
  301. package/lib/task-scheduler.test.mjs +27 -0
  302. package/lib/task-scheduler.ts +8 -1
  303. package/lib/task-store.activity.test.mjs +141 -0
  304. package/lib/task-store.meta-row.test.mjs +25 -5
  305. package/lib/task-store.partial-row.test.mjs +198 -0
  306. package/lib/task-store.subtree.test.mjs +172 -0
  307. package/lib/task-store.test.mjs +46 -20
  308. package/lib/task-store.ts +344 -68
  309. package/lib/tool-presets.ts +4 -0
  310. package/lib/types.ts +19 -0
  311. package/lib/worktree-isolation.test.mjs +51 -0
  312. package/next.config.ts +3 -1
  313. package/package.json +1 -1
  314. package/.next/static/bsW3fla0-BlBHVMKtAM5I/_buildManifest.js +0 -1
  315. package/.next/static/chunks/752.0ad92735d5f38143.js +0 -1
  316. package/.next/static/chunks/8088.53d9cdaf15634d4b.js +0 -1
  317. package/.next/static/chunks/app/_global-error/page-2d5f6296c7e693c6.js +0 -1
  318. package/.next/static/chunks/app/api/agent/[id]/bash-output/route-2d5f6296c7e693c6.js +0 -1
  319. package/.next/static/chunks/app/api/agent/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  320. package/.next/static/chunks/app/api/agent/[id]/route-2d5f6296c7e693c6.js +0 -1
  321. package/.next/static/chunks/app/api/agent/new/route-2d5f6296c7e693c6.js +0 -1
  322. package/.next/static/chunks/app/api/agent/running/route-2d5f6296c7e693c6.js +0 -1
  323. package/.next/static/chunks/app/api/app-update/route-2d5f6296c7e693c6.js +0 -1
  324. package/.next/static/chunks/app/api/auth/all-providers/route-2d5f6296c7e693c6.js +0 -1
  325. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-2d5f6296c7e693c6.js +0 -1
  326. package/.next/static/chunks/app/api/auth/login/[provider]/route-2d5f6296c7e693c6.js +0 -1
  327. package/.next/static/chunks/app/api/auth/logout/[provider]/route-2d5f6296c7e693c6.js +0 -1
  328. package/.next/static/chunks/app/api/auth/providers/route-2d5f6296c7e693c6.js +0 -1
  329. package/.next/static/chunks/app/api/board-assets/[...path]/route-2d5f6296c7e693c6.js +0 -1
  330. package/.next/static/chunks/app/api/board-assets/route-2d5f6296c7e693c6.js +0 -1
  331. package/.next/static/chunks/app/api/boards/[id]/reconcile/route-2d5f6296c7e693c6.js +0 -1
  332. package/.next/static/chunks/app/api/boards/[id]/route-2d5f6296c7e693c6.js +0 -1
  333. package/.next/static/chunks/app/api/boards/reorder/route-2d5f6296c7e693c6.js +0 -1
  334. package/.next/static/chunks/app/api/boards/route-2d5f6296c7e693c6.js +0 -1
  335. package/.next/static/chunks/app/api/cwd/browse/route-2d5f6296c7e693c6.js +0 -1
  336. package/.next/static/chunks/app/api/cwd/validate/route-2d5f6296c7e693c6.js +0 -1
  337. package/.next/static/chunks/app/api/default-cwd/route-2d5f6296c7e693c6.js +0 -1
  338. package/.next/static/chunks/app/api/file-index/route-2d5f6296c7e693c6.js +0 -1
  339. package/.next/static/chunks/app/api/files/[...path]/route-2d5f6296c7e693c6.js +0 -1
  340. package/.next/static/chunks/app/api/git/diff/route-2d5f6296c7e693c6.js +0 -1
  341. package/.next/static/chunks/app/api/git/status/route-2d5f6296c7e693c6.js +0 -1
  342. package/.next/static/chunks/app/api/home/route-2d5f6296c7e693c6.js +0 -1
  343. package/.next/static/chunks/app/api/mcp/route-2d5f6296c7e693c6.js +0 -1
  344. package/.next/static/chunks/app/api/models/route-2d5f6296c7e693c6.js +0 -1
  345. package/.next/static/chunks/app/api/models-config/catalog/route-2d5f6296c7e693c6.js +0 -1
  346. package/.next/static/chunks/app/api/models-config/discover/route-2d5f6296c7e693c6.js +0 -1
  347. package/.next/static/chunks/app/api/models-config/route-2d5f6296c7e693c6.js +0 -1
  348. package/.next/static/chunks/app/api/models-config/test/route-2d5f6296c7e693c6.js +0 -1
  349. package/.next/static/chunks/app/api/plugins/route-2d5f6296c7e693c6.js +0 -1
  350. package/.next/static/chunks/app/api/project-trust/route-2d5f6296c7e693c6.js +0 -1
  351. package/.next/static/chunks/app/api/quota/route-2d5f6296c7e693c6.js +0 -1
  352. package/.next/static/chunks/app/api/search/route-2d5f6296c7e693c6.js +0 -1
  353. package/.next/static/chunks/app/api/sessions/[id]/auto-name/route-2d5f6296c7e693c6.js +0 -1
  354. package/.next/static/chunks/app/api/sessions/[id]/context/route-2d5f6296c7e693c6.js +0 -1
  355. package/.next/static/chunks/app/api/sessions/[id]/entries/[entryId]/thinking/route-2d5f6296c7e693c6.js +0 -1
  356. package/.next/static/chunks/app/api/sessions/[id]/export/route-2d5f6296c7e693c6.js +0 -1
  357. package/.next/static/chunks/app/api/sessions/[id]/minimap/route-2d5f6296c7e693c6.js +0 -1
  358. package/.next/static/chunks/app/api/sessions/[id]/route-2d5f6296c7e693c6.js +0 -1
  359. package/.next/static/chunks/app/api/sessions/[id]/state/route-2d5f6296c7e693c6.js +0 -1
  360. package/.next/static/chunks/app/api/sessions/[id]/todos/route-2d5f6296c7e693c6.js +0 -1
  361. package/.next/static/chunks/app/api/sessions/route-2d5f6296c7e693c6.js +0 -1
  362. package/.next/static/chunks/app/api/sessions/summary/route-2d5f6296c7e693c6.js +0 -1
  363. package/.next/static/chunks/app/api/skills/check/route-2d5f6296c7e693c6.js +0 -1
  364. package/.next/static/chunks/app/api/skills/install/route-2d5f6296c7e693c6.js +0 -1
  365. package/.next/static/chunks/app/api/skills/route-2d5f6296c7e693c6.js +0 -1
  366. package/.next/static/chunks/app/api/skills/search/route-2d5f6296c7e693c6.js +0 -1
  367. package/.next/static/chunks/app/api/skills/update/route-2d5f6296c7e693c6.js +0 -1
  368. package/.next/static/chunks/app/api/task-card-questions/[id]/answer/route-2d5f6296c7e693c6.js +0 -1
  369. package/.next/static/chunks/app/api/task-card-questions/route-2d5f6296c7e693c6.js +0 -1
  370. package/.next/static/chunks/app/api/task-cards/[id]/route-2d5f6296c7e693c6.js +0 -1
  371. package/.next/static/chunks/app/api/task-cards/route-2d5f6296c7e693c6.js +0 -1
  372. package/.next/static/chunks/app/api/task-cards/unbind/route-2d5f6296c7e693c6.js +0 -1
  373. package/.next/static/chunks/app/api/task-scheduler/status/route-2d5f6296c7e693c6.js +0 -1
  374. package/.next/static/chunks/app/api/tasks/[id]/assign-session/route-2d5f6296c7e693c6.js +0 -1
  375. package/.next/static/chunks/app/api/tasks/[id]/board/route-2d5f6296c7e693c6.js +0 -1
  376. package/.next/static/chunks/app/api/tasks/[id]/route-2d5f6296c7e693c6.js +0 -1
  377. package/.next/static/chunks/app/api/tasks/reorder/route-2d5f6296c7e693c6.js +0 -1
  378. package/.next/static/chunks/app/api/tasks/route-2d5f6296c7e693c6.js +0 -1
  379. package/.next/static/chunks/app/api/terminal/[id]/events/route-2d5f6296c7e693c6.js +0 -1
  380. package/.next/static/chunks/app/api/terminal/[id]/route-2d5f6296c7e693c6.js +0 -1
  381. package/.next/static/chunks/app/api/terminal/route-2d5f6296c7e693c6.js +0 -1
  382. package/.next/static/chunks/app/api/worktrees/route-2d5f6296c7e693c6.js +0 -1
  383. package/.next/static/chunks/app/api/yjs-version/route-2d5f6296c7e693c6.js +0 -1
  384. package/.next/static/chunks/app/layout-dace09825b2d3857.js +0 -1
  385. package/.next/static/chunks/app/manifest.webmanifest/route-2d5f6296c7e693c6.js +0 -1
  386. package/.next/static/chunks/app/page-64b9f4b3567a2f73.js +0 -89
  387. package/.next/static/chunks/next/dist/client/components/builtin/app-error-2d5f6296c7e693c6.js +0 -1
  388. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-2d5f6296c7e693c6.js +0 -1
  389. package/.next/static/chunks/next/dist/client/components/builtin/not-found-2d5f6296c7e693c6.js +0 -1
  390. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-2d5f6296c7e693c6.js +0 -1
  391. package/.next/static/css/fd25673802257262.css +0 -1
  392. package/lib/extension-command.test.mjs +0 -63
  393. package/lib/extension-command.ts +0 -81
  394. package/lib/subagent-widget.test.mjs +0 -87
  395. package/lib/subagent-widget.ts +0 -109
  396. /package/.next/static/{bsW3fla0-BlBHVMKtAM5I → MPBMmQqmDG8zo8tM_bkDy}/_ssgManifest.js +0 -0
@@ -0,0 +1,602 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
3
+ import { dump as stringifyYaml } from "js-yaml";
4
+ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from "fs";
5
+ import { basename, dirname, join, resolve } from "path";
6
+ import { parseFrontmatter } from "./frontmatter";
7
+ import { writePrivateFileAtomicSync } from "./atomic-file";
8
+ import { isExistingPathWithinRoots } from "./path-security";
9
+ import { PRESET_READ_ONLY, DEFAULT_TOOLS } from "./tool-presets";
10
+ import type { SessionEntry, SubagentSessionStatus } from "./types";
11
+
12
+ export const SUBAGENT_META_TYPE = "pi-web:subagent";
13
+ export const SUBAGENT_STATUS_TYPE = "pi-web:subagent-status";
14
+ export const SUBAGENT_RESULT_TYPE = "pi-web:subagent-result";
15
+ export const SUBAGENT_CONTROL_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"] as const;
16
+
17
+ export type SubagentStatus = SubagentSessionStatus;
18
+ export type SubagentScope = "builtin" | "global" | "workspace" | "project";
19
+ export type SubagentWritableScope = Extract<SubagentScope, "global" | "project">;
20
+
21
+ export interface SubagentProfile {
22
+ name: string;
23
+ displayName: string;
24
+ description: string;
25
+ systemPrompt: string;
26
+ tools: string[];
27
+ extensionTools?: string[];
28
+ loadSkills: boolean;
29
+ loadExtensions: boolean;
30
+ model?: string;
31
+ thinking?: ThinkingLevel;
32
+ maxTurns?: number;
33
+ inheritContext: boolean;
34
+ runInBackground: boolean;
35
+ promptMode: "replace" | "append";
36
+ color?: string;
37
+ isolation?: "worktree" | "off";
38
+ persistSession?: boolean;
39
+ enabled: boolean;
40
+ scope: SubagentScope;
41
+ filePath?: string;
42
+ }
43
+
44
+ export interface SubagentMetadata {
45
+ version: 1;
46
+ parentSessionId: string;
47
+ parentSessionPath: string;
48
+ parentToolCallId: string;
49
+ profile: string;
50
+ description: string;
51
+ task: string;
52
+ runInBackground: boolean;
53
+ createdAt: string;
54
+ resourceSnapshot: SubagentResourceSnapshot;
55
+ worktreePath?: string;
56
+ worktreeBranch?: string;
57
+ }
58
+
59
+ export interface SubagentResourceSnapshot {
60
+ version: 1;
61
+ appendSystemPrompt: string[];
62
+ tools: string[];
63
+ loadSkills: boolean;
64
+ loadExtensions: boolean;
65
+ exactSystemPrompt?: string;
66
+ }
67
+
68
+ export interface SubagentSessionResources {
69
+ appendSystemPrompt: string[];
70
+ tools: string[];
71
+ loadSkills: boolean;
72
+ loadExtensions: boolean;
73
+ exactSystemPrompt?: string;
74
+ }
75
+
76
+ export interface SubagentResultMetadata {
77
+ version: 1;
78
+ status: Exclude<SubagentStatus, "starting" | "running" | "queued" | "interrupted">;
79
+ completedAt: string;
80
+ result?: string;
81
+ error?: string;
82
+ worktreeCleanupError?: string;
83
+ }
84
+
85
+ export interface SubagentStatusMetadata {
86
+ version: 1;
87
+ status: Extract<SubagentStatus, "queued" | "running">;
88
+ }
89
+
90
+ export interface SubagentRunInfo {
91
+ sessionId: string;
92
+ sessionPath: string;
93
+ parentSessionId: string;
94
+ parentToolCallId: string;
95
+ profile: string;
96
+ description: string;
97
+ task: string;
98
+ runInBackground: boolean;
99
+ status: SubagentStatus;
100
+ createdAt: string;
101
+ completedAt?: string;
102
+ result?: string;
103
+ error?: string;
104
+ worktreePath?: string;
105
+ worktreeBranch?: string;
106
+ worktreeCleanupError?: string;
107
+ }
108
+
109
+ const BUILTIN_TOOLS = new Set(DEFAULT_TOOLS);
110
+ const SUBAGENT_CONTROL_TOOLS = new Set<string>(SUBAGENT_CONTROL_TOOL_NAMES);
111
+ const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
112
+
113
+ /**
114
+ * Frontmatter keys the web UI owns. Everything else in a profile file belongs to
115
+ * whichever runtime reads it (pi-subagents and friends), so a save from this app must
116
+ * carry those keys through untouched. Dropping them silently changed behaviour:
117
+ * `allowed_subagents` was lost and an orchestrator could no longer spawn anything,
118
+ * `exclude_extensions` was lost and an opt-out became an opt-in.
119
+ */
120
+ const MANAGED_FRONTMATTER_KEYS = new Set([
121
+ "description",
122
+ "display_name",
123
+ "tools",
124
+ "load_skills",
125
+ "load_extensions",
126
+ "enabled",
127
+ "inherit_context",
128
+ "run_in_background",
129
+ "model",
130
+ "thinking",
131
+ "max_turns",
132
+ "prompt_mode",
133
+ "color",
134
+ "isolation",
135
+ "persist_session",
136
+ ]);
137
+
138
+ const FRONTMATTER_OPEN_RE = /^(?:\uFEFF)?---[ \t]*(?:\r\n|\n|\r)/;
139
+
140
+ /**
141
+ * The UI exposes two booleans (`load_skills` / `load_extensions`); pi-subagents reads
142
+ * the aliases `skills` / `extensions`, which also accept a whitelist. Aliases are
143
+ * carried through by `unmanagedFrontmatter` and only rewritten once we own them.
144
+ */
145
+ const OWNED_ALIAS_VALUES = new Set(["none", "all", "true", "false"]);
146
+
147
+ const BUILTIN_PROFILES: SubagentProfile[] = [
148
+ {
149
+ name: "general-purpose",
150
+ displayName: "General purpose",
151
+ description: "Handle a focused implementation or investigation task",
152
+ systemPrompt: "Work autonomously on the delegated task. Keep the final answer concise and include important files, decisions, and remaining risks.",
153
+ tools: DEFAULT_TOOLS,
154
+ loadSkills: false,
155
+ loadExtensions: false,
156
+ promptMode: "append",
157
+ inheritContext: false,
158
+ runInBackground: false,
159
+ enabled: true,
160
+ scope: "builtin",
161
+ },
162
+ {
163
+ name: "explore",
164
+ displayName: "Explore",
165
+ description: "Quickly inspect a codebase without modifying it",
166
+ systemPrompt: "Explore the codebase to answer the delegated question. Do not modify files. Report concrete findings with file paths and relevant symbols.",
167
+ tools: [...PRESET_READ_ONLY],
168
+ loadSkills: false,
169
+ loadExtensions: false,
170
+ promptMode: "append",
171
+ inheritContext: false,
172
+ runInBackground: false,
173
+ enabled: true,
174
+ scope: "builtin",
175
+ },
176
+ {
177
+ name: "plan",
178
+ displayName: "Plan",
179
+ description: "Design an implementation plan without modifying files",
180
+ systemPrompt: "Produce an implementation-ready plan for the delegated task. Inspect the repository as needed, do not modify files, and call out dependencies, risks, and verification steps.",
181
+ tools: [...PRESET_READ_ONLY],
182
+ loadSkills: false,
183
+ loadExtensions: false,
184
+ promptMode: "append",
185
+ inheritContext: false,
186
+ runInBackground: false,
187
+ enabled: true,
188
+ scope: "builtin",
189
+ },
190
+ ];
191
+
192
+ function stringValue(value: unknown): string | undefined {
193
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
194
+ }
195
+
196
+ function booleanValue(value: unknown, fallback: boolean): boolean {
197
+ return typeof value === "boolean" ? value : fallback;
198
+ }
199
+
200
+ function resourceBoolean(value: unknown, fallback: boolean): boolean {
201
+ if (typeof value === "boolean") return value;
202
+ return Array.isArray(value) || typeof value === "string" ? true : fallback;
203
+ }
204
+
205
+ function stringList(value: unknown): string[] {
206
+ const values = Array.isArray(value)
207
+ ? value
208
+ : typeof value === "string"
209
+ ? value.split(",")
210
+ : [];
211
+ return values.map((item) => String(item).trim()).filter(Boolean);
212
+ }
213
+
214
+ function parseTools(value: unknown, fallback: string[]): string[] {
215
+ const tools = stringList(value);
216
+ if (tools.includes("none")) return [];
217
+ if (tools.includes("all") || tools.includes("*")) return [...DEFAULT_TOOLS];
218
+ if (tools.length === 0) return [...fallback];
219
+ return [...new Set(tools.filter((tool) => BUILTIN_TOOLS.has(tool)))];
220
+ }
221
+
222
+ function rawToolValues(value: unknown): string[] {
223
+ return stringList(value);
224
+ }
225
+
226
+ function parseExtensionToolSelectors(value: unknown): string[] {
227
+ return [...new Set(rawToolValues(value).filter((tool) => tool.toLowerCase().startsWith("ext:")))];
228
+ }
229
+
230
+ /** Read existing frontmatter without allowing malformed metadata to be overwritten. */
231
+ function readStoredFrontmatter(filePath: string): Record<string, unknown> {
232
+ if (!existsSync(filePath)) return {};
233
+ const source = readFileSync(filePath, "utf8");
234
+ const { data } = parseFrontmatter(source);
235
+ if (data) return data;
236
+ if (FRONTMATTER_OPEN_RE.test(source)) {
237
+ throw new Error("Cannot save agent profile: existing frontmatter is invalid");
238
+ }
239
+ return {};
240
+ }
241
+
242
+ /** Keys another runtime owns, in file order, so a save round-trips them. */
243
+ function unmanagedFrontmatter(stored: Record<string, unknown>): Record<string, unknown> {
244
+ const preserved: Record<string, unknown> = {};
245
+ for (const [key, value] of Object.entries(stored)) {
246
+ if (!MANAGED_FRONTMATTER_KEYS.has(key)) preserved[key] = value;
247
+ }
248
+ return preserved;
249
+ }
250
+
251
+ /**
252
+ * pi-web filters `tools` down to the built-ins it can dispatch, which would drop
253
+ * another runtime's `ext:<name>` selectors on every save — carry them through.
254
+ */
255
+ function composeToolsField(tools: string[], storedTools: unknown): string {
256
+ const selectors = stringList(storedTools).filter((tool) => tool.startsWith("ext:"));
257
+ const combined = [...tools, ...selectors.filter((selector) => !tools.includes(selector))];
258
+ return combined.length > 0 ? combined.join(", ") : "none";
259
+ }
260
+
261
+ /**
262
+ * Keep the alias in step with the boolean the UI owns. A boolean (or a "none" /
263
+ * "all" spelling) is ours to rewrite; a whitelist such as `extensions:
264
+ * pi-advisor-flow` expresses scoping the UI cannot show, so it stays as authored.
265
+ */
266
+ function syncFlagAlias(
267
+ frontmatter: Record<string, unknown>,
268
+ alias: string,
269
+ storedValue: unknown,
270
+ flag: boolean,
271
+ ): void {
272
+ const owned = storedValue === undefined
273
+ || typeof storedValue === "boolean"
274
+ || (typeof storedValue === "string" && OWNED_ALIAS_VALUES.has(storedValue.trim().toLowerCase()));
275
+ if (owned) frontmatter[alias] = flag;
276
+ }
277
+ function parseProfileFile(filePath: string, scope: SubagentScope): SubagentProfile | null {
278
+ try {
279
+ const source = readFileSync(filePath, "utf8");
280
+ const { data, rest } = parseFrontmatter(source);
281
+ const name = stringValue(data?.name) ?? basename(filePath, ".md");
282
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) return null;
283
+ const thinkingValue = stringValue(data?.thinking) as ThinkingLevel | undefined;
284
+ const maxTurnsValue = typeof data?.max_turns === "number" ? Math.floor(data.max_turns) : undefined;
285
+ const tools = parseTools(data?.tools, DEFAULT_TOOLS);
286
+ const disallowedTools = new Set(parseTools(data?.disallowed_tools, []));
287
+ const disallowedExtensionTools = new Set(parseExtensionToolSelectors(data?.disallowed_tools).map((tool) => tool.toLowerCase()));
288
+ const extensionTools = parseExtensionToolSelectors(data?.tools)
289
+ .filter((tool) => !disallowedExtensionTools.has(tool.toLowerCase()));
290
+ return {
291
+ name,
292
+ displayName: stringValue(data?.display_name) ?? name,
293
+ description: stringValue(data?.description) ?? name,
294
+ systemPrompt: rest.trim(),
295
+ tools: tools.filter((tool) => !disallowedTools.has(tool)),
296
+ ...(extensionTools.length > 0 ? { extensionTools } : {}),
297
+ loadSkills: resourceBoolean(data?.load_skills ?? data?.skills, false),
298
+ loadExtensions: resourceBoolean(data?.load_extensions ?? data?.extensions, extensionTools.length > 0),
299
+ ...(stringValue(data?.model) ? { model: stringValue(data?.model) } : {}),
300
+ ...(thinkingValue && THINKING_LEVELS.has(thinkingValue) ? { thinking: thinkingValue } : {}),
301
+ ...(maxTurnsValue && maxTurnsValue > 0 ? { maxTurns: maxTurnsValue } : {}),
302
+ inheritContext: booleanValue(data?.inherit_context, false),
303
+ runInBackground: booleanValue(data?.run_in_background, false),
304
+ promptMode: data?.prompt_mode === "replace" ? "replace" : "append",
305
+ ...(stringValue(data?.color) ? { color: stringValue(data?.color) } : {}),
306
+ ...(data?.isolation === "worktree" || data?.isolation === "off" ? { isolation: data.isolation } : {}),
307
+ ...(typeof data?.persist_session === "boolean" ? { persistSession: data.persist_session } : {}),
308
+ enabled: booleanValue(data?.enabled, true),
309
+ scope,
310
+ filePath,
311
+ };
312
+ } catch {
313
+ return null;
314
+ }
315
+ }
316
+
317
+ function isProjectProfilePathAllowed(cwd: string, target: string): boolean {
318
+ return isExistingPathWithinRoots(target, new Set([cwd]));
319
+ }
320
+
321
+ function readProfileDirectory(dir: string, scope: SubagentScope, cwd: string): SubagentProfile[] {
322
+ if (!existsSync(dir)) return [];
323
+ if (scope !== "global" && !isProjectProfilePathAllowed(cwd, dir)) return [];
324
+ return readdirSync(dir, { withFileTypes: true })
325
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
326
+ .map((entry) => parseProfileFile(join(dir, entry.name), scope))
327
+ .filter((profile): profile is SubagentProfile => profile !== null);
328
+ }
329
+
330
+ function profileDirectories(cwd: string): Array<[string, Exclude<SubagentScope, "builtin">]> {
331
+ return [
332
+ [join(getAgentDir(), "agents"), "global"],
333
+ [join(resolve(cwd), ".agents", "agents"), "workspace"],
334
+ [join(resolve(cwd), ".pi", "agents"), "project"],
335
+ ];
336
+ }
337
+
338
+ /** Every configured source, including profiles shadowed by a higher-precedence scope. */
339
+ export function listSubagentProfileSources(cwd: string): SubagentProfile[] {
340
+ const profiles = BUILTIN_PROFILES.map((profile) => ({ ...profile, tools: [...profile.tools] }));
341
+ for (const [dir, scope] of profileDirectories(cwd)) {
342
+ profiles.push(...readProfileDirectory(dir, scope, cwd));
343
+ }
344
+ return profiles.sort((a, b) => a.displayName.localeCompare(b.displayName));
345
+ }
346
+
347
+ export function listSubagentProfiles(cwd: string): SubagentProfile[] {
348
+ const byName = new Map(BUILTIN_PROFILES.map((profile) => [profile.name.toLowerCase(), { ...profile, tools: [...profile.tools] }]));
349
+ for (const [dir, scope] of profileDirectories(cwd)) {
350
+ for (const profile of readProfileDirectory(dir, scope, cwd)) byName.set(profile.name.toLowerCase(), profile);
351
+ }
352
+ return [...byName.values()].sort((a, b) => a.displayName.localeCompare(b.displayName));
353
+ }
354
+
355
+ export function resolveSubagentProfile(cwd: string, name: string): SubagentProfile | undefined {
356
+ return listSubagentProfiles(cwd).find((profile) => profile.name.toLowerCase() === name.trim().toLowerCase() && profile.enabled);
357
+ }
358
+
359
+ function assertProfileName(name: string): string {
360
+ const normalized = name.trim();
361
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) {
362
+ throw new Error("Agent name may contain only letters, numbers, dots, underscores, and hyphens");
363
+ }
364
+ return normalized;
365
+ }
366
+
367
+ function writableProfileDirectory(cwd: string, scope: SubagentWritableScope): string {
368
+ if (scope === "global") return join(getAgentDir(), "agents");
369
+ if (scope === "project") return join(resolve(cwd), ".pi", "agents");
370
+ throw new Error("Agent scope must be global or project");
371
+ }
372
+
373
+ function assertWritableProfileDirectory(cwd: string, scope: SubagentWritableScope): string {
374
+ const dir = writableProfileDirectory(cwd, scope);
375
+ if (scope === "global") return dir;
376
+
377
+ let existingAncestor = dir;
378
+ while (!existsSync(existingAncestor)) {
379
+ const parent = dirname(existingAncestor);
380
+ if (parent === existingAncestor) throw new Error("Agent profile directory is outside the project root");
381
+ existingAncestor = parent;
382
+ }
383
+ if (!isProjectProfilePathAllowed(cwd, existingAncestor)) {
384
+ throw new Error("Agent profile directory is outside the project root");
385
+ }
386
+ return dir;
387
+ }
388
+
389
+ export function saveSubagentProfile(
390
+ cwd: string,
391
+ scope: SubagentWritableScope,
392
+ profile: Omit<SubagentProfile, "scope" | "filePath">,
393
+ ): SubagentProfile {
394
+ const name = assertProfileName(profile.name);
395
+ const tools = [...new Set(profile.tools.filter((tool) => BUILTIN_TOOLS.has(tool)))];
396
+ const extensionTools = [...new Set(profile.extensionTools ?? [])];
397
+ if (profile.thinking && !THINKING_LEVELS.has(profile.thinking)) {
398
+ throw new Error(`Invalid thinking level: ${profile.thinking}`);
399
+ }
400
+ if (profile.maxTurns !== undefined && (!Number.isFinite(profile.maxTurns) || profile.maxTurns < 0)) {
401
+ throw new Error("Max turns must be a non-negative number");
402
+ }
403
+ const maxTurns = profile.maxTurns && profile.maxTurns > 0
404
+ ? Math.floor(profile.maxTurns)
405
+ : undefined;
406
+ const displayName = profile.displayName.trim() || name;
407
+ const description = profile.description.trim() || name;
408
+ const systemPrompt = profile.systemPrompt.trim();
409
+ const model = profile.model?.trim() || undefined;
410
+ const loadSkills = profile.loadSkills === true;
411
+ const loadExtensions = profile.loadExtensions === true;
412
+ const promptMode = profile.promptMode === "replace" ? "replace" : "append";
413
+ const dir = assertWritableProfileDirectory(cwd, scope);
414
+ mkdirSync(dir, { recursive: true });
415
+ if (scope === "project" && !isProjectProfilePathAllowed(cwd, dir)) {
416
+ throw new Error("Agent profile directory is outside the project root");
417
+ }
418
+ const filePath = join(dir, `${name}.md`);
419
+ const stored = readStoredFrontmatter(filePath);
420
+ const managed: Record<string, unknown> = {
421
+ description,
422
+ display_name: displayName,
423
+ tools: composeToolsField([...tools, ...extensionTools], stored.tools),
424
+ load_skills: loadSkills,
425
+ load_extensions: loadExtensions,
426
+ enabled: profile.enabled,
427
+ inherit_context: profile.inheritContext,
428
+ run_in_background: profile.runInBackground,
429
+ prompt_mode: promptMode,
430
+ };
431
+ syncFlagAlias(managed, "skills", stored.skills, loadSkills);
432
+ syncFlagAlias(managed, "extensions", stored.extensions, loadExtensions);
433
+ if (model) managed.model = model;
434
+ if (profile.thinking) managed.thinking = profile.thinking;
435
+ if (maxTurns) managed.max_turns = maxTurns;
436
+ if (profile.color?.trim()) managed.color = profile.color.trim();
437
+ if (profile.isolation) managed.isolation = profile.isolation;
438
+ if (profile.persistSession !== undefined) managed.persist_session = profile.persistSession;
439
+ // Managed keys win; keys this app does not own follow in their original order.
440
+ const frontmatter: Record<string, unknown> = { ...managed };
441
+ for (const [key, value] of Object.entries(unmanagedFrontmatter(stored))) {
442
+ if (!(key in frontmatter)) frontmatter[key] = value;
443
+ }
444
+ const yaml = stringifyYaml(frontmatter, { noRefs: true, lineWidth: 1000 }).trimEnd();
445
+ writePrivateFileAtomicSync(filePath, `---\n${yaml}\n---\n\n${systemPrompt}\n`);
446
+ return {
447
+ ...profile,
448
+ name,
449
+ displayName,
450
+ description,
451
+ systemPrompt,
452
+ tools,
453
+ ...(extensionTools.length > 0 ? { extensionTools } : {}),
454
+ loadSkills,
455
+ loadExtensions,
456
+ ...(model ? { model } : { model: undefined }),
457
+ ...(maxTurns ? { maxTurns } : { maxTurns: undefined }),
458
+ promptMode,
459
+ ...(profile.color ? { color: profile.color } : {}),
460
+ ...(profile.isolation ? { isolation: profile.isolation } : {}),
461
+ ...(profile.persistSession !== undefined ? { persistSession: profile.persistSession } : {}),
462
+ scope,
463
+ filePath,
464
+ };
465
+ }
466
+
467
+ export function deleteSubagentProfile(cwd: string, scope: SubagentWritableScope, name: string): void {
468
+ const safeName = assertProfileName(name);
469
+ const filePath = join(assertWritableProfileDirectory(cwd, scope), `${safeName}.md`);
470
+ if (existsSync(filePath)) unlinkSync(filePath);
471
+ }
472
+
473
+ export function saveProjectSubagentProfile(cwd: string, profile: Omit<SubagentProfile, "scope" | "filePath">): SubagentProfile {
474
+ return saveSubagentProfile(cwd, "project", profile);
475
+ }
476
+
477
+ export function deleteProjectSubagentProfile(cwd: string, name: string): void {
478
+ deleteSubagentProfile(cwd, "project", name);
479
+ }
480
+
481
+ function isRecord(value: unknown): value is Record<string, unknown> {
482
+ return typeof value === "object" && value !== null && !Array.isArray(value);
483
+ }
484
+
485
+ type ValidSubagentMetadataData = Record<string, unknown> & {
486
+ version: 1;
487
+ parentSessionId: string;
488
+ parentSessionPath: string;
489
+ };
490
+
491
+ function subagentMetadataData(entries: readonly SessionEntry[]): ValidSubagentMetadataData | null {
492
+ const metaEntry = entries.find((entry) => entry.type === "custom" && entry.customType === SUBAGENT_META_TYPE);
493
+ if (!metaEntry || metaEntry.type !== "custom" || !isRecord(metaEntry.data)) return null;
494
+ const data = metaEntry.data;
495
+ if (data.version !== 1 || typeof data.parentSessionId !== "string" || typeof data.parentSessionPath !== "string") return null;
496
+ return data as ValidSubagentMetadataData;
497
+ }
498
+
499
+ /** Restore the isolated prompt and tool scope used by a persisted subagent session. */
500
+ export function readSubagentSessionResources(
501
+ entries: readonly SessionEntry[],
502
+ ): SubagentSessionResources | null {
503
+ const data = subagentMetadataData(entries);
504
+ if (!data) return null;
505
+ const snapshot = data.resourceSnapshot;
506
+ const loadSkills = isRecord(snapshot) && snapshot.loadSkills === true;
507
+ const loadExtensions = isRecord(snapshot) && snapshot.loadExtensions === true;
508
+ if (
509
+ isRecord(snapshot)
510
+ && snapshot.version === 1
511
+ && Array.isArray(snapshot.appendSystemPrompt)
512
+ && snapshot.appendSystemPrompt.every((item) => typeof item === "string")
513
+ && Array.isArray(snapshot.tools)
514
+ && snapshot.tools.every((item) =>
515
+ typeof item === "string"
516
+ && item.length > 0
517
+ && !SUBAGENT_CONTROL_TOOLS.has(item)
518
+ && (BUILTIN_TOOLS.has(item) || loadExtensions)
519
+ )
520
+ ) {
521
+ return {
522
+ appendSystemPrompt: [...snapshot.appendSystemPrompt],
523
+ tools: [...new Set(snapshot.tools)],
524
+ loadSkills,
525
+ loadExtensions,
526
+ ...(typeof snapshot.exactSystemPrompt === "string" ? { exactSystemPrompt: snapshot.exactSystemPrompt } : {}),
527
+ };
528
+ }
529
+ return null;
530
+ }
531
+
532
+ export function withSubagentExtensionTools(
533
+ profileTools: readonly string[],
534
+ extensionToolNames: Iterable<string>,
535
+ ): string[] {
536
+ return [...new Set([
537
+ ...profileTools,
538
+ ...[...extensionToolNames].filter((name) => !SUBAGENT_CONTROL_TOOLS.has(name)),
539
+ ])];
540
+ }
541
+
542
+ export function selectSubagentExtensionTools(
543
+ extensions: Iterable<{ path: string; sourceInfo?: { source?: string }; tools: Map<string, unknown> }>,
544
+ selectors: readonly string[],
545
+ ): string[] {
546
+ const wanted = selectors.map((selector) => selector.slice(4).toLowerCase());
547
+ return [...extensions].flatMap((extension) => {
548
+ const pathName = extension.path.replaceAll("\\", "/").split("/").at(-2) ?? extension.path;
549
+ const sourceName = (extension.sourceInfo?.source ?? "").replace(/^npm:/, "");
550
+ const extensionNames = new Set([pathName.toLowerCase(), sourceName.toLowerCase()]);
551
+ const selected = wanted.some((selector) => {
552
+ if (selector === "*") return true;
553
+ const [extensionName, toolName] = selector.split("/", 2);
554
+ return extensionNames.has(extensionName) && (!toolName || extension.tools.has(toolName));
555
+ });
556
+ if (!selected) return [];
557
+ return [...extension.tools.keys()].filter((toolName) => wanted.some((selector) => {
558
+ if (selector === "*" || selector.endsWith("/*")) return selector === "*" || extensionNames.has(selector.slice(0, -2));
559
+ const [extensionName, selectedTool] = selector.split("/", 2);
560
+ return extensionNames.has(extensionName) && (!selectedTool || selectedTool === toolName);
561
+ }));
562
+ });
563
+ }
564
+
565
+ export function readSubagentRun(entries: readonly SessionEntry[], sessionId: string, sessionPath: string): SubagentRunInfo | null {
566
+ const data = subagentMetadataData(entries);
567
+ if (!data) return null;
568
+ const lifecycleEntry = [...entries].reverse().find((entry) =>
569
+ entry.type === "custom" && (entry.customType === SUBAGENT_RESULT_TYPE || entry.customType === SUBAGENT_STATUS_TYPE)
570
+ );
571
+ const resultEntry = lifecycleEntry?.type === "custom" && lifecycleEntry.customType === SUBAGENT_RESULT_TYPE
572
+ ? lifecycleEntry
573
+ : undefined;
574
+ const result = resultEntry?.type === "custom" && isRecord(resultEntry.data) ? resultEntry.data : undefined;
575
+ const statusEntry = lifecycleEntry?.type === "custom" && lifecycleEntry.customType === SUBAGENT_STATUS_TYPE
576
+ ? lifecycleEntry
577
+ : undefined;
578
+ const statusData = statusEntry?.type === "custom" && isRecord(statusEntry.data) ? statusEntry.data : undefined;
579
+ const persistedStatus = result && (result.status === "completed" || result.status === "failed" || result.status === "aborted")
580
+ ? result.status
581
+ : statusData?.version === 1 && (statusData.status === "queued" || statusData.status === "running")
582
+ ? statusData.status
583
+ : "interrupted";
584
+ return {
585
+ sessionId,
586
+ sessionPath,
587
+ parentSessionId: data.parentSessionId,
588
+ parentToolCallId: typeof data.parentToolCallId === "string" ? data.parentToolCallId : "",
589
+ profile: typeof data.profile === "string" ? data.profile : "general-purpose",
590
+ description: typeof data.description === "string" ? data.description : "Subagent",
591
+ task: typeof data.task === "string" ? data.task : "",
592
+ runInBackground: data.runInBackground === true,
593
+ status: persistedStatus,
594
+ createdAt: typeof data.createdAt === "string" ? data.createdAt : "",
595
+ ...(result && typeof result.completedAt === "string" ? { completedAt: result.completedAt } : {}),
596
+ ...(result && typeof result.result === "string" ? { result: result.result } : {}),
597
+ ...(result && typeof result.error === "string" ? { error: result.error } : {}),
598
+ ...(typeof data.worktreePath === "string" ? { worktreePath: data.worktreePath } : {}),
599
+ ...(typeof data.worktreeBranch === "string" ? { worktreeBranch: data.worktreeBranch } : {}),
600
+ ...(result && typeof result.worktreeCleanupError === "string" ? { worktreeCleanupError: result.worktreeCleanupError } : {}),
601
+ };
602
+ }
@@ -1,4 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
2
3
  import test from "node:test";
3
4
  import { DatabaseSync } from "node:sqlite";
4
5
  import { createJiti } from "jiti";
@@ -76,3 +77,29 @@ test("clearSchedulerLeaderForTesting 清空后可重新注册", () => {
76
77
  assert.equal(getLeaderInstanceId(), null);
77
78
  assert.equal(tryBecomeLeader("inst-b"), true);
78
79
  });
80
+
81
+ // ── 派发时的归属失败不能静默 ──
82
+ // assignSessionToTask 会拒绝(返回 false):祖先属别的任务 / 会话解析不出。丢了返回值的话
83
+ // 「派发了但会话没进任务区」在日志里毫无痕迹(reconcile 会把它当孤儿卡)。
84
+
85
+ const schedulerSource = await readFile(new URL("./task-scheduler.ts", import.meta.url), "utf8");
86
+
87
+ /** 取 [from, to) 之间的源码片段;锚点找不到就报错(避免静默退化成整文件切片)。 */
88
+ function sliceBetween(text, from, to) {
89
+ const start = text.indexOf(from);
90
+ assert.ok(start >= 0, `切片起点不存在:${from}`);
91
+ const end = text.indexOf(to, start);
92
+ assert.ok(end > start, `切片终点不存在(或不在起点之后):${to}`);
93
+ return text.slice(start, end);
94
+ }
95
+
96
+ test("派发会话归属失败不静默:检查 assignSessionToTask 返回值并 console.warn", () => {
97
+ const assignBlock = sliceBetween(
98
+ schedulerSource,
99
+ "const board = getBoard(card.boardId);",
100
+ "if (session.session.isRunning())",
101
+ );
102
+ assert.match(assignBlock, /const assigned = await assignSessionToTask\(/, "返回值必须接住");
103
+ assert.match(assignBlock, /if \(!assigned\)[\s\S]*?console\.warn\(/, "失败必须可见(console.warn)");
104
+ assert.match(assignBlock, /派发的会话未能归属任务/);
105
+ });
@@ -156,7 +156,14 @@ export async function dispatchCard(card: TaskCard): Promise<boolean> {
156
156
 
157
157
  const board = getBoard(card.boardId);
158
158
  if (board?.taskId) {
159
- assignSessionToTask(session.realSessionId, board.taskId);
159
+ // 返回值不能丢:拒绝(祖先属别的任务 / 会话解析不出)时这个会话不会进任务区,
160
+ // 而 reconcile 按「卡在任务里」补卡——静默失败会让派发结果不可解释。
161
+ const assigned = await assignSessionToTask(session.realSessionId, board.taskId);
162
+ if (!assigned) {
163
+ console.warn(
164
+ `[task-scheduler] #${card.number} ${card.name} 派发的会话未能归属任务 task=${board.taskId} session=${session.realSessionId}`,
165
+ );
166
+ }
160
167
  // 注意:不在 assignSessionToTask 后 reconcile——此刻卡 sessionId 未落表,
161
168
  // reconcile 只能补会话卡、建不了 exec 线 → 造成「先出卡后出线」。
162
169
  // 统一在 updateCard(sessionId) 之后触发(补卡+建线同一次事务、一次广播)。