@ouro.bot/cli 0.1.0-alpha.48 → 0.1.0-alpha.481

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 (358) hide show
  1. package/README.md +132 -19
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  5. package/changelog.json +3069 -0
  6. package/dist/arc/attention-types.js +8 -0
  7. package/dist/arc/cares.js +140 -0
  8. package/dist/arc/episodes.js +117 -0
  9. package/dist/arc/intentions.js +133 -0
  10. package/dist/arc/json-store.js +117 -0
  11. package/dist/arc/obligations.js +237 -0
  12. package/dist/arc/packets.js +193 -0
  13. package/dist/arc/presence.js +185 -0
  14. package/dist/arc/task-lifecycle.js +65 -0
  15. package/dist/heart/active-work.js +857 -0
  16. package/dist/heart/agent-entry.js +58 -3
  17. package/dist/heart/attachments/image-normalize.js +194 -0
  18. package/dist/heart/attachments/materialize.js +97 -0
  19. package/dist/heart/attachments/originals.js +88 -0
  20. package/dist/heart/attachments/render.js +29 -0
  21. package/dist/heart/attachments/sources/adapter.js +2 -0
  22. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  23. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  24. package/dist/heart/attachments/sources/index.js +16 -0
  25. package/dist/heart/attachments/store.js +103 -0
  26. package/dist/heart/attachments/types.js +93 -0
  27. package/dist/heart/auth/auth-flow.js +426 -0
  28. package/dist/heart/background-operations.js +234 -0
  29. package/dist/heart/bridges/manager.js +358 -0
  30. package/dist/heart/bridges/state-machine.js +135 -0
  31. package/dist/heart/bridges/store.js +123 -0
  32. package/dist/heart/bundle-state.js +168 -0
  33. package/dist/heart/commitments.js +111 -0
  34. package/dist/heart/config-registry.js +304 -0
  35. package/dist/heart/config.js +110 -128
  36. package/dist/heart/core.js +745 -227
  37. package/dist/heart/cross-chat-delivery.js +131 -0
  38. package/dist/heart/daemon/agent-config-check.js +490 -0
  39. package/dist/heart/daemon/agent-discovery.js +79 -3
  40. package/dist/heart/daemon/agent-service.js +360 -0
  41. package/dist/heart/daemon/agentic-repair.js +216 -0
  42. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  43. package/dist/heart/daemon/cadence.js +70 -0
  44. package/dist/heart/daemon/cli-defaults.js +640 -0
  45. package/dist/heart/daemon/cli-exec.js +6933 -0
  46. package/dist/heart/daemon/cli-help.js +487 -0
  47. package/dist/heart/daemon/cli-parse.js +1527 -0
  48. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  49. package/dist/heart/daemon/cli-render.js +561 -0
  50. package/dist/heart/daemon/cli-types.js +8 -0
  51. package/dist/heart/daemon/connect-bay.js +323 -0
  52. package/dist/heart/daemon/daemon-cli.js +29 -1616
  53. package/dist/heart/daemon/daemon-entry.js +345 -3
  54. package/dist/heart/daemon/daemon-health.js +141 -0
  55. package/dist/heart/daemon/daemon-runtime-sync.js +190 -12
  56. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  57. package/dist/heart/daemon/daemon.js +677 -58
  58. package/dist/heart/daemon/dns-workflow.js +394 -0
  59. package/dist/heart/daemon/doctor-types.js +8 -0
  60. package/dist/heart/daemon/doctor.js +486 -0
  61. package/dist/heart/daemon/health-monitor.js +92 -1
  62. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  63. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  64. package/dist/heart/daemon/http-health-probe.js +80 -0
  65. package/dist/heart/daemon/human-command-screens.js +234 -0
  66. package/dist/heart/daemon/human-readiness.js +114 -0
  67. package/dist/heart/daemon/inner-status.js +89 -0
  68. package/dist/heart/daemon/interactive-repair.js +394 -0
  69. package/dist/heart/daemon/launchd.js +25 -5
  70. package/dist/heart/daemon/log-tailer.js +82 -12
  71. package/dist/heart/daemon/logs-prune.js +110 -0
  72. package/dist/heart/daemon/message-router.js +2 -2
  73. package/dist/heart/daemon/os-cron-deps.js +134 -0
  74. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  75. package/dist/heart/daemon/ouro-entry.js +3 -1
  76. package/dist/heart/daemon/process-manager.js +214 -0
  77. package/dist/heart/daemon/provider-discovery.js +137 -0
  78. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  79. package/dist/heart/daemon/pulse.js +475 -0
  80. package/dist/heart/daemon/readiness-repair.js +365 -0
  81. package/dist/heart/daemon/run-hooks.js +2 -0
  82. package/dist/heart/daemon/runtime-logging.js +67 -16
  83. package/dist/heart/daemon/runtime-metadata.js +73 -0
  84. package/dist/heart/daemon/runtime-mode.js +67 -0
  85. package/dist/heart/daemon/safe-mode.js +161 -0
  86. package/dist/heart/daemon/sense-manager.js +178 -37
  87. package/dist/heart/daemon/session-id-resolver.js +131 -0
  88. package/dist/heart/daemon/skill-management-installer.js +94 -0
  89. package/dist/heart/daemon/socket-client.js +109 -4
  90. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  91. package/dist/heart/daemon/startup-tui.js +264 -0
  92. package/dist/heart/daemon/task-scheduler.js +3 -25
  93. package/dist/heart/daemon/terminal-ui.js +499 -0
  94. package/dist/heart/daemon/thoughts.js +149 -10
  95. package/dist/heart/daemon/up-progress.js +366 -0
  96. package/dist/heart/daemon/vault-items.js +56 -0
  97. package/dist/heart/delegation.js +62 -0
  98. package/dist/heart/habits/habit-migration.js +189 -0
  99. package/dist/heart/habits/habit-parser.js +140 -0
  100. package/dist/heart/habits/habit-runtime-state.js +100 -0
  101. package/dist/heart/habits/habit-scheduler.js +372 -0
  102. package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
  103. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  104. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  105. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  106. package/dist/heart/identity.js +201 -66
  107. package/dist/heart/kept-notes.js +357 -0
  108. package/dist/heart/kicks.js +1 -1
  109. package/dist/heart/machine-identity.js +161 -0
  110. package/dist/heart/mcp/mcp-server.js +653 -0
  111. package/dist/heart/migrate-config.js +100 -0
  112. package/dist/heart/model-capabilities.js +59 -0
  113. package/dist/heart/outlook/outlook-http-hooks.js +66 -0
  114. package/dist/heart/outlook/outlook-http-response.js +7 -0
  115. package/dist/heart/outlook/outlook-http-routes.js +244 -0
  116. package/dist/heart/outlook/outlook-http-static.js +99 -0
  117. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  118. package/dist/heart/outlook/outlook-http.js +99 -0
  119. package/dist/heart/outlook/outlook-read.js +31 -0
  120. package/dist/heart/outlook/outlook-types.js +27 -0
  121. package/dist/heart/outlook/outlook-view.js +195 -0
  122. package/dist/heart/outlook/readers/agent-machine.js +359 -0
  123. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  124. package/dist/heart/outlook/readers/mail.js +362 -0
  125. package/dist/heart/outlook/readers/runtime-readers.js +644 -0
  126. package/dist/heart/outlook/readers/sessions.js +232 -0
  127. package/dist/heart/outlook/readers/shared.js +111 -0
  128. package/dist/heart/platform.js +81 -0
  129. package/dist/heart/progress-story.js +42 -0
  130. package/dist/heart/provider-attempt.js +134 -0
  131. package/dist/heart/provider-binding-resolver.js +255 -0
  132. package/dist/heart/provider-credentials.js +424 -0
  133. package/dist/heart/provider-failover.js +266 -0
  134. package/dist/heart/provider-models.js +81 -0
  135. package/dist/heart/provider-ping.js +262 -0
  136. package/dist/heart/provider-state.js +216 -0
  137. package/dist/heart/provider-visibility.js +188 -0
  138. package/dist/heart/providers/anthropic-token.js +131 -0
  139. package/dist/heart/providers/anthropic.js +193 -55
  140. package/dist/heart/providers/azure.js +103 -12
  141. package/dist/heart/providers/error-classification.js +63 -0
  142. package/dist/heart/providers/github-copilot.js +145 -0
  143. package/dist/heart/providers/minimax-vlm.js +189 -0
  144. package/dist/heart/providers/minimax.js +29 -7
  145. package/dist/heart/providers/openai-codex.js +62 -38
  146. package/dist/heart/runtime-capability-check.js +170 -0
  147. package/dist/heart/runtime-credentials.js +260 -0
  148. package/dist/heart/sense-truth.js +11 -4
  149. package/dist/heart/session-activity.js +190 -0
  150. package/dist/heart/session-events.js +855 -0
  151. package/dist/heart/session-transcript.js +167 -0
  152. package/dist/heart/start-of-turn-packet.js +345 -0
  153. package/dist/heart/streaming.js +36 -27
  154. package/dist/heart/sync.js +332 -0
  155. package/dist/heart/target-resolution.js +127 -0
  156. package/dist/heart/tempo.js +93 -0
  157. package/dist/heart/temporal-view.js +41 -0
  158. package/dist/heart/tool-activity-callbacks.js +36 -0
  159. package/dist/heart/tool-description.js +135 -0
  160. package/dist/heart/tool-friction.js +55 -0
  161. package/dist/heart/tool-loop.js +200 -0
  162. package/dist/heart/turn-context.js +361 -0
  163. package/dist/heart/turn-coordinator.js +24 -1
  164. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  165. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  166. package/dist/heart/versioning/ouro-path-installer.js +425 -0
  167. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  168. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  169. package/dist/heart/{daemon → versioning}/update-checker.js +5 -1
  170. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  171. package/dist/mailroom/attention.js +167 -0
  172. package/dist/mailroom/autonomy.js +209 -0
  173. package/dist/mailroom/blob-store.js +573 -0
  174. package/dist/mailroom/core.js +658 -0
  175. package/dist/mailroom/entry.js +160 -0
  176. package/dist/mailroom/file-store.js +400 -0
  177. package/dist/mailroom/mbox-import.js +341 -0
  178. package/dist/mailroom/outbound.js +380 -0
  179. package/dist/mailroom/policy.js +263 -0
  180. package/dist/mailroom/reader.js +197 -0
  181. package/dist/mailroom/smtp-ingress.js +176 -0
  182. package/dist/mailroom/source-state.js +176 -0
  183. package/dist/mailroom/travel-extract.js +89 -0
  184. package/dist/mind/bundle-manifest.js +7 -1
  185. package/dist/mind/context.js +132 -93
  186. package/dist/mind/diary-integrity.js +60 -0
  187. package/dist/mind/{memory.js → diary.js} +74 -93
  188. package/dist/mind/embedding-provider.js +60 -0
  189. package/dist/mind/file-state.js +179 -0
  190. package/dist/mind/friends/channel.js +30 -0
  191. package/dist/mind/friends/group-context.js +144 -0
  192. package/dist/mind/friends/resolver.js +38 -1
  193. package/dist/mind/friends/store-file.js +39 -3
  194. package/dist/mind/friends/trust-explanation.js +74 -0
  195. package/dist/mind/friends/types.js +2 -2
  196. package/dist/mind/journal-index.js +161 -0
  197. package/dist/mind/note-search.js +268 -0
  198. package/dist/mind/obligation-steering.js +221 -0
  199. package/dist/mind/pending.js +66 -7
  200. package/dist/mind/prompt-refresh.js +3 -2
  201. package/dist/mind/prompt.js +978 -169
  202. package/dist/mind/provenance-trust.js +26 -0
  203. package/dist/mind/scrutiny.js +173 -0
  204. package/dist/nerves/cli-logging.js +7 -1
  205. package/dist/nerves/coverage/audit-rules.js +15 -6
  206. package/dist/nerves/coverage/audit.js +28 -2
  207. package/dist/nerves/coverage/cli.js +1 -1
  208. package/dist/nerves/coverage/contract.js +5 -5
  209. package/dist/nerves/coverage/file-completeness.js +84 -5
  210. package/dist/nerves/coverage/run-artifacts.js +1 -1
  211. package/dist/nerves/event-buffer.js +111 -0
  212. package/dist/nerves/index.js +224 -4
  213. package/dist/nerves/observation.js +20 -0
  214. package/dist/nerves/redact.js +79 -0
  215. package/dist/nerves/runtime.js +5 -1
  216. package/dist/outlook-ui/assets/index-BPr5vNuM.css +1 -0
  217. package/dist/outlook-ui/assets/index-CPfhbn13.js +61 -0
  218. package/dist/outlook-ui/index.html +15 -0
  219. package/dist/repertoire/ado-client.js +15 -56
  220. package/dist/repertoire/ado-semantic.js +11 -10
  221. package/dist/repertoire/api-client.js +97 -0
  222. package/dist/repertoire/bitwarden-store.js +774 -0
  223. package/dist/repertoire/bundle-templates.js +72 -0
  224. package/dist/repertoire/bw-installer.js +180 -0
  225. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  226. package/dist/repertoire/coding/context-pack.js +330 -0
  227. package/dist/repertoire/coding/feedback.js +197 -30
  228. package/dist/repertoire/coding/manager.js +158 -9
  229. package/dist/repertoire/coding/spawner.js +55 -9
  230. package/dist/repertoire/coding/tools.js +170 -7
  231. package/dist/repertoire/commerce-errors.js +109 -0
  232. package/dist/repertoire/commerce-self-test.js +156 -0
  233. package/dist/repertoire/credential-access.js +111 -0
  234. package/dist/repertoire/duffel-client.js +185 -0
  235. package/dist/repertoire/github-client.js +14 -55
  236. package/dist/repertoire/graph-client.js +11 -52
  237. package/dist/repertoire/guardrails.js +396 -0
  238. package/dist/repertoire/mcp-client.js +255 -0
  239. package/dist/repertoire/mcp-manager.js +305 -0
  240. package/dist/repertoire/mcp-tools.js +63 -0
  241. package/dist/repertoire/shell-sessions.js +133 -0
  242. package/dist/repertoire/skills.js +15 -24
  243. package/dist/repertoire/stripe-client.js +131 -0
  244. package/dist/repertoire/tasks/board.js +43 -5
  245. package/dist/repertoire/tasks/fix.js +182 -0
  246. package/dist/repertoire/tasks/index.js +37 -4
  247. package/dist/repertoire/tasks/lifecycle.js +2 -2
  248. package/dist/repertoire/tasks/parser.js +3 -2
  249. package/dist/repertoire/tasks/scanner.js +194 -37
  250. package/dist/repertoire/tasks/transitions.js +16 -78
  251. package/dist/repertoire/tool-results.js +29 -0
  252. package/dist/repertoire/tools-attachments.js +317 -0
  253. package/dist/repertoire/tools-base.js +44 -740
  254. package/dist/repertoire/tools-bluebubbles.js +1 -0
  255. package/dist/repertoire/tools-bridge.js +141 -0
  256. package/dist/repertoire/tools-bundle.js +984 -0
  257. package/dist/repertoire/tools-config.js +185 -0
  258. package/dist/repertoire/tools-continuity.js +248 -0
  259. package/dist/repertoire/tools-credential.js +381 -0
  260. package/dist/repertoire/tools-files.js +342 -0
  261. package/dist/repertoire/tools-flight.js +224 -0
  262. package/dist/repertoire/tools-flow.js +105 -0
  263. package/dist/repertoire/tools-github.js +1 -7
  264. package/dist/repertoire/tools-mail.js +896 -0
  265. package/dist/repertoire/tools-notes.js +376 -0
  266. package/dist/repertoire/tools-session.js +746 -0
  267. package/dist/repertoire/tools-shell.js +120 -0
  268. package/dist/repertoire/tools-stripe.js +180 -0
  269. package/dist/repertoire/tools-surface.js +243 -0
  270. package/dist/repertoire/tools-teams.js +9 -39
  271. package/dist/repertoire/tools-travel.js +125 -0
  272. package/dist/repertoire/tools-user-profile.js +144 -0
  273. package/dist/repertoire/tools-vault.js +40 -0
  274. package/dist/repertoire/tools.js +144 -113
  275. package/dist/repertoire/travel-api-client.js +360 -0
  276. package/dist/repertoire/user-profile.js +131 -0
  277. package/dist/repertoire/vault-setup.js +246 -0
  278. package/dist/repertoire/vault-unlock.js +561 -0
  279. package/dist/scripts/claude-code-hook.js +41 -0
  280. package/dist/scripts/claude-code-stop-hook.js +47 -0
  281. package/dist/senses/attention-queue.js +116 -0
  282. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  283. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  284. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  285. package/dist/senses/bluebubbles/entry.js +73 -0
  286. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +7 -3
  287. package/dist/senses/{bluebubbles.js → bluebubbles/index.js} +705 -116
  288. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  289. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  290. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  291. package/dist/senses/bluebubbles/replay.js +129 -0
  292. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +2 -2
  293. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  294. package/dist/senses/cli/bracketed-paste.js +82 -0
  295. package/dist/senses/cli/image-paste.js +287 -0
  296. package/dist/senses/cli/image-ref-navigation.js +75 -0
  297. package/dist/senses/cli/ink-app.js +156 -0
  298. package/dist/senses/cli/inline-diff.js +64 -0
  299. package/dist/senses/cli/input-keys.js +174 -0
  300. package/dist/senses/cli/kill-ring.js +86 -0
  301. package/dist/senses/cli/message-list.js +51 -0
  302. package/dist/senses/cli/ouro-tui.js +605 -0
  303. package/dist/senses/cli/spinner-imperative.js +135 -0
  304. package/dist/senses/cli/spinner.js +101 -0
  305. package/dist/senses/cli/status-line.js +60 -0
  306. package/dist/senses/cli/streaming-markdown.js +526 -0
  307. package/dist/senses/cli/tool-display.js +83 -0
  308. package/dist/senses/cli/tool-render.js +85 -0
  309. package/dist/senses/cli/tui-store.js +240 -0
  310. package/dist/senses/cli/virtual-list.js +35 -0
  311. package/dist/senses/cli-entry.js +60 -8
  312. package/dist/senses/cli-layout.js +187 -0
  313. package/dist/senses/cli.js +516 -211
  314. package/dist/senses/commands.js +66 -3
  315. package/dist/senses/habit-turn-message.js +108 -0
  316. package/dist/senses/inner-dialog-worker.js +97 -17
  317. package/dist/senses/inner-dialog.js +404 -14
  318. package/dist/senses/mail-entry.js +66 -0
  319. package/dist/senses/mail.js +232 -0
  320. package/dist/senses/pipeline.js +533 -72
  321. package/dist/senses/proactive-content-guard.js +51 -0
  322. package/dist/senses/shared-turn.js +205 -0
  323. package/dist/senses/surface-tool.js +68 -0
  324. package/dist/senses/teams-entry.js +60 -8
  325. package/dist/senses/teams.js +413 -163
  326. package/dist/senses/trust-gate.js +5 -5
  327. package/package.json +37 -7
  328. package/skills/agent-commerce.md +106 -0
  329. package/skills/browser-navigation.md +117 -0
  330. package/skills/commerce-setup-guide.md +116 -0
  331. package/skills/commerce-setup.md +84 -0
  332. package/skills/configure-dev-tools.md +101 -0
  333. package/skills/travel-planning.md +138 -0
  334. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  335. package/dist/heart/daemon/subagent-installer.js +0 -166
  336. package/dist/mind/associative-recall.js +0 -209
  337. package/dist/senses/bluebubbles-entry.js +0 -13
  338. package/dist/senses/debug-activity.js +0 -127
  339. package/subagents/README.md +0 -86
  340. package/subagents/work-doer.md +0 -237
  341. package/subagents/work-merger.md +0 -618
  342. package/subagents/work-planner.md +0 -390
  343. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  344. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  345. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  346. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  347. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  348. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  349. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  350. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  351. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  352. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  353. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  354. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  355. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  356. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  357. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  358. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -34,12 +34,16 @@ 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;
