openmates 0.11.0-alpha.13 → 0.11.0-alpha.15

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.
@@ -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 WebSocket from "ws";
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
- postProcessingTimer = setTimeout(() => {
919
- cleanup();
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
- cleanup();
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,137 @@ 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) {
1518
+ const ref = JSON.stringify({ type, embed_id: embedId }, null, 2);
1519
+ return "```json\n" + ref + "\n```";
1520
+ }
1521
+ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId) {
1522
+ try {
1523
+ const embedKey = generateEmbedKey();
1524
+ const encryptedContent = await encryptAesGcm(embed.content, embedKey);
1525
+ const encryptedType = await encryptAesGcm(embed.type, embedKey);
1526
+ const encryptedTextPreview = await encryptAesGcm(embed.textPreview, embedKey);
1527
+ const hashedEmbedId = computeSHA256(embed.embedId);
1528
+ const hashedChatId = computeSHA256(chatId);
1529
+ const hashedMessageId = computeSHA256(messageId);
1530
+ const hashedUserId = computeSHA256(userId);
1531
+ const wrappedWithMaster = await wrapKey(embedKey, masterKey);
1532
+ const nowSeconds = Math.floor(Date.now() / 1e3);
1533
+ const embedKeys = [
1534
+ {
1535
+ hashed_embed_id: hashedEmbedId,
1536
+ key_type: "master",
1537
+ hashed_chat_id: null,
1538
+ encrypted_embed_key: wrappedWithMaster,
1539
+ hashed_user_id: hashedUserId,
1540
+ created_at: nowSeconds
1541
+ }
1542
+ ];
1543
+ if (chatKey) {
1544
+ const wrappedWithChat = await wrapKey(embedKey, chatKey);
1545
+ embedKeys.push({
1546
+ hashed_embed_id: hashedEmbedId,
1547
+ key_type: "chat",
1548
+ hashed_chat_id: hashedChatId,
1549
+ encrypted_embed_key: wrappedWithChat,
1550
+ hashed_user_id: hashedUserId,
1551
+ created_at: nowSeconds
1552
+ });
1553
+ }
1554
+ return {
1555
+ embed_id: embed.embedId,
1556
+ encrypted_type: encryptedType,
1557
+ encrypted_content: encryptedContent,
1558
+ encrypted_text_preview: encryptedTextPreview,
1559
+ status: embed.status,
1560
+ hashed_chat_id: hashedChatId,
1561
+ hashed_message_id: hashedMessageId,
1562
+ hashed_user_id: hashedUserId,
1563
+ file_path: embed.filePath,
1564
+ content_hash: embed.contentHash,
1565
+ text_length_chars: embed.textLengthChars,
1566
+ created_at: nowSeconds,
1567
+ updated_at: nowSeconds,
1568
+ embed_keys: embedKeys
1569
+ };
1570
+ } catch (error) {
1571
+ const msg = error instanceof Error ? error.message : String(error);
1572
+ process.stderr.write(`\x1B[31mError:\x1B[0m Failed to encrypt embed: ${msg}
1573
+ `);
1574
+ return null;
1575
+ }
1576
+ }
1577
+
1348
1578
  // src/shareEncryption.ts
1349
- import { webcrypto as webcrypto3 } from "crypto";
1350
- var crypto = webcrypto3;
1579
+ import { webcrypto as webcrypto4 } from "crypto";
1580
+ var crypto = webcrypto4;
1351
1581
  function base64UrlEncode(data) {
1352
1582
  return Buffer.from(data).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1353
1583
  }
@@ -1459,6 +1689,12 @@ function buildEmbedShareUrl(origin, embedId, blob) {
1459
1689
  }
1460
1690
 
1461
1691
  // src/client.ts
