@clawtell/clawtell 2026.2.44
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/LICENSE +21 -0
- package/README.md +481 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/src/bootstrap.js +238 -0
- package/dist/src/bootstrap.js.map +1 -0
- package/dist/src/channel.js +448 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/cli.js +140 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/poll.js +1146 -0
- package/dist/src/poll.js.map +1 -0
- package/dist/src/probe.js +43 -0
- package/dist/src/probe.js.map +1 -0
- package/dist/src/queue.js +71 -0
- package/dist/src/queue.js.map +1 -0
- package/dist/src/runtime.js +16 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/send.js +119 -0
- package/dist/src/send.js.map +1 -0
- package/index.ts +42 -0
- package/openclaw.plugin.json +46 -0
- package/package.json +70 -0
- package/scripts/canary-test.sh +116 -0
- package/scripts/postinstall.js +103 -0
- package/scripts/preflight.sh +160 -0
- package/skills/clawtell/SKILL.md +295 -0
- package/src/bootstrap.ts +284 -0
- package/src/channel.ts +524 -0
- package/src/cli.ts +157 -0
- package/src/poll.ts +1306 -0
- package/src/probe.ts +64 -0
- package/src/queue.ts +119 -0
- package/src/runtime.ts +43 -0
- package/src/send.ts +160 -0
package/src/bootstrap.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawTell Bootstrap Hook + Workspace Setup
|
|
3
|
+
*
|
|
4
|
+
* Provides 3 layers of agent instruction delivery:
|
|
5
|
+
*
|
|
6
|
+
* Layer 1: agent:bootstrap hook — injects CLAWTELL.md into every agent's
|
|
7
|
+
* bootstrap context at runtime (never touches filesystem)
|
|
8
|
+
*
|
|
9
|
+
* Layer 2: Startup file writer — writes CLAWTELL_INSTRUCTIONS.md to each
|
|
10
|
+
* routed agent's workspace (persistent reference)
|
|
11
|
+
*
|
|
12
|
+
* Layer 3: Env var writer — ensures CLAWTELL_API_KEY is in agent's .env
|
|
13
|
+
*
|
|
14
|
+
* Layer 4 (existing): messageToolHints in channel.ts — fires for ClawTell-context messages
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
20
|
+
|
|
21
|
+
interface ClawTellRouteEntry {
|
|
22
|
+
agent: string;
|
|
23
|
+
forward?: boolean;
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface BootstrapFile {
|
|
28
|
+
name: string;
|
|
29
|
+
path: string;
|
|
30
|
+
content: string;
|
|
31
|
+
missing: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface BootstrapContext {
|
|
35
|
+
bootstrapFiles: BootstrapFile[];
|
|
36
|
+
cfg: ClawdbotConfig;
|
|
37
|
+
agentId?: string;
|
|
38
|
+
workspaceDir?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the ClawTell name for a given agent from routing config.
|
|
43
|
+
* Returns the first name that routes to this agent.
|
|
44
|
+
*/
|
|
45
|
+
function resolveAgentClawTellName(
|
|
46
|
+
agentId: string,
|
|
47
|
+
routing: Record<string, ClawTellRouteEntry>
|
|
48
|
+
): { name: string; apiKey?: string } | null {
|
|
49
|
+
for (const [name, entry] of Object.entries(routing)) {
|
|
50
|
+
if (name === "_default") continue;
|
|
51
|
+
if (entry.agent === agentId) {
|
|
52
|
+
return { name, apiKey: entry.apiKey };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build the CLAWTELL.md content for a specific agent.
|
|
60
|
+
* Uses env var references instead of raw keys.
|
|
61
|
+
*/
|
|
62
|
+
function buildClawTellInstructions(opts: {
|
|
63
|
+
agentId: string;
|
|
64
|
+
tellName: string;
|
|
65
|
+
accountName?: string;
|
|
66
|
+
hasRouteSpecificKey: boolean;
|
|
67
|
+
}): string {
|
|
68
|
+
const { agentId, tellName, accountName } = opts;
|
|
69
|
+
|
|
70
|
+
return `# ClawTell Instructions
|
|
71
|
+
|
|
72
|
+
## Your Identity
|
|
73
|
+
- **ClawTell name:** \`${tellName}\`
|
|
74
|
+
- **Send as:** \`tell/${tellName}\`
|
|
75
|
+
|
|
76
|
+
## Receiving Messages
|
|
77
|
+
ClawTell messages arrive formatted with a \`🦞🦞 ClawTell Delivery 🦞🦞\` banner.
|
|
78
|
+
They include the sender name and subject. Reply naturally — the system handles routing your reply back through ClawTell automatically.
|
|
79
|
+
|
|
80
|
+
## Sending Messages
|
|
81
|
+
When the user says **"tell/<name> <message>"** or **"tell <name> <message>"**:
|
|
82
|
+
|
|
83
|
+
1. Extract the **recipient name** (after \`tell/\` or \`tell \`)
|
|
84
|
+
2. Compose the message naturally (write as yourself, not copy-paste) — unless the user says "send exactly this", then send verbatim
|
|
85
|
+
3. Send via:
|
|
86
|
+
|
|
87
|
+
\`\`\`bash
|
|
88
|
+
curl -s -X POST "https://www.clawtell.com/api/messages/send" \\
|
|
89
|
+
-H "Authorization: Bearer $CLAWTELL_API_KEY" \\
|
|
90
|
+
-H "Content-Type: application/json" \\
|
|
91
|
+
-d '{"to":"<recipient_name>","subject":"<brief topic 3-5 words>","body":"<composed message>"}'
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
4. Confirm: \`✅ Message sent to tell/<name>\`
|
|
95
|
+
5. If it fails, show the error and troubleshoot
|
|
96
|
+
|
|
97
|
+
## Key Rules
|
|
98
|
+
- **\`$CLAWTELL_API_KEY\`** is set in your environment — always use the env var, never hardcode keys
|
|
99
|
+
- **\`to\`** = the ClawTell name (e.g. \`tell/alice\` → \`"to": "alice"\`)
|
|
100
|
+
- **Subject** = brief topic (3-5 words)
|
|
101
|
+
- The API key identifies YOU as the sender (\`${tellName}\`)
|
|
102
|
+
|
|
103
|
+
## Our Names
|
|
104
|
+
${accountName ? `Account owner: \`${accountName}\`` : ""}
|
|
105
|
+
All names on this account can message each other internally.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
*Auto-generated by ClawTell plugin. Do not edit — regenerated on gateway restart.*
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Layer 1: Bootstrap Hook
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create the agent:bootstrap hook handler.
|
|
118
|
+
* Injects CLAWTELL.md into every agent's bootstrap context.
|
|
119
|
+
*/
|
|
120
|
+
export function createBootstrapHook(cfg: ClawdbotConfig) {
|
|
121
|
+
return async (event: { context: BootstrapContext }) => {
|
|
122
|
+
const ctx = event.context;
|
|
123
|
+
const clawtellConfig = (cfg.channels as any)?.clawtell;
|
|
124
|
+
if (!clawtellConfig?.enabled) return;
|
|
125
|
+
|
|
126
|
+
const routing = clawtellConfig.routing as Record<string, ClawTellRouteEntry> | undefined;
|
|
127
|
+
if (!routing) return;
|
|
128
|
+
|
|
129
|
+
const agentId = ctx.agentId || "main";
|
|
130
|
+
const agentInfo = resolveAgentClawTellName(agentId, routing);
|
|
131
|
+
|
|
132
|
+
// If this agent isn't in routing, check _default
|
|
133
|
+
const tellName = agentInfo?.name || clawtellConfig.name;
|
|
134
|
+
if (!tellName) return;
|
|
135
|
+
|
|
136
|
+
const instructions = buildClawTellInstructions({
|
|
137
|
+
agentId,
|
|
138
|
+
tellName,
|
|
139
|
+
accountName: clawtellConfig.name,
|
|
140
|
+
hasRouteSpecificKey: !!agentInfo?.apiKey,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Inject CLAWTELL.md into bootstrap files
|
|
144
|
+
const existing = ctx.bootstrapFiles.findIndex(f => f.name === "CLAWTELL.md");
|
|
145
|
+
const entry: BootstrapFile = {
|
|
146
|
+
name: "CLAWTELL.md",
|
|
147
|
+
path: "CLAWTELL.md",
|
|
148
|
+
content: instructions,
|
|
149
|
+
missing: false,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (existing >= 0) {
|
|
153
|
+
ctx.bootstrapFiles[existing] = entry;
|
|
154
|
+
} else {
|
|
155
|
+
ctx.bootstrapFiles.push(entry);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Layer 2: Startup File Writer
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Write CLAWTELL_INSTRUCTIONS.md to each routed agent's workspace.
|
|
166
|
+
* Called once on gateway startup.
|
|
167
|
+
*/
|
|
168
|
+
export async function writeAgentInstructionFiles(cfg: ClawdbotConfig): Promise<void> {
|
|
169
|
+
const clawtellConfig = (cfg.channels as any)?.clawtell;
|
|
170
|
+
if (!clawtellConfig?.enabled) return;
|
|
171
|
+
|
|
172
|
+
const routing = clawtellConfig.routing as Record<string, ClawTellRouteEntry> | undefined;
|
|
173
|
+
if (!routing) return;
|
|
174
|
+
|
|
175
|
+
const agentsConfig = (cfg as any).agents ?? {};
|
|
176
|
+
const agentList: Array<{ id: string; workspace?: string }> = agentsConfig.list ?? [];
|
|
177
|
+
const defaultWorkspace = agentsConfig.defaults?.workspace || path.join(process.env.HOME || "/home/claw", "workspace");
|
|
178
|
+
|
|
179
|
+
// Collect unique agents from routing
|
|
180
|
+
const agentNames = new Set<string>();
|
|
181
|
+
for (const [name, entry] of Object.entries(routing)) {
|
|
182
|
+
if (name !== "_default" && entry.agent) {
|
|
183
|
+
agentNames.add(entry.agent);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const agentId of agentNames) {
|
|
188
|
+
const agentConfig = agentList.find(a => a.id === agentId);
|
|
189
|
+
const workspace = agentConfig?.workspace || defaultWorkspace;
|
|
190
|
+
const agentInfo = resolveAgentClawTellName(agentId, routing);
|
|
191
|
+
const tellName = agentInfo?.name || clawtellConfig.name;
|
|
192
|
+
if (!tellName) continue;
|
|
193
|
+
|
|
194
|
+
const instructions = buildClawTellInstructions({
|
|
195
|
+
agentId,
|
|
196
|
+
tellName,
|
|
197
|
+
accountName: clawtellConfig.name,
|
|
198
|
+
hasRouteSpecificKey: !!agentInfo?.apiKey,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const filePath = path.join(workspace, "CLAWTELL_INSTRUCTIONS.md");
|
|
202
|
+
try {
|
|
203
|
+
await fs.writeFile(filePath, instructions, "utf8");
|
|
204
|
+
console.log(`[ClawTell] Wrote ${filePath} for agent:${agentId} (tell/${tellName})`);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(`[ClawTell] Failed to write ${filePath}:`, err);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Layer 3: Env Var Writer
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Ensure CLAWTELL_API_KEY is set in each agent's .env file.
|
|
217
|
+
* Uses the per-route key if available, otherwise account-level key.
|
|
218
|
+
*/
|
|
219
|
+
export async function writeAgentEnvVars(cfg: ClawdbotConfig): Promise<void> {
|
|
220
|
+
const clawtellConfig = (cfg.channels as any)?.clawtell;
|
|
221
|
+
if (!clawtellConfig?.enabled) return;
|
|
222
|
+
|
|
223
|
+
const routing = clawtellConfig.routing as Record<string, ClawTellRouteEntry> | undefined;
|
|
224
|
+
const accountApiKey = clawtellConfig.apiKey;
|
|
225
|
+
if (!accountApiKey) return;
|
|
226
|
+
|
|
227
|
+
const agentsConfig2 = (cfg as any).agents ?? {};
|
|
228
|
+
const agentList2: Array<{ id: string; workspace?: string }> = agentsConfig2.list ?? [];
|
|
229
|
+
const defaultWorkspace2 = agentsConfig2.defaults?.workspace || path.join(process.env.HOME || "/home/claw", "workspace");
|
|
230
|
+
|
|
231
|
+
// Collect agents and their keys
|
|
232
|
+
const agentKeys = new Map<string, string>();
|
|
233
|
+
|
|
234
|
+
if (routing) {
|
|
235
|
+
for (const [name, entry] of Object.entries(routing)) {
|
|
236
|
+
if (name === "_default") continue;
|
|
237
|
+
const key = entry.apiKey || accountApiKey;
|
|
238
|
+
agentKeys.set(entry.agent, key);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Also ensure main agent has the key
|
|
243
|
+
if (!agentKeys.has("main")) {
|
|
244
|
+
agentKeys.set("main", accountApiKey);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const [agentId, apiKey] of agentKeys) {
|
|
248
|
+
const agentConfig2 = agentList2.find(a => a.id === agentId);
|
|
249
|
+
const workspace = agentConfig2?.workspace || defaultWorkspace2;
|
|
250
|
+
const envPath = path.join(workspace, ".env");
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
let envContent = "";
|
|
254
|
+
try {
|
|
255
|
+
envContent = await fs.readFile(envPath, "utf8");
|
|
256
|
+
} catch {
|
|
257
|
+
// .env doesn't exist yet — that's fine
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if CLAWTELL_API_KEY already exists and matches
|
|
261
|
+
const keyRegex = /^CLAWTELL_API_KEY=(.+)$/m;
|
|
262
|
+
const match = envContent.match(keyRegex);
|
|
263
|
+
|
|
264
|
+
if (match && match[1] === apiKey) {
|
|
265
|
+
// Already correct
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (match) {
|
|
270
|
+
// Update existing
|
|
271
|
+
envContent = envContent.replace(keyRegex, `CLAWTELL_API_KEY=${apiKey}`);
|
|
272
|
+
} else {
|
|
273
|
+
// Append
|
|
274
|
+
const separator = envContent.endsWith("\n") || envContent === "" ? "" : "\n";
|
|
275
|
+
envContent += `${separator}\n# ClawTell API Key (auto-managed by plugin)\nCLAWTELL_API_KEY=${apiKey}\n`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await fs.writeFile(envPath, envContent, "utf8");
|
|
279
|
+
console.log(`[ClawTell] Set CLAWTELL_API_KEY in ${envPath} for agent:${agentId}`);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error(`[ClawTell] Failed to write env for agent:${agentId}:`, err);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|