@ouro.bot/cli 0.1.0-alpha.56 → 0.1.0-alpha.561

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 (396) hide show
  1. package/README.md +127 -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 +3604 -0
  13. package/dist/arc/attention-types.js +8 -0
  14. package/dist/arc/cares.js +140 -0
  15. package/dist/arc/episodes.js +117 -0
  16. package/dist/arc/intentions.js +133 -0
  17. package/dist/arc/json-store.js +117 -0
  18. package/dist/arc/obligations.js +237 -0
  19. package/dist/arc/packets.js +193 -0
  20. package/dist/arc/presence.js +185 -0
  21. package/dist/arc/task-lifecycle.js +65 -0
  22. package/dist/heart/active-work.js +837 -26
  23. package/dist/heart/agent-entry.js +58 -3
  24. package/dist/heart/attachments/image-normalize.js +194 -0
  25. package/dist/heart/attachments/materialize.js +97 -0
  26. package/dist/heart/attachments/originals.js +88 -0
  27. package/dist/heart/attachments/render.js +29 -0
  28. package/dist/heart/attachments/sources/adapter.js +2 -0
  29. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  30. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  31. package/dist/heart/attachments/sources/index.js +16 -0
  32. package/dist/heart/attachments/store.js +103 -0
  33. package/dist/heart/attachments/types.js +93 -0
  34. package/dist/heart/auth/auth-flow.js +479 -0
  35. package/dist/heart/background-operations.js +281 -0
  36. package/dist/heart/bundle-state.js +168 -0
  37. package/dist/heart/commitments.js +111 -0
  38. package/dist/heart/config-registry.js +322 -0
  39. package/dist/heart/config.js +114 -118
  40. package/dist/heart/core.js +913 -246
  41. package/dist/heart/cross-chat-delivery.js +3 -18
  42. package/dist/heart/daemon/agent-config-check.js +419 -0
  43. package/dist/heart/daemon/agent-discovery.js +102 -3
  44. package/dist/heart/daemon/agent-service.js +522 -0
  45. package/dist/heart/daemon/agentic-repair.js +547 -0
  46. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  47. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  48. package/dist/heart/daemon/cadence.js +70 -0
  49. package/dist/heart/daemon/cli-defaults.js +776 -0
  50. package/dist/heart/daemon/cli-exec.js +7457 -0
  51. package/dist/heart/daemon/cli-help.js +498 -0
  52. package/dist/heart/daemon/cli-parse.js +1592 -0
  53. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  54. package/dist/heart/daemon/cli-render.js +763 -0
  55. package/dist/heart/daemon/cli-types.js +8 -0
  56. package/dist/heart/daemon/connect-bay.js +323 -0
  57. package/dist/heart/daemon/daemon-cli.js +29 -1698
  58. package/dist/heart/daemon/daemon-entry.js +387 -2
  59. package/dist/heart/daemon/daemon-health.js +176 -0
  60. package/dist/heart/daemon/daemon-rollup.js +57 -0
  61. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  62. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  63. package/dist/heart/daemon/daemon.js +796 -71
  64. package/dist/heart/daemon/dns-workflow.js +394 -0
  65. package/dist/heart/daemon/doctor-types.js +8 -0
  66. package/dist/heart/daemon/doctor.js +826 -0
  67. package/dist/heart/daemon/health-monitor.js +122 -1
  68. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  69. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  70. package/dist/heart/daemon/http-health-probe.js +80 -0
  71. package/dist/heart/daemon/human-command-screens.js +234 -0
  72. package/dist/heart/daemon/human-readiness.js +114 -0
  73. package/dist/heart/daemon/inner-status.js +89 -0
  74. package/dist/heart/daemon/interactive-repair.js +394 -0
  75. package/dist/heart/daemon/launchd.js +37 -8
  76. package/dist/heart/daemon/log-tailer.js +82 -12
  77. package/dist/heart/daemon/logs-prune.js +110 -0
  78. package/dist/heart/daemon/mcp-canary.js +297 -0
  79. package/dist/heart/daemon/message-router.js +2 -2
  80. package/dist/heart/daemon/os-cron-deps.js +135 -0
  81. package/dist/heart/daemon/os-cron.js +14 -12
  82. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  83. package/dist/heart/daemon/ouro-entry.js +3 -1
  84. package/dist/heart/daemon/process-manager.js +375 -33
  85. package/dist/heart/daemon/provider-discovery.js +137 -0
  86. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  87. package/dist/heart/daemon/pulse.js +475 -0
  88. package/dist/heart/daemon/readiness-repair.js +365 -0
  89. package/dist/heart/daemon/run-hooks.js +2 -0
  90. package/dist/heart/daemon/runtime-logging.js +67 -16
  91. package/dist/heart/daemon/runtime-metadata.js +3 -31
  92. package/dist/heart/daemon/safe-mode.js +161 -0
  93. package/dist/heart/daemon/sense-manager.js +389 -38
  94. package/dist/heart/daemon/session-id-resolver.js +131 -0
  95. package/dist/heart/daemon/skill-management-installer.js +94 -0
  96. package/dist/heart/daemon/socket-client.js +158 -11
  97. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  98. package/dist/heart/daemon/startup-tui.js +330 -0
  99. package/dist/heart/daemon/task-scheduler.js +3 -25
  100. package/dist/heart/daemon/terminal-ui.js +499 -0
  101. package/dist/heart/daemon/thoughts.js +162 -17
  102. package/dist/heart/daemon/up-progress.js +366 -0
  103. package/dist/heart/daemon/vault-items.js +56 -0
  104. package/dist/heart/delegation.js +1 -1
  105. package/dist/heart/habits/habit-migration.js +189 -0
  106. package/dist/heart/habits/habit-parser.js +140 -0
  107. package/dist/heart/habits/habit-runtime-state.js +100 -0
  108. package/dist/heart/habits/habit-scheduler.js +372 -0
  109. package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -56
  110. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  111. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  112. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  113. package/dist/heart/identity.js +203 -57
  114. package/dist/heart/kept-notes.js +357 -0
  115. package/dist/heart/kicks.js +1 -1
  116. package/dist/heart/machine-identity.js +161 -0
  117. package/dist/heart/mail-import-discovery.js +353 -0
  118. package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
  119. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  120. package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
  121. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  122. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  123. package/dist/heart/mailbox/mailbox-http.js +99 -0
  124. package/dist/heart/mailbox/mailbox-read.js +31 -0
  125. package/dist/heart/mailbox/mailbox-types.js +27 -0
  126. package/dist/heart/mailbox/mailbox-view.js +195 -0
  127. package/dist/heart/mailbox/readers/agent-machine.js +382 -0
  128. package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
  129. package/dist/heart/mailbox/readers/mail.js +362 -0
  130. package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
  131. package/dist/heart/mailbox/readers/sessions.js +232 -0
  132. package/dist/heart/mailbox/readers/shared.js +111 -0
  133. package/dist/heart/mcp/mcp-server.js +683 -0
  134. package/dist/heart/migrate-config.js +100 -0
  135. package/dist/heart/model-capabilities.js +19 -0
  136. package/dist/heart/platform.js +81 -0
  137. package/dist/heart/provider-attempt.js +134 -0
  138. package/dist/heart/provider-binding-resolver.js +267 -0
  139. package/dist/heart/provider-credentials.js +425 -0
  140. package/dist/heart/provider-failover.js +301 -0
  141. package/dist/heart/provider-models.js +81 -0
  142. package/dist/heart/provider-ping.js +262 -0
  143. package/dist/heart/provider-readiness-cache.js +40 -0
  144. package/dist/heart/provider-visibility.js +188 -0
  145. package/dist/heart/providers/anthropic-token.js +131 -0
  146. package/dist/heart/providers/anthropic.js +139 -52
  147. package/dist/heart/providers/azure.js +97 -13
  148. package/dist/heart/providers/error-classification.js +127 -0
  149. package/dist/heart/providers/github-copilot.js +145 -0
  150. package/dist/heart/providers/minimax-vlm.js +189 -0
  151. package/dist/heart/providers/minimax.js +26 -8
  152. package/dist/heart/providers/openai-codex.js +55 -40
  153. package/dist/heart/runtime-capability-check.js +170 -0
  154. package/dist/heart/runtime-credentials.js +367 -0
  155. package/dist/heart/runtime-cwd.js +87 -0
  156. package/dist/heart/sense-truth.js +13 -4
  157. package/dist/heart/session-activity.js +43 -22
  158. package/dist/heart/session-events.js +1149 -0
  159. package/dist/heart/session-playback-cli-main.js +5 -0
  160. package/dist/heart/session-playback-cli.js +36 -0
  161. package/dist/heart/session-playback.js +231 -0
  162. package/dist/heart/session-stats-cli-main.js +5 -0
  163. package/dist/heart/session-stats.js +182 -0
  164. package/dist/heart/session-transcript.js +243 -0
  165. package/dist/heart/start-of-turn-packet.js +345 -0
  166. package/dist/heart/streaming.js +44 -27
  167. package/dist/heart/sync-classification.js +176 -0
  168. package/dist/heart/sync.js +449 -0
  169. package/dist/heart/target-resolution.js +9 -5
  170. package/dist/heart/tempo.js +93 -0
  171. package/dist/heart/temporal-view.js +41 -0
  172. package/dist/heart/timeouts.js +101 -0
  173. package/dist/heart/tool-activity-callbacks.js +59 -0
  174. package/dist/heart/tool-description.js +139 -0
  175. package/dist/heart/tool-friction.js +55 -0
  176. package/dist/heart/tool-loop.js +200 -0
  177. package/dist/heart/turn-context.js +389 -0
  178. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  179. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  180. package/dist/heart/versioning/ouro-path-installer.js +426 -0
  181. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  182. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  183. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  184. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  185. package/dist/mailbox-ui/assets/index-B-461hes.js +61 -0
  186. package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
  187. package/dist/mailbox-ui/index.html +15 -0
  188. package/dist/mailroom/attention.js +167 -0
  189. package/dist/mailroom/autonomy.js +209 -0
  190. package/dist/mailroom/blob-store.js +674 -0
  191. package/dist/mailroom/body-cache.js +61 -0
  192. package/dist/mailroom/core.js +720 -0
  193. package/dist/mailroom/entry.js +160 -0
  194. package/dist/mailroom/file-store.js +430 -0
  195. package/dist/mailroom/mbox-import.js +383 -0
  196. package/dist/mailroom/outbound.js +380 -0
  197. package/dist/mailroom/policy.js +263 -0
  198. package/dist/mailroom/reader.js +233 -0
  199. package/dist/mailroom/search-cache.js +256 -0
  200. package/dist/mailroom/search-relevance.js +319 -0
  201. package/dist/mailroom/smtp-ingress.js +176 -0
  202. package/dist/mailroom/source-state.js +176 -0
  203. package/dist/mailroom/thread.js +109 -0
  204. package/dist/mailroom/travel-extract.js +89 -0
  205. package/dist/mind/bundle-manifest.js +7 -1
  206. package/dist/mind/context.js +165 -101
  207. package/dist/mind/diary-integrity.js +60 -0
  208. package/dist/mind/{memory.js → diary.js} +62 -75
  209. package/dist/mind/embedding-provider.js +60 -0
  210. package/dist/mind/file-state.js +179 -0
  211. package/dist/mind/friends/channel.js +39 -0
  212. package/dist/mind/friends/resolver.js +54 -2
  213. package/dist/mind/friends/store-file.js +39 -3
  214. package/dist/mind/friends/types.js +2 -2
  215. package/dist/mind/journal-index.js +161 -0
  216. package/dist/mind/note-search.js +268 -0
  217. package/dist/mind/obligation-steering.js +221 -0
  218. package/dist/mind/pending.js +4 -0
  219. package/dist/mind/prompt-refresh.js +3 -2
  220. package/dist/mind/prompt.js +1011 -123
  221. package/dist/mind/provenance-trust.js +26 -0
  222. package/dist/mind/scrutiny.js +173 -0
  223. package/dist/nerves/cli-logging.js +7 -1
  224. package/dist/nerves/coverage/audit-rules.js +15 -6
  225. package/dist/nerves/coverage/audit.js +28 -2
  226. package/dist/nerves/coverage/cli.js +1 -1
  227. package/dist/nerves/coverage/contract.js +5 -5
  228. package/dist/nerves/coverage/file-completeness.js +129 -5
  229. package/dist/nerves/coverage/run-artifacts.js +1 -1
  230. package/dist/nerves/event-buffer.js +111 -0
  231. package/dist/nerves/index.js +224 -4
  232. package/dist/nerves/observation.js +20 -0
  233. package/dist/nerves/redact.js +79 -0
  234. package/dist/nerves/review/cli-main.js +5 -0
  235. package/dist/nerves/review/cli.js +156 -0
  236. package/dist/nerves/review/core.js +152 -0
  237. package/dist/nerves/runtime.js +5 -1
  238. package/dist/repertoire/ado-client.js +15 -56
  239. package/dist/repertoire/ado-semantic.js +11 -10
  240. package/dist/repertoire/api-client.js +97 -0
  241. package/dist/repertoire/bitwarden-store.js +963 -0
  242. package/dist/repertoire/bundle-templates.js +72 -0
  243. package/dist/repertoire/bw-installer.js +180 -0
  244. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  245. package/dist/repertoire/coding/context-pack.js +330 -0
  246. package/dist/repertoire/coding/feedback.js +197 -30
  247. package/dist/repertoire/coding/manager.js +158 -9
  248. package/dist/repertoire/coding/spawner.js +55 -9
  249. package/dist/repertoire/coding/tools.js +170 -7
  250. package/dist/repertoire/commerce-errors.js +109 -0
  251. package/dist/repertoire/commerce-self-test.js +156 -0
  252. package/dist/repertoire/credential-access.js +178 -0
  253. package/dist/repertoire/duffel-client.js +185 -0
  254. package/dist/repertoire/github-client.js +14 -55
  255. package/dist/repertoire/graph-client.js +11 -52
  256. package/dist/repertoire/guardrails.js +396 -0
  257. package/dist/repertoire/mcp-client.js +295 -0
  258. package/dist/repertoire/mcp-manager.js +362 -0
  259. package/dist/repertoire/mcp-tools.js +63 -0
  260. package/dist/repertoire/shell-sessions.js +133 -0
  261. package/dist/repertoire/skills.js +15 -24
  262. package/dist/repertoire/stripe-client.js +131 -0
  263. package/dist/repertoire/tasks/board.js +31 -5
  264. package/dist/repertoire/tasks/fix.js +182 -0
  265. package/dist/repertoire/tasks/index.js +16 -4
  266. package/dist/repertoire/tasks/lifecycle.js +2 -2
  267. package/dist/repertoire/tasks/parser.js +3 -2
  268. package/dist/repertoire/tasks/scanner.js +194 -37
  269. package/dist/repertoire/tasks/transitions.js +16 -78
  270. package/dist/repertoire/tool-results.js +29 -0
  271. package/dist/repertoire/tools-attachments.js +317 -0
  272. package/dist/repertoire/tools-base.js +47 -1075
  273. package/dist/repertoire/tools-bluebubbles.js +1 -0
  274. package/dist/repertoire/tools-bridge.js +142 -0
  275. package/dist/repertoire/tools-bundle.js +984 -0
  276. package/dist/repertoire/tools-config.js +185 -0
  277. package/dist/repertoire/tools-continuity.js +248 -0
  278. package/dist/repertoire/tools-credential.js +381 -0
  279. package/dist/repertoire/tools-files.js +342 -0
  280. package/dist/repertoire/tools-flight.js +224 -0
  281. package/dist/repertoire/tools-flow.js +119 -0
  282. package/dist/repertoire/tools-github.js +1 -7
  283. package/dist/repertoire/tools-mail.js +1857 -0
  284. package/dist/repertoire/tools-notes.js +421 -0
  285. package/dist/repertoire/tools-session.js +750 -0
  286. package/dist/repertoire/tools-shell.js +120 -0
  287. package/dist/repertoire/tools-stripe.js +180 -0
  288. package/dist/repertoire/tools-surface.js +243 -0
  289. package/dist/repertoire/tools-teams.js +9 -39
  290. package/dist/repertoire/tools-travel.js +125 -0
  291. package/dist/repertoire/tools-trip.js +604 -0
  292. package/dist/repertoire/tools-user-profile.js +144 -0
  293. package/dist/repertoire/tools-vault.js +40 -0
  294. package/dist/repertoire/tools.js +108 -100
  295. package/dist/repertoire/travel-api-client.js +360 -0
  296. package/dist/repertoire/user-profile.js +131 -0
  297. package/dist/repertoire/vault-setup.js +246 -0
  298. package/dist/repertoire/vault-unlock.js +594 -0
  299. package/dist/scripts/claude-code-hook.js +41 -0
  300. package/dist/scripts/claude-code-stop-hook.js +47 -0
  301. package/dist/senses/attention-queue.js +116 -0
  302. package/dist/senses/bluebubbles/active-turns.js +216 -0
  303. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  304. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  305. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  306. package/dist/senses/bluebubbles/entry.js +77 -0
  307. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  308. package/dist/senses/bluebubbles/index.js +2305 -0
  309. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  310. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  311. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  312. package/dist/senses/bluebubbles/processed-log.js +133 -0
  313. package/dist/senses/bluebubbles/replay.js +137 -0
  314. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  315. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  316. package/dist/senses/cli/bracketed-paste.js +82 -0
  317. package/dist/senses/cli/image-paste.js +287 -0
  318. package/dist/senses/cli/image-ref-navigation.js +75 -0
  319. package/dist/senses/cli/ink-app.js +156 -0
  320. package/dist/senses/cli/inline-diff.js +64 -0
  321. package/dist/senses/cli/input-keys.js +174 -0
  322. package/dist/senses/cli/kill-ring.js +86 -0
  323. package/dist/senses/cli/message-list.js +51 -0
  324. package/dist/senses/cli/ouro-tui.js +607 -0
  325. package/dist/senses/cli/spinner-imperative.js +135 -0
  326. package/dist/senses/cli/spinner.js +101 -0
  327. package/dist/senses/cli/status-line.js +60 -0
  328. package/dist/senses/cli/streaming-markdown.js +526 -0
  329. package/dist/senses/cli/tool-display.js +85 -0
  330. package/dist/senses/cli/tool-render.js +85 -0
  331. package/dist/senses/cli/tui-store.js +240 -0
  332. package/dist/senses/cli/virtual-list.js +35 -0
  333. package/dist/senses/cli-entry.js +60 -8
  334. package/dist/senses/cli-layout.js +187 -0
  335. package/dist/senses/cli.js +520 -209
  336. package/dist/senses/commands.js +66 -3
  337. package/dist/senses/habit-turn-message.js +108 -0
  338. package/dist/senses/inner-dialog-worker.js +175 -21
  339. package/dist/senses/inner-dialog.js +330 -27
  340. package/dist/senses/mail-entry.js +66 -0
  341. package/dist/senses/mail.js +379 -0
  342. package/dist/senses/pipeline.js +549 -181
  343. package/dist/senses/proactive-content-guard.js +51 -0
  344. package/dist/senses/shared-turn.js +251 -0
  345. package/dist/senses/surface-tool.js +68 -0
  346. package/dist/senses/teams-entry.js +60 -8
  347. package/dist/senses/teams.js +387 -98
  348. package/dist/senses/trust-gate.js +100 -5
  349. package/dist/senses/voice/audio-routing.js +119 -0
  350. package/dist/senses/voice/elevenlabs.js +178 -0
  351. package/dist/senses/voice/golden-path.js +116 -0
  352. package/dist/senses/voice/index.js +26 -0
  353. package/dist/senses/voice/meeting.js +113 -0
  354. package/dist/senses/voice/playback.js +139 -0
  355. package/dist/senses/voice/transcript.js +70 -0
  356. package/dist/senses/voice/turn.js +85 -0
  357. package/dist/senses/voice/types.js +2 -0
  358. package/dist/senses/voice/whisper.js +161 -0
  359. package/dist/senses/voice-entry.js +80 -0
  360. package/dist/trips/core.js +138 -0
  361. package/dist/trips/store.js +146 -0
  362. package/package.json +38 -7
  363. package/skills/agent-commerce.md +106 -0
  364. package/skills/browser-navigation.md +117 -0
  365. package/skills/commerce-setup-guide.md +116 -0
  366. package/skills/commerce-setup.md +84 -0
  367. package/skills/configure-dev-tools.md +101 -0
  368. package/skills/travel-planning.md +138 -0
  369. package/dist/heart/daemon/auth-flow.js +0 -351
  370. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  371. package/dist/heart/daemon/subagent-installer.js +0 -166
  372. package/dist/heart/session-recall.js +0 -116
  373. package/dist/mind/associative-recall.js +0 -209
  374. package/dist/senses/bluebubbles-entry.js +0 -13
  375. package/dist/senses/bluebubbles.js +0 -1177
  376. package/dist/senses/debug-activity.js +0 -148
  377. package/subagents/README.md +0 -86
  378. package/subagents/work-doer.md +0 -237
  379. package/subagents/work-merger.md +0 -618
  380. package/subagents/work-planner.md +0 -390
  381. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  382. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  383. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  384. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  385. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  386. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  387. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  388. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  389. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  390. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  391. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  392. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  393. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  394. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  395. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  396. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -34,10 +34,13 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DEFAULT_FLUSH_INTERVAL_MS = void 0;
