@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
@@ -8,10 +8,10 @@
8
8
  */
9
9
 
10
10
  import { Type } from "typebox";
11
- import { Box, Text, type Component } from "@earendil-works/pi-tui";
12
- import { UNIPI_EVENTS, emitEvent } from "@pi-unipi/core";
11
+ import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
12
+ import { UNIPI_EVENTS, emitEvent, meter, rail, type KitTheme } from "@pi-unipi/core";
13
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
- import type { SessionBackend, SearchHit, StoreResult } from "./session.js";
14
+ import type { SessionBackend, SearchHit } from "./session.js";
15
15
  import { findByTitle, findSimilar } from "./files.js";
16
16
  import { MEMORY_TYPES, type MemoryType } from "./paths.js";
17
17
 
@@ -31,60 +31,54 @@ export interface ToolActivity {
31
31
  onWriteDone?: () => void;
32
32
  }
33
33
 
34
- interface ThemeLike {
35
- fg: (color: string, text: string) => string;
36
- bold: (text: string) => string;
37
- bg?: (color: string, text: string) => string;
38
- }
34
+ type ThemeLike = KitTheme;
39
35
 
40
- /** Memory's own colour: sidekick owns accent/success/error for its rail. */
41
- const MEMORY_COLOR = "syntaxKeyword";
42
- const MEMORY_GLYPH = "◈";
36
+ /** Memory's own colour (purple) — the rail that marks every memory surface. */
37
+ const MEMORY_COLOR = "customMessageLabel";
43
38
 
44
39
  export type CardTone = "memory" | "success" | "warning" | "error";
45
40
 
46
- class RailComponent implements Component {
47
- constructor(private readonly inner: Component, private readonly rail: string) {}
48
- render(width: number): string[] {
49
- return this.inner.render(Math.max(1, width - 2)).map((line) => `${this.rail} ${line}`);
50
- }
51
- invalidate(): void {
52
- this.inner.invalidate?.();
53
- }
54
- }
41
+ /** One rail row: left text, optional right-aligned text. */
42
+ export type RailRow = string | { left: string; right?: string };
55
43
 