46
+ exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
43
47
  exports.drainAndSendPendingTeams = drainAndSendPendingTeams;
44
48
  exports.startTeamsApp = startTeamsApp;
45
49
  const fs = __importStar(require("fs"));
@@ -57,18 +61,34 @@ const context_1 = require("../mind/context");
57
61
  const commands_1 = require("./commands");
58
62
  const nerves_1 = require("../nerves");
59
63
  const runtime_1 = require("../nerves/runtime");
64
+ const proactive_content_guard_1 = require("./proactive-content-guard");
60
65
  const store_file_1 = require("../mind/friends/store-file");
61
66
  const types_1 = require("../mind/friends/types");
62
67
  const resolver_1 = require("../mind/friends/resolver");
63
68
  const tokens_1 = require("../mind/friends/tokens");
64
69
  const turn_coordinator_1 = require("../heart/turn-coordinator");
65
70
  const identity_1 = require("../heart/identity");
71
+ const mcp_manager_1 = require("../repertoire/mcp-manager");
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");
66
75
  const http = __importStar(require("http"));
67
76
  const path = __importStar(require("path"));
68
77
  const trust_gate_1 = require("./trust-gate");
69
78
  const pipeline_1 = require("./pipeline");
79
+ const teamsFailoverStates = new Map();
70
80
  const pending_1 = require("../mind/pending");
