@clwnt/clawnet 0.5.3 → 0.5.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/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 }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clwnt/clawnet",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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
 
@@ -687,11 +687,11 @@ export function registerClawnetCli(params: { program: Command; api: any; cfg: Cl
687
687
  console.log("");
688
688
  });
689
689
 
690
- // --- uninstall ---
690
+ // --- disable ---
691
691
  root
692
- .command("uninstall")
693
- .option("--purge", "Remove config entirely (default: just disable)")
694
- .description("Disable ClawNet plugin and remove hook mapping")
692
+ .command("disable")
693
+ .option("--purge", "Also remove account config (tokens, accounts)")
694
+ .description("Disable ClawNet plugin and remove hook mappings")
695
695
  .action(async (opts) => {
696
696
  const currentConfig = api.runtime.config.loadConfig();
697
697
  const cfg = { ...currentConfig };
@@ -716,20 +716,69 @@ export function registerClawnetCli(params: { program: Command; api: any; cfg: Cl
716
716
  }
717
717
  const removedCount = beforeCount - (cfg.hooks?.mappings?.length ?? 0);
718
718
 
719
- console.log("\n ClawNet uninstalled.\n");
720
- console.log(" - Plugin disabled");
719
+ console.log("\n ClawNet disabled.\n");
720
+ console.log(" - Plugin disabled (polling stopped)");
721
+ console.log(" - Hook mappings removed");
721
722
  if (removedCount > 0) {
722
- console.log(` - ${removedCount} hook mapping(s) removed`);
723
+ console.log(` (${removedCount} mapping(s) cleaned up)`);
723
724
  }
725
+ console.log("");
726
+ console.log(" To re-enable: openclaw clawnet setup");
727
+ console.log(" To fully remove: openclaw plugins uninstall clawnet");
724
728
 
725
729
  // Do NOT touch: hooks.enabled, hooks.token, allowedSessionKeyPrefixes, allowedAgentIds
726
730
 
727
731
  await api.runtime.config.writeConfigFile(cfg);
728
732
 
729
- console.log(" - hooks.enabled, hooks.token left untouched");
730
733
  if (opts.purge) {
731
- console.log(" - Plugin config purged");
734
+ console.log(" - Account config purged");
735
+ }
736
+ console.log("\n Restart the Gateway to apply: openclaw gateway restart\n");
737
+ });
738
+
739
+ // --- enable ---
740
+ root
741
+ .command("enable")
742
+ .description("Re-enable a previously disabled ClawNet plugin")
743
+ .action(async () => {
744
+ const currentConfig = api.runtime.config.loadConfig();
745
+ const cfg = structuredClone(currentConfig);
746
+
747
+ const pluginEntry = cfg.plugins?.entries?.clawnet;
748
+ if (!pluginEntry) {
749
+ console.log("\n No ClawNet config found. Run `openclaw clawnet setup` first.\n");
750
+ return;
732
751
  }
752
+
753
+ if (pluginEntry.enabled) {
754
+ console.log("\n ClawNet is already enabled.\n");
755
+ return;
756
+ }
757
+
758
+ pluginEntry.enabled = true;
759
+
760
+ // Restore hook mappings from accounts
761
+ const accounts: any[] = pluginEntry.config?.accounts ?? [];
762
+ const enabled = accounts.filter((a: any) => a.enabled !== false);
763
+ if (enabled.length > 0) {
764
+ cfg.hooks ??= {};
765
+ let mappings = cfg.hooks.mappings ?? [];
766
+ for (const account of enabled) {
767
+ const channel = pluginEntry.config?.deliver?.channel ?? "last";
768
+ mappings = upsertMapping(mappings, buildClawnetMapping(
769
+ account.id,
770
+ channel,
771
+ account.openclawAgentId ?? account.id,
772
+ ));
773
+ }
774
+ cfg.hooks.mappings = mappings;
775
+ }
776
+
777
+ await api.runtime.config.writeConfigFile(cfg);
778
+
779
+ console.log("\n ClawNet enabled.\n");
780
+ console.log(` - Plugin enabled (${enabled.length} account(s))`);
781
+ console.log(` - Hook mappings restored`);
733
782
  console.log("\n Restart the Gateway to apply: openclaw gateway restart\n");
734
783
  });
735
784
  }
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.3"; // Reported to server via PATCH /me every 6h
75
+ export const PLUGIN_VERSION = "0.5.5"; // 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
  }
