agentschat-mcp 0.36.7 → 0.36.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -1
- package/README.md +23 -5
- package/codex/README.md +98 -12
- package/codex/app-server.ts +37 -7
- package/codex/bots-config.ts +3 -2
- package/codex/config.ts +24 -9
- package/codex/manager.ts +1 -1
- package/codex/run.ts +16 -6
- package/codex/runtime-home.ts +65 -3
- package/dist/codex-bots.js +34 -8
- package/dist/codex-bridge.js +169 -27
- package/dist/server.js +111 -45
- package/package.json +2 -1
- package/skills/agentschat-team-lead/SKILL.md +8 -0
- package/skills/onboarding.md +96 -12
- package/src/identity.ts +7 -1
- package/src/profile-selection.ts +45 -0
- package/src/server.ts +32 -25
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ProjectProfileSelection {
|
|
5
|
+
selector: string;
|
|
6
|
+
source: "project-config" | "project-profile";
|
|
7
|
+
agentId?: string;
|
|
8
|
+
apiUrl?: string;
|
|
9
|
+
wsUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Read only this project's binding. Never scan parents or choose a sibling bot. */
|
|
13
|
+
export function projectProfileSelector(cwd: string): ProjectProfileSelection | undefined {
|
|
14
|
+
const config = join(cwd, ".agentschat/config.json");
|
|
15
|
+
let doc: any = {};
|
|
16
|
+
if (existsSync(config)) {
|
|
17
|
+
try { doc = JSON.parse(readFileSync(config, "utf8")); }
|
|
18
|
+
catch { throw new Error("Invalid project .agentschat/config.json; repair it before choosing an identity"); }
|
|
19
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) throw new Error("Invalid project identity configuration");
|
|
20
|
+
for (const field of ["profile", "agent_id", "api_url", "ws_url"]) {
|
|
21
|
+
if (doc[field] !== undefined && (typeof doc[field] !== "string" || !doc[field].trim())) throw new Error(`Invalid project ${field}`);
|
|
22
|
+
}
|
|
23
|
+
for (const [field, protocols] of [["api_url", ["https:", "http:"]], ["ws_url", ["wss:", "ws:"]]] as const) {
|
|
24
|
+
if (doc[field] === undefined) continue;
|
|
25
|
+
let url: URL;
|
|
26
|
+
try { url = new URL(doc[field]); } catch { throw new Error(`Invalid project ${field}`); }
|
|
27
|
+
if (!(protocols as readonly string[]).includes(url.protocol) || url.username || url.password || url.search || url.hash ||
|
|
28
|
+
(!url.protocol.endsWith("s:") && !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)))
|
|
29
|
+
throw new Error(`Invalid project ${field}: require TLS (except loopback) and no credentials/query`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const settings = {
|
|
33
|
+
...(doc.agent_id !== undefined ? { agentId: doc.agent_id as string } : {}),
|
|
34
|
+
...(doc.api_url !== undefined ? { apiUrl: doc.api_url as string } : {}),
|
|
35
|
+
...(doc.ws_url !== undefined ? { wsUrl: doc.ws_url as string } : {}),
|
|
36
|
+
};
|
|
37
|
+
if (doc.profile !== undefined) {
|
|
38
|
+
const value = doc.profile as string;
|
|
39
|
+
return { selector: !isAbsolute(value) && !value.startsWith("~/") && value.includes("/") ? resolve(cwd, value) : value, source: "project-config", ...settings };
|
|
40
|
+
}
|
|
41
|
+
const profile = join(cwd, ".agentschat/profile.json");
|
|
42
|
+
if (existsSync(profile)) return { selector: profile, source: "project-profile", ...settings };
|
|
43
|
+
// A declared agent/server scope must never silently disappear into a global identity.
|
|
44
|
+
if (Object.keys(settings).length) throw new Error("Project identity/server settings require a profile binding");
|
|
45
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { computeReconnectDelay } from "./reconnect.ts";
|
|
|
43
43
|
import { normalizeTimestampForCursor } from "./timestamps.ts";
|
|
44
44
|
import { validateToolArgs } from "./argcheck.ts";
|
|
45
45
|
import { decideIdentity, shouldMigrateDevToken, validateIdentityProfile } from "./identity.ts";
|
|
46
|
+
import { projectProfileSelector } from "./profile-selection.ts";
|
|
46
47
|
import type { ProfileSource } from "./identity.ts";
|
|
47
48
|
import {
|
|
48
49
|
decideGrokBind,
|
|
@@ -123,7 +124,8 @@ Setup guide for every supported host:
|
|
|
123
124
|
Read skills/onboarding.md relative to this installed package root.
|
|
124
125
|
Online: https://unpkg.com/agentschat-mcp@latest/skills/onboarding.md
|
|
125
126
|
Start with: npx -y agentschat-mcp@latest --help
|
|
126
|
-
The guide selects Claude Code, Codex, OpenClaw, Hermes, Grok Bot,
|
|
127
|
+
The guide selects Claude Code, Codex, OpenClaw, Hermes, Grok Bot, URL wake,
|
|
128
|
+
or the separately deployed dot remote MCP + Events integration.
|
|
127
129
|
Help only prints instructions; follow the guide and verify a real reply.
|
|
128
130
|
|
|
129
131
|
Options:
|
|
@@ -167,7 +169,7 @@ Grok multi-bot identity bind (Cursor / Grok Bot, no --profile):
|
|
|
167
169
|
Hermes relay connector (no Hermes patch): run with --connector. See --connector --help.
|
|
168
170
|
|
|
169
171
|
Identity is never created implicitly. Without explicit selectors, an existing
|
|
170
|
-
|
|
172
|
+
project/runtime binding or default profile is loaded; only when none exists and no
|
|
171
173
|
credentials are supplied does the server run ANONYMOUS (lists tools, no account).
|
|
172
174
|
|
|
173
175
|
Profiles stored in: ~/.agentschat/ (legacy fallback: ~/.agentchat/)
|
|
@@ -175,34 +177,32 @@ Docs: https://github.com/swswordholy-tech/AgentsChatProtocol`);
|
|
|
175
177
|
process.exit(0);
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// 2. AGENTCHAT_PROFILE env var (legacy singular)
|
|
181
|
-
// 3. --profile <name> CLI arg
|
|
182
|
-
// 4. --name <name> CLI arg (also used as profile name)
|
|
183
|
-
// 5. Grok bind: CURSOR_CONVERSATION_ID → ~/.agentschat/grok-binds.json
|
|
184
|
-
// (skipped if an explicit token is set; does NOT imply AGENTCHAT_WAKE_MODE)
|
|
185
|
-
// 6. default ~/.agentschat/profile.json, falling back to ~/.agentchat/profile.json
|
|
180
|
+
// Explicit CLI selection > project binding > runtime binding > environment > default.
|
|
181
|
+
// Onboarding resolves ambiguous unbound candidates with the user; never scan-and-pick.
|
|
186
182
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
187
183
|
const configDir = join(homeDir, ".agentschat");
|
|
188
184
|
const legacyConfigDir = join(homeDir, ".agentchat");
|
|
189
185
|
const profileDirs = [configDir, legacyConfigDir];
|
|
190
186
|
|
|
191
187
|
function profileNameToPaths(name: string): string[] {
|
|
188
|
+
if (name.startsWith("~/")) return [join(homeDir, name.slice(2))];
|
|
192
189
|
if (name.includes("/") || name.includes("\\")) return [name]; // explicit path
|
|
190
|
+
name = name.replace(/\.json$/, "");
|
|
193
191
|
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
194
192
|
return profileDirs.map((dir) => join(dir, `${safeName}.json`));
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
function nameToPath(name: string): string {
|
|
198
196
|
const candidates = profileNameToPaths(name);
|
|
197
|
+
const nested = join(configDir, "profiles", name.replace(/\.json$/, "") + ".json");
|
|
198
|
+
if (!name.includes("/") && !name.includes("\\") && existsSync(nested)) return nested;
|
|
199
199
|
return candidates.find((path) => existsSync(path)) || candidates[0];
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
function listProfileFiles(): Array<{ name: string; path: string }> {
|
|
203
203
|
const seen = new Set<string>();
|
|
204
204
|
const profiles: Array<{ name: string; path: string }> = [];
|
|
205
|
-
for (const dir of profileDirs) {
|
|
205
|
+
for (const dir of [join(configDir, "profiles"), ...profileDirs]) {
|
|
206
206
|
let files: string[] = [];
|
|
207
207
|
try { files = readdirSync(dir).filter((f: string) => f.endsWith(".json")); } catch {}
|
|
208
208
|
for (const file of files) {
|
|
@@ -216,19 +216,18 @@ function listProfileFiles(): Array<{ name: string; path: string }> {
|
|
|
216
216
|
return profiles;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function resolveProfile(): { path: string; source: ProfileSource; declaredName?: string } {
|
|
220
|
-
// 1. AGENTSCHAT_PROFILE env var (supports both name and full path)
|
|
221
|
-
if (process.env.AGENTSCHAT_PROFILE)
|
|
222
|
-
return { path: nameToPath(process.env.AGENTSCHAT_PROFILE), source: "env", declaredName: process.env.AGENTSCHAT_PROFILE };
|
|
223
|
-
// 2. AGENTCHAT_PROFILE env var (legacy alias)
|
|
224
|
-
if (process.env.AGENTCHAT_PROFILE)
|
|
225
|
-
return { path: nameToPath(process.env.AGENTCHAT_PROFILE), source: "legacy-env", declaredName: process.env.AGENTCHAT_PROFILE };
|
|
226
|
-
// 3. --profile <name>
|
|
219
|
+
function resolveProfile(): { path: string; source: ProfileSource; declaredName?: string; agentId?: string; apiUrl?: string; wsUrl?: string } {
|
|
227
220
|
if (cliArgs.profile) return { path: nameToPath(cliArgs.profile), source: "flag-profile", declaredName: cliArgs.profile };
|
|
228
|
-
// 4. --name <name>
|
|
229
221
|
if (cliArgs.name) return { path: nameToPath(cliArgs.name), source: "flag-name", declaredName: cliArgs.name };
|
|
230
222
|
|
|
231
|
-
//
|
|
223
|
+
// Paired direct credentials are a separate explicit authentication path.
|
|
224
|
+
// Do not borrow an unrelated project's identity when credentials are supplied.
|
|
225
|
+
if (!cliArgs.token && !process.env.AGENTCHAT_TOKEN) {
|
|
226
|
+
const project = projectProfileSelector(process.cwd());
|
|
227
|
+
if (project) return { ...project, path: nameToPath(project.selector), declaredName: project.selector };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Grok outbound bind. Explicit CLI/project selections already returned above. An
|
|
232
231
|
// explicit --token/AGENTCHAT_TOKEN also wins: do not steal identity from
|
|
233
232
|
// operator-supplied creds. CURSOR_CONVERSATION_ID unset → skip entirely
|
|
234
233
|
// (Claude Code / Hermes: zero change). A hit with a missing profile file
|
|
@@ -271,11 +270,16 @@ function resolveProfile(): { path: string; source: ProfileSource; declaredName?:
|
|
|
271
270
|
process.stderr.write(`[agentchat] no grok-bind matched CURSOR_CONVERSATION_ID=${grok.conversationId}\n`);
|
|
272
271
|
}
|
|
273
272
|
|
|
274
|
-
|
|
273
|
+
if (process.env.AGENTSCHAT_PROFILE)
|
|
274
|
+
return { path: nameToPath(process.env.AGENTSCHAT_PROFILE), source: "env", declaredName: process.env.AGENTSCHAT_PROFILE };
|
|
275
|
+
if (process.env.AGENTCHAT_PROFILE)
|
|
276
|
+
return { path: nameToPath(process.env.AGENTCHAT_PROFILE), source: "legacy-env", declaredName: process.env.AGENTCHAT_PROFILE };
|
|
277
|
+
|
|
278
|
+
// Default — nothing was declared. NOT a licence to invent an identity.
|
|
275
279
|
return { path: nameToPath("profile"), source: "default" };
|
|
276
280
|
}
|
|
277
281
|
|
|
278
|
-
const { path: profileFile, source: profileSource, declaredName } = resolveProfile();
|
|
282
|
+
const { path: profileFile, source: profileSource, declaredName, agentId: projectAgentId, apiUrl: projectApiUrl, wsUrl: projectWsUrl } = resolveProfile();
|
|
279
283
|
/**
|
|
280
284
|
* The profile file currently in effect. Mutable because `switch_profile` swaps
|
|
281
285
|
* identity at runtime — `whoami` must report the live one, not the boot-time one.
|
|
@@ -287,8 +291,8 @@ let anonymousMode = false;
|
|
|
287
291
|
let profile: any = {};
|
|
288
292
|
|
|
289
293
|
const DEFAULT_SERVER = "https://agents-chat.com";
|
|
290
|
-
const serverUrl = (cliArgs.url || process.env.AGENTCHAT_REST_URL || DEFAULT_SERVER).replace(/\/$/, "");
|
|
291
|
-
const WS_URL = process.env.AGENTCHAT_URL || (() => {
|
|
294
|
+
const serverUrl = (cliArgs.url || projectApiUrl || process.env.AGENTCHAT_REST_URL || DEFAULT_SERVER).replace(/\/$/, "");
|
|
295
|
+
const WS_URL = (!cliArgs.url && projectWsUrl) || (!projectApiUrl && process.env.AGENTCHAT_URL) || (() => {
|
|
292
296
|
const base = serverUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
293
297
|
return base.endsWith("/ws") ? base : base + "/ws";
|
|
294
298
|
})();
|
|
@@ -363,6 +367,9 @@ function readIdentityProfile(file: string): any {
|
|
|
363
367
|
if (identity.mode === "profile") {
|
|
364
368
|
try {
|
|
365
369
|
profile = readIdentityProfile(profileFile);
|
|
370
|
+
if (projectAgentId !== undefined && projectAgentId !== (cliArgs.id ?? process.env.AGENTCHAT_AGENT_ID ?? profile?.agent_id)) {
|
|
371
|
+
throw new Error("Project agent_id does not match selected profile");
|
|
372
|
+
}
|
|
366
373
|
validateIdentityProfile(profile && !Array.isArray(profile) ? {
|
|
367
374
|
...profile,
|
|
368
375
|
agent_id: cliArgs.id ?? process.env.AGENTCHAT_AGENT_ID ?? profile.agent_id,
|