@clwnt/clawnet 0.5.4 → 0.5.6
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/index.ts +20 -2
- package/package.json +1 -1
- package/src/cli.ts +3 -3
- package/src/service.ts +1 -1
- package/src/tools.ts +58 -23
package/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ const plugin = {
|
|
|
29
29
|
loadToolDescriptions();
|
|
30
30
|
|
|
31
31
|
// Register agent tools (inbox, send, status, capabilities, call)
|
|
32
|
-
registerTools(api
|
|
32
|
+
registerTools(api);
|
|
33
33
|
|
|
34
34
|
// Register CLI: `openclaw clawnet ...`
|
|
35
35
|
api.registerCli(({ program }) => {
|
|
@@ -119,9 +119,27 @@ const plugin = {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
if (args === "logs" || args.startsWith("logs ")) {
|
|
123
|
+
const count = parseInt(args.split(" ")[1], 10) || 50;
|
|
124
|
+
try {
|
|
125
|
+
const { readFile } = await import("node:fs/promises");
|
|
126
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
127
|
+
const logPath = `/tmp/openclaw/openclaw-${today}.log`;
|
|
128
|
+
const content = await readFile(logPath, "utf-8");
|
|
129
|
+
const lines = content.split("\n").filter((l) => /clawnet/i.test(l));
|
|
130
|
+
const tail = lines.slice(-count);
|
|
131
|
+
if (tail.length === 0) {
|
|
132
|
+
return { text: `No clawnet entries in today's log (${logPath}).` };
|
|
133
|
+
}
|
|
134
|
+
return { text: `Last ${tail.length} clawnet log entries:\n\n\`\`\`\n${tail.join("\n")}\n\`\`\`` };
|
|
135
|
+
} catch (err: any) {
|
|
136
|
+
return { text: `Could not read log: ${err.message}` };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
122
140
|
if (args !== "link" && args !== "link reset") {
|
|
123
141
|
const { PLUGIN_VERSION } = await import("./src/service.js");
|
|
124
|
-
return { text: `ClawNet Plugin v${PLUGIN_VERSION}\n\nCommands:\n /clawnet status — show plugin configuration and health\n /clawnet test — test delivery to this chat\n /clawnet link — pin message delivery to this chat (use if messages aren't arriving)\n /clawnet link reset — unpin and return to automatic delivery\n /clawnet pause — temporarily stop polling\n /clawnet resume — restart polling\n\nUpdate: openclaw plugins update clawnet` };
|
|
142
|
+
return { text: `ClawNet Plugin v${PLUGIN_VERSION}\n\nCommands:\n /clawnet status — show plugin configuration and health\n /clawnet test — test delivery to this chat\n /clawnet logs [n] — show last n clawnet log entries (default 50)\n /clawnet link — pin message delivery to this chat (use if messages aren't arriving)\n /clawnet link reset — unpin and return to automatic delivery\n /clawnet pause — temporarily stop polling\n /clawnet resume — restart polling\n\nUpdate: openclaw plugins update clawnet` };
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
// Load config and find clawnet accounts
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -29,9 +29,9 @@ function sleep(ms: number): Promise<void> {
|
|
|
29
29
|
// --- Hook mapping builder (from spec) ---
|
|
30
30
|
|
|
31
31
|
const DEFAULT_HOOK_TEMPLATE =
|
|
32
|
-
"You have {{count}} new ClawNet message(s).\n\n" +
|
|
33
|
-
"
|
|
34
|
-
"{{context}}";
|
|
32
|
+
"You have {{count}} new ClawNet message(s). Process ONLY the new messages below — the conversation history is provided for context only.\n\n" +
|
|
33
|
+
"New messages (action required):\n{{messages}}\n\n" +
|
|
34
|
+
"Prior conversation history (for context only — do NOT re-process these):\n{{context}}";
|
|
35
35
|
|
|
36
36
|
let cachedHookTemplate: string | null = null;
|
|
37
37
|
|
package/src/service.ts
CHANGED
|
@@ -72,7 +72,7 @@ async function reloadOnboardingMessage(): Promise<void> {
|
|
|
72
72
|
|
|
73
73
|
const SKILL_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
74
74
|
const SKILL_FILES = ["skill.json", "api-reference.md", "inbox-handler.md", "capabilities.json", "hook-template.txt", "tool-descriptions.json", "onboarding-message.txt"];
|
|
75
|
-
export const PLUGIN_VERSION = "0.5.
|
|
75
|
+
export const PLUGIN_VERSION = "0.5.6"; // Reported to server via PATCH /me every 6h
|
|
76
76
|
|
|
77
77
|
// --- Service ---
|
|
78
78
|
|
package/src/tools.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { resolveToken } from "./config.js";
|
|
1
|
+
import { type ClawnetConfig, parseConfig, resolveToken } from "./config.js";
|
|
3
2
|
|
|
4
3
|
// --- Helpers ---
|
|
5
4
|
|
|
6
|
-
function
|
|
7
|
-
|
|
5
|
+
function loadFreshConfig(api: any): ClawnetConfig {
|
|
6
|
+
const raw = api.runtime.config.loadConfig()?.plugins?.entries?.clawnet?.config ?? {};
|
|
7
|
+
return parseConfig(raw as Record<string, unknown>);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract ClawNet account ID from session key (e.g. "hook:clawnet:tom:inbox" -> "tom").
|
|
12
|
+
*/
|
|
13
|
+
function accountIdFromSessionKey(sessionKey?: string): string | null {
|
|
14
|
+
if (!sessionKey) return null;
|
|
15
|
+
const match = sessionKey.match(/^hook:clawnet:([^:]+):/);
|
|
16
|
+
return match ? match[1] : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getAccountForAgent(cfg: ClawnetConfig, openclawAgentId?: string, sessionKey?: string) {
|
|
20
|
+
// Best match: extract account ID from session key (multi-account safe)
|
|
21
|
+
const sessionAccountId = accountIdFromSessionKey(sessionKey);
|
|
22
|
+
if (sessionAccountId) {
|
|
23
|
+
const match = cfg.accounts.find((a) => a.enabled && a.id === sessionAccountId);
|
|
24
|
+
if (match) {
|
|
25
|
+
const token = resolveToken(match.token);
|
|
26
|
+
if (token) return { ...match, resolvedToken: token };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Match by OpenClaw agent ID if provided (single-account or non-hook context)
|
|
8
30
|
if (openclawAgentId) {
|
|
9
31
|
const match = cfg.accounts.find((a) => a.enabled && a.openclawAgentId === openclawAgentId);
|
|
10
32
|
if (match) {
|
|
@@ -35,8 +57,9 @@ async function apiCall(
|
|
|
35
57
|
path: string,
|
|
36
58
|
body?: unknown,
|
|
37
59
|
openclawAgentId?: string,
|
|
60
|
+
sessionKey?: string,
|
|
38
61
|
): Promise<{ ok: boolean; status: number; data: any }> {
|
|
39
|
-
const account = getAccountForAgent(cfg, openclawAgentId);
|
|
62
|
+
const account = getAccountForAgent(cfg, openclawAgentId, sessionKey);
|
|
40
63
|
if (!account) {
|
|
41
64
|
return { ok: false, status: 0, data: { error: "no_account", message: "No ClawNet account configured. Run: openclaw clawnet setup" } };
|
|
42
65
|
}
|
|
@@ -46,6 +69,9 @@ async function apiCall(
|
|
|
46
69
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
47
70
|
});
|
|
48
71
|
const data = await res.json().catch(() => ({}));
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
data._resolved_account = account.agentId;
|
|
74
|
+
}
|
|
49
75
|
return { ok: res.ok, status: res.status, data };
|
|
50
76
|
}
|
|
51
77
|
|
|
@@ -55,8 +81,9 @@ async function apiCallRaw(
|
|
|
55
81
|
path: string,
|
|
56
82
|
rawBody: string,
|
|
57
83
|
openclawAgentId?: string,
|
|
84
|
+
sessionKey?: string,
|
|
58
85
|
): Promise<{ ok: boolean; status: number; data: any }> {
|
|
59
|
-
const account = getAccountForAgent(cfg, openclawAgentId);
|
|
86
|
+
const account = getAccountForAgent(cfg, openclawAgentId, sessionKey);
|
|
60
87
|
if (!account) {
|
|
61
88
|
return { ok: false, status: 0, data: { error: "no_account", message: "No ClawNet account configured. Run: openclaw clawnet setup" } };
|
|
62
89
|
}
|
|
@@ -69,6 +96,9 @@ async function apiCallRaw(
|
|
|
69
96
|
body: rawBody,
|
|
70
97
|
});
|
|
71
98
|
const data = await res.json().catch(() => ({}));
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
data._resolved_account = account.agentId;
|
|
101
|
+
}
|
|
72
102
|
return { ok: res.ok, status: res.status, data };
|
|
73
103
|
}
|
|
74
104
|
|
|
@@ -299,8 +329,7 @@ function toolDesc(name: string, fallback: string): string {
|
|
|
299
329
|
|
|
300
330
|
// --- Tool registration ---
|
|
301
331
|
|
|
302
|
-
export function registerTools(api: any
|
|
303
|
-
// Load cached descriptions synchronously-safe (already loaded by service start)
|
|
332
|
+
export function registerTools(api: any) {
|
|
304
333
|
// --- Blessed tools (high-traffic, dedicated) ---
|
|
305
334
|
|
|
306
335
|
api.registerTool({
|
|
@@ -310,8 +339,9 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
310
339
|
type: "object",
|
|
311
340
|
properties: {},
|
|
312
341
|
},
|
|
313
|
-
async execute(_id: string, _params: unknown, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
314
|
-
const
|
|
342
|
+
async execute(_id: string, _params: unknown, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
343
|
+
const cfg = loadFreshConfig(api);
|
|
344
|
+
const result = await apiCall(cfg, "GET", "/inbox/check", undefined, ctx?.agentId, ctx?.sessionKey);
|
|
315
345
|
return textResult(result.data);
|
|
316
346
|
},
|
|
317
347
|
});
|
|
@@ -326,12 +356,13 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
326
356
|
limit: { type: "number", description: "Max messages to return (default 50, max 200)" },
|
|
327
357
|
},
|
|
328
358
|
},
|
|
329
|
-
async execute(_id: string, params: { status?: string; limit?: number }, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
359
|
+
async execute(_id: string, params: { status?: string; limit?: number }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
360
|
+
const cfg = loadFreshConfig(api);
|
|
330
361
|
const qs = new URLSearchParams();
|
|
331
362
|
if (params.status) qs.set("status", params.status);
|
|
332
363
|
if (params.limit) qs.set("limit", String(params.limit));
|
|
333
364
|
const query = qs.toString() ? `?${qs}` : "";
|
|
334
|
-
const result = await apiCall(cfg, "GET", `/inbox${query}`, undefined, ctx?.agentId);
|
|
365
|
+
const result = await apiCall(cfg, "GET", `/inbox${query}`, undefined, ctx?.agentId, ctx?.sessionKey);
|
|
335
366
|
return textResult(result.data);
|
|
336
367
|
},
|
|
337
368
|
});
|
|
@@ -348,15 +379,16 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
348
379
|
},
|
|
349
380
|
required: ["to", "message"],
|
|
350
381
|
},
|
|
351
|
-
async execute(_id: string, params: { to: string; message: string; subject?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
382
|
+
async execute(_id: string, params: { to: string; message: string; subject?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
383
|
+
const cfg = loadFreshConfig(api);
|
|
352
384
|
if (params.to.includes("@")) {
|
|
353
385
|
// Route to email endpoint
|
|
354
386
|
const body: Record<string, string> = { to: params.to, body: params.message };
|
|
355
387
|
if (params.subject) body.subject = params.subject;
|
|
356
|
-
const result = await apiCall(cfg, "POST", "/email/send", body, ctx?.agentId);
|
|
388
|
+
const result = await apiCall(cfg, "POST", "/email/send", body, ctx?.agentId, ctx?.sessionKey);
|
|
357
389
|
return textResult(result.data);
|
|
358
390
|
}
|
|
359
|
-
const result = await apiCall(cfg, "POST", "/send", { to: params.to, message: params.message }, ctx?.agentId);
|
|
391
|
+
const result = await apiCall(cfg, "POST", "/send", { to: params.to, message: params.message }, ctx?.agentId, ctx?.sessionKey);
|
|
360
392
|
return textResult(result.data);
|
|
361
393
|
},
|
|
362
394
|
}, { optional: true });
|
|
@@ -373,10 +405,11 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
373
405
|
},
|
|
374
406
|
required: ["message_id", "status"],
|
|
375
407
|
},
|
|
376
|
-
async execute(_id: string, params: { message_id: string; status: string; snoozed_until?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
408
|
+
async execute(_id: string, params: { message_id: string; status: string; snoozed_until?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
409
|
+
const cfg = loadFreshConfig(api);
|
|
377
410
|
const body: Record<string, unknown> = { status: params.status };
|
|
378
411
|
if (params.snoozed_until) body.snoozed_until = params.snoozed_until;
|
|
379
|
-
const result = await apiCall(cfg, "PATCH", `/messages/${params.message_id}/status`, body, ctx?.agentId);
|
|
412
|
+
const result = await apiCall(cfg, "PATCH", `/messages/${params.message_id}/status`, body, ctx?.agentId, ctx?.sessionKey);
|
|
380
413
|
return textResult(result.data);
|
|
381
414
|
},
|
|
382
415
|
}, { optional: true });
|
|
@@ -392,12 +425,13 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
392
425
|
scope: { type: "string", description: "'global' for network-wide rules, 'agent' for agent-specific rules, omit for both" },
|
|
393
426
|
},
|
|
394
427
|
},
|
|
395
|
-
async execute(_id: string, params: { scope?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
428
|
+
async execute(_id: string, params: { scope?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
429
|
+
const cfg = loadFreshConfig(api);
|
|
396
430
|
const qs = new URLSearchParams();
|
|
397
431
|
if (params.scope) qs.set("scope", params.scope);
|
|
398
432
|
if (ctx?.agentId) qs.set("agent_id", ctx.agentId);
|
|
399
433
|
const query = qs.toString() ? `?${qs}` : "";
|
|
400
|
-
const result = await apiCall(cfg, "GET", `/rules${query}`, undefined, ctx?.agentId);
|
|
434
|
+
const result = await apiCall(cfg, "GET", `/rules${query}`, undefined, ctx?.agentId, ctx?.sessionKey);
|
|
401
435
|
return textResult(result.data);
|
|
402
436
|
},
|
|
403
437
|
});
|
|
@@ -413,7 +447,7 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
413
447
|
filter: { type: "string", description: "Filter by prefix (e.g. 'email', 'calendar', 'post', 'profile')" },
|
|
414
448
|
},
|
|
415
449
|
},
|
|
416
|
-
async execute(_id: string, params: { filter?: string }, _onUpdate: unknown, _ctx?: { agentId?: string }) {
|
|
450
|
+
async execute(_id: string, params: { filter?: string }, _onUpdate: unknown, _ctx?: { agentId?: string; sessionKey?: string }) {
|
|
417
451
|
let ops = getOperations();
|
|
418
452
|
if (params.filter) {
|
|
419
453
|
const prefix = params.filter.toLowerCase();
|
|
@@ -449,7 +483,8 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
449
483
|
},
|
|
450
484
|
required: ["operation"],
|
|
451
485
|
},
|
|
452
|
-
async execute(_id: string, input: { operation: string; params?: Record<string, unknown> }, _onUpdate: unknown, ctx?: { agentId?: string }) {
|
|
486
|
+
async execute(_id: string, input: { operation: string; params?: Record<string, unknown> }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
|
|
487
|
+
const cfg = loadFreshConfig(api);
|
|
453
488
|
const op = getOperations().find((o) => o.operation === input.operation);
|
|
454
489
|
if (!op) {
|
|
455
490
|
return textResult({ error: "unknown_operation", message: `Unknown operation: ${input.operation}. Call clawnet_capabilities to see available operations.` });
|
|
@@ -507,8 +542,8 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
|
|
|
507
542
|
}
|
|
508
543
|
|
|
509
544
|
const result = rawBody !== undefined
|
|
510
|
-
? await apiCallRaw(cfg, op.method, path, rawBody, ctx?.agentId)
|
|
511
|
-
: await apiCall(cfg, op.method, path, body, ctx?.agentId);
|
|
545
|
+
? await apiCallRaw(cfg, op.method, path, rawBody, ctx?.agentId, ctx?.sessionKey)
|
|
546
|
+
: await apiCall(cfg, op.method, path, body, ctx?.agentId, ctx?.sessionKey);
|
|
512
547
|
return textResult(result.data);
|
|
513
548
|
},
|
|
514
549
|
}, { optional: true });
|