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