@masons/agent-network 0.6.23 → 0.6.25

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 (40) hide show
  1. package/README.md +40 -5
  2. package/dist/_vendor/runtime-adapter-client/exact-target-presentation.d.ts +7 -0
  3. package/dist/_vendor/runtime-adapter-client/exact-target-presentation.d.ts.map +1 -0
  4. package/dist/_vendor/runtime-adapter-client/exact-target-presentation.js +51 -0
  5. package/dist/_vendor/runtime-adapter-client/index.d.ts +1 -0
  6. package/dist/_vendor/runtime-adapter-client/index.d.ts.map +1 -1
  7. package/dist/_vendor/runtime-adapter-client/index.js +1 -0
  8. package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.d.ts +2 -2
  9. package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.d.ts.map +1 -1
  10. package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.js +105 -20
  11. package/dist/_vendor/runtime-adapter-client/types.d.ts +17 -5
  12. package/dist/_vendor/runtime-adapter-client/types.d.ts.map +1 -1
  13. package/dist/_vendor/runtime-adapter-client/types.js +13 -0
  14. package/dist/_vendor/runtime-adapter-client/work-target.d.ts.map +1 -1
  15. package/dist/_vendor/runtime-adapter-client/work-target.js +23 -5
  16. package/dist/channel-setup.d.ts +3 -1
  17. package/dist/channel-setup.d.ts.map +1 -1
  18. package/dist/channel-setup.js +146 -75
  19. package/dist/cli-setup.d.ts.map +1 -1
  20. package/dist/cli-setup.js +7 -7
  21. package/dist/config.js +1 -1
  22. package/dist/connector-client.d.ts.map +1 -1
  23. package/dist/connector-client.js +4 -2
  24. package/dist/handoff-acceptance.js +5 -5
  25. package/dist/handoff-deadline.js +1 -1
  26. package/dist/handoff.d.ts +14 -0
  27. package/dist/handoff.d.ts.map +1 -1
  28. package/dist/handoff.js +85 -11
  29. package/dist/plugin.d.ts.map +1 -1
  30. package/dist/plugin.js +10 -8
  31. package/dist/tools.d.ts +3 -0
  32. package/dist/tools.d.ts.map +1 -1
  33. package/dist/tools.js +92 -68
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/openclaw.plugin.json +4 -4
  37. package/package.json +1 -1
  38. package/skills/agent-network/SKILL.md +40 -37
  39. package/skills/agent-network/references/maintenance.md +3 -3
  40. package/skills/agent-network/references/troubleshooting.md +71 -13
package/dist/handoff.js CHANGED
@@ -15,6 +15,7 @@ export async function beginBridgeHandoff(options) {
15
15
  .toString("base64url");
16
16
  const apiBase = normalizeHttpBase(options.apiHost);
17
17
  let sessionId;
18
+ let ownerChoiceUrl;
18
19
  try {
19
20
  const initRes = await apiFetch(`${apiBase}/v1/cli-handoff/init`, {
20
21
  method: "POST",
@@ -24,25 +25,29 @@ export async function beginBridgeHandoff(options) {
24
25
  }),
25
26
  });
26
27
  if (!initRes.ok) {
27
- throw new SetupFlowError(`Setup service rejected init (HTTP ${initRes.status}). Retry in a moment; if it persists, check that ${apiBase} is reachable and not behind a proxy that strips the request body.`);
28
+ throw new SetupFlowError(`Link service rejected init (HTTP ${initRes.status}). Retry in a moment; if it persists, check that ${apiBase} is reachable and not behind a proxy that strips the request body.`);
28
29
  }
29
30
  const initBody = (await initRes.json());
30
31
  if (typeof initBody.session_id !== "string" ||
31
32
  initBody.session_id.length === 0) {
32
- throw new SetupFlowError("Setup service returned a malformed response (missing session_id). Retry; if it persists, this is a server-side bug worth reporting.");
33
+ throw new SetupFlowError("Link service returned a malformed response (missing session_id). Retry; if it persists, this is a server-side bug worth reporting.");
34
+ }
35
+ if (typeof initBody.owner_choice_url !== "string" ||
36
+ initBody.owner_choice_url.length === 0) {
37
+ throw new SetupFlowError("The Link service did not return a usable owner_choice_url in the init response (missing, empty, or not a string). Most likely this Services deployment predates the owner_choice_url contract (masons.ai#4320) — upgrade the server, or use a plugin release that predates the contract. This runtime does not compose that address itself.");
33
38
  }
34
39
  sessionId = initBody.session_id;
40
+ ownerChoiceUrl = initBody.owner_choice_url;
35
41
  }
36
42
  catch (err) {
37
43
  if (err instanceof SetupFlowError)
38
44
  throw err;
39
45
  const message = err instanceof Error ? err.message : "unknown error";
40
- throw new SetupFlowError(`Could not reach the setup service at ${apiBase} (${message}). Retry in a moment.`);
46
+ throw new SetupFlowError(`Could not reach the Link service at ${apiBase} (${message}). Retry in a moment.`);
41
47
  }