71
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
+ }
72
92
  // Strip @mention markup from incoming messages.
73
93
  // Removes <at>...</at> tags and trims extra whitespace.
74
94
  // Fallback safety net -- the SDK's activity.mentions.stripText should handle
@@ -123,6 +143,46 @@ function splitMessage(text, maxLen) {
123
143
  }
124
144
  return chunks;
125
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
+ }
126
186
  // Create Teams-specific callbacks for the agent loop.
127
187
  // The SDK handles cumulative text, debouncing (500ms), and the streaming
128
188
  // protocol (streamSequence, streamId, informative/streaming/final types).
@@ -135,12 +195,16 @@ function splitMessage(text, maxLen) {
135
195
  // (transient status) or safeSend (terminal errors). Reasoning is accumulated
136
196
  // and periodically pushed via safeUpdate on the same flush timer tick.
137
197
  function createTeamsCallbacks(stream, controller, sendMessage, options) {
198
+ const MIN_INITIAL_CHARS = 20;
138
199
  let stopped = false; // set when stream signals cancellation (403)
139
200
  let hadToolRun = false;
140
201
  let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
141
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
142
205
  let textBuffer = ""; // accumulated text output for chunked streaming
143
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
144
208
  let phraseTimer = null;
145
209
  let lastPhrase = "";
146
210
  let flushTimer = null;
@@ -190,15 +254,16 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
190
254
  result.catch(() => markStopped());
191
255
  }
192
256
  }
