openmates 0.11.0-alpha.14 → 0.11.0-alpha.16
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/dist/{chunk-NQ3NCRIL.js → chunk-TUPR2SL3.js} +509 -190
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/index.js +1 -1
- package/package.json +4 -2
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-SFTCIVE2.js";
|
|
4
4
|
|
|
5
5
|
// src/client.ts
|
|
6
|
-
import { randomUUID } from "crypto";
|
|
6
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7
7
|
import { platform as platform2, release } from "os";
|
|
8
8
|
import { createInterface } from "readline/promises";
|
|
9
9
|
import { stdin, stdout } from "process";
|
|
@@ -726,6 +726,12 @@ function loadSyncCache() {
|
|
|
726
726
|
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
727
727
|
return readJsonFile(filePath);
|
|
728
728
|
}
|
|
729
|
+
function clearSyncCache() {
|
|
730
|
+
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
731
|
+
if (existsSync2(filePath)) {
|
|
732
|
+
rmSync(filePath);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
729
735
|
function isSyncCacheFresh(maxAgeMs = 3e5) {
|
|
730
736
|
const cache = loadSyncCache();
|
|
731
737
|
if (!cache) return false;
|
|
@@ -733,7 +739,9 @@ function isSyncCacheFresh(maxAgeMs = 3e5) {
|
|
|
733
739
|
}
|
|
734
740
|
|
|
735
741
|
// src/ws.ts
|
|
736
|
-
import
|
|
742
|
+
import { createRequire } from "module";
|
|
743
|
+
var require2 = createRequire(import.meta.url);
|
|
744
|
+
var WebSocket = require2("ws");
|
|
737
745
|
var OpenMatesWsClient = class {
|
|
738
746
|
socket;
|
|
739
747
|
constructor(options) {
|
|
@@ -793,6 +801,14 @@ var OpenMatesWsClient = class {
|
|
|
793
801
|
send(type, payload) {
|
|
794
802
|
this.socket.send(JSON.stringify({ type, payload }));
|
|
795
803
|
}
|
|
804
|
+
sendAsync(type, payload) {
|
|
805
|
+
return new Promise((resolve4, reject) => {
|
|
806
|
+
this.socket.send(JSON.stringify({ type, payload }), (error) => {
|
|
807
|
+
if (error) reject(error);
|
|
808
|
+
else resolve4();
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
}
|
|
796
812
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
797
813
|
return new Promise((resolve4, reject) => {
|
|
798
814
|
const onMessage = (rawData) => {
|
|
@@ -895,35 +911,83 @@ var OpenMatesWsClient = class {
|
|
|
895
911
|
collectAiResponse(userMessageId, chatId, options) {
|
|
896
912
|
const timeoutMs = options?.timeoutMs ?? 9e4;
|
|
897
913
|
const onStream = options?.onStream;
|
|
914
|
+
const asyncEmbedWaitMs = options?.asyncEmbedWaitMs ?? 12e4;
|
|
898
915
|
return new Promise((resolve4, reject) => {
|
|
899
916
|
let latestContent = "";
|
|
917
|
+
let messageId = null;
|
|
918
|
+
let taskId = null;
|
|
900
919
|
let category = null;
|
|
901
920
|
let modelName = null;
|
|
902
921
|
let followUpSuggestions = [];
|
|
922
|
+
const embeds = /* @__PURE__ */ new Map();
|
|
923
|
+
const processingEmbedIds = /* @__PURE__ */ new Set();
|
|
903
924
|
let aiResponseDone = false;
|
|
925
|
+
let postProcessingDone = false;
|
|
904
926
|
const POST_PROCESSING_WINDOW_MS = 12e3;
|
|
905
927
|
let postProcessingTimer = null;
|
|
928
|
+
let asyncEmbedTimer = null;
|
|
906
929
|
const timeout = setTimeout(() => {
|
|
907
930
|
cleanup();
|
|
908
931
|
reject(new Error("Timed out waiting for AI response"));
|
|
909
932
|
}, timeoutMs);
|
|
910
933
|
const capture = (p) => {
|
|
934
|
+
if (typeof p.message_id === "string" && p.message_id)
|
|
935
|
+
messageId = p.message_id;
|
|
936
|
+
if (typeof p.task_id === "string" && p.task_id) taskId = p.task_id;
|
|
911
937
|
if (typeof p.category === "string" && p.category) category = p.category;
|
|
912
938
|
if (typeof p.model_name === "string" && p.model_name)
|
|
913
939
|
modelName = p.model_name;
|
|
914
940
|
};
|
|
941
|
+
const extractMessageContent = (message) => {
|
|
942
|
+
if (typeof message.content === "string") return message.content;
|
|
943
|
+
const content = message.content;
|
|
944
|
+
if (!content || typeof content !== "object") return "";
|
|
945
|
+
try {
|
|
946
|
+
const root = content;
|
|
947
|
+
const text = root.content?.[0]?.content?.[0]?.text;
|
|
948
|
+
return typeof text === "string" ? text : "";
|
|
949
|
+
} catch {
|
|
950
|
+
return "";
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
const finishPostProcessingWait = () => {
|
|
954
|
+
postProcessingDone = true;
|
|
955
|
+
maybeResolve();
|
|
956
|
+
};
|
|
957
|
+
const maybeResolve = () => {
|
|
958
|
+
if (!aiResponseDone || !postProcessingDone) return;
|
|
959
|
+
if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
|
|
960
|
+
asyncEmbedTimer = setTimeout(() => {
|
|
961
|
+
cleanup();
|
|
962
|
+
resolve4({
|
|
963
|
+
messageId,
|
|
964
|
+
taskId,
|
|
965
|
+
content: latestContent,
|
|
966
|
+
category,
|
|
967
|
+
modelName,
|
|
968
|
+
followUpSuggestions,
|
|
969
|
+
embeds: [...embeds.values()]
|
|
970
|
+
});
|
|
971
|
+
}, asyncEmbedWaitMs);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (processingEmbedIds.size > 0) return;
|
|
975
|
+
cleanup();
|
|
976
|
+
resolve4({
|
|
977
|
+
messageId,
|
|
978
|
+
taskId,
|
|
979
|
+
content: latestContent,
|
|
980
|
+
category,
|
|
981
|
+
modelName,
|
|
982
|
+
followUpSuggestions,
|
|
983
|
+
embeds: [...embeds.values()]
|
|
984
|
+
});
|
|
985
|
+
};
|
|
915
986
|
const scheduleResolve = (content) => {
|
|
916
987
|
aiResponseDone = true;
|
|
917
988
|
latestContent = content;
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
resolve4({
|
|
921
|
-
content: latestContent,
|
|
922
|
-
category,
|
|
923
|
-
modelName,
|
|
924
|
-
followUpSuggestions
|
|
925
|
-
});
|
|
926
|
-
}, POST_PROCESSING_WINDOW_MS);
|
|
989
|
+
clearTimeout(timeout);
|
|
990
|
+
postProcessingTimer = setTimeout(finishPostProcessingWait, POST_PROCESSING_WINDOW_MS);
|
|
927
991
|
};
|
|
928
992
|
const onMessage = (rawData) => {
|
|
929
993
|
try {
|
|
@@ -939,9 +1003,27 @@ var OpenMatesWsClient = class {
|
|
|
939
1003
|
);
|
|
940
1004
|
return;
|
|
941
1005
|
}
|
|
1006
|
+
if (type === "send_embed_data") {
|
|
1007
|
+
const embedPayload = p.payload && typeof p.payload === "object" ? p.payload : p;
|
|
1008
|
+
if (typeof embedPayload.chat_id === "string" && embedPayload.chat_id !== chatId) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const embedId = embedPayload.embed_id;
|
|
1012
|
+
if (typeof embedId === "string" && embedId) {
|
|
1013
|
+
const status = typeof embedPayload.status === "string" ? embedPayload.status : "finished";
|
|
1014
|
+
embeds.set(embedId, embedPayload);
|
|
1015
|
+
if (status === "processing") {
|
|
1016
|
+
processingEmbedIds.add(embedId);
|
|
1017
|
+
} else {
|
|
1018
|
+
processingEmbedIds.delete(embedId);
|
|
1019
|
+
}
|
|
1020
|
+
maybeResolve();
|
|
1021
|
+
}
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
942
1024
|
if (type === "ai_message_update") {
|
|
943
1025
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
944
|
-
if (msgId !== userMessageId) return;
|
|
1026
|
+
if (msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
945
1027
|
capture(p);
|
|
946
1028
|
if (typeof p.full_content_so_far === "string") {
|
|
947
1029
|
latestContent = p.full_content_so_far;
|
|
@@ -966,7 +1048,7 @@ var OpenMatesWsClient = class {
|
|
|
966
1048
|
}
|
|
967
1049
|
if (type === "ai_background_response_completed") {
|
|
968
1050
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
969
|
-
if (msgId && msgId !== userMessageId) return;
|
|
1051
|
+
if (msgId && msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
970
1052
|
if (!msgId && p.chat_id !== chatId) return;
|
|
971
1053
|
capture(p);
|
|
972
1054
|
const content = typeof p.full_content === "string" ? p.full_content : latestContent;
|
|
@@ -974,6 +1056,25 @@ var OpenMatesWsClient = class {
|
|
|
974
1056
|
scheduleResolve(content);
|
|
975
1057
|
return;
|
|
976
1058
|
}
|
|
1059
|
+
if (type === "chat_message_added") {
|
|
1060
|
+
if (p.chat_id !== chatId) return;
|
|
1061
|
+
const rawMessage = p.message;
|
|
1062
|
+
if (!rawMessage || typeof rawMessage !== "object") return;
|
|
1063
|
+
const message = rawMessage;
|
|
1064
|
+
if (message.role !== "assistant") return;
|
|
1065
|
+
const content = extractMessageContent(message);
|
|
1066
|
+
if (!content) return;
|
|
1067
|
+
capture(message);
|
|
1068
|
+
if (typeof message.category === "string" && message.category) {
|
|
1069
|
+
category = message.category;
|
|
1070
|
+
}
|
|
1071
|
+
if (typeof message.model_name === "string" && message.model_name) {
|
|
1072
|
+
modelName = message.model_name;
|
|
1073
|
+
}
|
|
1074
|
+
onStream?.({ kind: "done", content, category, modelName });
|
|
1075
|
+
scheduleResolve(content);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
977
1078
|
if (type === "ai_typing_started") {
|
|
978
1079
|
capture(p);
|
|
979
1080
|
onStream?.({
|
|
@@ -997,13 +1098,7 @@ var OpenMatesWsClient = class {
|
|
|
997
1098
|
clearTimeout(postProcessingTimer);
|
|
998
1099
|
postProcessingTimer = null;
|
|
999
1100
|
}
|
|
1000
|
-
|
|
1001
|
-
resolve4({
|
|
1002
|
-
content: latestContent,
|
|
1003
|
-
category,
|
|
1004
|
-
modelName,
|
|
1005
|
-
followUpSuggestions
|
|
1006
|
-
});
|
|
1101
|
+
finishPostProcessingWait();
|
|
1007
1102
|
}
|
|
1008
1103
|
return;
|
|
1009
1104
|
}
|
|
@@ -1018,10 +1113,13 @@ var OpenMatesWsClient = class {
|
|
|
1018
1113
|
if (aiResponseDone) {
|
|
1019
1114
|
cleanup();
|
|
1020
1115
|
resolve4({
|
|
1116
|
+
messageId,
|
|
1117
|
+
taskId,
|
|
1021
1118
|
content: latestContent,
|
|
1022
1119
|
category,
|
|
1023
1120
|
modelName,
|
|
1024
|
-
followUpSuggestions
|
|
1121
|
+
followUpSuggestions,
|
|
1122
|
+
embeds: [...embeds.values()]
|
|
1025
1123
|
});
|
|
1026
1124
|
return;
|
|
1027
1125
|
}
|
|
@@ -1034,6 +1132,10 @@ var OpenMatesWsClient = class {
|
|
|
1034
1132
|
clearTimeout(postProcessingTimer);
|
|
1035
1133
|
postProcessingTimer = null;
|
|
1036
1134
|
}
|
|
1135
|
+
if (asyncEmbedTimer) {
|
|
1136
|
+
clearTimeout(asyncEmbedTimer);
|
|
1137
|
+
asyncEmbedTimer = null;
|
|
1138
|
+
}
|
|
1037
1139
|
this.socket.off("message", onMessage);
|
|
1038
1140
|
this.socket.off("error", onError);
|
|
1039
1141
|
this.socket.off("close", onClose);
|
|
@@ -1345,9 +1447,139 @@ function escapeRegExp(s) {
|
|
|
1345
1447
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1346
1448
|
}
|
|
1347
1449
|
|
|
1450
|
+
// src/embedCreator.ts
|
|
1451
|
+
import { randomUUID, createHash as createHash3, randomBytes, webcrypto as webcrypto3 } from "crypto";
|
|
1452
|
+
import { encode as toonEncode } from "@toon-format/toon";
|
|
1453
|
+
var cryptoApi3 = webcrypto3;
|
|
1454
|
+
var AES_GCM_IV_LENGTH3 = 12;
|
|
1455
|
+
function bytesToBase642(input) {
|
|
1456
|
+
return Buffer.from(input).toString("base64");
|
|
1457
|
+
}
|
|
1458
|
+
function toArrayBuffer2(input) {
|
|
1459
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
1460
|
+
}
|
|
1461
|
+
async function encryptAesGcm(plaintext, rawKeyBytes) {
|
|
1462
|
+
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
1463
|
+
const key = await cryptoApi3.subtle.importKey(
|
|
1464
|
+
"raw",
|
|
1465
|
+
toArrayBuffer2(rawKeyBytes),
|
|
1466
|
+
{ name: "AES-GCM" },
|
|
1467
|
+
false,
|
|
1468
|
+
["encrypt"]
|
|
1469
|
+
);
|
|
1470
|
+
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
1471
|
+
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
1472
|
+
key,
|
|
1473
|
+
new TextEncoder().encode(plaintext)
|
|
1474
|
+
);
|
|
1475
|
+
const cipherBytes = new Uint8Array(encrypted);
|
|
1476
|
+
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
1477
|
+
combined.set(iv);
|
|
1478
|
+
combined.set(cipherBytes, iv.length);
|
|
1479
|
+
return bytesToBase642(combined);
|
|
1480
|
+
}
|
|
1481
|
+
async function wrapKey(embedKey, wrappingKey) {
|
|
1482
|
+
const cryptoKey = await cryptoApi3.subtle.importKey(
|
|
1483
|
+
"raw",
|
|
1484
|
+
toArrayBuffer2(wrappingKey),
|
|
1485
|
+
{ name: "AES-GCM" },
|
|
1486
|
+
false,
|
|
1487
|
+
["encrypt"]
|
|
1488
|
+
);
|
|
1489
|
+
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
1490
|
+
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
1491
|
+
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
1492
|
+
cryptoKey,
|
|
1493
|
+
toArrayBuffer2(embedKey)
|
|
1494
|
+
);
|
|
1495
|
+
const cipherBytes = new Uint8Array(encrypted);
|
|
1496
|
+
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
1497
|
+
combined.set(iv);
|
|
1498
|
+
combined.set(cipherBytes, iv.length);
|
|
1499
|
+
return bytesToBase642(combined);
|
|
1500
|
+
}
|
|
1501
|
+
function generateEmbedKey() {
|
|
1502
|
+
return new Uint8Array(randomBytes(32));
|
|
1503
|
+
}
|
|
1504
|
+
function computeSHA256(content) {
|
|
1505
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
1506
|
+
}
|
|
1507
|
+
function toonEncodeContent(data) {
|
|
1508
|
+
try {
|
|
1509
|
+
return toonEncode(data);
|
|
1510
|
+
} catch {
|
|
1511
|
+
return JSON.stringify(data);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
function generateEmbedId() {
|
|
1515
|
+
return randomUUID();
|
|
1516
|
+
}
|
|
1517
|
+
function createEmbedReferenceBlock(type, embedId, url) {
|
|
1518
|
+
const data = { type, embed_id: embedId };
|
|
1519
|
+
if (url) data.url = url;
|
|
1520
|
+
const ref = JSON.stringify(data, null, 2);
|
|
1521
|
+
return "```json\n" + ref + "\n```";
|
|
1522
|
+
}
|
|
1523
|
+
async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId) {
|
|
1524
|
+
try {
|
|
1525
|
+
const embedKey = generateEmbedKey();
|
|
1526
|
+
const encryptedContent = await encryptAesGcm(embed.content, embedKey);
|
|
1527
|
+
const encryptedType = await encryptAesGcm(embed.type, embedKey);
|
|
1528
|
+
const encryptedTextPreview = await encryptAesGcm(embed.textPreview, embedKey);
|
|
1529
|
+
const hashedEmbedId = computeSHA256(embed.embedId);
|
|
1530
|
+
const hashedChatId = computeSHA256(chatId);
|
|
1531
|
+
const hashedMessageId = computeSHA256(messageId);
|
|
1532
|
+
const hashedUserId = computeSHA256(userId);
|
|
1533
|
+
const wrappedWithMaster = await wrapKey(embedKey, masterKey);
|
|
1534
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
1535
|
+
const embedKeys = [
|
|
1536
|
+
{
|
|
1537
|
+
hashed_embed_id: hashedEmbedId,
|
|
1538
|
+
key_type: "master",
|
|
1539
|
+
hashed_chat_id: null,
|
|
1540
|
+
encrypted_embed_key: wrappedWithMaster,
|
|
1541
|
+
hashed_user_id: hashedUserId,
|
|
1542
|
+
created_at: nowSeconds
|
|
1543
|
+
}
|
|
1544
|
+
];
|
|
1545
|
+
if (chatKey) {
|
|
1546
|
+
const wrappedWithChat = await wrapKey(embedKey, chatKey);
|
|
1547
|
+
embedKeys.push({
|
|
1548
|
+
hashed_embed_id: hashedEmbedId,
|
|
1549
|
+
key_type: "chat",
|
|
1550
|
+
hashed_chat_id: hashedChatId,
|
|
1551
|
+
encrypted_embed_key: wrappedWithChat,
|
|
1552
|
+
hashed_user_id: hashedUserId,
|
|
1553
|
+
created_at: nowSeconds
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
return {
|
|
1557
|
+
embed_id: embed.embedId,
|
|
1558
|
+
encrypted_type: encryptedType,
|
|
1559
|
+
encrypted_content: encryptedContent,
|
|
1560
|
+
encrypted_text_preview: encryptedTextPreview,
|
|
1561
|
+
status: embed.status,
|
|
1562
|
+
hashed_chat_id: hashedChatId,
|
|
1563
|
+
hashed_message_id: hashedMessageId,
|
|
1564
|
+
hashed_user_id: hashedUserId,
|
|
1565
|
+
file_path: embed.filePath,
|
|
1566
|
+
content_hash: embed.contentHash,
|
|
1567
|
+
text_length_chars: embed.textLengthChars,
|
|
1568
|
+
created_at: nowSeconds,
|
|
1569
|
+
updated_at: nowSeconds,
|
|
1570
|
+
embed_keys: embedKeys
|
|
1571
|
+
};
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1574
|
+
process.stderr.write(`\x1B[31mError:\x1B[0m Failed to encrypt embed: ${msg}
|
|
1575
|
+
`);
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1348
1580
|
// src/shareEncryption.ts
|
|
1349
|
-
import { webcrypto as
|
|
1350
|
-
var crypto =
|
|
1581
|
+
import { webcrypto as webcrypto4 } from "crypto";
|
|
1582
|
+
var crypto = webcrypto4;
|
|
1351
1583
|
function base64UrlEncode(data) {
|
|
1352
1584
|
return Buffer.from(data).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1353
1585
|
}
|
|
@@ -1459,6 +1691,12 @@ function buildEmbedShareUrl(origin, embedId, blob) {
|
|
|
1459
1691
|
}
|
|
1460
1692
|
|
|
1461
1693
|
// src/client.ts
|
|
1694
|
+
function normalizeUnixSeconds(value, fallback) {
|
|
1695
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
1696
|
+
return fallback;
|
|
1697
|
+
}
|
|
1698
|
+
return value > 1e10 ? Math.floor(value / 1e3) : Math.floor(value);
|
|
1699
|
+
}
|
|
1462
1700
|
var MEMORY_TYPE_REGISTRY = {
|
|
1463
1701
|
"ai/communication_style": {
|
|
1464
1702
|
appId: "ai",
|
|
@@ -1892,7 +2130,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1892
2130
|
pin: pin.trim().toUpperCase(),
|
|
1893
2131
|
token
|
|
1894
2132
|
});
|
|
1895
|
-
const sessionId =
|
|
2133
|
+
const sessionId = randomUUID2();
|
|
1896
2134
|
const login = await this.http.post(
|
|
1897
2135
|
"/v1/auth/login",
|
|
1898
2136
|
{
|
|
@@ -2387,7 +2625,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2387
2625
|
async sendMessage(params) {
|
|
2388
2626
|
let chatId;
|
|
2389
2627
|
if (!params.chatId) {
|
|
2390
|
-
chatId =
|
|
2628
|
+
chatId = randomUUID2();
|
|
2391
2629
|
} else if (params.chatId.length < 36) {
|
|
2392
2630
|
const resolved = await this.resolveFullChatId(params.chatId);
|
|
2393
2631
|
if (!resolved) {
|
|
@@ -2400,7 +2638,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2400
2638
|
chatId = params.chatId;
|
|
2401
2639
|
}
|
|
2402
2640
|
const { ws, session } = await this.openWsClient();
|
|
2403
|
-
const messageId =
|
|
2641
|
+
const messageId = randomUUID2();
|
|
2404
2642
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
2405
2643
|
const isNewChat = !params.chatId;
|
|
2406
2644
|
ws.send("set_active_chat", { chat_id: chatId });
|
|
@@ -2420,6 +2658,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2420
2658
|
};
|
|
2421
2659
|
let chatKeyBytes = null;
|
|
2422
2660
|
let encryptedChatKey = null;
|
|
2661
|
+
let baselineMessagesV = 0;
|
|
2423
2662
|
if (!params.incognito) {
|
|
2424
2663
|
const masterKey = this.getMasterKeyBytes();
|
|
2425
2664
|
if (isNewChat) {
|
|
@@ -2439,6 +2678,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2439
2678
|
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
2440
2679
|
);
|
|
2441
2680
|
if (chat) {
|
|
2681
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
2442
2682
|
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
2443
2683
|
if (encKey) {
|
|
2444
2684
|
chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
|
|
@@ -2447,8 +2687,23 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2447
2687
|
}
|
|
2448
2688
|
}
|
|
2449
2689
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2690
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
2691
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
2692
|
+
const masterKey = this.getMasterKeyBytes();
|
|
2693
|
+
for (const embed of params.preparedEmbeds) {
|
|
2694
|
+
const encrypted = await encryptEmbed(
|
|
2695
|
+
embed,
|
|
2696
|
+
masterKey,
|
|
2697
|
+
chatKeyBytes,
|
|
2698
|
+
chatId,
|
|
2699
|
+
messageId,
|
|
2700
|
+
session.hashedEmail
|
|
2701
|
+
);
|
|
2702
|
+
if (encrypted) encryptedEmbeds.push(encrypted);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
if (encryptedEmbeds.length > 0) {
|
|
2706
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
2452
2707
|
}
|
|
2453
2708
|
ws.send("chat_message_added", messagePayload);
|
|
2454
2709
|
if (!params.incognito && chatKeyBytes) {
|
|
@@ -2476,6 +2731,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2476
2731
|
ws.send("encrypted_chat_metadata", metadataPayload);
|
|
2477
2732
|
}
|
|
2478
2733
|
let assistant = "";
|
|
2734
|
+
let assistantMessageId = null;
|
|
2479
2735
|
let category = null;
|
|
2480
2736
|
let modelName = null;
|
|
2481
2737
|
let followUpSuggestions = [];
|
|
@@ -2483,6 +2739,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2483
2739
|
if (params.incognito) {
|
|
2484
2740
|
try {
|
|
2485
2741
|
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
2742
|
+
assistantMessageId = resp.messageId;
|
|
2486
2743
|
assistant = resp.content;
|
|
2487
2744
|
category = resp.category;
|
|
2488
2745
|
modelName = resp.modelName;
|
|
@@ -2492,10 +2749,55 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2492
2749
|
} else {
|
|
2493
2750
|
try {
|
|
2494
2751
|
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
2752
|
+
assistantMessageId = resp.messageId;
|
|
2495
2753
|
assistant = resp.content;
|
|
2496
2754
|
category = resp.category;
|
|
2497
2755
|
modelName = resp.modelName;
|
|
2498
2756
|
followUpSuggestions = resp.followUpSuggestions;
|
|
2757
|
+
if (chatKeyBytes && assistant) {
|
|
2758
|
+
const completedAt = Math.floor(Date.now() / 1e3);
|
|
2759
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
2760
|
+
assistant,
|
|
2761
|
+
chatKeyBytes
|
|
2762
|
+
);
|
|
2763
|
+
const encryptedCategory = category ? await encryptWithAesGcmCombined(category, chatKeyBytes) : void 0;
|
|
2764
|
+
const encryptedModelName = modelName ? await encryptWithAesGcmCombined(modelName, chatKeyBytes) : void 0;
|
|
2765
|
+
const persistedAssistantMessageId = assistantMessageId ?? randomUUID2();
|
|
2766
|
+
ws.send("ai_response_completed", {
|
|
2767
|
+
chat_id: chatId,
|
|
2768
|
+
message: {
|
|
2769
|
+
message_id: persistedAssistantMessageId,
|
|
2770
|
+
chat_id: chatId,
|
|
2771
|
+
role: "assistant",
|
|
2772
|
+
created_at: completedAt,
|
|
2773
|
+
status: "synced",
|
|
2774
|
+
user_message_id: messageId,
|
|
2775
|
+
encrypted_content: encryptedAssistantContent,
|
|
2776
|
+
encrypted_category: encryptedCategory,
|
|
2777
|
+
encrypted_model_name: encryptedModelName
|
|
2778
|
+
},
|
|
2779
|
+
versions: {
|
|
2780
|
+
messages_v: baselineMessagesV + 2,
|
|
2781
|
+
last_edited_overall_timestamp: completedAt
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
await ws.waitForMessage(
|
|
2785
|
+
"ai_response_storage_confirmed",
|
|
2786
|
+
(payload) => {
|
|
2787
|
+
const p = payload;
|
|
2788
|
+
return p.message_id === persistedAssistantMessageId;
|
|
2789
|
+
},
|
|
2790
|
+
2e4
|
|
2791
|
+
);
|
|
2792
|
+
await this.persistStreamedEmbeds({
|
|
2793
|
+
ws,
|
|
2794
|
+
embeds: resp.embeds,
|
|
2795
|
+
chatId,
|
|
2796
|
+
chatKeyBytes,
|
|
2797
|
+
fallbackMessageId: persistedAssistantMessageId
|
|
2798
|
+
});
|
|
2799
|
+
clearSyncCache();
|
|
2800
|
+
}
|
|
2499
2801
|
} finally {
|
|
2500
2802
|
ws.close();
|
|
2501
2803
|
}
|
|
@@ -2510,6 +2812,110 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2510
2812
|
followUpSuggestions
|
|
2511
2813
|
};
|
|
2512
2814
|
}
|
|
2815
|
+
async persistStreamedEmbeds(params) {
|
|
2816
|
+
const finalized = new Map(
|
|
2817
|
+
params.embeds.filter((embed) => {
|
|
2818
|
+
const status = embed.status ?? "finished";
|
|
2819
|
+
return embed.embed_id && embed.content && status !== "processing" && status !== "error" && status !== "cancelled";
|
|
2820
|
+
}).map((embed) => [embed.embed_id, embed])
|
|
2821
|
+
);
|
|
2822
|
+
if (finalized.size === 0) return;
|
|
2823
|
+
const session = this.requireSession();
|
|
2824
|
+
const masterKey = this.getMasterKeyBytes();
|
|
2825
|
+
const parentKeys = /* @__PURE__ */ new Map();
|
|
2826
|
+
const processed = /* @__PURE__ */ new Set();
|
|
2827
|
+
const makeKey = () => {
|
|
2828
|
+
const key = new Uint8Array(32);
|
|
2829
|
+
globalThis.crypto.getRandomValues(key);
|
|
2830
|
+
return key;
|
|
2831
|
+
};
|
|
2832
|
+
const persistOne = async (embed, embedKey, isChild) => {
|
|
2833
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2834
|
+
const createdAt = normalizeUnixSeconds(embed.createdAt, now);
|
|
2835
|
+
const updatedAt = normalizeUnixSeconds(embed.updatedAt, now);
|
|
2836
|
+
const messageId = embed.message_id || params.fallbackMessageId;
|
|
2837
|
+
const userId = embed.user_id || session.hashedEmail;
|
|
2838
|
+
const hashedChatId = computeSHA256(params.chatId);
|
|
2839
|
+
const hashedMessageId = computeSHA256(messageId);
|
|
2840
|
+
const hashedUserId = computeSHA256(userId);
|
|
2841
|
+
const hashedEmbedId = computeSHA256(embed.embed_id);
|
|
2842
|
+
const encryptedContent = await encryptWithAesGcmCombined(
|
|
2843
|
+
embed.content ?? "",
|
|
2844
|
+
embedKey
|
|
2845
|
+
);
|
|
2846
|
+
const encryptedType = await encryptWithAesGcmCombined(
|
|
2847
|
+
embed.type || "app_skill_use",
|
|
2848
|
+
embedKey
|
|
2849
|
+
);
|
|
2850
|
+
const encryptedTextPreview = embed.text_preview ? await encryptWithAesGcmCombined(embed.text_preview, embedKey) : void 0;
|
|
2851
|
+
await params.ws.sendAsync("store_embed", {
|
|
2852
|
+
embed_id: embed.embed_id,
|
|
2853
|
+
encrypted_type: encryptedType,
|
|
2854
|
+
encrypted_content: encryptedContent,
|
|
2855
|
+
encrypted_text_preview: encryptedTextPreview,
|
|
2856
|
+
status: embed.status || "finished",
|
|
2857
|
+
hashed_chat_id: hashedChatId,
|
|
2858
|
+
hashed_message_id: hashedMessageId,
|
|
2859
|
+
hashed_task_id: embed.task_id ? computeSHA256(embed.task_id) : void 0,
|
|
2860
|
+
hashed_user_id: hashedUserId,
|
|
2861
|
+
embed_ids: embed.embed_ids,
|
|
2862
|
+
parent_embed_id: embed.parent_embed_id || void 0,
|
|
2863
|
+
version_number: embed.version_number,
|
|
2864
|
+
file_path: embed.file_path,
|
|
2865
|
+
content_hash: embed.content_hash,
|
|
2866
|
+
text_length_chars: embed.text_length_chars,
|
|
2867
|
+
is_private: embed.is_private ?? false,
|
|
2868
|
+
is_shared: embed.is_shared ?? false,
|
|
2869
|
+
created_at: createdAt,
|
|
2870
|
+
updated_at: updatedAt
|
|
2871
|
+
});
|
|
2872
|
+
if (!isChild) {
|
|
2873
|
+
const keys = [
|
|
2874
|
+
{
|
|
2875
|
+
hashed_embed_id: hashedEmbedId,
|
|
2876
|
+
key_type: "master",
|
|
2877
|
+
hashed_chat_id: null,
|
|
2878
|
+
encrypted_embed_key: await encryptBytesWithAesGcm(embedKey, masterKey),
|
|
2879
|
+
hashed_user_id: hashedUserId,
|
|
2880
|
+
created_at: now
|
|
2881
|
+
},
|
|
2882
|
+
{
|
|
2883
|
+
hashed_embed_id: hashedEmbedId,
|
|
2884
|
+
key_type: "chat",
|
|
2885
|
+
hashed_chat_id: hashedChatId,
|
|
2886
|
+
encrypted_embed_key: await encryptBytesWithAesGcm(
|
|
2887
|
+
embedKey,
|
|
2888
|
+
params.chatKeyBytes
|
|
2889
|
+
),
|
|
2890
|
+
hashed_user_id: hashedUserId,
|
|
2891
|
+
created_at: now
|
|
2892
|
+
}
|
|
2893
|
+
];
|
|
2894
|
+
await params.ws.sendAsync("store_embed_keys", { keys });
|
|
2895
|
+
parentKeys.set(embed.embed_id, embedKey);
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
let madeProgress = true;
|
|
2899
|
+
while (processed.size < finalized.size && madeProgress) {
|
|
2900
|
+
madeProgress = false;
|
|
2901
|
+
for (const embed of finalized.values()) {
|
|
2902
|
+
if (processed.has(embed.embed_id)) continue;
|
|
2903
|
+
const parentId = embed.parent_embed_id || null;
|
|
2904
|
+
if (parentId && finalized.has(parentId) && !parentKeys.has(parentId)) {
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
const parentKey = parentId ? parentKeys.get(parentId) : void 0;
|
|
2908
|
+
await persistOne(embed, parentKey ?? makeKey(), Boolean(parentKey));
|
|
2909
|
+
processed.add(embed.embed_id);
|
|
2910
|
+
madeProgress = true;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
for (const embed of finalized.values()) {
|
|
2914
|
+
if (processed.has(embed.embed_id)) continue;
|
|
2915
|
+
await persistOne(embed, makeKey(), false);
|
|
2916
|
+
processed.add(embed.embed_id);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2513
2919
|
/**
|
|
2514
2920
|
* Delete a chat by ID.
|
|
2515
2921
|
*
|
|
@@ -3284,7 +3690,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
3284
3690
|
);
|
|
3285
3691
|
}
|
|
3286
3692
|
}
|
|
3287
|
-
const entryId = params.entryId ??
|
|
3693
|
+
const entryId = params.entryId ?? randomUUID2();
|
|
3288
3694
|
const now = Math.floor(Date.now() / 1e3);
|
|
3289
3695
|
const hashedKey = hashItemKey(params.appId, params.itemType);
|
|
3290
3696
|
const plaintextPayload = {
|
|
@@ -3556,7 +3962,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
3556
3962
|
makeWsClient(session) {
|
|
3557
3963
|
return new OpenMatesWsClient({
|
|
3558
3964
|
apiUrl: session.apiUrl,
|
|
3559
|
-
sessionId:
|
|
3965
|
+
sessionId: randomUUID2(),
|
|
3560
3966
|
wsToken: session.wsToken,
|
|
3561
3967
|
refreshToken: session.cookies.auth_refresh_token ?? null,
|
|
3562
3968
|
// Same User-Agent as login so OS-based device fingerprint hash matches.
|
|
@@ -3928,7 +4334,7 @@ function printLogo() {
|
|
|
3928
4334
|
}
|
|
3929
4335
|
|
|
3930
4336
|
// ../secret-scanner/src/registry.ts
|
|
3931
|
-
import { createRequire } from "module";
|
|
4337
|
+
import { createRequire as createRequire2 } from "module";
|
|
3932
4338
|
|
|
3933
4339
|
// ../secret-scanner/src/types.ts
|
|
3934
4340
|
var SECRET_ENV_PATTERNS = [
|
|
@@ -4062,8 +4468,8 @@ var SECRET_PATTERNS = [
|
|
|
4062
4468
|
];
|
|
4063
4469
|
|
|
4064
4470
|
// ../secret-scanner/src/registry.ts
|
|
4065
|
-
var
|
|
4066
|
-
var AhoCorasick =
|
|
4471
|
+
var require3 = createRequire2(import.meta.url);
|
|
4472
|
+
var AhoCorasick = require3("ahocorasick");
|
|
4067
4473
|
var MIN_SECRET_LENGTH = 8;
|
|
4068
4474
|
var SecretRegistry = class {
|
|
4069
4475
|
/** Map from plaintext value → registry entry */
|
|
@@ -4605,136 +5011,6 @@ import { readFileSync as readFileSync3, statSync, existsSync as existsSync3 } fr
|
|
|
4605
5011
|
import { basename, extname, resolve } from "path";
|
|
4606
5012
|
import { homedir as homedir3 } from "os";
|
|
4607
5013
|
import { createHash as createHash4 } from "crypto";
|
|
4608
|
-
|
|
4609
|
-
// src/embedCreator.ts
|
|
4610
|
-
import { randomUUID as randomUUID2, createHash as createHash3, randomBytes, webcrypto as webcrypto4 } from "crypto";
|
|
4611
|
-
import { encode as toonEncode } from "@toon-format/toon";
|
|
4612
|
-
var cryptoApi3 = webcrypto4;
|
|
4613
|
-
var AES_GCM_IV_LENGTH3 = 12;
|
|
4614
|
-
function bytesToBase643(input) {
|
|
4615
|
-
return Buffer.from(input).toString("base64");
|
|
4616
|
-
}
|
|
4617
|
-
function toArrayBuffer2(input) {
|
|
4618
|
-
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
4619
|
-
}
|
|
4620
|
-
async function encryptAesGcm(plaintext, rawKeyBytes) {
|
|
4621
|
-
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
4622
|
-
const key = await cryptoApi3.subtle.importKey(
|
|
4623
|
-
"raw",
|
|
4624
|
-
toArrayBuffer2(rawKeyBytes),
|
|
4625
|
-
{ name: "AES-GCM" },
|
|
4626
|
-
false,
|
|
4627
|
-
["encrypt"]
|
|
4628
|
-
);
|
|
4629
|
-
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
4630
|
-
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
4631
|
-
key,
|
|
4632
|
-
new TextEncoder().encode(plaintext)
|
|
4633
|
-
);
|
|
4634
|
-
const cipherBytes = new Uint8Array(encrypted);
|
|
4635
|
-
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
4636
|
-
combined.set(iv);
|
|
4637
|
-
combined.set(cipherBytes, iv.length);
|
|
4638
|
-
return bytesToBase643(combined);
|
|
4639
|
-
}
|
|
4640
|
-
async function wrapKey(embedKey, wrappingKey) {
|
|
4641
|
-
const cryptoKey = await cryptoApi3.subtle.importKey(
|
|
4642
|
-
"raw",
|
|
4643
|
-
toArrayBuffer2(wrappingKey),
|
|
4644
|
-
{ name: "AES-GCM" },
|
|
4645
|
-
false,
|
|
4646
|
-
["encrypt"]
|
|
4647
|
-
);
|
|
4648
|
-
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
4649
|
-
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
4650
|
-
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
4651
|
-
cryptoKey,
|
|
4652
|
-
toArrayBuffer2(embedKey)
|
|
4653
|
-
);
|
|
4654
|
-
const cipherBytes = new Uint8Array(encrypted);
|
|
4655
|
-
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
4656
|
-
combined.set(iv);
|
|
4657
|
-
combined.set(cipherBytes, iv.length);
|
|
4658
|
-
return bytesToBase643(combined);
|
|
4659
|
-
}
|
|
4660
|
-
function generateEmbedKey() {
|
|
4661
|
-
return new Uint8Array(randomBytes(32));
|
|
4662
|
-
}
|
|
4663
|
-
function computeSHA256(content) {
|
|
4664
|
-
return createHash3("sha256").update(content).digest("hex");
|
|
4665
|
-
}
|
|
4666
|
-
function toonEncodeContent(data) {
|
|
4667
|
-
try {
|
|
4668
|
-
return toonEncode(data);
|
|
4669
|
-
} catch {
|
|
4670
|
-
return JSON.stringify(data);
|
|
4671
|
-
}
|
|
4672
|
-
}
|
|
4673
|
-
function generateEmbedId() {
|
|
4674
|
-
return randomUUID2();
|
|
4675
|
-
}
|
|
4676
|
-
function createEmbedReferenceBlock(type, embedId) {
|
|
4677
|
-
const ref = JSON.stringify({ type, embed_id: embedId }, null, 2);
|
|
4678
|
-
return "```json\n" + ref + "\n```";
|
|
4679
|
-
}
|
|
4680
|
-
async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId) {
|
|
4681
|
-
try {
|
|
4682
|
-
const embedKey = generateEmbedKey();
|
|
4683
|
-
const encryptedContent = await encryptAesGcm(embed.content, embedKey);
|
|
4684
|
-
const encryptedType = await encryptAesGcm(embed.type, embedKey);
|
|
4685
|
-
const encryptedTextPreview = await encryptAesGcm(embed.textPreview, embedKey);
|
|
4686
|
-
const hashedEmbedId = computeSHA256(embed.embedId);
|
|
4687
|
-
const hashedChatId = computeSHA256(chatId);
|
|
4688
|
-
const hashedMessageId = computeSHA256(messageId);
|
|
4689
|
-
const hashedUserId = computeSHA256(userId);
|
|
4690
|
-
const wrappedWithMaster = await wrapKey(embedKey, masterKey);
|
|
4691
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
4692
|
-
const embedKeys = [
|
|
4693
|
-
{
|
|
4694
|
-
hashed_embed_id: hashedEmbedId,
|
|
4695
|
-
key_type: "master",
|
|
4696
|
-
hashed_chat_id: null,
|
|
4697
|
-
encrypted_embed_key: wrappedWithMaster,
|
|
4698
|
-
hashed_user_id: hashedUserId,
|
|
4699
|
-
created_at: nowSeconds
|
|
4700
|
-
}
|
|
4701
|
-
];
|
|
4702
|
-
if (chatKey) {
|
|
4703
|
-
const wrappedWithChat = await wrapKey(embedKey, chatKey);
|
|
4704
|
-
embedKeys.push({
|
|
4705
|
-
hashed_embed_id: hashedEmbedId,
|
|
4706
|
-
key_type: "chat",
|
|
4707
|
-
hashed_chat_id: hashedChatId,
|
|
4708
|
-
encrypted_embed_key: wrappedWithChat,
|
|
4709
|
-
hashed_user_id: hashedUserId,
|
|
4710
|
-
created_at: nowSeconds
|
|
4711
|
-
});
|
|
4712
|
-
}
|
|
4713
|
-
return {
|
|
4714
|
-
embed_id: embed.embedId,
|
|
4715
|
-
encrypted_type: encryptedType,
|
|
4716
|
-
encrypted_content: encryptedContent,
|
|
4717
|
-
encrypted_text_preview: encryptedTextPreview,
|
|
4718
|
-
status: embed.status,
|
|
4719
|
-
hashed_chat_id: hashedChatId,
|
|
4720
|
-
hashed_message_id: hashedMessageId,
|
|
4721
|
-
hashed_user_id: hashedUserId,
|
|
4722
|
-
file_path: embed.filePath,
|
|
4723
|
-
content_hash: embed.contentHash,
|
|
4724
|
-
text_length_chars: embed.textLengthChars,
|
|
4725
|
-
created_at: nowSeconds,
|
|
4726
|
-
updated_at: nowSeconds,
|
|
4727
|
-
embed_keys: embedKeys
|
|
4728
|
-
};
|
|
4729
|
-
} catch (error) {
|
|
4730
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
4731
|
-
process.stderr.write(`\x1B[31mError:\x1B[0m Failed to encrypt embed: ${msg}
|
|
4732
|
-
`);
|
|
4733
|
-
return null;
|
|
4734
|
-
}
|
|
4735
|
-
}
|
|
4736
|
-
|
|
4737
|
-
// src/fileEmbed.ts
|
|
4738
5014
|
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
4739
5015
|
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4740
5016
|
".pem",
|
|
@@ -5125,6 +5401,70 @@ function formatEmbedsForMessage(embeds) {
|
|
|
5125
5401
|
return "\n" + embeds.map((e) => e.referenceBlock).join("\n");
|
|
5126
5402
|
}
|
|
5127
5403
|
|
|
5404
|
+
// src/urlEmbed.ts
|
|
5405
|
+
var URL_PATTERN = /\bhttps?:\/\/[^\s<>()`"']+/gi;
|
|
5406
|
+
var FENCED_BLOCK_PATTERN = /(```[\s\S]*?```)/g;
|
|
5407
|
+
var TRAILING_URL_PUNCTUATION = /[.,!?;:]+$/;
|
|
5408
|
+
function trimTrailingUrlPunctuation(rawUrl) {
|
|
5409
|
+
let url = rawUrl;
|
|
5410
|
+
let suffix = "";
|
|
5411
|
+
const punctuation = url.match(TRAILING_URL_PUNCTUATION)?.[0] ?? "";
|
|
5412
|
+
if (punctuation) {
|
|
5413
|
+
url = url.slice(0, -punctuation.length);
|
|
5414
|
+
suffix = punctuation + suffix;
|
|
5415
|
+
}
|
|
5416
|
+
while (url.endsWith(")")) {
|
|
5417
|
+
const openCount = (url.match(/\(/g) ?? []).length;
|
|
5418
|
+
const closeCount = (url.match(/\)/g) ?? []).length;
|
|
5419
|
+
if (closeCount <= openCount) break;
|
|
5420
|
+
url = url.slice(0, -1);
|
|
5421
|
+
suffix = `)${suffix}`;
|
|
5422
|
+
}
|
|
5423
|
+
return { url, suffix };
|
|
5424
|
+
}
|
|
5425
|
+
function createWebsiteEmbed(url) {
|
|
5426
|
+
const embedId = generateEmbedId();
|
|
5427
|
+
const content = toonEncodeContent({
|
|
5428
|
+
url,
|
|
5429
|
+
title: null,
|
|
5430
|
+
description: null,
|
|
5431
|
+
favicon: null,
|
|
5432
|
+
image: null,
|
|
5433
|
+
site_name: null,
|
|
5434
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5435
|
+
});
|
|
5436
|
+
return {
|
|
5437
|
+
embedId,
|
|
5438
|
+
type: "web-website",
|
|
5439
|
+
content,
|
|
5440
|
+
textPreview: url,
|
|
5441
|
+
status: "finished"
|
|
5442
|
+
};
|
|
5443
|
+
}
|
|
5444
|
+
function replaceUrlsInText(text, embeds) {
|
|
5445
|
+
return text.replace(URL_PATTERN, (rawUrl) => {
|
|
5446
|
+
const { url, suffix } = trimTrailingUrlPunctuation(rawUrl);
|
|
5447
|
+
if (!url) return rawUrl;
|
|
5448
|
+
try {
|
|
5449
|
+
new URL(url);
|
|
5450
|
+
} catch {
|
|
5451
|
+
return rawUrl;
|
|
5452
|
+
}
|
|
5453
|
+
const embed = createWebsiteEmbed(url);
|
|
5454
|
+
embeds.push(embed);
|
|
5455
|
+
return `${createEmbedReferenceBlock("website", embed.embedId, url)}${suffix}`;
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
function prepareUrlEmbeds(message) {
|
|
5459
|
+
const embeds = [];
|
|
5460
|
+
const parts = message.split(FENCED_BLOCK_PATTERN);
|
|
5461
|
+
const processed = parts.map((part, index) => {
|
|
5462
|
+
const isFence = index % 2 === 1 && part.startsWith("```");
|
|
5463
|
+
return isFence ? part : replaceUrlsInText(part, embeds);
|
|
5464
|
+
}).join("");
|
|
5465
|
+
return { message: processed, embeds };
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5128
5468
|
// src/embedRenderers.ts
|
|
5129
5469
|
var str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
5130
5470
|
var DIRECT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -8938,7 +9278,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8938
9278
|
processedRawLength = raw.length;
|
|
8939
9279
|
}
|
|
8940
9280
|
};
|
|
8941
|
-
const
|
|
9281
|
+
const preparedEmbeds = [];
|
|
8942
9282
|
let finalMessage = params.message;
|
|
8943
9283
|
if (params.message.includes("@")) {
|
|
8944
9284
|
try {
|
|
@@ -9045,31 +9385,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
9045
9385
|
if (embedRefs) {
|
|
9046
9386
|
finalMessage += embedRefs;
|
|
9047
9387
|
}
|
|
9048
|
-
|
|
9049
|
-
const { masterKey, userId } = client.getEmbedEncryptionKeys();
|
|
9050
|
-
for (const fe of fileResult.embeds) {
|
|
9051
|
-
const encrypted = await encryptEmbed(
|
|
9052
|
-
fe.embed,
|
|
9053
|
-
masterKey,
|
|
9054
|
-
null,
|
|
9055
|
-
// chatKey — not available in CLI yet for new chats
|
|
9056
|
-
params.chatId || "new",
|
|
9057
|
-
// will be replaced by server
|
|
9058
|
-
"pending",
|
|
9059
|
-
// messageId set during send
|
|
9060
|
-
userId
|
|
9061
|
-
);
|
|
9062
|
-
if (encrypted) {
|
|
9063
|
-
encryptedEmbeds.push(encrypted);
|
|
9064
|
-
}
|
|
9065
|
-
}
|
|
9066
|
-
} catch (err) {
|
|
9067
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
9068
|
-
process.stderr.write(
|
|
9069
|
-
`\x1B[33mWarning:\x1B[0m Embed encryption failed: ${msg}. Files sent without encryption.
|
|
9070
|
-
`
|
|
9071
|
-
);
|
|
9072
|
-
}
|
|
9388
|
+
preparedEmbeds.push(...fileResult.embeds.map((fileEmbed) => fileEmbed.embed));
|
|
9073
9389
|
}
|
|
9074
9390
|
if (parsed.resolved.length > 0 && !params.json) {
|
|
9075
9391
|
clearTyping();
|
|
@@ -9084,12 +9400,15 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
9084
9400
|
} catch {
|
|
9085
9401
|
}
|
|
9086
9402
|
}
|
|
9403
|
+
const urlResult = prepareUrlEmbeds(finalMessage);
|
|
9404
|
+
finalMessage = urlResult.message;
|
|
9405
|
+
preparedEmbeds.push(...urlResult.embeds);
|
|
9087
9406
|
const result = await client.sendMessage({
|
|
9088
9407
|
message: finalMessage,
|
|
9089
9408
|
chatId: params.chatId,
|
|
9090
9409
|
incognito: params.incognito,
|
|
9091
9410
|
onStream,
|
|
9092
|
-
|
|
9411
|
+
preparedEmbeds: preparedEmbeds.length > 0 ? preparedEmbeds : void 0
|
|
9093
9412
|
});
|
|
9094
9413
|
clearTyping();
|
|
9095
9414
|
if (!params.json) {
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,35 @@ interface MentionContext {
|
|
|
105
105
|
memoryEntries: MemoryEntryInfo[];
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @file Embed creation pipeline for the CLI — generates encrypted embeds
|
|
110
|
+
* with proper key wrapping, matching the web app's zero-knowledge architecture.
|
|
111
|
+
*
|
|
112
|
+
* For each embed:
|
|
113
|
+
* 1. Content is TOON-encoded (or JSON-fallback)
|
|
114
|
+
* 2. A random AES-256 embed key is generated
|
|
115
|
+
* 3. Content, type, and text_preview are encrypted with the embed key
|
|
116
|
+
* 4. The embed key is wrapped with both master key and chat key
|
|
117
|
+
* 5. SHA-256 hashes are computed for all IDs
|
|
118
|
+
* 6. The encrypted embed + wrapped keys are attached to chat_message_added
|
|
119
|
+
*
|
|
120
|
+
* Mirrors: cryptoService.ts (encryptWithEmbedKey, wrapEmbedKeyWithMasterKey,
|
|
121
|
+
* wrapEmbedKeyWithChatKey, generateEmbedKey)
|
|
122
|
+
* chatSyncServiceSenders.ts (encrypted_embeds construction)
|
|
123
|
+
*
|
|
124
|
+
* Architecture: docs/architecture/embeds.md
|
|
125
|
+
*/
|
|
126
|
+
/** A prepared embed ready for encryption and sending */
|
|
127
|
+
interface PreparedEmbed {
|
|
128
|
+
embedId: string;
|
|
129
|
+
type: string;
|
|
130
|
+
content: string;
|
|
131
|
+
textPreview: string;
|
|
132
|
+
status: string;
|
|
133
|
+
filePath?: string;
|
|
134
|
+
contentHash?: string;
|
|
135
|
+
textLengthChars?: number;
|
|
136
|
+
}
|
|
108
137
|
/** A fully encrypted embed ready for the WebSocket payload */
|
|
109
138
|
interface EncryptedEmbed {
|
|
110
139
|
embed_id: string;
|
|
@@ -424,6 +453,8 @@ declare class OpenMatesClient {
|
|
|
424
453
|
onStream?: (event: StreamEvent) => void;
|
|
425
454
|
/** Encrypted file embeds to attach to the message (code, images, PDFs). */
|
|
426
455
|
encryptedEmbeds?: EncryptedEmbed[];
|
|
456
|
+
/** Prepared embeds to encrypt after the real chat/message IDs are known. */
|
|
457
|
+
preparedEmbeds?: PreparedEmbed[];
|
|
427
458
|
}): Promise<{
|
|
428
459
|
chatId: string;
|
|
429
460
|
assistant: string;
|
|
@@ -433,6 +464,7 @@ declare class OpenMatesClient {
|
|
|
433
464
|
/** Follow-up suggestions from post-processing (may be empty for incognito chats). */
|
|
434
465
|
followUpSuggestions: string[];
|
|
435
466
|
}>;
|
|
467
|
+
private persistStreamedEmbeds;
|
|
436
468
|
/**
|
|
437
469
|
* Delete a chat by ID.
|
|
438
470
|
*
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmates",
|
|
3
|
-
"version": "0.11.0-alpha.
|
|
3
|
+
"version": "0.11.0-alpha.16",
|
|
4
4
|
"description": "OpenMates CLI and SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"test:unit:crypto": "node --test --experimental-strip-types tests/crypto.test.ts",
|
|
19
19
|
"test:unit:storage": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/storage.test.ts",
|
|
20
20
|
"test:unit:keychain": "node --test --experimental-strip-types tests/keychain.test.ts",
|
|
21
|
+
"test:unit:ws": "node --test --experimental-strip-types tests/ws.test.ts",
|
|
22
|
+
"test:unit:url-embed": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/urlEmbed.test.ts",
|
|
21
23
|
"test:unit:server": "node --test --experimental-strip-types tests/server.test.ts",
|
|
22
24
|
"test:unit:cli": "node --test tests/cli.test.ts",
|
|
23
|
-
"test": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/crypto.test.ts tests/storage.test.ts tests/keychain.test.ts tests/mentions.test.ts tests/outputRedactor.test.ts tests/fileEmbed.test.ts tests/embedCreator.test.ts tests/shareEncryption.test.ts tests/server.test.ts && node --test tests/cli.test.ts"
|
|
25
|
+
"test": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/crypto.test.ts tests/storage.test.ts tests/keychain.test.ts tests/mentions.test.ts tests/outputRedactor.test.ts tests/fileEmbed.test.ts tests/embedCreator.test.ts tests/shareEncryption.test.ts tests/server.test.ts tests/ws.test.ts tests/urlEmbed.test.ts && node --test tests/cli.test.ts"
|
|
24
26
|
},
|
|
25
27
|
"keywords": [
|
|
26
28
|
"openmates",
|