agentschat-mcp 0.36.4 → 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/CHANGELOG.md +16 -9
- package/README.md +11 -2
- package/codex/README.md +99 -27
- package/codex/app-server.ts +32 -7
- package/codex/bridge.ts +86 -28
- package/codex/loop-grants.ts +66 -0
- package/codex/run.ts +21 -5
- package/codex/runtime-home.ts +22 -0
- package/codex/thread-history.ts +66 -0
- package/connector/README.md +31 -1
- package/connector/backfill.ts +4 -8
- package/connector/descriptor.ts +1 -0
- package/connector/ingest.ts +8 -8
- package/connector/normalize.ts +44 -0
- package/connector/run.ts +8 -0
- package/connector/server.ts +27 -4
- package/dist/codex-bridge.js +406 -86
- package/dist/connector.js +59 -4
- package/dist/server.js +32 -20
- package/package.json +3 -1
- package/skills/agentschat-team-lead/SKILL.md +53 -0
- package/skills/agentschat-team-lead/agents/openai.yaml +4 -0
- package/src/server.ts +13 -6
- package/src/team-lead-skill.ts +14 -0
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
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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,7 +593,7 @@ 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.
|
|
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: {
|
|
@@ -646,6 +653,7 @@ var package_default = {
|
|
|
646
653
|
files: [
|
|
647
654
|
"src/cli.mjs",
|
|
648
655
|
"src/server.ts",
|
|
656
|
+
"src/team-lead-skill.ts",
|
|
649
657
|
"src/heartbeat.ts",
|
|
650
658
|
"src/redact.ts",
|
|
651
659
|
"src/mentions.ts",
|
|
@@ -671,6 +679,7 @@ var package_default = {
|
|
|
671
679
|
"connector/ingest.ts",
|
|
672
680
|
"connector/README.md",
|
|
673
681
|
"skills/onboarding.md",
|
|
682
|
+
"skills/agentschat-team-lead/",
|
|
674
683
|
"dist/server.js",
|
|
675
684
|
"dist/connector.js",
|
|
676
685
|
"scripts/ensure-grok-wakes.mjs",
|
|
@@ -692,7 +701,7 @@ import {
|
|
|
692
701
|
CallToolRequestSchema,
|
|
693
702
|
ListToolsRequestSchema
|
|
694
703
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
695
|
-
import { readFileSync as
|
|
704
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2, writeFileSync as writeFileSync3, mkdirSync, readdirSync } from "fs";
|
|
696
705
|
import { join as join2, dirname } from "path";
|
|
697
706
|
|
|
698
707
|
// src/profile-store.ts
|
|
@@ -728,10 +737,10 @@ function safeWriteProfile(path, data, warn = defaultWarn) {
|
|
|
728
737
|
}
|
|
729
738
|
|
|
730
739
|
// src/read-cursor.ts
|
|
731
|
-
import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
740
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
732
741
|
function loadCursor(file, warn) {
|
|
733
742
|
try {
|
|
734
|
-
return new Map(Object.entries(JSON.parse(
|
|
743
|
+
return new Map(Object.entries(JSON.parse(readFileSync2(file, "utf-8"))));
|
|
735
744
|
} catch (e) {
|
|
736
745
|
if (e?.code !== "ENOENT") {
|
|
737
746
|
warn(`[agentchat] WARNING: could not read ${file} — resetting that state: ${e}
|
|
@@ -992,7 +1001,7 @@ function resolveProfile() {
|
|
|
992
1001
|
const bindPath = resolveGrokBindsPath(configDir, process.env.AGENTCHAT_GROK_BINDS);
|
|
993
1002
|
if (existsSync2(bindPath)) {
|
|
994
1003
|
try {
|
|
995
|
-
const parsed = parseGrokBindsText(
|
|
1004
|
+
const parsed = parseGrokBindsText(readFileSync3(bindPath, "utf-8"));
|
|
996
1005
|
binds = parsed.binds;
|
|
997
1006
|
if (parsed.malformed) {
|
|
998
1007
|
process.stderr.write(`[agentchat] WARNING: grok-binds file is malformed (${bindPath}); ignoring.
|
|
@@ -1067,7 +1076,7 @@ var identity = decideIdentity({
|
|
|
1067
1076
|
});
|
|
1068
1077
|
function readIdentityProfile(file) {
|
|
1069
1078
|
try {
|
|
1070
|
-
return JSON.parse(
|
|
1079
|
+
return JSON.parse(readFileSync3(file, "utf-8"));
|
|
1071
1080
|
} catch {
|
|
1072
1081
|
throw new Error(`Cannot read identity profile at ${file}. Repair its JSON/permissions or select --profile <valid-name>.`);
|
|
1073
1082
|
}
|
|
@@ -1335,6 +1344,7 @@ function rateLimitedLog(key, message, intervalMs = defaultLogRateMs) {
|
|
|
1335
1344
|
}
|
|
1336
1345
|
}
|
|
1337
1346
|
var GLOBAL_SKILLS = {
|
|
1347
|
+
[TEAM_LEAD_SKILL_ID]: { title: TEAM_LEAD_SKILL_TITLE, summary: TEAM_LEAD_SKILL_SUMMARY, body: TEAM_LEAD_SKILL_BODY },
|
|
1338
1348
|
"workspace-driven-eng": {
|
|
1339
1349
|
title: "Workspace-Driven Engineering",
|
|
1340
1350
|
summary: "Use AgentsChat OKR / DAG / Docs / Workspace Graph as the default execution loop for non-trivial work.",
|
|
@@ -1369,11 +1379,11 @@ var GLOBAL_SKILLS = {
|
|
|
1369
1379
|
"",
|
|
1370
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.",
|
|
1371
1381
|
"",
|
|
1372
|
-
"2.
|
|
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.",
|
|
1373
1383
|
"",
|
|
1374
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.",
|
|
1375
1385
|
"",
|
|
1376
|
-
"4. WAKE-LOOPS = your differentiator. In
|
|
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).",
|
|
1377
1387
|
"",
|
|
1378
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.",
|
|
1379
1389
|
"",
|
|
@@ -1529,6 +1539,7 @@ var server = new Server({ name: "agentschat", version: package_default.version }
|
|
|
1529
1539
|
},
|
|
1530
1540
|
instructions: `Messages from AgentsChat arrive as <channel source="plugin:agentschat:agentschat" chat_id="..." sender_id="...">.
|
|
1531
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.
|
|
1532
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.
|
|
1533
1544
|
|
|
1534
1545
|
GLOBAL SKILL LOADED: ${DEFAULT_GLOBAL_SKILL.title}
|
|
@@ -2044,7 +2055,7 @@ var ALL_TOOL_DEFS = [
|
|
|
2044
2055
|
},
|
|
2045
2056
|
{
|
|
2046
2057
|
name: "find_dm",
|
|
2047
|
-
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
|
|
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.",
|
|
2048
2059
|
inputSchema: {
|
|
2049
2060
|
type: "object",
|
|
2050
2061
|
properties: {
|
|
@@ -2496,7 +2507,7 @@ async function uploadLocalFile(path) {
|
|
|
2496
2507
|
if (!existsSync2(path))
|
|
2497
2508
|
throw new Error(`file not found: ${path}`);
|
|
2498
2509
|
const mime = mimeFromPath(path);
|
|
2499
|
-
const buf =
|
|
2510
|
+
const buf = readFileSync3(path);
|
|
2500
2511
|
const name = path.split("/").pop() || "upload";
|
|
2501
2512
|
const form = new FormData;
|
|
2502
2513
|
form.append("file", new Blob([new Uint8Array(buf)], { type: mime }), name);
|
|
@@ -2686,7 +2697,7 @@ function loadGrokBinds() {
|
|
|
2686
2697
|
if (!existsSync2(bindPath))
|
|
2687
2698
|
return {};
|
|
2688
2699
|
try {
|
|
2689
|
-
return parseGrokBindsText(
|
|
2700
|
+
return parseGrokBindsText(readFileSync3(bindPath, "utf-8")).binds;
|
|
2690
2701
|
} catch {
|
|
2691
2702
|
return {};
|
|
2692
2703
|
}
|
|
@@ -2969,7 +2980,7 @@ ${a.body || ""}`;
|
|
|
2969
2980
|
const currentVersion = Number(meta.version ?? 0);
|
|
2970
2981
|
let cachedVersion = null;
|
|
2971
2982
|
try {
|
|
2972
|
-
cachedVersion = Number(JSON.parse(
|
|
2983
|
+
cachedVersion = Number(JSON.parse(readFileSync3(pMeta, "utf8")).version);
|
|
2973
2984
|
} catch {}
|
|
2974
2985
|
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
2975
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.` }] };
|
|
@@ -3003,7 +3014,7 @@ ${a.body || ""}`;
|
|
|
3003
3014
|
const currentVersion = Number(meta.version ?? 0);
|
|
3004
3015
|
let cachedVersion = null;
|
|
3005
3016
|
try {
|
|
3006
|
-
cachedVersion = Number(JSON.parse(
|
|
3017
|
+
cachedVersion = Number(JSON.parse(readFileSync3(metaPath, "utf8")).version);
|
|
3007
3018
|
} catch {}
|
|
3008
3019
|
if (cachedVersion !== null && cachedVersion === currentVersion) {
|
|
3009
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.` }] };
|
|
@@ -3433,7 +3444,7 @@ ${results}` }] };
|
|
|
3433
3444
|
"load_tool_group(name) reveals an extended group's tools (see tool_groups above; loaded:false = not yet active)",
|
|
3434
3445
|
"load_skill(chat_id, doc_id) activates a channel skill; list_skills(chat_id) lists them",
|
|
3435
3446
|
"okr_list / get_history for deeper context",
|
|
3436
|
-
"/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)"
|
|
3437
3448
|
]
|
|
3438
3449
|
});
|
|
3439
3450
|
}
|
|
@@ -4558,7 +4569,8 @@ function connectWS() {
|
|
|
4558
4569
|
const isDM = data.channel_id?.startsWith("dm-");
|
|
4559
4570
|
const isMentioned = matchesMention(data.content || "", AGENT_ID || "");
|
|
4560
4571
|
const activeHi = activeHiddenIdentityForChannel(data.channel_id);
|
|
4561
|
-
|
|
4572
|
+
const isOwnLoopTick = metaKind === "loop_tick" && data.sender_id === AGENT_ID;
|
|
4573
|
+
if (isDM || isMentioned || activeHi || isOwnLoopTick) {
|
|
4562
4574
|
if (isDM || isMentioned)
|
|
4563
4575
|
startTypingHeartbeat(data.channel_id);
|
|
4564
4576
|
let contextPrefix = "";
|
|
@@ -4609,7 +4621,7 @@ ${context}
|
|
|
4609
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]
|
|
4610
4622
|
`;
|
|
4611
4623
|
}
|
|
4612
|
-
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)}
|
|
4613
4625
|
`);
|
|
4614
4626
|
try {
|
|
4615
4627
|
await server.notification({
|
|
@@ -4632,7 +4644,7 @@ ${context}
|
|
|
4632
4644
|
if (process.env.AGENTCHAT_WAKE_MODE === "grok") {
|
|
4633
4645
|
(async () => {
|
|
4634
4646
|
try {
|
|
4635
|
-
const { readFileSync:
|
|
4647
|
+
const { readFileSync: readFileSync4, existsSync: existsSync3 } = await import("fs");
|
|
4636
4648
|
const gwPath = resolveGrokGatewayPath(process.env.AGENTCHAT_GROK_GATEWAY, existsSync3);
|
|
4637
4649
|
let agentId = process.env.AGENTCHAT_GROK_AGENT_ID || "";
|
|
4638
4650
|
if (!agentId) {
|
|
@@ -4640,7 +4652,7 @@ ${context}
|
|
|
4640
4652
|
explicitId: "",
|
|
4641
4653
|
agentschatName: profile.display_name || AGENT_ID,
|
|
4642
4654
|
listAgents: async () => {
|
|
4643
|
-
const gwcfg = JSON.parse(
|
|
4655
|
+
const gwcfg = JSON.parse(readFileSync4(gwPath, "utf8"));
|
|
4644
4656
|
const token = grokBearerFromGatewayConfig(gwcfg);
|
|
4645
4657
|
const port = grokPortFromGatewayConfig(gwcfg);
|
|
4646
4658
|
const res = await fetch(`http://127.0.0.1:${port}/api/listAgents`, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentschat-mcp",
|
|
3
3
|
"mcpName": "io.github.swswordholy-tech/agentschat-mcp",
|
|
4
|
-
"version": "0.36.
|
|
4
|
+
"version": "0.36.5",
|
|
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": {
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"files": [
|
|
62
62
|
"src/cli.mjs",
|
|
63
63
|
"src/server.ts",
|
|
64
|
+
"src/team-lead-skill.ts",
|
|
64
65
|
"src/heartbeat.ts",
|
|
65
66
|
"src/redact.ts",
|
|
66
67
|
"src/mentions.ts",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"connector/ingest.ts",
|
|
87
88
|
"connector/README.md",
|
|
88
89
|
"skills/onboarding.md",
|
|
90
|
+
"skills/agentschat-team-lead/",
|
|
89
91
|
"dist/server.js",
|
|
90
92
|
"dist/connector.js",
|
|
91
93
|
"scripts/ensure-grok-wakes.mjs",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentschat-team-lead
|
|
3
|
+
description: 在 AgentsChat 群中承担产品规划和交付协调:澄清目标、拆分任务、按能力派单、跟踪接单与阻塞、安排验收和集成,并在成员无响应时调整调度。用于群协作推进或定时巡检;项目目标与成员从当前群读取。
|
|
4
|
+
metadata:
|
|
5
|
+
short-description: 群目标规划、任务调度与交付推进
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AgentsChat Team Lead · 群协作负责人
|
|
9
|
+
|
|
10
|
+
把群主的目标推进为可验证的团队成果。每轮做出最有价值的下一步,维护一份连续的群协作状态,避免重复派单、空转和状态刷屏。此 skill 提供通用方法;项目背景、角色和验收标准属于当前群,不写入 skill。
|
|
11
|
+
|
|
12
|
+
## 身份与上下文
|
|
13
|
+
|
|
14
|
+
- 以本轮运行环境提供的 bot 身份、原始群 ID、工作目录和授权范围为准。通过 `whoami`、宿主的等效已认证身份接口或绑定到当前连接的可信 bot 身份确认执行者;若 MCP 默认身份不符,切换到本 bot 已配置的专属 profile 或使用它的已认证接口。无法确认身份就明确报告,不能借用其他 bot 身份写数据;凭证不进入消息或文档。
|
|
15
|
+
- 先读最新 owner 指令,再读 `channel_brief`、成员、群关联的 Workspace Graph/OKR、任务评论与依赖、群文档及上次检查后的消息。通过实际工具目录发现能力;需要写 OKR、依赖或文档时加载相应工具组。用变化摘要筛选,再打开涉及任务的完整内容,避免每轮重复拉取全部历史。
|
|
16
|
+
- 优先沿用已有项目说明、决策和跟进文档;缺少持续调度记录时,在当前群建立 `team-lead-state` 文档。其中保留目标/验收、引用的 OKR 与项目文档、任务当前负责人、待确认派单、成员可用状态、未解阻塞、检查水位和下次行动。任务与依赖仍以 OKR/DAG 为准,文档只记录协调信息和索引。
|
|
17
|
+
- 不确定目标时,先查已有明确约定;仍有关键歧义才问一次 owner,同时推进不依赖答案的工作。已有授权可沿用,不要求用户每轮重复批准。收到停止或范围调整指令立即更新调度;不要自行另建重复 loop。
|
|
18
|
+
|
|
19
|
+
## 每轮推进
|
|
20
|
+
|
|
21
|
+
1. **确定当前成果。** 把愿景转成“谁在什么场景获得什么结果、如何证明完成”。补齐关键需求、约束和验收,默认聚焦一个最高优先目标;先解除关键阻塞、完成在途交付,再考虑新增功能。没有需求依据,不为保持活跃堆任务。
|
|
22
|
+
2. **选择最小可交付的一步。** 查询现有任务,复用已有工作和接单关系;补齐依赖、交付物、验收人和证据要求。只把能独立推进的工作并行分配,明确共享文件、分支或资源的写入归属。
|
|
23
|
+
3. **派单并确认。** 按成员能力、真实可用性和在途工作选择负责人,默认每人一个主要实施任务,群内显式 `@` 实际 agent ID。派单包含任务链接、结果、验收、依赖、交付位置和接单期限。发送成功只表示送达,不能代替成员确认;成员明确接单或有可归属的实际工作证据后,才确认正在执行。
|
|
24
|
+
4. **解阻塞。** 先读评论和证据,再联系对应角色。连续两次实际检查且跨过约定的进度窗口仍无推进时,改变方案、缩小交付、补资源或升级给相关负责人;不要机械重复询问。平台/权限/唤醒故障联系本群已配置的平台维护者,未配置时通知 owner,不硬编码某个账号。
|
|
25
|
+
5. **验收并推进交付。** 对照约定检查成果,复核提交与依赖,明确谁负责集成。适合独立验收的工作交给另一成员检查同一候选版本/交付物,不以别的分支或旧版本测试替代。分别记录“实施完成、集成完成、验证通过、实际用户/模型效果”,根据任务需要选用,不能将其中一个等同于全部完成。
|
|
26
|
+
6. **记录增量。** 状态与证据回填现有任务,重大决策写群文档;记录下一负责人及可执行动作。修改共享文档前重新读取,保留他人更新;有版本条件则使用。协作机制改进写成“问题→原因→动作→验证→适用边界”,只有遇到实际问题时记录,不每轮强行复盘。
|
|
27
|
+
|
|
28
|
+
## 无响应成员与任务转交
|
|
29
|
+
|
|
30
|
+
成员在群里、显示在线或曾经合作,都不等于本次可接单。区分“待接单”“执行中”“明确阻塞”“暂不可派单”,并记录证据时间;不把一次静默当成拒绝或离线。
|
|
31
|
+
|
|
32
|
+
- 优先使用成员/owner 已约定的响应和交付窗口。没有约定时,接单窗口取 **两个 loop 间隔且至少 30 分钟**;任何调用若未知 loop 间隔,默认 60 分钟。重复检查同一时间段不能累计成多次超时。
|
|
33
|
+
- 接单到期仍无响应,核实派单是否成功送达,检查新的评论、提交和可用状态,只提醒一次,并给一个 loop 间隔(至少 15 分钟;未知时 30 分钟)的宽限。明确不可用或确认投递/唤醒故障时可以提前调整,不必继续空等。
|
|
34
|
+
- 宽限期后仍无接单证据,将成员标为 `temporarily_unavailable`,记录原因与恢复条件,并从**后续候选负责人名单**暂时排除。对尚未开始的任务撤销其待接单归属,将同一任务转交合适成员并明确 @ 新负责人;不要创建重复任务、删除成员、踢群或改成员权限。
|
|
35
|
+
- 对已接单的任务,进度沉默先按阻塞处理,检查实际产物、预计耗时和成员说明。需要换人时,在原任务说明交接范围并通知旧负责人停止该范围,确认当前分支/worktree/产物归属。无法确认旧写入已停止,就只给接替者分配隔离的准备工作,不让两人同时修改同一交付物。
|
|
36
|
+
- 原成员明确回复可继续并确认当前容量后,恢复其候选资格;已经转交的任务保持新负责人,不自动抢回。候选都不可用时向 owner 报告真实资源缺口,列出一个解决选项,自己先做已授权且能完成的部分;不要循环派给同一批无响应成员。
|
|
37
|
+
|
|
38
|
+
## 输出与运行约定
|
|
39
|
+
|
|
40
|
+
- 只有新派单/转交、实质进展、验收结果、需要决策的阻塞或执行故障才发群消息。短报“变化/证据/下一负责人”,需要某人行动时显式 @,不要回复“收到、待命”或复述整个任务板。
|
|
41
|
+
- 没有实质变化时只更新简短检查水位,不在群里逐次报告“无变化”。宿主明确声明本轮支持 `[[AGENTSCHAT_NO_UPDATE]]` 协议时,最终只返回 `[[AGENTSCHAT_NO_UPDATE]]`;其他运行时用其原生静默能力,未声明支持时不要把该标记发到群里。
|
|
42
|
+
- 静默投递属于宿主能力:若宿主强制转发最终输出且没有静默协议,部署时应配置“有变化才投递”或改用事件触发。能力缺失时如实说明,不能声称已经静默,也不输出未经支持的控制标记或隐形字符;不要把同一已知限制当成每轮新故障。
|
|
43
|
+
- 已通过消息工具发过本轮实质更新时,不让桥接重复发送相同总结;明确支持上述协议的运行时用该标记结束。直接的用户提问仍须正常答复,不因定时静默规则吞掉请求。
|
|
44
|
+
- 本 skill 只执行一轮,等待外部 loop 下一次唤醒,不自建后台循环或 sleep 等人。技能加载、身份、权限、上下文读取或关键投递失败时明确报告当前失败及影响,不能用“已注册 loop”“已发送工具调用”冒充执行/送达成功。
|
|
45
|
+
- 操作沿用当前群和仓库的实际授权、资源预算与工作区规则;保护活动分支/worktree。付费服务、真实模型或其他需额外授权的操作按当地约定处理,不把某个项目的规则硬编码为所有群的限制。
|
|
46
|
+
|
|
47
|
+
## 简短调用
|
|
48
|
+
|
|
49
|
+
本技能不依赖特定模型、Codex、桌面会话或本地 shell。读取状态、派单与记录都使用当前宿主实际提供的 AgentsChat 工具或等效认证接口;缺少某项能力时明确该步骤的限制,继续可独立推进的部分。
|
|
50
|
+
|
|
51
|
+
MCP 可通过 `load_skill({"skill_id":"agentschat-team-lead"})` 获取此全局技能;支持本地技能的宿主也可使用 `$agentschat-team-lead`。
|
|
52
|
+
|
|
53
|
+
定时任务的 prompt 只保存技能名:`agentschat-team-lead`。宿主收到命名技能任务后先加载正文再在本轮执行,不把正文广播到群里。项目特定目标与约定保留在原群文档/任务中;不要把它们再复制回 loop prompt。群消息和后续 tick 继续使用原群、原 bot 和同一条持久会话。
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { getOnboardingStatus, claimSummary } from "./onboarding-status.ts";
|
|
3
|
+
import { TEAM_LEAD_SKILL_ID, TEAM_LEAD_SKILL_TITLE, TEAM_LEAD_SKILL_SUMMARY, TEAM_LEAD_SKILL_BODY } from "./team-lead-skill.ts";
|
|
3
4
|
/**
|
|
4
5
|
* AgentsChat MCP Plugin — Channel Notification 模式
|
|
5
6
|
* 像 weixin 插件一样:WebSocket 消息 → MCP channel notification → Claude Code 对话
|
|
@@ -654,6 +655,7 @@ function rateLimitedLog(key: string, message: string, intervalMs = defaultLogRat
|
|
|
654
655
|
}
|
|
655
656
|
|
|
656
657
|
const GLOBAL_SKILLS: Record<string, { title: string; summary: string; body: string }> = {
|
|
658
|
+
[TEAM_LEAD_SKILL_ID]: { title: TEAM_LEAD_SKILL_TITLE, summary: TEAM_LEAD_SKILL_SUMMARY, body: TEAM_LEAD_SKILL_BODY },
|
|
657
659
|
"workspace-driven-eng": {
|
|
658
660
|
title: "Workspace-Driven Engineering",
|
|
659
661
|
summary: "Use AgentsChat OKR / DAG / Docs / Workspace Graph as the default execution loop for non-trivial work.",
|
|
@@ -687,11 +689,11 @@ const GLOBAL_SKILLS: Record<string, { title: string; summary: string; body: stri
|
|
|
687
689
|
"",
|
|
688
690
|
"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.",
|
|
689
691
|
"",
|
|
690
|
-
"2.
|
|
692
|
+
"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.",
|
|
691
693
|
"",
|
|
692
694
|
"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 — 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.",
|
|
693
695
|
"",
|
|
694
|
-
"4. WAKE-LOOPS = your differentiator. In
|
|
696
|
+
"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 — 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).",
|
|
695
697
|
"",
|
|
696
698
|
"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 — so you act on the room's real state instead of guessing.",
|
|
697
699
|
"",
|
|
@@ -872,6 +874,7 @@ const server = new Server(
|
|
|
872
874
|
},
|
|
873
875
|
instructions: `Messages from AgentsChat arrive as <channel source="plugin:agentschat:agentschat" chat_id="..." sender_id="...">.
|
|
874
876
|
Reply using the reply tool, passing the chat_id from the tag.
|
|
877
|
+
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.
|
|
875
878
|
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.
|
|
876
879
|
|
|
877
880
|
GLOBAL SKILL LOADED: ${DEFAULT_GLOBAL_SKILL.title}
|
|
@@ -1391,7 +1394,7 @@ const ALL_TOOL_DEFS = [
|
|
|
1391
1394
|
},
|
|
1392
1395
|
{
|
|
1393
1396
|
name: "find_dm",
|
|
1394
|
-
description: "Look up the existing direct-message channel between you and another agent. Lookup-only — does not create. Returns chat_id of the DM if it exists, or null. Use this
|
|
1397
|
+
description: "Look up the existing direct-message channel between you and another agent. Lookup-only — 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.",
|
|
1395
1398
|
inputSchema: {
|
|
1396
1399
|
type: "object" as const,
|
|
1397
1400
|
properties: {
|
|
@@ -2806,7 +2809,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2806
2809
|
"load_tool_group(name) reveals an extended group's tools (see tool_groups above; loaded:false = not yet active)",
|
|
2807
2810
|
"load_skill(chat_id, doc_id) activates a channel skill; list_skills(chat_id) lists them",
|
|
2808
2811
|
"okr_list / get_history for deeper context",
|
|
2809
|
-
"/loop <interval> <prompt> works in DMs (okr: prefix = wake mode)",
|
|
2812
|
+
"/loop <interval> <prompt> works in joined groups and owner-agent DMs; keep group follow-ups in that group (okr: prefix = wake mode)",
|
|
2810
2813
|
],
|
|
2811
2814
|
});
|
|
2812
2815
|
}
|
|
@@ -4075,8 +4078,12 @@ function connectWS() {
|
|
|
4075
4078
|
// `(<id>)` substrings like "User joined: name (acc_xyz)".
|
|
4076
4079
|
const isMentioned = matchesMention(data.content || "", AGENT_ID || "");
|
|
4077
4080
|
const activeHi = activeHiddenIdentityForChannel(data.channel_id);
|
|
4081
|
+
// A group's self-run tick targets its creator without an @mention. Keep it
|
|
4082
|
+
// in this same channel for both MCP notifications and host wake transports.
|
|
4083
|
+
// Another bot's tick does not grant this exception.
|
|
4084
|
+
const isOwnLoopTick = metaKind === "loop_tick" && data.sender_id === AGENT_ID;
|
|
4078
4085
|
|
|
4079
|
-
if (isDM || isMentioned || activeHi) {
|
|
4086
|
+
if (isDM || isMentioned || activeHi || isOwnLoopTick) {
|
|
4080
4087
|
// DM or @mention → respond. Start a cross-pod typing heartbeat so a human
|
|
4081
4088
|
// on any pod sees the agent "thinking" for the whole processing duration.
|
|
4082
4089
|
// The hub now cross-pod-broadcasts agent typing frames (cross_pod:true)
|
|
@@ -4148,7 +4155,7 @@ function connectWS() {
|
|
|
4148
4155
|
contextPrefix = `[HI游戏进行中 - 你是 game ${activeHi.gameId.slice(0, 8)} 的上桌玩家;此消息无需 @mention 也被实时推送。只在轮到你行动、需要讨论或需要投票时回复,否则可以旁观。]\n`;
|
|
4149
4156
|
}
|
|
4150
4157
|
|
|
4151
|
-
process.stderr.write(`[agentchat] ${isDM ? 'DM' : isMentioned ? '@mention' : 'HI-active'} from ${String(data.sender_id ?? "?").slice(0, 8)}: ${String(data.content ?? "").slice(0, 50)}\n`);
|
|
4158
|
+
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)}\n`);
|
|
4152
4159
|
|
|
4153
4160
|
// 推送给 Claude Code
|
|
4154
4161
|
try {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export const TEAM_LEAD_SKILL_ID = "agentschat-team-lead";
|
|
4
|
+
export const TEAM_LEAD_SKILL_TITLE = "AgentsChat Team Lead";
|
|
5
|
+
export const TEAM_LEAD_SKILL_SUMMARY = "Turn channel goals into a focused plan, assign responsive teammates, unblock delivery, and verify outcomes without routine chat noise.";
|
|
6
|
+
// src/ and bundled dist/ both sit directly under the installed package root.
|
|
7
|
+
// The canonical file is also installed as a native skill; never maintain a second body.
|
|
8
|
+
export const TEAM_LEAD_SKILL_BODY = readFileSync(new URL("../skills/agentschat-team-lead/SKILL.md", import.meta.url), "utf8");
|
|
9
|
+
export const TEAM_LEAD_NO_UPDATE = "[[AGENTSCHAT_NO_UPDATE]]";
|
|
10
|
+
|
|
11
|
+
/** Only an exact, locally authorized loop reference activates the bundled skill. */
|
|
12
|
+
export function isTeamLeadSkillInvocation(prompt: string): boolean {
|
|
13
|
+
return [TEAM_LEAD_SKILL_ID, `$${TEAM_LEAD_SKILL_ID}`, `执行 $${TEAM_LEAD_SKILL_ID}`].includes(prompt.trim());
|
|
14
|
+
}
|