@ouro.bot/cli 0.1.0-alpha.70 → 0.1.0-alpha.700

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 (501) hide show
  1. package/README.md +133 -23
  2. package/RepairGuide.ouro/agent.json +5 -0
  3. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  4. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  5. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  6. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  7. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  8. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  9. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  12. package/changelog.json +4454 -14
  13. package/dist/a2a/card.js +57 -0
  14. package/dist/a2a/client.js +180 -0
  15. package/dist/a2a/config.js +50 -0
  16. package/dist/a2a/delegation-stores.js +56 -0
  17. package/dist/a2a/did-resolution.js +93 -0
  18. package/dist/a2a/identity.js +170 -0
  19. package/dist/a2a/inbound-share.js +107 -0
  20. package/dist/a2a/mission-result-wire.js +196 -0
  21. package/dist/a2a/onboarding.js +115 -0
  22. package/dist/a2a/pin-store.js +132 -0
  23. package/dist/a2a/seen-ledger.js +120 -0
  24. package/dist/a2a/server.js +604 -0
  25. package/dist/a2a/task-store.js +69 -0
  26. package/dist/a2a/transport.js +45 -0
  27. package/dist/a2a/types.js +3 -0
  28. package/dist/arc/attention-types.js +8 -0
  29. package/dist/arc/cares.js +144 -0
  30. package/dist/arc/episodes.js +118 -0
  31. package/dist/arc/evolution.js +490 -0
  32. package/dist/arc/flight-recorder.js +928 -0
  33. package/dist/arc/intentions.js +134 -0
  34. package/dist/arc/json-store.js +117 -0
  35. package/dist/arc/obligations.js +348 -0
  36. package/dist/arc/packets.js +336 -0
  37. package/dist/arc/presence.js +185 -0
  38. package/dist/arc/task-lifecycle.js +57 -0
  39. package/dist/commerce/store.js +755 -0
  40. package/dist/commerce/types.js +3 -0
  41. package/dist/heart/active-work.js +867 -43
  42. package/dist/heart/agent-entry.js +190 -3
  43. package/dist/heart/attachments/image-normalize.js +194 -0
  44. package/dist/heart/attachments/materialize.js +97 -0
  45. package/dist/heart/attachments/originals.js +88 -0
  46. package/dist/heart/attachments/render.js +29 -0
  47. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  48. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  49. package/dist/heart/attachments/sources/index.js +16 -0
  50. package/dist/heart/attachments/store.js +103 -0
  51. package/dist/heart/attachments/types.js +93 -0
  52. package/dist/heart/auth/auth-flow.js +479 -0
  53. package/dist/heart/autonomy-budget.js +434 -0
  54. package/dist/heart/awaiting/await-alert.js +146 -0
  55. package/dist/heart/awaiting/await-expiry.js +108 -0
  56. package/dist/heart/awaiting/await-loader.js +91 -0
  57. package/dist/heart/awaiting/await-parser.js +141 -0
  58. package/dist/heart/awaiting/await-runtime-state.js +100 -0
  59. package/dist/heart/awaiting/await-scheduler.js +378 -0
  60. package/dist/heart/background-operations.js +281 -0
  61. package/dist/heart/bridges/manager.js +137 -17
  62. package/dist/heart/bridges/store.js +14 -2
  63. package/dist/heart/bundle-state.js +168 -0
  64. package/dist/heart/commitments.js +135 -0
  65. package/dist/heart/config-registry.js +331 -0
  66. package/dist/heart/config.js +119 -127
  67. package/dist/heart/context-loss-gauntlet.js +354 -0
  68. package/dist/heart/context-loss-sentinel.js +1155 -0
  69. package/dist/heart/core.js +1306 -278
  70. package/dist/heart/cross-chat-delivery.js +8 -22
  71. package/dist/heart/daemon/agent-config-check.js +451 -0
  72. package/dist/heart/daemon/agent-discovery.js +185 -3
  73. package/dist/heart/daemon/agent-service.js +523 -0
  74. package/dist/heart/daemon/agentic-repair.js +547 -0
  75. package/dist/heart/daemon/await-private-wake.js +59 -0
  76. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  77. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  78. package/dist/heart/daemon/cadence.js +180 -0
  79. package/dist/heart/daemon/cli-defaults.js +780 -0
  80. package/dist/heart/daemon/cli-desk.js +322 -0
  81. package/dist/heart/daemon/cli-exec.js +8142 -0
  82. package/dist/heart/daemon/cli-help.js +675 -0
  83. package/dist/heart/daemon/cli-parse.js +2461 -0
  84. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  85. package/dist/heart/daemon/cli-render.js +770 -0
  86. package/dist/heart/daemon/cli-types.js +8 -0
  87. package/dist/heart/daemon/connect-bay.js +323 -0
  88. package/dist/heart/daemon/daemon-cli.js +29 -1930
  89. package/dist/heart/daemon/daemon-entry.js +822 -10
  90. package/dist/heart/daemon/daemon-health.js +176 -0
  91. package/dist/heart/daemon/daemon-rollup.js +57 -0
  92. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  93. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  94. package/dist/heart/daemon/daemon.js +1264 -84
  95. package/dist/heart/daemon/dns-workflow.js +394 -0
  96. package/dist/heart/daemon/doctor-types.js +8 -0
  97. package/dist/heart/daemon/doctor.js +1126 -0
  98. package/dist/heart/daemon/habit-private-wake.js +56 -0
  99. package/dist/heart/daemon/health-monitor.js +142 -11
  100. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  101. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  102. package/dist/heart/daemon/http-health-probe.js +80 -0
  103. package/dist/heart/daemon/human-command-screens.js +234 -0
  104. package/dist/heart/daemon/human-readiness.js +114 -0
  105. package/dist/heart/daemon/inner-status.js +78 -0
  106. package/dist/heart/daemon/interactive-repair.js +394 -0
  107. package/dist/heart/daemon/launchd.js +37 -8
  108. package/dist/heart/daemon/log-tailer.js +79 -10
  109. package/dist/heart/daemon/logs-prune.js +110 -0
  110. package/dist/heart/daemon/mcp-canary.js +297 -0
  111. package/dist/heart/daemon/message-router.js +45 -5
  112. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  113. package/dist/heart/daemon/os-cron-deps.js +135 -0
  114. package/dist/heart/daemon/os-cron.js +22 -14
  115. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  116. package/dist/heart/daemon/ouro-entry.js +3 -1
  117. package/dist/heart/daemon/plugin-cli.js +432 -0
  118. package/dist/heart/daemon/process-manager.js +519 -40
  119. package/dist/heart/daemon/provider-discovery.js +137 -0
  120. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  121. package/dist/heart/daemon/pulse.js +511 -0
  122. package/dist/heart/daemon/readiness-repair.js +365 -0
  123. package/dist/heart/daemon/run-hooks.js +2 -0
  124. package/dist/heart/daemon/runtime-logging.js +41 -14
  125. package/dist/heart/daemon/runtime-metadata.js +2 -30
  126. package/dist/heart/daemon/safe-mode.js +161 -0
  127. package/dist/heart/daemon/sense-manager.js +670 -48
  128. package/dist/heart/daemon/session-id-resolver.js +131 -0
  129. package/dist/heart/daemon/skill-management-installer.js +1 -1
  130. package/dist/heart/daemon/socket-client.js +171 -12
  131. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  132. package/dist/heart/daemon/startup-tui.js +338 -0
  133. package/dist/heart/daemon/task-scheduler.js +117 -39
  134. package/dist/heart/daemon/terminal-ui.js +499 -0
  135. package/dist/heart/daemon/thoughts.js +255 -43
  136. package/dist/heart/daemon/up-progress.js +366 -0
  137. package/dist/heart/daemon/vault-items.js +56 -0
  138. package/dist/heart/delegation.js +1 -4
  139. package/dist/heart/external-events/router.js +121 -0
  140. package/dist/heart/habits/habit-migration.js +189 -0
  141. package/dist/heart/habits/habit-parser.js +214 -0
  142. package/dist/heart/habits/habit-runtime-state.js +114 -0
  143. package/dist/heart/habits/habit-scheduler.js +431 -0
  144. package/dist/heart/habits/habit-session-summary.js +318 -0
  145. package/dist/heart/habits/habit-session.js +617 -0
  146. package/dist/heart/{daemon → hatch}/hatch-flow.js +51 -60
  147. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  148. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  149. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  150. package/dist/heart/identity.js +174 -59
  151. package/dist/heart/kept-notes.js +289 -0
  152. package/dist/heart/kicks.js +1 -1
  153. package/dist/heart/machine-identity.js +161 -0
  154. package/dist/heart/mail-import-discovery.js +353 -0
  155. package/dist/heart/mailbox/mailbox-http-hooks.js +96 -0
  156. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  157. package/dist/heart/mailbox/mailbox-http-routes.js +375 -0
  158. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  159. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  160. package/dist/heart/mailbox/mailbox-http.js +99 -0
  161. package/dist/heart/mailbox/mailbox-read.js +37 -0
  162. package/dist/heart/mailbox/mailbox-types.js +27 -0
  163. package/dist/heart/mailbox/mailbox-view.js +197 -0
  164. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  165. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  166. package/dist/heart/mailbox/readers/mail.js +375 -0
  167. package/dist/heart/mailbox/readers/runtime-readers.js +815 -0
  168. package/dist/heart/mailbox/readers/sessions.js +232 -0
  169. package/dist/heart/mailbox/readers/shared.js +111 -0
  170. package/dist/heart/mcp/mcp-server.js +696 -0
  171. package/dist/heart/migrate-config.js +100 -0
  172. package/dist/heart/model-capabilities.js +11 -0
  173. package/dist/heart/orientation-frame.js +217 -0
  174. package/dist/heart/platform.js +81 -0
  175. package/dist/heart/primitives.js +1 -1
  176. package/dist/heart/private-runtime/decision-renderer.js +156 -0
  177. package/dist/heart/private-runtime/index.js +18 -0
  178. package/dist/heart/private-runtime/ledger.js +218 -0
  179. package/dist/heart/private-runtime/policy.js +214 -0
  180. package/dist/heart/private-runtime/types.js +2 -0
  181. package/dist/heart/progress-story.js +1 -1
  182. package/dist/heart/provider-attempt.js +134 -0
  183. package/dist/heart/provider-binding-resolver.js +273 -0
  184. package/dist/heart/provider-credentials.js +441 -0
  185. package/dist/heart/provider-failover.js +311 -0
  186. package/dist/heart/provider-models.js +81 -0
  187. package/dist/heart/provider-ping.js +262 -0
  188. package/dist/heart/provider-readiness-cache.js +119 -0
  189. package/dist/heart/provider-visibility.js +188 -0
  190. package/dist/heart/providers/anthropic-token.js +131 -0
  191. package/dist/heart/providers/anthropic.js +139 -52
  192. package/dist/heart/providers/azure.js +23 -11
  193. package/dist/heart/providers/error-classification.js +127 -0
  194. package/dist/heart/providers/github-copilot.js +34 -38
  195. package/dist/heart/providers/minimax-vlm.js +189 -0
  196. package/dist/heart/providers/minimax.js +26 -8
  197. package/dist/heart/providers/openai-codex-token.js +349 -0
  198. package/dist/heart/providers/openai-codex.js +55 -40
  199. package/dist/heart/run-ledger.js +247 -0
  200. package/dist/heart/runtime-capability-check.js +170 -0
  201. package/dist/heart/runtime-credentials.js +398 -0
  202. package/dist/heart/runtime-cwd.js +87 -0
  203. package/dist/heart/sense-truth.js +20 -4
  204. package/dist/heart/session-activity.js +90 -24
  205. package/dist/heart/session-events.js +1133 -0
  206. package/dist/heart/session-playback-cli-main.js +5 -0
  207. package/dist/heart/session-playback-cli.js +36 -0
  208. package/dist/heart/session-playback.js +231 -0
  209. package/dist/heart/session-stats-cli-main.js +5 -0
  210. package/dist/heart/session-stats.js +182 -0
  211. package/dist/heart/session-transcript.js +133 -0
  212. package/dist/heart/start-of-turn-packet.js +365 -0
  213. package/dist/heart/streaming.js +44 -27
  214. package/dist/heart/structured-output.js +196 -0
  215. package/dist/heart/sync-classification.js +176 -0
  216. package/dist/heart/sync.js +449 -0
  217. package/dist/heart/target-resolution.js +11 -7
  218. package/dist/heart/tempo.js +93 -0
  219. package/dist/heart/temporal-view.js +41 -0
  220. package/dist/heart/timeouts.js +101 -0
  221. package/dist/heart/tool-activity-callbacks.js +59 -0
  222. package/dist/heart/tool-description.js +155 -0
  223. package/dist/heart/tool-friction.js +55 -0
  224. package/dist/heart/tool-loop.js +200 -0
  225. package/dist/heart/turn-context.js +472 -0
  226. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  227. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  228. package/dist/heart/versioning/ouro-path-installer.js +426 -0
  229. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  230. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  231. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  232. package/dist/heart/versioning/update-hooks.js +154 -0
  233. package/dist/heart/work-card.js +380 -0
  234. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  235. package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
  236. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  237. package/dist/mailbox-ui/index.html +16 -0
  238. package/dist/mailroom/attention.js +167 -0
  239. package/dist/mailroom/autonomy.js +209 -0
  240. package/dist/mailroom/blob-store.js +715 -0
  241. package/dist/mailroom/body-cache.js +61 -0
  242. package/dist/mailroom/core.js +788 -0
  243. package/dist/mailroom/entry.js +160 -0
  244. package/dist/mailroom/file-store.js +568 -0
  245. package/dist/mailroom/mbox-import.js +393 -0
  246. package/dist/mailroom/migration.js +164 -0
  247. package/dist/mailroom/outbound.js +380 -0
  248. package/dist/mailroom/policy.js +263 -0
  249. package/dist/mailroom/reader.js +233 -0
  250. package/dist/mailroom/search-cache.js +334 -0
  251. package/dist/mailroom/search-relevance.js +319 -0
  252. package/dist/mailroom/smtp-ingress.js +176 -0
  253. package/dist/mailroom/source-state.js +176 -0
  254. package/dist/mailroom/thread.js +109 -0
  255. package/dist/mailroom/travel-extract.js +89 -0
  256. package/dist/mind/bundle-manifest.js +24 -2
  257. package/dist/mind/context.js +250 -101
  258. package/dist/mind/desk-section.js +362 -0
  259. package/dist/mind/diary-integrity.js +60 -0
  260. package/dist/mind/{memory.js → diary.js} +68 -77
  261. package/dist/mind/embedding-provider.js +60 -0
  262. package/dist/mind/file-state.js +179 -0
  263. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  264. package/dist/mind/obligation-steering.js +221 -0
  265. package/dist/mind/pending.js +13 -8
  266. package/dist/mind/prompt-budget.js +380 -0
  267. package/dist/mind/prompt-refresh.js +3 -2
  268. package/dist/mind/prompt.js +1028 -169
  269. package/dist/mind/provenance-trust.js +26 -0
  270. package/dist/mind/record-paths.js +312 -0
  271. package/dist/mind/scrutiny.js +173 -0
  272. package/dist/nerves/cli-logging.js +7 -1
  273. package/dist/nerves/coverage/audit-rules.js +15 -6
  274. package/dist/nerves/coverage/audit.js +28 -2
  275. package/dist/nerves/coverage/cli.js +1 -1
  276. package/dist/nerves/coverage/contract.js +5 -5
  277. package/dist/nerves/coverage/file-completeness.js +139 -5
  278. package/dist/nerves/event-buffer.js +111 -0
  279. package/dist/nerves/index.js +234 -4
  280. package/dist/nerves/observation.js +20 -0
  281. package/dist/nerves/redact.js +79 -0
  282. package/dist/nerves/review/cli-main.js +5 -0
  283. package/dist/nerves/review/cli.js +156 -0
  284. package/dist/nerves/review/core.js +152 -0
  285. package/dist/nerves/runtime.js +5 -1
  286. package/dist/repertoire/ado-client.js +15 -56
  287. package/dist/repertoire/ado-semantic.js +16 -10
  288. package/dist/repertoire/api-client.js +97 -0
  289. package/dist/repertoire/bitwarden-store.js +1042 -0
  290. package/dist/repertoire/bundle-templates.js +71 -0
  291. package/dist/repertoire/bw-installer.js +180 -0
  292. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  293. package/dist/repertoire/coding/context-pack.js +331 -0
  294. package/dist/repertoire/coding/feedback.js +218 -30
  295. package/dist/repertoire/coding/index.js +15 -3
  296. package/dist/repertoire/coding/manager.js +166 -10
  297. package/dist/repertoire/coding/spawner.js +58 -11
  298. package/dist/repertoire/coding/tools.js +242 -14
  299. package/dist/repertoire/coding/workbench-client.js +272 -0
  300. package/dist/repertoire/coding/workbench-manager.js +346 -0
  301. package/dist/repertoire/commerce-errors.js +109 -0
  302. package/dist/repertoire/commerce-self-test.js +156 -0
  303. package/dist/repertoire/credential-access.js +178 -0
  304. package/dist/repertoire/desk/classifier.js +362 -0
  305. package/dist/repertoire/duffel-client.js +185 -0
  306. package/dist/repertoire/github-client.js +14 -55
  307. package/dist/repertoire/graph-client.js +11 -52
  308. package/dist/repertoire/guardrails.js +156 -27
  309. package/dist/repertoire/mcp-client.js +44 -3
  310. package/dist/repertoire/mcp-manager.js +259 -20
  311. package/dist/repertoire/mcp-tools.js +83 -0
  312. package/dist/repertoire/plugin-mcp.js +175 -0
  313. package/dist/repertoire/plugins.js +253 -0
  314. package/dist/repertoire/shell-sessions.js +133 -0
  315. package/dist/repertoire/skills.js +48 -4
  316. package/dist/repertoire/stripe-client.js +131 -0
  317. package/dist/repertoire/tool-results.js +29 -0
  318. package/dist/repertoire/tools-a2a.js +699 -0
  319. package/dist/repertoire/tools-attachments.js +317 -0
  320. package/dist/repertoire/tools-awaiting.js +372 -0
  321. package/dist/repertoire/tools-base.js +65 -1095
  322. package/dist/repertoire/tools-bluebubbles.js +2 -0
  323. package/dist/repertoire/tools-bridge.js +144 -0
  324. package/dist/repertoire/tools-bundle.js +993 -0
  325. package/dist/repertoire/tools-commerce.js +253 -0
  326. package/dist/repertoire/tools-config.js +186 -0
  327. package/dist/repertoire/tools-continuity.js +252 -0
  328. package/dist/repertoire/tools-credential.js +383 -0
  329. package/dist/repertoire/tools-evolution.js +527 -0
  330. package/dist/repertoire/tools-files.js +344 -0
  331. package/dist/repertoire/tools-flight.js +290 -0
  332. package/dist/repertoire/tools-flow.js +119 -0
  333. package/dist/repertoire/tools-github.js +3 -8
  334. package/dist/repertoire/tools-mail.js +1975 -0
  335. package/dist/repertoire/tools-notes.js +459 -0
  336. package/dist/repertoire/tools-obligations.js +182 -0
  337. package/dist/repertoire/tools-orientation.js +31 -0
  338. package/dist/repertoire/tools-record.js +482 -0
  339. package/dist/repertoire/tools-rsvp.js +139 -0
  340. package/dist/repertoire/tools-runtime.js +150 -0
  341. package/dist/repertoire/tools-session.js +881 -0
  342. package/dist/repertoire/tools-shell.js +120 -0
  343. package/dist/repertoire/tools-stripe.js +224 -0
  344. package/dist/repertoire/tools-surface.js +355 -0
  345. package/dist/repertoire/tools-teams.js +12 -39
  346. package/dist/repertoire/tools-travel.js +125 -0
  347. package/dist/repertoire/tools-trip.js +982 -0
  348. package/dist/repertoire/tools-user-profile.js +146 -0
  349. package/dist/repertoire/tools-vault.js +40 -0
  350. package/dist/repertoire/tools-voice.js +145 -0
  351. package/dist/repertoire/tools.js +250 -80
  352. package/dist/repertoire/travel-api-client.js +360 -0
  353. package/dist/repertoire/user-profile.js +131 -0
  354. package/dist/repertoire/vault-setup.js +246 -0
  355. package/dist/repertoire/vault-unlock.js +594 -0
  356. package/dist/rsvp/aisleplanner-client.js +354 -0
  357. package/dist/rsvp/cli.js +704 -0
  358. package/dist/rsvp/config.js +327 -0
  359. package/dist/rsvp/cutover.js +431 -0
  360. package/dist/rsvp/diagnostics.js +222 -0
  361. package/dist/rsvp/diff-renderer.js +161 -0
  362. package/dist/rsvp/habit-policy.js +85 -0
  363. package/dist/rsvp/habit-stage.js +93 -0
  364. package/dist/rsvp/incident-bundle.js +97 -0
  365. package/dist/rsvp/migration.js +204 -0
  366. package/dist/rsvp/outbound-state.js +254 -0
  367. package/dist/rsvp/query.js +118 -0
  368. package/dist/rsvp/replay.js +233 -0
  369. package/dist/rsvp/snapshot.js +232 -0
  370. package/dist/scripts/claude-code-hook.js +41 -0
  371. package/dist/scripts/claude-code-stop-hook.js +47 -0
  372. package/dist/senses/a2a-entry.js +115 -0
  373. package/dist/senses/attention-queue.js +186 -0
  374. package/dist/senses/await-turn-message.js +58 -0
  375. package/dist/senses/bluebubbles/active-turns.js +216 -0
  376. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  377. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  378. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +226 -19
  379. package/dist/senses/bluebubbles/entry.js +77 -0
  380. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  381. package/dist/senses/bluebubbles/index.js +2937 -0
  382. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  383. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  384. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  385. package/dist/senses/bluebubbles/outbound-state.js +304 -0
  386. package/dist/senses/bluebubbles/processed-log.js +133 -0
  387. package/dist/senses/bluebubbles/replay.js +284 -0
  388. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  389. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  390. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  391. package/dist/senses/cli/bracketed-paste.js +82 -0
  392. package/dist/senses/cli/image-paste.js +287 -0
  393. package/dist/senses/cli/image-ref-navigation.js +75 -0
  394. package/dist/senses/cli/ink-app.js +156 -0
  395. package/dist/senses/cli/inline-diff.js +64 -0
  396. package/dist/senses/cli/input-keys.js +174 -0
  397. package/dist/senses/cli/kill-ring.js +86 -0
  398. package/dist/senses/cli/message-list.js +51 -0
  399. package/dist/senses/cli/ouro-tui.js +607 -0
  400. package/dist/senses/cli/spinner-imperative.js +135 -0
  401. package/dist/senses/cli/spinner.js +101 -0
  402. package/dist/senses/cli/status-line.js +60 -0
  403. package/dist/senses/cli/streaming-markdown.js +526 -0
  404. package/dist/senses/cli/tool-display.js +85 -0
  405. package/dist/senses/cli/tool-render.js +85 -0
  406. package/dist/senses/cli/tui-store.js +240 -0
  407. package/dist/senses/cli/virtual-list.js +35 -0
  408. package/dist/senses/cli-entry.js +60 -8
  409. package/dist/senses/cli-layout.js +100 -0
  410. package/dist/senses/cli.js +521 -214
  411. package/dist/senses/commands.js +66 -3
  412. package/dist/senses/context-packet-ledger.js +245 -0
  413. package/dist/senses/context-packets.js +154 -0
  414. package/dist/senses/habit-turn-message.js +160 -0
  415. package/dist/senses/mail-entry.js +66 -0
  416. package/dist/senses/mail.js +395 -0
  417. package/dist/senses/pipeline.js +952 -178
  418. package/dist/senses/private-runtime-worker.js +723 -0
  419. package/dist/senses/private-runtime.js +1157 -0
  420. package/dist/senses/proactive-content-guard.js +51 -0
  421. package/dist/senses/shared-turn.js +416 -0
  422. package/dist/senses/surface-tool.js +109 -0
  423. package/dist/senses/teams-entry.js +60 -8
  424. package/dist/senses/teams.js +397 -112
  425. package/dist/senses/trust-gate.js +104 -9
  426. package/dist/senses/voice/audio-playback.js +237 -0
  427. package/dist/senses/voice/audio-routing.js +119 -0
  428. package/dist/senses/voice/elevenlabs.js +202 -0
  429. package/dist/senses/voice/floor-control.js +431 -0
  430. package/dist/senses/voice/floor-controller.js +115 -0
  431. package/dist/senses/voice/golden-path.js +116 -0
  432. package/dist/senses/voice/index.js +29 -0
  433. package/dist/senses/voice/meeting.js +113 -0
  434. package/dist/senses/voice/outbound.js +190 -0
  435. package/dist/senses/voice/phone.js +33 -0
  436. package/dist/senses/voice/playback.js +139 -0
  437. package/dist/senses/voice/realtime-eval.js +496 -0
  438. package/dist/senses/voice/realtime-trace.js +531 -0
  439. package/dist/senses/voice/transcript.js +70 -0
  440. package/dist/senses/voice/turn.js +191 -0
  441. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  442. package/dist/senses/voice/twilio-phone.js +5081 -0
  443. package/dist/senses/voice/types.js +2 -0
  444. package/dist/senses/voice/whisper.js +161 -0
  445. package/dist/senses/voice-entry.js +81 -0
  446. package/dist/senses/voice-realtime-eval-command.js +99 -0
  447. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  448. package/dist/senses/voice-twilio-entry.js +87 -0
  449. package/dist/trips/core.js +138 -0
  450. package/dist/trips/store.js +265 -0
  451. package/dist/util/frontmatter.js +69 -0
  452. package/package.json +57 -12
  453. package/skills/agent-commerce.md +113 -0
  454. package/skills/browser-navigation.md +117 -0
  455. package/skills/commerce-setup-guide.md +116 -0
  456. package/skills/commerce-setup.md +84 -0
  457. package/skills/configure-dev-tools.md +99 -0
  458. package/skills/travel-planning.md +138 -0
  459. package/dist/heart/daemon/auth-flow.js +0 -409
  460. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  461. package/dist/heart/daemon/update-hooks.js +0 -138
  462. package/dist/heart/safe-workspace.js +0 -228
  463. package/dist/heart/session-recall.js +0 -116
  464. package/dist/mind/friends/channel.js +0 -84
  465. package/dist/mind/friends/group-context.js +0 -144
  466. package/dist/mind/friends/resolver.js +0 -84
  467. package/dist/mind/friends/store-file.js +0 -190
  468. package/dist/mind/friends/store.js +0 -4
  469. package/dist/mind/friends/tokens.js +0 -26
  470. package/dist/mind/friends/trust-explanation.js +0 -74
  471. package/dist/mind/friends/types.js +0 -29
  472. package/dist/repertoire/tasks/board.js +0 -134
  473. package/dist/repertoire/tasks/index.js +0 -224
  474. package/dist/repertoire/tasks/lifecycle.js +0 -80
  475. package/dist/repertoire/tasks/middleware.js +0 -65
  476. package/dist/repertoire/tasks/parser.js +0 -173
  477. package/dist/repertoire/tasks/scanner.js +0 -132
  478. package/dist/repertoire/tasks/transitions.js +0 -144
  479. package/dist/senses/bluebubbles-entry.js +0 -13
  480. package/dist/senses/bluebubbles.js +0 -1180
  481. package/dist/senses/debug-activity.js +0 -148
  482. package/dist/senses/inner-dialog-worker.js +0 -90
  483. package/dist/senses/inner-dialog.js +0 -480
  484. package/subagents/README.md +0 -7
  485. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  486. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  487. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  488. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  489. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  490. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  491. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  492. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  493. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  494. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  495. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  496. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  497. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  498. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  499. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  500. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  501. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -33,9 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
