@ouro.bot/cli 0.1.0-alpha.96 → 0.1.0-alpha.961

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 (759) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +6091 -16
  16. package/deploy/unraid/Dockerfile +58 -0
  17. package/deploy/unraid/README.txt +2997 -0
  18. package/deploy/unraid/audit-container-spec.sh +4 -0
  19. package/deploy/unraid/container-runtime.json +1 -0
  20. package/deploy/unraid/credit-gateway.md +95 -0
  21. package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
  22. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  23. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  24. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  25. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  26. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  27. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  28. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  29. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  30. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  31. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  32. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  33. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  34. package/deploy/unraid/sanctuary-butler-upgrade.mjs +1274 -0
  35. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  36. package/deploy/unraid/sanctuary-replay-gate.mjs +1760 -0
  37. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  38. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2086 -0
  39. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  40. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  41. package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
  42. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  43. package/deploy/unraid/sanctuary.ouro/awaiting/house-care-sweep.md +17 -0
  44. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  45. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  46. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +2245 -0
  47. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  48. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  49. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  50. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +33 -0
  51. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +27 -0
  52. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +22 -0
  53. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  54. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +232 -0
  55. package/deploy/unraid/sanctuary.xml +30 -0
  56. package/dist/a2a/card.js +57 -0
  57. package/dist/a2a/client.js +250 -0
  58. package/dist/a2a/config.js +50 -0
  59. package/dist/a2a/delegated-command-grants.js +93 -0
  60. package/dist/a2a/delegated-command.js +197 -0
  61. package/dist/a2a/delegation-stores.js +56 -0
  62. package/dist/a2a/did-resolution.js +93 -0
  63. package/dist/a2a/escalation-grants.js +84 -0
  64. package/dist/a2a/escalation-wiring.js +60 -0
  65. package/dist/a2a/identity.js +258 -0
  66. package/dist/a2a/inbound-share.js +110 -0
  67. package/dist/a2a/mission-result-wire.js +196 -0
  68. package/dist/a2a/onboarding.js +133 -0
  69. package/dist/a2a/operator-trust.js +399 -0
  70. package/dist/a2a/outbox-client.js +57 -0
  71. package/dist/a2a/outbox-store.js +188 -0
  72. package/dist/a2a/outbox-wire.js +95 -0
  73. package/dist/a2a/pin-store.js +132 -0
  74. package/dist/a2a/replay-grant-holders.js +33 -0
  75. package/dist/a2a/replay-harness.js +188 -0
  76. package/dist/a2a/resolution-proof.js +53 -0
  77. package/dist/a2a/sealed-chat.js +113 -0
  78. package/dist/a2a/seen-ledger.js +120 -0
  79. package/dist/a2a/server.js +771 -0
  80. package/dist/a2a/task-store.js +69 -0
  81. package/dist/a2a/transport.js +45 -0
  82. package/dist/a2a/trust-commands.js +203 -0
  83. package/dist/a2a/trusted-files.js +185 -0
  84. package/dist/a2a/types.js +3 -0
  85. package/dist/arc/attention-types.js +8 -0
  86. package/dist/arc/cares.js +360 -0
  87. package/dist/arc/episodes.js +118 -0
  88. package/dist/arc/evolution.js +490 -0
  89. package/dist/arc/flight-recorder.js +1034 -0
  90. package/dist/arc/intentions.js +134 -0
  91. package/dist/arc/json-store.js +117 -0
  92. package/dist/arc/obligations.js +386 -0
  93. package/dist/arc/packets.js +336 -0
  94. package/dist/arc/presence.js +185 -0
  95. package/dist/arc/task-lifecycle.js +57 -0
  96. package/dist/commerce/store.js +755 -0
  97. package/dist/commerce/types.js +3 -0
  98. package/dist/gateway/keys.js +117 -0
  99. package/dist/gateway/server.js +429 -0
  100. package/dist/heart/active-work.js +798 -40
  101. package/dist/heart/agent-entry.js +252 -3
  102. package/dist/heart/approval-files.js +113 -0
  103. package/dist/heart/approval-store.js +960 -0
  104. package/dist/heart/attachments/image-normalize.js +201 -0
  105. package/dist/heart/attachments/materialize.js +97 -0
  106. package/dist/heart/attachments/originals.js +88 -0
  107. package/dist/heart/attachments/render.js +29 -0
  108. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  109. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  110. package/dist/heart/attachments/sources/index.js +18 -0
  111. package/dist/heart/attachments/sources/telegram.js +71 -0
  112. package/dist/heart/attachments/store.js +132 -0
  113. package/dist/heart/attachments/types.js +93 -0
  114. package/dist/heart/auth/auth-flow.js +511 -0
  115. package/dist/heart/autonomy-budget.js +478 -0
  116. package/dist/heart/awaiting/a2a-await-delivery.js +109 -0
  117. package/dist/heart/awaiting/await-alert.js +152 -0
  118. package/dist/heart/awaiting/await-expiry.js +143 -0
  119. package/dist/heart/awaiting/await-loader.js +91 -0
  120. package/dist/heart/awaiting/await-parser.js +152 -0
  121. package/dist/heart/awaiting/await-runtime-state.js +111 -0
  122. package/dist/heart/awaiting/await-scheduler.js +391 -0
  123. package/dist/heart/background-operations.js +281 -0
  124. package/dist/heart/bluebubbles-host-account.js +78 -0
  125. package/dist/heart/bridges/manager.js +137 -17
  126. package/dist/heart/bridges/store.js +14 -2
  127. package/dist/heart/bundle-state.js +168 -0
  128. package/dist/heart/commitments.js +44 -18
  129. package/dist/heart/config-registry.js +331 -0
  130. package/dist/heart/config.js +178 -131
  131. package/dist/heart/context-loss-gauntlet.js +387 -0
  132. package/dist/heart/context-loss-sentinel.js +1159 -0
  133. package/dist/heart/core.js +2377 -586
  134. package/dist/heart/cross-chat-delivery.js +9 -22
  135. package/dist/heart/daemon/agent-config-check.js +474 -0
  136. package/dist/heart/daemon/agent-discovery.js +283 -3
  137. package/dist/heart/daemon/agent-home.js +319 -0
  138. package/dist/heart/daemon/agent-service.js +542 -0
  139. package/dist/heart/daemon/agentic-repair.js +547 -0
  140. package/dist/heart/daemon/await-private-wake.js +59 -0
  141. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  142. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  143. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  144. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  145. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  146. package/dist/heart/daemon/cadence.js +180 -0
  147. package/dist/heart/daemon/cli-arg-redaction.js +26 -0
  148. package/dist/heart/daemon/cli-defaults.js +881 -0
  149. package/dist/heart/daemon/cli-desk.js +322 -0
  150. package/dist/heart/daemon/cli-exec.js +9260 -0
  151. package/dist/heart/daemon/cli-help.js +754 -0
  152. package/dist/heart/daemon/cli-parse.js +3319 -0
  153. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  154. package/dist/heart/daemon/cli-render.js +819 -0
  155. package/dist/heart/daemon/cli-types.js +8 -0
  156. package/dist/heart/daemon/connect-bay.js +323 -0
  157. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  158. package/dist/heart/daemon/container-healthcheck.js +112 -0
  159. package/dist/heart/daemon/container-runtime.js +97 -0
  160. package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
  161. package/dist/heart/daemon/container-spec-auditor.js +235 -0
  162. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  163. package/dist/heart/daemon/daemon-cli.js +29 -2360
  164. package/dist/heart/daemon/daemon-entry.js +953 -11
  165. package/dist/heart/daemon/daemon-health.js +186 -0
  166. package/dist/heart/daemon/daemon-rollup.js +57 -0
  167. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  168. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  169. package/dist/heart/daemon/daemon.js +2110 -104
  170. package/dist/heart/daemon/dns-workflow.js +394 -0
  171. package/dist/heart/daemon/doctor-types.js +8 -0
  172. package/dist/heart/daemon/doctor.js +1766 -0
  173. package/dist/heart/daemon/freshness.js +345 -0
  174. package/dist/heart/daemon/habit-private-wake.js +61 -0
  175. package/dist/heart/daemon/health-monitor.js +154 -11
  176. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  177. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  178. package/dist/heart/daemon/http-health-probe.js +80 -0
  179. package/dist/heart/daemon/human-command-screens.js +234 -0
  180. package/dist/heart/daemon/human-readiness.js +114 -0
  181. package/dist/heart/daemon/inner-status.js +78 -0
  182. package/dist/heart/daemon/interactive-repair.js +394 -0
  183. package/dist/heart/daemon/launchd.js +37 -8
  184. package/dist/heart/daemon/log-tailer.js +79 -10
  185. package/dist/heart/daemon/logs-prune.js +132 -0
  186. package/dist/heart/daemon/mcp-canary.js +524 -0
  187. package/dist/heart/daemon/message-router.js +65 -5
  188. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  189. package/dist/heart/daemon/os-cron-deps.js +135 -0
  190. package/dist/heart/daemon/os-cron.js +22 -14
  191. package/dist/heart/daemon/ouro-bot-entry.js +6 -3
  192. package/dist/heart/daemon/ouro-entry.js +9 -3
  193. package/dist/heart/daemon/plugin-cli.js +432 -0
  194. package/dist/heart/daemon/process-manager.js +760 -61
  195. package/dist/heart/daemon/provider-discovery.js +137 -0
  196. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  197. package/dist/heart/daemon/prunable-bundle.js +144 -0
  198. package/dist/heart/daemon/pulse.js +511 -0
  199. package/dist/heart/daemon/readiness-repair.js +365 -0
  200. package/dist/heart/daemon/run-hooks.js +2 -0
  201. package/dist/heart/daemon/runtime-logging.js +47 -14
  202. package/dist/heart/daemon/runtime-metadata.js +2 -30
  203. package/dist/heart/daemon/runtime-mode.js +13 -2
  204. package/dist/heart/daemon/safe-mode.js +161 -0
  205. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
  206. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
  207. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  208. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
  209. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  210. package/dist/heart/daemon/sanctuary-authority-epoch.js +281 -0
  211. package/dist/heart/daemon/sanctuary-authority-installation.js +161 -0
  212. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  213. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +1282 -0
  214. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  215. package/dist/heart/daemon/sanctuary-bundle-migration.js +730 -0
  216. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  217. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +493 -0
  218. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  219. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  220. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  221. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  222. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  223. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  224. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  225. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +475 -0
  226. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  227. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +676 -0
  228. package/dist/heart/daemon/sense-manager.js +786 -48
  229. package/dist/heart/daemon/session-id-resolver.js +131 -0
  230. package/dist/heart/daemon/skill-management-installer.js +1 -1
  231. package/dist/heart/daemon/socket-client.js +180 -12
  232. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  233. package/dist/heart/daemon/startup-tui.js +339 -0
  234. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  235. package/dist/heart/daemon/task-scheduler.js +117 -39
  236. package/dist/heart/daemon/terminal-ui.js +503 -0
  237. package/dist/heart/daemon/thoughts.js +138 -54
  238. package/dist/heart/daemon/up-progress.js +366 -0
  239. package/dist/heart/daemon/vault-items.js +56 -0
  240. package/dist/heart/delegation.js +1 -4
  241. package/dist/heart/external-events/router.js +1531 -0
  242. package/dist/heart/failure-reports.js +336 -0
  243. package/dist/heart/file-lock.js +109 -0
  244. package/dist/heart/frontend-approval-runtime.js +506 -0
  245. package/dist/heart/frontend-journal.js +215 -0
  246. package/dist/heart/frontend-session-service.js +271 -0
  247. package/dist/heart/frontend-socket-client.js +173 -0
  248. package/dist/heart/frontend-socket.js +434 -0
  249. package/dist/heart/frontend-turn-errors.js +20 -0
  250. package/dist/heart/habits/habit-cancel.js +810 -0
  251. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  252. package/dist/heart/habits/habit-migration.js +194 -0
  253. package/dist/heart/habits/habit-parser.js +278 -0
  254. package/dist/heart/habits/habit-runtime-state.js +93 -0
  255. package/dist/heart/habits/habit-scheduler.js +653 -0
  256. package/dist/heart/habits/habit-session-summary.js +318 -0
  257. package/dist/heart/habits/habit-session.js +636 -0
  258. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  259. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  260. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  261. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  262. package/dist/heart/identity.js +239 -59
  263. package/dist/heart/kept-notes.js +289 -0
  264. package/dist/heart/kicks.js +1 -1
  265. package/dist/heart/machine-identity.js +175 -0
  266. package/dist/heart/mail-import-discovery.js +356 -0
  267. package/dist/heart/mailbox/mailbox-http-hooks.js +99 -0
  268. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  269. package/dist/heart/mailbox/mailbox-http-routes.js +406 -0
  270. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  271. package/dist/heart/mailbox/mailbox-http-transport.js +272 -0
  272. package/dist/heart/mailbox/mailbox-http.js +99 -0
  273. package/dist/heart/mailbox/mailbox-read.js +41 -0
  274. package/dist/heart/mailbox/mailbox-supervision.js +131 -0
  275. package/dist/heart/mailbox/mailbox-types.js +27 -0
  276. package/dist/heart/mailbox/mailbox-view.js +197 -0
  277. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  278. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  279. package/dist/heart/mailbox/readers/mail.js +375 -0
  280. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  281. package/dist/heart/mailbox/readers/sessions.js +231 -0
  282. package/dist/heart/mailbox/readers/shared.js +111 -0
  283. package/dist/heart/mcp/mcp-server.js +760 -0
  284. package/dist/heart/migrate-config.js +100 -0
  285. package/dist/heart/model-capabilities.js +83 -16
  286. package/dist/heart/orientation-frame.js +345 -0
  287. package/dist/heart/platform.js +81 -0
  288. package/dist/heart/primitives.js +1 -1
  289. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  290. package/dist/heart/private-runtime/index.js +18 -0
  291. package/dist/heart/private-runtime/ledger.js +221 -0
  292. package/dist/heart/private-runtime/policy.js +260 -0
  293. package/dist/heart/private-runtime/types.js +2 -0
  294. package/dist/heart/progress-story.js +1 -1
  295. package/dist/heart/provider-attempt.js +137 -0
  296. package/dist/heart/provider-binding-resolver.js +273 -0
  297. package/dist/heart/provider-credentials.js +449 -0
  298. package/dist/heart/provider-failover.js +311 -0
  299. package/dist/heart/provider-models.js +96 -0
  300. package/dist/heart/provider-ping.js +266 -0
  301. package/dist/heart/provider-readiness-cache.js +119 -0
  302. package/dist/heart/provider-visibility.js +188 -0
  303. package/dist/heart/providers/anthropic-token.js +131 -0
  304. package/dist/heart/providers/anthropic.js +248 -67
  305. package/dist/heart/providers/azure.js +23 -11
  306. package/dist/heart/providers/error-classification.js +129 -0
  307. package/dist/heart/providers/github-copilot.js +38 -39
  308. package/dist/heart/providers/minimax-vlm.js +189 -0
  309. package/dist/heart/providers/minimax.js +26 -8
  310. package/dist/heart/providers/openai-codex-token.js +349 -0
  311. package/dist/heart/providers/openai-codex.js +55 -40
  312. package/dist/heart/providers/openai-compatible.js +177 -0
  313. package/dist/heart/reach-channel.js +85 -0
  314. package/dist/heart/reply-style.js +52 -0
  315. package/dist/heart/run-ledger.js +247 -0
  316. package/dist/heart/runtime-capability-check.js +170 -0
  317. package/dist/heart/runtime-credentials.js +608 -0
  318. package/dist/heart/runtime-cwd.js +87 -0
  319. package/dist/heart/sense-truth.js +24 -4
  320. package/dist/heart/session-activity.js +94 -24
  321. package/dist/heart/session-events.js +1521 -0
  322. package/dist/heart/session-playback-cli-main.js +5 -0
  323. package/dist/heart/session-playback-cli.js +36 -0
  324. package/dist/heart/session-playback.js +231 -0
  325. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  326. package/dist/heart/session-redaction-repair.js +552 -0
  327. package/dist/heart/session-stats-cli-main.js +5 -0
  328. package/dist/heart/session-stats.js +183 -0
  329. package/dist/heart/session-transcript.js +133 -0
  330. package/dist/heart/shelf-claims.js +78 -0
  331. package/dist/heart/shepherd/custody-types.js +2 -0
  332. package/dist/heart/shepherd/mandates.js +352 -0
  333. package/dist/heart/shepherd/operation-commands.js +19 -0
  334. package/dist/heart/shepherd/operations.js +135 -0
  335. package/dist/heart/shepherd/supervision.js +114 -0
  336. package/dist/heart/shepherd/types.js +2 -0
  337. package/dist/heart/shepherd-config.js +123 -0
  338. package/dist/heart/source-grounding.js +302 -0
  339. package/dist/heart/start-of-turn-packet.js +372 -0
  340. package/dist/heart/steward-policy.js +656 -0
  341. package/dist/heart/streaming.js +631 -207
  342. package/dist/heart/structured-output.js +196 -0
  343. package/dist/heart/sync-classification.js +176 -0
  344. package/dist/heart/sync-health.js +152 -0
  345. package/dist/heart/sync.js +726 -0
  346. package/dist/heart/target-resolution.js +11 -7
  347. package/dist/heart/tempo.js +93 -0
  348. package/dist/heart/temporal-view.js +41 -0
  349. package/dist/heart/timeouts.js +101 -0
  350. package/dist/heart/tool-activity-callbacks.js +59 -0
  351. package/dist/heart/tool-approval.js +382 -0
  352. package/dist/heart/tool-description.js +155 -0
  353. package/dist/heart/tool-friction.js +55 -0
  354. package/dist/heart/tool-loop.js +206 -0
  355. package/dist/heart/turn-context.js +496 -0
  356. package/dist/heart/turn-execution-lease.js +30 -0
  357. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  358. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +3 -2
  359. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  360. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  361. package/dist/heart/versioning/ouro-version-manager.js +432 -0
  362. package/dist/heart/versioning/release-sources.js +173 -0
  363. package/dist/heart/versioning/self-update-defaults.js +294 -0
  364. package/dist/heart/versioning/self-update.js +196 -0
  365. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  366. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  367. package/dist/heart/versioning/update-hooks.js +154 -0
  368. package/dist/heart/versioning/update-state.js +126 -0
  369. package/dist/heart/versioning/updater-launchd.js +150 -0
  370. package/dist/heart/versioning/version-intent.js +114 -0
  371. package/dist/heart/work-card.js +380 -0
  372. package/dist/mailbox-ui/assets/index-Dbska1tZ.js +1 -0
  373. package/dist/mailbox-ui/assets/index-DsiKaPyH.css +1 -0
  374. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  375. package/dist/mailbox-ui/index.html +16 -0
  376. package/dist/mailroom/attention.js +167 -0
  377. package/dist/mailroom/autonomy.js +209 -0
  378. package/dist/mailroom/blob-store.js +889 -0
  379. package/dist/mailroom/body-cache.js +61 -0
  380. package/dist/mailroom/cache-sync-cli.js +58 -0
  381. package/dist/mailroom/core.js +803 -0
  382. package/dist/mailroom/entry.js +160 -0
  383. package/dist/mailroom/file-store.js +568 -0
  384. package/dist/mailroom/hosted-cache-sync.js +384 -0
  385. package/dist/mailroom/mbox-import.js +393 -0
  386. package/dist/mailroom/migration.js +164 -0
  387. package/dist/mailroom/outbound.js +380 -0
  388. package/dist/mailroom/policy.js +263 -0
  389. package/dist/mailroom/reader.js +292 -0
  390. package/dist/mailroom/search-cache.js +513 -0
  391. package/dist/mailroom/search-relevance.js +319 -0
  392. package/dist/mailroom/smtp-ingress.js +176 -0
  393. package/dist/mailroom/source-state.js +176 -0
  394. package/dist/mailroom/thread.js +109 -0
  395. package/dist/mailroom/travel-extract.js +89 -0
  396. package/dist/mind/bundle-manifest.js +24 -2
  397. package/dist/mind/context.js +328 -139
  398. package/dist/mind/desk-section.js +362 -0
  399. package/dist/mind/diary-integrity.js +60 -0
  400. package/dist/mind/{memory.js → diary.js} +68 -77
  401. package/dist/mind/embedding-provider.js +60 -0
  402. package/dist/mind/file-state.js +179 -0
  403. package/dist/mind/friend-link-codes.js +209 -0
  404. package/dist/mind/friend-merge.js +336 -0
  405. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  406. package/dist/mind/obligation-steering.js +199 -3
  407. package/dist/mind/outbound-record.js +168 -0
  408. package/dist/mind/pending.js +36 -10
  409. package/dist/mind/prompt-budget.js +479 -0
  410. package/dist/mind/prompt-refresh.js +3 -2
  411. package/dist/mind/prompt.js +1085 -198
  412. package/dist/mind/provenance-trust.js +26 -0
  413. package/dist/mind/record-paths.js +312 -0
  414. package/dist/mind/scrutiny.js +173 -0
  415. package/dist/mind/session-transaction.js +622 -0
  416. package/dist/nerves/cli-logging.js +7 -1
  417. package/dist/nerves/coverage/audit-rules.js +15 -6
  418. package/dist/nerves/coverage/audit.js +28 -2
  419. package/dist/nerves/coverage/cli.js +1 -1
  420. package/dist/nerves/coverage/contract.js +5 -5
  421. package/dist/nerves/coverage/file-completeness.js +143 -5
  422. package/dist/nerves/coverage/run-artifacts.js +55 -35
  423. package/dist/nerves/coverage/source-scanner.js +4 -4
  424. package/dist/nerves/event-buffer.js +111 -0
  425. package/dist/nerves/index.js +293 -9
  426. package/dist/nerves/observation.js +20 -0
  427. package/dist/nerves/redact.js +79 -0
  428. package/dist/nerves/review/cli-main.js +5 -0
  429. package/dist/nerves/review/cli.js +156 -0
  430. package/dist/nerves/review/core.js +159 -0
  431. package/dist/nerves/runtime.js +17 -1
  432. package/dist/repertoire/ado-client.js +15 -56
  433. package/dist/repertoire/ado-semantic.js +16 -10
  434. package/dist/repertoire/api-client.js +97 -0
  435. package/dist/repertoire/bitwarden-store.js +1201 -0
  436. package/dist/repertoire/bundle-templates.js +71 -0
  437. package/dist/repertoire/bw-installer.js +231 -0
  438. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  439. package/dist/repertoire/coding/context-pack.js +331 -0
  440. package/dist/repertoire/coding/feedback.js +174 -32
  441. package/dist/repertoire/coding/index.js +15 -3
  442. package/dist/repertoire/coding/manager.js +159 -10
  443. package/dist/repertoire/coding/spawner.js +40 -11
  444. package/dist/repertoire/coding/tools.js +182 -18
  445. package/dist/repertoire/coding/workbench-client.js +272 -0
  446. package/dist/repertoire/coding/workbench-manager.js +346 -0
  447. package/dist/repertoire/commerce-errors.js +109 -0
  448. package/dist/repertoire/commerce-self-test.js +156 -0
  449. package/dist/repertoire/credential-access.js +183 -0
  450. package/dist/repertoire/desk/classifier.js +362 -0
  451. package/dist/repertoire/duffel-client.js +185 -0
  452. package/dist/repertoire/github-client.js +14 -55
  453. package/dist/repertoire/graph-client.js +11 -52
  454. package/dist/repertoire/guardrails.js +971 -20
  455. package/dist/repertoire/house-sweep.js +471 -0
  456. package/dist/repertoire/mcp-client.js +58 -5
  457. package/dist/repertoire/mcp-manager.js +427 -139
  458. package/dist/repertoire/mcp-tools.js +132 -0
  459. package/dist/repertoire/mcp-write-guard.js +46 -0
  460. package/dist/repertoire/plugin-mcp.js +175 -0
  461. package/dist/repertoire/plugins.js +253 -0
  462. package/dist/repertoire/relationship-authorization.js +322 -0
  463. package/dist/repertoire/sanctuary-container-policy.js +46 -0
  464. package/dist/repertoire/shell-sessions.js +134 -0
  465. package/dist/repertoire/skills.js +48 -4
  466. package/dist/repertoire/stripe-client.js +131 -0
  467. package/dist/repertoire/tool-arguments.js +182 -0
  468. package/dist/repertoire/tool-results.js +29 -0
  469. package/dist/repertoire/tools-a2a.js +699 -0
  470. package/dist/repertoire/tools-attachments.js +322 -0
  471. package/dist/repertoire/tools-awaiting.js +891 -0
  472. package/dist/repertoire/tools-base.js +83 -1168
  473. package/dist/repertoire/tools-bluebubbles.js +2 -0
  474. package/dist/repertoire/tools-bridge.js +144 -0
  475. package/dist/repertoire/tools-bundle.js +995 -0
  476. package/dist/repertoire/tools-commerce.js +253 -0
  477. package/dist/repertoire/tools-config.js +186 -0
  478. package/dist/repertoire/tools-continuity.js +588 -0
  479. package/dist/repertoire/tools-credential.js +383 -0
  480. package/dist/repertoire/tools-escalation.js +84 -0
  481. package/dist/repertoire/tools-evolution.js +527 -0
  482. package/dist/repertoire/tools-files.js +344 -0
  483. package/dist/repertoire/tools-flight.js +290 -0
  484. package/dist/repertoire/tools-flow.js +119 -0
  485. package/dist/repertoire/tools-friend-link.js +57 -0
  486. package/dist/repertoire/tools-github.js +3 -8
  487. package/dist/repertoire/tools-habits.js +103 -0
  488. package/dist/repertoire/tools-house-care.js +174 -0
  489. package/dist/repertoire/tools-mail.js +1828 -0
  490. package/dist/repertoire/tools-notes.js +501 -0
  491. package/dist/repertoire/tools-obligations.js +182 -0
  492. package/dist/repertoire/tools-orientation.js +31 -0
  493. package/dist/repertoire/tools-record.js +482 -0
  494. package/dist/repertoire/tools-rsvp.js +139 -0
  495. package/dist/repertoire/tools-runtime.js +238 -0
  496. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  497. package/dist/repertoire/tools-session.js +1141 -0
  498. package/dist/repertoire/tools-shell.js +154 -0
  499. package/dist/repertoire/tools-shepherd-guidance.js +171 -0
  500. package/dist/repertoire/tools-shepherd.js +325 -0
  501. package/dist/repertoire/tools-steward-policy.js +147 -0
  502. package/dist/repertoire/tools-stripe.js +224 -0
  503. package/dist/repertoire/tools-surface.js +369 -0
  504. package/dist/repertoire/tools-teams.js +50 -39
  505. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  506. package/dist/repertoire/tools-travel.js +125 -0
  507. package/dist/repertoire/tools-trip.js +982 -0
  508. package/dist/repertoire/tools-unraid.js +556 -0
  509. package/dist/repertoire/tools-user-profile.js +146 -0
  510. package/dist/repertoire/tools-vault.js +40 -0
  511. package/dist/repertoire/tools-voice-local.js +198 -0
  512. package/dist/repertoire/tools-voice.js +145 -0
  513. package/dist/repertoire/tools.js +518 -156
  514. package/dist/repertoire/travel-api-client.js +360 -0
  515. package/dist/repertoire/unraid-client.js +155 -0
  516. package/dist/repertoire/unraid-restart.js +339 -0
  517. package/dist/repertoire/user-profile.js +131 -0
  518. package/dist/repertoire/vault-setup.js +246 -0
  519. package/dist/repertoire/vault-unlock.js +686 -0
  520. package/dist/rsvp/aisleplanner-client.js +354 -0
  521. package/dist/rsvp/cli.js +936 -0
  522. package/dist/rsvp/config.js +327 -0
  523. package/dist/rsvp/cutover.js +433 -0
  524. package/dist/rsvp/diagnostics.js +232 -0
  525. package/dist/rsvp/diff-renderer.js +176 -0
  526. package/dist/rsvp/habit-policy.js +110 -0
  527. package/dist/rsvp/habit-stage.js +123 -0
  528. package/dist/rsvp/incident-bundle.js +97 -0
  529. package/dist/rsvp/migration.js +204 -0
  530. package/dist/rsvp/native-habit-runner.js +630 -0
  531. package/dist/rsvp/outbound-state.js +628 -0
  532. package/dist/rsvp/query.js +118 -0
  533. package/dist/rsvp/replay.js +233 -0
  534. package/dist/rsvp/snapshot.js +232 -0
  535. package/dist/rsvp/spend-ledger.js +175 -0
  536. package/dist/scripts/claude-code-hook.js +41 -0
  537. package/dist/scripts/claude-code-stop-hook.js +47 -0
  538. package/dist/senses/a2a-entry.js +132 -0
  539. package/dist/senses/acp-server.js +386 -0
  540. package/dist/senses/attention-queue.js +186 -0
  541. package/dist/senses/await-turn-message.js +65 -0
  542. package/dist/senses/bluebubbles/active-turns.js +216 -0
  543. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  544. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  545. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +385 -43
  546. package/dist/senses/bluebubbles/context-packet.js +207 -0
  547. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  548. package/dist/senses/bluebubbles/entry.js +90 -0
  549. package/dist/senses/bluebubbles/host-account-verifier.js +88 -0
  550. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  551. package/dist/senses/bluebubbles/index.js +4040 -0
  552. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  553. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  554. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  555. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  556. package/dist/senses/bluebubbles/outbound-state.js +335 -0
  557. package/dist/senses/bluebubbles/processed-log.js +133 -0
  558. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  559. package/dist/senses/bluebubbles/replay.js +284 -0
  560. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  561. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  562. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  563. package/dist/senses/bluebubbles/webhook-registration.js +257 -0
  564. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  565. package/dist/senses/cli/bracketed-paste.js +82 -0
  566. package/dist/senses/cli/image-paste.js +287 -0
  567. package/dist/senses/cli/image-ref-navigation.js +75 -0
  568. package/dist/senses/cli/ink-app.js +156 -0
  569. package/dist/senses/cli/inline-diff.js +64 -0
  570. package/dist/senses/cli/input-keys.js +174 -0
  571. package/dist/senses/cli/kill-ring.js +86 -0
  572. package/dist/senses/cli/message-list.js +51 -0
  573. package/dist/senses/cli/ouro-tui.js +607 -0
  574. package/dist/senses/cli/spinner-imperative.js +135 -0
  575. package/dist/senses/cli/spinner.js +101 -0
  576. package/dist/senses/cli/status-line.js +60 -0
  577. package/dist/senses/cli/streaming-markdown.js +526 -0
  578. package/dist/senses/cli/tool-display.js +85 -0
  579. package/dist/senses/cli/tool-render.js +85 -0
  580. package/dist/senses/cli/tui-store.js +241 -0
  581. package/dist/senses/cli/virtual-list.js +35 -0
  582. package/dist/senses/cli-entry.js +60 -8
  583. package/dist/senses/cli.js +570 -309
  584. package/dist/senses/commands.js +66 -3
  585. package/dist/senses/context-packet-ledger.js +371 -0
  586. package/dist/senses/context-packets.js +154 -0
  587. package/dist/senses/habit-lifecycle-guard.js +24 -0
  588. package/dist/senses/habit-turn-message.js +160 -0
  589. package/dist/senses/ingress-evidence.js +2 -0
  590. package/dist/senses/mail-entry.js +66 -0
  591. package/dist/senses/mail.js +524 -0
  592. package/dist/senses/pipeline.js +1158 -204
  593. package/dist/senses/private-runtime-worker.js +857 -0
  594. package/dist/senses/private-runtime.js +1627 -0
  595. package/dist/senses/proactive-content-guard.js +51 -0
  596. package/dist/senses/root-host-approval-port.js +345 -0
  597. package/dist/senses/root-host-approval-runtime.js +393 -0
  598. package/dist/senses/sanctuary-authority-resident.js +102 -0
  599. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  600. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  601. package/dist/senses/sanctuary-grounding.js +118 -0
  602. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  603. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  604. package/dist/senses/sanctuary-health-runner.js +104 -0
  605. package/dist/senses/sanctuary-health.js +448 -0
  606. package/dist/senses/sanctuary-install-state-contract.js +169 -0
  607. package/dist/senses/sanctuary-interactive-control.js +410 -0
  608. package/dist/senses/sanctuary-media-catalog-contract.js +268 -0
  609. package/dist/senses/sanctuary-media-optimization.js +514 -0
  610. package/dist/senses/sanctuary-runtime.js +369 -0
  611. package/dist/senses/sanctuary-sab.js +85 -0
  612. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  613. package/dist/senses/shared-turn.js +804 -0
  614. package/dist/senses/shepherd/agent-presentation.js +88 -0
  615. package/dist/senses/shepherd/attention-page.js +166 -0
  616. package/dist/senses/shepherd/attention-sidebar.js +76 -0
  617. package/dist/senses/shepherd/attention.js +295 -0
  618. package/dist/senses/shepherd/checkin-config.js +76 -0
  619. package/dist/senses/shepherd/checkin-store.js +386 -0
  620. package/dist/senses/shepherd/client.js +196 -0
  621. package/dist/senses/shepherd/cmux.js +483 -0
  622. package/dist/senses/shepherd/composer.js +336 -0
  623. package/dist/senses/shepherd/copilot-app.js +648 -0
  624. package/dist/senses/shepherd/feedback-reply.js +108 -0
  625. package/dist/senses/shepherd/feedback-source.js +20 -0
  626. package/dist/senses/shepherd/feedback-store.js +331 -0
  627. package/dist/senses/shepherd/feedback-submit.js +182 -0
  628. package/dist/senses/shepherd/feedback-worker.js +208 -0
  629. package/dist/senses/shepherd/guidance.js +273 -0
  630. package/dist/senses/shepherd/herdr.js +427 -0
  631. package/dist/senses/shepherd/host.js +17 -0
  632. package/dist/senses/shepherd/human-decision-guard.js +29 -0
  633. package/dist/senses/shepherd/judge-evaluation-command.js +183 -0
  634. package/dist/senses/shepherd/judge-evaluation-entry.js +13 -0
  635. package/dist/senses/shepherd/judge-evaluation.js +155 -0
  636. package/dist/senses/shepherd/judge.js +202 -0
  637. package/dist/senses/shepherd/notice-outbox.js +262 -0
  638. package/dist/senses/shepherd/observation-publisher.js +320 -0
  639. package/dist/senses/shepherd/operations-defaults.js +86 -0
  640. package/dist/senses/shepherd/operations.js +159 -0
  641. package/dist/senses/shepherd/owner-notice.js +467 -0
  642. package/dist/senses/shepherd/prompt-state.js +60 -0
  643. package/dist/senses/shepherd/recovery.js +543 -0
  644. package/dist/senses/shepherd/redact.js +66 -0
  645. package/dist/senses/shepherd/returns.js +79 -0
  646. package/dist/senses/shepherd/sense.js +821 -0
  647. package/dist/senses/shepherd/shepherd-judge-corpus-v1.json +170 -0
  648. package/dist/senses/shepherd/surface.js +123 -0
  649. package/dist/senses/shepherd/target-binding.js +13 -0
  650. package/dist/senses/shepherd/terminal-process.js +147 -0
  651. package/dist/senses/shepherd-entry.js +98 -0
  652. package/dist/senses/surface-tool.js +109 -0
  653. package/dist/senses/teams-entry.js +68 -8
  654. package/dist/senses/teams-image.js +119 -0
  655. package/dist/senses/teams.js +949 -337
  656. package/dist/senses/telegram-admission.js +718 -0
  657. package/dist/senses/telegram-approval-runtime.js +793 -0
  658. package/dist/senses/telegram-attachments.js +186 -0
  659. package/dist/senses/telegram-audit-ledger.js +220 -0
  660. package/dist/senses/telegram-authority-transport.js +231 -0
  661. package/dist/senses/telegram-client.js +1749 -0
  662. package/dist/senses/telegram-effect-adapter.js +904 -0
  663. package/dist/senses/telegram-entry.js +83 -0
  664. package/dist/senses/telegram-handoff.js +154 -0
  665. package/dist/senses/telegram.js +2466 -0
  666. package/dist/senses/trust-gate.js +104 -9
  667. package/dist/senses/voice/audio-playback.js +237 -0
  668. package/dist/senses/voice/audio-routing.js +119 -0
  669. package/dist/senses/voice/call-auth.js +250 -0
  670. package/dist/senses/voice/elevenlabs.js +202 -0
  671. package/dist/senses/voice/floor-control.js +431 -0
  672. package/dist/senses/voice/floor-controller.js +115 -0
  673. package/dist/senses/voice/golden-path.js +116 -0
  674. package/dist/senses/voice/index.js +29 -0
  675. package/dist/senses/voice/local-audio-control.js +344 -0
  676. package/dist/senses/voice/local-audio-devices.js +516 -0
  677. package/dist/senses/voice/local-audio-disclosure.js +174 -0
  678. package/dist/senses/voice/local-audio-identity.js +103 -0
  679. package/dist/senses/voice/local-audio-join.js +325 -0
  680. package/dist/senses/voice/local-audio-mute.js +199 -0
  681. package/dist/senses/voice/local-audio-notify.js +124 -0
  682. package/dist/senses/voice/local-audio-prompts.js +116 -0
  683. package/dist/senses/voice/local-audio-transport.js +427 -0
  684. package/dist/senses/voice/local-audio.js +281 -0
  685. package/dist/senses/voice/local-media-stream.js +145 -0
  686. package/dist/senses/voice/meeting.js +113 -0
  687. package/dist/senses/voice/outbound.js +190 -0
  688. package/dist/senses/voice/phone.js +33 -0
  689. package/dist/senses/voice/playback.js +139 -0
  690. package/dist/senses/voice/process-guards.js +51 -0
  691. package/dist/senses/voice/realtime-eval.js +496 -0
  692. package/dist/senses/voice/realtime-tool-schema.js +195 -0
  693. package/dist/senses/voice/realtime-trace.js +531 -0
  694. package/dist/senses/voice/transcript.js +70 -0
  695. package/dist/senses/voice/turn.js +191 -0
  696. package/dist/senses/voice/twilio-phone-runtime.js +836 -0
  697. package/dist/senses/voice/twilio-phone.js +5454 -0
  698. package/dist/senses/voice/types.js +2 -0
  699. package/dist/senses/voice/whisper.js +161 -0
  700. package/dist/senses/voice-entry.js +83 -0
  701. package/dist/senses/voice-realtime-eval-command.js +99 -0
  702. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  703. package/dist/senses/voice-twilio-entry.js +87 -0
  704. package/dist/trips/core.js +138 -0
  705. package/dist/trips/store.js +265 -0
  706. package/dist/util/frontmatter.js +69 -0
  707. package/npm-shrinkwrap.json +8165 -0
  708. package/package.json +75 -13
  709. package/skills/agent-commerce.md +113 -0
  710. package/skills/browser-navigation.md +117 -0
  711. package/skills/commerce-setup-guide.md +116 -0
  712. package/skills/commerce-setup.md +84 -0
  713. package/skills/configure-dev-tools.md +99 -0
  714. package/skills/travel-planning.md +138 -0
  715. package/dist/heart/daemon/auth-flow.js +0 -430
  716. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  717. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  718. package/dist/heart/daemon/update-hooks.js +0 -138
  719. package/dist/heart/obligations.js +0 -191
  720. package/dist/heart/safe-workspace.js +0 -381
  721. package/dist/heart/session-recall.js +0 -116
  722. package/dist/mind/friends/channel.js +0 -84
  723. package/dist/mind/friends/group-context.js +0 -144
  724. package/dist/mind/friends/resolver.js +0 -84
  725. package/dist/mind/friends/store-file.js +0 -190
  726. package/dist/mind/friends/store.js +0 -4
  727. package/dist/mind/friends/tokens.js +0 -26
  728. package/dist/mind/friends/trust-explanation.js +0 -74
  729. package/dist/mind/friends/types.js +0 -29
  730. package/dist/repertoire/tasks/board.js +0 -134
  731. package/dist/repertoire/tasks/index.js +0 -224
  732. package/dist/repertoire/tasks/lifecycle.js +0 -80
  733. package/dist/repertoire/tasks/middleware.js +0 -65
  734. package/dist/repertoire/tasks/parser.js +0 -173
  735. package/dist/repertoire/tasks/scanner.js +0 -132
  736. package/dist/repertoire/tasks/transitions.js +0 -144
  737. package/dist/senses/bluebubbles-entry.js +0 -13
  738. package/dist/senses/bluebubbles.js +0 -1181
  739. package/dist/senses/debug-activity.js +0 -154
  740. package/dist/senses/inner-dialog-worker.js +0 -90
  741. package/dist/senses/inner-dialog.js +0 -525
  742. package/subagents/README.md +0 -7
  743. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  744. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  745. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  746. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  747. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  748. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  749. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  750. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  751. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  752. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  753. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  754. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  755. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  756. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  757. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  758. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  759. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -34,12 +34,17 @@ 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;
