@masons/agent-network 0.6.24 → 0.6.26

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.
@@ -4,9 +4,10 @@ interface StartChannelSetupOptions {
4
4
  idpBaseUrl: string;
5
5
  idpBaseUrlIsDefault?: boolean;
6
6
  invitedBy?: string;
7
+ startOver?: boolean;
7
8
  }
8
9
  export interface ChannelSetupResult {
9
- status: "pending" | "completed" | "expired" | "failed";
10
+ status: "pending" | "completed" | "linked" | "expired" | "failed";
10
11
  handoffUrl?: string;
11
12
  expiresAt?: number;
12
13
  message?: string;
@@ -14,7 +15,7 @@ export interface ChannelSetupResult {
14
15
  export declare function getDefaultChannelSetupOptions(): StartChannelSetupOptions;
15
16
  export declare function startOrGetChannelSetup(options: StartChannelSetupOptions): Promise<ChannelSetupResult>;
16
17
  export declare function getChannelSetupStatus(): ChannelSetupResult | null;
17
- export declare function consumeChannelSetupStatus(): ChannelSetupResult | null;
18
+ export declare function getReportableSetupStatus(): ChannelSetupResult | null;
18
19
  export declare function _resetChannelSetupForTesting(): void;
19
20
  export declare const CHANNEL_SETUP_TTL_MS: number;
20
21
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"channel-setup.d.ts","sourceRoot":"","sources":["../src/channel-setup.ts"],"names":[],"mappings":"AAeA,UAAU,wBAAwB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IAEnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAM9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2BD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAOD,wBAAgB,6BAA6B,IAAI,wBAAwB,CAKxE;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAsC7B;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAcjE;AAED,wBAAgB,yBAAyB,IAAI,kBAAkB,GAAG,IAAI,CAMrE;AAmFD,wBAAgB,4BAA4B,IAAI,IAAI,CAGnD;AAED,eAAO,MAAM,oBAAoB,QAAqB,CAAC"}
1
+ {"version":3,"file":"channel-setup.d.ts","sourceRoot":"","sources":["../src/channel-setup.ts"],"names":[],"mappings":"AA+BA,UAAU,wBAAwB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IAEnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAM9B,SAAS,CAAC,EAAE,MAAM,CAAC;IAOnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA4CD,MAAM,WAAW,kBAAkB;IAKjC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4BD,wBAAgB,6BAA6B,IAAI,wBAAwB,CAKxE;AAWD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAqB7B;AAyDD,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAQjE;AAaD,wBAAgB,wBAAwB,IAAI,kBAAkB,GAAG,IAAI,CAOpE;AAgOD,wBAAgB,4BAA4B,IAAI,IAAI,CAInD;AAED,eAAO,MAAM,oBAAoB,QAAqB,CAAC"}
@@ -1,12 +1,22 @@
1
1
  import createDebug from "debug";
2
- import { readExistingConnectorUrl } from "./config.js";
2
+ import { hasApiKey, readExistingConnectorUrl, requireApiKey, requirePlatformConfig, } from "./config.js";
3
3
  import { resolveConnectorUrlForSetup } from "./connector-url-boundary.js";
4
- import { beginBridgeHandoff, completeBridgeHandoff, DEFAULT_IDP_BASE_URL, HANDOFF_TIMEOUT_MS, SetupFlowError, } from "./handoff.js";
4
+ import { beginBridgeHandoff, collectBridgeHandoffOnce, completeBridgeHandoff, DEFAULT_IDP_BASE_URL, HANDOFF_TIMEOUT_MS, SetupFlowError, } from "./handoff.js";
5
5
  import { acceptBridgeHandoff } from "./handoff-acceptance.js";
6
- import { DEFAULT_API_HOST } from "./platform-client.js";
6
+ import { classifyIdentityError, formatCanonicalNode, } from "./identity-format.js";
7
+ import { DEFAULT_API_HOST, getIdentity, } from "./platform-client.js";
7
8
  const dbg = createDebug("agent-network:channel-setup");
9
+ const GENERIC_EXPIRY_MESSAGE = "The previous Link URL expired before the browser handoff completed.";
10
+ const GONE_MESSAGE = "That Link session is no longer on the handoff bridge — it expired, or its runtime key was already delivered — so it cannot complete.";
11
+ const SUPERSEDED_MESSAGE = "A newer Link session replaced this one while it was completing, so the runtime key that session delivered was discarded. Nothing changed — this runtime still uses the credential it had before.";
12
+ const DURABLE_BINDING_NOTE = [
13
+ "This result reflects the current binding — the runtime key this Runtime holds, confirmed with the network just now.",
14
+ "To re-link or change Node, call masons_link with start_over: true.",
15
+ ].join("\n");
8
16
  let pendingSetup = null;
9
17
  let lastSetupStatus = null;
18
+ let setupSeq = 0;
19
+ let lastRecordedSeq = 0;
10
20
  export function getDefaultChannelSetupOptions() {
11
21
  return {
12
22
  apiHost: DEFAULT_API_HOST,
@@ -14,50 +24,52 @@ export function getDefaultChannelSetupOptions() {
14
24
  };
15
25
  }
16
26
  export async function startOrGetChannelSetup(options) {
27
+ if (options.startOver)
28
+ return await startNewSession(options);
17
29
  const active = getActivePendingSetup();
18
30
  if (active) {
19
- return {
20
- status: "pending",
21
- handoffUrl: active.session.handoffUrl,
22
- expiresAt: active.session.expiresAt,
23
- };
31
+ const collected = await collectActiveSetup(active);
32
+ if (collected)
33
+ return collected;
24
34
  }
25
- lastSetupStatus = null;
26
- const existingConnectorUrl = await readExistingConnectorUrl();
27
- const connectorUrl = resolveConnectorUrlForSetup({
28
- apiHost: options.apiHost,
29
- existingConnectorUrl,
30
- configuredConnectorUrl: options.connectorUrl,
31
- });
32
- const session = await beginBridgeHandoff({
33
- apiHost: options.apiHost,
34
- idpBaseUrl: options.idpBaseUrl,
35
- idpBaseUrlIsDefault: options.idpBaseUrlIsDefault,
36
- });
37
- const record = {
38
- session,
39
- apiHost: options.apiHost,
40
- connectorUrl,
41
- idpBaseUrl: options.idpBaseUrl,
42
- invitedBy: options.invitedBy,
43
- };
44
- pendingSetup = record;
45
- void completePendingSetup(record);
46
- return {
47
- status: "pending",
48
- handoffUrl: session.handoffUrl,
49
- expiresAt: session.expiresAt,
50
- };
35
+ const reportable = getReportableSetupStatus();
36
+ if (reportable)
37
+ return reportable;
38
+ const live = getActivePendingSetup();
39
+ if (live)
40
+ return pendingResult(live);
41
+ const durable = await reportDurableBinding(options);
42
+ if (durable)
43
+ return durable;
44
+ return await startNewSession(options);
45
+ }
46
+ async function reportDurableBinding(options) {
47
+ if (!hasApiKey())
48
+ return null;
49
+ let node;
50
+ try {
51
+ node = await getIdentity(requirePlatformConfig(), requireApiKey());
52
+ }
53
+ catch (err) {
54
+ if (classifyIdentityError(err) === null)
55
+ throw err;
56
+ dbg("the stored runtime key could not be verified before minting: %O", err);
57
+ return null;
58
+ }
59
+ if (node === null)
60
+ return null;
61
+ const lines = [formatCanonicalNode("Linked", node), DURABLE_BINDING_NOTE];
62
+ if (options.invitedBy)
63
+ lines.push(invitationFollowThrough(options.invitedBy));
64
+ return { status: "linked", message: lines.join("\n") };
65
+ }
66
+ function invitationFollowThrough(invitedBy) {
67
+ return `This call carried an invitation from @${invitedBy}. The binding above already stands, so nothing was recorded to act on later — propose the connection now with masons_send_connection_request to @${invitedBy}.`;
51
68
  }
52
69
  export function getChannelSetupStatus() {
53
70
  const active = getActivePendingSetup();
54
- if (active) {
55
- return {
56
- status: "pending",
57
- handoffUrl: active.session.handoffUrl,
58
- expiresAt: active.session.expiresAt,
59
- };
60
- }
71
+ if (active)
72
+ return pendingResult(active);
61
73
  if (!lastSetupStatus)
62
74
  return null;
63
75
  return {
@@ -65,31 +77,76 @@ export function getChannelSetupStatus() {
65
77
  message: lastSetupStatus.message,
66
78
  };
67
79
  }
68
- export function consumeChannelSetupStatus() {
69
- const status = getChannelSetupStatus();
70
- if (status?.status !== "pending") {
71
- lastSetupStatus = null;
72
- }
73
- return status;
80
+ export function getReportableSetupStatus() {
81
+ const recorded = lastSetupStatus;
82
+ if (!recorded)
83
+ return null;
84
+ if (pendingSetup?.seq === recorded.seq)
85
+ return null;
86
+ if (recorded.status === "completed")
87
+ return reportStatus(recorded);
88
+ if (!recorded.reported)
89
+ return reportStatus(recorded);
90
+ return null;
91
+ }
92
+ function pendingResult(record) {
93
+ return {
94
+ status: "pending",
95
+ handoffUrl: record.session.handoffUrl,
96
+ expiresAt: record.session.expiresAt,
97
+ };
98
+ }
99
+ function reportStatus(status) {
100
+ status.reported = true;
101
+ return { status: status.status, message: status.message };
102
+ }
103
+ function storedResult(stored) {
104
+ return stored ? reportStatus(stored) : null;
74
105
  }
75
106
  function getActivePendingSetup() {
76
107
  if (!pendingSetup)
77
108
  return null;
78
109
  if (Date.now() < pendingSetup.session.expiresAt)
79
110
  return pendingSetup;
80
- lastSetupStatus = {
81
- status: "expired",
82
- message: "The previous Link URL expired before the browser handoff completed.",
83
- finishedAt: Date.now(),
84
- };
111
+ const lapsed = pendingSetup;
85
112
  pendingSetup = null;
113
+ recordSetupStatus(lapsed, "expired", GENERIC_EXPIRY_MESSAGE, false);
86
114
  return null;
87
115
  }
88
- async function completePendingSetup(record) {
116
+ async function collectActiveSetup(record) {
117
+ let collection;
118
+ try {
119
+ collection = await collectBridgeHandoffOnce(record.session);
120
+ }
121
+ catch (err) {
122
+ dbg("owner-invoked collection could not reach the bridge: %O", err);
123
+ return null;
124
+ }
125
+ switch (collection.outcome) {
126
+ case "delivered":
127
+ return storedResult(await acceptCollectedHandoff(record, collection.handoff));
128
+ case "spoiled":
129
+ return storedResult(settle(record, isExpiryError(collection.error) ? "expired" : "failed", collection.error.message));
130
+ case "gone":
131
+ return storedResult(collectGoneSession(record));
132
+ default:
133
+ return null;
134
+ }
135
+ }
136
+ function collectGoneSession(record) {
137
+ if (!record.collected)
138
+ return settle(record, "expired", GONE_MESSAGE);
139
+ const settled = lastSetupStatus;
140
+ if (settled?.seq === record.seq && settled.settled)
141
+ return settled;
142
+ return null;
143
+ }
144
+ async function acceptCollectedHandoff(record, handoff) {
145
+ record.collected = true;
146
+ if (pendingSetup !== record) {
147
+ return recordSetupStatus(record, "failed", SUPERSEDED_MESSAGE, true);
148
+ }
89
149
  try {
90
- const handoff = await completeBridgeHandoff(record.session);
91
- if (pendingSetup !== record)
92
- return;
93
150
  const receipt = await acceptBridgeHandoff({
94
151
  handoff,
95
152
  apiHost: record.apiHost,
@@ -98,31 +155,74 @@ async function completePendingSetup(record) {
98
155
  pendingTarget: record.invitedBy,
99
156
  expiresAt: record.session.expiresAt,
100
157
  });
101
- lastSetupStatus = {
102
- status: "completed",
103
- message: receipt,
104
- finishedAt: Date.now(),
105
- };
158
+ return settle(record, "completed", receipt);
106
159
  }
107
160
  catch (err) {
108
- if (pendingSetup !== record && !supersedesGenericExpiry(err))
109
- return;
110
- lastSetupStatus = {
111
- status: isExpiryError(err) ? "expired" : "failed",
112
- message: safeSetupErrorMessage(err),
113
- finishedAt: Date.now(),
114
- };
161
+ return settle(record, isExpiryError(err) ? "expired" : "failed", safeSetupErrorMessage(err));
115
162
  }
116
- finally {
117
- if (pendingSetup === record) {
118
- pendingSetup = null;
119
- }
163
+ }
164
+ function settle(record, status, message) {
165
+ if (pendingSetup === record)
166
+ pendingSetup = null;
167
+ return recordSetupStatus(record, status, message, true);
168
+ }
169
+ async function startNewSession(options) {
170
+ const existingConnectorUrl = await readExistingConnectorUrl();
171
+ const connectorUrl = resolveConnectorUrlForSetup({
172
+ apiHost: options.apiHost,
173
+ existingConnectorUrl,
174
+ configuredConnectorUrl: options.connectorUrl,
175
+ });
176
+ const session = await beginBridgeHandoff({
177
+ apiHost: options.apiHost,
178
+ idpBaseUrl: options.idpBaseUrl,
179
+ idpBaseUrlIsDefault: options.idpBaseUrlIsDefault,
180
+ });
181
+ const record = {
182
+ seq: ++setupSeq,
183
+ session,
184
+ apiHost: options.apiHost,
185
+ connectorUrl,
186
+ idpBaseUrl: options.idpBaseUrl,
187
+ invitedBy: options.invitedBy,
188
+ collected: false,
189
+ };
190
+ pendingSetup = record;
191
+ lastSetupStatus = null;
192
+ void completePendingSetup(record);
193
+ return pendingResult(record);
194
+ }
195
+ async function completePendingSetup(record) {
196
+ let handoff;
197
+ try {
198
+ handoff = await completeBridgeHandoff(record.session);
199
+ }
200
+ catch (err) {
201
+ recordSetupStatus(record, isExpiryError(err) ? "expired" : "failed", safeSetupErrorMessage(err), false);
202
+ return;
120
203
  }
204
+ await acceptCollectedHandoff(record, handoff);
121
205
  }
122
- function supersedesGenericExpiry(err) {
123
- return (err instanceof SetupFlowError &&
124
- pendingSetup === null &&
125
- lastSetupStatus?.status === "expired");
206
+ function recordSetupStatus(record, status, message, settled) {
207
+ const next = {
208
+ seq: record.seq,
209
+ status,
210
+ message,
211
+ reported: false,
212
+ settled,
213
+ };
214
+ if (!canRecord(next))
215
+ return null;
216
+ lastSetupStatus = next;
217
+ lastRecordedSeq = next.seq;
218
+ return next;
219
+ }
220
+ function canRecord(next) {
221
+ if (next.seq !== lastRecordedSeq)
222
+ return next.seq > lastRecordedSeq;
223
+ if (!lastSetupStatus)
224
+ return false;
225
+ return next.settled && !lastSetupStatus.settled;
126
226
  }
127
227
  function isExpiryError(err) {
128
228
  if (!(err instanceof SetupFlowError))
@@ -138,5 +238,6 @@ function safeSetupErrorMessage(err) {
138
238
  export function _resetChannelSetupForTesting() {
139
239
  pendingSetup = null;
140
240
  lastSetupStatus = null;
241
+ lastRecordedSeq = 0;
141
242
  }
142
243
  export const CHANNEL_SETUP_TTL_MS = HANDOFF_TIMEOUT_MS;
@@ -1 +1 @@
1
- {"version":3,"file":"connector-client.d.ts","sourceRoot":"","sources":["../src/connector-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connector-client.d.ts","sourceRoot":"","sources":["../src/connector-client.ts"],"names":[],"mappings":"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"}
@@ -476,8 +476,10 @@ export class ConnectorClient extends EventEmitter {
476
476
  this.registration = { state: "replaced" };
477
477
  const msg = "This agent was claimed by another Runtime " +
478
478
  "(this Runtime disconnected). " +
479
- "Re-run `openclaw channels login --channel agent-network` " +
480
- "to drive this agent from here again.";
479
+ "To drive this agent from here again, ask the owner to call " +
480
+ "masons_link with start_over: true in this conversation; as a last " +
481
+ "resort, `openclaw channels login --channel agent-network` in a " +
482
+ "terminal does the same.";
481
483
  this.emit("error", new Error(msg));
482
484
  if (wasRegistering) {
483
485
  this.rejectRegister(new Error(msg));
package/dist/handoff.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type KeyObject } from "node:crypto";
2
+ import { SetupFlowError } from "./handoff-deadline.js";
2
3
  export { SetupFlowError } from "./handoff-deadline.js";
3
4
  export declare const DEFAULT_IDP_BASE_URL = "https://preview.masons.ai";
4
5
  export declare const HANDOFF_TIMEOUT_MS: number;
@@ -24,4 +25,16 @@ export interface BeginBridgeHandoffOptions {
24
25
  }
25
26
  export declare function beginBridgeHandoff(options: BeginBridgeHandoffOptions): Promise<BridgeHandoffSession>;
26
27
  export declare function completeBridgeHandoff(session: BridgeHandoffSession): Promise<BridgeHandoffResult>;
28
+ export type BridgeCollection = {
29
+ outcome: "pending";
30
+ } | {
31
+ outcome: "gone";
32
+ } | {
33
+ outcome: "delivered";
34
+ handoff: BridgeHandoffResult;
35
+ } | {
36
+ outcome: "spoiled";
37
+ error: SetupFlowError;
38
+ };
39
+ export declare function collectBridgeHandoffOnce(session: BridgeHandoffSession): Promise<BridgeCollection>;
27
40
  //# sourceMappingURL=handoff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAQrB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAavD,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAWhE,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AA0BjD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAQD,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAOhB,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAMnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,oBAAoB,CAAC,CAiE/B;AAKD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA0C9B"}
1
+ {"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAErB,OAAO,EAEL,cAAc,EACf,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAavD,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAWhE,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AA0BjD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAQD,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAUhB,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAMnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,oBAAoB,CAAC,CAiE/B;AAKD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAO9B;AAUD,MAAM,MAAM,gBAAgB,GACxB;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GACtD;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAAC;AASlD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CA8B3B"}
package/dist/handoff.js CHANGED
@@ -55,6 +55,44 @@ export async function beginBridgeHandoff(options) {
55
55
  }
56
56
  export async function completeBridgeHandoff(session) {
57
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) {
58
96
  let payload;
59
97
  try {
60
98
  payload = decryptBridgeEnvelope(session.privateKey, envelope);
@@ -81,12 +119,15 @@ export async function completeBridgeHandoff(session) {
81
119
  },
82
120
  };
83
121
  }
122
+ function pollUrl(apiBase, sessionId) {
123
+ return `${apiBase}/v1/cli-handoff/${encodeURIComponent(sessionId)}/poll`;
124
+ }
84
125
  async function pollForCompletion(apiBase, sessionId, expiresAt) {
85
126
  let lastNetworkError;
86
127
  while (Date.now() < expiresAt) {
87
128
  let res;
88
129
  try {
89
- res = await apiFetch(`${apiBase}/v1/cli-handoff/${encodeURIComponent(sessionId)}/poll`, { method: "GET" });
130
+ res = await apiFetch(pollUrl(apiBase, sessionId), { method: "GET" });
90
131
  }
91
132
  catch (err) {
92
133
  if (err instanceof PlatformNetworkError) {
@@ -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,10 +194,12 @@ 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_link 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 " +
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 " +
201
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) {
package/dist/tools.d.ts CHANGED
@@ -26,7 +26,6 @@ interface ToolApi {
26
26
  }): void;
27
27
  }
28
28
  export declare function _resetToolsForTesting(): void;
29
- export declare function terminalSetupInstructions(): string;
30
29
  export declare function ownerRefusalText(reason: "known-non-owner" | "ambiguous"): string;
31
30
  export declare function formatChannelSetupResult(result: ChannelSetupResult): string;
32
31
  export declare function registerTools(api: ToolApi): void;
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,kBAAkB,EAIxB,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;AA6WD,wBAAgB,yBAAyB,IAAI,MAAM,CAiBlD;AAmBD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,GAAG,WAAW,GACtC,MAAM,CAqBR;AAGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAoC3E;AAcD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAmnChD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,oBAAoB,CAAC;AA0D5B,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;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"}
package/dist/tools.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import pluginManifest from "../openclaw.plugin.json" with { type: "json" };
3
3
  import { getOwnerPassportAddress } from "./channel.js";
4
- import { _resetChannelSetupForTesting, consumeChannelSetupStatus, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
4
+ import { _resetChannelSetupForTesting, getDefaultChannelSetupOptions, startOrGetChannelSetup, } from "./channel-setup.js";
5
5
  import { clearTargetHandle, extractNetworkConfig, getConnectorClient, getDmScope, getPendingTarget, hasApiKey, isProfileNeeded, markProfileComplete, 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";
@@ -142,17 +142,19 @@ function formatRegistration(outcome) {
142
142
  return "none";
143
143
  }
144
144
  }
145
+ function formatChannelStateLines(client) {
146
+ return [
147
+ `Gateway channel: ${client ? client.getChannelState() : "not established"}`,
148
+ `Registration (this connection): ${formatRegistration(client ? client.getRegistration() : { state: "none" })}`,
149
+ ];
150
+ }
145
151
  function formatReadinessSection() {
146
152
  const credentialsLine = hasApiKey()
147
153
  ? "Credentials: configured"
148
154
  : "Credentials: not configured";
149
- const client = getConnectorClient();
150
- const channel = client ? client.getChannelState() : "not established";
151
- const registration = formatRegistration(client ? client.getRegistration() : { state: "none" });
152
155
  return [
153
156
  credentialsLine,
154
- `Gateway channel: ${channel}`,
155
- `Registration (this connection): ${registration}`,
157
+ ...formatChannelStateLines(getConnectorClient()),
156
158
  ].join("\n");
157
159
  }
158
160
  async function resolveIdentitySection() {
@@ -222,24 +224,6 @@ function toRecord(value) {
222
224
  function stringValue(value) {
223
225
  return typeof value === "string" && value.length > 0 ? value : undefined;
224
226
  }
225
- export function terminalSetupInstructions() {
226
- return [
227
- "Run this in the terminal where OpenClaw is installed:",
228
- "",
229
- " openclaw channels login --channel agent-network",
230
- "",
231
- "It will:",
232
- " 1. Display a MASONS handoff URL.",
233
- " 2. Open it in your browser and sign in.",
234
- " 3. Pick an existing agent or create a new one.",
235
- " 4. Return to the terminal after the encrypted handoff completes.",
236
- " 5. Persist credentials to openclaw.json. OpenClaw reloads the channel.",
237
- "",
238
- "Note: on an already-linked install this is a Relink — choosing another Node here changes",
239
- "which Node this OpenClaw install drives. Each install holds its own runtime key, so",
240
- "relinking updates this OpenClaw install's credentials.",
241
- ].join("\n");
242
- }
243
227
  export function ownerRefusalText(reason) {
244
228
  if (reason === "known-non-owner") {
245
229
  return [
@@ -269,6 +253,7 @@ export function formatChannelSetupResult(result) {
269
253
  result.handoffUrl,
270
254
  "",
271
255
  "Open this link in your browser, sign in, choose an agent, then come back here.",
256
+ "This Link is still open, so asking me again returns this same URL, not a new one — open the one above.",
272
257
  "After browser sign-in and agent selection, OpenClaw will receive the encrypted handoff, persist the runtime key locally, and reload the channel.",
273
258
  "This URL is sensitive Link material; only the OpenClaw owner should open it.",
274
259
  ].join("\n");
@@ -278,24 +263,40 @@ export function formatChannelSetupResult(result) {
278
263
  result.message ?? "Agent Network Link completed.",
279
264
  "",
280
265
  "HTTP tools may work before the Gateway WebSocket is connected.",
281
- '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`.',
266
+ GATEWAY_RESTART_ACTION,
267
+ "This result stands until the owner asks to start over: to re-link or change Node, call masons_link with start_over: true.",
282
268
  ].join("\n");
283
269
  }
270
+ if (result.status === "linked") {
271
+ const receipt = result.message ?? "This Runtime is linked to an Agent Node.";
272
+ return [receipt, ...formatRealtimeGap()].join("\n");
273
+ }
284
274
  if (result.status === "expired") {
285
275
  return [
286
276
  result.message ?? "The previous Link URL expired.",
287
- "Ask again to generate a fresh owner-only Link URL, or use the terminal fallback:",
288
277
  "",
289
- terminalSetupInstructions(),
278
+ LINK_RETRY_GUIDANCE,
290
279
  ].join("\n");
291
280
  }
292
281
  return [
293
282
  result.message ?? "Agent Network Link failed.",
294
- "Ask again to retry, or use the terminal fallback:",
295
283
  "",
296
- terminalSetupInstructions(),
284
+ LINK_RETRY_GUIDANCE,
297
285
  ].join("\n");
298
286
  }
287
+ const GATEWAY_RESTART_ACTION = '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`.';
288
+ function formatRealtimeGap() {
289
+ const client = getConnectorClient();
290
+ if (client?.getChannelState() === "connected" &&
291
+ client.getRegistration().state === "accepted") {
292
+ return [];
293
+ }
294
+ return ["", ...formatChannelStateLines(client), GATEWAY_RESTART_ACTION];
295
+ }
296
+ const LINK_RETRY_GUIDANCE = [
297
+ "Call masons_link again in this conversation — the whole flow finishes here.",
298
+ "Last resort, only if that keeps failing: the owner can run `openclaw channels login --channel agent-network` in a terminal.",
299
+ ].join("\n");
299
300
  export function registerTools(api) {
300
301
  api.registerTool((ctx = {}) => ({
301
302
  name: "masons_link",
@@ -306,7 +307,12 @@ export function registerTools(api) {
306
307
  "Agent Network tools fail with a 'no credentials' error, or when the",
307
308
  "runtime owner wants a different MASONS agent driven by OpenClaw.",
308
309
  "On a Runtime that is already linked, present this to the user as",
309
- "Relink / Change Node — the same capability, not a second one.",
310
+ "Relink / Change Node — the same capability, not a second one. After a",
311
+ "Link has completed in this process, pass start_over: true to re-link",
312
+ "or link a different Node; a plain call re-reports the completed one.",
313
+ "To check on a Link in progress (after the owner opens the URL), call",
314
+ "with NO arguments — start_over would discard the pending Link and",
315
+ "issue a fresh URL instead of confirming the one the owner completed.",
310
316
  "",
311
317
  "Not to be confused with masons_link_identity: this tool binds a",
312
318
  "Runtime to a Node. masons_link_identity aliases your owner's identity",
@@ -319,7 +325,9 @@ export function registerTools(api) {
319
325
  "This tool returns a browser handoff URL on the channel where you are",
320
326
  "running. The user opens it, signs in, picks an agent, and the encrypted",
321
327
  "handoff completes in the background. After completion, restart the",
322
- "gateway to activate realtime messaging.",
328
+ "gateway to activate realtime messaging. When this Runtime already",
329
+ "holds a verified binding and no Link is pending, it reports that",
330
+ "standing binding instead of issuing a URL.",
323
331
  "",
324
332
  "If the user is reaching this agent through the MASONS network as a",
325
333
  "visitor (not the agent's owner), this tool refuses with an explanation",
@@ -333,6 +341,13 @@ export function registerTools(api) {
333
341
  "the plugin records this as a pending connection target so the " +
334
342
  "agent can propose a connection request automatically.",
335
343
  })),
344
+ start_over: Type.Optional(Type.Boolean({
345
+ description: "Discard whatever this Runtime is holding — a Link still in " +
346
+ "progress, a Link that already completed, or a binding already " +
347
+ "on disk — and issue a fresh Link URL. Without it a repeat call " +
348
+ "re-reports what stands, which after a Gateway restart is the " +
349
+ "standing binding rather than the completed result.",
350
+ })),
336
351
  }),
337
352
  execute: async (_id, params) => {
338
353
  const authority = resolveSetupAuthority();
@@ -342,15 +357,13 @@ export function registerTools(api) {
342
357
  const invitedBy = typeof params.invitedBy === "string" && params.invitedBy.length > 0
343
358
  ? params.invitedBy
344
359
  : undefined;
345
- const existing = consumeChannelSetupStatus();
346
360
  let result;
347
361
  try {
348
- result = existing
349
- ? existing
350
- : await startOrGetChannelSetup({
351
- ...getSetupOptionsFromToolContext(ctx),
352
- invitedBy,
353
- });
362
+ result = await startOrGetChannelSetup({
363
+ ...getSetupOptionsFromToolContext(ctx),
364
+ invitedBy,
365
+ startOver: params.start_over === true,
366
+ });
354
367
  }
355
368
  catch (err) {
356
369
  result = {
@@ -416,7 +429,7 @@ export function registerTools(api) {
416
429
  catch (err) {
417
430
  if (err instanceof PlatformApiError) {
418
431
  if (err.status === 401) {
419
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
432
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
420
433
  }
421
434
  if (err.status === 422) {
422
435
  return textResult(`Validation error: ${err.message}`);
@@ -497,7 +510,7 @@ export function registerTools(api) {
497
510
  : "This Services deployment does not serve the connection-request endpoint. Nothing was sent.");
498
511
  }
499
512
  if (err.status === 401) {
500
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
513
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
501
514
  }
502
515
  return textResult(`Connection request failed: ${err.message}`);
503
516
  }
@@ -581,7 +594,7 @@ export function registerTools(api) {
581
594
  catch (err) {
582
595
  if (err instanceof PlatformApiError) {
583
596
  if (err.status === 401) {
584
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
597
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
585
598
  }
586
599
  if (err.status === 404) {
587
600
  return textResult("This Services deployment does not serve the packaged connection-request endpoint. Nothing was read.");
@@ -769,7 +782,7 @@ export function registerTools(api) {
769
782
  catch (err) {
770
783
  if (err instanceof PlatformApiError) {
771
784
  if (err.status === 401) {
772
- return textResult("Authentication failed. The runtime key may be invalid. Ask the user to run `openclaw channels login --channel agent-network`.");
785
+ return textResult("Authentication failed. The runtime key may be invalid. Ask the owner to re-link here: call masons_link with start_over: true.");
773
786
  }
774
787
  return textResult(`Failed to list connections: ${err.message}`);
775
788
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.6.24";
1
+ export declare const PLUGIN_VERSION = "0.6.26";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.6.24";
1
+ export const PLUGIN_VERSION = "0.6.26";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.6.24",
3
+ "version": "0.6.26",
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)",
@@ -71,7 +71,7 @@ Check your current state and go to the right section:
71
71
 
72
72
  ## Link
73
73
 
74
- Binding this runtime to an agent Node takes about a minute. Link can run from an owner-verified channel through an encrypted browser handoff. Terminal login remains the fallback when owner authority is unavailable.
74
+ Binding this runtime to an agent Node takes about a minute. Link runs from an owner-verified channel through an encrypted browser handoff, and it finishes **in this conversation** — never send the owner to a terminal as your first move. Terminal login is the last resort, for when owner authority is unavailable here or Link keeps failing here.
75
75
 
76
76
  On a runtime that is already linked, this same flow is a **Relink / Change Node** — tell the user that, and use the same tool. There is no second capability.
77
77
 
@@ -83,11 +83,7 @@ On a runtime that is already linked, this same flow is a **Relink / Change Node*
83
83
 
84
84
  If the current sender is verified as the OpenClaw owner, the tool returns a MASONS handoff URL. Share the URL as ordinary chat text, on its own line, without wrapping it in backticks or a code block. Never ask for or display runtime keys, ciphertext payloads, decrypted token metadata, private key material, or debug output that looks credential-like.
85
85
 
86
- If owner authority is missing, false, or ambiguous, the tool returns the terminal fallback. Tell the user to run:
87
-
88
- ```sh
89
- openclaw channels login --channel agent-network
90
- ```
86
+ If owner authority is missing, false, or ambiguous, the tool refuses and explains why — relay that text. Where it names `openclaw channels login --channel agent-network`, that is the last resort for an owner with no channel their OpenClaw is configured for; try such a channel first.
91
87
 
92
88
  ### Step 2: User Completes Browser Handoff
93
89
 
@@ -97,7 +93,9 @@ The browser handoff page lets the owner sign in, select or create an agent, and
97
93
 
98
94
  After completion:
99
95
 
100
- 1. Call `masons_link` again to check whether the encrypted handoff completed.
96
+ 1. Call `masons_link` again to check whether the encrypted handoff completed. This call collects the owner's browser selection itself, so a step they already finished is confirmed here rather than costing a second browser walk. While the Link is still open the same call returns the SAME URL — pass it on, do not describe it as a new one.
97
+ - Some Hosts restart the Gateway the moment the runtime key is written, so a restart right after the owner says they finished is EXPECTED here — it is the success signal, not a fault.
98
+ - The restarted process confirms the Link as a **Linked** receipt (the present binding) instead of an **Accepted** one (the acceptance moment). Both mean the Link succeeded: relay it and move on, never re-issue a URL.
101
99
  2. If `masons_link` reports the Link completed, restart the Gateway to activate realtime messaging:
102
100
  - Prefer the host `gateway` tool with `action`: `"restart"` and `reason`: `"Activate Agent Network after Link"`.
103
101
  - If the `gateway` tool is unavailable, tell the user to run `openclaw gateway restart`.
@@ -110,10 +108,13 @@ After completion:
110
108
 
111
109
  ### Errors
112
110
 
113
- - If any tool returns an error, explain it simply and suggest next steps.
114
- - If the CLI says a handle is rejected or taken, ask the user to retry the login command and choose a different handle.
111
+ - If `masons_link` reports the Link expired or failed, that session is over: call `masons_link` again in this conversation. On a Runtime that still holds a working binding that call reports the standing binding, so add `start_over: true` to get a fresh URL. Do not offer the terminal on a first failure.
112
+ - If `masons_link` reports the Link completed or Linked and the user wants to re-link or change Node, call it with `start_over: true` — a plain call re-reports the current binding.
113
+ - Only after Link has failed here repeatedly, name `openclaw channels login --channel agent-network` as the last resort, and say so.
114
+ - If any other tool returns an error, explain it simply and suggest next steps.
115
+ - If the CLI says a handle is rejected or taken, ask the user to retry and choose a different handle.
115
116
  - If network errors, suggest checking internet connection.
116
- - If the tools still report no runtime key after login, read `references/troubleshooting.md`.
117
+ - If the tools still report no runtime key after a completed Link, read `references/troubleshooting.md`.
117
118
 
118
119
  ## Profile
119
120