36
+ exports.InputQueue = exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.StreamingWordWrapper = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
37
37
  exports.formatPendingPrefix = formatPendingPrefix;
38
38
  exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
39
+ exports.formatTimeAgo = formatTimeAgo;
40
+ exports.writeCliAsyncAssistantMessage = writeCliAsyncAssistantMessage;
41
+ exports.pauseActiveSpinner = pauseActiveSpinner;
42
+ exports.resumeActiveSpinner = resumeActiveSpinner;
43
+ exports.setActiveSpinner = setActiveSpinner;
39
44
  exports.handleSigint = handleSigint;
40
45
  exports.addHistory = addHistory;
41
46
  exports.renderMarkdown = renderMarkdown;
@@ -51,38 +56,41 @@ const prompt_1 = require("../mind/prompt");
51
56
  const phrases_1 = require("../mind/phrases");
52
57
  const format_1 = require("../mind/format");
53
58
  const config_1 = require("../heart/config");
59
+ const session_events_1 = require("../heart/session-events");
54
60
  const context_1 = require("../mind/context");
55
61
  const pending_1 = require("../mind/pending");
56
62
  const commands_1 = require("./commands");
57
63
  const identity_1 = require("../heart/identity");
58
64
  const mcp_manager_1 = require("../repertoire/mcp-manager");
