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