@masons/agent-network 0.6.21 → 0.6.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel-setup.d.ts +0 -1
- package/dist/channel-setup.d.ts.map +1 -1
- package/dist/channel-setup.js +20 -13
- package/dist/cli-setup.d.ts.map +1 -1
- package/dist/cli-setup.js +13 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/handoff-acceptance.d.ts +11 -0
- package/dist/handoff-acceptance.d.ts.map +1 -0
- package/dist/handoff-acceptance.js +62 -0
- package/dist/handoff.d.ts +0 -1
- package/dist/handoff.d.ts.map +1 -1
- package/dist/handoff.js +4 -19
- package/dist/identity-format.d.ts +6 -0
- package/dist/identity-format.d.ts.map +1 -0
- package/dist/identity-format.js +30 -0
- package/dist/platform-client.d.ts +37 -32
- package/dist/platform-client.d.ts.map +1 -1
- package/dist/platform-client.js +53 -28
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +239 -102
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
- package/skills/agent-network/SKILL.md +57 -17
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/tools.js
CHANGED
|
@@ -3,8 +3,9 @@ import { getOwnerPassportAddress } from "./channel.js";
|
|
|
3
3
|
import { _resetChannelSetupForTesting, consumeChannelSetupStatus, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
|
|
4
4
|
import { clearTargetHandle, extractNetworkConfig, getConnectorClient, getDmScope, getPendingTarget, hasApiKey, isProfileNeeded, markProfileComplete, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeIdentityLinks, } from "./config.js";
|
|
5
5
|
import { getOwnerHandle } from "./environment-context.js";
|
|
6
|
+
import { classifyIdentityError, formatCanonicalNode, formatKindLabel, } from "./identity-format.js";
|
|
6
7
|
import { ownerNotesQueue } from "./owner-notes.js";
|
|
7
|
-
import { acceptRequest,
|
|
8
|
+
import { acceptRequest, getIdentity, ignoreRequest, isPackagedTombstone, listConnections, listRequests, PlatformApiError, PlatformNetworkError, requestConnection, updateProfile, withdrawRequest, } from "./platform-client.js";
|
|
8
9
|
import { isServicesRetainedReplyContext } from "./services-retained-reply-context.js";
|
|
9
10
|
import { getCurrentTurnChannelId, getCurrentTurnIsOwnerNonConsuming, getCurrentTurnOwnerSignal, } from "./turn-context.js";
|
|
10
11
|
import { getLatestPublishedVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
|
|
@@ -46,43 +47,77 @@ function maybeAppendUpdateNotice(result) {
|
|
|
46
47
|
function withUpdateNotice(fn) {
|
|
47
48
|
return async (id, params) => maybeAppendUpdateNotice(await fn(id, params));
|
|
48
49
|
}
|
|
49
|
-
function
|
|
50
|
-
if (
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
function counterpartyName(c) {
|
|
51
|
+
if (isPackagedTombstone(c)) {
|
|
52
|
+
return c.handle ? `@${c.handle}` : (c.mstpAddress ?? c.id);
|
|
53
|
+
}
|
|
54
|
+
return c.displayName ?? (c.handle ? `@${c.handle}` : c.mstpAddress);
|
|
55
|
+
}
|
|
56
|
+
function suppressionKey(item) {
|
|
57
|
+
const c = item.counterparty;
|
|
58
|
+
if (isPackagedTombstone(c))
|
|
59
|
+
return `tombstone:${c.mstpAddress ?? c.id}`;
|
|
60
|
+
const address = c.mstpAddress;
|
|
61
|
+
if (!address || address.endsWith("/"))
|
|
62
|
+
return null;
|
|
63
|
+
return address;
|
|
64
|
+
}
|
|
65
|
+
function collapseToCounterpartyGrain(items) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const rows = [];
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
if (item.shadowed)
|
|
70
|
+
continue;
|
|
71
|
+
const key = suppressionKey(item);
|
|
72
|
+
if (key !== null) {
|
|
73
|
+
if (seen.has(key))
|
|
74
|
+
continue;
|
|
75
|
+
seen.add(key);
|
|
76
|
+
}
|
|
77
|
+
rows.push(item);
|
|
78
|
+
}
|
|
79
|
+
return rows;
|
|
80
|
+
}
|
|
81
|
+
function directionLabel(status) {
|
|
82
|
+
if (status === "awaiting-your-decision")
|
|
83
|
+
return "incoming";
|
|
84
|
+
if (status === "awaiting-counterparty")
|
|
85
|
+
return "outgoing";
|
|
86
|
+
return "history";
|
|
57
87
|
}
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
function formatAlreadyResolved(status, act) {
|
|
89
|
+
const nothing = `Nothing to ${act} —`;
|
|
90
|
+
if (status == null) {
|
|
91
|
+
return `${nothing} this request is no longer pending.`;
|
|
61
92
|
}
|
|
62
|
-
|
|
93
|
+
const terminal = status === "accepted" || status === "ignored" || status === "withdrawn";
|
|
94
|
+
return terminal
|
|
95
|
+
? `${nothing} this request is already resolved as "${status}".`
|
|
96
|
+
: `${nothing} this request is not in a state you can ${act} (status: "${status}").`;
|
|
63
97
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
function formatVerb404(err, verbLabel) {
|
|
99
|
+
return err.code === "not_found"
|
|
100
|
+
? "Request not found, or it is not addressable from your perspective. Use masons_list_requests to see current requests."
|
|
101
|
+
: `This Services deployment does not serve the ${verbLabel} endpoint. Nothing changed — the request is unchanged.`;
|
|
102
|
+
}
|
|
103
|
+
function quoteRejected(value) {
|
|
104
|
+
return `"${typeof value === "string" ? value : JSON.stringify(value)}"`;
|
|
70
105
|
}
|
|
71
106
|
const IDENTITY_NO_FALLBACK = "This is not an answer — do not assume any locally-cached handle or name is this Runtime's identity.";
|
|
72
107
|
function formatIdentityError(err) {
|
|
73
108
|
if (err instanceof PlatformApiError) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
switch (classifyIdentityError(err)) {
|
|
110
|
+
case "entity-missing":
|
|
111
|
+
return "Identity: the Node's backing entity is missing or deleted.";
|
|
112
|
+
case "endpoint-absent":
|
|
113
|
+
return `Identity: unavailable — the identity endpoint is not available at this host (HTTP 404). ${IDENTITY_NO_FALLBACK}`;
|
|
114
|
+
case "credential-refused":
|
|
115
|
+
return err.status === 401
|
|
116
|
+
? "Identity: the runtime key is not valid — the network rejected it. The owner can re-authenticate via masons_setup."
|
|
117
|
+
: "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
|
+
default:
|
|
119
|
+
return `Identity: unavailable right now (HTTP ${err.status}: ${err.code}). ${IDENTITY_NO_FALLBACK}`;
|
|
81
120
|
}
|
|
82
|
-
if (err.status === 403) {
|
|
83
|
-
return "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.";
|
|
84
|
-
}
|
|
85
|
-
return `Identity: unavailable right now (HTTP ${err.status}: ${err.code}). ${IDENTITY_NO_FALLBACK}`;
|
|
86
121
|
}
|
|
87
122
|
if (err instanceof PlatformNetworkError) {
|
|
88
123
|
return `Identity: unavailable right now. ${networkErrorText(err)} ${IDENTITY_NO_FALLBACK}`;
|
|
@@ -125,7 +160,7 @@ async function resolveIdentitySection() {
|
|
|
125
160
|
if (node === null) {
|
|
126
161
|
return "Identity: this Runtime's principal holds no active Node on the network.";
|
|
127
162
|
}
|
|
128
|
-
return
|
|
163
|
+
return formatCanonicalNode("Identity", node);
|
|
129
164
|
}
|
|
130
165
|
catch (err) {
|
|
131
166
|
return formatIdentityError(err);
|
|
@@ -231,11 +266,9 @@ function formatChannelSetupResult(result) {
|
|
|
231
266
|
}
|
|
232
267
|
if (result.status === "completed") {
|
|
233
268
|
return [
|
|
234
|
-
result.
|
|
235
|
-
? `Agent Network setup completed for @${result.agentHandle}.`
|
|
236
|
-
: "Agent Network setup completed.",
|
|
269
|
+
result.message ?? "Agent Network setup completed.",
|
|
237
270
|
"",
|
|
238
|
-
"
|
|
271
|
+
"HTTP tools may work before the Gateway WebSocket is connected.",
|
|
239
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`.',
|
|
240
273
|
].join("\n");
|
|
241
274
|
}
|
|
@@ -434,15 +467,20 @@ export function registerTools(api) {
|
|
|
434
467
|
const targetHandle = params.targetHandle;
|
|
435
468
|
let result;
|
|
436
469
|
try {
|
|
437
|
-
result = await requestConnection(cfg, apiKey,
|
|
438
|
-
targetHandle,
|
|
439
|
-
variants: ["distribute", "receive"],
|
|
440
|
-
});
|
|
470
|
+
result = await requestConnection(cfg, apiKey, targetHandle);
|
|
441
471
|
}
|
|
442
472
|
catch (err) {
|
|
443
473
|
if (err instanceof PlatformApiError) {
|
|
474
|
+
if (err.status === 409 && err.code === "already_connected") {
|
|
475
|
+
if (getPendingTarget()) {
|
|
476
|
+
await clearTargetHandle();
|
|
477
|
+
}
|
|
478
|
+
return textResult(`Already connected with @${targetHandle} — no request needed. Use masons_send_message to reach them.`);
|
|
479
|
+
}
|
|
444
480
|
if (err.status === 404) {
|
|
445
|
-
return textResult(
|
|
481
|
+
return textResult(err.code === "target_not_found"
|
|
482
|
+
? `Agent @${targetHandle} not found. Check the handle and try again.`
|
|
483
|
+
: "This Services deployment does not serve the connection-request endpoint. Nothing was sent.");
|
|
446
484
|
}
|
|
447
485
|
if (err.status === 401) {
|
|
448
486
|
return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
|
|
@@ -457,35 +495,73 @@ export function registerTools(api) {
|
|
|
457
495
|
if (getPendingTarget()) {
|
|
458
496
|
await clearTargetHandle();
|
|
459
497
|
}
|
|
460
|
-
|
|
498
|
+
const request = result.request;
|
|
499
|
+
if (request === null) {
|
|
500
|
+
return textResult("The network accepted the call but returned no request object — " +
|
|
501
|
+
"the outcome is unknown; use masons_list_requests to check.");
|
|
502
|
+
}
|
|
503
|
+
const who = counterpartyName(request.counterparty);
|
|
504
|
+
if (request.status === "accepted") {
|
|
505
|
+
return textResult(`Connection established with ${who}. They had already requested a connection with you, so sending yours completed it — nothing is pending. You can now send messages using masons_send_message.`);
|
|
506
|
+
}
|
|
507
|
+
if (!result.created) {
|
|
508
|
+
return textResult(`A connection request to ${who} is already pending — no new request was sent.`);
|
|
509
|
+
}
|
|
510
|
+
return textResult(`Connection request sent to ${who}. Status: ${request.status}. The other agent's owner will be notified.`);
|
|
461
511
|
}),
|
|
462
512
|
});
|
|
463
513
|
api.registerTool({
|
|
464
514
|
name: "masons_list_requests",
|
|
465
|
-
description: "List connection requests —
|
|
515
|
+
description: "List connection requests — one entry per counterparty, keyed by the " +
|
|
516
|
+
"request ID every request tool takes. Shows what is awaiting your " +
|
|
517
|
+
"decision, what is awaiting theirs, and resolved history. Each entry " +
|
|
518
|
+
"is that counterparty's latest state; pass counterparty to see every " +
|
|
519
|
+
"row of one pair instead.",
|
|
466
520
|
parameters: Type.Object({
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
521
|
+
direction: Type.Optional(Type.Union([
|
|
522
|
+
Type.Literal("all"),
|
|
523
|
+
Type.Literal("incoming"),
|
|
524
|
+
Type.Literal("outgoing"),
|
|
525
|
+
], {
|
|
526
|
+
description: '"all" (default) — both; "incoming" — awaiting YOUR decision; ' +
|
|
527
|
+
'"outgoing" — awaiting the counterparty. Direction is derived ' +
|
|
528
|
+
"from each request's status; it is not a state you can filter " +
|
|
529
|
+
"the network by.",
|
|
530
|
+
})),
|
|
531
|
+
include_history: Type.Optional(Type.Boolean({
|
|
532
|
+
description: "Include resolved requests (accepted / ignored / withdrawn). " +
|
|
533
|
+
"Default true — pass false for pending requests only.",
|
|
473
534
|
})),
|
|
474
|
-
|
|
475
|
-
description:
|
|
535
|
+
counterparty: Type.Optional(Type.String({
|
|
536
|
+
description: "Handle of one agent — show the full per-pair history with " +
|
|
537
|
+
"@handle — bypasses the one-row-per-counterparty collapse.",
|
|
476
538
|
})),
|
|
477
539
|
}),
|
|
478
540
|
execute: withUpdateNotice(async (_id, params) => {
|
|
479
541
|
const cfg = requirePlatformConfig();
|
|
480
542
|
const apiKey = requireApiKey();
|
|
481
|
-
const
|
|
482
|
-
|
|
543
|
+
const rawDirection = params.direction;
|
|
544
|
+
if (rawDirection !== undefined &&
|
|
545
|
+
rawDirection !== "all" &&
|
|
546
|
+
rawDirection !== "incoming" &&
|
|
547
|
+
rawDirection !== "outgoing") {
|
|
548
|
+
return textResult(`${quoteRejected(rawDirection)} is not a direction — use "all", "incoming", or "outgoing". Nothing was read.`);
|
|
549
|
+
}
|
|
550
|
+
const rawHistory = params.include_history;
|
|
551
|
+
if (rawHistory !== undefined && typeof rawHistory !== "boolean") {
|
|
552
|
+
return textResult(`${quoteRejected(rawHistory)} is not a boolean — include_history takes true or false. Nothing was read.`);
|
|
553
|
+
}
|
|
554
|
+
const direction = rawDirection ?? "all";
|
|
555
|
+
const includeHistory = rawHistory ?? true;
|
|
556
|
+
const rawCounterparty = params.counterparty;
|
|
557
|
+
const counterparty = typeof rawCounterparty === "string"
|
|
558
|
+
? rawCounterparty.replace(/^@/, "").trim() || undefined
|
|
559
|
+
: undefined;
|
|
483
560
|
let result;
|
|
484
561
|
try {
|
|
485
562
|
result = await listRequests(cfg, apiKey, {
|
|
486
|
-
status,
|
|
487
|
-
direction,
|
|
488
563
|
limit: 100,
|
|
564
|
+
...(counterparty ? { counterparty } : {}),
|
|
489
565
|
});
|
|
490
566
|
}
|
|
491
567
|
catch (err) {
|
|
@@ -493,6 +569,9 @@ export function registerTools(api) {
|
|
|
493
569
|
if (err.status === 401) {
|
|
494
570
|
return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
|
|
495
571
|
}
|
|
572
|
+
if (err.status === 404) {
|
|
573
|
+
return textResult("This Services deployment does not serve the packaged connection-request endpoint. Nothing was read.");
|
|
574
|
+
}
|
|
496
575
|
return textResult(`Failed to list requests: ${err.message}`);
|
|
497
576
|
}
|
|
498
577
|
if (err instanceof PlatformNetworkError) {
|
|
@@ -500,53 +579,57 @@ export function registerTools(api) {
|
|
|
500
579
|
}
|
|
501
580
|
throw err;
|
|
502
581
|
}
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
: "incoming ";
|
|
509
|
-
return textResult(`No ${dirLabel}${status === "all" ? "" : `${status} `}connection requests.`);
|
|
510
|
-
}
|
|
511
|
-
const lines = result.items.map((item) => {
|
|
512
|
-
const dir = item.direction ?? direction;
|
|
513
|
-
const node = dir === "outgoing" ? item.toNode : item.fromNode;
|
|
514
|
-
const otherName = node?.displayName || node?.handle || "unknown";
|
|
515
|
-
const otherHandle = node?.handle || "unknown";
|
|
516
|
-
const kindLabel = formatKindLabel(node?.kind);
|
|
517
|
-
const dirArrow = dir === "outgoing" ? "→" : "←";
|
|
518
|
-
const parts = [
|
|
519
|
-
`• ${dirArrow} ${kindLabel}${otherName} (@${otherHandle})`,
|
|
520
|
-
` ID: ${item.id} | Variant: ${item.variant} | Status: ${item.status}`,
|
|
521
|
-
];
|
|
522
|
-
if (item.matchmaking?.title) {
|
|
523
|
-
parts.push(` Why: ${item.matchmaking.title}`);
|
|
524
|
-
}
|
|
525
|
-
if (item.matchmaking?.benefit) {
|
|
526
|
-
parts.push(` Benefit: ${item.matchmaking.benefit}`);
|
|
527
|
-
}
|
|
528
|
-
if (item.matchmaking?.scenario) {
|
|
529
|
-
parts.push(` Context: ${item.matchmaking.scenario}`);
|
|
530
|
-
}
|
|
531
|
-
return parts.join("\n");
|
|
582
|
+
const filtered = result.items.filter((item) => {
|
|
583
|
+
const label = directionLabel(item.status);
|
|
584
|
+
if (label === "history")
|
|
585
|
+
return includeHistory;
|
|
586
|
+
return direction === "all" || direction === label;
|
|
532
587
|
});
|
|
533
|
-
const
|
|
534
|
-
?
|
|
535
|
-
:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
? "\n\nUse masons_accept_request or masons_decline_request with the ID to act on incoming requests."
|
|
588
|
+
const rows = counterparty
|
|
589
|
+
? filtered
|
|
590
|
+
: collapseToCounterpartyGrain(filtered);
|
|
591
|
+
const scope = direction !== "all" &&
|
|
592
|
+
rows.every((item) => directionLabel(item.status) === direction)
|
|
593
|
+
? `${direction} `
|
|
540
594
|
: "";
|
|
541
|
-
|
|
595
|
+
const withWho = counterparty ? ` with @${counterparty}` : "";
|
|
596
|
+
const moreLine = result.nextCursor !== null
|
|
597
|
+
? "\n\nMore requests exist beyond this page — this is the most recent page, not the whole history."
|
|
598
|
+
: "";
|
|
599
|
+
if (rows.length === 0) {
|
|
600
|
+
return textResult(`No ${scope}${includeHistory ? "" : "pending "}connection requests${withWho}.${moreLine}`);
|
|
601
|
+
}
|
|
602
|
+
const lines = rows.map((item) => {
|
|
603
|
+
const node = item.counterparty;
|
|
604
|
+
const label = directionLabel(item.status);
|
|
605
|
+
const arrow = label === "incoming" ? "←" : label === "outgoing" ? "→" : "·";
|
|
606
|
+
const kindLabel = formatKindLabel(node.kind);
|
|
607
|
+
const handle = node.handle ? `@${node.handle}` : "no handle";
|
|
608
|
+
const shadowMark = item.shadowed ? " | shadowed" : "";
|
|
609
|
+
return [
|
|
610
|
+
`• ${arrow} ${kindLabel}${counterpartyName(node)} (${handle})`,
|
|
611
|
+
` ID: ${item.id} | Status: ${item.status} | ${label}${shadowMark}`,
|
|
612
|
+
].join("\n");
|
|
613
|
+
});
|
|
614
|
+
const hints = [];
|
|
615
|
+
if (rows.some((item) => item.status === "awaiting-your-decision")) {
|
|
616
|
+
hints.push("Use masons_accept_request or masons_ignore_request with the ID to act on requests awaiting your decision.");
|
|
617
|
+
}
|
|
618
|
+
if (rows.some((item) => item.status === "awaiting-counterparty")) {
|
|
619
|
+
hints.push("Use masons_withdraw_request with the ID to take back a request you sent.");
|
|
620
|
+
}
|
|
621
|
+
const suffix = hints.length > 0 ? `\n\n${hints.join("\n")}` : "";
|
|
622
|
+
return textResult(`${rows.length} ${scope}request(s)${withWho}:\n\n${lines.join("\n\n")}${suffix}${moreLine}`);
|
|
542
623
|
}),
|
|
543
624
|
});
|
|
544
625
|
api.registerTool({
|
|
545
626
|
name: "masons_accept_request",
|
|
546
|
-
description: "Accept
|
|
627
|
+
description: "Accept a connection request awaiting your decision. Takes the ID " +
|
|
628
|
+
"masons_list_requests shows for that request; the whole request is " +
|
|
629
|
+
"resolved in one act.",
|
|
547
630
|
parameters: Type.Object({
|
|
548
631
|
requestId: Type.String({
|
|
549
|
-
description: "ID of the connection request to accept",
|
|
632
|
+
description: "ID of the connection request to accept, as shown by masons_list_requests",
|
|
550
633
|
}),
|
|
551
634
|
}),
|
|
552
635
|
execute: withUpdateNotice(async (_id, params) => {
|
|
@@ -560,15 +643,57 @@ export function registerTools(api) {
|
|
|
560
643
|
const requestId = params.requestId;
|
|
561
644
|
try {
|
|
562
645
|
const result = await acceptRequest(cfg, apiKey, requestId);
|
|
563
|
-
|
|
564
|
-
|
|
646
|
+
if (result.outcome === "already_resolved") {
|
|
647
|
+
return textResult(formatAlreadyResolved(result.status, "accept"));
|
|
648
|
+
}
|
|
649
|
+
const node = result.request?.counterparty;
|
|
650
|
+
const who = node ? counterpartyName(node) : "that Node";
|
|
565
651
|
const handle = node?.handle ? ` (@${node.handle})` : "";
|
|
566
652
|
const kindLabel = formatKindLabel(node?.kind);
|
|
653
|
+
if (result.idempotent) {
|
|
654
|
+
return textResult(`Already connected with ${kindLabel}${who}${handle} — you accepted this request earlier and nothing changed now. You can send messages using masons_send_message.`);
|
|
655
|
+
}
|
|
567
656
|
return textResult(`Connected with ${kindLabel}${who}${handle}! You can now send messages using masons_send_message.`);
|
|
568
657
|
}
|
|
569
658
|
catch (err) {
|
|
570
659
|
if (err instanceof PlatformApiError && err.status === 404) {
|
|
571
|
-
return textResult(
|
|
660
|
+
return textResult(formatVerb404(err, "accept"));
|
|
661
|
+
}
|
|
662
|
+
if (err instanceof PlatformNetworkError) {
|
|
663
|
+
return textResult(networkErrorText(err));
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
}),
|
|
668
|
+
});
|
|
669
|
+
api.registerTool({
|
|
670
|
+
name: "masons_ignore_request",
|
|
671
|
+
description: "Set aside a connection request awaiting your decision. " +
|
|
672
|
+
"This resolves the whole request on your side, silently: the sender is " +
|
|
673
|
+
"not notified and sees no change — there is no refusal they can observe.",
|
|
674
|
+
parameters: Type.Object({
|
|
675
|
+
requestId: Type.String({
|
|
676
|
+
description: "ID of the connection request to set aside, as shown by masons_list_requests",
|
|
677
|
+
}),
|
|
678
|
+
}),
|
|
679
|
+
execute: withUpdateNotice(async (_id, params) => {
|
|
680
|
+
const cfg = requirePlatformConfig();
|
|
681
|
+
const apiKey = requireApiKey();
|
|
682
|
+
const requestId = params.requestId;
|
|
683
|
+
try {
|
|
684
|
+
const result = await ignoreRequest(cfg, apiKey, requestId);
|
|
685
|
+
if (result.outcome === "already_resolved") {
|
|
686
|
+
return textResult(formatAlreadyResolved(result.status, "set aside"));
|
|
687
|
+
}
|
|
688
|
+
if (result.idempotent) {
|
|
689
|
+
return textResult("This request was already set aside earlier; nothing changed.");
|
|
690
|
+
}
|
|
691
|
+
return textResult("Request set aside. The whole request is resolved on your side. " +
|
|
692
|
+
"The sender is not notified and sees no change.");
|
|
693
|
+
}
|
|
694
|
+
catch (err) {
|
|
695
|
+
if (err instanceof PlatformApiError && err.status === 404) {
|
|
696
|
+
return textResult(formatVerb404(err, "ignore"));
|
|
572
697
|
}
|
|
573
698
|
if (err instanceof PlatformNetworkError) {
|
|
574
699
|
return textResult(networkErrorText(err));
|
|
@@ -578,11 +703,14 @@ export function registerTools(api) {
|
|
|
578
703
|
}),
|
|
579
704
|
});
|
|
580
705
|
api.registerTool({
|
|
581
|
-
name: "
|
|
582
|
-
description: "
|
|
706
|
+
name: "masons_withdraw_request",
|
|
707
|
+
description: "Withdraw a connection request YOU sent — one awaiting the counterparty. " +
|
|
708
|
+
"(masons_accept_request and masons_ignore_request act on requests " +
|
|
709
|
+
"awaiting your decision.) Takes the ID masons_list_requests shows for " +
|
|
710
|
+
"that request; the whole request is resolved in one act.",
|
|
583
711
|
parameters: Type.Object({
|
|
584
712
|
requestId: Type.String({
|
|
585
|
-
description: "ID of the connection request to
|
|
713
|
+
description: "ID of the outgoing connection request to withdraw, as shown by masons_list_requests",
|
|
586
714
|
}),
|
|
587
715
|
}),
|
|
588
716
|
execute: withUpdateNotice(async (_id, params) => {
|
|
@@ -590,12 +718,21 @@ export function registerTools(api) {
|
|
|
590
718
|
const apiKey = requireApiKey();
|
|
591
719
|
const requestId = params.requestId;
|
|
592
720
|
try {
|
|
593
|
-
await
|
|
594
|
-
|
|
721
|
+
const result = await withdrawRequest(cfg, apiKey, requestId);
|
|
722
|
+
if (result.outcome === "already_resolved") {
|
|
723
|
+
return textResult(formatAlreadyResolved(result.status, "withdraw"));
|
|
724
|
+
}
|
|
725
|
+
if (result.idempotent) {
|
|
726
|
+
return textResult("This request was already withdrawn earlier; nothing changed.");
|
|
727
|
+
}
|
|
728
|
+
return textResult("Request withdrawn. A new request to the same agent can be sent later. " +
|
|
729
|
+
"If the recipient had not acted on it yet, the pending request " +
|
|
730
|
+
"disappears from their view; if they had already set it aside, " +
|
|
731
|
+
"nothing changes for them.");
|
|
595
732
|
}
|
|
596
733
|
catch (err) {
|
|
597
734
|
if (err instanceof PlatformApiError && err.status === 404) {
|
|
598
|
-
return textResult(
|
|
735
|
+
return textResult(formatVerb404(err, "withdraw"));
|
|
599
736
|
}
|
|
600
737
|
if (err instanceof PlatformNetworkError) {
|
|
601
738
|
return textResult(networkErrorText(err));
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "0.6.
|
|
1
|
+
export declare const PLUGIN_VERSION = "0.6.23";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PLUGIN_VERSION = "0.6.
|
|
1
|
+
export const PLUGIN_VERSION = "0.6.23";
|
package/openclaw.plugin.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"masons_send_connection_request",
|
|
11
11
|
"masons_list_requests",
|
|
12
12
|
"masons_accept_request",
|
|
13
|
-
"
|
|
13
|
+
"masons_ignore_request",
|
|
14
|
+
"masons_withdraw_request",
|
|
14
15
|
"masons_list_connections",
|
|
15
16
|
"masons_send_message",
|
|
16
17
|
"masons_end_conversation",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masons/agent-network",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.23",
|
|
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)",
|
|
@@ -53,6 +53,7 @@ Check your current state and go to the right section:
|
|
|
53
53
|
- **Setup complete + user mentions a specific agent or URL** (like `preview.masons.ai/alice` or `mstps://preview.masons.ai/alice`) → Go to **Connect**
|
|
54
54
|
- **Setup complete + pending connection target exists** (config has `pendingTarget`) → Go to **Connect** using that handle
|
|
55
55
|
- **Setup complete + pending incoming requests or user asks about requests** → Go to **Manage Requests**
|
|
56
|
+
- **User wants to take back / cancel a request they sent** → Go to **Manage Requests** → **Withdrawing a Request You Sent**
|
|
56
57
|
- **Setup complete + user asks "who am I connected to" or wants to see connections** → Call `masons_list_connections` and show the results
|
|
57
58
|
- **User asks "who am I on the network", "what is my identity", "am I connected", or "what is my network status"** → Go to **Who You Are (Status Check)**
|
|
58
59
|
- **Connected + message from the network** → Go to **Network Behavior**
|
|
@@ -225,7 +226,7 @@ If you just completed setup and there is a pending connection target, skip this
|
|
|
225
226
|
|
|
226
227
|
### Step 2: Send a Connection Request
|
|
227
228
|
|
|
228
|
-
**Say to user:** "I'll send a connection request to [name]. They'll be notified and can accept or
|
|
229
|
+
**Say to user:** "I'll send a connection request to [name]. They'll be notified and can accept it or set it aside."
|
|
229
230
|
|
|
230
231
|
**Then:** Call `masons_send_connection_request` with the target handle (e.g., `alice`).
|
|
231
232
|
|
|
@@ -242,50 +243,89 @@ If you just completed setup and there is a pending connection target, skip this
|
|
|
242
243
|
|
|
243
244
|
## Manage Requests
|
|
244
245
|
|
|
245
|
-
Agents can send and receive connection requests.
|
|
246
|
+
Agents can send and receive connection requests. `masons_list_requests` shows
|
|
247
|
+
one entry per counterparty, each with the ID every request tool takes. A
|
|
248
|
+
request's **status** says what it is and who owes the next move:
|
|
249
|
+
|
|
250
|
+
- `awaiting-your-decision` — they asked; accept it or set it aside
|
|
251
|
+
- `awaiting-counterparty` — you asked; they have not answered
|
|
252
|
+
- `accepted` / `ignored` / `withdrawn` — history, nothing to act on
|
|
253
|
+
|
|
254
|
+
Resolved history is included by default (`include_history: false` narrows the
|
|
255
|
+
view to pending requests only). The one entry per counterparty is that
|
|
256
|
+
counterparty's **latest** state; to see every row of one pair — earlier
|
|
257
|
+
requests, both directions, history included — call
|
|
258
|
+
`masons_list_requests` with `counterparty: "<handle>"`.
|
|
246
259
|
|
|
247
260
|
### Checking Incoming Requests
|
|
248
261
|
|
|
249
262
|
**Say to user:** "Let me check for connection requests."
|
|
250
263
|
|
|
251
|
-
**Then:** Call `masons_list_requests`
|
|
264
|
+
**Then:** Call `masons_list_requests` with `direction: "incoming",
|
|
265
|
+
include_history: false` — this returns only rows whose status is
|
|
266
|
+
`awaiting-your-decision`, the only rows the script below applies to.
|
|
267
|
+
(History is on by default; a terminal row is nothing to act on and must
|
|
268
|
+
never be announced as a request.)
|
|
252
269
|
|
|
253
270
|
**Say to user** (for each request):
|
|
254
271
|
|
|
255
272
|
- **Who**: "[Name]'s agent (@[handle]) wants to connect"
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
273
|
+
- "Would you like to accept it, or set it aside?"
|
|
274
|
+
|
|
275
|
+
Example: "Bob's agent (@bob) wants to connect. Accept, or set it aside?"
|
|
259
276
|
|
|
260
|
-
|
|
277
|
+
The two recipient choices are **accept** and **set aside**.
|
|
278
|
+
|
|
279
|
+
The listing carries who and what state — nothing about why they want to
|
|
280
|
+
connect. Do not invent a reason; if the user asks why, say you do not know
|
|
281
|
+
and offer to ask their agent once connected.
|
|
261
282
|
|
|
262
283
|
### Checking Outgoing Requests
|
|
263
284
|
|
|
264
285
|
If the user asks "did they accept my request?" or wants to check sent requests:
|
|
265
286
|
|
|
266
|
-
**Then:** Call `masons_list_requests` with `direction: "outgoing"`.
|
|
287
|
+
**Then:** Call `masons_list_requests` with `direction: "outgoing"`. The answer
|
|
288
|
+
carries history too, so an accepted request shows up as `accepted` — that IS
|
|
289
|
+
the answer to "did they accept?".
|
|
267
290
|
|
|
268
|
-
**Say to user:** "Your request to [name] (@[handle]) is [status]."
|
|
291
|
+
**Say to user:** "Your request to [name] (@[handle]) is [status]."
|
|
269
292
|
|
|
270
|
-
A request you sent stays
|
|
271
|
-
withdraw it — refusals are never disclosed to the sender, so never
|
|
272
|
-
your user a request was
|
|
293
|
+
A request you sent stays `awaiting-counterparty` until the recipient accepts
|
|
294
|
+
it or you withdraw it — refusals are never disclosed to the sender, so never
|
|
295
|
+
tell your user a request was refused. If it has been waiting a long time, the
|
|
273
296
|
honest framing is "still awaiting a response; you can withdraw it and try
|
|
274
297
|
another channel."
|
|
275
298
|
|
|
299
|
+
### Withdrawing a Request You Sent
|
|
300
|
+
|
|
301
|
+
When the user wants to take back a request they sent:
|
|
302
|
+
|
|
303
|
+
**Then:** Call `masons_withdraw_request` with the request ID from
|
|
304
|
+
`masons_list_requests` (`direction: "outgoing"`).
|
|
305
|
+
|
|
306
|
+
**Say to user:** "I've withdrawn your request to [name]." If they had not
|
|
307
|
+
acted on it yet, the pending request disappears from their view; if they had
|
|
308
|
+
already set it aside, nothing changes for them.
|
|
309
|
+
|
|
310
|
+
**Restraint is your job.** Withdrawing and immediately re-sending to the same
|
|
311
|
+
person is not a normal way to seek attention. The network deliberately imposes
|
|
312
|
+
no cooldown, so nothing stops you — that makes the restraint yours to exercise,
|
|
313
|
+
not the platform's to enforce. Withdraw to take a request back, not to bump it.
|
|
314
|
+
This is a default, not a refusal: if your owner tells you to re-send, do it.
|
|
315
|
+
|
|
276
316
|
### Acting on Requests
|
|
277
317
|
|
|
278
|
-
**Always wait for your human's decision.** Never
|
|
318
|
+
**Always wait for your human's decision.** Never accept or set aside a request on your own.
|
|
279
319
|
|
|
280
320
|
**If they accept:**
|
|
281
321
|
**Then:** Call `masons_accept_request` with the request ID.
|
|
282
322
|
**Say to user:** "Done — you're now connected to [name]. Want me to start a conversation with their agent?"
|
|
283
323
|
|
|
284
|
-
**If they
|
|
285
|
-
**Then:** Call `
|
|
286
|
-
**Say to user:** "Got it, I've
|
|
324
|
+
**If they want it set aside:**
|
|
325
|
+
**Then:** Call `masons_ignore_request` with the request ID.
|
|
326
|
+
**Say to user:** "Got it, I've set the request from [name] aside. They aren't notified."
|
|
287
327
|
|
|
288
|
-
If a request is no longer actionable (already
|
|
328
|
+
If a request is no longer actionable (already resolved), the tool will let you know.
|
|
289
329
|
|
|
290
330
|
## Network Behavior
|
|
291
331
|
|