1692
+ function normalizeUnixSeconds(value, fallback) {
1693
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
1694
+ return fallback;
1695
+ }
1696
+ return value > 1e10 ? Math.floor(value / 1e3) : Math.floor(value);
1697
+ }
1462
1698
  var MEMORY_TYPE_REGISTRY = {
1463
1699
  "ai/communication_style": {
1464
1700
  appId: "ai",
@@ -1892,7 +2128,7 @@ var OpenMatesClient = class _OpenMatesClient {
1892
2128
  pin: pin.trim().toUpperCase(),
1893
2129
  token
1894
2130
  });
1895
- const sessionId = randomUUID();
2131
+ const sessionId = randomUUID2();
1896
2132
  const login = await this.http.post(
1897
2133
  "/v1/auth/login",
1898
2134
  {
@@ -2387,7 +2623,7 @@ var OpenMatesClient = class _OpenMatesClient {
2387
2623
  async sendMessage(params) {
2388
2624
  let chatId;
2389
2625
  if (!params.chatId) {
2390
- chatId = randomUUID();
2626
+ chatId = randomUUID2();
2391
2627
  } else if (params.chatId.length < 36) {
2392
2628
  const resolved = await this.resolveFullChatId(params.chatId);
2393
2629
  if (!resolved) {
@@ -2400,7 +2636,7 @@ var OpenMatesClient = class _OpenMatesClient {
2400
2636
  chatId = params.chatId;
2401
2637
  }
2402
2638
  const { ws, session } = await this.openWsClient();
2403
- const messageId = randomUUID();
2639
+ const messageId = randomUUID2();
2404
2640
  const createdAt = Math.floor(Date.now() / 1e3);
2405
2641
  const isNewChat = !params.chatId;
2406
2642
  ws.send("set_active_chat", { chat_id: chatId });
@@ -2420,6 +2656,7 @@ var OpenMatesClient = class _OpenMatesClient {
2420
2656
  };
2421
2657
  let chatKeyBytes = null;
2422
2658
  let encryptedChatKey = null;
2659
+ let baselineMessagesV = 0;
2423
2660
  if (!params.incognito) {
2424
2661
  const masterKey = this.getMasterKeyBytes();
2425
2662
  if (isNewChat) {
@@ -2439,6 +2676,7 @@ var OpenMatesClient = class _OpenMatesClient {
2439
2676
  (c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
2440
2677
  );
2441
2678
  if (chat) {
2679
+ baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
2442
2680
  const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
2443
2681
  if (encKey) {
2444
2682
  chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
@@ -2476,6 +2714,7 @@ var OpenMatesClient = class _OpenMatesClient {
2476
2714
  ws.send("encrypted_chat_metadata", metadataPayload);
2477
2715
  }
2478
2716
  let assistant = "";
2717
+ let assistantMessageId = null;
2479
2718
  let category = null;
2480
2719
  let modelName = null;
2481
2720
  let followUpSuggestions = [];
@@ -2483,6 +2722,7 @@ var OpenMatesClient = class _OpenMatesClient {
2483
2722
  if (params.incognito) {
2484
2723
  try {
2485
2724
  const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
2725
+ assistantMessageId = resp.messageId;
2486
2726
  assistant = resp.content;
2487
2727
  category = resp.category;
2488
2728
  modelName = resp.modelName;
@@ -2492,10 +2732,55 @@ var OpenMatesClient = class _OpenMatesClient {
2492
2732
  } else {
2493
2733
  try {
2494
2734
  const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
2735
+ assistantMessageId = resp.messageId;
2495
2736
  assistant = resp.content;
2496
2737
  category = resp.category;
2497
2738
  modelName = resp.modelName;
2498
2739
  followUpSuggestions = resp.followUpSuggestions;
2740
+ if (chatKeyBytes && assistant) {
2741
+ const completedAt = Math.floor(Date.now() / 1e3);
2742
+ const encryptedAssistantContent = await encryptWithAesGcmCombined(
2743
+ assistant,
2744
+ chatKeyBytes
2745
+ );
2746
+ const encryptedCategory = category ? await encryptWithAesGcmCombined(category, chatKeyBytes) : void 0;
2747
+ const encryptedModelName = modelName ? await encryptWithAesGcmCombined(modelName, chatKeyBytes) : void 0;
2748
+ const persistedAssistantMessageId = assistantMessageId ?? randomUUID2();
2749
+ ws.send("ai_response_completed", {
2750
+ chat_id: chatId,
2751
+ message: {
2752
+ message_id: persistedAssistantMessageId,
2753
+ chat_id: chatId,
2754
+ role: "assistant",
2755
+ created_at: completedAt,
2756
+ status: "synced",
2757
+ user_message_id: messageId,
2758
+ encrypted_content: encryptedAssistantContent,
2759
+ encrypted_category: encryptedCategory,
2760
+ encrypted_model_name: encryptedModelName
2761
+ },
2762
+ versions: {
2763
+ messages_v: baselineMessagesV + 2,
2764
+ last_edited_overall_timestamp: completedAt
2765
+ }
2766
+ });
2767
+ await ws.waitForMessage(
2768
+ "ai_response_storage_confirmed",
2769
+ (payload) => {
2770
+ const p = payload;
2771
+ return p.message_id === persistedAssistantMessageId;
2772
+ },
2773
+ 2e4
2774
+ );
2775
+ await this.persistStreamedEmbeds({
2776
+ ws,
2777
+ embeds: resp.embeds,
2778
+ chatId,
2779
+ chatKeyBytes,
2780
+ fallbackMessageId: persistedAssistantMessageId
2781
+ });
2782
+ clearSyncCache();
2783
+ }
2499
2784
  } finally {
2500
2785
  ws.close();
2501
2786
  }
@@ -2510,6 +2795,110 @@ var OpenMatesClient = class _OpenMatesClient {
2510
2795
  followUpSuggestions
2511
2796
  };
2512
2797
  }
2798
+ async persistStreamedEmbeds(params) {
2799
+ const finalized = new Map(
2800
+ params.embeds.filter((embed) => {
2801
+ const status = embed.status ?? "finished";
2802
+ return embed.embed_id && embed.content && status !== "processing" && status !== "error" && status !== "cancelled";
2803
+ }).map((embed) => [embed.embed_id, embed])
2804
+ );
2805
+ if (finalized.size === 0) return;
2806
+ const session = this.requireSession();
2807
+ const masterKey = this.getMasterKeyBytes();
2808
+ const parentKeys = /* @__PURE__ */ new Map();
2809
+ const processed = /* @__PURE__ */ new Set();
2810
+ const makeKey = () => {
2811
+ const key = new Uint8Array(32);
2812
+ globalThis.crypto.getRandomValues(key);
2813
+ return key;
2814
+ };
2815
+ const persistOne = async (embed, embedKey, isChild) => {
2816
+ const now = Math.floor(Date.now() / 1e3);
2817
+ const createdAt = normalizeUnixSeconds(embed.createdAt, now);
2818
+ const updatedAt = normalizeUnixSeconds(embed.updatedAt, now);
2819
+ const messageId = embed.message_id || params.fallbackMessageId;
2820
+ const userId = embed.user_id || session.hashedEmail;
2821
+ const hashedChatId = computeSHA256(params.chatId);
2822
+ const hashedMessageId = computeSHA256(messageId);
2823
+ const hashedUserId = computeSHA256(userId);
2824
+ const hashedEmbedId = computeSHA256(embed.embed_id);
2825
+ const encryptedContent = await encryptWithAesGcmCombined(
2826
+ embed.content ?? "",
2827
+ embedKey
2828
+ );
2829
+ const encryptedType = await encryptWithAesGcmCombined(
2830
+ embed.type || "app_skill_use",
2831
+ embedKey
2832
+ );
2833
+ const encryptedTextPreview = embed.text_preview ? await encryptWithAesGcmCombined(embed.text_preview, embedKey) : void 0;
2834
+ await params.ws.sendAsync("store_embed", {
2835
+ embed_id: embed.embed_id,
2836
+ encrypted_type: encryptedType,
2837
+ encrypted_content: encryptedContent,
2838
+ encrypted_text_preview: encryptedTextPreview,
2839
+ status: embed.status || "finished",
2840
+ hashed_chat_id: hashedChatId,
2841
+ hashed_message_id: hashedMessageId,
2842
+ hashed_task_id: embed.task_id ? computeSHA256(embed.task_id) : void 0,
2843
+ hashed_user_id: hashedUserId,
2844
+ embed_ids: embed.embed_ids,
2845
+ parent_embed_id: embed.parent_embed_id || void 0,
2846
+ version_number: embed.version_number,
2847
+ file_path: embed.file_path,
2848
+ content_hash: embed.content_hash,
2849
+ text_length_chars: embed.text_length_chars,
2850
+ is_private: embed.is_private ?? false,
2851
+ is_shared: embed.is_shared ?? false,
2852
+ created_at: createdAt,
2853
+ updated_at: updatedAt
2854
+ });
2855
+ if (!isChild) {
2856
+ const keys = [
2857
+ {
2858
+ hashed_embed_id: hashedEmbedId,
2859
+ key_type: "master",
2860
+ hashed_chat_id: null,
2861
+ encrypted_embed_key: await encryptBytesWithAesGcm(embedKey, masterKey),
2862
+ hashed_user_id: hashedUserId,
2863
+ created_at: now
2864
+ },
2865
+ {
2866
+ hashed_embed_id: hashedEmbedId,
2867
+ key_type: "chat",
2868
+ hashed_chat_id: hashedChatId,
2869
+ encrypted_embed_key: await encryptBytesWithAesGcm(
2870
+ embedKey,
2871
+ params.chatKeyBytes
2872
+ ),
2873
+ hashed_user_id: hashedUserId,
2874
+ created_at: now
2875
+ }
2876
+ ];
2877
+ await params.ws.sendAsync("store_embed_keys", { keys });
2878
+ parentKeys.set(embed.embed_id, embedKey);
2879
+ }
2880
+ };
2881
+ let madeProgress = true;
2882
+ while (processed.size < finalized.size && madeProgress) {
2883
+ madeProgress = false;
2884
+ for (const embed of finalized.values()) {
2885
+ if (processed.has(embed.embed_id)) continue;
2886
+ const parentId = embed.parent_embed_id || null;
2887
+ if (parentId && finalized.has(parentId) && !parentKeys.has(parentId)) {
2888
+ continue;
2889
+ }
2890
+ const parentKey = parentId ? parentKeys.get(parentId) : void 0;
2891
+ await persistOne(embed, parentKey ?? makeKey(), Boolean(parentKey));
2892
+ processed.add(embed.embed_id);
2893
+ madeProgress = true;
2894
+ }
2895
+ }
2896
+ for (const embed of finalized.values()) {
2897
+ if (processed.has(embed.embed_id)) continue;
2898
+ await persistOne(embed, makeKey(), false);
2899
+ processed.add(embed.embed_id);
2900
+ }
2901
+ }
2513
2902
  /**
2514
2903
  * Delete a chat by ID.
2515
2904
  *
@@ -3284,7 +3673,7 @@ Required: ${schema.required.join(", ")}`
3284
3673
  );
3285
3674
  }
3286
3675
  }
3287
- const entryId = params.entryId ?? randomUUID();
3676
+ const entryId = params.entryId ?? randomUUID2();
3288
3677
  const now = Math.floor(Date.now() / 1e3);
3289
3678
  const hashedKey = hashItemKey(params.appId, params.itemType);
3290
3679
  const plaintextPayload = {
@@ -3556,7 +3945,7 @@ Required: ${schema.required.join(", ")}`
3556
3945
  makeWsClient(session) {
3557
3946
  return new OpenMatesWsClient({
3558
3947
  apiUrl: session.apiUrl,
3559
- sessionId: randomUUID(),
3948
+ sessionId: randomUUID2(),
3560
3949
  wsToken: session.wsToken,
3561
3950
  refreshToken: session.cookies.auth_refresh_token ?? null,
3562
3951
  // Same User-Agent as login so OS-based device fingerprint hash matches.
@@ -3928,7 +4317,7 @@ function printLogo() {
3928
4317
  }
3929
4318
 
3930
4319
  // ../secret-scanner/src/registry.ts
3931
- import { createRequire } from "module";
4320
+ import { createRequire as createRequire2 } from "module";
3932
4321
 
3933
4322
  // ../secret-scanner/src/types.ts
3934
4323
  var SECRET_ENV_PATTERNS = [
@@ -4062,8 +4451,8 @@ var SECRET_PATTERNS = [
4062
4451
  ];
4063
4452
 
4064
4453
  // ../secret-scanner/src/registry.ts
4065
- var require2 = createRequire(import.meta.url);
4066
- var AhoCorasick = require2("ahocorasick");
4454
+ var require3 = createRequire2(import.meta.url);
4455
+ var AhoCorasick = require3("ahocorasick");
4067
4456
  var MIN_SECRET_LENGTH = 8;
4068
4457
  var SecretRegistry = class {
4069
4458
  /** Map from plaintext value → registry entry */
@@ -4605,136 +4994,6 @@ import { readFileSync as readFileSync3, statSync, existsSync as existsSync3 } fr
4605
4994
  import { basename, extname, resolve } from "path";
4606
4995
  import { homedir as homedir3 } from "os";
4607
4996
  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
4997
  var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
4739
4998
  var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
4740
4999
  ".pem",
@@ -6174,7 +6433,7 @@ function formatTs(ts) {
6174
6433
 
6175
6434
  // src/server.ts
6176
6435
  import { execSync, spawn as nodeSpawn } from "child_process";
6177
- import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync3 } from "fs";
6436
+ import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
6178
6437
  import { createInterface as createInterface2 } from "readline";
6179
6438
  import { homedir as homedir5 } from "os";
6180
6439
  import { join as join3, resolve as resolve3 } from "path";
@@ -6272,6 +6531,21 @@ function composeArgs(installPath, withOverrides) {
6272
6531
  }
6273
6532
  return args;
6274
6533
  }
6534
+ function ensureGitWorkDirEnv(installPath) {
6535
+ const envPath = join3(installPath, ".env");
6536
+ if (!existsSync5(envPath)) return;
6537
+ const content = readFileSync5(envPath, "utf-8");
6538
+ const lineRegex = /^GIT_WORK_DIR=.*$/m;
6539
+ const value = `GIT_WORK_DIR=${installPath}`;
6540
+ if (lineRegex.test(content)) {
6541
+ const next = content.replace(lineRegex, value);
6542
+ if (next !== content) writeFileSync3(envPath, next);
6543
+ return;
6544
+ }
6545
+ const separator = content.endsWith("\n") ? "" : "\n";
6546
+ writeFileSync3(envPath, `${content}${separator}${value}
6547
+ `);
6548
+ }
6275
6549
  function requireDocker() {
6276
6550
  try {
6277
6551
  execSync("docker version", { stdio: "pipe" });
@@ -6344,6 +6618,7 @@ function printJson(data) {
6344
6618
  async function serverStatus(flags) {
6345
6619
  requireDocker();
6346
6620
  const installPath = resolveServerPath(flags);
6621
+ ensureGitWorkDirEnv(installPath);
6347
6622
  const config = loadServerConfig();
6348
6623
  const withOverrides = config?.composeProfile === "full";
6349
6624
  const args = [...composeArgs(installPath, withOverrides), "ps"];
@@ -6356,6 +6631,7 @@ async function serverStatus(flags) {
6356
6631
  async function serverStart(flags) {
6357
6632
  requireDocker();
6358
6633
  const installPath = resolveServerPath(flags);
6634
+ ensureGitWorkDirEnv(installPath);
6359
6635
  warnIfMissingLlmCredentials(installPath);
6360
6636
  const withOverrides = flags["with-overrides"] === true;
6361
6637
  const args = [...composeArgs(installPath, withOverrides), "up", "-d"];
@@ -6381,6 +6657,7 @@ async function serverStart(flags) {
6381
6657
  async function serverStop(flags) {
6382
6658
  requireDocker();
6383
6659
  const installPath = resolveServerPath(flags);
6660
+ ensureGitWorkDirEnv(installPath);
6384
6661
  const config = loadServerConfig();
6385
6662
  const withOverrides = config?.composeProfile === "full";
6386
6663
  const args = [...composeArgs(installPath, withOverrides), "down"];
@@ -6396,6 +6673,7 @@ async function serverStop(flags) {
6396
6673
  async function serverRestart(flags) {
6397
6674
  requireDocker();
6398
6675
  const installPath = resolveServerPath(flags);
6676
+ ensureGitWorkDirEnv(installPath);
6399
6677
  const config = loadServerConfig();
6400
6678
  const withOverrides = config?.composeProfile === "full";
6401
6679
  if (flags.rebuild === true) {
@@ -6428,6 +6706,7 @@ async function serverRestart(flags) {
6428
6706
  async function serverLogs(flags) {
6429
6707
  requireDocker();
6430
6708
  const installPath = resolveServerPath(flags);
6709
+ ensureGitWorkDirEnv(installPath);
6431
6710
  const config = loadServerConfig();
6432
6711
  const withOverrides = config?.composeProfile === "full";
6433
6712
  const args = [...composeArgs(installPath, withOverrides), "logs"];
@@ -6514,6 +6793,7 @@ async function serverUpdate(flags) {
6514
6793
  requireGit();
6515
6794
  requireDocker();
6516
6795
  const installPath = resolveServerPath(flags);
6796
+ ensureGitWorkDirEnv(installPath);
6517
6797
  console.error("Updating OpenMates...");
6518
6798
  if (flags.force === true) {
6519
6799
  console.error("Stashing local changes...");
@@ -6560,6 +6840,7 @@ async function serverUpdate(flags) {
6560
6840
  async function serverReset(flags) {
6561
6841
  requireDocker();
6562
6842
  const installPath = resolveServerPath(flags);
6843
+ ensureGitWorkDirEnv(installPath);
6563
6844
  const config = loadServerConfig();
6564
6845
  const withOverrides = config?.composeProfile === "full";
6565
6846
  const userDataOnly = flags["delete-user-data-only"] === true;
@@ -6640,6 +6921,7 @@ async function serverMakeAdmin(rest, flags) {
6640
6921
  async function serverUninstall(flags) {
6641
6922
  requireDocker();
6642
6923
  const installPath = resolveServerPath(flags);
6924
+ ensureGitWorkDirEnv(installPath);
6643
6925
  const config = loadServerConfig();
6644
6926
  const withOverrides = config?.composeProfile === "full";
6645
6927
  const keepData = flags["keep-data"] === true;
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-NS4C7FTB.js";
5
+ } from "./chunk-A5UXPRLU.js";
6
6
  import "./chunk-SFTCIVE2.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -433,6 +433,7 @@ declare class OpenMatesClient {
433
433
  /** Follow-up suggestions from post-processing (may be empty for incognito chats). */
434
434
  followUpSuggestions: string[];
435
435
  }>;
436
+ private persistStreamedEmbeds;
436
437
  /**
437
438
  * Delete a chat by ID.
438
439
  *
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getExtForLang,
8
8
  parseNewChatSuggestionText,
9
9
  serializeToYaml
10
- } from "./chunk-NS4C7FTB.js";
10
+ } from "./chunk-A5UXPRLU.js";
11
11
  import "./chunk-SFTCIVE2.js";
12
12
  export {
13
13
  MATE_NAMES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.11.0-alpha.13",
3
+ "version": "0.11.0-alpha.15",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,9 +18,10 @@
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",
21
22
  "test:unit:server": "node --test --experimental-strip-types tests/server.test.ts",
22
23
  "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"
24
+ "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 && node --test tests/cli.test.ts"
24
25
  },
25
26
  "keywords": [
26
27
  "openmates",