44
+ exports.reasoningStatus = reasoningStatus;
41
45
  exports.withConversationLock = withConversationLock;
42
46
  exports.handleTeamsMessage = handleTeamsMessage;
47
+ exports.createTeamsSdkLogger = createTeamsSdkLogger;
43
48
  exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
44
49
  exports.drainAndSendPendingTeams = drainAndSendPendingTeams;
45
50
  exports.startTeamsApp = startTeamsApp;
@@ -48,7 +53,7 @@ const teams_apps_1 = require("@microsoft/teams.apps");
48
53
  const teams_dev_1 = require("@microsoft/teams.dev");
49
54
  const core_1 = require("../heart/core");
50
55
  const tools_1 = require("../repertoire/tools");
51
- const channel_1 = require("../mind/friends/channel");
56
+ const friends_1 = require("@ouro.bot/friends");
52
57
  const config_1 = require("../heart/config");
53
58
  const prompt_1 = require("../mind/prompt");
54
59
  const phrases_1 = require("../mind/phrases");
@@ -58,20 +63,35 @@ const context_1 = require("../mind/context");
58
63
  const commands_1 = require("./commands");
59
64
  const nerves_1 = require("../nerves");
60
65
  const runtime_1 = require("../nerves/runtime");
61
- const store_file_1 = require("../mind/friends/store-file");
62
- const types_1 = require("../mind/friends/types");
63
- const resolver_1 = require("../mind/friends/resolver");
64
- const tokens_1 = require("../mind/friends/tokens");
66
+ const proactive_content_guard_1 = require("./proactive-content-guard");
65
67
  const turn_coordinator_1 = require("../heart/turn-coordinator");