37
+ exports.aiLabelEntities = aiLabelEntities;
37
38
  exports.stripMentions = stripMentions;
38
39
  exports.splitMessage = splitMessage;
40
+ exports.sanitizeFeedbackComment = sanitizeFeedbackComment;
41
+ exports.buildFeedbackSyntheticText = buildFeedbackSyntheticText;
42
+ exports.buildWelcomeCard = buildWelcomeCard;
39
43
  exports.createTeamsCallbacks = createTeamsCallbacks;
40
- exports.resolvePendingConfirmation = resolvePendingConfirmation;
41
44
  exports.withConversationLock = withConversationLock;
42
45
  exports.handleTeamsMessage = handleTeamsMessage;
43
46
  exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
@@ -58,19 +61,34 @@ const context_1 = require("../mind/context");
58
61
  const commands_1 = require("./commands");
59
62
  const nerves_1 = require("../nerves");
60
63
  const runtime_1 = require("../nerves/runtime");
64
+ const proactive_content_guard_1 = require("./proactive-content-guard");
61
65
  const store_file_1 = require("../mind/friends/store-file");
62
66
  const types_1 = require("../mind/friends/types");
63
67
  const resolver_1 = require("../mind/friends/resolver");
64
68
  const tokens_1 = require("../mind/friends/tokens");