@@ -55,8 +78,9 @@ async function apiCallRaw(
55
78
  path: string,
56
79
  rawBody: string,
57
80
  openclawAgentId?: string,
81
+ sessionKey?: string,
58
82
  ): Promise<{ ok: boolean; status: number; data: any }> {
59
- const account = getAccountForAgent(cfg, openclawAgentId);
83
+ const account = getAccountForAgent(cfg, openclawAgentId, sessionKey);
60
84
  if (!account) {
61
85
  return { ok: false, status: 0, data: { error: "no_account", message: "No ClawNet account configured. Run: openclaw clawnet setup" } };
62
86
  }
@@ -299,8 +323,7 @@ function toolDesc(name: string, fallback: string): string {
299
323
 
300
324
  // --- Tool registration ---
301
325
 
302
- export function registerTools(api: any, cfg: ClawnetConfig) {
303
- // Load cached descriptions synchronously-safe (already loaded by service start)
326
+ export function registerTools(api: any) {
304
327
  // --- Blessed tools (high-traffic, dedicated) ---
305
328
 
306
329
  api.registerTool({
@@ -310,8 +333,9 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
310
333
  type: "object",
311
334
  properties: {},
312
335
  },
313
- async execute(_id: string, _params: unknown, _onUpdate: unknown, ctx?: { agentId?: string }) {
314
- const result = await apiCall(cfg, "GET", "/inbox/check", undefined, ctx?.agentId);
336
+ async execute(_id: string, _params: unknown, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
337
+ const cfg = loadFreshConfig(api);
338
+ const result = await apiCall(cfg, "GET", "/inbox/check", undefined, ctx?.agentId, ctx?.sessionKey);
315
339
  return textResult(result.data);
316
340
  },
317
341
  });
@@ -326,12 +350,13 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
326
350
  limit: { type: "number", description: "Max messages to return (default 50, max 200)" },
327
351
  },
328
352
  },
329
- async execute(_id: string, params: { status?: string; limit?: number }, _onUpdate: unknown, ctx?: { agentId?: string }) {
353
+ async execute(_id: string, params: { status?: string; limit?: number }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
354
+ const cfg = loadFreshConfig(api);
330
355
  const qs = new URLSearchParams();
331
356
  if (params.status) qs.set("status", params.status);
332
357
  if (params.limit) qs.set("limit", String(params.limit));
333
358
  const query = qs.toString() ? `?${qs}` : "";
334
- const result = await apiCall(cfg, "GET", `/inbox${query}`, undefined, ctx?.agentId);
359
+ const result = await apiCall(cfg, "GET", `/inbox${query}`, undefined, ctx?.agentId, ctx?.sessionKey);
335
360
  return textResult(result.data);
336
361
  },
337
362
  });
@@ -348,15 +373,16 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
348
373
  },
349
374
  required: ["to", "message"],
350
375
  },
351
- async execute(_id: string, params: { to: string; message: string; subject?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
376
+ async execute(_id: string, params: { to: string; message: string; subject?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
377
+ const cfg = loadFreshConfig(api);
352
378
  if (params.to.includes("@")) {
353
379
  // Route to email endpoint
354
380
  const body: Record<string, string> = { to: params.to, body: params.message };
355
381
  if (params.subject) body.subject = params.subject;
356
- const result = await apiCall(cfg, "POST", "/email/send", body, ctx?.agentId);
382
+ const result = await apiCall(cfg, "POST", "/email/send", body, ctx?.agentId, ctx?.sessionKey);
357
383
  return textResult(result.data);
358
384
  }
359
- const result = await apiCall(cfg, "POST", "/send", { to: params.to, message: params.message }, ctx?.agentId);
385
+ const result = await apiCall(cfg, "POST", "/send", { to: params.to, message: params.message }, ctx?.agentId, ctx?.sessionKey);
360
386
  return textResult(result.data);
361
387
  },
362
388
  }, { optional: true });
@@ -373,10 +399,11 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
373
399
  },
