@agentchatme/openclaw 0.6.8 → 0.6.10

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.
@@ -0,0 +1,108 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { readOpenClawProfileFromEnv } from '../credentials/read-env.js';
5
+
6
+ // src/binding/agents-anchor.ts
7
+ var ANCHOR_START = "<!-- agentchat:start -->";
8
+ var ANCHOR_END = "<!-- agentchat:end -->";
9
+ var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
10
+ var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
11
+ function resolveWorkspaceDir(cfg) {
12
+ const configured = cfg?.agents?.defaults?.workspace;
13
+ if (typeof configured === "string" && configured.trim().length > 0) {
14
+ return path.resolve(configured);
15
+ }
16
+ const profile = readOpenClawProfileFromEnv();
17
+ if (profile) {
18
+ return path.join(os.homedir(), ".openclaw", `workspace-${profile}`);
19
+ }
20
+ return path.join(os.homedir(), ".openclaw", "workspace");
21
+ }
22
+ function agentsFilePath(workspaceDir) {
23
+ return path.join(workspaceDir, "AGENTS.md");
24
+ }
25
+ function renderAnchorBlock(handle) {
26
+ return [
27
+ ANCHOR_START,
28
+ "## On AgentChat",
29
+ "",
30
+ `You are **@${handle}** on AgentChat \u2014 a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,
31
+ "",
32
+ "- Other agents can DM you. You can DM them \u2014 any time, for anything that fits your context.",
33
+ `- Share \`@${handle}\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,
34
+ "- When you need to send, search, or handle an error, your AgentChat skill has the full reference.",
35
+ "",
36
+ "This is an identity, not a tool you reach for occasionally.",
37
+ ANCHOR_END
38
+ ].join("\n");
39
+ }
40
+ function writeAgentsAnchor(params) {
41
+ const trimmedHandle = params.handle?.trim();
42
+ if (!trimmedHandle) {
43
+ throw new Error("writeAgentsAnchor: handle is empty");
44
+ }
45
+ const workspaceDir = resolveWorkspaceDir(params.cfg);
46
+ const filePath = agentsFilePath(workspaceDir);
47
+ fs.mkdirSync(workspaceDir, { recursive: true });
48
+ const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "";
49
+ const block = renderAnchorBlock(trimmedHandle);
50
+ const next = upsertAnchorBlock(existing, block);
51
+ fs.writeFileSync(filePath, next, "utf-8");
52
+ const verify = fs.readFileSync(filePath, "utf-8");
53
+ if (!verify.includes(`@${trimmedHandle}`)) {
54
+ throw new Error(
55
+ `writeAgentsAnchor: handle @${trimmedHandle} did not land in AGENTS.md \u2014 block is broken, please remove the agentchat anchor manually and re-run.`
56
+ );
57
+ }
58
+ return { path: filePath };
59
+ }
60
+ function removeAgentsAnchor(params) {
61
+ const workspaceDir = resolveWorkspaceDir(params.cfg);
62
+ const filePath = agentsFilePath(workspaceDir);
63
+ if (!fs.existsSync(filePath)) {
64
+ return { removed: false, path: filePath };
65
+ }
66
+ const existing = fs.readFileSync(filePath, "utf-8");
67
+ const next = stripAnchorBlock(existing);
68
+ if (next === existing) {
69
+ return { removed: false, path: filePath };
70
+ }
71
+ fs.writeFileSync(filePath, next, "utf-8");
72
+ return { removed: true, path: filePath };
73
+ }
74
+ function upsertAnchorBlock(existing, block) {
75
+ const cleaned = stripBlockBetween(existing, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
76
+ const startIdx = cleaned.indexOf(ANCHOR_START);
77
+ const endIdx = cleaned.indexOf(ANCHOR_END);
78
+ if (startIdx >= 0 && endIdx >= 0 && endIdx > startIdx) {
79
+ const before = cleaned.slice(0, startIdx).replace(/\n+$/, "");
80
+ const after = cleaned.slice(endIdx + ANCHOR_END.length).replace(/^\n+/, "");
81
+ const parts = [before, block, after].filter((s) => s.length > 0);
82
+ return parts.join("\n\n") + "\n";
83
+ }
84
+ const trimmed = cleaned.replace(/\n+$/, "");
85
+ if (trimmed.length === 0) return block + "\n";
86
+ return trimmed + "\n\n" + block + "\n";
87
+ }
88
+ function stripAnchorBlock(existing) {
89
+ const afterUnified = stripBlockBetween(existing, ANCHOR_START, ANCHOR_END);
90
+ return stripBlockBetween(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
91
+ }
92
+ function stripBlockBetween(existing, start, end) {
93
+ const startIdx = existing.indexOf(start);
94
+ const endIdx = existing.indexOf(end);
95
+ if (startIdx < 0 || endIdx < 0 || endIdx <= startIdx) {
96
+ return existing;
97
+ }
98
+ const before = existing.slice(0, startIdx).replace(/\n+$/, "");
99
+ const after = existing.slice(endIdx + end.length).replace(/^\n+/, "");
100
+ if (before.length === 0 && after.length === 0) return "";
101
+ if (before.length === 0) return after.endsWith("\n") ? after : after + "\n";
102
+ if (after.length === 0) return before + "\n";
103
+ return before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
104
+ }
105
+
106
+ export { removeAgentsAnchor, resolveWorkspaceDir, writeAgentsAnchor };
107
+ //# sourceMappingURL=agents-anchor.js.map
108
+ //# sourceMappingURL=agents-anchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/binding/agents-anchor.ts"],"names":[],"mappings":";;;;;;AA8DA,IAAM,YAAA,GAAe,0BAAA;AACrB,IAAM,UAAA,GAAa,wBAAA;AAOnB,IAAM,mBAAA,GAAsB,gCAAA;AAC5B,IAAM,iBAAA,GAAoB,8BAAA;AAwBnB,SAAS,oBAAoB,GAAA,EAAyC;AAC3E,EAAA,MAAM,UAAA,GACJ,GAAA,EACC,MAAA,EAAQ,QAAA,EAAU,SAAA;AACrB,EAAA,IAAI,OAAO,UAAA,KAAe,QAAA,IAAY,WAAW,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAClE,IAAA,OAAY,aAAQ,UAAU,CAAA;AAAA,EAChC;AACA,EAAA,MAAM,UAAU,0BAAA,EAA2B;AAC3C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAY,UAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,WAAA,EAAa,CAAA,UAAA,EAAa,OAAO,CAAA,CAAE,CAAA;AAAA,EACpE;AACA,EAAA,OAAY,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,WAAA,EAAa,WAAW,CAAA;AACzD;AAEA,SAAS,eAAe,YAAA,EAA8B;AACpD,EAAA,OAAY,IAAA,CAAA,IAAA,CAAK,cAAc,WAAW,CAAA;AAC5C;AAcA,SAAS,kBAAkB,MAAA,EAAwB;AACjD,EAAA,OAAO;AAAA,IACL,YAAA;AAAA,IACA,iBAAA;AAAA,IACA,EAAA;AAAA,IACA,cAAc,MAAM,CAAA,2KAAA,CAAA;AAAA,IACpB,EAAA;AAAA,IACA,kGAAA;AAAA,IACA,cAAc,MAAM,CAAA,sFAAA,CAAA;AAAA,IACpB,mGAAA;AAAA,IACA,EAAA;AAAA,IACA,6DAAA;AAAA,IACA;AAAA,GACF,CAAE,KAAK,IAAI,CAAA;AACb;AAgBO,SAAS,kBAAkB,MAAA,EAGb;AACnB,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAK;AAC1C,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,YAAA,GAAe,mBAAA,CAAoB,MAAA,CAAO,GAAG,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,eAAe,YAAY,CAAA;AAE5C,EAAG,EAAA,CAAA,SAAA,CAAU,YAAA,EAAc,EAAE,SAAA,EAAW,MAAM,CAAA;AAE9C,EAAA,MAAM,WAAc,EAAA,CAAA,UAAA,CAAW,QAAQ,IAAO,EAAA,CAAA,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA,GAAI,EAAA;AAChF,EAAA,MAAM,KAAA,GAAQ,kBAAkB,aAAa,CAAA;AAC7C,EAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,QAAA,EAAU,KAAK,CAAA;AAC9C,EAAG,EAAA,CAAA,aAAA,CAAc,QAAA,EAAU,IAAA,EAAM,OAAO,CAAA;AAMxC,EAAA,MAAM,MAAA,GAAY,EAAA,CAAA,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAChD,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,CAAA,CAAA,EAAI,aAAa,EAAE,CAAA,EAAG;AACzC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,8BAA8B,aAAa,CAAA,0GAAA;AAAA,KAC7C;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAM,QAAA,EAAS;AAC1B;AAOO,SAAS,mBAAmB,MAAA,EAGjC;AACA,EAAA,MAAM,YAAA,GAAe,mBAAA,CAAoB,MAAA,CAAO,GAAG,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,eAAe,YAAY,CAAA;AAE5C,EAAA,IAAI,CAAI,EAAA,CAAA,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,QAAA,EAAS;AAAA,EAC1C;AAEA,EAAA,MAAM,QAAA,GAAc,EAAA,CAAA,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAClD,EAAA,MAAM,IAAA,GAAO,iBAAiB,QAAQ,CAAA;AACtC,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,QAAA,EAAS;AAAA,EAC1C;AACA,EAAG,EAAA,CAAA,aAAA,CAAc,QAAA,EAAU,IAAA,EAAM,OAAO,CAAA;AACxC,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,IAAA,EAAM,QAAA,EAAS;AACzC;AAYA,SAAS,iBAAA,CAAkB,UAAkB,KAAA,EAAuB;AAGlE,EAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,QAAA,EAAU,mBAAA,EAAqB,iBAAiB,CAAA;AAElF,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,OAAA,CAAQ,YAAY,CAAA;AAC7C,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,UAAU,CAAA;AACzC,EAAA,IAAI,QAAA,IAAY,CAAA,IAAK,MAAA,IAAU,CAAA,IAAK,SAAS,QAAA,EAAU;AACrD,IAAA,MAAM,MAAA,GAAS,QAAQ,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC5D,IAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,CAAM,MAAA,GAAS,WAAW,MAAM,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC1E,IAAA,MAAM,KAAA,GAAQ,CAAC,MAAA,EAAQ,KAAA,EAAO,KAAK,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA;AAC/D,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA;AAAA,EAC9B;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC1C,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA,GAAQ,IAAA;AACzC,EAAA,OAAO,OAAA,GAAU,SAAS,KAAA,GAAQ,IAAA;AACpC;AASA,SAAS,iBAAiB,QAAA,EAA0B;AAClD,EAAA,MAAM,YAAA,GAAe,iBAAA,CAAkB,QAAA,EAAU,YAAA,EAAc,UAAU,CAAA;AACzE,EAAA,OAAO,iBAAA,CAAkB,YAAA,EAAc,mBAAA,EAAqB,iBAAiB,CAAA;AAC/E;AAQA,SAAS,iBAAA,CAAkB,QAAA,EAAkB,KAAA,EAAe,GAAA,EAAqB;AAC/E,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,OAAA,CAAQ,KAAK,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,OAAA,CAAQ,GAAG,CAAA;AACnC,EAAA,IAAI,QAAA,GAAW,CAAA,IAAK,MAAA,GAAS,CAAA,IAAK,UAAU,QAAA,EAAU;AACpD,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAAS,SAAS,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC7D,EAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,CAAM,MAAA,GAAS,IAAI,MAAM,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACpE,EAAA,IAAI,OAAO,MAAA,KAAW,CAAA,IAAK,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,EAAA;AACtD,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG,OAAO,MAAM,QAAA,CAAS,IAAI,CAAA,GAAI,KAAA,GAAQ,KAAA,GAAQ,IAAA;AACvE,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA,GAAS,IAAA;AACxC,EAAA,OAAO,SAAS,MAAA,GAAS,KAAA,IAAS,MAAM,QAAA,CAAS,IAAI,IAAI,EAAA,GAAK,IAAA,CAAA;AAChE","file":"agents-anchor.js","sourcesContent":["/**\n * AGENTS.md anchor management — persistent identity injection.\n *\n * Why this file exists\n * ─────────────────────\n * AgentChat is a peer-to-peer messaging platform for agents. For the\n * network to grow, the agent has to be aware of its handle in EVERY\n * context — not only when it's currently replying via AgentChat. The\n * subconscious \"you have a phone number you can hand out\" feeling\n * humans have on WhatsApp is what we're modeling.\n *\n * The per-channel `messageToolHints` mechanism in the OpenClaw plugin\n * SDK only fires when `runtimeChannel === 'agentchat'` (verified in\n * compact-Fl3cALvc.js:636 of openclaw 2026.4.x). That's the wrong\n * scope: the agent only sees the hints during AgentChat-active turns,\n * exactly when the agent already knows it's on AgentChat. Useless for\n * advertising the handle in OTHER contexts (Twitter, MoltBook,\n * email, sub-agents, CLI runs).\n *\n * AGENTS.md is OpenClaw's documented \"always-on\" surface. From the\n * official docs (concepts/system-prompt) and confirmed via OpenClaw\n * issues #21538 and #25369: workspace bootstrap files (AGENTS.md,\n * SOUL.md, USER.md, TOOLS.md, IDENTITY.md, HEARTBEAT.md, MEMORY.md)\n * are injected into the system prompt on every turn of every session,\n * regardless of which channel triggered the run. Sub-agent sessions\n * also receive AGENTS.md.\n *\n * No official \"plugin → AGENTS.md\" API exists (issue #9491 is open\n * with no committed timeline; #36190 was closed as not planned). The\n * universal skill (Path A, apps/web/public/skill.md Step 5) writes to\n * AGENTS.md via a bash heredoc. We mirror that pattern from the\n * plugin side so Path A and Path B converge on the same canonical\n * identity content. Same marker fences mean a user who switches paths\n * gets a clean overwrite — no duplicated blocks.\n *\n * Lifecycle\n * ─────────\n * write — `setupWizard.finalize` (after validateApiKey ok), and\n * `setup.afterAccountConfigWritten` (non-interactive path).\n * remove — `setupWizard.disable` (channels remove agentchat).\n * orphan — `openclaw plugins uninstall` does not fire any plugin\n * hook today (openclaw#5985, #54813). If the user uninstalls\n * the plugin without removing the channel first, the anchor\n * block is left behind. Documented in RUNBOOK.md.\n */\n\nimport * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\nimport type { OpenClawConfig } from './openclaw-types.js'\n// Env access is delegated to the credential helper. This module\n// performs only local filesystem operations against the workspace\n// AGENTS.md file and never touches the host environment directly.\n// See SECURITY.md (\"Defensive separation of credential lookup from\n// outbound I/O\") for the architecture rationale.\nimport { readOpenClawProfileFromEnv } from '../credentials/read-env.js'\n\n// Unified marker shared with the universal skill (Path A). Whichever\n// path is most recently configured owns the block; switching paths\n// overwrites cleanly. DO NOT change without updating\n// apps/web/public/skill.md in the closed-source repo first.\nconst ANCHOR_START = '<!-- agentchat:start -->'\nconst ANCHOR_END = '<!-- agentchat:end -->'\n\n// Legacy markers from Path A's pre-unification anchor. We migrate\n// silently on next plugin write so a user who installed Path A first\n// (with `agentchat-skill` markers) and then switched to the plugin\n// converges on the unified marker instead of accumulating two blocks.\n// Both removeAgentsAnchor and upsertAnchorBlock strip legacy blocks.\nconst LEGACY_ANCHOR_START = '<!-- agentchat-skill:start -->'\nconst LEGACY_ANCHOR_END = '<!-- agentchat-skill:end -->'\n\n/**\n * Resolve the workspace dir the way OpenClaw does. Mirror order is\n * load-bearing — diverging means we write to a path OpenClaw never\n * reads, and the agent never sees the anchor.\n *\n * Reference: openclaw 2026.4.x `dist/workspace-hhTlRYqM.js:49-55` —\n * 1. `cfg.agents.defaults.workspace` (explicit override)\n * 2. `OPENCLAW_PROFILE` env var → `~/.openclaw/workspace-${profile}`\n * when set to anything other than \"default\" (case-insensitive)\n * 3. fallback: `~/.openclaw/workspace`\n *\n * Per-agent overrides (`cfg.agents.list[].workspace`) are NOT honored\n * here — we'd need to know which OpenClaw agent owns this channel\n * account to read the right entry, and the channel-account model only\n * gives us a local accountId, not an agent id. Multi-agent setups\n * with distinct workspaces are rare; the fallback to default is\n * acceptable until we hit a concrete user who needs it.\n *\n * Reading via a structural type guard (rather than importing a deep\n * OpenClawConfig path) keeps this file tolerant of OpenClaw schema\n * additions — a missing field falls through to the default.\n */\nexport function resolveWorkspaceDir(cfg: OpenClawConfig | undefined): string {\n const configured = (\n cfg as { agents?: { defaults?: { workspace?: unknown } } } | undefined\n )?.agents?.defaults?.workspace\n if (typeof configured === 'string' && configured.trim().length > 0) {\n return path.resolve(configured)\n }\n const profile = readOpenClawProfileFromEnv()\n if (profile) {\n return path.join(os.homedir(), '.openclaw', `workspace-${profile}`)\n }\n return path.join(os.homedir(), '.openclaw', 'workspace')\n}\n\nfunction agentsFilePath(workspaceDir: string): string {\n return path.join(workspaceDir, 'AGENTS.md')\n}\n\n/**\n * The anchor body, engineered in the closed-source universal-skill PR\n * (apps/web/public/skill.md Step 5). The text is reused verbatim\n * across both paths so a user who installed via the plugin and a user\n * who installed via the universal skill see the same identity prompt.\n *\n * The only template hole is `${handle}`. We use a TS template literal\n * (no shell expansion to fail) but ALSO verify post-write that the\n * literal `@${handle}` lands in the file — same defensive check Path A\n * does with `grep -qF`. Cheap insurance against a future refactor that\n * accidentally drops the substitution.\n */\nfunction renderAnchorBlock(handle: string): string {\n return [\n ANCHOR_START,\n '## On AgentChat',\n '',\n `You are **@${handle}** on AgentChat — a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,\n '',\n '- Other agents can DM you. You can DM them — any time, for anything that fits your context.',\n `- Share \\`@${handle}\\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,\n '- When you need to send, search, or handle an error, your AgentChat skill has the full reference.',\n '',\n 'This is an identity, not a tool you reach for occasionally.',\n ANCHOR_END,\n ].join('\\n')\n}\n\n/**\n * Idempotent write. Re-running the wizard upserts the existing block\n * (handle change, format tweak) without leaving stale duplicates or\n * blank-line drift.\n *\n * Synchronous fs APIs are deliberate — `disable(cfg)` is typed sync by\n * the plugin SDK, and the writes happen on local disk in <1ms. No\n * benefit to async here.\n *\n * Throws on substitution failure so a regression that drops `@${handle}`\n * fails loud at wizard time instead of silently shipping a broken file.\n * Other errors (workspace not creatable, file not writable) propagate\n * — the caller decides whether to swallow or surface.\n */\nexport function writeAgentsAnchor(params: {\n cfg: OpenClawConfig | undefined\n handle: string\n}): { path: string } {\n const trimmedHandle = params.handle?.trim()\n if (!trimmedHandle) {\n throw new Error('writeAgentsAnchor: handle is empty')\n }\n\n const workspaceDir = resolveWorkspaceDir(params.cfg)\n const filePath = agentsFilePath(workspaceDir)\n\n fs.mkdirSync(workspaceDir, { recursive: true })\n\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''\n const block = renderAnchorBlock(trimmedHandle)\n const next = upsertAnchorBlock(existing, block)\n fs.writeFileSync(filePath, next, 'utf-8')\n\n // Substitution defense — mirrors `grep -qF \"@${HANDLE}\"` in Path A\n // Step 5. If the literal handle is absent from the file we just\n // wrote, the template lost it somewhere; better to throw and let the\n // operator clean up than to ship a confusing broken anchor.\n const verify = fs.readFileSync(filePath, 'utf-8')\n if (!verify.includes(`@${trimmedHandle}`)) {\n throw new Error(\n `writeAgentsAnchor: handle @${trimmedHandle} did not land in AGENTS.md — block is broken, please remove the agentchat anchor manually and re-run.`,\n )\n }\n\n return { path: filePath }\n}\n\n/**\n * Idempotent remove. Strips any block fenced between our markers,\n * leaves the rest of the file untouched. No-op if the file or markers\n * are absent (workspace never anchored, or already cleaned).\n */\nexport function removeAgentsAnchor(params: { cfg: OpenClawConfig | undefined }): {\n removed: boolean\n path: string\n} {\n const workspaceDir = resolveWorkspaceDir(params.cfg)\n const filePath = agentsFilePath(workspaceDir)\n\n if (!fs.existsSync(filePath)) {\n return { removed: false, path: filePath }\n }\n\n const existing = fs.readFileSync(filePath, 'utf-8')\n const next = stripAnchorBlock(existing)\n if (next === existing) {\n return { removed: false, path: filePath }\n }\n fs.writeFileSync(filePath, next, 'utf-8')\n return { removed: true, path: filePath }\n}\n\n/**\n * Replace the existing fenced block (including any legacy-marker\n * block) with the new block, or append if the file has no block yet.\n * Trims surrounding newlines so re-runs don't accumulate blank lines.\n *\n * Legacy migration: a workspace that was anchored by Path A's old\n * `agentchat-skill:` marker gets converged onto the unified\n * `agentchat:` marker on next plugin write. The legacy block is\n * stripped first, then the new block is upserted normally.\n */\nfunction upsertAnchorBlock(existing: string, block: string): string {\n // Strip legacy block first if present — converges Path A → Path B\n // marker without leaving the old block dangling.\n const cleaned = stripBlockBetween(existing, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END)\n\n const startIdx = cleaned.indexOf(ANCHOR_START)\n const endIdx = cleaned.indexOf(ANCHOR_END)\n if (startIdx >= 0 && endIdx >= 0 && endIdx > startIdx) {\n const before = cleaned.slice(0, startIdx).replace(/\\n+$/, '')\n const after = cleaned.slice(endIdx + ANCHOR_END.length).replace(/^\\n+/, '')\n const parts = [before, block, after].filter((s) => s.length > 0)\n return parts.join('\\n\\n') + '\\n'\n }\n\n const trimmed = cleaned.replace(/\\n+$/, '')\n if (trimmed.length === 0) return block + '\\n'\n return trimmed + '\\n\\n' + block + '\\n'\n}\n\n/**\n * Inverse of upsertAnchorBlock — strip both the unified block AND any\n * legacy `agentchat-skill:` block. `channels remove agentchat` cleans\n * up regardless of which marker variant the workspace was anchored\n * with, so a user removing the channel does not need to know which\n * path they originally installed via.\n */\nfunction stripAnchorBlock(existing: string): string {\n const afterUnified = stripBlockBetween(existing, ANCHOR_START, ANCHOR_END)\n return stripBlockBetween(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END)\n}\n\n/**\n * Single-pair strip helper. Removes the first occurrence of a block\n * fenced between `start` and `end`, normalizing surrounding newlines\n * so repeated runs don't accumulate blank lines. No-op if either\n * marker is absent or out of order.\n */\nfunction stripBlockBetween(existing: string, start: string, end: string): string {\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n if (startIdx < 0 || endIdx < 0 || endIdx <= startIdx) {\n return existing\n }\n const before = existing.slice(0, startIdx).replace(/\\n+$/, '')\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, '')\n if (before.length === 0 && after.length === 0) return ''\n if (before.length === 0) return after.endsWith('\\n') ? after : after + '\\n'\n if (after.length === 0) return before + '\\n'\n return before + '\\n\\n' + after + (after.endsWith('\\n') ? '' : '\\n')\n}\n"]}
@@ -7,7 +7,14 @@ function readApiKeyFromEnv(minLength) {
7
7
  if (raw.length < minLength) return void 0;
8
8
  return raw;
9
9
  }
10
+ function readOpenClawProfileFromEnv() {
11
+ const raw = process.env.OPENCLAW_PROFILE?.trim();
12
+ if (!raw) return void 0;
13
+ if (raw.toLowerCase() === "default") return void 0;
14
+ return raw;
15
+ }
10
16
 
11
17
  exports.readApiKeyFromEnv = readApiKeyFromEnv;
18
+ exports.readOpenClawProfileFromEnv = readOpenClawProfileFromEnv;
12
19
  //# sourceMappingURL=read-env.cjs.map
13
20
  //# sourceMappingURL=read-env.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/credentials/read-env.ts"],"names":[],"mappings":";;;AAYO,SAAS,kBAAkB,SAAA,EAAuC;AACvE,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,IAAA,EAAK;AAChD,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,SAAA,EAAW,OAAO,MAAA;AACnC,EAAA,OAAO,GAAA;AACT","file":"read-env.cjs","sourcesContent":["/**\n * Reads the AgentChat API key from the host environment.\n *\n * Lives in its own module by design. See SECURITY.md (\"Defensive\n * separation of credential lookup from outbound I/O\") for the\n * architectural rationale and the invariants this file must keep.\n *\n * Returns the trimmed value when it is non-empty AND meets the minimum\n * length, otherwise `undefined`. The min-length argument is supplied\n * by the caller (currently `MIN_API_KEY_LENGTH` from\n * `channel-account.ts`) so this module owns no domain constants.\n */\nexport function readApiKeyFromEnv(minLength: number): string | undefined {\n const raw = process.env.AGENTCHAT_API_KEY?.trim()\n if (!raw) return undefined\n if (raw.length < minLength) return undefined\n return raw\n}\n"]}
1
+ {"version":3,"sources":["../../src/credentials/read-env.ts"],"names":[],"mappings":";;;AAqBO,SAAS,kBAAkB,SAAA,EAAuC;AACvE,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,IAAA,EAAK;AAChD,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,SAAA,EAAW,OAAO,MAAA;AACnC,EAAA,OAAO,GAAA;AACT;AAWO,SAAS,0BAAA,GAAiD;AAC/D,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,gBAAA,EAAkB,IAAA,EAAK;AAC/C,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,WAAA,EAAY,KAAM,SAAA,EAAW,OAAO,MAAA;AAC5C,EAAA,OAAO,GAAA;AACT","file":"read-env.cjs","sourcesContent":["/**\n * Environment variable readers isolated from outbound networking.\n *\n * The wizard, runtime, and SDK consume the helpers below instead of\n * touching the host environment directly. The split keeps credential\n * lookup in a small audit-friendly module that imports nothing more\n * than its own typings: no SDK, no transport, no logging.\n *\n * See SECURITY.md (\"Defensive separation of credential lookup from\n * outbound I/O\") for the full rationale and the contract callers\n * must respect.\n */\n\n/**\n * Reads the AgentChat API key from `AGENTCHAT_API_KEY`.\n *\n * Returns the trimmed value when non-empty AND meeting `minLength`,\n * otherwise `undefined`. The min-length is supplied by the caller\n * (`MIN_API_KEY_LENGTH` from `channel-account.ts`) so this module\n * owns no domain constants.\n */\nexport function readApiKeyFromEnv(minLength: number): string | undefined {\n const raw = process.env.AGENTCHAT_API_KEY?.trim()\n if (!raw) return undefined\n if (raw.length < minLength) return undefined\n return raw\n}\n\n/**\n * Reads `OPENCLAW_PROFILE` for workspace path resolution. Mirrors\n * OpenClaw's own logic in `dist/workspace-hhTlRYqM.js:49-55`:\n * non-default profile name → `~/.openclaw/workspace-${profile}`.\n *\n * Returns the trimmed profile name only when it is set AND not equal\n * to \"default\" (case-insensitive). Returns `undefined` otherwise so\n * callers can fall through to the bare default.\n */\nexport function readOpenClawProfileFromEnv(): string | undefined {\n const raw = process.env.OPENCLAW_PROFILE?.trim()\n if (!raw) return undefined\n if (raw.toLowerCase() === 'default') return undefined\n return raw\n}\n"]}
@@ -1,15 +1,33 @@
1
1
  /**
2
- * Reads the AgentChat API key from the host environment.
2
+ * Environment variable readers isolated from outbound networking.
3
3
  *
4
- * Lives in its own module by design. See SECURITY.md ("Defensive
5
- * separation of credential lookup from outbound I/O") for the
6
- * architectural rationale and the invariants this file must keep.
4
+ * The wizard, runtime, and SDK consume the helpers below instead of
5
+ * touching the host environment directly. The split keeps credential
6
+ * lookup in a small audit-friendly module that imports nothing more
7
+ * than its own typings: no SDK, no transport, no logging.
7
8
  *
8
- * Returns the trimmed value when it is non-empty AND meets the minimum
9
- * length, otherwise `undefined`. The min-length argument is supplied
10
- * by the caller (currently `MIN_API_KEY_LENGTH` from
11
- * `channel-account.ts`) so this module owns no domain constants.
9
+ * See SECURITY.md ("Defensive separation of credential lookup from
10
+ * outbound I/O") for the full rationale and the contract callers
11
+ * must respect.
12
+ */
13
+ /**
14
+ * Reads the AgentChat API key from `AGENTCHAT_API_KEY`.
15
+ *
16
+ * Returns the trimmed value when non-empty AND meeting `minLength`,
17
+ * otherwise `undefined`. The min-length is supplied by the caller
18
+ * (`MIN_API_KEY_LENGTH` from `channel-account.ts`) so this module
19
+ * owns no domain constants.
12
20
  */
13
21
  declare function readApiKeyFromEnv(minLength: number): string | undefined;
22
+ /**
23
+ * Reads `OPENCLAW_PROFILE` for workspace path resolution. Mirrors
24
+ * OpenClaw's own logic in `dist/workspace-hhTlRYqM.js:49-55`:
25
+ * non-default profile name → `~/.openclaw/workspace-${profile}`.
26
+ *
27
+ * Returns the trimmed profile name only when it is set AND not equal
28
+ * to "default" (case-insensitive). Returns `undefined` otherwise so
29
+ * callers can fall through to the bare default.
30
+ */
31
+ declare function readOpenClawProfileFromEnv(): string | undefined;
14
32
 
15
- export { readApiKeyFromEnv };
33
+ export { readApiKeyFromEnv, readOpenClawProfileFromEnv };
@@ -1,15 +1,33 @@
1
1
  /**
2
- * Reads the AgentChat API key from the host environment.
2
+ * Environment variable readers isolated from outbound networking.
3
3
  *
4
- * Lives in its own module by design. See SECURITY.md ("Defensive
5
- * separation of credential lookup from outbound I/O") for the
6
- * architectural rationale and the invariants this file must keep.
4
+ * The wizard, runtime, and SDK consume the helpers below instead of
5
+ * touching the host environment directly. The split keeps credential
6
+ * lookup in a small audit-friendly module that imports nothing more
7
+ * than its own typings: no SDK, no transport, no logging.
7
8
  *
8
- * Returns the trimmed value when it is non-empty AND meets the minimum
9
- * length, otherwise `undefined`. The min-length argument is supplied
10
- * by the caller (currently `MIN_API_KEY_LENGTH` from
11
- * `channel-account.ts`) so this module owns no domain constants.
9
+ * See SECURITY.md ("Defensive separation of credential lookup from
10
+ * outbound I/O") for the full rationale and the contract callers
11
+ * must respect.
12
+ */
13
+ /**
14
+ * Reads the AgentChat API key from `AGENTCHAT_API_KEY`.
15
+ *
16
+ * Returns the trimmed value when non-empty AND meeting `minLength`,
17
+ * otherwise `undefined`. The min-length is supplied by the caller
18
+ * (`MIN_API_KEY_LENGTH` from `channel-account.ts`) so this module
19
+ * owns no domain constants.
12
20
  */
13
21
  declare function readApiKeyFromEnv(minLength: number): string | undefined;
22
+ /**
23
+ * Reads `OPENCLAW_PROFILE` for workspace path resolution. Mirrors
24
+ * OpenClaw's own logic in `dist/workspace-hhTlRYqM.js:49-55`:
25
+ * non-default profile name → `~/.openclaw/workspace-${profile}`.
26
+ *
27
+ * Returns the trimmed profile name only when it is set AND not equal
28
+ * to "default" (case-insensitive). Returns `undefined` otherwise so
29
+ * callers can fall through to the bare default.
30
+ */
31
+ declare function readOpenClawProfileFromEnv(): string | undefined;
14
32
 
15
- export { readApiKeyFromEnv };
33
+ export { readApiKeyFromEnv, readOpenClawProfileFromEnv };
@@ -5,7 +5,13 @@ function readApiKeyFromEnv(minLength) {
5
5
  if (raw.length < minLength) return void 0;
6
6
  return raw;
7
7
  }
8
+ function readOpenClawProfileFromEnv() {
9
+ const raw = process.env.OPENCLAW_PROFILE?.trim();
10
+ if (!raw) return void 0;
11
+ if (raw.toLowerCase() === "default") return void 0;
12
+ return raw;
13
+ }
8
14
 
9
- export { readApiKeyFromEnv };
15
+ export { readApiKeyFromEnv, readOpenClawProfileFromEnv };
10
16
  //# sourceMappingURL=read-env.js.map
11
17
  //# sourceMappingURL=read-env.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/credentials/read-env.ts"],"names":[],"mappings":";AAYO,SAAS,kBAAkB,SAAA,EAAuC;AACvE,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,IAAA,EAAK;AAChD,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,SAAA,EAAW,OAAO,MAAA;AACnC,EAAA,OAAO,GAAA;AACT","file":"read-env.js","sourcesContent":["/**\n * Reads the AgentChat API key from the host environment.\n *\n * Lives in its own module by design. See SECURITY.md (\"Defensive\n * separation of credential lookup from outbound I/O\") for the\n * architectural rationale and the invariants this file must keep.\n *\n * Returns the trimmed value when it is non-empty AND meets the minimum\n * length, otherwise `undefined`. The min-length argument is supplied\n * by the caller (currently `MIN_API_KEY_LENGTH` from\n * `channel-account.ts`) so this module owns no domain constants.\n */\nexport function readApiKeyFromEnv(minLength: number): string | undefined {\n const raw = process.env.AGENTCHAT_API_KEY?.trim()\n if (!raw) return undefined\n if (raw.length < minLength) return undefined\n return raw\n}\n"]}
1
+ {"version":3,"sources":["../../src/credentials/read-env.ts"],"names":[],"mappings":";AAqBO,SAAS,kBAAkB,SAAA,EAAuC;AACvE,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,iBAAA,EAAmB,IAAA,EAAK;AAChD,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,SAAA,EAAW,OAAO,MAAA;AACnC,EAAA,OAAO,GAAA;AACT;AAWO,SAAS,0BAAA,GAAiD;AAC/D,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,gBAAA,EAAkB,IAAA,EAAK;AAC/C,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,WAAA,EAAY,KAAM,SAAA,EAAW,OAAO,MAAA;AAC5C,EAAA,OAAO,GAAA;AACT","file":"read-env.js","sourcesContent":["/**\n * Environment variable readers isolated from outbound networking.\n *\n * The wizard, runtime, and SDK consume the helpers below instead of\n * touching the host environment directly. The split keeps credential\n * lookup in a small audit-friendly module that imports nothing more\n * than its own typings: no SDK, no transport, no logging.\n *\n * See SECURITY.md (\"Defensive separation of credential lookup from\n * outbound I/O\") for the full rationale and the contract callers\n * must respect.\n */\n\n/**\n * Reads the AgentChat API key from `AGENTCHAT_API_KEY`.\n *\n * Returns the trimmed value when non-empty AND meeting `minLength`,\n * otherwise `undefined`. The min-length is supplied by the caller\n * (`MIN_API_KEY_LENGTH` from `channel-account.ts`) so this module\n * owns no domain constants.\n */\nexport function readApiKeyFromEnv(minLength: number): string | undefined {\n const raw = process.env.AGENTCHAT_API_KEY?.trim()\n if (!raw) return undefined\n if (raw.length < minLength) return undefined\n return raw\n}\n\n/**\n * Reads `OPENCLAW_PROFILE` for workspace path resolution. Mirrors\n * OpenClaw's own logic in `dist/workspace-hhTlRYqM.js:49-55`:\n * non-default profile name → `~/.openclaw/workspace-${profile}`.\n *\n * Returns the trimmed profile name only when it is set AND not equal\n * to \"default\" (case-insensitive). Returns `undefined` otherwise so\n * callers can fall through to the bare default.\n */\nexport function readOpenClawProfileFromEnv(): string | undefined {\n const raw = process.env.OPENCLAW_PROFILE?.trim()\n if (!raw) return undefined\n if (raw.toLowerCase() === 'default') return undefined\n return raw\n}\n"]}
package/dist/index.cjs CHANGED
@@ -5,9 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var channelCore = require('openclaw/plugin-sdk/channel-core');
6
6
  var setup = require('openclaw/plugin-sdk/setup');
7
7
  var readEnv_js = require('./credentials/read-env.cjs');
8
- var fs = require('fs');
9
- var os = require('os');
10
- var path = require('path');
8
+ var agentsAnchor_js = require('./binding/agents-anchor.cjs');
11
9
  var zod = require('zod');
12
10
  var pino = require('pino');
13
11
  var ws = require('ws');
@@ -16,27 +14,6 @@ var typebox = require('@sinclair/typebox');
16
14
 
17
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
16
 
19
- function _interopNamespace(e) {
20
- if (e && e.__esModule) return e;
21
- var n = Object.create(null);
22
- if (e) {
23
- Object.keys(e).forEach(function (k) {
24
- if (k !== 'default') {
25
- var d = Object.getOwnPropertyDescriptor(e, k);
26
- Object.defineProperty(n, k, d.get ? d : {
27
- enumerable: true,
28
- get: function () { return e[k]; }
29
- });
30
- }
31
- });
32
- }
33
- n.default = e;
34
- return Object.freeze(n);
35
- }
36
-
37
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
38
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
39
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
40
17
  var pino__default = /*#__PURE__*/_interopDefault(pino);
41
18
 
42
19
  // src/channel.ts
@@ -290,9 +267,9 @@ async function registerAgentVerify(input, opts = {}) {
290
267
  }
291
268
  return { ok: false, reason: "server-error", status: res.status, message };
292
269
  }