59
65
  const nerves_1 = require("../nerves");
60
- const store_file_1 = require("../mind/friends/store-file");
61
- const resolver_1 = require("../mind/friends/resolver");
62
- const tokens_1 = require("../mind/friends/tokens");
66
+ const friends_1 = require("@ouro.bot/friends");
63
67
  const cli_logging_1 = require("../nerves/cli-logging");
64
68
  const runtime_1 = require("../nerves/runtime");
65
69
  const trust_gate_1 = require("./trust-gate");
66
70
  const pipeline_1 = require("./pipeline");
67
- const channel_1 = require("../mind/friends/channel");
68
71
  const session_lock_1 = require("./session-lock");
69
- const update_hooks_1 = require("../heart/daemon/update-hooks");
72
+ const update_hooks_1 = require("../heart/versioning/update-hooks");
70
73
  const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
74
+ const agent_config_v2_1 = require("../heart/daemon/hooks/agent-config-v2");
71
75
  const bundle_manifest_1 = require("../mind/bundle-manifest");
72
76
  const cli_layout_1 = require("./cli-layout");
77
+ const image_paste_1 = require("./cli/image-paste");
78
+ const spinner_imperative_1 = require("./cli/spinner-imperative");
79
+ const tool_display_1 = require("./cli/tool-display");
73
80
  var cli_layout_2 = require("./cli-layout");
