@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.800
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -176
- 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/SerpentGuide.ouro/agent.json +83 -0
- package/SerpentGuide.ouro/psyche/SOUL.md +25 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/bluebubbles-host +264 -0
- package/assets/ouroboros.png +0 -0
- package/changelog.json +5563 -0
- package/deploy/unraid/Dockerfile +48 -0
- package/deploy/unraid/README.txt +3012 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/docker-man-template-transaction.mjs +526 -0
- package/deploy/unraid/docker-man-template-xml.cjs +105 -0
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -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-deployment-target.mjs +709 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2030 -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 +24 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -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/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 +11 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +11 -0
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +23 -0
- package/deploy/unraid/sanctuary.xml +27 -0
- package/dist/a2a/card.js +57 -0
- package/dist/a2a/client.js +180 -0
- package/dist/a2a/config.js +50 -0
- package/dist/a2a/delegation-stores.js +56 -0
- package/dist/a2a/did-resolution.js +93 -0
- package/dist/a2a/identity.js +170 -0
- package/dist/a2a/inbound-share.js +107 -0
- package/dist/a2a/mission-result-wire.js +196 -0
- package/dist/a2a/onboarding.js +115 -0
- package/dist/a2a/pin-store.js +132 -0
- package/dist/a2a/seen-ledger.js +120 -0
- package/dist/a2a/server.js +604 -0
- package/dist/a2a/task-store.js +69 -0
- package/dist/a2a/transport.js +45 -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/heart/active-work.js +1009 -0
- package/dist/heart/agent-entry.js +252 -3
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +950 -0
- package/dist/heart/attachments/image-normalize.js +194 -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 +495 -0
- package/dist/heart/autonomy-budget.js +478 -0
- package/dist/heart/awaiting/await-alert.js +148 -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 +149 -0
- package/dist/heart/awaiting/await-runtime-state.js +100 -0
- package/dist/heart/awaiting/await-scheduler.js +382 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +478 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +135 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +135 -0
- package/dist/heart/config-registry.js +331 -0
- package/dist/heart/config.js +195 -137
- package/dist/heart/context-loss-gauntlet.js +387 -0
- package/dist/heart/context-loss-sentinel.js +1157 -0
- package/dist/heart/core.js +2257 -338
- package/dist/heart/cross-chat-delivery.js +133 -0
- package/dist/heart/daemon/agent-config-check.js +451 -0
- package/dist/heart/daemon/agent-discovery.js +263 -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-defaults.js +836 -0
- package/dist/heart/daemon/cli-desk.js +322 -0
- package/dist/heart/daemon/cli-exec.js +8730 -0
- package/dist/heart/daemon/cli-help.js +710 -0
- package/dist/heart/daemon/cli-parse.js +2719 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +777 -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 +170 -0
- package/dist/heart/daemon/container-spec-auditor.js +236 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +107 -0
- package/dist/heart/daemon/daemon-cli.js +30 -786
- package/dist/heart/daemon/daemon-entry.js +971 -13
- 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 +287 -0
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +2156 -28
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +1755 -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 +142 -11
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +226 -0
- 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 +188 -0
- package/dist/heart/daemon/log-tailer.js +82 -12
- 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 +80 -11
- 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 +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/plugin-cli.js +432 -0
- package/dist/heart/daemon/process-manager.js +739 -54
- 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 +39 -0
- package/dist/heart/daemon/runtime-logging.js +92 -16
- package/dist/heart/daemon/runtime-metadata.js +191 -0
- package/dist/{repertoire/tasks/lifecycle.js → heart/daemon/runtime-mode.js} +37 -39
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2609 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1866 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +976 -0
- package/dist/heart/daemon/sanctuary-bundle-migration.js +728 -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/sense-manager.js +948 -0
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +370 -0
- 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 +499 -0
- package/dist/heart/daemon/thoughts.js +591 -0
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +59 -0
- package/dist/heart/external-events/router.js +1414 -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-animation.js +10 -3
- package/dist/heart/{daemon → hatch}/hatch-flow.js +57 -139
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/hatch/specialist-orchestrator.js +129 -0
- package/dist/heart/hatch/specialist-prompt.js +102 -0
- package/dist/heart/hatch/specialist-tools.js +318 -0
- package/dist/heart/identity.js +297 -67
- package/dist/heart/kept-notes.js +289 -0
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +39 -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 +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +696 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/orientation-frame.js +345 -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 +252 -0
- package/dist/heart/private-runtime/types.js +2 -0
- package/dist/heart/progress-story.js +42 -0
- 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 +265 -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 +236 -58
- package/dist/heart/providers/azure.js +104 -13
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +29 -7
- package/dist/heart/providers/openai-codex-token.js +349 -0
- package/dist/heart/providers/openai-codex.js +63 -39
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +607 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +79 -0
- package/dist/heart/session-activity.js +235 -0
- package/dist/heart/session-events.js +1318 -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-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +133 -0
- package/dist/heart/start-of-turn-packet.js +372 -0
- package/dist/heart/steward-policy.js +302 -0
- package/dist/heart/streaming.js +682 -207
- package/dist/heart/structured-output.js +196 -0
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +127 -0
- 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 +342 -0
- package/dist/heart/tool-description.js +155 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +481 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +129 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +432 -0
- package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
- package/dist/heart/{daemon → versioning}/ouro-uti.js +11 -2
- package/dist/heart/versioning/ouro-version-manager.js +409 -0
- package/dist/heart/versioning/staged-restart.js +146 -0
- package/dist/heart/versioning/update-checker.js +116 -0
- package/dist/heart/versioning/update-hooks.js +154 -0
- package/dist/heart/versioning/version-intent.js +114 -0
- package/dist/heart/versioning/wrapper-publish-guard.js +86 -0
- package/dist/heart/work-card.js +380 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/index-J8zDwHmE.js +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 +93 -1
- package/dist/mind/context.js +287 -104
- package/dist/mind/desk-section.js +362 -0
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +90 -98
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/{associative-recall.js → note-search.js} +61 -60
- package/dist/mind/obligation-steering.js +227 -0
- package/dist/mind/pending.js +102 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-budget.js +479 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1377 -140
- 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 +415 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +22 -3
- 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 +139 -5
- package/dist/nerves/coverage/run-artifacts.js +56 -36
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +305 -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 +17 -56
- package/dist/repertoire/ado-semantic.js +16 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +1045 -0
- package/dist/repertoire/bundle-templates.js +71 -0
- package/dist/repertoire/bw-installer.js +180 -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 +322 -0
- package/dist/repertoire/coding/index.js +18 -3
- package/dist/repertoire/coding/manager.js +228 -14
- package/dist/repertoire/coding/spawner.js +61 -14
- package/dist/repertoire/coding/tools.js +279 -12
- 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 +178 -0
- package/dist/repertoire/data/ado-endpoints.json +188 -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 +1083 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +434 -0
- package/dist/repertoire/mcp-tools.js +83 -0
- package/dist/repertoire/plugin-mcp.js +175 -0
- package/dist/repertoire/plugins.js +253 -0
- package/dist/repertoire/relationship-authorization.js +194 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +45 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tool-arguments.js +148 -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 +575 -0
- package/dist/repertoire/tools-base.js +85 -707
- package/dist/repertoire/tools-bluebubbles.js +95 -0
- package/dist/repertoire/tools-bridge.js +144 -0
- package/dist/repertoire/tools-bundle.js +993 -0
- package/dist/repertoire/tools-commerce.js +253 -0
- package/dist/repertoire/tools-config.js +186 -0
- package/dist/repertoire/tools-continuity.js +575 -0
- package/dist/repertoire/tools-credential.js +383 -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-github.js +3 -8
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools-mail.js +1828 -0
- package/dist/repertoire/tools-notes.js +485 -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 +150 -0
- package/dist/repertoire/tools-session.js +1108 -0
- package/dist/repertoire/tools-shell.js +133 -0
- package/dist/repertoire/tools-steward-policy.js +95 -0
- package/dist/repertoire/tools-stripe.js +224 -0
- package/dist/repertoire/tools-surface.js +369 -0
- package/dist/repertoire/tools-teams.js +67 -61
- 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 +484 -0
- package/dist/repertoire/tools-user-profile.js +146 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +145 -0
- package/dist/repertoire/tools.js +441 -102
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +212 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +628 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +926 -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 +115 -0
- package/dist/senses/attention-queue.js +186 -0
- package/dist/senses/await-turn-message.js +58 -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 +799 -0
- 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/inbound-log.js +127 -0
- package/dist/senses/bluebubbles/index.js +3839 -0
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/bluebubbles/media.js +389 -0
- package/dist/senses/bluebubbles/model.js +392 -0
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +57 -6
- package/dist/senses/bluebubbles/outbound-state.js +308 -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 +137 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -0
- package/dist/senses/bluebubbles/webhook-registration.js +228 -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-layout.js +187 -0
- package/dist/senses/cli.js +813 -281
- 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/continuity.js +94 -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 +1213 -0
- package/dist/senses/private-runtime-worker.js +859 -0
- package/dist/senses/private-runtime.js +1496 -0
- package/dist/senses/proactive-content-guard.js +51 -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 +774 -0
- package/dist/senses/sanctuary-health-runner.js +101 -0
- package/dist/senses/sanctuary-health.js +408 -0
- package/dist/senses/sanctuary-interactive-control.js +306 -0
- package/dist/senses/sanctuary-media-catalog-contract.js +235 -0
- package/dist/senses/sanctuary-media-optimization.js +514 -0
- package/dist/senses/sanctuary-runtime.js +338 -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 +571 -0
- package/dist/senses/surface-tool.js +109 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +953 -211
- package/dist/senses/telegram-admission.js +711 -0
- package/dist/senses/telegram-approval-runtime.js +566 -0
- package/dist/senses/telegram-attachments.js +182 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-client.js +1677 -0
- package/dist/senses/telegram-effect-adapter.js +859 -0
- package/dist/senses/telegram-entry.js +83 -0
- package/dist/senses/telegram.js +2026 -0
- package/dist/senses/trust-gate.js +207 -2
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -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/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/realtime-eval.js +496 -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 +807 -0
- package/dist/senses/voice/twilio-phone.js +5081 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -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 +8144 -0
- package/package.json +82 -12
- 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/AdoptionSpecialist.ouro/agent.json +0 -20
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +0 -22
- package/dist/heart/daemon/specialist-orchestrator.js +0 -160
- package/dist/heart/daemon/specialist-prompt.js +0 -47
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/specialist-tools.js +0 -132
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/dist/mind/friends/channel.js +0 -49
- package/dist/mind/friends/resolver.js +0 -84
- package/dist/mind/friends/store-file.js +0 -171
- package/dist/mind/friends/store.js +0 -4
- package/dist/mind/friends/tokens.js +0 -26
- package/dist/mind/friends/types.js +0 -21
- package/dist/repertoire/tasks/board.js +0 -122
- package/dist/repertoire/tasks/index.js +0 -210
- 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 -145
- package/dist/senses/bluebubbles-client.js +0 -279
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles-model.js +0 -253
- package/dist/senses/bluebubbles.js +0 -332
- package/dist/senses/inner-dialog-worker.js +0 -61
- package/dist/senses/inner-dialog.js +0 -231
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
- /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/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/senses/teams.js
CHANGED
|
@@ -34,16 +34,24 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.DEFAULT_FLUSH_INTERVAL_MS = void 0;
|
|
37
|
+
exports.aiLabelEntities = aiLabelEntities;
|
|
37
38
|
exports.stripMentions = stripMentions;
|
|
38
39
|
exports.splitMessage = splitMessage;
|
|
40
|
+
exports.sanitizeFeedbackComment = sanitizeFeedbackComment;
|
|
41
|
+
exports.buildFeedbackSyntheticText = buildFeedbackSyntheticText;
|
|
42
|
+
exports.buildWelcomeCard = buildWelcomeCard;
|
|
39
43
|
exports.createTeamsCallbacks = createTeamsCallbacks;
|
|
40
|
-
exports.resolvePendingConfirmation = resolvePendingConfirmation;
|
|
41
44
|
exports.withConversationLock = withConversationLock;
|
|
42
45
|
exports.handleTeamsMessage = handleTeamsMessage;
|
|
46
|
+
exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
|
|
47
|
+
exports.drainAndSendPendingTeams = drainAndSendPendingTeams;
|
|
43
48
|
exports.startTeamsApp = startTeamsApp;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
44
50
|
const teams_apps_1 = require("@microsoft/teams.apps");
|
|
45
51
|
const teams_dev_1 = require("@microsoft/teams.dev");
|
|
46
52
|
const core_1 = require("../heart/core");
|
|
53
|
+
const tools_1 = require("../repertoire/tools");
|
|
54
|
+
const friends_1 = require("@ouro.bot/friends");
|
|
47
55
|
const config_1 = require("../heart/config");
|
|
48
56
|
const prompt_1 = require("../mind/prompt");
|
|
49
57
|
const phrases_1 = require("../mind/phrases");
|
|
@@ -53,13 +61,31 @@ const context_1 = require("../mind/context");
|
|
|
53
61
|
const commands_1 = require("./commands");
|
|
54
62
|
const nerves_1 = require("../nerves");
|
|
55
63
|
const runtime_1 = require("../nerves/runtime");
|
|
56
|
-
const
|
|
57
|
-
const resolver_1 = require("../mind/friends/resolver");
|
|
58
|
-
const tokens_1 = require("../mind/friends/tokens");
|
|
64
|
+
const proactive_content_guard_1 = require("./proactive-content-guard");
|
|
59
65
|
const turn_coordinator_1 = require("../heart/turn-coordinator");
|
|
60
66
|
const identity_1 = require("../heart/identity");
|
|
67
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
68
|
+
const progress_story_1 = require("../heart/progress-story");
|
|
69
|
+
const tool_activity_callbacks_1 = require("../heart/tool-activity-callbacks");
|
|
70
|
+
const commands_2 = require("./commands");
|
|
71
|
+
const http = __importStar(require("http"));
|
|
61
72
|
const path = __importStar(require("path"));
|
|
62
73
|
const trust_gate_1 = require("./trust-gate");
|
|
74
|
+
const pipeline_1 = require("./pipeline");
|
|
75
|
+
const session_transaction_1 = require("../mind/session-transaction");
|
|
76
|
+
const teamsFailoverStates = new Map();
|
|
77
|
+
const pending_1 = require("../mind/pending");
|
|
78
|
+
const continuity_1 = require("./continuity");
|
|
79
|
+
// AIGeneratedContent entity and feedbackLoopEnabled channelData for all outbound
|
|
80
|
+
// Teams messages. Required by Teams AI UX best practices.
|
|
81
|
+
function aiLabelEntities() {
|
|
82
|
+
return [{
|
|
83
|
+
type: "https://schema.org/Message",
|
|
84
|
+
"@type": "Message",
|
|
85
|
+
"@context": "https://schema.org",
|
|
86
|
+
additionalType: ["AIGeneratedContent"],
|
|
87
|
+
}];
|
|
88
|
+
}
|
|
63
89
|
// Strip @mention markup from incoming messages.
|
|
64
90
|
// Removes <at>...</at> tags and trims extra whitespace.
|
|
65
91
|
// Fallback safety net -- the SDK's activity.mentions.stripText should handle
|
|
@@ -114,6 +140,46 @@ function splitMessage(text, maxLen) {
|
|
|
114
140
|
}
|
|
115
141
|
return chunks;
|
|
116
142
|
}
|
|
143
|
+
// Sanitize user-provided feedback comments: truncate, strip control chars and newlines.
|
|
144
|
+
function sanitizeFeedbackComment(comment) {
|
|
145
|
+
const cleaned = comment.replace(/[\x00-\x1f\n\r]/g, "");
|
|
146
|
+
return cleaned.length > 200 ? cleaned.slice(0, 200) : cleaned;
|
|
147
|
+
}
|
|
148
|
+
// Build synthetic message text from a Teams feedback reaction.
|
|
149
|
+
function buildFeedbackSyntheticText(reaction, comment) {
|
|
150
|
+
const emoji = reaction === "like" ? "thumbs-up" : "thumbs-down";
|
|
151
|
+
if (comment) {
|
|
152
|
+
const sanitized = sanitizeFeedbackComment(comment);
|
|
153
|
+
return `[reacted with ${emoji} to your message: "${sanitized}"]`;
|
|
154
|
+
}
|
|
155
|
+
return `[reacted with ${emoji} to your message]`;
|
|
156
|
+
}
|
|
157
|
+
// Build a welcome Adaptive Card with prompt starters for new bot installs.
|
|
158
|
+
function buildWelcomeCard() {
|
|
159
|
+
const promptStarters = [
|
|
160
|
+
"What can you help me with?",
|
|
161
|
+
"Tell me about yourself",
|
|
162
|
+
"What's on my calendar today?",
|
|
163
|
+
"Summarize my recent emails",
|
|
164
|
+
];
|
|
165
|
+
return {
|
|
166
|
+
type: "AdaptiveCard",
|
|
167
|
+
version: "1.5",
|
|
168
|
+
body: [
|
|
169
|
+
{
|
|
170
|
+
type: "TextBlock",
|
|
171
|
+
text: "Hey! I'm here and ready to help. Try one of these to get started, or just ask me anything.",
|
|
172
|
+
wrap: true,
|
|
173
|
+
size: "Medium",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
actions: promptStarters.map((prompt) => ({
|
|
177
|
+
type: "Action.Submit",
|
|
178
|
+
title: prompt,
|
|
179
|
+
data: { msteams: { type: "messageBack", text: prompt, displayText: prompt } },
|
|
180
|
+
})),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
117
183
|
// Create Teams-specific callbacks for the agent loop.
|
|
118
184
|
// The SDK handles cumulative text, debouncing (500ms), and the streaming
|
|
119
185
|
// protocol (streamSequence, streamId, informative/streaming/final types).
|
|
@@ -126,12 +192,16 @@ function splitMessage(text, maxLen) {
|
|
|
126
192
|
// (transient status) or safeSend (terminal errors). Reasoning is accumulated
|
|
127
193
|
// and periodically pushed via safeUpdate on the same flush timer tick.
|
|
128
194
|
function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
195
|
+
const MIN_INITIAL_CHARS = 20;
|
|
129
196
|
let stopped = false; // set when stream signals cancellation (403)
|
|
130
197
|
let hadToolRun = false;
|
|
131
198
|
let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
|
|
132
199
|
let reasoningBuf = ""; // accumulated reasoning text for status display
|
|
200
|
+
let totalEmitted = 0; // cumulative chars emitted via safeEmit (for >4000 finalization)
|
|
201
|
+
let streamFinalized = false; // true after stream.close() — subsequent flushes go to safeSend
|
|
133
202
|
let textBuffer = ""; // accumulated text output for chunked streaming
|
|
134
203
|
let streamHasContent = false; // tracks whether primary output has received content
|
|
204
|
+
let firstContentEmitted = false; // true after first content push — disables MIN_INITIAL_CHARS threshold
|
|
135
205
|
let phraseTimer = null;
|
|
136
206
|
let lastPhrase = "";
|
|
137
207
|
let flushTimer = null;
|
|
@@ -181,15 +251,16 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
181
251
|
result.catch(() => markStopped());
|
|
182
252
|
}
|
|
183
253
|
}
|
|
184
|
-
// Safely emit a text delta to the stream.
|
|
254
|
+
// Safely emit a text delta to the stream with AI labels.
|
|
185
255
|
// On error (e.g. 403 from Teams stop button), abort the controller.
|
|
186
256
|
function safeEmit(text) {
|
|
187
257
|
/* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
|
|
188
258
|
if (stopped)
|
|
189
259
|
return;
|
|
190
260
|
try {
|
|
191
|
-
catchAsync(stream.emit(text));
|
|
261
|
+
catchAsync(stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } }));
|
|
192
262
|
streamHasContent = true;
|
|
263
|
+
totalEmitted += text.length;
|
|
193
264
|
}
|
|
194
265
|
catch {
|
|
195
266
|
markStopped();
|
|
@@ -204,7 +275,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
204
275
|
try {
|
|
205
276
|
// stream.emit() is typed as void but the Teams SDK returns a Promise
|
|
206
277
|
// internally (async HTTP). Cast to capture the result for awaiting.
|
|
207
|
-
const result = stream.emit(text);
|
|
278
|
+
const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
208
279
|
streamHasContent = true;
|
|
209
280
|
if (result && typeof result.then === "function") {
|
|
210
281
|
await result;
|
|
@@ -216,6 +287,26 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
216
287
|
return false;
|
|
217
288
|
}
|
|
218
289
|
}
|
|
290
|
+
// Non-aborting awaitable emit — returns true on success, false on failure WITHOUT
|
|
291
|
+
// calling markStopped() / aborting the controller. Used by flushNow (speak) so a
|
|
292
|
+
// primary-stream failure followed by a successful sendMessage fallback does NOT
|
|
293
|
+
// poison the rest of the turn. tryEmit's abort-on-failure behavior is correct for
|
|
294
|
+
// end-of-turn flush() (no fallback path forward) but wrong for mid-turn speak,
|
|
295
|
+
// which has a sendMessage fallback that may still succeed. Caller (flushNow) is
|
|
296
|
+
// responsible for the `!stopped` precondition; no defensive guard here.
|
|
297
|
+
async function tryEmitNoAbort(text) {
|
|
298
|
+
try {
|
|
299
|
+
const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
300
|
+
streamHasContent = true;
|
|
301
|
+
if (result && typeof result.then === "function") {
|
|
302
|
+
await result;
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
219
310
|
// Safely send a status update to the stream.
|
|
220
311
|
// On error (e.g. 403 from Teams stop button), abort the controller.
|
|
221
312
|
function safeUpdate(text) {
|
|
@@ -260,11 +351,49 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
260
351
|
// emitted text into a single streaming message (cumulative), so every
|
|
261
352
|
// periodic flush appends to the same response — not separate messages.
|
|
262
353
|
// No preemptive splitting — sends full text. Error recovery happens in flush().
|
|
354
|
+
// Hybrid MIN_INITIAL_CHARS: hold back until >= MIN_INITIAL_CHARS accumulated
|
|
355
|
+
// before the first content emit, so phrase rotation shows while real content
|
|
356
|
+
// buffers. After first emit, flush normally (no threshold).
|
|
263
357
|
function flushTextBuffer() {
|
|
264
358
|
if (!textBuffer)
|
|
265
359
|
return;
|
|
360
|
+
if (!firstContentEmitted && textBuffer.length < MIN_INITIAL_CHARS)
|
|
361
|
+
return;
|
|
362
|
+
// Proactive >4000 finalization: if cumulative emitted + buffer >= RECOVERY_CHUNK_SIZE,
|
|
363
|
+
// finalize the stream and send overflow via safeSend (follow-up message).
|
|
364
|
+
if (!streamFinalized && totalEmitted + textBuffer.length >= RECOVERY_CHUNK_SIZE) {
|
|
365
|
+
const remaining = RECOVERY_CHUNK_SIZE - totalEmitted;
|
|
366
|
+
/* v8 ignore next 2 -- defensive: remaining always > 0 because finalization runs once @preserve */
|
|
367
|
+
if (remaining > 0)
|
|
368
|
+
safeEmit(textBuffer.slice(0, remaining));
|
|
369
|
+
try {
|
|
370
|
+
stream.close();
|
|
371
|
+
}
|
|
372
|
+
catch { /* stream may already be dead */ }
|
|
373
|
+
streamFinalized = true;
|
|
374
|
+
/* v8 ignore next -- defensive ternary: remaining always > 0 at first finalization @preserve */
|
|
375
|
+
const overflow = textBuffer.slice(remaining > 0 ? remaining : 0);
|
|
376
|
+
textBuffer = "";
|
|
377
|
+
if (overflow)
|
|
378
|
+
safeSend(overflow);
|
|
379
|
+
if (!firstContentEmitted) {
|
|
380
|
+
firstContentEmitted = true;
|
|
381
|
+
stopPhraseRotation();
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (streamFinalized) {
|
|
386
|
+
// After finalization, all content goes to safeSend (follow-up messages)
|
|
387
|
+
safeSend(textBuffer);
|
|
388
|
+
textBuffer = "";
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
266
391
|
safeEmit(textBuffer);
|
|
267
392
|
textBuffer = "";
|
|
393
|
+
if (!firstContentEmitted) {
|
|
394
|
+
firstContentEmitted = true;
|
|
395
|
+
stopPhraseRotation();
|
|
396
|
+
}
|
|
268
397
|
}
|
|
269
398
|
function startPhraseRotation(pool) {
|
|
270
399
|
stopPhraseRotation();
|
|
@@ -281,6 +410,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
281
410
|
}
|
|
282
411
|
}
|
|
283
412
|
return {
|
|
413
|
+
settleOutputMode: "final_only",
|
|
284
414
|
onModelStart: () => {
|
|
285
415
|
if (hadRealOutput)
|
|
286
416
|
return; // real output already shown; don't overwrite with phrases
|
|
@@ -307,25 +437,108 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
307
437
|
onTextChunk: (text) => {
|
|
308
438
|
if (stopped)
|
|
309
439
|
return;
|
|
310
|
-
|
|
440
|
+
// Don't stop phrase rotation here — let it continue until first content
|
|
441
|
+
// emit (handled in flushTextBuffer when MIN_INITIAL_CHARS threshold met).
|
|
311
442
|
textBuffer += text;
|
|
312
443
|
startFlushTimer();
|
|
313
444
|
},
|
|
314
445
|
onClearText: () => {
|
|
315
446
|
textBuffer = "";
|
|
316
447
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
onToolEnd: (name, summary, success) => {
|
|
448
|
+
flushNow: async () => {
|
|
449
|
+
const trimmed = textBuffer.trim();
|
|
450
|
+
if (!trimmed)
|
|
451
|
+
return;
|
|
452
|
+
// Cancel pending periodic flush — we're delivering now.
|
|
453
|
+
stopFlushTimer();
|
|
454
|
+
// The actual speak message replaces any "thinking..." phrase cycling.
|
|
325
455
|
stopPhraseRotation();
|
|
326
|
-
|
|
327
|
-
|
|
456
|
+
// Bypass MIN_INITIAL_CHARS threshold — speak delivers immediately.
|
|
457
|
+
firstContentEmitted = true;
|
|
458
|
+
textBuffer = "";
|
|
459
|
+
// Try the stream first via the NON-ABORTING variant; on failure, fall back
|
|
460
|
+
// to sendMessage. Critical: do NOT call markStopped() / abort the controller
|
|
461
|
+
// when only the primary stream fails — the sendMessage fallback may still
|
|
462
|
+
// deliver the speak, and a successful fallback must not poison the rest of
|
|
463
|
+
// the turn. Only abort when ALL delivery paths fail (handled below).
|
|
464
|
+
// Contract: throws if the message could not be delivered through any available path.
|
|
465
|
+
let delivered = false;
|
|
466
|
+
let lastError = null;
|
|
467
|
+
if (!stopped) {
|
|
468
|
+
const ok = await tryEmitNoAbort(trimmed);
|
|
469
|
+
if (ok)
|
|
470
|
+
delivered = true;
|
|
471
|
+
else
|
|
472
|
+
lastError = new Error("stream emit failed");
|
|
473
|
+
}
|
|
474
|
+
if (!delivered && sendMessage) {
|
|
475
|
+
try {
|
|
476
|
+
await sendMessage(trimmed);
|
|
477
|
+
delivered = true;
|
|
478
|
+
lastError = null;
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
(0, runtime_1.emitNervesEvent)({
|
|
485
|
+
component: "senses",
|
|
486
|
+
event: "teams.speak_flush",
|
|
487
|
+
message: "teams flushed mid-turn speak",
|
|
488
|
+
meta: { messageLength: trimmed.length, delivered },
|
|
489
|
+
});
|
|
490
|
+
if (!delivered) {
|
|
491
|
+
// All delivery paths exhausted — now it is correct to abort the turn.
|
|
492
|
+
// markStopped() halts further stream activity and aborts the controller
|
|
493
|
+
// so the engine catches up and ends the turn cleanly.
|
|
494
|
+
markStopped();
|
|
495
|
+
throw new Error(`teams speak delivery failed: ${lastError?.message ?? "no fallback available"}`);
|
|
496
|
+
}
|
|
328
497
|
},
|
|
498
|
+
...(() => {
|
|
499
|
+
const toolCbs = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
|
|
500
|
+
onDescription: (text) => safeUpdate(text),
|
|
501
|
+
/* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
|
|
502
|
+
onResult: (text) => safeUpdate(text),
|
|
503
|
+
/* v8 ignore next -- onFailure tested via onToolEnd failure test @preserve */
|
|
504
|
+
onFailure: (text) => safeUpdate(text),
|
|
505
|
+
isDebug: commands_2.getDebugMode,
|
|
506
|
+
});
|
|
507
|
+
return {
|
|
508
|
+
onToolStart: (name, args) => {
|
|
509
|
+
// speak is flow-control: its visible output is the message itself
|
|
510
|
+
// (delivered via onTextChunk + flushNow). Do NOT stop phrase rotation
|
|
511
|
+
// here, do NOT emit the \u23f3 placeholder, do NOT post a tool-activity
|
|
512
|
+
// status update \u2014 all of those would create UI churn right before the
|
|
513
|
+
// actual speak content arrives.
|
|
514
|
+
if (name === "speak")
|
|
515
|
+
return;
|
|
516
|
+
stopPhraseRotation();
|
|
517
|
+
// Force-flush any accumulated text, bypassing MIN_INITIAL_CHARS threshold
|
|
518
|
+
firstContentEmitted = true;
|
|
519
|
+
flushTextBuffer();
|
|
520
|
+
// Emit a placeholder to satisfy the 15s Copilot timeout for initial
|
|
521
|
+
// stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
|
|
522
|
+
// never emit before the timeout and the user sees "this response was
|
|
523
|
+
// stopped". The placeholder is replaced by actual content on next emit.
|
|
524
|
+
// https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
|
|
525
|
+
if (!streamHasContent)
|
|
526
|
+
safeEmit("\u23f3");
|
|
527
|
+
toolCbs.onToolStart(name, args);
|
|
528
|
+
hadToolRun = true;
|
|
529
|
+
},
|
|
530
|
+
onToolEnd: (name, summary, success) => {
|
|
531
|
+
// speak is flow-control: skip phrase-rotation stop and tool-activity end
|
|
532
|
+
// callback (no safeUpdate for \u2713/\u2717). The flushNow call inside the engine
|
|
533
|
+
// already emitted the actual message and stopped any rotation as part of
|
|
534
|
+
// tryEmit's first-content-emitted flag.
|
|
535
|
+
if (name === "speak")
|
|
536
|
+
return;
|
|
537
|
+
stopPhraseRotation();
|
|
538
|
+
toolCbs.onToolEnd(name, summary, success);
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
})(),
|
|
329
542
|
onKick: () => {
|
|
330
543
|
stopPhraseRotation();
|
|
331
544
|
const msg = (0, format_1.formatKick)();
|
|
@@ -335,7 +548,11 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
335
548
|
stopPhraseRotation();
|
|
336
549
|
if (stopped)
|
|
337
550
|
return;
|
|
338
|
-
const msg = (0,
|
|
551
|
+
const msg = (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
552
|
+
scope: "shared-work",
|
|
553
|
+
phase: "errored",
|
|
554
|
+
outcomeText: (0, format_1.formatError)(error),
|
|
555
|
+
}));
|
|
339
556
|
if (severity === "transient") {
|
|
340
557
|
safeUpdate(msg);
|
|
341
558
|
}
|
|
@@ -343,38 +560,27 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
343
560
|
safeSend(msg);
|
|
344
561
|
}
|
|
345
562
|
},
|
|
346
|
-
onConfirmAction: options?.conversationId
|
|
347
|
-
? async (name, args) => {
|
|
348
|
-
const convId = options.conversationId;
|
|
349
|
-
const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
350
|
-
safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
|
|
351
|
-
return new Promise((resolve) => {
|
|
352
|
-
_pendingConfirmations.set(convId, resolve);
|
|
353
|
-
// Auto-deny after 2 minutes to prevent indefinite blocking
|
|
354
|
-
// (e.g. when the stream dies and the user never sees the prompt).
|
|
355
|
-
setTimeout(() => {
|
|
356
|
-
if (_pendingConfirmations.has(convId)) {
|
|
357
|
-
_pendingConfirmations.delete(convId);
|
|
358
|
-
resolve("denied");
|
|
359
|
-
}
|
|
360
|
-
}, 120_000);
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
: undefined,
|
|
364
563
|
flush: async () => {
|
|
365
564
|
stopFlushTimer();
|
|
565
|
+
stopPhraseRotation();
|
|
366
566
|
if (textBuffer) {
|
|
567
|
+
// Bypass MIN_INITIAL_CHARS threshold — flush delivers all remaining content
|
|
568
|
+
firstContentEmitted = true;
|
|
367
569
|
const text = textBuffer;
|
|
368
570
|
textBuffer = "";
|
|
369
|
-
if (
|
|
571
|
+
if (streamFinalized && sendMessage) {
|
|
572
|
+
// Stream already finalized (>4000 path) — send remaining content as follow-up
|
|
573
|
+
safeSend(text);
|
|
574
|
+
}
|
|
575
|
+
else if (!stopped) {
|
|
370
576
|
// Stream is alive — await the emit so we can catch async 413/failure
|
|
371
577
|
// and fall through to sendMessage recovery.
|
|
372
578
|
const ok = await tryEmit(text);
|
|
373
579
|
if (!ok)
|
|
374
580
|
markStopped();
|
|
375
581
|
}
|
|
376
|
-
if (stopped && sendMessage) {
|
|
377
|
-
// Stream is dead — fall back to sendMessage; split on failure as recovery.
|
|
582
|
+
if (stopped && !streamFinalized && sendMessage) {
|
|
583
|
+
// Stream is dead (not from finalization) — fall back to sendMessage; split on failure as recovery.
|
|
378
584
|
try {
|
|
379
585
|
await sendMessage(text);
|
|
380
586
|
}
|
|
@@ -385,32 +591,12 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
385
591
|
}
|
|
386
592
|
}
|
|
387
593
|
}
|
|
388
|
-
else if (!streamHasContent) {
|
|
389
|
-
safeEmit("(completed with tool calls only
|
|
594
|
+
else if (!streamHasContent && !options?.suppressEmptyStreamMessage) {
|
|
595
|
+
safeEmit("(completed with tool calls only — no text response)");
|
|
390
596
|
}
|
|
391
597
|
},
|
|
392
598
|
};
|
|
393
599
|
}
|
|
394
|
-
// Per-conversation pending confirmation resolvers.
|
|
395
|
-
// When a mutate tool needs confirmation, the resolver is stored here.
|
|
396
|
-
// The next message from the same conversation resolves it.
|
|
397
|
-
const _pendingConfirmations = new Map();
|
|
398
|
-
// Confirmation response words (case-insensitive)
|
|
399
|
-
const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
|
|
400
|
-
function resolvePendingConfirmation(convId, text) {
|
|
401
|
-
const resolver = _pendingConfirmations.get(convId);
|
|
402
|
-
if (!resolver)
|
|
403
|
-
return false;
|
|
404
|
-
_pendingConfirmations.delete(convId);
|
|
405
|
-
const word = text.trim().toLowerCase();
|
|
406
|
-
if (CONFIRM_WORDS.has(word)) {
|
|
407
|
-
resolver("confirmed");
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
resolver("denied");
|
|
411
|
-
}
|
|
412
|
-
return true;
|
|
413
|
-
}
|
|
414
600
|
const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
|
|
415
601
|
function teamsTurnKey(conversationId) {
|
|
416
602
|
return `teams:${conversationId}`;
|
|
@@ -422,160 +608,327 @@ async function withConversationLock(convId, fn) {
|
|
|
422
608
|
// are deleted while the process is alive.
|
|
423
609
|
function getFriendStore() {
|
|
424
610
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
425
|
-
return new
|
|
611
|
+
return new friends_1.FileFriendStore(friendsPath);
|
|
426
612
|
}
|
|
613
|
+
function createTeamsCommandRegistry() {
|
|
614
|
+
const registry = (0, commands_1.createCommandRegistry)();
|
|
615
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
616
|
+
return registry;
|
|
617
|
+
}
|
|
618
|
+
/* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
|
|
619
|
+
function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
|
|
620
|
+
const parsed = (0, commands_1.parseSlashCommand)(text);
|
|
621
|
+
if (!parsed)
|
|
622
|
+
return null;
|
|
623
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "teams" });
|
|
624
|
+
if (!dispatchResult.handled || !dispatchResult.result) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
if (dispatchResult.result.action === "new") {
|
|
628
|
+
(0, context_1.deleteSession)((0, config_2.sessionPath)(friendId, "teams", conversationId));
|
|
629
|
+
if (emitResponse) {
|
|
630
|
+
stream.emit("session cleared");
|
|
631
|
+
}
|
|
632
|
+
return "new";
|
|
633
|
+
}
|
|
634
|
+
if (dispatchResult.result.action === "response") {
|
|
635
|
+
if (emitResponse) {
|
|
636
|
+
stream.emit(dispatchResult.result.message || "");
|
|
637
|
+
}
|
|
638
|
+
return "response";
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
/* v8 ignore stop */
|
|
427
643
|
// Handle an incoming Teams message
|
|
428
|
-
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
|
|
644
|
+
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides, runtimeOverrides) {
|
|
429
645
|
const turnKey = teamsTurnKey(conversationId);
|
|
430
646
|
// NOTE: Confirmation resolution is handled in the app.on("message") handler
|
|
431
647
|
// BEFORE the conversation lock. By the time we get here, any pending
|
|
432
648
|
// confirmation has already been resolved and the reply consumed.
|
|
433
|
-
//
|
|
434
|
-
// before sync I/O (session load, trim) blocks the event loop.
|
|
435
|
-
stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
|
|
436
|
-
await new Promise(r => setImmediate(r));
|
|
437
|
-
// Resolve context kernel (identity + channel) early so we can use the friend UUID for session path
|
|
649
|
+
// Resolve identity provider early for friend resolution + slash command session path
|
|
438
650
|
const store = getFriendStore();
|
|
439
651
|
const provider = teamsContext?.aadObjectId ? "aad" : "teams-conversation";
|
|
440
652
|
const externalId = teamsContext?.aadObjectId || conversationId;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
653
|
+
// Build FriendResolver for the pipeline
|
|
654
|
+
const resolver = new friends_1.FriendResolver(store, {
|
|
655
|
+
provider,
|
|
656
|
+
externalId,
|
|
657
|
+
tenantId: teamsContext?.tenantId,
|
|
658
|
+
displayName: teamsContext?.displayName || "Unknown",
|
|
659
|
+
channel: "teams",
|
|
660
|
+
});
|
|
661
|
+
// Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
|
|
662
|
+
const resolvedContext = await resolver.resolve();
|
|
663
|
+
const friendId = resolvedContext.friend.id;
|
|
664
|
+
// ── Teams adapter concerns: controller, callbacks, session path ──────────
|
|
665
|
+
const controller = new AbortController();
|
|
666
|
+
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
667
|
+
const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
|
|
668
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
669
|
+
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
670
|
+
const teamsCapabilities = (0, friends_1.getChannelCapabilities)("teams");
|
|
671
|
+
const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "teams", conversationId);
|
|
672
|
+
const runWithLease = runtimeOverrides?._withSessionTurnLease ?? session_transaction_1.withSessionTurnLease;
|
|
673
|
+
try {
|
|
674
|
+
await runWithLease(sessPath, async (sessionTurnLease) => {
|
|
675
|
+
// Do not emit any activity until this turn owns the durable session.
|
|
676
|
+
if (!reactionOverrides) {
|
|
677
|
+
stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
|
|
678
|
+
}
|
|
679
|
+
await new Promise(r => setImmediate(r));
|
|
680
|
+
// Build Teams-specific toolContext fields for injection into the pipeline
|
|
681
|
+
const teamsToolContext = teamsContext ? {
|
|
682
|
+
graphToken: teamsContext.graphToken,
|
|
683
|
+
adoToken: teamsContext.adoToken,
|
|
684
|
+
githubToken: teamsContext.githubToken,
|
|
685
|
+
signin: teamsContext.signin,
|
|
686
|
+
summarize: (0, core_1.createSummarize)("human"),
|
|
687
|
+
tenantId: teamsContext.tenantId,
|
|
688
|
+
botApi: teamsContext.botApi,
|
|
689
|
+
} : {};
|
|
690
|
+
let currentText = text;
|
|
691
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
692
|
+
while (true) {
|
|
693
|
+
let drainedSteeringFollowUps = [];
|
|
694
|
+
// Build runAgentOptions with Teams-specific fields
|
|
695
|
+
const agentOptions = {
|
|
696
|
+
traceId,
|
|
697
|
+
toolContext: teamsToolContext,
|
|
698
|
+
mcpManager,
|
|
699
|
+
drainSteeringFollowUps: () => {
|
|
700
|
+
drainedSteeringFollowUps = _turnCoordinator.drainFollowUps(turnKey)
|
|
701
|
+
.map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
|
|
702
|
+
return drainedSteeringFollowUps;
|
|
703
|
+
},
|
|
704
|
+
...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
|
|
705
|
+
};
|
|
706
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
707
|
+
// Capture terminal errors — failover message replaces the error card if it triggers
|
|
708
|
+
let capturedTerminalError = null;
|
|
709
|
+
const teamsFailoverState = (() => {
|
|
710
|
+
if (!teamsFailoverStates.has(conversationId)) {
|
|
711
|
+
teamsFailoverStates.set(conversationId, { pending: null });
|
|
712
|
+
}
|
|
713
|
+
return teamsFailoverStates.get(conversationId);
|
|
714
|
+
})();
|
|
715
|
+
/* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
|
|
716
|
+
const failoverAwareCallbacks = {
|
|
717
|
+
...callbacks,
|
|
718
|
+
onError: (error, severity) => {
|
|
719
|
+
if (severity === "terminal" && teamsFailoverState) {
|
|
720
|
+
capturedTerminalError = error;
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
callbacks.onError(error, severity);
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
/* v8 ignore stop */
|
|
727
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
728
|
+
channel: "teams",
|
|
729
|
+
sessionKey: conversationId,
|
|
730
|
+
capabilities: teamsCapabilities,
|
|
731
|
+
messages: [{ role: "user", content: currentText }],
|
|
732
|
+
continuityIngressTexts: [currentText],
|
|
733
|
+
callbacks: failoverAwareCallbacks,
|
|
734
|
+
sessionTurnLease,
|
|
735
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
736
|
+
sessionLoader: {
|
|
737
|
+
loadOrCreate: async () => {
|
|
738
|
+
const existing = (0, context_1.loadSession)(sessPath);
|
|
739
|
+
const messages = existing?.messages && existing.messages.length > 0
|
|
740
|
+
? existing.messages
|
|
741
|
+
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("teams", {}, resolvedContext)) }];
|
|
742
|
+
(0, core_1.repairOrphanedToolCalls)(messages);
|
|
743
|
+
return {
|
|
744
|
+
messages,
|
|
745
|
+
sessionPath: sessPath,
|
|
746
|
+
state: existing?.state,
|
|
747
|
+
events: existing?.events,
|
|
748
|
+
structuredOutputs: existing?.structuredOutputs,
|
|
749
|
+
};
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
pendingDir,
|
|
753
|
+
friendStore: store,
|
|
754
|
+
provider,
|
|
755
|
+
externalId,
|
|
756
|
+
tenantId: teamsContext?.tenantId,
|
|
757
|
+
isGroupChat: false,
|
|
758
|
+
groupHasFamilyMember: false,
|
|
759
|
+
hasExistingGroupWithFamily: false,
|
|
760
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
761
|
+
drainPending: pending_1.drainPending,
|
|
762
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)((0, identity_1.getAgentName)(), deferredFriendId),
|
|
763
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
764
|
+
...opts,
|
|
765
|
+
toolContext: {
|
|
766
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
767
|
+
signin: async () => undefined,
|
|
768
|
+
...opts?.toolContext,
|
|
769
|
+
summarize: teamsToolContext.summarize,
|
|
770
|
+
},
|
|
771
|
+
}),
|
|
772
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
773
|
+
const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
|
|
774
|
+
return (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
|
|
775
|
+
},
|
|
776
|
+
accumulateFriendTokens: friends_1.accumulateFriendTokens,
|
|
777
|
+
signal: controller.signal,
|
|
778
|
+
runAgentOptions: agentOptions,
|
|
779
|
+
failoverState: teamsFailoverState,
|
|
780
|
+
});
|
|
781
|
+
// ── Handle pipeline-intercepted commands ────────────────────────
|
|
782
|
+
if (result.turnOutcome === "command") {
|
|
783
|
+
if (result.commandAction === "new") {
|
|
784
|
+
(0, context_1.deleteSession)(sessPath);
|
|
785
|
+
stream.emit("session cleared");
|
|
786
|
+
}
|
|
787
|
+
// For "response" commands: pipeline already emitted the response via onTextChunk
|
|
788
|
+
await callbacks.flush();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
/* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
|
|
792
|
+
if (result.failoverMessage) {
|
|
793
|
+
stream.emit(result.failoverMessage);
|
|
794
|
+
}
|
|
795
|
+
else if (capturedTerminalError) {
|
|
796
|
+
callbacks.onError(capturedTerminalError, "terminal");
|
|
797
|
+
}
|
|
798
|
+
/* v8 ignore stop */
|
|
799
|
+
// ── Handle gate result ────────────────────────────────────────
|
|
800
|
+
if (!result.gateResult.allowed) {
|
|
801
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
802
|
+
stream.emit(result.gateResult.autoReply);
|
|
803
|
+
}
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
// Flush any remaining accumulated text at end of turn
|
|
807
|
+
await callbacks.flush();
|
|
808
|
+
// After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
|
|
809
|
+
// This must happen after the stream is done so the OAuth card renders properly.
|
|
810
|
+
if (teamsContext && result.messages) {
|
|
811
|
+
const allContent = result.messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
|
|
812
|
+
if (allContent.includes("AUTH_REQUIRED:graph") && teamsContext.graphConnectionName)
|
|
813
|
+
await teamsContext.signin(teamsContext.graphConnectionName);
|
|
814
|
+
if (allContent.includes("AUTH_REQUIRED:ado") && teamsContext.adoConnectionName)
|
|
815
|
+
await teamsContext.signin(teamsContext.adoConnectionName);
|
|
816
|
+
if (allContent.includes("AUTH_REQUIRED:github") && teamsContext.githubConnectionName)
|
|
817
|
+
await teamsContext.signin(teamsContext.githubConnectionName);
|
|
818
|
+
}
|
|
819
|
+
if (result.turnOutcome !== "superseded") {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const supersedingIndex = drainedSteeringFollowUps
|
|
823
|
+
.map((followUp) => followUp.effect)
|
|
824
|
+
.lastIndexOf("clear_and_supersede");
|
|
825
|
+
if (supersedingIndex < 0) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const supersedingFollowUp = drainedSteeringFollowUps[supersedingIndex];
|
|
829
|
+
const replayTail = drainedSteeringFollowUps
|
|
830
|
+
.slice(supersedingIndex + 1)
|
|
831
|
+
.map((followUp) => followUp.text.trim())
|
|
832
|
+
.filter((followUpText) => followUpText.length > 0)
|
|
833
|
+
.join("\n");
|
|
834
|
+
if (replayTail) {
|
|
835
|
+
currentText = replayTail;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
currentText = supersedingFollowUp.text;
|
|
471
842
|
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
if (error instanceof session_transaction_1.SessionTurnBusyError || (error instanceof Error && error.name === "SessionTurnBusyError")) {
|
|
847
|
+
stream.emit("this conversation is busy finishing another turn; please try again");
|
|
472
848
|
return;
|
|
473
849
|
}
|
|
850
|
+
throw error;
|
|
474
851
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
852
|
+
}
|
|
853
|
+
// Internal port for the secondary bot App (not exposed externally).
|
|
854
|
+
// The primary app proxies /api/messages-secondary → localhost:SECONDARY_PORT/api/messages.
|
|
855
|
+
const SECONDARY_INTERNAL_PORT = 3979;
|
|
856
|
+
// Collect all unique OAuth connection names across top-level config and tenant overrides.
|
|
857
|
+
/* v8 ignore start -- runtime Teams SDK config; no unit-testable surface @preserve */
|
|
858
|
+
function allOAuthConnectionNames() {
|
|
859
|
+
const oauthConfig = (0, config_1.getOAuthConfig)();
|
|
860
|
+
const names = new Set();
|
|
861
|
+
if (oauthConfig.graphConnectionName)
|
|
862
|
+
names.add(oauthConfig.graphConnectionName);
|
|
863
|
+
if (oauthConfig.adoConnectionName)
|
|
864
|
+
names.add(oauthConfig.adoConnectionName);
|
|
865
|
+
if (oauthConfig.githubConnectionName)
|
|
866
|
+
names.add(oauthConfig.githubConnectionName);
|
|
867
|
+
if (oauthConfig.tenantOverrides) {
|
|
868
|
+
for (const ov of Object.values(oauthConfig.tenantOverrides)) {
|
|
869
|
+
if (ov.graphConnectionName)
|
|
870
|
+
names.add(ov.graphConnectionName);
|
|
871
|
+
if (ov.adoConnectionName)
|
|
872
|
+
names.add(ov.adoConnectionName);
|
|
873
|
+
if (ov.githubConnectionName)
|
|
874
|
+
names.add(ov.githubConnectionName);
|
|
492
875
|
}
|
|
493
876
|
}
|
|
494
|
-
|
|
495
|
-
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
496
|
-
const existing = (0, context_1.loadSession)(sessPath);
|
|
497
|
-
const messages = existing?.messages && existing.messages.length > 0
|
|
498
|
-
? existing.messages
|
|
499
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", undefined, toolContext?.context) }];
|
|
500
|
-
// Push user message
|
|
501
|
-
messages.push({ role: "user", content: text });
|
|
502
|
-
// Run agent
|
|
503
|
-
const controller = new AbortController();
|
|
504
|
-
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
505
|
-
const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs });
|
|
506
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
507
|
-
const agentOptions = {};
|
|
508
|
-
agentOptions.traceId = traceId;
|
|
509
|
-
if (toolContext)
|
|
510
|
-
agentOptions.toolContext = toolContext;
|
|
511
|
-
if (channelConfig.skipConfirmation)
|
|
512
|
-
agentOptions.skipConfirmation = true;
|
|
513
|
-
agentOptions.drainSteeringFollowUps = () => _turnCoordinator.drainFollowUps(turnKey).map((m) => ({ text: m.text }));
|
|
514
|
-
const result = await (0, core_1.runAgent)(messages, callbacks, "teams", controller.signal, agentOptions);
|
|
515
|
-
// Flush any remaining accumulated text at end of turn
|
|
516
|
-
await callbacks.flush();
|
|
517
|
-
// After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
|
|
518
|
-
// This must happen after the stream is done so the OAuth card renders properly.
|
|
519
|
-
if (teamsContext) {
|
|
520
|
-
const allContent = messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
|
|
521
|
-
if (allContent.includes("AUTH_REQUIRED:graph"))
|
|
522
|
-
await teamsContext.signin("graph");
|
|
523
|
-
if (allContent.includes("AUTH_REQUIRED:ado"))
|
|
524
|
-
await teamsContext.signin("ado");
|
|
525
|
-
if (allContent.includes("AUTH_REQUIRED:github"))
|
|
526
|
-
await teamsContext.signin("github");
|
|
527
|
-
}
|
|
528
|
-
// Trim context and save session
|
|
529
|
-
(0, context_1.postTurn)(messages, sessPath, result.usage);
|
|
530
|
-
// Accumulate token usage on friend record
|
|
531
|
-
if (toolContext?.context?.friend?.id) {
|
|
532
|
-
await (0, tokens_1.accumulateFriendTokens)(store, toolContext.context.friend.id, result.usage);
|
|
533
|
-
}
|
|
534
|
-
// SDK auto-closes the stream after our handler returns (app.process.js)
|
|
877
|
+
return [...names];
|
|
535
878
|
}
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
// Text is always accumulated in textBuffer and flushed periodically (chunked streaming).
|
|
539
|
-
function startTeamsApp() {
|
|
879
|
+
// Create an App instance from a TeamsConfig. Returns { app, mode }.
|
|
880
|
+
function createBotApp(teamsConfig) {
|
|
540
881
|
const mentionStripping = { activity: { mentions: { stripText: true } } };
|
|
541
|
-
const teamsConfig = (0, config_2.getTeamsConfig)();
|
|
542
|
-
let app;
|
|
543
|
-
let mode;
|
|
544
882
|
const oauthConfig = (0, config_1.getOAuthConfig)();
|
|
545
|
-
if (teamsConfig.clientId) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
883
|
+
if (teamsConfig.clientId && teamsConfig.clientSecret) {
|
|
884
|
+
return {
|
|
885
|
+
app: new teams_apps_1.App({
|
|
886
|
+
clientId: teamsConfig.clientId,
|
|
887
|
+
clientSecret: teamsConfig.clientSecret,
|
|
888
|
+
tenantId: teamsConfig.tenantId,
|
|
889
|
+
oauth: { defaultConnectionName: oauthConfig.graphConnectionName },
|
|
890
|
+
...mentionStripping,
|
|
891
|
+
}),
|
|
892
|
+
mode: "Bot Service (client secret)",
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
else if (teamsConfig.clientId) {
|
|
896
|
+
return {
|
|
897
|
+
app: new teams_apps_1.App({
|
|
898
|
+
clientId: teamsConfig.clientId,
|
|
899
|
+
tenantId: teamsConfig.tenantId,
|
|
900
|
+
...(teamsConfig.managedIdentityClientId ? { managedIdentityClientId: teamsConfig.managedIdentityClientId } : {}),
|
|
901
|
+
oauth: { defaultConnectionName: oauthConfig.graphConnectionName },
|
|
902
|
+
...mentionStripping,
|
|
903
|
+
}),
|
|
904
|
+
mode: "Bot Service (managed identity)",
|
|
905
|
+
};
|
|
555
906
|
}
|
|
556
907
|
else {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
908
|
+
return {
|
|
909
|
+
app: new teams_apps_1.App({
|
|
910
|
+
plugins: [new teams_dev_1.DevtoolsPlugin()],
|
|
911
|
+
...mentionStripping,
|
|
912
|
+
}),
|
|
913
|
+
mode: "DevtoolsPlugin",
|
|
914
|
+
};
|
|
563
915
|
}
|
|
916
|
+
}
|
|
917
|
+
/* v8 ignore stop */
|
|
918
|
+
// Register message, verify-state, and error handlers on an App instance.
|
|
919
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
920
|
+
function registerBotHandlers(app, label) {
|
|
921
|
+
const connectionNames = allOAuthConnectionNames();
|
|
564
922
|
// Override default OAuth verify-state handler. The SDK's built-in handler
|
|
565
923
|
// uses a single defaultConnectionName, which breaks multi-connection setups
|
|
566
924
|
// (graph + ado + github). The verifyState activity only carries a `state`
|
|
567
925
|
// code with no connectionName, so we try each configured connection until
|
|
568
926
|
// one succeeds.
|
|
569
|
-
|
|
570
|
-
oauthConfig.graphConnectionName,
|
|
571
|
-
oauthConfig.adoConnectionName,
|
|
572
|
-
oauthConfig.githubConnectionName,
|
|
573
|
-
].filter(Boolean);
|
|
574
|
-
app.on("signin.verify-state", async (ctx) => {
|
|
927
|
+
app.on("signin.verify-state", (async (ctx) => {
|
|
575
928
|
const { api, activity } = ctx;
|
|
576
929
|
if (!activity.value?.state)
|
|
577
930
|
return { status: 404 };
|
|
578
|
-
for (const cn of
|
|
931
|
+
for (const cn of connectionNames) {
|
|
579
932
|
try {
|
|
580
933
|
await api.users.token.get({
|
|
581
934
|
channelId: activity.channelId,
|
|
@@ -583,14 +936,82 @@ function startTeamsApp() {
|
|
|
583
936
|
connectionName: cn,
|
|
584
937
|
code: activity.value.state,
|
|
585
938
|
});
|
|
586
|
-
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.verify_state", component: "channels", message: `verify-state succeeded for connection "${cn}"`, meta: { connectionName: cn } });
|
|
939
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state succeeded for connection "${cn}"`, meta: { connectionName: cn } });
|
|
587
940
|
return { status: 200 };
|
|
588
941
|
}
|
|
589
942
|
catch { /* try next */ }
|
|
590
943
|
}
|
|
591
|
-
(0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message:
|
|
944
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state failed for all connections`, meta: {} });
|
|
592
945
|
return { status: 412 };
|
|
946
|
+
}));
|
|
947
|
+
// Handle Teams feedback reactions (thumbs up/down on AI-generated messages).
|
|
948
|
+
// SDK routes message/submitAction with actionName "feedback" to this event.
|
|
949
|
+
/* v8 ignore start -- Teams SDK invoke handler; requires live SDK context @preserve */
|
|
950
|
+
app.on("message.submit.feedback", async (ctx) => {
|
|
951
|
+
const { stream, activity } = ctx;
|
|
952
|
+
const reaction = activity.value?.actionValue?.reaction;
|
|
953
|
+
const comment = activity.value?.actionValue?.feedback;
|
|
954
|
+
const convId = activity.conversation?.id || "unknown";
|
|
955
|
+
const turnKey = teamsTurnKey(convId);
|
|
956
|
+
// Validate payload — graceful no-op for malformed invocations
|
|
957
|
+
if (activity.value?.actionName !== "feedback" || !reaction) {
|
|
958
|
+
return undefined;
|
|
959
|
+
}
|
|
960
|
+
const syntheticText = buildFeedbackSyntheticText(reaction, comment);
|
|
961
|
+
// Turn coordination: if a turn is active, enqueue as steering follow-up
|
|
962
|
+
if (!_turnCoordinator.tryBeginTurn(turnKey)) {
|
|
963
|
+
_turnCoordinator.enqueueFollowUp(turnKey, {
|
|
964
|
+
conversationId: convId,
|
|
965
|
+
text: syntheticText,
|
|
966
|
+
receivedAt: Date.now(),
|
|
967
|
+
effect: (0, continuity_1.classifySteeringFollowUpEffect)(syntheticText),
|
|
968
|
+
});
|
|
969
|
+
return undefined;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const teamsContext = {
|
|
973
|
+
signin: async () => undefined,
|
|
974
|
+
aadObjectId: activity.from?.aadObjectId,
|
|
975
|
+
tenantId: activity.conversation?.tenantId,
|
|
976
|
+
displayName: activity.from?.name,
|
|
977
|
+
};
|
|
978
|
+
const ctxSend = async (t) => {
|
|
979
|
+
await ctx.send({ type: "message", text: t, replyToId: activity.replyToId, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
980
|
+
};
|
|
981
|
+
await handleTeamsMessage(syntheticText, stream, convId, teamsContext, ctxSend, { isReactionSignal: true, suppressEmptyStreamMessage: true });
|
|
982
|
+
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
985
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.feedback_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
|
|
986
|
+
}
|
|
987
|
+
finally {
|
|
988
|
+
_turnCoordinator.endTurn(turnKey);
|
|
989
|
+
}
|
|
990
|
+
return undefined;
|
|
991
|
+
});
|
|
992
|
+
/* v8 ignore stop */
|
|
993
|
+
// Handle bot install — send welcome Adaptive Card with prompt starters.
|
|
994
|
+
/* v8 ignore start -- Teams SDK install handler; requires live SDK context @preserve */
|
|
995
|
+
app.on("install.add", async (ctx) => {
|
|
996
|
+
try {
|
|
997
|
+
const card = buildWelcomeCard();
|
|
998
|
+
await ctx.send({
|
|
999
|
+
type: "message",
|
|
1000
|
+
attachments: [{
|
|
1001
|
+
contentType: "application/vnd.microsoft.card.adaptive",
|
|
1002
|
+
content: card,
|
|
1003
|
+
}],
|
|
1004
|
+
entities: aiLabelEntities(),
|
|
1005
|
+
channelData: { feedbackLoopEnabled: true },
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
catch (err) {
|
|
1009
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1010
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.welcome_handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
|
|
1011
|
+
}
|
|
1012
|
+
return undefined;
|
|
593
1013
|
});
|
|
1014
|
+
/* v8 ignore stop */
|
|
594
1015
|
app.on("message", async (ctx) => {
|
|
595
1016
|
const { stream, activity, api, signin } = ctx;
|
|
596
1017
|
const text = activity.text || "";
|
|
@@ -598,17 +1019,41 @@ function startTeamsApp() {
|
|
|
598
1019
|
const turnKey = teamsTurnKey(convId);
|
|
599
1020
|
const userId = activity.from?.id || "";
|
|
600
1021
|
const channelId = activity.channelId || "msteams";
|
|
601
|
-
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1022
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: `[${label}] incoming teams message`, meta: { userId: userId.slice(0, 12), conversationId: convId.slice(0, 20) } });
|
|
1023
|
+
const commandRegistry = createTeamsCommandRegistry();
|
|
1024
|
+
const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
|
|
1025
|
+
if (parsedSlashCommand) {
|
|
1026
|
+
const dispatchResult = commandRegistry.dispatch(parsedSlashCommand.command, { channel: "teams" });
|
|
1027
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
1028
|
+
if (dispatchResult.result.action === "response") {
|
|
1029
|
+
stream.emit(dispatchResult.result.message || "");
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (dispatchResult.result.action === "new") {
|
|
1033
|
+
const commandStore = getFriendStore();
|
|
1034
|
+
const commandProvider = activity.from?.aadObjectId ? "aad" : "teams-conversation";
|
|
1035
|
+
const commandExternalId = activity.from?.aadObjectId || convId;
|
|
1036
|
+
const commandResolver = new friends_1.FriendResolver(commandStore, {
|
|
1037
|
+
provider: commandProvider,
|
|
1038
|
+
externalId: commandExternalId,
|
|
1039
|
+
tenantId: activity.conversation?.tenantId,
|
|
1040
|
+
displayName: activity.from?.name || "Unknown",
|
|
1041
|
+
channel: "teams",
|
|
1042
|
+
});
|
|
1043
|
+
const commandContext = await commandResolver.resolve();
|
|
1044
|
+
(0, context_1.deleteSession)((0, config_2.sessionPath)(commandContext.friend.id, "teams", convId));
|
|
1045
|
+
stream.emit("session cleared");
|
|
1046
|
+
if (_turnCoordinator.isTurnActive(turnKey)) {
|
|
1047
|
+
_turnCoordinator.enqueueFollowUp(turnKey, {
|
|
1048
|
+
conversationId: convId,
|
|
1049
|
+
text,
|
|
1050
|
+
receivedAt: Date.now(),
|
|
1051
|
+
effect: "clear_and_supersede",
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
612
1057
|
}
|
|
613
1058
|
// If this conversation already has an active turn, steer follow-up input
|
|
614
1059
|
// into that turn and avoid starting a second concurrent turn.
|
|
@@ -617,31 +1062,35 @@ function startTeamsApp() {
|
|
|
617
1062
|
conversationId: convId,
|
|
618
1063
|
text,
|
|
619
1064
|
receivedAt: Date.now(),
|
|
1065
|
+
effect: (0, continuity_1.classifySteeringFollowUpEffect)(text),
|
|
620
1066
|
});
|
|
621
1067
|
return;
|
|
622
1068
|
}
|
|
623
1069
|
try {
|
|
1070
|
+
// Resolve OAuth connection names for this user's tenant (supports per-tenant overrides).
|
|
1071
|
+
const tenantId = activity.conversation?.tenantId;
|
|
1072
|
+
const tenantOAuth = (0, config_1.resolveOAuthForTenant)(tenantId);
|
|
624
1073
|
// Fetch tokens for both OAuth connections independently.
|
|
625
1074
|
// Failures are silently caught -- the tool handler will request signin if needed.
|
|
626
1075
|
let graphToken;
|
|
627
1076
|
let adoToken;
|
|
628
1077
|
let githubToken;
|
|
629
1078
|
try {
|
|
630
|
-
const graphRes = await api.users.token.get({ userId, connectionName:
|
|
1079
|
+
const graphRes = await api.users.token.get({ userId, connectionName: tenantOAuth.graphConnectionName, channelId });
|
|
631
1080
|
graphToken = graphRes?.token;
|
|
632
1081
|
}
|
|
633
1082
|
catch { /* no token yet — tool handler will trigger signin */ }
|
|
634
1083
|
try {
|
|
635
|
-
const adoRes = await api.users.token.get({ userId, connectionName:
|
|
1084
|
+
const adoRes = await api.users.token.get({ userId, connectionName: tenantOAuth.adoConnectionName, channelId });
|
|
636
1085
|
adoToken = adoRes?.token;
|
|
637
1086
|
}
|
|
638
1087
|
catch { /* no token yet — tool handler will trigger signin */ }
|
|
639
1088
|
try {
|
|
640
|
-
const githubRes = await api.users.token.get({ userId, connectionName:
|
|
1089
|
+
const githubRes = await api.users.token.get({ userId, connectionName: tenantOAuth.githubConnectionName, channelId });
|
|
641
1090
|
githubToken = githubRes?.token;
|
|
642
1091
|
}
|
|
643
1092
|
catch { /* no token yet — tool handler will trigger signin */ }
|
|
644
|
-
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken } });
|
|
1093
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken, tenantId } });
|
|
645
1094
|
const teamsContext = {
|
|
646
1095
|
graphToken,
|
|
647
1096
|
adoToken,
|
|
@@ -661,12 +1110,17 @@ function startTeamsApp() {
|
|
|
661
1110
|
aadObjectId: activity.from?.aadObjectId,
|
|
662
1111
|
tenantId: activity.conversation?.tenantId,
|
|
663
1112
|
displayName: activity.from?.name,
|
|
1113
|
+
graphConnectionName: tenantOAuth.graphConnectionName,
|
|
1114
|
+
adoConnectionName: tenantOAuth.adoConnectionName,
|
|
1115
|
+
githubConnectionName: tenantOAuth.githubConnectionName,
|
|
1116
|
+
/* v8 ignore next -- bot API availability branch; requires live SDK context @preserve */
|
|
1117
|
+
botApi: app.id && api ? { id: app.id, conversations: api.conversations } : undefined,
|
|
664
1118
|
};
|
|
665
1119
|
/* v8 ignore next 5 -- bot-framework integration callback; tested via handleTeamsMessage sendMessage path @preserve */
|
|
666
1120
|
const ctxSend = async (t) => {
|
|
667
1121
|
// Use send with replyToId (not reply, which adds a blockquote).
|
|
668
1122
|
// replyToId anchors the message after the user's message in Copilot Chat.
|
|
669
|
-
await ctx.send({ type: "message", text: t, replyToId: activity.id });
|
|
1123
|
+
await ctx.send({ type: "message", text: t, replyToId: activity.id, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
670
1124
|
};
|
|
671
1125
|
await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
|
|
672
1126
|
}
|
|
@@ -678,6 +1132,251 @@ function startTeamsApp() {
|
|
|
678
1132
|
_turnCoordinator.endTurn(turnKey);
|
|
679
1133
|
}
|
|
680
1134
|
});
|
|
1135
|
+
app.event("error", ({ error }) => {
|
|
1136
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1137
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: `[${label}] ${msg}`, meta: {} });
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
function findAadObjectId(friend) {
|
|
1141
|
+
for (const ext of friend.externalIds) {
|
|
1142
|
+
if (ext.provider === "aad" && !ext.externalId.startsWith("group:")) {
|
|
1143
|
+
return { aadObjectId: ext.externalId, tenantId: ext.tenantId };
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return undefined;
|
|
1147
|
+
}
|
|
1148
|
+
function resolveTeamsFriendStore(deps) {
|
|
1149
|
+
return deps.store
|
|
1150
|
+
?? deps.createFriendStore?.()
|
|
1151
|
+
?? new friends_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
|
|
1152
|
+
}
|
|
1153
|
+
function getTeamsConversations(botApi) {
|
|
1154
|
+
return botApi.conversations;
|
|
1155
|
+
}
|
|
1156
|
+
function hasExplicitCrossChatAuthorization(params) {
|
|
1157
|
+
return params.intent === "explicit_cross_chat"
|
|
1158
|
+
&& friends_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
|
|
1159
|
+
}
|
|
1160
|
+
async function sendProactiveTeamsMessageToSession(params, deps) {
|
|
1161
|
+
const store = resolveTeamsFriendStore(deps);
|
|
1162
|
+
const conversations = getTeamsConversations(deps.botApi);
|
|
1163
|
+
let friend;
|
|
1164
|
+
try {
|
|
1165
|
+
friend = await store.get(params.friendId);
|
|
1166
|
+
}
|
|
1167
|
+
catch {
|
|
1168
|
+
friend = null;
|
|
1169
|
+
}
|
|
1170
|
+
if (!friend) {
|
|
1171
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1172
|
+
level: "warn",
|
|
1173
|
+
component: "senses",
|
|
1174
|
+
event: "senses.teams_proactive_no_friend",
|
|
1175
|
+
message: "proactive send skipped: friend not found",
|
|
1176
|
+
meta: { friendId: params.friendId, sessionKey: params.sessionKey },
|
|
1177
|
+
});
|
|
1178
|
+
return { delivered: false, reason: "friend_not_found" };
|
|
1179
|
+
}
|
|
1180
|
+
if (!hasExplicitCrossChatAuthorization(params) && !friends_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
|
|
1181
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1182
|
+
component: "senses",
|
|
1183
|
+
event: "senses.teams_proactive_trust_skip",
|
|
1184
|
+
message: "proactive send skipped: trust level not allowed",
|
|
1185
|
+
meta: {
|
|
1186
|
+
friendId: params.friendId,
|
|
1187
|
+
trustLevel: friend.trustLevel ?? "unknown",
|
|
1188
|
+
intent: params.intent ?? "generic_outreach",
|
|
1189
|
+
authorizingTrustLevel: params.authorizingSession?.trustLevel ?? null,
|
|
1190
|
+
},
|
|
1191
|
+
});
|
|
1192
|
+
return { delivered: false, reason: "trust_skip" };
|
|
1193
|
+
}
|
|
1194
|
+
const aadInfo = findAadObjectId(friend);
|
|
1195
|
+
if (!aadInfo) {
|
|
1196
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1197
|
+
level: "warn",
|
|
1198
|
+
component: "senses",
|
|
1199
|
+
event: "senses.teams_proactive_no_aad_id",
|
|
1200
|
+
message: "proactive send skipped: no AAD object ID found",
|
|
1201
|
+
meta: { friendId: params.friendId, sessionKey: params.sessionKey },
|
|
1202
|
+
});
|
|
1203
|
+
return { delivered: false, reason: "missing_target" };
|
|
1204
|
+
}
|
|
1205
|
+
const internalContentBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(params.text);
|
|
1206
|
+
if (internalContentBlockReason) {
|
|
1207
|
+
(0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
|
|
1208
|
+
friendId: params.friendId,
|
|
1209
|
+
sessionKey: params.sessionKey,
|
|
1210
|
+
reason: internalContentBlockReason,
|
|
1211
|
+
source: "session_send",
|
|
1212
|
+
});
|
|
1213
|
+
return { delivered: false, reason: "internal_content_blocked" };
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
const conversation = await conversations.create({
|
|
1217
|
+
bot: { id: deps.botApi.id },
|
|
1218
|
+
members: [{ id: aadInfo.aadObjectId, role: "user", name: friend.name || aadInfo.aadObjectId }],
|
|
1219
|
+
tenantId: aadInfo.tenantId,
|
|
1220
|
+
isGroup: false,
|
|
1221
|
+
});
|
|
1222
|
+
await conversations.activities(conversation.id).create({
|
|
1223
|
+
type: "message",
|
|
1224
|
+
text: params.text,
|
|
1225
|
+
});
|
|
1226
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1227
|
+
component: "senses",
|
|
1228
|
+
event: "senses.teams_proactive_sent",
|
|
1229
|
+
message: "proactive teams message sent",
|
|
1230
|
+
meta: { friendId: params.friendId, aadObjectId: aadInfo.aadObjectId, sessionKey: params.sessionKey },
|
|
1231
|
+
});
|
|
1232
|
+
return { delivered: true };
|
|
1233
|
+
}
|
|
1234
|
+
catch (error) {
|
|
1235
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1236
|
+
level: "error",
|
|
1237
|
+
component: "senses",
|
|
1238
|
+
event: "senses.teams_proactive_send_error",
|
|
1239
|
+
message: "proactive teams send failed",
|
|
1240
|
+
meta: {
|
|
1241
|
+
friendId: params.friendId,
|
|
1242
|
+
aadObjectId: aadInfo.aadObjectId,
|
|
1243
|
+
sessionKey: params.sessionKey,
|
|
1244
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1245
|
+
},
|
|
1246
|
+
});
|
|
1247
|
+
return { delivered: false, reason: "send_error" };
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function scanPendingTeamsFiles(pendingRoot) {
|
|
1251
|
+
const results = [];
|
|
1252
|
+
let friendIds;
|
|
1253
|
+
try {
|
|
1254
|
+
friendIds = fs.readdirSync(pendingRoot);
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
return results;
|
|
1258
|
+
}
|
|
1259
|
+
for (const friendId of friendIds) {
|
|
1260
|
+
const teamsDir = path.join(pendingRoot, friendId, "teams");
|
|
1261
|
+
let keys;
|
|
1262
|
+
try {
|
|
1263
|
+
keys = fs.readdirSync(teamsDir);
|
|
1264
|
+
}
|
|
1265
|
+
catch {
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
for (const key of keys) {
|
|
1269
|
+
const keyDir = path.join(teamsDir, key);
|
|
1270
|
+
let files;
|
|
1271
|
+
try {
|
|
1272
|
+
files = fs.readdirSync(keyDir);
|
|
1273
|
+
}
|
|
1274
|
+
catch {
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
for (const file of files.filter((f) => f.endsWith(".json")).sort()) {
|
|
1278
|
+
const filePath = path.join(keyDir, file);
|
|
1279
|
+
try {
|
|
1280
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1281
|
+
results.push({ friendId, key, filePath, content });
|
|
1282
|
+
}
|
|
1283
|
+
catch {
|
|
1284
|
+
// skip unreadable files
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return results;
|
|
1290
|
+
}
|
|
1291
|
+
async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
|
|
1292
|
+
const root = pendingRoot ?? path.join((0, identity_1.getAgentRoot)(), "state", "pending");
|
|
1293
|
+
const pendingFiles = scanPendingTeamsFiles(root);
|
|
1294
|
+
const result = { sent: 0, skipped: 0, failed: 0 };
|
|
1295
|
+
for (const { friendId, key, filePath, content } of pendingFiles) {
|
|
1296
|
+
let parsed;
|
|
1297
|
+
try {
|
|
1298
|
+
parsed = JSON.parse(content);
|
|
1299
|
+
}
|
|
1300
|
+
catch {
|
|
1301
|
+
result.failed++;
|
|
1302
|
+
try {
|
|
1303
|
+
fs.unlinkSync(filePath);
|
|
1304
|
+
}
|
|
1305
|
+
catch { /* ignore */ }
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
const messageText = typeof parsed.content === "string" ? parsed.content : "";
|
|
1309
|
+
if (!messageText.trim()) {
|
|
1310
|
+
result.skipped++;
|
|
1311
|
+
try {
|
|
1312
|
+
fs.unlinkSync(filePath);
|
|
1313
|
+
}
|
|
1314
|
+
catch { /* ignore */ }
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
const internalBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(messageText);
|
|
1318
|
+
if (internalBlockReason) {
|
|
1319
|
+
result.skipped++;
|
|
1320
|
+
try {
|
|
1321
|
+
fs.unlinkSync(filePath);
|
|
1322
|
+
}
|
|
1323
|
+
catch { /* ignore */ }
|
|
1324
|
+
(0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
|
|
1325
|
+
friendId,
|
|
1326
|
+
reason: internalBlockReason,
|
|
1327
|
+
source: "pending_drain",
|
|
1328
|
+
});
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const sendResult = await sendProactiveTeamsMessageToSession({
|
|
1332
|
+
friendId,
|
|
1333
|
+
sessionKey: key,
|
|
1334
|
+
text: messageText,
|
|
1335
|
+
intent: "generic_outreach",
|
|
1336
|
+
}, {
|
|
1337
|
+
botApi,
|
|
1338
|
+
store,
|
|
1339
|
+
});
|
|
1340
|
+
if (sendResult.delivered) {
|
|
1341
|
+
result.sent++;
|
|
1342
|
+
try {
|
|
1343
|
+
fs.unlinkSync(filePath);
|
|
1344
|
+
}
|
|
1345
|
+
catch { /* ignore */ }
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if (sendResult.reason === "friend_not_found" || sendResult.reason === "trust_skip" || sendResult.reason === "missing_target") {
|
|
1349
|
+
result.skipped++;
|
|
1350
|
+
try {
|
|
1351
|
+
fs.unlinkSync(filePath);
|
|
1352
|
+
}
|
|
1353
|
+
catch { /* ignore */ }
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
result.failed++;
|
|
1357
|
+
}
|
|
1358
|
+
if (result.sent > 0 || result.skipped > 0 || result.failed > 0) {
|
|
1359
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1360
|
+
component: "senses",
|
|
1361
|
+
event: "senses.teams_proactive_drain_complete",
|
|
1362
|
+
message: "teams proactive drain complete",
|
|
1363
|
+
meta: { sent: result.sent, skipped: result.skipped, failed: result.failed },
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return result;
|
|
1367
|
+
}
|
|
1368
|
+
// Start the Teams app in DevtoolsPlugin mode (local dev) or Bot Service mode (real Teams).
|
|
1369
|
+
// Mode is determined by getTeamsConfig().clientId.
|
|
1370
|
+
// Text is always accumulated in textBuffer and flushed periodically (chunked streaming).
|
|
1371
|
+
//
|
|
1372
|
+
// Dual-bot support: if teamsSecondary is configured with a clientId, a second App
|
|
1373
|
+
// instance starts on an internal port and the primary app proxies requests from
|
|
1374
|
+
// /api/messages-secondary to it. This lets a single App Service serve two bot
|
|
1375
|
+
// registrations (e.g. one per tenant) without SDK modifications.
|
|
1376
|
+
function startTeamsApp() {
|
|
1377
|
+
const teamsConfig = (0, config_2.getTeamsConfig)();
|
|
1378
|
+
const { app, mode } = createBotApp(teamsConfig);
|
|
1379
|
+
registerBotHandlers(app, "primary");
|
|
681
1380
|
if (!process.listeners("unhandledRejection").some((l) => l.__agentHandler)) {
|
|
682
1381
|
const handler = (err) => {
|
|
683
1382
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -686,11 +1385,54 @@ function startTeamsApp() {
|
|
|
686
1385
|
handler.__agentHandler = true;
|
|
687
1386
|
process.on("unhandledRejection", handler);
|
|
688
1387
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: msg, meta: {} });
|
|
692
|
-
});
|
|
693
|
-
const port = (0, config_2.getTeamsChannelConfig)().port;
|
|
1388
|
+
/* v8 ignore next -- PORT env branch; runtime-only @preserve */
|
|
1389
|
+
const port = process.env.PORT ? Number(process.env.PORT) : (0, config_2.getTeamsChannelConfig)().port;
|
|
694
1390
|
app.start(port);
|
|
695
|
-
|
|
1391
|
+
// Diagnostic: log tool count at startup to verify deploy
|
|
1392
|
+
const startupTools = (0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("teams"));
|
|
1393
|
+
const toolNames = startupTools.map((t) => t.function.name);
|
|
1394
|
+
(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") } });
|
|
1395
|
+
// --- Secondary bot (dual-bot support) ---
|
|
1396
|
+
// If teamsSecondary has a clientId, start a second App on an internal port
|
|
1397
|
+
// and proxy /api/messages-secondary on the primary app to it.
|
|
1398
|
+
/* v8 ignore start -- dual-bot proxy wiring; requires live Teams SDK + HTTP @preserve */
|
|
1399
|
+
const secondaryConfig = (0, config_1.getTeamsSecondaryConfig)();
|
|
1400
|
+
if (secondaryConfig.clientId) {
|
|
1401
|
+
const { app: secondaryApp, mode: secondaryMode } = createBotApp(secondaryConfig);
|
|
1402
|
+
registerBotHandlers(secondaryApp, "secondary");
|
|
1403
|
+
secondaryApp.start(SECONDARY_INTERNAL_PORT);
|
|
1404
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.app_started", component: "channels", message: `Secondary bot started on internal port ${SECONDARY_INTERNAL_PORT} with ${secondaryMode}`, meta: { port: SECONDARY_INTERNAL_PORT, mode: secondaryMode } });
|
|
1405
|
+
// Proxy: forward /api/messages-secondary on the primary app's Express
|
|
1406
|
+
// to localhost:SECONDARY_INTERNAL_PORT/api/messages.
|
|
1407
|
+
// The SDK's HttpPlugin exposes .post() bound to its Express instance.
|
|
1408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1409
|
+
const httpPlugin = app.http;
|
|
1410
|
+
httpPlugin.post("/api/messages-secondary", (req, res) => {
|
|
1411
|
+
const body = JSON.stringify(req.body);
|
|
1412
|
+
const proxyReq = http.request({
|
|
1413
|
+
hostname: "127.0.0.1",
|
|
1414
|
+
port: SECONDARY_INTERNAL_PORT,
|
|
1415
|
+
path: "/api/messages",
|
|
1416
|
+
method: "POST",
|
|
1417
|
+
headers: {
|
|
1418
|
+
...req.headers,
|
|
1419
|
+
"content-length": Buffer.byteLength(body).toString(),
|
|
1420
|
+
},
|
|
1421
|
+
}, (proxyRes) => {
|
|
1422
|
+
res.writeHead(proxyRes.statusCode ?? 200, proxyRes.headers);
|
|
1423
|
+
proxyRes.pipe(res);
|
|
1424
|
+
});
|
|
1425
|
+
proxyReq.on("error", (err) => {
|
|
1426
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.proxy_error", component: "channels", message: `secondary proxy error: ${err.message}`, meta: {} });
|
|
1427
|
+
if (!res.headersSent) {
|
|
1428
|
+
res.writeHead(502);
|
|
1429
|
+
res.end("Bad Gateway");
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
proxyReq.write(body);
|
|
1433
|
+
proxyReq.end();
|
|
1434
|
+
});
|
|
1435
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.proxy_ready", component: "channels", message: "proxy /api/messages-secondary → secondary bot ready", meta: {} });
|
|
1436
|
+
}
|
|
1437
|
+
/* v8 ignore stop */
|
|
696
1438
|
}
|