@ouro.bot/cli 0.1.0-alpha.750 → 0.1.0-alpha.752
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 +14 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +6 -6
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/attachments/sources/index.js +2 -0
- package/dist/heart/attachments/sources/telegram.js +71 -0
- package/dist/heart/attachments/store.js +31 -2
- package/dist/mind/prompt.js +5 -0
- package/dist/repertoire/tools-attachments.js +10 -5
- package/dist/repertoire/tools-steward-policy.js +8 -3
- package/dist/repertoire/tools-unraid.js +1 -0
- package/dist/repertoire/tools.js +6 -0
- package/dist/senses/sanctuary-media-optimization.js +436 -0
- package/dist/senses/sanctuary-runtime.js +32 -1
- package/dist/senses/telegram-admission.js +21 -5
- package/dist/senses/telegram-attachments.js +182 -0
- package/dist/senses/telegram-client.js +45 -4
- package/dist/senses/telegram-effect-adapter.js +5 -3
- package/dist/senses/telegram.js +59 -14
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,182 @@
|
|
|
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.MAX_TELEGRAM_ATTACHMENT_BYTES = void 0;
|
|
37
|
+
exports.ingestTelegramAttachments = ingestTelegramAttachments;
|
|
38
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const store_1 = require("../heart/attachments/store");
|
|
42
|
+
const store_2 = require("../heart/attachments/store");
|
|
43
|
+
const originals_1 = require("../heart/attachments/originals");
|
|
44
|
+
const originals_2 = require("../heart/attachments/originals");
|
|
45
|
+
const telegram_1 = require("../heart/attachments/sources/telegram");
|
|
46
|
+
const runtime_1 = require("../nerves/runtime");
|
|
47
|
+
exports.MAX_TELEGRAM_ATTACHMENT_BYTES = 20_000_000;
|
|
48
|
+
const MAX_TELEGRAM_ATTACHMENTS_PER_MESSAGE = 8;
|
|
49
|
+
const SAFE_FILE_PATH = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/-]{1,512}$/u;
|
|
50
|
+
async function readBounded(response, limit) {
|
|
51
|
+
const advertised = Number(response.headers.get("content-length"));
|
|
52
|
+
if (Number.isFinite(advertised) && advertised > limit)
|
|
53
|
+
throw new Error("advertised size exceeds limit");
|
|
54
|
+
if (!response.body)
|
|
55
|
+
throw new Error("empty response body");
|
|
56
|
+
const chunks = [];
|
|
57
|
+
let total = 0;
|
|
58
|
+
const reader = response.body.getReader();
|
|
59
|
+
try {
|
|
60
|
+
for (;;) {
|
|
61
|
+
const { done, value } = await reader.read();
|
|
62
|
+
if (done)
|
|
63
|
+
break;
|
|
64
|
+
total += value.byteLength;
|
|
65
|
+
if (total > limit)
|
|
66
|
+
throw new Error("download exceeds limit");
|
|
67
|
+
chunks.push(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await reader.cancel().catch(() => undefined);
|
|
72
|
+
}
|
|
73
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total);
|
|
74
|
+
}
|
|
75
|
+
async function atomicWrite(filePath, buffer) {
|
|
76
|
+
const directory = path.dirname(filePath);
|
|
77
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
78
|
+
const temporary = `${filePath}.${(0, node_crypto_1.randomUUID)()}.tmp`;
|
|
79
|
+
let handle;
|
|
80
|
+
try {
|
|
81
|
+
handle = await fs.open(temporary, "wx", 0o600);
|
|
82
|
+
await handle.writeFile(buffer);
|
|
83
|
+
await handle.sync();
|
|
84
|
+
await handle.close();
|
|
85
|
+
handle = undefined;
|
|
86
|
+
await fs.rename(temporary, filePath);
|
|
87
|
+
const directoryHandle = await fs.open(directory, "r");
|
|
88
|
+
try {
|
|
89
|
+
await directoryHandle.sync();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await directoryHandle.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await handle?.close().catch(() => undefined);
|
|
97
|
+
await fs.unlink(temporary).catch((error) => { if (error.code !== "ENOENT")
|
|
98
|
+
throw error; });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function ingestTelegramAttachments(input) {
|
|
102
|
+
const fetchImpl = input.fetch ?? globalThis.fetch;
|
|
103
|
+
const unique = [...new Map(input.attachments.map((attachment) => [attachment.fileId, attachment])).values()]
|
|
104
|
+
.slice(0, MAX_TELEGRAM_ATTACHMENTS_PER_MESSAGE);
|
|
105
|
+
const attachments = [];
|
|
106
|
+
const notices = [];
|
|
107
|
+
for (const candidate of unique) {
|
|
108
|
+
try {
|
|
109
|
+
if (!/^[A-Za-z0-9_-]{1,512}$/u.test(candidate.fileId))
|
|
110
|
+
throw new Error("invalid file id");
|
|
111
|
+
if (candidate.byteCount !== undefined && (!Number.isSafeInteger(candidate.byteCount) || candidate.byteCount < 0 || candidate.byteCount > exports.MAX_TELEGRAM_ATTACHMENT_BYTES))
|
|
112
|
+
throw new Error("advertised size exceeds limit");
|
|
113
|
+
const cached = (0, store_2.getRecentAttachment)(input.agentName, `attachment:telegram:${candidate.fileId}`, input.agentRoot);
|
|
114
|
+
if (cached?.source === "telegram") {
|
|
115
|
+
const localPath = cached.sourceData.localPath;
|
|
116
|
+
if (typeof localPath === "string") {
|
|
117
|
+
await (0, originals_2.ensureReadableFile)(localPath);
|
|
118
|
+
attachments.push(cached);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const recoverable = (0, telegram_1.buildTelegramAttachmentRecord)({
|
|
123
|
+
fileId: candidate.fileId,
|
|
124
|
+
displayName: candidate.displayName,
|
|
125
|
+
mimeType: candidate.mimeType,
|
|
126
|
+
byteCount: candidate.byteCount,
|
|
127
|
+
localPath: path.resolve(input.agentRoot, "state", "attachments", ".pending", candidate.fileId),
|
|
128
|
+
});
|
|
129
|
+
const recoverablePath = (0, originals_1.originalStoragePath)(input.agentRoot, recoverable, recoverable.mimeType);
|
|
130
|
+
try {
|
|
131
|
+
const stat = await fs.lstat(recoverablePath);
|
|
132
|
+
if (!stat.isFile() || stat.size > exports.MAX_TELEGRAM_ATTACHMENT_BYTES)
|
|
133
|
+
throw new Error("persisted attachment is invalid");
|
|
134
|
+
const recovered = (0, store_1.cacheRecentAttachment)(input.agentName, (0, telegram_1.buildTelegramAttachmentRecord)({
|
|
135
|
+
fileId: candidate.fileId,
|
|
136
|
+
displayName: candidate.displayName,
|
|
137
|
+
mimeType: candidate.mimeType,
|
|
138
|
+
byteCount: stat.size,
|
|
139
|
+
localPath: recoverablePath,
|
|
140
|
+
}), input.agentRoot);
|
|
141
|
+
attachments.push(recovered);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error.code !== "ENOENT")
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
const remote = await input.api.request("getFile", { file_id: candidate.fileId });
|
|
149
|
+
if (typeof remote.file_path !== "string" || !SAFE_FILE_PATH.test(remote.file_path))
|
|
150
|
+
throw new Error("invalid remote path");
|
|
151
|
+
if (remote.file_size !== undefined && (!Number.isSafeInteger(remote.file_size) || remote.file_size < 0 || remote.file_size > exports.MAX_TELEGRAM_ATTACHMENT_BYTES))
|
|
152
|
+
throw new Error("advertised size exceeds limit");
|
|
153
|
+
const response = await fetchImpl(`https://api.telegram.org/file/bot${input.botToken}/${remote.file_path}`, { signal: AbortSignal.timeout(30_000) });
|
|
154
|
+
if (!response.ok)
|
|
155
|
+
throw new Error(`download failed with HTTP ${response.status}`);
|
|
156
|
+
const buffer = await readBounded(response, exports.MAX_TELEGRAM_ATTACHMENT_BYTES);
|
|
157
|
+
const provisional = (0, telegram_1.buildTelegramAttachmentRecord)({
|
|
158
|
+
fileId: candidate.fileId,
|
|
159
|
+
displayName: candidate.displayName,
|
|
160
|
+
mimeType: candidate.mimeType ?? response.headers.get("content-type") ?? undefined,
|
|
161
|
+
byteCount: buffer.length,
|
|
162
|
+
localPath: path.resolve(input.agentRoot, "state", "attachments", ".pending", candidate.fileId),
|
|
163
|
+
});
|
|
164
|
+
const localPath = (0, originals_1.originalStoragePath)(input.agentRoot, provisional, provisional.mimeType);
|
|
165
|
+
await atomicWrite(localPath, buffer);
|
|
166
|
+
const record = (0, store_1.cacheRecentAttachment)(input.agentName, (0, telegram_1.buildTelegramAttachmentRecord)({
|
|
167
|
+
fileId: candidate.fileId,
|
|
168
|
+
displayName: candidate.displayName,
|
|
169
|
+
mimeType: provisional.mimeType,
|
|
170
|
+
byteCount: buffer.length,
|
|
171
|
+
localPath,
|
|
172
|
+
}), input.agentRoot);
|
|
173
|
+
attachments.push(record);
|
|
174
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.telegram_attachment_ingested", message: "Telegram attachment entered the shared attachment store", meta: { kind: record.kind, byteCount: buffer.length } });
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
notices.push(`attachment unavailable: ${candidate.displayName}`);
|
|
178
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.telegram_attachment_ingest_error", message: "Telegram attachment ingestion failed", meta: { kind: candidate.kind, reason: error instanceof Error ? error.name : "unknown" } });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { attachments, notices };
|
|
182
|
+
}
|
|
@@ -479,18 +479,56 @@ function createTelegramLongPoll(options) {
|
|
|
479
479
|
reject(signal.reason);
|
|
480
480
|
}, { once: true });
|
|
481
481
|
});
|
|
482
|
+
const inboundAttachments = (message) => {
|
|
483
|
+
const result = [];
|
|
484
|
+
const push = (media, kind, fallbackName, fallbackMimeType) => {
|
|
485
|
+
if (typeof media?.file_id !== "string" || !media.file_id.trim())
|
|
486
|
+
return;
|
|
487
|
+
result.push({
|
|
488
|
+
fileId: media.file_id,
|
|
489
|
+
kind,
|
|
490
|
+
displayName: media.file_name?.trim() || fallbackName,
|
|
491
|
+
...((media.mime_type?.trim() || fallbackMimeType) ? { mimeType: media.mime_type?.trim().toLowerCase() || fallbackMimeType } : {}),
|
|
492
|
+
...(Number.isSafeInteger(media.file_size) ? { byteCount: media.file_size } : {}),
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
const photo = message.photo?.filter((candidate) => typeof candidate.file_id === "string" && candidate.file_id.trim())
|
|
496
|
+
.sort((left, right) => (left.file_size ?? left.width ?? 0) - (right.file_size ?? right.width ?? 0)).at(-1);
|
|
497
|
+
if (photo)
|
|
498
|
+
result.push({ fileId: photo.file_id, kind: "image", displayName: "telegram-photo.jpg", mimeType: "image/jpeg", ...(Number.isSafeInteger(photo.file_size) ? { byteCount: photo.file_size } : {}) });
|
|
499
|
+
push(message.document, "document", "telegram-document");
|
|
500
|
+
push(message.audio, "audio", "telegram-audio", "audio/mpeg");
|
|
501
|
+
push(message.voice, "audio", "telegram-voice.ogg", "audio/ogg");
|
|
502
|
+
push(message.video, "binary", "telegram-video.mp4", "video/mp4");
|
|
503
|
+
push(message.animation, "binary", "telegram-animation.mp4", "video/mp4");
|
|
504
|
+
push(message.sticker, message.sticker?.is_animated || message.sticker?.is_video ? "binary" : "image", message.sticker?.is_animated ? "telegram-sticker.tgs" : message.sticker?.is_video ? "telegram-sticker.webm" : "telegram-sticker.webp", message.sticker?.is_animated ? "application/x-tgsticker" : message.sticker?.is_video ? "video/webm" : "image/webp");
|
|
505
|
+
return result;
|
|
506
|
+
};
|
|
507
|
+
const rawAttachmentCount = (message) => [
|
|
508
|
+
message.document, message.audio, message.video, message.voice, message.animation, message.sticker,
|
|
509
|
+
].filter(Boolean).length + (message.photo?.length ? 1 : 0);
|
|
482
510
|
const authorizedMessage = (update) => {
|
|
483
511
|
const message = update.message;
|
|
484
512
|
const userId = message?.from ? String(message.from.id) : "";
|
|
485
513
|
const chatId = message ? String(message.chat.id) : "";
|
|
486
|
-
if (!message || message.chat.type !== "private" || userId !== options.expectedUserId || chatId !== options.expectedChatId
|
|
514
|
+
if (!message || message.chat.type !== "private" || userId !== options.expectedUserId || chatId !== options.expectedChatId)
|
|
515
|
+
return null;
|
|
516
|
+
const attachments = inboundAttachments(message);
|
|
517
|
+
const attachmentCount = rawAttachmentCount(message);
|
|
518
|
+
const attachmentNotices = attachmentCount > attachments.length
|
|
519
|
+
? ["attachment unavailable: Telegram media metadata was incomplete"]
|
|
520
|
+
: [];
|
|
521
|
+
const text = message.text ?? message.caption ?? "";
|
|
522
|
+
if (typeof text !== "string" || (!text && attachmentCount === 0))
|
|
487
523
|
return null;
|
|
488
524
|
return {
|
|
489
525
|
updateId: update.update_id,
|
|
490
526
|
messageId: String(message.message_id),
|
|
491
527
|
userId,
|
|
492
528
|
chatId,
|
|
493
|
-
text
|
|
529
|
+
text,
|
|
530
|
+
attachments,
|
|
531
|
+
...(attachmentNotices.length ? { attachmentNotices } : {}),
|
|
494
532
|
...(Number.isSafeInteger(message.reply_to_message?.message_id) && message.reply_to_message.message_id > 0
|
|
495
533
|
? { replyToMessageId: String(message.reply_to_message.message_id) }
|
|
496
534
|
: {}),
|
|
@@ -506,6 +544,8 @@ function createTelegramLongPoll(options) {
|
|
|
506
544
|
return null;
|
|
507
545
|
const displayLabel = [message.from.first_name, message.from.last_name].filter(Boolean).join(" ")
|
|
508
546
|
|| (message.from.username ? `@${message.from.username}` : `Telegram user ${userId}`);
|
|
547
|
+
const attachments = inboundAttachments(message);
|
|
548
|
+
const attachmentCount = rawAttachmentCount(message);
|
|
509
549
|
return {
|
|
510
550
|
updateId: update.update_id,
|
|
511
551
|
messageId: message.message_id,
|
|
@@ -514,8 +554,9 @@ function createTelegramLongPoll(options) {
|
|
|
514
554
|
chatId,
|
|
515
555
|
text: message.text ?? message.caption ?? "",
|
|
516
556
|
displayLabel,
|
|
517
|
-
hasAttachments:
|
|
518
|
-
|
|
557
|
+
hasAttachments: attachmentCount > 0,
|
|
558
|
+
attachments,
|
|
559
|
+
...(attachmentCount > attachments.length ? { attachmentNotices: ["attachment unavailable: Telegram media metadata was incomplete"] } : {}),
|
|
519
560
|
};
|
|
520
561
|
};
|
|
521
562
|
const authorizedCallback = (update) => {
|
|
@@ -676,7 +676,7 @@ function appendTelegramInboundEvent(envelope, input) {
|
|
|
676
676
|
name: "telegram-user",
|
|
677
677
|
toolCallId: null,
|
|
678
678
|
toolCalls: [],
|
|
679
|
-
attachments: [],
|
|
679
|
+
attachments: [...new Set(input.attachmentIds ?? [])],
|
|
680
680
|
time: { authoredAt: null, authoredAtSource: "unknown", observedAt: input.recordedAt, observedAtSource: "ingest", recordedAt: input.recordedAt, recordedAtSource: "save" },
|
|
681
681
|
relations: {
|
|
682
682
|
replyToEventId: input.relations?.replyToEventId ?? null,
|
|
@@ -710,14 +710,16 @@ async function recordTelegramEffectsInSession(input) {
|
|
|
710
710
|
let changed = false;
|
|
711
711
|
if (input.inbound) {
|
|
712
712
|
const existing = envelope.events.filter((event) => event.relations.references.includes(input.inbound.reference));
|
|
713
|
-
|
|
713
|
+
const expectedAttachmentIds = [...new Set(input.inbound.attachmentIds ?? [])];
|
|
714
|
+
if (existing.length > 1 || (existing.length === 1 && (existing[0].role !== "user" || existing[0].content !== input.inbound.text
|
|
715
|
+
|| JSON.stringify(existing[0].attachments) !== JSON.stringify(expectedAttachmentIds)))) {
|
|
714
716
|
throw new Error("Telegram session has conflicting inbound ingress");
|
|
715
717
|
}
|
|
716
718
|
if (existing.length === 1) {
|
|
717
719
|
inboundReceipt = { eventId: existing[0].id, reference: input.inbound.reference };
|
|
718
720
|
}
|
|
719
721
|
else {
|
|
720
|
-
envelope = appendTelegramInboundEvent(envelope, { ...input.inbound, recordedAt: new Date().toISOString() });
|
|
722
|
+
envelope = appendTelegramInboundEvent(envelope, { ...input.inbound, attachmentIds: expectedAttachmentIds, recordedAt: new Date().toISOString() });
|
|
721
723
|
inboundReceipt = { eventId: envelope.events.at(-1).id, reference: input.inbound.reference };
|
|
722
724
|
changed = true;
|
|
723
725
|
}
|
package/dist/senses/telegram.js
CHANGED
|
@@ -78,6 +78,8 @@ const approval_store_1 = require("../heart/approval-store");
|
|
|
78
78
|
const tools_awaiting_1 = require("../repertoire/tools-awaiting");
|
|
79
79
|
const obligations_1 = require("../arc/obligations");
|
|
80
80
|
const orientation_frame_1 = require("../heart/orientation-frame");
|
|
81
|
+
const render_1 = require("../heart/attachments/render");
|
|
82
|
+
const telegram_attachments_1 = require("./telegram-attachments");
|
|
81
83
|
const pending_1 = require("../mind/pending");
|
|
82
84
|
const relationship_authorization_1 = require("../repertoire/relationship-authorization");
|
|
83
85
|
const telegram_admission_1 = require("./telegram-admission");
|
|
@@ -1094,9 +1096,11 @@ function createTelegramSenseApp(options) {
|
|
|
1094
1096
|
source: {
|
|
1095
1097
|
kind: "telegram_newly_admitted",
|
|
1096
1098
|
authority: "presentation_only",
|
|
1097
|
-
routingHint:
|
|
1098
|
-
|
|
1099
|
-
|
|
1099
|
+
routingHint: [
|
|
1100
|
+
"This is this person’s first admitted turn. Welcome them warmly and briefly explain what the household Butler can help with before answering their request.",
|
|
1101
|
+
input.orientation.relationship ? `The owner identified this person as their ${input.orientation.relationship}.` : null,
|
|
1102
|
+
input.orientation.attachmentsNeedResend ? "Their original message included attachments that were not downloaded; ask them to resend those attachments now." : null,
|
|
1103
|
+
].filter(Boolean).join(" "),
|
|
1100
1104
|
},
|
|
1101
1105
|
}
|
|
1102
1106
|
: undefined;
|
|
@@ -1110,7 +1114,7 @@ function createTelegramSenseApp(options) {
|
|
|
1110
1114
|
ingressRelations: { replyToEventId: null, threadRootEventId: null, references: [input.reference] },
|
|
1111
1115
|
precommittedIngress: { eventId: input.eventId, reference: input.reference },
|
|
1112
1116
|
...(orientationFrame ? { orientationFrame } : {}),
|
|
1113
|
-
toolContext: { ...(toolContext ?? {}) },
|
|
1117
|
+
toolContext: { ...(toolContext ?? {}), attachmentIds: input.attachmentIds ?? [] },
|
|
1114
1118
|
prepareRunAgentOptions: prepareRelationshipRunAgentOptions({ friendId: input.friendId, requestId: input.requestId,
|
|
1115
1119
|
sessionEventId: input.eventId, userId: input.userId, chatId: input.chatId, sessionKey: input.sessionKey }),
|
|
1116
1120
|
deliverySink: { onDelivery: (delivery) => deliver(delivery.text, delivery.kind === "settle") },
|
|
@@ -1202,7 +1206,26 @@ function createTelegramSenseApp(options) {
|
|
|
1202
1206
|
await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, target.friendId, "telegram", target.sessionKey, agentRoot), [artifact]);
|
|
1203
1207
|
}
|
|
1204
1208
|
};
|
|
1205
|
-
const
|
|
1209
|
+
const hydrateAuthorizedMessage = async (message) => {
|
|
1210
|
+
if (!message.attachments?.length)
|
|
1211
|
+
return { text: [message.text, ...(message.attachmentNotices ?? [])].filter(Boolean).join("\n"), attachmentIds: [] };
|
|
1212
|
+
const ingested = await (0, telegram_attachments_1.ingestTelegramAttachments)({
|
|
1213
|
+
agentName: options.agentName,
|
|
1214
|
+
agentRoot,
|
|
1215
|
+
botToken,
|
|
1216
|
+
api,
|
|
1217
|
+
...(options.attachmentFetch ? { fetch: options.attachmentFetch } : {}),
|
|
1218
|
+
attachments: message.attachments ?? [],
|
|
1219
|
+
});
|
|
1220
|
+
const attachmentIds = ingested.attachments.map((attachment) => attachment.id);
|
|
1221
|
+
return {
|
|
1222
|
+
text: [message.text, (0, render_1.renderAttachmentBlock)(ingested.attachments), ...(message.attachmentNotices ?? []), ...ingested.notices].filter(Boolean).join("\n"),
|
|
1223
|
+
attachmentIds,
|
|
1224
|
+
};
|
|
1225
|
+
};
|
|
1226
|
+
const onMessageBody = async (rawMessage) => {
|
|
1227
|
+
const hydrated = await hydrateAuthorizedMessage(rawMessage);
|
|
1228
|
+
const message = { ...rawMessage, text: hydrated.text };
|
|
1206
1229
|
const currentFriendId = configuredOwnerFriendId;
|
|
1207
1230
|
const currentSessionKey = configuredOwnerSessionKey;
|
|
1208
1231
|
const currentSessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, currentFriendId, "telegram", currentSessionKey, agentRoot);
|
|
@@ -1268,7 +1291,7 @@ function createTelegramSenseApp(options) {
|
|
|
1268
1291
|
store: getEffectJournal(),
|
|
1269
1292
|
sessionPath: currentSessionPath,
|
|
1270
1293
|
artifacts: [],
|
|
1271
|
-
inbound: { text: message.text, reference: inboundReference, relations: ingressRelations },
|
|
1294
|
+
inbound: { text: message.text, reference: inboundReference, attachmentIds: hydrated.attachmentIds, relations: ingressRelations },
|
|
1272
1295
|
})
|
|
1273
1296
|
: null;
|
|
1274
1297
|
const collected = await collectToolReceipts(() => runTurn({
|
|
@@ -1297,7 +1320,9 @@ function createTelegramSenseApp(options) {
|
|
|
1297
1320
|
}
|
|
1298
1321
|
},
|
|
1299
1322
|
},
|
|
1300
|
-
...(toolContext
|
|
1323
|
+
...(toolContext || options.resolveRelationshipAuthorization ? {
|
|
1324
|
+
toolContext: { ...(toolContext ?? {}), ...(options.resolveRelationshipAuthorization ? { attachmentIds: hydrated.attachmentIds } : {}) },
|
|
1325
|
+
} : {}),
|
|
1301
1326
|
...(options.resolveRelationshipAuthorization ? {
|
|
1302
1327
|
prepareRunAgentOptions: prepareRelationshipRunAgentOptions({
|
|
1303
1328
|
friendId: currentFriendId,
|
|
@@ -1369,7 +1394,7 @@ function createTelegramSenseApp(options) {
|
|
|
1369
1394
|
const fallback = "I couldn't complete that turn. The failure was recorded; please try again.";
|
|
1370
1395
|
if (deliveredMessageIds.length === 0)
|
|
1371
1396
|
turnEffects.push(await deliverButlerEffect(fallback, `turn:${subject}:${message.updateId}:fallback`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }));
|
|
1372
|
-
await recordAcceptedEffects(currentSessionPath, turnEffects, { text: message.text, reference: inboundReference });
|
|
1397
|
+
await recordAcceptedEffects(currentSessionPath, turnEffects, { text: message.text, reference: inboundReference, attachmentIds: hydrated.attachmentIds });
|
|
1373
1398
|
}
|
|
1374
1399
|
finally {
|
|
1375
1400
|
if (acceptanceMarker) {
|
|
@@ -1412,10 +1437,10 @@ function createTelegramSenseApp(options) {
|
|
|
1412
1437
|
}
|
|
1413
1438
|
};
|
|
1414
1439
|
const onMessage = (message) => runWithAcceptanceAuditOwner(async () => {
|
|
1415
|
-
const ownerDecision = admissionController?.parseOwnerDecision({
|
|
1440
|
+
const ownerDecision = !message.attachments?.length && !message.attachmentNotices?.length ? admissionController?.parseOwnerDecision({
|
|
1416
1441
|
text: message.text,
|
|
1417
1442
|
...(message.replyToMessageId ? { replyToMessageId: Number(message.replyToMessageId) } : {}),
|
|
1418
|
-
});
|
|
1443
|
+
}) : null;
|
|
1419
1444
|
if (ownerDecision) {
|
|
1420
1445
|
const actor = await options.admission?.resolveOwner({ botId: botId, userId: message.userId, chatId: message.chatId, sessionKey: configuredOwnerSessionKey });
|
|
1421
1446
|
if (!actor)
|
|
@@ -1423,6 +1448,11 @@ function createTelegramSenseApp(options) {
|
|
|
1423
1448
|
await admissionController.decide({ ...ownerDecision, actorFriendId: actor.friendId });
|
|
1424
1449
|
return;
|
|
1425
1450
|
}
|
|
1451
|
+
if ((message.attachments?.length || message.attachmentNotices?.length) && options.admission) {
|
|
1452
|
+
const actor = await options.admission.resolveOwner({ botId: botId, userId: message.userId, chatId: message.chatId, sessionKey: configuredOwnerSessionKey });
|
|
1453
|
+
if (!actor || actor.friendId !== configuredOwnerFriendId)
|
|
1454
|
+
throw new Error("Telegram attachment owner relationship is not active");
|
|
1455
|
+
}
|
|
1426
1456
|
await onMessageBody(message);
|
|
1427
1457
|
});
|
|
1428
1458
|
const onUnknownMessage = admissionController && options.admission ? async (message) => {
|
|
@@ -1431,12 +1461,21 @@ function createTelegramSenseApp(options) {
|
|
|
1431
1461
|
await admissionController.handleUnknown(message);
|
|
1432
1462
|
return;
|
|
1433
1463
|
}
|
|
1464
|
+
const hydrated = await hydrateAuthorizedMessage({
|
|
1465
|
+
updateId: message.updateId,
|
|
1466
|
+
messageId: String(message.messageId),
|
|
1467
|
+
userId: message.userId,
|
|
1468
|
+
chatId: message.chatId,
|
|
1469
|
+
text: message.text,
|
|
1470
|
+
attachments: message.attachments,
|
|
1471
|
+
attachmentNotices: message.attachmentNotices,
|
|
1472
|
+
});
|
|
1434
1473
|
const sessionKey = `telegram:${message.botId}:${message.userId}`;
|
|
1435
1474
|
const reference = `telegram-inbound:${(0, node_crypto_1.createHmac)("sha256", identityKey).update(`${message.botId}\0${message.userId}\0${message.updateId}\0${message.messageId}`).digest("hex")}`;
|
|
1436
1475
|
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, approved.friendId, "telegram", sessionKey, agentRoot);
|
|
1437
|
-
const receipt = await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts: [], inbound: { text:
|
|
1476
|
+
const receipt = await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts: [], inbound: { text: hydrated.text, reference, attachmentIds: hydrated.attachmentIds } });
|
|
1438
1477
|
await dispatchResolvedRelationshipTurn({ friendId: approved.friendId, sessionKey, requestId: reference, eventId: receipt.eventId,
|
|
1439
|
-
reference, text:
|
|
1478
|
+
reference, text: hydrated.text, attachmentIds: hydrated.attachmentIds, userId: message.userId, chatId: message.chatId });
|
|
1440
1479
|
} : undefined;
|
|
1441
1480
|
const onUpdate = async (update) => {
|
|
1442
1481
|
const callback = update.callback_query;
|
|
@@ -1812,8 +1851,14 @@ async function createProductionTelegramRelationshipComposition(agentName, creden
|
|
|
1812
1851
|
target: { kind: "create", record: { id: (0, node_crypto_1.randomUUID)(), name: "Household member" } },
|
|
1813
1852
|
});
|
|
1814
1853
|
const current = claimed.record;
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1854
|
+
const now = new Date().toISOString();
|
|
1855
|
+
const connections = input.relationship
|
|
1856
|
+
? [{ name: owner.name, relationship: input.relationship }, ...(current.connections ?? []).filter((connection) => !(connection.name === owner.name && connection.relationship === input.relationship))]
|
|
1857
|
+
: current.connections;
|
|
1858
|
+
const updated = { ...current,
|
|
1859
|
+
...(connections ? { connections } : {}),
|
|
1860
|
+
trustLevel: input.defaults.trustLevel, admissionState: input.defaults.admissionState,
|
|
1861
|
+
initiativePolicy: input.defaults.initiativePolicy, capabilityProfileId: input.defaults.capabilityProfileId, updatedAt: now };
|
|
1817
1862
|
await store.put(current.id, updated);
|
|
1818
1863
|
return { kind: claimed.status === "created" ? "created" : "existing", friendId: current.id };
|
|
1819
1864
|
},
|
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.752",
|
|
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.752",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|