74
81
  Object.defineProperty(exports, "formatEchoedInputSummary", { enumerable: true, get: function () { return cli_layout_2.formatEchoedInputSummary; } });
75
82
  Object.defineProperty(exports, "wrapCliText", { enumerable: true, get: function () { return cli_layout_2.wrapCliText; } });
83
+ Object.defineProperty(exports, "StreamingWordWrapper", { enumerable: true, get: function () { return cli_layout_2.StreamingWordWrapper; } });
76
84
  /**
77
85
  * Format pending messages as content-prefix strings for injection into
78
86
  * the next user message. Self-messages (from === agentName) become
79
- * `[inner thought: {content}]`, inter-agent messages become
87
+ * `[private-runtime note: {content}]`, inter-agent messages become
80
88
  * `[message from {name}: {content}]`.
81
89
  */
82
90
  function formatPendingPrefix(messages, agentName) {
83
91
  return messages
84
92
  .map((msg) => msg.from === agentName
85
- ? `[inner thought: ${msg.content}]`
93
+ ? `[private-runtime note: ${msg.content}]`
86
94
  : `[message from ${msg.from}: ${msg.content}]`)
87
95
  .join("\n");
88
96
  }
@@ -90,68 +98,46 @@ function getCliContinuityIngressTexts(input) {
90
98
  const trimmed = input.trim();
91
99
  return trimmed ? [trimmed] : [];
92
100
  }
