@wacando/agent 2.0.2 → 2.2.0

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.
Files changed (33) hide show
  1. package/README.md +6 -2
  2. package/STATUS.md +20 -2
  3. package/THIRD-PARTY-NOTICES.txt +0 -1113
  4. package/bin/wacando-agent.mjs +48 -3
  5. package/docs/AGENT_TOOLS.md +87 -5
  6. package/docs/MIGRATION-2.0.md +3 -2
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/PLUGIN_HANDOFF_FOLLOWUP.md +1 -1
  9. package/docs/RELEASE.md +10 -6
  10. package/docs/WACANDO_PACKAGES.md +76 -22
  11. package/package.json +19 -7
  12. package/packages/mcp/agent-tools.mjs +15 -0
  13. package/packages/mcp/server.mjs +1 -1
  14. package/packages/onboarding/version.mjs +1 -1
  15. package/packages/runtime/conversations.mjs +43 -10
  16. package/packages/runtime/device.mjs +1 -1
  17. package/packages/runtime/files.mjs +626 -0
  18. package/packages/runtime/recovery.mjs +5 -4
  19. package/packages/runtime/runtime.mjs +21 -3
  20. package/packages/runtime/session.mjs +1 -1
  21. package/packages/wacando/chunks/chunk-53E7SSTF.mjs +423 -0
  22. package/packages/wacando/chunks/chunk-PHGOH4AU.mjs +421 -0
  23. package/packages/wacando/chunks/chunk-PZDQXGA6.mjs +74 -0
  24. package/packages/wacando/omemo-wire.mjs +192 -0
  25. package/packages/wacando/omemo.mjs +3045 -0
  26. package/packages/wacando/xmpp.mjs +86 -0
  27. package/plugins/wacando/.codex-plugin/plugin.json +1 -1
  28. package/plugins/wacando/skills/wacando/SKILL.md +1 -0
  29. package/plugins/wacando-claude/.claude-plugin/plugin.json +1 -1
  30. package/plugins/wacando-claude/skills/open/SKILL.md +1 -0
  31. package/plugins/wacando-cursor/.cursor-plugin/plugin.json +1 -1
  32. package/plugins/wacando-cursor/skills/wacando/SKILL.md +1 -0
  33. package/provenance.json +129 -266
