@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.2
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/AdoptionSpecialist.ouro/agent.json +20 -0
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
- package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
- package/README.md +224 -6
- package/dist/heart/agent-entry.js +17 -0
- package/dist/heart/api-error.js +34 -0
- package/dist/heart/config.js +296 -0
- package/dist/heart/core.js +515 -0
- package/dist/heart/daemon/daemon-cli.js +675 -0
- package/dist/heart/daemon/daemon-entry.js +74 -0
- package/dist/heart/daemon/daemon.js +313 -0
- package/dist/heart/daemon/hatch-flow.js +285 -0
- package/dist/heart/daemon/hatch-specialist.js +107 -0
- package/dist/heart/daemon/health-monitor.js +79 -0
- package/dist/heart/daemon/log-tailer.js +146 -0
- package/dist/heart/daemon/message-router.js +98 -0
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-entry.js +23 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
- package/dist/heart/daemon/ouro-entry.js +23 -0
- package/dist/heart/daemon/ouro-uti.js +212 -0
- package/dist/heart/daemon/process-manager.js +237 -0
- package/dist/heart/daemon/runtime-logging.js +98 -0
- package/dist/heart/daemon/subagent-installer.js +125 -0
- package/dist/heart/daemon/task-scheduler.js +240 -0
- package/dist/heart/harness.js +26 -0
- package/dist/heart/identity.js +281 -0
- package/dist/heart/kicks.js +144 -0
- package/dist/heart/primitives.js +4 -0
- package/dist/heart/providers/anthropic.js +329 -0
- package/dist/heart/providers/azure.js +66 -0
- package/dist/heart/providers/minimax.js +53 -0
- package/dist/heart/providers/openai-codex.js +162 -0
- package/dist/heart/streaming.js +412 -0
- package/dist/heart/turn-coordinator.js +62 -0
- package/dist/inner-worker-entry.js +4 -0
- package/dist/mind/associative-recall.js +197 -0
- package/dist/mind/bundle-manifest.js +118 -0
- package/dist/mind/context.js +302 -0
- package/dist/mind/first-impressions.js +43 -0
- package/dist/mind/format.js +56 -0
- package/dist/mind/friends/channel.js +41 -0
- package/dist/mind/friends/resolver.js +84 -0
- package/dist/mind/friends/store-file.js +171 -0
- package/dist/mind/friends/store.js +4 -0
- package/dist/mind/friends/tokens.js +26 -0
- package/dist/mind/friends/types.js +21 -0
- package/dist/mind/memory.js +388 -0
- package/dist/mind/pending.js +93 -0
- package/dist/mind/phrases.js +43 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +352 -0
- package/dist/mind/token-estimate.js +119 -0
- package/dist/nerves/cli-logging.js +31 -0
- package/dist/nerves/coverage/audit-rules.js +81 -0
- package/dist/nerves/coverage/audit.js +200 -0
- package/dist/nerves/coverage/cli-main.js +5 -0
- package/dist/nerves/coverage/cli.js +51 -0
- package/dist/nerves/coverage/contract.js +23 -0
- package/dist/nerves/coverage/file-completeness.js +56 -0
- package/dist/nerves/coverage/run-artifacts.js +77 -0
- package/dist/nerves/coverage/source-scanner.js +34 -0
- package/dist/nerves/index.js +152 -0
- package/dist/nerves/runtime.js +38 -0
- package/dist/repertoire/ado-client.js +211 -0
- package/dist/repertoire/ado-context.js +73 -0
- package/dist/repertoire/ado-semantic.js +841 -0
- package/dist/repertoire/ado-templates.js +146 -0
- package/dist/repertoire/coding/index.js +36 -0
- package/dist/repertoire/coding/manager.js +489 -0
- package/dist/repertoire/coding/monitor.js +60 -0
- package/dist/repertoire/coding/reporter.js +45 -0
- package/dist/repertoire/coding/spawner.js +102 -0
- package/dist/repertoire/coding/tools.js +167 -0
- package/dist/repertoire/coding/types.js +2 -0
- package/dist/repertoire/data/ado-endpoints.json +122 -0
- package/dist/repertoire/data/graph-endpoints.json +212 -0
- package/dist/repertoire/github-client.js +64 -0
- package/dist/repertoire/graph-client.js +118 -0
- package/dist/repertoire/skills.js +156 -0
- package/dist/repertoire/tasks/board.js +122 -0
- package/dist/repertoire/tasks/index.js +210 -0
- package/dist/repertoire/tasks/lifecycle.js +80 -0
- package/dist/repertoire/tasks/middleware.js +65 -0
- package/dist/repertoire/tasks/parser.js +173 -0
- package/dist/repertoire/tasks/scanner.js +132 -0
- package/dist/repertoire/tasks/transitions.js +145 -0
- package/dist/repertoire/tasks/types.js +2 -0
- package/dist/repertoire/tools-base.js +714 -0
- package/dist/repertoire/tools-github.js +53 -0
- package/dist/repertoire/tools-teams.js +308 -0
- package/dist/repertoire/tools.js +199 -0
- package/dist/senses/cli-entry.js +15 -0
- package/dist/senses/cli.js +604 -0
- package/dist/senses/commands.js +98 -0
- package/dist/senses/inner-dialog-worker.js +61 -0
- package/dist/senses/inner-dialog.js +231 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams-entry.js +15 -0
- package/dist/senses/teams.js +696 -0
- package/dist/senses/trust-gate.js +150 -0
- package/package.json +34 -11
- package/subagents/README.md +73 -0
- package/subagents/work-doer.md +233 -0
- package/subagents/work-merger.md +624 -0
- package/subagents/work-planner.md +373 -0
- package/bin/ouro.js +0 -6
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DEFAULT_FLUSH_INTERVAL_MS = void 0;
|
|
37
|
+
exports.stripMentions = stripMentions;
|
|
38
|
+
exports.splitMessage = splitMessage;
|
|
39
|
+
exports.createTeamsCallbacks = createTeamsCallbacks;
|
|
40
|
+
exports.resolvePendingConfirmation = resolvePendingConfirmation;
|
|
41
|
+
exports.withConversationLock = withConversationLock;
|
|
42
|
+
exports.handleTeamsMessage = handleTeamsMessage;
|
|
43
|
+
exports.startTeamsApp = startTeamsApp;
|
|
44
|
+
const teams_apps_1 = require("@microsoft/teams.apps");
|
|
45
|
+
const teams_dev_1 = require("@microsoft/teams.dev");
|
|
46
|
+
const core_1 = require("../heart/core");
|
|
47
|
+
const config_1 = require("../heart/config");
|
|
48
|
+
const prompt_1 = require("../mind/prompt");
|
|
49
|
+
const phrases_1 = require("../mind/phrases");
|
|
50
|
+
const format_1 = require("../mind/format");
|
|
51
|
+
const config_2 = require("../heart/config");
|
|
52
|
+
const context_1 = require("../mind/context");
|
|
53
|
+
const commands_1 = require("./commands");
|
|
54
|
+
const nerves_1 = require("../nerves");
|
|
55
|
+
const runtime_1 = require("../nerves/runtime");
|
|
56
|
+
const store_file_1 = require("../mind/friends/store-file");
|
|
57
|
+
const resolver_1 = require("../mind/friends/resolver");
|
|
58
|
+
const tokens_1 = require("../mind/friends/tokens");
|
|
59
|
+
const turn_coordinator_1 = require("../heart/turn-coordinator");
|
|
60
|
+
const identity_1 = require("../heart/identity");
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const trust_gate_1 = require("./trust-gate");
|
|
63
|
+
// Strip @mention markup from incoming messages.
|
|
64
|
+
// Removes <at>...</at> tags and trims extra whitespace.
|
|
65
|
+
// Fallback safety net -- the SDK's activity.mentions.stripText should handle
|
|
66
|
+
// this automatically, but this utility is exported for testability.
|
|
67
|
+
function stripMentions(text) {
|
|
68
|
+
if (!text)
|
|
69
|
+
return "";
|
|
70
|
+
return text.replace(/<at>[^<]*<\/at>/g, "").trim();
|
|
71
|
+
}
|
|
72
|
+
// Recovery chunk size for error-recovery splitting. When a full-text send fails
|
|
73
|
+
// (e.g. 413 from Teams), we split into chunks of this size and retry.
|
|
74
|
+
// Not used preemptively — the harness tries to send the full message first.
|
|
75
|
+
const RECOVERY_CHUNK_SIZE = 4000;
|
|
76
|
+
// Default interval (ms) for the periodic flush timer in chunked streaming mode.
|
|
77
|
+
// Text is accumulated in textBuffer and flushed via safeEmit/safeSend at this
|
|
78
|
+
// interval. This replaces per-token streaming, which caused compounding latency:
|
|
79
|
+
//
|
|
80
|
+
// - Teams throttles streaming updates to 1 req/sec with exponential backoff
|
|
81
|
+
// https://learn.microsoft.com/en-us/microsoftteams/platform/bots/streaming-ux
|
|
82
|
+
// - SDK debounces at 500ms internally and re-sends ALL cumulative text each chunk
|
|
83
|
+
// - Per-token streaming generates 100+ HTTP POSTs per response, each throttled
|
|
84
|
+
// - Copilot enforces a 15s timeout for the initial stream.emit()
|
|
85
|
+
// https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
|
|
86
|
+
//
|
|
87
|
+
// At 1000ms (1 req/sec), we stay at the Teams throttle floor while keeping the
|
|
88
|
+
// stream alive well within the 15s Copilot timeout. Tune up if 429s observed.
|
|
89
|
+
exports.DEFAULT_FLUSH_INTERVAL_MS = 1_000;
|
|
90
|
+
// Split text into chunks that fit within maxLen, breaking at paragraph
|
|
91
|
+
// boundaries (\n\n), then line boundaries (\n), then word boundaries.
|
|
92
|
+
// Never loses content — all text is preserved across chunks.
|
|
93
|
+
function splitMessage(text, maxLen) {
|
|
94
|
+
if (text.length <= maxLen)
|
|
95
|
+
return [text];
|
|
96
|
+
const chunks = [];
|
|
97
|
+
let remaining = text;
|
|
98
|
+
while (remaining.length > 0) {
|
|
99
|
+
if (remaining.length <= maxLen) {
|
|
100
|
+
chunks.push(remaining);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
// Find best split point: paragraph > line > word > hard cut
|
|
104
|
+
const slice = remaining.slice(0, maxLen);
|
|
105
|
+
let splitAt = slice.lastIndexOf("\n\n");
|
|
106
|
+
if (splitAt <= 0)
|
|
107
|
+
splitAt = slice.lastIndexOf("\n");
|
|
108
|
+
if (splitAt <= 0)
|
|
109
|
+
splitAt = slice.lastIndexOf(" ");
|
|
110
|
+
if (splitAt <= 0)
|
|
111
|
+
splitAt = maxLen; // hard cut as last resort
|
|
112
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
113
|
+
remaining = remaining.slice(splitAt).replace(/^[\n ]+/, ""); // trim leading whitespace from next chunk
|
|
114
|
+
}
|
|
115
|
+
return chunks;
|
|
116
|
+
}
|
|
117
|
+
// Create Teams-specific callbacks for the agent loop.
|
|
118
|
+
// The SDK handles cumulative text, debouncing (500ms), and the streaming
|
|
119
|
+
// protocol (streamSequence, streamId, informative/streaming/final types).
|
|
120
|
+
//
|
|
121
|
+
// Chunked streaming (unified mode):
|
|
122
|
+
// Text is always accumulated in textBuffer and flushed periodically (via a
|
|
123
|
+
// flush timer started on first onTextChunk) or at end-of-turn via flush().
|
|
124
|
+
// First flush goes to safeEmit (primary output), subsequent flushes go to
|
|
125
|
+
// safeSend (ctx.send). Tool results, kicks, and errors use safeUpdate
|
|
126
|
+
// (transient status) or safeSend (terminal errors). Reasoning is accumulated
|
|
127
|
+
// and periodically pushed via safeUpdate on the same flush timer tick.
|
|
128
|
+
function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
129
|
+
let stopped = false; // set when stream signals cancellation (403)
|
|
130
|
+
let hadToolRun = false;
|
|
131
|
+
let hadRealOutput = false; // true once reasoning/tool output shown; suppresses phrases
|
|
132
|
+
let reasoningBuf = ""; // accumulated reasoning text for status display
|
|
133
|
+
let textBuffer = ""; // accumulated text output for chunked streaming
|
|
134
|
+
let streamHasContent = false; // tracks whether primary output has received content
|
|
135
|
+
let phraseTimer = null;
|
|
136
|
+
let lastPhrase = "";
|
|
137
|
+
let flushTimer = null;
|
|
138
|
+
const flushInterval = options?.flushIntervalMs ?? exports.DEFAULT_FLUSH_INTERVAL_MS;
|
|
139
|
+
// Track whether reasoning has changed since last flush (avoid redundant updates).
|
|
140
|
+
let lastFlushedReasoningLen = 0;
|
|
141
|
+
// Periodic tick: flush text buffer and push reasoning updates.
|
|
142
|
+
function flushTick() {
|
|
143
|
+
flushTextBuffer();
|
|
144
|
+
if (reasoningBuf.length > lastFlushedReasoningLen) {
|
|
145
|
+
safeUpdate(reasoningBuf);
|
|
146
|
+
lastFlushedReasoningLen = reasoningBuf.length;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Start the periodic flush timer. Idempotent -- no-op if already running.
|
|
150
|
+
function startFlushTimer() {
|
|
151
|
+
if (flushTimer)
|
|
152
|
+
return;
|
|
153
|
+
flushTimer = setInterval(flushTick, flushInterval);
|
|
154
|
+
}
|
|
155
|
+
// Stop the periodic flush timer. Idempotent.
|
|
156
|
+
function stopFlushTimer() {
|
|
157
|
+
if (flushTimer) {
|
|
158
|
+
clearInterval(flushTimer);
|
|
159
|
+
flushTimer = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Mark stream as broken and abort the agent loop.
|
|
163
|
+
function markStopped() {
|
|
164
|
+
if (stopped)
|
|
165
|
+
return;
|
|
166
|
+
stopped = true;
|
|
167
|
+
stopPhraseRotation();
|
|
168
|
+
stopFlushTimer();
|
|
169
|
+
controller.abort();
|
|
170
|
+
}
|
|
171
|
+
// Clean up timers when the controller is aborted externally
|
|
172
|
+
controller.signal.addEventListener("abort", () => {
|
|
173
|
+
stopPhraseRotation();
|
|
174
|
+
stopFlushTimer();
|
|
175
|
+
});
|
|
176
|
+
// Handle the result of a stream call: if it returns a Promise (the Teams SDK
|
|
177
|
+
// does async HTTP under the hood even though the interface types it as void),
|
|
178
|
+
// catch its rejection so we detect a dead stream and abort the agent loop.
|
|
179
|
+
function catchAsync(result) {
|
|
180
|
+
if (result && typeof result.catch === "function") {
|
|
181
|
+
result.catch(() => markStopped());
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Safely emit a text delta to the stream.
|
|
185
|
+
// On error (e.g. 403 from Teams stop button), abort the controller.
|
|
186
|
+
function safeEmit(text) {
|
|
187
|
+
/* v8 ignore next -- defensive guard: stopped set by prior 403; tested via flush abort path @preserve */
|
|
188
|
+
if (stopped)
|
|
189
|
+
return;
|
|
190
|
+
try {
|
|
191
|
+
catchAsync(stream.emit(text));
|
|
192
|
+
streamHasContent = true;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
markStopped();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Awaitable emit — returns true if the emit succeeded, false if it failed.
|
|
199
|
+
// Used by flush() so it can fall back to sendMessage on async 413/failure.
|
|
200
|
+
async function tryEmit(text) {
|
|
201
|
+
/* v8 ignore next -- defensive guard: stopped set by prior error; tested via flush abort path @preserve */
|
|
202
|
+
if (stopped)
|
|
203
|
+
return false;
|
|
204
|
+
try {
|
|
205
|
+
// stream.emit() is typed as void but the Teams SDK returns a Promise
|
|
206
|
+
// internally (async HTTP). Cast to capture the result for awaiting.
|
|
207
|
+
const result = stream.emit(text);
|
|
208
|
+
streamHasContent = true;
|
|
209
|
+
if (result && typeof result.then === "function") {
|
|
210
|
+
await result;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
markStopped();
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Safely send a status update to the stream.
|
|
220
|
+
// On error (e.g. 403 from Teams stop button), abort the controller.
|
|
221
|
+
function safeUpdate(text) {
|
|
222
|
+
if (stopped)
|
|
223
|
+
return;
|
|
224
|
+
try {
|
|
225
|
+
catchAsync(stream.update(text));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
markStopped();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Safely send a separate message via sendMessage (for content after first emit).
|
|
232
|
+
// Serialized via promise chain -- concurrent calls execute sequentially.
|
|
233
|
+
// If any send fails, the chain halts via markStopped().
|
|
234
|
+
let sendChain = Promise.resolve();
|
|
235
|
+
let sendChainBusy = false;
|
|
236
|
+
function safeSend(text) {
|
|
237
|
+
if (stopped || !sendMessage)
|
|
238
|
+
return;
|
|
239
|
+
if (!sendChainBusy) {
|
|
240
|
+
// Chain is idle -- start the send synchronously and mark busy
|
|
241
|
+
sendChainBusy = true;
|
|
242
|
+
try {
|
|
243
|
+
sendChain = sendMessage(text).catch(() => markStopped()).finally(() => { sendChainBusy = false; });
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
sendChainBusy = false;
|
|
247
|
+
markStopped();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Chain is busy -- queue onto the existing chain
|
|
252
|
+
sendChain = sendChain.then(() => {
|
|
253
|
+
if (stopped)
|
|
254
|
+
return;
|
|
255
|
+
return sendMessage(text);
|
|
256
|
+
}).catch(() => markStopped());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Flush accumulated text buffer via safeEmit. The Teams SDK accumulates
|
|
260
|
+
// emitted text into a single streaming message (cumulative), so every
|
|
261
|
+
// periodic flush appends to the same response — not separate messages.
|
|
262
|
+
// No preemptive splitting — sends full text. Error recovery happens in flush().
|
|
263
|
+
function flushTextBuffer() {
|
|
264
|
+
if (!textBuffer)
|
|
265
|
+
return;
|
|
266
|
+
safeEmit(textBuffer);
|
|
267
|
+
textBuffer = "";
|
|
268
|
+
}
|
|
269
|
+
function startPhraseRotation(pool) {
|
|
270
|
+
stopPhraseRotation();
|
|
271
|
+
phraseTimer = setInterval(() => {
|
|
272
|
+
const next = (0, phrases_1.pickPhrase)(pool, lastPhrase);
|
|
273
|
+
lastPhrase = next;
|
|
274
|
+
safeUpdate(next + "...");
|
|
275
|
+
}, 1500);
|
|
276
|
+
}
|
|
277
|
+
function stopPhraseRotation() {
|
|
278
|
+
if (phraseTimer) {
|
|
279
|
+
clearInterval(phraseTimer);
|
|
280
|
+
phraseTimer = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
onModelStart: () => {
|
|
285
|
+
if (hadRealOutput)
|
|
286
|
+
return; // real output already shown; don't overwrite with phrases
|
|
287
|
+
const phrases = (0, phrases_1.getPhrases)();
|
|
288
|
+
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
289
|
+
const first = (0, phrases_1.pickPhrase)(pool);
|
|
290
|
+
lastPhrase = first;
|
|
291
|
+
safeUpdate(first + "...");
|
|
292
|
+
startPhraseRotation(pool);
|
|
293
|
+
},
|
|
294
|
+
onModelStreamStart: () => {
|
|
295
|
+
// No-op: don't stop rotation here — keep cycling phrases through
|
|
296
|
+
// the reasoning phase until actual content arrives.
|
|
297
|
+
},
|
|
298
|
+
onReasoningChunk: (text) => {
|
|
299
|
+
/* v8 ignore next -- defensive guard: stopped set by prior error @preserve */
|
|
300
|
+
if (stopped)
|
|
301
|
+
return;
|
|
302
|
+
stopPhraseRotation();
|
|
303
|
+
hadRealOutput = true;
|
|
304
|
+
reasoningBuf += text;
|
|
305
|
+
startFlushTimer();
|
|
306
|
+
},
|
|
307
|
+
onTextChunk: (text) => {
|
|
308
|
+
if (stopped)
|
|
309
|
+
return;
|
|
310
|
+
stopPhraseRotation();
|
|
311
|
+
textBuffer += text;
|
|
312
|
+
startFlushTimer();
|
|
313
|
+
},
|
|
314
|
+
onClearText: () => {
|
|
315
|
+
textBuffer = "";
|
|
316
|
+
},
|
|
317
|
+
onToolStart: (name, args) => {
|
|
318
|
+
stopPhraseRotation();
|
|
319
|
+
flushTextBuffer();
|
|
320
|
+
const argSummary = Object.values(args).join(", ");
|
|
321
|
+
safeUpdate(`running ${name} (${argSummary})...`);
|
|
322
|
+
hadToolRun = true;
|
|
323
|
+
},
|
|
324
|
+
onToolEnd: (name, summary, success) => {
|
|
325
|
+
stopPhraseRotation();
|
|
326
|
+
const msg = (0, format_1.formatToolResult)(name, summary, success);
|
|
327
|
+
safeUpdate(msg);
|
|
328
|
+
},
|
|
329
|
+
onKick: () => {
|
|
330
|
+
stopPhraseRotation();
|
|
331
|
+
const msg = (0, format_1.formatKick)();
|
|
332
|
+
safeUpdate(msg);
|
|
333
|
+
},
|
|
334
|
+
onError: (error, severity) => {
|
|
335
|
+
stopPhraseRotation();
|
|
336
|
+
if (stopped)
|
|
337
|
+
return;
|
|
338
|
+
const msg = (0, format_1.formatError)(error);
|
|
339
|
+
if (severity === "transient") {
|
|
340
|
+
safeUpdate(msg);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
safeSend(msg);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
onConfirmAction: options?.conversationId
|
|
347
|
+
? async (name, args) => {
|
|
348
|
+
const convId = options.conversationId;
|
|
349
|
+
const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
350
|
+
safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
|
|
351
|
+
return new Promise((resolve) => {
|
|
352
|
+
_pendingConfirmations.set(convId, resolve);
|
|
353
|
+
// Auto-deny after 2 minutes to prevent indefinite blocking
|
|
354
|
+
// (e.g. when the stream dies and the user never sees the prompt).
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
if (_pendingConfirmations.has(convId)) {
|
|
357
|
+
_pendingConfirmations.delete(convId);
|
|
358
|
+
resolve("denied");
|
|
359
|
+
}
|
|
360
|
+
}, 120_000);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
: undefined,
|
|
364
|
+
flush: async () => {
|
|
365
|
+
stopFlushTimer();
|
|
366
|
+
if (textBuffer) {
|
|
367
|
+
const text = textBuffer;
|
|
368
|
+
textBuffer = "";
|
|
369
|
+
if (!stopped) {
|
|
370
|
+
// Stream is alive — await the emit so we can catch async 413/failure
|
|
371
|
+
// and fall through to sendMessage recovery.
|
|
372
|
+
const ok = await tryEmit(text);
|
|
373
|
+
if (!ok)
|
|
374
|
+
markStopped();
|
|
375
|
+
}
|
|
376
|
+
if (stopped && sendMessage) {
|
|
377
|
+
// Stream is dead — fall back to sendMessage; split on failure as recovery.
|
|
378
|
+
try {
|
|
379
|
+
await sendMessage(text);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
const chunks = splitMessage(text, RECOVERY_CHUNK_SIZE);
|
|
383
|
+
for (const chunk of chunks)
|
|
384
|
+
await sendMessage(chunk);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else if (!streamHasContent) {
|
|
389
|
+
safeEmit("(completed with tool calls only \u2014 no text response)");
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
// Per-conversation pending confirmation resolvers.
|
|
395
|
+
// When a mutate tool needs confirmation, the resolver is stored here.
|
|
396
|
+
// The next message from the same conversation resolves it.
|
|
397
|
+
const _pendingConfirmations = new Map();
|
|
398
|
+
// Confirmation response words (case-insensitive)
|
|
399
|
+
const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
|
|
400
|
+
function resolvePendingConfirmation(convId, text) {
|
|
401
|
+
const resolver = _pendingConfirmations.get(convId);
|
|
402
|
+
if (!resolver)
|
|
403
|
+
return false;
|
|
404
|
+
_pendingConfirmations.delete(convId);
|
|
405
|
+
const word = text.trim().toLowerCase();
|
|
406
|
+
if (CONFIRM_WORDS.has(word)) {
|
|
407
|
+
resolver("confirmed");
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
resolver("denied");
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
|
|
415
|
+
function teamsTurnKey(conversationId) {
|
|
416
|
+
return `teams:${conversationId}`;
|
|
417
|
+
}
|
|
418
|
+
async function withConversationLock(convId, fn) {
|
|
419
|
+
await _turnCoordinator.withTurnLock(teamsTurnKey(convId), fn);
|
|
420
|
+
}
|
|
421
|
+
// Create a fresh friend store per request so mkdirSync re-runs if directories
|
|
422
|
+
// are deleted while the process is alive.
|
|
423
|
+
function getFriendStore() {
|
|
424
|
+
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
425
|
+
return new store_file_1.FileFriendStore(friendsPath);
|
|
426
|
+
}
|
|
427
|
+
// Handle an incoming Teams message
|
|
428
|
+
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage) {
|
|
429
|
+
const turnKey = teamsTurnKey(conversationId);
|
|
430
|
+
// NOTE: Confirmation resolution is handled in the app.on("message") handler
|
|
431
|
+
// BEFORE the conversation lock. By the time we get here, any pending
|
|
432
|
+
// confirmation has already been resolved and the reply consumed.
|
|
433
|
+
// Send first thinking phrase immediately so the user sees feedback
|
|
434
|
+
// before sync I/O (session load, trim) blocks the event loop.
|
|
435
|
+
stream.update((0, phrases_1.pickPhrase)((0, phrases_1.getPhrases)().thinking) + "...");
|
|
436
|
+
await new Promise(r => setImmediate(r));
|
|
437
|
+
// Resolve context kernel (identity + channel) early so we can use the friend UUID for session path
|
|
438
|
+
const store = getFriendStore();
|
|
439
|
+
const provider = teamsContext?.aadObjectId ? "aad" : "teams-conversation";
|
|
440
|
+
const externalId = teamsContext?.aadObjectId || conversationId;
|
|
441
|
+
const toolContext = teamsContext ? {
|
|
442
|
+
graphToken: teamsContext.graphToken,
|
|
443
|
+
adoToken: teamsContext.adoToken,
|
|
444
|
+
githubToken: teamsContext.githubToken,
|
|
445
|
+
signin: teamsContext.signin,
|
|
446
|
+
friendStore: store,
|
|
447
|
+
summarize: (0, core_1.createSummarize)(),
|
|
448
|
+
} : undefined;
|
|
449
|
+
if (toolContext) {
|
|
450
|
+
const resolver = new resolver_1.FriendResolver(store, {
|
|
451
|
+
provider,
|
|
452
|
+
externalId,
|
|
453
|
+
tenantId: teamsContext?.tenantId,
|
|
454
|
+
displayName: teamsContext?.displayName || "Unknown",
|
|
455
|
+
channel: "teams",
|
|
456
|
+
});
|
|
457
|
+
toolContext.context = await resolver.resolve();
|
|
458
|
+
}
|
|
459
|
+
const friendId = toolContext?.context?.friend?.id || "default";
|
|
460
|
+
if (toolContext?.context?.friend) {
|
|
461
|
+
const trustGate = (0, trust_gate_1.enforceTrustGate)({
|
|
462
|
+
friend: toolContext.context.friend,
|
|
463
|
+
provider,
|
|
464
|
+
externalId,
|
|
465
|
+
tenantId: teamsContext?.tenantId,
|
|
466
|
+
channel: "teams",
|
|
467
|
+
});
|
|
468
|
+
if (!trustGate.allowed) {
|
|
469
|
+
if (trustGate.reason === "stranger_first_reply") {
|
|
470
|
+
stream.emit(trustGate.autoReply);
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const registry = (0, commands_1.createCommandRegistry)();
|
|
476
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
477
|
+
// Check for slash commands
|
|
478
|
+
const parsed = (0, commands_1.parseSlashCommand)(text);
|
|
479
|
+
if (parsed) {
|
|
480
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "teams" });
|
|
481
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
482
|
+
if (dispatchResult.result.action === "new") {
|
|
483
|
+
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
484
|
+
(0, context_1.deleteSession)(sessPath);
|
|
485
|
+
stream.emit("session cleared");
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
else if (dispatchResult.result.action === "response") {
|
|
489
|
+
stream.emit(dispatchResult.result.message || "");
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Load or create session
|
|
495
|
+
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
496
|
+
const existing = (0, context_1.loadSession)(sessPath);
|
|
497
|
+
const messages = existing?.messages && existing.messages.length > 0
|
|
498
|
+
? existing.messages
|
|
499
|
+
: [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", undefined, toolContext?.context) }];
|
|
500
|
+
// Push user message
|
|
501
|
+
messages.push({ role: "user", content: text });
|
|
502
|
+
// Run agent
|
|
503
|
+
const controller = new AbortController();
|
|
504
|
+
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
505
|
+
const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs });
|
|
506
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
507
|
+
const agentOptions = {};
|
|
508
|
+
agentOptions.traceId = traceId;
|
|
509
|
+
if (toolContext)
|
|
510
|
+
agentOptions.toolContext = toolContext;
|
|
511
|
+
if (channelConfig.skipConfirmation)
|
|
512
|
+
agentOptions.skipConfirmation = true;
|
|
513
|
+
agentOptions.drainSteeringFollowUps = () => _turnCoordinator.drainFollowUps(turnKey).map((m) => ({ text: m.text }));
|
|
514
|
+
const result = await (0, core_1.runAgent)(messages, callbacks, "teams", controller.signal, agentOptions);
|
|
515
|
+
// Flush any remaining accumulated text at end of turn
|
|
516
|
+
await callbacks.flush();
|
|
517
|
+
// After the agent loop, check if any tool returned AUTH_REQUIRED and trigger signin.
|
|
518
|
+
// This must happen after the stream is done so the OAuth card renders properly.
|
|
519
|
+
if (teamsContext) {
|
|
520
|
+
const allContent = messages.map(m => typeof m.content === "string" ? m.content : "").join("\n");
|
|
521
|
+
if (allContent.includes("AUTH_REQUIRED:graph"))
|
|
522
|
+
await teamsContext.signin("graph");
|
|
523
|
+
if (allContent.includes("AUTH_REQUIRED:ado"))
|
|
524
|
+
await teamsContext.signin("ado");
|
|
525
|
+
if (allContent.includes("AUTH_REQUIRED:github"))
|
|
526
|
+
await teamsContext.signin("github");
|
|
527
|
+
}
|
|
528
|
+
// Trim context and save session
|
|
529
|
+
(0, context_1.postTurn)(messages, sessPath, result.usage);
|
|
530
|
+
// Accumulate token usage on friend record
|
|
531
|
+
if (toolContext?.context?.friend?.id) {
|
|
532
|
+
await (0, tokens_1.accumulateFriendTokens)(store, toolContext.context.friend.id, result.usage);
|
|
533
|
+
}
|
|
534
|
+
// SDK auto-closes the stream after our handler returns (app.process.js)
|
|
535
|
+
}
|
|
536
|
+
// Start the Teams app in DevtoolsPlugin mode (local dev) or Bot Service mode (real Teams).
|
|
537
|
+
// Mode is determined by getTeamsConfig().clientId.
|
|
538
|
+
// Text is always accumulated in textBuffer and flushed periodically (chunked streaming).
|
|
539
|
+
function startTeamsApp() {
|
|
540
|
+
const mentionStripping = { activity: { mentions: { stripText: true } } };
|
|
541
|
+
const teamsConfig = (0, config_2.getTeamsConfig)();
|
|
542
|
+
let app;
|
|
543
|
+
let mode;
|
|
544
|
+
const oauthConfig = (0, config_1.getOAuthConfig)();
|
|
545
|
+
if (teamsConfig.clientId) {
|
|
546
|
+
// Bot Service mode -- real Teams connection with SingleTenant credentials
|
|
547
|
+
app = new teams_apps_1.App({
|
|
548
|
+
clientId: teamsConfig.clientId,
|
|
549
|
+
clientSecret: teamsConfig.clientSecret,
|
|
550
|
+
tenantId: teamsConfig.tenantId,
|
|
551
|
+
oauth: { defaultConnectionName: oauthConfig.graphConnectionName },
|
|
552
|
+
...mentionStripping,
|
|
553
|
+
});
|
|
554
|
+
mode = "Bot Service";
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// DevtoolsPlugin mode -- local development with Teams DevtoolsPlugin UI
|
|
558
|
+
app = new teams_apps_1.App({
|
|
559
|
+
plugins: [new teams_dev_1.DevtoolsPlugin()],
|
|
560
|
+
...mentionStripping,
|
|
561
|
+
});
|
|
562
|
+
mode = "DevtoolsPlugin";
|
|
563
|
+
}
|
|
564
|
+
// Override default OAuth verify-state handler. The SDK's built-in handler
|
|
565
|
+
// uses a single defaultConnectionName, which breaks multi-connection setups
|
|
566
|
+
// (graph + ado + github). The verifyState activity only carries a `state`
|
|
567
|
+
// code with no connectionName, so we try each configured connection until
|
|
568
|
+
// one succeeds.
|
|
569
|
+
const allConnectionNames = [
|
|
570
|
+
oauthConfig.graphConnectionName,
|
|
571
|
+
oauthConfig.adoConnectionName,
|
|
572
|
+
oauthConfig.githubConnectionName,
|
|
573
|
+
].filter(Boolean);
|
|
574
|
+
app.on("signin.verify-state", async (ctx) => {
|
|
575
|
+
const { api, activity } = ctx;
|
|
576
|
+
if (!activity.value?.state)
|
|
577
|
+
return { status: 404 };
|
|
578
|
+
for (const cn of allConnectionNames) {
|
|
579
|
+
try {
|
|
580
|
+
await api.users.token.get({
|
|
581
|
+
channelId: activity.channelId,
|
|
582
|
+
userId: activity.from.id,
|
|
583
|
+
connectionName: cn,
|
|
584
|
+
code: activity.value.state,
|
|
585
|
+
});
|
|
586
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.verify_state", component: "channels", message: `verify-state succeeded for connection "${cn}"`, meta: { connectionName: cn } });
|
|
587
|
+
return { status: 200 };
|
|
588
|
+
}
|
|
589
|
+
catch { /* try next */ }
|
|
590
|
+
}
|
|
591
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", event: "channel.verify_state", component: "channels", message: "verify-state failed for all connections", meta: {} });
|
|
592
|
+
return { status: 412 };
|
|
593
|
+
});
|
|
594
|
+
app.on("message", async (ctx) => {
|
|
595
|
+
const { stream, activity, api, signin } = ctx;
|
|
596
|
+
const text = activity.text || "";
|
|
597
|
+
const convId = activity.conversation?.id || "unknown";
|
|
598
|
+
const turnKey = teamsTurnKey(convId);
|
|
599
|
+
const userId = activity.from?.id || "";
|
|
600
|
+
const channelId = activity.channelId || "msteams";
|
|
601
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: "incoming teams message", meta: { userId: userId.slice(0, 12), conversationId: convId.slice(0, 20) } });
|
|
602
|
+
// Resolve pending confirmations IMMEDIATELY — before token fetches or
|
|
603
|
+
// the conversation lock. The original message holds the lock while
|
|
604
|
+
// awaiting confirmation, so acquiring it here would deadlock. Token
|
|
605
|
+
// fetches are also unnecessary (and slow) for a simple yes/no reply.
|
|
606
|
+
if (resolvePendingConfirmation(convId, text)) {
|
|
607
|
+
// Don't emit on this stream — the original message's stream is still
|
|
608
|
+
// active. Opening a second streaming response in the same conversation
|
|
609
|
+
// can corrupt the first. The original stream will show tool progress
|
|
610
|
+
// once the confirmation Promise resolves.
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
// If this conversation already has an active turn, steer follow-up input
|
|
614
|
+
// into that turn and avoid starting a second concurrent turn.
|
|
615
|
+
if (!_turnCoordinator.tryBeginTurn(turnKey)) {
|
|
616
|
+
_turnCoordinator.enqueueFollowUp(turnKey, {
|
|
617
|
+
conversationId: convId,
|
|
618
|
+
text,
|
|
619
|
+
receivedAt: Date.now(),
|
|
620
|
+
});
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
// Fetch tokens for both OAuth connections independently.
|
|
625
|
+
// Failures are silently caught -- the tool handler will request signin if needed.
|
|
626
|
+
let graphToken;
|
|
627
|
+
let adoToken;
|
|
628
|
+
let githubToken;
|
|
629
|
+
try {
|
|
630
|
+
const graphRes = await api.users.token.get({ userId, connectionName: oauthConfig.graphConnectionName, channelId });
|
|
631
|
+
graphToken = graphRes?.token;
|
|
632
|
+
}
|
|
633
|
+
catch { /* no token yet — tool handler will trigger signin */ }
|
|
634
|
+
try {
|
|
635
|
+
const adoRes = await api.users.token.get({ userId, connectionName: oauthConfig.adoConnectionName, channelId });
|
|
636
|
+
adoToken = adoRes?.token;
|
|
637
|
+
}
|
|
638
|
+
catch { /* no token yet — tool handler will trigger signin */ }
|
|
639
|
+
try {
|
|
640
|
+
const githubRes = await api.users.token.get({ userId, connectionName: oauthConfig.githubConnectionName, channelId });
|
|
641
|
+
githubToken = githubRes?.token;
|
|
642
|
+
}
|
|
643
|
+
catch { /* no token yet — tool handler will trigger signin */ }
|
|
644
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken } });
|
|
645
|
+
const teamsContext = {
|
|
646
|
+
graphToken,
|
|
647
|
+
adoToken,
|
|
648
|
+
githubToken,
|
|
649
|
+
signin: async (cn) => {
|
|
650
|
+
try {
|
|
651
|
+
const result = await signin({ connectionName: cn });
|
|
652
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.signin_result", component: "channels", message: `signin(${cn}): ${result ? "token received" : "no token"}`, meta: { connectionName: cn, hasToken: !!result } });
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
catch (e) {
|
|
656
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
657
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.signin_error", component: "channels", message: `signin(${cn}) failed`, meta: { connectionName: cn, reason: msg.slice(0, 100) } });
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
aadObjectId: activity.from?.aadObjectId,
|
|
662
|
+
tenantId: activity.conversation?.tenantId,
|
|
663
|
+
displayName: activity.from?.name,
|
|
664
|
+
};
|
|
665
|
+
/* v8 ignore next 5 -- bot-framework integration callback; tested via handleTeamsMessage sendMessage path @preserve */
|
|
666
|
+
const ctxSend = async (t) => {
|
|
667
|
+
// Use send with replyToId (not reply, which adds a blockquote).
|
|
668
|
+
// replyToId anchors the message after the user's message in Copilot Chat.
|
|
669
|
+
await ctx.send({ type: "message", text: t, replyToId: activity.id });
|
|
670
|
+
};
|
|
671
|
+
await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
675
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
|
|
676
|
+
}
|
|
677
|
+
finally {
|
|
678
|
+
_turnCoordinator.endTurn(turnKey);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
if (!process.listeners("unhandledRejection").some((l) => l.__agentHandler)) {
|
|
682
|
+
const handler = (err) => {
|
|
683
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
684
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.unhandled_rejection", component: "channels", message: msg.slice(0, 200), meta: {} });
|
|
685
|
+
};
|
|
686
|
+
handler.__agentHandler = true;
|
|
687
|
+
process.on("unhandledRejection", handler);
|
|
688
|
+
}
|
|
689
|
+
app.event("error", ({ error }) => {
|
|
690
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
691
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: msg, meta: {} });
|
|
692
|
+
});
|
|
693
|
+
const port = (0, config_2.getTeamsChannelConfig)().port;
|
|
694
|
+
app.start(port);
|
|
695
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.app_started", component: "channels", message: `Teams bot started on port ${port} with ${mode} (chunked streaming)`, meta: { port, mode } });
|
|
696
|
+
}
|