@masons/agent-network 0.5.14 → 0.5.16

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.
Files changed (66) hide show
  1. package/dist/channel.d.ts +0 -7
  2. package/dist/channel.d.ts.map +1 -1
  3. package/dist/channel.js +3 -174
  4. package/dist/cli-setup.d.ts +0 -109
  5. package/dist/cli-setup.d.ts.map +1 -1
  6. package/dist/cli-setup.js +16 -570
  7. package/dist/config-fs.d.ts +4 -0
  8. package/dist/config-fs.d.ts.map +1 -0
  9. package/dist/config-fs.js +23 -0
  10. package/dist/config-schema.js +2 -2
  11. package/dist/config.d.ts +2 -210
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +14 -334
  14. package/dist/connector-client.d.ts +0 -32
  15. package/dist/connector-client.d.ts.map +1 -1
  16. package/dist/connector-client.js +1 -89
  17. package/dist/constants.d.ts +0 -1
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +2 -3
  20. package/dist/conversation-manager.d.ts +0 -106
  21. package/dist/conversation-manager.d.ts.map +1 -1
  22. package/dist/conversation-manager.js +2 -131
  23. package/dist/environment-context.d.ts +0 -24
  24. package/dist/environment-context.d.ts.map +1 -1
  25. package/dist/environment-context.js +0 -42
  26. package/dist/handle-utils.d.ts +0 -14
  27. package/dist/handle-utils.d.ts.map +1 -1
  28. package/dist/handle-utils.js +0 -14
  29. package/dist/index.js +0 -9
  30. package/dist/owner-notes.d.ts +0 -33
  31. package/dist/owner-notes.d.ts.map +1 -1
  32. package/dist/owner-notes.js +2 -41
  33. package/dist/owner-session-state.d.ts +0 -26
  34. package/dist/owner-session-state.d.ts.map +1 -1
  35. package/dist/owner-session-state.js +0 -37
  36. package/dist/platform-client.d.ts +13 -202
  37. package/dist/platform-client.d.ts.map +1 -1
  38. package/dist/platform-client.js +22 -171
  39. package/dist/plugin.d.ts +5 -0
  40. package/dist/plugin.d.ts.map +1 -1
  41. package/dist/plugin.js +3 -167
  42. package/dist/sent-message-buffer.d.ts +0 -36
  43. package/dist/sent-message-buffer.d.ts.map +1 -1
  44. package/dist/sent-message-buffer.js +1 -45
  45. package/dist/tools.d.ts +0 -28
  46. package/dist/tools.d.ts.map +1 -1
  47. package/dist/tools.js +36 -240
  48. package/dist/turn-context.d.ts +0 -45
  49. package/dist/turn-context.d.ts.map +1 -1
  50. package/dist/turn-context.js +0 -57
  51. package/dist/types.d.ts +0 -67
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/types.js +0 -7
  54. package/dist/update-cache.d.ts +0 -17
  55. package/dist/update-cache.d.ts.map +1 -1
  56. package/dist/update-cache.js +1 -21
  57. package/dist/update-check.d.ts +1 -40
  58. package/dist/update-check.d.ts.map +1 -1
  59. package/dist/update-check.js +7 -66
  60. package/dist/version.d.ts +1 -2
  61. package/dist/version.d.ts.map +1 -1
  62. package/dist/version.js +1 -2
  63. package/openclaw.plugin.json +94 -3
  64. package/package.json +11 -10
  65. package/skills/agent-network/SKILL.md +21 -47
  66. package/skills/agent-network/references/troubleshooting.md +5 -5
package/dist/tools.js CHANGED
@@ -1,81 +1,28 @@
1
- /**
2
- * LLM tools — setup, connection, profile, and conversation tools.
3
- *
4
- * Registers tools with OpenClaw's Plugin API so the LLM can
5
- * drive setup, profile completion, connection listing, connection
6
- * requests, request management, and real-time conversations,
7
- * guided by SKILL.md.
8
- *
9
- * Two access patterns:
10
- * - **HTTP tools** (setup, connection): read config via `requirePlatformConfig()`,
11
- * call Platform API via `platform-client.ts`.
12
- * - **WebSocket tools** (conversation): use `requireConversationManager()` for
13
- * identity-based messaging. Session management is fully transparent.
14
- *
15
- * Session Abstraction (#741):
16
- * - `masons_send_message(to, content)` — sends via ConversationManager
17
- * - `masons_end_conversation(contact)` — ends via ConversationManager
18
- * (Deprecated shims masons_create_session and masons_end_session have been removed.)
19
- */
20
- import { tmpdir } from "node:os";
21
1
  import { Type } from "@sinclair/typebox";
22
2
  import { getOwnerPassportAddress } from "./channel.js";