65
69
  const turn_coordinator_1 = require("../heart/turn-coordinator");
66
70
  const identity_1 = require("../heart/identity");
71
+ const mcp_manager_1 = require("../repertoire/mcp-manager");
67
72
  const progress_story_1 = require("../heart/progress-story");
73
+ const tool_activity_callbacks_1 = require("../heart/tool-activity-callbacks");
74
+ const commands_2 = require("./commands");
68
75
  const http = __importStar(require("http"));
69
76
  const path = __importStar(require("path"));
70
77
  const trust_gate_1 = require("./trust-gate");
71
78
  const pipeline_1 = require("./pipeline");
79
+ const teamsFailoverStates = new Map();
72
80
  const pending_1 = require("../mind/pending");
73
81
  const continuity_1 = require("./continuity");
82
+ // AIGeneratedContent entity and feedbackLoopEnabled channelData for all outbound
83
+ // Teams messages. Required by Teams AI UX best practices.
84
+ function aiLabelEntities() {
85
+ return [{
86
+ type: "https://schema.org/Message",
87
+ "@type": "Message",
88
+ "@context": "https://schema.org",
89
+ additionalType: ["AIGeneratedContent"],
90
+ }];
91
+ }
74
92
  // Strip @mention markup from incoming messages.
75
93
  // Removes <at>...</at> tags and trims extra whitespace.
