negotium 0.4.8 → 0.5.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.
package/dist/main.js CHANGED
@@ -273,7 +273,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
273
273
  function codexAuthFilePath() {
274
274
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
275
275
  }
276
- var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.22", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.7", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
276
+ var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.23", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.7", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
277
277
  var init_config = __esm(() => {
278
278
  init_config_helpers();
279
279
  init_logger();
@@ -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.5.0";
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
  });
@@ -33130,13 +33212,60 @@ function compactDetail(decision) {
33130
33212
  const rationale = decision.reasoning.replace(/\s+/g, " ").trim();
33131
33213
  return `${rationale} \xB7 ${decision.agent}${decision.model ? `/${decision.model}` : ""}`;
33132
33214
  }
33215
+ function chronologicalSequenceEdges(ordered) {
33216
+ const parent = new Map;
33217
+ for (const decision of ordered)
33218
+ parent.set(decision.id, decision.id);
33219
+ const find = (id) => {
33220
+ let root = id;
33221
+ while (parent.get(root) !== root) {
33222
+ const next = parent.get(root);
33223
+ if (next === undefined)
33224
+ break;
33225
+ root = next;
33226
+ }
33227
+ return root;
33228
+ };
33229
+ const union = (a, b) => {
33230
+ const rootA = find(a);
33231
+ const rootB = find(b);
33232
+ if (rootA !== rootB)
33233
+ parent.set(rootA, rootB);
33234
+ };
33235
+ for (const decision of ordered) {
33236
+ for (const upstream of decision.causedBy ?? []) {
33237
+ if (parent.has(upstream))
33238
+ union(upstream, decision.id);
33239
+ }
33240
+ }
33241
+ const edges = [];
33242
+ for (let index = 1;index < ordered.length; index += 1) {
33243
+ const previous = ordered[index - 1];
33244
+ const current3 = ordered[index];
33245
+ if (!previous || !current3)
33246
+ continue;
33247
+ if (find(previous.id) === find(current3.id))
33248
+ continue;
33249
+ union(previous.id, current3.id);
33250
+ edges.push({
33251
+ id: `sequence:${previous.id}:${current3.id}`,
33252
+ source: previous.id,
33253
+ target: current3.id,
33254
+ kind: "sequence",
33255
+ direction: "none",
33256
+ style: "dashed"
33257
+ });
33258
+ }
33259
+ return edges;
33260
+ }
33133
33261
  function buildDecisionGraph(decisions2, topicTitle) {
33134
33262
  const ids = new Set(decisions2.map((decision) => decision.id));
33263
+ const ordered = [...decisions2].sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0) || a.id.localeCompare(b.id));
33135
33264
  return {
33136
33265
  id: "decisions",
33137
33266
  title: topicTitle,
33138
33267
  direction: "DOWN",
33139
- nodes: decisions2.map((decision) => ({
33268
+ nodes: ordered.map((decision) => ({
33140
33269
  id: decision.id,
33141
33270
  label: `#${decision.id} ${decision.action}`,
33142
33271
  detail: compactDetail(decision),
@@ -33144,15 +33273,17 @@ function buildDecisionGraph(decisions2, topicTitle) {
33144
33273
  state: decisionNodeState(decision.status),
33145
33274
  metadata: { timestamp: decision.timestamp, agent: decision.agent, model: decision.model }
33146
33275
  })),
33147
- edges: decisions2.flatMap((decision) => (decision.causedBy ?? []).filter((upstream) => ids.has(upstream)).map((upstream) => ({
33148
- id: `caused:${upstream}:${decision.id}`,
33149
- source: upstream,
33150
- target: decision.id,
33151
- label: "caused",
33152
- kind: "caused",
33153
- direction: "forward",
33154
- style: "solid"
33155
- })))
33276
+ edges: [
33277
+ ...ordered.flatMap((decision) => (decision.causedBy ?? []).filter((upstream) => ids.has(upstream)).map((upstream) => ({
33278
+ id: `caused:${upstream}:${decision.id}`,
33279
+ source: upstream,
33280
+ target: decision.id,
33281
+ kind: "caused",
33282
+ direction: "forward",
33283
+ style: "solid"
33284
+ }))),
33285
+ ...chronologicalSequenceEdges(ordered)
33286
+ ]
33156
33287
  };
33157
33288
  }
33158
33289
  async function renderDecisionGraph(graph, spacing = DEFAULT_DECISION_GRAPH_SPACING, signal) {
@@ -33160,14 +33291,15 @@ async function renderDecisionGraph(graph, spacing = DEFAULT_DECISION_GRAPH_SPACI
33160
33291
  direction: "DOWN",
33161
33292
  spacing,
33162
33293
  signal,
33163
- timeoutMs: 15000
33294
+ timeoutMs: 15000,
33295
+ maxNodeWidth: DECISION_NODE_MAX_WIDTH
33164
33296
  });
33165
33297
  return {
33166
33298
  canvas: { ...renderTerminalGraph(graph, geometry), title: graph.title },
33167
33299
  svg: renderSvgGraph(graph, geometry, { title: graph.title })
33168
33300
  };
33169
33301
  }
33170
- var DEFAULT_DECISION_GRAPH_SPACING = 4, MIN_DECISION_GRAPH_SPACING = 2, MAX_DECISION_GRAPH_SPACING = 10;
33302
+ var DEFAULT_DECISION_GRAPH_SPACING = 4, MIN_DECISION_GRAPH_SPACING = 2, MAX_DECISION_GRAPH_SPACING = 10, DECISION_NODE_MAX_WIDTH = 30;
33171
33303
  var init_decision_graph = () => {};
33172
33304
 
33173
33305
  // ../../adapters/terminal/src/subagent-graph.ts
@@ -35474,7 +35606,7 @@ function decisionGraphLines(state, width, height) {
35474
35606
  bold: true
35475
35607
  }),
35476
35608
  line(` [/] spacing ${state.decisionGraphSpacing} \xB7 drag/wheel/arrows/hjkl move \xB7 Ctrl-D/Esc close${position}`, { fg: theme.muted }),
35477
- line(" \u25CB accepted \xB7 \u25F7 proposed \xB7 \u2713 executed \xB7 \u2717 rejected \xB7 ! superseded", {
35609
+ line(" \u25CB accepted \xB7 \u25CC proposed \xB7 \u2713 executed \xB7 \u2715 rejected \xB7 \u25C6 superseded", {
35478
35610
  fg: theme.subtle
35479
35611
  }),
35480
35612
  line("")
@@ -40800,6 +40932,11 @@ function createTables(db4) {
40800
40932
  key TEXT PRIMARY KEY,
40801
40933
  value TEXT NOT NULL
40802
40934
  )`);
40935
+ db4.run(`CREATE TABLE IF NOT EXISTS runtime_inbox (
40936
+ seq INTEGER PRIMARY KEY,
40937
+ runtime_message_id TEXT,
40938
+ event_json TEXT NOT NULL
40939
+ )`);
40803
40940
  }
40804
40941
  function migrateOutboxSchema(db4) {
40805
40942
  try {
@@ -40810,6 +40947,13 @@ function migrateOutboxSchema(db4) {
40810
40947
  } catch {}
40811
40948
  db4.run("CREATE INDEX IF NOT EXISTS idx_telegram_outbox_runtime_message ON outbox(runtime_message_id)");
40812
40949
  }
40950
+ function migrateRuntimeInboxSchema(db4) {
40951
+ try {
40952
+ db4.run("ALTER TABLE runtime_inbox ADD COLUMN runtime_message_id TEXT");
40953
+ } catch {}
40954
+ db4.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_telegram_runtime_inbox_message
40955
+ ON runtime_inbox(runtime_message_id) WHERE runtime_message_id IS NOT NULL`);
40956
+ }
40813
40957
  function openMappingStore(path) {
40814
40958
  const dbPath = path ?? join45(DATA_DIR, "adapter-telegram.db");
40815
40959
  if (dbPath !== ":memory:")
@@ -40818,6 +40962,7 @@ function openMappingStore(path) {
40818
40962
  migrateLegacySchema(db4);
40819
40963
  createTables(db4);
40820
40964
  migrateOutboxSchema(db4);
40965
+ migrateRuntimeInboxSchema(db4);
40821
40966
  return {
40822
40967
  load() {
40823
40968
  const rows = db4.query("SELECT chat_id, thread_id, topic_id FROM mappings").all();
@@ -40891,6 +41036,40 @@ function openMappingStore(path) {
40891
41036
  entry.lastError ?? null
40892
41037
  ]);
40893
41038
  },
41039
+ runtimeEventCursor() {
41040
+ const row = db4.query("SELECT value FROM settings WHERE key = 'runtime_event_cursor'").get();
41041
+ if (!row)
41042
+ return;
41043
+ const value = Number.parseInt(row.value, 10);
41044
+ return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
41045
+ },
41046
+ captureRuntimeEvent(event) {
41047
+ const messageId = event.payload && typeof event.payload === "object" && "id" in event.payload && typeof event.payload.id === "string" ? event.payload.id : null;
41048
+ db4.transaction(() => {
41049
+ db4.run(`INSERT OR IGNORE INTO runtime_inbox (seq, runtime_message_id, event_json)
41050
+ VALUES (?, ?, ?)`, [event.seq, messageId, JSON.stringify(event)]);
41051
+ db4.run(`INSERT INTO settings (key, value) VALUES ('runtime_event_cursor', ?)
41052
+ ON CONFLICT(key) DO UPDATE SET value = MAX(CAST(value AS INTEGER), CAST(excluded.value AS INTEGER))`, [String(event.seq)]);
41053
+ })();
41054
+ },
41055
+ advanceRuntimeEventCursor(seq) {
41056
+ db4.run(`INSERT INTO settings (key, value) VALUES ('runtime_event_cursor', ?)
41057
+ ON CONFLICT(key) DO UPDATE SET value = MAX(CAST(value AS INTEGER), CAST(excluded.value AS INTEGER))`, [String(seq)]);
41058
+ },
41059
+ pendingRuntimeEvents() {
41060
+ const rows = db4.query("SELECT seq, event_json FROM runtime_inbox ORDER BY seq ASC").all();
41061
+ return rows.flatMap((row) => {
41062
+ try {
41063
+ return [{ seq: row.seq, event: JSON.parse(row.event_json) }];
41064
+ } catch {
41065
+ db4.run("DELETE FROM runtime_inbox WHERE seq = ?", [row.seq]);
41066
+ return [];
41067
+ }
41068
+ });
41069
+ },
41070
+ acknowledgeRuntimeEvent(seq) {
41071
+ db4.run("DELETE FROM runtime_inbox WHERE seq = ?", [seq]);
41072
+ },
40894
41073
  outboxDue(now) {
40895
41074
  const rows = db4.query("SELECT * FROM outbox WHERE dead = 0 AND next_try_at <= ? ORDER BY id ASC").all(now);
40896
41075
  return rows.map(outboxRowToEntry);
@@ -41768,15 +41947,29 @@ function startTelegramAdapter(opts) {
41768
41947
  }
41769
41948
  const sendQueues = new Map;
41770
41949
  function enqueueSend(topicId, task) {
41771
- const prev = sendQueues.get(topicId) ?? Promise.resolve();
41950
+ const prev = sendQueues.get(topicId) ?? Promise.resolve(true);
41772
41951
  const next = prev.then(() => new Promise((resolve24) => {
41952
+ let settled = false;
41773
41953
  const timer = setTimeout(() => {
41954
+ if (settled)
41955
+ return;
41956
+ settled = true;
41774
41957
  logger.warn({ topicId, sendTimeoutMs }, "telegram adapter: send timed out \u2014 abandoning it and continuing the queue");
41775
- resolve24();
41958
+ resolve24(false);
41776
41959
  }, sendTimeoutMs);
41777
- task().catch((err2) => logger.warn({ err: err2, topicId }, "telegram adapter: send task failed")).finally(() => {
41960
+ task().then(() => {
41961
+ if (settled)
41962
+ return;
41963
+ settled = true;
41778
41964
  clearTimeout(timer);
41779
- resolve24();
41965
+ resolve24(true);
41966
+ }).catch((err2) => {
41967
+ logger.warn({ err: err2, topicId }, "telegram adapter: send task failed");
41968
+ if (settled)
41969
+ return;
41970
+ settled = true;
41971
+ clearTimeout(timer);
41972
+ resolve24(false);
41780
41973
  });
41781
41974
  }));
41782
41975
  sendQueues.set(topicId, next);
@@ -41784,13 +41977,14 @@ function startTelegramAdapter(opts) {
41784
41977
  if (sendQueues.get(topicId) === next)
41785
41978
  sendQueues.delete(topicId);
41786
41979
  });
41980
+ return next;
41787
41981
  }
41788
41982
  function enqueueFanout(topicId, mappings, payload) {
41789
41983
  const targets = [...mappings];
41790
- enqueueSend(topicId, () => deliverToTargets(topicId, targets, payload));
41984
+ enqueueSend(topicId, () => deliverToTargets(topicId, targets, payload)).then(payload.onSettled);
41791
41985
  }
41792
41986
  function enqueueTarget(topicId, target, payload) {
41793
- enqueueSend(topicId, () => deliverToTargets(topicId, [target], payload));
41987
+ enqueueSend(topicId, () => deliverToTargets(topicId, [target], payload)).then(payload.onSettled);
41794
41988
  }
41795
41989
  const pendingByTopic = new Map;
41796
41990
  const permissionBlockedTopics = new Map;
@@ -41808,8 +42002,9 @@ function startTelegramAdapter(opts) {
41808
42002
  });
41809
42003
  }
41810
42004
  function flushBuffered(topicId, buffer, send) {
41811
- for (const payload of buffer)
41812
- enqueueSend(topicId, () => send(payload));
42005
+ for (const payload of buffer) {
42006
+ enqueueSend(topicId, () => send(payload)).then(payload.onSettled);
42007
+ }
41813
42008
  }
41814
42009
  function materializeTopic(topic) {
41815
42010
  if (!forumManageTopicsAvailable || suppressMaterialize > 0 || byTopic.has(topic.id) || pendingByTopic.has(topic.id) || permissionBlockedTopics.has(topic.id) || materializeTombstones.has(topic.id)) {
@@ -41871,8 +42066,12 @@ function startTelegramAdapter(opts) {
41871
42066
  if (materializeTombstones.delete(topicId))
41872
42067
  store.deleteTombstone(topicId);
41873
42068
  const pending2 = pendingByTopic.get(topicId);
41874
- if (pending2)
42069
+ if (pending2) {
41875
42070
  pending2.cancelled = true;
42071
+ for (const payload of pending2.buffer)
42072
+ payload.onSettled?.(true);
42073
+ pending2.buffer = [];
42074
+ }
41876
42075
  for (const [messageId, message] of runtimeMessages) {
41877
42076
  if (message.topicId === topicId)
41878
42077
  deleteDeliveredRuntimeMessage(topicId, messageId);
@@ -41919,7 +42118,7 @@ function startTelegramAdapter(opts) {
41919
42118
  }
41920
42119
  const tombstoneTitle = materializeTombstones.get(topicId);
41921
42120
  if (tombstoneTitle !== undefined) {
41922
- enqueueSend(topicId, () => deliverFallback(topicId, tombstoneTitle, payload));
42121
+ enqueueSend(topicId, () => deliverFallback(topicId, tombstoneTitle, payload)).then(payload.onSettled);
41923
42122
  return true;
41924
42123
  }
41925
42124
  const topic = getTopic(topicId);
@@ -41927,7 +42126,7 @@ function startTelegramAdapter(opts) {
41927
42126
  return false;
41928
42127
  if (forumMode) {
41929
42128
  if (!forumManageTopicsAvailable || permissionBlockedTopics.has(topicId)) {
41930
- enqueueSend(topicId, () => deliverFallback(topicId, topic.title, payload));
42129
+ enqueueSend(topicId, () => deliverFallback(topicId, topic.title, payload)).then(payload.onSettled);
41931
42130
  return true;
41932
42131
  }
41933
42132
  if (!materializeTopic(topic))
@@ -42375,6 +42574,113 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42375
42574
  }
42376
42575
  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
42576
  });
42577
+ async function deliverPersistedRuntimeMessage(event) {
42578
+ const msg = event.payload;
42579
+ if (msg.authorId === userId && msg.sourceAdapter === "telegram")
42580
+ return true;
42581
+ if (msg.kind === "tool")
42582
+ return true;
42583
+ const hasAttachments = Boolean(msg.attachments && msg.attachments.length > 0);
42584
+ if (!msg.text && !hasAttachments)
42585
+ return true;
42586
+ const runtimeMessageId = msg.authorId === "ai" ? msg.id : undefined;
42587
+ if (runtimeMessageId) {
42588
+ runtimeMessages.set(runtimeMessageId, msg);
42589
+ if (!msg.queryId) {
42590
+ const timer = setTimeout(() => {
42591
+ runtimeMessages.delete(runtimeMessageId);
42592
+ deliveredByRuntimeMessageId.delete(runtimeMessageId);
42593
+ }, 5 * 60000);
42594
+ timer.unref?.();
42595
+ }
42596
+ }
42597
+ const tagFiles = msg.text ? extractFileTagPaths(msg.text).map((path) => ({ path, filename: basename12(path) })) : [];
42598
+ const attachmentFiles = hasAttachments ? (msg.attachments ?? []).map((attachment) => {
42599
+ const path = resolveUploadedFilePathByFileId(attachment.id);
42600
+ if (!path)
42601
+ return null;
42602
+ return {
42603
+ path,
42604
+ filename: basename12(attachment.filename) || basename12(path),
42605
+ ...attachment.mimeType ? { mimeType: attachment.mimeType } : {}
42606
+ };
42607
+ }).filter((file) => file !== null) : [];
42608
+ const files = [...tagFiles, ...attachmentFiles];
42609
+ const rawText = tagFiles.length > 0 && msg.text ? stripFileTags(msg.text) : msg.text ?? "";
42610
+ const text2 = msg.authorId === userId && rawText ? `[From: User] ${rawText}` : rawText;
42611
+ if (!text2 && files.length === 0)
42612
+ return true;
42613
+ let settle;
42614
+ const settled = new Promise((resolve24) => {
42615
+ settle = resolve24;
42616
+ });
42617
+ const payload = {
42618
+ text: text2,
42619
+ files,
42620
+ runtimeMessageId,
42621
+ deliveryAckRequested: msg.deliveryAckRequested === true,
42622
+ onSettled: settle
42623
+ };
42624
+ const routed = routeMessage(event.topicId, payload, msg.queryId);
42625
+ if (routed && payload.deliveryAckRequested)
42626
+ claimDeliveryAck(event.topicId, msg.id);
42627
+ return routed ? settled : true;
42628
+ }
42629
+ const savedRuntimeEventCursor = store.runtimeEventCursor();
42630
+ let runtimeEventCursor = savedRuntimeEventCursor ?? latestRuntimeEventSeq();
42631
+ if (savedRuntimeEventCursor === undefined) {
42632
+ store.advanceRuntimeEventCursor(runtimeEventCursor);
42633
+ }
42634
+ const runtimeEventConsumerId = `telegram:${userId}:${opts.mappingDbPath ?? "default"}`;
42635
+ heartbeatRuntimeEventConsumer(runtimeEventConsumerId, runtimeEventCursor);
42636
+ let pollingRuntimeEvents = false;
42637
+ let drainingRuntimeInbox = false;
42638
+ const drainRuntimeInbox = async () => {
42639
+ if (drainingRuntimeInbox || stopped)
42640
+ return;
42641
+ drainingRuntimeInbox = true;
42642
+ try {
42643
+ while (!stopped) {
42644
+ const pending2 = store.pendingRuntimeEvents()[0];
42645
+ if (!pending2)
42646
+ break;
42647
+ const delivered = await deliverPersistedRuntimeMessage(pending2.event);
42648
+ if (!delivered || stopped)
42649
+ break;
42650
+ store.acknowledgeRuntimeEvent(pending2.seq);
42651
+ }
42652
+ } catch (err2) {
42653
+ if (!stopped)
42654
+ logger.warn({ err: err2 }, "telegram adapter: durable runtime inbox drain failed");
42655
+ } finally {
42656
+ drainingRuntimeInbox = false;
42657
+ }
42658
+ };
42659
+ const pollRuntimeEvents = () => {
42660
+ if (pollingRuntimeEvents || stopped)
42661
+ return;
42662
+ pollingRuntimeEvents = true;
42663
+ try {
42664
+ for (let batch = 0;batch < 5; batch += 1) {
42665
+ const events = listRuntimeEventsAfter(runtimeEventCursor, 500);
42666
+ if (events.length === 0)
42667
+ break;
42668
+ for (const event of events) {
42669
+ if (event.type === "message")
42670
+ store.captureRuntimeEvent({ ...event, seq: event.seq });
42671
+ else
42672
+ store.advanceRuntimeEventCursor(event.seq);
42673
+ runtimeEventCursor = event.seq;
42674
+ }
42675
+ if (events.length < 500)
42676
+ break;
42677
+ }
42678
+ drainRuntimeInbox();
42679
+ heartbeatRuntimeEventConsumer(runtimeEventConsumerId, runtimeEventCursor);
42680
+ } finally {
42681
+ pollingRuntimeEvents = false;
42682
+ }
42683
+ };
42378
42684
  const unsubscribe = runtimeBus().subscribe((event) => {
42379
42685
  if (stopped)
42380
42686
  return;
@@ -42387,22 +42693,22 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42387
42693
  return;
42388
42694
  }
42389
42695
  if (event.type === "message-updated") {
42390
- const payload2 = event.payload;
42391
- if (!payload2.messageId || !payload2.patch)
42696
+ const payload = event.payload;
42697
+ if (!payload.messageId || !payload.patch)
42392
42698
  return;
42393
- if (payload2.patch.deleted) {
42394
- deleteDeliveredRuntimeMessage(event.topicId, payload2.messageId);
42699
+ if (payload.patch.deleted) {
42700
+ deleteDeliveredRuntimeMessage(event.topicId, payload.messageId);
42395
42701
  return;
42396
42702
  }
42397
- const current3 = runtimeMessages.get(payload2.messageId);
42703
+ const current3 = runtimeMessages.get(payload.messageId);
42398
42704
  if (!current3)
42399
42705
  return;
42400
- const updated = { ...current3, ...payload2.patch };
42401
- runtimeMessages.set(payload2.messageId, updated);
42402
- if (footerEnabled && payload2.patch.usage && updated.authorId === "ai") {
42706
+ const updated = { ...current3, ...payload.patch };
42707
+ runtimeMessages.set(payload.messageId, updated);
42708
+ if (footerEnabled && payload.patch.usage && updated.authorId === "ai") {
42403
42709
  const footer = renderTurnFooter(updated);
42404
42710
  if (footer)
42405
- attachUpdatedFooter(event.topicId, payload2.messageId, footer);
42711
+ attachUpdatedFooter(event.topicId, payload.messageId, footer);
42406
42712
  }
42407
42713
  return;
42408
42714
  }
@@ -42425,53 +42731,10 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42425
42731
  }
42426
42732
  return;
42427
42733
  }
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
42734
  });
42735
+ pollRuntimeEvents();
42736
+ const runtimeEventPollTimer = setInterval(pollRuntimeEvents, 100);
42737
+ runtimeEventPollTimer.unref?.();
42475
42738
  if (forumMode) {
42476
42739
  bindMapping(forumChat, undefined, personalGeneral.id);
42477
42740
  materializeVisibleTopics();
@@ -42491,6 +42754,7 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
42491
42754
  for (const queryId of toolStatusByQueryId.keys())
42492
42755
  closeToolStatus(queryId);
42493
42756
  mediaIntake.stop();
42757
+ clearInterval(runtimeEventPollTimer);
42494
42758
  unsubscribe();
42495
42759
  store.close();
42496
42760
  }
@@ -44802,8 +45066,18 @@ class TunnelClient {
44802
45066
  this.bridgedSockets.clear();
44803
45067
  }
44804
45068
  send(frame) {
44805
- if (this.ws?.readyState === WebSocket.OPEN)
44806
- this.ws.send(encodeFrame(frame));
45069
+ const ws = this.ws;
45070
+ if (!ws || ws.readyState !== WebSocket.OPEN)
45071
+ return;
45072
+ if (Number(ws.bufferedAmount ?? 0) > MAX_TUNNEL_BUFFERED_AMOUNT) {
45073
+ this.disconnectActiveConnection(ws, this.connectionGeneration, { reason: "relay outbound buffer limit exceeded" }, true);
45074
+ return;
45075
+ }
45076
+ try {
45077
+ ws.send(encodeFrame(frame));
45078
+ } catch {
45079
+ this.disconnectActiveConnection(ws, this.connectionGeneration, { reason: "relay send failed" }, true);
45080
+ }
44807
45081
  }
44808
45082
  handleFrame(frame) {
44809
45083
  switch (frame.type) {
@@ -44838,11 +45112,23 @@ class TunnelClient {
44838
45112
  break;
44839
45113
  case "http_req_chunk": {
44840
45114
  const pending2 = this.pendingHttp.get(frame.id);
45115
+ if (!pending2)
45116
+ break;
44841
45117
  try {
44842
- pending2?.bodyController?.enqueue(fromB64(frame.dataB64));
44843
- if (pending2)
44844
- this.startPendingHttp(frame.id, pending2);
44845
- } catch {}
45118
+ if (!pending2.body)
45119
+ throw new Error("body chunk received for bodyless request");
45120
+ const chunk = fromB64(frame.dataB64);
45121
+ pending2.receivedBytes += chunk.byteLength;
45122
+ if (pending2.receivedBytes > MAX_HTTP_REQUEST_BODY_BYTES) {
45123
+ throw new Error("relay HTTP request body limit exceeded");
45124
+ }
45125
+ pending2.bodyController?.enqueue(chunk);
45126
+ this.startPendingHttp(frame.id, pending2);
45127
+ } catch {
45128
+ pending2.abort.abort();
45129
+ this.pendingHttp.delete(frame.id);
45130
+ throw new Error("invalid or oversized relay HTTP request body");
45131
+ }
44846
45132
  break;
44847
45133
  }
44848
45134
  case "http_req_end": {
@@ -44873,6 +45159,12 @@ class TunnelClient {
44873
45159
  if (!bridged)
44874
45160
  break;
44875
45161
  if (!bridged.opened) {
45162
+ bridged.bufferedBytes += frame.text === undefined ? Math.floor((frame.dataB64 ?? "").length * 3 / 4) : Buffer.byteLength(frame.text, "utf8");
45163
+ if (bridged.bufferedBytes > MAX_PREOPEN_WEBSOCKET_BYTES) {
45164
+ this.bridgedSockets.delete(frame.id);
45165
+ bridged.ws.close(1009, "pre-open buffer limit exceeded");
45166
+ throw new Error("bridged WebSocket pre-open buffer limit exceeded");
45167
+ }
44876
45168
  bridged.buffer.push(frame);
44877
45169
  break;
44878
45170
  }
@@ -44892,6 +45184,11 @@ class TunnelClient {
44892
45184
  }
44893
45185
  }
44894
45186
  startHttpRequest(frame) {
45187
+ if (this.pendingHttp.has(frame.id))
45188
+ throw new Error("duplicate relay HTTP request id");
45189
+ if (this.pendingHttp.size >= MAX_IN_FLIGHT_HTTP_REQUESTS) {
45190
+ throw new Error("too many in-flight relay HTTP requests");
45191
+ }
44895
45192
  const abort = new AbortController;
44896
45193
  const headers = new Headers;
44897
45194
  for (const [name, value] of frame.headers)
@@ -44903,7 +45200,8 @@ class TunnelClient {
44903
45200
  method: frame.method,
44904
45201
  path: frame.path,
44905
45202
  headers,
44906
- started: false
45203
+ started: false,
45204
+ receivedBytes: 0
44907
45205
  };
44908
45206
  if (frame.hasBody) {
44909
45207
  pending2.body = new ReadableStream({
@@ -44964,6 +45262,11 @@ class TunnelClient {
44964
45262
  }
44965
45263
  }
44966
45264
  openBridgedSocket(id, path, headers) {
45265
+ if (this.bridgedSockets.has(id))
45266
+ throw new Error("duplicate bridged WebSocket id");
45267
+ if (this.bridgedSockets.size >= MAX_BRIDGED_SOCKETS) {
45268
+ throw new Error("too many bridged WebSockets");
45269
+ }
44967
45270
  const wsOrigin = this.opts.targetOrigin.replace(/^http/, "ws");
44968
45271
  let local;
44969
45272
  try {
@@ -44980,7 +45283,7 @@ class TunnelClient {
44980
45283
  });
44981
45284
  return;
44982
45285
  }
44983
- const bridged = { ws: local, opened: false, buffer: [] };
45286
+ const bridged = { ws: local, opened: false, buffer: [], bufferedBytes: 0 };
44984
45287
  this.bridgedSockets.set(id, bridged);
44985
45288
  local.onopen = () => {
44986
45289
  bridged.opened = true;
@@ -44988,6 +45291,7 @@ class TunnelClient {
44988
45291
  for (const frame of bridged.buffer)
44989
45292
  this.deliverToLocal(local, frame);
44990
45293
  bridged.buffer = [];
45294
+ bridged.bufferedBytes = 0;
44991
45295
  };
44992
45296
  local.onmessage = (event) => {
44993
45297
  if (typeof event.data === "string") {
@@ -45019,11 +45323,14 @@ class TunnelClient {
45019
45323
  local.send(fromB64(frame.dataB64));
45020
45324
  }
45021
45325
  }
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;
45326
+ 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
45327
  var init_tunnel_client = __esm(() => {
45024
45328
  init_relay_protocol();
45025
45329
  MAX_WS_UPGRADE_HEADER_BYTES = 32 * 1024;
45026
45330
  WS_PROTOCOL_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
45331
+ MAX_HTTP_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
45332
+ MAX_PREOPEN_WEBSOCKET_BYTES = 1024 * 1024;
45333
+ MAX_TUNNEL_BUFFERED_AMOUNT = 8 * 1024 * 1024;
45027
45334
  silentLogger = { info: noop, warn: noop, error: noop };
45028
45335
  });
45029
45336
 
@@ -47332,4 +47639,4 @@ switch (command) {
47332
47639
  }
47333
47640
  }
47334
47641
 
47335
- //# debugId=F3E282057E27AACC64756E2164756E21
47642
+ //# debugId=A120DD1C769C1B1164756E2164756E21