agentschat-mcp 0.36.5 → 0.36.8

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.
@@ -216,9 +216,9 @@ ${text}`,
216
216
  }
217
217
 
218
218
  // codex/run.ts
219
- import { readFileSync as readFileSync7 } from "node:fs";
219
+ import { readFileSync as readFileSync8 } from "node:fs";
220
220
  import { homedir as homedir4 } from "node:os";
221
- import { join as join8 } from "node:path";
221
+ import { join as join9 } from "node:path";
222
222
 
223
223
  // codex/bots-config.ts
224
224
  import { readFileSync as readFileSync3, realpathSync as realpathSync3, mkdirSync as mkdirSync3 } from "node:fs";
@@ -1117,6 +1117,13 @@ function parse(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
1117
1117
  */
1118
1118
 
1119
1119
  // codex/config.ts
1120
+ function reasoningEffort(value) {
1121
+ if (value === undefined)
1122
+ return;
1123
+ if (typeof value !== "string" || !value.trim())
1124
+ throw new Error("effort must be a nonempty model-supported reasoning effort name");
1125
+ return value.trim();
1126
+ }
1120
1127
  function permissionMode(value) {
1121
1128
  if (value === undefined)
1122
1129
  return "full-access";
@@ -1144,7 +1151,7 @@ function resolveConfig(opts, env = process.env, home = homedir2()) {
1144
1151
  const project = opts.settings ?? (existsSync2(configFile) ? readJson(configFile) : {});
1145
1152
  if (!project || typeof project !== "object" || Array.isArray(project))
1146
1153
  throw new Error("Invalid project config");
1147
- const allowed = new Set(["profile", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions"]);
1154
+ const allowed = new Set(["profile", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions", "effort"]);
1148
1155
  if (Object.keys(project).some((k) => !allowed.has(k)))
1149
1156
  throw new Error("Unknown project config field (credentials belong in a private profile)");
1150
1157
  for (const k of ["profile", "agent_id", "api_url", "ws_url"])
@@ -1214,6 +1221,7 @@ function resolveConfig(opts, env = process.env, home = homedir2()) {
1214
1221
  profileFile,
1215
1222
  source,
1216
1223
  permissions: permissionMode(project.permissions),
1224
+ effort: reasoningEffort(project.effort),
1217
1225
  agentId: profile.agent_id,
1218
1226
  token: profile.token,
1219
1227
  apiUrl: canonicalApi,
@@ -1253,7 +1261,7 @@ function loadBots(file = defaultRegistry(), home = homedir3()) {
1253
1261
  const names = new Set, identities = new Set;
1254
1262
  const bots = [];
1255
1263
  for (const bot of doc.bots) {
1256
- fields(bot, ["name", "profile", "workdir", "enabled", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions"]);
1264
+ fields(bot, ["name", "profile", "workdir", "enabled", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions", "effort"]);
1257
1265
  if (!text(bot.name) || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(bot.name) || names.has(bot.name))
1258
1266
  throw new Error("Bot names must be unique simple labels");
1259
1267
  names.add(bot.name);
@@ -1269,7 +1277,7 @@ function loadBots(file = defaultRegistry(), home = homedir3()) {
1269
1277
  mkdirSync3(defaultDir, { recursive: true, mode: 448 });
1270
1278
  const cwd = realpathSync3(bot.workdir ? path(bot.workdir) : defaultDir);
1271
1279
  const settings = {};
1272
- for (const k of ["agent_id", "channels", "senders", "api_url", "ws_url", "permissions"])
1280
+ for (const k of ["agent_id", "channels", "senders", "api_url", "ws_url", "permissions", "effort"])
1273
1281
  if (bot[k] !== undefined)
1274
1282
  settings[k] = bot[k];
1275
1283
  const config = resolveConfig({ cwd, profile: bot.profile, settings, codexBin: doc.codex_bin }, {}, home);
@@ -1299,9 +1307,9 @@ class ThreadBusyError extends Error {
1299
1307
  class AppServer {
1300
1308
  bin;
1301
1309
  args;
1302
- timeoutMs;
1303
1310
  permissions;
1304
1311
  runtime;
1312
+ effort;
1305
1313
  onFatal;
1306
1314
  closed = false;
1307
1315
  child;
@@ -1310,12 +1318,12 @@ class AppServer {
1310
1318
  active;
1311
1319
  threadPermissions = new Map;
1312
1320
  disabledMcp = {};
1313
- constructor(bin = "codex", args = ["app-server", "--listen", "stdio://"], timeoutMs = 600000, permissions = "full-access", runtime) {
1321
+ constructor(bin = "codex", args = ["app-server", "--listen", "stdio://"], _legacyTurnTimeoutMs, permissions = "full-access", runtime, effort) {
1314
1322
  this.bin = bin;
1315
1323
  this.args = args;
1316
- this.timeoutMs = timeoutMs;
1317
1324
  this.permissions = permissions;
1318
1325
  this.runtime = runtime;
1326
+ this.effort = effort;
1319
1327
  }
1320
1328
  get namespace() {
1321
1329
  return this.runtime?.home;
@@ -1351,12 +1359,10 @@ class AppServer {
1351
1359
  request(method, params) {
1352
1360
  return new Promise((resolve4, reject) => {
1353
1361
  const id = ++this.nextId;
1354
- const timer = setTimeout(() => this.fatal(new Error(`App-server ${method} timed out`)), 30000);
1355
- this.pending.set(id, { method, resolve: resolve4, reject, timer });
1362
+ this.pending.set(id, { method, resolve: resolve4, reject });
1356
1363
  try {
1357
1364
  this.write({ id, method, params });
1358
1365
  } catch (e) {
1359
- clearTimeout(timer);
1360
1366
  this.pending.delete(id);
1361
1367
  reject(e);
1362
1368
  }
@@ -1370,7 +1376,6 @@ class AppServer {
1370
1376
  if (message.id !== undefined) {
1371
1377
  const waiter = this.pending.get(message.id);
1372
1378
  if (waiter) {
1373
- clearTimeout(waiter.timer);
1374
1379
  this.pending.delete(message.id);
1375
1380
  message.error ? waiter.reject(waiter.method === "thread/resume" && /already has an active writer/i.test(message.error.message ?? "") ? new ThreadBusyError : new Error(`App-server request rejected (${message.error.code})`)) : waiter.resolve(message.result);
1376
1381
  }
@@ -1388,7 +1393,6 @@ class AppServer {
1388
1393
  if (message.method === "item/completed" && p.item?.type === "agentMessage" && (!p.item.phase || p.item.phase === "final_answer"))
1389
1394
  a.items.set(p.item.id, p.item.text);
1390
1395
  if (message.method === "turn/completed") {
1391
- clearTimeout(a.timer);
1392
1396
  this.active = undefined;
1393
1397
  if (p.turn.status !== "completed") {
1394
1398
  a.reject(new Error(`Codex turn ${p.turn.status}`));
@@ -1435,7 +1439,7 @@ class AppServer {
1435
1439
  async readLegacyThread(thread) {
1436
1440
  if (!this.runtime?.legacyHome)
1437
1441
  return this.readThread(thread);
1438
- const reader = new AppServer(this.bin, undefined, this.timeoutMs, this.permissions, { home: this.runtime.legacyHome });
1442
+ const reader = new AppServer(this.bin, undefined, undefined, this.permissions, { home: this.runtime.legacyHome });
1439
1443
  try {
1440
1444
  await reader.start();
1441
1445
  return await reader.readThread(thread);
@@ -1446,21 +1450,14 @@ class AppServer {
1446
1450
  async nameThread(thread, name) {
1447
1451
  await this.request("thread/name/set", { threadId: thread, name });
1448
1452
  }
1449
- async generate(thread, text, effort) {
1453
+ async generate(thread, text, effort = this.effort) {
1450
1454
  if (this.active)
1451
1455
  throw new Error("App-server is busy");
1452
1456
  const permissions = this.threadPermissions.get(thread);
1453
1457
  if (!permissions)
1454
1458
  throw new Error("Thread permissions have not been configured");
1455
1459
  const completed = new Promise((resolve4, reject) => {
1456
- this.active = {
1457
- thread,
1458
- items: new Map,
1459
- early: [],
1460
- resolve: resolve4,
1461
- reject,
1462
- timer: setTimeout(() => this.fatal(new Error("Codex turn timed out")), this.timeoutMs)
1463
- };
1460
+ this.active = { thread, items: new Map, early: [], resolve: resolve4, reject };
1464
1461
  });
1465
1462
  completed.catch(() => {});
1466
1463
  try {
@@ -1481,13 +1478,10 @@ class AppServer {
1481
1478
  }
1482
1479
  }
1483
1480
  fail(error) {
1484
- for (const p of this.pending.values()) {
1485
- clearTimeout(p.timer);
1481
+ for (const p of this.pending.values())
1486
1482
  p.reject(error);
1487
- }
1488
1483
  this.pending.clear();
1489
1484
  if (this.active) {
1490
- clearTimeout(this.active.timer);
1491
1485
  this.active.reject(error);
1492
1486
  this.active = undefined;
1493
1487
  }
@@ -1763,6 +1757,17 @@ class Bridge {
1763
1757
  redact(text) {
1764
1758
  return redactSecrets(text.split(this.config.token).join("[REDACTED]"));
1765
1759
  }
1760
+ recover(raw) {
1761
+ if (this.stopped)
1762
+ return false;
1763
+ if (!addressed(raw, this.config))
1764
+ return true;
1765
+ if (this.state.entries.some((e) => e.message.id === raw.id && e.message.channel_id === raw.channel_id))
1766
+ return true;
1767
+ if (raw.meta?.kind === "loop_tick" && this.state.entries.some((e) => e.message.meta?.loop_id === raw.meta.loop_id && e.message.meta.next_tick_ms === raw.meta.next_tick_ms))
1768
+ return true;
1769
+ return this.accept(raw);
1770
+ }
1766
1771
  drain() {
1767
1772
  if (this.retry)
1768
1773
  return Promise.resolve();
@@ -1975,11 +1980,142 @@ class HeartbeatMonitor {
1975
1980
  }
1976
1981
  }
1977
1982
 
1983
+ // codex/inbox-sync.ts
1984
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
1985
+ import { join as join8 } from "node:path";
1986
+
1987
+ // src/timestamps.ts
1988
+ function normalizeTimestampForCursor(ts, mode) {
1989
+ if (!ts || typeof ts !== "string")
1990
+ return ts;
1991
+ const padChar = mode === "before" ? "9" : "0";
1992
+ const withFrac = ts.match(/^(.*\.)(\d+)(Z)$/);
1993
+ if (withFrac) {
1994
+ const frac = withFrac[2];
1995
+ if (frac.length >= 9)
1996
+ return ts;
1997
+ return withFrac[1] + frac + padChar.repeat(9 - frac.length) + withFrac[3];
1998
+ }
1999
+ const noFrac = ts.match(/^(.*\d)(Z)$/);
2000
+ if (noFrac) {
2001
+ return noFrac[1] + "." + padChar.repeat(9) + noFrac[2];
2002
+ }
2003
+ return ts;
2004
+ }
2005
+
2006
+ // codex/inbox-sync.ts
2007
+ function timestamp(value) {
2008
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
2009
+ throw new Error("Invalid history timestamp");
2010
+ return normalizeTimestampForCursor(value, "after");
2011
+ }
2012
+
2013
+ class InboxSync {
2014
+ api;
2015
+ receive;
2016
+ active;
2017
+ log;
2018
+ file;
2019
+ state;
2020
+ channels = new Set;
2021
+ running;
2022
+ constructor(stateDir, api, receive, active, log = console.error) {
2023
+ this.api = api;
2024
+ this.receive = receive;
2025
+ this.active = active;
2026
+ this.log = log;
2027
+ mkdirSync6(stateDir, { recursive: true, mode: 448 });
2028
+ this.file = join8(stateDir, "inbound-cursors.json");
2029
+ this.state = existsSync4(this.file) ? JSON.parse(readFileSync7(this.file, "utf8")) : { version: 1, channels: {} };
2030
+ if (this.state.version !== 1 || !this.state.channels || typeof this.state.channels !== "object" || Array.isArray(this.state.channels))
2031
+ throw new Error("Invalid inbound cursors; refusing to discard checkpoint");
2032
+ for (const value of Object.values(this.state.channels))
2033
+ timestamp(value);
2034
+ this.state.channels = Object.assign(Object.create(null), this.state.channels);
2035
+ }
2036
+ save() {
2037
+ writeFileSync4(this.file + ".tmp", JSON.stringify(this.state), { mode: 384 });
2038
+ renameSync4(this.file + ".tmp", this.file);
2039
+ }
2040
+ watch(channel) {
2041
+ this.channels.add(channel);
2042
+ if (!Object.hasOwn(this.state.channels, channel)) {
2043
+ this.state.channels[channel] = timestamp(new Date().toISOString());
2044
+ this.save();
2045
+ }
2046
+ }
2047
+ memberships(channels) {
2048
+ this.channels.clear();
2049
+ for (const channel of channels)
2050
+ this.watch(channel);
2051
+ }
2052
+ sync() {
2053
+ if (!this.running)
2054
+ this.running = this.run().finally(() => {
2055
+ this.running = undefined;
2056
+ });
2057
+ return this.running;
2058
+ }
2059
+ async run() {
2060
+ if (!this.active())
2061
+ return;
2062
+ let failed = false;
2063
+ for (const channel of [...this.channels]) {
2064
+ if (!this.active())
2065
+ return;
2066
+ try {
2067
+ const after = this.state.channels[channel];
2068
+ const body = await this.api(`/api/channels/${encodeURIComponent(channel)}/messages?after=${encodeURIComponent(after)}&limit=50`);
2069
+ if (!this.active())
2070
+ return;
2071
+ if (!this.channels.has(channel))
2072
+ continue;
2073
+ const messages = Array.isArray(body) ? body : body?.messages;
2074
+ if (!Array.isArray(messages))
2075
+ throw new Error("Invalid history response");
2076
+ const rows = messages.map((m) => {
2077
+ if (!m || m.channel_id !== channel || typeof m.id !== "string")
2078
+ throw new Error("Invalid history message");
2079
+ return { message: m, time: timestamp(m.timestamp) };
2080
+ }).sort((a, b) => a.time.localeCompare(b.time));
2081
+ let newest = after, complete = true;
2082
+ for (const { message, time } of rows) {
2083
+ if (time <= after)
2084
+ continue;
2085
+ if (!this.receive(message)) {
2086
+ complete = false;
2087
+ break;
2088
+ }
2089
+ newest = time;
2090
+ }
2091
+ if (complete) {
2092
+ this.state.channels[channel] = newest;
2093
+ this.save();
2094
+ }
2095
+ } catch {
2096
+ failed = true;
2097
+ this.log("Inbound history reconciliation failed; checkpoint retained for retry");
2098
+ }
2099
+ }
2100
+ if (!this.active())
2101
+ return;
2102
+ this.state.last_check_at = new Date().toISOString();
2103
+ if (failed)
2104
+ this.state.last_error = "History reconciliation failed; retry pending";
2105
+ else {
2106
+ this.state.last_success_at = this.state.last_check_at;
2107
+ delete this.state.last_error;
2108
+ }
2109
+ this.save();
2110
+ }
2111
+ }
2112
+
1978
2113
  // codex/transport.ts
1979
2114
  class AgentsChatTransport {
1980
2115
  config;
1981
2116
  receive;
1982
2117
  log;
2118
+ syncIntervalMs;
1983
2119
  socket;
1984
2120
  authenticated = false;
1985
2121
  typing = new Map;
@@ -1988,10 +2124,15 @@ class AgentsChatTransport {
1988
2124
  retry;
1989
2125
  closed = false;
1990
2126
  delay = 1000;
1991
- constructor(config, receive, log = console.error) {
2127
+ inboxSync;
2128
+ syncTimer;
2129
+ constructor(config, receive, log = console.error, recover, syncIntervalMs = 60000) {
1992
2130
  this.config = config;
1993
2131
  this.receive = receive;
1994
2132
  this.log = log;
2133
+ this.syncIntervalMs = syncIntervalMs;
2134
+ if (recover)
2135
+ this.inboxSync = new InboxSync(config.stateDir, (path) => this.api(path), recover, () => !this.closed && this.authenticated, log);
1995
2136
  }
1996
2137
  async api(path, body) {
1997
2138
  let response;
@@ -2076,9 +2217,11 @@ class AgentsChatTransport {
2076
2217
  if (current() && socket.readyState === WebSocket.OPEN)
2077
2218
  socket.send(JSON.stringify(value));
2078
2219
  };
2079
- const join8 = (channel) => {
2080
- if (!this.config.channels.length || this.config.channels.includes(channel))
2220
+ const join9 = (channel) => {
2221
+ if (!this.config.channels.length || this.config.channels.includes(channel)) {
2222
+ this.inboxSync?.watch(channel);
2081
2223
  send({ type: "join_channel", channel_id: channel, agent_id: this.config.agentId });
2224
+ }
2082
2225
  };
2083
2226
  this.heartbeat = new HeartbeatMonitor({
2084
2227
  getReadyState: () => socket.readyState,
@@ -2101,15 +2244,21 @@ class AgentsChatTransport {
2101
2244
  this.authenticated = true;
2102
2245
  this.delay = 1000;
2103
2246
  this.log(`AgentsChat connected as ${this.config.agentId}`);
2247
+ if (this.inboxSync && !this.syncTimer)
2248
+ this.syncTimer = setInterval(() => {
2249
+ this.inboxSync.sync();
2250
+ }, this.syncIntervalMs);
2104
2251
  this.api("/api/channels/mine").then((body) => {
2105
2252
  if (!current())
2106
2253
  return;
2107
2254
  const channels = Array.isArray(body) ? body : body.channels;
2108
2255
  if (!Array.isArray(channels))
2109
2256
  throw new Error("Invalid membership response");
2110
- for (const c of channels)
2111
- if (typeof (c.id ?? c.channel_id) === "string")
2112
- join8(c.id ?? c.channel_id);
2257
+ const ids = channels.map((c) => c.id ?? c.channel_id).filter((id) => typeof id === "string" && (!this.config.channels.length || this.config.channels.includes(id)));
2258
+ this.inboxSync?.memberships(ids);
2259
+ for (const id of ids)
2260
+ join9(id);
2261
+ this.inboxSync?.sync();
2113
2262
  }).catch(() => {
2114
2263
  if (current()) {
2115
2264
  this.log("Membership sync failed; reconnecting");
@@ -2124,10 +2273,10 @@ class AgentsChatTransport {
2124
2273
  pending.resolve();
2125
2274
  }
2126
2275
  } else if (data.type === "channel_created" && typeof data.channel_id === "string")
2127
- join8(data.channel_id);
2276
+ join9(data.channel_id);
2128
2277
  else if (["message", "thread_reply"].includes(data.type))
2129
2278
  this.receive(data);
2130
- else if (data.type === "shard_moved")
2279
+ else if (data.type === "shard_moved" || data.type === "please_reconnect")
2131
2280
  socket.terminate();
2132
2281
  else if (data.type === "auth_error" || data.type === "error")
2133
2282
  this.log("AgentsChat returned an error; check account and channel permissions");
@@ -2139,7 +2288,7 @@ class AgentsChatTransport {
2139
2288
  this.authenticated = false;
2140
2289
  this.rejectPending();
2141
2290
  this.heartbeat?.stop();
2142
- this.log("AgentsChat disconnected; reconnecting (offline messages are not replayed)");
2291
+ this.log("AgentsChat disconnected; reconnecting with history reconciliation");
2143
2292
  this.retry = setTimeout(() => this.start(), this.delay);
2144
2293
  this.delay = Math.min(this.delay * 2, 30000);
2145
2294
  });
@@ -2152,6 +2301,7 @@ class AgentsChatTransport {
2152
2301
  this.authenticated = false;
2153
2302
  this.rejectPending();
2154
2303
  clearTimeout(this.retry);
2304
+ clearInterval(this.syncTimer);
2155
2305
  this.heartbeat?.stop();
2156
2306
  this.socket?.terminate();
2157
2307
  }
@@ -2169,11 +2319,11 @@ CWD/.agentschat/profile.json > CWD/.codex/config.toml MCP profile > AGENTSCHAT_P
2169
2319
  Only the exact CWD is searched. Named profiles live in ~/.agentschat (legacy ~/.agentchat).
2170
2320
  An optional project agent_id must match the selected profile; it cannot replace it.
2171
2321
  Credentials: private profile JSON {agent_id, token}, chmod 600; never put keys in argv.
2172
- Project config fields: profile, agent_id, channels, senders, api_url, ws_url, permissions.
2322
+ Project config fields: profile, agent_id, channels, senders, api_url, ws_url, permissions, effort.
2173
2323
  --onboarding-status checks authentication/ownership and prints safe claim/chat links; it does not send messages.
2174
2324
  --check validates identity and official app-server initialization without opening chat.
2175
2325
  Live DMs and exact mentions trigger replies; channels/senders restrict this further.
2176
- All accepted messages share one persisted thread per channel, with full access by default. Set permissions: "read-only" to disable writes and inherited MCP. No offline message replay.
2326
+ All accepted messages share one persisted thread per channel, with full access by default. Set permissions: "read-only" to disable writes and inherited MCP. After initial installation, persisted history cursors recover missed messages on reconnect and every minute.
2177
2327
  State: ~/.agentschat/codex-bridge/<project-server-identity hash>/ (private).
2178
2328
  --conversations lists this bot's channel/task mappings; --read-conversation CHANNEL reads history without acquiring a writer.
2179
2329
  GUI outbox: --gui-thread THREAD_ID --gui-message-file PATH; --gui-status lists receipts.
@@ -2206,14 +2356,14 @@ async function main() {
2206
2356
  return;
2207
2357
  }
2208
2358
  if (values["gui-thread"] || values["gui-message-file"] || values["gui-status"]) {
2209
- const channel = new GuiChannel(join8(homedir4(), ".agentschat/codex-gui-outbox"), values["gui-thread"] ? [values["gui-thread"]] : []);
2359
+ const channel = new GuiChannel(join9(homedir4(), ".agentschat/codex-gui-outbox"), values["gui-thread"] ? [values["gui-thread"]] : []);
2210
2360
  if (values["gui-status"]) {
2211
2361
  console.log(JSON.stringify(channel.list().map(({ prompt: prompt2, ...receipt2 }) => receipt2)));
2212
2362
  return;
2213
2363
  }
2214
2364
  if (!values["gui-thread"] || !values["gui-message-file"])
2215
2365
  throw new Error("GUI submission requires --gui-thread and --gui-message-file");
2216
- const { prompt, ...receipt } = channel.enqueue(values["gui-thread"], readFileSync7(values["gui-message-file"], "utf8"));
2366
+ const { prompt, ...receipt } = channel.enqueue(values["gui-thread"], readFileSync8(values["gui-message-file"], "utf8"));
2217
2367
  console.log(JSON.stringify(receipt));
2218
2368
  return;
2219
2369
  }
@@ -2245,9 +2395,9 @@ async function main() {
2245
2395
  }
2246
2396
  console.log(JSON.stringify({ cwd: c.cwd, agent_id: c.agentId, profile: c.profileFile, source: c.source, stateDir: c.stateDir }));
2247
2397
  const runtime = prepareRuntimeHome(c.stateDir);
2248
- codex = new AppServer(c.codexBin, undefined, undefined, c.permissions, runtime);
2398
+ codex = new AppServer(c.codexBin, undefined, undefined, c.permissions, runtime, c.effort);
2249
2399
  if (values.conversations || values["read-conversation"]) {
2250
- const state = JSON.parse(readFileSync7(join8(c.stateDir, "state.json"), "utf8"));
2400
+ const state = JSON.parse(readFileSync8(join9(c.stateDir, "state.json"), "utf8"));
2251
2401
  const channels = state.channels ?? {};
2252
2402
  if (values.conversations) {
2253
2403
  console.log(JSON.stringify(Object.entries(channels).map(([channel2, data]) => ({ channel: channel2, thread: data.thread, isolated: data.namespace === runtime.home }))));
@@ -2272,7 +2422,7 @@ async function main() {
2272
2422
  }
2273
2423
  transport = new AgentsChatTransport(c, (m) => {
2274
2424
  bridge.accept(m);
2275
- });
2425
+ }, console.error, (m) => bridge.recover(m));
2276
2426
  bridge = new Bridge(c, codex, (chat, text) => transport.send(chat, text), console.error, (chat, active) => transport.setTyping(chat, active), () => getBotOwner(c.apiUrl, c.agentId, c.token), () => transport.api("/api/loops/mine"));
2277
2427
  let stopping = false;
2278
2428
  const stop = async () => {