@ouro.bot/cli 0.1.0-alpha.968 → 0.1.0-alpha.969
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 +6 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/orientation-frame.js +29 -11
- package/dist/senses/bluebubbles/client.js +9 -1
- package/dist/senses/bluebubbles/index.js +18 -4
- package/dist/senses/shared-turn.js +9 -5
- package/dist/senses/teams.js +50 -1
- package/dist/senses/telegram-client.js +7 -1
- package/dist/senses/telegram.js +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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.969",
|
|
6
|
+
"changes": [
|
|
7
|
+
"When someone replies to an earlier message in Telegram, iMessage or Teams, the agent now sees what they replied to and whose words it was, even when that message isn't in its own history."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.968",
|
|
6
12
|
"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.969</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -191,14 +191,26 @@ function deriveActionPolicy(signals, combinedSpeech, priorAssistantReferents, la
|
|
|
191
191
|
}
|
|
192
192
|
// A reply names the message it answers. The sense stamps that message's text on the
|
|
193
193
|
// user message, so "yes" binds to what was replied to, not to the latest thing said.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
// The author says whose words they were; a person can quote their own earlier message.
|
|
195
|
+
function stampReplyingToText(message, text, author) {
|
|
196
|
+
const record = message;
|
|
197
|
+
record._replyingToText = text;
|
|
198
|
+
if (author)
|
|
199
|
+
record._replyingToAuthor = author;
|
|
197
200
|
}
|
|
198
|
-
function
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
+
function replyingToOf(message) {
|
|
202
|
+
const record = message;
|
|
203
|
+
const text = record._replyingToText;
|
|
204
|
+
if (typeof text !== "string" || !text.trim())
|
|
205
|
+
return undefined;
|
|
206
|
+
const author = record._replyingToAuthor;
|
|
207
|
+
return author === "agent" || author === "person" || author === "other" ? { text: text.trim(), author } : { text: text.trim() };
|
|
201
208
|
}
|
|
209
|
+
const REPLYING_TO_LABEL = {
|
|
210
|
+
agent: " (your message)",
|
|
211
|
+
person: " (their own earlier message)",
|
|
212
|
+
other: " (someone else's message)",
|
|
213
|
+
};
|
|
202
214
|
function buildOrientationFrame(input) {
|
|
203
215
|
const derivedCurrent = input.currentUserMessages
|
|
204
216
|
? { messages: input.currentUserMessages, firstIndex: input.messages.length }
|
|
@@ -206,10 +218,14 @@ function buildOrientationFrame(input) {
|
|
|
206
218
|
const currentUserSpeech = derivedCurrent.messages
|
|
207
219
|
.map((message) => extractMessageText(message))
|
|
208
220
|
.filter((text) => text.length > 0);
|
|
209
|
-
const
|
|
221
|
+
const replyingTo = derivedCurrent.messages.map(replyingToOf).find((reply) => reply !== undefined);
|
|
210
222
|
const previousAssistant = latestAssistantBefore(input.messages, derivedCurrent.firstIndex);
|
|
211
|
-
|
|
212
|
-
const
|
|
223
|
+
// Only the agent's own message holds the list a "the second one" points into.
|
|
224
|
+
const replyListText = replyingTo && (replyingTo.author ?? "agent") === "agent" ? replyingTo.text : undefined;
|
|
225
|
+
const priorAssistantReferents = extractOrderedListReferents(replyListText ?? extractMessageText(previousAssistant));
|
|
226
|
+
const source = replyingTo && !input.source?.replyingToText
|
|
227
|
+
? { kind: input.channel, ...input.source, replyingToText: replyingTo.text, ...(replyingTo.author ? { replyingToAuthor: replyingTo.author } : {}) }
|
|
228
|
+
: input.source;
|
|
213
229
|
const candidateStructuredOutput = latestStructuredOutputFrom(input.structuredOutputs);
|
|
214
230
|
const speechKind = input.speechKind ?? "utterance";
|
|
215
231
|
const signals = deriveSignals(currentUserSpeech, priorAssistantReferents, candidateStructuredOutput, speechKind);
|
|
@@ -316,8 +332,10 @@ function renderOrientationFrame(frame) {
|
|
|
316
332
|
lines.push(`- default reply target: ${frame.source.defaultReplyTarget}`);
|
|
317
333
|
if (frame.source.threadId)
|
|
318
334
|
lines.push(`- thread id: ${frame.source.threadId}`);
|
|
319
|
-
if (frame.source.replyingToText)
|
|
320
|
-
|
|
335
|
+
if (frame.source.replyingToText) {
|
|
336
|
+
const label = frame.source.replyingToAuthor ? REPLYING_TO_LABEL[frame.source.replyingToAuthor] : "";
|
|
337
|
+
lines.push(`- replying to${label}: ${frame.source.replyingToText}`);
|
|
338
|
+
}
|
|
321
339
|
if (frame.source.repairNotice)
|
|
322
340
|
lines.push(`- repair notice: ${frame.source.repairNotice}`);
|
|
323
341
|
if (frame.source.routingHint)
|
|
@@ -774,6 +774,9 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
|
|
|
774
774
|
}
|
|
775
775
|
},
|
|
776
776
|
async getMessageText(messageGuid) {
|
|
777
|
+
return (await this.getRepliedMessage(messageGuid))?.text ?? null;
|
|
778
|
+
},
|
|
779
|
+
async getRepliedMessage(messageGuid) {
|
|
777
780
|
await assertHostAccount();
|
|
778
781
|
const url = buildRepairUrl(config.serverUrl, messageGuid, config.password);
|
|
779
782
|
try {
|
|
@@ -809,7 +812,12 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
|
|
|
809
812
|
message: "fetched message text by guid",
|
|
810
813
|
meta: { messageGuid },
|
|
811
814
|
});
|
|
812
|
-
|
|
815
|
+
const text = data.text.trim();
|
|
816
|
+
if (!text)
|
|
817
|
+
return null;
|
|
818
|
+
const handle = asRecord(data.handle);
|
|
819
|
+
const senderAddress = handle ? readString(handle, "address")?.trim() : undefined;
|
|
820
|
+
return { text, fromMe: data.isFromMe === true, ...(senderAddress ? { senderAddress } : {}) };
|
|
813
821
|
}
|
|
814
822
|
catch (error) {
|
|
815
823
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -531,7 +531,13 @@ function extractHistoricalLaneSummary(messages) {
|
|
|
531
531
|
}
|
|
532
532
|
return summaries;
|
|
533
533
|
}
|
|
534
|
-
|
|
534
|
+
// Whose words the replied-to message was: the agent's own account, the person replying, or someone else.
|
|
535
|
+
function repliedMessageAuthor(replied, actorExternalId) {
|
|
536
|
+
if (replied.fromMe)
|
|
537
|
+
return "agent";
|
|
538
|
+
return replied.senderAddress && replied.senderAddress === actorExternalId ? "person" : "other";
|
|
539
|
+
}
|
|
540
|
+
function buildBlueBubblesOrientationSource(event, existingMessages, evidence, conversationKind, repliedToText, repliedToAuthor) {
|
|
535
541
|
const repairNotice = event.repairNotice?.trim();
|
|
536
542
|
const actor = evidence.actor;
|
|
537
543
|
const participants = evidence.participants
|
|
@@ -569,7 +575,7 @@ function buildBlueBubblesOrientationSource(event, existingMessages, evidence, co
|
|
|
569
575
|
lane: threadId ? "thread" : "top_level",
|
|
570
576
|
defaultReplyTarget: threadId ? "current_lane" : "top_level",
|
|
571
577
|
...(threadId ? { threadId } : {}),
|
|
572
|
-
...(repliedToText ? { replyingToText: repliedToText } : {}),
|
|
578
|
+
...(repliedToText ? { replyingToText: repliedToText, ...(repliedToAuthor ? { replyingToAuthor: repliedToAuthor } : {}) } : {}),
|
|
573
579
|
...(repairNotice ? { repairNotice } : {}),
|
|
574
580
|
...(summaries.length > 0 ? { recentLanes: summaries } : {}),
|
|
575
581
|
...(hasThreadRoute
|
|
@@ -1602,8 +1608,16 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1602
1608
|
// Fetch the text of the message being replied to (if this is a threaded reply)
|
|
1603
1609
|
const threadGuid = event.threadOriginatorGuid?.trim();
|
|
1604
1610
|
let repliedToText = null;
|
|
1611
|
+
let repliedToAuthor;
|
|
1605
1612
|
if (threadGuid) {
|
|
1606
|
-
|
|
1613
|
+
if (client.getRepliedMessage) {
|
|
1614
|
+
const replied = await client.getRepliedMessage(threadGuid).catch(() => null);
|
|
1615
|
+
repliedToText = replied?.text ?? null;
|
|
1616
|
+
repliedToAuthor = replied ? repliedMessageAuthor(replied, options.orientationEvidence.actor.externalId) : undefined;
|
|
1617
|
+
}
|
|
1618
|
+
else {
|
|
1619
|
+
repliedToText = await client.getMessageText(threadGuid).catch(() => null);
|
|
1620
|
+
}
|
|
1607
1621
|
(0, runtime_1.emitNervesEvent)({
|
|
1608
1622
|
component: "senses",
|
|
1609
1623
|
event: "senses.bluebubbles_reply_context",
|
|
@@ -1619,7 +1633,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1619
1633
|
messages: [...priorMessages, userMessage],
|
|
1620
1634
|
currentUserMessages: [userMessage],
|
|
1621
1635
|
structuredOutputs: existing?.structuredOutputs ?? [],
|
|
1622
|
-
source: buildBlueBubblesOrientationSource(event, priorMessages, options.orientationEvidence, options.orientationConversationKind, repliedToText),
|
|
1636
|
+
source: buildBlueBubblesOrientationSource(event, priorMessages, options.orientationEvidence, options.orientationConversationKind, repliedToText, repliedToAuthor),
|
|
1623
1637
|
});
|
|
1624
1638
|
if (!(0, latest_turn_1.isCurrent)(options.latestTurnCapability)) {
|
|
1625
1639
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "superseded" };
|
|
@@ -481,7 +481,11 @@ async function runSenseTurnExclusive(options, owner) {
|
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
483
|
const replyToEventId = precommittedIngressEvent?.relations.replyToEventId ?? options.ingressRelations?.replyToEventId;
|
|
484
|
-
const
|
|
484
|
+
const matchedReplyText = spokenText(existing?.events?.find((event) => event.id === replyToEventId));
|
|
485
|
+
const fallbackReplyText = options.replyingTo?.text.trim().slice(0, 1200) ?? "";
|
|
486
|
+
const repliedTo = matchedReplyText
|
|
487
|
+
? { text: matchedReplyText, author: "agent" }
|
|
488
|
+
: fallbackReplyText ? { text: fallbackReplyText, author: options.replyingTo.author } : undefined;
|
|
485
489
|
if (precommittedIngressEvent) {
|
|
486
490
|
const projectedIngress = exactProjectedIngressMessage(existing, sessionMessages, precommittedIngressEvent.id, baseSession.value);
|
|
487
491
|
if (!projectedIngress || projectedIngress.role !== "user" || projectedIngress.content !== userMessage)
|
|
@@ -491,8 +495,8 @@ async function runSenseTurnExclusive(options, owner) {
|
|
|
491
495
|
threadRootEventId: precommittedIngressEvent.relations.threadRootEventId,
|
|
492
496
|
references: precommittedIngressEvent.relations.references,
|
|
493
497
|
});
|
|
494
|
-
if (
|
|
495
|
-
(0, orientation_frame_1.stampReplyingToText)(projectedIngress,
|
|
498
|
+
if (repliedTo)
|
|
499
|
+
(0, orientation_frame_1.stampReplyingToText)(projectedIngress, repliedTo.text, repliedTo.author);
|
|
496
500
|
}
|
|
497
501
|
// Pending dir
|
|
498
502
|
const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, channel, sessionKey);
|
|
@@ -602,8 +606,8 @@ async function runSenseTurnExclusive(options, owner) {
|
|
|
602
606
|
(0, session_events_1.stampIngressTime)(userMsg);
|
|
603
607
|
if (options.ingressRelations)
|
|
604
608
|
(0, session_events_1.stampIngressRelations)(userMsg, options.ingressRelations);
|
|
605
|
-
if (
|
|
606
|
-
(0, orientation_frame_1.stampReplyingToText)(userMsg,
|
|
609
|
+
if (repliedTo)
|
|
610
|
+
(0, orientation_frame_1.stampReplyingToText)(userMsg, repliedTo.text, repliedTo.author);
|
|
607
611
|
inboundMessages.push(userMsg);
|
|
608
612
|
}
|
|
609
613
|
const turnResult = await (0, pipeline_1.handleInboundTurn)({
|
package/dist/senses/teams.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.buildWelcomeCard = buildWelcomeCard;
|
|
|
43
43
|
exports.createTeamsCallbacks = createTeamsCallbacks;
|
|
44
44
|
exports.reasoningStatus = reasoningStatus;
|
|
45
45
|
exports.withConversationLock = withConversationLock;
|
|
46
|
+
exports.parseTeamsQuotedReply = parseTeamsQuotedReply;
|
|
46
47
|
exports.handleTeamsMessage = handleTeamsMessage;
|
|
47
48
|
exports.createTeamsSdkLogger = createTeamsSdkLogger;
|
|
48
49
|
exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
|
|
@@ -76,6 +77,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
76
77
|
const outbound_record_1 = require("../mind/outbound-record");
|
|
77
78
|
const trust_gate_1 = require("./trust-gate");
|
|
78
79
|
const pipeline_1 = require("./pipeline");
|
|
80
|
+
const orientation_frame_1 = require("../heart/orientation-frame");
|
|
79
81
|
const session_transaction_1 = require("../mind/session-transaction");
|
|
80
82
|
const teamsFailoverStates = new Map();
|
|
81
83
|
const pending_1 = require("../mind/pending");
|
|
@@ -737,6 +739,52 @@ function getFriendStore() {
|
|
|
737
739
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
738
740
|
return new friends_1.FileFriendStore(friendsPath);
|
|
739
741
|
}
|
|
742
|
+
function decodeHtmlText(html) {
|
|
743
|
+
return html
|
|
744
|
+
.replace(/<br\s*\/?>/giu, "\n")
|
|
745
|
+
.replace(/<[^>]*>/gu, "")
|
|
746
|
+
.replace(/ /gu, " ")
|
|
747
|
+
.replace(/</gu, "<")
|
|
748
|
+
.replace(/>/gu, ">")
|
|
749
|
+
.replace(/"/gu, '"')
|
|
750
|
+
.replace(/'/gu, "'")
|
|
751
|
+
.replace(/&/gu, "&")
|
|
752
|
+
.replace(/\s+/gu, " ")
|
|
753
|
+
.trim();
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Teams carries a quoted reply as a text/html attachment holding a
|
|
757
|
+
* schema.skype.com/Reply blockquote: the quoted author's id (itemprop="mri")
|
|
758
|
+
* and a preview of their words (itemprop="preview"). Ids compare without the
|
|
759
|
+
* "28:"/"29:" channel prefixes, so the bot's own words read as the agent's.
|
|
760
|
+
*/
|
|
761
|
+
// The person's message, with the quote they replied to stamped on it for the orientation frame.
|
|
762
|
+
function teamsUserMessage(content, quotedReply) {
|
|
763
|
+
const message = { role: "user", content };
|
|
764
|
+
if (quotedReply)
|
|
765
|
+
(0, orientation_frame_1.stampReplyingToText)(message, quotedReply.text, quotedReply.author);
|
|
766
|
+
return message;
|
|
767
|
+
}
|
|
768
|
+
function parseTeamsQuotedReply(attachments, ids) {
|
|
769
|
+
for (const attachment of attachments ?? []) {
|
|
770
|
+
if (attachment.contentType !== "text/html" || typeof attachment.content !== "string")
|
|
771
|
+
continue;
|
|
772
|
+
const quote = /<blockquote[^>]*itemtype="http:\/\/schema\.skype\.com\/Reply"[^>]*>([\s\S]*?)<\/blockquote>/iu.exec(attachment.content);
|
|
773
|
+
if (!quote)
|
|
774
|
+
continue;
|
|
775
|
+
const preview = /<p[^>]*itemprop="preview"[^>]*>([\s\S]*?)<\/p>/iu.exec(quote[1]);
|
|
776
|
+
const text = decodeHtmlText(preview ? preview[1] : quote[1]).slice(0, 1200);
|
|
777
|
+
if (!text)
|
|
778
|
+
continue;
|
|
779
|
+
const mri = /itemprop="mri"[^>]*itemid="([^"]+)"/iu.exec(quote[1])?.[1];
|
|
780
|
+
const bare = (id) => id?.replace(/^\d+:/u, "");
|
|
781
|
+
const author = !mri ? undefined
|
|
782
|
+
: bare(mri) === bare(ids.botId) ? "agent"
|
|
783
|
+
: bare(mri) === bare(ids.userId) ? "person" : "other";
|
|
784
|
+
return { text, ...(author ? { author } : {}) };
|
|
785
|
+
}
|
|
786
|
+
return undefined;
|
|
787
|
+
}
|
|
740
788
|
function reportTeamsRequest(stage, conversationId, context, details = {}) {
|
|
741
789
|
if (!context?.inboundActivityId)
|
|
742
790
|
return;
|
|
@@ -894,7 +942,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
894
942
|
channel: "teams",
|
|
895
943
|
sessionKey: conversationId,
|
|
896
944
|
capabilities: teamsCapabilities,
|
|
897
|
-
messages: [
|
|
945
|
+
messages: [teamsUserMessage(currentText, currentText === text ? teamsContext?.quotedReply : undefined)],
|
|
898
946
|
continuityIngressTexts: [currentText],
|
|
899
947
|
callbacks: failoverAwareCallbacks,
|
|
900
948
|
sessionTurnLease,
|
|
@@ -1375,6 +1423,7 @@ function registerBotHandlers(app, label) {
|
|
|
1375
1423
|
const teamsContext = {
|
|
1376
1424
|
inboundActivityId: activity.id,
|
|
1377
1425
|
replyToId: activity.replyToId,
|
|
1426
|
+
quotedReply: parseTeamsQuotedReply(activity.attachments, { botId: activity.recipient?.id ?? app.id, userId }),
|
|
1378
1427
|
graphToken,
|
|
1379
1428
|
adoToken,
|
|
1380
1429
|
githubToken,
|
|
@@ -525,6 +525,12 @@ function createTelegramLongPoll(options) {
|
|
|
525
525
|
const rawAttachmentCount = (message) => [
|
|
526
526
|
message.document, message.audio, message.video, message.voice, message.animation, message.sticker,
|
|
527
527
|
].filter(Boolean).length + (message.photo?.length ? 1 : 0);
|
|
528
|
+
const repliedTo = (message, userId) => {
|
|
529
|
+
const reply = message.reply_to_message;
|
|
530
|
+
const quoted = [message.quote?.text, reply.text, reply.caption].find((value) => typeof value === "string" && value.trim());
|
|
531
|
+
const author = reply.from?.is_bot ? "agent" : reply.from && String(reply.from.id) === userId ? "person" : reply.from ? "other" : undefined;
|
|
532
|
+
return { ...(quoted ? { replyToText: quoted.trim() } : {}), ...(author ? { replyToAuthor: author } : {}) };
|
|
533
|
+
};
|
|
528
534
|
const authorizedMessage = (update, authority) => {
|
|
529
535
|
const message = update.message;
|
|
530
536
|
const userId = message?.from ? String(message.from.id) : "";
|
|
@@ -548,7 +554,7 @@ function createTelegramLongPoll(options) {
|
|
|
548
554
|
attachments,
|
|
549
555
|
...(attachmentNotices.length ? { attachmentNotices } : {}),
|
|
550
556
|
...(Number.isSafeInteger(message.reply_to_message?.message_id) && message.reply_to_message.message_id > 0
|
|
551
|
-
? { replyToMessageId: String(message.reply_to_message.message_id) }
|
|
557
|
+
? { replyToMessageId: String(message.reply_to_message.message_id), ...repliedTo(message, userId) }
|
|
552
558
|
: {}),
|
|
553
559
|
...(authority ? { authority } : {}),
|
|
554
560
|
};
|
package/dist/senses/telegram.js
CHANGED
|
@@ -1605,6 +1605,7 @@ function createTelegramSenseApp(options) {
|
|
|
1605
1605
|
},
|
|
1606
1606
|
userMessage: message.text,
|
|
1607
1607
|
ingressRelations,
|
|
1608
|
+
...(message.replyToText ? { replyingTo: { text: message.replyToText, ...(message.replyToAuthor ? { author: message.replyToAuthor } : {}) } } : {}),
|
|
1608
1609
|
...(ingressReceipt ? { precommittedIngress: ingressReceipt } : {}),
|
|
1609
1610
|
turnMetricsObserver,
|
|
1610
1611
|
emptyResponseFallback: fullVisibility.emptyResponseFallback,
|
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.969",
|
|
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.969",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|