@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
@@ -49,7 +49,10 @@ export const ANTI_POISONING_SUFFIX =
49
49
  "other persistent instruction files.";
50
50
 
51
51
  /** Tools whose repetition with varying args indicates polling, not work. */
52
- const POLLING_TOOLS = new Set(["bg_status", "bg_logs", "get_helper_result", "loop_status", "get_goal"]);
52
+ const POLLING_TOOLS = new Set(["bg_status", "bg_logs", "read_subagent", "loop_status", "get_goal"]);
53
+
54
+ /** Goal bookkeeping tools: always exempt from repetition reminders. */
55
+ const EXEMPT_TOOLS = new Set(["get_goal", "update_goal"]);
53
56
 
54
57
  export interface RunawayStep {
55
58
  readonly tool: string;
@@ -64,15 +67,60 @@ export interface RunawayGuardDeps {
64
67
  remindAfter?: number;
65
68
  }
66
69
 
70
+ /** Stable JSON: object keys sorted so equal args hash equal regardless of order. */
71
+ export function stableStringify(value: unknown): string {
72
+ if (value === undefined) return "null";
73
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
74
+ if (value !== null && typeof value === "object") {
75
+ const record = value as Record<string, unknown>;
76
+ return `{${Object.keys(record)
77
+ .sort()
78
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
79
+ .join(",")}}`;
80
+ }
81
+ try {
82
+ return JSON.stringify(value) ?? "null";
83
+ } catch {
84
+ return String(value);
85
+ }
86
+ }
87
+
67
88
  function fingerprint(value: unknown): string {
68
- const json = (() => {
69
- try {
70
- return JSON.stringify(value ?? null);
71
- } catch {
72
- return String(value);
73
- }
74
- })();
75
- return createHash("sha256").update(json).digest("hex").slice(0, 16);
89
+ return createHash("sha256").update(stableStringify(value)).digest("hex").slice(0, 16);
90
+ }
91
+
92
+ function commandOf(input: unknown): string {
93
+ if (typeof input === "string") return input;
94
+ if (typeof input !== "object" || input === null) return "";
95
+ const record = input as Record<string, unknown>;
96
+ for (const key of ["command", "cmd", "script"]) {
97
+ if (typeof record[key] === "string" && record[key].length > 0) return record[key] as string;
98
+ }
99
+ return "";
100
+ }
101
+
102
+ /** bash rg/grep that found nothing (exit 1) is expected work, not a loop. */
103
+ export function isExpectedShellSearchNoMatch(input: unknown, isError: boolean, resultText: string): boolean {
104
+ if (!isError) return false;
105
+ const command = commandOf(input);
106
+ if (!/\b(?:rg|grep|findstr)\b/.test(command)) return false;
107
+ return /exit (?:code |status )?1\b|^no matches|^no match|^$/i.test(resultText.trim()) || resultText.trim() === "";
108
+ }
109
+
110
+ /** kanboard read subcommands via bash never count as repeated work. */
111
+ function isKanboardRead(command: string): boolean {
112
+ return /(?:^|[\s/"'])unipi-kanboard(?:\.exe)?\s+(?:--\S+\s+)*(?:list|show|search|next|chain|attachments|status)\b/.test(command);
113
+ }
114
+
115
+ /** Steps that reflect legitimate state inspection, not loops. */
116
+ export function isExemptStep(step: RunawayStep): boolean {
117
+ if (EXEMPT_TOOLS.has(step.tool)) return true;
118
+ if (step.tool === "bash" || step.tool === "powershell") {
119
+ const command = commandOf(step.input);
120
+ if (isKanboardRead(command)) return true;
121
+ if (isExpectedShellSearchNoMatch(step.input, step.isError, step.resultText)) return true;
122
+ }
123
+ return false;
76
124
  }
77
125
 
78
126
  /** Errors normalize to their family: first line, digits and paths removed. */
@@ -93,13 +141,22 @@ export interface RunawayObservation {
93
141
 
94
142
  /** Pure detector: returns the highest-priority qualifying signal, if any. */
95
143
  export function detectRunaway(
96
- steps: readonly RunawayStep[],
144
+ allSteps: readonly RunawayStep[],
97
145
  remindAfter: number = DEFAULT_REMIND_AFTER,
98
146
  ): RunawayObservation | null {
147
+ // Exempt steps (goal bookkeeping, search no-matches, kanboard reads) never
148
+ // count toward any repeat.
149
+ const steps = allSteps.filter((step) => !isExemptStep(step));
99
150
  if (steps.length < remindAfter) return null;
100
151
 
152
+ // Action identity = tool + stable-sorted args: "identical arguments" must
153
+ // mean actually identical arguments.
101
154
  const actionKeys = steps.map((step) => `${step.tool}:${fingerprint(step.input)}`);
102
- const resultKeys = steps.map((step) => fingerprint(step.resultText.slice(0, 400)));
155
+ // Result identity is bound to the action: identical output under different
156
+ // args is not a repeat (e.g. two different reads returning empty).
157
+ const resultKeys = steps.map(
158
+ (step) => `${step.tool}:${fingerprint(step.input)}:${fingerprint(step.resultText.slice(0, 400))}`,
159
+ );
103
160
  const errorKeys = steps.map((step) => (step.isError ? errorFamily(step.resultText) : null));
104
161
 
105
162
  const count = (keys: readonly string[]): { key: string; count: number } => {
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { createHash } from "node:crypto";
21
+ import { gatherEvidence } from "@pi-unipi/core";
21
22
 
22
23
  export const MAX_CHANGE_CHARS = 4_000;
23
24
  export const MAX_CHANGE_ITEMS = 100;
@@ -49,6 +50,8 @@ export interface VerifierDeps {
49
50
  readonly timeoutMs?: number;
50
51
  readonly setTimeout?: (fn: () => void, ms: number) => unknown;
51
52
  readonly clearTimeout?: (handle: unknown) => void;
53
+ /** Cross-module evidence (kanboard claims, …); defaults to core's registry. */
54
+ readonly gather?: typeof gatherEvidence;
52
55
  }
53
56
 
54
57
  const VERIFIER_SYSTEM = `You are the completion verifier for an autonomous coding agent. Given an OBJECTIVE and an EVIDENCE BRIEF, judge whether the objective is fully satisfied.
@@ -161,6 +164,22 @@ export async function verifyCompletion(
161
164
  brief: string,
162
165
  abortSignal?: AbortSignal,
163
166
  ): Promise<VerificationVerdict> {
167
+ // Cross-module evidence first: any blocking item forces not_met WITHOUT
168
+ // paying for an evaluator call (the evidence is already conclusive).
169
+ const gather = deps.gather ?? gatherEvidence;
170
+ const evidence = await gather();
171
+ if (evidence.blocking.length > 0) {
172
+ return {
173
+ verdict: "not_met",
174
+ reason: "board: open work remains",
175
+ missing: [...evidence.blocking],
176
+ evaluatorFailed: false,
177
+ };
178
+ }
179
+ if (evidence.notes.length > 0) {
180
+ brief = `${brief}\n--- MODULE EVIDENCE ---\n${evidence.notes.join("\n")}`;
181
+ }
182
+
164
183
  const prompt = buildVerificationPrompt(objective, brief);
165
184
  const timeoutMs = deps.timeoutMs ?? DEFAULT_VERIFIER_TIMEOUT_MS;
166
185
  const setT = deps.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
20
- import { emitEvent, UNIPI_EVENTS, setSharedLongHorizonMode } from "@pi-unipi/core";
20
+ import { emitEvent, isChildProcess, UNIPI_EVENTS, setSharedLongHorizonMode } from "@pi-unipi/core";
21
21
  import { LH_MODES, MODE_REGISTRY, modeForOwnerKind, type LhMode } from "./modes.js";
22
22
  import type { OwnerCoordinator, OwnerState } from "./owner.js";
23
23
  import { resolveMode, type ResolutionSource } from "./judge/resolve.js";
@@ -30,6 +30,8 @@ export interface GateState {
30
30
  readonly mode: LhMode;
31
31
  readonly source: ResolutionSource;
32
32
  readonly confidence?: number;
33
+ /** Resolved from the sticky session mode (/unipi:regular, goal stop), not a one-shot. */
34
+ readonly sticky?: boolean;
33
35
  }
34
36
 
35
37
  export interface GateDeps {
@@ -48,12 +50,9 @@ export interface GateDeps {
48
50
  }
49
51
 
50
52
  /** Delegation tools governed by the exposure matrix (design §8). */
51
- export const DELEGATION_TOOLS: readonly string[] = [
52
- "spawn_helper",
53
- "get_helper_result",
54
- "bg_delegate",
55
- "bg_result",
56
- ];
53
+ // Devin-model subagents are first-class: run_subagent is available in every
54
+ // mode. The delegation matrix mechanism is kept for future mode tools.
55
+ export const DELEGATION_TOOLS: readonly string[] = [];
57
56
 
58
57
  /** Every mode-control tool name across modes (todowrite rides all four). */
59
58
  export const ALL_MODE_TOOLS: readonly string[] = [
@@ -122,7 +121,7 @@ export function renderModeFragment(state: GateState, owner?: OwnerState, parked?
122
121
  `Available long-horizon tools: ${definition.controlTools.join(", ")}.` +
123
122
  (definition.delegation === "full"
124
123
  ? " Delegation tools are available for parallel work."
125
- : " Delegation tools are hidden; finish here or ask the user to switch modes."),
124
+ : ""),
126
125
  );
127
126
  }
128
127
  const status = ownerPresenceLine(owner, parked);
@@ -137,6 +136,8 @@ export function renderModeFragment(state: GateState, owner?: OwnerState, parked?
137
136
  export class Gate {
138
137
  private turn: GateState | null = null;
139
138
  private pendingExplicit: LhMode | null = null;
139
+ /** Sticky per-session mode (/unipi:regular): judge/default skip until an explicit command clears it. */
140
+ private sessionOverride: LhMode | null = null;
140
141
  /** Mode of the last badge shown, so only mode TRANSITIONS reprint. */
141
142
  private lastBadge: LhMode | null = null;
142
143
  private readonly deps: GateDeps;
@@ -145,9 +146,17 @@ export class Gate {
145
146
  this.deps = deps;
146
147
  }
147
148
 
148
- /** Commands call this before sendUserMessage to force next-turn mode. */
149
+ /** Commands call this before sendUserMessage to force next-turn mode (one-shot;
150
+ * an explicit command also escapes a sticky session mode). */
149
151
  setExplicit(mode: LhMode): void {
150
152
  this.pendingExplicit = mode;
153
+ this.sessionOverride = null;
154
+ }
155
+
156
+ /** Pin the session to a mode (/unipi:regular, goal stop): every later turn resolves to it. */
157
+ setSessionMode(mode: LhMode): void {
158
+ this.sessionOverride = mode;
159
+ this.pendingExplicit = null;
151
160
  }
152
161
 
153
162
  current(): GateState | null {
@@ -155,10 +164,17 @@ export class Gate {
155
164
  }
156
165
 
157
166
  async resolveForTurn(prompt: string): Promise<GateState> {
167
+ // Children are the hands, the lead is the voice: no LH modes, no judge,
168
+ // no owners in fusion/subagent children (escape hatch: UNIPI_LH_ALLOW_CHILD).
169
+ if (isChildProcess() && process.env.UNIPI_LH_ALLOW_CHILD !== "1") {
170
+ this.pendingExplicit = null;
171
+ this.turn = { mode: "none", source: "child" };
172
+ return this.turn;
173
+ }
158
174
  const settings = this.deps.loadSettings?.() ?? loadSettings();
159
175
  // Explicit switch away from an active owner suspends it first (max-1
160
176
  // park slot; a held slot refuses the override and the owner keeps mode).
161
- let explicit = this.pendingExplicit;
177
+ let explicit = this.sessionOverride ?? this.pendingExplicit;
162
178
  if (explicit) {
163
179
  const active = this.deps.owner.getActive();
164
180
  if (active && modeForOwnerKind(active.kind) !== explicit) {
@@ -184,6 +200,7 @@ export class Gate {
184
200
  mode: resolution.mode,
185
201
  source: resolution.source,
186
202
  ...(resolution.confidence !== undefined ? { confidence: resolution.confidence } : {}),
203
+ ...(this.sessionOverride !== null && explicit === this.sessionOverride ? { sticky: true } : {}),
187
204
  };
188
205
  return this.turn;
189
206
  }
@@ -212,7 +229,7 @@ export class Gate {
212
229
  // - mode changed from last → worth showing
213
230
  // - same mode as last shown → silent (footer already reflects it)
214
231
  const showBadge =
215
- state.source === "explicit" || this.lastBadge !== state.mode;
232
+ (state.source === "explicit" && !state.sticky) || this.lastBadge !== state.mode;
216
233
  this.lastBadge = state.mode;
217
234
  if (showBadge) {
218
235
  try {
@@ -230,7 +247,15 @@ export class Gate {
230
247
  this.deps.owner.getActive(),
231
248
  this.deps.owner.getParked(),
232
249
  );
233
- return { systemPrompt: `${event.systemPrompt}\n\n${fragment}` };
250
+ // A named section, not a systemPrompt replacement: a forced prompt is
251
+ // opaque to pi (forceSystemPrompt) and would drop sections other modules
252
+ // added — e.g. fusion's lead policy. renderModeFragment already emits
253
+ // the <long-horizon> wrapper; strip it, pi re-wraps the section.
254
+ const inner = fragment
255
+ .replace(/^<long-horizon[^>]*>\n?/, "")
256
+ .replace(/\n?<\/long-horizon>(\n|$)/, "$1");
257
+ event.systemPromptOptions.sections["long-horizon"] = inner;
258
+ return undefined;
234
259
  });
235
260
 
236
261
  pi.on("before_provider_request", (event) => {
@@ -26,6 +26,7 @@ export type ResolutionSource =
26
26
  | "owner"
27
27
  | "judge"
28
28
  | "judge_abstained_low_confidence"
29
+ | "child"
29
30
  | "default";
30
31
 
31
32
  export interface Resolution {
@@ -12,9 +12,13 @@
12
12
  */
13
13
 
14
14
  import { randomUUID } from "node:crypto";
15
- import { ensureDir, tryRead, writeJson } from "@pi-unipi/core";
15
+ import { ensureDir, isChildProcess, markSharedOwnerStopped, setSharedOwner, tryRead, writeJson } from "@pi-unipi/core";
16
16
  import type { OwnerKind } from "./modes.js";
17
17
 
18
+ /** Surfaced by create_goal / ralph / swarm / graph tools inside children. */
19
+ export const CHILD_OWNER_REFUSAL =
20
+ "Long-horizon owners are not available inside a child agent — do the work and report back to the lead.";
21
+
18
22
  export type OwnerStatus = "active" | "parked";
19
23
 
20
24
  export interface OwnerLease {
@@ -109,9 +113,13 @@ export class OwnerCoordinator {
109
113
 
110
114
  /**
111
115
  * Create and activate a new owner. Refuses while another owner is active —
112
- * callers surface "finish or suspend the current owner first".
116
+ * callers surface "finish or suspend the current owner first". In child
117
+ * processes owning is refused outright (throw — tool failures must throw).
113
118
  */
114
119
  activate(kind: OwnerKind, label: string): OwnerState | undefined {
120
+ if (isChildProcess() && process.env.UNIPI_LH_ALLOW_CHILD !== "1") {
121
+ throw new Error(CHILD_OWNER_REFUSAL);
122
+ }
115
123
  if (this.active) return undefined;
116
124
  const ownerId = randomUUID();
117
125
  const now = new Date(this.deps.now?.() ?? Date.now()).toISOString();
@@ -216,15 +224,32 @@ export class OwnerCoordinator {
216
224
  }
217
225
  const snapshot = this.snapshot();
218
226
  this.deps.onChange?.(snapshot, { type: "restored", snapshot });
227
+ this.publishStatus({ type: "restored", snapshot });
219
228
  return snapshot;
220
229
  }
221
230
 
222
231
  private commit(event: OwnerEvent): OwnerState | undefined {
223
232
  this.persist();
233
+ this.publishStatus(event);
224
234
  this.deps.onChange?.(this.snapshot(), event);
225
235
  return "owner" in event ? event.owner : undefined;
226
236
  }
227
237
 
238
+ /** Publish the transition to the shared holder kanboard's monitor reads. */
239
+ private publishStatus(event: OwnerEvent): void {
240
+ try {
241
+ if (event.type === "finished") markSharedOwnerStopped(stopKindOf(event.reason));
242
+ // Parked (paused) goals go through suspend, not finish — kanboard must
243
+ // see them as a stop of kind "paused" this run, same as finish would.
244
+ if (event.type === "suspended") markSharedOwnerStopped("paused");
245
+ if (this.active) setSharedOwner({ kind: this.active.kind, status: "active" });
246
+ else if (this.parked) setSharedOwner({ kind: this.parked.kind, status: "parked" });
247
+ else setSharedOwner(undefined);
248
+ } catch {
249
+ // Status publishing must never roll back a committed transition.
250
+ }
251
+ }
252
+
228
253
  private commitActive(patch: Partial<OwnerState>): OwnerState {
229
254
  if (!this.active) throw new Error("commitActive without an active owner");
230
255
  const next = this.withOwner(this.active, patch);
@@ -253,3 +278,11 @@ function renewLease(owner: OwnerState): OwnerLease {
253
278
  generation: owner.lease.generation + 1,
254
279
  });
255
280
  }
281
+
282
+ /** Map a terminal reason to the coarse stop kind kanboard's monitor needs. */
283
+ export function stopKindOf(reason: string): "complete" | "paused" | "budget" | "other" {
284
+ if (reason === "complete" || reason.startsWith("complete(")) return "complete";
285
+ if (reason === "paused" || reason.startsWith("paused(")) return "paused";
286
+ if (reason.includes("budget")) return "budget";
287
+ return "other";
288
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * User-only progress bars for long-horizon owners.
3
+ *
4
+ * ↻ Ralph · docs-cleanup ████████▒▒░░░░░░░░░░ 4/10 items iteration 3/20
5
+ * ◎ Goal ███████████▒▒░░░░░░░ ~55% turn 4/30
6
+ * Parser and CLI flags done; tests for the error paths are left.
7
+ *
8
+ * Ralph counts its checklist. A goal has no countable progress, so a one-off
9
+ * side call (never in the session context) estimates % done + a summary.
10
+ */
11
+
12
+ import type { ProgressData } from "@pi-unipi/core";
13
+ import type { GoalState } from "./engine/goal-state.js";
14
+ import type { RalphLoop } from "./engine/ralph.js";
15
+
16
+ export function ralphProgressData(ralph: Pick<RalphLoop, "get" | "progressSummary">): ProgressData | undefined {
17
+ const st = ralph.get();
18
+ if (!st) return undefined;
19
+ const p = ralph.progressSummary();
20
+ const live = st.status === "active";
21
+ const active = live ? Math.min(st.itemsPerIteration, Math.max(0, p.total - p.checked)) : 0;
22
+ return {
23
+ icon: "↻",
24
+ label: `Ralph · ${st.name}`,
25
+ done: p.checked,
26
+ active,
27
+ total: p.total,
28
+ unit: "items",
29
+ detail: `iteration ${String(st.iteration)}/${String(st.maxIterations)}${live ? "" : ` · ${st.status}`}`,
30
+ summary: live && p.next.length > 0 ? `next: ${p.next.slice(0, 3).join(" · ")}` : undefined,
31
+ };
32
+ }
33
+
34
+ export interface GoalEvidence {
35
+ commands: readonly string[];
36
+ changedFiles: readonly string[];
37
+ recentTail: ReadonlyArray<{ role: string; text: string }>;
38
+ }
39
+
40
+ export function goalEstimatePrompt(goal: Pick<GoalState, "objective" | "turn" | "maxTurns">, evidence: GoalEvidence | undefined, previous: number | undefined): string {
41
+ const lines = [
42
+ "Estimate progress on a goal for a progress bar shown to the user. Be honest and conservative: 100 only when the objective is fully met.",
43
+ "",
44
+ `Objective: ${goal.objective}`,
45
+ `Turn ${String(goal.turn)} of at most ${String(goal.maxTurns)}.`,
46
+ ];
47
+ if (previous !== undefined) lines.push(`Previous estimate: ${String(previous)}%.`);
48
+ if (evidence) {
49
+ if (evidence.changedFiles.length) lines.push(`Files touched recently: ${[...new Set(evidence.changedFiles)].slice(-12).join(", ")}`);
50
+ if (evidence.commands.length) lines.push(`Commands run recently: ${evidence.commands.slice(-8).join(" | ")}`);
51
+ if (evidence.recentTail.length) {
52
+ lines.push("", "Latest messages:");
53
+ for (const m of evidence.recentTail.slice(-4)) lines.push(`[${m.role}] ${m.text.slice(0, 600)}`);
54
+ }
55
+ }
56
+ lines.push("", 'Reply with JSON only: {"percent": <integer 0-100>, "summary": "<one or two short sentences: what is done, what is left>"}');
57
+ return lines.join("\n");
58
+ }
59
+
60
+ /** Pull `{percent, summary}` out of a model reply (tolerates fences and prose). */
61
+ export function parseEstimate(text: string): { percent: number; summary: string } | undefined {
62
+ const match = text.match(/\{[\s\S]*\}/u);
63
+ if (!match) return undefined;
64
+ try {
65
+ const raw = JSON.parse(match[0]) as { percent?: unknown; summary?: unknown };
66
+ const n = typeof raw.percent === "number" ? raw.percent : Number(raw.percent);
67
+ if (!Number.isFinite(n)) return undefined;
68
+ return { percent: Math.max(0, Math.min(100, Math.round(n))), summary: typeof raw.summary === "string" ? raw.summary.trim() : "" };
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
73
+
74
+ /** Shade = the slice in progress; a live goal always shows one. */
75
+ export function goalProgressData(goal: Pick<GoalState, "turn" | "maxTurns" | "status">, percent: number, summary: string): ProgressData {
76
+ const live = goal.status === "active" || goal.status === "waiting";
77
+ return {
78
+ icon: "◎",
79
+ label: "Goal",
80
+ done: goal.status === "complete" ? 100 : percent,
81
+ active: live ? Math.max(1, Math.min(10, 100 - percent)) : 0,
82
+ total: 100,
83
+ estimated: goal.status !== "complete",
84
+ detail: `turn ${String(goal.turn)}/${String(goal.maxTurns)}${live ? "" : ` · ${goal.status}`}`,
85
+ summary: summary || undefined,
86
+ };
87
+ }
@@ -96,7 +96,7 @@ export function renderContinuationHint(goal: GoalState, verifierReason?: string)
96
96
  goal.noProgressStreak > 0 ? `, ${goal.noProgressStreak}/${goal.stallCap} no-progress` : "";
97
97
  const budget =
98
98
  goal.tokenBudget !== null && !goal.tokensBaselinePending
99
- ? `, ${Math.max(0, goal.tokensNow - goal.tokensAtStart)}/${goal.tokenBudget} tok`
99
+ ? `, ${Math.max(0, goal.tokensNow - goal.tokensAtStart)}/${goal.tokenBudget} tok${goal.tokensEstimated ? " (est.)" : ""}`
100
100
  : "";
101
101
  return (
102
102
  `${CONTINUATION_HINT}` +