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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @pi-unipi/kanboard — transcript badges (user-only, never LLM context).
3
+ *
4
+ * A successful lead `start` / `finish` / `move <ID> blocked` appends a custom
5
+ * transcript entry rendered as `▣ UNI-30 started`, `✓ UNI-30 → In Review`,
6
+ * `⊘ UNI-30 blocked: <comment>`. Custom entries persist for the user but are
7
+ * outside the LLM context (same mechanism as core's progress bars).
8
+ */
9
+
10
+ import type { ExtensionAPI, ThemeColor } from "@earendil-works/pi-coding-agent";
11
+ import { Text, truncateToWidth, type Component } from "@earendil-works/pi-tui";
12
+
13
+ import { kanboardInvocations } from "./guard.js";
14
+
15
+ export const BADGE_ENTRY = "unipi:kanboard-badge";
16
+
17
+ export type BadgeKind = "started" | "finished" | "blocked";
18
+
19
+ export interface BadgeData {
20
+ kind: BadgeKind;
21
+ id: string;
22
+ /** Short human text (a blocked task carries what is needed). */
23
+ text?: string;
24
+ }
25
+
26
+ /** The badge kind a kanboard CLI call produces, or null. */
27
+ export function badgeKindFor(sub: string, args: readonly string[]): BadgeKind | null {
28
+ if (sub === "start") return "started";
29
+ if (sub === "finish") return "finished";
30
+ if (sub === "move" && args.includes("blocked")) return "blocked";
31
+ return null;
32
+ }
33
+
34
+ /** Extract a badge from a successful kanboard bash call, or null. */
35
+ export function badgeFromToolCall(toolName: string, input: Record<string, unknown> | undefined, isError: boolean): BadgeData | null {
36
+ if (isError || (toolName !== "bash" && toolName !== "powershell")) return null;
37
+ for (const invocation of kanboardInvocations(String(input?.command ?? ""))) {
38
+ const kind = badgeKindFor(invocation.sub, invocation.args);
39
+ if (!kind) continue;
40
+ const id = invocation.args.find((arg) => !arg.startsWith("-"));
41
+ if (!id) continue;
42
+ let text: string | undefined;
43
+ if (kind === "blocked") {
44
+ const flagIndex = invocation.args.indexOf("--comment");
45
+ text = flagIndex >= 0 ? (invocation.args[flagIndex + 1] ?? "").replace(/^["']|["']$/g, "") : undefined;
46
+ }
47
+ return { kind, id, ...(text !== undefined && text.length > 0 ? { text } : {}) };
48
+ }
49
+ return null;
50
+ }
51
+
52
+ export function badgeText(data: BadgeData): string {
53
+ if (data.kind === "started") return `▣ ${data.id} started`;
54
+ if (data.kind === "finished") return `✓ ${data.id} → In Review`;
55
+ const what = data.text ? `: ${data.text.slice(0, 80)}` : "";
56
+ return `⊘ ${data.id} blocked${what}`;
57
+ }
58
+
59
+ function badgeColor(kind: BadgeKind): ThemeColor {
60
+ if (kind === "started") return "accent";
61
+ if (kind === "finished") return "success";
62
+ return "warning";
63
+ }
64
+
65
+ /** Register the entry renderer (once per extension load; lead only). */
66
+ export function registerBadgeRenderer(pi: ExtensionAPI): void {
67
+ try {
68
+ pi.registerEntryRenderer<BadgeData>(BADGE_ENTRY, (entry, _options, theme) => {
69
+ const data = entry.data;
70
+ if (!data || typeof data.id !== "string") return undefined;
71
+ const t = theme as unknown as {
72
+ fg?: (color: ThemeColor, text: string) => string;
73
+ bold?: (text: string) => string;
74
+ };
75
+ const line = t.fg?.(badgeColor(data.kind), badgeText(data)) ?? badgeText(data);
76
+ return new Text(t.bold ? t.bold(line) : line, 0, 0);
77
+ });
78
+ } catch {
79
+ // Renderer registration is UI-dependent; skip where unavailable.
80
+ }
81
+ }
82
+
83
+ /** Emit a badge for a successful tool result (lead tool_result hook body). */
84
+ export function maybeBadgeToolResult(pi: ExtensionAPI, toolName: string, input: Record<string, unknown> | undefined, isError: boolean, width = 80): Component | null {
85
+ const badge = badgeFromToolCall(toolName, input, isError);
86
+ if (!badge) return null;
87
+ try {
88
+ pi.appendEntry<BadgeData>(BADGE_ENTRY, badge);
89
+ return new Text(truncateToWidth(badgeText(badge), width), 0, 0);
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
@@ -80,10 +80,12 @@ export function resolveBinary(env: NodeJS.ProcessEnv = process.env, from?: strin
80
80
  if (explicit) {
81
81
  return isRunnable(explicit) ? { path: explicit, source: "env" } : null;
82
82
  }
83
- const packaged = platformPackagePath(from);
84
- if (packaged && isRunnable(packaged)) return { path: packaged, source: "platform-package" };
83
+ // In a source checkout the dev build wins — the platform package can ship a
84
+ // stale prebuilt binary (seen on coffee: alpha.0 shadowed the debug build).
85
85
  const dev = devBuildPath();
86
86
  if (dev) return { path: dev, source: "dev-build" };
87
+ const packaged = platformPackagePath(from);
88
+ if (packaged && isRunnable(packaged)) return { path: packaged, source: "platform-package" };
87
89
  return null;
88
90
  }
89
91
 
@@ -3,6 +3,7 @@
3
3
  * `/unipi:kanboard-add`, `/unipi:kanboard-do`, `/unipi:kanboard-autowork`.
4
4
  */
5
5
 
6
+ import { boardProgressData } from "./progress.js";
6
7
  import { execFile, spawn } from "node:child_process";
7
8
  import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8
9
  import { homedir, hostname as osHostname, networkInterfaces, tmpdir, type NetworkInterfaceInfo } from "node:os";
@@ -10,9 +11,11 @@ import { join } from "node:path";
10
11
  import { promisify } from "node:util";
11
12
 
12
13
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
13
- import { getSettings } from "@pi-unipi/core";
14
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
15
+ import { getSettings, sendHarnessUserMessage } from "@pi-unipi/core";
14
16
 
15
17
  import { KanboardCliError, type KanboardCli } from "./bin.js";
18
+ import { DEFAULT_DO_TASKS, DEFAULT_DO_WRITES } from "./guard.js";
16
19
  import { asProject, asStopResult, asTask, asTaskList, type KanboardTask } from "./shapes.js";
17
20
  import { applyLimitEnv, readKanboardSettings, type KanboardSettings } from "./settings.js";
18
21
 
@@ -31,21 +34,26 @@ export const HELP = `Kanboard commands
31
34
  start the board and print its link
32
35
  /unipi:kanboard close shut the board down
33
36
  /unipi:kanboard onboard register this project
34
- /unipi:kanboard status board, claims and queue
37
+ /unipi:kanboard status board and claims
35
38
  /unipi:kanboard show [--all] the board, in chat (lanes + claim order)
36
39
  /unipi:kanboard doctor check the setup
37
40
  /unipi:kanboard-add [-p 1-5] <title> add a task; lines below the title are the description (paste images there)
38
- /unipi:kanboard-do <request> let the agent use the board for this turn
39
- /unipi:kanboard-autowork start|stop work ready tasks chain by chain; stop finishes the current one first
41
+ /unipi:kanboard-do <request> grant the agent task slots + a write budget
42
+ /unipi:kanboard-autowork start|stop work every ready task in this session; stop just turns the offers off
40
43
  Priority -p: 1 none · 2 low · 3 medium · 4 high · 5 urgent`;
41
44
 
42
- export function doText(slug: string, cli: string, request: string, queueMax = 10): string {
43
- const limit = queueMax === 0 ? "Todo tasks" : `Todo tasks, at most ${queueMax}`;
44
- return `[kanboard] For this request you may use the kanboard skill on project ${slug} (CLI: \`${cli} --actor agent --project ${slug} …\`). Board writes are allowed until this turn ends. You can add tasks, move them between backlog and todo, link, order, note, and edit tasks you created. To have tasks worked, queue them in order with \`queue <IDs>\` (${limit}); the runner starts them one by one after this turn, so do not start the work yourself. If the request is unclear, ask me instead of guessing.
45
+ export function doText(slug: string, cli: string, request: string, slots = DEFAULT_DO_TASKS, writes = DEFAULT_DO_WRITES): string {
46
+ const s = slots === 1 ? "" : "s";
47
+ const ws = writes === 1 ? "" : "s";
48
+ return `[kanboard] For this request you may use the kanboard skill on project ${slug} (CLI: \`${cli} --actor agent --project ${slug} …\`). Budget this session: ${String(slots)} task slot${s} — each \`start\` uses one — and ${String(writes)} board write${ws} (add, edit, link, order, move backlog↔todo, note on tasks you don't hold); /unipi:kanboard-do tops both back up. Always free: reads, and \`finish\`, \`move <ID> blocked --comment\`, \`note\` and \`attach\` on tasks you started. Work the tasks yourself in this session, in whatever mode fits: \`start <ID>\` right before you work it, then \`finish <ID> --comment "<summary>"\` or \`move <ID> blocked --comment "<what you need>"\` — never leave a task you started In Progress. Before starting anything, count the tasks this request needs; if that is more than ${String(slots)}, start nothing — tell me you can do ${String(slots)} now and ask whether to raise the limit (setting kanboard.doTasks) or work in batches. Read a task with \`show <ID>\` before editing or starting it — \`list\` only shows titles and a one-line excerpt. Sidekicks and subagents can read the board but not write it: brief them with the task, then update the board yourself from their report. If the request is unclear, ask me instead of guessing.
45
49
 
46
50
  Request: ${request}`;
47
51
  }
48
52
 
53
+ export function autoworkText(slug: string, cli: string): string {
54
+ return `[kanboard] Autowork on project ${slug} (CLI: \`${cli} --actor agent --project ${slug} …\`): work every ready task on the board, one at a time, in this session — choose any mode yourself (regular, goal, ralph, swarm, graph). No budget limits. Pick with \`next\` or \`list --ready\`; for each: \`show <ID>\`, \`start <ID>\`, work it, then \`finish <ID> --comment "<summary>"\` or \`move <ID> blocked --comment "<what you need>"\`. When you finish one, I'll offer the next ready task. Sidekicks and subagents can read the board but not write it — update the board yourself from their reports.`;
55
+ }
56
+
49
57
  /**
50
58
  * Split a command line into words the way a shell would: whitespace separates,
51
59
  * a token may start with a quoted span, and a quote inside a word is literal
@@ -184,18 +192,15 @@ export interface CommandDeps {
184
192
  settings: () => KanboardSettings;
185
193
  /** Reveal the kanboard skill for this session (append-only). */
186
194
  revealSkill: (ctx: ExtensionContext | ExtensionCommandContext) => void;
187
- /** Autowork: claim-and-run loop (queue first, then claim-next). */
188
- work: (ctx: ExtensionContext) => Promise<void>;
189
- /** Finish the current task, then stop the loop. */
190
- stop: (ctx: ExtensionContext) => void;
191
- /** Drain this session's queue after a -do turn (no-op when empty/running). */
192
- drainQueue: (ctx: ExtensionContext) => Promise<void>;
193
- status: () => { taskId: string | null; mode: string | null; phase: string };
195
+ /** Autowork on/off (guard budget, monitor, status holder). */
196
+ setAutowork(on: boolean): void;
194
197
  /** The write window opened by -do (read by the tool_call gate). */
195
198
  guard: import("./guard.js").WriteGuard;
196
199
  /** This session's id (UNIPI_KANBOARD_SESSION / pi-<pid>). */
197
200
  session: () => string;
198
201
  debug: (line: string) => void;
202
+ /** Post a user-only progress bar (optional: absent in tests). */
203
+ progress?: (data: import("@pi-unipi/core").ProgressData) => void;
199
204
  }
200
205
 
201
206
  export interface DaemonInfo {
@@ -407,7 +412,6 @@ export async function ensureDaemon(
407
412
  ...process.env,
408
413
  UNIPI_KANBOARD_ACTOR: "user",
409
414
  UNIPI_KANBOARD_CHAIN_GATE: settings.chainGate,
410
- UNIPI_KANBOARD_QUEUE_MAX: String(settings.queueMax),
411
415
  UNIPI_KANBOARD_MAX_SESSIONS: String(settings.maxSessions),
412
416
  },
413
417
  });
@@ -573,15 +577,18 @@ export async function runAdd(deps: CommandDeps, ctx: ExtensionCommandContext, te
573
577
  ctx.ui.notify(`kanboard: ${parsed.error} — /unipi:kanboard-add [-p 1-5] [--after ID] [--status backlog|todo] <title>`, "warning");
574
578
  return;
575
579
  }
576
- if (!parsed.title) {
577
- ctx.ui.notify("kanboard: add needs a title — /unipi:kanboard-add <title>", "warning");
580
+ const hasDescription = parsed.description.trim().length > 0;
581
+ if (!parsed.title && !hasDescription) {
582
+ ctx.ui.notify("kanboard: add needs a title or a description — /unipi:kanboard-add <title> (lines below are the description)", "warning");
578
583
  return;
579
584
  }
580
585
  if (!(await ensureOnboarded(deps, ctx))) return;
581
586
 
582
587
  let bodyFile: string | null = null;
583
588
  try {
584
- const argv = ["add", parsed.title, ...parsed.flags];
589
+ // A description-only add omits the title positional entirely (the binary
590
+ // stores an empty title and derives the display title from the body).
591
+ const argv = ["add", ...(parsed.title ? [parsed.title] : []), ...parsed.flags];
585
592
  if (parsed.description) {
586
593
  bodyFile = join(mkdtempSync(join(tmpdir(), "kb-add-")), "body.md");
587
594
  writeFileSync(bodyFile, parsed.description);
@@ -603,6 +610,55 @@ export async function runAdd(deps: CommandDeps, ctx: ExtensionCommandContext, te
603
610
  }
604
611
  }
605
612
 
613
+ export type CaptureToBacklogResult =
614
+ | { ok: true; id: string; attachments: number }
615
+ | { ok: false; reason: string };
616
+
617
+ /**
618
+ * Capture editor text as a Backlog task body — the entry point the
619
+ * input-shortcuts K chord calls through `globalThis.__unipi_kanboard_api`.
620
+ * No title: the body carries the point (the binary derives the display
621
+ * title). No prompts: when the folder is not onboarded the capture is
622
+ * refused with the reason instead.
623
+ */
624
+ export async function captureToBacklog(
625
+ deps: CommandDeps,
626
+ opts: { cwd: string; text: string },
627
+ ): Promise<CaptureToBacklogResult> {
628
+ const client = deps.cli;
629
+ if (!client) return { ok: false, reason: deps.unavailable ?? "kanboard binary unavailable" };
630
+ let slug: string | null = null;
631
+ try {
632
+ const settings = getSettings("kanboard", opts.cwd) as { slug?: string };
633
+ slug = typeof settings.slug === "string" && settings.slug.length > 0 ? settings.slug : null;
634
+ } catch {
635
+ slug = null;
636
+ }
637
+ if (!slug) {
638
+ return { ok: false, reason: "kanboard is not set up here — run /unipi:kanboard" };
639
+ }
640
+ const text = opts.text.trim();
641
+ if (!text) return { ok: false, reason: "nothing to add" };
642
+ const attaches = detectFilePaths(text);
643
+ let bodyFile: string | null = null;
644
+ try {
645
+ bodyFile = join(mkdtempSync(join(tmpdir(), "kb-capture-")), "body.md");
646
+ writeFileSync(bodyFile, text);
647
+ const argv = ["--project", slug, "add", "--status", "backlog", "--body-file", bodyFile];
648
+ for (const file of attaches) argv.push("--attach", file);
649
+ const task = asTask("add", await client.run<unknown>(argv, { cwd: opts.cwd }));
650
+ deps.debug(`captureToBacklog ${task.id} (${attaches.length} attachments)`);
651
+ return { ok: true, id: task.id, attachments: attaches.length };
652
+ } catch (error) {
653
+ return {
654
+ ok: false,
655
+ reason: error instanceof KanboardCliError ? error.message : String(error),
656
+ };
657
+ } finally {
658
+ if (bodyFile) rmSync(join(bodyFile, ".."), { recursive: true, force: true });
659
+ }
660
+ }
661
+
606
662
  export async function runStatus(deps: CommandDeps, ctx: ExtensionCommandContext | ExtensionContext): Promise<void> {
607
663
  const client = deps.cli;
608
664
  const lines: string[] = [];
@@ -615,7 +671,10 @@ export async function runStatus(deps: CommandDeps, ctx: ExtensionCommandContext
615
671
  const slug = currentSlug();
616
672
  if (slug) {
617
673
  try {
618
- const { tasks, problems } = asTaskList(await client!.run<unknown>(["list"], {}));
674
+ // --all: the status view counts every lane; the CLI's bare `list`
675
+ // default (todo/in_progress/blocked/in_review/done) would hide backlog
676
+ // and cancelled here (UNI-62).
677
+ const { tasks, problems } = asTaskList(await client!.run<unknown>(["list", "--all"], {}));
619
678
  const counts = tasks.reduce<Record<string, number>>((acc, task) => {
620
679
  acc[task.status] = (acc[task.status] ?? 0) + 1;
621
680
  return acc;
@@ -628,7 +687,9 @@ export async function runStatus(deps: CommandDeps, ctx: ExtensionCommandContext
628
687
  if (problems && problems.length > 0) {
629
688
  lines.push(`⚠ ${problems.length} task file(s) need repair — unipi-kanboard validate --fix`);
630
689
  }
631
- // Active claims and this session's queue.
690
+ const bar = boardProgressData(tasks, slug);
691
+ if (bar) deps.progress?.(bar);
692
+ // Active claims of this session.
632
693
  const running = tasks.filter((task) => task.status === "in_progress");
633
694
  if (running.length > 0) {
634
695
  lines.push("claims:");
@@ -640,44 +701,15 @@ export async function runStatus(deps: CommandDeps, ctx: ExtensionCommandContext
640
701
  );
641
702
  }
642
703
  }
643
- try {
644
- const queue = (await client!.run<unknown>(["queue", "--list"], {
645
- extraEnv: { UNIPI_KANBOARD_SESSION: deps.session() },
646
- })) as { queue?: string[] };
647
- lines.push(`queue (${deps.session()}): ${(queue.queue ?? []).join(" ") || "empty"}`);
648
- } catch {
649
- lines.push("queue: (unreadable)");
650
- }
651
704
  } catch {
652
705
  lines.push(`project: ${slug} (unreadable board)`);
653
706
  }
654
707
  } else {
655
708
  lines.push("project: not registered here — /unipi:kanboard onboard");
656
709
  }
657
- const run = deps.status();
658
- lines.push(run.taskId ? `runner: ${run.taskId} · ${run.mode} · ${run.phase}` : "runner: idle");
659
710
  ctx.ui.notify(lines.join("\n"), "info");
660
711
  }
661
712
 
662
- /**
663
- * After a -do window closed (the first real agent_end), start draining this
664
- * session's queue when it is non-empty. Returns true when draining started.
665
- */
666
- export async function drainQueueAfterDo(deps: CommandDeps, ctx: ExtensionContext): Promise<boolean> {
667
- const client = deps.cli;
668
- const slug = currentSlug();
669
- if (!client || !slug) return false;
670
- const queued = await client
671
- .run<{ queue?: string[] }>(["queue", "--list"], {
672
- extraEnv: { UNIPI_KANBOARD_PROJECT: slug, UNIPI_KANBOARD_SESSION: deps.session() },
673
- })
674
- .then((payload) => payload.queue ?? [])
675
- .catch(() => [] as string[]);
676
- if (queued.length === 0) return false;
677
- await deps.drainQueue(ctx);
678
- return true;
679
- }
680
-
681
713
 
682
714
  /** `doctor`: ✓/✗ lines for the whole setup, posted as a display-only message. */
683
715
  export async function runDoctor(deps: CommandDeps, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
@@ -830,6 +862,8 @@ export async function runRotateTokenAction(
830
862
  interface ShowTask {
831
863
  id: string;
832
864
  title: string;
865
+ /** Derived title (Rust `display_title`) — shown when `title` is empty. */
866
+ displayTitle?: string;
833
867
  status: string;
834
868
  priority?: string;
835
869
  order?: number;
@@ -855,6 +889,11 @@ const SHOW_EXTRA_LANES: Array<{ id: string; label: string }> = [
855
889
  const PRIO_GLYPH: Record<string, string> = { urgent: "⇈", high: "↑", medium: "·", low: "↓", none: " " };
856
890
  const PRIO_RANK: Record<string, number> = { urgent: 0, high: 1, medium: 2, low: 3, none: 4 };
857
891
 
892
+ /** The title a row shows: the derived one when the raw title is empty. */
893
+ function showTitle(task: ShowTask): string {
894
+ return task.displayTitle || task.title || "(untitled)";
895
+ }
896
+
858
897
  /** Same ordering as the CLI's claim_sort: priority, then lane order, then id. */
859
898
  export function claimOrder(tasks: ShowTask[]): ShowTask[] {
860
899
  return [...tasks].sort(
@@ -899,7 +938,7 @@ function laneRows(tasks: ShowTask[], lane: string): ShowRow[] {
899
938
  number: `${number}.`,
900
939
  id: task.id,
901
940
  prio: PRIO_GLYPH[task.priority ?? "none"] ?? " ",
902
- title: task.title,
941
+ title: showTitle(task),
903
942
  extra:
904
943
  task.ready === false || waits.length > 0
905
944
  ? `waits for ${waits.join(", ")}`
@@ -922,7 +961,7 @@ function laneRows(tasks: ShowTask[], lane: string): ShowRow[] {
922
961
  branch: "",
923
962
  id: task.id,
924
963
  prio: PRIO_GLYPH[task.priority ?? "none"] ?? " ",
925
- title: task.title,
964
+ title: showTitle(task),
926
965
  extra:
927
966
  lane === "in_progress" && task.run?.session
928
967
  ? `· ${task.run.session}`
@@ -976,15 +1015,29 @@ export function showRenderer(
976
1015
  lines.push(theme.fg("accent", lane.label));
977
1016
  for (const row of laneRows(inLane, lane.id)) {
978
1017
  const head = ` ${row.branch}${row.number ? `${row.number} ` : ""}${theme.fg("muted", row.id)} ${row.prio} `;
979
- // visible length so far, then truncate the title to fit.
980
- const prefixLen = 2 + row.branch.length + (row.number ? row.number.length + 1 : 0) + row.id.length + 2;
981
- const budget = Math.max(10, width - prefixLen - (row.extra ? row.extra.length + 2 : 0));
982
- const title = row.title.length > budget ? `${row.title.slice(0, Math.max(1, budget - 1))}…` : row.title;
983
- const extra = row.extra ? ` ${row.tone === "error" ? theme.fg("error", row.extra) : theme.fg("dim", row.extra)}` : "";
984
- lines.push(`${head}${title}${extra}`);
1018
+ const styledExtra = row.extra
1019
+ ? ` ${row.tone === "error" ? theme.fg("error", row.extra) : theme.fg("dim", row.extra)}`
1020
+ : "";
1021
+ // Budgets in visible cells (CJK ≈ 2 cells, ANSI = 0 — .length lies on both).
1022
+ // Title takes all the room left after the extra when both fit comfortably,
1023
+ // else it is capped at 10 cells; the extra then takes whatever remains, so a
1024
+ // huge blockedReason is truncated instead of overflowing the terminal (which
1025
+ // hard-crashes pi). Final truncate is the belt-and-braces guarantee.
1026
+ const avail = Math.max(0, width - visibleWidth(head));
1027
+ const extraW = styledExtra ? visibleWidth(styledExtra) : 0;
1028
+ const titleBudget = Math.max(0, avail - extraW >= 10 ? avail - extraW : Math.min(avail, 10));
1029
+ const title = truncateToWidth(row.title, titleBudget, "…");
1030
+ let line = `${head}${title}`;
1031
+ if (styledExtra) {
1032
+ const extraBudget = avail - visibleWidth(title) - 2;
1033
+ if (extraBudget > 0) line += truncateToWidth(styledExtra, extraBudget + 2, "…");
1034
+ }
1035
+ lines.push(line);
985
1036
  }
986
1037
  }
987
- return lines;
1038
+ // Hard guarantee: a line wider than the terminal hard-crashes pi, so every
1039
+ // emitted line — header and lane labels included — is truncated to `width`.
1040
+ return lines.map((line) => truncateToWidth(line, width));
988
1041
  },
989
1042
  invalidate: () => undefined,
990
1043
  };
@@ -1000,7 +1053,8 @@ async function runShow(deps: CommandDeps, ctx: ExtensionCommandContext, pi: Exte
1000
1053
  const slug = currentSlug()!;
1001
1054
  let tasks: ShowTask[] = [];
1002
1055
  try {
1003
- const payload = (await client.run(["list", "--json"], { cwd: ctx.cwd })) as { tasks?: ShowTask[] };
1056
+ // --all: this display lists every lane; the CLI bare default hides backlog/cancelled (UNI-62).
1057
+ const payload = (await client.run(["list", "--all", "--json"], { cwd: ctx.cwd })) as { tasks?: ShowTask[] };
1004
1058
  tasks = payload.tasks ?? [];
1005
1059
  } catch (error) {
1006
1060
  ctx.ui.notify(`kanboard: show failed — ${error instanceof KanboardCliError ? error.message : String(error)}`, "error");
@@ -1039,7 +1093,7 @@ const SUB_DESCRIPTIONS: Record<Subcommand, string> = {
1039
1093
  open: "Start the daemon and print the board URL (--host 0.0.0.0|tailscale, --port N)",
1040
1094
  close: "Shut down the board daemon (the web UI goes offline)",
1041
1095
  onboard: "Register this project on the board",
1042
- status: "Daemon, project counts, claims, queue and the runner state",
1096
+ status: "Daemon, project counts and claims",
1043
1097
  doctor: "Check the whole setup (binary, daemon, project, agent, claims)",
1044
1098
  show: "The board in chat: lanes + claim order (--all adds cancelled/archived)",
1045
1099
  };
@@ -1088,13 +1142,27 @@ export function kanboardCompletions(prefix: string): CompletionItem[] | null {
1088
1142
  /** Live task list for `--after` / `-do` completions, cached ~5s per client. */
1089
1143
  const taskCache = new WeakMap<KanboardCli, { at: number; tasks: ShowTask[] }>();
1090
1144
 
1091
- export async function taskCompletions(deps: CommandDeps, prefix: string, before: string): Promise<CompletionItem[] | null> {
1145
+ export interface TaskCompletionOptions {
1146
+ /** Also match task TITLES (default true); false = id prefixes only. */
1147
+ titleSearch?: boolean;
1148
+ /** Row style: "status" = label id, description "<status> — <title>";
1149
+ * "id-title" = label "<id> <title>", description "<status>". */
1150
+ rowStyle?: "status" | "id-title";
1151
+ }
1152
+
1153
+ export async function taskCompletions(
1154
+ deps: CommandDeps,
1155
+ prefix: string,
1156
+ before: string,
1157
+ opts: TaskCompletionOptions = {},
1158
+ ): Promise<CompletionItem[] | null> {
1159
+ const { titleSearch = true, rowStyle = "status" } = opts;
1092
1160
  const client = deps.cli;
1093
1161
  if (!client) return null;
1094
1162
  let cache = taskCache.get(client);
1095
1163
  if (!cache || Date.now() - cache.at > 5_000) {
1096
1164
  try {
1097
- const payload = (await client.run(["list", "--json"], { cwd: process.cwd() })) as { tasks?: ShowTask[] };
1165
+ const payload = (await client.run(["list", "--all", "--json"], { cwd: process.cwd() })) as { tasks?: ShowTask[] };
1098
1166
  cache = { at: Date.now(), tasks: payload.tasks ?? [] };
1099
1167
  taskCache.set(client, cache);
1100
1168
  } catch {
@@ -1104,13 +1172,21 @@ export async function taskCompletions(deps: CommandDeps, prefix: string, before:
1104
1172
  const needle = prefix.toLowerCase();
1105
1173
  const items = cache.tasks
1106
1174
  .filter((task) => !["cancelled", "archived"].includes(task.status))
1107
- .filter((task) => task.id.toLowerCase().startsWith(needle) || task.title.toLowerCase().includes(needle))
1175
+ .filter((task) => task.id.toLowerCase().startsWith(needle) || (titleSearch && showTitle(task).toLowerCase().includes(needle)))
1108
1176
  .slice(0, 12)
1109
- .map((task) => ({
1110
- value: fullArgs(before, task.id),
1111
- label: task.id,
1112
- description: `${task.status} — ${task.title}`,
1113
- }));
1177
+ .map((task) =>
1178
+ rowStyle === "id-title"
1179
+ ? {
1180
+ value: fullArgs(before, task.id),
1181
+ label: `${task.id} ${showTitle(task).slice(0, 64)}`,
1182
+ description: task.status,
1183
+ }
1184
+ : {
1185
+ value: fullArgs(before, task.id),
1186
+ label: task.id,
1187
+ description: `${task.status} — ${showTitle(task)}`,
1188
+ },
1189
+ );
1114
1190
  return items.length > 0 ? items : null;
1115
1191
  }
1116
1192
 
@@ -1186,15 +1262,19 @@ export async function kanboardAddCompletions(deps: CommandDeps, prefix: string):
1186
1262
  return null;
1187
1263
  }
1188
1264
 
1189
- /** /unipi:kanboard-do … — complete a trailing task-id prefix. */
1265
+ /** /unipi:kanboard-do … — complete a trailing task-id token; plain prose never matches. */
1190
1266
  export async function kanboardDoCompletions(deps: CommandDeps, prefix: string): Promise<CompletionItem[] | null> {
1191
1267
  const raw = prefix ?? "";
1192
- const match = /^(.*?)(\S*)$/.exec(raw)!;
1193
- const before = match[1]!;
1194
- const token = match[2]!;
1195
- // Only when the last token looks like a task id prefix (ABC-…).
1196
- if (!/^[A-Za-z]{2,6}-?[0-9]*$/.test(token) || token.length < 2) return null;
1197
- return taskCompletions(deps, token, before);
1268
+ const at = raw.lastIndexOf(" ");
1269
+ const before = at === -1 ? "" : raw.slice(0, at + 1);
1270
+ const token = raw.slice(at + 1);
1271
+ // Only a task-id-like tail ("UNI-1", "uni", "UNI-") completes; words that
1272
+ // match no id shape or prefix ("note", "set") yield nothing.
1273
+ if (!/^[A-Za-z]{2,10}-?\d*$/.test(token)) return null;
1274
+ // Title search only when the token looks like a real id prefix (has a digit
1275
+ // or ends with the dash); otherwise id prefixes only.
1276
+ const idLike = /\d/.test(token) || token.endsWith("-");
1277
+ return taskCompletions(deps, token, before, { titleSearch: idLike, rowStyle: "id-title" });
1198
1278
  }
1199
1279
 
1200
1280
  export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): void {
@@ -1209,7 +1289,14 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1209
1289
  }
1210
1290
 
1211
1291
  pi.on("context", (event) => {
1212
- const hidden = new Set([HELP_CUSTOM_TYPE, DOCTOR_CUSTOM_TYPE, SHOW_CUSTOM_TYPE]);
1292
+ // Monitor notices ride custom ENTRIES (never messages) — this is defense
1293
+ // in depth in case one ever becomes a message.
1294
+ const hidden = new Set([
1295
+ HELP_CUSTOM_TYPE,
1296
+ DOCTOR_CUSTOM_TYPE,
1297
+ SHOW_CUSTOM_TYPE,
1298
+ "unipi:kanboard-notice",
1299
+ ]);
1213
1300
  const filtered = event.messages.filter(
1214
1301
  (message) => !hidden.has((message as { customType?: string }).customType ?? ""),
1215
1302
  );
@@ -1226,7 +1313,18 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1226
1313
  // Limits live in the environment; refresh so this bash call inherits the
1227
1314
  // current settings (the spawned CLI reads them).
1228
1315
  applyLimitEnv(deps.settings());
1229
- const reason = deps.guard.check(command);
1316
+ const reason = await deps.guard.check(command, {
1317
+ ownsClaim: async (id) => {
1318
+ const client = deps.cli;
1319
+ const slug = currentSlug();
1320
+ if (!client || !slug) return false;
1321
+ const task = asTask("show", await client.run([ "show", id, "--json" ], {
1322
+ extraEnv: { UNIPI_KANBOARD_PROJECT: slug, UNIPI_KANBOARD_SESSION: deps.session() },
1323
+ }));
1324
+ const run = task.run as { session?: string } | null | undefined;
1325
+ return task.status === "in_progress" && run?.session === deps.session();
1326
+ },
1327
+ });
1230
1328
  return reason ? { block: true, reason } : undefined;
1231
1329
  });
1232
1330
 
@@ -1250,11 +1348,11 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1250
1348
  return;
1251
1349
  }
1252
1350
  if (sub === "work") {
1253
- ctx.ui.notify("kanboard: use /unipi:kanboard-autowork start to work the queue", "info");
1351
+ ctx.ui.notify("kanboard: use /unipi:kanboard-autowork start to work every ready task in this session", "info");
1254
1352
  return;
1255
1353
  }
1256
1354
  if (sub === "stop") {
1257
- ctx.ui.notify("kanboard: use /unipi:kanboard-autowork stop to finish the current task and stop", "info");
1355
+ ctx.ui.notify("kanboard: use /unipi:kanboard-autowork stop to turn autowork offers off", "info");
1258
1356
  return;
1259
1357
  }
1260
1358
 
@@ -1303,10 +1401,15 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1303
1401
  });
1304
1402
 
1305
1403
  pi.registerCommand("unipi:kanboard-do", {
1306
- description: "Let the agent use the kanboard for this turn — writes are allowed until the turn ends",
1404
+ description: "Grant the agent kanboard task slots and a write budget (/unipi:kanboard-do <request>; off revokes)",
1307
1405
  getArgumentCompletions: (prefix: string) => kanboardDoCompletions(deps, prefix ?? ""),
1308
1406
  handler: async (args: string, ctx: ExtensionCommandContext) => {
1309
1407
  const request = (args ?? "").trim();
1408
+ if (request.toLowerCase() === "off") {
1409
+ deps.guard.revoke();
1410
+ ctx.ui.notify("kanboard: -do budget revoked — reads stay free", "info");
1411
+ return;
1412
+ }
1310
1413
  if (!request) {
1311
1414
  ctx.ui.notify("kanboard: -do needs a request — /unipi:kanboard-do <what the agent may do on the board>", "warning");
1312
1415
  return;
@@ -1323,9 +1426,19 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1323
1426
  const busy = typeof (ctx as unknown as { isIdle?: () => boolean }).isIdle === "function"
1324
1427
  ? !(ctx as unknown as { isIdle: () => boolean }).isIdle!()
1325
1428
  : false;
1326
- pi.sendUserMessage(doText(slug, client.binary.path, request, deps.settings().queueMax), busy ? { deliverAs: "followUp" } : undefined);
1429
+ const budget = deps.guard.remaining();
1430
+ sendHarnessUserMessage(
1431
+ pi,
1432
+ doText(slug, client.binary.path, request, budget.slots, budget.writes),
1433
+ {
1434
+ source: "Kanboard",
1435
+ title: "Task budget",
1436
+ synopsis: `slots ${String(budget.slots)} · writes ${String(budget.writes)} · user request included`,
1437
+ },
1438
+ busy ? { deliverAs: "followUp" } : undefined,
1439
+ );
1327
1440
  deps.guard.noteSent();
1328
- deps.debug(`do window open (${slug})`);
1441
+ deps.debug(`do open (${slug}): ${JSON.stringify(deps.guard.remaining())}`);
1329
1442
  },
1330
1443
  });
1331
1444
 
@@ -1343,11 +1456,32 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
1343
1456
  const sub = (args ?? "").trim().toLowerCase();
1344
1457
  if (sub === "start") {
1345
1458
  if (!(await ensureOnboarded(deps, ctx))) return;
1346
- await deps.work(ctx as unknown as ExtensionContext);
1459
+ const client = deps.cli;
1460
+ if (!client) {
1461
+ ctx.ui.notify(`kanboard: ${deps.unavailable}`, "warning");
1462
+ return;
1463
+ }
1464
+ const slug = currentSlug()!;
1465
+ deps.revealSkill(ctx);
1466
+ deps.setAutowork(true);
1467
+ deps.guard.setAutowork(true);
1468
+ deps.debug(`autowork on (${slug})`);
1469
+ const busy = typeof (ctx as unknown as { isIdle?: () => boolean }).isIdle === "function"
1470
+ ? !(ctx as unknown as { isIdle: () => boolean }).isIdle!()
1471
+ : false;
1472
+ sendHarnessUserMessage(
1473
+ pi,
1474
+ autoworkText(slug, client.binary.path),
1475
+ { source: "Kanboard", title: "Autowork", synopsis: "no budget limits · user request included" },
1476
+ busy ? { deliverAs: "followUp" } : undefined,
1477
+ );
1347
1478
  return;
1348
1479
  }
1349
1480
  if (sub === "stop") {
1350
- deps.stop(ctx as unknown as ExtensionContext);
1481
+ deps.setAutowork(false);
1482
+ deps.guard.setAutowork(false);
1483
+ deps.debug("autowork off");
1484
+ ctx.ui.notify("kanboard: autowork off", "info");
1351
1485
  return;
1352
1486
  }
1353
1487
  ctx.ui.notify("kanboard: /unipi:kanboard-autowork start|stop", "warning");