66
68
  const identity_1 = require("../heart/identity");
67
69
  const mcp_manager_1 = require("../repertoire/mcp-manager");
68
70
  const progress_story_1 = require("../heart/progress-story");
71
+ const tool_activity_callbacks_1 = require("../heart/tool-activity-callbacks");
72
+ const commands_2 = require("./commands");
69
73
  const http = __importStar(require("http"));
70
74
  const path = __importStar(require("path"));
75
+ const node_crypto_1 = require("node:crypto");
76
+ const outbound_record_1 = require("../mind/outbound-record");
71
77
  const trust_gate_1 = require("./trust-gate");
72
78
  const pipeline_1 = require("./pipeline");
79
+ const session_transaction_1 = require("../mind/session-transaction");
80
+ const teamsFailoverStates = new Map();
73
81
  const pending_1 = require("../mind/pending");
74
82
  const continuity_1 = require("./continuity");
83
+ const teams_image_1 = require("./teams-image");
84
+ const feedback_reply_1 = require("./shepherd/feedback-reply");
85
+ // AIGeneratedContent entity and feedbackLoopEnabled channelData for all outbound
86
+ // Teams messages. Required by Teams AI UX best practices.
87
+ function aiLabelEntities() {
88
+ return [{
89
+ type: "https://schema.org/Message",
90
+ "@type": "Message",
91
+ "@context": "https://schema.org",
92
+ additionalType: ["AIGeneratedContent"],
93
+ }];
94
+ }
75
95
  // Strip @mention markup from incoming messages.
76
96
  // Removes <at>...</at> tags and trims extra whitespace.
77
97
  // Fallback safety net -- the SDK's activity.mentions.stripText should handle
@@ -126,6 +146,46 @@ function splitMessage(text, maxLen) {
126
146
  }
127
147
  return chunks;
128
148
  }
