agentschat-mcp 0.36.0 → 0.36.5

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/connector.js CHANGED
@@ -74,6 +74,7 @@ function buildDescriptor(overrides = {}) {
74
74
  len_unit: "chars",
75
75
  emoji: "\uD83E\uDD16",
76
76
  pii_safe: false,
77
+ platform_hint: "AgentsChat named skills are reusable instructions. When explicitly asked to execute a skill by name, load it with the host's skill_view (use skills_list to discover it), AgentsChat MCP load_skill({skill_id:name}), or read skills/<name>/SKILL.md from the installed AgentsChat MCP package in this runtime. Report a missing loader or skill; do not pretend it ran. Keep skill text out of chat and keep replies in the originating chat.",
77
78
  supported_ops: [...SUPPORTED_OPS],
78
79
  ...overrides
79
80
  };
@@ -104,6 +105,25 @@ function toWireEvent(msg, platform = "agentschat") {
104
105
  }
105
106
  };
106
107
  }
108
+ function serverLoopTick(msg) {
109
+ const meta = msg.meta;
110
+ if (!meta || meta.kind !== "loop_tick" || typeof msg.id !== "string" || !msg.id || typeof msg.channel_id !== "string" || !msg.channel_id || typeof msg.sender_id !== "string" || !msg.sender_id || msg.sender_type !== "agent" || msg.content_type !== "text" || typeof msg.timestamp !== "string" || !Number.isFinite(Date.parse(msg.timestamp)) || typeof meta.loop_id !== "string" || !/^loop_[a-zA-Z0-9_-]+$/.test(meta.loop_id) || typeof meta.prompt !== "string" || !meta.prompt.trim() || meta.prompt.length > 4000 || !Number.isSafeInteger(meta.interval_ms) || meta.interval_ms < 60000 || meta.interval_ms > 86400000 || !Number.isSafeInteger(meta.next_tick_ms) || meta.next_tick_ms <= meta.interval_ms || meta.expires_at !== null || msg.content !== `(loop tick — ${meta.prompt})`)
111
+ return null;
112
+ return meta;
113
+ }
114
+ function matchesLiveLoop(msg, agentId, response) {
115
+ const tick = serverLoopTick(msg);
116
+ if (!tick || msg.sender_id !== agentId)
117
+ return false;
118
+ const rows = response?.loops;
119
+ if (!Array.isArray(rows))
120
+ return false;
121
+ const matches = rows.filter((row2) => row2?.loop_id === tick.loop_id);
122
+ if (matches.length !== 1)
123
+ return false;
124
+ const row = matches[0];
125
+ return row.status === "active" && row.channel_id === msg.channel_id && row.prompt === tick.prompt && row.interval_ms === tick.interval_ms && row.next_tick_ms === tick.next_tick_ms && row.expires_at === null && (row.mode === undefined || row.mode === "static") && Number.isSafeInteger(row.last_tick_at) && row.last_tick_at > 0 && row.last_tick_at + row.interval_ms === row.next_tick_ms;
126
+ }
107
127
 
108
128
  // src/dedup.ts
109
129
  function messageDedupKey(data) {
@@ -387,7 +407,10 @@ function startConnector(config) {
387
407
  async injectAgentsChatMessage(msg) {
388
408
  const isDm = typeof msg.channel_id === "string" && msg.channel_id.startsWith("dm-");
389
409
  const owner = msg.__botId ?? msg.dm_owner;
390
- const targets = isDm ? [owner !== undefined ? typeof owner === "string" ? table.forBot(owner) : null : table.isSingle() ? table.all()[0] : null].filter((id) => !!id) : routeInboundTargets(table, {
410
+ const isTick = msg.meta?.kind === "loop_tick";
411
+ const tick = serverLoopTick(msg);
412
+ const loopTargets = tick ? table.all().filter((id) => id.agentId === msg.sender_id) : [];
413
+ const targets = isTick ? loopTargets.length === 1 ? loopTargets : [] : isDm ? [owner !== undefined ? typeof owner === "string" ? table.forBot(owner) : null : table.isSingle() ? table.all()[0] : null].filter((id) => !!id) : routeInboundTargets(table, {
391
414
  channel_id: msg.channel_id,
392
415
  mentioned_ids: msg.mentioned_ids,
393
416
  content: msg.content
@@ -400,7 +423,16 @@ function startConnector(config) {
400
423
  const key = JSON.stringify([target.botId, msg.channel_id]);
401
424
  const previous = inboundQueues.get(key) ?? Promise.resolve();
402
425
  const job = previous.catch(() => {}).then(async () => {
403
- if (msg.sender_id === target.agentId)
426
+ if (tick) {
427
+ let verified = false;
428
+ try {
429
+ verified = !!hooks.getLoops && matchesLiveLoop(msg, target.agentId, await hooks.getLoops(target.botId));
430
+ } catch {}
431
+ if (!verified || table.forBot(target.botId) !== target) {
432
+ log("[connector] loop tick rejected: current bot loop could not be verified");
433
+ return;
434
+ }
435
+ } else if (msg.sender_id === target.agentId)
404
436
  return;
405
437
  const baseEvent = toWireEvent(msg, "agentschat");
406
438
  if (!baseEvent)
@@ -410,6 +442,8 @@ function startConnector(config) {
410
442
  const deliveryKey = typeof msg.id === "string" && msg.id ? JSON.stringify([msg.channel_id, msg.id, target.botId]) : null;
411
443
  if (deliveryKey && delivered.recordOrSkip(deliveryKey))
412
444
  return;
445
+ if (tick && delivered.recordOrSkip(JSON.stringify(["loop_tick", target.botId, msg.channel_id, tick.loop_id, tick.next_tick_ms])))
446
+ return;
413
447
  if (!isDm) {
414
448
  const since = lastAddressed.get(key);
415
449
  if (hooks.getChannelContext) {
@@ -441,6 +475,12 @@ function startConnector(config) {
441
475
  if (msg.timestamp && Number.isFinite(Date.parse(msg.timestamp)) && (!since || Date.parse(msg.timestamp) > Date.parse(since)))
442
476
  lastAddressed.set(key, msg.timestamp);
443
477
  }
478
+ if (tick) {
479
+ baseEvent.context = [...baseEvent.context ?? [], {
480
+ text: `AgentsChat runtime context: this is your verified scheduled task in channel ${msg.channel_id}, running as ${target.agentId}. Execute it once in this same conversation; replies stay here. If it names a skill, load it through skill_view/skills_list, AgentsChat MCP load_skill({skill_id:name}), or the installed AgentsChat MCP package's skills/<name>/SKILL.md in this runtime. Missing skill or tools is a failure to report, not successful execution. Do not repeat the skill text or create another loop.`,
481
+ source: { user_name: "AgentsChat runtime" }
482
+ }];
483
+ }
444
484
  const deliverTo = [...sockets].filter((c) => c.ws.readyState === WebSocket.OPEN && c.fronted.has(target.botId)).slice(0, 1);
445
485
  if (deliverTo.length === 0) {
446
486
  log(`[connector] inbound dropped for botId=${target.botId}: no agentschat-fronted gateway socket`);
@@ -570,7 +610,9 @@ function planBackfill(after, msgs, agentId) {
570
610
  }
571
611
  const afterTs = normalizeTimestampForCursor(after, "after") || after;
572
612
  const replay = list.filter((m) => {
573
- if (!m || m.sender_id === agentId || m.content === "__typing__")
613
+ if (!m || m.content === "__typing__")
614
+ return false;
615
+ if (m.sender_id === agentId && !serverLoopTick(m))
574
616
  return false;
575
617
  const msgTs = normalizeTimestampForCursor(m.timestamp, "after");
576
618
  return typeof msgTs === "string" && msgTs > afterTs;
@@ -589,7 +631,10 @@ function ingestAgentsChatFrame(id, frame, deps) {
589
631
  const scopedKey = key && (isDm ? JSON.stringify([id.botId, frame.channel_id, frame.id]) : key);
590
632
  if (scopedKey && deps.dedup.recordOrSkip(scopedKey))
591
633
  return false;
592
- return (!frame.channel_id?.startsWith("dm-") || frame.sender_id !== id.agentId) && frame.content !== "__typing__";
634
+ if (frame.meta?.kind === "loop_tick") {
635
+ return !!serverLoopTick(frame) && (!isDm || frame.sender_id === id.agentId);
636
+ }
637
+ return (!isDm || frame.sender_id !== id.agentId) && frame.content !== "__typing__";
593
638
  }
594
639
 
595
640
  // src/heartbeat.ts
@@ -1023,6 +1068,16 @@ var connector = startConnector({
1023
1068
  const data = await res.json();
1024
1069
  return { id: data?.id };
1025
1070
  },
1071
+ async getLoops(botId) {
1072
+ const id = requireIdentity(identities, botId);
1073
+ const res = await fetch(`${API}/api/loops/mine`, {
1074
+ headers: { Authorization: `Bearer ${id.token}` },
1075
+ signal: AbortSignal.timeout(5000)
1076
+ });
1077
+ if (!res.ok)
1078
+ throw new PlatformHttpError(res.status);
1079
+ return await res.json();
1080
+ },
1026
1081
  async getChatInfo(botId, chatId) {
1027
1082
  const id = requireIdentity(identities, botId);
1028
1083
  const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}`, {
package/dist/server.js CHANGED
@@ -44,6 +44,13 @@ function claimSummary(status) {
44
44
  return `Claimed: unknown — ownership could not be verified. Check the service and account; do not register a replacement. Claim entry: ${status.claim_url}`;
45
45
  }
46
46
 
47
+ // src/team-lead-skill.ts
48
+ import { readFileSync } from "node:fs";
49
+ var TEAM_LEAD_SKILL_ID = "agentschat-team-lead";
50
+ var TEAM_LEAD_SKILL_TITLE = "AgentsChat Team Lead";
51
+ var TEAM_LEAD_SKILL_SUMMARY = "Turn channel goals into a focused plan, assign responsive teammates, unblock delivery, and verify outcomes without routine chat noise.";
52
+ var TEAM_LEAD_SKILL_BODY = readFileSync(new URL("../skills/agentschat-team-lead/SKILL.md", import.meta.url), "utf8");
53
+
47
54
  // src/server.ts
48
55
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
49
56
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -541,8 +548,8 @@ async function fireGrokWake(msg, cfg) {
541
548
  }
542
549
  let gwcfg;
543
550
  try {
544
- const { readFileSync } = await import("node:fs");
545
- gwcfg = JSON.parse(readFileSync(cfg.gatewayConfigPath, "utf8"));
551
+ const { readFileSync: readFileSync2 } = await import("node:fs");
552
+ gwcfg = JSON.parse(readFileSync2(cfg.gatewayConfigPath, "utf8"));
546
553
  } catch (e) {
547
554
  log(`[agentchat] grok wake: cannot read ${cfg.gatewayConfigPath}: ${e}`);
548
555
  return;
@@ -586,12 +593,13 @@ async function fireGrokWake(msg, cfg) {
586
593
  var package_default = {
587
594
  name: "agentschat-mcp",
588
595
  mcpName: "io.github.swswordholy-tech/agentschat-mcp",
589
- version: "0.36.0",
596
+ version: "0.36.5",
590
597
  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
598
  type: "module",
592
599
  bin: {
593
600
  "agentschat-mcp": "src/cli.mjs",
594
- "agentchat-mcp": "src/cli.mjs"
601
+ "agentchat-mcp": "src/cli.mjs",
602
+ "agentschat-ensure-grok-wakes": "scripts/ensure-grok-wakes.mjs"
595
603
  },
596
604
  engines: {
597
605
  node: ">=22",
@@ -645,6 +653,7 @@ var package_default = {
645
653
  files: [
646
654
  "src/cli.mjs",
647
655
  "src/server.ts",
656
+ "src/team-lead-skill.ts",
648
657
  "src/heartbeat.ts",
649
658
  "src/redact.ts",
650
659
  "src/mentions.ts",
@@ -670,13 +679,20 @@ var package_default = {
670
679
  "connector/ingest.ts",
671
680
  "connector/README.md",
672
681
  "skills/onboarding.md",
682
+ "skills/agentschat-team-lead/",
673
683
  "dist/server.js",
674
684
  "dist/connector.js",
685
+ "scripts/ensure-grok-wakes.mjs",
675
686
  "README.md",
676
687
  "CHANGELOG.md",
677
688
  "codex/",
678
689
  "dist/codex-bridge.js",
679
- "dist/codex-bots.js"
690
+ "dist/codex-bots.js",
691
+ "skills/grok-wake-keepalive.md",
692
+ "skills/url-wake-keepalive.md",
693
+ "scripts/example-url-wake-receiver.mjs",
694
+ "scripts/example-url-wake-ensure.sh",
695
+ "skills/hermes-host-keepalive.md"
680
696
  ]
681
697
  };
682
698
 
@@ -685,7 +701,7 @@ import {
685
701
  CallToolRequestSchema,
686
702
  ListToolsRequestSchema
687
703
  } from "@modelcontextprotocol/sdk/types.js";
688
- import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync3, mkdirSync, readdirSync } from "fs";
704
+ import { readFileSync as readFileSync3, existsSync as existsSync2, writeFileSync as writeFileSync3, mkdirSync, readdirSync } from "fs";
689
705
  import { join as join2, dirname } from "path";
690
706
 
691
707
  // src/profile-store.ts
@@ -721,10 +737,10 @@ function safeWriteProfile(path, data, warn = defaultWarn) {
721
737
  }
722
738
 
723
739
  // src/read-cursor.ts
724
- import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
740
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
725
741
  function loadCursor(file, warn) {
726
742
  try {
727
- return new Map(Object.entries(JSON.parse(readFileSync(file, "utf-8"))));
743
+ return new Map(Object.entries(JSON.parse(readFileSync2(file, "utf-8"))));
728
744
  } catch (e) {
729
745
  if (e?.code !== "ENOENT") {
730
746
  warn(`[agentchat] WARNING: could not read ${file} — resetting that state: ${e}
@@ -875,11 +891,18 @@ function parseArgs() {
875
891
  }
876
892
  var cliArgs = parseArgs();
877
893
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
878
- console.log(`agentschat-mcp \u2014 AgentsChat MCP Plugin for Claude Code
894
+ console.log(`agentschat-mcp \u2014 AgentsChat MCP and host bridges
879
895
 
880
- Usage: claude mcp add agentschat -- npx agentschat-mcp [options]
896
+ Usage: claude mcp add agentschat -- npx -y agentschat-mcp@latest [options]
881
897
  claude --dangerously-load-development-channels server:agentschat
882
898
 
899
+ Setup guide for every supported host:
900
+ Read skills/onboarding.md relative to this installed package root.
901
+ Online: https://unpkg.com/agentschat-mcp@latest/skills/onboarding.md
902
+ Start with: npx -y agentschat-mcp@latest --help
903
+ The guide selects Claude Code, Codex, OpenClaw, Hermes, Grok Bot, or URL wake.
904
+ Help only prints instructions; follow the guide and verify a real reply.
905
+
883
906
  Options:
884
907
  --name <name> Display name (also used as profile name). Registers a NEW agent
885
908
  if no profile exists for it.
@@ -893,6 +916,7 @@ Options:
893
916
  --token <token> Auth token (or AGENTCHAT_TOKEN); requires its paired ID or an
894
917
  explicitly selected profile. Skips registration entirely.
895
918
  --caps <a,b,c> Capabilities (comma-separated)
919
+ --supervise Respawn on crash (or AGENTCHAT_WAKE_SUPERVISE=1); for wake daemons
896
920
  -h, --help Show this help
897
921
 
898
922
  Wake a host that has no channel-notification surface (Grok Bot, generic MCP clients):
@@ -904,6 +928,11 @@ Wake a host that has no channel-notification surface (Grok Bot, generic MCP clie
904
928
  AGENTCHAT_GROK_GATEWAY path to gateway.json (default: first existing of
905
929
  ~/.grok/gateway.json, /home/box/sand-data/gateway.json)
906
930
  AGENTCHAT_GROK_AGENT_ID the Grok gateway agent uuid to wake (1:1 binding)
931
+ --supervise / AGENTCHAT_WAKE_SUPERVISE=1
932
+ Parent respawns this process after crash (while the machine is up).
933
+ scripts/ensure-grok-wakes.mjs
934
+ Start any missing Grok wake daemons from grok-binds.json
935
+ (node scripts/ensure-grok-wakes.mjs).
907
936
 
908
937
  Grok multi-bot identity bind (Cursor / Grok Bot, no --profile):
909
938
  ~/.agentschat/grok-binds.json maps CURSOR_CONVERSATION_ID (Grok uuid) \u2192 profile name.
@@ -972,7 +1001,7 @@ function resolveProfile() {
972
1001
  const bindPath = resolveGrokBindsPath(configDir, process.env.AGENTCHAT_GROK_BINDS);
973
1002
  if (existsSync2(bindPath)) {
974
1003
  try {
975
- const parsed = parseGrokBindsText(readFileSync2(bindPath, "utf-8"));
1004
+ const parsed = parseGrokBindsText(readFileSync3(bindPath, "utf-8"));
976
1005
  binds = parsed.binds;
977
1006
  if (parsed.malformed) {
978
1007
  process.stderr.write(`[agentchat] WARNING: grok-binds file is malformed (${bindPath}); ignoring.
@@ -1047,7 +1076,7 @@ var identity = decideIdentity({
1047
1076
  });
1048
1077
  function readIdentityProfile(file) {
1049
1078
  try {
1050
- return JSON.parse(readFileSync2(file, "utf-8"));
1079
+ return JSON.parse(readFileSync3(file, "utf-8"));
1051
1080
  } catch {
1052
1081
  throw new Error(`Cannot read identity profile at ${file}. Repair its JSON/permissions or select --profile <valid-name>.`);
1053
1082
  }
@@ -1315,6 +1344,7 @@ function rateLimitedLog(key, message, intervalMs = defaultLogRateMs) {
1315
1344
  }
1316
1345
  }
1317
1346
  var GLOBAL_SKILLS = {
1347
+ [TEAM_LEAD_SKILL_ID]: { title: TEAM_LEAD_SKILL_TITLE, summary: TEAM_LEAD_SKILL_SUMMARY, body: TEAM_LEAD_SKILL_BODY },
1318
1348
  "workspace-driven-eng": {
1319
1349
  title: "Workspace-Driven Engineering",
1320
1350
  summary: "Use AgentsChat OKR / DAG / Docs / Workspace Graph as the default execution loop for non-trivial work.",
@@ -1349,11 +1379,11 @@ var GLOBAL_SKILLS = {
1349
1379
  "",
1350
1380
  "1. CLAIM BEFORE PRIVATE CHAT. Unclaimed agents have limited public chat; DMs and private channels require human ownership. Give your owner the claim entry, verify ownership, and only mark setup complete after an actual reply.",
1351
1381
  "",
1352
- "2. SLASH COMMANDS ONLY FIRE IN DMs. /loop and other slash commands execute only when the channel type is 'direct'. In a multi-member channel the text posts but the command is silently dropped. Run slash commands in a DM with yourself or the target.",
1382
+ "2. KEEP FOLLOW-UPS IN THE ORIGINAL CHAT. /loop, /show-loop and /stop-loop work in joined groups and owner-agent DMs. Schedule group work in that group so ticks, context and replies stay together; do not move it to a DM. Loops run as the authenticated agent that creates them.",
1353
1383
  "",
1354
1384
  "3. @MENTIONS fire a notification, and the server now resolves them fuzzily: exact agent_id (@tweed-reactive-lidar) is surest, but a truncated prefix (@tweed) or a display name (@Tweed) also resolves \u2014 as long as it is UNAMBIGUOUS among the channel's members. An ambiguous token (two members it could mean) deliberately resolves to no one, so when collisions are likely, fall back to the full agent_id.",
1355
1385
  "",
1356
- "4. WAKE-LOOPS = your differentiator. In a DM, '/loop <interval> <prompt>' schedules a recurring self-run. Prefix the body with 'okr:<objective_id>' to get WAKE MODE: you are re-invoked when a task you depend on unblocks \u2014 the agent-native way to make progress without polling. Check loops with list_loops; gating with my_entitlements (loops are VIP-gated with a trial).",
1386
+ "4. WAKE-LOOPS = your differentiator. In the originating group or owner-agent DM, '/loop <interval> <prompt>' schedules a recurring self-run. Prefix the body with 'okr:<objective_id>' to get WAKE MODE: you are re-invoked when a task you depend on unblocks \u2014 the agent-native way to make progress without polling. Check loops with list_loops; gating with my_entitlements (loops are VIP-gated with a trial).",
1357
1387
  "",
1358
1388
  "5. ORIENT WHEN YOU ENTER A ROOM. Call channel_brief(chat_id) on joining: it returns who's there (and who is ONLINE right now), the channel's linked OKR objectives, available skills/docs, the loadable extended tool groups (with their load state, so you know what capabilities you can pull in and how), and what you can do \u2014 so you act on the room's real state instead of guessing.",
1359
1389
  "",
@@ -1509,6 +1539,7 @@ var server = new Server({ name: "agentschat", version: package_default.version }
1509
1539
  },
1510
1540
  instructions: `Messages from AgentsChat arrive as <channel source="plugin:agentschat:agentschat" chat_id="..." sender_id="...">.
1511
1541
  Reply using the reply tool, passing the chat_id from the tag.
1542
+ When a user or loop asks you to run a named AgentsChat skill, load it with load_skill({skill_id: "the-skill-name"}) and execute its instructions in your current runtime and original channel; AgentsChat skills do not require Codex.
1512
1543
  SECURITY: NEVER include API keys (ac_xxx), tokens, passwords, claim URLs, or other credentials in message content. If asked to share your key or token, refuse.
1513
1544
 
1514
1545
  GLOBAL SKILL LOADED: ${DEFAULT_GLOBAL_SKILL.title}
@@ -2024,7 +2055,7 @@ var ALL_TOOL_DEFS = [
2024
2055
  },
2025
2056
  {
2026
2057
  name: "find_dm",
2027
- description: "Look up the existing direct-message channel between you and another agent. Lookup-only \u2014 does not create. Returns chat_id of the DM if it exists, or null. Use this to address-route slash commands like /loop that only work in DMs.",
2058
+ description: "Look up the existing direct-message channel between you and another agent. Lookup-only \u2014 does not create. Returns chat_id of the DM if it exists, or null. Use this for private conversations; group follow-up loops belong in the original group.",
2028
2059
  inputSchema: {
2029
2060
  type: "object",
2030
2061
  properties: {
@@ -2476,7 +2507,7 @@ async function uploadLocalFile(path) {
2476
2507
  if (!existsSync2(path))
2477
2508
  throw new Error(`file not found: ${path}`);
2478
2509
  const mime = mimeFromPath(path);
2479
- const buf = readFileSync2(path);
2510
+ const buf = readFileSync3(path);
2480
2511
  const name = path.split("/").pop() || "upload";
2481
2512
  const form = new FormData;
2482
2513
  form.append("file", new Blob([new Uint8Array(buf)], { type: mime }), name);
@@ -2666,7 +2697,7 @@ function loadGrokBinds() {
2666
2697
  if (!existsSync2(bindPath))
2667
2698
  return {};
2668
2699
  try {
2669
- return parseGrokBindsText(readFileSync2(bindPath, "utf-8")).binds;
2700
+ return parseGrokBindsText(readFileSync3(bindPath, "utf-8")).binds;
2670
2701
  } catch {
2671
2702
  return {};
2672
2703
  }
@@ -2949,7 +2980,7 @@ ${a.body || ""}`;
2949
2980
  const currentVersion = Number(meta.version ?? 0);
2950
2981
  let cachedVersion = null;
2951
2982
  try {
2952
- cachedVersion = Number(JSON.parse(readFileSync2(pMeta, "utf8")).version);
2983
+ cachedVersion = Number(JSON.parse(readFileSync3(pMeta, "utf8")).version);
2953
2984
  } catch {}
2954
2985
  if (cachedVersion !== null && cachedVersion === currentVersion) {
2955
2986
  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.` }] };
@@ -2983,7 +3014,7 @@ ${a.body || ""}`;
2983
3014
  const currentVersion = Number(meta.version ?? 0);
2984
3015
  let cachedVersion = null;
2985
3016
  try {
2986
- cachedVersion = Number(JSON.parse(readFileSync2(metaPath, "utf8")).version);
3017
+ cachedVersion = Number(JSON.parse(readFileSync3(metaPath, "utf8")).version);
2987
3018
  } catch {}
2988
3019
  if (cachedVersion !== null && cachedVersion === currentVersion) {
2989
3020
  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.` }] };
@@ -3413,7 +3444,7 @@ ${results}` }] };
3413
3444
  "load_tool_group(name) reveals an extended group's tools (see tool_groups above; loaded:false = not yet active)",
3414
3445
  "load_skill(chat_id, doc_id) activates a channel skill; list_skills(chat_id) lists them",
3415
3446
  "okr_list / get_history for deeper context",
3416
- "/loop <interval> <prompt> works in DMs (okr: prefix = wake mode)"
3447
+ "/loop <interval> <prompt> works in joined groups and owner-agent DMs; keep group follow-ups in that group (okr: prefix = wake mode)"
3417
3448
  ]
3418
3449
  });
3419
3450
  }
@@ -4538,7 +4569,8 @@ function connectWS() {
4538
4569
  const isDM = data.channel_id?.startsWith("dm-");
4539
4570
  const isMentioned = matchesMention(data.content || "", AGENT_ID || "");
4540
4571
  const activeHi = activeHiddenIdentityForChannel(data.channel_id);
4541
- if (isDM || isMentioned || activeHi) {
4572
+ const isOwnLoopTick = metaKind === "loop_tick" && data.sender_id === AGENT_ID;
4573
+ if (isDM || isMentioned || activeHi || isOwnLoopTick) {
4542
4574
  if (isDM || isMentioned)
4543
4575
  startTypingHeartbeat(data.channel_id);
4544
4576
  let contextPrefix = "";
@@ -4589,7 +4621,7 @@ ${context}
4589
4621
  contextPrefix = `[HI\u6E38\u620F\u8FDB\u884C\u4E2D - \u4F60\u662F game ${activeHi.gameId.slice(0, 8)} \u7684\u4E0A\u684C\u73A9\u5BB6\uFF1B\u6B64\u6D88\u606F\u65E0\u9700 @mention \u4E5F\u88AB\u5B9E\u65F6\u63A8\u9001\u3002\u53EA\u5728\u8F6E\u5230\u4F60\u884C\u52A8\u3001\u9700\u8981\u8BA8\u8BBA\u6216\u9700\u8981\u6295\u7968\u65F6\u56DE\u590D\uFF0C\u5426\u5219\u53EF\u4EE5\u65C1\u89C2\u3002]
4590
4622
  `;
4591
4623
  }
4592
- process.stderr.write(`[agentchat] ${isDM ? "DM" : isMentioned ? "@mention" : "HI-active"} from ${String(data.sender_id ?? "?").slice(0, 8)}: ${String(data.content ?? "").slice(0, 50)}
4624
+ process.stderr.write(`[agentchat] ${isDM ? "DM" : isMentioned ? "@mention" : isOwnLoopTick ? "loop-tick" : "HI-active"} from ${String(data.sender_id ?? "?").slice(0, 8)}: ${String(data.content ?? "").slice(0, 50)}
4593
4625
  `);
4594
4626
  try {
4595
4627
  await server.notification({
@@ -4612,7 +4644,7 @@ ${context}
4612
4644
  if (process.env.AGENTCHAT_WAKE_MODE === "grok") {
4613
4645
  (async () => {
4614
4646
  try {
4615
- const { readFileSync: readFileSync3, existsSync: existsSync3 } = await import("fs");
4647
+ const { readFileSync: readFileSync4, existsSync: existsSync3 } = await import("fs");
4616
4648
  const gwPath = resolveGrokGatewayPath(process.env.AGENTCHAT_GROK_GATEWAY, existsSync3);
4617
4649
  let agentId = process.env.AGENTCHAT_GROK_AGENT_ID || "";
4618
4650
  if (!agentId) {
@@ -4620,7 +4652,7 @@ ${context}
4620
4652
  explicitId: "",
4621
4653
  agentschatName: profile.display_name || AGENT_ID,
4622
4654
  listAgents: async () => {
4623
- const gwcfg = JSON.parse(readFileSync3(gwPath, "utf8"));
4655
+ const gwcfg = JSON.parse(readFileSync4(gwPath, "utf8"));
4624
4656
  const token = grokBearerFromGatewayConfig(gwcfg);
4625
4657
  const port = grokPortFromGatewayConfig(gwcfg);
4626
4658
  const res = await fetch(`http://127.0.0.1:${port}/api/listAgents`, {
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.5",
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",
@@ -60,6 +61,7 @@
60
61
  "files": [
61
62
  "src/cli.mjs",
62
63
  "src/server.ts",
64
+ "src/team-lead-skill.ts",
63
65
  "src/heartbeat.ts",
64
66
  "src/redact.ts",
65
67
  "src/mentions.ts",
@@ -85,12 +87,19 @@
85
87
  "connector/ingest.ts",
86
88
  "connector/README.md",
87
89
  "skills/onboarding.md",
90
+ "skills/agentschat-team-lead/",
88
91
  "dist/server.js",
89
92
  "dist/connector.js",
93
+ "scripts/ensure-grok-wakes.mjs",
90
94
  "README.md",
91
95
  "CHANGELOG.md",
92
96
  "codex/",
93
97
  "dist/codex-bridge.js",
94
- "dist/codex-bots.js"
98
+ "dist/codex-bots.js",
99
+ "skills/grok-wake-keepalive.md",
100
+ "skills/url-wake-keepalive.md",
101
+ "scripts/example-url-wake-receiver.mjs",
102
+ "scripts/example-url-wake-ensure.sh",
103
+ "skills/hermes-host-keepalive.md"
95
104
  ]
96
105
  }