@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @pi-unipi/skill-registry — the skill vault
3
+ *
4
+ * `~/.unipi/skill-vault/` holds skills the user keeps but does not want
5
+ * everywhere. With the proxy on, pi discovers the vault (resources_discover)
6
+ * and the registry keeps vault skills OFF until a scope turns them on.
7
+ * Layout: any directory containing SKILL.md, up to three levels deep
8
+ * (`vault/<skill>/SKILL.md` or `vault/<pack>/<skill>/SKILL.md`).
9
+ */
10
+
11
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ export function vaultDir(): string {
16
+ return process.env.UNIPI_SKILL_VAULT || join(homedir(), ".unipi", "skill-vault");
17
+ }
18
+
19
+ export interface VaultSkill {
20
+ name: string;
21
+ description: string;
22
+ filePath: string;
23
+ baseDir: string;
24
+ }
25
+
26
+ /** Minimal frontmatter reader: `name:` and `description:` (plain, `>`, `|`). */
27
+ export function parseFrontmatter(text: string): { name?: string; description?: string } {
28
+ const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
29
+ if (!match) return {};
30
+ const lines = match[1]!.split(/\r?\n/);
31
+ const out: { name?: string; description?: string } = {};
32
+ for (let i = 0; i < lines.length; i++) {
33
+ const m = lines[i]!.match(/^(name|description):\s*(.*)$/);
34
+ if (!m) continue;
35
+ const key = m[1] as "name" | "description";
36
+ let value = m[2]!.trim();
37
+ if (/^[>|][+-]?$/.test(value)) {
38
+ const block: string[] = [];
39
+ while (i + 1 < lines.length && (/^\s+\S/.test(lines[i + 1]!) || lines[i + 1] === "")) block.push(lines[++i]!.trim());
40
+ value = block.join(" ").trim();
41
+ }
42
+ out[key] = value.replace(/^["']|["']$/g, "");
43
+ }
44
+ return out;
45
+ }
46
+
47
+ export function listVaultSkills(dir: string = vaultDir()): VaultSkill[] {
48
+ const out: VaultSkill[] = [];
49
+ const walk = (d: string, depth: number) => {
50
+ if (depth > 3 || !existsSync(d)) return;
51
+ let names: string[];
52
+ try {
53
+ names = readdirSync(d);
54
+ } catch {
55
+ return;
56
+ }
57
+ if (names.includes("SKILL.md") && d !== dir) {
58
+ const filePath = join(d, "SKILL.md");
59
+ try {
60
+ const fm = parseFrontmatter(readFileSync(filePath, "utf-8"));
61
+ const name = fm.name || d.split("/").pop()!;
62
+ if (fm.description) out.push({ name, description: fm.description, filePath, baseDir: d });
63
+ } catch {
64
+ // unreadable skill — skip
65
+ }
66
+ return;
67
+ }
68
+ for (const n of names) {
69
+ if (n.startsWith(".")) continue;
70
+ const p = join(d, n);
71
+ try {
72
+ if (statSync(p).isDirectory()) walk(p, depth + 1);
73
+ } catch {
74
+ // skip
75
+ }
76
+ }
77
+ };
78
+ walk(dir, 0);
79
+ return out.sort((a, b) => a.name.localeCompare(b.name));
80
+ }
@@ -1,121 +1,41 @@
1
1
  # @pi-unipi/subagents
2
2
 
3
- Delegate work to focused child agents — in parallel, in the background, or as scripted multi-agent workflows. Feature parity with [pi-subagents](https://github.com/nicobailon/pi-subagents), built on unipi conventions: foreground children run in-process (live widget streaming), background/fork/resume/worktree runs use child `pi` processes.
4
-
5
- ## Agents
6
-
7
- Built-in agents (lowest discovery priority — user/project definitions override):
8
-
9
- | Agent | Use it for |
10
- |-------|------------|
11
- | `explore` | Read-only file research and parallel reads |
12
- | `work` | File modifications with transparent locking |
13
- | `scout` | Fast codebase recon: entry points, data flow, risks |
14
- | `researcher` | Web research with sources (`web_search`, `multi_web_content_read`) |
15
- | `worker` | Implementation: narrow edits, validation, escalation |
16
- | `reviewer` | Code review of diffs, plans, solutions |
17
- | `oracle` | Second opinion; challenges assumptions without editing |
18
- | `delegate` | Lightweight general delegate close to the parent session |
19
-
20
- Custom agents are markdown files with YAML frontmatter:
21
-
22
- ```markdown
23
- ---
24
- name: security-reviewer
25
- description: Security-focused review
26
- tools: read, grep, find
27
- thinking: high
28
- memory: { scope: "project", path: "security-reviewer" }
29
- ---
30
-
31
- Review changes for unsafe input handling...
32
- ```
33
-
34
- Discovery: project `.unipi/config/agents/` > global `~/.unipi/config/agents/` > builtins. Aliases resolve (`developer` → `worker`, `advisor` → `oracle`). Per-agent overrides live in `subagents.json`.
35
-
36
- ## Tools
37
-
38
- | Tool | Description |
39
- |------|-------------|
40
- | `spawn_helper` | Launch agents: single child, `workflowScript` orchestration, or management `action`s |
41
- | `get_helper_result` | Wait on / inspect background runs; `nonBlocking` wake subscriptions |
42
-
43
- ### Single child
44
-
45
- ```
46
- spawn_helper({ agent: "scout", task: "Analyze the auth flow" })
47
- spawn_helper({ agent: "worker", task: "Implement it", run_in_background: true })
48
- spawn_helper({ agent: "reviewer", task: "Review", gate: "npm test" })
49
- ```
50
-
51
- Legacy aliases (`type`, `prompt`, `max_turns`) still work.
52
-
53
- ### Scripted workflows
54
-
55
- ```js
56
- spawn_helper({ workflowScript: `
57
- const scan = await runs.run("scan", { agent: "scout", task: "Analyze auth" });
58
- const reviews = await runs.all([
59
- { key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
60
- { key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
61
- ]);
62
- return reviews.map(r => r.output);
63
- `, async: false })
64
- ```
65
-
66
- `runs.run` / `runs.all` / `runs.steer` inside a sandboxed VM. Budgets (`turnBudget`, `toolBudget`, `usageBudget`), worktree isolation, fork context, and acceptance gates available per child.
67
-
68
- ### Management actions
69
-
70
- ```
71
- spawn_helper({ action: "list" | "get" | "status" | "children.list" })
72
- spawn_helper({ action: "resume", id: "<run>", message: "Reconsider X" })
73
- spawn_helper({ action: "doctor" | "guide", topic: "workflows" })
74
- spawn_helper({ action: "mission.create", mission: { title, objective } })
75
- spawn_helper({ action: "schedule.create", name, agent, task, every: "30m" })
76
- ```
77
-
78
- ## Observability
79
-
80
- - **FleetView** panel: active work from both transports. `↓` to inspect, `j/k` navigate, `enter` opens transcripts, `esc` closes.
81
- - **`/unipi:subagents-fleet`** · **`/unipi:subagents-doctor`** · **`/unipi:subagents-guide [topic]`**
82
- - Background completions arrive as `<task-notification>` follow-ups automatically.
83
- - Supervisor channel: blocked children can `contact_supervisor` for decisions.
84
-
85
- ## Configuration
86
-
87
- `~/.unipi/config/subagents.json` (global) + `<workspace>/.unipi/config/subagents.json`:
88
-
89
- ```json
90
- {
91
- "subagents": {
92
- "defaultModel": "ds/deepseek-v4-flash",
93
- "asyncByDefault": true,
94
- "maxSubagentSpawnsPerRun": 64,
95
- "fleetViewPlacement": "belowEditor"
96
- }
97
- }
98
- ```
99
-
100
- Full key reference: `spawn_helper({ action: "guide", topic: "configuration" })`. Env overrides use the `UNIPI_SUBAGENT_*` prefix.
101
-
102
- ## Storage
103
-
104
- | Path | Contents |
105
- |------|----------|
106
- | `~/.unipi/missions/<project-hash>/` | Durable mission records |
107
- | `~/.unipi/schedules/<project-hash>/` | Scheduled runs |
108
- | `~/.unipi/agent-memory/` | Per-agent persistent memory |
109
- | temp root (`unipi-subagents-*`) | Run artifacts, results, channels (auto-cleaned) |
110
-
111
- ## Commands
112
-
113
- | Command | Description |
114
- |---------|-------------|
115
- | `/unipi:subagents-fleet` | Show active fleet |
116
- | `/unipi:subagents-doctor` | Config + capacity diagnosis |
117
- | `/unipi:subagents-guide [topic]` | Bundled guide |
118
-
119
- ## Prompt shortcuts
120
-
121
- `/council`, `/parallel-review`, `/review-loop`, `/parallel-research`, `/gather-context-and-clarify`, `/parallel-cleanup` — packaged prompt templates for common orchestration patterns.
3
+ Devin-model subagents for pi: the lead delegates self-contained work to independent child
4
+ agents (`pi --mode rpc`) that run alongside it.
5
+
6
+ - `run_subagent` — foreground (wait + report) or background (`<subagent_completion_notification>`
7
+ followUp, exactly once). Approval prompts reach the user only while a foreground waiter is
8
+ attached; background subagents are denied with a reason ("running in the background… don't
9
+ retry"). `resume:<agent_id>` continues an earlier agent in its session file.
10
+ - `read_subagent` — shared tool (core registry): reads subagents AND Fusion sidekick handoffs.
11
+ - Profiles: `subagent_explore` (read-only allowlist + cheap default model),
12
+ `subagent_general` (all tools minus nesting, parent's model), and custom markdown agents from
13
+ `~/.unipi/config/agents/` + `<workspace>/.unipi/config/agents/` (project wins).
14
+ - Nesting: children spawn only below `UNIPI_SUBAGENT_MAX_DEPTH` (default 1 = no nesting).
15
+
16
+ ## Screen
17
+
18
+ | Where | What |
19
+ |---|---|
20
+ | Chat | `● Explore subagent <title>` card — live tail + `└ General · ds/deepseek-flash · 32s · 20 tool calls · 10k in · 0.2k out · $0.11 · ctrl+b background · esc cancel` while it runs, then `└ Completed · 32s · 20 tool calls · 10k in · 0.2k out · $0.11` (ctrl+o shows the report). The stats line ticks live — profile, model, tool-call count, duration, tokens and cost (cost is omitted when the model's pricing is unknown). Background finishes: `● Subagent "<title>" completed └ …`. |
21
+ | Below the input | `N subagents (k running) · ↓ select` plus one stat line per running agent, ticking each second — stays for the session, survives restart + resume. |
22
+ | Dock (↓ from an empty input, or `/unipi:subagents`) | `↑↓` navigate · `↵` view the live transcript · `f` foreground · `x` cancel · `esc` close. |
23
+ | Transcript view | task, tool calls with output, text; `↑↓`/PgUp/PgDn scroll, `g`/`G` top/end, `o` full tool output. |
24
+ | Foreground | Spinner reads `Subagent running · Ctrl+B to run in background`. **Ctrl+B** sends every foreground subagent to the background; **Esc** cancels a foreground run. `f` on a background agent shows its live steps above the input and routes its approvals to you. |
25
+
26
+ States: running `◐`, completed `✓`, failed `✗`, cancelled `⊘` (its own state — "cancelled by you"
27
+ when you pressed Esc or `x`).
28
+
29
+ ## Config
30
+
31
+ `/unipi:settings → Subagents`: enabled, default subagent model (picker; empty = Fusion sidekick →
32
+ your model), default thinking, max running at once, **Manage agents…**. Settings and agent files
33
+ are re-read every turn — no restart.
34
+
35
+ `/unipi:agents` — list profiles; create a custom agent step by step (name → this project / all
36
+ projects → description → model → tools → prompt); edit, copy between global/project, or delete.
37
+ Files use the Devin/Claude format (`name`, `description`, `model`, `allowed-tools`|`tools`,
38
+ `thinking`, `max-nesting`; body = system prompt).
39
+
40
+ State: `~/.unipi/workspace/<id>/state/subagents/sessions/<lead-session-id>/` (`index.json` + one
41
+ `<agent_id>.jsonl` child session per agent).
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "3.0.0-alpha.2",
4
- "description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
3
+ "version": "3.0.0-alpha.21",
4
+ "description": "Subagents for UniPi — Devin-model independent child agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
@@ -9,7 +9,8 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "3.0.0-alpha.2",
12
+ "@pi-unipi/core": "3.0.0-alpha.21",
13
+ "@pi-unipi/utility": "3.0.0-alpha.21",
13
14
  "@earendil-works/pi-agent-core": "^0.87.1",
14
15
  "acorn": "8.18.0"
15
16
  },
@@ -24,22 +25,14 @@
24
25
  "files": [
25
26
  "src/**/*.ts",
26
27
  "!src/__tests__/**",
27
- "src/global.d.ts",
28
- "agents/*.md",
29
- "prompts/*.md",
30
- "skills/**/*",
31
28
  "README.md"
32
29
  ],
33
30
  "pi": {
34
31
  "extensions": [
35
32
  "./src/index.ts"
36
33
  ],
37
- "skills": [
38
- "./skills"
39
- ],
40
- "prompts": [
41
- "./prompts"
42
- ],
34
+ "skills": [],
35
+ "prompts": [],
43
36
  "themes": []
44
37
  }
45
38
  }
@@ -1,49 +1,25 @@
1
1
  ---
2
- name: unipi-subagents
3
- description: |
4
- Delegate work to builtin or custom subagents with single-agent, parallel,
5
- scripted-chaining, async, forked-context, and coordinated workflows. Use
6
- for advisory review, implementation handoffs, and multi-step tasks where a
7
- single agent should stay in control while other agents contribute context,
8
- planning, or execution.
2
+ name: subagents
3
+ description: Delegating work to independent subagents (run_subagent/read_subagent).
9
4
  ---
10
5
 
11
- # Pi Subagents
6
+ # Subagents
12
7
 
13
- This skill is for the main parent orchestrator only. Do not inject or follow it inside spawned child subagents. The parent session owns delegation, orchestration, review fanout, and final fix-worker launches. Ordinary children should not run their own subagent workflows; the explicit exception is a delegated fanout child whose resolved builtin `tools` includes `subagent`, and that child may use `subagent` only for the fanout work the parent assigned.
8
+ The model drives delegation through tools. For the user: `/unipi:subagents` (or ↓ from an empty
9
+ input) opens the subagent panel; `/unipi:agents` manages custom agents; Ctrl+B moves a foreground
10
+ subagent to the background.
14
11
 
15
- Use this skill when the parent orchestrator needs one specialized child or composed orchestration. Use `workflowScript` for all execution, including one isolated child. Chaining is still supported, but it is code-driven: use `await runs.run(...)` for sequential steps, `runs.all([...])` for parallel fanout, and ordinary JavaScript for branching, retries, gate monitors, and aggregation. Keep workflow helpers portable: use plain helper functions or explicit Promise chains, not nested `async function` helpers, async arrows, or async methods. Do not use legacy top-level `chain` / `tasks` inputs or durable `.chain.md` execution. Scripted workflows normally start asynchronously unless config sets `asyncByDefault:false`; set `async:true` explicitly when async behavior matters. Pass `async:false` only when the parent must block until completion. Async mode still shows progress. Do not use `async:false` for final reviews, backlog gates, run-to-completion convenience, or because no other work is available.
12
+ - `run_subagent({title, task, profile, is_background?, resume?})` launches an independent agent
13
+ with its own context. It does not see this conversation — put everything it needs in `task`.
14
+ - `read_subagent({agent_id?, block?, timeout?})` reads/waits on a subagent (or a Fusion
15
+ sidekick handoff) by id.
16
16
 
17
- Package-installed agents appear in `spawn_helper({ action: "list" })` with builtin, user, and project agents. If `surf-cli` is installed as a Pi package, the Surf browser extension is loaded, and Chrome is logged into a ChatGPT Pro account, Surf can expose `gpt-pro`: a read-only async advisor that reaches ChatGPT web through Surf Oracle. Check it with `spawn_helper({ action: "get", agent: "gpt-pro" })` and run it with `subagent({ agent: "gpt-pro", task: "Review this plan and identify release risks." })`.
17
+ Profiles:
18
+ - `subagent_explore` — read-only (read/grep/find/ls/web_search/memory_search/memory_list), cheaper default model.
19
+ - `subagent_general` — all tools except nesting tools, runs on your model.
20
+ - Custom `.md` agents under `~/.unipi/config/agents/` (global) or
21
+ `<workspace>/.unipi/config/agents/` (project), YAML frontmatter:
22
+ `name, description, model, tools|allowed-tools, thinking, max-nesting`; body = system prompt.
18
23
 
19
- ## How to use this router
20
-
21
- Read the matching reference file before acting. Paths are relative to this `SKILL.md`; resolve them against `skills/subagents/` and load them with the read tool.
22
-
23
- | Task | Read |
24
- | --- | --- |
25
- | Decide whether to delegate, choose agents, compare tool versus slash commands, apply prompt techniques, or understand builtin roles | `references/prompting-and-roles.md` |
26
- | Use council mode, convene several advisors, debate a decision, cross-examine recommendations, critique or improve a plan with multiple model perspectives, or run `/council` | `../council-mode/SKILL.md` |
27
- | Run one-child, scripted, async, scheduled, mission-backed, forked, watchdog, oracle, or intercom-coordinated workflows | `references/execution-controls.md` |
28
- | Coordinate several independent tasks, worktrees, repositories, or writer lanes | `references/multi-lane-orchestration.md` |
29
- | List/create/update/delete/eject/disable agents, inspect legacy chain records, edit agent files, use prompt-template integration, or expose extension RPC | `references/management-authoring-rpc.md` |
30
- | Check safety constraints, best practices, standard workflows, or error handling | `references/constraints-and-recipes.md` |
31
-
32
- For broad or uncertain requests, read more than one reference. For complex work, start with `references/prompting-and-roles.md` and `references/execution-controls.md`, then consult `references/constraints-and-recipes.md` before launching or reviewing child work.
33
-
34
- ## Always-on constraints
35
-
36
- - Keep the parent as orchestrator and final decision-maker.
37
- - Before multiple mutation-capable lanes, record a lane board and each lane's isolation path.
38
- - For plan, design, or architecture advice that asks for council mode, asks to convene several advisors, compare model perspectives, debate a decision, cross-examine recommendations, or critique and improve a plan, read `../council-mode/SKILL.md` and use Council Mode instead of ad hoc parallel oracle calls.
39
- - For plan, design, or architecture advice that asks to consult, discuss with, or come to agreement with one `oracle`, use a short same-session consultation loop: read the first result, resume once with a targeted challenge when material tradeoffs remain, then synthesize the parent decision. Keep explicit one-shot, trivial, and fully settled consultations one-shot.
40
- - Use one writer per cwd/worktree unless isolated worktrees are intentional.
41
- - For cross-codebase work, record the target repo, explicit `cwd`, authority boundary, and expected output before launch. Do not assume the parent session cwd is the child repo.
42
- - For parallel fanout, compare child prompts before launch. Do not send clone prompts with only issue numbers, titles, or broad file globs swapped; each child needs a lane-specific task, source seam, prior evidence, and decision that remains distinct without the item number. Launch that fanout as one async `workflowScript` with stable keys and aggregate output unless there is truly only one child.
43
- - Prefer fresh-context review/validation fanout, then synthesize and apply fixes in the parent.
44
- - Use async/background by default. Final reviews, gate checks, oracle checks, and backlog lanes stay async. Use `async:false` only when the parent must block until completion. Do not poll just to wait. For adaptive gates, branch in `workflowScript`.
45
- - For Pi extension repos whose canonical checkout is under `~/.pi/agent/extensions`, never create lane worktrees as sibling directories there. Pi auto-loads `~/.pi/agent/extensions/*/index.ts`, so sibling worktrees can register duplicate tools. Put lanes under `~/.pi/agent/worktrees`, another worktree base outside auto-discovery, or a temporary clone. If a lane must run the modified extension itself, use an isolated Pi config home with `PI_CODING_AGENT_DIR=<lane-config> pi --no-extensions -e <lane>/index.ts`. Use full containers only when path and config isolation are insufficient.
46
- - Preserve capability ceilings, including child tool restrictions and session-scoped allowed-agent restrictions.
47
- - Escalate unresolved product, architecture, authority, release, merge, or safety decisions upward instead of letting a child decide silently.
48
- - Treat receipts, CI, review bots, and external-run records as evidence, not authority to merge, close, comment, publish, or release.
49
- - As a conservative orchestration policy, do not pass `turnBudget`, a hard `toolBudget`, or a tight `usageBudget` to mutation-capable workers. The default tool budget blocks read/search tools rather than mutation tools, and reported usage has no reservation model. If a worker is interrupted after a tool call starts, checkpoint after the current tool returns with changed files, build/test state, and commit or PR state.
24
+ Rules: parallelize independent work; foreground waits for the report; background can't ask
25
+ for approval (auto-denied); resume a finished/failed/cancelled agent with `resume:<agent_id>`.
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @pi-unipi/subagents — `/unipi:agents`: manage custom agents without
3
+ * hand-writing frontmatter. List built-ins + customs, create one step by step
4
+ * (name → scope → description → model → tools → prompt), edit the file in
5
+ * pi's editor, copy between global and project, delete.
6
+ *
7
+ * Files: ~/.unipi/config/agents/<name>.md (global) and
8
+ * <workspace>/.unipi/config/agents/<name>.md (project, wins).
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
15
+ import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
16
+ import { getSharedFusionStatus, registerCommandRunner } from "@pi-unipi/core";
17
+ import { EXPLORE_TOOLS, builtinProfiles, loadProfiles, type AgentProfile } from "./profiles.js";
18
+ import { resolveModel } from "./model-resolver.js";
19
+
20
+ export const AGENTS_COMMAND = "unipi:agents";
21
+ const NAME_RE = /^[a-z0-9][a-z0-9_-]{0,47}$/;
22
+ const NEW_LABEL = "+ New agent…";
23
+
24
+ export function agentsDir(scope: "global" | "project", cwd: string, home = homedir()): string {
25
+ return join(scope === "global" ? home : cwd, ".unipi", "config", "agents");
26
+ }
27
+
28
+ /** Where a custom profile's file lives (flat file or directory layout). */
29
+ export function agentFile(profile: Pick<AgentProfile, "id" | "source">, cwd: string, home = homedir()): string | undefined {
30
+ if (profile.source === "builtin") return undefined;
31
+ const dir = agentsDir(profile.source, cwd, home);
32
+ for (const candidate of [join(dir, `${profile.id}.md`), ...["AGENT.md", "AGENTS.md", "agent.md", "agents.md"].map((f) => join(dir, profile.id, f))]) {
33
+ if (existsSync(candidate)) return candidate;
34
+ }
35
+ return undefined;
36
+ }
37
+
38
+ export interface AgentDraft {
39
+ name: string;
40
+ description: string;
41
+ model?: string;
42
+ tools?: string[];
43
+ thinking?: string;
44
+ prompt: string;
45
+ }
46
+
47
+ /** Render a draft as the Devin/Claude-compatible markdown file. */
48
+ export function agentMarkdown(d: AgentDraft): string {
49
+ const fm = [`name: ${d.name}`, `description: ${JSON.stringify(d.description)}`];
50
+ if (d.model) fm.push(`model: ${d.model}`);
51
+ if (d.thinking) fm.push(`thinking: ${d.thinking}`);
52
+ if (d.tools && d.tools.length > 0) fm.push("allowed-tools:", ...d.tools.map((t) => ` - ${t}`));
53
+ return `---\n${fm.join("\n")}\n---\n\n${d.prompt.trim()}\n`;
54
+ }
55
+
56
+ export function validateName(name: string, existing: readonly string[]): string | null {
57
+ if (!NAME_RE.test(name)) return "Use lowercase letters, digits, - or _ (max 48).";
58
+ if (builtinProfiles().some((b) => b.id === name)) return `${name} is a built-in profile name.`;
59
+ if (existing.includes(name)) return `An agent named ${name} already exists.`;
60
+ return null;
61
+ }
62
+
63
+ function promptTemplate(name: string, description: string): string {
64
+ return `You are the ${name} subagent: ${description}\n\nWork only on the task you are given. You do not see the parent conversation; everything you need is in the task.\n\nFinish with a concise report for the parent agent: what you found or did, with file paths and line numbers, and anything you could not determine.`;
65
+ }
66
+
67
+ function describe(p: AgentProfile): string {
68
+ const tools = p.tools === undefined ? "all tools" : p.tools.length <= 4 ? p.tools.join(", ") : `${String(p.tools.length)} tools`;
69
+ const model = p.model ?? (p.id === "subagent_general" ? "your model" : "default subagent model");
70
+ return `${p.id} · ${p.source} · ${model} · ${tools} — ${p.description}`;
71
+ }
72
+
73
+ type UI = ExtensionContext["ui"];
74
+
75
+ /** Short list + "Other…" with a fuzzy name match (hundreds of models don't
76
+ * fit a plain select). undefined = default subagent model; null = cancelled. */
77
+ async function pickModel(ui: UI, ctx: ExtensionContext): Promise<string | undefined | null> {
78
+ const DEFAULT = "Default subagent model (recommended)";
79
+ const OTHER = "Other… (type a name — fuzzy match)";
80
+ const cur = ctx.model as { provider?: string; id?: string } | undefined;
81
+ const choices = new Map<string, string>();
82
+ if (cur?.provider && cur.id) choices.set(`Your current model — ${cur.provider}/${cur.id}`, `${cur.provider}/${cur.id}`);
83
+ const sidekick = getSharedFusionStatus()?.sidekickKey;
84
+ if (sidekick) choices.set(`Fusion sidekick — ${sidekick}`, sidekick);
85
+ const choice = await ui.select("Model", [DEFAULT, ...choices.keys(), OTHER]);
86
+ if (choice === undefined) return null;
87
+ if (choice === DEFAULT) return undefined;
88
+ if (choice !== OTHER) return choices.get(choice);
89
+ while (true) {
90
+ const raw = (await ui.input("Model name (e.g. deepseek-flash, sonnet, openrouter/…)", ""))?.trim();
91
+ if (!raw) return null;
92
+ const found = ctx.modelRegistry ? resolveModel(raw, ctx.modelRegistry as never) : raw;
93
+ if (typeof found !== "string") return `${found.provider}/${found.id}`;
94
+ if (!ctx.modelRegistry) return raw;
95
+ ui.notify(`No model matches "${raw}".`, "warning");
96
+ }
97
+ }
98
+
99
+ async function pickTools(ui: UI): Promise<string[] | undefined | null> {
100
+ const READ = `Read-only (${EXPLORE_TOOLS.join(", ")})`;
101
+ const ALL = "All tools (like subagent_general)";
102
+ const CUSTOM = "Custom list…";
103
+ const choice = await ui.select("Tools", [READ, ALL, CUSTOM]);
104
+ if (choice === undefined) return null;
105
+ if (choice === READ) return [...EXPLORE_TOOLS];
106
+ if (choice === ALL) return undefined;
107
+ const raw = await ui.input("Tools (comma-separated)", "read, grep, find, ls, bash");
108
+ if (raw === undefined) return null;
109
+ const list = raw.split(",").map((t) => t.trim()).filter(Boolean);
110
+ return list.length > 0 ? list : undefined;
111
+ }
112
+
113
+ async function createAgent(ctx: ExtensionContext, existing: readonly string[], onChange: () => void): Promise<void> {
114
+ const ui = ctx.ui;
115
+ let name: string | undefined;
116
+ while (name === undefined) {
117
+ const raw = await ui.input("New agent — name (e.g. reviewer)", "");
118
+ if (raw === undefined) return;
119
+ const err = validateName(raw.trim(), existing);
120
+ if (err) ui.notify(err, "warning");
121
+ else name = raw.trim();
122
+ }
123
+ const scopeChoice = await ui.select(`Save ${name} for`, ["This project (.unipi/config/agents)", "All projects (~/.unipi/config/agents)"]);
124
+ if (scopeChoice === undefined) return;
125
+ const scope = scopeChoice.startsWith("This") ? "project" : "global";
126
+ const description = (await ui.input("Description — the parent agent reads this to pick the profile", ""))?.trim();
127
+ if (!description) return;
128
+ const model = await pickModel(ui, ctx);
129
+ if (model === null) return;
130
+ const tools = await pickTools(ui);
131
+ if (tools === null) return;
132
+ const prompt = await ui.editor(`${name} — system prompt (enter saves)`, promptTemplate(name, description));
133
+ if (prompt === undefined || !prompt.trim()) return;
134
+ const file = join(agentsDir(scope, ctx.cwd), `${name}.md`);
135
+ mkdirSync(dirname(file), { recursive: true });
136
+ writeFileSync(file, agentMarkdown({ name, description, model, tools, prompt }), "utf8");
137
+ onChange();
138
+ ui.notify(`Agent ${name} saved to ${file}. The agent can use it from the next turn.`, "info");
139
+ }
140
+
141
+ async function editAgent(ctx: ExtensionContext, file: string, onChange: () => void): Promise<void> {
142
+ const before = readFileSync(file, "utf8");
143
+ const after = await ctx.ui.editor(`Edit ${file}`, before);
144
+ if (after === undefined || after === before) return;
145
+ try {
146
+ parseFrontmatter(after);
147
+ } catch (e) {
148
+ ctx.ui.notify(`Not saved — frontmatter doesn't parse: ${e instanceof Error ? e.message : String(e)}`, "error");
149
+ return;
150
+ }
151
+ writeFileSync(file, after, "utf8");
152
+ onChange();
153
+ ctx.ui.notify(`Saved ${file}.`, "info");
154
+ }
155
+
156
+ async function manageCustom(ctx: ExtensionContext, profile: AgentProfile, onChange: () => void): Promise<void> {
157
+ const file = agentFile(profile, ctx.cwd);
158
+ if (file === undefined) {
159
+ ctx.ui.notify(`Can't find the file for ${profile.id}.`, "error");
160
+ return;
161
+ }
162
+ const other = profile.source === "project" ? "global" : "project";
163
+ const EDIT = "Edit file";
164
+ const COPY = `Copy to ${other === "global" ? "all projects (global)" : "this project"}`;
165
+ const DELETE = "Delete";
166
+ const choice = await ctx.ui.select(`${profile.id} (${profile.source}) — ${file}`, [EDIT, COPY, DELETE]);
167
+ if (choice === EDIT) return editAgent(ctx, file, onChange);
168
+ if (choice === COPY) {
169
+ const target = join(agentsDir(other, ctx.cwd), `${profile.id}.md`);
170
+ if (existsSync(target) && !(await ctx.ui.confirm("Overwrite?", `${target} already exists.`))) return;
171
+ mkdirSync(dirname(target), { recursive: true });
172
+ writeFileSync(target, readFileSync(file, "utf8"), "utf8");
173
+ onChange();
174
+ ctx.ui.notify(`Copied to ${target}.`, "info");
175
+ return;
176
+ }
177
+ if (choice === DELETE && (await ctx.ui.confirm(`Delete ${profile.id}?`, file))) {
178
+ rmSync(file);
179
+ onChange();
180
+ ctx.ui.notify(`Deleted ${file}.`, "info");
181
+ }
182
+ }
183
+
184
+ export async function agentsManager(ctx: ExtensionContext, onChange: () => void): Promise<void> {
185
+ if (!ctx.hasUI) return;
186
+ while (true) {
187
+ const { profiles, warnings } = loadProfiles(ctx.cwd);
188
+ for (const w of warnings) ctx.ui.notify(`subagents: ${w}`, "warning");
189
+ const labels = profiles.map(describe);
190
+ const choice = await ctx.ui.select("Subagent profiles — pick one to manage, or create a new agent", [...labels, NEW_LABEL]);
191
+ if (choice === undefined) return;
192
+ if (choice === NEW_LABEL) {
193
+ await createAgent(ctx, profiles.map((p) => p.id), onChange);
194
+ continue;
195
+ }
196
+ const profile = profiles[labels.indexOf(choice)];
197
+ if (profile === undefined) return;
198
+ if (profile.source === "builtin") {
199
+ ctx.ui.notify(`${profile.id} is built in and can't be edited. Create a custom agent to change the model, tools or prompt.`, "info");
200
+ continue;
201
+ }
202
+ await manageCustom(ctx, profile, onChange);
203
+ }
204
+ }
205
+
206
+ export function registerAgentsCommand(pi: ExtensionAPI, deps: { onChange: (cwd: string) => void }): void {
207
+ const run = (ctx: ExtensionContext) => agentsManager(ctx, () => deps.onChange(ctx.cwd));
208
+ pi.registerCommand("unipi:agents", {
209
+ description: "Manage subagent profiles: list, create, edit, copy, delete custom agents",
210
+ handler: async (_args, ctx) => run(ctx),
211
+ });
212
+ registerCommandRunner(AGENTS_COMMAND, async (rawCtx) => run(rawCtx as ExtensionContext));
213
+ }