@ouro.bot/cli 0.1.0-alpha.40 → 0.1.0-alpha.400
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 +109 -14
- 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 +2455 -6
- 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 +832 -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 +417 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -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 +101 -128
- package/dist/heart/core.js +801 -217
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +397 -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 +213 -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 +599 -0
- package/dist/heart/daemon/cli-exec.js +3616 -0
- package/dist/heart/daemon/cli-help.js +396 -0
- package/dist/heart/daemon/cli-parse.js +1118 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +560 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +28 -1582
- package/dist/heart/daemon/daemon-entry.js +356 -3
- package/dist/heart/daemon/daemon-health.js +141 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +684 -58
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +419 -0
- package/dist/heart/daemon/health-monitor.js +79 -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/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +209 -0
- package/dist/heart/daemon/launchd.js +46 -9
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +105 -0
- package/dist/heart/daemon/message-router.js +17 -8
- 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 +201 -0
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +216 -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 +119 -30
- 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 +307 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +237 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +510 -0
- package/dist/heart/daemon/up-progress.js +135 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-scheduler.js +371 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +53 -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 +33 -12
- package/dist/heart/identity.js +161 -65
- 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/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 +64 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +232 -0
- package/dist/heart/outlook/outlook-http-static.js +99 -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 +28 -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 +359 -0
- package/dist/heart/outlook/readers/continuity-readers.js +332 -0
- package/dist/heart/outlook/readers/runtime-readers.js +660 -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 +133 -0
- package/dist/heart/provider-binding-resolver.js +239 -0
- package/dist/heart/provider-credentials.js +383 -0
- package/dist/heart/provider-failover.js +266 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +237 -0
- package/dist/heart/provider-state.js +216 -0
- package/dist/heart/provider-visibility.js +186 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +193 -55
- package/dist/heart/providers/azure.js +103 -12
- 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 +39 -29
- package/dist/heart/runtime-credentials.js +181 -0
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +855 -0
- package/dist/heart/session-transcript.js +167 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +36 -27
- 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 +351 -0
- package/dist/heart/turn-coordinator.js +28 -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 +301 -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 +12 -2
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +134 -87
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +14 -1
- package/dist/mind/friends/channel.js +21 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +38 -1
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +1 -1
- 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 +66 -7
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +946 -167
- 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 +83 -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-BAcU08c-.css +1 -0
- package/dist/outlook-ui/assets/index-D7l3l4vY.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 +461 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +79 -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 +107 -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 +371 -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 +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +26 -1
- 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 +42 -687
- 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 +182 -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-notes.js +376 -0
- package/dist/repertoire/tools-session.js +739 -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-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +144 -113
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +382 -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} +260 -9
- package/dist/senses/bluebubbles/entry.js +70 -0
- package/dist/senses/bluebubbles/inbound-log.js +113 -0
- package/dist/senses/bluebubbles/index.js +1620 -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} +45 -3
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- 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 +526 -211
- package/dist/senses/commands.js +66 -3
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +600 -95
- package/dist/senses/pipeline.js +539 -61
- 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 +569 -237
- package/dist/senses/trust-gate.js +5 -5
- package/package.json +28 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +110 -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 +134 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/mind/associative-recall.js +0 -197
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -832
- package/dist/senses/debug-activity.js +0 -127
- package/subagents/README.md +0 -60
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /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,17 +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");
|
|
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
|
+
}
|
|
71
92
|
// Strip @mention markup from incoming messages.
|
|
72
93
|
// Removes <at>...</at> tags and trims extra whitespace.
|
|
73
94
|
// Fallback safety net -- the SDK's activity.mentions.stripText should handle
|
|
@@ -122,6 +143,46 @@ function splitMessage(text, maxLen) {
|
|
|
122
143
|
}
|
|
123
144
|
return chunks;
|
|
124
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
|
+
}
|
|
125
186
|
// Create Teams-specific callbacks for the agent loop.
|
|
126
187
|
// The SDK handles cumulative text, debouncing (500ms), and the streaming
|
|
127
188
|
// protocol (streamSequence, streamId, informative/streaming/final types).
|
|
@@ -134,12 +195,16 @@ function splitMessage(text, maxLen) {
|
|
|
134
195
|
// (transient status) or safeSend (terminal errors). Reasoning is accumulated
|
|
135
196
|
// and periodically pushed via safeUpdate on the same flush timer tick.
|
|
136
197
|
function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
198
|
+
const MIN_INITIAL_CHARS = 20;
|
|
137
199
|
let stopped = false; // set when stream signals cancellation (403)
|
|
138
200
|
let hadToolRun = false;
|
|
139
201
|
let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
|
|
140
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
|
|
141
205
|
let textBuffer = ""; // accumulated text output for chunked streaming
|
|
142
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
|
|
143
208
|
let phraseTimer = null;
|
|
144
209
|
let lastPhrase = "";
|
|
145
210
|
let flushTimer = null;
|
|
@@ -189,15 +254,16 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
189
254
|
result.catch(() => markStopped());
|
|
190
255
|
}
|
|
191
256
|
}
|
|
192
|
-
// Safely emit a text delta to the stream.
|
|
257
|
+
// Safely emit a text delta to the stream with AI labels.
|
|
193
258
|
// On error (e.g. 403 from Teams stop button), abort the controller.
|
|
194
259
|
function safeEmit(text) {
|
|
195
260
|
/* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
|
|
196
261
|
if (stopped)
|
|
197
262
|
return;
|
|
198
263
|
try {
|
|
199
|
-
catchAsync(stream.emit(text));
|
|
264
|
+
catchAsync(stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } }));
|
|
200
265
|
streamHasContent = true;
|
|
266
|
+
totalEmitted += text.length;
|
|
201
267
|
}
|
|
202
268
|
catch {
|
|
203
269
|
markStopped();
|
|
@@ -212,7 +278,7 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
212
278
|
try {
|
|
213
279
|
// stream.emit() is typed as void but the Teams SDK returns a Promise
|
|
214
280
|
// internally (async HTTP). Cast to capture the result for awaiting.
|
|
215
|
-
const result = stream.emit(text);
|
|
281
|
+
const result = stream.emit({ text, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
216
282
|
streamHasContent = true;
|
|
217
283
|
if (result && typeof result.then === "function") {
|
|
218
284
|
await result;
|
|
@@ -268,11 +334,49 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
268
334
|
// emitted text into a single streaming message (cumulative), so every
|
|
269
335
|
// periodic flush appends to the same response — not separate messages.
|
|
270
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).
|
|
271
340
|
function flushTextBuffer() {
|
|
272
341
|
if (!textBuffer)
|
|
273
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
|
+
}
|
|
274
374
|
safeEmit(textBuffer);
|
|
275
375
|
textBuffer = "";
|
|
376
|
+
if (!firstContentEmitted) {
|
|
377
|
+
firstContentEmitted = true;
|
|
378
|
+
stopPhraseRotation();
|
|
379
|
+
}
|
|
276
380
|
}
|
|
277
381
|
function startPhraseRotation(pool) {
|
|
278
382
|
stopPhraseRotation();
|
|
@@ -315,32 +419,45 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
315
419
|
onTextChunk: (text) => {
|
|
316
420
|
if (stopped)
|
|
317
421
|
return;
|
|
318
|
-
|
|
422
|
+
// Don't stop phrase rotation here — let it continue until first content
|
|
423
|
+
// emit (handled in flushTextBuffer when MIN_INITIAL_CHARS threshold met).
|
|
319
424
|
textBuffer += text;
|
|
320
425
|
startFlushTimer();
|
|
321
426
|
},
|
|
322
427
|
onClearText: () => {
|
|
323
428
|
textBuffer = "";
|
|
324
429
|
},
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
})(),
|
|
344
461
|
onKick: () => {
|
|
345
462
|
stopPhraseRotation();
|
|
346
463
|
const msg = (0, format_1.formatKick)();
|
|
@@ -350,7 +467,11 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
350
467
|
stopPhraseRotation();
|
|
351
468
|
if (stopped)
|
|
352
469
|
return;
|
|
353
|
-
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
|
+
}));
|
|
354
475
|
if (severity === "transient") {
|
|
355
476
|
safeUpdate(msg);
|
|
356
477
|
}
|
|
@@ -358,38 +479,27 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
358
479
|
safeSend(msg);
|
|
359
480
|
}
|
|
360
481
|
},
|
|
361
|
-
onConfirmAction: options?.conversationId
|
|
362
|
-
? async (name, args) => {
|
|
363
|
-
const convId = options.conversationId;
|
|
364
|
-
const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
365
|
-
safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
|
|
366
|
-
return new Promise((resolve) => {
|
|
367
|
-
_pendingConfirmations.set(convId, resolve);
|
|
368
|
-
// Auto-deny after 2 minutes to prevent indefinite blocking
|
|
369
|
-
// (e.g. when the stream dies and the user never sees the prompt).
|
|
370
|
-
setTimeout(() => {
|
|
371
|
-
if (_pendingConfirmations.has(convId)) {
|
|
372
|
-
_pendingConfirmations.delete(convId);
|
|
373
|
-
resolve("denied");
|
|
374
|
-
}
|
|
375
|
-
}, 120_000);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
: undefined,
|
|
379
482
|
flush: async () => {
|
|
380
483
|
stopFlushTimer();
|
|
484
|
+
stopPhraseRotation();
|
|
381
485
|
if (textBuffer) {
|
|
486
|
+
// Bypass MIN_INITIAL_CHARS threshold — flush delivers all remaining content
|
|
487
|
+
firstContentEmitted = true;
|
|
382
488
|
const text = textBuffer;
|
|
383
489
|
textBuffer = "";
|
|
384
|
-
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) {
|
|
385
495
|
// Stream is alive — await the emit so we can catch async 413/failure
|
|
386
496
|
// and fall through to sendMessage recovery.
|
|
387
497
|
const ok = await tryEmit(text);
|
|
388
498
|
if (!ok)
|
|
389
499
|
markStopped();
|
|
390
500
|
}
|
|
391
|
-
if (stopped && sendMessage) {
|
|
392
|
-
// 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.
|
|
393
503
|
try {
|
|
394
504
|
await sendMessage(text);
|
|
395
505
|
}
|
|
@@ -400,32 +510,12 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
400
510
|
}
|
|
401
511
|
}
|
|
402
512
|
}
|
|
403
|
-
else if (!streamHasContent) {
|
|
404
|
-
safeEmit("(completed with tool calls only
|
|
513
|
+
else if (!streamHasContent && !options?.suppressEmptyStreamMessage) {
|
|
514
|
+
safeEmit("(completed with tool calls only — no text response)");
|
|
405
515
|
}
|
|
406
516
|
},
|
|
407
517
|
};
|
|
408
518
|
}
|
|
409
|
-
// Per-conversation pending confirmation resolvers.
|
|
410
|
-
// When a mutate tool needs confirmation, the resolver is stored here.
|
|
411
|
-
// The next message from the same conversation resolves it.
|
|
412
|
-
const _pendingConfirmations = new Map();
|
|
413
|
-
// Confirmation response words (case-insensitive)
|
|
414
|
-
const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
|
|
415
|
-
function resolvePendingConfirmation(convId, text) {
|
|
416
|
-
const resolver = _pendingConfirmations.get(convId);
|
|
417
|
-
if (!resolver)
|
|
418
|
-
return false;
|
|
419
|
-
_pendingConfirmations.delete(convId);
|
|
420
|
-
const word = text.trim().toLowerCase();
|
|
421
|
-
if (CONFIRM_WORDS.has(word)) {
|
|
422
|
-
resolver("confirmed");
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
resolver("denied");
|
|
426
|
-
}
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
519
|
const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
|
|
430
520
|
function teamsTurnKey(conversationId) {
|
|
431
521
|
return `teams:${conversationId}`;
|
|
@@ -439,15 +529,48 @@ function getFriendStore() {
|
|
|
439
529
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
440
530
|
return new store_file_1.FileFriendStore(friendsPath);
|
|
441
531
|
}
|
|
532
|
+
function createTeamsCommandRegistry() {
|
|
533
|
+
const registry = (0, commands_1.createCommandRegistry)();
|
|
534
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
535
|
+
return registry;
|
|
536
|
+
}
|
|
537
|
+
/* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
|
|
538
|
+
function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
|
|
539
|
+
const parsed = (0, commands_1.parseSlashCommand)(text);
|
|
540
|
+
if (!parsed)
|
|
541
|
+
return null;
|
|
542
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "teams" });
|
|
543
|
+
if (!dispatchResult.handled || !dispatchResult.result) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
if (dispatchResult.result.action === "new") {
|
|
547
|
+
(0, context_1.deleteSession)((0, config_2.sessionPath)(friendId, "teams", conversationId));
|
|
548
|
+
if (emitResponse) {
|
|
549
|
+
stream.emit("session cleared");
|
|
550
|
+
}
|
|
551
|
+
return "new";
|
|
552
|
+
}
|
|
553
|
+
if (dispatchResult.result.action === "response") {
|
|
554
|
+
if (emitResponse) {
|
|
555
|
+
stream.emit(dispatchResult.result.message || "");
|
|
556
|
+
}
|
|
557
|
+
return "response";
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
/* v8 ignore stop */
|
|
442
562
|
// Handle an incoming Teams message
|
|
443
|
-
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
|
|
563
|
+
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides) {
|
|
444
564
|
const turnKey = teamsTurnKey(conversationId);
|
|
445
565
|
// NOTE: Confirmation resolution is handled in the app.on("message") handler
|
|
446
566
|
// BEFORE the conversation lock. By the time we get here, any pending
|
|
447
567
|
// confirmation has already been resolved and the reply consumed.
|
|
448
568
|
// Send first thinking phrase immediately so the user sees feedback
|
|
449
569
|
// before sync I/O (session load, trim) blocks the event loop.
|
|
450
|
-
|
|
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
|
+
}
|
|
451
574
|
await new Promise(r => setImmediate(r));
|
|
452
575
|
// Resolve identity provider early for friend resolution + slash command session path
|
|
453
576
|
const store = getFriendStore();
|
|
@@ -464,29 +587,10 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
464
587
|
// Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
|
|
465
588
|
const resolvedContext = await resolver.resolve();
|
|
466
589
|
const friendId = resolvedContext.friend.id;
|
|
467
|
-
const registry = (0, commands_1.createCommandRegistry)();
|
|
468
|
-
(0, commands_1.registerDefaultCommands)(registry);
|
|
469
|
-
// Check for slash commands (before pipeline -- these are transport-level concerns)
|
|
470
|
-
const parsed = (0, commands_1.parseSlashCommand)(text);
|
|
471
|
-
if (parsed) {
|
|
472
|
-
const dispatchResult = registry.dispatch(parsed.command, { channel: "teams" });
|
|
473
|
-
if (dispatchResult.handled && dispatchResult.result) {
|
|
474
|
-
if (dispatchResult.result.action === "new") {
|
|
475
|
-
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
476
|
-
(0, context_1.deleteSession)(sessPath);
|
|
477
|
-
stream.emit("session cleared");
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
else if (dispatchResult.result.action === "response") {
|
|
481
|
-
stream.emit(dispatchResult.result.message || "");
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
590
|
// ── Teams adapter concerns: controller, callbacks, session path ──────────
|
|
487
591
|
const controller = new AbortController();
|
|
488
592
|
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
489
|
-
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 } : {}) });
|
|
490
594
|
const traceId = (0, nerves_1.createTraceId)();
|
|
491
595
|
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
492
596
|
const teamsCapabilities = (0, channel_1.getChannelCapabilities)("teams");
|
|
@@ -497,80 +601,161 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
497
601
|
adoToken: teamsContext.adoToken,
|
|
498
602
|
githubToken: teamsContext.githubToken,
|
|
499
603
|
signin: teamsContext.signin,
|
|
500
|
-
summarize: (0, core_1.createSummarize)(),
|
|
604
|
+
summarize: (0, core_1.createSummarize)("human"),
|
|
501
605
|
tenantId: teamsContext.tenantId,
|
|
502
606
|
botApi: teamsContext.botApi,
|
|
503
607
|
} : {};
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
callbacks,
|
|
518
|
-
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
519
|
-
sessionLoader: {
|
|
520
|
-
loadOrCreate: async () => {
|
|
521
|
-
const existing = (0, context_1.loadSession)(sessPath);
|
|
522
|
-
const messages = existing?.messages && existing.messages.length > 0
|
|
523
|
-
? existing.messages
|
|
524
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", undefined, resolvedContext) }];
|
|
525
|
-
(0, core_1.repairOrphanedToolCalls)(messages);
|
|
526
|
-
return { messages, sessionPath: sessPath };
|
|
608
|
+
let currentText = text;
|
|
609
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
610
|
+
while (true) {
|
|
611
|
+
let drainedSteeringFollowUps = [];
|
|
612
|
+
// Build runAgentOptions with Teams-specific fields
|
|
613
|
+
const agentOptions = {
|
|
614
|
+
traceId,
|
|
615
|
+
toolContext: teamsToolContext,
|
|
616
|
+
mcpManager,
|
|
617
|
+
drainSteeringFollowUps: () => {
|
|
618
|
+
drainedSteeringFollowUps = _turnCoordinator.drainFollowUps(turnKey)
|
|
619
|
+
.map(({ text: followUpText, effect }) => ({ text: followUpText, effect }));
|
|
620
|
+
return drainedSteeringFollowUps;
|
|
527
621
|
},
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
622
|
+
...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
|
|
623
|
+
};
|
|
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);
|
|
546
642
|
},
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
643
|
+
};
|
|
644
|
+
/* v8 ignore stop */
|
|
645
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
646
|
+
channel: "teams",
|
|
647
|
+
sessionKey: conversationId,
|
|
648
|
+
capabilities: teamsCapabilities,
|
|
649
|
+
messages: [{ role: "user", content: currentText }],
|
|
650
|
+
continuityIngressTexts: [currentText],
|
|
651
|
+
callbacks: failoverAwareCallbacks,
|
|
652
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
653
|
+
sessionLoader: {
|
|
654
|
+
loadOrCreate: async () => {
|
|
655
|
+
const existing = (0, context_1.loadSession)(sessPath);
|
|
656
|
+
const messages = existing?.messages && existing.messages.length > 0
|
|
657
|
+
? existing.messages
|
|
658
|
+
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("teams", {}, resolvedContext)) }];
|
|
659
|
+
(0, core_1.repairOrphanedToolCalls)(messages);
|
|
660
|
+
return {
|
|
661
|
+
messages,
|
|
662
|
+
sessionPath: sessPath,
|
|
663
|
+
state: existing?.state,
|
|
664
|
+
events: existing?.events,
|
|
665
|
+
};
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
pendingDir,
|
|
669
|
+
friendStore: store,
|
|
670
|
+
provider,
|
|
671
|
+
externalId,
|
|
672
|
+
tenantId: teamsContext?.tenantId,
|
|
673
|
+
isGroupChat: false,
|
|
674
|
+
groupHasFamilyMember: false,
|
|
675
|
+
hasExistingGroupWithFamily: false,
|
|
676
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
677
|
+
drainPending: pending_1.drainPending,
|
|
678
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)((0, identity_1.getAgentName)(), deferredFriendId),
|
|
679
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
680
|
+
...opts,
|
|
681
|
+
toolContext: {
|
|
682
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
683
|
+
signin: async () => undefined,
|
|
684
|
+
...opts?.toolContext,
|
|
685
|
+
summarize: teamsToolContext.summarize,
|
|
686
|
+
},
|
|
687
|
+
}),
|
|
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
|
+
},
|
|
692
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
693
|
+
signal: controller.signal,
|
|
694
|
+
runAgentOptions: agentOptions,
|
|
695
|
+
failoverState: teamsFailoverState,
|
|
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 */
|
|
715
|
+
// ── Handle gate result ────────────────────────────────────────
|
|
716
|
+
if (!result.gateResult.allowed) {
|
|
717
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
718
|
+
stream.emit(result.gateResult.autoReply);
|
|
719
|
+
}
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Flush any remaining accumulated text at end of turn
|
|
723
|
+
await callbacks.flush();
|
|
724
|
+
// After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
|
|
725
|
+
// This must happen after the stream is done so the OAuth card renders properly.
|
|
726
|
+
if (teamsContext && result.messages) {
|
|
727
|
+
const allContent = result.messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
|
|
728
|
+
if (allContent.includes("AUTH_REQUIRED:graph") && teamsContext.graphConnectionName)
|
|
729
|
+
await teamsContext.signin(teamsContext.graphConnectionName);
|
|
730
|
+
if (allContent.includes("AUTH_REQUIRED:ado") && teamsContext.adoConnectionName)
|
|
731
|
+
await teamsContext.signin(teamsContext.adoConnectionName);
|
|
732
|
+
if (allContent.includes("AUTH_REQUIRED:github") && teamsContext.githubConnectionName)
|
|
733
|
+
await teamsContext.signin(teamsContext.githubConnectionName);
|
|
734
|
+
}
|
|
735
|
+
if (result.turnOutcome !== "superseded") {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const supersedingIndex = drainedSteeringFollowUps
|
|
739
|
+
.map((followUp) => followUp.effect)
|
|
740
|
+
.lastIndexOf("clear_and_supersede");
|
|
741
|
+
if (supersedingIndex < 0) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const supersedingFollowUp = drainedSteeringFollowUps[supersedingIndex];
|
|
745
|
+
const replayTail = drainedSteeringFollowUps
|
|
746
|
+
.slice(supersedingIndex + 1)
|
|
747
|
+
.map((followUp) => followUp.text.trim())
|
|
748
|
+
.filter((followUpText) => followUpText.length > 0)
|
|
749
|
+
.join("\n");
|
|
750
|
+
if (replayTail) {
|
|
751
|
+
currentText = replayTail;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
currentText = supersedingFollowUp.text;
|
|
758
|
+
}
|
|
574
759
|
}
|
|
575
760
|
// Internal port for the secondary bot App (not exposed externally).
|
|
576
761
|
// The primary app proxies /api/messages-secondary → localhost:SECONDARY_PORT/api/messages.
|
|
@@ -646,7 +831,7 @@ function registerBotHandlers(app, label) {
|
|
|
646
831
|
// (graph + ado + github). The verifyState activity only carries a `state`
|
|
647
832
|
// code with no connectionName, so we try each configured connection until
|
|
648
833
|
// one succeeds.
|
|
649
|
-
app.on("signin.verify-state", async (ctx) => {
|
|
834
|
+
app.on("signin.verify-state", (async (ctx) => {
|
|
650
835
|
const { api, activity } = ctx;
|
|
651
836
|
if (!activity.value?.state)
|
|
652
837
|
return { status: 404 };
|
|
@@ -665,7 +850,73 @@ function registerBotHandlers(app, label) {
|
|
|
665
850
|
}
|
|
666
851
|
(0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: `[${label}] verify-state failed for all connections`, meta: {} });
|
|
667
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
|
+
}
|
|
668
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 */
|
|
669
920
|
app.on("message", async (ctx) => {
|
|
670
921
|
const { stream, activity, api, signin } = ctx;
|
|
671
922
|
const text = activity.text || "";
|
|
@@ -674,12 +925,40 @@ function registerBotHandlers(app, label) {
|
|
|
674
925
|
const userId = activity.from?.id || "";
|
|
675
926
|
const channelId = activity.channelId || "msteams";
|
|
676
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) } });
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
928
|
+
const commandRegistry = createTeamsCommandRegistry();
|
|
929
|
+
const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
|
|
930
|
+
if (parsedSlashCommand) {
|
|
931
|
+
const dispatchResult = commandRegistry.dispatch(parsedSlashCommand.command, { channel: "teams" });
|
|
932
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
933
|
+
if (dispatchResult.result.action === "response") {
|
|
934
|
+
stream.emit(dispatchResult.result.message || "");
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (dispatchResult.result.action === "new") {
|
|
938
|
+
const commandStore = getFriendStore();
|
|
939
|
+
const commandProvider = activity.from?.aadObjectId ? "aad" : "teams-conversation";
|
|
940
|
+
const commandExternalId = activity.from?.aadObjectId || convId;
|
|
941
|
+
const commandResolver = new resolver_1.FriendResolver(commandStore, {
|
|
942
|
+
provider: commandProvider,
|
|
943
|
+
externalId: commandExternalId,
|
|
944
|
+
tenantId: activity.conversation?.tenantId,
|
|
945
|
+
displayName: activity.from?.name || "Unknown",
|
|
946
|
+
channel: "teams",
|
|
947
|
+
});
|
|
948
|
+
const commandContext = await commandResolver.resolve();
|
|
949
|
+
(0, context_1.deleteSession)((0, config_2.sessionPath)(commandContext.friend.id, "teams", convId));
|
|
950
|
+
stream.emit("session cleared");
|
|
951
|
+
if (_turnCoordinator.isTurnActive(turnKey)) {
|
|
952
|
+
_turnCoordinator.enqueueFollowUp(turnKey, {
|
|
953
|
+
conversationId: convId,
|
|
954
|
+
text,
|
|
955
|
+
receivedAt: Date.now(),
|
|
956
|
+
effect: "clear_and_supersede",
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
683
962
|
}
|
|
684
963
|
// If this conversation already has an active turn, steer follow-up input
|
|
685
964
|
// into that turn and avoid starting a second concurrent turn.
|
|
@@ -688,6 +967,7 @@ function registerBotHandlers(app, label) {
|
|
|
688
967
|
conversationId: convId,
|
|
689
968
|
text,
|
|
690
969
|
receivedAt: Date.now(),
|
|
970
|
+
effect: (0, continuity_1.classifySteeringFollowUpEffect)(text),
|
|
691
971
|
});
|
|
692
972
|
return;
|
|
693
973
|
}
|
|
@@ -745,7 +1025,7 @@ function registerBotHandlers(app, label) {
|
|
|
745
1025
|
const ctxSend = async (t) => {
|
|
746
1026
|
// Use send with replyToId (not reply, which adds a blockquote).
|
|
747
1027
|
// replyToId anchors the message after the user's message in Copilot Chat.
|
|
748
|
-
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 } });
|
|
749
1029
|
};
|
|
750
1030
|
await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
|
|
751
1031
|
}
|
|
@@ -770,6 +1050,108 @@ function findAadObjectId(friend) {
|
|
|
770
1050
|
}
|
|
771
1051
|
return undefined;
|
|
772
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
|
+
}
|
|
773
1155
|
function scanPendingTeamsFiles(pendingRoot) {
|
|
774
1156
|
const results = [];
|
|
775
1157
|
let friendIds;
|
|
@@ -815,8 +1197,7 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
|
|
|
815
1197
|
const root = pendingRoot ?? path.join((0, identity_1.getAgentRoot)(), "state", "pending");
|
|
816
1198
|
const pendingFiles = scanPendingTeamsFiles(root);
|
|
817
1199
|
const result = { sent: 0, skipped: 0, failed: 0 };
|
|
818
|
-
const
|
|
819
|
-
for (const { friendId, filePath, content } of pendingFiles) {
|
|
1200
|
+
for (const { friendId, key, filePath, content } of pendingFiles) {
|
|
820
1201
|
let parsed;
|
|
821
1202
|
try {
|
|
822
1203
|
parsed = JSON.parse(content);
|
|
@@ -838,95 +1219,46 @@ async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
|
|
|
838
1219
|
catch { /* ignore */ }
|
|
839
1220
|
continue;
|
|
840
1221
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
friend = await store.get(friendId);
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
friend = null;
|
|
847
|
-
}
|
|
848
|
-
if (!friend) {
|
|
1222
|
+
const internalBlockReason = (0, proactive_content_guard_1.getProactiveInternalContentBlockReason)(messageText);
|
|
1223
|
+
if (internalBlockReason) {
|
|
849
1224
|
result.skipped++;
|
|
850
1225
|
try {
|
|
851
1226
|
fs.unlinkSync(filePath);
|
|
852
1227
|
}
|
|
853
1228
|
catch { /* ignore */ }
|
|
854
|
-
(0,
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
message: "proactive send skipped: friend not found",
|
|
859
|
-
meta: { friendId },
|
|
1229
|
+
(0, proactive_content_guard_1.emitProactiveInternalContentBlocked)({
|
|
1230
|
+
friendId,
|
|
1231
|
+
reason: internalBlockReason,
|
|
1232
|
+
source: "pending_drain",
|
|
860
1233
|
});
|
|
861
1234
|
continue;
|
|
862
1235
|
}
|
|
863
|
-
|
|
864
|
-
|
|
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++;
|
|
865
1247
|
try {
|
|
866
1248
|
fs.unlinkSync(filePath);
|
|
867
1249
|
}
|
|
868
1250
|
catch { /* ignore */ }
|
|
869
|
-
(0, runtime_1.emitNervesEvent)({
|
|
870
|
-
component: "senses",
|
|
871
|
-
event: "senses.teams_proactive_trust_skip",
|
|
872
|
-
message: "proactive send skipped: trust level not allowed",
|
|
873
|
-
meta: { friendId, trustLevel: friend.trustLevel ?? "unknown" },
|
|
874
|
-
});
|
|
875
1251
|
continue;
|
|
876
1252
|
}
|
|
877
|
-
|
|
878
|
-
if (!aadInfo) {
|
|
1253
|
+
if (sendResult.reason === "friend_not_found" || sendResult.reason === "trust_skip" || sendResult.reason === "missing_target") {
|
|
879
1254
|
result.skipped++;
|
|
880
1255
|
try {
|
|
881
1256
|
fs.unlinkSync(filePath);
|
|
882
1257
|
}
|
|
883
1258
|
catch { /* ignore */ }
|
|
884
|
-
(0, runtime_1.emitNervesEvent)({
|
|
885
|
-
level: "warn",
|
|
886
|
-
component: "senses",
|
|
887
|
-
event: "senses.teams_proactive_no_aad_id",
|
|
888
|
-
message: "proactive send skipped: no AAD object ID found",
|
|
889
|
-
meta: { friendId },
|
|
890
|
-
});
|
|
891
1259
|
continue;
|
|
892
1260
|
}
|
|
893
|
-
|
|
894
|
-
const conversation = await conversations.create({
|
|
895
|
-
bot: { id: botApi.id },
|
|
896
|
-
members: [{ id: aadInfo.aadObjectId, role: "user", name: friend.name || aadInfo.aadObjectId }],
|
|
897
|
-
tenantId: aadInfo.tenantId,
|
|
898
|
-
isGroup: false,
|
|
899
|
-
});
|
|
900
|
-
await conversations.activities(conversation.id).create({
|
|
901
|
-
type: "message",
|
|
902
|
-
text: messageText,
|
|
903
|
-
});
|
|
904
|
-
result.sent++;
|
|
905
|
-
try {
|
|
906
|
-
fs.unlinkSync(filePath);
|
|
907
|
-
}
|
|
908
|
-
catch { /* ignore */ }
|
|
909
|
-
(0, runtime_1.emitNervesEvent)({
|
|
910
|
-
component: "senses",
|
|
911
|
-
event: "senses.teams_proactive_sent",
|
|
912
|
-
message: "proactive teams message sent",
|
|
913
|
-
meta: { friendId, aadObjectId: aadInfo.aadObjectId },
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
catch (error) {
|
|
917
|
-
result.failed++;
|
|
918
|
-
(0, runtime_1.emitNervesEvent)({
|
|
919
|
-
level: "error",
|
|
920
|
-
component: "senses",
|
|
921
|
-
event: "senses.teams_proactive_send_error",
|
|
922
|
-
message: "proactive teams send failed",
|
|
923
|
-
meta: {
|
|
924
|
-
friendId,
|
|
925
|
-
aadObjectId: aadInfo.aadObjectId,
|
|
926
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
927
|
-
},
|
|
928
|
-
});
|
|
929
|
-
}
|
|
1261
|
+
result.failed++;
|
|
930
1262
|
}
|
|
931
1263
|
if (result.sent > 0 || result.skipped > 0 || result.failed > 0) {
|
|
932
1264
|
(0, runtime_1.emitNervesEvent)({
|