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