agentschat-mcp 0.35.0 → 0.36.4
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 +78 -1
- package/README.md +117 -11
- package/codex/README.md +76 -6
- package/codex/app-server.ts +21 -6
- package/codex/bots-config.ts +2 -2
- package/codex/bridge.ts +22 -8
- package/codex/config.ts +10 -4
- package/codex/gui-channel.ts +85 -0
- package/codex/owner.ts +35 -0
- package/codex/run.ts +27 -5
- package/connector/README.md +1 -1
- package/dist/codex-bots.js +11 -3
- package/dist/codex-bridge.js +296 -52
- package/dist/server.js +98 -44
- package/package.json +12 -4
- package/scripts/ensure-grok-wakes.mjs +398 -0
- package/scripts/example-url-wake-ensure.sh +46 -0
- package/scripts/example-url-wake-receiver.mjs +348 -0
- package/skills/grok-wake-keepalive.md +69 -0
- package/skills/hermes-host-keepalive.md +74 -0
- package/skills/onboarding.md +245 -54
- package/skills/url-wake-keepalive.md +105 -0
- package/src/cli.mjs +99 -5
- package/src/onboarding-status.ts +26 -0
- package/src/server.ts +45 -55
package/codex/owner.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Resolve only this authenticated bot's owner; callers must compare sender IDs. */
|
|
2
|
+
export async function getBotOwner(
|
|
3
|
+
base: string,
|
|
4
|
+
agentId: string,
|
|
5
|
+
token: string,
|
|
6
|
+
request: typeof fetch = fetch,
|
|
7
|
+
): Promise<string | null> {
|
|
8
|
+
try {
|
|
9
|
+
const origin = base.replace(/\/+$/, "");
|
|
10
|
+
const options: RequestInit = {
|
|
11
|
+
method: "GET",
|
|
12
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
13
|
+
cache: "no-store",
|
|
14
|
+
redirect: "error",
|
|
15
|
+
signal: AbortSignal.timeout(8_000),
|
|
16
|
+
};
|
|
17
|
+
const [statusResponse, entitlementResponse] = await Promise.all([
|
|
18
|
+
request(`${origin}/api/account/onboarding`, options),
|
|
19
|
+
request(`${origin}/api/me/entitlements`, options),
|
|
20
|
+
]);
|
|
21
|
+
if (!statusResponse.ok || !entitlementResponse.ok) return null;
|
|
22
|
+
const [status, entitlement] = await Promise.all([
|
|
23
|
+
statusResponse.json(),
|
|
24
|
+
entitlementResponse.json(),
|
|
25
|
+
]);
|
|
26
|
+
if (!status || Array.isArray(status) || status.agent_id !== agentId || status.claimed !== true) return null;
|
|
27
|
+
if (!entitlement || Array.isArray(entitlement)) return null;
|
|
28
|
+
const owner = entitlement.owner_account_id;
|
|
29
|
+
return typeof owner === "string" && owner.trim().length > 0 && owner !== agentId ? owner : null;
|
|
30
|
+
} catch {
|
|
31
|
+
// Missing endpoints, failed authentication and unavailable ownership all fail closed.
|
|
32
|
+
// Never retain a previous owner or expose response bodies / credentials in diagnostics.
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/codex/run.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { getBotOwner } from "./owner.ts";
|
|
2
|
+
import { getOnboardingStatus } from "../src/onboarding-status.ts";
|
|
3
|
+
import { GuiChannel } from "./gui-channel.ts";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
1
7
|
import { loadBots } from "./bots-config.ts";
|
|
2
8
|
import type { BridgeConfig } from "./config.ts";
|
|
3
9
|
import { parseArgs } from "node:util";
|
|
@@ -17,20 +23,30 @@ CWD/.agentschat/profile.json > CWD/.codex/config.toml MCP profile > AGENTSCHAT_P
|
|
|
17
23
|
Only the exact CWD is searched. Named profiles live in ~/.agentschat (legacy ~/.agentchat).
|
|
18
24
|
An optional project agent_id must match the selected profile; it cannot replace it.
|
|
19
25
|
Credentials: private profile JSON {agent_id, token}, chmod 600; never put keys in argv.
|
|
20
|
-
Project config fields: profile, agent_id, channels, senders, api_url, ws_url.
|
|
26
|
+
Project config fields: profile, agent_id, channels, senders, api_url, ws_url, permissions.
|
|
27
|
+
--onboarding-status checks authentication/ownership and prints safe claim/chat links; it does not send messages.
|
|
21
28
|
--check validates identity and official app-server initialization without opening chat.
|
|
22
29
|
Live DMs and exact mentions trigger replies; channels/senders restrict this further.
|
|
23
|
-
|
|
30
|
+
Server-verified owner requests use full access by default; other senders stay read-only. Set permissions: "read-only" to disable writes and inherited MCP. No offline message replay.
|
|
24
31
|
State: ~/.agentschat/codex-bridge/<project-server-identity hash>/ (private).
|
|
32
|
+
GUI outbox: --gui-thread THREAD_ID --gui-message-file PATH; --gui-status lists receipts.
|
|
33
|
+
Requires an authorized GUI host to dispatch; enqueue alone does not wake a task.
|
|
25
34
|
See codex/README.md for setup, verification, limitations and recovery.
|
|
26
35
|
`;
|
|
27
36
|
|
|
28
37
|
let codex: AppServer | undefined, bridge: Bridge | undefined, transport: AgentsChatTransport | undefined;
|
|
29
38
|
async function main() {
|
|
30
39
|
const { values } = parseArgs({ options: { "codex-bridge": { type: "boolean" }, cwd: { type: "string" },
|
|
31
|
-
"managed-worker": { type: "boolean" }, registry: { type: "string" }, bot: { type: "string" }, profile: { type: "string" }, "codex-bin": { type: "string" }, check: { type: "boolean" },
|
|
40
|
+
"gui-thread": { type: "string" }, "gui-message-file": { type: "string" }, "gui-status": { type: "boolean" }, "managed-worker": { type: "boolean" }, registry: { type: "string" }, bot: { type: "string" }, profile: { type: "string" }, "codex-bin": { type: "string" }, check: { type: "boolean" }, "onboarding-status": { type: "boolean" },
|
|
32
41
|
help: { type: "boolean", short: "h" } }, strict: true });
|
|
33
42
|
if (values.help) { console.log(HELP); return; }
|
|
43
|
+
if (values["gui-thread"] || values["gui-message-file"] || values["gui-status"]) {
|
|
44
|
+
const channel = new GuiChannel(join(homedir(), ".agentschat/codex-gui-outbox"), values["gui-thread"] ? [values["gui-thread"]] : []);
|
|
45
|
+
if (values["gui-status"]) { console.log(JSON.stringify(channel.list().map(({prompt, ...receipt}) => receipt))); return; }
|
|
46
|
+
if (!values["gui-thread"] || !values["gui-message-file"]) throw new Error("GUI submission requires --gui-thread and --gui-message-file");
|
|
47
|
+
const {prompt, ...receipt} = channel.enqueue(values["gui-thread"], readFileSync(values["gui-message-file"], "utf8"));
|
|
48
|
+
console.log(JSON.stringify(receipt)); return;
|
|
49
|
+
}
|
|
34
50
|
const snapshot = values["managed-worker"] ? await new Promise<BridgeConfig>((resolve, reject) => {
|
|
35
51
|
if (!process.connected) { reject(new Error("Managed worker needs parent IPC")); return; }
|
|
36
52
|
const timer = setTimeout(() => reject(new Error("Parent configuration missing")), 10000);
|
|
@@ -38,11 +54,17 @@ async function main() {
|
|
|
38
54
|
}) : undefined;
|
|
39
55
|
const c = snapshot ?? (values.bot ? loadBots(values.registry).find(b => b.name === values.bot) : resolveConfig({ cwd: values.cwd, profile: values.profile, codexBin: values["codex-bin"] }));
|
|
40
56
|
if (!c) throw new Error("Bot is absent or disabled");
|
|
57
|
+
if (values["onboarding-status"]) {
|
|
58
|
+
const status = await getOnboardingStatus(c.apiUrl, c.agentId, c.token);
|
|
59
|
+
console.log(JSON.stringify({...status, workdir: c.cwd, profile_file: c.profileFile, permissions: c.permissions,
|
|
60
|
+
startup_service: "check manager --status separately", reply_verified: false}));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
41
63
|
console.log(JSON.stringify({ cwd: c.cwd, agent_id: c.agentId, profile: c.profileFile, source: c.source, stateDir: c.stateDir }));
|
|
42
|
-
codex = new AppServer(c.codexBin);
|
|
64
|
+
codex = new AppServer(c.codexBin, undefined, undefined, c.permissions);
|
|
43
65
|
if (values.check) { await codex.start(); console.log("Official app-server initialization: OK (no chat connection or generation)"); codex.close(); return; }
|
|
44
66
|
transport = new AgentsChatTransport(c, m => { bridge!.accept(m); });
|
|
45
|
-
bridge = new Bridge(c, codex, (chat, text) => transport!.send(chat, text), console.error, (chat, active) => transport!.setTyping(chat, active));
|
|
67
|
+
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));
|
|
46
68
|
let stopping = false;
|
|
47
69
|
const stop = async () => { if (stopping) return; stopping = true; if (values["managed-worker"]) { const deadline = setTimeout(() => { try { process.kill(-process.pid, "SIGKILL"); } catch {} }, 20000); deadline.unref(); } bridge?.pause(); transport?.stop(); codex?.close(); await bridge?.stop(); if (process.connected) process.disconnect?.(); };
|
|
48
70
|
codex.onFatal = () => { console.error("Codex backend stopped; pending inbox preserved. Restart the bridge after checking failed entries."); process.exitCode = 1; void stop(); };
|
package/connector/README.md
CHANGED
|
@@ -67,7 +67,7 @@ For the bundled Hermes adaptation skill and profile-specific setup commands, rea
|
|
|
67
67
|
[`skills/onboarding.md` §4](../skills/onboarding.md). Upgrades from older connectors
|
|
68
68
|
must follow the [0.34.0 migration notes](../CHANGELOG.md).
|
|
69
69
|
|
|
70
|
-
**0.
|
|
70
|
+
**0.36.4 is unpublished:** use the [local build procedure](../skills/onboarding.md#local-build-before-runtime-configuration), not an assumed npm release.
|
|
71
71
|
Requires Node ≥22, Bun ≥1.0 for installing/building, and a configured Hermes
|
|
72
72
|
v0.21.1 profile. In a reviewed `AgentsChatProtocol/mcp-plugin` checkout:
|
|
73
73
|
|
package/dist/codex-bots.js
CHANGED
|
@@ -904,6 +904,13 @@ function parse(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
|
|
|
904
904
|
*/
|
|
905
905
|
|
|
906
906
|
// codex/config.ts
|
|
907
|
+
function permissionMode(value) {
|
|
908
|
+
if (value === undefined)
|
|
909
|
+
return "full-access";
|
|
910
|
+
if (value !== "full-access" && value !== "read-only")
|
|
911
|
+
throw new Error("permissions must be full-access or read-only");
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
907
914
|
function readJson(file) {
|
|
908
915
|
try {
|
|
909
916
|
return JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -924,7 +931,7 @@ function resolveConfig(opts, env = process.env, home = homedir()) {
|
|
|
924
931
|
const project = opts.settings ?? (existsSync(configFile) ? readJson(configFile) : {});
|
|
925
932
|
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
926
933
|
throw new Error("Invalid project config");
|
|
927
|
-
const allowed = new Set(["profile", "agent_id", "channels", "senders", "api_url", "ws_url"]);
|
|
934
|
+
const allowed = new Set(["profile", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions"]);
|
|
928
935
|
if (Object.keys(project).some((k) => !allowed.has(k)))
|
|
929
936
|
throw new Error("Unknown project config field (credentials belong in a private profile)");
|
|
930
937
|
for (const k of ["profile", "agent_id", "api_url", "ws_url"])
|
|
@@ -993,6 +1000,7 @@ function resolveConfig(opts, env = process.env, home = homedir()) {
|
|
|
993
1000
|
cwd,
|
|
994
1001
|
profileFile,
|
|
995
1002
|
source,
|
|
1003
|
+
permissions: permissionMode(project.permissions),
|
|
996
1004
|
agentId: profile.agent_id,
|
|
997
1005
|
token: profile.token,
|
|
998
1006
|
apiUrl: canonicalApi,
|
|
@@ -1032,7 +1040,7 @@ function loadBots(file = defaultRegistry(), home = homedir2()) {
|
|
|
1032
1040
|
const names = new Set, identities = new Set;
|
|
1033
1041
|
const bots = [];
|
|
1034
1042
|
for (const bot of doc.bots) {
|
|
1035
|
-
fields(bot, ["name", "profile", "workdir", "enabled", "agent_id", "channels", "senders", "api_url", "ws_url"]);
|
|
1043
|
+
fields(bot, ["name", "profile", "workdir", "enabled", "agent_id", "channels", "senders", "api_url", "ws_url", "permissions"]);
|
|
1036
1044
|
if (!text(bot.name) || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(bot.name) || names.has(bot.name))
|
|
1037
1045
|
throw new Error("Bot names must be unique simple labels");
|
|
1038
1046
|
names.add(bot.name);
|
|
@@ -1048,7 +1056,7 @@ function loadBots(file = defaultRegistry(), home = homedir2()) {
|
|
|
1048
1056
|
mkdirSync(defaultDir, { recursive: true, mode: 448 });
|
|
1049
1057
|
const cwd = realpathSync2(bot.workdir ? path(bot.workdir) : defaultDir);
|
|
1050
1058
|
const settings = {};
|
|
1051
|
-
for (const k of ["agent_id", "channels", "senders", "api_url", "ws_url"])
|
|
1059
|
+
for (const k of ["agent_id", "channels", "senders", "api_url", "ws_url", "permissions"])
|
|
1052
1060
|
if (bot[k] !== undefined)
|
|
1053
1061
|
settings[k] = bot[k];
|
|
1054
1062
|
const config = resolveConfig({ cwd, profile: bot.profile, settings, codexBin: doc.codex_bin }, {}, home);
|