agentschat-mcp 0.29.1 → 0.31.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/README.md +13 -4
- package/connector/README.md +85 -0
- package/connector/auth.ts +103 -0
- package/connector/descriptor.ts +55 -0
- package/connector/normalize.ts +77 -0
- package/connector/run.ts +131 -0
- package/connector/server.ts +190 -0
- package/dist/connector.js +334 -0
- package/dist/server.js +325 -106
- package/package.json +16 -2
- package/src/cli.mjs +47 -12
- package/src/identity.ts +114 -0
- package/src/profile-store.ts +55 -0
- package/src/read-cursor.ts +60 -0
- package/src/server.ts +240 -87
- package/src/terms.ts +67 -0
package/dist/server.js
CHANGED
|
@@ -132,11 +132,72 @@ function matchesJsonType(val, expected) {
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
// src/identity.ts
|
|
137
|
+
function decideIdentity(i) {
|
|
138
|
+
if (i.profileExists)
|
|
139
|
+
return { mode: "profile" };
|
|
140
|
+
if (i.hasToken)
|
|
141
|
+
return { mode: "env-creds" };
|
|
142
|
+
if (i.cliName)
|
|
143
|
+
return { mode: "register", displayName: i.cliName };
|
|
144
|
+
if (i.registerFlag)
|
|
145
|
+
return { mode: "register", displayName: i.fallbackName };
|
|
146
|
+
if (i.source !== "default") {
|
|
147
|
+
const name = i.declaredName ?? i.cliName ?? "(unknown)";
|
|
148
|
+
return {
|
|
149
|
+
mode: "error",
|
|
150
|
+
message: `no profile for "${name}" at ${i.profileFile}.
|
|
151
|
+
` + ` Refusing to auto-register — that creates a real account under a name you did not choose.
|
|
152
|
+
` + ` Use an existing profile: --profile <name> (or AGENTSCHAT_PROFILE=<name>)
|
|
153
|
+
` + ` Register a NEW agent: --name <new-name> (or --register)
|
|
154
|
+
` + ` Authenticate directly: AGENTCHAT_TOKEN=<token>`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
mode: "anonymous",
|
|
159
|
+
reason: `no agent identity configured — running ANONYMOUS (tools are listed; any call needing auth will fail).
|
|
160
|
+
` + ` Refusing to auto-register: it would create a real account and persist its
|
|
161
|
+
` + ` credentials to the shared default profile (${i.profileFile}), which every later
|
|
162
|
+
` + ` identity-less session would then load as its own.
|
|
163
|
+
` + ` To fix: --name <your-agent> register a new agent
|
|
164
|
+
` + ` --profile <name> use an existing profile (or AGENTSCHAT_PROFILE=<name>)
|
|
165
|
+
` + ` AGENTCHAT_TOKEN=<t> authenticate directly`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function shouldMigrateDevToken(i) {
|
|
169
|
+
if (i.hasToken)
|
|
170
|
+
return false;
|
|
171
|
+
if (i.registerFlag)
|
|
172
|
+
return true;
|
|
173
|
+
return i.source !== "default";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/terms.ts
|
|
177
|
+
var TERMS_URL = "https://agents-chat.com/terms";
|
|
178
|
+
var TERMS_VERSION = "2026-05-29";
|
|
179
|
+
function isTruthy(v) {
|
|
180
|
+
return typeof v === "string" && /^(1|true|yes)$/i.test(v.trim());
|
|
181
|
+
}
|
|
182
|
+
function decideTermsConsent(i) {
|
|
183
|
+
if (i.acceptFlag || isTruthy(i.acceptEnv)) {
|
|
184
|
+
return { mode: "accepted", version: TERMS_VERSION };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
mode: "refused",
|
|
188
|
+
message: `registering an agent requires accepting the AgentsChat terms (version ${TERMS_VERSION}).
|
|
189
|
+
` + ` Read them: ${TERMS_URL}
|
|
190
|
+
` + ` Then re-run with: --accept-terms (or AGENTSCHAT_ACCEPT_TERMS=1)
|
|
191
|
+
` + ` Prefer a browser? Register at https://agents-chat.com/join and pass the
|
|
192
|
+
` + ` resulting credentials via --profile <name> or AGENTCHAT_TOKEN=<token>.
|
|
193
|
+
` + ` Refusing to send acceptance you did not give — no account was created.`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
135
196
|
// package.json
|
|
136
197
|
var package_default = {
|
|
137
198
|
name: "agentschat-mcp",
|
|
138
199
|
mcpName: "io.github.swswordholy-tech/agentschat-mcp",
|
|
139
|
-
version: "0.
|
|
200
|
+
version: "0.31.0",
|
|
140
201
|
description: "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
|
|
141
202
|
type: "module",
|
|
142
203
|
bin: {
|
|
@@ -144,6 +205,7 @@ var package_default = {
|
|
|
144
205
|
"agentchat-mcp": "src/cli.mjs"
|
|
145
206
|
},
|
|
146
207
|
engines: {
|
|
208
|
+
node: ">=22",
|
|
147
209
|
bun: ">=1.0.0"
|
|
148
210
|
},
|
|
149
211
|
scripts: {
|
|
@@ -182,10 +244,12 @@ var package_default = {
|
|
|
182
244
|
},
|
|
183
245
|
homepage: "https://agents-chat.com/landing",
|
|
184
246
|
dependencies: {
|
|
185
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
247
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
248
|
+
ws: "^8.21.3"
|
|
186
249
|
},
|
|
187
250
|
devDependencies: {
|
|
188
251
|
"@types/bun": "latest",
|
|
252
|
+
"@types/ws": "^8.18.1",
|
|
189
253
|
typescript: "^5.9.3"
|
|
190
254
|
},
|
|
191
255
|
files: [
|
|
@@ -198,7 +262,18 @@ var package_default = {
|
|
|
198
262
|
"src/reconnect.ts",
|
|
199
263
|
"src/timestamps.ts",
|
|
200
264
|
"src/argcheck.ts",
|
|
265
|
+
"src/identity.ts",
|
|
266
|
+
"src/terms.ts",
|
|
267
|
+
"src/profile-store.ts",
|
|
268
|
+
"src/read-cursor.ts",
|
|
269
|
+
"connector/auth.ts",
|
|
270
|
+
"connector/descriptor.ts",
|
|
271
|
+
"connector/normalize.ts",
|
|
272
|
+
"connector/server.ts",
|
|
273
|
+
"connector/run.ts",
|
|
274
|
+
"connector/README.md",
|
|
201
275
|
"dist/server.js",
|
|
276
|
+
"dist/connector.js",
|
|
202
277
|
"README.md"
|
|
203
278
|
]
|
|
204
279
|
};
|
|
@@ -208,8 +283,74 @@ import {
|
|
|
208
283
|
CallToolRequestSchema,
|
|
209
284
|
ListToolsRequestSchema
|
|
210
285
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
211
|
-
import { readFileSync, existsSync, writeFileSync
|
|
286
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync3, mkdirSync, readdirSync } from "fs";
|
|
212
287
|
import { join, dirname } from "path";
|
|
288
|
+
|
|
289
|
+
// src/profile-store.ts
|
|
290
|
+
import { existsSync, writeFileSync, renameSync, chmodSync, unlinkSync, statSync } from "fs";
|
|
291
|
+
var defaultWarn = (m) => process.stderr.write(m);
|
|
292
|
+
function safeWriteProfile(path, data, warn = defaultWarn) {
|
|
293
|
+
const tmp = path + ".tmp";
|
|
294
|
+
try {
|
|
295
|
+
if (existsSync(tmp))
|
|
296
|
+
unlinkSync(tmp);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
warn(`[agentchat] WARNING: stale ${tmp} could not be removed: ${e}
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
302
|
+
renameSync(tmp, path);
|
|
303
|
+
try {
|
|
304
|
+
chmodSync(path, 384);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
warn(`[agentchat] WARNING: could not chmod ${path} to 0600: ${e}
|
|
307
|
+
`);
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const mode = statSync(path).mode & 511;
|
|
311
|
+
if (mode !== 384) {
|
|
312
|
+
warn(`[agentchat] WARNING: ${path} is mode ${mode.toString(8)}, expected 600 — it holds your agent key. Fix: chmod 600 ${path}
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
} catch (e) {
|
|
316
|
+
warn(`[agentchat] WARNING: could not verify permissions of ${path}: ${e}
|
|
317
|
+
`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/read-cursor.ts
|
|
322
|
+
import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
323
|
+
function loadCursor(file, warn) {
|
|
324
|
+
try {
|
|
325
|
+
return new Map(Object.entries(JSON.parse(readFileSync(file, "utf-8"))));
|
|
326
|
+
} catch (e) {
|
|
327
|
+
if (e?.code !== "ENOENT") {
|
|
328
|
+
warn(`[agentchat] WARNING: could not read ${file} — resetting that state: ${e}
|
|
329
|
+
`);
|
|
330
|
+
}
|
|
331
|
+
return new Map;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function persistCursor(file, cursor, warn) {
|
|
335
|
+
try {
|
|
336
|
+
writeFileSync2(file, JSON.stringify(Object.fromEntries(cursor)));
|
|
337
|
+
return true;
|
|
338
|
+
} catch (e) {
|
|
339
|
+
warn(`[agentchat] WARNING: failed to persist read cursor to ${file}: ${e}
|
|
340
|
+
`);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function flushCursor(state, persist) {
|
|
345
|
+
if (!state.dirty)
|
|
346
|
+
return false;
|
|
347
|
+
const ok = persist();
|
|
348
|
+
if (ok)
|
|
349
|
+
state.dirty = false;
|
|
350
|
+
return ok;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/server.ts
|
|
213
354
|
import { randomUUID } from "crypto";
|
|
214
355
|
|
|
215
356
|
// src/heartbeat.ts
|
|
@@ -293,14 +434,6 @@ if (process.env.AGENTCHAT_NO_PROXY === "1") {
|
|
|
293
434
|
delete process.env.http_proxy;
|
|
294
435
|
delete process.env.https_proxy;
|
|
295
436
|
}
|
|
296
|
-
function safeWriteProfile(path, data) {
|
|
297
|
-
const tmp = path + ".tmp";
|
|
298
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
299
|
-
renameSync(tmp, path);
|
|
300
|
-
try {
|
|
301
|
-
chmodSync(path, 384);
|
|
302
|
-
} catch {}
|
|
303
|
-
}
|
|
304
437
|
function parseArgs() {
|
|
305
438
|
const args = process.argv.slice(2);
|
|
306
439
|
const parsed = {};
|
|
@@ -317,6 +450,10 @@ function parseArgs() {
|
|
|
317
450
|
parsed.caps = args[++i];
|
|
318
451
|
else if (args[i] === "--profile" && args[i + 1])
|
|
319
452
|
parsed.profile = args[++i];
|
|
453
|
+
else if (args[i] === "--register")
|
|
454
|
+
parsed.register = "1";
|
|
455
|
+
else if (args[i] === "--accept-terms")
|
|
456
|
+
parsed.acceptTerms = "1";
|
|
320
457
|
}
|
|
321
458
|
return parsed;
|
|
322
459
|
}
|
|
@@ -327,14 +464,21 @@ Usage: claude mcp add agentschat -- npx agentschat-mcp [options]
|
|
|
327
464
|
claude --dangerously-load-development-channels server:agentschat
|
|
328
465
|
|
|
329
466
|
Options:
|
|
330
|
-
--name <name> Display name (also used as profile name)
|
|
467
|
+
--name <name> Display name (also used as profile name). Registers a NEW agent
|
|
468
|
+
if no profile exists for it.
|
|
331
469
|
--profile <name> Use specific profile (~/.agentschat/<name>.json, falls back to ~/.agentchat)
|
|
470
|
+
--register Explicitly opt in to registering a new agent (implied by --name)
|
|
471
|
+
--accept-terms Accept the terms at https://agents-chat.com/terms. REQUIRED to
|
|
472
|
+
register (or AGENTSCHAT_ACCEPT_TERMS=1); never assumed for you.
|
|
332
473
|
--id <id> Agent ID (default: auto-generated)
|
|
333
474
|
--url <url> Server URL (default: production)
|
|
334
|
-
--token <token> Auth token (
|
|
475
|
+
--token <token> Auth token (skips registration entirely)
|
|
335
476
|
--caps <a,b,c> Capabilities (comma-separated)
|
|
336
477
|
-h, --help Show this help
|
|
337
478
|
|
|
479
|
+
Identity is never created implicitly: with no --name/--profile/AGENTSCHAT_PROFILE and
|
|
480
|
+
no token, the server runs ANONYMOUS (lists tools, but never registers an account).
|
|
481
|
+
|
|
338
482
|
Profiles stored in: ~/.agentschat/ (legacy fallback: ~/.agentchat/)
|
|
339
483
|
Docs: https://github.com/swswordholy-tech/AgentsChatProtocol`);
|
|
340
484
|
process.exit(0);
|
|
@@ -352,7 +496,7 @@ function profileNameToPaths(name) {
|
|
|
352
496
|
}
|
|
353
497
|
function nameToPath(name) {
|
|
354
498
|
const candidates = profileNameToPaths(name);
|
|
355
|
-
return candidates.find((path) =>
|
|
499
|
+
return candidates.find((path) => existsSync2(path)) || candidates[0];
|
|
356
500
|
}
|
|
357
501
|
function listProfileFiles() {
|
|
358
502
|
const seen = new Set;
|
|
@@ -372,18 +516,20 @@ function listProfileFiles() {
|
|
|
372
516
|
}
|
|
373
517
|
return profiles;
|
|
374
518
|
}
|
|
375
|
-
function
|
|
519
|
+
function resolveProfile() {
|
|
376
520
|
if (process.env.AGENTSCHAT_PROFILE)
|
|
377
|
-
return nameToPath(process.env.AGENTSCHAT_PROFILE);
|
|
521
|
+
return { path: nameToPath(process.env.AGENTSCHAT_PROFILE), source: "env", declaredName: process.env.AGENTSCHAT_PROFILE };
|
|
378
522
|
if (process.env.AGENTCHAT_PROFILE)
|
|
379
|
-
return nameToPath(process.env.AGENTCHAT_PROFILE);
|
|
523
|
+
return { path: nameToPath(process.env.AGENTCHAT_PROFILE), source: "legacy-env", declaredName: process.env.AGENTCHAT_PROFILE };
|
|
380
524
|
if (cliArgs.profile)
|
|
381
|
-
return nameToPath(cliArgs.profile);
|
|
525
|
+
return { path: nameToPath(cliArgs.profile), source: "flag-profile", declaredName: cliArgs.profile };
|
|
382
526
|
if (cliArgs.name)
|
|
383
|
-
return nameToPath(cliArgs.name);
|
|
384
|
-
return nameToPath("profile");
|
|
527
|
+
return { path: nameToPath(cliArgs.name), source: "flag-name", declaredName: cliArgs.name };
|
|
528
|
+
return { path: nameToPath("profile"), source: "default" };
|
|
385
529
|
}
|
|
386
|
-
var profileFile =
|
|
530
|
+
var { path: profileFile, source: profileSource, declaredName } = resolveProfile();
|
|
531
|
+
var activeProfileFile = profileFile;
|
|
532
|
+
var anonymousMode = false;
|
|
387
533
|
var profile = {};
|
|
388
534
|
var DEFAULT_SERVER = "https://agents-chat.com";
|
|
389
535
|
var serverUrl = (cliArgs.url || process.env.AGENTCHAT_REST_URL || DEFAULT_SERVER).replace(/\/$/, "");
|
|
@@ -392,20 +538,77 @@ var WS_URL = process.env.AGENTCHAT_URL || (() => {
|
|
|
392
538
|
return base.endsWith("/ws") ? base : base + "/ws";
|
|
393
539
|
})();
|
|
394
540
|
var REST_URL = serverUrl;
|
|
395
|
-
|
|
396
|
-
|
|
541
|
+
var AGENT_ID = "";
|
|
542
|
+
var TOKEN = "";
|
|
543
|
+
var CAPABILITIES = [];
|
|
544
|
+
var nativeFetch = fetch;
|
|
545
|
+
var REST_TIMEOUT_MS = 15000;
|
|
546
|
+
async function apiFetch(input, init = {}, timeoutMs = REST_TIMEOUT_MS) {
|
|
547
|
+
const headers = { ...init.headers };
|
|
548
|
+
if (TOKEN && !("Authorization" in headers))
|
|
549
|
+
headers["Authorization"] = `Bearer ${TOKEN}`;
|
|
550
|
+
const controller = new AbortController;
|
|
551
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
552
|
+
try {
|
|
553
|
+
return await nativeFetch(input, { ...init, headers, signal: init.signal ?? controller.signal });
|
|
554
|
+
} finally {
|
|
555
|
+
clearTimeout(timer);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
var hasToken = !!(cliArgs.token || process.env.AGENTCHAT_TOKEN);
|
|
559
|
+
var identity = decideIdentity({
|
|
560
|
+
profileExists: existsSync2(profileFile),
|
|
561
|
+
source: profileSource,
|
|
562
|
+
profileFile,
|
|
563
|
+
cliName: cliArgs.name,
|
|
564
|
+
declaredName,
|
|
565
|
+
registerFlag: !!cliArgs.register,
|
|
566
|
+
hasToken,
|
|
567
|
+
fallbackName: `Claude-${randomUUID().slice(0, 6)}`
|
|
568
|
+
});
|
|
569
|
+
if (identity.mode === "profile") {
|
|
570
|
+
profile = JSON.parse(readFileSync2(profileFile, "utf-8"));
|
|
397
571
|
process.stderr.write(`[agentchat] Profile loaded: ${profileFile}
|
|
398
572
|
`);
|
|
573
|
+
} else if (identity.mode === "env-creds") {
|
|
574
|
+
activeProfileFile = null;
|
|
575
|
+
process.stderr.write(`[agentchat] Using credentials from environment \u2014 not registering.
|
|
576
|
+
`);
|
|
577
|
+
} else if (identity.mode === "error") {
|
|
578
|
+
process.stderr.write(`[agentchat] ERROR: ${identity.message}
|
|
579
|
+
`);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
} else if (identity.mode === "anonymous") {
|
|
582
|
+
anonymousMode = true;
|
|
583
|
+
activeProfileFile = null;
|
|
584
|
+
process.stderr.write(`[agentchat] ${identity.reason}
|
|
585
|
+
`);
|
|
399
586
|
} else {
|
|
400
|
-
const displayName =
|
|
587
|
+
const displayName = identity.displayName;
|
|
401
588
|
const caps = ["claude-code", "coding", "chat"];
|
|
402
|
-
|
|
589
|
+
const consent = decideTermsConsent({
|
|
590
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
591
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
592
|
+
});
|
|
593
|
+
if (consent.mode === "refused") {
|
|
594
|
+
process.stderr.write(`[agentchat] ERROR: ${consent.message}
|
|
595
|
+
`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
process.stderr.write(`[agentchat] Registering "${displayName}" with server...
|
|
403
599
|
`);
|
|
404
600
|
try {
|
|
405
601
|
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
406
602
|
method: "POST",
|
|
407
603
|
headers: { "Content-Type": "application/json" },
|
|
408
|
-
body: JSON.stringify({
|
|
604
|
+
body: JSON.stringify({
|
|
605
|
+
name: displayName,
|
|
606
|
+
type: "agent",
|
|
607
|
+
capabilities: caps,
|
|
608
|
+
source: "mcp",
|
|
609
|
+
accepted_terms: true,
|
|
610
|
+
terms_version: consent.version
|
|
611
|
+
})
|
|
409
612
|
});
|
|
410
613
|
if (regRes.ok) {
|
|
411
614
|
const data = await regRes.json();
|
|
@@ -423,70 +626,91 @@ if (existsSync(profileFile)) {
|
|
|
423
626
|
process.stderr.write(`[agentchat] Next steps: say hi in the welcome channel (reply tool) \xB7 try \`/loop 30m <prompt>\` in a DM (14-day trial) \xB7 call my_entitlements to see your powers
|
|
424
627
|
`);
|
|
425
628
|
} else {
|
|
426
|
-
|
|
629
|
+
const body = await regRes.text().catch(() => "");
|
|
630
|
+
process.stderr.write(`[agentchat] ERROR: registration refused by ${REST_URL} \u2014 HTTP ${regRes.status} ${body.slice(0, 300)}
|
|
631
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
632
|
+
` + ` If this mentions terms, read ${TERMS_URL} and re-run with --accept-terms.
|
|
427
633
|
`);
|
|
428
|
-
|
|
634
|
+
process.exit(1);
|
|
429
635
|
}
|
|
430
636
|
} catch (e) {
|
|
431
|
-
process.stderr.write(`[agentchat]
|
|
637
|
+
process.stderr.write(`[agentchat] ERROR: Registration failed: ${e}
|
|
638
|
+
` + ` No profile was written and no account exists. Nothing is running.
|
|
432
639
|
`);
|
|
433
|
-
|
|
640
|
+
process.exit(1);
|
|
434
641
|
}
|
|
435
642
|
mkdirSync(dirname(profileFile), { recursive: true });
|
|
436
643
|
safeWriteProfile(profileFile, profile);
|
|
437
644
|
process.stderr.write(`[agentchat] Profile saved: ${profileFile}
|
|
438
645
|
`);
|
|
439
646
|
}
|
|
440
|
-
if (profile.token === "dev-token") {
|
|
441
|
-
process.stderr.write(`[agentchat]
|
|
647
|
+
if (profile.token === "dev-token" && !shouldMigrateDevToken({ source: profileSource, hasToken, registerFlag: !!cliArgs.register })) {
|
|
648
|
+
process.stderr.write(`[agentchat] Profile at ${profileFile} carries a dev-token but no identity was declared \u2014 ` + `refusing to auto-register. Pass --name <name> or --register to create a real agent.
|
|
442
649
|
`);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
profile.agent_id = data.id;
|
|
452
|
-
profile.token = data.key;
|
|
453
|
-
safeWriteProfile(profileFile, profile);
|
|
454
|
-
process.stderr.write(`[agentchat] Migrated! New key saved. ID: ${data.id}
|
|
650
|
+
} else if (profile.token === "dev-token") {
|
|
651
|
+
const migrationConsent = decideTermsConsent({
|
|
652
|
+
acceptFlag: !!cliArgs.acceptTerms,
|
|
653
|
+
acceptEnv: process.env.AGENTSCHAT_ACCEPT_TERMS
|
|
654
|
+
});
|
|
655
|
+
if (migrationConsent.mode === "refused") {
|
|
656
|
+
process.stderr.write(`[agentchat] Profile at ${profileFile} carries a placeholder dev-token and cannot authenticate.
|
|
657
|
+
` + ` Healing it registers a real account: ${migrationConsent.message}
|
|
455
658
|
`);
|
|
456
|
-
|
|
457
|
-
|
|
659
|
+
} else {
|
|
660
|
+
const terms = { accepted_terms: true, terms_version: migrationConsent.version };
|
|
661
|
+
process.stderr.write(`[agentchat] Migrating dev-token profile \u2014 registering with server...
|
|
662
|
+
`);
|
|
663
|
+
try {
|
|
664
|
+
const regRes = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
458
665
|
method: "POST",
|
|
459
666
|
headers: { "Content-Type": "application/json" },
|
|
460
|
-
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [] })
|
|
667
|
+
body: JSON.stringify({ id: profile.agent_id, name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
461
668
|
});
|
|
462
|
-
if (
|
|
463
|
-
const data = await
|
|
669
|
+
if (regRes.ok) {
|
|
670
|
+
const data = await regRes.json();
|
|
464
671
|
profile.agent_id = data.id;
|
|
465
672
|
profile.token = data.key;
|
|
466
673
|
safeWriteProfile(profileFile, profile);
|
|
467
|
-
process.stderr.write(`[agentchat] Migrated
|
|
674
|
+
process.stderr.write(`[agentchat] Migrated! New key saved. ID: ${data.id}
|
|
675
|
+
`);
|
|
676
|
+
} else {
|
|
677
|
+
const regRes2 = await apiFetch(`${REST_URL}/api/account/register`, {
|
|
678
|
+
method: "POST",
|
|
679
|
+
headers: { "Content-Type": "application/json" },
|
|
680
|
+
body: JSON.stringify({ name: profile.display_name, type: "agent", capabilities: profile.capabilities || [], ...terms })
|
|
681
|
+
});
|
|
682
|
+
if (regRes2.ok) {
|
|
683
|
+
const data = await regRes2.json();
|
|
684
|
+
profile.agent_id = data.id;
|
|
685
|
+
profile.token = data.key;
|
|
686
|
+
safeWriteProfile(profileFile, profile);
|
|
687
|
+
process.stderr.write(`[agentchat] Migrated with new ID: ${data.id}
|
|
688
|
+
`);
|
|
689
|
+
} else {
|
|
690
|
+
const body = await regRes2.text().catch(() => "");
|
|
691
|
+
process.stderr.write(`[agentchat] WARNING: dev-token migration refused \u2014 HTTP ${regRes2.status} ${body.slice(0, 200)}
|
|
692
|
+
` + ` This profile still holds a placeholder token; authenticated calls will fail.
|
|
468
693
|
`);
|
|
694
|
+
}
|
|
469
695
|
}
|
|
696
|
+
} catch (e) {
|
|
697
|
+
process.stderr.write(`[agentchat] dev-token migration failed: ${e}
|
|
698
|
+
`);
|
|
470
699
|
}
|
|
471
|
-
} catch {}
|
|
472
|
-
}
|
|
473
|
-
var AGENT_ID = cliArgs.id || process.env.AGENTCHAT_AGENT_ID || profile.agent_id || randomUUID();
|
|
474
|
-
var TOKEN = cliArgs.token || process.env.AGENTCHAT_TOKEN || profile.token || "dev-token";
|
|
475
|
-
var CAPABILITIES = cliArgs.caps?.split(",") || profile.capabilities || ["claude-code", "coding", "chat"];
|
|
476
|
-
var nativeFetch = fetch;
|
|
477
|
-
var REST_TIMEOUT_MS = 15000;
|
|
478
|
-
async function apiFetch(input, init = {}, timeoutMs = REST_TIMEOUT_MS) {
|
|
479
|
-
const headers = { ...init.headers };
|
|
480
|
-
if (TOKEN && !("Authorization" in headers))
|
|
481
|
-
headers["Authorization"] = `Bearer ${TOKEN}`;
|
|
482
|
-
const controller = new AbortController;
|
|
483
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
484
|
-
try {
|
|
485
|
-
return await nativeFetch(input, { ...init, headers, signal: init.signal ?? controller.signal });
|
|
486
|
-
} finally {
|
|
487
|
-
clearTimeout(timer);
|
|
488
700
|
}
|
|
489
701
|
}
|
|
702
|
+
if (profile.token === "dev-token") {
|
|
703
|
+
process.stderr.write(`[agentchat] ERROR: profile ${profileFile} holds a placeholder dev-token, which cannot authenticate.
|
|
704
|
+
` + ` Not starting \u2014 a server that lists tools it cannot use is worse than one that fails.
|
|
705
|
+
` + ` Heal it: --accept-terms (registers a real account for this profile)
|
|
706
|
+
` + ` Or replace: register at https://agents-chat.com/join, then use --profile <name>
|
|
707
|
+
` + ` or AGENTCHAT_TOKEN=<token>
|
|
708
|
+
`);
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
AGENT_ID = cliArgs.id || process.env.AGENTCHAT_AGENT_ID || profile.agent_id || randomUUID();
|
|
712
|
+
TOKEN = cliArgs.token || process.env.AGENTCHAT_TOKEN || profile.token || "dev-token";
|
|
713
|
+
CAPABILITIES = cliArgs.caps?.split(",") || profile.capabilities || ["claude-code", "coding", "chat"];
|
|
490
714
|
if (cliArgs.name && profile.display_name !== cliArgs.name) {
|
|
491
715
|
profile.display_name = cliArgs.name;
|
|
492
716
|
}
|
|
@@ -1748,10 +1972,10 @@ function mimeFromPath(p) {
|
|
|
1748
1972
|
return MEDIA_MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
1749
1973
|
}
|
|
1750
1974
|
async function uploadLocalFile(path) {
|
|
1751
|
-
if (!
|
|
1975
|
+
if (!existsSync2(path))
|
|
1752
1976
|
throw new Error(`file not found: ${path}`);
|
|
1753
1977
|
const mime = mimeFromPath(path);
|
|
1754
|
-
const buf =
|
|
1978
|
+
const buf = readFileSync2(path);
|
|
1755
1979
|
const name = path.split("/").pop() || "upload";
|
|
1756
1980
|
const form = new FormData;
|
|
1757
1981
|
form.append("file", new Blob([new Uint8Array(buf)], { type: mime }), name);
|
|
@@ -2156,7 +2380,7 @@ ${a.body || ""}`;
|
|
|
2156
2380
|
const currentVersion = Number(meta.version ?? 0);
|
|
2157
2381
|
let cachedVersion = null;
|
|
2158
2382
|
try {
|
|
2159
|
-
cachedVersion = Number(JSON.parse(
|
|
2383
|
+
cachedVersion = Number(JSON.parse(readFileSync2(pMeta, "utf8")).version);
|
|
2160
2384
|
} catch {}
|
|
2161
2385
|
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
2162
2386
|
return { content: [{ type: "text", text: `up-to-date: personal skill "${a.name}" v${currentVersion} already at ${pMd} \u2014 no download. Read that file to run it.` }] };
|
|
@@ -2166,8 +2390,8 @@ ${a.body || ""}`;
|
|
|
2166
2390
|
return { content: [{ type: "text", text: `sync_skill (personal): body fetch failed (${bodyR.status})` }], isError: true };
|
|
2167
2391
|
const doc = JSON.parse(await bodyR.text());
|
|
2168
2392
|
mkdirSync(dirname(pMd), { recursive: true });
|
|
2169
|
-
|
|
2170
|
-
|
|
2393
|
+
writeFileSync3(pMd, String(doc?.body_markdown ?? ""));
|
|
2394
|
+
writeFileSync3(pMeta, JSON.stringify({ version: currentVersion, name: a.name, syncedAt: new Date().toISOString() }));
|
|
2171
2395
|
return { content: [{ type: "text", text: `synced personal skill "${a.name}" v${currentVersion} \u2192 ${pMd} (was ${cachedVersion === null ? "missing" : `stale v${cachedVersion}`}). Read that file to run it.` }] };
|
|
2172
2396
|
} catch (e) {
|
|
2173
2397
|
return { content: [{ type: "text", text: `sync_skill (personal) error: ${String(e?.message || e).slice(0, 140)}` }], isError: true };
|
|
@@ -2190,7 +2414,7 @@ ${a.body || ""}`;
|
|
|
2190
2414
|
const currentVersion = Number(meta.version ?? 0);
|
|
2191
2415
|
let cachedVersion = null;
|
|
2192
2416
|
try {
|
|
2193
|
-
cachedVersion = Number(JSON.parse(
|
|
2417
|
+
cachedVersion = Number(JSON.parse(readFileSync2(metaPath, "utf8")).version);
|
|
2194
2418
|
} catch {}
|
|
2195
2419
|
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
2196
2420
|
return { content: [{ type: "text", text: `up-to-date: "${a.doc_id}" v${currentVersion} already at ${mdPath} \u2014 no download. Read that file to run it.` }] };
|
|
@@ -2201,8 +2425,8 @@ ${a.body || ""}`;
|
|
|
2201
2425
|
const doc = JSON.parse(await docR.text());
|
|
2202
2426
|
const body = String(doc?.body_markdown ?? doc?.bodyMarkdown ?? "");
|
|
2203
2427
|
mkdirSync(dirname(mdPath), { recursive: true });
|
|
2204
|
-
|
|
2205
|
-
|
|
2428
|
+
writeFileSync3(mdPath, body);
|
|
2429
|
+
writeFileSync3(metaPath, JSON.stringify({ version: currentVersion, title: doc?.title, doc_id: a.doc_id, chat_id: a.chat_id, syncedAt: new Date().toISOString() }));
|
|
2206
2430
|
const was = cachedVersion === null ? "missing" : `stale v${cachedVersion}`;
|
|
2207
2431
|
return { content: [{ type: "text", text: `synced "${doc?.title || a.doc_id}" v${currentVersion} \u2192 ${mdPath} (was ${was}). Read that file to run it in your runtime.` }] };
|
|
2208
2432
|
} catch (e) {
|
|
@@ -2869,7 +3093,7 @@ ${authLine}
|
|
|
2869
3093
|
${claimedLine}${claimHint ? `
|
|
2870
3094
|
${claimHint}` : ""}
|
|
2871
3095
|
Capabilities: ${CAPABILITIES.join(", ")}
|
|
2872
|
-
Profile file: ${
|
|
3096
|
+
Profile file: ${activeProfileFile ?? (anonymousMode ? "(none \u2014 anonymous, no profile written)" : "(none \u2014 credentials from environment)")}` }] };
|
|
2873
3097
|
}
|
|
2874
3098
|
if (name === "list_channels") {
|
|
2875
3099
|
const { limit = 50 } = args;
|
|
@@ -3042,10 +3266,10 @@ Available profiles:
|
|
|
3042
3266
|
${list}` }] };
|
|
3043
3267
|
}
|
|
3044
3268
|
const targetFile = nameToPath(profile_name);
|
|
3045
|
-
if (!
|
|
3269
|
+
if (!existsSync2(targetFile)) {
|
|
3046
3270
|
return { content: [{ type: "text", text: `Profile "${profile_name}" not found. Available: ${available.join(", ")}` }], isError: true };
|
|
3047
3271
|
}
|
|
3048
|
-
const newProfile = JSON.parse(
|
|
3272
|
+
const newProfile = JSON.parse(readFileSync2(targetFile, "utf-8"));
|
|
3049
3273
|
heartbeat.stop();
|
|
3050
3274
|
if (backfillTimer) {
|
|
3051
3275
|
clearTimeout(backfillTimer);
|
|
@@ -3067,6 +3291,8 @@ ${list}` }] };
|
|
|
3067
3291
|
TOKEN = newProfile.token || "dev-token";
|
|
3068
3292
|
CAPABILITIES = newProfile.capabilities || ["claude-code", "coding", "chat"];
|
|
3069
3293
|
profile = newProfile;
|
|
3294
|
+
activeProfileFile = targetFile;
|
|
3295
|
+
anonymousMode = false;
|
|
3070
3296
|
wsReconnectAttempt = 0;
|
|
3071
3297
|
heartbeat.start();
|
|
3072
3298
|
connectWS();
|
|
@@ -3421,49 +3647,31 @@ ${list}` }] };
|
|
|
3421
3647
|
});
|
|
3422
3648
|
var mentionTsFile = join(configDir, `mention-ts-${AGENT_ID}.json`);
|
|
3423
3649
|
function loadMentionTimestamps() {
|
|
3424
|
-
|
|
3425
|
-
const raw = readFileSync(mentionTsFile, "utf-8");
|
|
3426
|
-
return new Map(Object.entries(JSON.parse(raw)));
|
|
3427
|
-
} catch {
|
|
3428
|
-
return new Map;
|
|
3429
|
-
}
|
|
3650
|
+
return loadCursor(mentionTsFile, safeStderrWrite);
|
|
3430
3651
|
}
|
|
3431
3652
|
function saveMentionTimestamps(m) {
|
|
3432
|
-
|
|
3433
|
-
writeFileSync(mentionTsFile, JSON.stringify(Object.fromEntries(m)));
|
|
3434
|
-
} catch {}
|
|
3653
|
+
persistCursor(mentionTsFile, m, safeStderrWrite);
|
|
3435
3654
|
}
|
|
3436
3655
|
var lastMentionTimestamp = loadMentionTimestamps();
|
|
3437
3656
|
var lastSeenMessageTsFile = join(configDir, `last-seen-msg-ts-${AGENT_ID}.json`);
|
|
3438
3657
|
function loadLastSeenMessageTs() {
|
|
3439
|
-
|
|
3440
|
-
const raw = readFileSync(lastSeenMessageTsFile, "utf-8");
|
|
3441
|
-
return new Map(Object.entries(JSON.parse(raw)));
|
|
3442
|
-
} catch {
|
|
3443
|
-
return new Map;
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
function saveLastSeenMessageTs(m) {
|
|
3447
|
-
try {
|
|
3448
|
-
writeFileSync(lastSeenMessageTsFile, JSON.stringify(Object.fromEntries(m)));
|
|
3449
|
-
} catch {}
|
|
3658
|
+
return loadCursor(lastSeenMessageTsFile, safeStderrWrite);
|
|
3450
3659
|
}
|
|
3451
3660
|
var lastSeenMessageTs = loadLastSeenMessageTs();
|
|
3452
3661
|
var cursorFlushIntervalMs = Math.max(500, Number(process.env.AGENTSCHAT_MCP_CURSOR_FLUSH_MS || 5000));
|
|
3453
|
-
var
|
|
3662
|
+
var cursorState = { dirty: false };
|
|
3454
3663
|
var lastSeenMessageTsTimer = null;
|
|
3455
3664
|
function flushLastSeenMessageTs() {
|
|
3456
|
-
if (!
|
|
3665
|
+
if (!cursorState.dirty)
|
|
3457
3666
|
return;
|
|
3458
|
-
lastSeenMessageTsDirty = false;
|
|
3459
3667
|
if (lastSeenMessageTsTimer) {
|
|
3460
3668
|
clearTimeout(lastSeenMessageTsTimer);
|
|
3461
3669
|
lastSeenMessageTsTimer = null;
|
|
3462
3670
|
}
|
|
3463
|
-
|
|
3671
|
+
flushCursor(cursorState, () => persistCursor(lastSeenMessageTsFile, lastSeenMessageTs, safeStderrWrite));
|
|
3464
3672
|
}
|
|
3465
3673
|
function scheduleLastSeenMessageTsSave() {
|
|
3466
|
-
|
|
3674
|
+
cursorState.dirty = true;
|
|
3467
3675
|
if (lastSeenMessageTsTimer)
|
|
3468
3676
|
return;
|
|
3469
3677
|
lastSeenMessageTsTimer = setTimeout(() => {
|
|
@@ -3943,7 +4151,10 @@ function shutdownFromStdio(reason) {
|
|
|
3943
4151
|
`);
|
|
3944
4152
|
try {
|
|
3945
4153
|
flushLastSeenMessageTs();
|
|
3946
|
-
} catch {
|
|
4154
|
+
} catch (e) {
|
|
4155
|
+
safeStderrWrite(`[agentchat] WARNING: read-cursor flush failed on shutdown: ${e}
|
|
4156
|
+
`);
|
|
4157
|
+
}
|
|
3947
4158
|
try {
|
|
3948
4159
|
heartbeat.stop();
|
|
3949
4160
|
} catch {}
|
|
@@ -3987,7 +4198,10 @@ function installStdioLifecycleGuards() {
|
|
|
3987
4198
|
process.on("beforeExit", () => {
|
|
3988
4199
|
try {
|
|
3989
4200
|
flushLastSeenMessageTs();
|
|
3990
|
-
} catch {
|
|
4201
|
+
} catch (e) {
|
|
4202
|
+
safeStderrWrite(`[agentchat] WARNING: read-cursor fallback flush failed: ${e}
|
|
4203
|
+
`);
|
|
4204
|
+
}
|
|
3991
4205
|
});
|
|
3992
4206
|
}
|
|
3993
4207
|
async function checkVersionStaleness() {
|
|
@@ -4008,7 +4222,12 @@ async function checkVersionStaleness() {
|
|
|
4008
4222
|
}
|
|
4009
4223
|
async function main() {
|
|
4010
4224
|
installStdioLifecycleGuards();
|
|
4011
|
-
|
|
4225
|
+
if (anonymousMode) {
|
|
4226
|
+
process.stderr.write(`[agentchat] Anonymous \u2014 not connecting to the hub.
|
|
4227
|
+
`);
|
|
4228
|
+
} else {
|
|
4229
|
+
connectWS();
|
|
4230
|
+
}
|
|
4012
4231
|
transport = new StdioServerTransport;
|
|
4013
4232
|
await server.connect(transport);
|
|
4014
4233
|
process.stderr.write(`[agentchat] MCP server started (Stdio)
|