56
- /** Shaded card with a memory-coloured rail (same shape as the sidekick frame).
57
- * The first line is the header; `tone` recolours the rail for outcomes. */
58
- export function memoryCard(theme: ThemeLike, lines: string[], tone: CardTone = "memory"): Component {
59
- const [head = "", ...rest] = lines;
60
- const body = [theme.fg(MEMORY_COLOR, theme.bold(head)), ...rest.map((l) => theme.fg("muted", l))].join("\n");
61
- const rail = theme.fg(tone === "memory" ? MEMORY_COLOR : tone, "▍");
62
- const box = new Box(1, 0, (text: string) => (theme.bg ? theme.bg("customMessageBg", text) : text));
63
- box.addChild(new RailComponent(new Text(body, 0, 0), rail));
64
- return box;
44
+ /**
45
+ * Rail card (no tinted box):
46
+ * ▌ Memory "subagent test UX" 5 hits · 1 project
47
+ * ▌ ██████▌░ pi_test_kanboard_sanity pi_test › summary · pi
48
+ * `tone` recolours the rail for warning/error outcomes.
49
+ */
50
+ export function memoryCard(theme: ThemeLike, rows: RailRow[], tone: CardTone = "memory"): Component {
51
+ const color = tone === "warning" || tone === "error" ? tone : MEMORY_COLOR;
52
+ return {
53
+ invalidate() {},
54
+ render: (w: number) => rows.map((r) => truncateToWidth(typeof r === "string" ? rail(theme, color, r, "", w) : rail(theme, color, r.left, r.right ?? "", w), w)),
55
+ };
65
56
  }
66
57
 
67
- function frame(theme: ThemeLike, lines: string[], tone: CardTone = "memory"): Component {
68
- return memoryCard(theme, lines, tone);
58
+ const EMPTY: Component = { invalidate() {}, render: () => [] };
59
+
60
+ /** Pending call line; disappears once the result card takes over. */
61
+ function pendingLine(theme: ThemeLike, context: unknown, text: string): Component {
62
+ if ((context as { isPartial?: boolean } | undefined)?.isPartial === false) return EMPTY;
63
+ return memoryCard(theme, [`${theme.bold("Memory")} ${theme.fg("dim", text)}`]);
69
64
  }
70
65
 
71
66
  function outcomeTone(outcome: unknown): CardTone {
72
67
  return outcome === "filed" ? "success" : outcome === "queued" ? "warning" : outcome ? "error" : "memory";
73
68
  }
74
69
 
75
- function scoreBar(score: number): string {
76
- const filled = Math.max(0, Math.min(5, Math.round(score * 5)));
77
- return "▰".repeat(filled) + "▱".repeat(5 - filled);
78
- }
79
-
80
70
  function outcomeLabel(outcome: string): string {
81
71
  switch (outcome) {
82
- case "filed": return "filed ✓";
83
- case "queued": return "queued ⧗";
84
- default: return "markdown only ⚠";
72
+ case "filed": return "✓ filed";
73
+ case "queued": return "⧗ queued";
74
+ default: return "⚠ markdown only";
85
75
  }
86
76
  }
87
77
 
78
+ function outcomeColor(outcome: string): "success" | "warning" | "error" {
79
+ return outcome === "filed" ? "success" : outcome === "queued" ? "warning" : "error";
80
+ }
81
+
88
82
  function sourceLabel(raw: string): string {
89
83
  switch (raw) {
90
84
  case "unipi":
@@ -95,35 +89,84 @@ function sourceLabel(raw: string): string {
95
89
  }
96
90
  }
97
91
 
98
- /** Text-only mirror of the renderers, for tests + headless output. */
99
- export function renderStoreLine(res: StoreResult, similar: string[] = []): string {
100
- const lines = [
101
- `${MEMORY_GLYPH} remembered ${res.record.title}`,
102
- `${res.record.project} › ${res.record.type} · ${outcomeLabel(res.outcome)}`,
92
+ const plural = (n: number, word: string) => `${String(n)} ${word}${n === 1 ? "" : "s"}`;
93
+
94
+ /** `▌ Memory title ✓ filed project › type` (+ similar rows). */
95
+ export function storeRows(
96
+ t: ThemeLike,
97
+ d: { action?: string; title?: string; project?: string; type?: string; outcome?: string; similar?: string[] },
98
+ ): RailRow[] {
99
+ const outcome = d.outcome ?? "filed";
100
+ return [
101
+ {
102
+ left: `${t.bold("Memory")} ${d.action === "updated" ? t.fg("dim", "updated ") : ""}${d.title ?? ""}`,
103
+ right: `${t.fg(outcomeColor(outcome), outcomeLabel(outcome))} ${t.fg("dim", `${d.project ?? ""} › ${d.type ?? ""}`)}`,
104
+ },
105
+ ...(d.similar ?? []).map((s) => t.fg("dim", `~ similar: ${s}`)),
103
106
  ];
104
- for (const s of similar) lines.push(`~ similar: ${s}`);
105
- return lines.join("\n");
106
107
  }
107
108
 
108
- export function renderSearchLines(query: string, hits: SearchHit[], shown = hits.length): string[] {
109
+ /** Head `Memory "query" … N hits · P projects`, then one meter row per hit. */
110
+ export function searchRows(t: ThemeLike, query: string, hits: SearchHit[], shown = hits.length): RailRow[] {
109
111
  const projects = new Set(hits.map((h) => h.wing)).size;
110
- const lines = [`${MEMORY_GLYPH} recalled "${query}" · ${hits.length} memories · ${projects} projects`];
111
- for (const h of hits.slice(0, shown)) {
112
- lines.push(`${scoreBar(h.score)} ${h.title} ${h.wing} › ${h.room} · ${sourceLabel(h.sourceLabel)}`);
113
- }
114
- return lines;
112
+ return [
113
+ { left: `${t.bold("Memory")} "${query}"`, right: t.fg("dim", `${plural(hits.length, "hit")} · ${plural(projects, "project")}`) },
114
+ ...hits.slice(0, shown).map((h) => ({
115
+ left: `${meter(t, h.score, 8, MEMORY_COLOR)} ${h.title}`,
116
+ right: t.fg("dim", `${h.wing} › ${h.room} · ${sourceLabel(h.sourceLabel)}`),
117
+ })),
118
+ ];
115
119
  }
116
120
 
117
- export function registerMemoryTools(
118
- pi: ExtensionAPI,
121
+ /** Search result card (also used by the global alias). */
122
+ function searchCard(t: ThemeLike, details: unknown, expanded: boolean): Component {
123
+ const d = details as { query?: string; hits?: SearchHit[] } | undefined;
124
+ const hits = d?.hits ?? [];
125
+ if (hits.length === 0) return memoryCard(t, [{ left: `${t.bold("Memory")} "${d?.query ?? ""}"`, right: t.fg("dim", "no matches") }]);
126
+ const shown = expanded ? hits.length : Math.min(5, hits.length);
127
+ const rows = searchRows(t, d?.query ?? "", hits, shown);
128
+ if (hits.length > shown) rows.push(t.fg("dim", `… ${String(hits.length - shown)} more`));
129
+ return memoryCard(t, rows);
130
+ }
131
+
132
+ /** `Memory 12 stored` + one row per memory (title … type / project). */
133
+ function listCard(t: ThemeLike, details: unknown, expanded: boolean, global: boolean): Component {
134
+ const mems = (details as { memories?: Array<{ title: string; type: string; project?: string }> } | undefined)?.memories ?? [];
135
+ const shown = expanded ? mems : mems.slice(0, 8);
136
+ const rows: RailRow[] = [{ left: `${t.bold("Memory")} ${String(mems.length)} ${mems.length === 1 ? "memory" : "memories"}`, right: t.fg("dim", global ? "all projects" : "this project") }];
137
+ for (const m of shown) rows.push({ left: m.title, right: t.fg("dim", global ? `${m.project ?? ""} › ${m.type}` : m.type) });
138
+ if (mems.length > shown.length) rows.push(t.fg("dim", `… ${String(mems.length - shown.length)} more`));
139
+ return memoryCard(t, rows);
140
+ }
141
+
142
+ /** The real tool bodies, callable outside the main registration — the
143
+ * background save session mounts these on its stub tools. */
144
+ export interface MemoryExecutors {
145
+ store: (
146
+ params: { title: string; content: string; tags?: string[]; type?: string },
147
+ ctx: { cwd: string },
148
+ ) => Promise<{ content: { type: "text"; text: string }[]; details: Record<string, unknown> }>;
149
+ search: (params: {
150
+ query: string;
151
+ limit?: number;
152
+ scope?: string;
153
+ }) => Promise<{ content: { type: "text"; text: string }[]; details: Record<string, unknown> }>;
154
+ remove: (params: {
155
+ title?: string;
156
+ id?: string;
157
+ }) => Promise<{ content: { type: "text"; text: string }[]; details: Record<string, unknown> }>;
158
+ list: () => Promise<{ content: { type: "text"; text: string }[]; details: Record<string, unknown> }>;
159
+ globalList: () => Promise<{ content: { type: "text"; text: string }[]; details: Record<string, unknown> }>;
160
+ }
161
+
162
+ type EventSink = { events: { emit: (name: string, payload: unknown) => void } };
163
+
164
+ export function memoryExecutors(
165
+ pi: EventSink,
119
166
  backend: () => SessionBackend | null,
120
167
  activity?: ToolActivity,
121
- options?: { neutral?: boolean },
122
- ): void {
168
+ ): MemoryExecutors {
123
169
  const W = () => backend();
124
- // recallAtStart=off swaps the "call BEFORE work" nudge for neutral wording —
125
- // the tools stay callable, the agent just isn't ordered to use them first.
126
- const neutral = options?.neutral === true;
127
170
  const localPeek = (title: string) => {
128
171
  const b = W();
129
172
  if (!b) return { exact: null as ReturnType<typeof findByTitle>, similar: [] as string[] };
@@ -135,7 +178,7 @@ export function registerMemoryTools(
135
178
  return { exact, similar };
136
179
  };
137
180
 
138
- const storeExecute = async (
181
+ const store = async (
139
182
  params: { title: string; content: string; tags?: string[]; type?: string },
140
183
  ctx: { cwd: string },
141
184
  ) => {
@@ -195,6 +238,116 @@ export function registerMemoryTools(
195
238
  };
196
239
  };
197
240
 
241
+ const search = async (
242
+ params: { query: string; limit?: number; scope?: string },
243
+ ) => {
244
+ activity?.onRecall?.();
245
+ const b = W();
246
+ if (!b) {
247
+ return {
248
+ content: [{ type: "text" as const, text: `Memory search unavailable — the memory backend isn't running.` }],
249
+ details: { results: [] },
250
+ };
251
+ }
252
+ const limit = params.limit || 10;
253
+ const scope = params.scope === "project" ? "project" : "all";
254
+ const hits = await b.search(params.query, limit, scope);
255
+ if (hits.length === 0) {
256
+ return {
257
+ content: [{ type: "text" as const, text: `No memories found for: "${params.query}"` }],
258
+ details: { query: params.query, hits: [] },
259
+ };
260
+ }
261
+ const output = hits
262
+ .map((h, i) => `${i + 1}. [${h.wing}] **${h.title}** (${h.room} · ${sourceLabel(h.sourceLabel)})\n ${h.snippet}`)
263
+ .join("\n\n");
264
+ return {
265
+ content: [{ type: "text" as const, text: `Found ${hits.length} memories:\n\n${output}` }],
266
+ details: { query: params.query, hits },
267
+ };
268
+ };
269
+
270
+ const remove = async (params: { title?: string; id?: string }) => {
271
+ activity?.onStore?.();
272
+ const b = W();
273
+ if (!b) {
274
+ return { content: [{ type: "text" as const, text: `Memory backend unavailable — nothing deleted.` }], details: { deleted: false } };
275
+ }
276
+ const key = params.id || params.title;
277
+ if (!key) {
278
+ return { content: [{ type: "text" as const, text: "Provide a title or id to delete." }], details: { deleted: false } };
279
+ }
280
+ const res = await b.delete(b.project, key);
281
+ activity?.onWriteDone?.();
282
+ if (res.found) {
283
+ emitEvent(pi, UNIPI_EVENTS.MEMORY_DELETED, { id: key, title: key, project: b.project });
284
+ }
285
+ return {
286
+ content: [{ type: "text" as const, text: res.found ? `Deleted memory: ${key} (${res.outcome})` : `Memory not found: ${key}` }],
287
+ details: { deleted: res.found, id: key, title: key, outcome: res.outcome },
288
+ };
289
+ };
290
+
291
+ const list = async () => {
292
+ activity?.onRecall?.();
293
+ const b = W();
294
+ const memories = b ? await b.list() : [];
295
+ if (memories.length === 0) {
296
+ return { content: [{ type: "text" as const, text: "No memories stored for this project." }], details: { memories: [] } };
297
+ }
298
+ const output = memories.map((m) => `- ${m.title} (${m.type})`).join("\n");
299
+ return {
300
+ content: [{ type: "text" as const, text: `Project memories (${memories.length}):\n\n${output}` }],
301
+ details: { memories },
302
+ };
303
+ };
304
+
305
+ const globalList = async () => {
306
+ activity?.onRecall?.();
307
+ const b = W();
308
+ if (!b) {
309
+ return { content: [{ type: "text" as const, text: "Memory backend unavailable." }], details: { memories: [] } };
310
+ }
311
+ // Pull every project dir's md files for a cross-project list.
312
+ const { listProjectDirs } = await import("./files.js");
313
+ const { sanitizeProjectName } = await import("./paths.js");
314
+ const memories: Array<{ project: string; id: string; title: string; type: string }> = [];
315
+ for (const { name } of listProjectDirs()) {
316
+ for (const m of await b.list(sanitizeProjectName(name))) {
317
+ memories.push(m);
318
+ }
319
+ }
320
+ if (memories.length === 0) {
321
+ return { content: [{ type: "text" as const, text: "No memories stored in any project." }], details: { memories: [] } };
322
+ }
323
+ const grouped = new Map<string, typeof memories>();
324
+ for (const m of memories) {
325
+ grouped.set(m.project, [...(grouped.get(m.project) ?? []), m]);
326
+ }
327
+ let output = "";
328
+ for (const [p, ms] of grouped) {
329
+ output += `\n**${p}** (${ms.length}):\n` + ms.map((m) => ` - ${m.title} (${m.type})`).join("\n") + "\n";
330
+ }
331
+ return {
332
+ content: [{ type: "text" as const, text: `All memories across ${grouped.size} projects (${memories.length} total):${output}` }],
333
+ details: { memories },
334
+ };
335
+ };
336
+
337
+ return { store, search, remove, list, globalList };
338
+ }
339
+
340
+ export function registerMemoryTools(
341
+ pi: ExtensionAPI,
342
+ backend: () => SessionBackend | null,
343
+ activity?: ToolActivity,
344
+ options?: { neutral?: boolean },
345
+ ): void {
346
+ // recallAtStart=off swaps the "call BEFORE work" nudge for neutral wording —
347
+ // the tools stay callable, the agent just isn't ordered to use them first.
348
+ const neutral = options?.neutral === true;
349
+ const ex = memoryExecutors(pi, backend, activity);
350
+
198
351
  pi.registerTool({
199
352
  name: MEMORY_TOOLS.STORE,
200
353
  label: "Store Memory",
@@ -218,52 +371,19 @@ export function registerMemoryTools(
218
371
  tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorization" })),
219
372
  type: Type.Optional(Type.String({ description: "Memory type", enum: [...MEMORY_TYPES] })),
220
373
  }),
221
- renderCall: (args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", `remembering ${String(args.title ?? "").slice(0, 60)}…`)}`, 0, 0),
374
+ renderShell: "self",
375
+ renderCall: (args, theme, context) => pendingLine(theme, context, `remembering ${String(args.title ?? "").slice(0, 60)}…`),
222
376
  renderResult: (result, _o, theme) => {
223
377
  const d = result.details as { action?: string; outcome?: string; similar?: string[]; title?: string; project?: string; type?: string } | undefined;
224
378
  const text = result.content?.map((c) => ("text" in c ? c.text : "")).join("\n") ?? "";
225
- if (!d?.outcome) return frame(theme as ThemeLike, [`${MEMORY_GLYPH} ${text.split("\n")[0] ?? "memory"}`]);
226
- const lines = [
227
- `${MEMORY_GLYPH} ${d.action === "updated" ? "updated" : "remembered"} ${d.title ?? ""}`,
228
- `${d.project ?? ""} › ${d.type ?? ""} · ${outcomeLabel(d.outcome)}`,
229
- ...(d.similar ?? []).map((s) => `~ similar: ${s}`),
230
- ];
231
- return frame(theme as ThemeLike, lines, outcomeTone(d.outcome));
379
+ if (!d?.outcome) return memoryCard(theme, [`${theme.bold("Memory")} ${theme.fg("warning", text.split("\n")[0] ?? "")}`], "warning");
380
+ return memoryCard(theme, storeRows(theme, d), outcomeTone(d.outcome));
232
381
  },
233
382
  async execute(_id, params, _s, _o, ctx) {
234
- return storeExecute(params, ctx);
383
+ return ex.store(params, ctx);
235
384
  },
236
385
  });
237
386
 
238
- const searchExecute = async (
239
- params: { query: string; limit?: number; scope?: string },
240
- ) => {
241
- activity?.onRecall?.();
242
- const b = W();
243
- if (!b) {
244
- return {
245
- content: [{ type: "text" as const, text: `Memory search unavailable — the memory backend isn't running.` }],
246
- details: { results: [] },
247
- };
248
- }
249
- const limit = params.limit || 10;
250
- const scope = params.scope === "project" ? "project" : "all";
251
- const hits = await b.search(params.query, limit, scope);
252
- if (hits.length === 0) {
253
- return {
254
- content: [{ type: "text" as const, text: `No memories found for: "${params.query}"` }],
255
- details: { results: [] },
256
- };
257
- }
258
- const output = hits
259
- .map((h, i) => `${i + 1}. [${h.wing}] **${h.title}** (${h.room} · ${sourceLabel(h.sourceLabel)})\n ${h.snippet}`)
260
- .join("\n\n");
261
- return {
262
- content: [{ type: "text" as const, text: `Found ${hits.length} memories:\n\n${output}` }],
263
- details: { query: params.query, hits },
264
- };
265
- };
266
-
267
387
  pi.registerTool({
268
388
  name: MEMORY_TOOLS.SEARCH,
269
389
  label: "Search Memory",
@@ -291,17 +411,19 @@ export function registerMemoryTools(
291
411
  limit: Type.Optional(Type.Number({ description: "Max results (default 10)", default: 10 })),
292
412
  scope: Type.Optional(Type.String({ description: "'all' (default) or 'project'", enum: ["all", "project"], default: "all" })),
293
413
  }),
294
- renderCall: (args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", `searching "${String(args.query).slice(0, 60)}"`)}`, 0, 0),
295
- renderResult: (result, options, theme) => {
296
- const d = result.details as { query?: string; hits?: SearchHit[] } | undefined;
297
- const hits = d?.hits ?? [];
298
- const shown = options.expanded ? hits.length : Math.min(5, hits.length);
299
- const lines = renderSearchLines(d?.query ?? "", hits, shown);
300
- if (hits.length > shown) lines.push(`… ${hits.length - shown} more`);
301
- return frame(theme as ThemeLike, lines);
302
- },
414
+ renderShell: "self",
415
+ renderCall: (args, theme, context) => pendingLine(theme, context, `searching "${String(args.query).slice(0, 60)}"…`),
416
+ renderResult: (result, options, theme) => searchCard(theme, result.details, options.expanded),
417
+ // Retrieval is the deliberate exception to compact tool results: simple
418
+ // mode keeps the full search rail (every hit with its meter and source)
419
+ // under the collapsed row — a bare "N output lines" would hide exactly
420
+ // what the search was for.
421
+ ...( {
422
+ simpleMeta: () => "",
423
+ simpleResult: (result: { details?: unknown }, theme: ThemeLike) => searchCard(theme, result.details, true),
424
+ } as object),
303
425
  async execute(_id, params, _s, _o, _ctx) {
304
- return searchExecute(params);
426
+ return ex.search(params);
305
427
  },
306
428
  });
307
429
 
@@ -314,18 +436,16 @@ export function registerMemoryTools(
314
436
  query: Type.String({ description: "Search query" }),
315
437
  limit: Type.Optional(Type.Number({ default: 10 })),
316
438
  }),
317
- renderCall: (args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", `searching "${String(args.query).slice(0, 60)}"`)}`, 0, 0),
318
- renderResult: (result, options, theme) => {
319
- const d = result.details as { query?: string; hits?: SearchHit[] } | undefined;
320
- const hits = d?.hits ?? [];
321
- const shown = options.expanded ? hits.length : Math.min(5, hits.length);
322
- const lines = renderSearchLines(d?.query ?? "", hits, shown);
323
- if (hits.length > shown) lines.push(`… ${hits.length - shown} more`);
324
- return frame(theme as ThemeLike, lines);
325
- },
439
+ renderShell: "self",
440
+ renderCall: (args, theme, context) => pendingLine(theme, context, `searching "${String(args.query).slice(0, 60)}"…`),
441
+ renderResult: (result, options, theme) => searchCard(theme, result.details, options.expanded),
442
+ // Same retrieval exception as memory_search.
443
+ ...( {
444
+ simpleMeta: () => "",
445
+ simpleResult: (result: { details?: unknown }, theme: ThemeLike) => searchCard(theme, result.details, true),
446
+ } as object),
326
447
  async execute(_id, params, _s, _o, _ctx) {
327
- activity?.onRecall?.();
328
- return searchExecute({ query: params.query, limit: params.limit, scope: "all" });
448
+ return ex.search({ query: params.query, limit: params.limit, scope: "all" });
329
449
  },
330
450
  });
331
451
 
@@ -338,34 +458,16 @@ export function registerMemoryTools(
338
458
  title: Type.Optional(Type.String({ description: "Memory title to delete" })),
339
459
  id: Type.Optional(Type.String({ description: "Memory ID to delete" })),
340
460
  }),
341
- renderCall: (_args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", "forgetting…")}`, 0, 0),
461
+ renderShell: "self",
462
+ renderCall: (_args, theme, context) => pendingLine(theme, context, "forgetting…"),
342
463
  renderResult: (result, _o, theme) => {
343
464
  const d = result.details as Record<string, unknown> | undefined;
344
- const lines = [
345
- `${MEMORY_GLYPH} forgot ${String(d?.title ?? d?.id ?? "memory")}`,
346
- String(d?.deleted ? outcomeLabel(String(d.outcome ?? "filed")) : "not found"),
347
- ];
348
- return frame(theme as ThemeLike, lines, d?.deleted ? outcomeTone(d.outcome ?? "filed") : "warning");
465
+ const outcome = String(d?.outcome ?? "filed");
466
+ const right = d?.deleted ? theme.fg(outcomeColor(outcome), outcomeLabel(outcome)) : theme.fg("warning", "not found");
467
+ return memoryCard(theme, [{ left: `${theme.bold("Memory")} ${theme.fg("dim", "forgot")} ${String(d?.title ?? d?.id ?? "memory")}`, right }], d?.deleted ? outcomeTone(outcome) : "warning");
349
468
  },
350
469
  async execute(_id, params, _s, _o, _ctx) {
351
- activity?.onStore?.();
352
- const b = W();
353
- if (!b) {
354
- return { content: [{ type: "text" as const, text: "Memory backend unavailable — nothing deleted." }], details: { deleted: false } };
355
- }
356
- const key = params.id || params.title;
357
- if (!key) {
358
- return { content: [{ type: "text" as const, text: "Provide a title or id to delete." }], details: { deleted: false } };
359
- }
360
- const res = await b.delete(b.project, key);
361
- activity?.onWriteDone?.();
362
- if (res.found) {
363
- emitEvent(pi, UNIPI_EVENTS.MEMORY_DELETED, { id: key, title: key, project: b.project });
364
- }
365
- return {
366
- content: [{ type: "text" as const, text: res.found ? `Deleted memory: ${key} (${res.outcome})` : `Memory not found: ${key}` }],
367
- details: { deleted: res.found, id: key, title: key, outcome: res.outcome },
368
- };
470
+ return ex.remove(params);
369
471
  },
370
472
  });
371
473
 
@@ -375,28 +477,11 @@ export function registerMemoryTools(
375
477
  description: "List all memories for the current project.",
376
478
  promptSnippet: "List all project memories.",
377
479
  parameters: Type.Object({}),
378
- renderCall: (_args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", "listing")}`, 0, 0),
379
- renderResult: (result, options, theme) => {
380
- const d = result.details as { memories?: Array<{ title: string; type: string }> } | undefined;
381
- const mems = d?.memories ?? [];
382
- const shown = options.expanded ? mems : mems.slice(0, 8);
383
- const lines = [`${MEMORY_GLYPH} memories · ${mems.length} stored`];
384
- for (const m of shown) lines.push(`${m.title} · ${m.type}`);
385
- if (!options.expanded && mems.length > shown.length) lines.push(`… ${mems.length - shown.length} more`);
386
- return frame(theme as ThemeLike, lines);
387
- },
480
+ renderShell: "self",
481
+ renderCall: (_args, theme, context) => pendingLine(theme, context, "listing…"),
482
+ renderResult: (result, options, theme) => listCard(theme, result.details, options.expanded, false),
388
483
  async execute(_id, _p, _s, _o, _ctx) {
389
- activity?.onRecall?.();
390
- const b = W();
391
- const memories = b ? await b.list() : [];
392
- if (memories.length === 0) {
393
- return { content: [{ type: "text" as const, text: "No memories stored for this project." }], details: { memories: [] } };
394
- }
395
- const output = memories.map((m) => `- ${m.title} (${m.type})`).join("\n");
396
- return {
397
- content: [{ type: "text" as const, text: `Project memories (${memories.length}):\n\n${output}` }],
398
- details: { memories },
399
- };
484
+ return ex.list();
400
485
  },
401
486
  });
402
487
 
@@ -405,46 +490,11 @@ export function registerMemoryTools(
405
490
  label: "List All Project Memories",
406
491
  description: "List all memories across all projects with project names.",
407
492
  parameters: Type.Object({}),
408
- renderCall: (_args, theme) => new Text(`${theme.fg(MEMORY_COLOR, theme.bold(`${MEMORY_GLYPH} memory`))} ${theme.fg("dim", "listing")}`, 0, 0),
409
- renderResult: (result, options, theme) => {
410
- const d = result.details as { memories?: Array<{ title: string; project: string; type: string }> } | undefined;
411
- const mems = d?.memories ?? [];
412
- const shown = options.expanded ? mems : mems.slice(0, 8);
413
- const lines = [`${MEMORY_GLYPH} memories · ${mems.length} stored across projects`];
414
- for (const m of shown) lines.push(`[${m.project}] ${m.title} · ${m.type}`);
415
- if (!options.expanded && mems.length > shown.length) lines.push(`… ${mems.length - shown.length} more`);
416
- return frame(theme as ThemeLike, lines);
417
- },
493
+ renderShell: "self",
494
+ renderCall: (_args, theme, context) => pendingLine(theme, context, "listing…"),
495
+ renderResult: (result, options, theme) => listCard(theme, result.details, options.expanded, true),
418
496
  async execute(_id, _p, _s, _o, _ctx) {
419
- activity?.onRecall?.();
420
- const b = W();
421
- if (!b) {
422
- return { content: [{ type: "text" as const, text: "Memory backend unavailable." }], details: { memories: [] } };
423
- }
424
- // Pull every project dir's md files for a cross-project list.
425
- const { listProjectDirs } = await import("./files.js");
426
- const { sanitizeProjectName } = await import("./paths.js");
427
- const memories: Array<{ project: string; id: string; title: string; type: string }> = [];
428
- for (const { name } of listProjectDirs()) {
429
- for (const m of await b.list(sanitizeProjectName(name))) {
430
- memories.push(m);
431
- }
432
- }
433
- if (memories.length === 0) {
434
- return { content: [{ type: "text" as const, text: "No memories stored in any project." }], details: { memories: [] } };
435
- }
436
- const grouped = new Map<string, typeof memories>();
437
- for (const m of memories) {
438
- grouped.set(m.project, [...(grouped.get(m.project) ?? []), m]);
439
- }
440
- let output = "";
441
- for (const [p, ms] of grouped) {
442
- output += `\n**${p}** (${ms.length}):\n` + ms.map((m) => ` - ${m.title} (${m.type})`).join("\n") + "\n";
443
- }
444
- return {
445
- content: [{ type: "text" as const, text: `All memories across ${grouped.size} projects (${memories.length} total):${output}` }],
446
- details: { memories },
447
- };
497
+ return ex.globalList();
448
498
  },
449
499
  });
450
500
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.21",
4
4
  "description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "3.0.0-alpha.2",
37
+ "@pi-unipi/core": "3.0.0-alpha.21",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {