agentschat-mcp 0.36.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/dist/server.js CHANGED
@@ -586,12 +586,13 @@ async function fireGrokWake(msg, cfg) {
586
586
  var package_default = {
587
587
  name: "agentschat-mcp",
588
588
  mcpName: "io.github.swswordholy-tech/agentschat-mcp",
589
- version: "0.36.0",
589
+ version: "0.36.4",
590
590
  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.",
591
591
  type: "module",
592
592
  bin: {
593
593
  "agentschat-mcp": "src/cli.mjs",
594
- "agentchat-mcp": "src/cli.mjs"
594
+ "agentchat-mcp": "src/cli.mjs",
595
+ "agentschat-ensure-grok-wakes": "scripts/ensure-grok-wakes.mjs"
595
596
  },
596
597
  engines: {
597
598
  node: ">=22",
@@ -672,11 +673,17 @@ var package_default = {
672
673
  "skills/onboarding.md",
673
674
  "dist/server.js",
674
675
  "dist/connector.js",
676
+ "scripts/ensure-grok-wakes.mjs",
675
677
  "README.md",
676
678
  "CHANGELOG.md",
677
679
  "codex/",
678
680
  "dist/codex-bridge.js",
679
- "dist/codex-bots.js"
681
+ "dist/codex-bots.js",
682
+ "skills/grok-wake-keepalive.md",
683
+ "skills/url-wake-keepalive.md",
684
+ "scripts/example-url-wake-receiver.mjs",
685
+ "scripts/example-url-wake-ensure.sh",
686
+ "skills/hermes-host-keepalive.md"
680
687
  ]
681
688
  };
682
689
 
@@ -875,11 +882,18 @@ function parseArgs() {
875
882
  }
876
883
  var cliArgs = parseArgs();
877
884
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
878
- console.log(`agentschat-mcp \u2014 AgentsChat MCP Plugin for Claude Code
885
+ console.log(`agentschat-mcp \u2014 AgentsChat MCP and host bridges
879
886
 
880
- Usage: claude mcp add agentschat -- npx agentschat-mcp [options]
887
+ Usage: claude mcp add agentschat -- npx -y agentschat-mcp@latest [options]
881
888
  claude --dangerously-load-development-channels server:agentschat
882
889
 
890
+ Setup guide for every supported host:
891
+ Read skills/onboarding.md relative to this installed package root.
892
+ Online: https://unpkg.com/agentschat-mcp@latest/skills/onboarding.md
893
+ Start with: npx -y agentschat-mcp@latest --help
894
+ The guide selects Claude Code, Codex, OpenClaw, Hermes, Grok Bot, or URL wake.
895
+ Help only prints instructions; follow the guide and verify a real reply.
896
+
883
897
  Options:
884
898
  --name <name> Display name (also used as profile name). Registers a NEW agent
885
899
  if no profile exists for it.
@@ -893,6 +907,7 @@ Options:
893
907
  --token <token> Auth token (or AGENTCHAT_TOKEN); requires its paired ID or an
894
908
  explicitly selected profile. Skips registration entirely.
895
909
  --caps <a,b,c> Capabilities (comma-separated)
910
+ --supervise Respawn on crash (or AGENTCHAT_WAKE_SUPERVISE=1); for wake daemons
896
911
  -h, --help Show this help
897
912
 
898
913
  Wake a host that has no channel-notification surface (Grok Bot, generic MCP clients):
@@ -904,6 +919,11 @@ Wake a host that has no channel-notification surface (Grok Bot, generic MCP clie
904
919
  AGENTCHAT_GROK_GATEWAY path to gateway.json (default: first existing of
905
920
  ~/.grok/gateway.json, /home/box/sand-data/gateway.json)
906
921
  AGENTCHAT_GROK_AGENT_ID the Grok gateway agent uuid to wake (1:1 binding)
922
+ --supervise / AGENTCHAT_WAKE_SUPERVISE=1
923
+ Parent respawns this process after crash (while the machine is up).
924
+ scripts/ensure-grok-wakes.mjs
925
+ Start any missing Grok wake daemons from grok-binds.json
926
+ (node scripts/ensure-grok-wakes.mjs).
907
927
 
908
928
  Grok multi-bot identity bind (Cursor / Grok Bot, no --profile):
909
929
  ~/.agentschat/grok-binds.json maps CURSOR_CONVERSATION_ID (Grok uuid) \u2192 profile name.
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
3
  "mcpName": "io.github.swswordholy-tech/agentschat-mcp",
4
- "version": "0.36.0",
5
- "description": "Connect Claude Code to AgentsChat \u2014 AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
4
+ "version": "0.36.4",
5
+ "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.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "agentschat-mcp": "src/cli.mjs",
9
- "agentchat-mcp": "src/cli.mjs"
9
+ "agentchat-mcp": "src/cli.mjs",
10
+ "agentschat-ensure-grok-wakes": "scripts/ensure-grok-wakes.mjs"
10
11
  },
11
12
  "engines": {
12
13
  "node": ">=22",
@@ -87,10 +88,16 @@
87
88
  "skills/onboarding.md",
88
89
  "dist/server.js",
89
90
  "dist/connector.js",
91
+ "scripts/ensure-grok-wakes.mjs",
90
92
  "README.md",
91
93
  "CHANGELOG.md",
92
94
  "codex/",
93
95
  "dist/codex-bridge.js",
94
- "dist/codex-bots.js"
96
+ "dist/codex-bots.js",
97
+ "skills/grok-wake-keepalive.md",
98
+ "skills/url-wake-keepalive.md",
99
+ "scripts/example-url-wake-receiver.mjs",
100
+ "scripts/example-url-wake-ensure.sh",
101
+ "skills/hermes-host-keepalive.md"
95
102
  ]
96
103
  }
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Idempotent reconcile: ensure each Grok AgentsChat inbound wake daemon is up,
4
+ * then prune orphans not present in the binds map.
5
+ *
6
+ * Reads uuid → profile from AGENTCHAT_GROK_BINDS or ~/.agentschat/grok-binds.json
7
+ * (legacy ~/.agentchat/). For each entry, if no live process has
8
+ * AGENTCHAT_WAKE_MODE=grok AND (AGENTCHAT_GROK_AGENT_ID=<uuid> OR matching
9
+ * --profile), starts one detached:
10
+ *
11
+ * AGENTCHAT_WAKE_MODE=grok AGENTCHAT_GROK_AGENT_ID=<uuid> AGENTCHAT_NO_PROXY=1 \
12
+ * <agentschat-mcp> --profile <profileName>
13
+ *
14
+ * After starts (or when binds are empty), prunes any AGENTCHAT_WAKE_MODE=grok
15
+ * process whose AGENTCHAT_GROK_AGENT_ID is not a binds key AND whose --profile
16
+ * is not a binds value. Never kills processes without WAKE_MODE=grok (outbound
17
+ * Cursor MCP).
18
+ *
19
+ * Prints already-up|started|stopped|failed lines. Exit 0 if no failures.
20
+ * Never prints tokens.
21
+ */
22
+ import { spawn, spawnSync } from "node:child_process";
23
+ import { existsSync, openSync, readFileSync, readdirSync, closeSync } from "node:fs";
24
+ import { homedir } from "node:os";
25
+ import { dirname, join, resolve } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+
28
+ export const DEFAULT_BINDS_FILENAME = "grok-binds.json";
29
+ export const BINDS_ENV = "AGENTCHAT_GROK_BINDS";
30
+ export const BIN_ENV = "AGENTSCHAT_MCP_BIN";
31
+ export const LOG_DIR_ENV = "AGENTCHAT_WAKE_LOG_DIR";
32
+
33
+ /** @param {unknown} raw */
34
+ export function parseBinds(raw) {
35
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
36
+ /** @type {Record<string, string>} */
37
+ const out = {};
38
+ for (const [k, v] of Object.entries(/** @type {Record<string, unknown>} */ (raw))) {
39
+ if (typeof v !== "string") continue;
40
+ const name = v.trim();
41
+ const key = k.trim();
42
+ if (!name || !key) continue;
43
+ out[key] = name;
44
+ }
45
+ return out;
46
+ }
47
+
48
+ /** @param {string} text */
49
+ export function parseBindsText(text) {
50
+ try {
51
+ return { binds: parseBinds(JSON.parse(text)), malformed: false };
52
+ } catch {
53
+ return { binds: {}, malformed: true };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Resolve binds file path. Prefer AGENTCHAT_GROK_BINDS; else first existing of
59
+ * ~/.agentschat/grok-binds.json, ~/.agentchat/grok-binds.json.
60
+ * @param {{ home?: string, envOverride?: string }} [opts]
61
+ */
62
+ export function resolveBindsPath(opts = {}) {
63
+ const home = opts.home ?? process.env.HOME ?? homedir();
64
+ if (opts.envOverride && opts.envOverride.length > 0) return opts.envOverride;
65
+ const candidates = [
66
+ join(home, ".agentschat", DEFAULT_BINDS_FILENAME),
67
+ join(home, ".agentchat", DEFAULT_BINDS_FILENAME),
68
+ ];
69
+ return candidates.find((p) => existsSync(p)) ?? candidates[0];
70
+ }
71
+
72
+ /**
73
+ * Locate agentschat-mcp entry: AGENTSCHAT_MCP_BIN, else sibling ../src/cli.mjs,
74
+ * else `agentschat-mcp` on PATH.
75
+ * @param {{ scriptDir?: string, envBin?: string, pathEnv?: string }} [opts]
76
+ */
77
+ export function resolveMcpBin(opts = {}) {
78
+ if (opts.envBin && opts.envBin.length > 0) return opts.envBin;
79
+ const scriptDir = opts.scriptDir ?? dirname(fileURLToPath(import.meta.url));
80
+ const sibling = resolve(scriptDir, "../src/cli.mjs");
81
+ if (existsSync(sibling)) return sibling;
82
+ const which = spawnSync("which", ["agentschat-mcp"], {
83
+ encoding: "utf8",
84
+ env: { PATH: opts.pathEnv ?? process.env.PATH ?? "" },
85
+ });
86
+ const found = (which.stdout || "").trim();
87
+ if (found) return found;
88
+ return "agentschat-mcp";
89
+ }
90
+
91
+ /**
92
+ * @param {string} environNullSep null-separated /proc environ
93
+ * @param {string} cmdlineNullSep null-separated /proc cmdline
94
+ * @param {string} uuid
95
+ * @param {string} profileName
96
+ */
97
+ export function isLiveWakeDaemon(environNullSep, cmdlineNullSep, uuid, profileName) {
98
+ const envLines = environNullSep.split("\0");
99
+ if (!envLines.includes("AGENTCHAT_WAKE_MODE=grok")) return false;
100
+ if (uuid && envLines.includes(`AGENTCHAT_GROK_AGENT_ID=${uuid}`)) return true;
101
+ const args = cmdlineNullSep.split("\0").filter(Boolean);
102
+ for (let i = 0; i < args.length; i++) {
103
+ if (args[i] === "--profile" && args[i + 1] === profileName) return true;
104
+ if (args[i] === `--profile=${profileName}`) return true;
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /** @returns {Array<{ pid: number, environ: string, cmdline: string }>} */
110
+ export function listProcSnapshots() {
111
+ /** @type {Array<{ pid: number, environ: string, cmdline: string }>} */
112
+ const out = [];
113
+ let entries;
114
+ try {
115
+ entries = readdirSync("/proc");
116
+ } catch {
117
+ return out;
118
+ }
119
+ for (const ent of entries) {
120
+ if (!/^\d+$/.test(ent)) continue;
121
+ const pid = Number(ent);
122
+ let environ = "";
123
+ let cmdline = "";
124
+ try {
125
+ environ = readFileSync(`/proc/${pid}/environ`, "utf8");
126
+ } catch {
127
+ continue;
128
+ }
129
+ try {
130
+ cmdline = readFileSync(`/proc/${pid}/cmdline`, "utf8");
131
+ } catch {
132
+ continue;
133
+ }
134
+ out.push({ pid, environ, cmdline });
135
+ }
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * @param {string} uuid
141
+ * @param {string} profileName
142
+ * @param {ReturnType<typeof listProcSnapshots>} [snapshots]
143
+ */
144
+ export function findLiveWake(uuid, profileName, snapshots) {
145
+ const snaps = snapshots ?? listProcSnapshots();
146
+ for (const s of snaps) {
147
+ if (isLiveWakeDaemon(s.environ, s.cmdline, uuid, profileName)) return s.pid;
148
+ }
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * List PIDs of processes with AGENTCHAT_WAKE_MODE=grok.
154
+ * @param {ReturnType<typeof listProcSnapshots>} [snapshots]
155
+ * @returns {number[]}
156
+ */
157
+ export function listGrokWakePids(snapshots) {
158
+ const snaps = snapshots ?? listProcSnapshots();
159
+ /** @type {number[]} */
160
+ const out = [];
161
+ for (const s of snaps) {
162
+ if (s.environ.split("\0").includes("AGENTCHAT_WAKE_MODE=grok")) out.push(s.pid);
163
+ }
164
+ return out;
165
+ }
166
+
167
+ /**
168
+ * Extract --profile from null-separated cmdline.
169
+ * @param {string} cmdlineNullSep
170
+ */
171
+ export function profileFromCmdline(cmdlineNullSep) {
172
+ const args = cmdlineNullSep.split("\0").filter(Boolean);
173
+ for (let i = 0; i < args.length; i++) {
174
+ if (args[i] === "--profile" && args[i + 1]) return args[i + 1];
175
+ if (args[i].startsWith("--profile=")) return args[i].slice("--profile=".length);
176
+ }
177
+ return "";
178
+ }
179
+
180
+ /**
181
+ * Extract AGENTCHAT_GROK_AGENT_ID from null-separated environ.
182
+ * @param {string} environNullSep
183
+ */
184
+ export function agentIdFromEnviron(environNullSep) {
185
+ for (const line of environNullSep.split("\0")) {
186
+ if (line.startsWith("AGENTCHAT_GROK_AGENT_ID=")) {
187
+ return line.slice("AGENTCHAT_GROK_AGENT_ID=".length);
188
+ }
189
+ }
190
+ return "";
191
+ }
192
+
193
+ /**
194
+ * True when a WAKE_MODE=grok process should be pruned (not represented in binds).
195
+ * Keep if agent id is a binds key OR --profile is a binds value.
196
+ * Processes without WAKE_MODE=grok must never be pruned (caller should filter).
197
+ * @param {string} environNullSep
198
+ * @param {string} cmdlineNullSep
199
+ * @param {Record<string, string>} binds
200
+ */
201
+ export function shouldPruneWake(environNullSep, cmdlineNullSep, binds) {
202
+ const envLines = environNullSep.split("\0");
203
+ if (!envLines.includes("AGENTCHAT_WAKE_MODE=grok")) return false;
204
+ const uuids = new Set(Object.keys(binds));
205
+ const profiles = new Set(Object.values(binds));
206
+ const agentId = agentIdFromEnviron(environNullSep);
207
+ const profile = profileFromCmdline(cmdlineNullSep);
208
+ if (agentId && uuids.has(agentId)) return false;
209
+ if (profile && profiles.has(profile)) return false;
210
+ return true;
211
+ }
212
+
213
+ /**
214
+ * Best-effort stop a wake pid (and obvious parent setsid shell).
215
+ * @param {number} pid
216
+ * @returns {boolean}
217
+ */
218
+ export function stopWakePid(pid) {
219
+ let parentHint = null;
220
+ try {
221
+ const status = readFileSync(`/proc/${pid}/status`, "utf8");
222
+ const m = status.match(/^PPid:\s+(\d+)/m);
223
+ if (m) parentHint = Number(m[1]);
224
+ } catch {
225
+ /* gone */
226
+ }
227
+ try {
228
+ process.kill(pid, "SIGTERM");
229
+ } catch {
230
+ return false;
231
+ }
232
+ if (parentHint && parentHint > 1) {
233
+ try {
234
+ const pcmd = readFileSync(`/proc/${parentHint}/cmdline`, "utf8");
235
+ if (pcmd.includes("agentschat-mcp") || pcmd.includes("tail -f /dev/null")) {
236
+ try {
237
+ process.kill(parentHint, "SIGTERM");
238
+ } catch {
239
+ /* ignore */
240
+ }
241
+ }
242
+ } catch {
243
+ /* ignore */
244
+ }
245
+ }
246
+ return true;
247
+ }
248
+
249
+ /**
250
+ * Start a detached wake daemon. Returns true on spawn success (best-effort).
251
+ * @param {{ bin: string, uuid: string, profileName: string, logDir: string, env?: NodeJS.ProcessEnv }} opts
252
+ */
253
+ export function startWakeDaemon(opts) {
254
+ const logDir = opts.logDir;
255
+ try {
256
+ spawnSync("mkdir", ["-p", logDir], { stdio: "ignore" });
257
+ } catch {
258
+ /* best-effort */
259
+ }
260
+ const logPath = join(logDir, `agentschat-wake-${safeLogName(opts.profileName)}.log`);
261
+ let logFd;
262
+ try {
263
+ logFd = openSync(logPath, "a");
264
+ } catch {
265
+ return false;
266
+ }
267
+ const env = {
268
+ ...process.env,
269
+ ...(opts.env ?? {}),
270
+ AGENTCHAT_WAKE_MODE: "grok",
271
+ AGENTCHAT_GROK_AGENT_ID: opts.uuid,
272
+ AGENTCHAT_NO_PROXY: "1",
273
+ };
274
+ delete env.AGENTCHAT_TOKEN;
275
+ delete env.AGENTCHAT_AGENT_ID;
276
+
277
+ const bin = opts.bin;
278
+ const useNode = bin.endsWith(".mjs") || bin.endsWith(".js");
279
+ const command = useNode ? process.execPath : bin;
280
+ const args = useNode ? [bin, "--profile", opts.profileName] : ["--profile", opts.profileName];
281
+
282
+ try {
283
+ const child = spawn(command, args, {
284
+ env,
285
+ detached: true,
286
+ stdio: ["ignore", logFd, logFd],
287
+ cwd: process.cwd(),
288
+ });
289
+ child.unref();
290
+ closeSync(logFd);
291
+ return child.pid != null;
292
+ } catch {
293
+ try {
294
+ closeSync(logFd);
295
+ } catch {
296
+ /* ignore */
297
+ }
298
+ return false;
299
+ }
300
+ }
301
+
302
+ /** @param {string} name */
303
+ export function safeLogName(name) {
304
+ return name.replace(/[^a-zA-Z0-9_-]+/g, "_") || "wake";
305
+ }
306
+
307
+ /** @param {number} ms */
308
+ function sleep(ms) {
309
+ return new Promise((r) => setTimeout(r, ms));
310
+ }
311
+
312
+ async function main() {
313
+ const envOverride = process.env[BINDS_ENV];
314
+ const bindsPath = resolveBindsPath({ envOverride });
315
+ /** @type {Record<string, string>} */
316
+ let binds = {};
317
+
318
+ if (envOverride && !existsSync(bindsPath)) {
319
+ console.error(`failed binds-missing path=${bindsPath}`);
320
+ process.exit(1);
321
+ }
322
+
323
+ if (existsSync(bindsPath)) {
324
+ let text;
325
+ try {
326
+ text = readFileSync(bindsPath, "utf8");
327
+ } catch {
328
+ console.error(`failed binds-read path=${bindsPath}`);
329
+ process.exit(1);
330
+ }
331
+ const parsed = parseBindsText(text);
332
+ if (parsed.malformed) {
333
+ console.error(`failed binds-malformed path=${bindsPath}`);
334
+ process.exit(1);
335
+ }
336
+ binds = parsed.binds;
337
+ } else {
338
+ console.log(`no-binds path=${bindsPath}`);
339
+ }
340
+
341
+ const entries = Object.entries(binds);
342
+ const bin = resolveMcpBin({ envBin: process.env[BIN_ENV] });
343
+ const logDir = process.env[LOG_DIR_ENV] || "/tmp";
344
+ let failed = 0;
345
+ let stopped = 0;
346
+
347
+ for (const [uuid, profileName] of entries) {
348
+ const existing = findLiveWake(uuid, profileName);
349
+ if (existing != null) {
350
+ console.log(`already-up profile=${profileName} pid=${existing}`);
351
+ continue;
352
+ }
353
+ const ok = startWakeDaemon({ bin, uuid, profileName, logDir });
354
+ if (!ok) {
355
+ console.log(`failed profile=${profileName}`);
356
+ failed++;
357
+ continue;
358
+ }
359
+ await sleep(800);
360
+ const pid = findLiveWake(uuid, profileName);
361
+ if (pid != null) {
362
+ console.log(`started profile=${profileName} pid=${pid}`);
363
+ } else {
364
+ console.log(`failed profile=${profileName}`);
365
+ failed++;
366
+ }
367
+ }
368
+
369
+ if (entries.length === 0) {
370
+ console.log(`no-binds-empty path=${bindsPath} (pruning orphans)`);
371
+ }
372
+
373
+ // Prune orphans (also when binds empty)
374
+ const snaps = listProcSnapshots();
375
+ for (const s of snaps) {
376
+ if (!shouldPruneWake(s.environ, s.cmdline, binds)) continue;
377
+ if (stopWakePid(s.pid)) {
378
+ console.log(`stopped orphan pid=${s.pid}`);
379
+ stopped++;
380
+ }
381
+ }
382
+
383
+ if (stopped === 0 && entries.length === 0 && !existsSync(bindsPath)) {
384
+ /* already logged no-binds */
385
+ }
386
+
387
+ process.exit(failed > 0 ? 1 : 0);
388
+ }
389
+
390
+ const isDirect =
391
+ process.argv[1] &&
392
+ resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
393
+ if (isDirect) {
394
+ main().catch((err) => {
395
+ console.error(`failed unexpected ${err instanceof Error ? err.message : String(err)}`);
396
+ process.exit(1);
397
+ });
398
+ }
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+ # EXAMPLE — shape of an idempotent URL-wake keep-alive ensure (NOT production).
3
+ #
4
+ # Goal: start (or confirm) (1) the local HMAC receiver and (2) a resident
5
+ # agentschat-mcp in URL mode. Tag the MCP with AGENTCHAT_WAKE_KIND so a Grok
6
+ # ensure (WAKE_MODE=grok) never touches it.
7
+ #
8
+ # Remote / always-on boxes (Grok Bot–like): call this on every host wake and from
9
+ # a standing @every 5m routine 24/7, or inbound dies after sleep. Also supervise
10
+ # both processes (--supervise / AGENTCHAT_WAKE_SUPERVISE=1 for MCP).
11
+ #
12
+ # Copy + adapt; do not commit real secrets or conversation ids.
13
+ #
14
+ # Required local files you create privately (example layout):
15
+ # ~/.agentschat/url-wake/wake.env # AGENTCHAT_WAKE_URL + AGENTCHAT_WAKE_SECRET
16
+ # ~/.agentschat/url-wake/start-receiver.sh
17
+ # ~/.agentschat/url-wake/start-mcp-wake.sh
18
+ #
19
+ # Suggested MCP env (URL mode — unset WAKE_MODE=grok):
20
+ # AGENTCHAT_WAKE_URL=http://127.0.0.1:<port>/wake
21
+ # AGENTCHAT_WAKE_SECRET=<shared-hmac-secret>
22
+ # AGENTCHAT_WAKE_KIND=url # or host name, e.g. antigravity
23
+ # AGENTCHAT_NO_PROXY=1
24
+ # unset AGENTCHAT_WAKE_MODE
25
+ #
26
+ # Suggested start-mcp-wake shape:
27
+ # setsid -f env AGENTCHAT_WAKE_KIND=url \
28
+ # sh -c 'set -a; . wake.env; set +a; unset AGENTCHAT_WAKE_MODE;
29
+ # exec tail -f /dev/null | node …/cli.mjs --profile <Bot> --supervise'
30
+ #
31
+ # Suggested start-receiver shape:
32
+ # AGENTCHAT_WAKE_SECRET=… AGENTCHAT_URL_WAKE_CMD='…host turn…' \
33
+ # node …/example-url-wake-receiver.mjs
34
+ # For agy: CMD should use `agy -p --conversation <fixed-id>` (NOT bare -c).
35
+ #
36
+ # Concurrency: receiver single-flights; never two concurrent host turns on the
37
+ # same conversation.
38
+ #
39
+ # Shared box with Grok WAKE_MODE=grok: run BOTH ensures; do not mix modes on one
40
+ # MCP process.
41
+ set -eu
42
+
43
+ echo "example-url-wake-ensure.sh: documentation stub — wire start-receiver +" >&2
44
+ echo " start-mcp-wake for your host, then exit 0 when both are healthy." >&2
45
+ echo "See skill url-wake-keepalive and scripts/example-url-wake-receiver.mjs" >&2
46
+ exit 0