@@ -0,0 +1,421 @@
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
+ import {
3
+ DomainError
4
+ } from "./chunk-PZDQXGA6.mjs";
5
+
6
+ // packages/omemo/src/ns.ts
7
+ var NS_OMEMO = "eu.siacs.conversations.axolotl";
8
+ var NS_DEVICELIST = `${NS_OMEMO}.devicelist`;
9
+ var NS_EME = "urn:xmpp:eme:0";
10
+ var NS_HINTS = "urn:xmpp:hints";
11
+ var NS_SCE = "urn:xmpp:sce:1";
12
+ var NS_WACANDO_OMEMO = "urn:wacando:omemo:1";
13
+ function bundleNode(deviceId) {
14
+ return `${NS_OMEMO}.bundles:${deviceId}`;
15
+ }
16
+ var PEP_ITEM_ID = "current";
17
+ var BODY_MARKER = "WACANDO_OMEMO_V1";
18
+ var MESSAGE_KEY_BYTES = 32;
19
+ var GCM_IV_BYTES = 12;
20
+ var GCM_TAG_BYTES = 16;
21
+
22
+ // packages/omemo/src/errors.ts
23
+ var OmemoError = class extends DomainError {
24
+ constructor(message) {
25
+ super("SECURE_MESSAGING_UNAVAILABLE", message);
26
+ this.name = "OmemoError";
27
+ }
28
+ };
29
+ var UnsupportedSignalFormatError = class extends OmemoError {
30
+ };
31
+ var OmemoReceiveError = class extends OmemoError {
32
+ failure;
33
+ constructor(message, failure) {
34
+ super(message);
35
+ this.failure = failure;
36
+ }
37
+ };
38
+
39
+ // packages/omemo/src/sce.ts
40
+ import { escapeXMLText } from "@xmpp/xml";
41
+ import ltxParse from "ltx/lib/parse.js";
42
+ function buildSceEnvelope(bodyText) {
43
+ return `<content xmlns="${NS_SCE}"><payload><body xmlns="jabber:client">${escapeXMLText(bodyText)}</body></payload></content>`;
44
+ }
45
+ function parseXmlDocument(xmlText) {
46
+ let root;
47
+ try {
48
+ root = ltxParse(xmlText);
49
+ } catch (err) {
50
+ throw new OmemoError(`XML parse failed: ${err.message}`);
51
+ }
52
+ if (!root || typeof root.getName !== "function") {
53
+ throw new OmemoError("empty XML document");
54
+ }
55
+ return root;
56
+ }
57
+ function parseSceEnvelope(xmlText) {
58
+ const root = parseXmlDocument(xmlText);
59
+ if (root.getName() !== "content" || root.getNS() !== NS_SCE) {
60
+ throw new OmemoError("decrypted payload is not a SCE content envelope");
61
+ }
62
+ const payload = root.getChild("payload");
63
+ const body = payload?.getChild("body", "jabber:client");
64
+ if (!body) {
65
+ throw new OmemoError("SCE payload has no <body>");
66
+ }
67
+ return body.text();
68
+ }
69
+
70
+ // packages/omemo/src/wire.ts
71
+ import xml from "@xmpp/xml";
72
+
73
+ // packages/omemo/src/bytes.ts
74
+ import { fromByteArray, toByteArray } from "base64-js";
75
+ function b64encode(bytes) {
76
+ return fromByteArray(bytes);
77
+ }
78
+ function b64decode(s) {
79
+ return new Uint8Array(toByteArray(s));
80
+ }
81
+ function randomBytes(length) {
82
+ const out = new Uint8Array(length);
83
+ globalThis.crypto.getRandomValues(out);
84
+ return out;
85
+ }
86
+ function utf8Encode(s) {
87
+ return new Uint8Array(new TextEncoder().encode(s));
88
+ }
89
+ function utf8Decode(bytes) {
90
+ return new TextDecoder().decode(bytes);
91
+ }
92
+
93
+ // packages/omemo/src/wire.ts
94
+ function buildEncryptedMessageEl(parts) {
95
+ const headerChildren = [
96
+ ...parts.keys.map(
97
+ (k) => xml(
98
+ "key",
99
+ { rid: String(k.rid), jid: k.jid, ...k.isPreKey ? { prekey: "1" } : {} },
100
+ k.ciphertextB64
101
+ )
102
+ ),
103
+ xml("iv", {}, parts.ivB64)
104
+ ];
105
+ return xml(
106
+ "message",
107
+ { to: parts.roomJid, type: "groupchat", id: parts.stanzaId ?? newStanzaId() },
108
+ xml(
109
+ "encrypted",
110
+ { xmlns: NS_OMEMO },
111
+ xml("header", { sid: String(parts.senderDeviceId) }, ...headerChildren),
112
+ xml("payload", {}, parts.payloadB64)
113
+ ),
114
+ xml("sender", { xmlns: NS_WACANDO_OMEMO, jid: parts.senderBareJid }),
115
+ xml("encryption", { xmlns: NS_EME, namespace: NS_OMEMO, name: "OMEMO" }),
116
+ xml("store", { xmlns: NS_HINTS }),
117
+ xml("body", {}, BODY_MARKER)
118
+ );
119
+ }
120
+ function newStanzaId() {
121
+ return `om-${[...randomBytes(8)].map((b) => b.toString(16).padStart(2, "0")).join("")}`;
122
+ }
123
+ function parseEncrypted(stanza) {
124
+ const encrypted = findDescendant(stanza, "encrypted", NS_OMEMO);
125
+ if (!encrypted) return null;
126
+ const header = encrypted.getChild("header");
127
+ const payload = encrypted.getChild("payload");
128
+ const sender = findDescendant(stanza, "sender", NS_WACANDO_OMEMO);
129
+ const sid = Number(header?.getAttr("sid"));
130
+ const senderJid = sender?.getAttr("jid");
131
+ const ivB64 = header?.getChild("iv")?.text();
132
+ const payloadB64 = payload?.text();
133
+ if (!Number.isInteger(sid) || !senderJid || !ivB64 || !payloadB64) {
134
+ return null;
135
+ }
136
+ const keys = [];
137
+ for (const k of header?.getChildren("key") ?? []) {
138
+ const rid = Number(k.getAttr("rid"));
139
+ if (!Number.isInteger(rid)) continue;
140
+ const jid = k.getAttr("jid");
141
+ keys.push({
142
+ rid,
143
+ jid: typeof jid === "string" ? jid : void 0,
144
+ ciphertextB64: k.text(),
145
+ isPreKey: k.getAttr("prekey") === "1" || k.getAttr("prekey") === "true"
146
+ });
147
+ }
148
+ if (keys.length === 0) return null;
149
+ return { senderDeviceId: sid, senderBareJid: senderJid, keys, ivB64, payloadB64 };
150
+ }
151
+ function findDescendant(el, name, ns) {
152
+ for (const child of el.getChildren(name)) {
153
+ if (child.getNS() === ns) return child;
154
+ }
155
+ for (const child of el.children) {
156
+ if (typeof child === "string") continue;
157
+ const found = findDescendant(child, name, ns);
158
+ if (found) return found;
159
+ }
160
+ return void 0;
161
+ }
162
+
163
+ // packages/omemo/src/recovery-wire.ts
164
+ import xml2 from "@xmpp/xml";
165
+ var NS_RECOVERY = "urn:wacando:omemo:recovery:1";
166
+ var RECOVERY_DOMAIN = "wacando-omemo-session-recovery-v1";
167
+ var RECOVERY_VERSION = "1";
168
+ var RECOVERY_PHASES = ["OFFER", "INIT", "CONFIRM", "COMMIT", "COMMITTED"];
169
+ var MAX_RECORD_BYTES = 4096;
170
+ var MAX_CTL_BYTES = 2048;
171
+ var SIGNATURE_BYTES = 64;
172
+ var FIELD_COUNT = 21;
173
+ var BARE_JID = /^[a-z0-9][a-z0-9._-]{0,127}@[a-z0-9][a-z0-9.-]{0,253}$/;
174
+ var CONVERSATION_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
175
+ var DECIMAL = /^(0|[1-9][0-9]{0,15})$/;
176
+ var POSITIVE = /^[1-9][0-9]{0,9}$/;
177
+ var HEX64 = /^[0-9a-f]{64}$/;
178
+ var STANZA_ID = /^[A-Za-z0-9_.:-]{1,64}$/;
179
+ var B64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
180
+ function signerRole(phase) {
181
+ return phase === "INIT" || phase === "COMMIT" ? "A" : "B";
182
+ }
183
+ function signerJid(record) {
184
+ return signerRole(record.phase) === "A" ? record.aJid : record.bJid;
185
+ }
186
+ function targetJid(record) {
187
+ return signerRole(record.phase) === "A" ? record.bJid : record.aJid;
188
+ }
189
+ function positive(value, max = 4294967295) {
190
+ if (!POSITIVE.test(value)) return null;
191
+ const n = Number(value);
192
+ return n >= 1 && n <= max ? n : null;
193
+ }
194
+ function encodeRecord(r) {
195
+ const fields = [
196
+ RECOVERY_DOMAIN,
197
+ r.phase,
198
+ r.conversationId,
199
+ r.roomJid,
200
+ r.aJid,
201
+ String(r.aDevice),
202
+ r.aFingerprint,
203
+ r.bJid,
204
+ String(r.bDevice),
205
+ r.bFingerprint,
206
+ r.nonce,
207
+ String(r.createdAt),
208
+ String(r.expiresAt),
209
+ String(r.consumedPreKeyId),
210
+ r.stanzaId,
211
+ r.keyDigest,
212
+ String(r.generation),
213
+ r.bundleDigest,
214
+ r.prevDigest,
215
+ r.ctlDigest,
216
+ r.selectedPreKeyId === null ? "" : String(r.selectedPreKeyId)
217
+ ];
218
+ return utf8Encode(JSON.stringify(fields));
219
+ }
220
+ function parseRecord(bytes) {
221
+ if (bytes.length === 0 || bytes.length > MAX_RECORD_BYTES)
222
+ return { ok: false, reason: "oversized" };
223
+ let raw;
224
+ try {
225
+ raw = JSON.parse(utf8Decode(bytes));
226
+ } catch {
227
+ return { ok: false, reason: "malformed" };
228
+ }
229
+ if (!Array.isArray(raw) || raw.length !== FIELD_COUNT || !raw.every((f2) => typeof f2 === "string"))
230
+ return { ok: false, reason: "malformed" };
231
+ const f = raw;
232
+ const [
233
+ domain,
234
+ phase,
235
+ conversationId,
236
+ roomJid,
237
+ aJid,
238
+ aDevice,
239
+ aFingerprint,
240
+ bJid,
241
+ bDevice,
242
+ bFingerprint,
243
+ nonce,
244
+ createdAt,
245
+ expiresAt,
246
+ consumedPreKeyId,
247
+ stanzaId,
248
+ keyDigest,
249
+ generation,
250
+ bundleDigest,
251
+ prevDigest,
252
+ ctlDigest,
253
+ selected
254
+ ] = f;
255
+ if (domain !== RECOVERY_DOMAIN) return { ok: false, reason: "malformed" };
256
+ if (!RECOVERY_PHASES.includes(phase))
257
+ return { ok: false, reason: "malformed" };
258
+ const p = phase;
259
+ const aDev = positive(aDevice);
260
+ const bDev = positive(bDevice);
261
+ const consumed = positive(consumedPreKeyId, 16777215);
262
+ const selectedId = selected === "" ? null : positive(selected, 16777215);
263
+ const roomLocal = roomJid.split("@")[0];
264
+ const valid = CONVERSATION_ID.test(conversationId) && BARE_JID.test(roomJid) && roomLocal === `c_${conversationId}` && BARE_JID.test(aJid) && BARE_JID.test(bJid) && aDev !== null && bDev !== null && HEX64.test(aFingerprint) && HEX64.test(bFingerprint) && HEX64.test(nonce) && DECIMAL.test(createdAt) && DECIMAL.test(expiresAt) && Number(expiresAt) > Number(createdAt) && consumed !== null && STANZA_ID.test(stanzaId) && HEX64.test(keyDigest) && DECIMAL.test(generation) && HEX64.test(bundleDigest) && (p === "OFFER" ? prevDigest === "" : HEX64.test(prevDigest)) && (p === "OFFER" ? ctlDigest === "" : HEX64.test(ctlDigest)) && (p === "OFFER" ? selected === "" : selectedId !== null && selectedId !== consumed) && !(aJid === bJid && aDev === bDev);
265
+ if (!valid) return { ok: false, reason: "malformed" };
266
+ const record = {
267
+ phase: p,
268
+ conversationId,
269
+ roomJid,
270
+ aJid,
271
+ aDevice: aDev,
272
+ aFingerprint,
273
+ bJid,
274
+ bDevice: bDev,
275
+ bFingerprint,
276
+ nonce,
277
+ createdAt: Number(createdAt),
278
+ expiresAt: Number(expiresAt),
279
+ consumedPreKeyId: consumed,
280
+ stanzaId,
281
+ keyDigest,
282
+ generation: Number(generation),
283
+ bundleDigest,
284
+ prevDigest,
285
+ ctlDigest,
286
+ selectedPreKeyId: selectedId
287
+ };
288
+ const again = encodeRecord(record);
289
+ if (again.length !== bytes.length || again.some((b, i) => b !== bytes[i]))
290
+ return { ok: false, reason: "noncanonical" };
291
+ return { ok: true, record };
292
+ }
293
+ function canonicalB64(text, maxBytes) {
294
+ if (text.length > Math.ceil(maxBytes / 3) * 4 + 4) return "oversized";
295
+ if (text.length === 0 || !B64.test(text)) return null;
296
+ let bytes;
297
+ try {
298
+ bytes = b64decode(text);
299
+ } catch {
300
+ return null;
301
+ }
302
+ if (bytes.length > maxBytes) return "oversized";
303
+ if (bytes.length === 0 || b64encode(bytes) !== text) return null;
304
+ return bytes;
305
+ }
306
+ var FULL_JID = /^[a-z0-9][a-z0-9._-]{0,127}@[a-z0-9][a-z0-9.-]{0,253}\/[^\s/]{1,256}$/;
307
+ function parseRecoveryEl(el, withOrigin) {
308
+ if (!el.is("recovery", NS_RECOVERY)) return { ok: false, reason: "malformed" };
309
+ const attrs = Object.keys(el.attrs).filter((k) => k !== "xmlns");
310
+ if (attrs.length !== 1 || el.attrs.v !== RECOVERY_VERSION)
311
+ return { ok: false, reason: "malformed" };
312
+ let recordB64 = null;
313
+ let sigB64 = null;
314
+ let ctlB64 = null;
315
+ let origin = null;
316
+ let originSeen = false;
317
+ for (const child of el.children) {
318
+ if (typeof child === "string") {
319
+ if (child.trim() !== "") return { ok: false, reason: "malformed" };
320
+ continue;
321
+ }
322
+ const name = child.getName();
323
+ if (child.getNS() !== NS_RECOVERY) return { ok: false, reason: "malformed" };
324
+ if (name === "origin") {
325
+ if (!withOrigin) return { ok: false, reason: "unexpected-origin" };
326
+ if (originSeen || child.children.length !== 0) return { ok: false, reason: "malformed" };
327
+ const keys = Object.keys(child.attrs).filter((k) => k !== "xmlns");
328
+ const jid = child.attrs.jid;
329
+ if (keys.length !== 1 || typeof jid !== "string" || !FULL_JID.test(jid))
330
+ return { ok: false, reason: "malformed" };
331
+ origin = jid;
332
+ originSeen = true;
333
+ continue;
334
+ }
335
+ const text = (() => {
336
+ const plain = { ...child.attrs };
337
+ delete plain.xmlns;
338
+ if (Object.keys(plain).length !== 0) return null;
339
+ return child.children.every((c) => typeof c === "string") ? child.children.join("") : null;
340
+ })();
341
+ if (text === null) return { ok: false, reason: "malformed" };
342
+ if (name === "record" && recordB64 === null) recordB64 = text;
343
+ else if (name === "sig" && sigB64 === null) sigB64 = text;
344
+ else if (name === "ctl" && ctlB64 === null) ctlB64 = text;
345
+ else return { ok: false, reason: "malformed" };
346
+ }
347
+ if (withOrigin && origin === null) return { ok: false, reason: "missing-origin" };
348
+ if (recordB64 === null || sigB64 === null) return { ok: false, reason: "malformed" };
349
+ const recordBytes = canonicalB64(recordB64, MAX_RECORD_BYTES);
350
+ const signature = canonicalB64(sigB64, SIGNATURE_BYTES);
351
+ const ctl = ctlB64 === null ? null : canonicalB64(ctlB64, MAX_CTL_BYTES);
352
+ if (recordBytes === "oversized" || ctl === "oversized") return { ok: false, reason: "oversized" };
353
+ if (!recordBytes || !signature || signature === "oversized" || signature.length !== SIGNATURE_BYTES || ctlB64 !== null && !ctl)
354
+ return { ok: false, reason: "malformed" };
355
+ const parsed = parseRecord(recordBytes);
356
+ if (!parsed.ok) return parsed;
357
+ if (parsed.record.phase === "OFFER" !== (ctl === null))
358
+ return { ok: false, reason: "malformed" };
359
+ return {
360
+ ok: true,
361
+ control: {
362
+ packet: { recordB64, sigB64, ...ctlB64 !== null ? { ctlB64 } : {} },
363
+ record: parsed.record,
364
+ recordBytes,
365
+ signature,
366
+ ctl,
367
+ originJid: origin
368
+ }
369
+ };
370
+ }
371
+ function buildRecoveryEl(packet, originJid) {
372
+ return xml2(
373
+ "recovery",
374
+ { xmlns: NS_RECOVERY, v: RECOVERY_VERSION },
375
+ xml2("record", {}, packet.recordB64),
376
+ xml2("sig", {}, packet.sigB64),
377
+ ...packet.ctlB64 !== void 0 ? [xml2("ctl", {}, packet.ctlB64)] : [],
378
+ ...originJid !== void 0 ? [xml2("origin", { jid: originJid })] : []
379
+ );
380
+ }
381
+
382
+ export {
383
+ NS_OMEMO,
384
+ NS_DEVICELIST,
385
+ NS_EME,
386
+ NS_HINTS,
387
+ NS_SCE,
388
+ NS_WACANDO_OMEMO,
389
+ bundleNode,
390
+ PEP_ITEM_ID,
391
+ BODY_MARKER,
392
+ MESSAGE_KEY_BYTES,
393
+ GCM_IV_BYTES,
394
+ GCM_TAG_BYTES,
395
+ OmemoError,
396
+ UnsupportedSignalFormatError,
397
+ OmemoReceiveError,
398
+ b64encode,
399
+ b64decode,
400
+ randomBytes,
401
+ utf8Encode,
402
+ buildSceEnvelope,
403
+ parseXmlDocument,
404
+ parseSceEnvelope,
405
+ buildEncryptedMessageEl,
406
+ parseEncrypted,
407
+ NS_RECOVERY,
408
+ RECOVERY_DOMAIN,
409
+ RECOVERY_VERSION,
410
+ RECOVERY_PHASES,
411
+ MAX_RECORD_BYTES,
412
+ MAX_CTL_BYTES,
413
+ SIGNATURE_BYTES,
414
+ signerRole,
415
+ signerJid,
416
+ targetJid,
417
+ encodeRecord,
418
+ parseRecord,
419
+ parseRecoveryEl,
420
+ buildRecoveryEl
421
+ };
@@ -0,0 +1,74 @@
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
+
3
+ // packages/contracts/src/errors.ts
4
+ var DomainError = class extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "DomainError";
9
+ this.code = code;
10
+ }
11
+ };
12
+
13
+ // packages/contracts/src/files.ts
14
+ var ALLOWED_MEDIA_TYPES = [
15
+ "text/plain",
16
+ "text/markdown",
17
+ "application/pdf",
18
+ "application/zip",
19
+ "application/gzip"
20
+ ];
21
+ var MAX_FILE_BYTES = 10 * 1024 * 1024;
22
+ var MIN_FILE_BYTES = 16;
23
+ var TEXT_HEAD_MIN_BYTES = 32;
24
+ function startsWith(bytes, sig) {
25
+ return bytes.length >= sig.length && sig.every((b, i) => bytes[i] === b);
26
+ }
27
+ function isGzipHeader(bytes) {
28
+ if (bytes.length < 10 || !startsWith(bytes, [31, 139, 8])) return false;
29
+ const flg = bytes[3];
30
+ const xfl = bytes[8];
31
+ const os = bytes[9];
32
+ return (flg & 224) === 0 && [0, 2, 4].includes(xfl) && (os <= 13 || os === 255);
33
+ }
34
+ function isTextHead(bytes) {
35
+ const head = bytes.subarray(0, Math.min(bytes.length, 512));
36
+ if (head.length < TEXT_HEAD_MIN_BYTES) return false;
37
+ const cuts = head.length < bytes.length ? [0, 1, 2, 3] : [0];
38
+ for (const cut of cuts) {
39
+ let text;
40
+ try {
41
+ text = new TextDecoder("utf-8", { fatal: true }).decode(head.subarray(0, head.length - cut));
42
+ } catch {
43
+ continue;
44
+ }
45
+ return !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(text);
46
+ }
47
+ return false;
48
+ }
49
+ function looksLikePlaintext(bytes) {
50
+ return startsWith(bytes, [37, 80, 68, 70, 45]) || // %PDF-
51
+ startsWith(bytes, [80, 75, 3, 4]) || // zip local file header
52
+ startsWith(bytes, [80, 75, 5, 6]) || // empty zip
53
+ isGzipHeader(bytes) || isTextHead(bytes);
54
+ }
55
+
56
+ // packages/contracts/src/internal.ts
57
+ function xmppDomain() {
58
+ const env = globalThis.process?.env;
59
+ return env?.XMPP_DOMAIN ?? "wacando.localhost";
60
+ }
61
+ var XMPP_DOMAIN = xmppDomain();
62
+ var XMPP_MUC_DOMAIN = `conference.${XMPP_DOMAIN}`;
63
+
64
+ // packages/contracts/src/message.ts
65
+ var MAX_MESSAGE_PLAINTEXT_BYTES = 64 * 1024;
66
+
67
+ export {
68
+ DomainError,
69
+ ALLOWED_MEDIA_TYPES,
70
+ MAX_FILE_BYTES,
71
+ MIN_FILE_BYTES,
72
+ looksLikePlaintext,
73
+ XMPP_DOMAIN
74
+ };
@@ -0,0 +1,192 @@
1
+ // Embedded from lbframe/wacando@618922ecaf2213da18949a8b27d343c8767b64c5 by scripts/embed-wacando.mjs. Do not edit; see provenance.json.
2
+ import {
3
+ BODY_MARKER,
4
+ GCM_IV_BYTES,
5
+ GCM_TAG_BYTES,
6
+ MAX_CTL_BYTES,
7
+ MAX_RECORD_BYTES,
8
+ MESSAGE_KEY_BYTES,
9
+ NS_DEVICELIST,
10
+ NS_EME,
11
+ NS_HINTS,
12
+ NS_OMEMO,
13
+ NS_RECOVERY,
14
+ NS_SCE,
15
+ NS_WACANDO_OMEMO,
16
+ PEP_ITEM_ID,
17
+ RECOVERY_DOMAIN,
18
+ RECOVERY_PHASES,
19
+ RECOVERY_VERSION,
20
+ SIGNATURE_BYTES,
21
+ buildEncryptedMessageEl,
22
+ buildRecoveryEl,
23
+ bundleNode,
24
+ encodeRecord,
25
+ parseEncrypted,
26
+ parseRecord,
27
+ parseRecoveryEl,
28
+ parseXmlDocument,
29
+ signerJid,
30
+ signerRole,
31
+ targetJid
32
+ } from "./chunks/chunk-PHGOH4AU.mjs";
33
+ import "./chunks/chunk-PZDQXGA6.mjs";
34
+
35
+ // packages/omemo/src/policy-wire.ts
36
+ import xml from "@xmpp/xml";
37
+ var NS_WACANDO_POLICY = "urn:wacando:policy:0";
38
+ var DIGEST_VERSION = "wacando-omemo-digest-v1";
39
+ var STANZA_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
40
+ var BARE_JID_RE = /^[^\s|/@]{1,1023}@[^\s|/@]{1,1023}$/;
41
+ var B64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
42
+ function isCanonicalBareJid(value) {
43
+ return typeof value === "string" && BARE_JID_RE.test(value);
44
+ }
45
+ function parseBoundEncrypted(stanza) {
46
+ const parsed = parseEncrypted(stanza);
47
+ if (!parsed) return null;
48
+ if (!isCanonicalBareJid(parsed.senderBareJid)) return null;
49
+ if (!B64_RE.test(parsed.ivB64.trim()) || !B64_RE.test(parsed.payloadB64.trim())) return null;
50
+ for (const k of parsed.keys) {
51
+ if (!isCanonicalBareJid(k.jid) || !B64_RE.test(k.ciphertextB64.trim())) return null;
52
+ }
53
+ return parsed;
54
+ }
55
+ function canonicalCiphertext(parsed) {
56
+ const lines = [
57
+ DIGEST_VERSION,
58
+ `sender=${parsed.senderBareJid}`,
59
+ `sid=${parsed.senderDeviceId}`,
60
+ ...parsed.keys.map(
61
+ (k) => `key=${k.jid}|${k.rid}|${k.isPreKey ? 1 : 0}|${k.ciphertextB64.trim()}`
62
+ ),
63
+ `iv=${parsed.ivB64.trim()}`,
64
+ `payload=${parsed.payloadB64.trim()}`
65
+ ];
66
+ return `${lines.join("\n")}
67
+ `;
68
+ }
69
+ async function sha256Hex(text) {
70
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
71
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
72
+ }
73
+ async function ciphertextDigest(stanza) {
74
+ const parsed = parseBoundEncrypted(stanza);
75
+ return parsed ? sha256Hex(canonicalCiphertext(parsed)) : null;
76
+ }
77
+ function digestOfParsed(parsed) {
78
+ return sha256Hex(canonicalCiphertext(parsed));
79
+ }
80
+ function isValidStanzaId(id) {
81
+ return typeof id === "string" && STANZA_ID_RE.test(id);
82
+ }
83
+ function toPolicySubmission(groupchat, componentJid) {
84
+ const room = groupchat.getAttr("to");
85
+ const id = groupchat.getAttr("id");
86
+ const parsed = parseBoundEncrypted(groupchat);
87
+ if (typeof room !== "string" || !isValidStanzaId(id) || !parsed) {
88
+ throw new Error("not an encrypted groupchat stanza");
89
+ }
90
+ const canonical = buildHeldGroupchat(room, id, parsed);
91
+ const encrypted = canonical.getChild("encrypted", NS_OMEMO);
92
+ const sender = canonical.getChild("sender", NS_WACANDO_OMEMO);
93
+ if (!encrypted || !sender) throw new Error("not an encrypted groupchat stanza");
94
+ return xml(
95
+ "message",
96
+ { to: componentJid, id },
97
+ xml("submit", { xmlns: NS_WACANDO_POLICY, room }),
98
+ encrypted,
99
+ sender
100
+ );
101
+ }
102
+ function parsePolicySubmission(stanza) {
103
+ if (!stanza.is("message") || stanza.getAttr("type") === "error") {
104
+ return { ok: false, reason: "malformed" };
105
+ }
106
+ const submit = stanza.getChild("submit", NS_WACANDO_POLICY);
107
+ const id = stanza.getAttr("id");
108
+ const from = stanza.getAttr("from");
109
+ const room = submit?.getAttr("room");
110
+ if (!submit || !isValidStanzaId(id) || typeof from !== "string" || typeof room !== "string") {
111
+ return { ok: false, reason: "malformed", ...isValidStanzaId(id) ? { stanzaId: id } : {} };
112
+ }
113
+ const parsed = parseBoundEncrypted(stanza);
114
+ if (!parsed) return { ok: false, reason: "not-encrypted", stanzaId: id };
115
+ const fromBareJid = from.split("/")[0] ?? "";
116
+ if (parsed.senderBareJid !== fromBareJid) {
117
+ return { ok: false, reason: "sender-mismatch", stanzaId: id };
118
+ }
119
+ return { ok: true, roomJid: room, stanzaId: id, fromBareJid, parsed };
120
+ }
121
+ function buildHeldGroupchat(roomJid, stanzaId, parsed) {
122
+ return buildEncryptedMessageEl({
123
+ roomJid,
124
+ senderBareJid: parsed.senderBareJid,
125
+ senderDeviceId: parsed.senderDeviceId,
126
+ keys: parsed.keys,
127
+ ivB64: parsed.ivB64,
128
+ payloadB64: parsed.payloadB64,
129
+ stanzaId
130
+ });
131
+ }
132
+ function buildRelayEl(roomJid, stanzaId, parsed, digest) {
133
+ const el = buildHeldGroupchat(roomJid, stanzaId, parsed);
134
+ el.append(xml("relay", { xmlns: NS_WACANDO_POLICY, jid: parsed.senderBareJid, digest }));
135
+ return el;
136
+ }
137
+ function submissionStatusEl(to, stanzaId, state, extra = {}) {
138
+ return xml(
139
+ "message",
140
+ { to, type: "normal" },
141
+ xml("submission", {
142
+ xmlns: NS_WACANDO_POLICY,
143
+ id: stanzaId,
144
+ state,
145
+ ...extra.reason ? { reason: extra.reason } : {},
146
+ ...extra.digest ? { digest: extra.digest } : {}
147
+ })
148
+ );
149
+ }
150
+ export {
151
+ BODY_MARKER,
152
+ GCM_IV_BYTES,
153
+ GCM_TAG_BYTES,
154
+ MAX_CTL_BYTES,
155
+ MAX_RECORD_BYTES,
156
+ MESSAGE_KEY_BYTES,
157
+ NS_DEVICELIST,
158
+ NS_EME,
159
+ NS_HINTS,
160
+ NS_OMEMO,
161
+ NS_RECOVERY,
162
+ NS_SCE,
163
+ NS_WACANDO_OMEMO,
164
+ NS_WACANDO_POLICY,
165
+ PEP_ITEM_ID,
166
+ RECOVERY_DOMAIN,
167
+ RECOVERY_PHASES,
168
+ RECOVERY_VERSION,
169
+ SIGNATURE_BYTES,
170
+ buildEncryptedMessageEl,
171
+ buildHeldGroupchat,
172
+ buildRecoveryEl,
173
+ buildRelayEl,
174
+ bundleNode,
175
+ canonicalCiphertext,
176
+ ciphertextDigest,
177
+ digestOfParsed,
178
+ encodeRecord,
179
+ isCanonicalBareJid,
180
+ isValidStanzaId,
181
+ parseBoundEncrypted,
182
+ parseEncrypted,
183
+ parsePolicySubmission,
184
+ parseRecord,
185
+ parseRecoveryEl,
186
+ parseXmlDocument,
187
+ signerJid,
188
+ signerRole,
189
+ submissionStatusEl,
190
+ targetJid,
191
+ toPolicySubmission
192
+ };