193
- // Safely emit a text delta to the stream.
257
+ // Safely emit a text delta to the stream with AI labels.
194
258
  // On error (e.g. 403 from Teams stop button), abort the controller.
195
259
  function safeEmit(text) {
196
260
  /* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
197
261
  if (stopped)
198
262
  return;
199
263
  try {
200
- catchAsync(stream.emit(text));
264
+ catchAsync(stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } }));
201
265
  streamHasContent = true;
266
+ totalEmitted += text.length;
202
267
  }
203
268
  catch {
204
269
  markStopped();
@@ -213,7 +278,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
213
278
  try {
214
279
  // stream.emit() is typed as void but the Teams SDK returns a Promise
215
280
  // internally (async HTTP). Cast to capture the result for awaiting.
216
- const result = stream.emit(text);
281
+ const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
217
282
  streamHasContent = true;
218
283
  if (result && typeof result.then === "function") {
219
284
  await result;
@@ -269,11 +334,49 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
269
334
  // emitted text into a single streaming message (cumulative), so every
270
335
  // periodic flush appends to the same response — not separate messages.
271
336
  // No preemptive splitting — sends full text. Error recovery happens in flush().
337
+ // Hybrid MIN_INITIAL_CHARS: hold back until >= MIN_INITIAL_CHARS accumulated
338
+ // before the first content emit, so phrase rotation shows while real content
339
+ // buffers. After first emit, flush normally (no threshold).
272
340
  function flushTextBuffer() {
273
341
  if (!textBuffer)
274
342
  return;
343
+ if (!firstContentEmitted && textBuffer.length < MIN_INITIAL_CHARS)
344
+ return;
345
+ // Proactive >4000 finalization: if cumulative emitted + buffer >= RECOVERY_CHUNK_SIZE,
346
+ // finalize the stream and send overflow via safeSend (follow-up message).
347
+ if (!streamFinalized && totalEmitted + textBuffer.length >= RECOVERY_CHUNK_SIZE) {
348
+ const remaining = RECOVERY_CHUNK_SIZE - totalEmitted;
349
+ /* v8 ignore next 2 -- defensive: remaining always > 0 because finalization runs once @preserve */
350
+ if (remaining > 0)
351
+ safeEmit(textBuffer.slice(0, remaining));
352
+ try {
353
+ stream.close();
354
+ }
355
+ catch { /* stream may already be dead */ }
356
+ streamFinalized = true;
357
+ /* v8 ignore next -- defensive ternary: remaining always > 0 at first finalization @preserve */
358
+ const overflow = textBuffer.slice(remaining > 0 ? remaining : 0);
359
+ textBuffer = "";
360
+ if (overflow)
361
+ safeSend(overflow);
362
+ if (!firstContentEmitted) {
363
+ firstContentEmitted = true;
364
+ stopPhraseRotation();
365
+ }
366
+ return;
367
+ }
368
+ if (streamFinalized) {
369
+ // After finalization, all content goes to safeSend (follow-up messages)
370
+ safeSend(textBuffer);
371
+ textBuffer = "";
372
+ return;
373
+ }
275
374
  safeEmit(textBuffer);
276
375
  textBuffer = "";
376
+ if (!firstContentEmitted) {
377
+ firstContentEmitted = true;
378
+ stopPhraseRotation();
379
+ }
277
380
  }
