@agentvault/agentvault 0.9.5 → 0.9.7

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/cli.js CHANGED
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // ../../node_modules/libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs
13
- var __filename, __dirname, url, path, Module, Module, root, window_, crypto_, randomValuesStandard, crypto, randomValueNodeJS, _Module, libsodium_sumo_default;
13
+ var __filename, __dirname, url, path, Module, Module, root, window_, crypto_, randomValuesStandard, crypto2, randomValueNodeJS, _Module, libsodium_sumo_default;
14
14
  var init_libsodium_sumo = __esm({
15
15
  async "../../node_modules/libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs"() {
16
16
  try {
@@ -74,9 +74,9 @@ var init_libsodium_sumo = __esm({
74
74
  Module.getRandomValue = randomValuesStandard;
75
75
  } catch (e) {
76
76
  try {
77
- crypto = null;
77
+ crypto2 = null;
78
78
  randomValueNodeJS = function() {
79
- var buf = crypto["randomBytes"](4);
79
+ var buf = crypto2["randomBytes"](4);
80
80
  return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
81
81
  };
82
82
  randomValueNodeJS();
@@ -40279,7 +40279,7 @@ var init_libsodium_sumo = __esm({
40279
40279
  try {
40280
40280
  var window_ = "object" === typeof window ? window : self;
40281
40281
  var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
40282
- crypto_ = crypto_ === void 0 ? crypto : crypto_;
40282
+ crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
40283
40283
  var randomValuesStandard = function() {
40284
40284
  var buf = new Uint32Array(1);
40285
40285
  crypto_.getRandomValues(buf);
@@ -40289,9 +40289,9 @@ var init_libsodium_sumo = __esm({
40289
40289
  Module3.getRandomValue = randomValuesStandard;
40290
40290
  } catch (e) {
40291
40291
  try {
40292
- var crypto = null;
40292
+ var crypto2 = null;
40293
40293
  var randomValueNodeJS = function() {
40294
- var buf = crypto["randomBytes"](4);
40294
+ var buf = crypto2["randomBytes"](4);
40295
40295
  return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
40296
40296
  };
40297
40297
  randomValueNodeJS();
@@ -41339,7 +41339,7 @@ var init_libsodium_sumo = __esm({
41339
41339
  try {
41340
41340
  var window_ = "object" === typeof window ? window : self;
41341
41341
  var crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
41342
- crypto_ = crypto_ === void 0 ? crypto : crypto_;
41342
+ crypto_ = crypto_ === void 0 ? crypto2 : crypto_;
41343
41343
  var randomValuesStandard = function() {
41344
41344
  var buf = new Uint32Array(1);
41345
41345
  crypto_.getRandomValues(buf);
@@ -41349,9 +41349,9 @@ var init_libsodium_sumo = __esm({
41349
41349
  Module2.getRandomValue = randomValuesStandard;
41350
41350
  } catch (e) {
41351
41351
  try {
41352
- var crypto = null;
41352
+ var crypto2 = null;
41353
41353
  var randomValueNodeJS = function() {
41354
- var buf = crypto["randomBytes"](4);
41354
+ var buf = crypto2["randomBytes"](4);
41355
41355
  return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0;
41356
41356
  };
41357
41357
  randomValueNodeJS();
@@ -44783,6 +44783,22 @@ var init_ratchet = __esm({
44783
44783
  const currentDhHex = this.state.dhReceivingPublicKey ? libsodium_wrappers_default.to_hex(this.state.dhReceivingPublicKey) : null;
44784
44784
  if (currentDhHex === null && this.state.receivingChain) {
44785
44785
  this.state.dhReceivingPublicKey = message.header.dhPublicKey;
44786
+ } else if (currentDhHex === null && !this.state.receivingChain) {
44787
+ if (message.header.messageNumber === 0) {
44788
+ try {
44789
+ const { messageKey: testKey, nextChainKey: nextChainKey2 } = kdfChainKey(this.state.rootKey);
44790
+ const ad2 = serializeHeader(message.header);
44791
+ const ptBytes = libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.ciphertext, ad2, message.nonce, testKey);
44792
+ this.state.dhReceivingPublicKey = message.header.dhPublicKey;
44793
+ this.state.receivingChain = {
44794
+ chainKey: nextChainKey2,
44795
+ messageNumber: 1
44796
+ };
44797
+ return libsodium_wrappers_default.to_string(ptBytes);
44798
+ } catch {
44799
+ }
44800
+ }
44801
+ this.dhRatchetReceive(message.header.dhPublicKey);
44786
44802
  } else if (headerDhHex !== currentDhHex) {
44787
44803
  if (this.state.receivingChain && this.state.dhReceivingPublicKey) {
44788
44804
  this.skipMessages(this.state.receivingChain, message.header.previousChainLength, this.state.dhReceivingPublicKey);
@@ -45151,6 +45167,14 @@ var init_merkle = __esm({
45151
45167
  }
45152
45168
  });
45153
45169
 
45170
+ // ../crypto/dist/vc.js
45171
+ var init_vc = __esm({
45172
+ async "../crypto/dist/vc.js"() {
45173
+ "use strict";
45174
+ await init_did();
45175
+ }
45176
+ });
45177
+
45154
45178
  // ../crypto/dist/transport.js
45155
45179
  function hexToBytes(hex) {
45156
45180
  return libsodium_wrappers_default.from_hex(hex);
@@ -45200,6 +45224,220 @@ var init_transport = __esm({
45200
45224
  }
45201
45225
  });
45202
45226
 
45227
+ // ../crypto/dist/telemetry.js
45228
+ function randomHex(byteCount) {
45229
+ const bytes = new Uint8Array(byteCount);
45230
+ crypto.getRandomValues(bytes);
45231
+ let hex = "";
45232
+ for (let i2 = 0; i2 < bytes.length; i2++) {
45233
+ hex += bytes[i2].toString(16).padStart(2, "0");
45234
+ }
45235
+ return hex;
45236
+ }
45237
+ function generateTraceId() {
45238
+ return randomHex(16);
45239
+ }
45240
+ function generateSpanId() {
45241
+ return randomHex(8);
45242
+ }
45243
+ function buildLlmSpan(opts) {
45244
+ const now = Date.now();
45245
+ const attributes = {
45246
+ "ai.agent.llm.model": opts.model,
45247
+ "ai.agent.llm.latency_ms": opts.latencyMs,
45248
+ "ai.agent.llm.tokens_input": opts.tokensInput,
45249
+ "ai.agent.llm.tokens_output": opts.tokensOutput
45250
+ };
45251
+ if (opts.provider !== void 0) {
45252
+ attributes["ai.agent.llm.provider"] = opts.provider;
45253
+ }
45254
+ const isError = opts.status === "error";
45255
+ const status = isError ? { code: 2, ...opts.statusMessage ? { message: opts.statusMessage } : {} } : { code: 0 };
45256
+ return {
45257
+ traceId: opts.traceId ?? generateTraceId(),
45258
+ spanId: opts.spanId ?? generateSpanId(),
45259
+ parentSpanId: opts.parentSpanId,
45260
+ name: "llm.inference",
45261
+ kind: "internal",
45262
+ startTime: now - opts.latencyMs,
45263
+ endTime: now,
45264
+ attributes,
45265
+ status
45266
+ };
45267
+ }
45268
+ function buildToolSpan(opts) {
45269
+ const now = Date.now();
45270
+ const attributes = {
45271
+ "ai.agent.tool.name": opts.toolName,
45272
+ "ai.agent.tool.latency_ms": opts.latencyMs,
45273
+ "ai.agent.tool.success": opts.success
45274
+ };
45275
+ const status = opts.success ? { code: 0 } : { code: 2, ...opts.errorMessage ? { message: opts.errorMessage } : {} };
45276
+ return {
45277
+ traceId: opts.traceId ?? generateTraceId(),
45278
+ spanId: opts.spanId ?? generateSpanId(),
45279
+ parentSpanId: opts.parentSpanId,
45280
+ name: "tool.execute",
45281
+ kind: "internal",
45282
+ startTime: now - opts.latencyMs,
45283
+ endTime: now,
45284
+ attributes,
45285
+ status
45286
+ };
45287
+ }
45288
+ function buildErrorSpan(opts) {
45289
+ const now = Date.now();
45290
+ return {
45291
+ traceId: opts.traceId ?? generateTraceId(),
45292
+ spanId: opts.spanId ?? generateSpanId(),
45293
+ parentSpanId: opts.parentSpanId,
45294
+ name: "error",
45295
+ kind: opts.spanKind ?? "internal",
45296
+ startTime: now,
45297
+ endTime: now,
45298
+ attributes: {
45299
+ "ai.agent.error.type": opts.errorType,
45300
+ "ai.agent.error.message": opts.errorMessage
45301
+ },
45302
+ status: { code: 2, message: opts.errorMessage }
45303
+ };
45304
+ }
45305
+ var init_telemetry = __esm({
45306
+ "../crypto/dist/telemetry.js"() {
45307
+ "use strict";
45308
+ }
45309
+ });
45310
+
45311
+ // ../crypto/dist/telemetry-reporter.js
45312
+ function toOtlpAttributes(attrs) {
45313
+ return Object.entries(attrs).map(([key, val]) => {
45314
+ if (typeof val === "string") {
45315
+ return { key, value: { stringValue: val } };
45316
+ }
45317
+ if (typeof val === "boolean") {
45318
+ return { key, value: { boolValue: val } };
45319
+ }
45320
+ if (Number.isInteger(val)) {
45321
+ return { key, value: { intValue: val } };
45322
+ }
45323
+ return { key, value: { doubleValue: val } };
45324
+ });
45325
+ }
45326
+ function spanToOtlp(span) {
45327
+ const otlp = {
45328
+ traceId: span.traceId,
45329
+ spanId: span.spanId,
45330
+ name: span.name,
45331
+ kind: span.kind,
45332
+ startTimeUnixNano: String(span.startTime * 1e6),
45333
+ endTimeUnixNano: String(span.endTime * 1e6),
45334
+ attributes: toOtlpAttributes(span.attributes)
45335
+ };
45336
+ if (span.parentSpanId !== void 0) {
45337
+ otlp.parentSpanId = span.parentSpanId;
45338
+ }
45339
+ if (span.status) {
45340
+ otlp.status = span.status;
45341
+ }
45342
+ if (span.events && span.events.length > 0) {
45343
+ otlp.events = span.events;
45344
+ }
45345
+ return otlp;
45346
+ }
45347
+ var TelemetryReporter;
45348
+ var init_telemetry_reporter = __esm({
45349
+ "../crypto/dist/telemetry-reporter.js"() {
45350
+ "use strict";
45351
+ init_telemetry();
45352
+ TelemetryReporter = class {
45353
+ _apiBase;
45354
+ _hubId;
45355
+ _authHeader;
45356
+ _fetch;
45357
+ _buffer = [];
45358
+ _timer = null;
45359
+ constructor(config) {
45360
+ this._apiBase = config.apiBase.replace(/\/+$/, "");
45361
+ this._hubId = config.hubId;
45362
+ this._authHeader = config.authHeader;
45363
+ this._fetch = config.fetchImpl ?? globalThis.fetch;
45364
+ }
45365
+ /** Number of spans waiting to be flushed. */
45366
+ get pendingCount() {
45367
+ return this._buffer.length;
45368
+ }
45369
+ // -- Report methods ---------------------------------------------------------
45370
+ /** Record an LLM inference call. */
45371
+ reportLlmCall(opts) {
45372
+ this._buffer.push(buildLlmSpan(opts));
45373
+ }
45374
+ /** Record a tool/function invocation. */
45375
+ reportToolCall(opts) {
45376
+ this._buffer.push(buildToolSpan(opts));
45377
+ }
45378
+ /** Record an error event. */
45379
+ reportError(opts) {
45380
+ this._buffer.push(buildErrorSpan(opts));
45381
+ }
45382
+ /** Record an arbitrary pre-built span. */
45383
+ reportCustomSpan(span) {
45384
+ this._buffer.push(span);
45385
+ }
45386
+ // -- Flush ------------------------------------------------------------------
45387
+ /**
45388
+ * POST all buffered spans to the backend ingest endpoint.
45389
+ *
45390
+ * - On success (HTTP 2xx): clears the buffer.
45391
+ * - On failure: keeps spans in the buffer for retry.
45392
+ * - Never throws — telemetry is best-effort.
45393
+ */
45394
+ async flush() {
45395
+ if (this._buffer.length === 0) {
45396
+ return;
45397
+ }
45398
+ const spans = this._buffer;
45399
+ this._buffer = [];
45400
+ try {
45401
+ const response = await this._fetch(`${this._apiBase}/api/v1/telemetry/ingest`, {
45402
+ method: "POST",
45403
+ headers: {
45404
+ "Content-Type": "application/json",
45405
+ Authorization: this._authHeader
45406
+ },
45407
+ body: JSON.stringify({
45408
+ hub_id: this._hubId,
45409
+ spans: spans.map(spanToOtlp)
45410
+ })
45411
+ });
45412
+ if (!response.ok) {
45413
+ this._buffer = spans.concat(this._buffer);
45414
+ }
45415
+ } catch {
45416
+ this._buffer = spans.concat(this._buffer);
45417
+ }
45418
+ }
45419
+ // -- Auto-flush -------------------------------------------------------------
45420
+ /**
45421
+ * Start a periodic flush timer.
45422
+ * @param intervalMs Flush interval in milliseconds (default 30 000).
45423
+ */
45424
+ startAutoFlush(intervalMs = 3e4) {
45425
+ this.stopAutoFlush();
45426
+ this._timer = setInterval(() => {
45427
+ void this.flush();
45428
+ }, intervalMs);
45429
+ }
45430
+ /** Stop the periodic flush timer. Safe to call when not started. */
45431
+ stopAutoFlush() {
45432
+ if (this._timer !== null) {
45433
+ clearInterval(this._timer);
45434
+ this._timer = null;
45435
+ }
45436
+ }
45437
+ };
45438
+ }
45439
+ });
45440
+
45203
45441
  // ../crypto/dist/index.js
45204
45442
  var init_dist = __esm({
45205
45443
  async "../crypto/dist/index.js"() {
@@ -45211,7 +45449,10 @@ var init_dist = __esm({
45211
45449
  await init_did();
45212
45450
  init_scan_engine();
45213
45451
  await init_merkle();
45452
+ await init_vc();
45214
45453
  await init_transport();
45454
+ init_telemetry();
45455
+ init_telemetry_reporter();
45215
45456
  }
45216
45457
  });
45217
45458
 
@@ -45393,12 +45634,13 @@ var init_channel = __esm({
45393
45634
  _lastWakeTick = Date.now();
45394
45635
  _pendingPollTimer = null;
45395
45636
  _syncMessageIds = null;
45396
- /** Recently handled message IDs via WS — survives reconnects so sync skips them. Max 500. */
45397
- _recentlyHandledIds = /* @__PURE__ */ new Set();
45398
45637
  /** Queued A2A messages for responder channels not yet activated (no first initiator message received). */
45399
45638
  _a2aPendingQueue = {};
45400
45639
  _scanEngine = null;
45401
45640
  _scanRuleSetVersion = 0;
45641
+ _telemetryReporter = null;
45642
+ /** Topic ID from the most recent inbound message — used as fallback for replies. */
45643
+ _lastIncomingTopicId;
45402
45644
  // Liveness detection: server sends app-level {"event":"ping"} every 30s.
45403
45645
  // We check every 30s; if no data received in 90s (3 missed pings), connection is dead.
45404
45646
  static PING_INTERVAL_MS = 3e4;
@@ -45427,6 +45669,10 @@ var init_channel = __esm({
45427
45669
  get sessionCount() {
45428
45670
  return this._sessions.size;
45429
45671
  }
45672
+ /** Returns the TelemetryReporter instance (available after WebSocket connect). */
45673
+ get telemetry() {
45674
+ return this._telemetryReporter;
45675
+ }
45430
45676
  async start() {
45431
45677
  this._stopped = false;
45432
45678
  await libsodium_wrappers_default.ready;
@@ -45509,7 +45755,7 @@ var init_channel = __esm({
45509
45755
  if (this._sessions.size === 0) {
45510
45756
  throw new Error("No active sessions");
45511
45757
  }
45512
- const topicId = options?.topicId ?? this._persisted?.defaultTopicId;
45758
+ const topicId = options?.topicId ?? this._lastIncomingTopicId ?? this._persisted?.defaultTopicId;
45513
45759
  const messageType = options?.messageType ?? "text";
45514
45760
  const priority = options?.priority ?? "normal";
45515
45761
  const parentSpanId = options?.parentSpanId;
@@ -45971,6 +46217,11 @@ var init_channel = __esm({
45971
46217
  clearTimeout(this._reconnectTimer);
45972
46218
  this._reconnectTimer = null;
45973
46219
  }
46220
+ if (this._telemetryReporter) {
46221
+ this._telemetryReporter.stopAutoFlush();
46222
+ await this._telemetryReporter.flush();
46223
+ this._telemetryReporter = null;
46224
+ }
45974
46225
  if (this._ws) {
45975
46226
  this._ws.removeAllListeners();
45976
46227
  this._ws.close();
@@ -46494,26 +46745,20 @@ var init_channel = __esm({
46494
46745
  this._scanEngine = new ScanEngine();
46495
46746
  await this._fetchScanRules();
46496
46747
  }
46748
+ if (!this._telemetryReporter && this._persisted?.deviceJwt && this._persisted?.hubAddress) {
46749
+ this._telemetryReporter = new TelemetryReporter({
46750
+ apiBase: this.config.apiUrl,
46751
+ hubId: this._persisted.hubAddress,
46752
+ authHeader: `Bearer ${this._persisted.deviceJwt}`
46753
+ });
46754
+ this._telemetryReporter.startAutoFlush(3e4);
46755
+ }
46497
46756
  this.emit("ready");
46498
46757
  } catch (openErr) {
46499
46758
  console.error("[SecureChannel] Error in WS open handler:", openErr);
46500
46759
  this.emit("error", openErr);
46501
46760
  }
46502
46761
  });
46503
- const _onUnhandledRejection = (reason) => {
46504
- console.error("[SecureChannel] UNHANDLED REJECTION (would crash process):", reason);
46505
- };
46506
- const _onUncaughtException = (err) => {
46507
- console.error("[SecureChannel] UNCAUGHT EXCEPTION (would crash process):", err);
46508
- };
46509
- process.on("unhandledRejection", _onUnhandledRejection);
46510
- process.on("uncaughtException", _onUncaughtException);
46511
- ws.on("close", (code, reason) => {
46512
- const reasonStr = reason?.toString() || "";
46513
- console.log(`[SecureChannel] WS CLOSED: code=${code} reason=${JSON.stringify(reasonStr)}`);
46514
- process.removeListener("unhandledRejection", _onUnhandledRejection);
46515
- process.removeListener("uncaughtException", _onUncaughtException);
46516
- });
46517
46762
  ws.on("message", async (raw) => {
46518
46763
  this._lastServerMessage = Date.now();
46519
46764
  this._lastWakeTick = Date.now();
@@ -46533,10 +46778,8 @@ var init_channel = __esm({
46533
46778
  return;
46534
46779
  }
46535
46780
  if (data.event === "message") {
46536
- console.log(`[SecureChannel] \u2190 Direct message received: msg=${data.data?.message_id?.slice(0, 8) ?? "?"} conv=${data.data?.conversation_id?.slice(0, 8) ?? "?"}`);
46537
46781
  try {
46538
46782
  await this._handleIncomingMessage(data.data);
46539
- console.log(`[SecureChannel] \u2190 Direct message processed OK: msg=${data.data?.message_id?.slice(0, 8) ?? "?"}`);
46540
46783
  } catch (msgErr) {
46541
46784
  console.error(
46542
46785
  `[SecureChannel] Message handler failed for conv ${data.data?.conversation_id?.slice(0, 8) ?? "?"}...:`,
@@ -46868,9 +47111,7 @@ var init_channel = __esm({
46868
47111
  this.emit("error", err);
46869
47112
  }
46870
47113
  });
46871
- ws.on("close", (code, reason) => {
46872
- const reasonStr = reason?.toString() || "";
46873
- console.log(`[SecureChannel] WS close handler: code=${code} reason=${JSON.stringify(reasonStr)}`);
47114
+ ws.on("close", () => {
46874
47115
  this._stopPing();
46875
47116
  this._stopWakeDetector();
46876
47117
  this._stopPendingPoll();
@@ -46892,11 +47133,6 @@ var init_channel = __esm({
46892
47133
  if (this._syncMessageIds?.has(msgData.message_id)) {
46893
47134
  return;
46894
47135
  }
46895
- this._recentlyHandledIds.add(msgData.message_id);
46896
- if (this._recentlyHandledIds.size > 500) {
46897
- const all = [...this._recentlyHandledIds];
46898
- this._recentlyHandledIds = new Set(all.slice(all.length - 400));
46899
- }
46900
47136
  const convId = msgData.conversation_id;
46901
47137
  const session = this._sessions.get(convId);
46902
47138
  if (!session) {
@@ -46909,14 +47145,7 @@ var init_channel = __esm({
46909
47145
  header_blob: msgData.header_blob,
46910
47146
  ciphertext: msgData.ciphertext
46911
47147
  });
46912
- let plaintext;
46913
- try {
46914
- plaintext = session.ratchet.decrypt(encrypted);
46915
- } catch (decryptErr) {
46916
- console.error(`[SecureChannel] Direct message decrypt FAILED for conv ${convId.slice(0, 8)}...: ${String(decryptErr)}`);
46917
- throw decryptErr;
46918
- }
46919
- console.log(`[SecureChannel] Direct message decrypted OK for conv ${convId.slice(0, 8)}...`);
47148
+ const plaintext = session.ratchet.decrypt(encrypted);
46920
47149
  this._sendAck(msgData.message_id);
46921
47150
  if (!session.activated) {
46922
47151
  session.activated = true;
@@ -46951,6 +47180,7 @@ var init_channel = __esm({
46951
47180
  }
46952
47181
  if (messageType === "message") {
46953
47182
  const topicId = msgData.topic_id;
47183
+ this._lastIncomingTopicId = topicId;
46954
47184
  let attachData;
46955
47185
  if (attachmentInfo) {
46956
47186
  try {
@@ -47009,13 +47239,7 @@ ${messageText}`;
47009
47239
  Promise.resolve(this.config.onMessage?.(emitText, metadata)).catch((err) => {
47010
47240
  console.error("[SecureChannel] onMessage callback error:", err);
47011
47241
  });
47012
- console.log(`[SecureChannel] Relaying sync to ${this._sessions.size - 1} siblings for conv ${convId.slice(0, 8)}...`);
47013
- try {
47014
- await this._relaySyncToSiblings(convId, session.ownerDeviceId, messageText, topicId);
47015
- console.log(`[SecureChannel] Sync relay complete for conv ${convId.slice(0, 8)}...`);
47016
- } catch (relayErr) {
47017
- console.error(`[SecureChannel] Sync relay FAILED: ${String(relayErr)}`);
47018
- }
47242
+ await this._relaySyncToSiblings(convId, session.ownerDeviceId, messageText, topicId);
47019
47243
  }
47020
47244
  if (this._persisted) {
47021
47245
  this._persisted.lastMessageTimestamp = msgData.created_at;
@@ -47140,29 +47364,22 @@ ${messageText}`;
47140
47364
  ts: (/* @__PURE__ */ new Date()).toISOString(),
47141
47365
  topicId
47142
47366
  });
47143
- let relayed = 0;
47144
47367
  for (const [siblingConvId, siblingSession] of this._sessions) {
47145
47368
  if (siblingConvId === sourceConvId) continue;
47146
47369
  if (!siblingSession.activated) continue;
47147
- try {
47148
- const syncEncrypted = siblingSession.ratchet.encrypt(syncPayload);
47149
- const syncTransport = encryptedMessageToTransport(syncEncrypted);
47150
- this._ws.send(
47151
- JSON.stringify({
47152
- event: "message",
47153
- data: {
47154
- conversation_id: siblingConvId,
47155
- header_blob: syncTransport.header_blob,
47156
- ciphertext: syncTransport.ciphertext
47157
- }
47158
- })
47159
- );
47160
- relayed++;
47161
- } catch (err) {
47162
- console.error(`[SecureChannel] Sync send failed for sibling ${siblingConvId.slice(0, 8)}...: ${String(err)}`);
47163
- }
47370
+ const syncEncrypted = siblingSession.ratchet.encrypt(syncPayload);
47371
+ const syncTransport = encryptedMessageToTransport(syncEncrypted);
47372
+ this._ws.send(
47373
+ JSON.stringify({
47374
+ event: "message",
47375
+ data: {
47376
+ conversation_id: siblingConvId,
47377
+ header_blob: syncTransport.header_blob,
47378
+ ciphertext: syncTransport.ciphertext
47379
+ }
47380
+ })
47381
+ );
47164
47382
  }
47165
- console.log(`[SecureChannel] _relaySyncToSiblings: relayed to ${relayed}/${this._sessions.size - 1} siblings`);
47166
47383
  }
47167
47384
  /**
47168
47385
  * Send stored message history to a newly-activated session.
@@ -47264,12 +47481,52 @@ ${messageText}`;
47264
47481
  );
47265
47482
  return;
47266
47483
  }
47267
- const session = this._sessions.get(convId);
47484
+ let session = this._sessions.get(convId);
47268
47485
  if (!session) {
47269
47486
  console.warn(
47270
- `[SecureChannel] No session for room conv ${convId.slice(0, 8)}..., skipping`
47487
+ `[SecureChannel] No session for room conv ${convId.slice(0, 8)}..., fetching room data`
47271
47488
  );
47272
- return;
47489
+ try {
47490
+ const roomRes = await fetch(
47491
+ `${this.config.apiUrl}/api/v1/rooms/${msgData.room_id}`,
47492
+ {
47493
+ headers: {
47494
+ Authorization: `Bearer ${this._persisted.deviceJwt}`
47495
+ }
47496
+ }
47497
+ );
47498
+ if (roomRes.ok) {
47499
+ const roomData = await roomRes.json();
47500
+ await this.joinRoom({
47501
+ roomId: roomData.id,
47502
+ name: roomData.name,
47503
+ members: (roomData.members || []).map((m2) => ({
47504
+ deviceId: m2.device_id,
47505
+ entityType: m2.entity_type,
47506
+ displayName: m2.display_name,
47507
+ identityPublicKey: m2.identity_public_key,
47508
+ ephemeralPublicKey: m2.ephemeral_public_key
47509
+ })),
47510
+ conversations: (roomData.conversations || []).map((c2) => ({
47511
+ id: c2.id,
47512
+ participantA: c2.participant_a,
47513
+ participantB: c2.participant_b
47514
+ }))
47515
+ });
47516
+ session = this._sessions.get(convId);
47517
+ }
47518
+ } catch (fetchErr) {
47519
+ console.error(
47520
+ `[SecureChannel] Failed to fetch room data for ${msgData.room_id}:`,
47521
+ fetchErr
47522
+ );
47523
+ }
47524
+ if (!session) {
47525
+ console.warn(
47526
+ `[SecureChannel] Still no session for room conv ${convId.slice(0, 8)}... after refresh, skipping`
47527
+ );
47528
+ return;
47529
+ }
47273
47530
  }
47274
47531
  const encrypted = transportToEncryptedMessage({
47275
47532
  header_blob: msgData.header_blob,
@@ -47399,7 +47656,6 @@ ${messageText}`;
47399
47656
  const PAGE_SIZE = 200;
47400
47657
  let since = this._persisted.lastMessageTimestamp;
47401
47658
  let totalProcessed = 0;
47402
- let totalSkipped = 0;
47403
47659
  try {
47404
47660
  for (let page = 0; page < MAX_PAGES; page++) {
47405
47661
  const url = `${this.config.apiUrl}/api/v1/devices/${this._deviceId}/messages?since=${encodeURIComponent(since)}&limit=${PAGE_SIZE}`;
@@ -47409,24 +47665,15 @@ ${messageText}`;
47409
47665
  if (!res.ok) break;
47410
47666
  const messages = await res.json();
47411
47667
  if (messages.length === 0) break;
47412
- console.log(`[SecureChannel] Sync page ${page}: ${messages.length} messages since ${since}`);
47413
47668
  for (const msg of messages) {
47414
47669
  if (msg.sender_device_id === this._deviceId) continue;
47415
47670
  if (this._syncMessageIds.has(msg.id)) continue;
47416
47671
  this._syncMessageIds.add(msg.id);
47417
- if (this._recentlyHandledIds.has(msg.id)) {
47418
- this._persisted.lastMessageTimestamp = msg.created_at;
47419
- since = msg.created_at;
47420
- totalSkipped++;
47421
- continue;
47422
- }
47423
47672
  const session = this._sessions.get(msg.conversation_id);
47424
47673
  if (!session) {
47425
47674
  console.warn(
47426
47675
  `[SecureChannel] No session for conversation ${msg.conversation_id} during sync, skipping`
47427
47676
  );
47428
- this._persisted.lastMessageTimestamp = msg.created_at;
47429
- since = msg.created_at;
47430
47677
  continue;
47431
47678
  }
47432
47679
  try {
@@ -47460,6 +47707,9 @@ ${messageText}`;
47460
47707
  topicId
47461
47708
  };
47462
47709
  this.emit("message", messageText, metadata);
47710
+ Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
47711
+ console.error("[SecureChannel] onMessage callback error:", err);
47712
+ });
47463
47713
  }
47464
47714
  this._persisted.lastMessageTimestamp = msg.created_at;
47465
47715
  since = msg.created_at;
@@ -47476,15 +47726,10 @@ ${messageText}`;
47476
47726
  await this._persistState();
47477
47727
  if (messages.length < PAGE_SIZE) break;
47478
47728
  }
47479
- if (totalProcessed > 0 || totalSkipped > 0) {
47480
- console.log(`[SecureChannel] Sync complete: ${totalProcessed} processed, ${totalSkipped} skipped (already handled via WS)`);
47481
- }
47482
- } catch (outerErr) {
47483
- console.warn(`[SecureChannel] Sync interrupted: ${String(outerErr)}`);
47484
- try {
47485
- await this._persistState();
47486
- } catch {
47729
+ if (totalProcessed > 0) {
47730
+ console.log(`[SecureChannel] Synced ${totalProcessed} missed messages`);
47487
47731
  }
47732
+ } catch {
47488
47733
  }
47489
47734
  this._syncMessageIds = null;
47490
47735
  }