openmates 0.27.0-alpha.61 → 0.27.0-alpha.63
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-OR3BDIWR.js → chunk-D6TL3ILK.js} +899 -37
- package/dist/{chunk-5SFXCJJ4.js → chunk-Q6PGA2B3.js} +41 -0
- package/dist/{cli-CKAB-eW0.d.ts → cli-bEgF9_RV.d.ts} +21 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +2 -2
- package/dist/privacyDaemon.js +1 -1
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/manifest.json +9 -9
- package/package.json +3 -2
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getCredentialStorageMode,
|
|
9
9
|
installPrivacyModel,
|
|
10
10
|
loadAnonymousId,
|
|
11
|
+
loadCachedDirectChatAccountKey,
|
|
11
12
|
loadLocalTeamKey,
|
|
12
13
|
loadSession,
|
|
13
14
|
loadStoredSessionApiUrl,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
readPrivacyPreferences,
|
|
26
27
|
resolveStateDir,
|
|
27
28
|
saveAnonymousId,
|
|
29
|
+
saveCachedDirectChatAccountKey,
|
|
28
30
|
saveLocalTeamKey,
|
|
29
31
|
saveSession,
|
|
30
32
|
saveSyncCache,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
verifyInstalledModel,
|
|
35
37
|
verifyPrivacyRuntime,
|
|
36
38
|
withSessionRefreshLock
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-Q6PGA2B3.js";
|
|
38
40
|
import {
|
|
39
41
|
RemoteAccessReplayGuard,
|
|
40
42
|
createRemoteAccessHandshake,
|
|
@@ -1634,6 +1636,183 @@ async function prepareCliMessagePrivacy(text7, memories, existing = [], scope2 =
|
|
|
1634
1636
|
|
|
1635
1637
|
// src/client.ts
|
|
1636
1638
|
import { createHash as createHash13, randomBytes as randomBytes4, randomUUID as randomUUID9, timingSafeEqual } from "crypto";
|
|
1639
|
+
|
|
1640
|
+
// ../pairing-crypto/src/directChatCrypto.ts
|
|
1641
|
+
import nacl from "tweetnacl";
|
|
1642
|
+
var PREFIX = "OMDM1.";
|
|
1643
|
+
var MAX_ENVELOPE_LENGTH = 2048;
|
|
1644
|
+
var MAX_PAYLOAD_BYTES = 768;
|
|
1645
|
+
var BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
1646
|
+
var KEY_ID = /^[0-9a-f]{64}$/;
|
|
1647
|
+
var encoder = new TextEncoder();
|
|
1648
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1649
|
+
function ownedBuffer(bytes2) {
|
|
1650
|
+
const copy = new Uint8Array(bytes2.length);
|
|
1651
|
+
copy.set(bytes2);
|
|
1652
|
+
return copy.buffer;
|
|
1653
|
+
}
|
|
1654
|
+
function directChatBase64Url(bytes2) {
|
|
1655
|
+
let binary = "";
|
|
1656
|
+
for (let offset = 0; offset < bytes2.length; offset += 8192) {
|
|
1657
|
+
binary += String.fromCharCode(...bytes2.subarray(offset, offset + 8192));
|
|
1658
|
+
}
|
|
1659
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1660
|
+
}
|
|
1661
|
+
function directChatBase64UrlBytes(value, length, field3 = "key", maxLength = length ?? MAX_PAYLOAD_BYTES) {
|
|
1662
|
+
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxLength * 4 / 3) + 2 || !BASE64URL.test(value)) {
|
|
1663
|
+
throw new Error(`Invalid direct-chat ${field3}`);
|
|
1664
|
+
}
|
|
1665
|
+
let decoded;
|
|
1666
|
+
try {
|
|
1667
|
+
const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4));
|
|
1668
|
+
decoded = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
1669
|
+
} catch {
|
|
1670
|
+
throw new Error(`Invalid direct-chat ${field3}`);
|
|
1671
|
+
}
|
|
1672
|
+
if (length !== void 0 && decoded.length !== length || decoded.length > maxLength || directChatBase64Url(decoded) !== value) throw new Error(`Invalid direct-chat ${field3}`);
|
|
1673
|
+
return decoded;
|
|
1674
|
+
}
|
|
1675
|
+
function boundedIdentity(identity3) {
|
|
1676
|
+
if (!identity3 || typeof identity3 !== "object") throw new Error("Invalid direct-chat identity");
|
|
1677
|
+
for (const value of [identity3.teamId, identity3.chatId, identity3.recipientHash]) {
|
|
1678
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128 || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
1679
|
+
throw new Error("Invalid direct-chat identity");
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (!KEY_ID.test(identity3.keyId)) throw new Error("Invalid direct-chat key ID");
|
|
1683
|
+
}
|
|
1684
|
+
function validNonzeroKey(value, field3) {
|
|
1685
|
+
if (value.length !== 32 || value.every((byte) => byte === 0)) throw new Error(`Invalid direct-chat ${field3}`);
|
|
1686
|
+
}
|
|
1687
|
+
function validatePublicKey(value) {
|
|
1688
|
+
const bytes2 = directChatBase64UrlBytes(value, 32, "public key");
|
|
1689
|
+
validNonzeroKey(bytes2, "public key");
|
|
1690
|
+
const probe = nacl.scalarMult(new Uint8Array(32).fill(7), bytes2);
|
|
1691
|
+
if (probe.every((byte) => byte === 0)) throw new Error("Invalid direct-chat public key");
|
|
1692
|
+
return bytes2;
|
|
1693
|
+
}
|
|
1694
|
+
async function directChatKeyId(publicKey) {
|
|
1695
|
+
const bytes2 = validatePublicKey(publicKey);
|
|
1696
|
+
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256", ownedBuffer(bytes2)));
|
|
1697
|
+
return [...digest4].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1698
|
+
}
|
|
1699
|
+
async function generateIdentity() {
|
|
1700
|
+
const pair = nacl.box.keyPair();
|
|
1701
|
+
const publicKey = directChatBase64Url(pair.publicKey);
|
|
1702
|
+
return { publicKey, privateKey: directChatBase64Url(pair.secretKey), keyId: await directChatKeyId(publicKey) };
|
|
1703
|
+
}
|
|
1704
|
+
async function sealDirectChatKey(rawKey, identity3, recipientPublicKey) {
|
|
1705
|
+
boundedIdentity(identity3);
|
|
1706
|
+
validNonzeroKey(rawKey, "chat key");
|
|
1707
|
+
const recipient = validatePublicKey(recipientPublicKey);
|
|
1708
|
+
if (identity3.keyId !== await directChatKeyId(recipientPublicKey)) throw new Error("Direct-chat recipient key ID mismatch");
|
|
1709
|
+
const ephemeral = nacl.box.keyPair();
|
|
1710
|
+
const nonce = nacl.randomBytes(24);
|
|
1711
|
+
const payload = encoder.encode(JSON.stringify({
|
|
1712
|
+
v: 1,
|
|
1713
|
+
team_id: identity3.teamId,
|
|
1714
|
+
chat_id: identity3.chatId,
|
|
1715
|
+
recipient_hash: identity3.recipientHash,
|
|
1716
|
+
key_id: identity3.keyId,
|
|
1717
|
+
key: directChatBase64Url(rawKey)
|
|
1718
|
+
}));
|
|
1719
|
+
const ciphertext = nacl.box(payload, nonce, recipient, ephemeral.secretKey);
|
|
1720
|
+
const envelope = PREFIX + directChatBase64Url(encoder.encode(JSON.stringify({
|
|
1721
|
+
v: 1,
|
|
1722
|
+
ephemeral_public_key: directChatBase64Url(ephemeral.publicKey),
|
|
1723
|
+
nonce: directChatBase64Url(nonce),
|
|
1724
|
+
ciphertext: directChatBase64Url(ciphertext)
|
|
1725
|
+
})));
|
|
1726
|
+
ephemeral.secretKey.fill(0);
|
|
1727
|
+
if (envelope.length > MAX_ENVELOPE_LENGTH) throw new Error("Direct-chat envelope too large");
|
|
1728
|
+
return envelope;
|
|
1729
|
+
}
|
|
1730
|
+
async function openDirectChatKey(envelope, expected, privateKey) {
|
|
1731
|
+
boundedIdentity(expected);
|
|
1732
|
+
if (typeof envelope !== "string" || !envelope.startsWith(PREFIX) || envelope.length > MAX_ENVELOPE_LENGTH) {
|
|
1733
|
+
throw new Error("Invalid direct-chat envelope");
|
|
1734
|
+
}
|
|
1735
|
+
const encoded = envelope.slice(PREFIX.length);
|
|
1736
|
+
const decoded = directChatBase64UrlBytes(encoded, void 0, "envelope");
|
|
1737
|
+
if (decoded.length > MAX_PAYLOAD_BYTES) throw new Error("Direct-chat envelope too large");
|
|
1738
|
+
let parsed;
|
|
1739
|
+
try {
|
|
1740
|
+
parsed = JSON.parse(decoder.decode(decoded));
|
|
1741
|
+
} catch {
|
|
1742
|
+
throw new Error("Invalid direct-chat envelope");
|
|
1743
|
+
}
|
|
1744
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid direct-chat envelope");
|
|
1745
|
+
const outer = parsed;
|
|
1746
|
+
if (Object.keys(outer).sort().join(",") !== "ciphertext,ephemeral_public_key,nonce,v" || outer.v !== 1) {
|
|
1747
|
+
throw new Error("Invalid direct-chat envelope version or fields");
|
|
1748
|
+
}
|
|
1749
|
+
const sender = validatePublicKey(String(outer.ephemeral_public_key));
|
|
1750
|
+
const nonce = directChatBase64UrlBytes(String(outer.nonce), 24, "nonce");
|
|
1751
|
+
const ciphertext = directChatBase64UrlBytes(String(outer.ciphertext), void 0, "ciphertext", 512);
|
|
1752
|
+
if (ciphertext.length < 17) throw new Error("Invalid direct-chat ciphertext");
|
|
1753
|
+
const secret = directChatBase64UrlBytes(privateKey, 32, "private key");
|
|
1754
|
+
validNonzeroKey(secret, "private key");
|
|
1755
|
+
const ownPublic = directChatBase64Url(nacl.scalarMult.base(secret));
|
|
1756
|
+
if (await directChatKeyId(ownPublic) !== expected.keyId) throw new Error("Direct-chat private key ID mismatch");
|
|
1757
|
+
const plaintext = nacl.box.open(ciphertext, nonce, sender, secret);
|
|
1758
|
+
if (!plaintext) throw new Error("Direct-chat key authentication failed");
|
|
1759
|
+
let inner;
|
|
1760
|
+
try {
|
|
1761
|
+
inner = JSON.parse(decoder.decode(plaintext));
|
|
1762
|
+
} catch {
|
|
1763
|
+
throw new Error("Invalid direct-chat key payload");
|
|
1764
|
+
}
|
|
1765
|
+
if (!inner || typeof inner !== "object" || Array.isArray(inner)) throw new Error("Invalid direct-chat key payload");
|
|
1766
|
+
const payload = inner;
|
|
1767
|
+
if (Object.keys(payload).sort().join(",") !== "chat_id,key,key_id,recipient_hash,team_id,v" || payload.v !== 1 || payload.team_id !== expected.teamId || payload.chat_id !== expected.chatId || payload.recipient_hash !== expected.recipientHash || payload.key_id !== expected.keyId) {
|
|
1768
|
+
throw new Error("Direct-chat key binding mismatch");
|
|
1769
|
+
}
|
|
1770
|
+
const key = directChatBase64UrlBytes(String(payload.key), 32, "chat key");
|
|
1771
|
+
validNonzeroKey(key, "chat key");
|
|
1772
|
+
return key;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/directChats.ts
|
|
1776
|
+
function validatePublicChatEncryptionKey(value) {
|
|
1777
|
+
const row3 = value;
|
|
1778
|
+
if (!row3 || typeof row3.public_key !== "string" || typeof row3.key_id !== "string" || !/^[0-9a-f]{64}$/.test(row3.key_id)) {
|
|
1779
|
+
throw new Error("Invalid member chat encryption key");
|
|
1780
|
+
}
|
|
1781
|
+
directChatBase64UrlBytes(row3.public_key, 32, "public key");
|
|
1782
|
+
return { key_id: row3.key_id, public_key: row3.public_key };
|
|
1783
|
+
}
|
|
1784
|
+
async function validatePublicChatEncryptionKeyId(value) {
|
|
1785
|
+
const key = validatePublicChatEncryptionKey(value);
|
|
1786
|
+
if (await directChatKeyId(key.public_key) !== key.key_id) throw new Error("Member chat encryption key ID mismatch");
|
|
1787
|
+
return key;
|
|
1788
|
+
}
|
|
1789
|
+
async function recoverAccountChatIdentity(record10, masterKey) {
|
|
1790
|
+
if (record10.version !== 1 || record10.algorithm !== "x25519-xsalsa20poly1305") throw new Error("Unsupported account chat encryption key");
|
|
1791
|
+
const publicKey = await validatePublicChatEncryptionKeyId(record10);
|
|
1792
|
+
if (typeof record10.encrypted_private_key !== "string" || record10.encrypted_private_key.length > 2048) throw new Error("Invalid account chat private key backup");
|
|
1793
|
+
const encoded = record10.encrypted_private_key;
|
|
1794
|
+
if (!/^[A-Za-z0-9+/_-]+={0,2}$/.test(encoded)) throw new Error("Invalid account chat private key backup");
|
|
1795
|
+
const privateKey = await decryptWithAesGcmCombined(Buffer.from(encoded, "base64url").toString("base64"), masterKey);
|
|
1796
|
+
if (!privateKey) throw new Error("Could not decrypt account chat private key");
|
|
1797
|
+
const secret = directChatBase64UrlBytes(privateKey, 32, "private key");
|
|
1798
|
+
if (secret.every((byte) => byte === 0)) throw new Error("Invalid account chat private key");
|
|
1799
|
+
const nacl2 = (await import("tweetnacl")).default;
|
|
1800
|
+
if (directChatBase64Url(nacl2.scalarMult.base(secret)) !== publicKey.public_key) throw new Error("Account chat key pair mismatch");
|
|
1801
|
+
return { publicKey: publicKey.public_key, privateKey, keyId: publicKey.key_id };
|
|
1802
|
+
}
|
|
1803
|
+
async function createAccountChatIdentity(masterKey) {
|
|
1804
|
+
const identity3 = await generateIdentity();
|
|
1805
|
+
const backup = await encryptWithAesGcmCombined(identity3.privateKey, masterKey);
|
|
1806
|
+
return { identity: identity3, record: {
|
|
1807
|
+
version: 1,
|
|
1808
|
+
algorithm: "x25519-xsalsa20poly1305",
|
|
1809
|
+
key_id: identity3.keyId,
|
|
1810
|
+
public_key: identity3.publicKey,
|
|
1811
|
+
encrypted_private_key: Buffer.from(backup, "base64").toString("base64url")
|
|
1812
|
+
} };
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/client.ts
|
|
1637
1816
|
import { arch, platform, release } from "os";
|
|
1638
1817
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
1639
1818
|
import { stdin as stdin2, stdout } from "process";
|
|
@@ -2475,13 +2654,13 @@ var OpenMatesHttpClient = class {
|
|
|
2475
2654
|
throw new Error("SSE response body is not readable");
|
|
2476
2655
|
}
|
|
2477
2656
|
const reader = response.body.getReader();
|
|
2478
|
-
const
|
|
2657
|
+
const decoder3 = new TextDecoder();
|
|
2479
2658
|
let buffer = "";
|
|
2480
2659
|
try {
|
|
2481
2660
|
while (true) {
|
|
2482
2661
|
const { done, value } = await reader.read();
|
|
2483
2662
|
if (done) break;
|
|
2484
|
-
buffer +=
|
|
2663
|
+
buffer += decoder3.decode(value, { stream: true });
|
|
2485
2664
|
let separatorIndex = buffer.indexOf("\n\n");
|
|
2486
2665
|
while (separatorIndex >= 0) {
|
|
2487
2666
|
const rawEvent = buffer.slice(0, separatorIndex);
|
|
@@ -2701,12 +2880,12 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
2701
2880
|
|
|
2702
2881
|
// ../ui/src/utils/projectIgnoredReadGrant.ts
|
|
2703
2882
|
var GRANT_LIFETIME_MS = 5 * 60 * 1e3;
|
|
2704
|
-
var
|
|
2883
|
+
var encoder2 = new TextEncoder();
|
|
2705
2884
|
function validScope(scope2) {
|
|
2706
2885
|
return [scope2.projectId, scope2.sourceId, scope2.requestId, scope2.chatId, scope2.operationId].every((value) => typeof value === "string" && value.length > 0 && value.length <= 128) && typeof scope2.path === "string" && scope2.path.length > 0 && scope2.path.length <= 4096 && !scope2.path.includes("\\") && !scope2.path.startsWith("/") && !/^[a-z]:/i.test(scope2.path) && !Array.from(scope2.path).some((value) => value.charCodeAt(0) < 32 || value.charCodeAt(0) === 127) && scope2.path.split("/").every((part) => part && part !== "." && part !== "..");
|
|
2707
2886
|
}
|
|
2708
2887
|
function material(grant) {
|
|
2709
|
-
return
|
|
2888
|
+
return encoder2.encode(JSON.stringify([
|
|
2710
2889
|
"openmates-ignored-read-v1",
|
|
2711
2890
|
grant.projectId,
|
|
2712
2891
|
grant.sourceId,
|
|
@@ -3923,6 +4102,14 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
3923
4102
|
hashed_user_id: hashedUserId,
|
|
3924
4103
|
file_path: embed.filePath,
|
|
3925
4104
|
content_hash: embed.contentHash,
|
|
4105
|
+
...embed.appId && embed.skillId ? {
|
|
4106
|
+
app_id: embed.appId,
|
|
4107
|
+
skill_id: embed.skillId,
|
|
4108
|
+
root_embed_id: embed.embedId,
|
|
4109
|
+
workspace_origin: "chat"
|
|
4110
|
+
} : {},
|
|
4111
|
+
...embed.versionNumber ? { version_number: embed.versionNumber } : {},
|
|
4112
|
+
encryption_mode: "client",
|
|
3926
4113
|
text_length_chars: embed.textLengthChars,
|
|
3927
4114
|
created_at: nowSeconds3,
|
|
3928
4115
|
updated_at: nowSeconds3,
|
|
@@ -7311,6 +7498,35 @@ import { basename as basename2, extname as extname2, resolve as resolve3 } from
|
|
|
7311
7498
|
import { homedir as homedir2 } from "os";
|
|
7312
7499
|
import { createHash as createHash10 } from "crypto";
|
|
7313
7500
|
import JSZip2 from "jszip";
|
|
7501
|
+
|
|
7502
|
+
// src/diagramHtml.ts
|
|
7503
|
+
function inspectDiagramHtml(source) {
|
|
7504
|
+
if (!source || Buffer.byteLength(source, "utf8") > 1024 * 1024) return null;
|
|
7505
|
+
if (!/^\s*<!doctype html\s*>/i.test(source) || !/<html\b[^>]*>[\s\S]*<head\b[^>]*>[\s\S]*<\/head\s*>[\s\S]*<body\b[^>]*>[\s\S]*<svg\b/i.test(source) || !/<\/svg\s*>[\s\S]*<\/body\s*>[\s\S]*<\/html\s*>\s*$/i.test(source) || !/<g\b[^>]*\bdata-om-component\s*=\s*["'][a-z][a-z0-9_-]{0,63}["']/i.test(source)) return null;
|
|
7506
|
+
let checked = source.replace(/\sxmlns(?::[\w-]+)?\s*=\s*["']http:\/\/www\.w3\.org\/[A-Za-z0-9/.-]+["']/gi, "");
|
|
7507
|
+
checked = checked.replace(/<!--[\s\S]*?-->/g, "").replace(/&/gi, "&");
|
|
7508
|
+
checked = checked.replace(/data:font\/woff2;base64,([A-Za-z0-9+/]+={0,2})/gi, (whole, encoded) => {
|
|
7509
|
+
const bytes2 = Buffer.from(encoded, "base64");
|
|
7510
|
+
return bytes2.length > 0 && bytes2.length <= 256 * 1024 && bytes2.subarray(0, 4).toString() === "wOF2" && /@font-face\s*\{[^}]*$/i.test(checked.slice(0, checked.indexOf(whole))) ? "#embedded-font" : "unsafe-font-data";
|
|
7511
|
+
});
|
|
7512
|
+
if (/<!--|-->|<![^d]|<\?|&#|&[a-z][a-z0-9]+;/i.test(checked) || /<\s*\/?\s*(?:script|iframe|frame|object|embed|applet|link|base|img|image|video|audio|source|track|form|input|textarea|select|button|template|portal|foreignobject|animate|set|discard|feimage|math|noscript)\b/i.test(source) || /\s(?:on[a-z]+|srcdoc|src|srcset|poster|data|action|formaction|background|http-equiv)\s*=/i.test(source) || /(?:https?:|file:|javascript:|data:|\/\/|unsafe-font-data)/i.test(checked) || /@\s*(?:import|namespace|document)|expression\s*\(|image-set\s*\(|behavior\s*:|-moz-binding\s*:|\\/i.test(checked)) return null;
|
|
7513
|
+
for (const match of checked.matchAll(/url\(\s*(['"]?)([^)"']+)\1\s*\)/gi)) {
|
|
7514
|
+
if (!/^#[a-z_][\w.:-]{0,119}$/i.test(match[2].trim())) return null;
|
|
7515
|
+
}
|
|
7516
|
+
if (/url\s*\(/i.test(checked.replace(/url\(\s*(['"]?)([^)"']+)\1\s*\)/gi, ""))) return null;
|
|
7517
|
+
const title = /<title\b[^>]*>([^<>]{1,160})<\/title\s*>/i.exec(source)?.[1]?.trim();
|
|
7518
|
+
const viewBox = /<svg\b[^>]*\bviewBox\s*=\s*["']([^"']+)["']/i.exec(source)?.[1];
|
|
7519
|
+
if (!title || !viewBox) return null;
|
|
7520
|
+
const parts = viewBox.trim().split(/[\s,]+/).map(Number);
|
|
7521
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n)) || parts[2] < 1 || parts[2] > 8192 || parts[3] < 1 || parts[3] > 8192) return null;
|
|
7522
|
+
const kind = /<svg\b[^>]*\bdata-diagram-kind\s*=\s*["']([a-z][a-z0-9_-]{0,63})["']/i.exec(source)?.[1] ?? /<svg\b[^>]*\bdata-om-diagram\s*=\s*["']([a-z][a-z0-9_-]{0,63})["']/i.exec(source)?.[1] ?? /<meta\b[^>]*\bname\s*=\s*["']diagram-kind["'][^>]*\bcontent\s*=\s*["']([a-z][a-z0-9_-]{0,63})["']/i.exec(source)?.[1] ?? "diagram";
|
|
7523
|
+
const renderWidth = Number(/<html\b[^>]*\bdata-om-render-width\s*=\s*["']([0-9]{1,4})["']/i.exec(source)?.[1] ?? parts[2]);
|
|
7524
|
+
const renderHeight = Number(/<html\b[^>]*\bdata-om-render-height\s*=\s*["']([0-9]{1,4})["']/i.exec(source)?.[1] ?? parts[3]);
|
|
7525
|
+
if (!(renderWidth >= 1 && renderWidth <= 8192 && renderHeight >= 1 && renderHeight <= 8192)) return null;
|
|
7526
|
+
return { title, diagramKind: kind, renderWidth: Math.round(renderWidth), renderHeight: Math.round(renderHeight) };
|
|
7527
|
+
}
|
|
7528
|
+
|
|
7529
|
+
// src/fileEmbed.ts
|
|
7314
7530
|
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
7315
7531
|
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7316
7532
|
".pem",
|
|
@@ -7833,6 +8049,47 @@ function processCodeFile(filePath, filename, redactor) {
|
|
|
7833
8049
|
}
|
|
7834
8050
|
}
|
|
7835
8051
|
const language = detectLanguage(filename);
|
|
8052
|
+
if (language === "html") {
|
|
8053
|
+
const diagram = inspectDiagramHtml(content);
|
|
8054
|
+
if (diagram) {
|
|
8055
|
+
const embedId2 = generateEmbedId();
|
|
8056
|
+
const embedRef2 = createEmbedRef("diagram_html", `${filename}:${embedId2}`);
|
|
8057
|
+
return {
|
|
8058
|
+
embed: {
|
|
8059
|
+
embedId: embedId2,
|
|
8060
|
+
embedRef: embedRef2,
|
|
8061
|
+
type: "diagram_html",
|
|
8062
|
+
content: toonEncodeContent({
|
|
8063
|
+
type: "diagram_html",
|
|
8064
|
+
app_id: "diagrams",
|
|
8065
|
+
skill_id: "html",
|
|
8066
|
+
html_source: content,
|
|
8067
|
+
title: diagram.title,
|
|
8068
|
+
diagram_kind: diagram.diagramKind,
|
|
8069
|
+
render_width: diagram.renderWidth,
|
|
8070
|
+
render_height: diagram.renderHeight,
|
|
8071
|
+
schema_version: 1,
|
|
8072
|
+
version_number: 1,
|
|
8073
|
+
embed_ref: embedRef2,
|
|
8074
|
+
status: "finished"
|
|
8075
|
+
}),
|
|
8076
|
+
textPreview: `${diagram.title} (${diagram.diagramKind})`,
|
|
8077
|
+
status: "finished",
|
|
8078
|
+
filePath: filename,
|
|
8079
|
+
contentHash: createHash10("sha256").update(content).digest("hex"),
|
|
8080
|
+
textLengthChars: content.length,
|
|
8081
|
+
appId: "diagrams",
|
|
8082
|
+
skillId: "html",
|
|
8083
|
+
versionNumber: 1
|
|
8084
|
+
},
|
|
8085
|
+
referenceBlock: createEmbedReferenceBlock(embedRef2),
|
|
8086
|
+
displayName: filename,
|
|
8087
|
+
secretsRedacted,
|
|
8088
|
+
zeroKnowledge,
|
|
8089
|
+
requiresUpload: false
|
|
8090
|
+
};
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
7836
8093
|
const lineCount = content.split("\n").length;
|
|
7837
8094
|
const embedId = generateEmbedId();
|
|
7838
8095
|
const embedRef = createEmbedRef("code", `${filename}:${embedId}`);
|
|
@@ -9906,13 +10163,13 @@ import { client, ready, server } from "@serenity-kit/opaque";
|
|
|
9906
10163
|
var PAIR_PIN_ALPHABET = "ABCDEFGHJKLMNPQRTUVWXY3468";
|
|
9907
10164
|
var PIN_LENGTH = 6;
|
|
9908
10165
|
var KSF = { "argon2id-custom": { memory: 8192, iterations: 3, parallelism: 1 } };
|
|
9909
|
-
var
|
|
9910
|
-
var
|
|
9911
|
-
var BUNDLE_INFO =
|
|
9912
|
-
var
|
|
10166
|
+
var encoder3 = new TextEncoder();
|
|
10167
|
+
var decoder2 = new TextDecoder("utf-8", { fatal: true });
|
|
10168
|
+
var BUNDLE_INFO = encoder3.encode("openmates-pair-v2/bundle");
|
|
10169
|
+
var BASE64URL2 = /^[A-Za-z0-9_-]+$/;
|
|
9913
10170
|
var HEX_256 = /^[0-9a-f]{64}$/;
|
|
9914
10171
|
var BUNDLE_KEYS = /* @__PURE__ */ new Set(["protocol_version", "master_key_exported", "grant_secret", "user_email_salt", "hashed_email", "user_id", "account_context"]);
|
|
9915
|
-
function
|
|
10172
|
+
function ownedBuffer2(bytes2) {
|
|
9916
10173
|
return bytes2;
|
|
9917
10174
|
}
|
|
9918
10175
|
function requireAscii(value, name, max = 128) {
|
|
@@ -9952,7 +10209,7 @@ function validateContext(context) {
|
|
|
9952
10209
|
auto_logout_minutes: parsed[6]
|
|
9953
10210
|
};
|
|
9954
10211
|
if (createPairContext(fields) !== context) throw new Error("Noncanonical pairing context");
|
|
9955
|
-
return { text: context, bytes:
|
|
10212
|
+
return { text: context, bytes: encoder3.encode(context), fields };
|
|
9956
10213
|
}
|
|
9957
10214
|
function identifiers(context) {
|
|
9958
10215
|
return { client: `openmates-pair-v2/client/${context}`, server: `openmates-pair-v2/server/${context}` };
|
|
@@ -9968,7 +10225,7 @@ function base64url(bytes2) {
|
|
|
9968
10225
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
9969
10226
|
}
|
|
9970
10227
|
function decodeBase64url(value, maxBytes) {
|
|
9971
|
-
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxBytes * 4 / 3) + 2 || !
|
|
10228
|
+
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxBytes * 4 / 3) + 2 || !BASE64URL2.test(value)) {
|
|
9972
10229
|
throw new Error("Invalid pairing encoding");
|
|
9973
10230
|
}
|
|
9974
10231
|
let bytes2;
|
|
@@ -9995,7 +10252,7 @@ async function hashPairSecret(secret) {
|
|
|
9995
10252
|
const bytes2 = decodeBase64url(secret, 32);
|
|
9996
10253
|
if (bytes2.length !== 32) throw new Error("Invalid pairing secret");
|
|
9997
10254
|
try {
|
|
9998
|
-
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256",
|
|
10255
|
+
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256", ownedBuffer2(bytes2)));
|
|
9999
10256
|
return [...digest4].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
10000
10257
|
} finally {
|
|
10001
10258
|
bytes2.fill(0);
|
|
@@ -10011,10 +10268,10 @@ async function deriveBundleKey(sessionKey, contextBytes) {
|
|
|
10011
10268
|
const raw = decodeBase64url(sessionKey, 64);
|
|
10012
10269
|
if (raw.length !== 64) throw new Error("Invalid pairing session key");
|
|
10013
10270
|
try {
|
|
10014
|
-
const salt = await crypto.subtle.digest("SHA-256",
|
|
10015
|
-
const ikm = await crypto.subtle.importKey("raw",
|
|
10271
|
+
const salt = await crypto.subtle.digest("SHA-256", ownedBuffer2(contextBytes));
|
|
10272
|
+
const ikm = await crypto.subtle.importKey("raw", ownedBuffer2(raw), "HKDF", false, ["deriveKey"]);
|
|
10016
10273
|
return crypto.subtle.deriveKey(
|
|
10017
|
-
{ name: "HKDF", hash: "SHA-256", salt, info:
|
|
10274
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: ownedBuffer2(BUNDLE_INFO) },
|
|
10018
10275
|
ikm,
|
|
10019
10276
|
{ name: "AES-GCM", length: 256 },
|
|
10020
10277
|
false,
|
|
@@ -10090,13 +10347,13 @@ async function createPairReceiver(context, pin) {
|
|
|
10090
10347
|
if (nonce.length !== 12) throw new Error("Invalid pairing IV");
|
|
10091
10348
|
const ciphertext = decodeBase64url(encryptedBundle, 65536);
|
|
10092
10349
|
const plaintext = new Uint8Array(await crypto.subtle.decrypt(
|
|
10093
|
-
{ name: "AES-GCM", iv:
|
|
10350
|
+
{ name: "AES-GCM", iv: ownedBuffer2(nonce), additionalData: ownedBuffer2(binding.bytes), tagLength: 128 },
|
|
10094
10351
|
key,
|
|
10095
|
-
|
|
10352
|
+
ownedBuffer2(ciphertext)
|
|
10096
10353
|
));
|
|
10097
10354
|
try {
|
|
10098
10355
|
assertActive();
|
|
10099
|
-
return validateBundle(JSON.parse(
|
|
10356
|
+
return validateBundle(JSON.parse(decoder2.decode(plaintext)), binding.fields.authorizer_user_id);
|
|
10100
10357
|
} finally {
|
|
10101
10358
|
nonce.fill(0);
|
|
10102
10359
|
ciphertext.fill(0);
|
|
@@ -11157,6 +11414,7 @@ function extractVersionedEmbedContent(content) {
|
|
|
11157
11414
|
].join("\n").trim();
|
|
11158
11415
|
}
|
|
11159
11416
|
if (typeof content.remotion_source === "string") return content.remotion_source;
|
|
11417
|
+
if (typeof content.html_source === "string") return content.html_source;
|
|
11160
11418
|
if (typeof content.code === "string") return content.code;
|
|
11161
11419
|
if (typeof content.html === "string") return content.html;
|
|
11162
11420
|
if (typeof content.table === "string") return content.table;
|
|
@@ -11172,6 +11430,7 @@ function buildRestoredEmbedContentObject(current2, restoredContent, newVersion)
|
|
|
11172
11430
|
if (typeof current2.remotion_source === "string") {
|
|
11173
11431
|
return { ...restored, remotion_source: restoredContent, current_source_version: newVersion };
|
|
11174
11432
|
}
|
|
11433
|
+
if (typeof current2.html_source === "string") return { ...restored, html_source: restoredContent };
|
|
11175
11434
|
if (typeof current2.code === "string") return { ...restored, code: restoredContent };
|
|
11176
11435
|
if (typeof current2.html === "string") return { ...restored, html: restoredContent };
|
|
11177
11436
|
if (typeof current2.table === "string") return { ...restored, table: restoredContent };
|
|
@@ -11286,6 +11545,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11286
11545
|
session;
|
|
11287
11546
|
http;
|
|
11288
11547
|
explicitSession;
|
|
11548
|
+
directChatIdentity;
|
|
11549
|
+
directChatUserHash;
|
|
11289
11550
|
constructor(options = {}) {
|
|
11290
11551
|
this.explicitSession = options.session !== void 0;
|
|
11291
11552
|
const diskSession = options.session ?? (options.loadStoredSession === false ? null : this.getValidSessionFromDisk());
|
|
@@ -11551,6 +11812,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11551
11812
|
if (!response.ok) throw new Error(`Team list failed with HTTP ${response.status}`);
|
|
11552
11813
|
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11553
11814
|
const teams = response.data.teams ?? [];
|
|
11815
|
+
if (teams.length) await this.ensureDirectChatIdentity();
|
|
11816
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11554
11817
|
await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team, owner2.isCurrent)));
|
|
11555
11818
|
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11556
11819
|
const teamIds = teams.map((team) => team.team_id).filter((teamId) => typeof teamId === "string" && teamId.length > 0);
|
|
@@ -11560,6 +11823,60 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11560
11823
|
}
|
|
11561
11824
|
return teams;
|
|
11562
11825
|
}
|
|
11826
|
+
async openTeamDirectChat(teamId, recipientHash) {
|
|
11827
|
+
if (!teamId || !/^[0-9a-f]{64}$/.test(recipientHash)) throw new Error("A Team ID and member hash are required.");
|
|
11828
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
11829
|
+
const [ownHash, ownIdentity] = await Promise.all([this.ownDirectChatHash(), this.ensureDirectChatIdentity()]);
|
|
11830
|
+
if (recipientHash === ownHash) throw new Error("Choose another Team member to message.");
|
|
11831
|
+
const cache = await this.ensureSynced(false, [], { teamId });
|
|
11832
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11833
|
+
const existing = cache.chats.find((row4) => row4.details.chat_visibility === "direct" && (!row4.details.team_id || row4.details.team_id === teamId) && (!row4.details.hashed_team_id || row4.details.hashed_team_id === computeSHA256(teamId)) && Array.isArray(row4.details.participant_user_hashes) && row4.details.participant_user_hashes.length === 2 && row4.details.participant_user_hashes.includes(ownHash) && row4.details.participant_user_hashes.includes(recipientHash));
|
|
11834
|
+
if (existing) {
|
|
11835
|
+
const wrapping2 = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
11836
|
+
return this.decryptChatListItem(existing, wrapping2, cache, teamId);
|
|
11837
|
+
}
|
|
11838
|
+
const recipient = await this.http.get(
|
|
11839
|
+
`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(recipientHash)}/chat-encryption-key`,
|
|
11840
|
+
this.getCliRequestHeaders()
|
|
11841
|
+
);
|
|
11842
|
+
if (recipient.status === 404) throw new Error("Direct message pending: this Team member has not enrolled a chat encryption key yet.");
|
|
11843
|
+
if (!recipient.ok) throw new Error(`Team member chat encryption key failed with HTTP ${recipient.status}`);
|
|
11844
|
+
const recipientKey = await validatePublicChatEncryptionKeyId(recipient.data);
|
|
11845
|
+
const chatId = randomUUID9();
|
|
11846
|
+
const chatKey = new Uint8Array(randomBytes4(32));
|
|
11847
|
+
const ownEnvelope = await sealDirectChatKey(chatKey, { teamId, chatId, recipientHash: ownHash, keyId: ownIdentity.keyId }, ownIdentity.publicKey);
|
|
11848
|
+
const recipientEnvelope = await sealDirectChatKey(chatKey, { teamId, chatId, recipientHash, keyId: recipientKey.key_id }, recipientKey.public_key);
|
|
11849
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11850
|
+
const response = await this.http.post(
|
|
11851
|
+
`/v1/teams/${encodeURIComponent(teamId)}/direct-chats`,
|
|
11852
|
+
{
|
|
11853
|
+
chat_id: chatId,
|
|
11854
|
+
recipient_user_hash: recipientHash,
|
|
11855
|
+
participant_key_envelopes: {
|
|
11856
|
+
[ownHash]: { key_id: ownIdentity.keyId, encrypted_chat_key: ownEnvelope },
|
|
11857
|
+
[recipientHash]: { key_id: recipientKey.key_id, encrypted_chat_key: recipientEnvelope }
|
|
11858
|
+
},
|
|
11859
|
+
encrypted_title: await encryptWithAesGcmCombined("Private Team message", chatKey)
|
|
11860
|
+
},
|
|
11861
|
+
this.getCliRequestHeaders()
|
|
11862
|
+
);
|
|
11863
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11864
|
+
if (!response.ok || !response.data.chat) throw new Error(`Direct chat creation failed with HTTP ${response.status}`);
|
|
11865
|
+
const details = response.data.chat;
|
|
11866
|
+
if (details.chat_visibility !== "direct" || details.team_id !== teamId || details.id !== chatId) {
|
|
11867
|
+
throw new Error("Direct chat creation returned mismatched metadata.");
|
|
11868
|
+
}
|
|
11869
|
+
const latest = loadSyncCache(teamId) ?? cache;
|
|
11870
|
+
const row3 = { details, messages: [] };
|
|
11871
|
+
saveSyncCache({
|
|
11872
|
+
...latest,
|
|
11873
|
+
chats: [row3, ...latest.chats],
|
|
11874
|
+
loadedChatCount: latest.loadedChatCount + 1,
|
|
11875
|
+
totalChatCount: latest.totalChatCount + 1
|
|
11876
|
+
}, teamId);
|
|
11877
|
+
const wrapping = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
11878
|
+
return this.decryptChatListItem(row3, wrapping, loadSyncCache(teamId), teamId);
|
|
11879
|
+
}
|
|
11563
11880
|
async approveTeamName(name) {
|
|
11564
11881
|
this.requireSession();
|
|
11565
11882
|
const normalized = name.trim().toLowerCase();
|
|
@@ -11610,6 +11927,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11610
11927
|
return response.data.team;
|
|
11611
11928
|
}
|
|
11612
11929
|
async getTeam(teamId) {
|
|
11930
|
+
await this.ensureDirectChatIdentity();
|
|
11613
11931
|
const owner2 = this.captureTeamAccountOwner();
|
|
11614
11932
|
const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}`, this.getCliRequestHeaders());
|
|
11615
11933
|
if (!response.ok || !response.data.team) throw new Error(`Team get failed with HTTP ${response.status}`);
|
|
@@ -11659,6 +11977,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11659
11977
|
}
|
|
11660
11978
|
async listTeamMembers(teamId) {
|
|
11661
11979
|
this.requireSession();
|
|
11980
|
+
await this.ensureDirectChatIdentity();
|
|
11662
11981
|
const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}/members`, this.getCliRequestHeaders());
|
|
11663
11982
|
if (!response.ok) throw new Error(`Team members failed with HTTP ${response.status}`);
|
|
11664
11983
|
return Promise.all((response.data.members ?? []).map((member) => this.decryptTeamMember(teamId, member)));
|
|
@@ -12785,6 +13104,111 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
12785
13104
|
return response.data.user ?? {};
|
|
12786
13105
|
});
|
|
12787
13106
|
}
|
|
13107
|
+
async ownDirectChatHash() {
|
|
13108
|
+
const session = this.requireSession();
|
|
13109
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
13110
|
+
const scope2 = `${session.apiUrl}:${session.sessionId}:${session.hashedEmail}:${session.masterKeyExportedB64}`;
|
|
13111
|
+
if (this.directChatUserHash?.scope !== scope2) {
|
|
13112
|
+
const cached = loadCachedDirectChatAccountKey(session, this.apiUrl);
|
|
13113
|
+
const promise = (cached ? Promise.resolve({ id: cached.accountId }) : this.whoAmI()).then((user2) => {
|
|
13114
|
+
const id = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : null;
|
|
13115
|
+
if (!id) throw new Error("Authenticated identity is required for direct chats.");
|
|
13116
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading direct chat identity.");
|
|
13117
|
+
return createHash13("sha256").update(id).digest("hex");
|
|
13118
|
+
}).catch((error) => {
|
|
13119
|
+
if (!(error instanceof TypeError)) throw error;
|
|
13120
|
+
const cached2 = loadCachedDirectChatAccountKey(session, this.apiUrl);
|
|
13121
|
+
if (!cached2) throw error;
|
|
13122
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading direct chat identity.");
|
|
13123
|
+
return createHash13("sha256").update(cached2.accountId).digest("hex");
|
|
13124
|
+
}).catch((error) => {
|
|
13125
|
+
if (this.directChatUserHash?.promise === promise) this.directChatUserHash = void 0;
|
|
13126
|
+
throw error;
|
|
13127
|
+
});
|
|
13128
|
+
this.directChatUserHash = { scope: scope2, promise };
|
|
13129
|
+
}
|
|
13130
|
+
return this.directChatUserHash.promise;
|
|
13131
|
+
}
|
|
13132
|
+
async ensureDirectChatIdentity() {
|
|
13133
|
+
const session = this.requireSession();
|
|
13134
|
+
const scope2 = `${session.apiUrl}:${session.sessionId}:${session.hashedEmail}:${session.masterKeyExportedB64}`;
|
|
13135
|
+
if (this.directChatIdentity?.scope !== scope2) {
|
|
13136
|
+
const promise = this.loadDirectChatIdentity().catch((error) => {
|
|
13137
|
+
if (this.directChatIdentity?.promise === promise) this.directChatIdentity = void 0;
|
|
13138
|
+
throw error;
|
|
13139
|
+
});
|
|
13140
|
+
this.directChatIdentity = { scope: scope2, promise };
|
|
13141
|
+
}
|
|
13142
|
+
return this.directChatIdentity.promise;
|
|
13143
|
+
}
|
|
13144
|
+
async loadDirectChatIdentity() {
|
|
13145
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
13146
|
+
const masterKey = this.getMasterKeyBytes();
|
|
13147
|
+
const recover = async (record11) => {
|
|
13148
|
+
const identity4 = await recoverAccountChatIdentity(record11, masterKey);
|
|
13149
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13150
|
+
return identity4;
|
|
13151
|
+
};
|
|
13152
|
+
const local = loadCachedDirectChatAccountKey(this.requireSession(), this.apiUrl);
|
|
13153
|
+
if (local) return recover(local.record);
|
|
13154
|
+
const cacheVerifiedRecord = async (record11) => {
|
|
13155
|
+
try {
|
|
13156
|
+
const user2 = await this.whoAmI();
|
|
13157
|
+
if (!owner2.isCurrent()) return;
|
|
13158
|
+
const accountId = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : null;
|
|
13159
|
+
const trustedAccountId = loadTrustedAccountId();
|
|
13160
|
+
if (accountId && (trustedAccountId ? trustedAccountId === accountId : process.env.OPENMATES_ACCOUNT_GUARD !== "required")) {
|
|
13161
|
+
saveCachedDirectChatAccountKey(this.requireSession(), this.apiUrl, accountId, record11);
|
|
13162
|
+
}
|
|
13163
|
+
} catch {
|
|
13164
|
+
}
|
|
13165
|
+
};
|
|
13166
|
+
let existing;
|
|
13167
|
+
try {
|
|
13168
|
+
existing = await this.http.get(
|
|
13169
|
+
"/v1/account/chat-encryption-key",
|
|
13170
|
+
this.getCliRequestHeaders()
|
|
13171
|
+
);
|
|
13172
|
+
} catch (error) {
|
|
13173
|
+
if (!(error instanceof TypeError)) throw error;
|
|
13174
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13175
|
+
const cached = loadCachedDirectChatAccountKey(this.requireSession(), this.apiUrl);
|
|
13176
|
+
if (!cached) throw error;
|
|
13177
|
+
return recover(cached.record);
|
|
13178
|
+
}
|
|
13179
|
+
if (!existing.ok && existing.status !== 404) throw new Error(`Account chat encryption key failed with HTTP ${existing.status}`);
|
|
13180
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key enrollment.");
|
|
13181
|
+
if (existing.data.key) {
|
|
13182
|
+
const identity4 = await recover(existing.data.key);
|
|
13183
|
+
await cacheVerifiedRecord(existing.data.key);
|
|
13184
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13185
|
+
return identity4;
|
|
13186
|
+
}
|
|
13187
|
+
const generated = await createAccountChatIdentity(masterKey);
|
|
13188
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key enrollment.");
|
|
13189
|
+
const saved = await this.http.put(
|
|
13190
|
+
"/v1/account/chat-encryption-key",
|
|
13191
|
+
generated.record,
|
|
13192
|
+
this.getCliRequestHeaders()
|
|
13193
|
+
);
|
|
13194
|
+
if (saved.status === 409) {
|
|
13195
|
+
const current2 = await this.http.get(
|
|
13196
|
+
"/v1/account/chat-encryption-key",
|
|
13197
|
+
this.getCliRequestHeaders()
|
|
13198
|
+
);
|
|
13199
|
+
if (!owner2.isCurrent() || !current2.ok || !current2.data.key) throw new Error("Account chat encryption key enrollment conflicted. Retry.");
|
|
13200
|
+
const identity4 = await recover(current2.data.key);
|
|
13201
|
+
await cacheVerifiedRecord(current2.data.key);
|
|
13202
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13203
|
+
return identity4;
|
|
13204
|
+
}
|
|
13205
|
+
if (!owner2.isCurrent() || !saved.ok) throw new Error(`Account chat encryption key enrollment failed with HTTP ${saved.status}`);
|
|
13206
|
+
const record10 = saved.data.key ?? generated.record;
|
|
13207
|
+
const identity3 = await recover(record10);
|
|
13208
|
+
await cacheVerifiedRecord(record10);
|
|
13209
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13210
|
+
return identity3;
|
|
13211
|
+
}
|
|
12788
13212
|
async getTopicPreferences() {
|
|
12789
13213
|
const user2 = await this.whoAmI();
|
|
12790
13214
|
return await this.decryptTopicPreferences(user2.encrypted_settings);
|
|
@@ -13151,6 +13575,20 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13151
13575
|
}
|
|
13152
13576
|
async resolveChatKey(cache, cached, wrappingKey, teamId = null) {
|
|
13153
13577
|
const chatId = String(cached.details.id ?? "");
|
|
13578
|
+
if (cached.details.chat_visibility === "direct") {
|
|
13579
|
+
if (!teamId || !chatId || cached.details.team_id && cached.details.team_id !== teamId || cached.details.hashed_team_id && cached.details.hashed_team_id !== computeSHA256(teamId)) {
|
|
13580
|
+
throw new Error("Direct chat Team binding mismatch.");
|
|
13581
|
+
}
|
|
13582
|
+
const ownHash = await this.ownDirectChatHash();
|
|
13583
|
+
const participants = cached.details.participant_user_hashes;
|
|
13584
|
+
if (!Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash)) {
|
|
13585
|
+
throw new Error("Direct chat participant binding mismatch.");
|
|
13586
|
+
}
|
|
13587
|
+
const encrypted = cached.details.encrypted_chat_key;
|
|
13588
|
+
if (typeof encrypted !== "string") throw new Error("Direct chat envelope unavailable.");
|
|
13589
|
+
const identity3 = await this.ensureDirectChatIdentity();
|
|
13590
|
+
return openDirectChatKey(encrypted, { teamId, chatId, recipientHash: ownHash, keyId: identity3.keyId }, identity3.privateKey);
|
|
13591
|
+
}
|
|
13154
13592
|
const wrapperKey = chatId ? this.getContextWrapperEncryptedChatKey(cache, chatId, teamId) : null;
|
|
13155
13593
|
const encryptedChatKey = wrapperKey ?? (typeof cached.details.encrypted_chat_key === "string" ? cached.details.encrypted_chat_key : null);
|
|
13156
13594
|
return encryptedChatKey ? this.decryptChatKey(encryptedChatKey, wrappingKey) : null;
|
|
@@ -13182,6 +13620,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13182
13620
|
const slug = chatKeyBytes && typeof d.encrypted_slug === "string" ? slugValue ?? "" : null;
|
|
13183
13621
|
return {
|
|
13184
13622
|
id,
|
|
13623
|
+
chatVisibility: d.chat_visibility === "direct" ? "direct" : teamId ? "team" : "personal",
|
|
13624
|
+
participantUserHashes: Array.isArray(d.participant_user_hashes) ? d.participant_user_hashes.filter((value) => typeof value === "string") : void 0,
|
|
13185
13625
|
shortId: id.slice(0, 8),
|
|
13186
13626
|
slug,
|
|
13187
13627
|
title,
|
|
@@ -14328,17 +14768,40 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14328
14768
|
const selectedTeamId = this.getActiveTeamId();
|
|
14329
14769
|
const current2 = () => {
|
|
14330
14770
|
const active = this.session;
|
|
14331
|
-
if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] ||
|
|
14771
|
+
if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] || this.getActiveTeamId() !== selectedTeamId || this.resolveTeamContext(options) !== teamId)
|
|
14332
14772
|
throw new Error("Chat workspace changed");
|
|
14333
14773
|
};
|
|
14334
|
-
|
|
14774
|
+
let cache = options.preferCache ? loadSyncCache(teamId) : await this.ensureSynced(false, [], options);
|
|
14335
14775
|
current2();
|
|
14336
14776
|
if (!cache) throw new Error("Chat cache unavailable. Open the chat to refresh it.");
|
|
14777
|
+
const diskCacheAtStart = loadSyncCache(teamId);
|
|
14778
|
+
const diskChatIdsAtStart = diskCacheAtStart?.chats.map((chat) => String(chat.details.id));
|
|
14337
14779
|
const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(owner2[1], teamId) : null;
|
|
14338
14780
|
if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline. Reconnect to refresh Team access.");
|
|
14339
14781
|
const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
|
|
14340
14782
|
current2();
|
|
14341
|
-
|
|
14783
|
+
let found;
|
|
14784
|
+
let fetchedMetadata = false;
|
|
14785
|
+
try {
|
|
14786
|
+
found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
|
|
14787
|
+
} catch (error) {
|
|
14788
|
+
if (options.preferCache || !(error instanceof Error) || !/^Chat '.*' not found\./.test(error.message) || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query)) throw error;
|
|
14789
|
+
const response2 = await this.http.post(
|
|
14790
|
+
this.appendTeamQuery("/v1/chats/metadata/batch", { teamId }),
|
|
14791
|
+
{ chat_ids: [query] },
|
|
14792
|
+
this.getCliRequestHeaders()
|
|
14793
|
+
);
|
|
14794
|
+
current2();
|
|
14795
|
+
if (!response2.ok) throw new Error(`Chat metadata failed with HTTP ${response2.status}`);
|
|
14796
|
+
const details = response2.data.chats.find((chat) => String(chat.id ?? "") === query);
|
|
14797
|
+
if (!details) throw error;
|
|
14798
|
+
found = { details, messages: [] };
|
|
14799
|
+
fetchedMetadata = true;
|
|
14800
|
+
cache = { ...cache, chats: [...cache.chats, found], chatKeyWrappers: [
|
|
14801
|
+
...cache.chatKeyWrappers ?? [],
|
|
14802
|
+
...Array.isArray(details.chat_key_wrappers) ? details.chat_key_wrappers : []
|
|
14803
|
+
] };
|
|
14804
|
+
}
|
|
14342
14805
|
current2();
|
|
14343
14806
|
const chatItem = await this.decryptChatListItem(found, wrappingKey, cache, teamId);
|
|
14344
14807
|
current2();
|
|
@@ -14366,17 +14829,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14366
14829
|
const rawMessages = Array.isArray(response.data.messages) ? response.data.messages : [];
|
|
14367
14830
|
const messages = await this.decryptRawChatMessages(rawMessages, chatItem, chatKeyBytes, cache.embeds);
|
|
14368
14831
|
current2();
|
|
14369
|
-
if (
|
|
14832
|
+
if ((options.direction ?? "latest") === "latest") {
|
|
14370
14833
|
const latestCache = loadSyncCache(teamId);
|
|
14371
14834
|
const row3 = latestCache?.chats.find((chat) => String(chat.details.id) === chatId);
|
|
14372
|
-
|
|
14835
|
+
const sameVersion = row3 && row3.details.messages_v === found.details.messages_v && row3.details.encrypted_chat_key === found.details.encrypted_chat_key && row3.messages.length === found.messages.length && row3.messages.every((raw, index) => raw === found.messages[index]);
|
|
14836
|
+
if (latestCache && (sameVersion || fetchedMetadata && !row3 && diskCacheAtStart && latestCache.syncedAt === diskCacheAtStart.syncedAt && latestCache.totalChatCount === diskCacheAtStart.totalChatCount && latestCache.loadedChatCount === diskCacheAtStart.loadedChatCount && latestCache.chats.length === diskChatIdsAtStart?.length && latestCache.chats.every((chat, index) => String(chat.details.id) === diskChatIdsAtStart[index]))) {
|
|
14373
14837
|
const canonicalId = (raw) => {
|
|
14374
14838
|
const value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
14375
14839
|
return String(value.client_message_id ?? value.id ?? value.message_id ?? "");
|
|
14376
14840
|
};
|
|
14377
|
-
const
|
|
14841
|
+
const existing = row3?.messages ?? [];
|
|
14842
|
+
const existingById = new Map(existing.map((raw, index) => [canonicalId(raw), index]));
|
|
14378
14843
|
const overlap = rawMessages.find((raw) => !!canonicalId(raw) && existingById.has(canonicalId(raw)));
|
|
14379
|
-
const older = response.data.has_more_before !== true ? [] : overlap ?
|
|
14844
|
+
const older = response.data.has_more_before !== true ? [] : overlap ? existing.slice(0, existingById.get(canonicalId(overlap))) : existing;
|
|
14380
14845
|
const byId = /* @__PURE__ */ new Map();
|
|
14381
14846
|
for (const raw of [...older, ...rawMessages]) {
|
|
14382
14847
|
const value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
@@ -14386,8 +14851,21 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14386
14851
|
}
|
|
14387
14852
|
const merged = [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).map((message) => message.raw);
|
|
14388
14853
|
current2();
|
|
14389
|
-
|
|
14390
|
-
|
|
14854
|
+
try {
|
|
14855
|
+
if (!row3) {
|
|
14856
|
+
const wrappers = Array.isArray(found.details.chat_key_wrappers) ? found.details.chat_key_wrappers : [];
|
|
14857
|
+
saveSyncCache({
|
|
14858
|
+
...latestCache,
|
|
14859
|
+
chats: [...latestCache.chats, { details: found.details, messages: merged }],
|
|
14860
|
+
loadedChatCount: latestCache.loadedChatCount + 1,
|
|
14861
|
+
totalChatCount: Math.max(latestCache.totalChatCount, latestCache.loadedChatCount + 1),
|
|
14862
|
+
chatKeyWrappers: [...latestCache.chatKeyWrappers ?? [], ...wrappers]
|
|
14863
|
+
}, teamId);
|
|
14864
|
+
} else if (merged.length !== row3.messages.length || merged.some((raw, index) => raw !== row3.messages[index])) {
|
|
14865
|
+
saveSyncCache({ ...latestCache, chats: latestCache.chats.map((chat) => chat === row3 ? { ...row3, messages: merged } : chat) }, teamId);
|
|
14866
|
+
}
|
|
14867
|
+
} catch {
|
|
14868
|
+
}
|
|
14391
14869
|
}
|
|
14392
14870
|
}
|
|
14393
14871
|
return {
|
|
@@ -15232,7 +15710,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15232
15710
|
code: "code",
|
|
15233
15711
|
pcb_schematic: "code",
|
|
15234
15712
|
sheet: "table",
|
|
15235
|
-
mermaid: "diagram_code"
|
|
15713
|
+
mermaid: "diagram_code",
|
|
15714
|
+
diagram_html: "html_source"
|
|
15236
15715
|
};
|
|
15237
15716
|
const field3 = historicalFieldByType[content.type];
|
|
15238
15717
|
if (!field3 || typeof sealedEmbed[field3] !== "string") {
|
|
@@ -15683,7 +16162,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15683
16162
|
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
15684
16163
|
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
15685
16164
|
if (encKey) {
|
|
15686
|
-
chatKeyBytes = await
|
|
16165
|
+
chatKeyBytes = await this.resolveChatKey(cache, chat, wrappingKey, teamId);
|
|
15687
16166
|
encryptedChatKey = encKey;
|
|
15688
16167
|
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
15689
16168
|
if (typeof chat.details.encrypted_title === "string") {
|
|
@@ -16598,7 +17077,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16598
17077
|
chatId,
|
|
16599
17078
|
chatKeyBytes,
|
|
16600
17079
|
fallbackMessageId: assistantId,
|
|
16601
|
-
ownerId: ownerId2
|
|
17080
|
+
ownerId: ownerId2,
|
|
17081
|
+
teamId
|
|
16602
17082
|
});
|
|
16603
17083
|
clearSyncCache(teamId);
|
|
16604
17084
|
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
@@ -16749,7 +17229,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16749
17229
|
chatId,
|
|
16750
17230
|
chatKeyBytes,
|
|
16751
17231
|
fallbackMessageId: assistantId,
|
|
16752
|
-
ownerId: ownerId2
|
|
17232
|
+
ownerId: ownerId2,
|
|
17233
|
+
teamId
|
|
16753
17234
|
});
|
|
16754
17235
|
if (isNewChat && (resp.generatedTitle || resp.generatedIcon)) {
|
|
16755
17236
|
const metadataStored = ws.waitForMessage(
|
|
@@ -17196,6 +17677,12 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
17196
17677
|
version_number: embed.version_number,
|
|
17197
17678
|
file_path: embed.file_path,
|
|
17198
17679
|
content_hash: embed.content_hash,
|
|
17680
|
+
...embed.type === "diagram_html" ? {
|
|
17681
|
+
app_id: "diagrams",
|
|
17682
|
+
skill_id: "html",
|
|
17683
|
+
chat_id: params.chatId,
|
|
17684
|
+
...params.teamId ? { team_id: params.teamId } : {}
|
|
17685
|
+
} : {},
|
|
17199
17686
|
text_length_chars: embed.text_length_chars,
|
|
17200
17687
|
is_private: embed.is_private ?? false,
|
|
17201
17688
|
is_shared: embed.is_shared ?? false,
|
|
@@ -21004,6 +21491,66 @@ Required: ${schema.required.join(", ")}`
|
|
|
21004
21491
|
const now = Math.floor(Date.now() / 1e3);
|
|
21005
21492
|
const { ws } = await this.openWsClient();
|
|
21006
21493
|
try {
|
|
21494
|
+
if (currentObject.type === "diagram_html") {
|
|
21495
|
+
const chat = cache.chats.find((entry) => computeSHA256(String(entry.details.id ?? "")) === embed.hashed_chat_id);
|
|
21496
|
+
const chatId = String(chat?.details.id ?? "");
|
|
21497
|
+
if (!chatId || typeof embed.content_hash !== "string" || embed.content_hash !== computeSHA256(currentContent)) {
|
|
21498
|
+
throw new Error("Diagram restore requires a current chat and matching source hash.");
|
|
21499
|
+
}
|
|
21500
|
+
const teamId = this.resolveTeamContext();
|
|
21501
|
+
const versions = await this.listEmbedVersions(embedId, { chatId, teamId: teamId ?? void 0 });
|
|
21502
|
+
if (versions.readonly) throw new Error("Diagram version history is read-only.");
|
|
21503
|
+
const historyRows = [];
|
|
21504
|
+
if (currentVersion === 1 && !versions.versions.some((row3) => row3.version_number === 1)) {
|
|
21505
|
+
historyRows.push({
|
|
21506
|
+
version_number: 1,
|
|
21507
|
+
encrypted_snapshot: await encryptWithAesGcmCombined(currentContent, embedKey),
|
|
21508
|
+
created_at: normalizeUnixSeconds(embed.created_at, now)
|
|
21509
|
+
});
|
|
21510
|
+
}
|
|
21511
|
+
historyRows.push({
|
|
21512
|
+
version_number: newVersion,
|
|
21513
|
+
encrypted_patch: encryptedPatch,
|
|
21514
|
+
encrypted_snapshot: encryptedSnapshot,
|
|
21515
|
+
created_at: now
|
|
21516
|
+
});
|
|
21517
|
+
const requestId = randomUUID9();
|
|
21518
|
+
const operationId = randomUUID9();
|
|
21519
|
+
const receipt = ws.waitForMessage(
|
|
21520
|
+
"commit_chat_diagram_revision_result",
|
|
21521
|
+
(payload) => payload.request_id === requestId,
|
|
21522
|
+
3e4
|
|
21523
|
+
);
|
|
21524
|
+
await ws.sendAsync("commit_chat_diagram_revision", {
|
|
21525
|
+
request_id: requestId,
|
|
21526
|
+
operation_id: operationId,
|
|
21527
|
+
embed_id: embedId,
|
|
21528
|
+
chat_id: chatId,
|
|
21529
|
+
...teamId ? { team_id: teamId } : {},
|
|
21530
|
+
expected_revision: currentVersion,
|
|
21531
|
+
expected_content_hash: embed.content_hash,
|
|
21532
|
+
head: {
|
|
21533
|
+
encrypted_content: encryptedRestoredContent,
|
|
21534
|
+
encrypted_text_preview: embed.encrypted_text_preview ?? null,
|
|
21535
|
+
content_hash: contentHash,
|
|
21536
|
+
status: "finished",
|
|
21537
|
+
updated_at: now
|
|
21538
|
+
},
|
|
21539
|
+
history_rows: historyRows
|
|
21540
|
+
});
|
|
21541
|
+
const result = (await receipt).payload;
|
|
21542
|
+
if (result.status !== "committed" || result.current_revision !== newVersion || result.current_content_hash !== contentHash) {
|
|
21543
|
+
throw new Error(result.status === "conflict" ? "Diagram advanced on another device; reload and retry restore." : `Diagram restore was rejected: ${String(result.code ?? "unknown")}`);
|
|
21544
|
+
}
|
|
21545
|
+
clearSyncCache(teamId);
|
|
21546
|
+
return {
|
|
21547
|
+
embed_id: embedId,
|
|
21548
|
+
restored_from_version: version,
|
|
21549
|
+
version_number: newVersion,
|
|
21550
|
+
content: target.content,
|
|
21551
|
+
content_hash: contentHash
|
|
21552
|
+
};
|
|
21553
|
+
}
|
|
21007
21554
|
const storeRequestId = randomUUID9();
|
|
21008
21555
|
const storeConfirmed = ws.waitForMessage(
|
|
21009
21556
|
"store_embed_confirmed",
|
|
@@ -28870,6 +29417,7 @@ var OpenMates = class {
|
|
|
28870
29417
|
sdkName;
|
|
28871
29418
|
sdkSessionPromise;
|
|
28872
29419
|
masterKeyPromise;
|
|
29420
|
+
chatIdentityPromise;
|
|
28873
29421
|
constructor(options = {}) {
|
|
28874
29422
|
this.apiKey = options.apiKey ?? process.env.OPENMATES_API_KEY;
|
|
28875
29423
|
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL2).replace(/\/$/, "");
|
|
@@ -28974,6 +29522,34 @@ var OpenMates = class {
|
|
|
28974
29522
|
sdkSession() {
|
|
28975
29523
|
return this.getSdkSession();
|
|
28976
29524
|
}
|
|
29525
|
+
async chatIdentity() {
|
|
29526
|
+
if (!this.chatIdentityPromise) this.chatIdentityPromise = this.loadChatIdentity().catch((error) => {
|
|
29527
|
+
this.chatIdentityPromise = void 0;
|
|
29528
|
+
throw error;
|
|
29529
|
+
});
|
|
29530
|
+
return this.chatIdentityPromise;
|
|
29531
|
+
}
|
|
29532
|
+
async loadChatIdentity() {
|
|
29533
|
+
const masterKey = await this.masterKey();
|
|
29534
|
+
let response;
|
|
29535
|
+
try {
|
|
29536
|
+
response = await this.get("/v1/account/chat-encryption-key");
|
|
29537
|
+
} catch (error) {
|
|
29538
|
+
if (!(error instanceof OpenMatesApiError) || error.status !== 404) throw error;
|
|
29539
|
+
response = { key: null };
|
|
29540
|
+
}
|
|
29541
|
+
if (response.key) return recoverAccountChatIdentity(response.key, masterKey);
|
|
29542
|
+
const generated = await createAccountChatIdentity(masterKey);
|
|
29543
|
+
try {
|
|
29544
|
+
const saved = await this.put("/v1/account/chat-encryption-key", generated.record);
|
|
29545
|
+
return recoverAccountChatIdentity(saved.key ?? generated.record, masterKey);
|
|
29546
|
+
} catch (error) {
|
|
29547
|
+
if (!(error instanceof OpenMatesApiError) || error.status !== 409) throw error;
|
|
29548
|
+
const current2 = await this.get("/v1/account/chat-encryption-key");
|
|
29549
|
+
if (!current2.key) throw new OpenMatesConfigError("Account chat encryption key enrollment conflicted; retry.");
|
|
29550
|
+
return recoverAccountChatIdentity(current2.key, masterKey);
|
|
29551
|
+
}
|
|
29552
|
+
}
|
|
28977
29553
|
/** @internal Resource-key resolver shared by the Project namespace. */
|
|
28978
29554
|
async _getProjectGrantKey(projectId) {
|
|
28979
29555
|
const session = await this.getSdkSession();
|
|
@@ -29048,6 +29624,26 @@ var OpenMates = class {
|
|
|
29048
29624
|
return { ...payload, chat: decryptedChat, messages, embeds };
|
|
29049
29625
|
}
|
|
29050
29626
|
async resolveLoadedChatKey(chat, chatKeyWrappers) {
|
|
29627
|
+
if (chat.chat_visibility === "direct") {
|
|
29628
|
+
const session = await this.sdkSession();
|
|
29629
|
+
const userId = session.user?.id;
|
|
29630
|
+
if (!userId) throw new OpenMatesConfigError("Authenticated identity is required for a direct chat");
|
|
29631
|
+
const ownHash = createHash14("sha256").update(userId).digest("hex");
|
|
29632
|
+
const participants = chat.participant_user_hashes;
|
|
29633
|
+
if (!Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash)) {
|
|
29634
|
+
throw new OpenMatesConfigError("Direct chat participant binding mismatch");
|
|
29635
|
+
}
|
|
29636
|
+
const identity3 = await this.chatIdentity();
|
|
29637
|
+
if (typeof chat.team_id !== "string" || typeof chat.encrypted_chat_key !== "string") {
|
|
29638
|
+
throw new OpenMatesConfigError("Direct chat key metadata is incomplete");
|
|
29639
|
+
}
|
|
29640
|
+
return openDirectChatKey(chat.encrypted_chat_key, {
|
|
29641
|
+
teamId: chat.team_id,
|
|
29642
|
+
chatId: chat.id,
|
|
29643
|
+
recipientHash: ownHash,
|
|
29644
|
+
keyId: identity3.keyId
|
|
29645
|
+
}, identity3.privateKey);
|
|
29646
|
+
}
|
|
29051
29647
|
const hashedChatId = createHash14("sha256").update(chat.id).digest("hex");
|
|
29052
29648
|
const wrapper = (chatKeyWrappers ?? []).find(
|
|
29053
29649
|
(entry) => entry.key_type === "master" && entry.hashed_chat_id === hashedChatId && typeof entry.encrypted_chat_key === "string"
|
|
@@ -32684,9 +33280,11 @@ var OpenMatesTeams = class {
|
|
|
32684
33280
|
}
|
|
32685
33281
|
async list() {
|
|
32686
33282
|
const result = await this.client.get("/v1/teams");
|
|
33283
|
+
if (result.teams?.length) await this.client.chatIdentity();
|
|
32687
33284
|
return result.teams ?? [];
|
|
32688
33285
|
}
|
|
32689
33286
|
async get(teamId) {
|
|
33287
|
+
await this.client.chatIdentity();
|
|
32690
33288
|
const result = await this.client.get(`/v1/teams/${encodeURIComponent(teamId)}`);
|
|
32691
33289
|
return result.team ?? result;
|
|
32692
33290
|
}
|
|
@@ -32744,6 +33342,62 @@ var OpenMatesTeams = class {
|
|
|
32744
33342
|
async members(teamId) {
|
|
32745
33343
|
return this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/members`);
|
|
32746
33344
|
}
|
|
33345
|
+
async memberChatEncryptionKey(teamId, memberHash) {
|
|
33346
|
+
if (!/^[0-9a-f]{64}$/.test(memberHash)) throw new OpenMatesConfigError("Member hash must be a SHA-256 hex digest");
|
|
33347
|
+
try {
|
|
33348
|
+
const result = await this.client.get(
|
|
33349
|
+
`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberHash)}/chat-encryption-key`
|
|
33350
|
+
);
|
|
33351
|
+
return validatePublicChatEncryptionKeyId(result);
|
|
33352
|
+
} catch (error) {
|
|
33353
|
+
if (error instanceof OpenMatesApiError && error.status === 404) {
|
|
33354
|
+
throw new OpenMatesConfigError("Direct message pending: this Team member has not enrolled a chat encryption key yet.");
|
|
33355
|
+
}
|
|
33356
|
+
throw error;
|
|
33357
|
+
}
|
|
33358
|
+
}
|
|
33359
|
+
async openDirectChat(teamId, recipientHash) {
|
|
33360
|
+
if (!teamId || !/^[0-9a-f]{64}$/.test(recipientHash)) throw new OpenMatesConfigError("A Team ID and member hash are required");
|
|
33361
|
+
const session = await this.client.sdkSession();
|
|
33362
|
+
const userId = session.user?.id;
|
|
33363
|
+
if (!userId) throw new OpenMatesConfigError("Authenticated identity is required for a direct chat");
|
|
33364
|
+
const ownHash = createHash14("sha256").update(userId).digest("hex");
|
|
33365
|
+
if (ownHash === recipientHash) throw new OpenMatesConfigError("Choose another Team member to message");
|
|
33366
|
+
const [ownIdentity, recipientKey] = await Promise.all([
|
|
33367
|
+
this.client.chatIdentity(),
|
|
33368
|
+
this.memberChatEncryptionKey(teamId, recipientHash)
|
|
33369
|
+
]);
|
|
33370
|
+
const chatId = randomUUID10();
|
|
33371
|
+
const chatKey = new Uint8Array(randomBytes6(32));
|
|
33372
|
+
const ownEnvelope = await sealDirectChatKey(
|
|
33373
|
+
chatKey,
|
|
33374
|
+
{ teamId, chatId, recipientHash: ownHash, keyId: ownIdentity.keyId },
|
|
33375
|
+
ownIdentity.publicKey
|
|
33376
|
+
);
|
|
33377
|
+
const recipientEnvelope = await sealDirectChatKey(
|
|
33378
|
+
chatKey,
|
|
33379
|
+
{ teamId, chatId, recipientHash, keyId: recipientKey.key_id },
|
|
33380
|
+
recipientKey.public_key
|
|
33381
|
+
);
|
|
33382
|
+
const result = await this.client.request(
|
|
33383
|
+
`/v1/teams/${encodeURIComponent(teamId)}/direct-chats`,
|
|
33384
|
+
{
|
|
33385
|
+
chat_id: chatId,
|
|
33386
|
+
recipient_user_hash: recipientHash,
|
|
33387
|
+
participant_key_envelopes: {
|
|
33388
|
+
[ownHash]: { key_id: ownIdentity.keyId, encrypted_chat_key: ownEnvelope },
|
|
33389
|
+
[recipientHash]: { key_id: recipientKey.key_id, encrypted_chat_key: recipientEnvelope }
|
|
33390
|
+
},
|
|
33391
|
+
encrypted_title: await encryptWithAesGcmCombined("Private Team message", chatKey)
|
|
33392
|
+
}
|
|
33393
|
+
);
|
|
33394
|
+
if (!result.chat) throw new OpenMatesConfigError("Direct chat creation returned no metadata");
|
|
33395
|
+
const participants = result.chat.participant_user_hashes;
|
|
33396
|
+
if (result.chat.chat_visibility !== "direct" || result.chat.team_id !== teamId || result.chat.id !== chatId || !Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash) || !participants.includes(recipientHash)) {
|
|
33397
|
+
throw new OpenMatesConfigError("Direct chat creation returned mismatched metadata");
|
|
33398
|
+
}
|
|
33399
|
+
return this.client.decryptChatMetadata(result.chat);
|
|
33400
|
+
}
|
|
32747
33401
|
async decryptMemberProfile(teamKey, member) {
|
|
32748
33402
|
const encrypted = member.encrypted_member_profile;
|
|
32749
33403
|
if (typeof encrypted !== "string" || !encrypted) return { ...member, profile: null };
|
|
@@ -34367,6 +35021,8 @@ var DIRECT_TYPES = /* @__PURE__ */ new Set([
|
|
|
34367
35021
|
"recording",
|
|
34368
35022
|
"mail-email",
|
|
34369
35023
|
"math-plot",
|
|
35024
|
+
"diagram_html",
|
|
35025
|
+
"diagrams-html",
|
|
34370
35026
|
"mindmap",
|
|
34371
35027
|
"mindmaps-mindmap",
|
|
34372
35028
|
"events-event",
|
|
@@ -34398,6 +35054,8 @@ var DIRECT_TYPE_LABELS = {
|
|
|
34398
35054
|
"recording": "recording",
|
|
34399
35055
|
"mail-email": "email",
|
|
34400
35056
|
"math-plot": "plot",
|
|
35057
|
+
"diagram_html": "diagram",
|
|
35058
|
+
"diagrams-html": "diagram",
|
|
34401
35059
|
"mindmap": "Mind Map",
|
|
34402
35060
|
"mindmaps-mindmap": "Mind Map",
|
|
34403
35061
|
"events-event": "event",
|
|
@@ -35469,6 +36127,15 @@ function renderByDirectType(embed, c, ln) {
|
|
|
35469
36127
|
ln("\x1B[2m[mathematical plot]\x1B[0m");
|
|
35470
36128
|
break;
|
|
35471
36129
|
}
|
|
36130
|
+
case "diagram_html":
|
|
36131
|
+
case "diagrams-html": {
|
|
36132
|
+
ln(str(c.title) ?? "Diagram");
|
|
36133
|
+
const kind = str(c.diagram_kind) ?? "diagram";
|
|
36134
|
+
const width = typeof c.render_width === "number" ? c.render_width : 0;
|
|
36135
|
+
const height = typeof c.render_height === "number" ? c.render_height : 0;
|
|
36136
|
+
ln(`\x1B[2m${kind}${width && height ? ` \xB7 ${width} \xD7 ${height}` : ""}\x1B[0m`);
|
|
36137
|
+
break;
|
|
36138
|
+
}
|
|
35472
36139
|
case "mindmap":
|
|
35473
36140
|
case "mindmaps-mindmap": {
|
|
35474
36141
|
const document = mindMapDocumentFromContent(c);
|
|
@@ -35660,6 +36327,18 @@ function renderDirectTypeFullscreen(embed, c, write) {
|
|
|
35660
36327
|
}
|
|
35661
36328
|
break;
|
|
35662
36329
|
}
|
|
36330
|
+
case "diagram_html":
|
|
36331
|
+
case "diagrams-html": {
|
|
36332
|
+
writeEmbedLine(write, `Title: ${str(c.title) ?? "Diagram"}`);
|
|
36333
|
+
writeEmbedLine(write, `Kind: ${str(c.diagram_kind) ?? "diagram"}`);
|
|
36334
|
+
if (typeof c.render_width === "number" && typeof c.render_height === "number") {
|
|
36335
|
+
writeEmbedLine(write, `Size: ${c.render_width} \xD7 ${c.render_height}`);
|
|
36336
|
+
}
|
|
36337
|
+
writeEmbedLine(write, `Version: ${typeof c.version_number === "number" ? c.version_number : 1}`);
|
|
36338
|
+
writeEmbedLine(write, `Source: ${typeof c.html_source === "string" ? c.html_source.length : 0} characters`);
|
|
36339
|
+
writeEmbedLine(write, "Export with: openmates chats download <chat> --output <directory>");
|
|
36340
|
+
break;
|
|
36341
|
+
}
|
|
35663
36342
|
case "mindmap":
|
|
35664
36343
|
case "mindmaps-mindmap": {
|
|
35665
36344
|
const document = mindMapDocumentFromContent(c);
|
|
@@ -67743,6 +68422,62 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
67743
68422
|
}
|
|
67744
68423
|
}
|
|
67745
68424
|
},
|
|
68425
|
+
diagrams: {
|
|
68426
|
+
html: {
|
|
68427
|
+
text: "Editable Diagram"
|
|
68428
|
+
},
|
|
68429
|
+
edit: {
|
|
68430
|
+
text: "Edit diagram"
|
|
68431
|
+
},
|
|
68432
|
+
loading: {
|
|
68433
|
+
text: "Preparing diagram\u2026"
|
|
68434
|
+
},
|
|
68435
|
+
invalid_source: {
|
|
68436
|
+
text: "This diagram contains unsupported content."
|
|
68437
|
+
},
|
|
68438
|
+
conflict: {
|
|
68439
|
+
text: "The diagram changed on another device. Reload before editing."
|
|
68440
|
+
},
|
|
68441
|
+
saved: {
|
|
68442
|
+
text: "Diagram saved"
|
|
68443
|
+
},
|
|
68444
|
+
saved_refresh: {
|
|
68445
|
+
text: "Diagram saved. Refresh this page to sync this device before editing again."
|
|
68446
|
+
},
|
|
68447
|
+
history_unavailable: {
|
|
68448
|
+
text: "This diagram version could not be loaded. Choose another version to retry."
|
|
68449
|
+
},
|
|
68450
|
+
history_version: {
|
|
68451
|
+
text: "Viewing version"
|
|
68452
|
+
},
|
|
68453
|
+
save_failed: {
|
|
68454
|
+
text: "Could not save diagram"
|
|
68455
|
+
},
|
|
68456
|
+
zoom_out: {
|
|
68457
|
+
text: "Zoom out"
|
|
68458
|
+
},
|
|
68459
|
+
zoom_in: {
|
|
68460
|
+
text: "Zoom in"
|
|
68461
|
+
},
|
|
68462
|
+
fit: {
|
|
68463
|
+
text: "Fit diagram"
|
|
68464
|
+
},
|
|
68465
|
+
export: {
|
|
68466
|
+
text: "Export HTML"
|
|
68467
|
+
},
|
|
68468
|
+
undo: {
|
|
68469
|
+
text: "Undo"
|
|
68470
|
+
},
|
|
68471
|
+
cancel: {
|
|
68472
|
+
text: "Cancel"
|
|
68473
|
+
},
|
|
68474
|
+
save: {
|
|
68475
|
+
text: "Save changes"
|
|
68476
|
+
},
|
|
68477
|
+
saving: {
|
|
68478
|
+
text: "Saving\u2026"
|
|
68479
|
+
}
|
|
68480
|
+
},
|
|
67746
68481
|
digital_team_mates: {
|
|
67747
68482
|
digital_team_mates: {
|
|
67748
68483
|
text: "Digital Team Mates"
|
|
@@ -70474,6 +71209,9 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
70474
71209
|
diagrams: {
|
|
70475
71210
|
mermaid: {
|
|
70476
71211
|
text: "Mermaid Diagram"
|
|
71212
|
+
},
|
|
71213
|
+
html: {
|
|
71214
|
+
text: "Editable Diagram"
|
|
70477
71215
|
}
|
|
70478
71216
|
},
|
|
70479
71217
|
mail: {
|
|
@@ -78196,6 +78934,14 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
78196
78934
|
}
|
|
78197
78935
|
},
|
|
78198
78936
|
notifications: {
|
|
78937
|
+
team_direct_chat: {
|
|
78938
|
+
title: {
|
|
78939
|
+
text: "Private Team message"
|
|
78940
|
+
},
|
|
78941
|
+
body: {
|
|
78942
|
+
text: "You have a new private conversation in your Team."
|
|
78943
|
+
}
|
|
78944
|
+
},
|
|
78199
78945
|
auto_logout: {
|
|
78200
78946
|
title: {
|
|
78201
78947
|
text: "Auto logout"
|
|
@@ -86391,6 +87137,18 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
86391
87137
|
members_description: {
|
|
86392
87138
|
text: "Manage your team members and their permissions."
|
|
86393
87139
|
},
|
|
87140
|
+
direct_message_action: {
|
|
87141
|
+
text: "Message"
|
|
87142
|
+
},
|
|
87143
|
+
direct_message_scope: {
|
|
87144
|
+
text: "Only you and this member can read this chat."
|
|
87145
|
+
},
|
|
87146
|
+
direct_message_pending: {
|
|
87147
|
+
text: "Secure messaging setup is pending. Ask this member to open OpenMates."
|
|
87148
|
+
},
|
|
87149
|
+
direct_message_failed: {
|
|
87150
|
+
text: "Could not open a private message. Try again."
|
|
87151
|
+
},
|
|
86394
87152
|
admins: {
|
|
86395
87153
|
text: "Admins"
|
|
86396
87154
|
},
|
|
@@ -99434,6 +100192,8 @@ var TUI_ACTIONS = [
|
|
|
99434
100192
|
{ label: "Show all workspace items", command: "/browse" },
|
|
99435
100193
|
{ label: "Use selected app skill", command: "/app-run" },
|
|
99436
100194
|
{ label: "Recent chats", command: "/chats" },
|
|
100195
|
+
{ label: "Team members", command: "/members" },
|
|
100196
|
+
{ label: "Message Team member", command: "/message" },
|
|
99437
100197
|
{ label: "Example chats", command: "/examples" },
|
|
99438
100198
|
{ label: "Projects", command: "/projects" },
|
|
99439
100199
|
{ label: "Tasks", command: "/tasks" },
|
|
@@ -102073,7 +102833,7 @@ function relativeTime(timestamp) {
|
|
|
102073
102833
|
return `${Math.floor(seconds / 86400)}d ago`;
|
|
102074
102834
|
}
|
|
102075
102835
|
function renderStatus(state, width) {
|
|
102076
|
-
return ["OpenMates", "", state.status ?? "Working..."].flatMap((line3) => wrap(line3, width));
|
|
102836
|
+
return ["OpenMates", "", ...(state.status ?? "Working...").split("\n")].flatMap((line3) => wrap(line3, width).map((text7) => line3 === "Message \xB7 Enter" ? pointerLine(text7, { kind: "key", name: "return" }) : line3 === "Next member \xB7 \u2193" ? pointerLine(text7, { kind: "key", name: "down" }) : text7));
|
|
102077
102837
|
}
|
|
102078
102838
|
function prepareMessageContent(content, width, state, questionBlocks = true) {
|
|
102079
102839
|
const segments2 = parseMessageSegments(content, { preserveCodeFences: true }).map((segment) => segment.type === "embed" ? segment : {
|
|
@@ -104801,6 +105561,19 @@ function createChatModelPreferences(client2) {
|
|
|
104801
105561
|
}
|
|
104802
105562
|
|
|
104803
105563
|
// src/tui.ts
|
|
105564
|
+
var teamMemberActions = /* @__PURE__ */ new WeakMap();
|
|
105565
|
+
function showSelectedTeamMember(state) {
|
|
105566
|
+
const selection = teamMemberActions.get(state);
|
|
105567
|
+
if (!selection?.members.length) {
|
|
105568
|
+
state.status = "No other active Team members are available.";
|
|
105569
|
+
return;
|
|
105570
|
+
}
|
|
105571
|
+
const member = selection.members[state.selectedIndex] ?? selection.members[0];
|
|
105572
|
+
state.status = `Team members ${state.selectedIndex + 1}/${selection.members.length}
|
|
105573
|
+
${member.label}
|
|
105574
|
+
Message \xB7 Enter
|
|
105575
|
+
Next member \xB7 \u2193`;
|
|
105576
|
+
}
|
|
104804
105577
|
function defaultModeForStreams(input, output) {
|
|
104805
105578
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
104806
105579
|
}
|
|
@@ -105083,6 +105856,21 @@ async function handleKey(params) {
|
|
|
105083
105856
|
modelShell,
|
|
105084
105857
|
mentionShell
|
|
105085
105858
|
};
|
|
105859
|
+
const memberActions = teamMemberActions.get(state);
|
|
105860
|
+
if (state.screen === "status" && memberActions && !memberActions.isCurrent()) teamMemberActions.delete(state);
|
|
105861
|
+
if (state.screen === "status" && memberActions && memberActions.isCurrent() && memberActions.teamId === client2.getActiveTeamId()) {
|
|
105862
|
+
if (memberActions.members.length > 0 && (key.name === "up" || key.name === "down")) {
|
|
105863
|
+
state.selectedIndex = (state.selectedIndex + (key.name === "down" ? 1 : -1) + memberActions.members.length) % memberActions.members.length;
|
|
105864
|
+
showSelectedTeamMember(state);
|
|
105865
|
+
render();
|
|
105866
|
+
return;
|
|
105867
|
+
}
|
|
105868
|
+
if (key.name === "return" && !state.input.trim()) {
|
|
105869
|
+
const member = memberActions.members[state.selectedIndex];
|
|
105870
|
+
if (member) await handleCommand({ command: `/message ${member.hash}`, state, client: client2, terminal, render, finish, modelShell });
|
|
105871
|
+
return;
|
|
105872
|
+
}
|
|
105873
|
+
}
|
|
105086
105874
|
if (key.name === "mouseclick") {
|
|
105087
105875
|
if (!key.mouse || key.ctrl || key.meta || key.shift) return;
|
|
105088
105876
|
const action2 = pointerTargetAt(state, key.mouse.column, key.mouse.row, terminal.width, terminal.height);
|
|
@@ -105258,6 +106046,50 @@ async function handleCommand(params) {
|
|
|
105258
106046
|
rememberDraft(state);
|
|
105259
106047
|
state.input = "";
|
|
105260
106048
|
state.inputCursor = null;
|
|
106049
|
+
if (name === "/members") {
|
|
106050
|
+
const teamId = client2.getActiveTeamId();
|
|
106051
|
+
if (!teamId) {
|
|
106052
|
+
state.status = "Select a Team to view members.";
|
|
106053
|
+
state.screen = "status";
|
|
106054
|
+
render();
|
|
106055
|
+
return;
|
|
106056
|
+
}
|
|
106057
|
+
const isCurrent = captureTuiWorkspaceOwner(client2);
|
|
106058
|
+
const members = await client2.listTeamMembers(teamId);
|
|
106059
|
+
if (!isCurrent() || client2.getActiveTeamId() !== teamId) return;
|
|
106060
|
+
const user2 = await client2.whoAmI();
|
|
106061
|
+
if (!isCurrent() || client2.getActiveTeamId() !== teamId) return;
|
|
106062
|
+
const ownId = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : "";
|
|
106063
|
+
teamMemberActions.set(state, { teamId, isCurrent, members: members.filter((member) => member.status === "active" && member.hashed_user_id && member.user_id !== ownId).map((member) => ({ hash: member.hashed_user_id, label: `${member.profile?.display_name || member.user_id} \xB7 ${member.role}` })) });
|
|
106064
|
+
state.selectedIndex = 0;
|
|
106065
|
+
state.screen = "status";
|
|
106066
|
+
showSelectedTeamMember(state);
|
|
106067
|
+
render();
|
|
106068
|
+
return;
|
|
106069
|
+
}
|
|
106070
|
+
if (name === "/message") {
|
|
106071
|
+
const teamId = client2.getActiveTeamId();
|
|
106072
|
+
if (!teamId) {
|
|
106073
|
+
state.status = "Select a Team to message a member.";
|
|
106074
|
+
state.screen = "status";
|
|
106075
|
+
render();
|
|
106076
|
+
return;
|
|
106077
|
+
}
|
|
106078
|
+
const memberHash = parts[0];
|
|
106079
|
+
if (!memberHash) {
|
|
106080
|
+
state.status = "Use /members to find a member, then /message <member-hash>.";
|
|
106081
|
+
state.screen = "status";
|
|
106082
|
+
render();
|
|
106083
|
+
return;
|
|
106084
|
+
}
|
|
106085
|
+
const chat = await client2.openTeamDirectChat(teamId, memberHash);
|
|
106086
|
+
if (client2.getActiveTeamId() !== teamId) return;
|
|
106087
|
+
state.recentChats = [chat, ...state.recentChats.filter((item) => item.id !== chat.id)];
|
|
106088
|
+
await openSavedChat({ state, client: client2, terminal, render, command: async () => {
|
|
106089
|
+
}, send: async () => {
|
|
106090
|
+
} }, chat.id);
|
|
106091
|
+
return;
|
|
106092
|
+
}
|
|
105261
106093
|
if (name === "/privacy") {
|
|
105262
106094
|
const action2 = parts[0] ?? "status";
|
|
105263
106095
|
if (action2 === "later") {
|
|
@@ -115109,24 +115941,33 @@ async function handleTeams(client2, subcommand, rest, flags) {
|
|
|
115109
115941
|
else printTeamRecord(team, client2.getActiveTeamId());
|
|
115110
115942
|
return;
|
|
115111
115943
|
}
|
|
115112
|
-
if (subcommand === "members") {
|
|
115944
|
+
if (subcommand === "members" || subcommand === "member") {
|
|
115113
115945
|
const action2 = rest[0];
|
|
115114
115946
|
const teamId = requiredStringFlag(rest[1], "<team-id>");
|
|
115947
|
+
if (action2 === "message") {
|
|
115948
|
+
const memberHash = requiredStringFlag(rest[2], "<member-hash>");
|
|
115949
|
+
const chat = await client2.openTeamDirectChat(teamId, memberHash);
|
|
115950
|
+
const message = rest.slice(3).join(" ").trim();
|
|
115951
|
+
const response = message ? await client2.sendMessage({ message, chatId: chat.id, teamId }) : void 0;
|
|
115952
|
+
if (flags.json === true) printJson2({ chat_id: chat.id, chat, ...response ? { response } : {} });
|
|
115953
|
+
else console.log(message ? `Message sent in direct chat ${chat.id}` : `Direct chat: ${chat.id}`);
|
|
115954
|
+
return;
|
|
115955
|
+
}
|
|
115115
115956
|
if (action2 === "list") {
|
|
115116
115957
|
const members = (await client2.listTeamMembers(teamId)).map(publicTeamMember);
|
|
115117
115958
|
if (flags.json === true) printJson2({ members });
|
|
115118
115959
|
else if (!members.length) console.log("No Team members.");
|
|
115119
|
-
else for (const member of members) console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
|
|
115960
|
+
else for (const member of members) console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.hashed_user_id || member.user_id}`.trim());
|
|
115120
115961
|
return;
|
|
115121
115962
|
}
|
|
115122
115963
|
if (action2 === "show") {
|
|
115123
115964
|
const userId = requiredStringFlag(rest[2], "<member-user-id>");
|
|
115124
115965
|
const member = publicTeamMember(await client2.getTeamMember(teamId, userId));
|
|
115125
115966
|
if (flags.json === true) printJson2({ member });
|
|
115126
|
-
else console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
|
|
115967
|
+
else console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.hashed_user_id || member.user_id}`.trim());
|
|
115127
115968
|
return;
|
|
115128
115969
|
}
|
|
115129
|
-
throw new Error("Use teams members list
|
|
115970
|
+
throw new Error("Use teams members list, show, or message <team-id> <member-hash> [text].");
|
|
115130
115971
|
}
|
|
115131
115972
|
if (subcommand === "profile") {
|
|
115132
115973
|
const action2 = rest[0];
|
|
@@ -116889,6 +117730,7 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
|
|
|
116889
117730
|
}
|
|
116890
117731
|
const codeEmbeds = [];
|
|
116891
117732
|
const transcriptEmbeds = [];
|
|
117733
|
+
const diagramEmbeds = [];
|
|
116892
117734
|
const processedEmbedIds = /* @__PURE__ */ new Set();
|
|
116893
117735
|
const allEmbedIds = [];
|
|
116894
117736
|
for (const msg of messages) {
|
|
@@ -116905,6 +117747,9 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
|
|
|
116905
117747
|
const eid = embedQueue.shift();
|
|
116906
117748
|
try {
|
|
116907
117749
|
const embed = await client2.getEmbed(eid);
|
|
117750
|
+
if ((embed.type === "diagram_html" || embed.type === "diagrams-html") && typeof embed.content?.html_source === "string" && embed.content.html_source) {
|
|
117751
|
+
diagramEmbeds.push({ embedId: embed.embedId, content: embed.content.html_source });
|
|
117752
|
+
}
|
|
116908
117753
|
if (embed.type === "code" && embed.content) {
|
|
116909
117754
|
const code = typeof embed.content.code === "string" ? embed.content.code : typeof embed.content.content === "string" ? embed.content.content : null;
|
|
116910
117755
|
if (code) {
|
|
@@ -116982,6 +117827,13 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
|
|
|
116982
117827
|
await writeFile3(join31(tDir, te.filename), te.content);
|
|
116983
117828
|
}
|
|
116984
117829
|
}
|
|
117830
|
+
if (diagramEmbeds.length > 0) {
|
|
117831
|
+
const dDir = join31(tmpDir, "diagrams");
|
|
117832
|
+
await mkdir4(dDir, { recursive: true });
|
|
117833
|
+
for (const diagram of diagramEmbeds) {
|
|
117834
|
+
await writeFile3(join31(dDir, `${diagram.embedId}.html`), diagram.content);
|
|
117835
|
+
}
|
|
117836
|
+
}
|
|
116985
117837
|
const zipPath = join31(outputDir, `${filenameBase}.zip`);
|
|
116986
117838
|
const { execSync: execSync2 } = await import("child_process");
|
|
116987
117839
|
try {
|
|
@@ -117023,6 +117875,15 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
|
|
|
117023
117875
|
written.push(`transcripts/${te.filename}`);
|
|
117024
117876
|
}
|
|
117025
117877
|
}
|
|
117878
|
+
if (diagramEmbeds.length > 0) {
|
|
117879
|
+
const dDir = join31(chatDir, "diagrams");
|
|
117880
|
+
await mkdir4(dDir, { recursive: true });
|
|
117881
|
+
for (const diagram of diagramEmbeds) {
|
|
117882
|
+
const filename = `${diagram.embedId}.html`;
|
|
117883
|
+
await writeFile3(join31(dDir, filename), diagram.content);
|
|
117884
|
+
written.push(`diagrams/${filename}`);
|
|
117885
|
+
}
|
|
117886
|
+
}
|
|
117026
117887
|
const label4 = chat.title ? `"${chat.title}"` : chatHandle(chat);
|
|
117027
117888
|
process.stdout.write(
|
|
117028
117889
|
`\x1B[1mDownloaded chat ${label4}\x1B[0m \u2192 ${chatDir}
|
|
@@ -124360,6 +125221,7 @@ function printTeamsHelp() {
|
|
|
124360
125221
|
openmates teams role <team-id> --user <user-id> --role admin|member|viewer [--json]
|
|
124361
125222
|
openmates teams members list <team-id> [--json]
|
|
124362
125223
|
openmates teams members show <team-id> <member-user-id> [--json]
|
|
125224
|
+
openmates teams member message <team-id> <member-hash> [text] [--json]
|
|
124363
125225
|
openmates teams profile show <team-id> [--json]
|
|
124364
125226
|
openmates teams profile edit <team-id> [--name <display-name>] [--avatar <value>] [--json]
|
|
124365
125227
|
openmates teams remove-member <team-id> --user <user-id> [--json]
|