149
+ // Sanitize user-provided feedback comments: truncate, strip control chars and newlines.
150
+ function sanitizeFeedbackComment(comment) {
151
+ const cleaned = comment.replace(/[\x00-\x1f\n\r]/g, "");
152
+ return cleaned.length > 200 ? cleaned.slice(0, 200) : cleaned;
153
+ }
154
+ // Build synthetic message text from a Teams feedback reaction.
155
+ function buildFeedbackSyntheticText(reaction, comment) {
156
+ const emoji = reaction === "like" ? "thumbs-up" : "thumbs-down";
157
+ if (comment) {
158
+ const sanitized = sanitizeFeedbackComment(comment);
159
+ return `[reacted with ${emoji} to your message: "${sanitized}"]`;
160
+ }
161
+ return `[reacted with ${emoji} to your message]`;
162
+ }
163
+ // Build a welcome Adaptive Card with prompt starters for new bot installs.
164
+ function buildWelcomeCard() {
165
+ const promptStarters = [
166
+ "What can you help me with?",
167
+ "Tell me about yourself",
168
+ "What's on my calendar today?",
169
+ "Summarize my recent emails",
170
+ ];
171
+ return {
172
+ type: "AdaptiveCard",
173
+ version: "1.5",
174
+ body: [
175
+ {
176
+ type: "TextBlock",
177
+ text: "Hey! I'm here and ready to help. Try one of these to get started, or just ask me anything.",
178
+ wrap: true,
179
+ size: "Medium",
180
+ },
181
+ ],
182
+ actions: promptStarters.map((prompt) => ({
183
+ type: "Action.Submit",
184
+ title: prompt,
185
+ data: { msteams: { type: "messageBack", text: prompt, displayText: prompt } },
186
+ })),
187
+ };
188
+ }
129
189
  // Create Teams-specific callbacks for the agent loop.
130
190
  // The SDK handles cumulative text, debouncing (500ms), and the streaming
131
191
  // protocol (streamSequence, streamId, informative/streaming/final types).
@@ -137,25 +197,49 @@ function splitMessage(text, maxLen) {
137
197
  // safeSend (ctx.send). Tool results, kicks, and errors use safeUpdate
138
198
  // (transient status) or safeSend (terminal errors). Reasoning is accumulated
139
199
  // and periodically pushed via safeUpdate on the same flush timer tick.
200
+ function isTeamsSizeRejection(error) {
201
+ return typeof error === "object" && error !== null && "response" in error
202
+ && typeof error.response === "object" && error.response !== null && "status" in error.response
203
+ && error.response.status === 413;
204
+ }
140
205
  function createTeamsCallbacks(stream, controller, sendMessage, options) {
206
+ const MIN_INITIAL_CHARS = 20;
141
207
  let stopped = false; // set when stream signals cancellation (403)
142
208
  let hadToolRun = false;
143
209
  let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
144
210
  let reasoningBuf = ""; // accumulated reasoning text for status display
211
+ let totalEmitted = 0; // cumulative chars emitted via safeEmit (for >4000 finalization)
212
+ let streamFinalized = false; // true after stream.close() — subsequent flushes go to safeSend
145
213
  let textBuffer = ""; // accumulated text output for chunked streaming
146
214
  let streamHasContent = false; // tracks whether primary output has received content
215
+ let firstContentEmitted = false; // true after first content push — disables MIN_INITIAL_CHARS threshold
147
216
  let phraseTimer = null;
148
217
  let lastPhrase = "";
149
218
  let flushTimer = null;
150
219
  const flushInterval = options?.flushIntervalMs ?? exports.DEFAULT_FLUSH_INTERVAL_MS;
220
+ // The SDK merges every chunk's entities into the final message, and Teams rejects a final
221
+ // message with more than one root message entity (400 BadSyntax), so only the first chunk
222
+ // carries the AI label.
223
+ let aiLabelAttached = false;
224
+ function streamChunk(text) {
225
+ if (aiLabelAttached)
226
+ return { type: "message", text, channelData: { feedbackLoopEnabled: true } };
227
+ aiLabelAttached = true;
228
+ return { type: "message", text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } };
229
+ }
151
230
  // Track whether reasoning has changed since last flush (avoid redundant updates).
152
231
  let lastFlushedReasoningLen = 0;
232
+ let lastReasoningStatus = "";
153
233
  // Periodic tick: flush text buffer and push reasoning updates.
154
234
  function flushTick() {
155
235
  flushTextBuffer();
156
236
  if (reasoningBuf.length > lastFlushedReasoningLen) {
157
- safeUpdate(reasoningBuf);
158
237
  lastFlushedReasoningLen = reasoningBuf.length;
238
+ const status = reasoningStatus(reasoningBuf);
239
+ if (status && status !== lastReasoningStatus) {
240
+ lastReasoningStatus = status;
241
+ safeUpdate(status);
242
+ }
159
243
  }
160
244
  }
161
245
  // Start the periodic flush timer. Idempotent -- no-op if already running.
@@ -172,9 +256,16 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
172
256
  }
173
257
  }
174
258
  // Mark stream as broken and abort the agent loop.
175
- function markStopped() {
259
+ function markStopped(where, err) {
176
260
  if (stopped)
177
261
  return;
262
+ (0, runtime_1.emitNervesEvent)({
263
+ level: "warn",
264
+ component: "senses",
265
+ event: "teams.stream_stopped",
266
+ message: `teams stream stopped at ${where}`,
267
+ meta: { where, error: err instanceof Error ? err.message : err === undefined ? null : String(err), conversationId: options?.conversationId ?? null },
268
+ });
178
269
  stopped = true;
179
270
  stopPhraseRotation();
180
271
  stopFlushTimer();
@@ -190,21 +281,22 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
190
281
  // catch its rejection so we detect a dead stream and abort the agent loop.
191
282
  function catchAsync(result) {
192
283
  if (result && typeof result.catch === "function") {
193
- result.catch(() => markStopped());
284
+ result.catch((err) => markStopped("stream-async", err));
194
285
  }
195
286
  }
196
- // Safely emit a text delta to the stream.
287
+ // Safely emit a text delta to the stream with AI labels.
197
288
  // On error (e.g. 403 from Teams stop button), abort the controller.
198
289
  function safeEmit(text) {
199
290
  /* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
200
291
  if (stopped)
201
292
  return;
202
293
  try {
203
- catchAsync(stream.emit(text));
294
+ catchAsync(stream.emit(streamChunk(text)));
204
295
  streamHasContent = true;
296
+ totalEmitted += text.length;
205
297
  }
206
- catch {
207
- markStopped();
298
+ catch (err) {
299
+ markStopped("safeEmit", err);
208
300
  }
209
301
  }
210
302
  // Awaitable emit — returns true if the emit succeeded, false if it failed.
@@ -213,18 +305,48 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
213
305
  /* v8 ignore next -- defensive guard: stopped set by prior error; tested via flush abort path @preserve */
214
306
  if (stopped)
215
307
  return false;
308
+ let awaitingTransport = false;
216
309
  try {
217
310
  // stream.emit() is typed as void but the Teams SDK returns a Promise
218
311
  // internally (async HTTP). Cast to capture the result for awaiting.
219
- const result = stream.emit(text);
312
+ const result = stream.emit(streamChunk(text));
220
313
  streamHasContent = true;
221
314
  if (result && typeof result.then === "function") {
315
+ awaitingTransport = true;
222
316
  await result;
223
317
  }
224
318
  return true;
225
319
  }
226
- catch {
227
- markStopped();
320
+ catch (err) {
321
+ markStopped("tryEmit", err);
322
+ if (awaitingTransport && !isTeamsSizeRejection(err))
323
+ throw err;
324
+ return false;
325
+ }
326
+ }
327
+ // Non-aborting awaitable emit — returns true on success, false on failure WITHOUT
328
+ // calling markStopped() / aborting the controller. Used by flushNow (speak) so a
329
+ // primary-stream failure followed by a successful sendMessage fallback does NOT
330
+ // poison the rest of the turn. tryEmit's abort-on-failure behavior is correct for
331
+ // end-of-turn flush() (no fallback path forward) but wrong for mid-turn speak,
332
+ // which has a sendMessage fallback that may still succeed. Caller (flushNow) is
333
+ // responsible for the `!stopped` precondition; no defensive guard here.
334
+ async function tryEmitNoAbort(text) {
335
+ let awaitingTransport = false;
336
+ try {
337
+ const result = stream.emit(streamChunk(text));
338
+ streamHasContent = true;
339
+ if (result && typeof result.then === "function") {
340
+ awaitingTransport = true;
341
+ await result;
342
+ }
343
+ return true;
344
+ }
345
+ catch (error) {
346
+ if (awaitingTransport && !isTeamsSizeRejection(error)) {
347
+ markStopped("speak-unknown", error);
348
+ throw error;
349
+ }
228
350
  return false;
229
351
  }
230
352
  }
@@ -236,8 +358,8 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
236
358
  try {
237
359
  catchAsync(stream.update(text));
238
360
  }
239
- catch {
240
- markStopped();
361
+ catch (err) {
362
+ markStopped("safeUpdate", err);
241
363
  }
242
364
  }
243
365
  // Safely send a separate message via sendMessage (for content after first emit).
@@ -252,11 +374,11 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
252
374
  // Chain is idle -- start the send synchronously and mark busy
253
375
  sendChainBusy = true;
254
376
  try {
255
- sendChain = sendMessage(text).catch(() => markStopped()).finally(() => { sendChainBusy = false; });
377
+ sendChain = sendMessage(text).catch((err) => markStopped("send", err)).finally(() => { sendChainBusy = false; });
256
378
  }
257
- catch {
379
+ catch (err) {
258
380
  sendChainBusy = false;
259
- markStopped();
381
+ markStopped("send", err);
260
382
  }
261
383
  }
262
384
  else {
@@ -265,18 +387,56 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
265
387
  if (stopped)
266
388
  return;
267
389
  return sendMessage(text);
268
- }).catch(() => markStopped());
390
+ }).catch((err) => markStopped("send-queued", err));
269
391
  }
270
392
  }
271
393
  // Flush accumulated text buffer via safeEmit. The Teams SDK accumulates
272
394
  // emitted text into a single streaming message (cumulative), so every
273
395
  // periodic flush appends to the same response — not separate messages.
274
396
  // No preemptive splitting — sends full text. Error recovery happens in flush().
397
+ // Hybrid MIN_INITIAL_CHARS: hold back until >= MIN_INITIAL_CHARS accumulated
398
+ // before the first content emit, so phrase rotation shows while real content
399
+ // buffers. After first emit, flush normally (no threshold).
275
400
  function flushTextBuffer() {
276
401
  if (!textBuffer)
277
402
  return;
403
+ if (!firstContentEmitted && textBuffer.length < MIN_INITIAL_CHARS)
404
+ return;
405
+ // Proactive >4000 finalization: if cumulative emitted + buffer >= RECOVERY_CHUNK_SIZE,
406
+ // finalize the stream and send overflow via safeSend (follow-up message).
407
+ if (!streamFinalized && totalEmitted + textBuffer.length >= RECOVERY_CHUNK_SIZE) {
408
+ const remaining = RECOVERY_CHUNK_SIZE - totalEmitted;
409
+ /* v8 ignore next 2 -- defensive: remaining always > 0 because finalization runs once @preserve */
410
+ if (remaining > 0)
411
+ safeEmit(textBuffer.slice(0, remaining));
412
+ try {
413
+ stream.close();
414
+ }
415
+ catch { /* stream may already be dead */ }
416
+ streamFinalized = true;
417
+ /* v8 ignore next -- defensive ternary: remaining always > 0 at first finalization @preserve */
418
+ const overflow = textBuffer.slice(remaining > 0 ? remaining : 0);
419
+ textBuffer = "";
420
+ if (overflow)
421
+ safeSend(overflow);
422
+ if (!firstContentEmitted) {
423
+ firstContentEmitted = true;
424
+ stopPhraseRotation();
425
+ }
426
+ return;
427
+ }
428
+ if (streamFinalized) {
429
+ // After finalization, all content goes to safeSend (follow-up messages)
430
+ safeSend(textBuffer);
431
+ textBuffer = "";
432
+ return;
433
+ }
278
434
  safeEmit(textBuffer);
279
435
  textBuffer = "";
436
+ if (!firstContentEmitted) {
437
+ firstContentEmitted = true;
438
+ stopPhraseRotation();
439
+ }
280
440
  }
281
441
  function startPhraseRotation(pool) {
282
442
  stopPhraseRotation();
@@ -293,6 +453,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
293
453
  }
294
454
  }
295
455
  return {
456
+ settleOutputMode: "final_only",
296
457
  onModelStart: () => {
297
458
  if (hadRealOutput)
298
459
  return; // real output already shown; don't overwrite with phrases
@@ -311,48 +472,112 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
311
472
  /* v8 ignore next -- defensive guard: stopped set by prior error @preserve */
312
473
  if (stopped)
313
474
  return;
314
- stopPhraseRotation();
315
- hadRealOutput = true;
316
475
  reasoningBuf += text;
476
+ // Keep the friendly phrases until a reasoning heading can replace them.
477
+ if (reasoningStatus(reasoningBuf)) {
478
+ stopPhraseRotation();
479
+ hadRealOutput = true;
480
+ }
317
481
  startFlushTimer();
318
482
  },
319
483
  onTextChunk: (text) => {
320
484
  if (stopped)
321
485
  return;
322
- stopPhraseRotation();
486
+ // Don't stop phrase rotation here — let it continue until first content
487
+ // emit (handled in flushTextBuffer when MIN_INITIAL_CHARS threshold met).
323
488
  textBuffer += text;
324
489
  startFlushTimer();
325
490
  },
326
491
  onClearText: () => {
327
492
  textBuffer = "";
328
493
  },
329
- onToolStart: (name, args) => {
330
- stopPhraseRotation();
331
- flushTextBuffer();
332
- // Emit a placeholder to satisfy the 15s Copilot timeout for initial
333
- // stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
334
- // never emit before the timeout and the user sees "this response was
335
- // stopped". The placeholder is replaced by actual content on next emit.
336
- // https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
337
- if (!streamHasContent)
338
- safeEmit("⏳");
339
- const argSummary = (0, tools_1.summarizeArgs)(name, args) || Object.keys(args).join(", ");
340
- safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
341
- scope: "shared-work",
342
- phase: "processing",
343
- objective: `running ${name} (${argSummary})...`,
344
- })));
345
- hadToolRun = true;
346
- },
347
- onToolEnd: (name, summary, success) => {
494
+ flushNow: async () => {
495
+ const trimmed = textBuffer.trim();
496
+ if (!trimmed)
497
+ return;
498
+ // Cancel pending periodic flush — we're delivering now.
499
+ stopFlushTimer();
500
+ // The actual speak message replaces any "thinking..." phrase cycling.
348
501
  stopPhraseRotation();
349
- const msg = (0, format_1.formatToolResult)(name, summary, success);
350
- safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
351
- scope: "shared-work",
352
- phase: "processing",
353
- objective: msg,
354
- })));
502
+ // Bypass MIN_INITIAL_CHARS threshold — speak delivers immediately.
503
+ firstContentEmitted = true;
504
+ textBuffer = "";
505
+ // Try the stream first via the NON-ABORTING variant; on failure, fall back
506
+ // to sendMessage. Critical: do NOT call markStopped() / abort the controller
507
+ // when only the primary stream fails — the sendMessage fallback may still
508
+ // deliver the speak, and a successful fallback must not poison the rest of
509
+ // the turn. Only abort when ALL delivery paths fail (handled below).
510
+ // Contract: throws if the message could not be delivered through any available path.
511
+ let delivered = false;
512
+ let lastError = null;
513
+ if (!stopped) {
514
+ const ok = await tryEmitNoAbort(trimmed);
515
+ if (ok)
516
+ delivered = true;
517
+ else
518
+ lastError = new Error("stream emit failed");
519
+ }
520
+ if (!delivered && sendMessage) {
521
+ try {
522
+ await sendMessage(trimmed);
523
+ delivered = true;
524
+ lastError = null;
525
+ }
526
+ catch (err) {
527
+ lastError = err instanceof Error ? err : new Error(String(err));
528
+ }
529
+ }
530
+ (0, runtime_1.emitNervesEvent)({
531
+ component: "senses",
532
+ event: "teams.speak_flush",
533
+ message: "teams flushed mid-turn speak",
534
+ meta: { messageLength: trimmed.length, delivered },
535
+ });
536
+ if (!delivered) {
537
+ // All delivery paths exhausted — now it is correct to abort the turn.
538
+ // markStopped() halts further stream activity and aborts the controller
539
+ // so the engine catches up and ends the turn cleanly.
540
+ markStopped("speak", lastError);
541
+ throw new Error(`teams speak delivery failed: ${lastError?.message ?? "no fallback available"}`);
542
+ }
355
543
  },