23
- import { clearTargetHandle, getDmScope, getOpenClawHome, getPendingTarget, isProfileNeeded, markProfileComplete, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
3
+ import { clearTargetHandle, getDmScope, getPendingTarget, isProfileNeeded, markProfileComplete, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
24
4
  import { getOwnerHandle } from "./environment-context.js";
25
5
  import { ownerNotesQueue } from "./owner-notes.js";
26
- import { acceptRequest, declineRequest, getConnectionStatus, listConnections, listRequests, PlatformApiError, requestConnection, updateProfile, } from "./platform-client.js";
6
+ import { acceptRequest, declineRequest, listConnections, listRequests, PlatformApiError, requestConnection, updateProfile, } from "./platform-client.js";
27
7
  import { sentMessageBuffer } from "./sent-message-buffer.js";
28
8
  import { getCurrentTurnChannelId, getCurrentTurnIsOwnerNonConsuming, } from "./turn-context.js";
29
- import { fetchLatestVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
30
- // ---------------------------------------------------------------------------
31
- // Constants
32
- // ---------------------------------------------------------------------------
33
- /** Accepted field names for masons_update_profile — reject anything else. */
9
+ import { getLatestPublishedVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
34
10
  const PROFILE_FIELDS = new Set(["name", "scope", "about", "audience"]);
35
- /** CLI command to update the plugin via OpenClaw. */
36
11
  const UPGRADE_CMD = "openclaw plugins update agent-network";
37
- /** Fallback: manual update via npm pack + in-place extract. Used when
38
- * `openclaw plugins update` fails (integrity conflict, npm cache issue, etc.)
39
- * while preserving credentials and node_modules.
40
- * Placeholders `{version}`, `{home}`, and `{tmpdir}` are replaced at runtime. */
41
- const FALLBACK_UPGRADE_CMD = "npm pack @masons/agent-network@{version} --pack-destination {tmpdir} && " +
42
- "tar xzf {tmpdir}/masons-agent-network-{version}.tgz --strip-components=1 -C {home}/extensions/agent-network/ && " +
43
- "cd {home}/extensions/agent-network && npm install --omit=dev && " +
44
- "rm -f {tmpdir}/masons-agent-network-{version}.tgz";
45
- /** CLI command to restart the gateway. */
46
12
  const GATEWAY_RESTART_CMD = "openclaw gateway restart";
47
- /** Simple semver regex — validates x.y.z before inserting into shell commands. */
13
+ function reinstallCmd(version) {
14
+ return `openclaw plugins install @masons/agent-network@${version} --force`;
15
+ }
48
16
  const SEMVER_RE = /^\d+\.\d+\.\d+$/;
49
- // ---------------------------------------------------------------------------
50
- // Module-level state (not persisted across process restarts)
51
- // ---------------------------------------------------------------------------
52
- /**
53
- * Tracks the version we already tried to upgrade to this session.
54
- * null = no attempt yet. Set to the target version string on the first
55
- * normal-path attempt. If the tool is called again while current version
56
- * still differs, the fallback path is returned instead.
57
- */
58
17
  let upgradeAttemptedVersion = null;
59
- /** @internal Reset module state for test isolation. */
60
18
  export function _resetToolsForTesting() {
61
19
  updateNoticeShown = false;
62
20
  upgradeAttemptedVersion = null;
63
21
  }
64
- // ---------------------------------------------------------------------------
65
- // Format helpers
66
- // ---------------------------------------------------------------------------
67
22
  function textResult(text) {
68
23
  return { content: [{ type: "text", text }] };
69
24
  }
70
- // Show update notice only once per gateway session to avoid being annoying.
71
25
  let updateNoticeShown = false;
72
- /**
73
- * Append an update notice to tool output if a newer version is available.
74
- *
75
- * Called on every tool result. Shows the notice at most once per session —
76
- * after the first tool call surfaces it, the Agent has the information
77
- * and can guide the user through upgrading.
78
- */
79
26
  function maybeAppendUpdateNotice(result) {
80
27
  const info = getUpdateInfo();
81
28
  if (!info?.updateAvailable || updateNoticeShown)
@@ -89,28 +36,9 @@ function maybeAppendUpdateNotice(result) {
89
36
  }
90
37
  return result;
91
38
  }
92
- /**
93
- * Wrap a tool's execute function to append update notice to its output.
94
- *
95
- * Applied at registration time so every tool automatically surfaces
96
- * update information without modifying individual return sites.
97
- */
98
39
  function withUpdateNotice(fn) {
99
40
  return async (id, params) => maybeAppendUpdateNotice(await fn(id, params));
100
41
  }
101
- /**
102
- * Format a Node kind discriminator as a prefix label for list rendering.
103
- *
104
- * Returns:
105
- * - "[Human Node] " when kind === "user"
106
- * - "[Agent Node] " when kind === "agent"
107
- * - "[Node:<kind>] " for future / unknown kinds (forward-compat per
108
- * `docs/concepts/human-node-capability-obligations.md § 5` — never
109
- * suppress, never crash)
110
- * - "" (empty) when kind is absent — preserves backward-compatible
111
- * rendering against Connector versions that have not adopted NodeRef
112
- * projection.
113
- */
114
42
  function formatKindLabel(kind) {
115
43
  if (!kind)
116
44
  return "";
@@ -126,42 +54,18 @@ function formatConnectionResult(requestIds, status) {
126
54
  }
127
55
  return `Connection request sent. Status: ${status}. The other agent's owner will be notified.`;
128
56
  }
129
- // ---------------------------------------------------------------------------
130
- // Tool registration
131
- // ---------------------------------------------------------------------------
132
- /**
133
- * Register agent network tools with the OpenClaw Plugin API.
134
- *
135
- * Called from `plugin.ts` during plugin registration. Tools become available
136
- * to the LLM after the plugin loads. Tools that require config will fail-fast
137
- * with a clear error if `initToolConfig()` hasn't been called yet (i.e.,
138
- * `startAccount()` hasn't run).
139
- */
140
57
  export function registerTools(api) {
141
- // --- masons_setup ---------------------------------------------------------
142
- // Lightweight nudge for the no-credentials state (#1264). The legacy
143
- // `masons_setup_init / check / complete` trio (removed in 0.5.0) ran the
144
- // device flow inside the LLM; that's not viable for an OAuth Device
145
- // Authorization Grant flow which requires terminal I/O (the user enters a
146
- // user_code in their browser, returns to the terminal to pick or create
147
- // an agent). This tool's only job is to surface the CLI command and let
148
- // the user run it.
149
- //
150
- // Layer B's `before_prompt_build` hook in plugin.ts already injects this
151
- // guidance for the no-credentials state — this tool is here so the LLM
152
- // can reach it explicitly when the user asks "how do I set this up?"
153
- // (e.g., as a fallback after a failed tool call surfaces "no credentials").
154
58
  api.registerTool({
155
59
  name: "masons_setup",
156
60
  description: [
157
61
  "Returns instructions for connecting OpenClaw to the agent network.",
158
62
  "Use when the user asks how to set up Agent Network, when other Agent",
159
- "Network tools fail with a 'no credentials' / 'no API key' error, or",
63
+ "Network tools fail with a 'no credentials' / 'no runtime key' error, or",
160
64
  "when the user wants to switch which agent OpenClaw is driving.",
161
65
  "",
162
66
  "This tool does NOT perform setup itself — it surfaces the terminal",
163
- "command. The user must run it from their shell because the device",
164
- "authorization flow requires browser interaction and terminal prompts.",
67
+ "command. The user must run it from their shell because login requires",
68
+ "a browser handoff.",
165
69
  ].join("\n"),
166
70
  parameters: Type.Object({}),
167
71
  execute: async () => ({
@@ -174,21 +78,21 @@ export function registerTools(api) {
174
78
  " openclaw channels login --channel agent-network",
175
79
  "",
176
80
  "It will:",
177
- " 1. Display a short setup code and a verification URL.",
178
- " 2. Open the URL in your browser, sign in to MASONS, approve.",
179
- " 3. Return to the terminal — pick an existing agent or create a new one.",
180
- " 4. Persist credentials to openclaw.json. OpenClaw reloads the channel.",
81
+ " 1. Display a MASONS handoff URL.",
82
+ " 2. Open it in your browser and sign in.",
83
+ " 3. Pick an existing agent or create a new one.",
84
+ " 4. Return to the terminal after the encrypted handoff completes.",
85
+ " 5. Persist credentials to openclaw.json. OpenClaw reloads the channel.",
181
86
  "",
182
- "Note (single-Runtime semantic): if you re-run this with another",
183
- "agent on a different machine, that agent's API key is rotated and",
184
- "any previously-connected OpenClaw is disconnected. Only one",
185
- "Runtime drives an agent at a time.",
87
+ "Note: if you re-run this with another",
88
+ "agent on this machine, this OpenClaw install receives",
89
+ "its own runtime key. Re-running login here",
90
+ "updates this OpenClaw install's credentials.",
186
91
  ].join("\n"),
187
92
  },
188
93
  ],
189
94
  }),
190
95
  });
191
- // --- masons_update_profile ------------------------------------------------
192
96
  api.registerTool({
193
97
  name: "masons_update_profile",
194
98
  description: [
@@ -217,8 +121,6 @@ export function registerTools(api) {
217
121
  })),
218
122
  }),
219
123
  execute: withUpdateNotice(async (_id, params) => {
220
- // Guard: reject unknown field names immediately so the LLM
221
- // sees an actionable error and can self-correct.
222
124
  const unknownFields = Object.keys(params).filter((k) => !PROFILE_FIELDS.has(k));
223
125
  if (unknownFields.length > 0) {
224
126
  return textResult(`Unknown field(s): ${unknownFields.join(", ")}. ` +
@@ -246,7 +148,7 @@ export function registerTools(api) {
246
148
  catch (err) {
247
149
  if (err instanceof PlatformApiError) {
248
150
  if (err.status === 401) {
249
- return textResult("Authentication failed. The API key may be invalid. Ask the user to re-authorize at masons.ai/device.");
151
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
250
152
  }
251
153
  if (err.status === 422) {
252
154
  return textResult(`Validation error: ${err.message}`);
@@ -254,15 +156,12 @@ export function registerTools(api) {
254
156
  }
255
157
  throw err;
256
158
  }
257
- // Completion gate: scope + about + audience must ALL be non-empty.
258
- // name is set during onboard — not a completion gate.
259
159
  if (result.profile) {
260
160
  const { scope, about, audience } = result.profile;
261
161
  const profileComplete = scope.length > 0 && about.length > 0 && audience.length > 0;
262
162
  if (profileComplete) {
263
163
  await markProfileComplete();
264
164
  }
265
- // Echo saved values for LLM verification
266
165
  const lines = [
267
166
  "Profile updated successfully. Current profile:",
268
167
  `• name: ${result.profile.name}`,
@@ -285,14 +184,12 @@ export function registerTools(api) {
285
184
  }
286
185
  return textResult(lines.join("\n"));
287
186
  }
288
- // Backward compat: old Platform without profile in response
289
187
  if (profileFields.scope && profileFields.scope.length > 0) {
290
188
  await markProfileComplete();
291
189
  }
292
190
  return textResult("Profile updated successfully. The agent's identity is now visible on the network.");
293
191
  }),
294
192
  });
295
- // --- masons_send_connection_request --------------------------------------
296
193
  api.registerTool({
297
194
  name: "masons_send_connection_request",
298
195
  description: "Send a connection request to another Agent on the agent network.",
@@ -304,9 +201,6 @@ export function registerTools(api) {
304
201
  execute: withUpdateNotice(async (_id, params) => {
305
202
  const cfg = requirePlatformConfig();
306
203
  const apiKey = requireApiKey();
307
- // Profile gate: block connection requests until profile is complete.
308
- // Without a profile the other agent sees an anonymous stranger,
309
- // making acceptance unlikely.
310
204
  if (isProfileNeeded()) {
311
205
  return textResult("Your agent's profile is not complete yet. Without a profile, " +
312
206
  "the other agent can't see who you are — connection requests from " +
@@ -327,20 +221,18 @@ export function registerTools(api) {
327
221
  return textResult(`Agent @${targetHandle} not found. Check the handle and try again.`);
328
222
  }
329
223
  if (err.status === 401) {
330
- return textResult("Authentication failed. The API key may be invalid. Ask the user to re-authorize at masons.ai/device.");
224
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
331
225
  }
332
226
  return textResult(`Connection request failed: ${err.message}`);
333
227
  }
334
228
  throw err;
335
229
  }
336
- // Clear pending target if one was set
337
230
  if (getPendingTarget()) {
338
231
  await clearTargetHandle();
339
232
  }
340
233
  return textResult(formatConnectionResult(result.requestIds, result.status));
341
234
  }),
342
235
  });
343
- // --- masons_list_requests --------------------------------------------------
344
236
  api.registerTool({
345
237
  name: "masons_list_requests",
346
238
  description: "List connection requests — incoming from other agents, outgoing ones you sent, or all.",
@@ -368,7 +260,7 @@ export function registerTools(api) {
368
260
  catch (err) {
369
261
  if (err instanceof PlatformApiError) {
370
262
  if (err.status === 401) {
371
- return textResult("Authentication failed. The API key may be invalid. Ask the user to re-authorize at masons.ai/device.");
263
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
372
264
  }
373
265
  return textResult(`Failed to list requests: ${err.message}`);
374
266
  }
@@ -384,24 +276,9 @@ export function registerTools(api) {
384
276
  }
385
277
  const lines = result.items.map((item) => {
386
278
  const dir = item.direction ?? direction;
387
- // For incoming: sender NodeRef is in `fromNode` (since #1450 Phase 1).
388
- // For outgoing: target NodeRef is in `toNode`. Both may be null when
389
- // the Connector ran NodeRef enrichment but the entity record was
390
- // missing. Back-compat fallback: `fromAgent` / `toAgent` carry just
391
- // `{ handle, name }` from older Connector versions or the back-compat
392
- // shape #1450 still emits.
393
279
  const node = dir === "outgoing" ? item.toNode : item.fromNode;
394
- const compat = dir === "outgoing" ? item.toAgent : item.fromAgent;
395
- // Display fallback chain per `human-node-capability-obligations.md § 2`:
396
- // NodeRef.displayName → NodeRef.handle → back-compat name → handle → "unknown".
397
- const otherName = node?.displayName ||
398
- node?.handle ||
399
- compat?.name ||
400
- compat?.handle ||
401
- "unknown";
402
- const otherHandle = node?.handle || compat?.handle || "unknown";
403
- // Kind discriminator: surface "Human Node" / "Agent Node" / future kinds
404
- // when the Connector projects `kind` per the same doc § 5 forward-compat.
280
+ const otherName = node?.displayName || node?.handle || "unknown";
281
+ const otherHandle = node?.handle || "unknown";
405
282
  const kindLabel = formatKindLabel(node?.kind);
406
283
  const dirArrow = dir === "outgoing" ? "→" : "←";
407
284
  const parts = [
@@ -430,7 +307,6 @@ export function registerTools(api) {
430
307
  return textResult(`${result.total} ${dirLabel}${status === "all" ? "" : `${status} `}request(s):\n\n${lines.join("\n\n")}${suffix}`);
431
308
  }),
432
309
  });
433
- // --- masons_accept_request ------------------------------------------------
434
310
  api.registerTool({
435
311
  name: "masons_accept_request",
436
312
  description: "Accept an incoming connection request from another agent.",
@@ -442,9 +318,6 @@ export function registerTools(api) {
442
318
  execute: withUpdateNotice(async (_id, params) => {
443
319
  const cfg = requirePlatformConfig();
444
320
  const apiKey = requireApiKey();
445
- // Profile gate: block accepting requests until profile is complete.
446
- // When a connection is established, the other agent sees your profile —
447
- // an empty profile means they won't know who you are.
448
321
  if (isProfileNeeded()) {
449
322
  return textResult("Your agent's profile is not complete yet. Without a profile, " +
450
323
  "the other agent won't know who you are when the connection is established. " +
@@ -453,17 +326,11 @@ export function registerTools(api) {
453
326
  const requestId = params.requestId;
454
327
  try {
455
328
  const result = await acceptRequest(cfg, apiKey, requestId);
456
- // Prefer NodeRef on `counterparty` (since #1450 Phase 1); fall
457
- // back to legacy `connection.{name, handle}` for older Connector
458
- // versions or when enrichment returned null.
459
329
  const node = result.counterparty;
460
- const who = node?.displayName ||
461
- node?.handle ||
462
- result.connection.name ||
463
- result.connection.handle;
464
- const handle = node?.handle || result.connection.handle;
330
+ const who = node?.displayName || node?.handle || "that Node";
331
+ const handle = node?.handle ? ` (@${node.handle})` : "";
465
332
  const kindLabel = formatKindLabel(node?.kind);
466
- return textResult(`Connected with ${kindLabel}${who} (@${handle})! You can now send messages using masons_send_message.`);
333
+ return textResult(`Connected with ${kindLabel}${who}${handle}! You can now send messages using masons_send_message.`);
467
334
  }
468
335
  catch (err) {
469
336
  if (err instanceof PlatformApiError && err.status === 404) {
@@ -473,7 +340,6 @@ export function registerTools(api) {
473
340
  }
474
341
  }),
475
342
  });
476
- // --- masons_decline_request -----------------------------------------------
477
343
  api.registerTool({
478
344
  name: "masons_decline_request",
479
345
  description: "Decline an incoming connection request from another agent.",
@@ -498,7 +364,6 @@ export function registerTools(api) {
498
364
  }
499
365
  }),
500
366
  });
501
- // --- masons_list_connections -----------------------------------------------
502
367
  api.registerTool({
503
368
  name: "masons_list_connections",
504
369
  description: "List your established connections on the agent network. Returns the name and address of each connected agent.",
@@ -513,7 +378,7 @@ export function registerTools(api) {
513
378
  catch (err) {
514
379
  if (err instanceof PlatformApiError) {
515
380
  if (err.status === 401) {
516
- return textResult("Authentication failed. The API key may be invalid. Ask the user to re-authorize at masons.ai/device.");
381
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
517
382
  }
518
383
  return textResult(`Failed to list connections: ${err.message}`);
519
384
  }
@@ -522,22 +387,16 @@ export function registerTools(api) {
522
387
  if (result.total === 0) {
523
388
  return textResult("No connections yet. Use masons_send_connection_request to connect with other agents.");
524
389
  }
525
- const lines = result.items.map((item, i) => {
526
- // NodeRef on `counterparty` (since #1450 Phase 1); falls back to
527
- // legacy `name + address` for older Connector versions.
390
+ const lines = result.connections.map((item, i) => {
528
391
  const node = item.counterparty;
529
- const displayName = node?.displayName || node?.handle || item.name || "(unnamed)";
530
- const address = node?.mstpAddress || item.address;
392
+ const displayName = node.displayName || node.handle || "(unnamed)";
393
+ const address = node.mstpAddress;
531
394
  const kindLabel = formatKindLabel(node?.kind);
532
395
  return `${i + 1}. ${kindLabel}${displayName} (${address})`;
533
396
  });
534
397
  return textResult(`${result.total} connection(s):\n\n${lines.join("\n")}\n\nUse masons_send_message with a handle or address to send a message.`);
535
398
  }),
536
399
  });
537
- // =========================================================================
538
- // Conversation tools — identity-based via ConversationManager
539
- // =========================================================================
540
- // --- masons_send_message --------------------------------------------------
541
400
  api.registerTool({
542
401
  name: "masons_send_message",
543
402
  description: "Send a message to a remote agent on the agent network. This is agent-to-agent communication — separate from your owner's conversation. Sessions are managed automatically — just provide the contact handle or address.",
@@ -553,33 +412,11 @@ export function registerTools(api) {
553
412
  const cm = requireConversationManager();
554
413
  const to = params.to;
555
414
  const content = params.content;
556
- // Extract handle once — shared by pre-flight check and sent message buffer.
557
415
  const handle = to.startsWith("mstps://") || to.startsWith("mstp://")
558
416
  ? to.replace(/^mstps?:\/\/[^/]+\//, "").replace(/\/+$/, "")
559
417
  : to;
560
- // --- Pre-flight: check connection status (advisory, not gate) ---
561
- // If the check fails (network error, API down), proceed anyway —
562
- // the Connector gate (Layer 2) is the authoritative enforcement.
563
- try {
564
- if (handle) {
565
- const cfg = requirePlatformConfig();
566
- const apiKey = requireApiKey();
567
- const status = await getConnectionStatus(cfg, apiKey, handle);
568
- if (status && !status.canCreateSession) {
569
- if (status.status === "pending") {
570
- return textResult(`Connection request to @${handle} is pending. Wait for the other agent to accept your request.`);
571
- }
572
- return textResult(`You are not connected to @${handle}. Send a connection request first using masons_send_connection_request.`);
573
- }
574
- }
575
- }
576
- catch {
577
- // Advisory check failed — proceed to Connector gate
578
- }
579
418
  const result = await cm.send(to, content);
580
419
  if (result.status === "sent") {
581
- // Record in sent message buffer so the agent-network session has
582
- // context when the remote agent replies (cross-session bridge, #918).
583
420
  sentMessageBuffer.record(handle, content);
584
421
  return textResult("Message sent.");
585
422
  }
@@ -587,7 +424,6 @@ export function registerTools(api) {
587
424
  "Failed to send message. The network connection may be temporarily unavailable. Try again in a moment.");
588
425
  }),
589
426
  });
590
- // --- masons_end_conversation -----------------------------------------------
591
427
  api.registerTool({
592
428
  name: "masons_end_conversation",
593
429
  description: "End the conversation with a connected agent. This immediately closes the session — do not send any text reply after calling this tool.",
@@ -603,7 +439,6 @@ export function registerTools(api) {
603
439
  return textResult("Conversation ended.");
604
440
  }),
605
441
  });
606
- // --- masons_note_for_owner ------------------------------------------------
607
442
  api.registerTool({
608
443
  name: "masons_note_for_owner",
609
444
  description: "Save a note for your owner (Principal). The note will be delivered " +
@@ -626,54 +461,34 @@ export function registerTools(api) {
626
461
  return textResult("Note saved. Your owner will see it in their next turn.");
627
462
  }),
628
463
  });
629
- // --- masons_upgrade -------------------------------------------------------
630
464
  api.registerTool({
631
465
  name: "masons_upgrade",
632
466
  description: "Check for and install plugin updates via `openclaw plugins update`. Do not use ClawHub (it distributes skills, not plugins).",
633
467
  parameters: Type.Object({}),
634
- // NOT wrapped with withUpdateNotice — this tool IS the upgrade path.
635
- // Wrapping would create a circular reference in the update notice text.
636
468
  execute: async () => {
637
- // Drain guard: upgrading restarts the Gateway, which clears the
638
- // in-memory owner notes queue. Warn the LLM to deliver pending
639
- // notes before upgrading.
640
469
  const pendingNotes = ownerNotesQueue.size();
641
470
  if (pendingNotes > 0) {
642
471
  return textResult(`You have ${pendingNotes} pending note(s) for your owner that haven't been delivered yet. ` +
643
472
  `Report them to your owner first, then call masons_upgrade again.`);
644
473
  }
645
474
  const currentVersion = getPluginVersion();
646
- // 1. Determine latest version.
647
- // Always fetch directly from npm — this is an explicit upgrade
648
- // command, not a passive check. The 24h startup cache
649
- // (checkForUpdate / getUpdateInfo) is for the withUpdateNotice
650
- // passive hint only. An explicit "upgrade" must see the real
651
- // latest version, even if it was published minutes ago.
652
- const latestVersion = await fetchLatestVersion();
653
- // 2. Network error — can't determine latest version.
475
+ const latestVersion = await getLatestPublishedVersion();
654
476
  if (!latestVersion) {
655
477
  return textResult("Could not check for updates (network issue). Try again later.");
656
478
  }
657
- // 3. Already up to date.
658
479
  if (latestVersion === currentVersion) {
659
- upgradeAttemptedVersion = null; // Reset on success
480
+ upgradeAttemptedVersion = null;
660
481
  return textResult(`You're on the latest version (v${currentVersion}). No update needed.`);
661
482
  }
662
- // 4. Semver validation — never insert unvalidated strings into shell commands.
663
483
  if (!SEMVER_RE.test(latestVersion)) {
664
484
  return textResult(`Registry returned an unexpected version format: "${latestVersion}". Try again later.`);
665
485
  }
666
- // 5. Determine path: normal vs fallback.
667
486
  const useFallback = upgradeAttemptedVersion === latestVersion;
668
487
  if (useFallback) {
669
- // Fallback path — previous normal-path attempt for this version didn't work.
670
- upgradeAttemptedVersion = null; // Reset so next call starts fresh
671
- const home = getOpenClawHome();
672
- const fallbackCmd = FALLBACK_UPGRADE_CMD.replaceAll("{tmpdir}", tmpdir())
673
- .replaceAll("{home}", home)
674
- .replaceAll("{version}", latestVersion);
488
+ upgradeAttemptedVersion = null;
489
+ const fallbackCmd = reinstallCmd(latestVersion);
675
490
  return textResult([
676
- `Update to v${latestVersion} — manual update (the automatic update command may not have detected the new version).`,
491
+ `Update to v${latestVersion} — reinstall through OpenClaw (the normal update command may not have detected the new version).`,
677
492
  "",
678
493
  "Run this command using the exec tool:",
679
494
  fallbackCmd,
@@ -683,7 +498,6 @@ export function registerTools(api) {
683
498
  `${fallbackCmd} && ${GATEWAY_RESTART_CMD}`,
684
499
  ].join("\n"));
685
500
  }
686
- // Normal path — first attempt for this version.
687
501
  upgradeAttemptedVersion = latestVersion;
688
502
  return textResult([
689
503
  `Update available: v${currentVersion} → v${latestVersion}`,
@@ -697,11 +511,6 @@ export function registerTools(api) {
697
511
  ].join("\n"));
698
512
  },
699
513
  });
700
- // --- masons_link_identity -------------------------------------------------
701
- // Simplified interface (#986 redesign): LLM provides only the current
702
- // channel's entry. Canonical is auto-generated from getOwnerHandle().
703
- // Passport entry is auto-included from storedOwnerPassportAddress.
704
- // Access gate: deterministic owner-only check prevents prompt injection.
705
514
  api.registerTool({
706
515
  name: "masons_link_identity",
707
516
  description: "Link your owner's identity on the current channel to their Passport identity. " +
@@ -716,9 +525,6 @@ export function registerTools(api) {
716
525
  }),
717
526
  }),
718
527
  execute: withUpdateNotice(async (_id, params) => {
719
- // --- Access gate (deterministic) ---
720
- // Only the owner on an agent-network turn can invoke this tool.
721
- // Prevents prompt injection from visitors or remote agents.
722
528
  const hookChannelId = getCurrentTurnChannelId();
723
529
  const turnIsOwner = getCurrentTurnIsOwnerNonConsuming();
724
530
  if (hookChannelId === "agent-network" && !turnIsOwner) {
@@ -731,20 +537,16 @@ export function registerTools(api) {
731
537
  const entry = String(params.entry ?? "")
732
538
  .trim()
733
539
  .toLowerCase();
734
- // Validate channel:peerId format — split on first colon only,
735
- // both parts must be non-empty (e.g., "telegram:5099353300").
736
540
  const colonIdx = entry.indexOf(":");
737
541
  if (colonIdx < 1 || colonIdx === entry.length - 1) {
738
542
  return textResult("Invalid format. Use 'channel:peerId' (e.g., 'telegram:5099353300').");
739
543
  }
740
- // Build entries: user-provided entry + auto-include Passport entry
741
544
  const entries = [entry];
742
545
  const passportAddr = getOwnerPassportAddress();
743
546
  if (passportAddr) {
744
547
  entries.push(`agent-network:${passportAddr}`);
745
548
  }
746
549
  try {
747
- // writeIdentityLinks does entry-level additive merge
748
550
  await writeIdentityLinks(ownerHandle, entries);
749
551
  }
750
552
  catch (err) {
@@ -766,19 +568,13 @@ export function registerTools(api) {
766
568
  'reason: "Activate cross-channel identity linking".',
767
569
  ].join("\n"));
768
570
  }),
769
- },
770
- // optional: not all Gateway versions support this tool.
771
- { optional: true });
772
- // --- masons_unlink_identity -----------------------------------------------
773
- // Undo path: removes the owner's canonical identity group from session.identityLinks.
774
- // Access gate: same owner-only check as masons_link_identity.
571
+ }, { optional: true });
775
572
  api.registerTool({
776
573
  name: "masons_unlink_identity",
777
574
  description: "Remove your owner's cross-channel identity link. Reverses masons_link_identity. " +
778
575
  "Requires a gateway restart to take effect.",
779
576
  parameters: Type.Object({}),
780
577
  execute: withUpdateNotice(async () => {
781
- // --- Access gate (deterministic) ---
782
578
  const hookChannelId = getCurrentTurnChannelId();
783
579
  const turnIsOwner = getCurrentTurnIsOwnerNonConsuming();
784
580
  if (hookChannelId === "agent-network" && !turnIsOwner) {
@@ -1,52 +1,7 @@
1
- /**
2
- * Turn Context — per-turn metadata captured during inbound message dispatch.
3
- *
4
- * Tracks the sender identity for the current LLM turn. Set in channel.ts
5
- * message_received handler (using the closure-scoped ConversationManager),
6
- * consumed by before_prompt_build in plugin.ts to inject interaction context.
7
- *
8
- * Follows the same consume-on-read pattern as owner-session-state.ts:
9
- * - Written BEFORE dispatch (so before_prompt_build can read it synchronously)
10
- * - Consumed (read + reset) to prevent stale state across turns
11
- *
12
- * Race condition caveat: if two inbound messages arrive on different sessions
13
- * near-simultaneously, the sender may reflect the wrong session. Acceptable
14
- * for Phase 1 — OpenClaw processes turns sequentially per agent. Same caveat
15
- * as owner-session-state.ts currentTurnIsOwner.
16
- *
17
- * Module-level singleton — shared between channel.ts (write) and plugin.ts (read).
18
- */
19
- /**
20
- * Set the sender identity for the current turn.
21
- * Called in message_received before dispatch, using the closure-scoped
22
- * ConversationManager to resolve sessionId → contact handle.
23
- */
24
1
  export declare function setCurrentTurnSender(contact: string | null): void;
25
- /**
26
- * Set the channel ID for the current turn.
27
- * Called in before_prompt_build — the hook provides channelId.
28
- * Persists through tool execution within the same turn.
29
- */
30
2
  export declare function setCurrentTurnChannelId(channelId: string | null): void;
31
- /**
32
- * Set the owner flag for the current turn (non-consuming copy).
33
- * Called in before_prompt_build after consuming from owner-session-state.
34
- * This copy persists through tool execution within the same turn.
35
- */
36
3
  export declare function setCurrentTurnIsOwnerForTools(value: boolean): void;
37
- /**
38
- * Consume the per-turn sender identity. Returns the value and resets to null.
39
- * Consuming prevents stale state from leaking into subsequent turns.
40
- */
41
4
  export declare function consumeCurrentTurnSender(): string | null;
42
- /**
43
- * Get the channel ID for the current turn (non-consuming).
44
- * Used by tool access gates to determine which channel triggered the turn.
45
- */
46
5
  export declare function getCurrentTurnChannelId(): string | null;
47
- /**
48
- * Get the owner flag for the current turn (non-consuming).
49
- * Used by tool access gates to enforce owner-only tools.
50
- */
51
6
  export declare function getCurrentTurnIsOwnerNonConsuming(): boolean;
52
7
  //# sourceMappingURL=turn-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"turn-context.d.ts","sourceRoot":"","sources":["../src/turn-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAmBH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElE;AAMD;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAIxD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAE3D"}
1
+ {"version":3,"file":"turn-context.d.ts","sourceRoot":"","sources":["../src/turn-context.ts"],"names":[],"mappings":"AAyCA,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjE;AAOD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtE;AAOD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElE;AAUD,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAIxD;AAMD,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAMD,wBAAgB,iCAAiC,IAAI,OAAO,CAE3D"}