278
381
  function startPhraseRotation(pool) {
279
382
  stopPhraseRotation();
@@ -316,32 +419,45 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
316
419
  onTextChunk: (text) => {
317
420
  if (stopped)
318
421
  return;
319
- stopPhraseRotation();
422
+ // Don't stop phrase rotation here — let it continue until first content
423
+ // emit (handled in flushTextBuffer when MIN_INITIAL_CHARS threshold met).
320
424
  textBuffer += text;
321
425
  startFlushTimer();
322
426
  },
323
427
  onClearText: () => {
324
428
  textBuffer = "";
325
429
  },
326
- onToolStart: (name, args) => {
327
- stopPhraseRotation();
328
- flushTextBuffer();
329
- // Emit a placeholder to satisfy the 15s Copilot timeout for initial
330
- // stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
331
- // never emit before the timeout and the user sees "this response was
332
- // stopped". The placeholder is replaced by actual content on next emit.
333
- // https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
334
- if (!streamHasContent)
335
- safeEmit("⏳");
336
- const argSummary = (0, tools_1.summarizeArgs)(name, args) || Object.keys(args).join(", ");
337
- safeUpdate(`running ${name} (${argSummary})...`);
338
- hadToolRun = true;
339
- },
340
- onToolEnd: (name, summary, success) => {
341
- stopPhraseRotation();
342
- const msg = (0, format_1.formatToolResult)(name, summary, success);
343
- safeUpdate(msg);
344
- },
430
+ ...(() => {
431
+ const toolCbs = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
432
+ onDescription: (text) => safeUpdate(text),
433
+ /* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
434
+ onResult: (text) => safeUpdate(text),
435
+ /* v8 ignore next -- onFailure tested via onToolEnd failure test @preserve */
436
+ onFailure: (text) => safeUpdate(text),
437
+ isDebug: commands_2.getDebugMode,
438
+ });
439
+ return {
440
+ onToolStart: (name, args) => {
441
+ stopPhraseRotation();
442
+ // Force-flush any accumulated text, bypassing MIN_INITIAL_CHARS threshold
443
+ firstContentEmitted = true;
444
+ flushTextBuffer();
445
+ // Emit a placeholder to satisfy the 15s Copilot timeout for initial
446
+ // stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
447
+ // never emit before the timeout and the user sees "this response was
448
+ // stopped". The placeholder is replaced by actual content on next emit.
449
+ // https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
450
+ if (!streamHasContent)
451
+ safeEmit("\u23f3");
452
+ toolCbs.onToolStart(name, args);
453
+ hadToolRun = true;
454
+ },
455
+ onToolEnd: (name, summary, success) => {
456
+ stopPhraseRotation();
457
+ toolCbs.onToolEnd(name, summary, success);
458
+ },
459
+ };
460
+ })(),
345
461
  onKick: () => {
346
462
  stopPhraseRotation();
347
463
  const msg = (0, format_1.formatKick)();
@@ -351,7 +467,11 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
351
467
  stopPhraseRotation();
352
468
  if (stopped)
353
469
  return;
354
- const msg = (0, format_1.formatError)(error);
470
+ const msg = (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
471
+ scope: "shared-work",
472
+ phase: "errored",
473
+ outcomeText: (0, format_1.formatError)(error),
474
+ }));
355
475
  if (severity === "transient") {
356
476
  safeUpdate(msg);
357
477
  }
@@ -359,38 +479,27 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
359
479
  safeSend(msg);
360
480
  }
361
481
  },
362
- onConfirmAction: options?.conversationId
363
- ? async (name, args) => {
364
- const convId = options.conversationId;
365
- const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
366
- safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
367
- return new Promise((resolve) => {
368
- _pendingConfirmations.set(convId, resolve);
369
- // Auto-deny after 2 minutes to prevent indefinite blocking
370
- // (e.g. when the stream dies and the user never sees the prompt).
371
- setTimeout(() => {
372
- if (_pendingConfirmations.has(convId)) {
373
- _pendingConfirmations.delete(convId);
374
- resolve("denied");
375
- }
376
- }, 120_000);
377
- });
378
- }
379
- : undefined,
380
482
  flush: async () => {
381
483
  stopFlushTimer();
484
+ stopPhraseRotation();
382
485
  if (textBuffer) {
486
+ // Bypass MIN_INITIAL_CHARS threshold — flush delivers all remaining content
487
+ firstContentEmitted = true;
383
488
  const text = textBuffer;
384
489
  textBuffer = "";
385
- if (!stopped) {
490
+ if (streamFinalized && sendMessage) {
491
+ // Stream already finalized (>4000 path) — send remaining content as follow-up
492
+ safeSend(text);
493
+ }
494
+ else if (!stopped) {
386
495
  // Stream is alive — await the emit so we can catch async 413/failure
387
496
  // and fall through to sendMessage recovery.
388
497
  const ok = await tryEmit(text);
389
498
  if (!ok)
390
499
  markStopped();
391
500
  }
392
- if (stopped && sendMessage) {
393
- // Stream is dead — fall back to sendMessage; split on failure as recovery.
501
+ if (stopped && !streamFinalized && sendMessage) {
502
+ // Stream is dead (not from finalization) — fall back to sendMessage; split on failure as recovery.
394
503
  try {
395
504
  await sendMessage(text);
396
505
  }
@@ -401,32 +510,12 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
401
510
  }
402
511
  }
403
512
  }
404
- else if (!streamHasContent) {
405
- safeEmit("(completed with tool calls only \u2014 no text response)");
513
+ else if (!streamHasContent && !options?.suppressEmptyStreamMessage) {
514
+ safeEmit("(completed with tool calls only no text response)");
406
515
  }
407
516
  },
408
517
  };
409
518
  }
410
- // Per-conversation pending confirmation resolvers.
411
- // When a mutate tool needs confirmation, the resolver is stored here.
412
- // The next message from the same conversation resolves it.
413
- const _pendingConfirmations = new Map();
414
- // Confirmation response words (case-insensitive)
415
- const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
416
- function resolvePendingConfirmation(convId, text) {
417
- const resolver = _pendingConfirmations.get(convId);
418
- if (!resolver)
419
- return false;
420
- _pendingConfirmations.delete(convId);
421
- const word = text.trim().toLowerCase();
422
- if (CONFIRM_WORDS.has(word)) {
423
- resolver("confirmed");
424
- }
425
- else {
426
- resolver("denied");
427
- }
428
- return true;
429
- }
430
519
  const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
431
520
  function teamsTurnKey(conversationId) {
432
521
  return `teams:${conversationId}`;
@@ -445,6 +534,7 @@ function createTeamsCommandRegistry() {
445
534
  (0, commands_1.registerDefaultCommands)(registry);
446
535
  return registry;
447
536
  }
537
+ /* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
448
538
  function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
449
539
  const parsed = (0, commands_1.parseSlashCommand)(text);
450
540
  if (!parsed)
@@ -468,15 +558,19 @@ function handleTeamsSlashCommand(text, registry, friendId, conversationId, strea
468
558
  }
469
559
  return null;
470
560
  }
561
+ /* v8 ignore stop */
471
562
  // Handle an incoming Teams message
472
- async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
563
+ async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides) {
473
564
  const turnKey = teamsTurnKey(conversationId);
474
565
  // NOTE: Confirmation resolution is handled in the app.on("message") handler
475
566
  // BEFORE the conversation lock. By the time we get here, any pending
476
567
  // confirmation has already been resolved and the reply consumed.
477
568
  // Send first thinking phrase immediately so the user sees feedback
478
569
  // before sync I/O (session load, trim) blocks the event loop.
479
- stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
570
+ // Skip for reaction signals — they should be processed quietly.
571
+ if (!reactionOverrides) {
572
+ stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
573
+ }
480
574
  await new Promise(r => setImmediate(r));
481
575
  // Resolve identity provider early for friend resolution + slash command session path
482
576
  const store = getFriendStore();
@@ -493,15 +587,10 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
493
587
  // Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
494
588
  const resolvedContext = await resolver.resolve();
495
589
  const friendId = resolvedContext.friend.id;
496
- const registry = createTeamsCommandRegistry();
497
- // Check for slash commands (before pipeline -- these are transport-level concerns)
498
- if (handleTeamsSlashCommand(text, registry, friendId, conversationId, stream)) {
499
- return;
500
- }
501
590
  // ── Teams adapter concerns: controller, callbacks, session path ──────────
502
591
  const controller = new AbortController();
503
592
  const channelConfig = (0, config_2.getTeamsChannelConfig)();
504
- const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs });
593
+ const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
505
594
  const traceId = (0, nerves_1.createTraceId)();