544
+ ...(() => {
545
+ const toolCbs = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
546
+ onDescription: (text) => safeUpdate(text),
547
+ /* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
548
+ onResult: (text) => safeUpdate(text),
549
+ /* v8 ignore next -- onFailure tested via onToolEnd failure test @preserve */
550
+ onFailure: (text) => safeUpdate(text),
551
+ isDebug: commands_2.getDebugMode,
552
+ });
553
+ return {
554
+ onToolStart: (name, args) => {
555
+ // speak is flow-control: its visible output is the message itself
556
+ // (delivered via onTextChunk + flushNow). Do NOT stop phrase rotation
557
+ // here, do NOT post a tool-activity
558
+ // status update \u2014 all of those would create UI churn right before the
559
+ // actual speak content arrives.
560
+ if (name === "speak")
561
+ return;
562
+ stopPhraseRotation();
563
+ // Force-flush any accumulated text, bypassing MIN_INITIAL_CHARS threshold
564
+ firstContentEmitted = true;
565
+ flushTextBuffer();
566
+ toolCbs.onToolStart(name, args);
567
+ hadToolRun = true;
568
+ },
569
+ onToolEnd: (name, summary, success) => {
570
+ // speak is flow-control: skip phrase-rotation stop and tool-activity end
571
+ // callback (no safeUpdate for \u2713/\u2717). The flushNow call inside the engine
572
+ // already emitted the actual message and stopped any rotation as part of
573
+ // tryEmit's first-content-emitted flag.
574
+ if (name === "speak")
575
+ return;
576
+ stopPhraseRotation();
577
+ toolCbs.onToolEnd(name, summary, success);
578
+ },
579
+ };
580
+ })(),
356
581
  onKick: () => {
357
582
  stopPhraseRotation();
358
583
  const msg = (0, format_1.formatKick)();
@@ -374,78 +599,135 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
374
599
  safeSend(msg);
375
600
  }
376
601
  },
