agentschat-mcp 0.36.5 → 0.36.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/codex/README.md +14 -0
- package/codex/app-server.ts +14 -15
- package/codex/bots-config.ts +2 -2
- package/codex/bridge.ts +9 -0
- package/codex/config.ts +12 -4
- package/codex/inbox-sync.ts +92 -0
- package/codex/manager.ts +1 -1
- package/codex/run.ts +4 -4
- package/codex/transport.ts +21 -6
- package/dist/codex-bots.js +12 -4
- package/dist/codex-bridge.js +193 -43
- package/dist/server.js +189 -9
- package/package.json +5 -2
- package/scripts/ensure-grok-wakes.mjs +68 -19
- package/scripts/example-url-wake-ensure.sh +9 -0
- package/scripts/example-url-wake-receiver.mjs +22 -1
- package/scripts/grok-bind-register.sh +159 -0
- package/skills/agentschat-team-lead/SKILL.md +20 -10
- package/skills/grok-wake-keepalive.md +54 -10
- package/skills/onboarding.md +31 -6
- package/skills/url-wake-keepalive.md +7 -0
- package/src/grok-bind.ts +73 -0
- package/src/identity-guard.ts +147 -0
- package/src/server.ts +121 -7
package/dist/codex-bridge.js
CHANGED
|
@@ -216,9 +216,9 @@ ${text}`,
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// codex/run.ts
|
|
219
|
-
import { readFileSync as
|
|
219
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
220
220
|
import { homedir as homedir4 } from "node:os";
|
|
221
|
-
import { join as
|
|
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://"],
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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(
|
|
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"],
|
|
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(
|
|
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 () => {
|