@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
@@ -35,13 +35,8 @@ export interface IconSet {
35
35
  directory: string;
36
36
 
37
37
  // Compactor segments
38
- sessionEvents: string;
39
- compactions: string;
40
- tokensSaved: string;
41
- compressionRatio: string;
42
38
  indexedDocs: string;
43
39
  sandboxRuns: string;
44
- searchQueries: string;
45
40
 
46
41
  // Memory segments
47
42
  projectCount: string;
@@ -106,13 +101,8 @@ export const NERD_ICONS: IconSet = {
106
101
  thinkingLevel: "\uF400", //
107
102
 
108
103
  // Compactor
109
- sessionEvents: "\uEA86", //
110
- compactions: "\u{F0C8F}", // 󰲏
111
- tokensSaved: "\uF155", // (kept — missing from customization)
112
- compressionRatio:"\u{F0C8F}", // 󰲏
113
104
  indexedDocs: "\u{F0219}", // 󰈙
114
105
  sandboxRuns: "\uF233", //
115
- searchQueries: "\uF002", //
116
106
 
117
107
  // Memory
118
108
  projectCount: "\uEE9C", //
@@ -177,13 +167,8 @@ export const EMOJI_ICONS: IconSet = {
177
167
  directory: "📁",
178
168
 
179
169
  // Compactor
180
- sessionEvents: "📈",
181
- compactions: "🗜️",
182
- tokensSaved: "💲",
183
- compressionRatio:"📐",
184
170
  indexedDocs: "📑",
185
171
  sandboxRuns: "▶️",
186
- searchQueries: "🔍",
187
172
 
188
173
  // Memory
189
174
  projectCount: "🧠",
@@ -248,13 +233,8 @@ export const TEXT_ICONS: IconSet = {
248
233
  directory: "DIR",
249
234
 
250
235
  // Compactor
251
- sessionEvents: "EVT",
252
- compactions: "CMP",
253
- tokensSaved: "SVD",
254
- compressionRatio:"RAT",
255
236
  indexedDocs: "IDX",
256
237
  sandboxRuns: "SBX",
257
- searchQueries: "QRY",
258
238
 
259
239
  // Memory
260
240
  projectCount: "MEM",
@@ -1,23 +1,16 @@
1
1
  /**
2
- * @pi-unipi/footer — Compactor segments
2
+ * @pi-unipi/footer — Compactor segment
3
3
  *
4
- * Segment renderers for the compactor group: session_events, compactions,
5
- * tokens_saved, compression_ratio, sandbox_runs, search_queries.
4
+ * One dense, icon-free segment, shown once the session has compacted:
6
5
  *
7
- * Data sourced from piContext.sessionManager (live session data).
8
- * Segments without a reliable data source are hidden (visible: false)
9
- * rather than showing a placeholder like "—".
6
+ * cmp 4× 39k→13k · 3m
7
+ *
8
+ * count, tokens before → after across all compactions, time since the last.
9
+ * Read from the session branch (Pi's compaction entries).
10
10
  */
11
11
 
12
12
  import type { FooterSegment, FooterSegmentContext, RenderedSegment } from "../types.js";
13
- import { applyColor, mutedPlaceholder } from "../rendering/theme.js";
14
- import { getIcon } from "../rendering/icons.js";
15
- import { isSegmentEnabled } from "../config.js";
16
-
17
- function withIcon(segmentId: string, text: string): string {
18
- const icon = getIcon(segmentId);
19
- return icon ? `${icon} ${text}` : text;
20
- }
13
+ import { applyColor } from "../rendering/theme.js";
21
14
 
22
15
  function formatTokens(n: number): string {
23
16
  if (n < 1000) return n.toString();
@@ -26,149 +19,66 @@ function formatTokens(n: number): string {
26
19
  return `${(n / 1000000).toFixed(1)}M`;
27
20
  }
28
21
 
29
- /** Hidden segment — no reliable data source available */
30
- function hidden(): RenderedSegment {
31
- return { content: "", visible: false };
22
+ function ago(ms: number): string {
23
+ const s = Math.max(0, Math.round(ms / 1000));
24
+ if (s < 60) return "now";
25
+ if (s < 3600) return `${Math.round(s / 60)}m`;
26
+ if (s < 86400) return `${Math.round(s / 3600)}h`;
27
+ return `${Math.round(s / 86400)}d`;
32
28
  }
33
29
 
34
- /** Safely extract sessionManager from piContext */
35
- function getSessionManager(ctx: FooterSegmentContext): any {
36
- const piCtx = ctx.piContext as Record<string, unknown> | undefined;
37
- return piCtx?.sessionManager as any | undefined;
30
+ /** Tokens left after a compaction: our recorded estimate, else the summary size. */
31
+ function tokensAfterOf(entry: any, before: number): number {
32
+ const recorded = Number(entry?.details?.tokensAfter ?? 0);
33
+ if (recorded > 0) return Math.min(recorded, before);
34
+ const summaryChars = typeof entry?.summary === "string" ? entry.summary.length : 0;
35
+ return Math.min(before, Math.ceil(summaryChars / 4));
38
36
  }
39
37
 
40
- /** Get all session events from sessionManager branch */
41
- function getSessionEvents(ctx: FooterSegmentContext): any[] {
42
- const sm = getSessionManager(ctx);
43
- if (!sm || typeof sm.getBranch !== "function") return [];
38
+ function branchOf(ctx: FooterSegmentContext): any[] {
39
+ const sm = (ctx.piContext as Record<string, unknown> | undefined)?.sessionManager as any;
44
40
  try {
45
- return sm.getBranch() ?? [];
41
+ return typeof sm?.getBranch === "function" ? (sm.getBranch() ?? []) : [];
46
42
  } catch {
47
43
  return [];
48
44
  }
49
45
  }
50
46
 
51
- function renderSessionEventsSegment(ctx: FooterSegmentContext): RenderedSegment {
52
- const events = getSessionEvents(ctx);
53
- const count = events.length;
54
- if (count === 0) {
55
- if (isSegmentEnabled("compactor", "session_events")) {
56
- return { content: mutedPlaceholder(withIcon("sessionEvents", "0")), visible: true };
57
- }
58
- return hidden();
47
+ export function compactionSummary(branch: readonly any[], now = Date.now()): { count: number; before: number; after: number; lastAt?: number } {
48
+ let count = 0;
49
+ let before = 0;
50
+ let after = 0;
51
+ let lastAt: number | undefined;
52
+ for (const e of branch) {
53
+ if (e?.type !== "compaction") continue;
54
+ count++;
55
+ const b = Number(e.tokensBefore ?? 0);
56
+ before += b;
57
+ after += tokensAfterOf(e, b);
58
+ const at = Date.parse(e.timestamp ?? "");
59
+ if (Number.isFinite(at) && at <= now) lastAt = at;
59
60
  }
60
-
61
- const content = withIcon("sessionEvents", `${count}`);
62
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
61
+ return { count, before, after, ...(lastAt != null ? { lastAt } : {}) };
63
62
  }
64
63
 
65
64
  function renderCompactionsSegment(ctx: FooterSegmentContext): RenderedSegment {
66
- // Count compaction entries in the session events
67
- const events = getSessionEvents(ctx);
68
- let compactionCount = 0;
69
- for (const e of events) {
70
- if (!e || typeof e !== "object") continue;
71
- if (e.type === "compaction" || e.type === "compacted") {
72
- compactionCount++;
73
- }
74
- }
75
- if (compactionCount === 0) {
76
- if (isSegmentEnabled("compactor", "compactions")) {
77
- return { content: mutedPlaceholder(withIcon("compactions", "0")), visible: true };
78
- }
79
- return hidden();
80
- }
81
-
82
- const content = withIcon("compactions", `${compactionCount}`);
83
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
84
- }
85
-
86
- function renderTokensSavedSegment(ctx: FooterSegmentContext): RenderedSegment {
87
- // Sum tokens saved from compaction entries.
88
- // Pi's CompactionEntry has tokensBefore (total tokens before compaction).
89
- // Compaction keeps ~10-15% of context, so tokens saved ≈ tokensBefore × 0.85.
90
- const events = getSessionEvents(ctx);
91
- let tokensSaved = 0;
92
- let hasCompaction = false;
93
- for (const e of events) {
94
- if (!e || typeof e !== "object") continue;
95
- if (e.type === "compaction") {
96
- hasCompaction = true;
97
- const tokensBefore = Number(e.tokensBefore ?? 0);
98
- // Estimate tokens kept at ~12% (compaction summary + recent messages)
99
- const tokensAfter = Math.round(tokensBefore * 0.12);
100
- tokensSaved += Math.max(0, tokensBefore - tokensAfter);
101
- }
102
- }
103
- if (!hasCompaction || tokensSaved === 0) return hidden();
104
-
105
- const content = withIcon("tokensSaved", formatTokens(tokensSaved));
106
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
107
- }
108
-
109
- function renderCompressionRatioSegment(ctx: FooterSegmentContext): RenderedSegment {
110
- // Calculate compression ratio from Pi's CompactionEntry.tokensBefore.
111
- // Compaction keeps ~12% of context, giving ~8:1 compression.
112
- const events = getSessionEvents(ctx);
113
- let totalBefore = 0;
114
- let totalAfter = 0;
115
- for (const e of events) {
116
- if (!e || typeof e !== "object") continue;
117
- if (e.type === "compaction") {
118
- const before = Number(e.tokensBefore ?? 0);
119
- if (before > 0) {
120
- totalBefore += before;
121
- totalAfter += Math.round(before * 0.12);
122
- }
123
- }
124
- }
125
- if (totalBefore === 0 || totalAfter === 0) return hidden();
126
-
127
- const ratio = totalBefore / totalAfter;
128
- const content = withIcon("compressionRatio", `${ratio.toFixed(1)}x`);
129
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
130
- }
131
-
132
- function renderSandboxRunsSegment(ctx: FooterSegmentContext): RenderedSegment {
133
- // Count sandbox events from session manager branch
134
- const events = getSessionEvents(ctx);
135
- let sandboxCount = 0;
136
- for (const e of events) {
137
- if (!e || typeof e !== "object") continue;
138
- // Count tool calls that are sandbox/execute tools
139
- const name = String((e as any).name ?? "").toLowerCase();
140
- if (name.includes("sandbox") || name.includes("ctx_execute") || name === "execute") {
141
- sandboxCount++;
142
- }
143
- }
144
- if (sandboxCount === 0) return hidden();
145
-
146
- const content = withIcon("sandboxRuns", `${sandboxCount}`);
147
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
148
- }
149
-
150
- function renderSearchQueriesSegment(ctx: FooterSegmentContext): RenderedSegment {
151
- // Count search events from session manager branch
152
- const events = getSessionEvents(ctx);
153
- let searchCount = 0;
154
- for (const e of events) {
155
- if (!e || typeof e !== "object") continue;
156
- const name = String((e as any).name ?? "").toLowerCase();
157
- if (name.includes("search") || name.includes("ctx_search")) {
158
- searchCount++;
159
- }
160
- }
161
- if (searchCount === 0) return hidden();
162
-
163
- const content = withIcon("searchQueries", `${searchCount}`);
164
- return { content: applyColor("compactor", content, ctx.theme, ctx.colors), visible: true };
65
+ const s = compactionSummary(branchOf(ctx));
66
+ if (s.count === 0) return { content: "", visible: false };
67
+ const dim = (t: string) => `\x1b[2m${t}\x1b[22m`;
68
+ const sizes = s.before > 0 ? ` ${formatTokens(s.before)}→${formatTokens(s.after)}` : "";
69
+ const age = s.lastAt != null ? dim(` · ${ago(Date.now() - s.lastAt)}`) : "";
70
+ const content = `${dim("cmp")} ${applyColor("compactor", `${s.count}×${sizes}`, ctx.theme, ctx.colors)}${age}`;
71
+ return { content, visible: true };
165
72
  }
166
73
 
167
74
  export const COMPACTOR_SEGMENTS: FooterSegment[] = [
168
- { id: "session_events", label: "Session Events", shortLabel: "EVT", description: "Number of session events", zone: "center", render: renderSessionEventsSegment, defaultShow: true },
169
- { id: "compactions", label: "Compactions", shortLabel: "CMP", description: "Number of context compactions", zone: "center", render: renderCompactionsSegment, defaultShow: true },
170
- { id: "tokens_saved", label: "Tokens Saved", shortLabel: "SVD", description: "Tokens saved by compaction", zone: "center", render: renderTokensSavedSegment, defaultShow: true },
171
- { id: "compression_ratio", label: "Compression Ratio", shortLabel: "RAT", description: "Last compaction compression ratio", zone: "center", render: renderCompressionRatioSegment, defaultShow: false },
172
- { id: "sandbox_runs", label: "Sandbox Runs", shortLabel: "SBX", description: "Number of sandbox code runs", zone: "center", render: renderSandboxRunsSegment, defaultShow: false },
173
- { id: "search_queries", label: "Search Queries", shortLabel: "QRY", description: "Number of search queries", zone: "center", render: renderSearchQueriesSegment, defaultShow: false },
75
+ {
76
+ id: "compactions",
77
+ label: "Compactions",
78
+ shortLabel: "CMP",
79
+ description: "Compactions this session: count, tokens before → after, time since the last (hidden until the first)",
80
+ zone: "center",
81
+ render: renderCompactionsSegment,
82
+ defaultShow: true,
83
+ },
174
84
  ];
@@ -23,7 +23,6 @@ const STATUS_DISPLAY: Record<string, { short: string; segmentId: string }> = {
23
23
  workflow: { short: "WF", segmentId: "currentCommand" },
24
24
  ralph: { short: "RL", segmentId: "activeLoops" },
25
25
  memory: { short: "MEM", segmentId: "projectCount" },
26
- compactor: { short: "CMP", segmentId: "compactions" },
27
26
  mcp: { short: "MCP", segmentId: "serversTotal" },
28
27
  notify: { short: "NTF", segmentId: "platformsEnabled" },
29
28
  kanboard: { short: "KB", segmentId: "docsCount" },
@@ -23,7 +23,7 @@ pi's extension and RPC APIs natively.
23
23
  │ Model key openrouter/deepseek/deepseek-v4.1-flash │
24
24
  │ │
25
25
  │ ✱ New ✱ Promotion ✱ Beta · Pairs frontier intelligence with cost-efficient execution │
26
- │↑↓ select · tab lead · ←→ effort · ↵ confirm · esc cancel │
26
+ │↑↓ select · tab lead · ←→ lead effort · space → side · ↵ confirm · esc │
27
27
  ╰───────────────────────────────────────────────────────────────────────╯
28
28
  ```
29
29
 
@@ -41,6 +41,11 @@ pi's extension and RPC APIs natively.
41
41
  - **`tab`** on the Fusion row cycles focus: effort → lead → sidekick
42
42
  (`shift+tab` reverses). Focused columns open an inline dropdown over the
43
43
  preset lists; `↵` applies, `esc` collapses.
44
+ - **`space`** on the Fusion row toggles which side `←`/`→` adjusts: lead
45
+ effort (default) ↔ sidekick effort. The row shows both levels (`Lead … ▸High`,
46
+ `Sidekick … Low` — `▸` marks the side `←`/`→` moves) and the hint line names
47
+ the target. A changed sidekick effort reaches a running sidekick over rpc
48
+ (`set_thinking_level`) and lands in the spawn args for the next process.
44
49
  - **Price panel**: the highlighted model's blended price is marked on a
45
50
  logarithmic red→orange→yellow→green→cyan→blue→violet slider. Fusion shows
46
51
  `Input`, `Cached input`, `Output`, `Sidekick input`, `Sidekick cached input`,
@@ -109,9 +114,14 @@ notification and any follow-up turn, so the report is not released at an
109
114
  intermediate checkpoint. If a prompt arrives while the child is processing,
110
115
  Fusion retries it once with pi's `followUp` streaming behavior. Sidekick work
111
116
  renders as ordinary tool activity — handoff ids, `agent_id`, and
112
- `read_subagent` protocol text never reach the terminal. Sidekick output keeps
113
- that same format on the main surface, marked as sidekick-origin by a `▍` rail.
114
- While the sidekick works and the lead's turn is over, an animated
117
+ `read_subagent` protocol text never reach the terminal. Sidekick steps draw in
118
+ the active render style — mcode rows in simple, `◆` + guttered output in
119
+ advanced, pi's own finished-call card (its exported `ToolExecutionComponent` +
120
+ per-tool defs) in regular — each line marked as sidekick-origin by a `▏` rail
121
+ on the custom-message background. Consecutive steps join into one rail block
122
+ with no blank separator; in simple mode a run of tool steps draws the same
123
+ `├…├…└` tree the lead's tools get, and prose steps stay `●`-anchored inside
124
+ the block. While the sidekick works and the lead's turn is over, an animated
115
125
  `sidekick working` line above the editor shows the live tool count and elapsed
116
126
  time until the report lands.
117
127
  `read_subagent({agent_id?, block?, timeout?})` reads or waits for a handoff.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/fusion",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.21",
4
4
  "description": "Devin-style model picker, fusion presets (lead + sidekick), and Local Fusion runtime for UniPi",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,8 +32,9 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.2",
36
- "@pi-unipi/subagents": "3.0.0-alpha.2"
35
+ "@pi-unipi/core": "3.0.0-alpha.21",
36
+ "@pi-unipi/subagents": "3.0.0-alpha.21",
37
+ "@pi-unipi/utility": "3.0.0-alpha.21"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "@earendil-works/pi-ai": "^0.87.1",
@@ -0,0 +1 @@
1
+ export { leadExtensionArgs } from "@pi-unipi/core/child-agent.js";
@@ -16,13 +16,14 @@
16
16
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
17
  import type { Api, Model } from "@earendil-works/pi-ai";
18
18
  import type { AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
19
- import { createSpinnerLine, registerCommandRunner, setHerdrWorking, setSharedFusionStatus, stateDir, UNIPI_PREFIX, HUB_OVERLAY_OPTIONS } from "@pi-unipi/core";
19
+ import { createSpinnerLine, harnessToolResultDetails, registerCommandRunner, setHerdrWorking, setSharedFusionStatus, stateDir, UNIPI_PREFIX, HUB_OVERLAY_OPTIONS, HUB_PICKER_OVERLAY_OPTIONS } from "@pi-unipi/core";
20
20
  import { join } from "node:path";
21
21
  import {
22
22
  effortLabel,
23
23
  globalPresetPath,
24
24
  isEffortLevel,
25
25
  loadPreset,
26
+ mirrorStartupFromPi,
26
27
  modelKey,
27
28
  pushRecent,
28
29
  saveCuration,
@@ -39,7 +40,10 @@ import { estimateSavings } from "./savings.js";
39
40
  import { EDIT_NUDGE, bashNudge, leadPolicy, sidekickSystemPrompt, type FusionIdentity } from "./prompts.js";
40
41
  import { isTrivialShell, BASH_NUDGE_EVERY } from "./nudge.js";
41
42
  import { registerFusionTools } from "./tools.js";
42
- import { duration } from "./transcript.js";
43
+ import { duration, renderSidekickStep } from "./transcript.js";
44
+ import { persistDefaultModel, piSettingsPath } from "./pi-settings.js";
45
+ import { ensureReadSubagentTool, setReadSubagentDemand } from "@pi-unipi/core/child-agent.js";
46
+ import { readFileSync } from "node:fs";
43
47
 
44
48
  export const MODEL_COMMAND = `${UNIPI_PREFIX}model`;
45
49
 
@@ -125,6 +129,15 @@ export function createSidekickWakeLine(deps: {
125
129
 
126
130
  type Registry = { getAvailable(): Model<Api>[]; find(provider: string, id: string): Model<Api> | undefined };
127
131
 
132
+ /** Keep /unipi:settings → Fusion → Startup showing pi's real default. */
133
+ function mirrorStartup(cwd: string): void {
134
+ try {
135
+ mirrorStartupFromPi(cwd);
136
+ } catch {
137
+ /* display mirror only */
138
+ }
139
+ }
140
+
128
141
  function registryOf(ctx: ExtensionContext): Registry | undefined {
129
142
  const r = (ctx as unknown as { modelRegistry?: Registry }).modelRegistry;
130
143
  return r && typeof r.getAvailable === "function" ? r : undefined;
@@ -133,7 +146,11 @@ function registryOf(ctx: ExtensionContext): Registry | undefined {
133
146
  /** Key-based lookup (provider ids can contain slashes, so splitModelKey+find is unreliable). */
134
147
  const modelBykey = new Map<string, Model<Api>>();
135
148
  function findModel(reg: Registry | undefined, key: string): Model<Api> | undefined {
136
- if (modelBykey.size === 0 && reg) for (const m of reg.getAvailable()) modelBykey.set(modelKey(m), m);
149
+ // Refill on a miss too: providers (e.g. bridges) can register models after the first lookup.
150
+ if (!modelBykey.has(key) && reg) {
151
+ modelBykey.clear();
152
+ for (const m of reg.getAvailable()) modelBykey.set(modelKey(m), m);
153
+ }
137
154
  return modelBykey.get(key);
138
155
  }
139
156
 
@@ -184,6 +201,27 @@ export function createModelBoostProvider(current: AutocompleteProvider): Autocom
184
201
  };
185
202
  }
186
203
 
204
+ export const FUSION_TOOLS = ["sidekick"] as const;
205
+
206
+ /**
207
+ * Adds the fusion tools to pi's active set while Fusion is active, removes
208
+ * them otherwise — touching ONLY `sidekick`/`read_subagent`, never another
209
+ * module's tools. Compares against the live active set (pi rebuilds it on
210
+ * session changes), and skips `setActiveTools` when nothing would change.
211
+ */
212
+ export function makeFusionToolSync(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">): (want: boolean) => void {
213
+ return (want: boolean) => {
214
+ const current = pi.getActiveTools();
215
+ if (FUSION_TOOLS.every((tool) => current.includes(tool) === want)) return;
216
+ const set = new Set(current);
217
+ for (const tool of FUSION_TOOLS) {
218
+ if (want) set.add(tool);
219
+ else set.delete(tool);
220
+ }
221
+ pi.setActiveTools([...set]);
222
+ };
223
+ }
224
+
187
225
  export default function fusionExtension(pi: ExtensionAPI): void {
188
226
  if (process.env.UNIPI_FUSION_CHILD === "1") return;
189
227
 
@@ -203,6 +241,16 @@ export default function fusionExtension(pi: ExtensionAPI): void {
203
241
  onHeld: (label) => setHerdrWorking(pi, "fusion-sidekick", label),
204
242
  });
205
243
 
244
+ const syncTools = makeFusionToolSync(pi);
245
+ // `read_subagent` presence is demand-driven across owners (core registry):
246
+ // fusion wants it only while a Fusion pair is active.
247
+ const syncFusionTools = () => {
248
+ const fusion = active?.kind === "fusion";
249
+ syncTools(fusion);
250
+ setReadSubagentDemand(pi, "fusion", fusion);
251
+ };
252
+ ensureReadSubagentTool(pi);
253
+
206
254
  function identity(ctx: ExtensionContext): FusionIdentity {
207
255
  const reg = registryOf(ctx);
208
256
  const names = (k: string) => findModel(reg, k)?.name || splitModelKey(k)?.id || k;
@@ -232,6 +280,8 @@ export default function fusionExtension(pi: ExtensionAPI): void {
232
280
  // the working lead lit, the sidekick muted.
233
281
  setSharedFusionStatus({
234
282
  leadName: names(active.lead),
283
+ leadKey: active.lead,
284
+ sidekickKey: active.sidekick,
235
285
  leadEffort: active.leadEffort ?? "",
236
286
  sidekickName: names(active.sidekick),
237
287
  sidekickEffort: active.sidekickEffort ?? "",
@@ -268,6 +318,17 @@ export default function fusionExtension(pi: ExtensionAPI): void {
268
318
  sessionFile: sidekickSessionPath(leadSessionId(ctx)),
269
319
  systemPrompt: sidekickSystemPrompt(identity(ctx)),
270
320
  onProgress: () => publishStatusLater(),
321
+ onStep: (step) => {
322
+ try {
323
+ // Group per handoff so resumed handoffs render as separate panels
324
+ // even with no lead turn between them; old entries without a
325
+ // group fall back to the shared "sidekick" run.
326
+ const handoffId = runtime?.latest()?.id;
327
+ pi.appendEntry("sidekick-step", { ...step, group: `sidekick:${handoffId ?? "default"}`, label: "Sidekick" });
328
+ } catch {
329
+ /* entry rendering never affects the handoff */
330
+ }
331
+ },
271
332
  });
272
333
  }
273
334
  return runtime;
@@ -293,6 +354,14 @@ export default function fusionExtension(pi: ExtensionAPI): void {
293
354
  return `Sidekick tokens: in ${String(runtime.usage.input)} · out ${String(runtime.usage.output)} · cached ${String(runtime.usage.cacheRead)} · cache write ${String(runtime.usage.cacheWrite)}\nSidekick cost: $${savings.sidekickUsd.toFixed(2)} · at lead prices: $${savings.atLeadUsd.toFixed(2)} · saved: $${savings.savedUsd.toFixed(2)}\nHandoffs: ${String(runtime.reports.size)} · runtime alive: ${String(runtime.isAlive())} · busy: ${String(runtime.isBusy())}${pricing}`;
294
355
  }
295
356
 
357
+ pi.registerEntryRenderer("sidekick-step", (entry: { data?: { group?: string; label?: string } }, options: { expanded?: boolean }, theme) =>
358
+ renderSidekickStep(
359
+ entry.data as never,
360
+ options.expanded === true,
361
+ theme as never,
362
+ { group: entry.data?.group ?? "sidekick", label: entry.data?.label },
363
+ ));
364
+
296
365
  registerFusionTools(pi, {
297
366
  getRuntime,
298
367
  onReport: (ctx) => publishStatusLater(ctx),
@@ -304,7 +373,19 @@ export default function fusionExtension(pi: ExtensionAPI): void {
304
373
  description: "Estimated Fusion savings (sidekick tokens priced at lead rates)",
305
374
  handler: async (_args, ctx) => ctx.ui.notify(savingsStats(ctx), "info"),
306
375
  });
307
- pi.on("before_agent_start", (event, ctx) => active?.kind === "fusion" ? { systemPrompt: `${event.systemPrompt}\n\n${leadPolicy(identity(ctx))}` } : undefined);
376
+ // Lead policy rides as a named system-prompt SECTION so pi records the
377
+ // section diff in the transcript (docs/extensions.md prefers sections over
378
+ // replacing `systemPrompt`, which nukes the cached prefix). The section is
379
+ // present only while Fusion is active.
380
+ pi.on("before_agent_start", (event, ctx) => {
381
+ syncFusionTools(); // safety net: re-sync after any module re-juggled tools
382
+ if (active?.kind === "fusion") {
383
+ event.systemPromptOptions.sections["fusion-lead-policy"] = leadPolicy(identity(ctx));
384
+ } else {
385
+ delete event.systemPromptOptions.sections["fusion-lead-policy"];
386
+ }
387
+ return undefined;
388
+ });
308
389
  pi.on("turn_start", () => {
309
390
  editNudgedThisTurn = false;
310
391
  });
@@ -324,16 +405,34 @@ export default function fusionExtension(pi: ExtensionAPI): void {
324
405
  if (toolName === "edit" || toolName === "write") {
325
406
  if (editNudgedThisTurn) return;
326
407
  editNudgedThisTurn = true;
327
- return { content: [...event.content, { type: "text" as const, text: EDIT_NUDGE }] };
408
+ return {
409
+ content: [...event.content, { type: "text" as const, text: EDIT_NUDGE }],
410
+ details: harnessToolResultDetails(
411
+ (event as { details?: unknown }).details,
412
+ { source: "Fusion", title: "Delegate edits", synopsis: "Direct edit instead of sidekick delegation", severity: "warning" },
413
+ "boundary",
414
+ EDIT_NUDGE,
415
+ ),
416
+ };
328
417
  }
329
418
  if (toolName !== "bash") return;
330
419
  const command = typeof event.input.command === "string" ? event.input.command : "";
331
420
  if (isTrivialShell(command)) return;
332
421
  bashStreak += 1;
333
422
  if (bashStreak < BASH_NUDGE_EVERY) return;
334
- const content = [...event.content, { type: "text" as const, text: bashNudge(bashStreak) }];
423
+ const annotation = bashNudge(bashStreak);
424
+ const synopsis = "Non-trivial shell work since last handoff";
425
+ const content = [...event.content, { type: "text" as const, text: annotation }];
335
426
  bashStreak = 0;
336
- return { content };
427
+ return {
428
+ content,
429
+ details: harnessToolResultDetails(
430
+ (event as { details?: unknown }).details,
431
+ { source: "Fusion", title: "Delegate shell work", synopsis, severity: "warning" },
432
+ "boundary",
433
+ annotation,
434
+ ),
435
+ };
337
436
  });
338
437
 
339
438
  async function applyResult(ctx: ExtensionContext, result: PickerResult, preset: FusionPreset, loaded: { globalPath: string; projectPath: string; hasProjectLayer: boolean }): Promise<void> {
@@ -352,6 +451,16 @@ export default function fusionExtension(pi: ExtensionAPI): void {
352
451
  return;
353
452
  }
354
453
  if (!samePair) stopRuntime();
454
+ else if (
455
+ result.type === "fusion" &&
456
+ active?.kind === "fusion" &&
457
+ active.sidekickEffort !== result.sidekickEffort
458
+ ) {
459
+ // Same pair, new sidekick strength: a live child gets it over rpc
460
+ // (set_thinking_level); the value also lands in the spawn args for the
461
+ // next process start.
462
+ runtime?.setThinking(result.sidekickEffort);
463
+ }
355
464
  const effort = result.type === "single" ? result.effort : result.leadEffort;
356
465
  try {
357
466
  pi.setThinkingLevel(effort);
@@ -382,6 +491,7 @@ export default function fusionExtension(pi: ExtensionAPI): void {
382
491
  });
383
492
  }
384
493
  saveRuntimeState(loaded.globalPath, { effort: result.effortMap, recent, active });
494
+ syncFusionTools();
385
495
  publishStatus(ctx);
386
496
  const label =
387
497
  result.type === "single"
@@ -410,6 +520,16 @@ export default function fusionExtension(pi: ExtensionAPI): void {
410
520
  return;
411
521
  }
412
522
  const currentKey = ctx.model ? modelKey(ctx.model) : undefined;
523
+ // pi's persisted startup default (settings.json) marks the `default` row.
524
+ let defaultKey: string | undefined;
525
+ try {
526
+ const raw = JSON.parse(readFileSync(piSettingsPath(), "utf8")) as { defaultProvider?: string; defaultModel?: string };
527
+ if (typeof raw.defaultProvider === "string" && typeof raw.defaultModel === "string") {
528
+ defaultKey = `${raw.defaultProvider}/${raw.defaultModel}`;
529
+ }
530
+ } catch {
531
+ /* no settings file yet */
532
+ }
413
533
  // Session truth wins over persisted state: if the user switched via pi's
414
534
  // own /model since, show that as the pinned row.
415
535
  if (active === undefined && preset.active !== undefined) active = preset.active;
@@ -430,14 +550,44 @@ export default function fusionExtension(pi: ExtensionAPI): void {
430
550
  recent: preset.recent,
431
551
  active,
432
552
  currentModelKey: currentKey,
553
+ defaultModelKey: defaultKey,
433
554
  effort: preset.effort,
434
555
  fallbackEffort,
435
556
  },
436
557
  theme: { fg: (c, s) => theme.fg(c as never, s), bold: (s) => theme.bold(s) },
437
558
  onDone: done,
559
+ onSetDefault: (r) => {
560
+ if (r.type === "cancelled") return;
561
+ const saveDefault = (key: string, effort: string): boolean => {
562
+ const parts = splitModelKey(key);
563
+ if (parts === undefined) {
564
+ ctx.ui.notify(`Could not save default: bad model key ${key}`, "error");
565
+ return false;
566
+ }
567
+ try {
568
+ persistDefaultModel({ provider: parts.provider, model: parts.id, thinkingLevel: effort });
569
+ return true;
570
+ } catch (error) {
571
+ ctx.ui.notify(`Could not save default: ${error instanceof Error ? error.message : String(error)}`, "error");
572
+ return false;
573
+ }
574
+ };
575
+ if (r.type === "single") {
576
+ if (saveDefault(r.model, r.effort)) ctx.ui.notify(`Default: ${r.model} · ${r.effort}`, "info");
577
+ mirrorStartup(cwd);
578
+ } else if (saveDefault(r.lead, r.leadEffort)) {
579
+ mirrorStartup(cwd);
580
+ saveRuntimeState(loaded.globalPath, {
581
+ effort: r.effortMap,
582
+ recent: preset.recent,
583
+ active: { kind: "fusion", lead: r.lead, sidekick: r.sidekick, leadEffort: r.leadEffort, sidekickEffort: r.sidekickEffort },
584
+ });
585
+ ctx.ui.notify(`Default: Fusion (${r.lead} + ${r.sidekick})`, "info");
586
+ }
587
+ },
438
588
  onRenderRequest: () => tui.requestRender(),
439
589
  }),
440
- HUB_OVERLAY_OPTIONS,
590
+ HUB_PICKER_OVERLAY_OPTIONS,
441
591
  );
442
592
  await applyResult(ctx, result, preset, loaded);
443
593
  },
@@ -485,6 +635,7 @@ export default function fusionExtension(pi: ExtensionAPI): void {
485
635
  bashStreak = 0;
486
636
  modelBykey.clear();
487
637
  active = loadPreset(ctx.cwd ?? process.cwd()).preset.active;
638
+ mirrorStartup(ctx.cwd ?? process.cwd());
488
639
  if (active?.kind === "fusion" && (!ctx.model || modelKey(ctx.model) !== active.lead)) {
489
640
  const leadKey = active.lead;
490
641
  const lead = findModel(registryOf(ctx), leadKey);
@@ -500,6 +651,7 @@ export default function fusionExtension(pi: ExtensionAPI): void {
500
651
  if (ctx.hasUI) ctx.ui.notify(`Fusion lead ${leadKey} unavailable — Fusion off`, "warning");
501
652
  }
502
653
  }
654
+ syncFusionTools();
503
655
  publishStatus(ctx);
504
656
  if (ctx.hasUI) ctx.ui.addAutocompleteProvider(createModelBoostProvider);
505
657
  });
@@ -517,6 +669,7 @@ export default function fusionExtension(pi: ExtensionAPI): void {
517
669
  active = { kind: "single", model: modelKey(event.model) };
518
670
  const loaded = loadPreset(ctx.cwd ?? process.cwd());
519
671
  saveRuntimeState(globalPresetPath(), { effort: loaded.preset.effort, recent: loaded.preset.recent, active });
672
+ syncFusionTools();
520
673
  publishStatus(ctx);
521
674
  }
522
675
  });