@ouro.bot/cli 0.1.0-alpha.931 → 0.1.0-alpha.933
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/changelog.json +15 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/senses/shepherd/judge.js +8 -6
- package/dist/senses/shepherd/owner-notice.js +85 -31
- package/dist/senses/shepherd/prompt-state.js +1 -1
- package/dist/senses/shepherd/sense.js +103 -26
- package/dist/senses/teams.js +38 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.933",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Recover existing supervised Teams tunnels after permanent rejection or sustained readiness loss, with scoped and confirmed bot endpoint registration.",
|
|
8
|
+
"Recover accepted Shepherd notice intent without importing audit history, and compact pending observations into bounded historical session digests with durable receipt custody.",
|
|
9
|
+
"Preserve valid premature judgments independently of invalid suggested replies; keep length-only periodic fixed-text continuation behind all existing input safety guards.",
|
|
10
|
+
"Correlate Teams request and reply stages without message bodies, and contain notice-ledger failures so later returns remain runnable."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.932",
|
|
15
|
+
"changes": [
|
|
16
|
+
"Shepherd recognizes the current Copilot block-framed composer border without relaxing draft, background-work, approval/menu, identity or screen-stability guards."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.931",
|
|
6
21
|
"changes": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.933</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -25,11 +25,11 @@ const CALLBACKS = { onModelStart: NOOP, onModelStreamStart: NOOP, onTextChunk: N
|
|
|
25
25
|
function replyTextProblem(text) {
|
|
26
26
|
const line = text.replace(/`/g, "").replace(/\s+/g, " ").trim();
|
|
27
27
|
if (!line)
|
|
28
|
-
return { problem: "an empty reply" };
|
|
29
|
-
if (line.length > exports.REPLY_MAX_CHARS)
|
|
30
|
-
return { problem: `a reply of ${line.length} characters (the limit is ${exports.REPLY_MAX_CHARS})` };
|
|
28
|
+
return { problem: "an empty reply", issue: "missing" };
|
|
31
29
|
if (/[\\@;$|<>&\p{Cc}\p{Cf}]/u.test(line) || /^[/!#]/.test(line))
|
|
32
|
-
return { problem: "a reply with characters a terminal could read as a command" };
|
|
30
|
+
return { problem: "a reply with characters a terminal could read as a command", issue: "unsafe" };
|
|
31
|
+
if (line.length > exports.REPLY_MAX_CHARS)
|
|
32
|
+
return { problem: `a reply of ${line.length} characters (the limit is ${exports.REPLY_MAX_CHARS})`, issue: "too_long" };
|
|
33
33
|
return { line };
|
|
34
34
|
}
|
|
35
35
|
function safeReplyText(text) {
|
|
@@ -81,8 +81,10 @@ function parseVerdict(content) {
|
|
|
81
81
|
return { kind: "unclear", reply: null, reason: `premature, but the key came from JSON inside prose: ${reason}` };
|
|
82
82
|
return { kind, reply: { key: reply.key }, reason };
|
|
83
83
|
}
|
|
84
|
-
const checked =
|
|
85
|
-
|
|
84
|
+
const checked = Object.hasOwn(reply, "key") ? { problem: "an unsupported key", issue: "unsafe" }
|
|
85
|
+
: typeof reply.text === "string" ? replyTextProblem(reply.text) : { problem: "no reply", issue: "missing" };
|
|
86
|
+
return "line" in checked ? { kind, reply: { text: checked.line }, reason }
|
|
87
|
+
: { kind: parsed.inProse ? "unclear" : kind, reply: null, replyIssue: checked.issue, reason: `premature, but the judge gave ${checked.problem}: ${reason}` };
|
|
86
88
|
}
|
|
87
89
|
function judgeMessages(packet) {
|
|
88
90
|
return [
|
|
@@ -47,7 +47,6 @@ const friends_1 = require("@ouro.bot/friends");
|
|
|
47
47
|
const await_alert_1 = require("../../heart/awaiting/await-alert");
|
|
48
48
|
const config_1 = require("../../heart/config");
|
|
49
49
|
const runtime_1 = require("../../nerves/runtime");
|
|
50
|
-
const returns_1 = require("./returns");
|
|
51
50
|
const notice_outbox_1 = require("./notice-outbox");
|
|
52
51
|
/**
|
|
53
52
|
* The owner notice: after a judged return, the agent's owner hears what happened in one short plain message on their
|
|
@@ -55,12 +54,23 @@ const notice_outbox_1 = require("./notice-outbox");
|
|
|
55
54
|
* policy covers whether or not the Ouro agent decides it matters. Returns that arrive close together share one message.
|
|
56
55
|
*/
|
|
57
56
|
exports.NOTICE_COALESCE_MS = 5_000;
|
|
58
|
-
/**
|
|
57
|
+
/** Distinct sessions named in a digest; the remaining sessions are counted. */
|
|
59
58
|
exports.NOTICE_LIST_MAX = 6;
|
|
60
59
|
exports.NOTICE_RETRY_MS = 30_000;
|
|
61
60
|
const NAME_MAX = 60;
|
|
62
61
|
const REASON_MAX = 300;
|
|
63
62
|
const REPLY_MAX = 240;
|
|
63
|
+
const BLOCK_DESCRIPTION = {
|
|
64
|
+
reply_invalid: "the suggested reply was invalid",
|
|
65
|
+
menu_disallowed: "scheduled continuation cannot approve a menu",
|
|
66
|
+
host_unverified: "the host could not verify safe input state",
|
|
67
|
+
guidance: "operator guidance blocked input",
|
|
68
|
+
loop_guard: "the reply limit was reached",
|
|
69
|
+
focus: "the human owns the focused session",
|
|
70
|
+
state_changed: "the target state changed",
|
|
71
|
+
custody: "input delivery was already claimed or needs reconciliation",
|
|
72
|
+
stopped: "Shepherd stopped",
|
|
73
|
+
};
|
|
64
74
|
/** Kinds a "needs-me" owner still hears about. */
|
|
65
75
|
const NEEDS_ME = new Set(["gate", "done", "loop_guard"]);
|
|
66
76
|
function noticeWanted(policy, record) {
|
|
@@ -103,6 +113,8 @@ function noticeLine(record, location) {
|
|
|
103
113
|
const sent = record.reply.startsWith("key:") ? `pressed ${plain(record.reply.slice(4), 10)}` : `sent it back: "${plain(record.reply, REPLY_MAX)}"`;
|
|
104
114
|
return `${subject} stopped early, so I ${sent}. ${why}`;
|
|
105
115
|
}
|
|
116
|
+
if (record.actionBlockReason)
|
|
117
|
+
return `${subject} stopped early; no input sent because ${BLOCK_DESCRIPTION[record.actionBlockReason]}. ${why}`;
|
|
106
118
|
return `${subject} stopped early, but I did not answer it, so it is waiting for you. ${why}`;
|
|
107
119
|
case "gate":
|
|
108
120
|
return `${subject} needs you. ${why}`;
|
|
@@ -120,13 +132,30 @@ function noticeLine(record, location) {
|
|
|
120
132
|
return `${subject} stopped but I could not judge it, so I left it. ${why}`;
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
|
-
/**
|
|
124
|
-
function noticeMessage(records, refs = new Map()) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
/** A bounded display of latest observations; all input records retain delivery custody. */
|
|
136
|
+
function noticeMessage(records, refs = new Map(), context = {}) {
|
|
137
|
+
const latest = new Map();
|
|
138
|
+
for (const record of records) {
|
|
139
|
+
const key = JSON.stringify([record.host, record.session]);
|
|
140
|
+
const previous = latest.get(key);
|
|
141
|
+
const at = Date.parse(record.at);
|
|
142
|
+
const previousAt = previous ? Date.parse(previous.at) : NaN;
|
|
143
|
+
if (!previous || (Number.isFinite(at) && (!Number.isFinite(previousAt) || at > previousAt))
|
|
144
|
+
|| ((!Number.isFinite(at) && !Number.isFinite(previousAt) || at === previousAt) && record.transition > previous.transition))
|
|
145
|
+
latest.set(key, record);
|
|
146
|
+
}
|
|
147
|
+
const sessions = [...latest].sort(([a], [b]) => a < b ? -1 : 1).map(([, record]) => record);
|
|
148
|
+
const lines = sessions.slice(0, exports.NOTICE_LIST_MAX).map(record => {
|
|
149
|
+
const line = noticeLine(record, refs.get(record.session));
|
|
150
|
+
if (!context.recovered)
|
|
151
|
+
return line;
|
|
152
|
+
const at = Date.parse(record.at);
|
|
153
|
+
return `${line} (${Number.isFinite(at) ? `observed at ${new Date(at).toISOString()}` : "observation time unknown"})`;
|
|
154
|
+
});
|
|
155
|
+
const more = sessions.length - lines.length;
|
|
156
|
+
const body = sessions.length === 1 ? lines[0]
|
|
157
|
+
: [`Updates from ${sessions.length} coding sessions:`, ...lines.map(line => `- ${line}`), ...(more > 0 ? [`- and ${more} more.`] : [])].join("\n");
|
|
158
|
+
return context.recovered ? `Includes recovered historical observations, not live status:\n${body}` : body;
|
|
130
159
|
}
|
|
131
160
|
function teamsAadId(friend) {
|
|
132
161
|
const ext = friend.externalIds.find((entry) => entry.provider === "aad" && !entry.externalId.startsWith("group:"));
|
|
@@ -192,30 +221,43 @@ function createTeamsNoticeSender(agentRoot, deps = {}) {
|
|
|
192
221
|
return sendProactiveTeamsMessageToSession({ friendId: target.friendId, sessionKey: target.key, text, textFormat: "plain", intent: "generic_outreach", captureReceipt: true }, { botApi: await bot, store: new friends_1.FileFriendStore(path.join(agentRoot, "friends")) });
|
|
193
222
|
};
|
|
194
223
|
}
|
|
195
|
-
/** Collects
|
|
224
|
+
/** Collects durable notice intents; acceptance is distinct from transport delivery. */
|
|
196
225
|
function createOwnerNotifier(options) {
|
|
197
226
|
const { agent, policy } = options;
|
|
198
227
|
const resolveTargets = options.resolveTargets ?? (() => resolveNoticeTargets(options.agentRoot));
|
|
199
228
|
const send = options.send ?? createTeamsNoticeSender(options.agentRoot);
|
|
200
229
|
const retryWrites = new Map();
|
|
230
|
+
const recoveredNoticeIds = new Set();
|
|
201
231
|
const writes = new Set();
|
|
202
232
|
let cancel = null;
|
|
203
233
|
let stopped = false;
|
|
204
234
|
let flushing = null;
|
|
235
|
+
let drainRequested = false;
|
|
205
236
|
function schedule(ms) {
|
|
206
237
|
if (!stopped && policy !== "off")
|
|
207
|
-
cancel ??= options.schedule(() => {
|
|
238
|
+
cancel ??= options.schedule(() => {
|
|
239
|
+
cancel?.();
|
|
240
|
+
cancel = null;
|
|
241
|
+
if (flushing)
|
|
242
|
+
drainRequested = true;
|
|
243
|
+
else
|
|
244
|
+
void flush();
|
|
245
|
+
}, ms);
|
|
208
246
|
}
|
|
209
247
|
function persistIntent(record) {
|
|
210
248
|
const key = `${record.host}:${record.session}:${record.transition}:${record.at}`;
|
|
211
249
|
retryWrites.set(key, record);
|
|
212
|
-
const write = (0, notice_outbox_1.enqueueNotice)(options.agentRoot, record).then(
|
|
250
|
+
const write = (0, notice_outbox_1.enqueueNotice)(options.agentRoot, record).then(id => {
|
|
213
251
|
retryWrites.delete(key);
|
|
252
|
+
return id;
|
|
214
253
|
}).catch(error => {
|
|
215
254
|
(0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.shepherd_notice_error", message: "could not persist a Shepherd owner notice intent; it remains waiting", meta: { agent, error: error.message } });
|
|
216
255
|
schedule(exports.NOTICE_RETRY_MS);
|
|
217
|
-
|
|
256
|
+
throw error;
|
|
257
|
+
});
|
|
218
258
|
writes.add(write);
|
|
259
|
+
// Observe cleanup immediately, while the producer still receives the rejecting promise.
|
|
260
|
+
void write.then(() => { writes.delete(write); }, () => { writes.delete(write); });
|
|
219
261
|
return write;
|
|
220
262
|
}
|
|
221
263
|
async function runFlush() {
|
|
@@ -223,10 +265,11 @@ function createOwnerNotifier(options) {
|
|
|
223
265
|
cancel = null;
|
|
224
266
|
if (stopped || policy === "off")
|
|
225
267
|
return;
|
|
226
|
-
await Promise.all([...writes]);
|
|
227
|
-
for (const record of [...retryWrites.values()])
|
|
228
|
-
await persistIntent(record);
|
|
229
268
|
try {
|
|
269
|
+
await Promise.allSettled([...writes]);
|
|
270
|
+
const retried = await Promise.allSettled([...retryWrites.values()].map(persistIntent));
|
|
271
|
+
if (retried.some(result => result.status === "rejected"))
|
|
272
|
+
schedule(exports.NOTICE_RETRY_MS);
|
|
230
273
|
const entries = (0, notice_outbox_1.readNoticeOutbox)(options.agentRoot).filter(entry => noticeWanted(policy, entry.record) && (Object.keys(entry.deliveries).length === 0 || Object.values(entry.deliveries).some(value => value.state !== "sent")));
|
|
231
274
|
if (entries.length === 0)
|
|
232
275
|
return;
|
|
@@ -239,6 +282,8 @@ function createOwnerNotifier(options) {
|
|
|
239
282
|
// Without the host's handles, each notice names a short session id instead.
|
|
240
283
|
}
|
|
241
284
|
const targets = await resolveTargets();
|
|
285
|
+
if (stopped)
|
|
286
|
+
return;
|
|
242
287
|
if (targets.length === 0) {
|
|
243
288
|
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.shepherd_notice_no_target", message: "no family friend is reachable on Teams, so the Shepherd owner notice was not sent", meta: { agent, returns: entries.length } });
|
|
244
289
|
schedule(exports.NOTICE_RETRY_MS);
|
|
@@ -248,15 +293,21 @@ function createOwnerNotifier(options) {
|
|
|
248
293
|
const reachable = new Set(targets.map(target => target.friendId));
|
|
249
294
|
let retry = entries.some(entry => Object.entries(entry.deliveries).some(([friendId, delivery]) => delivery.state === "pending" && !reachable.has(friendId)));
|
|
250
295
|
for (const target of targets) {
|
|
296
|
+
if (stopped)
|
|
297
|
+
break;
|
|
251
298
|
const pending = entries.filter(entry => (0, notice_outbox_1.noticeDeliveryState)(entry, target.friendId) === "pending");
|
|
252
|
-
|
|
299
|
+
if (pending.length > 0) {
|
|
253
300
|
// Only the worker that atomically claims a pending entry may initiate transport.
|
|
254
|
-
const batch = await (0, notice_outbox_1.claimPendingNotices)(options.agentRoot, pending.
|
|
301
|
+
const batch = await (0, notice_outbox_1.claimPendingNotices)(options.agentRoot, pending.map(entry => entry.id), target.friendId, new Date().toISOString());
|
|
255
302
|
if (batch.length === 0)
|
|
256
303
|
continue;
|
|
257
304
|
const ids = batch.map(entry => entry.id);
|
|
305
|
+
if (stopped) {
|
|
306
|
+
await (0, notice_outbox_1.recordNoticeDelivery)(options.agentRoot, ids, target.friendId, { state: "pending", at: new Date().toISOString(), reason: "stopped before transport" });
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
258
309
|
try {
|
|
259
|
-
const result = await send(target, noticeMessage(batch.map(entry => entry.record), refs));
|
|
310
|
+
const result = await send(target, noticeMessage(batch.map(entry => entry.record), refs, { recovered: ids.some(id => recoveredNoticeIds.has(id)) }));
|
|
260
311
|
const state = result.delivered ? "sent"
|
|
261
312
|
: result.delivery ? result.delivery.state === "not_sent" ? "pending" : "unknown"
|
|
262
313
|
: result.reason && result.reason !== "send_error" ? "pending" : "unknown";
|
|
@@ -288,19 +339,20 @@ function createOwnerNotifier(options) {
|
|
|
288
339
|
function flush() {
|
|
289
340
|
if (flushing)
|
|
290
341
|
return flushing;
|
|
291
|
-
flushing = runFlush().finally(() => {
|
|
342
|
+
flushing = runFlush().finally(() => {
|
|
343
|
+
flushing = null;
|
|
344
|
+
if (drainRequested) {
|
|
345
|
+
drainRequested = false;
|
|
346
|
+
schedule(exports.NOTICE_COALESCE_MS);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
292
349
|
return flushing;
|
|
293
350
|
}
|
|
294
351
|
try {
|
|
295
352
|
const existing = (0, notice_outbox_1.readNoticeOutbox)(options.agentRoot);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
void persistIntent(record);
|
|
300
|
-
recovered = true;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (recovered || existing.some(entry => Object.keys(entry.deliveries).length === 0 || Object.values(entry.deliveries).some(value => value.state === "pending")))
|
|
353
|
+
for (const entry of existing)
|
|
354
|
+
recoveredNoticeIds.add(entry.id);
|
|
355
|
+
if (existing.some(entry => Object.keys(entry.deliveries).length === 0 || Object.values(entry.deliveries).some(value => value.state === "pending")))
|
|
304
356
|
schedule(exports.NOTICE_RETRY_MS);
|
|
305
357
|
}
|
|
306
358
|
catch (error) {
|
|
@@ -309,11 +361,13 @@ function createOwnerNotifier(options) {
|
|
|
309
361
|
}
|
|
310
362
|
return {
|
|
311
363
|
notify(record) {
|
|
312
|
-
if (stopped
|
|
313
|
-
return Promise.resolve();
|
|
364
|
+
if (stopped)
|
|
365
|
+
return Promise.resolve({ state: "suppressed", reason: "stopped" });
|
|
366
|
+
if (!noticeWanted(policy, record))
|
|
367
|
+
return Promise.resolve({ state: "suppressed", reason: "policy" });
|
|
314
368
|
const write = persistIntent(record);
|
|
315
369
|
schedule(exports.NOTICE_COALESCE_MS);
|
|
316
|
-
return write;
|
|
370
|
+
return write.then(noticeId => ({ state: "queued", noticeId }));
|
|
317
371
|
},
|
|
318
372
|
flush,
|
|
319
373
|
stop() {
|
|
@@ -16,7 +16,7 @@ function inspectCodingPrompt(agent, screen) {
|
|
|
16
16
|
let input;
|
|
17
17
|
let footer;
|
|
18
18
|
if (name === "copilot") {
|
|
19
|
-
const top = lines.findLastIndex(line => /^\s
|
|
19
|
+
const top = lines.findLastIndex(line => /^\s*[╭╻]▄{8,}$/.test(line));
|
|
20
20
|
const bottom = lines.findIndex((line, i) => i > top && /^\s*╹▀{8,}$/.test(line));
|
|
21
21
|
if (top < 0 || bottom <= top + 1)
|
|
22
22
|
return unknown;
|
|
@@ -368,6 +368,7 @@ async function startShepherdSenseApp(options) {
|
|
|
368
368
|
guidanceRevision: String(record.guidanceRevision), reply: "key" in reply ? `key:${reply.key}` : reply.text,
|
|
369
369
|
trigger: record.trigger, transition: record.transition, at: record.at }, dispatchOwner);
|
|
370
370
|
if (!claim) {
|
|
371
|
+
record.actionBlockReason = "custody";
|
|
371
372
|
record.reason = `durable dispatch custody refused duplicate or unresolved input: ${record.reason}`;
|
|
372
373
|
return false;
|
|
373
374
|
}
|
|
@@ -385,6 +386,7 @@ async function startShepherdSenseApp(options) {
|
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
388
|
catch (error) {
|
|
389
|
+
record.actionBlockReason = "custody";
|
|
388
390
|
record.dispatch.state = "unknown";
|
|
389
391
|
try {
|
|
390
392
|
custody.completeDispatch(claim.id, dispatchOwner, { state: "unknown", reason: field((0, redact_1.redactSecrets)(String(error)), 200) });
|
|
@@ -399,6 +401,7 @@ async function startShepherdSenseApp(options) {
|
|
|
399
401
|
...(result.reason ? { reason: field((0, redact_1.redactSecrets)(result.reason), 200) } : {}) });
|
|
400
402
|
record.dispatch.state = result.state;
|
|
401
403
|
if (result.state === "not_sent") {
|
|
404
|
+
record.actionBlockReason = "state_changed";
|
|
402
405
|
record.reason = `guarded host refused before input: ${result.reason ?? "target changed"}; ${record.reason}`;
|
|
403
406
|
return false;
|
|
404
407
|
}
|
|
@@ -420,18 +423,26 @@ async function startShepherdSenseApp(options) {
|
|
|
420
423
|
/** False for a return nothing new was decided about: the loop guard still holds, or the same background wait. */
|
|
421
424
|
let notice = true;
|
|
422
425
|
let noticeSignature = null;
|
|
426
|
+
let observationSignature = null;
|
|
427
|
+
let noticeBasis = null;
|
|
428
|
+
let status = null;
|
|
429
|
+
let quietWorkingStatus = false;
|
|
430
|
+
let escalateOutcome = false;
|
|
431
|
+
let wakeOutcome = false;
|
|
423
432
|
let judged = false;
|
|
424
433
|
try {
|
|
425
434
|
let current;
|
|
426
435
|
if (periodic) {
|
|
427
436
|
notice = false;
|
|
428
437
|
if (periodic.generation !== generationFor(event.sessionId) || now() - periodic.observedAt < cmux_1.QUIET_MS) {
|
|
438
|
+
record.actionBlockReason = "state_changed";
|
|
429
439
|
record.reason = "the check-in observation was invalidated or has not been quiet long enough";
|
|
430
440
|
return;
|
|
431
441
|
}
|
|
432
442
|
current = await host.inspect(event.sessionId);
|
|
433
443
|
if (stopped || periodic.generation !== generationFor(event.sessionId) ||
|
|
434
444
|
inspectionFingerprint(current) !== inspectionFingerprint(periodic.first) || current.session.focused) {
|
|
445
|
+
record.actionBlockReason = stopped ? "stopped" : current.session.focused ? "focus" : "state_changed";
|
|
435
446
|
record.reason = "the check-in target changed during the quiet interval";
|
|
436
447
|
return;
|
|
437
448
|
}
|
|
@@ -440,6 +451,7 @@ async function startShepherdSenseApp(options) {
|
|
|
440
451
|
if (state.tripped) {
|
|
441
452
|
// Tripped: no judge call and no new escalation until the human looks at the session.
|
|
442
453
|
Object.assign(record, { kind: "loop_guard", reason: `Shepherd stopped answering this session until ${human} focuses it` });
|
|
454
|
+
record.actionBlockReason = "loop_guard";
|
|
443
455
|
notice = false;
|
|
444
456
|
return;
|
|
445
457
|
}
|
|
@@ -456,11 +468,21 @@ async function startShepherdSenseApp(options) {
|
|
|
456
468
|
record.task = [...text.matchAll(/Desk-Task:\s*(\S+)/g)].pop()?.[1] ?? null;
|
|
457
469
|
const screen = (0, redact_1.screenTail)(text, exports.SCREEN_LINES);
|
|
458
470
|
if (recovery && screen !== recovery.screen) {
|
|
471
|
+
record.actionBlockReason = "state_changed";
|
|
459
472
|
record.reason = "the screen changed since the network stop, so recovery was not sent";
|
|
460
473
|
return;
|
|
461
474
|
}
|
|
462
475
|
const target = { host: host.name, session: event.sessionId, cwd: record.cwd, task: record.task };
|
|
463
|
-
const
|
|
476
|
+
const checkedGuidance = () => {
|
|
477
|
+
try {
|
|
478
|
+
return guidanceFor(target);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
record.actionBlockReason = "guidance";
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const guidance = checkedGuidance();
|
|
464
486
|
record.guidanceRevision = guidance.revision;
|
|
465
487
|
let verdict;
|
|
466
488
|
try {
|
|
@@ -478,117 +500,172 @@ async function startShepherdSenseApp(options) {
|
|
|
478
500
|
}
|
|
479
501
|
judged = true;
|
|
480
502
|
if (periodic && (stopped || periodic.generation !== generationFor(event.sessionId))) {
|
|
503
|
+
record.actionBlockReason = stopped ? "stopped" : "state_changed";
|
|
481
504
|
record.reason = "the check-in was invalidated during judgment; no input or notice sent";
|
|
482
505
|
return;
|
|
483
506
|
}
|
|
484
507
|
// The host's own word that background work is running outranks a reading of the screen: such a stop is never a return.
|
|
485
508
|
const kind = (current ? current.backgroundWork : event.backgroundWork) ? "working" : verdict.kind;
|
|
486
509
|
const news = verdict.news === true || (kind === "working" && verdict.kind === "gate");
|
|
487
|
-
Object.assign(record, { kind, reason: verdict.reason, latencyMs: verdict.latencyMs, inputTokens: verdict.inputTokens
|
|
510
|
+
Object.assign(record, { kind, judgeKind: verdict.kind, reason: verdict.reason, latencyMs: verdict.latencyMs, inputTokens: verdict.inputTokens,
|
|
511
|
+
...(verdict.replyIssue ? { replyIssue: verdict.replyIssue } : {}) });
|
|
488
512
|
if (!recovery && !periodic && kind === "waiting" && verdict.waitingOnNetwork)
|
|
489
513
|
rememberNetworkWait(event, screen);
|
|
490
|
-
|
|
491
|
-
|
|
514
|
+
const noticeScreen = screen.replace(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\b/g, "<observation-time>");
|
|
515
|
+
noticeBasis = [host.name, event.sessionId, record.agent, record.cwd, record.task, noticeScreen, guidance.revision];
|
|
516
|
+
notice = true;
|
|
492
517
|
// A terminal that keeps going quiet without needing anything is checked less often.
|
|
493
518
|
if (!periodic)
|
|
494
519
|
host.pace?.(event.sessionId, kind === "working" || kind === "unclear");
|
|
495
520
|
state.answers = state.answers.filter((at) => now() - at < 3_600_000);
|
|
496
521
|
if (kind === "working") {
|
|
497
522
|
// Still working: a progress update, never an answer. It reaches the Ouro agent only when it carries news, and wakes nothing.
|
|
498
|
-
if (news
|
|
499
|
-
|
|
523
|
+
if (news)
|
|
524
|
+
escalateOutcome = true;
|
|
500
525
|
else
|
|
501
526
|
notice = false;
|
|
502
|
-
|
|
503
|
-
|
|
527
|
+
quietWorkingStatus = !periodic && !news;
|
|
528
|
+
status = `${record.agent ?? "agent"} waiting on its background work: ${verdict.reason}`;
|
|
504
529
|
}
|
|
505
|
-
|
|
506
|
-
|
|
530
|
+
const actionableReply = verdict.reply ?? (periodic && verdict.replyIssue === "too_long" ? { text: exports.PERIODIC_RESUME } : null);
|
|
531
|
+
if (kind === "premature" && !actionableReply)
|
|
532
|
+
record.actionBlockReason = "reply_invalid";
|
|
533
|
+
if (kind === "premature" && actionableReply) {
|
|
534
|
+
if ((recovery || periodic) && "key" in actionableReply) {
|
|
535
|
+
record.actionBlockReason = "menu_disallowed";
|
|
507
536
|
record.reason = `${periodic ? "periodic check-in" : "connectivity recovery"} never presses approval or menu keys: ${verdict.reason}`;
|
|
508
537
|
}
|
|
509
538
|
else if (periodic && (!current.instance || current.activity !== "idle" || current.composer !== "empty" ||
|
|
510
539
|
current.backgroundWork !== false || !current.session.agent || !host.promptIfUnchanged)) {
|
|
540
|
+
record.actionBlockReason = "host_unverified";
|
|
511
541
|
record.reason = `the host cannot verify an idle, empty, supported agent process for guarded input: ${verdict.reason}`;
|
|
512
542
|
}
|
|
513
543
|
else if (guidance.paused) {
|
|
544
|
+
record.actionBlockReason = "guidance";
|
|
514
545
|
record.reason = `Shepherd guidance has this session paused, so nothing was sent: ${verdict.reason}`;
|
|
515
546
|
}
|
|
516
547
|
else if (state.streak >= exports.STREAK_MAX || state.answers.length >= exports.HOURLY_MAX) {
|
|
517
548
|
Object.assign(record, { kind: "loop_guard", reason: `loop guard: ${verdict.reason}` });
|
|
549
|
+
record.actionBlockReason = "loop_guard";
|
|
518
550
|
state.tripped = true;
|
|
519
551
|
}
|
|
520
552
|
else {
|
|
521
553
|
const sessions = await host.list();
|
|
522
554
|
const session = sessions.find(session => session.id === event.sessionId);
|
|
523
555
|
if (now() - state.focusedAt < exports.FOCUS_HOLD_MS || session?.focused) {
|
|
556
|
+
record.actionBlockReason = "focus";
|
|
524
557
|
// Never type into the session the human is in, or one they just left.
|
|
525
558
|
record.reason = `${human} is in this session or just left it, so it is theirs: ${verdict.reason}`;
|
|
526
559
|
}
|
|
527
560
|
else if (periodic && (!session || session.agent !== current.session.agent || session.cwd !== current.session.cwd)) {
|
|
561
|
+
record.actionBlockReason = "state_changed";
|
|
528
562
|
record.reason = `the check-in target changed while judging, so nothing was sent: ${verdict.reason}`;
|
|
529
563
|
}
|
|
530
564
|
else if (!periodic && (0, redact_1.withoutNumbers)((0, redact_1.screenTail)((0, redact_1.redactSecrets)(await host.read(event.sessionId, exports.READ_LINES)), exports.SCREEN_LINES)) !== (0, redact_1.withoutNumbers)(screen)) {
|
|
565
|
+
record.actionBlockReason = "state_changed";
|
|
531
566
|
record.reason = `the screen changed while judging, so nothing was sent: ${verdict.reason}`;
|
|
532
567
|
}
|
|
533
568
|
else {
|
|
534
569
|
const finalInspection = periodic ? await host.inspect(event.sessionId) : undefined;
|
|
535
570
|
if (periodic && (stopped || periodic.generation !== generationFor(event.sessionId) ||
|
|
536
571
|
finalInspection.session.focused || inspectionFingerprint(finalInspection) !== inspectionFingerprint(current))) {
|
|
572
|
+
record.actionBlockReason = stopped ? "stopped" : finalInspection.session.focused ? "focus" : "state_changed";
|
|
537
573
|
record.reason = `the check-in changed while judging, so nothing was sent: ${verdict.reason}`;
|
|
538
574
|
return;
|
|
539
575
|
}
|
|
540
576
|
// No awaited host reads between this final check and initiating the send.
|
|
541
|
-
const latest =
|
|
577
|
+
const latest = checkedGuidance();
|
|
542
578
|
if (stopped || (recovery && (latestReturns.get(event.sessionId) !== event || now() - state.focusedAt < exports.FOCUS_HOLD_MS)) ||
|
|
543
579
|
periodic && (periodic.generation !== generationFor(event.sessionId) || now() - state.focusedAt < exports.FOCUS_HOLD_MS)) {
|
|
580
|
+
record.actionBlockReason = stopped ? "stopped" : now() - state.focusedAt < exports.FOCUS_HOLD_MS ? "focus" : "state_changed";
|
|
544
581
|
record.reason = `Shepherd stopped, the human focused this session, or it returned again while judging, so nothing was sent: ${verdict.reason}`;
|
|
545
582
|
}
|
|
546
583
|
else if (latest.paused || latest.revision !== guidance.revision) {
|
|
584
|
+
record.actionBlockReason = "guidance";
|
|
547
585
|
record.reason = `Shepherd guidance paused or changed while judging (revision ${guidance.revision} to ${latest.revision}), so nothing was sent: ${verdict.reason}`;
|
|
548
586
|
}
|
|
549
587
|
else {
|
|
550
|
-
const reply = periodic ? { text: exports.PERIODIC_RESUME } : recovery ? { text: NETWORK_RESUME } :
|
|
588
|
+
const reply = periodic ? { text: exports.PERIODIC_RESUME } : recovery ? { text: NETWORK_RESUME } : actionableReply;
|
|
551
589
|
const canSend = periodic ? () => {
|
|
552
590
|
if (stopped || periodic.generation !== generationFor(event.sessionId) || now() - state.focusedAt < exports.FOCUS_HOLD_MS)
|
|
553
591
|
return false;
|
|
554
|
-
const live =
|
|
592
|
+
const live = checkedGuidance();
|
|
555
593
|
return !live.paused && live.revision === guidance.revision;
|
|
556
594
|
} : undefined;
|
|
557
595
|
if (await dispatch(record, reply, current?.instance ?? session?.instance ?? null, fingerprint(screen), periodic ? current : undefined, canSend)) {
|
|
558
596
|
Object.assign(record, { action: "respond", reply: "key" in reply ? `key:${reply.key}` : reply.text });
|
|
559
597
|
state.streak += 1;
|
|
560
598
|
state.answers.push(now());
|
|
561
|
-
|
|
562
|
-
await host.signal(event.sessionId, field(`answered for ${human}: ${verdict.reason}`, 120));
|
|
599
|
+
status = `answered for ${human}: ${verdict.reason}`;
|
|
563
600
|
}
|
|
564
601
|
}
|
|
565
602
|
}
|
|
566
603
|
}
|
|
567
604
|
}
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
605
|
+
if (record.kind === "gate" || record.kind === "done" || record.kind === "waiting" || record.kind === "loop_guard") {
|
|
606
|
+
escalateOutcome = true;
|
|
607
|
+
wakeOutcome = true;
|
|
608
|
+
status = `${record.kind === "gate" ? `needs ${human}` : record.kind === "done" ? "done" : record.kind === "waiting" ? "waiting on an outside dependency" : "stopped answering"}: ${verdict.reason}`;
|
|
572
609
|
}
|
|
573
610
|
}
|
|
574
611
|
catch (error) {
|
|
575
612
|
Object.assign(record, { kind: record.reason ? record.kind : "error", reason: `${record.reason ? `${record.reason}; then ` : ""}${error.message}` });
|
|
576
613
|
}
|
|
577
614
|
finally {
|
|
615
|
+
if (noticeBasis && notice) {
|
|
616
|
+
observationSignature = fingerprint([...noticeBasis, record.kind, record.actionBlockReason === "custody" ? null : record.actionBlockReason ?? null]);
|
|
617
|
+
noticeSignature = record.action === "respond" ? fingerprint([observationSignature, "respond", record.reply])
|
|
618
|
+
: record.dispatch?.state === "unknown" ? fingerprint([observationSignature, "unknown"]) : observationSignature;
|
|
619
|
+
try {
|
|
620
|
+
notice = custody.shouldNotify(noticeSignature);
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
notice = false;
|
|
624
|
+
record.noticeState = "failed";
|
|
625
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.shepherd_notice_error", message: "could not compare owner-notice suppression; no new delivery intent accepted", meta: { agent, reason: "signature_read_failed", error: error.message } });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
if (notice && escalateOutcome) {
|
|
630
|
+
escalate(escalationContent(record, human));
|
|
631
|
+
if (wakeOutcome)
|
|
632
|
+
requestWake(record.transition);
|
|
633
|
+
}
|
|
634
|
+
if (status && (notice || quietWorkingStatus))
|
|
635
|
+
await host.signal(event.sessionId, field(status, 120));
|
|
636
|
+
}
|
|
637
|
+
catch (error) {
|
|
638
|
+
record.reason += `; outcome signaling failed: ${error.message}`;
|
|
639
|
+
}
|
|
578
640
|
record.noticeEligible = notice;
|
|
579
|
-
log(record);
|
|
580
|
-
if (judged || !periodic)
|
|
581
|
-
history.set(event.sessionId, record);
|
|
582
641
|
if (notice) {
|
|
583
642
|
try {
|
|
584
|
-
await notifyOwner(record);
|
|
585
|
-
|
|
586
|
-
|
|
643
|
+
const acceptance = await notifyOwner(record);
|
|
644
|
+
record.noticeState = acceptance.state;
|
|
645
|
+
if (acceptance.state === "queued")
|
|
646
|
+
record.noticeId = acceptance.noticeId;
|
|
647
|
+
else
|
|
648
|
+
record.noticeSuppressionReason = acceptance.reason;
|
|
649
|
+
if (noticeSignature && (acceptance.state === "queued" || acceptance.reason === "policy")) {
|
|
650
|
+
try {
|
|
651
|
+
custody.markNotified(noticeSignature, record.at);
|
|
652
|
+
// A sent/uncertain effect also settles the unchanged observation, not just that action outcome.
|
|
653
|
+
if (observationSignature && observationSignature !== noticeSignature)
|
|
654
|
+
custody.markNotified(observationSignature, record.at);
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.shepherd_notice_error", message: "owner notice was accepted but its suppression signature could not be recorded", meta: { agent, error: error.message } });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
587
660
|
}
|
|
588
661
|
catch (error) {
|
|
589
|
-
|
|
662
|
+
record.noticeState = "failed";
|
|
663
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.shepherd_notice_error", message: "owner notice custody failed; the observation is not accepted delivery intent", meta: { agent, error: error.message } });
|
|
590
664
|
}
|
|
591
665
|
}
|
|
666
|
+
log(record);
|
|
667
|
+
if (judged || !periodic)
|
|
668
|
+
history.set(event.sessionId, record);
|
|
592
669
|
}
|
|
593
670
|
}
|
|
594
671
|
function guidanceFor(target) {
|
package/dist/senses/teams.js
CHANGED
|
@@ -694,6 +694,14 @@ function getFriendStore() {
|
|
|
694
694
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
695
695
|
return new friends_1.FileFriendStore(friendsPath);
|
|
696
696
|
}
|
|
697
|
+
function reportTeamsRequest(stage, conversationId, context, details = {}) {
|
|
698
|
+
if (!context?.inboundActivityId)
|
|
699
|
+
return;
|
|
700
|
+
(0, runtime_1.emitNervesEvent)({
|
|
701
|
+
component: "channels", event: "channel.teams_request_stage", message: "Teams conversational request stage",
|
|
702
|
+
meta: { stage, inboundActivityId: context.inboundActivityId, conversationId, tenantId: context.tenantId, botId: context.botApi?.id, ...details },
|
|
703
|
+
});
|
|
704
|
+
}
|
|
697
705
|
function createTeamsCommandRegistry() {
|
|
698
706
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
699
707
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
@@ -750,14 +758,24 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
750
758
|
// ── Teams adapter concerns: controller, callbacks, session path ──────────
|
|
751
759
|
const controller = new AbortController();
|
|
752
760
|
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
753
|
-
const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
|
|
754
761
|
const traceId = (0, nerves_1.createTraceId)();
|
|
762
|
+
const observedStream = {
|
|
763
|
+
emit(activity) {
|
|
764
|
+
const result = stream.emit(activity);
|
|
765
|
+
reportTeamsRequest("reply_emitted", conversationId, teamsContext, { traceId, delivery: "unconfirmed_stream" });
|
|
766
|
+
return result;
|
|
767
|
+
},
|
|
768
|
+
update(text) { return stream.update(text); },
|
|
769
|
+
close: stream.close.bind(stream),
|
|
770
|
+
};
|
|
771
|
+
const callbacks = createTeamsCallbacks(observedStream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
|
|
755
772
|
const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
|
|
756
773
|
const teamsCapabilities = (0, friends_1.getChannelCapabilities)("teams");
|
|
757
774
|
const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, "teams", conversationId);
|
|
758
775
|
const runWithLease = runtimeOverrides?._withSessionTurnLease ?? session_transaction_1.withSessionTurnLease;
|
|
759
776
|
try {
|
|
760
777
|
await runWithLease(sessPath, async (sessionTurnLease) => {
|
|
778
|
+
reportTeamsRequest("running", conversationId, teamsContext, { traceId, friendId });
|
|
761
779
|
// Proactive feedback acknowledgements are not magically in this model session's
|
|
762
780
|
// history. Recover their frozen packet only after identity and lease ownership.
|
|
763
781
|
let feedbackReply;
|
|
@@ -962,9 +980,11 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
962
980
|
}
|
|
963
981
|
catch (error) {
|
|
964
982
|
if (error instanceof session_transaction_1.SessionTurnBusyError || (error instanceof Error && error.name === "SessionTurnBusyError")) {
|
|
983
|
+
reportTeamsRequest("failed", conversationId, teamsContext, { traceId, reason: "lease_busy" });
|
|
965
984
|
stream.emit("this conversation is busy finishing another turn; please try again");
|
|
966
985
|
return;
|
|
967
986
|
}
|
|
987
|
+
reportTeamsRequest("failed", conversationId, teamsContext, { traceId, reason: "turn_failed" });
|
|
968
988
|
throw error;
|
|
969
989
|
}
|
|
970
990
|
}
|
|
@@ -1159,7 +1179,11 @@ function registerBotHandlers(app, label) {
|
|
|
1159
1179
|
const turnKey = teamsTurnKey(convId);
|
|
1160
1180
|
const userId = activity.from?.id || "";
|
|
1161
1181
|
const channelId = activity.channelId || "msteams";
|
|
1162
|
-
|
|
1182
|
+
const requestContext = {
|
|
1183
|
+
inboundActivityId: activity.id, tenantId: activity.conversation?.tenantId,
|
|
1184
|
+
botApi: app.id && api ? { id: app.id, conversations: api.conversations } : undefined,
|
|
1185
|
+
};
|
|
1186
|
+
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: `[${label}] incoming teams message`, meta: { stage: "received", inboundActivityId: activity.id, botId: app.id, tenantId: requestContext.tenantId, userId: userId.slice(0, 12), conversationId: convId } });
|
|
1163
1187
|
const commandRegistry = createTeamsCommandRegistry();
|
|
1164
1188
|
const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
|
|
1165
1189
|
if (parsedSlashCommand) {
|
|
@@ -1200,6 +1224,7 @@ function registerBotHandlers(app, label) {
|
|
|
1200
1224
|
replyToId: activity.replyToId,
|
|
1201
1225
|
}, async () => {
|
|
1202
1226
|
try {
|
|
1227
|
+
reportTeamsRequest("admitted", convId, requestContext);
|
|
1203
1228
|
// Resolve OAuth connection names for this user's tenant (supports per-tenant overrides).
|
|
1204
1229
|
const tenantId = activity.conversation?.tenantId;
|
|
1205
1230
|
const tenantOAuth = (0, config_1.resolveOAuthForTenant)(tenantId);
|
|
@@ -1225,6 +1250,7 @@ function registerBotHandlers(app, label) {
|
|
|
1225
1250
|
catch { /* no token yet — tool handler will trigger signin */ }
|
|
1226
1251
|
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.token_status", component: "channels", message: "oauth token availability", meta: { graph: !!graphToken, ado: !!adoToken, github: !!githubToken, tenantId } });
|
|
1227
1252
|
const teamsContext = {
|
|
1253
|
+
inboundActivityId: activity.id,
|
|
1228
1254
|
replyToId: activity.replyToId,
|
|
1229
1255
|
graphToken,
|
|
1230
1256
|
adoToken,
|
|
@@ -1255,11 +1281,20 @@ function registerBotHandlers(app, label) {
|
|
|
1255
1281
|
const ctxSend = async (t) => {
|
|
1256
1282
|
// Use send with replyToId (not reply, which adds a blockquote).
|
|
1257
1283
|
// replyToId anchors the message after the user's message in Copilot Chat.
|
|
1258
|
-
|
|
1284
|
+
try {
|
|
1285
|
+
const receipt = await ctx.send({ type: "message", text: t, replyToId: activity.id, entities: aiLabelEntities(), channelData: { feedbackLoopEnabled: true } });
|
|
1286
|
+
const messageId = receipt && typeof receipt === "object" && "id" in receipt && typeof receipt.id === "string" ? receipt.id : undefined;
|
|
1287
|
+
reportTeamsRequest(messageId ? "replied" : "reply_emitted", convId, teamsContext, { delivery: messageId ? "accepted" : "unconfirmed", ...(messageId ? { outboundActivityId: messageId } : {}) });
|
|
1288
|
+
}
|
|
1289
|
+
catch (error) {
|
|
1290
|
+
reportTeamsRequest("failed", convId, teamsContext, { reason: "send_unknown" });
|
|
1291
|
+
throw error;
|
|
1292
|
+
}
|
|
1259
1293
|
};
|
|
1260
1294
|
await handleTeamsMessage(text, stream, convId, teamsContext, ctxSend);
|
|
1261
1295
|
}
|
|
1262
1296
|
catch (err) {
|
|
1297
|
+
reportTeamsRequest("failed", convId, requestContext, { reason: "handler_failed" });
|
|
1263
1298
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1264
1299
|
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.handler_error", component: "channels", message: msg.slice(0, 200), meta: {} });
|
|
1265
1300
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.933",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.933",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|