negotium 0.4.8 → 0.4.9

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/main.js CHANGED
@@ -1868,7 +1868,7 @@ var exports_version = {};
1868
1868
  __export(exports_version, {
1869
1869
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
1870
1870
  });
1871
- var NEGOTIUM_VERSION = "0.4.8";
1871
+ var NEGOTIUM_VERSION = "0.4.9";
1872
1872
 
1873
1873
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
1874
1874
  import { spawn } from "child_process";
@@ -6962,6 +6962,10 @@ function latestRuntimeEventSeq() {
6962
6962
  const row = db.query("SELECT MAX(seq) AS seq FROM runtime_events").get();
6963
6963
  return Number(row?.seq ?? 0);
6964
6964
  }
6965
+ function earliestRuntimeEventSeq() {
6966
+ const row = db.query("SELECT MIN(seq) AS seq FROM runtime_events").get();
6967
+ return Number(row?.seq ?? 0);
6968
+ }
6965
6969
  function appendRuntimeEvent(sourceId, event) {
6966
6970
  const createdAt = new Date().toISOString();
6967
6971
  const result = db.query(`INSERT INTO runtime_events
@@ -6978,6 +6982,30 @@ function listRuntimeEventsAfter(seq, limit = 500) {
6978
6982
  LIMIT ?`).all(Math.max(0, seq), Math.max(1, Math.min(limit, 5000)));
6979
6983
  return rows.map(rowToEvent).filter((event) => event !== null);
6980
6984
  }
6985
+ function heartbeatRuntimeEventConsumer(consumerId, cursor, now = Date.now()) {
6986
+ db.query(`INSERT INTO runtime_event_consumers (consumer_id, cursor, heartbeat_at)
6987
+ VALUES (?, ?, ?)
6988
+ ON CONFLICT(consumer_id) DO UPDATE SET
6989
+ cursor = MAX(runtime_event_consumers.cursor, excluded.cursor),
6990
+ heartbeat_at = excluded.heartbeat_at`).run(consumerId, Math.max(0, cursor), now);
6991
+ }
6992
+ function pruneRuntimeEvents(options = {}) {
6993
+ const maxEvents = Math.max(1, Math.floor(options.maxEvents ?? 1e5));
6994
+ const now = options.now ?? Date.now();
6995
+ const activeAfter = now - Math.max(1000, options.activeConsumerMaxAgeMs ?? 5 * 60000);
6996
+ db.query("DELETE FROM runtime_event_consumers WHERE heartbeat_at < ?").run(activeAfter);
6997
+ const boundary = db.query("SELECT seq FROM runtime_events ORDER BY seq DESC LIMIT 1 OFFSET ?").get(maxEvents - 1);
6998
+ if (!boundary)
6999
+ return { deleted: 0, cutoff: 0, retainedForConsumer: null };
7000
+ const targetCutoff = Math.max(0, Number(boundary.seq) - 1);
7001
+ const consumer = db.query("SELECT MIN(cursor) AS cursor FROM runtime_event_consumers").get();
7002
+ const retainedForConsumer = consumer?.cursor == null ? null : Number(consumer.cursor);
7003
+ const cutoff = Math.min(targetCutoff, retainedForConsumer ?? targetCutoff);
7004
+ if (cutoff <= 0)
7005
+ return { deleted: 0, cutoff, retainedForConsumer };
7006
+ const result = db.query("DELETE FROM runtime_events WHERE seq <= ?").run(cutoff);
7007
+ return { deleted: Number(result.changes), cutoff, retainedForConsumer };
7008
+ }
6981
7009
  function listRecentRuntimeEventsForTopic(topicId, limit = 300) {
6982
7010
  const rows = db.query(`SELECT seq, source_id, event_type, topic_id, payload_json, created_at
6983
7011
  FROM (
@@ -7021,6 +7049,13 @@ var init_runtime_events = __esm(async () => {
7021
7049
  payload_json TEXT NOT NULL,
7022
7050
  created_at TEXT NOT NULL
7023
7051
  )
7052
+ `);
7053
+ database.exec(`
7054
+ CREATE TABLE IF NOT EXISTS runtime_event_consumers (
7055
+ consumer_id TEXT PRIMARY KEY,
7056
+ cursor INTEGER NOT NULL,
7057
+ heartbeat_at INTEGER NOT NULL
7058
+ )
7024
7059
  `);
7025
7060
  database.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
7026
7061
  });
