@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.800

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