@masons/agent-network 0.6.27 → 0.7.0

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.
@@ -136,18 +136,65 @@ function resolveVerb(verb) {
136
136
  export const acceptRequest = resolveVerb("accept");
137
137
  export const ignoreRequest = resolveVerb("ignore");
138
138
  export const withdrawRequest = resolveVerb("withdraw");
139
- export async function updateProfile(cfg, runtimeKey, params) {
140
- const res = await apiFetch(`${baseUrl(cfg)}/me/profile`, {
139
+ const ICM_CARD_PATH = "/runtime/icm-profile";
140
+ export async function getIcmCard(cfg, runtimeKey) {
141
+ const res = await apiFetch(`${baseUrl(cfg)}${ICM_CARD_PATH}`, {
142
+ headers: { Authorization: `Bearer ${runtimeKey}` },
143
+ });
144
+ if (!res.ok)
145
+ return handleError(res);
146
+ return parseVersionedCard(res, await res.json().catch(() => null));
147
+ }
148
+ export async function updateIcmCard(cfg, runtimeKey, draft) {
149
+ const body = {
150
+ profileRevision: draft.profileRevision,
151
+ displayName: draft.displayName,
152
+ update: draft.update,
153
+ };
154
+ if (draft.removeAvatar)
155
+ body.avatarUrl = null;
156
+ const res = await apiFetch(`${baseUrl(cfg)}${ICM_CARD_PATH}`, {
141
157
  method: "PATCH",
142
158
  headers: {
143
159
  "Content-Type": "application/json",
144
160
  Authorization: `Bearer ${runtimeKey}`,
145
161
  },
146
- body: JSON.stringify(params),
162
+ body: JSON.stringify(body),
147
163
  });
164
+ if (res.status === 409) {
165
+ const conflict = (await res.json().catch(() => null));
166
+ if (conflict?.error === "profile_revision_conflict") {
167
+ return {
168
+ outcome: "revision_conflict",
169
+ current: parseVersionedCard(res, conflict),
170
+ };
171
+ }
172
+ throw new PlatformApiError(res.status, typeof conflict?.error === "string" ? conflict.error : "unknown", res.statusText);
173
+ }
148
174
  if (!res.ok)
149
175
  return handleError(res);
150
- return (await res.json());
176
+ return {
177
+ outcome: "updated",
178
+ current: parseVersionedCard(res, await res.json().catch(() => null)),
179
+ };
180
+ }
181
+ function parseVersionedCard(res, body) {
182
+ const parsed = body;
183
+ if (!parsed ||
184
+ typeof parsed.profileRevision !== "string" ||
185
+ parsed.profileRevision.length === 0 ||
186
+ !isIcmCard(parsed.profile)) {
187
+ throw new PlatformApiError(res.status, "malformed_response", "icm-profile response carried no card and revision");
188
+ }
189
+ return { card: parsed.profile, profileRevision: parsed.profileRevision };
190
+ }
191
+ function isIcmCard(value) {
192
+ if (typeof value !== "object" || value === null)
193
+ return false;
194
+ const card = value;
195
+ return (typeof card.canonicalAddress === "string" &&
196
+ card.canonicalAddress.length > 0 &&
197
+ typeof card.displayName === "string");
151
198
  }
152
199
  export async function listConnections(cfg, runtimeKey) {
153
200
  const res = await apiFetch(`${baseUrl(cfg)}/me/connections`, {
@@ -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;CAsahC,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;CAsZhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -206,24 +206,10 @@ const plugin = {
206
206
  dynamicContext += ` After the Link completes, send a connection request to ${state.pendingTarget} — they invited your user to join.`;
207
207
  }
208
208
  }
209
- else if (state.needsProfile) {
210
- dynamicContext =
211
- "[Context: Agent Network] You are linked to the agent network, but your agent's profile is empty. " +
212
- "Generate a profile based on what you know about the user and their agent, then show it to the user for confirmation. " +
213
- "Once confirmed, call masons_update_profile with all four fields (name, scope, about, audience). " +
214
- "Without a profile, other agents can't see who you are — " +
215
- "connection requests from agents without a profile are rarely accepted.";
216
- if (state.pendingTarget) {
217
- dynamicContext += ` After completing the profile, send a connection request to ${state.pendingTarget} — they'll be able to see who you are and why connecting makes sense.`;
218
- }
219
- }
220
209
  else if (state.pendingTarget) {
221
210
  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.`;
222
211
  }
223
- if (!dynamicContext &&
224
- state.hasCredentials &&
225
- !state.needsProfile &&
226
- !state.pendingTarget) {
212
+ if (!dynamicContext && state.hasCredentials && !state.pendingTarget) {
227
213
  if (messageProvider === "agent-network") {
228
214
  if (turnIsOwner) {
229
215
  dynamicContext =
@@ -290,7 +276,7 @@ const plugin = {
290
276
  }
291
277
  }
292
278
  }
293
- if (state.hasCredentials && !state.needsProfile && !state.pendingTarget) {
279
+ if (state.hasCredentials && !state.pendingTarget) {
294
280
  const dmScope = getDmScope();
295
281
  if (dmScope !== "main") {
296
282
  const linkingKnowledge = "[Cross-Channel Identity] When your owner visits your Passport page, " +
package/dist/tools.d.ts CHANGED
@@ -27,6 +27,7 @@ interface ToolApi {
27
27
  }
28
28
  export declare function _resetToolsForTesting(): void;
29
29
  export declare function ownerRefusalText(reason: "known-non-owner" | "ambiguous"): string;
30
+ export declare function cardWriteRefusalText(reason: "known-non-owner" | "ambiguous"): string;
30
31
  export declare function formatChannelSetupResult(result: ChannelSetupResult): string;
31
32
  export declare function registerTools(api: ToolApi): void;
32
33
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,oBAAoB,CAAC;AA2D5B,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;AAqED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAkYD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GAAG,WAAW,GACtC,MAAM,CAqBR;AAGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAyC3E;AA+CD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAqoChD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,oBAAoB,CAAC;AA2D5B,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;AAwED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AA0YD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GAAG,WAAW,GACtC,MAAM,CAqBR;AAsCD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,iBAAiB,GAAG,WAAW,GACtC,MAAM,CAsBR;AAkID,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAyC3E;AA+CD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAioChD"}
package/dist/tools.js CHANGED
@@ -2,15 +2,14 @@ import { Type } from "@sinclair/typebox";
2
2
  import pluginManifest from "../openclaw.plugin.json" with { type: "json" };
3
3
  import { getOwnerPassportAddress } from "./channel.js";
4
4
  import { _resetChannelSetupForTesting, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
5
- import { clearTargetHandle, extractNetworkConfig, getConnectorClient, getDmScope, getPendingTarget, hasApiKey, isProfileNeeded, markProfileComplete, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
5
+ import { clearTargetHandle, extractNetworkConfig, getConnectorClient, getDmScope, getPendingTarget, hasApiKey, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
6
6
  import { getOwnerHandle } from "./environment-context.js";
7
7
  import { classifyIdentityError, formatCanonicalNode, formatKindLabel, } from "./identity-format.js";
8
8
  import { ownerNotesQueue } from "./owner-notes.js";
9
- import { acceptRequest, getIdentity, ignoreRequest, isPackagedTombstone, listConnections, listRequests, PlatformApiError, PlatformNetworkError, requestConnection, updateProfile, withdrawRequest, } from "./platform-client.js";
9
+ import { acceptRequest, getIcmCard, getIdentity, ignoreRequest, isPackagedTombstone, listConnections, listRequests, PlatformApiError, PlatformNetworkError, requestConnection, updateIcmCard, withdrawRequest, } from "./platform-client.js";
10
10
  import { isServicesRetainedReplyContext } from "./services-retained-reply-context.js";
11
11
  import { getCurrentTurnChannelId, getCurrentTurnIsOwnerNonConsuming, getCurrentTurnOwnerSignal, } from "./turn-context.js";
12
12
  import { getLatestPublishedVersion, getPluginVersion, getUpdateInfo, NPM_PACKAGE_NAME, } from "./update-check.js";
13
- const PROFILE_FIELDS = new Set(["name", "scope", "about", "audience"]);
14
13
  const GATEWAY_RESTART_CMD = "openclaw gateway restart";
15
14
  const PACKAGED_TOOL_ROSTER = pluginManifest.contracts.tools;
16
15
  const ROSTER_HEADER = "This version ships these tools:";
@@ -245,6 +244,121 @@ export function ownerRefusalText(reason) {
245
244
  " openclaw channels login --channel agent-network",
246
245
  ].join("\n");
247
246
  }
247
+ function formatIcmCard(versioned) {
248
+ const { card, profileRevision } = versioned;
249
+ const handlePart = card.handle ? `@${card.handle} — ` : "";
250
+ return [
251
+ `Card: ${formatKindLabel(card.kind)}${handlePart}${card.canonicalAddress}`,
252
+ `Display name: ${card.displayName}`,
253
+ `Status line (update): ${card.update ?? "(none)"}`,
254
+ `Avatar: ${card.avatarUrl ? "present" : "none"}`,
255
+ `Revision (needed to update): ${profileRevision}`,
256
+ ].join("\n");
257
+ }
258
+ export function cardWriteRefusalText(reason) {
259
+ if (reason === "known-non-owner") {
260
+ return [
261
+ "Editing the card is owner-only. You are reaching this agent through",
262
+ "the MASONS network as a visitor or as a peer agent. The card is the",
263
+ "owner's own portrayal of this Node, so only they may change it.",
264
+ "",
265
+ "You can still read it with masons_view_card — the card scope is what",
266
+ "counterparties see anyway.",
267
+ ].join("\n");
268
+ }
269
+ return [
270
+ "Editing the card is owner-only, and this turn carried no owner signal,",
271
+ "so I cannot verify that you are this agent's owner. An unverified turn",
272
+ "is refused rather than assumed to be the owner.",
273
+ "",
274
+ "Ask from a channel the owner operates directly (e.g. Telegram or the",
275
+ "terminal) — card editing is available there.",
276
+ "",
277
+ "Reading the card is unaffected: call masons_view_card.",
278
+ ].join("\n");
279
+ }
280
+ function formatCardConflict(current) {
281
+ return [
282
+ "Not updated — the card changed since you read it, so the confirmed draft",
283
+ "was based on a card that no longer exists. Nothing was written.",
284
+ "",
285
+ "This is the card as it stands now:",
286
+ "",
287
+ formatIcmCard(current),
288
+ "",
289
+ "Do NOT retry the rejected draft, and do not merge it with the above.",
290
+ "Rebuild the COMPLETE card on this current state, show it to the owner,",
291
+ "get a fresh confirmation, then call masons_update_card again with the",
292
+ "revision printed above.",
293
+ ].join("\n");
294
+ }
295
+ function cardWriteOutcomeUnknownText(detail) {
296
+ return [
297
+ "The network request's outcome is unknown — it may or may not have",
298
+ "landed. Do not resubmit blindly: read the card with masons_view_card",
299
+ "first; if the change is already there, you are done; if not, rebuild",
300
+ "and reconfirm with the owner.",
301
+ "",
302
+ detail,
303
+ ].join("\n");
304
+ }
305
+ function icmCardErrorText(err, surface) {
306
+ if (err instanceof PlatformNetworkError) {
307
+ if (surface === "read")
308
+ return networkErrorText(err);
309
+ return cardWriteOutcomeUnknownText(`Transport detail: ${err.detail}.`);
310
+ }
311
+ if (!(err instanceof PlatformApiError))
312
+ return null;
313
+ if (err.status === 401) {
314
+ return "Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link.";
315
+ }
316
+ if (err.code === "profile_access_not_authorized") {
317
+ return [
318
+ "This Runtime's credential is not authorized to reach this card. The",
319
+ "Runtime Authorization Grant behind the key is not active for it — it",
320
+ "may have been revoked, disabled, or expired. Nothing was read or",
321
+ "changed.",
322
+ ].join("\n");
323
+ }
324
+ if (err.code === "profile_not_enrolled") {
325
+ return [
326
+ "This Node has no ICM card. It is reachable and conversable on the",
327
+ "network, but it is not enrolled in In-Context Messaging, so there is",
328
+ "no card to read or edit.",
329
+ "",
330
+ "Enrollment is what creates the card, and it happens on the owner's",
331
+ "authenticated management surface for this deployment — a Runtime",
332
+ "cannot create one.",
333
+ ].join("\n");
334
+ }
335
+ if (err.status === 404) {
336
+ if (err.code === "node_not_found") {
337
+ return [
338
+ "This Runtime's principal holds no active Node, so there is no card.",
339
+ "Ask the owner to link a Node here: call masons_link.",
340
+ ].join("\n");
341
+ }
342
+ return [
343
+ "This deployment does not serve the canonical card endpoint, so the",
344
+ "card is unavailable here — this says nothing about whether the Node",
345
+ "has one. A self-hosted or older MASONS API may predate it.",
346
+ ].join("\n");
347
+ }
348
+ if (err.status === 422) {
349
+ return [
350
+ "The network rejected the card as invalid; nothing was changed. The",
351
+ "display name must be 1–40 characters after trimming, and the status",
352
+ "line at most 80 characters on a single line. Fix the draft, show the",
353
+ "owner the corrected card, and call again with the same revision.",
354
+ ].join("\n");
355
+ }
356
+ const unmapped = `HTTP ${err.status}${err.code === "unknown" ? "" : `, ${err.code}`}`;
357
+ if (surface === "write") {
358
+ return cardWriteOutcomeUnknownText(`Provider detail: ${unmapped}.`);
359
+ }
360
+ return `The network could not answer for the card (${unmapped}). Nothing was changed.`;
361
+ }
248
362
  export function formatChannelSetupResult(result) {
249
363
  if (result.status === "pending" && result.handoffUrl) {
250
364
  return [
@@ -375,103 +489,114 @@ export function registerTools(api) {
375
489
  },
376
490
  }), { name: "masons_link" });
377
491
  api.registerTool({
378
- name: "masons_update_profile",
492
+ name: "masons_view_card",
493
+ description: [
494
+ "Read this Runtime's canonical card on the MASONS network — the portrayal other Nodes see: display name, status line, and avatar.",
495
+ "Use when the owner asks what their card says, before proposing any change to it, and whenever masons_update_card needs a current revision.",
496
+ "The result reports a revision token. masons_update_card requires the revision from a read like this one — read first, then update.",
497
+ ].join("\n"),
498
+ parameters: Type.Object({}),
499
+ execute: withUpdateNotice(async () => {
500
+ const cfg = requirePlatformConfig();
501
+ const apiKey = requireApiKey();
502
+ try {
503
+ return textResult(formatIcmCard(await getIcmCard(cfg, apiKey)));
504
+ }
505
+ catch (err) {
506
+ const text = icmCardErrorText(err, "read");
507
+ if (text === null)
508
+ throw err;
509
+ return textResult(text);
510
+ }
511
+ }),
512
+ });
513
+ api.registerTool({
514
+ name: "masons_update_card",
379
515
  description: [
380
- "Update this agent's profile on the network. Use this after creating a new agent to establish its identity.",
381
- "Generate all four fields based on what you know about the user and their agent, using the guidance below:",
516
+ "Update this Runtime's canonical card on the MASONS network — display name, status line, and removal of the avatar. The write is owner-only on the MASONS network channel, where a visitor or an unverified turn is refused; on the channels your host operates, the host's own owner rules apply.",
517
+ "This is the only portrayal this Runtime writes: the card IS the profile other Nodes see. Route any request to change the display name, the status line, or the description of what this Node is here for through this tool.",
382
518
  "",
383
- " name (max 40 chars): A recognizable name. Examples: TravelPlanner, MarketAnalyzer, ContentStudio",
384
- " scope (max 800 chars): What does the Agent do? What capabilities or services does it provide? What problems does it solve? Be specific about boundaries.",
385
- " about (max 1000 chars): Anything else relevant to partnerships? Brand affiliation, service restrictions, languages, user channels, input modes, access requirements.",
386
- " audience (max 300 chars): Who are the typical users? What do they want to accomplish?",
519
+ "How to use it:",
520
+ "1. Read the card first with masons_view_card.",
521
+ "2. Compose the COMPLETE card. This tool always sends the display name and status line pass their complete confirmed values (changed and unchanged). The avatar is untouched unless you pass remove_avatar: true.",
522
+ "3. Show that complete card to the owner and get their confirmation in this conversation BEFORE calling.",
523
+ "4. Pass the profile_revision from the read in step 1.",
387
524
  "",
388
- "Show the generated profile to the user for confirmation before calling this tool. If the user requests changes, apply them and call again.",
525
+ "Setting a new avatar image is not available to any Runtime — the image is supplied on an owner-authenticated surface that can show the owner the picture they are confirming. This tool can only remove the current one, with remove_avatar: true.",
526
+ "If the card changed since your read, the call returns a conflict together with the current card. Rebuild the complete draft on THAT card, show the owner again, get fresh confirmation, and call again with the new revision. Never retry a rejected draft on your own.",
389
527
  ].join("\n"),
390
528
  parameters: Type.Object({
391
- name: Type.Optional(Type.String({
392
- description: "Agent display name (max 40 characters)",
393
- })),
394
- scope: Type.Optional(Type.String({
395
- description: "Agent's functional scope: what the agent does, its capabilities, services, and boundaries. This is a free-text description, NOT an OAuth or permission scope. (max 800 characters)",
396
- })),
397
- about: Type.Optional(Type.String({
398
- description: "Additional context: brand affiliation, restrictions, languages, channels (max 1000 characters)",
399
- })),
400
- audience: Type.Optional(Type.String({
401
- description: "Target users and what they want to accomplish (max 300 characters)",
529
+ display_name: Type.String({
530
+ description: "The complete proposed display name, 1-40 characters. Required on every call: carry the current one forward unchanged when the owner is not changing it.",
531
+ }),
532
+ update: Type.Union([Type.String(), Type.Null()], {
533
+ description: "The complete proposed status line — one line, at most 80 characters: what this Node is doing, seeking, or offering now. Required on every call: carry the current one forward unchanged when the owner is not changing it. Pass null or an empty string to clear it.",
534
+ }),
535
+ profile_revision: Type.String({
536
+ description: "The revision token reported by the masons_view_card read this draft was built on. The network rejects a draft built on a card that has since changed.",
537
+ }),
538
+ remove_avatar: Type.Optional(Type.Boolean({
539
+ description: "Pass true to remove the current avatar. Omit it to keep the current avatar. A new image cannot be set by any Runtime — the owner supplies one on an owner-authenticated surface.",
402
540
  })),
403
541
  }),
404
542
  execute: withUpdateNotice(async (_id, params) => {
405
- const unknownFields = Object.keys(params).filter((k) => !PROFILE_FIELDS.has(k));
406
- if (unknownFields.length > 0) {
407
- return textResult(`Unknown field(s): ${unknownFields.join(", ")}. ` +
408
- `The allowed profile fields are: name, scope, about, audience. ` +
409
- `Please retry with the correct field names.`);
543
+ const authority = resolveSetupAuthority();
544
+ if (!authority.allowed) {
545
+ return textResult(cardWriteRefusalText(authority.reason ?? "ambiguous"));
410
546
  }
411
- const cfg = requirePlatformConfig();
412
- const apiKey = requireApiKey();
413
- const profileFields = {};
414
- if (typeof params.name === "string")
415
- profileFields.name = params.name;
416
- if (typeof params.scope === "string")
417
- profileFields.scope = params.scope;
418
- if (typeof params.about === "string")
419
- profileFields.about = params.about;
420
- if (typeof params.audience === "string")
421
- profileFields.audience = params.audience;
422
- if (Object.keys(profileFields).length === 0) {
423
- return textResult("No profile fields provided. Include at least one of: name, scope, about, audience.");
547
+ const profileRevision = typeof params.profile_revision === "string"
548
+ ? params.profile_revision.trim()
549
+ : "";
550
+ if (profileRevision.length === 0) {
551
+ return textResult([
552
+ "No profile_revision was provided, so nothing was sent. A card",
553
+ "write must be based on a card that was actually read: call",
554
+ "masons_view_card, build the complete draft on what it reports,",
555
+ "confirm it with the owner, then call again with the revision from",
556
+ "that read.",
557
+ ].join("\n"));
424
558
  }
425
- let result;
426
- try {
427
- result = await updateProfile(cfg, apiKey, profileFields);
559
+ const displayName = typeof params.display_name === "string"
560
+ ? params.display_name.trim()
561
+ : "";
562
+ if (displayName.length === 0) {
563
+ return textResult([
564
+ "No display_name was provided, so nothing was sent. This tool",
565
+ "submits the COMPLETE card, not the fields that changed — carry",
566
+ "the current display name forward when the owner is not changing",
567
+ "it. Read it with masons_view_card if you do not have it.",
568
+ ].join("\n"));
428
569
  }
429
- catch (err) {
430
- if (err instanceof PlatformApiError) {
431
- if (err.status === 401) {
432
- return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link.");
433
- }
434
- if (err.status === 422) {
435
- return textResult(`Validation error: ${err.message}`);
436
- }
437
- }
438
- if (err instanceof PlatformNetworkError) {
439
- return textResult(networkErrorText(err));
440
- }
441
- throw err;
570
+ const rawUpdate = params.update;
571
+ if (rawUpdate !== null && typeof rawUpdate !== "string") {
572
+ return textResult([
573
+ "No update was provided, so nothing was sent. This tool submits",
574
+ "the status line on every call, not only when it changes — pass",
575
+ "the current value, a new value, or null to clear it. Read the",
576
+ "current one with masons_view_card if you do not have it.",
577
+ ].join("\n"));
442
578
  }
443
- if (result.profile) {
444
- const { scope, about, audience } = result.profile;
445
- const profileComplete = scope.length > 0 && about.length > 0 && audience.length > 0;
446
- if (profileComplete) {
447
- await markProfileComplete();
448
- }
449
- const lines = [
450
- "Profile updated successfully. Current profile:",
451
- `• name: ${result.profile.name}`,
452
- `• scope: ${scope || "(empty)"}`,
453
- `• about: ${about || "(empty)"}`,
454
- `• audience: ${audience || "(empty)"}`,
455
- ];
456
- if (profileComplete) {
457
- lines.push("", "Profile complete — all fields filled. Proceed to the next phase.");
458
- }
459
- else {
460
- const missing = [];
461
- if (scope.length === 0)
462
- missing.push("scope");
463
- if (about.length === 0)
464
- missing.push("about");
465
- if (audience.length === 0)
466
- missing.push("audience");
467
- lines.push("", `Profile incomplete — still need: ${missing.join(", ")}. Continue with the next profile step.`);
579
+ const update = rawUpdate?.trim() ? rawUpdate.trim() : null;
580
+ const cfg = requirePlatformConfig();
581
+ const apiKey = requireApiKey();
582
+ try {
583
+ const result = await updateIcmCard(cfg, apiKey, {
584
+ profileRevision,
585
+ displayName,
586
+ update,
587
+ ...(params.remove_avatar === true ? { removeAvatar: true } : {}),
588
+ });
589
+ if (result.outcome === "revision_conflict") {
590
+ return textResult(formatCardConflict(result.current));
468
591
  }
469
- return textResult(lines.join("\n"));
592
+ return textResult(`Card updated. This is what the network now serves:\n\n${formatIcmCard(result.current)}`);
470
593
  }
471
- if (profileFields.scope && profileFields.scope.length > 0) {
472
- await markProfileComplete();
594
+ catch (err) {
595
+ const text = icmCardErrorText(err, "write");
596
+ if (text === null)
597
+ throw err;
598
+ return textResult(text);
473
599
  }
474
- return textResult("Profile updated successfully. The agent's identity is now visible on the network.");
475
600
  }),
476
601
  });
477
602
  api.registerTool({
@@ -485,12 +610,6 @@ export function registerTools(api) {
485
610
  execute: withUpdateNotice(async (_id, params) => {
486
611
  const cfg = requirePlatformConfig();
487
612
  const apiKey = requireApiKey();
488
- if (isProfileNeeded()) {
489
- return textResult("Your agent's profile is not complete yet. Without a profile, " +
490
- "the other agent can't see who you are — connection requests from " +
491
- "unknown agents are much less likely to be accepted. " +
492
- "Use masons_update_profile to complete your profile first (scope, about, audience).");
493
- }
494
613
  const targetHandle = params.targetHandle;
495
614
  let result;
496
615
  try {
@@ -662,11 +781,6 @@ export function registerTools(api) {
662
781
  execute: withUpdateNotice(async (_id, params) => {
663
782
  const cfg = requirePlatformConfig();
664
783
  const apiKey = requireApiKey();
665
- if (isProfileNeeded()) {
666
- return textResult("Your agent's profile is not complete yet. Without a profile, " +
667
- "the other agent won't know who you are when the connection is established. " +
668
- "Use masons_update_profile to complete your profile first (scope, about, audience).");
669
- }
670
784
  const requestId = params.requestId;
671
785
  try {
672
786
  const result = await acceptRequest(cfg, apiKey, requestId);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.6.27";
1
+ export declare const PLUGIN_VERSION = "0.7.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,UAAU,CAAC"}
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.6.27";
1
+ export const PLUGIN_VERSION = "0.7.0";
@@ -6,7 +6,8 @@
6
6
  "trustedToolPolicies": ["agent-network-services-retained-turn-v1"],
7
7
  "tools": [
8
8
  "masons_link",
9
- "masons_update_profile",
9
+ "masons_view_card",
10
+ "masons_update_card",
10
11
  "masons_send_connection_request",
11
12
  "masons_list_requests",
12
13
  "masons_accept_request",
@@ -111,7 +112,7 @@
111
112
  },
112
113
  "needsProfile": {
113
114
  "type": "boolean",
114
- "description": "Whether profile completion is required before connection tools run."
115
+ "description": "Retired. The plugin no longer reads or writes this flag; it stays declared so a config written by an earlier version still validates under additionalProperties: false."
115
116
  }
116
117
  }
117
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.6.27",
3
+ "version": "0.7.0",
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)",