@@ -23001,6 +23036,7 @@ __export(exports_src, {
23001
23036
  reconcilePendingAskUserQuestionGates: () => reconcilePendingAskUserQuestionGates,
23002
23037
  reapOrphanBrowsers: () => reapPlaywrightOrphans,
23003
23038
  purgeTopicLogs: () => purgeTopicLogs,
23039
+ pruneRuntimeEvents: () => pruneRuntimeEvents,
23004
23040
  prepareDeliveryAck: () => prepareDeliveryAck,
23005
23041
  onShutdown: () => onShutdown,
23006
23042
  normalizeVaultKey: () => normalizeVaultKey,
@@ -23040,6 +23076,7 @@ __export(exports_src, {
23040
23076
  isAgentKind: () => isAgentKind,
23041
23077
  interSessionQueue: () => interSessionQueue,
23042
23078
  ingestAttachment: () => ingestAttachment,
23079
+ heartbeatRuntimeEventConsumer: () => heartbeatRuntimeEventConsumer,
23043
23080
  grantSubagentTellTarget: () => grantSubagentTellTarget,
23044
23081
  getVisibleTopics: () => getVisibleTopics,
23045
23082
  getTopics: () => getTopics,
@@ -23074,6 +23111,7 @@ __export(exports_src, {
23074
23111
  errMsg: () => errMsg,
23075
23112
  ensurePersonalGeneral: () => ensurePersonalGeneral,
23076
23113
  ensureBgBash: () => ensureBgBash,
23114
+ earliestRuntimeEventSeq: () => earliestRuntimeEventSeq,
23077
23115
  dispatchPeerRuntimeVisual: () => dispatchPeerRuntimeVisual,
23078
23116
  dispatchPeerRuntimeSpawn: () => dispatchPeerRuntimeSpawn,
23079
23117
  dispatchPeerRuntimeSelfConfig: () => dispatchPeerRuntimeSelfConfig,
@@ -23572,6 +23610,7 @@ __export(exports_node_host, {
23572
23610
  reconcilePendingAskUserQuestionGates: () => reconcilePendingAskUserQuestionGates,
23573
23611
  reapOrphanBrowsers: () => reapPlaywrightOrphans,
23574
23612
  readDecisions: () => readDecisions,
23613
+ pruneRuntimeEvents: () => pruneRuntimeEvents,
23575
23614
  onShutdown: () => onShutdown,
23576
23615
  nodeRequestHandlerNames: () => nodeRequestHandlerNames,
23577
23616
  logger: () => logger,
@@ -23595,6 +23634,7 @@ __export(exports_node_host, {
23595
23634
  flushBashrsCompletions: () => flushBashrsCompletions,
23596
23635
  executeVaultCommand: () => executeVaultCommand,
23597
23636
  ensurePersonalGeneral: () => ensurePersonalGeneral,
23637
+ earliestRuntimeEventSeq: () => earliestRuntimeEventSeq,
23598
23638
  deleteVaultEntry: () => deleteVaultEntry,
23599
23639
  compactTopicSession: () => compactTopicSession,
23600
23640
  appendApiMessage: () => appendApiMessage,
@@ -29840,8 +29880,8 @@ function createPollingSseStream(req, options) {
29840
29880
  } catch {}
29841
29881
  };
29842
29882
  const send = (event, data, id) => {
29843
- if (closed)
29844
- return;
29883
+ if (closed || controller.desiredSize !== null && controller.desiredSize <= 0)
29884
+ return false;
29845
29885
  try {
29846
29886
  const lines = [
29847
29887
  id === undefined ? "" : `id: ${id}`,
@@ -29852,8 +29892,10 @@ function createPollingSseStream(req, options) {
29852
29892
  controller.enqueue(encoder.encode(`${lines}
29853
29893
 
29854
29894
  `));
29895
+ return true;
29855
29896
  } catch (error2) {
29856
29897
  fail(error2);
29898
+ return false;
29857
29899
  }
29858
29900
  };
29859
29901
  const close = () => {
@@ -29896,7 +29938,7 @@ function createPollingSseStream(req, options) {
29896
29938
  return;
29897
29939
  pollTimer = setInterval(pump, options.pollIntervalMs ?? 100);
29898
29940
  heartbeatTimer = setInterval(() => {
29899
- if (closed)
29941
+ if (closed || controller.desiredSize !== null && controller.desiredSize <= 0)
29900
29942
  return;
29901
29943
  try {
29902
29944
  controller.enqueue(encoder.encode(`: heartbeat ${Date.now()}
@@ -29910,7 +29952,7 @@ function createPollingSseStream(req, options) {
29910
29952
  cancel() {
29911
29953
  stop();
29912
29954
  }
29913
- });
29955
+ }, { highWaterMark: 64 });
29914
29956
  return new Response(stream, {
29915
29957
  headers: {
29916
29958
  "cache-control": "no-cache, no-transform",
@@ -30018,7 +30060,6 @@ function runtimeEvent(event) {
30018
30060
  }
30019
30061
  function createRuntimeContractEventStream(req, after, topicId, scope = {}) {
30020
30062
  const scoped = "surfaceScope" in scope;
30021
- const wanted = scope.surfaceScope ?? null;
30022
30063
  const visible = new Map;
30023
30064
  const eventInScope = (eventTopicId) => {
30024
30065
  if (!scoped)
@@ -30026,26 +30067,39 @@ function createRuntimeContractEventStream(req, after, topicId, scope = {}) {
30026
30067
  const cached = visible.get(eventTopicId);
30027
30068
  if (cached !== undefined)
30028
30069
  return cached;
30029
- const roomScope = getTopic(eventTopicId)?.surfaceScope ?? null;
30030
- const allowed = roomScope === null || roomScope === wanted;
30070
+ const topic = getTopic(eventTopicId);
30071
+ const allowed = Boolean(topic && topicInRequestScope(req, topic));
30031
30072
  visible.set(eventTopicId, allowed);
30032
30073
  return allowed;
30033
30074
  };
30034
30075
  let cursor = Math.max(0, after);
30076
+ let reportedCursor = cursor;
30077
+ const oldestCursor = earliestRuntimeEventSeq();
30035
30078
  return createPollingSseStream(req, {
30036
- ready: { v: NODE_RUNTIME_CONTRACT_VERSION, cursor },
30079
+ ready: {
30080
+ v: NODE_RUNTIME_CONTRACT_VERSION,
30081
+ cursor,
30082
+ oldestCursor,
30083
+ truncated: oldestCursor > 0 && cursor < oldestCursor - 1
30084
+ },
30037
30085
  pump(send) {
30038
- while (true) {
30086
+ for (let batch = 0;batch < 5; batch += 1) {
30039
30087
  const events = listRuntimeEventsAfter(cursor, 500);
30040
- if (events.length === 0)
30088
+ if (events.length === 0) {
30089
+ if (reportedCursor < cursor && send("cursor", { cursor }, cursor))
30090
+ reportedCursor = cursor;
30041
30091
  break;
30092
+ }
30042
30093
  for (const event of events) {
30043
- cursor = event.seq;
30044
30094
  if ((!topicId || event.topicId === topicId) && eventInScope(event.topicId)) {
30045
- send("runtime", runtimeEvent(event), event.seq);
30095
+ if (!send("runtime", runtimeEvent(event), event.seq))
30096
+ return;
30046
30097
  }
30098
+ cursor = event.seq;
30047
30099
  }
30048
- send("cursor", { cursor }, cursor);
30100
+ if (!send("cursor", { cursor }, cursor))
30101
+ return;
30102
+ reportedCursor = cursor;
30049
30103
  if (events.length < 500)
30050
30104
  break;
30051
30105
  }
@@ -30055,15 +30109,24 @@ function createRuntimeContractEventStream(req, after, topicId, scope = {}) {
30055
30109
  function createEventStream(req, userId, after, surface) {
30056
30110
  const allowedTopics = new Set(topicsForUser(userId, surface).map((topic) => topic.id));
30057
30111
  let cursor = Math.max(0, after);
30112
+ let reportedCursor = cursor;
30113
+ const oldestCursor = earliestRuntimeEventSeq();
30058
30114
  return createPollingSseStream(req, {
30059
- ready: { protocolVersion: NODE_CONTROL_PROTOCOL_VERSION, cursor },
30115
+ ready: {
30116
+ protocolVersion: NODE_CONTROL_PROTOCOL_VERSION,
30117
+ cursor,
30118
+ oldestCursor,
30119
+ truncated: oldestCursor > 0 && cursor < oldestCursor - 1
30120
+ },
30060
30121
  pump(send) {
30061
- while (true) {
30122
+ for (let batch = 0;batch < 5; batch += 1) {
30062
30123
  const events = listRuntimeEventsAfter(cursor, 500);
30063
- if (events.length === 0)
30124
+ if (events.length === 0) {
30125
+ if (reportedCursor < cursor && send("cursor", { cursor }, cursor))
30126
+ reportedCursor = cursor;
30064
30127
  break;
30128
+ }
30065
30129
  for (const event of events) {
30066
- cursor = event.seq;
30067
30130
  if (event.type === "topic-created" || event.type === "topic-updated") {
30068
30131
  const topic = getTopic(event.topicId);
30069
30132
  const admissible = topic && isParticipant(topic, userId) && (!surface || topic.surface === surface);
@@ -30073,12 +30136,15 @@ function createEventStream(req, userId, after, surface) {
30073
30136
  allowedTopics.delete(event.topicId);
30074
30137
  }
30075
30138
  const visible = allowedTopics.has(event.topicId);
30076
- if (visible)
30077
- send("runtime", runtimeEvent(event), event.seq);
30139
+ if (visible && !send("runtime", runtimeEvent(event), event.seq))
30140
+ return;
30141
+ cursor = event.seq;
30078
30142
  if (event.type === "topic-deleted")
30079
30143
  allowedTopics.delete(event.topicId);
30080
30144
  }
30081
- send("cursor", { cursor }, cursor);
30145
+ if (!send("cursor", { cursor }, cursor))
30146
+ return;
30147
+ reportedCursor = cursor;
30082
30148
  if (events.length < 500)
30083
30149
  break;
30084
30150
  }
@@ -32830,6 +32896,19 @@ function startNode(opts = {}) {
32830
32896
  setRuntimeMcpPort(port);
32831
32897
  const stopTurnRequests = startDurableTurnRequestWorker();
32832
32898
  const stopInbox = startSessionInboxWorker();
32899
+ const pruneEventLog = () => {
32900
+ try {
32901
+ const result = pruneRuntimeEvents();
32902
+ if (result.deleted > 0) {
32903
+ logger.info(result, "runtime event log pruned");
32904
+ }
32905
+ } catch (err2) {
32906
+ logger.warn({ err: err2 }, "runtime event log prune failed");
32907
+ }
32908
+ };
32909
+ pruneEventLog();
32910
+ const eventPruneTimer = setInterval(pruneEventLog, 5 * 60000);
32911
+ eventPruneTimer.unref?.();
32833
32912
  const stopBashrsCompletions = startBashrsCompletionsWorker();
32834
32913
  let modules;
32835
32914
  try {
@@ -32844,6 +32923,7 @@ function startNode(opts = {}) {
32844
32923
  } catch (error2) {
32845
32924
  stopTurnRequests();
32846
32925
  stopInbox();
32926
+ clearInterval(eventPruneTimer);
32847
32927
  stopBashrsCompletions();
32848
32928
  server.stop(true);
32849
32929
  processLease?.stop();
@@ -32870,6 +32950,7 @@ function startNode(opts = {}) {
32870
32950
  });
32871
32951
  stopTurnRequests();
32872
32952
  stopInbox();
32953
+ clearInterval(eventPruneTimer);
32873
32954
  stopBashrsCompletions();
32874
32955
  server.stop(true);
32875
32956
  processLease?.stop();
@@ -32882,6 +32963,7 @@ function startNode(opts = {}) {
32882
32963
  onShutdown("node-server", 130, () => {
32883
32964
  stopTurnRequests();
32884
32965
  stopInbox();
32966
+ clearInterval(eventPruneTimer);
32885
32967
  stopBashrsCompletions();
32886
32968
  server.stop(true);
32887
32969
  });
@@ -40800,6 +40882,11 @@ function createTables(db4) {
40800
40882
  key TEXT PRIMARY KEY,
40801
40883
  value TEXT NOT NULL
40802
40884
  )`);
40885
+ db4.run(`CREATE TABLE IF NOT EXISTS runtime_inbox (
40886
+ seq INTEGER PRIMARY KEY,
40887
+ runtime_message_id TEXT,
40888
+ event_json TEXT NOT NULL
40889
+ )`);
40803
40890
  }
40804
40891
  function migrateOutboxSchema(db4) {
40805
40892
  try {
@@ -40810,6 +40897,13 @@ function migrateOutboxSchema(db4) {
40810
40897
  } catch {}
40811
40898
  db4.run("CREATE INDEX IF NOT EXISTS idx_telegram_outbox_runtime_message ON outbox(runtime_message_id)");
40812
40899
  }
40900
+ function migrateRuntimeInboxSchema(db4) {
40901
+ try {
40902
+ db4.run("ALTER TABLE runtime_inbox ADD COLUMN runtime_message_id TEXT");
40903
+ } catch {}
40904
+ db4.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_telegram_runtime_inbox_message
40905
+ ON runtime_inbox(runtime_message_id) WHERE runtime_message_id IS NOT NULL`);
40906
+ }
40813
40907
  function openMappingStore(path) {
40814
40908
  const dbPath = path ?? join45(DATA_DIR, "adapter-telegram.db");
40815
40909
  if (dbPath !== ":memory:")
@@ -40818,6 +40912,7 @@ function openMappingStore(path) {
40818
40912
  migrateLegacySchema(db4);
40819
40913
  createTables(db4);
40820
40914
  migrateOutboxSchema(db4);
40915
+ migrateRuntimeInboxSchema(db4);
40821
40916
  return {
40822
40917
  load() {
40823
40918
  const rows = db4.query("SELECT chat_id, thread_id, topic_id FROM mappings").all();
@@ -40891,6 +40986,40 @@ function openMappingStore(path) {
40891
40986
  entry.lastError ?? null
40892
40987
  ]);
40893
40988
  },
40989
+ runtimeEventCursor() {
40990
+ const row = db4.query("SELECT value FROM settings WHERE key = 'runtime_event_cursor'").get();
40991
+ if (!row)
40992
+ return;
40993
+ const value = Number.parseInt(row.value, 10);
40994
+ return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
40995
+ },
40996
+ captureRuntimeEvent(event) {
40997
+ const messageId = event.payload && typeof event.payload === "object" && "id" in event.payload && typeof event.payload.id === "string" ? event.payload.id : null;
40998
+ db4.transaction(() => {
40999
+ db4.run(`INSERT OR IGNORE INTO runtime_inbox (seq, runtime_message_id, event_json)
41000
+ VALUES (?, ?, ?)`, [event.seq, messageId, JSON.stringify(event)]);
41001
+ db4.run(`INSERT INTO settings (key, value) VALUES ('runtime_event_cursor', ?)
41002
+ ON CONFLICT(key) DO UPDATE SET value = MAX(CAST(value AS INTEGER), CAST(excluded.value AS INTEGER))`, [String(event.seq)]);
41003
+ })();
41004
+ },
41005
+ advanceRuntimeEventCursor(seq) {
41006
+ db4.run(`INSERT INTO settings (key, value) VALUES ('runtime_event_cursor', ?)
41007
+ ON CONFLICT(key) DO UPDATE SET value = MAX(CAST(value AS INTEGER), CAST(excluded.value AS INTEGER))`, [String(seq)]);
41008
+ },
41009
+ pendingRuntimeEvents() {
41010
+ const rows = db4.query("SELECT seq, event_json FROM runtime_inbox ORDER BY seq ASC").all();
41011
+ return rows.flatMap((row) => {
41012
+ try {
41013
+ return [{ seq: row.seq, event: JSON.parse(row.event_json) }];
41014
+ } catch {
41015
+ db4.run("DELETE FROM runtime_inbox WHERE seq = ?", [row.seq]);
41016
+ return [];
41017
+ }
41018
+ });
41019
+ },
41020
+ acknowledgeRuntimeEvent(seq) {
41021
+ db4.run("DELETE FROM runtime_inbox WHERE seq = ?", [seq]);
41022
+ },
40894
41023
  outboxDue(now) {
40895
41024
  const rows = db4.query("SELECT * FROM outbox WHERE dead = 0 AND next_try_at <= ? ORDER BY id ASC").all(now);
40896
41025
  return rows.map(outboxRowToEntry);
@@ -41768,15 +41897,29 @@ function startTelegramAdapter(opts) {
41768
41897
  }
41769
41898
  const sendQueues = new Map;
41770
41899
  function enqueueSend(topicId, task) {
41771
- const prev = sendQueues.get(topicId) ?? Promise.resolve();
41900
+ const prev = sendQueues.get(topicId) ?? Promise.resolve(true);
41772
41901
  const next = prev.then(() => new Promise((resolve24) => {
41902
+ let settled = false;
41773
41903
  const timer = setTimeout(() => {
41904
+ if (settled)
41905
+ return;
41906
+ settled = true;
41774
41907
  logger.warn({ topicId, sendTimeoutMs }, "telegram adapter: send timed out \u2014 abandoning it and continuing the queue");
41775
- resolve24();
41908
+ resolve24(false);
41776
41909
  }, sendTimeoutMs);
41777
- task().catch((err2) => logger.warn({ err: err2, topicId }, "telegram adapter: send task failed")).finally(() => {
41910
+ task().then(() => {
41911
+ if (settled)
41912
+ return;
41913
+ settled = true;
41914
+ clearTimeout(timer);
41915
+ resolve24(true);
41916
+ }).catch((err2) => {
41917
+ logger.warn({ err: err2, topicId }, "telegram adapter: send task failed");
41918
+ if (settled)
41919
+ return;
41920
+ settled = true;
41778
41921
  clearTimeout(timer);
41779
- resolve24();
41922
+ resolve24(false);
41780
41923
  });
41781
41924
  }));
41782
41925
  sendQueues.set(topicId, next);
@@ -41784,13 +41927,14 @@ function startTelegramAdapter(opts) {
41784
41927
  if (sendQueues.get(topicId) === next)
41785
41928
  sendQueues.delete(topicId);
41786
41929
  });
41930
+ return next;
41787
41931
  }
41788
41932
  function enqueueFanout(topicId, mappings, payload) {
41789
41933
  const targets = [...mappings];
41790
- enqueueSend(topicId, () => deliverToTargets(topicId, targets, payload));
41934
+ enqueueSend(topicId, () => deliverToTargets(topicId, targets, payload)).then(payload.onSettled);
41791
41935
  }
41792
41936
  function enqueueTarget(topicId, target, payload) {
41793
- enqueueSend(topicId, () => deliverToTargets(topicId, [target], payload));
41937
+ enqueueSend(topicId, () => deliverToTargets(topicId, [target], payload)).then(payload.onSettled);
41794
41938
  }
41795
41939
  const pendingByTopic = new Map;
41796
41940
  const permissionBlockedTopics = new Map;
@@ -41808,8 +41952,9 @@ function startTelegramAdapter(opts) {
41808
41952
  });
41809
41953
  }
41810
41954
  function flushBuffered(topicId, buffer, send) {
41811
- for (const payload of buffer)
41812
- enqueueSend(topicId, () => send(payload));
41955
+ for (const payload of buffer) {
41956
+ enqueueSend(topicId, () => send(payload)).then(payload.onSettled);
41957
+ }
41813
41958
  }
41814
41959
  function materializeTopic(topic) {
41815
41960
  if (!forumManageTopicsAvailable || suppressMaterialize > 0 || byTopic.has(topic.id) || pendingByTopic.has(topic.id) || permissionBlockedTopics.has(topic.id) || materializeTombstones.has(topic.id)) {
@@ -41871,8 +42016,12 @@ function startTelegramAdapter(opts) {
41871
42016
  if (materializeTombstones.delete(topicId))
41872
42017
  store.deleteTombstone(topicId);
41873
42018
  const pending2 = pendingByTopic.get(topicId);
41874
- if (pending2)
42019
+ if (pending2) {
41875
42020
  pending2.cancelled = true;
42021
+ for (const payload of pending2.buffer)
42022
+ payload.onSettled?.(true);
42023
+ pending2.buffer = [];
42024
+ }
41876
42025
  for (const [messageId, message] of runtimeMessages) {
41877
42026
  if (message.topicId === topicId)
41878
42027
  deleteDeliveredRuntimeMessage(topicId, messageId);
@@ -41919,7 +42068,7 @@ function startTelegramAdapter(opts) {
41919
42068
  }
41920
42069
  const tombstoneTitle = materializeTombstones.get(topicId);
41921
42070
  if (tombstoneTitle !== undefined) {
41922
- enqueueSend(topicId, () => deliverFallback(topicId, tombstoneTitle, payload));
42071
+ enqueueSend(topicId, () => deliverFallback(topicId, tombstoneTitle, payload)).then(payload.onSettled);
41923
42072
  return true;
41924
42073
  }
41925
42074
  const topic = getTopic(topicId);
@@ -41927,7 +42076,7 @@ function startTelegramAdapter(opts) {
41927
42076
  return false;
41928
42077
  if (forumMode) {
41929
42078
  if (!forumManageTopicsAvailable || permissionBlockedTopics.has(topicId)) {
41930
- enqueueSend(topicId, () => deliverFallback(topicId, topic.title, payload));
42079
+ enqueueSend(topicId, () => deliverFallback(topicId, topic.title, payload)).then(payload.onSettled);
41931
42080
  return true;
41932
42081
  }
41933
42082
  if (!materializeTopic(topic))
@@ -42375,6 +42524,113 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42375
42524
  }
42376
42525
  linkForumAndAnnounce(update.from.id, update.chat, update.new_chat_member).catch((err2) => logger.warn({ err: err2, groupId: update.chat.id }, "telegram adapter: forum auto-connect failed"));
42377
42526
  });
42527
+ async function deliverPersistedRuntimeMessage(event) {
42528
+ const msg = event.payload;
42529
+ if (msg.authorId === userId && msg.sourceAdapter === "telegram")
42530
+ return true;
42531
+ if (msg.kind === "tool")
42532
+ return true;
42533
+ const hasAttachments = Boolean(msg.attachments && msg.attachments.length > 0);
42534
+ if (!msg.text && !hasAttachments)
42535
+ return true;
42536
+ const runtimeMessageId = msg.authorId === "ai" ? msg.id : undefined;
42537
+ if (runtimeMessageId) {
42538
+ runtimeMessages.set(runtimeMessageId, msg);
42539
+ if (!msg.queryId) {
42540
+ const timer = setTimeout(() => {
42541
+ runtimeMessages.delete(runtimeMessageId);
42542
+ deliveredByRuntimeMessageId.delete(runtimeMessageId);
42543
+ }, 5 * 60000);
42544
+ timer.unref?.();
42545
+ }
42546
+ }
42547
+ const tagFiles = msg.text ? extractFileTagPaths(msg.text).map((path) => ({ path, filename: basename12(path) })) : [];
42548
+ const attachmentFiles = hasAttachments ? (msg.attachments ?? []).map((attachment) => {
42549
+ const path = resolveUploadedFilePathByFileId(attachment.id);
42550
+ if (!path)
42551
+ return null;
42552
+ return {
42553
+ path,
42554
+ filename: basename12(attachment.filename) || basename12(path),
42555
+ ...attachment.mimeType ? { mimeType: attachment.mimeType } : {}
42556
+ };
42557
+ }).filter((file) => file !== null) : [];
42558
+ const files = [...tagFiles, ...attachmentFiles];
42559
+ const rawText = tagFiles.length > 0 && msg.text ? stripFileTags(msg.text) : msg.text ?? "";
42560
+ const text2 = msg.authorId === userId && rawText ? `[From: User] ${rawText}` : rawText;
42561
+ if (!text2 && files.length === 0)
42562
+ return true;
42563
+ let settle;
42564
+ const settled = new Promise((resolve24) => {
42565
+ settle = resolve24;
42566
+ });
42567
+ const payload = {
42568
+ text: text2,
42569
+ files,
42570
+ runtimeMessageId,
42571
+ deliveryAckRequested: msg.deliveryAckRequested === true,
42572
+ onSettled: settle
42573
+ };
42574
+ const routed = routeMessage(event.topicId, payload, msg.queryId);
42575
+ if (routed && payload.deliveryAckRequested)
42576
+ claimDeliveryAck(event.topicId, msg.id);
42577
+ return routed ? settled : true;
42578
+ }
42579
+ const savedRuntimeEventCursor = store.runtimeEventCursor();
42580
+ let runtimeEventCursor = savedRuntimeEventCursor ?? latestRuntimeEventSeq();
42581
+ if (savedRuntimeEventCursor === undefined) {
42582
+ store.advanceRuntimeEventCursor(runtimeEventCursor);
42583
+ }
42584
+ const runtimeEventConsumerId = `telegram:${userId}:${opts.mappingDbPath ?? "default"}`;
42585
+ heartbeatRuntimeEventConsumer(runtimeEventConsumerId, runtimeEventCursor);
42586
+ let pollingRuntimeEvents = false;
42587
+ let drainingRuntimeInbox = false;
42588
+ const drainRuntimeInbox = async () => {
42589
+ if (drainingRuntimeInbox || stopped)
42590
+ return;
42591
+ drainingRuntimeInbox = true;
42592
+ try {
42593
+ while (!stopped) {
42594
+ const pending2 = store.pendingRuntimeEvents()[0];
42595
+ if (!pending2)
42596
+ break;
42597
+ const delivered = await deliverPersistedRuntimeMessage(pending2.event);
42598
+ if (!delivered || stopped)
42599
+ break;
42600
+ store.acknowledgeRuntimeEvent(pending2.seq);
42601
+ }
42602
+ } catch (err2) {
42603
+ if (!stopped)
42604
+ logger.warn({ err: err2 }, "telegram adapter: durable runtime inbox drain failed");
42605
+ } finally {
42606
+ drainingRuntimeInbox = false;
42607
+ }
42608
+ };
42609
+ const pollRuntimeEvents = () => {
42610
+ if (pollingRuntimeEvents || stopped)
42611
+ return;
42612
+ pollingRuntimeEvents = true;
42613
+ try {
42614
+ for (let batch = 0;batch < 5; batch += 1) {
42615
+ const events = listRuntimeEventsAfter(runtimeEventCursor, 500);
42616
+ if (events.length === 0)
42617
+ break;
42618
+ for (const event of events) {
42619
+ if (event.type === "message")
42620
+ store.captureRuntimeEvent({ ...event, seq: event.seq });
42621
+ else
42622
+ store.advanceRuntimeEventCursor(event.seq);
42623
+ runtimeEventCursor = event.seq;
42624
+ }
42625
+ if (events.length < 500)
42626
+ break;
42627
+ }
42628
+ drainRuntimeInbox();
42629
+ heartbeatRuntimeEventConsumer(runtimeEventConsumerId, runtimeEventCursor);
42630
+ } finally {
42631
+ pollingRuntimeEvents = false;
42632
+ }
42633
+ };
42378
42634
  const unsubscribe = runtimeBus().subscribe((event) => {
42379
42635
  if (stopped)
42380
42636
  return;
@@ -42387,22 +42643,22 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42387
42643
  return;
42388
42644
  }
42389
42645
  if (event.type === "message-updated") {
42390
- const payload2 = event.payload;
42391
- if (!payload2.messageId || !payload2.patch)
42646
+ const payload = event.payload;
42647
+ if (!payload.messageId || !payload.patch)
42392
42648
  return;
42393
- if (payload2.patch.deleted) {
42394
- deleteDeliveredRuntimeMessage(event.topicId, payload2.messageId);
42649
+ if (payload.patch.deleted) {
42650
+ deleteDeliveredRuntimeMessage(event.topicId, payload.messageId);
42395
42651
  return;
42396
42652
  }
42397
- const current3 = runtimeMessages.get(payload2.messageId);
42653
+ const current3 = runtimeMessages.get(payload.messageId);
42398
42654
  if (!current3)
42399
42655
  return;
42400
- const updated = { ...current3, ...payload2.patch };
42401
- runtimeMessages.set(payload2.messageId, updated);
42402
- if (footerEnabled && payload2.patch.usage && updated.authorId === "ai") {
42656
+ const updated = { ...current3, ...payload.patch };
42657
+ runtimeMessages.set(payload.messageId, updated);
42658
+ if (footerEnabled && payload.patch.usage && updated.authorId === "ai") {
42403
42659
  const footer = renderTurnFooter(updated);
42404
42660
  if (footer)
42405
- attachUpdatedFooter(event.topicId, payload2.messageId, footer);
42661
+ attachUpdatedFooter(event.topicId, payload.messageId, footer);
42406
42662
  }
42407
42663
  return;
42408
42664
  }
@@ -42425,53 +42681,10 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42425
42681
  }
42426
42682
  return;
42427
42683
  }
42428
- if (event.type !== "message")
42429
- return;
42430
- const msg = event.payload;
42431
- if (msg.authorId === userId && msg.sourceAdapter === "telegram")
42432
- return;
42433
- if (msg.kind === "tool")
42434
- return;
42435
- const hasAttachments = Boolean(msg.attachments && msg.attachments.length > 0);
42436
- if (!msg.text && !hasAttachments)
42437
- return;
42438
- const runtimeMessageId = msg.authorId === "ai" ? msg.id : undefined;
42439
- if (runtimeMessageId) {
42440
- runtimeMessages.set(runtimeMessageId, msg);
42441
- if (!msg.queryId) {
42442
- const timer = setTimeout(() => {
42443
- runtimeMessages.delete(runtimeMessageId);
42444
- deliveredByRuntimeMessageId.delete(runtimeMessageId);
42445
- }, 5 * 60000);
42446
- timer.unref?.();
42447
- }
42448
- }
42449
- const tagFiles = msg.text ? extractFileTagPaths(msg.text).map((path) => ({ path, filename: basename12(path) })) : [];
42450
- const attachmentFiles = hasAttachments ? (msg.attachments ?? []).map((attachment) => {
42451
- const path = resolveUploadedFilePathByFileId(attachment.id);
42452
- if (!path)
42453
- return null;
42454
- return {
42455
- path,
42456
- filename: basename12(attachment.filename) || basename12(path),
42457
- ...attachment.mimeType ? { mimeType: attachment.mimeType } : {}
42458
- };
42459
- }).filter((file) => file !== null) : [];
42460
- const files = [...tagFiles, ...attachmentFiles];
42461
- const rawText = tagFiles.length > 0 && msg.text ? stripFileTags(msg.text) : msg.text ?? "";
42462
- const text2 = msg.authorId === userId && rawText ? `[From: User] ${rawText}` : rawText;
42463
- if (!text2 && files.length === 0)
42464
- return;
42465
- const payload = {
42466
- text: text2,
42467
- files,
42468
- runtimeMessageId,
42469
- deliveryAckRequested: msg.deliveryAckRequested === true
42470
- };
42471
- if (routeMessage(event.topicId, payload, msg.queryId) && payload.deliveryAckRequested) {
42472
- claimDeliveryAck(event.topicId, msg.id);
42473
- }
42474
42684
  });
42685
+ pollRuntimeEvents();
42686
+ const runtimeEventPollTimer = setInterval(pollRuntimeEvents, 100);
42687
+ runtimeEventPollTimer.unref?.();
42475
42688
  if (forumMode) {
42476
42689
  bindMapping(forumChat, undefined, personalGeneral.id);
42477
42690
  materializeVisibleTopics();
@@ -42491,6 +42704,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42491
42704
  for (const queryId of toolStatusByQueryId.keys())
42492
42705
  closeToolStatus(queryId);
42493
42706
  mediaIntake.stop();
42707
+ clearInterval(runtimeEventPollTimer);
42494
42708
  unsubscribe();
42495
42709
  store.close();
42496
42710
  }
@@ -44802,8 +45016,18 @@ class TunnelClient {
44802
45016
  this.bridgedSockets.clear();
44803
45017
  }
44804
45018
  send(frame) {
44805
- if (this.ws?.readyState === WebSocket.OPEN)
44806
- this.ws.send(encodeFrame(frame));
45019
+ const ws = this.ws;
45020
+ if (!ws || ws.readyState !== WebSocket.OPEN)
45021
+ return;
45022
+ if (Number(ws.bufferedAmount ?? 0) > MAX_TUNNEL_BUFFERED_AMOUNT) {
45023
+ this.disconnectActiveConnection(ws, this.connectionGeneration, { reason: "relay outbound buffer limit exceeded" }, true);
45024
+ return;
45025
+ }
45026
+ try {
45027
+ ws.send(encodeFrame(frame));
45028
+ } catch {
45029
+ this.disconnectActiveConnection(ws, this.connectionGeneration, { reason: "relay send failed" }, true);
45030
+ }
44807
45031
  }
44808
45032
  handleFrame(frame) {
44809
45033
  switch (frame.type) {
@@ -44838,11 +45062,23 @@ class TunnelClient {
44838
45062
  break;
44839
45063
  case "http_req_chunk": {
44840
45064
  const pending2 = this.pendingHttp.get(frame.id);
45065
+ if (!pending2)
45066
+ break;
44841
45067
  try {
44842
- pending2?.bodyController?.enqueue(fromB64(frame.dataB64));
44843
- if (pending2)
44844
- this.startPendingHttp(frame.id, pending2);
44845
- } catch {}
45068
+ if (!pending2.body)
45069
+ throw new Error("body chunk received for bodyless request");
45070
+ const chunk = fromB64(frame.dataB64);
45071
+ pending2.receivedBytes += chunk.byteLength;
45072
+ if (pending2.receivedBytes > MAX_HTTP_REQUEST_BODY_BYTES) {
45073
+ throw new Error("relay HTTP request body limit exceeded");
45074
+ }
45075
+ pending2.bodyController?.enqueue(chunk);
45076
+ this.startPendingHttp(frame.id, pending2);
45077
+ } catch {
45078
+ pending2.abort.abort();
45079
+ this.pendingHttp.delete(frame.id);
45080
+ throw new Error("invalid or oversized relay HTTP request body");
45081
+ }
44846
45082
  break;
44847
45083
  }
44848
45084
  case "http_req_end": {
@@ -44873,6 +45109,12 @@ class TunnelClient {
44873
45109
  if (!bridged)
44874
45110
  break;
44875
45111
  if (!bridged.opened) {
45112
+ bridged.bufferedBytes += frame.text === undefined ? Math.floor((frame.dataB64 ?? "").length * 3 / 4) : Buffer.byteLength(frame.text, "utf8");
45113
+ if (bridged.bufferedBytes > MAX_PREOPEN_WEBSOCKET_BYTES) {
45114
+ this.bridgedSockets.delete(frame.id);
45115
+ bridged.ws.close(1009, "pre-open buffer limit exceeded");
45116
+ throw new Error("bridged WebSocket pre-open buffer limit exceeded");
45117
+ }
44876
45118
  bridged.buffer.push(frame);
44877
45119
  break;
44878
45120
  }
@@ -44892,6 +45134,11 @@ class TunnelClient {
44892
45134
  }
44893
45135
  }
44894
45136
  startHttpRequest(frame) {
45137
+ if (this.pendingHttp.has(frame.id))
45138
+ throw new Error("duplicate relay HTTP request id");
45139
+ if (this.pendingHttp.size >= MAX_IN_FLIGHT_HTTP_REQUESTS) {
45140
+ throw new Error("too many in-flight relay HTTP requests");
45141
+ }
44895
45142
  const abort = new AbortController;
44896
45143
  const headers = new Headers;
44897
45144
  for (const [name, value] of frame.headers)
@@ -44903,7 +45150,8 @@ class TunnelClient {
44903
45150
  method: frame.method,
44904
45151
  path: frame.path,
44905
45152
  headers,
44906
- started: false
45153
+ started: false,
45154
+ receivedBytes: 0
44907
45155
  };
44908
45156
  if (frame.hasBody) {
44909
45157
  pending2.body = new ReadableStream({
@@ -44964,6 +45212,11 @@ class TunnelClient {
44964
45212
  }
44965
45213
  }
44966
45214
  openBridgedSocket(id, path, headers) {
45215
+ if (this.bridgedSockets.has(id))
45216
+ throw new Error("duplicate bridged WebSocket id");
45217
+ if (this.bridgedSockets.size >= MAX_BRIDGED_SOCKETS) {
45218
+ throw new Error("too many bridged WebSockets");
45219
+ }
44967
45220
  const wsOrigin = this.opts.targetOrigin.replace(/^http/, "ws");
44968
45221
  let local;
44969
45222
  try {
@@ -44980,7 +45233,7 @@ class TunnelClient {
44980
45233
  });
44981
45234
  return;
44982
45235
  }
44983
- const bridged = { ws: local, opened: false, buffer: [] };
45236
+ const bridged = { ws: local, opened: false, buffer: [], bufferedBytes: 0 };
44984
45237
  this.bridgedSockets.set(id, bridged);
44985
45238
  local.onopen = () => {
44986
45239
  bridged.opened = true;
@@ -44988,6 +45241,7 @@ class TunnelClient {
44988
45241
  for (const frame of bridged.buffer)
44989
45242
  this.deliverToLocal(local, frame);
44990
45243
  bridged.buffer = [];
45244
+ bridged.bufferedBytes = 0;
44991
45245
  };
44992
45246
  local.onmessage = (event) => {
44993
45247
  if (typeof event.data === "string") {
@@ -45019,11 +45273,14 @@ class TunnelClient {
45019
45273
  local.send(fromB64(frame.dataB64));
45020
45274
  }
45021
45275
  }
45022
- var MAX_WS_UPGRADE_HEADER_BYTES, WS_PROTOCOL_TOKEN, DEFAULT_MIN_RECONNECT_MS = 1000, DEFAULT_MAX_RECONNECT_MS = 30000, RECONNECT_JITTER_RATIO = 0.2, LIVENESS_INTERVALS = 3, noop = () => {}, silentLogger;
45276
+ var MAX_WS_UPGRADE_HEADER_BYTES, WS_PROTOCOL_TOKEN, DEFAULT_MIN_RECONNECT_MS = 1000, DEFAULT_MAX_RECONNECT_MS = 30000, MAX_IN_FLIGHT_HTTP_REQUESTS = 128, MAX_BRIDGED_SOCKETS = 128, MAX_HTTP_REQUEST_BODY_BYTES, MAX_PREOPEN_WEBSOCKET_BYTES, MAX_TUNNEL_BUFFERED_AMOUNT, RECONNECT_JITTER_RATIO = 0.2, LIVENESS_INTERVALS = 3, noop = () => {}, silentLogger;
45023
45277
  var init_tunnel_client = __esm(() => {
45024
45278
  init_relay_protocol();
45025
45279
  MAX_WS_UPGRADE_HEADER_BYTES = 32 * 1024;
45026
45280
  WS_PROTOCOL_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
45281
+ MAX_HTTP_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
45282
+ MAX_PREOPEN_WEBSOCKET_BYTES = 1024 * 1024;
45283
+ MAX_TUNNEL_BUFFERED_AMOUNT = 8 * 1024 * 1024;
45027
45284
  silentLogger = { info: noop, warn: noop, error: noop };
45028
45285
  });
45029
45286
 
@@ -47332,4 +47589,4 @@ switch (command) {
47332
47589
  }
47333
47590
  }
47334
47591
 
47335
- //# debugId=F3E282057E27AACC64756E2164756E21
47592
+ //# debugId=A95E28BED5799E3A64756E2164756E21