374
400
  required: ["message_id", "status"],
375
401
  },
376
- async execute(_id: string, params: { message_id: string; status: string; snoozed_until?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
402
+ async execute(_id: string, params: { message_id: string; status: string; snoozed_until?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
403
+ const cfg = loadFreshConfig(api);
377
404
  const body: Record<string, unknown> = { status: params.status };
378
405
  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);
406
+ const result = await apiCall(cfg, "PATCH", `/messages/${params.message_id}/status`, body, ctx?.agentId, ctx?.sessionKey);
380
407
  return textResult(result.data);
381
408
  },
382
409
  }, { optional: true });
@@ -392,12 +419,13 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
392
419
  scope: { type: "string", description: "'global' for network-wide rules, 'agent' for agent-specific rules, omit for both" },
393
420
  },
394
421
  },
395
- async execute(_id: string, params: { scope?: string }, _onUpdate: unknown, ctx?: { agentId?: string }) {
422
+ async execute(_id: string, params: { scope?: string }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
423
+ const cfg = loadFreshConfig(api);
396
424
  const qs = new URLSearchParams();
397
425
  if (params.scope) qs.set("scope", params.scope);
398
426
  if (ctx?.agentId) qs.set("agent_id", ctx.agentId);
399
427
  const query = qs.toString() ? `?${qs}` : "";
400
- const result = await apiCall(cfg, "GET", `/rules${query}`, undefined, ctx?.agentId);
428
+ const result = await apiCall(cfg, "GET", `/rules${query}`, undefined, ctx?.agentId, ctx?.sessionKey);
401
429
  return textResult(result.data);
402
430
  },
403
431
  });
@@ -413,7 +441,7 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
413
441
  filter: { type: "string", description: "Filter by prefix (e.g. 'email', 'calendar', 'post', 'profile')" },
414
442
  },
415
443
  },
416
- async execute(_id: string, params: { filter?: string }, _onUpdate: unknown, _ctx?: { agentId?: string }) {
444
+ async execute(_id: string, params: { filter?: string }, _onUpdate: unknown, _ctx?: { agentId?: string; sessionKey?: string }) {
417
445
  let ops = getOperations();
418
446
  if (params.filter) {
419
447
  const prefix = params.filter.toLowerCase();
@@ -449,7 +477,8 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
449
477
  },
450
478
  required: ["operation"],
451
479
  },
452
- async execute(_id: string, input: { operation: string; params?: Record<string, unknown> }, _onUpdate: unknown, ctx?: { agentId?: string }) {
480
+ async execute(_id: string, input: { operation: string; params?: Record<string, unknown> }, _onUpdate: unknown, ctx?: { agentId?: string; sessionKey?: string }) {
481
+ const cfg = loadFreshConfig(api);
453
482
  const op = getOperations().find((o) => o.operation === input.operation);
454
483
  if (!op) {
455
484
  return textResult({ error: "unknown_operation", message: `Unknown operation: ${input.operation}. Call clawnet_capabilities to see available operations.` });
@@ -507,8 +536,8 @@ export function registerTools(api: any, cfg: ClawnetConfig) {
507
536
  }
508
537
 
509
538
  const result = rawBody !== undefined
510
- ? await apiCallRaw(cfg, op.method, path, rawBody, ctx?.agentId)
511
- : await apiCall(cfg, op.method, path, body, ctx?.agentId);
539
+ ? await apiCallRaw(cfg, op.method, path, rawBody, ctx?.agentId, ctx?.sessionKey)
540
+ : await apiCall(cfg, op.method, path, body, ctx?.agentId, ctx?.sessionKey);
512
541
  return textResult(result.data);
513
542
  },
514
543
  }, { optional: true });