76
94
  // Fallback safety net -- the SDK's activity.mentions.stripText should handle
@@ -125,6 +143,46 @@ function splitMessage(text, maxLen) {
125
143
  }
126
144
  return chunks;
127
145
  }
146
+ // Sanitize user-provided feedback comments: truncate, strip control chars and newlines.
147
+ function sanitizeFeedbackComment(comment) {
148
+ const cleaned = comment.replace(/[\x00-\x1f\n\r]/g, "");
149
+ return cleaned.length > 200 ? cleaned.slice(0, 200) : cleaned;
150
+ }
151
+ // Build synthetic message text from a Teams feedback reaction.
152
+ function buildFeedbackSyntheticText(reaction, comment) {
153
+ const emoji = reaction === "like" ? "thumbs-up" : "thumbs-down";
154
+ if (comment) {
155
+ const sanitized = sanitizeFeedbackComment(comment);
156
+ return `[reacted with ${emoji} to your message: "${sanitized}"]`;
157
+ }
158
+ return `[reacted with ${emoji} to your message]`;
159
+ }
160
+ // Build a welcome Adaptive Card with prompt starters for new bot installs.
161
+ function buildWelcomeCard() {
162
+ const promptStarters = [
163
+ "What can you help me with?",
164
+ "Tell me about yourself",
165
+ "What's on my calendar today?",
166
+ "Summarize my recent emails",
167
+ ];
168
+ return {
169
+ type: "AdaptiveCard",
170
+ version: "1.5",
171
+ body: [
172
+ {
173
+ type: "TextBlock",
174
+ text: "Hey! I'm here and ready to help. Try one of these to get started, or just ask me anything.",
175
+ wrap: true,
176
+ size: "Medium",
177
+ },
178
+ ],
179
+ actions: promptStarters.map((prompt) => ({
180
+ type: "Action.Submit",
181
+ title: prompt,
182
+ data: { msteams: { type: "messageBack", text: prompt, displayText: prompt } },
183
+ })),
184
+ };
185
+ }
128
186
  // Create Teams-specific callbacks for the agent loop.
129
187
  // The SDK handles cumulative text, debouncing (500ms), and the streaming
130
188
  // protocol (streamSequence, streamId, informative/streaming/final types).
@@ -137,12 +195,16 @@ function splitMessage(text, maxLen) {
137
195
  // (transient status) or safeSend (terminal errors). Reasoning is accumulated
138
196
  // and periodically pushed via safeUpdate on the same flush timer tick.
139
197
  function createTeamsCallbacks(stream, controller, sendMessage, options) {
198
+ const MIN_INITIAL_CHARS = 20;
140
199
  let stopped = false; // set when stream signals cancellation (403)
141
200
  let hadToolRun = false;
142
201
  let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
143
202
  let reasoningBuf = ""; // accumulated reasoning text for status display
203
+ let totalEmitted = 0; // cumulative chars emitted via safeEmit (for >4000 finalization)
204
+ let streamFinalized = false; // true after stream.close() — subsequent flushes go to safeSend
144
205
  let textBuffer = ""; // accumulated text output for chunked streaming
145
206
  let streamHasContent = false; // tracks whether primary output has received content
207
+ let firstContentEmitted = false; // true after first content push — disables MIN_INITIAL_CHARS threshold
146
208
  let phraseTimer = null;
147
209
  let lastPhrase = "";
148
210
  let flushTimer = null;
@@ -192,15 +254,16 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
192
254
  result.catch(() => markStopped());
193
255
  }
194
256
  }