377
- onConfirmAction: options?.conversationId
378
- ? async (name, args) => {
379
- const convId = options.conversationId;
380
- const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
381
- safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
382
- return new Promise((resolve) => {
383
- _pendingConfirmations.set(convId, resolve);
384
- // Auto-deny after 2 minutes to prevent indefinite blocking
385
- // (e.g. when the stream dies and the user never sees the prompt).
386
- setTimeout(() => {
387
- if (_pendingConfirmations.has(convId)) {
388
- _pendingConfirmations.delete(convId);
389
- resolve("denied");
390
- }
391
- }, 120_000);
392
- });
393
- }
394
- : undefined,
395
602
  flush: async () => {
396
603
  stopFlushTimer();
604
+ stopPhraseRotation();
397
605
  if (textBuffer) {
606
+ // Bypass MIN_INITIAL_CHARS threshold — flush delivers all remaining content
607
+ firstContentEmitted = true;
398
608
  const text = textBuffer;
399
609
  textBuffer = "";
400
- if (!stopped) {
610
+ if (streamFinalized && sendMessage) {
611
+ // Stream already finalized (>4000 path) — send remaining content as follow-up
612
+ safeSend(text);
613
+ }
614
+ else if (!stopped) {
401
615
  // Stream is alive — await the emit so we can catch async 413/failure
402
616
  // and fall through to sendMessage recovery.
403
617
  const ok = await tryEmit(text);
404
618
  if (!ok)
405
- markStopped();
619
+ markStopped("flush");
406
620
  }
407
- if (stopped && sendMessage) {
408
- // Stream is dead — fall back to sendMessage; split on failure as recovery.
621
+ if (stopped && !streamFinalized && sendMessage) {
622
+ // Stream is dead (not from finalization) — fall back to sendMessage; split on failure as recovery.
409
623
  try {
410
624
  await sendMessage(text);
411
625
  }
412
- catch {
626
+ catch (error) {
627
+ // A lost acknowledgement is not a rejection. Only a definite size rejection permits a smaller retry.
628
+ if (!isTeamsSizeRejection(error) || text.length <= RECOVERY_CHUNK_SIZE)
629
+ throw error;
413
630
  const chunks = splitMessage(text, RECOVERY_CHUNK_SIZE);
414
631
  for (const chunk of chunks)
415
632
  await sendMessage(chunk);
416
633
  }
417
634
  }
418
635
  }
419
- else if (!streamHasContent) {
420
- safeEmit("(completed with tool calls only \u2014 no text response)");
636
+ else if (!streamHasContent && !options?.suppressEmptyStreamMessage) {
637
+ safeEmit("(completed with tool calls only — no text response)");
421
638
  }
639
+ (0, runtime_1.emitNervesEvent)({
640
+ component: "senses",
641
+ event: "teams.flush_done",
642
+ message: "teams end-of-turn flush finished",
643
+ meta: { stopped, streamHasContent, streamFinalized, conversationId: options?.conversationId ?? null },
644
+ });
422
645
  },
423
646
  };
424
647
  }
425
- // Per-conversation pending confirmation resolvers.
426
- // When a mutate tool needs confirmation, the resolver is stored here.
427
- // The next message from the same conversation resolves it.
428
- const _pendingConfirmations = new Map();
429
- // Confirmation response words (case-insensitive)
430
- const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
431
- function resolvePendingConfirmation(convId, text) {
432
- const resolver = _pendingConfirmations.get(convId);
433
- if (!resolver)
434
- return false;
435
- _pendingConfirmations.delete(convId);
436
- const word = text.trim().toLowerCase();
437
- if (CONFIRM_WORDS.has(word)) {
438
- resolver("confirmed");
439
- }
440
- else {
441
- resolver("denied");
442
- }
443
- return true;
648
+ // The status bubble shows the latest reasoning heading (models write "**Heading**"
649
+ // before each reasoning step), never the raw reasoning text itself.
650
+ function reasoningStatus(reasoning) {
651
+ const headings = [...reasoning.matchAll(/\*\*([^*\n]+)\*\*/g)];
652
+ const last = headings.at(-1)?.[1]?.trim();
653
+ return last ? `${last}…` : null;
444
654
  }
445
655
  const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
446
656
  function teamsTurnKey(conversationId) {
447
657
  return `teams:${conversationId}`;
448
658
  }
659
+ function teamsAdmissionKey(conversationId) {
660
+ return `teams-admission:${conversationId}`;
661
+ }
662
+ const queuedTeamsTurns = new Map();
663
+ const activeTeamsActors = new Map();
664
+ const consumingTeamsSteering = new Set();
665
+ function drainTeamsSteering(key) {
666
+ const followUps = _turnCoordinator.drainFollowUps(key);
667
+ const consumed = new Set(followUps);
668
+ const queue = queuedTeamsTurns.get(key);
669
+ if (queue)
670
+ queuedTeamsTurns.set(key, queue.filter(turn => !turn.steering || !consumed.has(turn.steering)));
671
+ return followUps;
672
+ }
673
+ /** Context-bearing replies cannot steer an already-running tool loop. Keep the
674
+ * original SDK context/transport alive until a fresh handleTeamsMessage lease.
675
+ * Ordinary same-actor steering still uses the existing coordinator buffer. */
676
+ async function coordinateTeamsTurn(conversationId, text, identity, run) {
677
+ const key = teamsTurnKey(conversationId);
678
+ const actorKey = JSON.stringify([identity.aadObjectId || identity.userId || "", identity.tenantId ?? ""]);
679
+ if (!_turnCoordinator.tryBeginTurn(key)) {
680
+ if (!identity.replyToId && !/\bfeedback:/i.test(text) && consumingTeamsSteering.has(key) &&
681
+ activeTeamsActors.get(key) === actorKey && !queuedTeamsTurns.get(key)?.some(turn => !turn.steering)) {
682
+ const steering = { conversationId, text, receivedAt: Date.now(), effect: (0, continuity_1.classifySteeringFollowUpEffect)(text) };
683
+ _turnCoordinator.enqueueFollowUp(key, steering);
684
+ const queue = queuedTeamsTurns.get(key) ?? [];
685
+ queue.push({ actorKey, run, steering, resolve: () => { }, reject: () => { } });
686
+ queuedTeamsTurns.set(key, queue);
687
+ return;
688
+ }
689
+ await new Promise((resolve, reject) => {
690
+ const queue = queuedTeamsTurns.get(key) ?? [];
691
+ queue.push({ actorKey, run, resolve, reject });
692
+ queuedTeamsTurns.set(key, queue);
693
+ });
694
+ return;
695
+ }
696
+ let next = { actorKey, run, resolve: () => { }, reject: () => { } };
697
+ let initialError;
698
+ let first = true;
699
+ try {
700
+ while (next) {
701
+ activeTeamsActors.set(key, next.actorKey);
702
+ if (next.steering) {
703
+ for (const followUp of _turnCoordinator.drainFollowUps(key)) {
704
+ if (followUp !== next.steering)
705
+ _turnCoordinator.enqueueFollowUp(key, followUp);
706
+ }
707
+ }
708
+ try {
709
+ await next.run();
710
+ next.resolve();
711
+ }
712
+ catch (error) {
713
+ if (first)
714
+ initialError = error;
715
+ next.reject(error);
716
+ }
717
+ first = false;
718
+ // Preserve received order while identity/admission I/O finishes, without holding admission through a model turn.
719
+ await _turnCoordinator.withTurnLock(teamsAdmissionKey(conversationId), async () => { });
720
+ next = queuedTeamsTurns.get(key)?.shift();
721
+ }
722
+ }
723
+ finally {
724
+ queuedTeamsTurns.delete(key);
725
+ activeTeamsActors.delete(key);
726
+ _turnCoordinator.endTurn(key);
727
+ }
728
+ if (initialError !== undefined)
729
+ throw initialError;
730
+ }
449
731
  async function withConversationLock(convId, fn) {
450
732
  await _turnCoordinator.withTurnLock(teamsTurnKey(convId), fn);
451
733
  }
@@ -453,13 +735,22 @@ async function withConversationLock(convId, fn) {
453
735
  // are deleted while the process is alive.
454
736
  function getFriendStore() {
455
737
  const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
456
- return new store_file_1.FileFriendStore(friendsPath);
738
+ return new friends_1.FileFriendStore(friendsPath);
739
+ }
740
+ function reportTeamsRequest(stage, conversationId, context, details = {}) {
741
+ if (!context?.inboundActivityId)
742
+ return;
743
+ (0, runtime_1.emitNervesEvent)({
744
+ component: "channels", event: "channel.teams_request_stage", message: "Teams conversational request stage",
745
+ meta: { stage, inboundActivityId: context.inboundActivityId, conversationId, tenantId: context.tenantId, botId: context.botApi?.id, ...details },
746
+ });
457
747
  }
458
748
  function createTeamsCommandRegistry() {
459
749
  const registry = (0, commands_1.createCommandRegistry)();
460
750
  (0, commands_1.registerDefaultCommands)(registry);
461
751
  return registry;
462
752
  }
753
+ /* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
463
754
  function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
464
755
  const parsed = (0, commands_1.parseSlashCommand)(text);
465
756
  if (!parsed)
@@ -483,22 +774,21 @@ function handleTeamsSlashCommand(text, registry, friendId, conversationId, strea
483
774
  }
484
775
  return null;
485
776
  }
777
+ /* v8 ignore stop */
486
778
  // Handle an incoming Teams message
487
- async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
779
+ async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides, runtimeOverrides) {
488
780
  const turnKey = teamsTurnKey(conversationId);
781
+ const agentName = (0, identity_1.getAgentName)();
782
+ const owner = { agentName, agentRoot: (0, identity_1.getAgentRoot)(agentName) };
489
783
  // NOTE: Confirmation resolution is handled in the app.on("message") handler
490
784
  // BEFORE the conversation lock. By the time we get here, any pending
491
785
  // confirmation has already been resolved and the reply consumed.
492
- // Send first thinking phrase immediately so the user sees feedback
493
- // before sync I/O (session load, trim) blocks the event loop.
494
- stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
495
- await new Promise(r => setImmediate(r));
496
786
  // Resolve identity provider early for friend resolution + slash command session path
497
787
  const store = getFriendStore();
498
788
  const provider = teamsContext?.aadObjectId ? "aad" : "teams-conversation";
499
789
  const externalId = teamsContext?.aadObjectId || conversationId;
500
790
  // Build FriendResolver for the pipeline
501
- const resolver = new resolver_1.FriendResolver(store, {
791
+ const resolver = new friends_1.FriendResolver(store, {
502
792
  provider,
503
793
  externalId,
504
794
  tenantId: teamsContext?.tenantId,
@@ -508,133 +798,247 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
508
798
  // Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
509
799
  const resolvedContext = await resolver.resolve();
510
800
  const friendId = resolvedContext.friend.id;
511
- const registry = createTeamsCommandRegistry();
512
- // Check for slash commands (before pipeline -- these are transport-level concerns)
513
- if (handleTeamsSlashCommand(text, registry, friendId, conversationId, stream)) {
514
- return;
515
- }
516
801
  // ── Teams adapter concerns: controller, callbacks, session path ──────────
517
802
  const controller = new AbortController();
518
803
  const channelConfig = (0, config_2.getTeamsChannelConfig)();
519
- const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs });
520
804
  const traceId = (0, nerves_1.createTraceId)();
805
+ const observedStream = {
806
+ emit(activity) {
807
+ const result = stream.emit(activity);
808
+ reportTeamsRequest("reply_emitted", conversationId, teamsContext, { traceId, delivery: "unconfirmed_stream" });
809
+ return result;
810
+ },
811
+ update(text) { return stream.update(text); },
812
+ close: stream.close.bind(stream),
813
+ };
814
+ const callbacks = createTeamsCallbacks(observedStream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
521
815
  const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
522
- const teamsCapabilities = (0, channel_1.getChannelCapabilities)("teams");
523
- const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "teams", conversationId);
524
- // Build Teams-specific toolContext fields for injection into the pipeline
525
- const teamsToolContext = teamsContext ? {
526
- graphToken: teamsContext.graphToken,
527
- adoToken: teamsContext.adoToken,
528
- githubToken: teamsContext.githubToken,
529
- signin: teamsContext.signin,
530
- summarize: (0, core_1.createSummarize)(),
531
- tenantId: teamsContext.tenantId,
532
- botApi: teamsContext.botApi,
533
- } : {};
534
- let currentText = text;
535
- const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
536
- while (true) {
537
- let drainedSteeringFollowUps = [];
538
- // Build runAgentOptions with Teams-specific fields
539
- const agentOptions = {
540
- traceId,
541
- toolContext: teamsToolContext,
542
- mcpManager,
543
- drainSteeringFollowUps: () => {
544
- drainedSteeringFollowUps = _turnCoordinator.drainFollowUps(turnKey)
545
- .map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
546
- return drainedSteeringFollowUps;
547
- },
548
- };
549
- if (channelConfig.skipConfirmation)
550
- agentOptions.skipConfirmation = true;
551
- // ── Call shared pipeline ──────────────────────────────────────────
552
- const result = await (0, pipeline_1.handleInboundTurn)({
553
- channel: "teams",
554
- sessionKey: conversationId,
555
- capabilities: teamsCapabilities,
556
- messages: [{ role: "user", content: currentText }],
557
- continuityIngressTexts: [currentText],
558
- callbacks,
559
- friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
560
- sessionLoader: {
561
- loadOrCreate: async () => {
562
- const existing = (0, context_1.loadSession)(sessPath);
563
- const messages = existing?.messages && existing.messages.length > 0
564
- ? existing.messages
565
- : [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", { mcpManager }, resolvedContext) }];
566
- (0, core_1.repairOrphanedToolCalls)(messages);
567
- return { messages, sessionPath: sessPath, state: existing?.state };
568
- },
569
- },
570
- pendingDir,
571
- friendStore: store,
572
- provider,
573
- externalId,
574
- tenantId: teamsContext?.tenantId,
575
- isGroupChat: false,
576
- groupHasFamilyMember: false,
577
- hasExistingGroupWithFamily: false,
578
- enforceTrustGate: trust_gate_1.enforceTrustGate,
579
- drainPending: pending_1.drainPending,
580
- drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)((0, identity_1.getAgentName)(), deferredFriendId),
581
- runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
582
- ...opts,
583
- toolContext: {
584
- /* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
585
- signin: async () => undefined,
586
- ...opts?.toolContext,
587
- summarize: teamsToolContext.summarize,
588
- },
589
- }),
590
- postTurn: context_1.postTurn,
591
- accumulateFriendTokens: tokens_1.accumulateFriendTokens,
592
- signal: controller.signal,
593
- runAgentOptions: agentOptions,
594
- });
595
- // ── Handle gate result ────────────────────────────────────────
596
- if (!result.gateResult.allowed) {
597
- if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
598
- stream.emit(result.gateResult.autoReply);
816
+ const teamsCapabilities = (0, friends_1.getChannelCapabilities)("teams");
817
+ const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, "teams", conversationId);
818
+ const runWithLease = runtimeOverrides?._withSessionTurnLease ?? session_transaction_1.withSessionTurnLease;
819
+ try {
820
+ await runWithLease(sessPath, async (sessionTurnLease) => {
821
+ reportTeamsRequest("running", conversationId, teamsContext, { traceId, friendId });
822
+ // Messages sent into this conversation while another turn held it wait outside the session; they go in first.
823
+ (0, outbound_record_1.drainOutboundRecords)(owner.agentRoot, friendId, "teams", conversationId, sessionTurnLease);
824
+ // Proactive feedback acknowledgements are not magically in this model session's
825
+ // history. Recover their frozen packet only after identity and lease ownership.
826
+ let feedbackReply;
827
+ try {
828
+ feedbackReply = (0, feedback_reply_1.resolveFeedbackReply)({ agentRoot: owner.agentRoot, friendId,
829
+ conversationId, replyToId: teamsContext?.replyToId, text });
599
830
  }
831
+ catch (error) {
832
+ if (!(error instanceof feedback_reply_1.FeedbackReplyRefusal))
833
+ throw error;
834
+ await stream.emit(error.message);
835
+ return;
836
+ }
837
+ // Do not emit any activity until this turn owns the durable session.
838
+ if (!reactionOverrides) {
839
+ stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
840
+ }
841
+ await new Promise(r => setImmediate(r));
842
+ // Build Teams-specific toolContext fields for injection into the pipeline
843
+ const teamsToolContext = { ...owner, ...(teamsContext ? {
844
+ graphToken: teamsContext.graphToken,
845
+ adoToken: teamsContext.adoToken,
846
+ githubToken: teamsContext.githubToken,
847
+ signin: teamsContext.signin,
848
+ summarize: (0, core_1.createSummarize)("human"),
849
+ tenantId: teamsContext.tenantId,
850
+ botApi: teamsContext.botApi,
851
+ teamsSendImage: teamsContext.teamsSendImage,
852
+ } : {}) };
853
+ let currentText = text;
854
+ const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)(owner) ?? undefined;
855
+ while (true) {
856
+ let drainedSteeringFollowUps = [];
857
+ // Build runAgentOptions with Teams-specific fields
858
+ const agentOptions = {
859
+ traceId,
860
+ toolContext: {
861
+ ...teamsToolContext,
862
+ ...(feedbackReply ? { currentShepherdSurface: feedbackReply.source } : {}),
863
+ },
864
+ mcpManager,
865
+ drainSteeringFollowUps: () => {
866
+ drainedSteeringFollowUps = drainTeamsSteering(turnKey)
867
+ .map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
868
+ return drainedSteeringFollowUps;
869
+ },
870
+ ...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
871
+ };
872
+ // ── Call shared pipeline ──────────────────────────────────────────
873
+ // Capture terminal errors — failover message replaces the error card if it triggers
874
+ let capturedTerminalError = null;
875
+ const teamsFailoverState = (() => {
876
+ if (!teamsFailoverStates.has(conversationId)) {
877
+ teamsFailoverStates.set(conversationId, { pending: null });
878
+ }
879
+ return teamsFailoverStates.get(conversationId);
880
+ })();
881
+ /* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
882
+ const failoverAwareCallbacks = {
883
+ ...callbacks,
884
+ onError: (error, severity) => {
885
+ if (severity === "terminal" && teamsFailoverState) {
886
+ capturedTerminalError = error;
887
+ return;
888
+ }
889
+ callbacks.onError(error, severity);
890
+ },
891
+ };
892
+ /* v8 ignore stop */
893
+ const result = await (0, pipeline_1.handleInboundTurn)({
894
+ channel: "teams",
895
+ sessionKey: conversationId,
896
+ capabilities: teamsCapabilities,
897
+ messages: [{ role: "user", content: currentText }],
898
+ continuityIngressTexts: [currentText],
899
+ callbacks: failoverAwareCallbacks,
900
+ sessionTurnLease,
901
+ friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
902
+ sessionLoader: {
903
+ loadOrCreate: async () => {
904
+ const existing = (0, context_1.loadSession)(sessPath);
905
+ const messages = existing?.messages && existing.messages.length > 0
906
+ ? existing.messages
907
+ : [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("teams", {}, resolvedContext)) }];
908
+ (0, core_1.repairOrphanedToolCalls)(messages);
909
+ return {
910
+ messages,
911
+ sessionPath: sessPath,
912
+ state: existing?.state,
913
+ events: existing?.events,
914
+ structuredOutputs: existing?.structuredOutputs,
915
+ };
916
+ },
917
+ },
918
+ pendingDir,
919
+ friendStore: store,
920
+ provider,
921
+ externalId,
922
+ tenantId: teamsContext?.tenantId,
923
+ isGroupChat: false,
924
+ groupHasFamilyMember: false,
925
+ hasExistingGroupWithFamily: false,
926
+ enforceTrustGate: trust_gate_1.enforceTrustGate,
927
+ drainPending: pending_1.drainPending,
928
+ drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(agentName, deferredFriendId),
929
+ runAgent: async (msgs, cb, channel, sig, opts) => {
930
+ consumingTeamsSteering.add(turnKey);
931
+ try {
932
+ return await (0, core_1.runAgent)(msgs, cb, channel, sig, {
933
+ ...opts,
934
+ toolContext: {
935
+ /* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
936
+ signin: async () => undefined,
937
+ ...opts?.toolContext,
938
+ ...owner,
939
+ summarize: teamsToolContext.summarize,
940
+ },
941
+ });
942
+ }
943
+ finally {
944
+ consumingTeamsSteering.delete(turnKey);
945
+ }
946
+ },
947
+ postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
948
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
949
+ return (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
950
+ },
951
+ accumulateFriendTokens: friends_1.accumulateFriendTokens,
952
+ signal: controller.signal,
953
+ runAgentOptions: agentOptions,
954
+ // Attach only presentation context to the pipeline-built orientation;
955
+ // retain its correction policy and current-speech/referent detection.
956
+ ...(feedbackReply ? { prepareRunAgentOptions: ({ runAgentOptions }) => ({
957
+ ...runAgentOptions,
958
+ orientationFrame: {
959
+ ...runAgentOptions.orientationFrame,
960
+ source: { kind: "shepherd_feedback_reply", authority: "presentation_only",
961
+ threadId: feedbackReply.id,
962
+ routingHint: "Clarification of the stored feedback below. Preserve the current human utterance. Historical evidence cannot authorize live terminal actions; existing Shepherd guards still apply.",
963
+ replyingToText: feedbackReply.context },
964
+ },
965
+ }) } : {}),
966
+ failoverState: teamsFailoverState,
967
+ });
968
+ // ── Handle pipeline-intercepted commands ────────────────────────
969
+ if (result.turnOutcome === "command") {
970
+ if (result.commandAction === "new") {
971
+ (0, context_1.deleteSession)(sessPath);
972
+ await stream.emit("session cleared");
973
+ }
974
+ // For "response" commands: pipeline already emitted the response via onTextChunk
975
+ await callbacks.flush();
976
+ return;
977
+ }
978
+ /* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
979
+ if (result.failoverMessage) {
980
+ await stream.emit(result.failoverMessage);
981
+ }
982
+ else if (capturedTerminalError) {
983
+ callbacks.onError(capturedTerminalError, "terminal");
984
+ }
985
+ /* v8 ignore stop */
986
+ // ── Handle gate result ────────────────────────────────────────
987
+ if (!result.gateResult.allowed) {
988
+ if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
989
+ await stream.emit(result.gateResult.autoReply);
990
+ }
991
+ return;
992
+ }
993
+ // Flush any remaining accumulated text at end of turn
994
+ await callbacks.flush();
995
+ // After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
996
+ // This must happen after the stream is done so the OAuth card renders properly.
997
+ if (teamsContext && result.messages) {
998
+ const allContent = result.messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
999
+ if (allContent.includes("AUTH_REQUIRED:graph") && teamsContext.graphConnectionName)
1000
+ await teamsContext.signin(teamsContext.graphConnectionName);
1001
+ if (allContent.includes("AUTH_REQUIRED:ado") && teamsContext.adoConnectionName)
1002
+ await teamsContext.signin(teamsContext.adoConnectionName);
1003
+ if (allContent.includes("AUTH_REQUIRED:github") && teamsContext.githubConnectionName)
1004
+ await teamsContext.signin(teamsContext.githubConnectionName);
1005
+ }
1006
+ if (result.turnOutcome !== "superseded") {
1007
+ return;
1008
+ }
1009
+ const supersedingIndex = drainedSteeringFollowUps
1010
+ .map((followUp) => followUp.effect)
1011
+ .lastIndexOf("clear_and_supersede");
1012
+ if (supersedingIndex < 0) {
1013
+ return;
1014
+ }
1015
+ // A session reset must not carry the old feedback binding into its replay.
1016
+ feedbackReply = null;
1017
+ const supersedingFollowUp = drainedSteeringFollowUps[supersedingIndex];
1018
+ const replayTail = drainedSteeringFollowUps
1019
+ .slice(supersedingIndex + 1)
1020
+ .map((followUp) => followUp.text.trim())
1021
+ .filter((followUpText) => followUpText.length > 0)
1022
+ .join("\n");
1023
+ if (replayTail) {
1024
+ currentText = replayTail;
1025
+ continue;
1026
+ }
1027
+ if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
1028
+ return;
1029
+ }
1030
+ currentText = supersedingFollowUp.text;
1031
+ }
1032
+ });
1033
+ }
1034
+ catch (error) {
1035
+ if (error instanceof session_transaction_1.SessionTurnBusyError || (error instanceof Error && error.name === "SessionTurnBusyError")) {
1036
+ reportTeamsRequest("failed", conversationId, teamsContext, { traceId, reason: "lease_busy" });
1037
+ await stream.emit("this conversation is busy finishing another turn; please try again");
600
1038
  return;
601
1039
  }
602
- // Flush any remaining accumulated text at end of turn
603
- await callbacks.flush();
604
- // After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
605
- // This must happen after the stream is done so the OAuth card renders properly.
606
- if (teamsContext && result.messages) {
607
- const allContent = result.messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
608
- if (allContent.includes("AUTH_REQUIRED:graph") && teamsContext.graphConnectionName)
609
- await teamsContext.signin(teamsContext.graphConnectionName);
610
- if (allContent.includes("AUTH_REQUIRED:ado") && teamsContext.adoConnectionName)
611
- await teamsContext.signin(teamsContext.adoConnectionName);
612
- if (allContent.includes("AUTH_REQUIRED:github") && teamsContext.githubConnectionName)
613
- await teamsContext.signin(teamsContext.githubConnectionName);
614
- }
615
- if (result.turnOutcome !== "superseded") {
616
- return;
617
- }
618
- const supersedingIndex = drainedSteeringFollowUps
619
- .map((followUp) => followUp.effect)
620
- .lastIndexOf("clear_and_supersede");
621
- if (supersedingIndex < 0) {
622
- return;
623
- }
624
- const supersedingFollowUp = drainedSteeringFollowUps[supersedingIndex];
625
- const replayTail = drainedSteeringFollowUps
626
- .slice(supersedingIndex + 1)
627
- .map((followUp) => followUp.text.trim())
628
- .filter((followUpText) => followUpText.length > 0)
629
- .join("\n");
630
- if (replayTail) {
631
- currentText = replayTail;
632
- continue;
633
- }
634
- if (handleTeamsSlashCommand(supersedingFollowUp.text, registry, friendId, conversationId, stream, false)) {
635
- return;
636
- }
637
- currentText = supersedingFollowUp.text;
1040
+ reportTeamsRequest("failed", conversationId, teamsContext, { traceId, reason: "turn_failed" });
1041
+ throw error;
638
1042
  }
639
1043
  }
640
1044
  // Internal port for the secondary bot App (not exposed externally).
@@ -675,6 +1079,7 @@ function createBotApp(teamsConfig) {
675
1079
  tenantId: teamsConfig.tenantId,
676
1080
  oauth: { defaultConnectionName: oauthConfig.graphConnectionName },
677
1081
  ...mentionStripping,
1082
+ logger: createTeamsSdkLogger(),
678
1083
  }),
679
1084
  mode: "Bot Service (client secret)",
680
1085
  };
@@ -687,21 +1092,50 @@ function createBotApp(teamsConfig) {
687
1092
  ...(teamsConfig.managedIdentityClientId ? { managedIdentityClientId: teamsConfig.managedIdentityClientId } : {}),
688
1093
  oauth: { defaultConnectionName: oauthConfig.graphConnectionName },
689
1094
  ...mentionStripping,
1095
+ logger: createTeamsSdkLogger(),
690
1096
  }),
691
1097
  mode: "Bot Service (managed identity)",
692
1098
  };
693
1099
  }
694
1100
  else {
1101
+ // With no credentials the Teams SDK validates no sender, so anyone who can reach the port can
1102
+ // talk to the agent. That is only acceptable when a developer asks for it on their own machine.
1103
+ if (process.env.OURO_TEAMS_DEVTOOLS !== "1") {
1104
+ throw new Error("Teams bot credentials are missing (teams.clientId), so the Teams sense will not start: without them it would accept messages from anyone. Run `ouro connect teams`, or set OURO_TEAMS_DEVTOOLS=1 for local development with no real bot.");
1105
+ }
695
1106
  return {
696
1107
  app: new teams_apps_1.App({
697
1108
  plugins: [new teams_dev_1.DevtoolsPlugin()],
698
1109
  ...mentionStripping,
1110
+ logger: createTeamsSdkLogger(),
699
1111
  }),
700
1112
  mode: "DevtoolsPlugin",
701
1113
  };
702
1114
  }
703
1115
  }
704
- /* v8 ignore stop */
1116
+ function createTeamsSdkLogger(name = "teams-sdk") {
1117
+ const verbose = /stream/i.test(name);
1118
+ const write = (level, msg) => {
1119
+ if ((level === "debug" || level === "trace" || level === "info") && !verbose)
1120
+ return;
1121
+ (0, runtime_1.emitNervesEvent)({
1122
+ level: level === "error" ? "error" : level === "warn" ? "warn" : "info",
1123
+ component: "senses",
1124
+ event: "teams.sdk_log",
1125
+ message: `[${name}] ${msg.map((m) => (m instanceof Error ? m.message : typeof m === "string" ? m : JSON.stringify(m))).join(" ").slice(0, 300)}`,
1126
+ meta: { logger: name, sdkLevel: level },
1127
+ });
1128
+ };
1129
+ return {
1130
+ debug: (...m) => write("debug", m),
1131
+ info: (...m) => write("info", m),
1132
+ warn: (...m) => write("warn", m),
1133
+ error: (...m) => write("error", m),
1134
+ trace: (...m) => write("trace", m),
1135
+ log: (level, ...m) => write(level, m),
1136
+ child: (child) => createTeamsSdkLogger(`${name}/${child}`),
1137
+ };
1138
+ }
705
1139
  // Register message, verify-state, and error handlers on an App instance.
706
1140
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
707
1141
  function registerBotHandlers(app, label) {
@@ -711,7 +1145,7 @@ function registerBotHandlers(app, label) {
711
1145
  // (graph + ado + github). The verifyState activity only carries a `state`
712
1146
  // code with no connectionName, so we try each configured connection until
713
1147
  // one succeeds.
714
- app.on("signin.verify-state", async (ctx) => {
1148
+ app.on("signin.verify-state", (async (ctx) => {
715
1149
  const { api, activity } = ctx;
716
1150
  if (!activity.value?.state)
717
1151
  return { status: 404 };
@@ -730,137 +1164,256 @@ function registerBotHandlers(app, label) {
730
1164
  }
731
1165
  (0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state failed for all connections`, meta: {} });
732
1166
  return { status: 412 };
1167
+ }));
1168
+ // Handle Teams feedback reactions (thumbs up/down on AI-generated messages).
1169
+ // SDK routes message/submitAction with actionName "feedback" to this event.
1170
+ /* v8 ignore start -- Teams SDK invoke handler; requires live SDK context @preserve */
1171
+ app.on("message.submit.feedback", async (ctx) => {
1172
+ const { stream, activity } = ctx;
1173
+ const reaction = activity.value?.actionValue?.reaction;
1174
+ const comment = activity.value?.actionValue?.feedback;
1175
+ const convId = activity.conversation?.id || "unknown";
1176
+ // Validate payload — graceful no-op for malformed invocations
1177
+ if (activity.value?.actionName !== "feedback" || !reaction) {
1178
+ return undefined;
1179
+ }
1180
+ const syntheticText = buildFeedbackSyntheticText(reaction, comment);
1181
+ await coordinateTeamsTurn(convId, syntheticText, {
1182
+ aadObjectId: activity.from?.aadObjectId, userId: activity.from?.id,
1183
+ tenantId: activity.conversation?.tenantId, replyToId: activity.replyToId,
1184
+ }, async () => {
1185
+ try {
1186
+ const teamsContext = {
1187
+ replyToId: activity.replyToId,
1188
+ signin: async () => undefined,
1189
+ aadObjectId: activity.from?.aadObjectId,
1190
+ tenantId: activity.conversation?.tenantId,
1191
+ displayName: activity.from?.name,
1192
+ };
1193
+ const ctxSend = async (t) => {
1194
+ await ctx.send({ type: "message", text: t, replyToId: activity.replyToId, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
1195
+ };
1196
+ await handleTeamsMessage(syntheticText, stream, convId, teamsContext, ctxSend, { isReactionSignal: true, suppressEmptyStreamMessage: true });
1197
+ }
1198
+ catch (err) {
1199
+ const msg = err instanceof Error ? err.message : String(err);
1200
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.feedback_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
1201
+ }
1202
+ });
1203
+ return undefined;
1204
+ });
1205
+ /* v8 ignore stop */
1206
+ // Handle bot install — send welcome Adaptive Card with prompt starters.
1207
+ /* v8 ignore start -- Teams SDK install handler; requires live SDK context @preserve */
1208
+ app.on("install.add", async (ctx) => {
1209
+ try {
1210
+ const card = buildWelcomeCard();
1211
+ await ctx.send({
1212
+ type: "message",
1213
+ attachments: [{
1214
+ contentType: "application/vnd.microsoft.card.adaptive",
1215
+ content: card,
1216
+ }],
1217
+ entities: aiLabelEntities(),
1218
+ channelData: { feedbackLoopEnabled: true },
1219
+ });
1220
+ }
1221
+ catch (err) {
1222
+ const msg = err instanceof Error ? err.message : String(err);
1223
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.welcome_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
1224
+ }
1225
+ return undefined;
733
1226
  });
1227
+ /* v8 ignore stop */
734
1228
  app.on("message", async (ctx) => {
735
- const { stream, activity, api, signin } = ctx;
1229
+ const { activity, api, signin } = ctx;
736
1230
  const text = activity.text || "";
737
1231
  const convId = activity.conversation?.id || "unknown";
738
1232
  const turnKey = teamsTurnKey(convId);
739
1233
  const userId = activity.from?.id || "";
740
1234
  const channelId = activity.channelId || "msteams";
741
- (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) } });
742
- // Resolve pending confirmations IMMEDIATELY — before token fetches or
743
- // the conversation lock. The original message holds the lock while
744
- // awaiting confirmation, so acquiring it here would deadlock. Token
745
- // fetches are also unnecessary (and slow) for a simple yes/no reply.
746
- if (resolvePendingConfirmation(convId, text)) {
747
- return;
748
- }
749
- const commandRegistry = createTeamsCommandRegistry();
750
- const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
751
- if (parsedSlashCommand) {
752
- const dispatchResult = commandRegistry.dispatch(parsedSlashCommand.command, { channel: "teams" });
753
- if (dispatchResult.handled && dispatchResult.result) {
754
- if (dispatchResult.result.action === "response") {
755
- stream.emit(dispatchResult.result.message || "");
756
- return;
1235
+ const requestContext = {
1236
+ inboundActivityId: activity.id, tenantId: activity.conversation?.tenantId,
1237
+ botApi: app.id && api ? { id: app.id, conversations: api.conversations } : undefined,
1238
+ };
1239
+ const sendText = async (text) => {
1240
+ try {
1241
+ const receipt = await ctx.send({ type: "message", text, replyToId: activity.id, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
1242
+ const messageId = receipt && typeof receipt === "object" && "id" in receipt && typeof receipt.id === "string" ? receipt.id : undefined;
1243
+ reportTeamsRequest(messageId ? "replied" : "reply_emitted", convId, requestContext, { delivery: messageId ? "accepted" : "unconfirmed", ...(messageId ? { outboundActivityId: messageId } : {}) });
1244
+ }
1245
+ catch (error) {
1246
+ reportTeamsRequest("failed", convId, requestContext, { reason: "send_unknown" });
1247
+ throw error;
1248
+ }
1249
+ };
1250
+ // HttpStream queues void emits and retries unknown HTTP failures internally.
1251
+ // Use the SDK's single-attempt, awaitable send instead of that hidden retry loop.
1252
+ const stream = {
1253
+ emit: value => sendText(typeof value === "string" ? value : value.text),
1254
+ update: async () => {
1255
+ try {
1256
+ await ctx.send({ type: "typing" });
1257
+ }
1258
+ catch (error) {
1259
+ (0, runtime_1.emitNervesEvent)({
1260
+ level: "warn", component: "channels", event: "channel.teams_typing_failed",
1261
+ message: "Teams rejected an advisory typing update; the reply is still pending",
1262
+ meta: { conversationId: convId, inboundActivityId: activity.id,
1263
+ error: error instanceof Error ? error.message : String(error) },
1264
+ });
1265
+ }
1266
+ },
1267
+ close: async () => undefined,
1268
+ };
1269
+ (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: `[${label}] incoming teams message`, meta: { stage: "received", inboundActivityId: activity.id, botId: app.id, tenantId: requestContext.tenantId, userId: userId.slice(0, 12), conversationId: convId } });
1270
+ let work;
1271
+ await _turnCoordinator.withTurnLock(teamsAdmissionKey(convId), async () => {
1272
+ try {
1273
+ if (process.env.OURO_TEAMS_DEVTOOLS !== "1" && [
1274
+ app.id, activity.id, activity.channelId, activity.conversation?.id,
1275
+ activity.conversation?.tenantId, activity.from?.aadObjectId || activity.from?.id,
1276
+ ].some(value => typeof value !== "string" || !value.trim())) {
1277
+ throw new session_transaction_1.SessionTransactionError("Teams activity identity is incomplete");
757
1278
  }
758
- if (dispatchResult.result.action === "new") {
759
- const commandStore = getFriendStore();
760
- const commandProvider = activity.from?.aadObjectId ? "aad" : "teams-conversation";
761
- const commandExternalId = activity.from?.aadObjectId || convId;
762
- const commandResolver = new resolver_1.FriendResolver(commandStore, {
763
- provider: commandProvider,
764
- externalId: commandExternalId,
1279
+ if (activity.id) {
1280
+ const store = getFriendStore();
1281
+ const identity = await new friends_1.FriendResolver(store, {
1282
+ provider: activity.from?.aadObjectId ? "aad" : "teams-conversation",
1283
+ externalId: activity.from?.aadObjectId || convId,
765
1284
  tenantId: activity.conversation?.tenantId,
766
1285
  displayName: activity.from?.name || "Unknown",
767
1286
  channel: "teams",
1287
+ }).resolve();
1288
+ const admission = (0, session_transaction_1.admitSessionActivity)((0, config_2.sessionPath)(identity.friend.id, "teams", convId), {
1289
+ namespace: JSON.stringify(["teams.message", channelId, app.id?.toLowerCase() ?? null,
1290
+ activity.conversation?.tenantId?.toLowerCase() ?? null,
1291
+ activity.from?.aadObjectId?.toLowerCase() || userId, convId]),
1292
+ activityId: activity.id,
1293
+ payloadDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify([text, activity.replyToId ?? null])).digest("hex"),
768
1294
  });
769
- const commandContext = await commandResolver.resolve();
770
- (0, context_1.deleteSession)((0, config_2.sessionPath)(commandContext.friend.id, "teams", convId));
771
- stream.emit("session cleared");
772
- if (_turnCoordinator.isTurnActive(turnKey)) {
773
- _turnCoordinator.enqueueFollowUp(turnKey, {
774
- conversationId: convId,
775
- text,
776
- receivedAt: Date.now(),
777
- effect: "clear_and_supersede",
778
- });
1295
+ if (admission.state === "duplicate") {
1296
+ reportTeamsRequest("duplicate_held", convId, requestContext, { receiptId: admission.receiptId, outcome: "not_replayed" });
1297
+ return;
779
1298
  }
780
- return;
781
1299
  }
1300
+ reportTeamsRequest("admitted", convId, requestContext);
782
1301
  }
783
- }
784
- // If this conversation already has an active turn, steer follow-up input
785
- // into that turn and avoid starting a second concurrent turn.
786
- if (!_turnCoordinator.tryBeginTurn(turnKey)) {
787
- _turnCoordinator.enqueueFollowUp(turnKey, {
788
- conversationId: convId,
789
- text,
790
- receivedAt: Date.now(),
791
- effect: (0, continuity_1.classifySteeringFollowUpEffect)(text),
792
- });
793
- return;
794
- }
795
- try {
796
- // Resolve OAuth connection names for this user's tenant (supports per-tenant overrides).
797
- const tenantId = activity.conversation?.tenantId;
798
- const tenantOAuth = (0, config_1.resolveOAuthForTenant)(tenantId);
799
- // Fetch tokens for both OAuth connections independently.
800
- // Failures are silently caught -- the tool handler will request signin if needed.
801
- let graphToken;
802
- let adoToken;
803
- let githubToken;
804
- try {
805
- const graphRes = await api.users.token.get({ userId, connectionName: tenantOAuth.graphConnectionName, channelId });
806
- graphToken = graphRes?.token;
807
- }
808
- catch { /* no token yet — tool handler will trigger signin */ }
809
- try {
810
- const adoRes = await api.users.token.get({ userId, connectionName: tenantOAuth.adoConnectionName, channelId });
811
- adoToken = adoRes?.token;
1302
+ catch (error) {
1303
+ reportTeamsRequest("failed", convId, requestContext, { reason: "activity_admission_failed" });
1304
+ (0, runtime_1.emitNervesEvent)({
1305
+ level: "error", component: "channels", event: "channel.activity_admission_error",
1306
+ message: "Teams activity was not safely admitted; no new turn or steering was started",
1307
+ meta: { conversationId: convId, inboundActivityId: activity.id, reason: "activity_admission_failed" },
1308
+ });
1309
+ throw error;
812
1310
  }
813
- catch { /* no token yet — tool handler will trigger signin */ }
814
- try {
815
- const githubRes = await api.users.token.get({ userId, connectionName: tenantOAuth.githubConnectionName, channelId });
816
- githubToken = githubRes?.token;
1311
+ const commandRegistry = createTeamsCommandRegistry();
1312
+ const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
1313
+ if (parsedSlashCommand) {
1314
+ const dispatchResult = commandRegistry.dispatch(parsedSlashCommand.command, { channel: "teams" });
1315
+ if (dispatchResult.handled && dispatchResult.result) {
1316
+ if (dispatchResult.result.action === "response") {
1317
+ await stream.emit(dispatchResult.result.message || "");
1318
+ return;
1319
+ }
1320
+ if (dispatchResult.result.action === "new") {
1321
+ const commandStore = getFriendStore();
1322
+ const commandProvider = activity.from?.aadObjectId ? "aad" : "teams-conversation";
1323
+ const commandExternalId = activity.from?.aadObjectId || convId;
1324
+ const commandResolver = new friends_1.FriendResolver(commandStore, {
1325
+ provider: commandProvider,
1326
+ externalId: commandExternalId,
1327
+ tenantId: activity.conversation?.tenantId,
1328
+ displayName: activity.from?.name || "Unknown",
1329
+ channel: "teams",
1330
+ });
1331
+ const commandContext = await commandResolver.resolve();
1332
+ (0, context_1.deleteSession)((0, config_2.sessionPath)(commandContext.friend.id, "teams", convId));
1333
+ await stream.emit("session cleared");
1334
+ if (_turnCoordinator.isTurnActive(turnKey)) {
1335
+ _turnCoordinator.enqueueFollowUp(turnKey, {
1336
+ conversationId: convId,
1337
+ text,
1338
+ receivedAt: Date.now(),
1339
+ effect: "clear_and_supersede",
1340
+ });
1341
+ }
1342
+ return;
1343
+ }
1344
+ }
817
1345
  }
818
- catch { /* no token yet — tool handler will trigger signin */ }
819
- (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken, tenantId } });
820
- const teamsContext = {
821
- graphToken,
822
- adoToken,
823
- githubToken,
824
- signin: async (cn) => {
1346
+ work = coordinateTeamsTurn(convId, text, {
1347
+ aadObjectId: activity.from?.aadObjectId, userId, tenantId: activity.conversation?.tenantId,
1348
+ replyToId: activity.replyToId,
1349
+ }, async () => {
1350
+ try {
1351
+ // Resolve OAuth connection names for this user's tenant (supports per-tenant overrides).
1352
+ const tenantId = activity.conversation?.tenantId;
1353
+ const tenantOAuth = (0, config_1.resolveOAuthForTenant)(tenantId);
1354
+ // Fetch tokens for both OAuth connections independently.
1355
+ // Failures are silently caught -- the tool handler will request signin if needed.
1356
+ let graphToken;
1357
+ let adoToken;
1358
+ let githubToken;
825
1359
  try {
826
- const result = await signin({ connectionName: cn });
827
- (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.signin_result", component: "channels", message: `signin(${cn}): ${result ? "token received" : "no token"}`, meta: { connectionName: cn, hasToken: !!result } });
828
- return result;
1360
+ const graphRes = await api.users.token.get({ userId, connectionName: tenantOAuth.graphConnectionName, channelId });
1361
+ graphToken = graphRes?.token;
829
1362
  }
830
- catch (e) {
831
- const msg = e instanceof Error ? e.message : String(e);
832
- (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.signin_error", component: "channels", message: `signin(${cn}) failed`, meta: { connectionName: cn, reason: msg.slice(0, 100) } });
833
- return undefined;
1363
+ catch { /* no token yet — tool handler will trigger signin */ }
1364
+ try {
1365
+ const adoRes = await api.users.token.get({ userId, connectionName: tenantOAuth.adoConnectionName, channelId });
1366
+ adoToken = adoRes?.token;
834
1367
  }
835
- },
836
- aadObjectId: activity.from?.aadObjectId,
837
- tenantId: activity.conversation?.tenantId,
838
- displayName: activity.from?.name,
839
- graphConnectionName: tenantOAuth.graphConnectionName,
840
- adoConnectionName: tenantOAuth.adoConnectionName,
841
- githubConnectionName: tenantOAuth.githubConnectionName,
842
- /* v8 ignore next -- bot API availability branch; requires live SDK context @preserve */
843
- botApi: app.id && api ? { id: app.id, conversations: api.conversations } : undefined,
844
- };
845
- /* v8 ignore next 5 -- bot-framework integration callback; tested via handleTeamsMessage sendMessage path @preserve */
846
- const ctxSend = async (t) => {
847
- // Use send with replyToId (not reply, which adds a blockquote).
848
- // replyToId anchors the message after the user's message in Copilot Chat.
849
- await ctx.send({ type: "message", text: t, replyToId: activity.id });
850
- };
851
- await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
852
- }
853
- catch (err) {
854
- const msg = err instanceof Error ? err.message : String(err);
855
- (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
856
- }
857
- finally {
858
- _turnCoordinator.endTurn(turnKey);
859
- }
1368
+ catch { /* no token yet — tool handler will trigger signin */ }
1369
+ try {
1370
+ const githubRes = await api.users.token.get({ userId, connectionName: tenantOAuth.githubConnectionName, channelId });
1371
+ githubToken = githubRes?.token;
1372
+ }
1373
+ catch { /* no token yet — tool handler will trigger signin */ }
1374
+ (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken, tenantId } });
1375
+ const teamsContext = {
1376
+ inboundActivityId: activity.id,
1377
+ replyToId: activity.replyToId,
1378
+ graphToken,
1379
+ adoToken,
1380
+ githubToken,
1381
+ signin: async (cn) => {
1382
+ try {
1383
+ const result = await signin({ connectionName: cn });
1384
+ (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.signin_result", component: "channels", message: `signin(${cn}): ${result ? "token received" : "no token"}`, meta: { connectionName: cn, hasToken: !!result } });
1385
+ return result;
1386
+ }
1387
+ catch (e) {
1388
+ const msg = e instanceof Error ? e.message : String(e);
1389
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.signin_error", component: "channels", message: `signin(${cn}) failed`, meta: { connectionName: cn, reason: msg.slice(0, 100) } });
1390
+ return undefined;
1391
+ }
1392
+ },
1393
+ aadObjectId: activity.from?.aadObjectId,
1394
+ tenantId: activity.conversation?.tenantId,
1395
+ displayName: activity.from?.name,
1396
+ graphConnectionName: tenantOAuth.graphConnectionName,
1397
+ adoConnectionName: tenantOAuth.adoConnectionName,
1398
+ githubConnectionName: tenantOAuth.githubConnectionName,
1399
+ /* v8 ignore next -- bot API availability branch; requires live SDK context @preserve */
1400
+ botApi: app.id && api ? { id: app.id, conversations: api.conversations } : undefined,
1401
+ teamsSendImage: (0, teams_image_1.createTeamsImageSender)((message) => ctx.send(message), activity.id, aiLabelEntities()),
1402
+ };
1403
+ await handleTeamsMessage(text, stream, convId, teamsContext, sendText);
1404
+ }
1405
+ catch (err) {
1406
+ reportTeamsRequest("failed", convId, requestContext, { reason: "handler_failed" });
1407
+ const msg = err instanceof Error ? err.message : String(err);
1408
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.handler_error", component: "channels", message: msg.slice(0, 200), meta: { conversationId: convId, inboundActivityId: activity.id, botId: app.id } });
1409
+ }
1410
+ });
1411
+ });
1412
+ await work;
860
1413
  });
861
1414
  app.event("error", ({ error }) => {
862
1415
  const msg = error instanceof Error ? error.message : String(error);
863
- (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: `[${label}] ${msg}`, meta: {} });
1416
+ (0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: `[${label}] ${msg}`, meta: { botId: app.id, label } });
864
1417
  });
865
1418
  }
866
1419
  function findAadObjectId(friend) {
@@ -874,14 +1427,14 @@ function findAadObjectId(friend) {
874
1427
  function resolveTeamsFriendStore(deps) {
875
1428
  return deps.store
876
1429
  ?? deps.createFriendStore?.()
877
- ?? new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
1430
+ ?? new friends_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
878
1431
  }
879
1432
  function getTeamsConversations(botApi) {
880
1433
  return botApi.conversations;
881
1434
  }
882
1435
  function hasExplicitCrossChatAuthorization(params) {
883
1436
  return params.intent === "explicit_cross_chat"
884
- && types_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
1437
+ && friends_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
885
1438
  }
886
1439
  async function sendProactiveTeamsMessageToSession(params, deps) {
887
1440
  const store = resolveTeamsFriendStore(deps);
@@ -903,7 +1456,7 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
903
1456
  });
904
1457
  return { delivered: false, reason: "friend_not_found" };
905
1458
  }
906
- if (!hasExplicitCrossChatAuthorization(params) && !types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
1459
+ if (!hasExplicitCrossChatAuthorization(params) && !friends_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
907
1460
  (0, runtime_1.emitNervesEvent)({
908
1461
  component: "senses",
909
1462
  event: "senses.teams_proactive_trust_skip",
@@ -928,6 +1481,18 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
928
1481
  });
929
1482
  return { delivered: false, reason: "missing_target" };
930
1483
  }
1484
+ const internalContentBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(params.text);
1485
+ if (internalContentBlockReason) {
1486
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1487
+ friendId: params.friendId,
1488
+ sessionKey: params.sessionKey,
1489
+ reason: internalContentBlockReason,
1490
+ source: "session_send",
1491
+ });
1492
+ return { delivered: false, reason: "internal_content_blocked" };
1493
+ }
1494
+ let conversationId;
1495
+ let activityAttempted = false;
931
1496
  try {
932
1497
  const conversation = await conversations.create({
933
1498
  bot: { id: deps.botApi.id },
@@ -935,9 +1500,12 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
935
1500
  tenantId: aadInfo.tenantId,
936
1501
  isGroup: false,
937
1502
  });
938
- await conversations.activities(conversation.id).create({
1503
+ conversationId = conversation.id;
1504
+ activityAttempted = true;
1505
+ const activity = await conversations.activities(conversation.id).create({
939
1506
  type: "message",
940
1507
  text: params.text,
1508
+ ...(params.textFormat ? { textFormat: params.textFormat } : {}),
941
1509
  });
942
1510
  (0, runtime_1.emitNervesEvent)({
943
1511
  component: "senses",
@@ -945,7 +1513,25 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
945
1513
  message: "proactive teams message sent",
946
1514
  meta: { friendId: params.friendId, aadObjectId: aadInfo.aadObjectId, sessionKey: params.sessionKey },
947
1515
  });
948
- return { delivered: true };
1516
+ const messageId = activity && typeof activity === "object" && "id" in activity && typeof activity.id === "string" ? activity.id : undefined;
1517
+ // Saved into the conversation it was sent into, so the friend's reply there carries it.
1518
+ const origin = params.origin ?? "proactive";
1519
+ // Recording never changes the result of a send that already went out.
1520
+ try {
1521
+ (0, outbound_record_1.recordOutboundInSession)({
1522
+ agentRoot: (0, identity_1.getAgentRoot)(), friendId: friend.id, channel: "teams", key: conversation.id, content: params.text, origin,
1523
+ deliveryId: params.deliveryId ?? `proactive:${(0, node_crypto_1.randomUUID)()}`, originLabel: outbound_record_1.OUTBOUND_ORIGIN_LABELS[origin],
1524
+ });
1525
+ }
1526
+ catch (error) {
1527
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.teams_proactive_record_error",
1528
+ message: "the Teams message was sent, but could not be saved into its conversation",
1529
+ meta: { friendId: friend.id, error: error instanceof Error ? error.message : String(error) } });
1530
+ }
1531
+ return {
1532
+ delivered: true,
1533
+ ...(params.captureReceipt ? { delivery: { state: "accepted", conversationId, ...(messageId ? { messageId } : {}) } } : {}),
1534
+ };
949
1535
  }
950
1536
  catch (error) {
951
1537
  (0, runtime_1.emitNervesEvent)({
@@ -960,7 +1546,19 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
960
1546
  reason: error instanceof Error ? error.message : String(error),
961
1547
  },
962
1548
  });
963
- return { delivered: false, reason: "send_error" };
1549
+ let status;
1550
+ if (error && typeof error === "object") {
1551
+ if ("statusCode" in error)
1552
+ status = error.statusCode;
1553
+ else if ("status" in error)
1554
+ status = error.status;
1555
+ }
1556
+ const rejected = typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 409;
1557
+ const state = !activityAttempted || rejected ? "not_sent" : "unknown";
1558
+ return {
1559
+ delivered: false, reason: "send_error",
1560
+ ...(params.captureReceipt ? { delivery: { state, ...(conversationId ? { conversationId } : {}) } } : {}),
1561
+ };
964
1562
  }
965
1563
  }
966
1564
  function scanPendingTeamsFiles(pendingRoot) {
@@ -1030,6 +1628,20 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
1030
1628
  catch { /* ignore */ }
1031
1629
  continue;
1032
1630
  }
1631
+ const internalBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(messageText);
1632
+ if (internalBlockReason) {
1633
+ result.skipped++;
1634
+ try {
1635
+ fs.unlinkSync(filePath);
1636
+ }
1637
+ catch { /* ignore */ }
1638
+ (0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
1639
+ friendId,
1640
+ reason: internalBlockReason,
1641
+ source: "pending_drain",
1642
+ });
1643
+ continue;
1644
+ }
1033
1645
  const sendResult = await sendProactiveTeamsMessageToSession({
1034
1646
  friendId,
1035
1647
  sessionKey: key,
@@ -1091,7 +1703,7 @@ function startTeamsApp() {
1091
1703
  const port = process.env.PORT ? Number(process.env.PORT) : (0, config_2.getTeamsChannelConfig)().port;
1092
1704
  app.start(port);
1093
1705
  // Diagnostic: log tool count at startup to verify deploy
1094
- const startupTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("teams"));
1706
+ const startupTools = (0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("teams"));
1095
1707
  const toolNames = startupTools.map((t) => t.function.name);
1096
1708
  (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.app_started", component: "channels", message: `Teams bot started on port ${port} with ${mode} (chunked streaming)`, meta: { port, mode, toolCount: toolNames.length, hasProactive: toolNames.includes("teams_send_message") } });
1097
1709
  // --- Secondary bot (dual-bot support) ---