93
- // spinner that only touches stderr, cleans up after itself
94
- // exported for direct testability (stop-without-start branch)
95
- class Spinner {
96
- frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
97
- i = 0;
98
- iv = null;
99
- piv = null;
100
- msg = "";
101
- phrases = null;
102
- lastPhrase = "";
103
- stopped = false;
104
- constructor(m = "working", phrases) {
105
- this.msg = m;
106
- if (phrases && phrases.length > 0)
107
- this.phrases = phrases;
108
- }
109
- start() {
110
- this.stopped = false;
111
- process.stderr.write("\r\x1b[K");
112
- this.spin();
113
- this.iv = setInterval(() => this.spin(), 80);
114
- if (this.phrases) {
115
- this.piv = setInterval(() => this.rotatePhrase(), 1500);
116
- }
117
- }
118
- spin() {
119
- // Guard: clearInterval can't prevent already-dequeued callbacks
120
- /* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
121
- if (this.stopped)
122
- return;
123
- process.stderr.write(`\r\x1b[K${this.frames[this.i]} ${this.msg}... `);
124
- this.i = (this.i + 1) % this.frames.length;
125
- }
126
- rotatePhrase() {
127
- /* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
128
- if (this.stopped)
129
- return;
130
- const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
131
- this.lastPhrase = next;
132
- this.msg = next;
133
- }
134
- stop(ok) {
135
- this.stopped = true;
136
- if (this.iv) {
137
- clearInterval(this.iv);
138
- this.iv = null;
139
- }
140
- if (this.piv) {
141
- clearInterval(this.piv);
142
- this.piv = null;
143
- }
144
- process.stderr.write("\r\x1b[K");
145
- /* v8 ignore next -- ok parameter currently unused by callers @preserve */
146
- if (ok)
147
- process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
148
- }
149
- fail(msg) {
150
- this.stop();
151
- process.stderr.write(`\x1b[31m\u2717\x1b[0m ${msg}\n`);
101
+ /* v8 ignore start -- cosmetic time formatting @preserve */
102
+ function formatTimeAgo(date) {
103
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
104
+ if (seconds < 60)
105
+ return "just now";
106
+ const minutes = Math.floor(seconds / 60);
107
+ if (minutes < 60)
108
+ return `${minutes}m ago`;
109
+ const hours = Math.floor(minutes / 60);
110
+ if (hours < 24)
111
+ return `${hours}h ago`;
112
+ const days = Math.floor(hours / 24);
113
+ return `${days}d ago`;
114
+ }
115
+ const CLI_PROMPT = "\x1b[36m) \x1b[0m";
116
+ function writeCliAsyncAssistantMessage(rl, message, stdout = process.stdout) {
117
+ const rlInt = rl;
118
+ const currentLine = rlInt.line ?? "";
119
+ const currentCursor = rlInt.cursor ?? currentLine.length;
120
+ stdout.write("\r\x1b[K");
121
+ stdout.write(`${renderMarkdown(message)}\n`);
122
+ stdout.write(CLI_PROMPT);
123
+ if (!currentLine)
124
+ return;
125
+ stdout.write(currentLine);
126
+ if (currentCursor < currentLine.length) {
127
+ readline.cursorTo(process.stdout, 2 + currentCursor);
152
128
  }
153
129
  }
154
- exports.Spinner = Spinner;
130
+ // Module-level active spinner for log coordination.
131
+ // The terminal log sink calls these to avoid interleaving with spinner output.
132
+ let _activeSpinner = null;
133
+ /* v8 ignore start -- spinner coordination: exercised at runtime, not unit-testable without real terminal @preserve */
134
+ function pauseActiveSpinner() { _activeSpinner?.pause(); }
135
+ function resumeActiveSpinner() { _activeSpinner?.resume(); }
136
+ /* v8 ignore stop */
137
+ function setActiveSpinner(s) { _activeSpinner = s; }
138
+ // Re-export ImperativeSpinner as Spinner for backward compatibility
139
+ var spinner_imperative_2 = require("./cli/spinner-imperative");
140
+ Object.defineProperty(exports, "Spinner", { enumerable: true, get: function () { return spinner_imperative_2.ImperativeSpinner; } });
155
141
  // Input controller: pauses readline during model/tool execution.
156
142
  // Does NOT touch raw mode — readline with terminal:true manages raw mode
157
143
  // internally. Touching it causes ^C to be echoed by the terminal driver.
@@ -319,60 +305,65 @@ function createCliCallbacks() {
319
305
  meta: {},
320
306
  });
321
307
  let currentSpinner = null;
322
- let hadReasoning = false;
308
+ function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
323
309
  let hadToolRun = false;
324
310
  let textDirty = false; // true when text/reasoning was written without a trailing newline
325
311
  const streamer = new MarkdownStreamer();
312
+ const wrapper = new cli_layout_1.StreamingWordWrapper();
326
313
  return {
327
314
  onModelStart: () => {
328
315
  currentSpinner?.stop();
329
- currentSpinner = null;
330
- hadReasoning = false;
316
+ setSpinner(null);
331
317
  textDirty = false;
332
318
  streamer.reset();
319
+ wrapper.reset();
333
320
  const phrases = (0, phrases_1.getPhrases)();
334
321
  const pool = hadToolRun ? phrases.followup : phrases.thinking;
335
322
  const first = (0, phrases_1.pickPhrase)(pool);
336
- currentSpinner = new Spinner(first, pool);
323
+ setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
337
324
  currentSpinner.start();
338
325
  },
339
326
  onModelStreamStart: () => {
340
327
  // No-op: content callbacks (onTextChunk, onReasoningChunk) handle
341
328
  // stopping the spinner. onModelStreamStart fires too early and
342
- // doesn't fire at all for final_answer tool streaming.
329
+ // doesn't fire at all for settle tool streaming.
343
330
  },
344
331
  onClearText: () => {
345
332
  streamer.reset();
333
+ wrapper.reset();
346
334
  },
347
335
  onTextChunk: (text) => {
348
- // Stop spinner if still running — final_answer streaming and Anthropic
336
+ // Stop spinner if still running — settle streaming and Anthropic
349
337
  // tool-only responses bypass onModelStreamStart, so the spinner would
350
338
  // otherwise keep running (and its \r writes overwrite response text).
351
339
  if (currentSpinner) {
352
340
  currentSpinner.stop();
353
- currentSpinner = null;
354
- }
355
- if (hadReasoning) {
356
- // Single newline to separate reasoning from reply — reasoning
357
- // output often ends with its own trailing newline(s)
358
- process.stdout.write("\n");
359
- hadReasoning = false;
341
+ setSpinner(null);
360
342
  }
361
343
  const rendered = streamer.push(text);
362
- if (rendered)
363
- process.stdout.write(rendered);
364
- textDirty = text.length > 0 && !text.endsWith("\n");
365
- },
366
- onReasoningChunk: (text) => {
367
- if (currentSpinner) {
368
- currentSpinner.stop();
369
- currentSpinner = null;
344
+ /* v8 ignore start -- wrapper integration: tested via cli.test.ts onTextChunk tests @preserve */
345
+ if (rendered) {
346
+ const wrapped = wrapper.push(rendered);
347
+ if (wrapped)
348
+ process.stdout.write(wrapped);
370
349
  }
371
- hadReasoning = true;
372
- process.stdout.write(`\x1b[2m${text}\x1b[0m`);
350
+ /* v8 ignore stop */
373
351
  textDirty = text.length > 0 && !text.endsWith("\n");
374
352
  },
353
+ flushNow: () => {
354
+ // CLI flushes immediately on each onTextChunk; nothing buffered to push.
355
+ },
356
+ onReasoningChunk: (_text) => {
357
+ // Keep reasoning private in the CLI surface. The spinner continues to
358
+ // represent active thinking until actual tool or answer output arrives.
359
+ },
375
360
  onToolStart: (_name, _args) => {
361
+ // speak is flow-control: its visible output is the message itself (delivered
362
+ // via onTextChunk/flushNow). Do NOT start a tool spinner — that would write
363
+ // a "running speak..." phrase to stderr right before the actual message
364
+ // arrives, which is the visual churn the user explicitly does not want.
365
+ if (_name === "speak")
366
+ return;
376
367
  // Stop the model-start spinner: when the model returns only tool calls
377
368
  // (no content/reasoning), onModelStreamStart never fires, so the old
378
369
  // spinner's intervals would leak.
@@ -385,31 +376,29 @@ function createCliCallbacks() {
385
376
  }
386
377
  const toolPhrases = (0, phrases_1.getPhrases)().tool;
387
378
  const first = (0, phrases_1.pickPhrase)(toolPhrases);
388
- currentSpinner = new Spinner(first, toolPhrases);
379
+ setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
389
380
  currentSpinner.start();
390
381
  hadToolRun = true;
391
382
  },
392
383
  onToolEnd: (name, argSummary, success) => {
393
384
  currentSpinner?.stop();
394
- currentSpinner = null;
395
- const msg = (0, format_1.formatToolResult)(name, argSummary, success);
396
- const color = success ? "\x1b[32m" : "\x1b[31m";
397
- process.stderr.write(`${color}${msg}\x1b[0m\n`);
385
+ setSpinner(null);
386
+ (0, tool_display_1.writeToolEnd)(name, argSummary, success);
398
387
  },
399
388
  onError: (error, severity) => {
400
389
  if (severity === "transient") {
401
390
  currentSpinner?.fail(error.message);
402
- currentSpinner = null;
391
+ setSpinner(null);
403
392
  }
404
393
  else {
405
394
  currentSpinner?.stop();
406
- currentSpinner = null;
395
+ setSpinner(null);
407
396
  process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
408
397
  }
409
398
  },
410
399
  onKick: () => {
411
400
  currentSpinner?.stop();
412
- currentSpinner = null;
401
+ setSpinner(null);
413
402
  if (textDirty) {
414
403
  process.stdout.write("\n");
415
404
  textDirty = false;
@@ -418,10 +407,18 @@ function createCliCallbacks() {
418
407
  },
419
408
  flushMarkdown: () => {
420
409
  currentSpinner?.stop();
421
- currentSpinner = null;
410
+ setSpinner(null);
411
+ /* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
422
412
  const remaining = streamer.flush();
423
- if (remaining)
424
- process.stdout.write(remaining);
413
+ if (remaining) {
414
+ const wrapped = wrapper.push(remaining);
415
+ if (wrapped)
416
+ process.stdout.write(wrapped);
417
+ }
418
+ const tail = wrapper.flush();
419
+ if (tail)
420
+ process.stdout.write(tail);
421
+ /* v8 ignore stop */
425
422
  },
426
423
  };
427
424
  }
@@ -462,29 +459,243 @@ async function* createDebouncedLines(source, debounceMs) {
462
459
  yield lines.join("\n");
463
460
  }
464
461
  }
462
+ /**
463
+ * Async queue that bridges push-based Ink input to pull-based async iteration.
464
+ * Input from Ink's onSubmit callback is pushed; the business logic loop awaits via for-await.
465
+ */
466
+ class InputQueue {
467
+ queue = [];
468
+ resolve = null;
469
+ done = false;
470
+ push(input) {
471
+ if (this.done)
472
+ return;
473
+ if (this.resolve) {
474
+ const r = this.resolve;
475
+ this.resolve = null;
476
+ r({ value: input, done: false });
477
+ }
478
+ else {
479
+ this.queue.push(input);
480
+ }
481
+ }
482
+ close() {
483
+ this.done = true;
484
+ if (this.resolve) {
485
+ const r = this.resolve;
486
+ this.resolve = null;
487
+ r({ value: undefined, done: true });
488
+ }
489
+ }
490
+ /** Drain all buffered items, leaving any pending async awaiter untouched. */
491
+ drainAll() {
492
+ const items = [...this.queue];
493
+ this.queue = [];
494
+ return items;
495
+ }
496
+ [Symbol.asyncIterator]() {
497
+ return {
498
+ next: () => {
499
+ if (this.queue.length > 0) {
500
+ return Promise.resolve({ value: this.queue.shift(), done: false });
501
+ }
502
+ if (this.done) {
503
+ return Promise.resolve({ value: undefined, done: true });
504
+ }
505
+ return new Promise((resolve) => {
506
+ this.resolve = resolve;
507
+ });
508
+ },
509
+ };
510
+ }
511
+ }
512
+ exports.InputQueue = InputQueue;
465
513
  async function runCliSession(options) {
466
514
  /* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
467
- const pasteDebounceMs = options.pasteDebounceMs ?? 50;
468
515
  const registry = (0, commands_1.createCommandRegistry)();
469
516
  if (!options.disableCommands) {
470
517
  (0, commands_1.registerDefaultCommands)(registry);
471
518
  }
472
519
  const messages = options.messages
473
- ?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
474
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
475
- const ctrl = new InputController(rl);
520
+ ?? [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli")) }];
521
+ // ─── Rendering: TUI (Ink + Static) for TTY, imperative for tests/pipes ───
522
+ const useTui = !options._testInputSource && process.stdin.isTTY === true;
476
523
  let currentAbort = null;
477
- const history = [];
478
524
  let closed = false;
479
- rl.on("close", () => { closed = true; });
480
- if (options.banner !== false) {
481
- const bannerText = typeof options.banner === "string"
482
- ? options.banner
483
- : `${options.agentName} (type /commands for help)`;
484
- // eslint-disable-next-line no-console -- terminal UX: startup banner
485
- console.log(`\n${bannerText}\n`);
525
+ // eslint-disable-next-line prefer-const -- set by onImageMap callback during input
526
+ let pendingImages = null;
527
+ let cliCallbacks;
528
+ let tuiStore = null;
529
+ let inkRef = null;
530
+ const inputQueue = useTui ? new InputQueue() : null;
531
+ let rl = null;
532
+ if (useTui) {
533
+ try {
534
+ const [ink, React, tuiMod, storeMod] = await Promise.all([
535
+ Promise.resolve().then(() => __importStar(require("ink"))),
536
+ Promise.resolve().then(() => __importStar(require("react"))),
537
+ Promise.resolve().then(() => __importStar(require("./cli/ouro-tui"))),
538
+ Promise.resolve().then(() => __importStar(require("./cli/tui-store"))),
539
+ ]);
540
+ const { OuroTui } = tuiMod;
541
+ const { TuiStore, createTuiCallbacks } = storeMod;
542
+ tuiStore = new TuiStore();
543
+ cliCallbacks = createTuiCallbacks(tuiStore);
544
+ // Seed input history from previous session (for up/down arrows) — NOT display
545
+ const prevUserMsgs = messages
546
+ .filter((msg) => msg.role === "user" && typeof msg.content === "string")
547
+ .map(msg => msg.content);
548
+ tuiStore.seedHistory(prevUserMsgs);
549
+ // Show session resume context: last 2 exchanges as normal messages
550
+ if (messages.length > 1) {
551
+ const userAssistantMsgs = messages.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && m.content.trim().length > 0);
552
+ // Extract last 2 exchanges (up to 4 messages)
553
+ const lastExchanges = [];
554
+ for (let i = userAssistantMsgs.length - 1; i >= 0 && lastExchanges.length < 4; i--) {
555
+ lastExchanges.unshift({ role: userAssistantMsgs[i].role, content: userAssistantMsgs[i].content });
556
+ }
557
+ tuiStore.addResumeMessages(lastExchanges);
558
+ }
559
+ // Compute resumeInfo for header banner
560
+ const resumeInfo = messages.length > 1
561
+ ? {
562
+ messageCount: messages.filter(m => m.role === "user" || m.role === "assistant").length,
563
+ timeAgo: options.lastActivityAt ? formatTimeAgo(new Date(options.lastActivityAt)) : "unknown",
564
+ }
565
+ : undefined;
566
+ // Ctrl-C state machine (Claude Code behavior):
567
+ // During generation: abort current request
568
+ // Idle with text: clear input
569
+ // Idle empty (first): warn
570
+ // Idle empty (second): exit
571
+ let ctrlCWarned = false;
572
+ let ctrlCTimer = null;
573
+ const handleCtrlC = (hasInput) => {
574
+ if (currentAbort) {
575
+ currentAbort.abort();
576
+ ctrlCWarned = false;
577
+ return "abort";
578
+ }
579
+ if (hasInput) {
580
+ ctrlCWarned = false;
581
+ return "clear";
582
+ }
583
+ if (ctrlCWarned) {
584
+ ctrlCWarned = false;
585
+ if (ctrlCTimer) {
586
+ clearTimeout(ctrlCTimer);
587
+ ctrlCTimer = null;
588
+ }
589
+ closed = true;
590
+ inputQueue.close();
591
+ return "exit";
592
+ }
593
+ ctrlCWarned = true;
594
+ // Reset after 2 seconds — must press twice within window
595
+ ctrlCTimer = setTimeout(() => { ctrlCWarned = false; }, 2000);
596
+ return "warn";
597
+ };
598
+ // TUI root: subscribes to store, passes props to OuroTui
599
+ // Elapsed timer is local React state (no store.notify overhead)
600
+ const storeRef = tuiStore;
601
+ function TuiRoot() {
602
+ const [, forceUpdate] = React.useState(0);
603
+ const [elapsed, setElapsed] = React.useState(0);
604
+ React.useEffect(() => storeRef.subscribe(() => {
605
+ forceUpdate((n) => n + 1);
606
+ // Reset ctrlC warning on any state change (new turn, etc.)
607
+ }), []);
608
+ React.useEffect(() => {
609
+ const iv = setInterval(() => setElapsed(storeRef.getElapsed()), 1000);
610
+ return () => clearInterval(iv);
611
+ }, []);
612
+ return React.createElement(OuroTui, {
613
+ agentName: options.agentName,
614
+ model: (0, identity_1.loadAgentConfig)().humanFacing?.model ?? "",
615
+ completedMessages: storeRef.completedMessages,
616
+ inputHistory: storeRef.inputHistory,
617
+ queuedInputs: storeRef.queuedInputs,
618
+ live: storeRef.live,
619
+ elapsedSeconds: elapsed,
620
+ contextPercent: 0,
621
+ onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); storeRef.enqueueInput(text); },
622
+ onCtrlC: handleCtrlC,
623
+ onPopQueue: () => { const items = storeRef.popAllQueuedForEditing(); inputQueue.drainAll(); return items; },
624
+ headerShown: storeRef.headerShown,
625
+ cwd: process.cwd().replace(process.env.HOME ?? "", "~"),
626
+ resumeInfo,
627
+ onImageMap: (images) => { pendingImages = images; },
628
+ onHistoryAdd: (text) => { storeRef.addToHistoryOnly(text); },
629
+ });
630
+ }
631
+ inkRef = ink.render(React.createElement(TuiRoot), { exitOnCtrlC: false, patchConsole: false });
632
+ }
633
+ catch (err) {
634
+ // Ink failed to load (CJS compat, missing deps, etc.) — fall through to imperative
635
+ (0, runtime_1.emitNervesEvent)({
636
+ component: "senses",
637
+ event: "senses.tui_fallback",
638
+ message: `TUI failed to load, falling back to imperative: ${err instanceof Error ? err.message : String(err)}`,
639
+ meta: {},
640
+ });
641
+ }
642
+ }
643
+ // Fallback to imperative callbacks if TUI didn't initialize
644
+ if (!tuiStore) {
645
+ cliCallbacks = createCliCallbacks();
646
+ if (options.banner !== false) {
647
+ const bannerText = typeof options.banner === "string"
648
+ ? options.banner
649
+ : `${options.agentName} (type /commands for help)`;
650
+ // eslint-disable-next-line no-console -- terminal UX: startup banner
651
+ console.log(`\n${bannerText}\n`);
652
+ }
486
653
  }
487
- const cliCallbacks = createCliCallbacks();
654
+ // Display helpers: route to TUI store or imperative stderr/stdout
655
+ const display = {
656
+ error: (msg) => {
657
+ if (tuiStore)
658
+ tuiStore.setError(msg);
659
+ else
660
+ process.stderr.write(`\x1b[31m${msg}\x1b[0m\n`);
661
+ },
662
+ warn: (msg) => {
663
+ if (tuiStore)
664
+ tuiStore.setError(msg); // TUI shows warnings as errors (amber color handled in component)
665
+ else
666
+ process.stderr.write(`\x1b[33m${msg}\x1b[0m\n`);
667
+ },
668
+ text: (msg) => {
669
+ if (tuiStore)
670
+ tuiStore.appendText(msg);
671
+ else
672
+ process.stdout.write(`${msg}\n`);
673
+ },
674
+ suppressInput: () => {
675
+ if (tuiStore)
676
+ tuiStore.suppressInput();
677
+ },
678
+ restoreInput: () => {
679
+ if (tuiStore)
680
+ tuiStore.restoreInput();
681
+ },
682
+ };
683
+ const effectiveToolContext = {
684
+ signin: options.toolContext?.signin ?? (async () => undefined),
685
+ ...options.toolContext,
686
+ codingFeedback: {
687
+ send: async (message) => {
688
+ const assistantMessage = {
689
+ role: "assistant",
690
+ content: message,
691
+ };
692
+ messages.push(assistantMessage);
693
+ await options.onAsyncAssistantMessage?.(messages, assistantMessage);
694
+ display.text(message);
695
+ await options.toolContext?.codingFeedback?.send(message);
696
+ },
697
+ },
698
+ };
488
699
  // exitOnToolCall machinery: wrap execTool to detect target tool
489
700
  let exitToolResult;
490
701
  let exitToolFired = false;
@@ -495,8 +706,6 @@ async function runCliSession(options) {
495
706
  if (name === options.exitOnToolCall) {
496
707
  exitToolResult = result;
497
708
  exitToolFired = true;
498
- // Abort immediately so the model doesn't generate more output
499
- // (e.g. reasoning about calling final_answer after complete_adoption)
500
709
  currentAbort?.abort();
501
710
  }
502
711
  return result;
@@ -504,28 +713,6 @@ async function runCliSession(options) {
504
713
  : resolvedExecTool;
505
714
  // Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
506
715
  const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
507
- // Ctrl-C at the input prompt: clear line or warn/exit
508
- rl.on("SIGINT", () => {
509
- const rlInt = rl;
510
- const currentLine = rlInt.line || "";
511
- const result = handleSigint(rl, currentLine);
512
- if (result === "clear") {
513
- rlInt.line = "";
514
- rlInt.cursor = 0;
515
- process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
516
- }
517
- else if (result === "warn") {
518
- rlInt.line = "";
519
- rlInt.cursor = 0;
520
- process.stdout.write("\r\x1b[K");
521
- process.stderr.write("press Ctrl-C again to exit\n");
522
- process.stdout.write("\x1b[36m> \x1b[0m");
523
- }
524
- else {
525
- rl.close();
526
- }
527
- });
528
- const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
529
716
  (0, runtime_1.emitNervesEvent)({
530
717
  component: "senses",
531
718
  event: "senses.cli_session_start",
@@ -538,7 +725,7 @@ async function runCliSession(options) {
538
725
  if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
539
726
  currentAbort = new AbortController();
540
727
  const traceId = (0, nerves_1.createTraceId)();
541
- ctrl.suppress(() => currentAbort.abort());
728
+ display.suppressInput();
542
729
  let result;
543
730
  try {
544
731
  result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
@@ -546,119 +733,145 @@ async function runCliSession(options) {
546
733
  traceId,
547
734
  tools: options.tools,
548
735
  execTool: wrappedExecTool,
549
- toolContext: options.toolContext,
736
+ toolContext: effectiveToolContext,
550
737
  });
551
738
  }
552
739
  catch (err) {
553
- // AbortError (Ctrl-C) -- silently continue to prompt
554
- // All other errors: show the user what happened
555
740
  if (!(err instanceof DOMException && err.name === "AbortError")) {
556
- process.stderr.write(`\x1b[31m${err instanceof Error ? err.message : String(err)}\x1b[0m\n`);
741
+ display.error(err instanceof Error ? err.message : String(err));
557
742
  }
558
743
  }
559
744
  cliCallbacks.flushMarkdown();
560
- ctrl.restore();
745
+ display.restoreInput();
561
746
  currentAbort = null;
562
747
  if (exitToolFired) {
563
748
  exitReason = "tool_exit";
564
- rl.close();
749
+ closed = true;
565
750
  }
566
751
  else {
567
752
  const lastMsg = messages[messages.length - 1];
568
753
  if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
569
- process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
754
+ display.warn("(empty response)");
570
755
  }
571
- process.stdout.write("\n\n");
572
756
  if (options.onTurnEnd) {
573
757
  await options.onTurnEnd(messages, result ?? { usage: undefined });
574
758
  }
575
759
  }
576
760
  }
577
- if (!exitToolFired) {
578
- process.stdout.write("\x1b[36m> \x1b[0m");
579
- }
580
761
  try {
581
- for await (const input of debouncedLines(rl)) {
762
+ // Input source: TUI queue for Ink, test source for tests, readline fallback
763
+ let inputSource;
764
+ let inputCtrl = null;
765
+ if (tuiStore && inputQueue) {
766
+ // TUI path: input comes from Ink's onSubmit → InputQueue
767
+ inputSource = inputQueue;
768
+ }
769
+ else if (options._testInputSource) {
770
+ inputSource = options._testInputSource;
771
+ }
772
+ else {
773
+ // Imperative fallback: readline
774
+ const isTTY = process.stdin.isTTY === true;
775
+ rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: isTTY });
776
+ inputCtrl = new InputController(rl);
777
+ inputSource = rl;
778
+ process.stdout.write(CLI_PROMPT);
779
+ }
780
+ for await (const input of inputSource) {
582
781
  if (closed)
583
782
  break;
584
- if (!input.trim()) {
585
- process.stdout.write("\x1b[36m> \x1b[0m");
783
+ if (!input.trim())
586
784
  continue;
587
- }
785
+ // Remove from TUI queue display as the agent picks up this message
786
+ tuiStore?.dequeueInput(input);
588
787
  // Optional input gate (e.g. trust gate in main)
589
788
  if (options.onInput) {
590
789
  const gate = options.onInput(input);
591
790
  if (!gate.allowed) {
592
791
  if (gate.reply) {
593
- process.stdout.write(`${gate.reply}\n`);
792
+ display.text(gate.reply);
594
793
  }
595
794
  if (closed)
596
795
  break;
597
- process.stdout.write("\x1b[36m> \x1b[0m");
598
796
  continue;
599
797
  }
600
798
  }
601
- // Check for slash commands
602
- const parsed = (0, commands_1.parseSlashCommand)(input);
603
- if (parsed) {
604
- const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
605
- if (dispatchResult.handled && dispatchResult.result) {
606
- if (dispatchResult.result.action === "exit") {
607
- break;
608
- }
609
- else if (dispatchResult.result.action === "new") {
610
- messages.length = 0;
611
- messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
612
- await options.onNewSession?.();
613
- // eslint-disable-next-line no-console -- terminal UX: session cleared
614
- console.log("session cleared");
615
- process.stdout.write("\x1b[36m> \x1b[0m");
616
- continue;
617
- }
618
- else if (dispatchResult.result.action === "response") {
619
- // eslint-disable-next-line no-console -- terminal UX: command dispatch result
620
- console.log(dispatchResult.result.message || "");
621
- process.stdout.write("\x1b[36m> \x1b[0m");
622
- continue;
799
+ // Check for slash commands (legacy path only — pipeline handles commands for runTurn path)
800
+ if (!options.runTurn) {
801
+ const parsed = (0, commands_1.parseSlashCommand)(input);
802
+ if (parsed) {
803
+ const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
804
+ if (dispatchResult.handled && dispatchResult.result) {
805
+ if (dispatchResult.result.action === "exit") {
806
+ break;
807
+ }
808
+ else if (dispatchResult.result.action === "response") {
809
+ display.text(dispatchResult.result.message || "");
810
+ continue;
811
+ }
623
812
  }
624
813
  }
625
814
  }
626
- // Re-style the echoed input lines without leaving wrapped paste remnants behind.
627
- const cols = process.stdout.columns || 80;
628
- process.stdout.write((0, cli_layout_1.formatEchoedInputSummary)(input, cols));
629
- addHistory(history, input);
815
+ // Track user message in TUI for display
816
+ if (tuiStore)
817
+ tuiStore.addUserMessage(input);
630
818
  currentAbort = new AbortController();
631
- ctrl.suppress(() => currentAbort.abort());
819
+ if (tuiStore)
820
+ tuiStore.suppressInput();
821
+ inputCtrl?.suppress(() => { currentAbort?.abort(); });
822
+ // Resolve pending image content before the turn executes
823
+ let contentParts = null;
824
+ const currentImages = pendingImages;
825
+ if (currentImages && currentImages.size > 0) {
826
+ contentParts = await (0, image_paste_1.resolveImageContent)(input, currentImages);
827
+ pendingImages = null;
828
+ }
632
829
  let result;
633
830
  try {
634
831
  if (options.runTurn) {
635
832
  // Pipeline-based turn: the runTurn callback handles user message assembly,
636
833
  // pending drain, trust gate, runAgent, postTurn, and token accumulation.
637
- result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal);
834
+ result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal, effectiveToolContext, contentParts ?? undefined);
835
+ // Handle pipeline-intercepted commands with loop-control side effects
836
+ if (result?.turnOutcome === "command") {
837
+ if (result.commandAction === "exit") {
838
+ break;
839
+ }
840
+ // For "response" commands: the pipeline already emitted the response via onTextChunk
841
+ cliCallbacks.flushMarkdown();
842
+ continue;
843
+ }
638
844
  }
639
845
  else {
640
- // Legacy path: inline runAgent (used by adoption specialist and tests)
846
+ // Legacy path: inline runAgent (used by serpent guide and tests)
641
847
  const prefix = options.getContentPrefix?.();
642
- messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
848
+ const userContent = contentParts
849
+ ? contentParts
850
+ : (prefix ? `${prefix}\n\n${input}` : input);
851
+ const userMsg = { role: "user", content: userContent };
852
+ (0, session_events_1.stampIngressTime)(userMsg);
853
+ messages.push(userMsg);
643
854
  const traceId = (0, nerves_1.createTraceId)();
644
855
  result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
645
856
  toolChoiceRequired: getEffectiveToolChoiceRequired(),
646
857
  traceId,
647
858
  tools: options.tools,
648
859
  execTool: wrappedExecTool,
649
- toolContext: options.toolContext,
860
+ toolContext: effectiveToolContext,
650
861
  });
651
862
  }
652
863
  }
653
864
  catch (err) {
654
- // AbortError (Ctrl-C) -- silently return to prompt
655
- // All other errors: show the user what happened
656
865
  if (!(err instanceof DOMException && err.name === "AbortError")) {
657
- process.stderr.write(`\x1b[31m${err instanceof Error ? err.message : String(err)}\x1b[0m\n`);
866
+ display.error(err instanceof Error ? err.message : String(err));
658
867
  }
659
868
  }
660
869
  cliCallbacks.flushMarkdown();
661
- ctrl.restore();
870
+ if (!tuiStore)
871
+ process.stdout.write("\n"); // ensure response ends with newline before prompt (imperative only)
872
+ if (tuiStore)
873
+ tuiStore.restoreInput();
874
+ inputCtrl?.restore();
662
875
  currentAbort = null;
663
876
  // Check if exit tool was fired during this turn
664
877
  if (exitToolFired) {
@@ -668,20 +881,35 @@ async function runCliSession(options) {
668
881
  // Safety net: never silently swallow an empty response
669
882
  const lastMsg = messages[messages.length - 1];
670
883
  if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
671
- process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
884
+ display.warn("(empty response)");
672
885
  }
673
- process.stdout.write("\n\n");
674
886
  // Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
675
887
  if (options.onTurnEnd) {
676
888
  await options.onTurnEnd(messages, result ?? { usage: undefined });
677
889
  }
678
890
  if (closed)
679
891
  break;
680
- process.stdout.write("\x1b[36m> \x1b[0m");
681
892
  }
682
893
  }
683
894
  finally {
684
- rl.close();
895
+ rl?.close();
896
+ if (inkRef) {
897
+ // Suppress React "state update on unmounted component" warnings during cleanup.
898
+ // Ink's useInput hook fires after unmount — this is harmless but noisy.
899
+ // This also covers the SerpentGuide exitOnToolCall path, which aborts the
900
+ // current request and breaks out of the loop into this finally block.
901
+ // eslint-disable-next-line no-console -- intentional console.warn/error override for cleanup
902
+ const origWarn = console.warn;
903
+ const origError = console.error; // eslint-disable-line no-console
904
+ // eslint-disable-next-line no-console -- suppress React unmount warnings
905
+ console.warn = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
906
+ return; origWarn.apply(console, args); };
907
+ // eslint-disable-next-line no-console -- suppress React unmount warnings
908
+ console.error = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
909
+ return; origError.apply(console, args); };
910
+ inkRef.unmount();
911
+ setTimeout(() => { console.warn = origWarn; console.error = origError; }, 100); // eslint-disable-line no-console
912
+ }
685
913
  if (options.banner !== false) {
686
914
  // eslint-disable-next-line no-console -- terminal UX: goodbye
687
915
  console.log("bye");
@@ -694,18 +922,33 @@ async function main(agentName, options) {
694
922
  if (agentName)
695
923
  (0, identity_1.setAgentName)(agentName);
696
924
  const pasteDebounceMs = options?.pasteDebounceMs ?? 50;
925
+ // Safety net: process-level SIGINT handler ensures Ctrl+C always exits,
926
+ // even when Ink's event loop is blocked by expensive renders.
927
+ /* v8 ignore start -- process signal handler @preserve */
928
+ let sigintCount = 0;
929
+ const sigintHandler = () => {
930
+ sigintCount++;
931
+ if (sigintCount >= 2)
932
+ process.exit(1);
933
+ };
934
+ if (!options?._testInputSource) {
935
+ process.on("SIGINT", sigintHandler);
936
+ }
937
+ /* v8 ignore stop */
938
+ // Register spinner hooks so log output clears the spinner before printing
939
+ (0, nerves_1.registerSpinnerHooks)(pauseActiveSpinner, resumeActiveSpinner);
697
940
  // Fallback: apply pending updates for daemon-less direct CLI usage
698
941
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
942
+ (0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
699
943
  await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
700
944
  // Fail fast if provider is misconfigured (triggers human-readable error + exit)
701
- (0, core_1.getProvider)();
945
+ (0, core_1.getProvider)("human");
702
946
  // Resolve context kernel (identity + channel) for CLI
703
947
  const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
704
- const friendStore = new store_file_1.FileFriendStore(friendsPath);
948
+ const friendStore = new friends_1.FileFriendStore(friendsPath);
705
949
  const username = os.userInfo().username;
706
- const hostname = os.hostname();
707
- const localExternalId = `${username}@${hostname}`;
708
- const resolver = new resolver_1.FriendResolver(friendStore, {
950
+ const localExternalId = username;
951
+ const resolver = new friends_1.FriendResolver(friendStore, {
709
952
  provider: "local",
710
953
  externalId: localExternalId,
711
954
  displayName: username,
@@ -735,32 +978,74 @@ async function main(agentName, options) {
735
978
  // Load existing session or start fresh
736
979
  const existing = (0, context_1.loadSession)(sessPath);
737
980
  let sessionState = existing?.state;
981
+ let sessionEvents = existing?.events ?? [];
738
982
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
739
983
  const sessionMessages = existing?.messages && existing.messages.length > 0
740
984
  ? existing.messages
741
- : [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", { mcpManager }, resolvedContext) }];
985
+ : [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli", {}, resolvedContext)) }];
986
+ // Repair any orphaned tool calls from a crash mid-turn
987
+ (0, core_1.repairOrphanedToolCalls)(sessionMessages);
742
988
  // Per-turn pipeline input: CLI capabilities and pending dir
743
- const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
989
+ const cliCapabilities = (0, friends_1.getChannelCapabilities)("cli");
744
990
  const currentAgentName = (0, identity_1.getAgentName)();
745
991
  const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
746
- const summarize = (0, core_1.createSummarize)();
992
+ const summarize = (0, core_1.createSummarize)("human");
993
+ const cliFailoverState = { pending: null };
747
994
  try {
748
995
  await runCliSession({
749
996
  agentName: currentAgentName,
750
997
  pasteDebounceMs,
751
998
  messages: sessionMessages,
752
- runTurn: async (messages, userInput, callbacks, signal) => {
999
+ lastActivityAt: sessionState?.lastFriendActivityAt,
1000
+ _testInputSource: options?._testInputSource,
1001
+ onAsyncAssistantMessage: async (messages, _assistantMessage) => {
1002
+ const prepared = (0, context_1.postTurnTrim)(messages);
1003
+ const events = (0, context_1.postTurnPersist)(sessPath, prepared, undefined, sessionState);
1004
+ /* v8 ignore next -- defensive: postTurnPersist always returns events in practice @preserve */
1005
+ sessionEvents = events.length > 0 ? events : sessionEvents;
1006
+ },
1007
+ runTurn: async (messages, userInput, callbacks, signal, toolContext, userContent) => {
753
1008
  // Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
754
1009
  // User message passed via input.messages so the pipeline can prepend pending messages to it.
1010
+ const failoverState = cliFailoverState;
1011
+ // Capture terminal errors instead of displaying immediately — the failover
1012
+ // message replaces the raw error if failover triggers successfully.
1013
+ let capturedTerminalError = null;
1014
+ /* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
1015
+ const failoverAwareCallbacks = {
1016
+ ...callbacks,
1017
+ // Save session after each tool result for crash recovery (deferred to avoid blocking)
1018
+ onToolResult: (turnMessages) => {
1019
+ const prepared = (0, context_1.postTurnTrim)(turnMessages);
1020
+ (0, context_1.deferPostTurnPersist)(sessPath, prepared, undefined, sessionState);
1021
+ },
1022
+ onError: (error, severity) => {
1023
+ if (severity === "terminal" && failoverState) {
1024
+ capturedTerminalError = error;
1025
+ callbacks.onError(new Error(""), "transient");
1026
+ return;
1027
+ }
1028
+ callbacks.onError(error, severity);
1029
+ },
1030
+ };
1031
+ /* v8 ignore stop */
755
1032
  const result = await (0, pipeline_1.handleInboundTurn)({
756
1033
  channel: "cli",
757
1034
  sessionKey: "session",
758
1035
  capabilities: cliCapabilities,
759
- messages: [{ role: "user", content: userInput }],
1036
+ messages: [{ role: "user", content: userContent ?? userInput }],
760
1037
  continuityIngressTexts: getCliContinuityIngressTexts(userInput),
761
- callbacks,
1038
+ callbacks: failoverAwareCallbacks,
762
1039
  friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
763
- sessionLoader: { loadOrCreate: () => Promise.resolve({ messages, sessionPath: sessPath, state: sessionState }) },
1040
+ sessionLoader: {
1041
+ loadOrCreate: () => Promise.resolve({
1042
+ messages,
1043
+ sessionPath: sessPath,
1044
+ state: sessionState,
1045
+ events: sessionEvents,
1046
+ structuredOutputs: existing?.structuredOutputs,
1047
+ }),
1048
+ },
764
1049
  pendingDir,
765
1050
  friendStore,
766
1051
  provider: "local",
@@ -778,31 +1063,53 @@ async function main(agentName, options) {
778
1063
  },
779
1064
  }),
780
1065
  postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
781
- (0, context_1.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
1066
+ // Trim synchronously (mutates turnMessages for next turn),
1067
+ // then defer envelope build + disk I/O to avoid blocking the TUI.
1068
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
782
1069
  sessionState = state;
1070
+ (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state).then((events) => {
1071
+ /* v8 ignore next -- defensive: deferPostTurnPersist always resolves events in practice @preserve */
1072
+ sessionEvents = events.length > 0 ? events : sessionEvents;
1073
+ });
783
1074
  },
784
- accumulateFriendTokens: tokens_1.accumulateFriendTokens,
1075
+ accumulateFriendTokens: friends_1.accumulateFriendTokens,
785
1076
  signal,
786
1077
  runAgentOptions: {
787
1078
  toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
788
1079
  traceId: (0, nerves_1.createTraceId)(),
789
1080
  mcpManager,
1081
+ toolContext,
790
1082
  },
1083
+ failoverState,
791
1084
  });
1085
+ /* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
1086
+ if (result.failoverMessage) {
1087
+ // Failover handled it — show the actionable message instead of the raw error
1088
+ process.stdout.write(`\x1b[33m${result.failoverMessage}\x1b[0m\n`);
1089
+ }
1090
+ else if (capturedTerminalError) {
1091
+ // Failover didn't trigger (no failoverState, or sequence failed) — show the raw error
1092
+ process.stderr.write(`\x1b[31m${(0, format_1.formatError)(capturedTerminalError)}\x1b[0m\n`);
1093
+ }
1094
+ /* v8 ignore stop */
792
1095
  // Handle gate rejection: display auto-reply if present
793
1096
  if (!result.gateResult.allowed) {
794
1097
  if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
795
1098
  process.stdout.write(`${result.gateResult.autoReply}\n`);
796
1099
  }
797
1100
  }
798
- return { usage: result.usage };
799
- },
800
- onNewSession: () => {
801
- (0, context_1.deleteSession)(sessPath);
1101
+ return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
802
1102
  },
803
1103
  });
804
1104
  }
805
1105
  finally {
806
1106
  sessionLock?.release();
807
1107
  }
1108
+ // Force exit: lingering handles (Ink cleanup timers, MCP connections) keep the
1109
+ // event loop alive after the interactive session ends. This is safe because all
1110
+ // session persistence has already completed in the finally block above.
1111
+ /* v8 ignore next -- process.exit not callable in vitest @preserve */
1112
+ if (!options?._testInputSource)
1113
+ process.exit(0);
808
1114
  }
1115
+ // CI trigger