195
- // Safely emit a text delta to the stream.
257
+ // Safely emit a text delta to the stream with AI labels.
196
258
  // On error (e.g. 403 from Teams stop button), abort the controller.
197
259
  function safeEmit(text) {
198
260
  /* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
199
261
  if (stopped)
200
262
  return;
201
263
  try {
202
- catchAsync(stream.emit(text));
264
+ catchAsync(stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } }));
203
265
  streamHasContent = true;
266
+ totalEmitted += text.length;
204
267
  }
205
268
  catch {
206
269
  markStopped();
@@ -215,7 +278,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
215
278
  try {
216
279
  // stream.emit() is typed as void but the Teams SDK returns a Promise
217
280
  // internally (async HTTP). Cast to capture the result for awaiting.
218
- const result = stream.emit(text);
281
+ const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
219
282
  streamHasContent = true;
220
283
  if (result && typeof result.then === "function") {
221
284
  await result;
@@ -227,6 +290,26 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
227
290
  return false;
228
291
  }
229
292
  }
293
+ // Non-aborting awaitable emit — returns true on success, false on failure WITHOUT
294
+ // calling markStopped() / aborting the controller. Used by flushNow (speak) so a
295
+ // primary-stream failure followed by a successful sendMessage fallback does NOT
296
+ // poison the rest of the turn. tryEmit's abort-on-failure behavior is correct for
297
+ // end-of-turn flush() (no fallback path forward) but wrong for mid-turn speak,
298
+ // which has a sendMessage fallback that may still succeed. Caller (flushNow) is
299
+ // responsible for the `!stopped` precondition; no defensive guard here.
300
+ async function tryEmitNoAbort(text) {
301
+ try {
302
+ const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
303
+ streamHasContent = true;
304
+ if (result && typeof result.then === "function") {
305
+ await result;
306
+ }
307
+ return true;
308
+ }
309
+ catch {
310
+ return false;
311
+ }
312
+ }
230
313
  // Safely send a status update to the stream.
231
314
  // On error (e.g. 403 from Teams stop button), abort the controller.
232
315
  function safeUpdate(text) {
@@ -271,11 +354,49 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
271
354
  // emitted text into a single streaming message (cumulative), so every
272
355
  // periodic flush appends to the same response — not separate messages.
273
356
  // No preemptive splitting — sends full text. Error recovery happens in flush().
357
+ // Hybrid MIN_INITIAL_CHARS: hold back until >= MIN_INITIAL_CHARS accumulated
358
+ // before the first content emit, so phrase rotation shows while real content
359
+ // buffers. After first emit, flush normally (no threshold).
274
360
  function flushTextBuffer() {
275
361
  if (!textBuffer)
276
362
  return;
363
+ if (!firstContentEmitted && textBuffer.length < MIN_INITIAL_CHARS)
364
+ return;
365
+ // Proactive >4000 finalization: if cumulative emitted + buffer >= RECOVERY_CHUNK_SIZE,
366
+ // finalize the stream and send overflow via safeSend (follow-up message).
367
+ if (!streamFinalized && totalEmitted + textBuffer.length >= RECOVERY_CHUNK_SIZE) {
368
+ const remaining = RECOVERY_CHUNK_SIZE - totalEmitted;
369
+ /* v8 ignore next 2 -- defensive: remaining always > 0 because finalization runs once @preserve */
370
+ if (remaining > 0)
371
+ safeEmit(textBuffer.slice(0, remaining));
372
+ try {
373
+ stream.close();
374
+ }
375
+ catch { /* stream may already be dead */ }
376
+ streamFinalized = true;
377
+ /* v8 ignore next -- defensive ternary: remaining always > 0 at first finalization @preserve */
378
+ const overflow = textBuffer.slice(remaining > 0 ? remaining : 0);
379
+ textBuffer = "";
380
+ if (overflow)
381
+ safeSend(overflow);
382
+ if (!firstContentEmitted) {
383
+ firstContentEmitted = true;
384
+ stopPhraseRotation();
385
+ }
386
+ return;
387
+ }
388
+ if (streamFinalized) {
389
+ // After finalization, all content goes to safeSend (follow-up messages)
390
+ safeSend(textBuffer);
391
+ textBuffer = "";
392
+ return;
393
+ }
277
394
  safeEmit(textBuffer);
278
395
  textBuffer = "";
396
+ if (!firstContentEmitted) {
397
+ firstContentEmitted = true;
398
+ stopPhraseRotation();
399
+ }
279
400
  }
280
401
  function startPhraseRotation(pool) {
281
402
  stopPhraseRotation();
@@ -318,40 +439,108 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
318
439
  onTextChunk: (text) => {
319
440
  if (stopped)
320
441
  return;
321
- stopPhraseRotation();
442
+ // Don't stop phrase rotation here — let it continue until first content
443
+ // emit (handled in flushTextBuffer when MIN_INITIAL_CHARS threshold met).
322
444
  textBuffer += text;
323
445
  startFlushTimer();
324
446
  },
325
447
  onClearText: () => {
326
448
  textBuffer = "";
327
449
  },
328
- onToolStart: (name, args) => {
329
- stopPhraseRotation();
330
- flushTextBuffer();
331
- // Emit a placeholder to satisfy the 15s Copilot timeout for initial
332
- // stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
333
- // never emit before the timeout and the user sees "this response was
334
- // stopped". The placeholder is replaced by actual content on next emit.
335
- // https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
336
- if (!streamHasContent)
337
- safeEmit("⏳");
338
- const argSummary = (0, tools_1.summarizeArgs)(name, args) || Object.keys(args).join(", ");
339
- safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
340
- scope: "shared-work",
341
- phase: "processing",
342
- objective: `running ${name} (${argSummary})...`,
343
- })));
344
- hadToolRun = true;
345
- },
346
- onToolEnd: (name, summary, success) => {
450
+ flushNow: async () => {
451
+ const trimmed = textBuffer.trim();
452
+ if (!trimmed)
453
+ return;
454
+ // Cancel pending periodic flush we're delivering now.
455
+ stopFlushTimer();
456
+ // The actual speak message replaces any "thinking..." phrase cycling.
347
457
  stopPhraseRotation();
348
- const msg = (0, format_1.formatToolResult)(name, summary, success);
349
- safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
350
- scope: "shared-work",
351
- phase: "processing",
352
- objective: msg,
353
- })));
458
+ // Bypass MIN_INITIAL_CHARS threshold speak delivers immediately.
459
+ firstContentEmitted = true;
460
+ textBuffer = "";
461
+ // Try the stream first via the NON-ABORTING variant; on failure, fall back
462
+ // to sendMessage. Critical: do NOT call markStopped() / abort the controller
463
+ // when only the primary stream fails — the sendMessage fallback may still
464
+ // deliver the speak, and a successful fallback must not poison the rest of
465
+ // the turn. Only abort when ALL delivery paths fail (handled below).
466
+ // Contract: throws if the message could not be delivered through any available path.
467
+ let delivered = false;
468
+ let lastError = null;
469
+ if (!stopped) {
470
+ const ok = await tryEmitNoAbort(trimmed);
471
+ if (ok)
472
+ delivered = true;
473
+ else
474
+ lastError = new Error("stream emit failed");
475
+ }
476
+ if (!delivered && sendMessage) {
477
+ try {
478
+ await sendMessage(trimmed);
479
+ delivered = true;
480
+ lastError = null;
481
+ }
482
+ catch (err) {
483
+ lastError = err instanceof Error ? err : new Error(String(err));
484
+ }
485
+ }
486
+ (0, runtime_1.emitNervesEvent)({
487
+ component: "senses",
488
+ event: "teams.speak_flush",
489
+ message: "teams flushed mid-turn speak",
490
+ meta: { messageLength: trimmed.length, delivered },
491
+ });
492
+ if (!delivered) {
493
+ // All delivery paths exhausted — now it is correct to abort the turn.
494
+ // markStopped() halts further stream activity and aborts the controller
495
+ // so the engine catches up and ends the turn cleanly.
496
+ markStopped();
497
+ throw new Error(`teams speak delivery failed: ${lastError?.message ?? "no fallback available"}`);
498
+ }
354
499
  },