506
595
  const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
507
596
  const teamsCapabilities = (0, channel_1.getChannelCapabilities)("teams");
@@ -512,41 +601,68 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
512
601
  adoToken: teamsContext.adoToken,
513
602
  githubToken: teamsContext.githubToken,
514
603
  signin: teamsContext.signin,
515
- summarize: (0, core_1.createSummarize)(),
604
+ summarize: (0, core_1.createSummarize)("human"),
516
605
  tenantId: teamsContext.tenantId,
517
606
  botApi: teamsContext.botApi,
518
607
  } : {};
519
608
  let currentText = text;
609
+ const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
520
610
  while (true) {
521
611
  let drainedSteeringFollowUps = [];
522
612
  // Build runAgentOptions with Teams-specific fields
523
613
  const agentOptions = {
524
614
  traceId,
525
615
  toolContext: teamsToolContext,
616
+ mcpManager,
526
617
  drainSteeringFollowUps: () => {
527
618
  drainedSteeringFollowUps = _turnCoordinator.drainFollowUps(turnKey)
528
619
  .map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
529
620
  return drainedSteeringFollowUps;
530
621
  },
622
+ ...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
531
623
  };
532
- if (channelConfig.skipConfirmation)
533
- agentOptions.skipConfirmation = true;
534
624
  // ── Call shared pipeline ──────────────────────────────────────────
625
+ // Capture terminal errors — failover message replaces the error card if it triggers
626
+ let capturedTerminalError = null;
627
+ const teamsFailoverState = (() => {
628
+ if (!teamsFailoverStates.has(conversationId)) {
629
+ teamsFailoverStates.set(conversationId, { pending: null });
630
+ }
631
+ return teamsFailoverStates.get(conversationId);
632
+ })();
633
+ /* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
634
+ const failoverAwareCallbacks = {
635
+ ...callbacks,
636
+ onError: (error, severity) => {
637
+ if (severity === "terminal" && teamsFailoverState) {
638
+ capturedTerminalError = error;
639
+ return;
640
+ }
641
+ callbacks.onError(error, severity);
642
+ },
643
+ };
644
+ /* v8 ignore stop */
535
645
  const result = await (0, pipeline_1.handleInboundTurn)({
536
646
  channel: "teams",
647
+ sessionKey: conversationId,
537
648
  capabilities: teamsCapabilities,
538
649
  messages: [{ role: "user", content: currentText }],
539
650
  continuityIngressTexts: [currentText],
540
- callbacks,
651
+ callbacks: failoverAwareCallbacks,
541
652
  friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
542
653
  sessionLoader: {
543
654
  loadOrCreate: async () => {
544
655
  const existing = (0, context_1.loadSession)(sessPath);
545
656
  const messages = existing?.messages && existing.messages.length > 0
546
657
  ? existing.messages
547
- : [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", undefined, resolvedContext) }];
658
+ : [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("teams", {}, resolvedContext)) }];
548
659
  (0, core_1.repairOrphanedToolCalls)(messages);
549
- return { messages, sessionPath: sessPath, state: existing?.state };
660
+ return {
661
+ messages,
662
+ sessionPath: sessPath,
663
+ state: existing?.state,
664
+ events: existing?.events,
665
+ };
550
666
  },
551
667
  },
552
668
  pendingDir,
@@ -559,6 +675,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
559
675
  hasExistingGroupWithFamily: false,
560
676
  enforceTrustGate: trust_gate_1.enforceTrustGate,
561
677
  drainPending: pending_1.drainPending,
678
+ drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)((0, identity_1.getAgentName)(), deferredFriendId),
562
679
  runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
563
680
  ...opts,
564
681
  toolContext: {
@@ -568,11 +685,33 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
568
685
  summarize: teamsToolContext.summarize,
569
686
  },
570
687
  }),
571
- postTurn: context_1.postTurn,
688
+ postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
689
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
690
+ (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
691
+ },
572
692
  accumulateFriendTokens: tokens_1.accumulateFriendTokens,
573
693
  signal: controller.signal,
574
694
  runAgentOptions: agentOptions,
695
+ failoverState: teamsFailoverState,
575
696
  });
697
+ // ── Handle pipeline-intercepted commands ────────────────────────
698
+ if (result.turnOutcome === "command") {
699
+ if (result.commandAction === "new") {
700
+ (0, context_1.deleteSession)(sessPath);
701
+ stream.emit("session cleared");
702
+ }
703
+ // For "response" commands: pipeline already emitted the response via onTextChunk
704
+ await callbacks.flush();
705
+ return;
706
+ }
707
+ /* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
708
+ if (result.failoverMessage) {
709
+ stream.emit(result.failoverMessage);
710
+ }
711
+ else if (capturedTerminalError) {
712
+ callbacks.onError(capturedTerminalError, "terminal");
713
+ }
714
+ /* v8 ignore stop */
576
715
  // ── Handle gate result ────────────────────────────────────────
577
716
  if (!result.gateResult.allowed) {
578
717
  if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
@@ -612,7 +751,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
612
751
  currentText = replayTail;
613
752
  continue;
614
753
  }
615
- if (handleTeamsSlashCommand(supersedingFollowUp.text, registry, friendId, conversationId, stream, false)) {
754
+ if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
616
755
  return;
617
756
  }
618
757
  currentText = supersedingFollowUp.text;
