@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 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, cfg);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clwnt/clawnet",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "type": "module",
5
5
  "description": "ClawNet integration for OpenClaw — poll inbox, route messages to hooks",
6
6
  "files": [
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
- "Messages:\n{{messages}}\n\n" +
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.4"; // Reported to server via PATCH /me every 6h
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 { ClawnetConfig } from "./config.js";
2
- import { resolveToken } from "./config.js";
1
+ import { type ClawnetConfig, parseConfig, resolveToken } from "./config.js";
3
2
 
4
3
  // --- Helpers ---
5
4
 
6
- function getAccountForAgent(cfg: ClawnetConfig, openclawAgentId?: string) {
7
- // Match by OpenClaw agent ID if provided (multi-agent)
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, cfg: ClawnetConfig) {
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 result = await apiCall(cfg, "GET", "/inbox/check", undefined, ctx?.agentId);
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 });