500
+ ...(() => {
501
+ const toolCbs = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
502
+ onDescription: (text) => safeUpdate(text),
503
+ /* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
504
+ onResult: (text) => safeUpdate(text),
505
+ /* v8 ignore next -- onFailure tested via onToolEnd failure test @preserve */
506
+ onFailure: (text) => safeUpdate(text),
507
+ isDebug: commands_2.getDebugMode,
508
+ });
509
+ return {
510
+ onToolStart: (name, args) => {
511
+ // speak is flow-control: its visible output is the message itself
512
+ // (delivered via onTextChunk + flushNow). Do NOT stop phrase rotation
513
+ // here, do NOT emit the \u23f3 placeholder, do NOT post a tool-activity
514
+ // status update \u2014 all of those would create UI churn right before the
515
+ // actual speak content arrives.
516
+ if (name === "speak")
517
+ return;
518
+ stopPhraseRotation();
519
+ // Force-flush any accumulated text, bypassing MIN_INITIAL_CHARS threshold
520
+ firstContentEmitted = true;
521
+ flushTextBuffer();
522
+ // Emit a placeholder to satisfy the 15s Copilot timeout for initial
523
+ // stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
524
+ // never emit before the timeout and the user sees "this response was
525
+ // stopped". The placeholder is replaced by actual content on next emit.
526
+ // https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
527
+ if (!streamHasContent)
528
+ safeEmit("\u23f3");
529
+ toolCbs.onToolStart(name, args);
530
+ hadToolRun = true;
531
+ },
532
+ onToolEnd: (name, summary, success) => {
533
+ // speak is flow-control: skip phrase-rotation stop and tool-activity end
534
+ // callback (no safeUpdate for \u2713/\u2717). The flushNow call inside the engine
535
+ // already emitted the actual message and stopped any rotation as part of
536
+ // tryEmit's first-content-emitted flag.
537
+ if (name === "speak")
538
+ return;
539
+ stopPhraseRotation();
540
+ toolCbs.onToolEnd(name, summary, success);
541
+ },
542
+ };
543
+ })(),
355
544
  onKick: () => {
356
545
  stopPhraseRotation();
357
546
  const msg = (0, format_1.formatKick)();
@@ -373,38 +562,27 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
373
562
  safeSend(msg);
374
563
  }
375
564
  },
376
- onConfirmAction: options?.conversationId
377
- ? async (name, args) => {
378
- const convId = options.conversationId;
379
- const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
380
- safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
381
- return new Promise((resolve) => {
382
- _pendingConfirmations.set(convId, resolve);
383
- // Auto-deny after 2 minutes to prevent indefinite blocking
384
- // (e.g. when the stream dies and the user never sees the prompt).
385
- setTimeout(() => {
386
- if (_pendingConfirmations.has(convId)) {
387
- _pendingConfirmations.delete(convId);
388
- resolve("denied");
389
- }
390
- }, 120_000);
391
- });
392
- }
393
- : undefined,
394
565
  flush: async () => {
395
566
  stopFlushTimer();
567
+ stopPhraseRotation();
396
568
  if (textBuffer) {
569
+ // Bypass MIN_INITIAL_CHARS threshold — flush delivers all remaining content
570
+ firstContentEmitted = true;
397
571
  const text = textBuffer;
398
572
  textBuffer = "";
399
- if (!stopped) {
573
+ if (streamFinalized && sendMessage) {
574
+ // Stream already finalized (>4000 path) — send remaining content as follow-up
575
+ safeSend(text);
576
+ }
577
+ else if (!stopped) {
400
578
  // Stream is alive — await the emit so we can catch async 413/failure
401
579
  // and fall through to sendMessage recovery.
402
580
  const ok = await tryEmit(text);
403
581
  if (!ok)
404
582
  markStopped();
405
583
  }
406
- if (stopped && sendMessage) {
407
- // Stream is dead — fall back to sendMessage; split on failure as recovery.
584
+ if (stopped && !streamFinalized && sendMessage) {
585
+ // Stream is dead (not from finalization) — fall back to sendMessage; split on failure as recovery.
408
586
  try {
409
587
  await sendMessage(text);
410
588
  }
@@ -415,32 +593,12 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
415
593
  }
416
594
  }
417
595
  }
418
- else if (!streamHasContent) {
419
- safeEmit("(completed with tool calls only \u2014 no text response)");
596
+ else if (!streamHasContent && !options?.suppressEmptyStreamMessage) {
597
+ safeEmit("(completed with tool calls only no text response)");
420
598
  }
421
599
  },
422
600
  };
423
601
  }
424
- // Per-conversation pending confirmation resolvers.
425
- // When a mutate tool needs confirmation, the resolver is stored here.
426
- // The next message from the same conversation resolves it.
427
- const _pendingConfirmations = new Map();
428
- // Confirmation response words (case-insensitive)
429
- const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
430
- function resolvePendingConfirmation(convId, text) {
431
- const resolver = _pendingConfirmations.get(convId);
432
- if (!resolver)
433
- return false;
434
- _pendingConfirmations.delete(convId);
435
- const word = text.trim().toLowerCase();
436
- if (CONFIRM_WORDS.has(word)) {
437
- resolver("confirmed");
438
- }
439
- else {
440
- resolver("denied");
441
- }
442
- return true;
443
- }
444
602
  const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