@@ -692,7 +831,7 @@ function registerBotHandlers(app, label) {
692
831
  // (graph + ado + github). The verifyState activity only carries a `state`
693
832
  // code with no connectionName, so we try each configured connection until
694
833
  // one succeeds.
695
- app.on("signin.verify-state", async (ctx) => {
834
+ app.on("signin.verify-state", (async (ctx) => {
696
835
  const { api, activity } = ctx;
697
836
  if (!activity.value?.state)
698
837
  return { status: 404 };
@@ -711,7 +850,73 @@ function registerBotHandlers(app, label) {
711
850
  }
712
851
  (0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state failed for all connections`, meta: {} });
713
852
  return { status: 412 };
853
+ }));
854
+ // Handle Teams feedback reactions (thumbs up/down on AI-generated messages).
855
+ // SDK routes message/submitAction with actionName "feedback" to this event.
856
+ /* v8 ignore start -- Teams SDK invoke handler; requires live SDK context @preserve */
857
+ app.on("message.submit.feedback", async (ctx) => {
858
+ const { stream, activity } = ctx;
859
+ const reaction = activity.value?.actionValue?.reaction;
860
+ const comment = activity.value?.actionValue?.feedback;
861
+ const convId = activity.conversation?.id || "unknown";
862
+ const turnKey = teamsTurnKey(convId);
863
+ // Validate payload — graceful no-op for malformed invocations
864
+ if (activity.value?.actionName !== "feedback" || !reaction) {
865
+ return;
866
+ }
867
+ const syntheticText = buildFeedbackSyntheticText(reaction, comment);
868
+ // Turn coordination: if a turn is active, enqueue as steering follow-up
869
+ if (!_turnCoordinator.tryBeginTurn(turnKey)) {
870
+ _turnCoordinator.enqueueFollowUp(turnKey, {
871
+ conversationId: convId,
872
+ text: syntheticText,
873
+ receivedAt: Date.now(),
874
+ effect: (0, continuity_1.classifySteeringFollowUpEffect)(syntheticText),
875
+ });
876
+ return;
877
+ }
878
+ try {
879
+ const teamsContext = {
880
+ signin: async () => undefined,
881
+ aadObjectId: activity.from?.aadObjectId,
882
+ tenantId: activity.conversation?.tenantId,
883
+ displayName: activity.from?.name,
884
+ };
885
+ const ctxSend = async (t) => {
886
+ await ctx.send({ type: "message", text: t, replyToId: activity.replyToId, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
887
+ };
888
+ await handleTeamsMessage(syntheticText, stream, convId, teamsContext, ctxSend, { isReactionSignal: true, suppressEmptyStreamMessage: true });
889
+ }
890
+ catch (err) {
891
+ const msg = err instanceof Error ? err.message : String(err);
892
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.feedback_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
893
+ }
894
+ finally {
895
+ _turnCoordinator.endTurn(turnKey);
896
+ }
714
897
  });
898
+ /* v8 ignore stop */
899
+ // Handle bot install — send welcome Adaptive Card with prompt starters.
900
+ /* v8 ignore start -- Teams SDK install handler; requires live SDK context @preserve */
901
+ app.on("install.add", async (ctx) => {
902
+ try {
903
+ const card = buildWelcomeCard();
904
+ await ctx.send({
905
+ type: "message",
906
+ attachments: [{
907
+ contentType: "application/vnd.microsoft.card.adaptive",
908
+ content: card,
909
+ }],
910
+ entities: aiLabelEntities(),
911
+ channelData: { feedbackLoopEnabled: true },
912
+ });
913
+ }
914
+ catch (err) {
915
+ const msg = err instanceof Error ? err.message : String(err);
916
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.welcome_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
917
+ }
918
+ });
919
+ /* v8 ignore stop */
715
920
  app.on("message", async (ctx) => {
716
921
  const { stream, activity, api, signin } = ctx;
717
922
  const text = activity.text || "";
@@ -720,13 +925,6 @@ function registerBotHandlers(app, label) {
720
925
  const userId = activity.from?.id || "";
721
926
  const channelId = activity.channelId || "msteams";
722
927
  (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) } });
723
- // Resolve pending confirmations IMMEDIATELY — before token fetches or
724
- // the conversation lock. The original message holds the lock while
725
- // awaiting confirmation, so acquiring it here would deadlock. Token
726
- // fetches are also unnecessary (and slow) for a simple yes/no reply.
727
- if (resolvePendingConfirmation(convId, text)) {
728
- return;
729
- }
730
928
  const commandRegistry = createTeamsCommandRegistry();
731
929
  const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
732
930
  if (parsedSlashCommand) {
@@ -827,7 +1025,7 @@ function registerBotHandlers(app, label) {
827
1025
  const ctxSend = async (t) => {
828
1026
  // Use send with replyToId (not reply, which adds a blockquote).
829
1027
  // replyToId anchors the message after the user's message in Copilot Chat.
830
- await ctx.send({ type: "message", text: t, replyToId: activity.id });
1028
+ await ctx.send({ type: "message", text: t, replyToId: activity.id, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
831
1029
  };
832
1030
  await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
833
1031
  }
@@ -852,6 +1050,108 @@ function findAadObjectId(friend) {
852
1050
  }
853
1051
  return undefined;
854
1052
  }
1053
+ function resolveTeamsFriendStore(deps) {
1054
+ return deps.store
1055
+ ?? deps.createFriendStore?.()
1056
+ ?? new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
1057
+ }
1058
+ function getTeamsConversations(botApi) {
1059
+ return botApi.conversations;
1060
+ }
1061
+ function hasExplicitCrossChatAuthorization(params) {
1062
+ return params.intent === "explicit_cross_chat"
1063
+ && types_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
1064
+ }
1065
+ async function sendProactiveTeamsMessageToSession(params, deps) {
1066
+ const store = resolveTeamsFriendStore(deps);
1067
+ const conversations = getTeamsConversations(deps.botApi);
1068
+ let friend;
1069
+ try {
1070
+ friend = await store.get(params.friendId);
1071
+ }
1072
+ catch {
1073
+ friend = null;
1074
+ }
1075
+ if (!friend) {
1076
+ (0, runtime_1.emitNervesEvent)({
1077
+ level: "warn",
1078
+ component: "senses",
1079
+ event: "senses.teams_proactive_no_friend",
1080
+ message: "proactive send skipped: friend not found",
1081
+ meta: { friendId: params.friendId, sessionKey: params.sessionKey },
1082
+ });
1083
+ return { delivered: false, reason: "friend_not_found" };
1084
+ }
1085
+ if (!hasExplicitCrossChatAuthorization(params) && !types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
1086
+ (0, runtime_1.emitNervesEvent)({
1087
+ component: "senses",
1088
+ event: "senses.teams_proactive_trust_skip",
1089
+ message: "proactive send skipped: trust level not allowed",
1090
+ meta: {
1091
+ friendId: params.friendId,
1092
+ trustLevel: friend.trustLevel ?? "unknown",
1093
+ intent: params.intent ?? "generic_outreach",
1094
+ authorizingTrustLevel: params.authorizingSession?.trustLevel ?? null,
1095
+ },
1096
+ });
1097
+ return { delivered: false, reason: "trust_skip" };
1098
+ }
1099
+ const aadInfo = findAadObjectId(friend);
1100
+ if (!aadInfo) {
1101
+ (0, runtime_1.emitNervesEvent)({
1102
+ level: "warn",
1103
+ component: "senses",
1104
+ event: "senses.teams_proactive_no_aad_id",
1105
+ message: "proactive send skipped: no AAD object ID found",
1106
+ meta: { friendId: params.friendId, sessionKey: params.sessionKey },
1107
+ });
1108
+ return { delivered: false, reason: "missing_target" };
1109
+ }
1110
+ const internalContentBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(params.text);
1111
+ if (internalContentBlockReason) {
1112
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1113
+ friendId: params.friendId,
1114
+ sessionKey: params.sessionKey,
1115
+ reason: internalContentBlockReason,
1116
+ source: "session_send",
1117
+ });
1118
+ return { delivered: false, reason: "internal_content_blocked" };
1119
+ }
1120
+ try {
1121
+ const conversation = await conversations.create({
1122
+ bot: { id: deps.botApi.id },
1123
+ members: [{ id: aadInfo.aadObjectId, role: "user", name: friend.name || aadInfo.aadObjectId }],
1124
+ tenantId: aadInfo.tenantId,
1125
+ isGroup: false,
1126
+ });
1127
+ await conversations.activities(conversation.id).create({
1128
+ type: "message",
1129
+ text: params.text,
1130
+ });
1131
+ (0, runtime_1.emitNervesEvent)({
1132
+ component: "senses",
1133
+ event: "senses.teams_proactive_sent",
1134
+ message: "proactive teams message sent",
1135
+ meta: { friendId: params.friendId, aadObjectId: aadInfo.aadObjectId, sessionKey: params.sessionKey },
1136
+ });
1137
+ return { delivered: true };
1138
+ }
1139
+ catch (error) {
1140
+ (0, runtime_1.emitNervesEvent)({
1141
+ level: "error",
1142
+ component: "senses",
1143
+ event: "senses.teams_proactive_send_error",
1144
+ message: "proactive teams send failed",
1145
+ meta: {
1146
+ friendId: params.friendId,
1147
+ aadObjectId: aadInfo.aadObjectId,
1148
+ sessionKey: params.sessionKey,
1149
+ reason: error instanceof Error ? error.message : String(error),
1150
+ },
1151
+ });
1152
+ return { delivered: false, reason: "send_error" };
1153
+ }
1154
+ }
855
1155
  function scanPendingTeamsFiles(pendingRoot) {
856
1156
  const results = [];
857
1157
  let friendIds;
@@ -897,8 +1197,7 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
897
1197
  const root = pendingRoot ?? path.join((0, identity_1.getAgentRoot)(), "state", "pending");
898
1198
  const pendingFiles = scanPendingTeamsFiles(root);
899
1199
  const result = { sent: 0, skipped: 0, failed: 0 };
900
- const conversations = botApi.conversations;
901
- for (const { friendId, filePath, content } of pendingFiles) {
1200
+ for (const { friendId, key, filePath, content } of pendingFiles) {
902
1201
  let parsed;
903
1202
  try {
904
1203
  parsed = JSON.parse(content);
@@ -920,95 +1219,46 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
920
1219
  catch { /* ignore */ }
921
1220
  continue;
922
1221
  }
923
- let friend;
924
- try {
925
- friend = await store.get(friendId);
926
- }
927
- catch {
928
- friend = null;
929
- }
930
- if (!friend) {
1222
+ const internalBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(messageText);
1223
+ if (internalBlockReason) {
931
1224
  result.skipped++;
932
1225
  try {
933
1226
  fs.unlinkSync(filePath);
934
1227
  }
935
1228
  catch { /* ignore */ }
936
- (0, runtime_1.emitNervesEvent)({
937
- level: "warn",
938
- component: "senses",
939
- event: "senses.teams_proactive_no_friend",
940
- message: "proactive send skipped: friend not found",
941
- meta: { friendId },
1229
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1230
+ friendId,
1231
+ reason: internalBlockReason,
1232
+ source: "pending_drain",
942
1233
  });
943
1234
  continue;
944
1235
  }
945
- if (!types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
946
- result.skipped++;
1236
+ const sendResult = await sendProactiveTeamsMessageToSession({
1237
+ friendId,
1238
+ sessionKey: key,
1239
+ text: messageText,
1240
+ intent: "generic_outreach",
1241
+ }, {
1242
+ botApi,
1243
+ store,
1244
+ });
1245
+ if (sendResult.delivered) {
1246
+ result.sent++;
947
1247
  try {
948
1248
  fs.unlinkSync(filePath);
949
1249
  }
950
1250
  catch { /* ignore */ }
951
- (0, runtime_1.emitNervesEvent)({
952
- component: "senses",
953
- event: "senses.teams_proactive_trust_skip",
954
- message: "proactive send skipped: trust level not allowed",
955
- meta: { friendId, trustLevel: friend.trustLevel ?? "unknown" },
956
- });
957
1251
  continue;
958
1252
  }
959
- const aadInfo = findAadObjectId(friend);
960
- if (!aadInfo) {
1253
+ if (sendResult.reason === "friend_not_found" || sendResult.reason === "trust_skip" || sendResult.reason === "missing_target") {
961
1254
  result.skipped++;
962
1255
  try {
963
1256
  fs.unlinkSync(filePath);
964
1257
  }
965
1258
  catch { /* ignore */ }
966
- (0, runtime_1.emitNervesEvent)({
967
- level: "warn",
968
- component: "senses",
969
- event: "senses.teams_proactive_no_aad_id",
970
- message: "proactive send skipped: no AAD object ID found",
971
- meta: { friendId },
972
- });
973
1259
  continue;
974
1260
  }
975
- try {
976
- const conversation = await conversations.create({
977
- bot: { id: botApi.id },
978
- members: [{ id: aadInfo.aadObjectId, role: "user", name: friend.name || aadInfo.aadObjectId }],
979
- tenantId: aadInfo.tenantId,
980
- isGroup: false,
981
- });
982
- await conversations.activities(conversation.id).create({
983
- type: "message",
984
- text: messageText,
985
- });
986
- result.sent++;
987
- try {
988
- fs.unlinkSync(filePath);
989
- }
990
- catch { /* ignore */ }
991
- (0, runtime_1.emitNervesEvent)({
992
- component: "senses",
993
- event: "senses.teams_proactive_sent",
994
- message: "proactive teams message sent",
995
- meta: { friendId, aadObjectId: aadInfo.aadObjectId },
996
- });
997
- }
998
- catch (error) {
999
- result.failed++;
1000
- (0, runtime_1.emitNervesEvent)({
1001
- level: "error",
1002
- component: "senses",
1003
- event: "senses.teams_proactive_send_error",
1004
- message: "proactive teams send failed",
1005
- meta: {
1006
- friendId,
1007
- aadObjectId: aadInfo.aadObjectId,
1008
- reason: error instanceof Error ? error.message : String(error),
1009
- },
1010
- });
1011
- }
1261
+ result.failed++;
1012
1262
  }
1013
1263
  if (result.sent > 0 || result.skipped > 0 || result.failed > 0) {
1014
1264
  (0, runtime_1.emitNervesEvent)({