@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
@@ -2,9 +2,12 @@
2
2
  * Long-horizon commands — the explicit mode triggers.
3
3
  *
4
4
  * /unipi:goal|ralph|swarm|graph <prompt> turn override + prompt
5
+ * /unipi:goal|swarm|graph stop END the active owner (terminal)
6
+ * /unipi:ralph stop park the active loop
5
7
  * /unipi:goal|ralph|swarm|graph status owner + mode snapshot
6
8
  * /unipi:goal|ralph|swarm|graph resume reactivate the parked owner
7
9
  * /unipi:goal|ralph|swarm|graph clear drop the parked owner
10
+ * /unipi:regular stop any owner, pin regular mode
8
11
  * /unipi:continue resume the parked owner
9
12
  *
10
13
  * Switching while an owner is active suspends it (max-1 park slot; the
@@ -13,14 +16,23 @@
13
16
  */
14
17
 
15
18
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
16
- import { emitEvent, UNIPI_EVENTS } from "@pi-unipi/core";
17
- import type { LhMode } from "./modes.js";
19
+ import { appendProgress, emitEvent, sendHarnessUserMessage, UNIPI_EVENTS, type ProgressData } from "@pi-unipi/core";
20
+ import type { LhMode, OwnerKind } from "./modes.js";
18
21
  import { MODE_REGISTRY } from "./modes.js";
19
22
  import type { Gate } from "./gate.js";
20
23
  import type { OwnerCoordinator } from "./owner.js";
21
24
  import type { RalphLoop } from "./engine/ralph.js";
25
+ import type { GoalMachine } from "./engine/goal-state.js";
26
+ import type { GoalToolset } from "./tools/goal.js";
22
27
  import { loadSettings } from "./settings.js";
23
28
 
29
+ /** Progress-bar hooks (all output is user-only; see src/progress.ts). */
30
+ export interface ProgressHooks {
31
+ ralphBar(): ProgressData | undefined;
32
+ estimateGoal(ctx: ExtensionCommandContext): Promise<"ok" | "none" | "failed">;
33
+ goalEstimateOn(): boolean;
34
+ }
35
+
24
36
  export interface LongHorizonCommandDeps {
25
37
  readonly gate: Gate;
26
38
  readonly owner: OwnerCoordinator;
@@ -32,6 +44,37 @@ function notify(ctx: ExtensionCommandContext, text: string): void {
32
44
  if (ctx.hasUI) ctx.ui.notify(text, "info");
33
45
  }
34
46
 
47
+ /**
48
+ * Terminally end the active owner (optionally only one kind): goal marked
49
+ * complete(user_requested), pending proposal dropped, owner → history, session
50
+ * pinned to regular mode. Returns the stopped owner id, undefined when nothing
51
+ * (of that kind) is active. Shared by /unipi:<mode> stop, /unipi:regular, and
52
+ * the unipi:goal-stop runner.
53
+ */
54
+ export function stopActiveOwner(
55
+ pi: ExtensionAPI,
56
+ gate: Gate,
57
+ owner: OwnerCoordinator,
58
+ machine?: GoalMachine,
59
+ toolset?: GoalToolset,
60
+ kind?: OwnerKind,
61
+ ): string | undefined {
62
+ const active = owner.getActive();
63
+ if (!active || (kind !== undefined && active.kind !== kind)) return undefined;
64
+ if (active.kind === "goal") {
65
+ machine?.clear();
66
+ toolset?.discardProposal();
67
+ }
68
+ const finished = owner.finish("stopped(user_requested)");
69
+ emitEvent(pi, UNIPI_EVENTS.LONG_HORIZON_OWNER_CHANGED, {
70
+ event: "stopped",
71
+ kind: active.kind,
72
+ ...(finished ? { ownerId: finished.ownerId } : {}),
73
+ });
74
+ gate.setSessionMode("none");
75
+ return finished?.ownerId ?? active.ownerId;
76
+ }
77
+
35
78
  function ownerSnapshotText(owner: OwnerCoordinator): string {
36
79
  const active = owner.getActive();
37
80
  const parked = owner.getParked();
@@ -55,10 +98,10 @@ function ownerSnapshotText(owner: OwnerCoordinator): string {
55
98
 
56
99
  /** Short user-facing mode descriptions: use case + cost/success (design §2 rubric). */
57
100
  const MODE_DESCRIPTIONS = {
58
- goal: "One objective until verifiably true. Use: medium-complex single deliverables. Cost/success: pareto per success. (<prompt> | status | resume | clear)",
101
+ goal: "One objective until verifiably true. Use: medium-complex single deliverables. Cost/success: pareto per success. (<prompt> | status | stop | resume | clear)",
59
102
  ralph: "Checklist grind over iterations. Use: enumerable chores, repo-scale plans. Cost/success: low cost, solid success. (start <name> | stop | status | resume | clear | <prompt>)",
60
- swarm: "Parallel fan-out + one synthesis. Use: complex decomposable work. Cost/success: higher cost, high coverage. (<prompt> | status | resume | clear)",
61
- graph: "Dependent multi-step work. Use: later steps need earlier results. Cost/success: highest; run when the shape demands it. (<prompt> | status | resume | clear)",
103
+ swarm: "Parallel fan-out + one synthesis. Use: complex decomposable work. Cost/success: higher cost, high coverage. (<prompt> | status | stop | resume | clear)",
104
+ graph: "Dependent multi-step work. Use: later steps need earlier results. Cost/success: highest; run when the shape demands it. (<prompt> | status | stop | resume | clear)",
62
105
  } as const;
63
106
 
64
107
  interface CompletionItem {
@@ -71,6 +114,7 @@ interface CompletionItem {
71
114
  const goalCompletions = (prefix: string): CompletionItem[] => {
72
115
  const subs: CompletionItem[] = [
73
116
  { value: "status", label: "status", description: "show owner + mode snapshot" },
117
+ { value: "stop", label: "stop", description: "end the active owner (terminal)" },
74
118
  { value: "resume", label: "resume", description: "reactivate the parked owner" },
75
119
  { value: "clear", label: "clear", description: "drop the parked owner" },
76
120
  ];
@@ -98,7 +142,13 @@ export function registerLongHorizonCommands(
98
142
  gate: Gate,
99
143
  owner: OwnerCoordinator,
100
144
  ralph?: RalphLoop,
145
+ progressHooks?: ProgressHooks,
146
+ machine?: GoalMachine,
147
+ toolset?: GoalToolset,
101
148
  ): void {
149
+ /** Kept for the modeHandler closure — see exported stopActiveOwner. */
150
+ const stopActiveOwnerFor = (kind?: OwnerKind): string | undefined =>
151
+ stopActiveOwner(pi, gate, owner, machine, toolset, kind);
102
152
  const modeHandler = (mode: LhMode) => async (args: string, ctx: ExtensionCommandContext): Promise<void> => {
103
153
  const definition = MODE_REGISTRY[mode];
104
154
  const parts = args.trim().split(/\s+/);
@@ -146,6 +196,22 @@ export function registerLongHorizonCommands(
146
196
  ? `Loop "${loopState.name}": iteration ${loopState.iteration}, ${progress.checked}/${progress.total} items checked.\nNext: ${progress.next.slice(0, 3).join(" · ") || "(all checked)"}\n${ownerSnapshotText(owner)}`
147
197
  : ownerSnapshotText(owner),
148
198
  );
199
+ const bar = progress.total > 0 ? progressHooks?.ralphBar() : undefined;
200
+ if (bar) appendProgress(pi, bar);
201
+ return;
202
+ }
203
+
204
+ // stop — terminally END the active owner (goal/swarm/graph; ralph's stop
205
+ // stays park). A subcommand, never a prompt: it must not fall through to
206
+ // the <prompt> branch below.
207
+ if (mode !== "ralph" && sub === "stop") {
208
+ const stopped = stopActiveOwnerFor(mode as OwnerKind);
209
+ notify(
210
+ ctx,
211
+ stopped !== undefined
212
+ ? `${definition.label} stopped.`
213
+ : `No active ${definition.label.toLowerCase()}.`,
214
+ );
149
215
  return;
150
216
  }
151
217
 
@@ -158,6 +224,13 @@ export function registerLongHorizonCommands(
158
224
  `\nJudge: ${settings.judge.enabled ? `${settings.judge.provider}/${settings.judge.model} (threshold ${settings.judge.threshold})` : "off"} — default mode: ${settings.defaultMode}` +
159
225
  `\nSettings: ~/.pi/agent/settings.json → unipi.longHorizon`,
160
226
  );
227
+ // /goal status: estimate progress + summary in a side call (user-only).
228
+ if (mode === "goal" && progressHooks?.goalEstimateOn()) {
229
+ ctx.ui.setStatus?.("lh-progress", "◎ estimating goal progress…");
230
+ const outcome = await progressHooks.estimateGoal(ctx);
231
+ ctx.ui.setStatus?.("lh-progress", undefined);
232
+ if (outcome === "failed") notify(ctx, "Goal progress estimate failed (model unavailable or unparseable reply).");
233
+ }
161
234
  return;
162
235
  }
163
236
 
@@ -174,17 +247,32 @@ export function registerLongHorizonCommands(
174
247
  emitEvent(pi, UNIPI_EVENTS.LONG_HORIZON_OWNER_CHANGED, { event: "resumed", ownerId: resumed.ownerId });
175
248
  const resumeMode = resumed.kind === "ralph-loop" ? "ralph" : resumed.kind;
176
249
  gate.setExplicit(resumeMode as LhMode);
177
- await pi.sendUserMessage("Continue the resumed owner from its own durable state; re-read its status before acting.");
250
+ await sendHarnessUserMessage(
251
+ pi,
252
+ "Continue the resumed owner from its own durable state; re-read its status before acting.",
253
+ { source: "Long-horizon", title: "Resume", synopsis: "Owner resumed from durable state" },
254
+ );
178
255
  return;
179
256
  }
180
257
 
181
258
  if (sub === "clear") {
259
+ const active = owner.getActive();
260
+ if (active) {
261
+ const noun = active.kind === "ralph-loop" ? "ralph loop" : active.kind;
262
+ notify(
263
+ ctx,
264
+ active.kind === "ralph-loop"
265
+ ? "A ralph loop is running — /unipi:ralph stop parks it, or /unipi:regular ends it."
266
+ : `A ${noun} is running — use /unipi:${noun} stop to end it.`,
267
+ );
268
+ return;
269
+ }
182
270
  const cleared = owner.clearParked();
183
271
  notify(
184
272
  ctx,
185
273
  cleared
186
274
  ? `Cleared parked ${cleared.kind} "${cleared.label}".`
187
- : "Nothing parked to clear.",
275
+ : "Nothing to clear.",
188
276
  );
189
277
  if (cleared) {
190
278
  emitEvent(pi, UNIPI_EVENTS.LONG_HORIZON_OWNER_CHANGED, { event: "cleared", ownerId: cleared.ownerId });
@@ -221,5 +309,13 @@ export function registerLongHorizonCommands(
221
309
  pi.registerCommand("unipi:ralph", { description: MODE_DESCRIPTIONS.ralph, getArgumentCompletions: ralphCompletions, handler: modeHandler("ralph") });
222
310
  pi.registerCommand("unipi:swarm", { description: MODE_DESCRIPTIONS.swarm, getArgumentCompletions: goalCompletions, handler: modeHandler("swarm") });
223
311
  pi.registerCommand("unipi:graph", { description: MODE_DESCRIPTIONS.graph, getArgumentCompletions: goalCompletions, handler: modeHandler("graph") });
312
+ pi.registerCommand("unipi:regular", {
313
+ description: "Regular mode — stop any active owner and run prompts without long-horizon routing",
314
+ handler: async (_args: string, ctx: ExtensionCommandContext): Promise<void> => {
315
+ stopActiveOwnerFor();
316
+ gate.setSessionMode("none");
317
+ notify(ctx, "Regular mode.");
318
+ },
319
+ });
224
320
 
225
321
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Active-work brief for compaction summaries: what the goal / ralph loop is
3
+ * doing right now, from durable state rather than transcript guesses.
4
+ */
5
+
6
+ import type { GoalState } from "./engine/goal-state.js";
7
+ import type { LoopFileState } from "./engine/ralph.js";
8
+
9
+ const clip = (text: string, max: number): string => {
10
+ const flat = text.replace(/\s+/g, " ").trim();
11
+ return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`;
12
+ };
13
+
14
+ export function longHorizonCompactionBrief(
15
+ goal: GoalState | null,
16
+ loop: LoopFileState | null,
17
+ ralphDir: string,
18
+ ): string | null {
19
+ const lines: string[] = [];
20
+ if (loop && loop.status === "active") {
21
+ const max = loop.maxIterations > 0 ? `/${loop.maxIterations}` : "";
22
+ lines.push(
23
+ `Ralph loop "${loop.name}" is running — iteration ${loop.iteration}${max}.`,
24
+ `Task file (the checklist is the source of truth, re-read it): ${ralphDir}/${loop.taskFile}`,
25
+ "Work the next unchecked items, update the task file, then call ralph_done.",
26
+ );
27
+ } else if (goal && goal.status === "active") {
28
+ const stall = goal.noProgressStreak > 0 ? `, ${goal.noProgressStreak}/${goal.stallCap} no-progress` : "";
29
+ lines.push(
30
+ `Goal (active, turn ${goal.turn}/${goal.maxTurns}${stall}): "${clip(goal.objective, 700)}"`,
31
+ "Keep working toward it; call get_goal for the durable state. Completion is judged by an independent verifier.",
32
+ );
33
+ }
34
+ return lines.length > 0 ? lines.join("\n") : null;
35
+ }
@@ -55,8 +55,20 @@ export interface ContinuationDeps {
55
55
  readonly owner: OwnerCoordinator;
56
56
  /** Verifier wiring; defaults to a VerifierDeps the caller provides. */
57
57
  readonly verifier: VerifierDeps;
58
- /** Deliver a continuation/kickoff/wrap-up message (tail message). */
59
- send(message: string): void;
58
+ /**
59
+ * Deliver a continuation/kickoff/wrap-up message. Messages go to the
60
+ * arbiter's nudge stash (long-horizon/index.ts), NOT the message queue;
61
+ * `kind: "kickoff"` marks the kickoff contract, which is never overwritten
62
+ * while undelivered.
63
+ */
64
+ send(message: string, kind?: "kickoff"): void;
65
+ /**
66
+ * Direct delivery for TIMER wakes while idle (waiting-backoff): an event,
67
+ * not a nudge — bypasses the stash and prompts immediately.
68
+ */
69
+ sendNow?(message: string): void;
70
+ /** User-only notification (wired to ctx.ui.notify at agent_end; optional in tests). */
71
+ notify?(text: string, level?: "info" | "warning" | "error"): void;
60
72
  getTokenCount?(): number | undefined;
61
73
  now?(): number;
62
74
  schedule?(callback: () => void, delayMs: number): void;
@@ -108,6 +120,23 @@ export class GoalContinuation {
108
120
  this.evaluateOverride = evaluate;
109
121
  }
110
122
 
123
+ private tokensEstimated = false;
124
+ private notifyOverride?: NonNullable<ContinuationDeps["notify"]>;
125
+
126
+ /** Late-bound: the last turn's tokens were estimated, not read from usage. */
127
+ setTokensEstimated(estimated: boolean): void {
128
+ this.tokensEstimated = estimated;
129
+ }
130
+
131
+ /** Late-bound user notification (ctx.ui.notify at agent_end). */
132
+ setNotify(notify: NonNullable<ContinuationDeps["notify"]>): void {
133
+ this.notifyOverride = notify;
134
+ }
135
+
136
+ private inform(text: string, level: "info" | "warning" | "error" = "warning"): void {
137
+ (this.notifyOverride ?? this.deps.notify)?.(text, level);
138
+ }
139
+
111
140
  /** Crash/interrupt marker: the next continuation carries the recovery fragment. */
112
141
  armRecovery(): void {
113
142
  this.recoveryArmed = true;
@@ -115,7 +144,7 @@ export class GoalContinuation {
115
144
 
116
145
  /** Deliver the kickoff contract once for a fresh goal (cache-stable). */
117
146
  deliverKickoff(goal: GoalState): void {
118
- this.deps.send(renderKickoff(goal.objective));
147
+ this.deps.send(renderKickoff(goal.objective), "kickoff");
119
148
  this.deps.machine.markKickoffDelivered(goal.goalId);
120
149
  this.consecutiveWaits = 0;
121
150
  this.lastVerifierReason = undefined;
@@ -128,11 +157,19 @@ export class GoalContinuation {
128
157
  async onTurnEnd(activity: TurnActivity): Promise<ContinuationDecision> {
129
158
  const machine = this.deps.machine;
130
159
  const goal = machine.getActive();
131
- if (!goal) return { action: "none", reason: "no-active-goal" };
160
+ if (!goal) {
161
+ // No drivable goal: a pending proposal could never be settled — drop it
162
+ // so get_goal stops advertising "verification pending" forever.
163
+ this.deps.toolset.discardProposal();
164
+ return { action: "none", reason: "no-active-goal" };
165
+ }
132
166
 
133
167
  // Kickoff not yet delivered (e.g., goal created mid-turn): contract first.
168
+ // The kickoff turn never settles — a proposal made beside creation is
169
+ // stale by definition; discard it instead of letting it outlive the turn.
134
170
  if (!goal.kickoffDelivered) {
135
171
  this.deliverKickoff(goal);
172
+ this.deps.toolset.discardProposal();
136
173
  return { action: "none", reason: "kickoff-delivered" };
137
174
  }
138
175
 
@@ -149,11 +186,16 @@ export class GoalContinuation {
149
186
  commands: activity.commands,
150
187
  recentTail: activity.recentTail,
151
188
  });
152
- const verdict = await verifyCompletion(
153
- { ...this.deps.verifier, ...(this.evaluateOverride ? { evaluate: this.evaluateOverride } : {}) },
154
- goal.objective,
155
- brief,
156
- );
189
+ const verify = () =>
190
+ verifyCompletion(
191
+ { ...this.deps.verifier, ...(this.evaluateOverride ? { evaluate: this.evaluateOverride } : {}) },
192
+ goal.objective,
193
+ brief,
194
+ );
195
+ // One transient evaluator failure gets a retry inside the same
196
+ // settlement; a second failure is `verifierUnavailable`, never a verdict.
197
+ let verdict = await verify();
198
+ if (verdict.evaluatorFailed) verdict = await verify();
157
199
  this.lastVerifierReason = verdict.evaluatorFailed
158
200
  ? undefined
159
201
  : `${verdict.reason}${verdict.missing.length > 0 ? ` (missing: ${verdict.missing.join("; ")})` : ""}`;
@@ -162,11 +204,27 @@ export class GoalContinuation {
162
204
  revision: goal.revision,
163
205
  completionClaim: { ...(proposal.summary !== undefined ? { summary: proposal.summary } : {}) },
164
206
  verifier: { verdict: verdict.verdict, ...(verdict.missing.length > 0 ? { missing: verdict.missing } : {}) },
165
- // A rejected claim is no progress; a met claim completed above; an
166
- // inconclusive/failed verifier is neutral (undefined).
167
- madeProgress: verdict.verdict === "not_met" ? false : undefined,
207
+ ...(verdict.evaluatorFailed ? { verifierUnavailable: true } : {}),
208
+ // A rejected claim is no progress unless the turn also changed files
209
+ // (work done, claimed early — common in batched goals); a met claim
210
+ // completed above; an inconclusive/failed verifier is neutral.
211
+ madeProgress: verdict.verdict === "not_met" ? activity.changedFiles.length > 0 : undefined,
168
212
  ...(tokensNow !== undefined ? { tokensNow } : {}),
213
+ ...(tokensNow !== undefined ? { tokensEstimated: this.tokensEstimated } : {}),
169
214
  });
215
+ this.tokensEstimated = false;
216
+ if (settled?.status === "paused") {
217
+ if (settled.reason === "paused(verifier_unavailable)") {
218
+ this.inform(`Goal verifier unavailable twice (${verdict.reason}) — goal paused. /unipi:goal resume to retry.`);
219
+ } else if (settled.reason === "paused(verifier_inconclusive)") {
220
+ this.inform(
221
+ `Goal verifier returned inconclusive twice — goal paused. Last evaluation: ${verdict.reason}. /unipi:goal resume.`,
222
+ );
223
+ } else if (settled.reason === "paused(no_progress_after_completion_claim)") {
224
+ const missing = verdict.missing.length > 0 ? ` Missing: ${verdict.missing.join("; ")}.` : "";
225
+ this.inform(`Completion claim rejected ${settled.notMetStreak} times — goal paused.${missing} /unipi:goal resume.`);
226
+ }
227
+ }
170
228
  } else if (proposal?.kind === "blocked") {
171
229
  settled = machine.settleTurn({
172
230
  goalId: goal.goalId,
@@ -184,7 +242,9 @@ export class GoalContinuation {
184
242
  madeProgress: activity.toolCalls > 0 ? true : undefined,
185
243
  ...(activity.waiting ? { waiting: true } : {}),
186
244
  ...(tokensNow !== undefined ? { tokensNow } : {}),
245
+ ...(tokensNow !== undefined ? { tokensEstimated: this.tokensEstimated } : {}),
187
246
  });
247
+ this.tokensEstimated = false;
188
248
  }
189
249
 
190
250
  if (!settled) {
@@ -192,12 +252,19 @@ export class GoalContinuation {
192
252
  return { action: "none", reason: "no-active-goal" };
193
253
  }
194
254
 
195
- // Terminal / paused → stop the owner; budget_limited adds a wrap-up turn.
255
+ // Terminal → stop the owner; PAUSED → park it (resumable via
256
+ // /unipi:goal resume); budget_limited adds a wrap-up turn.
196
257
  if (settled.status !== "active" && settled.status !== "waiting") {
197
258
  const terminal = settled.reason ?? settled.status;
198
259
  const wrapUp = settled.status === "budget_limited" && !machine.isWrapUpDelivered(settled);
199
260
  if (wrapUp) machine.markWrapUpDelivered(settled.goalId, settled.revision);
200
- this.deps.owner.finish(terminal);
261
+ if (settled.status === "paused") {
262
+ // Park (resume keeps the same goal); fall back to finish if the
263
+ // single park slot is held — the loop must still stop.
264
+ if (!this.deps.owner.suspend(terminal)) this.deps.owner.finish(terminal);
265
+ } else {
266
+ this.deps.owner.finish(terminal);
267
+ }
201
268
  if (wrapUp) this.deps.send(WRAP_UP_PROMPT);
202
269
  return { action: "stopped", terminal, wrapUp };
203
270
  }
@@ -210,7 +277,9 @@ export class GoalContinuation {
210
277
  const woken = this.deps.machine.wakeWaiting();
211
278
  if (woken) {
212
279
  this.consecutiveWaits = 0;
213
- this.deps.send(renderContinuationHint(woken));
280
+ // Timer wake while idle: an event, delivered directly — never
281
+ // through the stash (nothing will settle to drain it).
282
+ (this.deps.sendNow ?? this.deps.send)(renderContinuationHint(woken));
214
283
  }
215
284
  }, delayMs);
216
285
  return { action: "wait", delayMs };
@@ -16,7 +16,8 @@
16
16
  * can neither fake progress nor trip the stall detector)
17
17
  * - worker completion proposals never complete directly: only verifier met
18
18
  * or user request does (propose + verify). A rejected claim counts as
19
- * no-progress for that turn and feeds notMetStreak.
19
+ * no-progress for that turn (unless the turn changed files) and feeds
20
+ * notMetStreak.
20
21
  * - blocked requires 3 consecutive blocked proposals (mcode threshold);
21
22
  * safety/policy refusals are immediate
22
23
  * - every settlement carries {goalId, revision} — CAS; stale settlements
@@ -55,6 +56,7 @@ export const GOAL_STATUS_REASONS = [
55
56
  "paused(user_requested)",
56
57
  "paused(superseded)",
57
58
  "paused(verifier_unavailable)",
59
+ "paused(verifier_inconclusive)",
58
60
  "paused(no_progress)",
59
61
  "paused(no_progress_after_completion_claim)",
60
62
  "blocked(threshold_3turns)",
@@ -76,7 +78,9 @@ export const STALL_CAP_CEILING = 50;
76
78
  export const TOKEN_BUDGET_MINIMUM = 1_000;
77
79
  export const BLOCKED_PROPOSAL_THRESHOLD = 3;
78
80
  /** mcode: a verifier that keeps saying not_met this many times after claims parks the goal. */
79
- export const NOT_MET_STREAK_CAP = 3;
81
+ export const NOT_MET_STREAK_CAP = 5;
82
+ /** Consecutive inconclusive verdicts before the goal pauses for a human call. */
83
+ export const INCONCLUSIVE_STREAK_CAP = 2;
80
84
 
81
85
  export const GOAL_CONDITION_LIMIT = { codeUnits: 500, utf8Bytes: 1_500 } as const;
82
86
 
@@ -96,6 +100,8 @@ export interface GoalState {
96
100
  readonly tokenBudget: number | null;
97
101
  /** Written at the first settlement that observes tokensNow. */
98
102
  readonly tokensBaselinePending: boolean;
103
+ /** True when the current tokens figure was estimated (no provider usage). */
104
+ readonly tokensEstimated?: boolean;
99
105
  readonly tokensAtStart: number;
100
106
  readonly tokensNow: number;
101
107
  readonly turn: number;
@@ -104,6 +110,8 @@ export interface GoalState {
104
110
  readonly stallCap: number;
105
111
  /** Consecutive verifier not_met verdicts on completion claims. */
106
112
  readonly notMetStreak: number;
113
+ /** Consecutive inconclusive verdicts (weak evidence — neither met nor not_met). */
114
+ readonly inconclusiveStreak: number;
107
115
  /** Consecutive blocked proposals (blocked needs 3 in a row). */
108
116
  readonly blockedProposalStreak: number;
109
117
  readonly lease: GoalLease;
@@ -133,6 +141,10 @@ export interface GoalSettlement {
133
141
  readonly blockedProposal?: boolean;
134
142
  /** Safety/policy refusal — immediately terminal. */
135
143
  readonly safetyRefusal?: boolean;
144
+ /** The evaluator itself failed twice (error/timeout/unparseable) — pause, don't guess. */
145
+ readonly verifierUnavailable?: boolean;
146
+ /** tokensNow was estimated from message sizes, not read from provider usage. */
147
+ readonly tokensEstimated?: boolean;
136
148
  }
137
149
 
138
150
  export interface GoalCreateOptions {
@@ -233,11 +245,13 @@ export class GoalMachine {
233
245
  tokensBaselinePending: true,
234
246
  tokensAtStart: 0,
235
247
  tokensNow: 0,
248
+ tokensEstimated: false,
236
249
  turn: 0,
237
250
  maxTurns: Math.min(options.maxTurns ?? DEFAULT_MAX_TURNS, MAX_TURNS_CEILING),
238
251
  noProgressStreak: 0,
239
252
  stallCap: Math.min(options.stallCap ?? DEFAULT_STALL_CAP, STALL_CAP_CEILING),
240
253
  notMetStreak: 0,
254
+ inconclusiveStreak: 0,
241
255
  blockedProposalStreak: 0,
242
256
  lease: Object.freeze({ goalId, generation: 0 }),
243
257
  revision: 0,
@@ -338,33 +352,68 @@ export class GoalMachine {
338
352
  let turn = goal.turn;
339
353
  let noProgressStreak = goal.noProgressStreak;
340
354
  let notMetStreak = goal.notMetStreak;
355
+ let inconclusiveStreak = goal.inconclusiveStreak ?? 0;
341
356
  let blockedProposalStreak = goal.blockedProposalStreak;
342
357
 
343
358
  // 1. Verification of a completion claim decides completion.
344
359
  if (input.completionClaim && input.verifier) {
345
- if (input.verifier.verdict === "met") {
346
- const next = this.withGoal(goal, { status: "complete", reason: "complete(verifier_met)" });
360
+ // 1a. The evaluator itself failed twice in this settlement: pause — an
361
+ // unproven verdict must never advance (or stall) the goal.
362
+ if (input.verifierUnavailable) {
363
+ const next = this.withGoal(goal, {
364
+ status: "paused",
365
+ reason: "paused(verifier_unavailable)",
366
+ inconclusiveStreak: 0,
367
+ ...(input.tokensEstimated !== undefined ? { tokensEstimated: input.tokensEstimated } : {}),
368
+ });
347
369
  this.goal = next;
348
370
  this.commit(next, goal.status);
349
371
  return next;
350
372
  }
351
- if (input.verifier.verdict === "impossible") {
352
- const next = this.withGoal(goal, { status: "blocked", reason: "blocked(verifier_impossible)" });
373
+ if (input.verifier.verdict === "met") {
374
+ const next = this.withGoal(goal, { status: "complete", reason: "complete(verifier_met)", inconclusiveStreak: 0 });
353
375
  this.goal = next;
354
376
  this.commit(next, goal.status);
355
377
  return next;
356
378
  }
357
- // not_met / inconclusive: the claim failed. Count it.
358
- notMetStreak = input.verifier.verdict === "not_met" ? notMetStreak + 1 : notMetStreak;
359
- if (notMetStreak >= NOT_MET_STREAK_CAP && noProgressStreak + 1 >= goal.stallCap) {
360
- const next = this.withGoal(goal, {
361
- status: "paused",
362
- reason: "paused(no_progress_after_completion_claim)",
363
- });
379
+ if (input.verifier.verdict === "impossible") {
380
+ const next = this.withGoal(goal, { status: "blocked", reason: "blocked(verifier_impossible)", inconclusiveStreak: 0 });
364
381
  this.goal = next;
365
382
  this.commit(next, goal.status);
366
383
  return next;
367
384
  }
385
+ // not_met: the claim failed. Count it; the cap parks unconditionally.
386
+ if (input.verifier.verdict === "not_met") {
387
+ notMetStreak = notMetStreak + 1;
388
+ inconclusiveStreak = 0;
389
+ if (notMetStreak >= NOT_MET_STREAK_CAP) {
390
+ const next = this.withGoal(goal, {
391
+ status: "paused",
392
+ reason: "paused(no_progress_after_completion_claim)",
393
+ notMetStreak,
394
+ inconclusiveStreak,
395
+ });
396
+ this.goal = next;
397
+ this.commit(next, goal.status);
398
+ return next;
399
+ }
400
+ }
401
+ // inconclusive: evidence too weak either way; twice in a row = human call.
402
+ if (input.verifier.verdict === "inconclusive") {
403
+ inconclusiveStreak = inconclusiveStreak + 1;
404
+ if (inconclusiveStreak >= INCONCLUSIVE_STREAK_CAP) {
405
+ const next = this.withGoal(goal, {
406
+ status: "paused",
407
+ reason: "paused(verifier_inconclusive)",
408
+ inconclusiveStreak,
409
+ });
410
+ this.goal = next;
411
+ this.commit(next, goal.status);
412
+ return next;
413
+ }
414
+ }
415
+ } else {
416
+ inconclusiveStreak = 0;
368
417
  }
369
418
 
370
419
  // 2. Safety refusal is immediately terminal.
@@ -457,9 +506,11 @@ export class GoalMachine {
457
506
  tokensAtStart,
458
507
  tokensNow,
459
508
  tokensBaselinePending,
509
+ ...(input.tokensEstimated !== undefined ? { tokensEstimated: input.tokensEstimated } : {}),
460
510
  turn,
461
511
  noProgressStreak,
462
512
  notMetStreak,
513
+ inconclusiveStreak,
463
514
  blockedProposalStreak,
464
515
  });
465
516
  this.goal = next;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * One-slot nudge stash — the long-horizon handoff between `agent_end`
3
+ * settlement and the arbiter's `agent_before_settle` boundary.
4
+ *
5
+ * Continuation/ralph "sends" land here instead of pi's message queue; the
6
+ * package's nudge provider (priority 100) returns the stash at the boundary
7
+ * and takes it on delivery. A put overwrites an undelivered nudge (the newest
8
+ * intent wins) EXCEPT when the undelivered stash is a kickoff: the kickoff
9
+ * contract must never be lost, so a new put appends with a blank line and the
10
+ * merged stash stays kickoff-flagged until delivered.
11
+ */
12
+
13
+ export interface NudgeStashPutOptions {
14
+ /** Marks the kickoff contract (append-only while undelivered). */
15
+ kickoff?: boolean;
16
+ }
17
+
18
+ export class NudgeStash {
19
+ private text: string | null = null;
20
+ private kickoff = false;
21
+
22
+ put(text: string, options: NudgeStashPutOptions = {}): void {
23
+ if (this.text !== null && this.kickoff) {
24
+ this.text = `${this.text}\n\n${text}`;
25
+ return;
26
+ }
27
+ this.text = text;
28
+ this.kickoff = options.kickoff ?? false;
29
+ }
30
+
31
+ /** Undelivered text, or null. */
32
+ peek(): string | null {
33
+ return this.text;
34
+ }
35
+
36
+ /** Take the text out (delivered). */
37
+ take(): string | null {
38
+ const text = this.text;
39
+ this.text = null;
40
+ this.kickoff = false;
41
+ return text;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Owner lifecycle events that invalidate an undelivered nudge: when the owner
47
+ * stops or parks, a queued hint targets a machine that no longer drives. The
48
+ * wrap-up survives because continuation puts it AFTER owner.finish commits.
49
+ */
50
+ export interface StashMetaState<M> {
51
+ meta: M | undefined;
52
+ kickoff: boolean;
53
+ }
54
+
55
+ /** Pure metadata mirror of the stash merge semantics: a put into an undelivered
56
+ * KICKOFF appends and KEEPS the original provenance; any other put replaces it.
57
+ * `hasText` mirrors stash.peek() !== null. */
58
+ export function nextStashMetaState<M>(
59
+ prev: StashMetaState<M> | undefined,
60
+ hasText: boolean,
61
+ kind: "kickoff" | undefined,
62
+ incoming: M,
63
+ ): StashMetaState<M> {
64
+ const merging = hasText && (prev?.kickoff ?? false);
65
+ if (merging) return { meta: prev?.meta, kickoff: true };
66
+ return { meta: incoming, kickoff: kind === "kickoff" };
67
+ }
68
+
69
+ export function ownerEventClearsStash(event: { type: string }): boolean {
70
+ return event.type === "finished" || event.type === "suspended";
71
+ }
@@ -93,7 +93,8 @@ export interface RalphLoopDeps {
93
93
  readonly owner: OwnerCoordinator;
94
94
  /** Resolve the ralph dir (usually ctx.cwd-based). */
95
95
  ralphDir(): string;
96
- send(message: string): void;
96
+ /** Lands in the arbiter's nudge stash; the first prompt is the kickoff contract. */
97
+ send(message: string, kind?: "kickoff"): void;
97
98
  now?(): number;
98
99
  /** Footer/info-screen events (loop_start / iteration_done / loop_end). */
99
100
  onEvent?(event: RalphEvent): void;
@@ -214,7 +215,7 @@ export class RalphLoop {
214
215
  const itemsTotal = parseChecklist(taskContent).length;
215
216
  this.deps.onEvent?.({ type: "loop_start", name: state.name, iteration: 1, total: itemsTotal });
216
217
  const firstPrompt = this.buildIterationPrompt(state, false);
217
- this.deps.send(firstPrompt);
218
+ this.deps.send(firstPrompt, "kickoff");
218
219
  return { ok: true, state, firstPrompt };
219
220
  }
220
221