445
603
  function teamsTurnKey(conversationId) {
446
604
  return `teams:${conversationId}`;
@@ -459,6 +617,7 @@ function createTeamsCommandRegistry() {
459
617
  (0, commands_1.registerDefaultCommands)(registry);
460
618
  return registry;
461
619
  }
620
+ /* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
462
621
  function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
463
622
  const parsed = (0, commands_1.parseSlashCommand)(text);
464
623
  if (!parsed)
@@ -482,15 +641,19 @@ function handleTeamsSlashCommand(text, registry, friendId, conversationId, strea
482
641
  }
483
642
  return null;
484
643
  }
644
+ /* v8 ignore stop */
485
645
  // Handle an incoming Teams message
486
- async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
646
+ async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides) {
487
647
  const turnKey = teamsTurnKey(conversationId);
488
648
  // NOTE: Confirmation resolution is handled in the app.on("message") handler
489
649
  // BEFORE the conversation lock. By the time we get here, any pending
490
650
  // confirmation has already been resolved and the reply consumed.
491
651
  // Send first thinking phrase immediately so the user sees feedback
492
652
  // before sync I/O (session load, trim) blocks the event loop.
493
- stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
653
+ // Skip for reaction signals — they should be processed quietly.
654
+ if (!reactionOverrides) {
655
+ stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
656
+ }
494
657
  await new Promise(r => setImmediate(r));
495
658
  // Resolve identity provider early for friend resolution + slash command session path
496
659
  const store = getFriendStore();
@@ -507,15 +670,10 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
507
670
  // Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
508
671
  const resolvedContext = await resolver.resolve();
509
672
  const friendId = resolvedContext.friend.id;
510
- const registry = createTeamsCommandRegistry();
511
- // Check for slash commands (before pipeline -- these are transport-level concerns)
512
- if (handleTeamsSlashCommand(text, registry, friendId, conversationId, stream)) {
513
- return;
514
- }
515
673
  // ── Teams adapter concerns: controller, callbacks, session path ──────────
516
674
  const controller = new AbortController();
517
675
  const channelConfig = (0, config_2.getTeamsChannelConfig)();
518
- const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs });
676
+ const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
519
677
  const traceId = (0, nerves_1.createTraceId)();
520
678
  const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
521
679
  const teamsCapabilities = (0, channel_1.getChannelCapabilities)("teams");
@@ -526,42 +684,68 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
526
684
  adoToken: teamsContext.adoToken,
527
685
  githubToken: teamsContext.githubToken,
528
686
  signin: teamsContext.signin,
529
- summarize: (0, core_1.createSummarize)(),
687
+ summarize: (0, core_1.createSummarize)("human"),
530
688
  tenantId: teamsContext.tenantId,
531
689
  botApi: teamsContext.botApi,
532
690
  } : {};
533
691
  let currentText = text;
692
+ const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
534
693
  while (true) {
535
694
  let drainedSteeringFollowUps = [];
536
695
  // Build runAgentOptions with Teams-specific fields
537
696
  const agentOptions = {
538
697
  traceId,
539
698
  toolContext: teamsToolContext,
699
+ mcpManager,
540
700
  drainSteeringFollowUps: () => {
541
701
  drainedSteeringFollowUps = _turnCoordinator.drainFollowUps(turnKey)
542
702
  .map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
543
703
  return drainedSteeringFollowUps;
544
704
  },
705
+ ...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
545
706
  };
546
- if (channelConfig.skipConfirmation)
547
- agentOptions.skipConfirmation = true;
548
707
  // ── Call shared pipeline ──────────────────────────────────────────
708
+ // Capture terminal errors — failover message replaces the error card if it triggers
709
+ let capturedTerminalError = null;
710
+ const teamsFailoverState = (() => {
711
+ if (!teamsFailoverStates.has(conversationId)) {
712
+ teamsFailoverStates.set(conversationId, { pending: null });
713
+ }
714
+ return teamsFailoverStates.get(conversationId);
715
+ })();
716
+ /* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
717
+ const failoverAwareCallbacks = {
718
+ ...callbacks,
719
+ onError: (error, severity) => {
720
+ if (severity === "terminal" && teamsFailoverState) {
721
+ capturedTerminalError = error;
722
+ return;
723
+ }
724
+ callbacks.onError(error, severity);
725
+ },
726
+ };
727
+ /* v8 ignore stop */
549
728
  const result = await (0, pipeline_1.handleInboundTurn)({
550
729
  channel: "teams",
551
730
  sessionKey: conversationId,
552
731
  capabilities: teamsCapabilities,
553
732
  messages: [{ role: "user", content: currentText }],
554
733
  continuityIngressTexts: [currentText],
555
- callbacks,
734
+ callbacks: failoverAwareCallbacks,
556
735
  friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
557
736
  sessionLoader: {
558
737
  loadOrCreate: async () => {
559
738
  const existing = (0, context_1.loadSession)(sessPath);
560
739
  const messages = existing?.messages && existing.messages.length > 0
561
740
  ? existing.messages
562
- : [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", undefined, resolvedContext) }];
741
+ : [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("teams", {}, resolvedContext)) }];
563
742
  (0, core_1.repairOrphanedToolCalls)(messages);
564
- return { messages, sessionPath: sessPath, state: existing?.state };
743
+ return {
744
+ messages,
745
+ sessionPath: sessPath,
746
+ state: existing?.state,
747
+ events: existing?.events,
748
+ };
565
749
  },
566
750
  },
567
751
  pendingDir,
@@ -584,11 +768,33 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
584
768
  summarize: teamsToolContext.summarize,
585
769
  },
586
770
  }),
587
- postTurn: context_1.postTurn,
771
+ postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
772
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
773
+ (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
774
+ },
588
775
  accumulateFriendTokens: tokens_1.accumulateFriendTokens,
589
776
  signal: controller.signal,
590
777
  runAgentOptions: agentOptions,
778
+ failoverState: teamsFailoverState,
591
779
  });
780
+ // ── Handle pipeline-intercepted commands ────────────────────────
781
+ if (result.turnOutcome === "command") {
782
+ if (result.commandAction === "new") {
783
+ (0, context_1.deleteSession)(sessPath);
784
+ stream.emit("session cleared");
785
+ }
786
+ // For "response" commands: pipeline already emitted the response via onTextChunk
787
+ await callbacks.flush();
788
+ return;
789
+ }
790
+ /* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
791
+ if (result.failoverMessage) {
792
+ stream.emit(result.failoverMessage);
793
+ }
794
+ else if (capturedTerminalError) {
795
+ callbacks.onError(capturedTerminalError, "terminal");
796
+ }
797
+ /* v8 ignore stop */
592
798
  // ── Handle gate result ────────────────────────────────────────
593
799
  if (!result.gateResult.allowed) {
594
800
  if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
@@ -628,7 +834,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
628
834
  currentText = replayTail;
629
835
  continue;
630
836
  }