42
- const handoffUrl = `${options.idpBaseUrl}/console/handoff?session=${encodeURIComponent(sessionId)}`;
43
48
  return {
44
49
  sessionId,
45
- handoffUrl,
50
+ handoffUrl: verifyOwnerChoiceUrl(ownerChoiceUrl, options.idpBaseUrl, options.idpBaseUrlIsDefault ?? false),
46
51
  apiBase,
47
52
  privateKey,
48
53
  expiresAt: Date.now() + HANDOFF_TIMEOUT_MS,
@@ -50,20 +55,58 @@ export async function beginBridgeHandoff(options) {
50
55
  }
51
56
  export async function completeBridgeHandoff(session) {
52
57
  const envelope = await pollForCompletion(session.apiBase, session.sessionId, session.expiresAt);
58
+ return readBridgeEnvelope(session, envelope);
59
+ }
60
+ export async function collectBridgeHandoffOnce(session) {
61
+ assertHandoffDeadlineActive(session.expiresAt);
62
+ const res = await apiFetch(pollUrl(session.apiBase, session.sessionId), {
63
+ method: "GET",
64
+ });
65
+ if (res.status === 404)
66
+ return { outcome: "gone" };
67
+ if (!res.ok)
68
+ return { outcome: "pending" };
69
+ let body;
70
+ try {
71
+ body = (await res.json());
72
+ }
73
+ catch (err) {
74
+ return { outcome: "spoiled", error: unreadableDelivery(err) };
75
+ }
76
+ if (body.status !== "completed" || !body.payload)
77
+ return { outcome: "pending" };
78
+ try {
79
+ return {
80
+ outcome: "delivered",
81
+ handoff: readBridgeEnvelope(session, body.payload),
82
+ };
83
+ }
84
+ catch (err) {
85
+ return {
86
+ outcome: "spoiled",
87
+ error: err instanceof SetupFlowError ? err : unreadableDelivery(err),
88
+ };
89
+ }
90
+ }
91
+ function unreadableDelivery(err) {
92
+ const detail = err instanceof Error ? err.message : "unknown error";
93
+ return new SetupFlowError(`The handoff bridge released this Link's runtime key but the response could not be read (${detail}), so the key was lost with it.`);
94
+ }
95
+ function readBridgeEnvelope(session, envelope) {
53
96
  let payload;
54
97
  try {
55
98
  payload = decryptBridgeEnvelope(session.privateKey, envelope);
56
99
  }
57
100
  catch (err) {
58
101
  const message = err instanceof Error ? err.message : "unknown error";
59
- throw new SetupFlowError(`Handoff received but could not be decrypted (${message}). The link may have been tampered with — re-run setup. If it keeps failing, your network may be inserting a TLS-terminating proxy that mangled the ciphertext.`);
102
+ throw new SetupFlowError(`Handoff received but could not be decrypted (${message}). The link may have been tampered with — re-run Link. If it keeps failing, your network may be inserting a TLS-terminating proxy that mangled the ciphertext.`);
60
103
  }
61
104
  assertHandoffDeadlineActive(session.expiresAt);
62
105
  if (typeof payload.token !== "string" ||
63
106
  !RUNTIME_KEY_PATTERN.test(payload.token) ||
64
107
  typeof payload.agent?.handle !== "string" ||
65
108
  typeof payload.agent?.agentId !== "string") {
66
- throw new SetupFlowError("Handoff payload did not match the expected shape. Re-run setup; if it keeps happening, this is a server-side bug worth reporting.");
109
+ throw new SetupFlowError("Handoff payload did not match the expected shape. Re-run Link; if it keeps happening, this is a server-side bug worth reporting.");
67
110
  }
68
111
  return {
69
112
  token: payload.token,
@@ -76,12 +119,15 @@ export async function completeBridgeHandoff(session) {
76
119
  },
77
120
  };
78
121
  }
122
+ function pollUrl(apiBase, sessionId) {
123
+ return `${apiBase}/v1/cli-handoff/${encodeURIComponent(sessionId)}/poll`;
124
+ }
79
125
  async function pollForCompletion(apiBase, sessionId, expiresAt) {
80
126
  let lastNetworkError;
81
127
  while (Date.now() < expiresAt) {
82
128
  let res;
83
129
  try {
84
- res = await apiFetch(`${apiBase}/v1/cli-handoff/${encodeURIComponent(sessionId)}/poll`, { method: "GET" });
130
+ res = await apiFetch(pollUrl(apiBase, sessionId), { method: "GET" });
85
131
  }
86
132
  catch (err) {
87
133
  if (err instanceof PlatformNetworkError) {
@@ -94,7 +140,7 @@ async function pollForCompletion(apiBase, sessionId, expiresAt) {
94
140
  if (Date.now() >= expiresAt)
95
141
  break;
96
142
  if (res.status === 404) {
97
- throw new SetupFlowError("Handoff session expired before the runtime key was delivered. Re-run setup.");
143
+ throw new SetupFlowError("Handoff session expired before the runtime key was delivered. Re-run Link.");
98
144
  }
99
145
  if (!res.ok) {
100
146
  await sleepBeforeNextPoll(expiresAt);
@@ -109,8 +155,8 @@ async function pollForCompletion(apiBase, sessionId, expiresAt) {
109
155
  await sleepBeforeNextPoll(expiresAt);
110
156
  }
111
157
  throw new SetupFlowError(lastNetworkError
112
- ? `Handoff timed out before the runtime key was delivered; the last poll could not reach ${apiBase} (${lastNetworkError.message}). Re-run setup; if you're behind a proxy that blocks long-running fetches, retry on a different network.`
113
- : "Handoff timed out before the runtime key was delivered. Re-run setup; if you're behind a proxy that blocks long-running fetches, retry on a different network.");
158
+ ? `Handoff timed out before the runtime key was delivered; the last poll could not reach ${apiBase} (${lastNetworkError.message}). Re-run Link; if you're behind a proxy that blocks long-running fetches, retry on a different network.`
159
+ : "Handoff timed out before the runtime key was delivered. Re-run Link; if you're behind a proxy that blocks long-running fetches, retry on a different network.");
114
160
  }
115
161
  async function sleepBeforeNextPoll(expiresAt) {
116
162
  const remainingMs = expiresAt - Date.now();
@@ -144,6 +190,34 @@ function decryptBridgeEnvelope(privateKey, envelope) {
144
190
  ]);
145
191
  return JSON.parse(plaintext.toString("utf-8"));
146
192
  }
193
+ function verifyOwnerChoiceUrl(ownerChoiceUrl, idpBaseUrl, idpBaseUrlIsDefault) {
194
+ const configured = parseAbsoluteUrl(idpBaseUrl);
195
+ if (!configured) {
196
+ throw new SetupFlowError("This Runtime's identity provider (idpBaseUrl) is not an absolute http(s) URL, so the owner-choice address the Link service named cannot be verified. Set idpBaseUrl for this channel to an absolute http(s) URL and re-run Link.");
197
+ }
198
+ const named = parseAbsoluteUrl(ownerChoiceUrl);
199
+ if (!named) {
200
+ throw new SetupFlowError("The Link service named an owner-choice address that is not a valid absolute URL; refusing to direct the owner's browser to it. Retry; if it persists, this is a server-side bug worth reporting.");
201
+ }
202
+ if (named.protocol !== "http:" && named.protocol !== "https:") {
203
+ throw new SetupFlowError(`The Link service named an owner-choice address using the "${named.protocol}" scheme, which is not an address a browser can be sent to; refusing it. Retry; if it persists, this is a server-side bug worth reporting.`);
204
+ }
205
+ if (named.origin !== configured.origin) {
206
+ const anchor = idpBaseUrlIsDefault
207
+ ? "the built-in default identity provider (idpBaseUrl is not set)"
208
+ : "your configured identity provider (idpBaseUrl)";
209
+ throw new SetupFlowError(`The Link service named an owner-choice address on ${named.origin}, which is not ${anchor}: ${configured.origin}. Refusing to direct the owner's browser there. If your deployment's identity provider genuinely moved, update idpBaseUrl for this channel. If you did not expect this, do not change configuration — the server may be misdirecting your sign-in; report it.`);
210
+ }
211
+ return named.href;
212
+ }
213
+ function parseAbsoluteUrl(value) {
214
+ try {
215
+ return new URL(value);
216
+ }
217
+ catch {
218
+ return null;
219
+ }
220
+ }
147
221
  function normalizeHttpBase(apiHost) {
148
222
  const trimmed = apiHost.replace(/\/+$/, "");
149
223
  if (/^https?:\/\//.test(trimmed))
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAUrE,OAAO,EACL,kCAAkC,EAEnC,MAAM,oCAAoC,CAAC;AA8D5C,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,mBAAmB,GAAE,mBAAmB,GAAG,IAA+B,GACzE,MAAM,GAAG,SAAS,CA6EpB;AAKD,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,IAAI,CAAC;IACR,yBAAyB,CAAC,MAAM,EAAE;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,OAAO,kCAAkC,CAAC;KACrD,GAAG,IAAI,CAAC;IACT,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAqCD,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAaI,iBAAiB;CAoahC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAUrE,OAAO,EACL,kCAAkC,EAEnC,MAAM,oCAAoC,CAAC;AA8D5C,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,mBAAmB,GAAE,mBAAmB,GAAG,IAA+B,GACzE,MAAM,GAAG,SAAS,CA6EpB;AAKD,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,IAAI,CAAC;IACR,yBAAyB,CAAC,MAAM,EAAE;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,OAAO,kCAAkC,CAAC;KACrD,GAAG,IAAI,CAAC;IACT,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAqCD,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAaI,iBAAiB;CAsahC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -194,19 +194,21 @@ const plugin = {
194
194
  if (!state.hasCredentials) {
195
195
  dynamicContext =
196
196
  "[Context: Agent Network] You recently installed the agent network plugin " +
197
- "but it is not yet authorized. If your current channel sender is the verified " +
198
- "OpenClaw owner, call masons_setup to generate a MASONS handoff URL here. " +
199
- "If owner authority is unavailable, tell your user to run this terminal fallback: " +
200
- "`openclaw channels login --channel agent-network`. Browser sign-in handles " +
201
- "agent selection or creation. After setup completes, OpenClaw will reload and " +
197
+ "but it is not yet authorized. Link finishes in this conversation: if your " +
198
+ "current channel sender is the verified OpenClaw owner, call masons_link to " +
199
+ "generate a MASONS handoff URL here. If owner authority is unavailable here, " +
200
+ "ask the owner to start Link from a channel their OpenClaw is configured for; " +
201
+ "only as a last resort, `openclaw channels login --channel agent-network` in a " +
202
+ "terminal. Browser sign-in handles " +
203
+ "agent selection or creation. After the Link completes, OpenClaw will reload and " +
202
204
  "the agent network tools will become available.";
203
205
  if (state.pendingTarget) {
204
- dynamicContext += ` After setup, send a connection request to ${state.pendingTarget} — they invited your user to join.`;
206
+ dynamicContext += ` After the Link completes, send a connection request to ${state.pendingTarget} — they invited your user to join.`;
205
207
  }
206
208
  }
207
209
  else if (state.needsProfile) {
208
210
  dynamicContext =
209
- "[Context: Agent Network] You are set up on the agent network, but your agent's profile is empty. " +
211
+ "[Context: Agent Network] You are linked to the agent network, but your agent's profile is empty. " +
210
212
  "Generate a profile based on what you know about the user and their agent, then show it to the user for confirmation. " +
211
213
  "Once confirmed, call masons_update_profile with all four fields (name, scope, about, audience). " +
212
214
  "Without a profile, other agents can't see who you are — " +
@@ -216,7 +218,7 @@ const plugin = {
216
218
  }
217
219
  }
218
220
  else if (state.pendingTarget) {
219
- dynamicContext = `[Context: Agent Network] You are set up on the agent network. You have a pending connection — call the masons_send_connection_request tool with targetHandle "${state.pendingTarget}" to send a connection request.`;
221
+ dynamicContext = `[Context: Agent Network] You are linked to the agent network. You have a pending connection — call the masons_send_connection_request tool with targetHandle "${state.pendingTarget}" to send a connection request.`;
220
222
  }
221
223
  if (!dynamicContext &&
222
224
  state.hasCredentials &&
package/dist/tools.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type ChannelSetupResult } from "./channel-setup.js";
1
2
  interface ToolContent {
2
3
  content: Array<{
3
4
  type: "text";
@@ -25,6 +26,8 @@ interface ToolApi {
25
26
  }): void;
26
27
  }
27
28
  export declare function _resetToolsForTesting(): void;
29
+ export declare function ownerRefusalText(reason: "known-non-owner" | "ambiguous"): string;
30
+ export declare function formatChannelSetupResult(result: ChannelSetupResult): string;
28
31
  export declare function registerTools(api: ToolApi): void;
29
32
  export {};
30
33
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAmFA,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;AAE3E,UAAU,OAAO;IACf,YAAY,CACV,IAAI,EAAE,cAAc,GAAG,WAAW,EAWlC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC7D,IAAI,CAAC;CACT;AAoDD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAodD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAqlChD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,oBAAoB,CAAC;AAuD5B,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;AAE3E,UAAU,OAAO;IACf,YAAY,CACV,IAAI,EAAE,cAAc,GAAG,WAAW,EAWlC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC7D,IAAI,CAAC;CACT;AAkED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AA6XD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GAAG,WAAW,GACtC,MAAM,CAqBR;AAGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAoC3E;AAyBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAmoChD"}
package/dist/tools.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Type } from "@sinclair/typebox";
2
+ import pluginManifest from "../openclaw.plugin.json" with { type: "json" };
2
3
  import { getOwnerPassportAddress } from "./channel.js";
3
- import { _resetChannelSetupForTesting, consumeChannelSetupStatus, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
4
+ import { _resetChannelSetupForTesting, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
4
5
  import { clearTargetHandle, extractNetworkConfig, getConnectorClient, getDmScope, getPendingTarget, hasApiKey, isProfileNeeded, markProfileComplete, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
5
6
  import { getOwnerHandle } from "./environment-context.js";
6
7
  import { classifyIdentityError, formatCanonicalNode, formatKindLabel, } from "./identity-format.js";
@@ -11,6 +12,11 @@ import { getCurrentTurnChannelId, getCurrentTurnIsOwnerNonConsuming, getCurrentT
11
12
  import { getLatestPublishedVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
12
13
  const PROFILE_FIELDS = new Set(["name", "scope", "about", "audience"]);
13
14
  const GATEWAY_RESTART_CMD = "openclaw gateway restart";
15
+ const PACKAGED_TOOL_ROSTER = pluginManifest.contracts.tools;
16
+ const ROSTER_HEADER = "This version ships these tools:";
17
+ function withRoster(text) {
18
+ return `${text}\n\n${ROSTER_HEADER}\n${PACKAGED_TOOL_ROSTER.join(", ")}`;
19
+ }
14
20
  function upgradeCmd(version) {
15
21
  return `openclaw plugins install @masons/agent-network@${version} --force`;
16
22
  }
@@ -113,7 +119,7 @@ function formatIdentityError(err) {
113
119
  return `Identity: unavailable — the identity endpoint is not available at this host (HTTP 404). ${IDENTITY_NO_FALLBACK}`;
114
120
  case "credential-refused":
115
121
  return err.status === 401
116
- ? "Identity: the runtime key is not valid — the network rejected it. The owner can re-authenticate via masons_setup."
122
+ ? "Identity: the runtime key is not valid — the network rejected it. The owner can re-authenticate via masons_link."
117
123
  : "Identity: refused (HTTP 403) — this runtime key is not permitted to read this Runtime's identity. That is a credential-class refusal, not a transient outage: retrying will not change it.";
118
124
  default:
119
125
  return `Identity: unavailable right now (HTTP ${err.status}: ${err.code}). ${IDENTITY_NO_FALLBACK}`;
@@ -151,7 +157,7 @@ function formatReadinessSection() {
151
157
  }
152
158
  async function resolveIdentitySection() {
153
159
  if (!hasApiKey()) {
154
- return "Identity: no runtime key configured — cannot ask the network who this Runtime is. Run masons_setup first.";
160
+ return "Identity: no runtime key configured — cannot ask the network who this Runtime is. Run masons_link first.";
155
161
  }
156
162
  const cfg = requirePlatformConfig();
157
163
  const apiKey = requireApiKey();
@@ -182,10 +188,12 @@ function resolveSetupAuthority() {
182
188
  function getSetupOptionsFromToolContext(ctx) {
183
189
  const defaults = getDefaultChannelSetupOptions();
184
190
  const cfg = extractSetupConfig(ctx);
191
+ const configuredIdpBaseUrl = stringValue(cfg.idpBaseUrl);
185
192
  return {
186
193
  apiHost: stringValue(cfg.apiHost) ?? defaults.apiHost,
187
194
  connectorUrl: stringValue(cfg.connectorUrl),
188
- idpBaseUrl: stringValue(cfg.idpBaseUrl) ?? defaults.idpBaseUrl,
195
+ idpBaseUrl: configuredIdpBaseUrl ?? defaults.idpBaseUrl,
196
+ idpBaseUrlIsDefault: configuredIdpBaseUrl === undefined,
189
197
  };
190
198
  }
191
199
  function extractSetupConfig(ctx) {
@@ -214,37 +222,20 @@ function toRecord(value) {
214
222
  function stringValue(value) {
215
223
  return typeof value === "string" && value.length > 0 ? value : undefined;
216
224
  }
217
- function terminalSetupInstructions() {
218
- return [
219
- "Run this in the terminal where OpenClaw is installed:",
220
- "",
221
- " openclaw channels login --channel agent-network",
222
- "",
223
- "It will:",
224
- " 1. Display a MASONS handoff URL.",
225
- " 2. Open it in your browser and sign in.",
226
- " 3. Pick an existing agent or create a new one.",
227
- " 4. Return to the terminal after the encrypted handoff completes.",
228
- " 5. Persist credentials to openclaw.json. OpenClaw reloads the channel.",
229
- "",
230
- "Note: if you re-run this with another agent on this machine, this OpenClaw install receives",
231
- "its own runtime key. Re-running login here updates this OpenClaw install's credentials.",
232
- ].join("\n");
233
- }
234
- function ownerRefusalText(reason) {
225
+ export function ownerRefusalText(reason) {
235
226
  if (reason === "known-non-owner") {
236
227
  return [
237
- "Setup is owner-only. You are reaching this agent through the MASONS",
238
- "network as a visitor or as a peer agent. Setup re-binds the OpenClaw",
228
+ "Link is owner-only. You are reaching this agent through the MASONS",
229
+ "network as a visitor or as a peer agent. Link binds the OpenClaw",
239
230
  "runtime to a specific agent identity, so it must be initiated by the",
240
231
  "agent's runtime owner from a channel their OpenClaw is configured for.",
241
232
  ].join("\n");
242
233
  }
243
234
  return [
244
- "Setup is owner-only on the MASONS network channel. I could not verify",
235
+ "Link is owner-only on the MASONS network channel. I could not verify",
245
236
  "that you are this agent's owner from the message received.",
246
237
  "",
247
- "If you are the agent's runtime owner, please initiate setup from a",
238
+ "If you are the agent's runtime owner, please initiate Link from a",
248
239
  "channel your OpenClaw runtime is configured for (e.g., Lark, Telegram,",
249
240
  "desktop). The terminal command works as a last resort if no other channel",
250
241
  "is reachable:",
@@ -252,53 +243,69 @@ function ownerRefusalText(reason) {
252
243
  " openclaw channels login --channel agent-network",
253
244
  ].join("\n");
254
245
  }
255
- function formatChannelSetupResult(result) {
246
+ export function formatChannelSetupResult(result) {
256
247
  if (result.status === "pending" && result.handoffUrl) {
257
248
  return [
258
- "MASONS setup link:",
249
+ "Link URL:",
259
250
  "",
260
251
  result.handoffUrl,
261
252
  "",
262
253
  "Open this link in your browser, sign in, choose an agent, then come back here.",
254
+ "This Link is still open, so asking me again returns this same URL, not a new one — open the one above.",
263
255
  "After browser sign-in and agent selection, OpenClaw will receive the encrypted handoff, persist the runtime key locally, and reload the channel.",
264
- "This link is sensitive setup material; only the OpenClaw owner should open it.",
256
+ "This URL is sensitive Link material; only the OpenClaw owner should open it.",
265
257
  ].join("\n");
266
258
  }
267
259
  if (result.status === "completed") {
268
260
  return [
269
- result.message ?? "Agent Network setup completed.",
261
+ result.message ?? "Agent Network Link completed.",
270
262
  "",
271
263
  "HTTP tools may work before the Gateway WebSocket is connected.",
272
- 'If the gateway tool is available, call it with action "restart" and reason "Activate Agent Network after setup". If not, ask the user to run `openclaw gateway restart`.',
264
+ 'If the gateway tool is available, call it with action "restart" and reason "Activate Agent Network after Link". If not, ask the user to run `openclaw gateway restart`.',
265
+ "This result stands until the owner asks to start over: to re-link or change Node, call masons_link with start_over: true.",
273
266
  ].join("\n");
274
267
  }
275
268
  if (result.status === "expired") {
276
269
  return [
277
- result.message ?? "The previous setup link expired.",
278
- "Ask again to generate a fresh owner-only setup link, or use the terminal fallback:",
270
+ result.message ?? "The previous Link URL expired.",
279
271
  "",
280
- terminalSetupInstructions(),
272
+ LINK_RETRY_GUIDANCE,
281
273
  ].join("\n");
282
274
  }
283
275
  return [
284
- result.message ?? "Agent Network setup failed.",
285
- "Ask again to retry, or use the terminal fallback:",
276
+ result.message ?? "Agent Network Link failed.",
286
277
  "",
287
- terminalSetupInstructions(),
278
+ LINK_RETRY_GUIDANCE,
288
279
  ].join("\n");
289
280
  }
281
+ const LINK_RETRY_GUIDANCE = [
282
+ "Call masons_link again in this conversation and I will issue a fresh Link URL — the whole flow finishes here.",
283
+ "Last resort, only if that keeps failing: the owner can run `openclaw channels login --channel agent-network` in a terminal.",
284
+ ].join("\n");
290
285
  export function registerTools(api) {
291
286
  api.registerTool((ctx = {}) => ({
292
- name: "masons_setup",
287
+ name: "masons_link",
293
288
  description: [
294
- "Start or inspect Agent Network setup.",
295
- "Use when the user asks to set up Agent Network, when other Agent",
296
- "Network tools fail with a 'no credentials' error, or when the runtime",
297
- "owner wants to switch which MASONS agent OpenClaw is driving.",
289
+ "Link this OpenClaw Runtime to a MASONS Agent Node, or inspect a Link",
290
+ "already in progress.",
291
+ "Use when the user asks to link (or set up) Agent Network, when other",
292
+ "Agent Network tools fail with a 'no credentials' error, or when the",
293
+ "runtime owner wants a different MASONS agent driven by OpenClaw.",
294
+ "On a Runtime that is already linked, present this to the user as",
295
+ "Relink / Change Node — the same capability, not a second one. After a",
296
+ "Link has completed in this process, pass start_over: true to re-link",
297
+ "or link a different Node; a plain call re-reports the completed one.",
298
+ "To check on a Link in progress (after the owner opens the URL), call",
299
+ "with NO arguments — start_over would discard the pending Link and",
300
+ "issue a fresh URL instead of confirming the one the owner completed.",
301
+ "",
302
+ "Not to be confused with masons_link_identity: this tool binds a",
303
+ "Runtime to a Node. masons_link_identity aliases your owner's identity",
304
+ "on one channel to their canonical owner identity.",
298
305
  "",
299
306
  "Pass `invitedBy` if the user came from another agent's profile page",
300
307
  "(handle extracted from the URL); the plugin will record it and propose",
301
- "a connection request after setup completes.",
308
+ "a connection request once the Link completes.",
302
309
  "",
303
310
  "This tool returns a browser handoff URL on the channel where you are",
304
311
  "running. The user opens it, signs in, picks an agent, and the encrypted",
@@ -307,16 +314,23 @@ export function registerTools(api) {
307
314
  "",
308
315
  "If the user is reaching this agent through the MASONS network as a",
309
316
  "visitor (not the agent's owner), this tool refuses with an explanation",
310
- "— setup is owner-only because it re-binds the OpenClaw runtime to a",
317
+ "— Link is owner-only because it binds the OpenClaw runtime to a",
311
318
  "specific agent identity.",
312
319
  ].join("\n"),
313
320
  parameters: Type.Object({
314
321
  invitedBy: Type.Optional(Type.String({
315
322
  description: "Handle of the agent whose profile page the user came from " +
316
- "(e.g., 'alice' from 'masons.ai/alice'). When setup completes, " +
323
+ "(e.g., 'alice' from 'masons.ai/alice'). When the Link completes, " +
317
324
  "the plugin records this as a pending connection target so the " +
318
325
  "agent can propose a connection request automatically.",
319
326
  })),
327
+ start_over: Type.Optional(Type.Boolean({
328
+ description: "Discard whatever this Runtime is holding — a Link still in " +
329
+ "progress, or a Link that already completed — and issue a fresh " +
330
+ "Link URL. After a completed Link, pass start_over: true to " +
331
+ "re-link or link a different Node; without it a repeat call " +
332
+ "re-reports the completed result instead of starting again.",
333
+ })),
320
334
  }),
321
335
  execute: async (_id, params) => {
322
336
  const authority = resolveSetupAuthority();
@@ -326,27 +340,23 @@ export function registerTools(api) {
326
340
  const invitedBy = typeof params.invitedBy === "string" && params.invitedBy.length > 0
327
341
  ? params.invitedBy
328
342
  : undefined;
329
- const existing = consumeChannelSetupStatus();
330
343
  let result;
331
344
  try {
332
- result = existing
333
- ? existing
334
- : await startOrGetChannelSetup({
335
- ...getSetupOptionsFromToolContext(ctx),
336
- invitedBy,
337
- });
345
+ result = await startOrGetChannelSetup({
346
+ ...getSetupOptionsFromToolContext(ctx),
347
+ invitedBy,
348
+ startOver: params.start_over === true,
349
+ });
338
350
  }
339
351
  catch (err) {
340
352
  result = {
341
353
  status: "failed",
342
- message: err instanceof Error
343
- ? err.message
344
- : "Agent Network setup failed.",
354
+ message: err instanceof Error ? err.message : "Agent Network Link failed.",
345
355
  };
346
356
  }
347
357
  return textResult(formatChannelSetupResult(result));
348
358
  },
349
- }), { name: "masons_setup" });
359
+ }), { name: "masons_link" });
350
360
  api.registerTool({
351
361
  name: "masons_update_profile",
352
362
  description: [
@@ -402,7 +412,7 @@ export function registerTools(api) {
402
412
  catch (err) {
403
413
  if (err instanceof PlatformApiError) {
404
414
  if (err.status === 401) {
405
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
415
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
406
416
  }
407
417
  if (err.status === 422) {
408
418
  return textResult(`Validation error: ${err.message}`);
@@ -483,7 +493,7 @@ export function registerTools(api) {
483
493
  : "This Services deployment does not serve the connection-request endpoint. Nothing was sent.");
484
494
  }
485
495
  if (err.status === 401) {
486
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
496
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
487
497
  }
488
498
  return textResult(`Connection request failed: ${err.message}`);
489
499
  }
@@ -567,7 +577,7 @@ export function registerTools(api) {
567
577
  catch (err) {
568
578
  if (err instanceof PlatformApiError) {
569
579
  if (err.status === 401) {
570
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
580
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
571
581
  }
572
582
  if (err.status === 404) {
573
583
  return textResult("This Services deployment does not serve the packaged connection-request endpoint. Nothing was read.");
@@ -755,7 +765,7 @@ export function registerTools(api) {
755
765
  catch (err) {
756
766
  if (err instanceof PlatformApiError) {
757
767
  if (err.status === 401) {
758
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
768
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
759
769
  }
760
770
  return textResult(`Failed to list connections: ${err.message}`);
761
771
  }
@@ -881,36 +891,50 @@ export function registerTools(api) {
881
891
  execute: async () => {
882
892
  const pendingNotes = ownerNotesQueue.size();
883
893
  if (pendingNotes > 0) {
884
- return textResult(`You have ${pendingNotes} pending note(s) for your owner that haven't been delivered yet. ` +
885
- `Report them to your owner first, then call masons_upgrade again.`);
894
+ return textResult(withRoster(`You have ${pendingNotes} pending note(s) for your owner that haven't been delivered yet. ` +
895
+ `Report them to your owner first, then call masons_upgrade again.`));
886
896
  }
887
897
  const currentVersion = getPluginVersion();
888
898
  const latestVersion = await getLatestPublishedVersion();
889
899
  if (!latestVersion) {
890
- return textResult("Could not check for updates (network issue). Try again later.");
900
+ return textResult(withRoster("Could not check for updates (network issue). Try again later."));
891
901
  }
892
902
  if (latestVersion === currentVersion) {
893
- return textResult(`You're on the latest version (v${currentVersion}). No update needed.`);
903
+ return textResult(withRoster(`You're on the latest version (v${currentVersion}). No update needed.`));
894
904
  }
895
905
  if (!SEMVER_RE.test(latestVersion)) {
896
- return textResult(`Registry returned an unexpected version format: "${latestVersion}". Try again later.`);
906
+ return textResult(withRoster(`Registry returned an unexpected version format: "${latestVersion}". Try again later.`));
897
907
  }
898
908
  const cmd = upgradeCmd(latestVersion);
899
- return textResult([
909
+ return textResult(withRoster([
900
910
  `Update available: v${currentVersion} → v${latestVersion}`,
901
911
  "",
902
912
  "Run this command using the exec tool:",
903
913
  cmd,
904
914
  "",
915
+ "After the install finishes and BEFORE restarting: read " +
916
+ "`contracts.tools` from the newly installed package's " +
917
+ "`openclaw.plugin.json` (its installed package directory under " +
918
+ "`~/.openclaw/`) and compare it with the roster below. Tell your " +
919
+ "owner about every tool the new version adds. If their " +
920
+ "`openclaw.json` allows plugin tools by individual name under " +
921
+ "`tools.alsoAllow`, the new names must be added there — or the " +
922
+ "entry replaced with `group:plugins` — or the new tools will be " +
923
+ "missing after the restart with no error reported.",
924
+ "",
905
925
  "Then restart the gateway using the gateway tool.",
906
- "If you cannot run commands, tell the user to run in Terminal:",
926
+ "If you cannot run commands, tell the user to run in Terminal — and " +
927
+ "compare the new package's `openclaw.plugin.json` with the roster " +
928
+ "below before that restart:",
907
929
  `${cmd} && ${GATEWAY_RESTART_CMD}`,
908
- ].join("\n"));
930
+ ].join("\n")));
909
931
  },
910
932
  });
911
933
  api.registerTool({
912
934
  name: "masons_link_identity",
913
935
  description: "Link your owner's identity on the current channel to their Passport identity. " +
936
+ "Not to be confused with masons_link: that tool binds this Runtime to " +
937
+ "an Agent Node. This one aliases owner identities across channels. " +
914
938
  "Provide only the current channel's entry in 'channel:peerId' format " +
915
939
  "(e.g., 'telegram:5099353300', 'feishu:ou_abc123'). " +
916
940
  "The canonical name and Passport entry are added automatically. " +
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.6.23";
1
+ export declare const PLUGIN_VERSION = "0.6.25";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.6.23";
1
+ export const PLUGIN_VERSION = "0.6.25";
@@ -5,7 +5,7 @@
5
5
  "contracts": {
6
6
  "trustedToolPolicies": ["agent-network-services-retained-turn-v1"],
7
7
  "tools": [
8
- "masons_setup",
8
+ "masons_link",
9
9
  "masons_update_profile",
10
10
  "masons_send_connection_request",
11
11
  "masons_list_requests",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "idpBaseUrl": {
40
40
  "type": "string",
41
- "description": "Better Auth IdP base URL used by encrypted MASONS browser handoff setup. Defaults to the preview environment.",
41
+ "description": "Better Auth IdP base URL used by the encrypted MASONS browser handoff that completes Link. Defaults to the preview environment.",
42
42
  "default": "https://preview.masons.ai"
43
43
  },
44
44
  "updateCheck": {
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "idpBaseUrl": {
70
70
  "type": "string",
71
- "description": "Better Auth IdP base URL used by encrypted MASONS browser handoff setup. Defaults to the preview environment.",
71
+ "description": "Better Auth IdP base URL used by the encrypted MASONS browser handoff that completes Link. Defaults to the preview environment.",
72
72
  "default": "https://preview.masons.ai"
73
73
  },
74
74
  "updateCheck": {
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "pendingTarget": {
109
109
  "type": "string",
110
- "description": "Pending MASONS handle captured during invitation setup."
110
+ "description": "Pending MASONS handle captured during an invitation Link."
111
111
  },
112
112
  "needsProfile": {
113
113
  "type": "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.6.23",
3
+ "version": "0.6.25",
4
4
  "description": "MASONS Agent Network — OpenClaw channel plugin for connecting agent runtimes to the Agent Network over MSTP.",
5
5
  "license": "MIT",
6
6
  "author": "MASONS.ai <hello@masons.ai> (https://masons.ai)",