293
- async function post(path2, body, opts) {
270
+ async function post(path, body, opts) {
294
271
  const base = (opts.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
295
- const url = `${base}${path2}`;
272
+ const url = `${base}${path}`;
296
273
  const controller = new AbortController();
297
274
  const fetchImpl = opts.fetch ?? fetch;
298
275
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -328,106 +305,6 @@ async function assertApiKeyValid(apiKey, opts = {}) {
328
305
  statusCode: result.status
329
306
  });
330
307
  }
331
- var ANCHOR_START = "<!-- agentchat:start -->";
332
- var ANCHOR_END = "<!-- agentchat:end -->";
333
- var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
334
- var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
335
- function resolveWorkspaceDir(cfg) {
336
- const configured = cfg?.agents?.defaults?.workspace;
337
- if (typeof configured === "string" && configured.trim().length > 0) {
338
- return path__namespace.resolve(configured);
339
- }
340
- const profile = process.env["OPENCLAW_PROFILE"]?.trim();
341
- if (profile && profile.toLowerCase() !== "default") {
342
- return path__namespace.join(os__namespace.homedir(), ".openclaw", `workspace-${profile}`);
343
- }
344
- return path__namespace.join(os__namespace.homedir(), ".openclaw", "workspace");
345
- }
346
- function agentsFilePath(workspaceDir) {
347
- return path__namespace.join(workspaceDir, "AGENTS.md");
348
- }
349
- function renderAnchorBlock(handle) {
350
- return [
351
- ANCHOR_START,
352
- "## On AgentChat",
353
- "",
354
- `You are **@${handle}** on AgentChat \u2014 a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,
355
- "",
356
- "- Other agents can DM you. You can DM them \u2014 any time, for anything that fits your context.",
357
- `- Share \`@${handle}\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,
358
- "- When you need to send, search, or handle an error, your AgentChat skill has the full reference.",
359
- "",
360
- "This is an identity, not a tool you reach for occasionally.",
361
- ANCHOR_END
362
- ].join("\n");
363
- }
364
- function writeAgentsAnchor(params) {
365
- const trimmedHandle = params.handle?.trim();
366
- if (!trimmedHandle) {
367
- throw new Error("writeAgentsAnchor: handle is empty");
368
- }
369
- const workspaceDir = resolveWorkspaceDir(params.cfg);
370
- const filePath = agentsFilePath(workspaceDir);
371
- fs__namespace.mkdirSync(workspaceDir, { recursive: true });
372
- const existing = fs__namespace.existsSync(filePath) ? fs__namespace.readFileSync(filePath, "utf-8") : "";
373
- const block = renderAnchorBlock(trimmedHandle);
374
- const next = upsertAnchorBlock(existing, block);
375
- fs__namespace.writeFileSync(filePath, next, "utf-8");
376
- const verify = fs__namespace.readFileSync(filePath, "utf-8");
377
- if (!verify.includes(`@${trimmedHandle}`)) {
378
- throw new Error(
379
- `writeAgentsAnchor: handle @${trimmedHandle} did not land in AGENTS.md \u2014 block is broken, please remove the agentchat anchor manually and re-run.`
380
- );
381
- }
382
- return { path: filePath };
383
- }
384
- function removeAgentsAnchor(params) {
385
- const workspaceDir = resolveWorkspaceDir(params.cfg);
386
- const filePath = agentsFilePath(workspaceDir);
387
- if (!fs__namespace.existsSync(filePath)) {
388
- return { removed: false, path: filePath };
389
- }
390
- const existing = fs__namespace.readFileSync(filePath, "utf-8");
391
- const next = stripAnchorBlock(existing);
392
- if (next === existing) {
393
- return { removed: false, path: filePath };
394
- }
395
- fs__namespace.writeFileSync(filePath, next, "utf-8");
396
- return { removed: true, path: filePath };
397
- }
398
- function upsertAnchorBlock(existing, block) {
399
- const cleaned = stripBlockBetween(existing, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
400
- const startIdx = cleaned.indexOf(ANCHOR_START);
401
- const endIdx = cleaned.indexOf(ANCHOR_END);
402
- if (startIdx >= 0 && endIdx >= 0 && endIdx > startIdx) {
403
- const before = cleaned.slice(0, startIdx).replace(/\n+$/, "");
404
- const after = cleaned.slice(endIdx + ANCHOR_END.length).replace(/^\n+/, "");
405
- const parts = [before, block, after].filter((s) => s.length > 0);
406
- return parts.join("\n\n") + "\n";
407
- }
408
- const trimmed = cleaned.replace(/\n+$/, "");
409
- if (trimmed.length === 0) return block + "\n";
410
- return trimmed + "\n\n" + block + "\n";
411
- }
412
- function stripAnchorBlock(existing) {
413
- const afterUnified = stripBlockBetween(existing, ANCHOR_START, ANCHOR_END);
414
- return stripBlockBetween(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
415
- }
416
- function stripBlockBetween(existing, start, end) {
417
- const startIdx = existing.indexOf(start);
418
- const endIdx = existing.indexOf(end);
419
- if (startIdx < 0 || endIdx < 0 || endIdx <= startIdx) {
420
- return existing;
421
- }
422
- const before = existing.slice(0, startIdx).replace(/\n+$/, "");
423
- const after = existing.slice(endIdx + end.length).replace(/^\n+/, "");
424
- if (before.length === 0 && after.length === 0) return "";
425
- if (before.length === 0) return after.endsWith("\n") ? after : after + "\n";
426
- if (after.length === 0) return before + "\n";
427
- return before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
428
- }
429
-
430
- // src/channel.wizard.ts
431
308
  var JUST_REGISTERED_SENTINEL = "_agentchatJustRegistered";
432
309
  var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
433
310
  var HANDLE_MIN_LENGTH = 3;
@@ -906,7 +783,7 @@ var agentchatSetupWizard = {
906
783
  if (result.ok) {
907
784
  spinner.stop(`Authenticated as @${result.agent.handle}`);
908
785
  try {
909
- writeAgentsAnchor({ cfg, handle: result.agent.handle });
786
+ agentsAnchor_js.writeAgentsAnchor({ cfg, handle: result.agent.handle });
910
787
  } catch (err3) {
911
788
  await prompter.note(
912
789
  [
@@ -977,7 +854,7 @@ var agentchatSetupWizard = {
977
854
  // documented in RUNBOOK.md.
978
855
  disable: (cfg) => {
979
856
  try {
980
- removeAgentsAnchor({ cfg });
857
+ agentsAnchor_js.removeAgentsAnchor({ cfg });
981
858
  } catch {
982
859
  }
983
860
  return setup.setSetupChannelEnabled(cfg, AGENTCHAT_CHANNEL_ID, false);
@@ -1856,7 +1733,7 @@ function normalizeGroupDeleted(frame) {
1856
1733
 
1857
1734
  // src/retry.ts
1858
1735
  function defaultSleep(ms) {
1859
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1736
+ return new Promise((resolve) => setTimeout(resolve, ms));
1860
1737
  }
1861
1738
  async function retryWithPolicy(fn, policy) {
1862
1739
  const random = policy.random ?? Math.random;
@@ -1984,7 +1861,7 @@ var CircuitBreaker = class {
1984
1861
  };
1985
1862
 
1986
1863
  // src/version.ts
1987
- var PACKAGE_VERSION = "0.6.8";
1864
+ var PACKAGE_VERSION = "0.6.10";
1988
1865
 
1989
1866
  // src/outbound.ts
1990
1867
  var DEFAULT_RETRY_POLICY = {
@@ -2200,11 +2077,11 @@ var OutboundAdapter = class {
2200
2077
  `outbound queue full (${this.queue.length}) \u2014 shedding load`
2201
2078
  );
2202
2079
  }
2203
- return new Promise((resolve2) => {
2080
+ return new Promise((resolve) => {
2204
2081
  this.queue.push(() => {
2205
2082
  this.inFlight++;
2206
2083
  this.metrics.setInFlightDepth(this.inFlight);
2207
- resolve2();
2084
+ resolve();
2208
2085
  });
2209
2086
  });
2210
2087
  }
@@ -2280,10 +2157,10 @@ var AgentchatChannelRuntime = class {
2280
2157
  stop(deadlineMs) {
2281
2158
  if (this.stopPromise) return this.stopPromise;
2282
2159
  const deadline = deadlineMs ?? this.now() + 5e3;
2283
- this.stopPromise = new Promise((resolve2) => {
2160
+ this.stopPromise = new Promise((resolve) => {
2284
2161
  const off = this.ws.on("closed", () => {
2285
2162
  off();
2286
- resolve2();
2163
+ resolve();
2287
2164
  });
2288
2165
  this.ws.stop(deadline);
2289
2166
  });
@@ -2908,8 +2785,8 @@ async function uploadMediaFromUrl(ctx, mediaUrl) {
2908
2785
  let contentType;
2909
2786
  let filename = "attachment";
2910
2787
  if (mediaUrl.startsWith("file://") && ctx.mediaReadFile) {
2911
- const path2 = decodeURIComponent(mediaUrl.replace(/^file:\/\//, ""));
2912
- const buf = await ctx.mediaReadFile(path2);
2788
+ const path = decodeURIComponent(mediaUrl.replace(/^file:\/\//, ""));
2789
+ const buf = await ctx.mediaReadFile(path);
2913
2790
  if (buf.byteLength > MAX_MEDIA_BYTES) {
2914
2791
  throw new AgentChatChannelError(
2915
2792
  "terminal-user",
@@ -2919,7 +2796,7 @@ async function uploadMediaFromUrl(ctx, mediaUrl) {
2919
2796
  const copy = new Uint8Array(buf.byteLength);
2920
2797
  copy.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
2921
2798
  bytes = copy.buffer;
2922
- filename = path2.split(/[\\/]/).pop() ?? filename;
2799
+ filename = path.split(/[\\/]/).pop() ?? filename;
2923
2800
  } else {
2924
2801
  assertMediaUrlSafe(mediaUrl);
2925
2802
  const controller = new AbortController();
@@ -4345,8 +4222,6 @@ var agentchatStatusAdapter = {
4345
4222
  return "not linked";
4346
4223
  }
4347
4224
  };
4348
-
4349
- // src/channel.ts
4350
4225
  function resolveAgentchatAccount(cfg, accountId) {
4351
4226
  const id = accountId ?? AGENTCHAT_DEFAULT_ACCOUNT_ID;
4352
4227
  const section = readChannelSection(cfg);
@@ -4525,7 +4400,7 @@ var agentchatPlugin = {
4525
4400
  `[agentchat:${accountId}] authenticated as @${result.agent.handle} (${result.agent.email})`
4526
4401
  );
4527
4402
  try {
4528
- writeAgentsAnchor({ cfg, handle: result.agent.handle });
4403
+ agentsAnchor_js.writeAgentsAnchor({ cfg, handle: result.agent.handle });
4529
4404
  } catch (err3) {
4530
4405
  logger?.warn?.(
4531
4406
  `[agentchat:${accountId}] AGENTS.md anchor write failed: ${err3 instanceof Error ? err3.message : String(err3)}`