631
- if (handleTeamsSlashCommand(supersedingFollowUp.text, registry, friendId, conversationId, stream, false)) {
837
+ if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
632
838
  return;
633
839
  }
634
840
  currentText = supersedingFollowUp.text;
@@ -708,7 +914,7 @@ function registerBotHandlers(app, label) {
708
914
  // (graph + ado + github). The verifyState activity only carries a `state`
709
915
  // code with no connectionName, so we try each configured connection until
710
916
  // one succeeds.
711
- app.on("signin.verify-state", async (ctx) => {
917
+ app.on("signin.verify-state", (async (ctx) => {
712
918
  const { api, activity } = ctx;
713
919
  if (!activity.value?.state)
714
920
  return { status: 404 };
@@ -727,7 +933,73 @@ function registerBotHandlers(app, label) {
727
933
  }
728
934
  (0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state failed for all connections`, meta: {} });
729
935
  return { status: 412 };
936
+ }));
937
+ // Handle Teams feedback reactions (thumbs up/down on AI-generated messages).
938
+ // SDK routes message/submitAction with actionName "feedback" to this event.
939
+ /* v8 ignore start -- Teams SDK invoke handler; requires live SDK context @preserve */
940
+ app.on("message.submit.feedback", async (ctx) => {
941
+ const { stream, activity } = ctx;
942
+ const reaction = activity.value?.actionValue?.reaction;
943
+ const comment = activity.value?.actionValue?.feedback;
944
+ const convId = activity.conversation?.id || "unknown";
945
+ const turnKey = teamsTurnKey(convId);
946
+ // Validate payload — graceful no-op for malformed invocations
947
+ if (activity.value?.actionName !== "feedback" || !reaction) {
948
+ return;
949
+ }
950
+ const syntheticText = buildFeedbackSyntheticText(reaction, comment);
951
+ // Turn coordination: if a turn is active, enqueue as steering follow-up
952
+ if (!_turnCoordinator.tryBeginTurn(turnKey)) {
953
+ _turnCoordinator.enqueueFollowUp(turnKey, {
954
+ conversationId: convId,
955
+ text: syntheticText,
956
+ receivedAt: Date.now(),
957
+ effect: (0, continuity_1.classifySteeringFollowUpEffect)(syntheticText),
958
+ });
959
+ return;
960
+ }
961
+ try {
962
+ const teamsContext = {
963
+ signin: async () => undefined,
964
+ aadObjectId: activity.from?.aadObjectId,
965
+ tenantId: activity.conversation?.tenantId,
966
+ displayName: activity.from?.name,
967
+ };
968
+ const ctxSend = async (t) => {
969
+ await ctx.send({ type: "message", text: t, replyToId: activity.replyToId, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
970
+ };
971
+ await handleTeamsMessage(syntheticText, stream, convId, teamsContext, ctxSend, { isReactionSignal: true, suppressEmptyStreamMessage: true });
972
+ }
973
+ catch (err) {
974
+ const msg = err instanceof Error ? err.message : String(err);
975
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.feedback_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
976
+ }
977
+ finally {
978
+ _turnCoordinator.endTurn(turnKey);
979
+ }
980
+ });
981
+ /* v8 ignore stop */
982
+ // Handle bot install — send welcome Adaptive Card with prompt starters.
983
+ /* v8 ignore start -- Teams SDK install handler; requires live SDK context @preserve */
984
+ app.on("install.add", async (ctx) => {
985
+ try {
986
+ const card = buildWelcomeCard();
987
+ await ctx.send({
988
+ type: "message",
989
+ attachments: [{
990
+ contentType: "application/vnd.microsoft.card.adaptive",
991
+ content: card,
992
+ }],
993
+ entities: aiLabelEntities(),
994
+ channelData: { feedbackLoopEnabled: true },
995
+ });
996
+ }
997
+ catch (err) {
998
+ const msg = err instanceof Error ? err.message : String(err);
999
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.welcome_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
1000
+ }
730
1001
  });
1002
+ /* v8 ignore stop */
731
1003
  app.on("message", async (ctx) => {
732
1004
  const { stream, activity, api, signin } = ctx;
733
1005
  const text = activity.text || "";
@@ -736,13 +1008,6 @@ function registerBotHandlers(app, label) {
736
1008
  const userId = activity.from?.id || "";
737
1009
  const channelId = activity.channelId || "msteams";
738
1010
  (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: `[${label}] incoming teams message`, meta: { userId: userId.slice(0, 12), conversationId: convId.slice(0, 20) } });
739
- // Resolve pending confirmations IMMEDIATELY — before token fetches or
740
- // the conversation lock. The original message holds the lock while
741
- // awaiting confirmation, so acquiring it here would deadlock. Token
742
- // fetches are also unnecessary (and slow) for a simple yes/no reply.
743
- if (resolvePendingConfirmation(convId, text)) {
744
- return;
745
- }
746
1011
  const commandRegistry = createTeamsCommandRegistry();
747
1012
  const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
748
1013
  if (parsedSlashCommand) {
@@ -843,7 +1108,7 @@ function registerBotHandlers(app, label) {
843
1108
  const ctxSend = async (t) => {
844
1109
  // Use send with replyToId (not reply, which adds a blockquote).
845
1110
  // replyToId anchors the message after the user's message in Copilot Chat.
846
- await ctx.send({ type: "message", text: t, replyToId: activity.id });
1111
+ await ctx.send({ type: "message", text: t, replyToId: activity.id, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
847
1112
  };
848
1113
  await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
849
1114
  }
@@ -925,6 +1190,16 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
925
1190
  });
926
1191
  return { delivered: false, reason: "missing_target" };
927
1192
  }
1193
+ const internalContentBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(params.text);
1194
+ if (internalContentBlockReason) {
1195
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1196
+ friendId: params.friendId,
1197
+ sessionKey: params.sessionKey,
1198
+ reason: internalContentBlockReason,
1199
+ source: "session_send",
1200
+ });
1201
+ return { delivered: false, reason: "internal_content_blocked" };
1202
+ }
928
1203
  try {
929
1204
  const conversation = await conversations.create({
930
1205
  bot: { id: deps.botApi.id },
@@ -1027,6 +1302,20 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
1027
1302
  catch { /* ignore */ }
1028
1303
  continue;
1029
1304
  }
1305
+ const internalBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(messageText);
1306
+ if (internalBlockReason) {
1307
+ result.skipped++;
1308
+ try {
1309
+ fs.unlinkSync(filePath);
1310
+ }
1311
+ catch { /* ignore */ }
1312
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1313
+ friendId,
1314
+ reason: internalBlockReason,
1315
+ source: "pending_drain",
1316
+ });
1317
+ continue;
1318
+ }
1030
1319
  const sendResult = await sendProactiveTeamsMessageToSession({
1031
1320
  friendId,
1032
1321
  sessionKey: key,