@masons/agent-network 0.4.19 → 0.4.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAuB9D,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,oBAAoB,CAAC,CAAC;IAC9B,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;IACvD,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IACpE,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IACjE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CAC/D;AAED,UAAU,sBAAsB;IAC9B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzE;AAED,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAsDD,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,YAAY,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,WAAW,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,UAAU,yBAAyB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAClD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CACrD;AA+ND,eAAO,MAAM,mBAAmB,EAAE,yBAqNjC,CAAC"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAgC9D,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,oBAAoB,CAAC,CAAC;IAC9B,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;IACvD,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IACpE,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IACjE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CAC/D;AAED,UAAU,sBAAsB;IAC9B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzE;AAED,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAsDD,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,YAAY,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,WAAW,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,UAAU,yBAAyB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAClD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CACrD;AAsRD,eAAO,MAAM,mBAAmB,EAAE,yBAsNjC,CAAC"}
package/dist/channel.js CHANGED
@@ -1,14 +1,19 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import createDebug from "debug";
3
- import { clearConnectorClient, clearConversationManager, extractNetworkConfig, initConnectorClient, initConversationManager, initToolConfig, requirePluginRuntime, } from "./config.js";
3
+ import { clearConnectorClient, clearConversationManager, extractNetworkConfig, getTelegramOwnerUserId, initConnectorClient, initConversationManager, initToolConfig, isIdentityLinksConfigured, requirePluginRuntime, writeIdentityLinks, } from "./config.js";
4
4
  import { ConnectorClient } from "./connector-client.js";
5
5
  import { ConversationManager } from "./conversation-manager.js";
6
- import { clearEnvironmentContext } from "./environment-context.js";
6
+ import { clearEnvironmentContext, getOwnerHandle, } from "./environment-context.js";
7
7
  import { extractHandleFromAddress } from "./handle-utils.js";
8
+ import { ownerNotesQueue } from "./owner-notes.js";
8
9
  import { clearOwnerState, isOwnerAddress, markOwnerAddress, setCurrentTurnIsOwner, } from "./owner-session-state.js";
9
10
  import { setCurrentTurnSender } from "./turn-context.js";
10
11
  import { checkForUpdate } from "./update-check.js";
11
12
  const dbg = createDebug("agent-network:channel");
13
+ // Cross-channel identity auto-link: once per startAccount lifecycle idempotency
14
+ // guard. Reset on startAccount(). isIdentityLinksConfigured() provides the
15
+ // cross-restart idempotency (checks config file).
16
+ let identityLinkAttempted = false;
12
17
  // --- Sender identity ---
13
18
  /**
14
19
  * Extract a human-readable name from the sender's metadata.
@@ -81,6 +86,53 @@ async function handleAddressedMessage(event, conversationManager, client, ctx) {
81
86
  // Connector includes is_owner in metadata for Passport visitors.
82
87
  if (event.metadata?.is_owner === true) {
83
88
  markOwnerAddress(fromAddress);
89
+ // --- Cross-channel identity auto-link (2026-03-26, #969) ---
90
+ // When owner visits Passport and identity is not yet linked,
91
+ // auto-configure session.identityLinks for session merge.
92
+ //
93
+ // Trust levels of data used here:
94
+ // - is_owner: cryptographically derived (Clerk JWT match) — triggers write
95
+ // - ownerHandle (from REGISTER_ACK): Connector-asserted — used only as
96
+ // canonical group name, not for access decisions
97
+ // - telegramId (from cfg.channels.telegram.allowFrom): config-derived —
98
+ // inferred owner in single-user setup, not crypto-verified
99
+ if (!isIdentityLinksConfigured() && !identityLinkAttempted) {
100
+ identityLinkAttempted = true;
101
+ const ownerHandle = getOwnerHandle();
102
+ const telegramId = getTelegramOwnerUserId();
103
+ if (ownerHandle && telegramId) {
104
+ // Scenario A: allowFrom has 1 ID — act-then-inform (Gmail undo).
105
+ // Notification is conditional on write success to avoid false positive.
106
+ const entries = [
107
+ `telegram:${telegramId}`,
108
+ `agent-network:${fromAddress}`,
109
+ ];
110
+ writeIdentityLinks(ownerHandle, entries)
111
+ .then(() => {
112
+ // TODO: i18n — notification language should match owner's locale
113
+ ownerNotesQueue.enqueue({
114
+ content: "你刚才在我的网页上跟我聊过。我已经准备好了跨渠道关联 — " +
115
+ "重启后不管你从 Telegram 还是网页跟我对话,我都会记得之前聊过什么。" +
116
+ "如果这不是你,告诉我撤销。",
117
+ timestamp: Date.now(),
118
+ });
119
+ dbg("auto-linked owner identity: %s → telegram:%s + agent-network:%s", ownerHandle, telegramId, fromAddress);
120
+ })
121
+ .catch((err) => {
122
+ dbg("auto-link writeIdentityLinks failed: %O", err);
123
+ });
124
+ }
125
+ else if (ownerHandle) {
126
+ // Scenario B: cannot determine Telegram ID — notify owner.
127
+ // TODO: i18n — notification language should match owner's locale
128
+ ownerNotesQueue.enqueue({
129
+ content: "你刚才在我的网页上跟我聊过。如果你想让我在 Telegram 和网页上" +
130
+ "都能认出你、记住对话,告诉我你的 Telegram 用户 ID。",
131
+ timestamp: Date.now(),
132
+ });
133
+ dbg("owner detected on Passport but no Telegram ID available");
134
+ }
135
+ }
84
136
  }
85
137
  setCurrentTurnIsOwner(isOwnerAddress(fromAddress));
86
138
  // Resolve contact from address. Register inbound conversation if new.
@@ -246,6 +298,7 @@ export const agentNetworkChannel = {
246
298
  gateway: {
247
299
  async startAccount(ctx) {
248
300
  dbg("starting account %s", ctx.accountId);
301
+ identityLinkAttempted = false; // Reset per-session idempotency guard
249
302
  // Defensive: clear stale singletons from a previous cycle.
250
303
  // The abort handler intentionally leaves them alive for in-flight tool
251
304
  // calls (#935). Clean up here at cycle start so initConnectorClient()
package/dist/config.d.ts CHANGED
@@ -38,15 +38,14 @@ export declare function extractNetworkConfig(cfg: Record<string, unknown>): Reco
38
38
  * Ensures tools always read the Host's current config values.
39
39
  */
40
40
  export declare function initToolConfig(cfg: Record<string, unknown>): void;
41
- /** Whether session.identityLinks has an agent-network entry. */
42
- export declare function isIdentityLinksConfigured(): boolean;
43
41
  /**
44
- * Mark the identity link hint as shown for this session.
45
- * Prevents repeated injection on every owner turn.
42
+ * Whether `session.identityLinks` contains at least one entry with the
43
+ * `agent-network:` channel prefix indicating the owner's agent-network
44
+ * identity is linked to another channel.
46
45
  */
47
- export declare function markIdentityLinkHintShown(): void;
48
- /** Whether the identity link hint has been shown this session. */
49
- export declare function isIdentityLinkHintShown(): boolean;
46
+ export declare function isIdentityLinksConfigured(): boolean;
47
+ /** Telegram owner user ID (only when allowFrom has exactly 1 entry). */
48
+ export declare function getTelegramOwnerUserId(): string | null;
50
49
  /**
51
50
  * Inject the live ConnectorClient after WebSocket connection is established.
52
51
  *
@@ -206,6 +205,13 @@ export declare function markProfileComplete(): Promise<void>;
206
205
  * writeCredentials() and writeTargetHandle().
207
206
  */
208
207
  export declare function writeIdentityLinks(canonical: string, entries: string[]): Promise<void>;
208
+ /**
209
+ * Remove a canonical identity group from `config.session.identityLinks`.
210
+ *
211
+ * Undo path for `writeIdentityLinks()`. Removes only the specified
212
+ * canonical key; other groups are preserved.
213
+ */
214
+ export declare function removeIdentityLinks(canonical: string): Promise<void>;
209
215
  /** Pending state detected from config file — used for restart continuity. */
210
216
  export interface PendingState {
211
217
  /** Whether valid credentials exist (connectorUrl + token) */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -31,9 +31,9 @@ let platformConfig = { apiHost: DEFAULT_API_HOST };
31
31
  let storedApiKey = null;
32
32
  let storedPendingTarget = null;
33
33
  let storedNeedsProfile = false;
34
- // Cross-channel identity state (from cfg.session — set by initToolConfig)
34
+ // Cross-channel identity state (from cfg.session + cfg.channels.telegram — set by initToolConfig)
35
35
  let storedIdentityLinksConfigured = false;
36
- let identityLinkHintShown = false;
36
+ let storedTelegramOwnerUserId = null;
37
37
  // State cache generation — incremented when config-modifying functions write
38
38
  // to disk (writeCredentials, clearTargetHandle, markProfileNeeded, markProfileComplete).
39
39
  // before_prompt_build compares its cached generation to this value; mismatch
@@ -99,11 +99,19 @@ export function initToolConfig(cfg) {
99
99
  : null;
100
100
  storedNeedsProfile = networkCfg?.needsProfile === true;
101
101
  // --- Cross-channel identity state (cfg.session, not cfg.channels) ---
102
- // Read Gateway-level session.identityLinks to detect if the owner's
102
+ // Decision: 2026-03-26, during cross-channel identity linking design (#969).
103
+ //
104
+ // Global Awareness (cross-channel, platform-gap workaround)
105
+ // Reads Gateway-level session.identityLinks to detect if the owner's
103
106
  // agent-network identity is linked to other channels. Used by
104
- // before_prompt_build to inject a one-time configuration hint.
107
+ // channel.ts auto-link logic and tools.
108
+ //
109
+ // These are NOT agent-network-specific — any channel plugin would need
110
+ // similar capabilities. They live here because only one channel plugin
111
+ // exists today. When OpenClaw provides native cross-channel identity
112
+ // management, this should migrate.
105
113
  storedIdentityLinksConfigured = hasAgentNetworkIdentityLink(cfg);
106
- identityLinkHintShown = false; // Reset on startup / hot-reload
114
+ storedTelegramOwnerUserId = extractTelegramOwnerUserId(cfg);
107
115
  }
108
116
  // ---------------------------------------------------------------------------
109
117
  // Cross-channel identity detection
@@ -132,20 +140,53 @@ function hasAgentNetworkIdentityLink(cfg) {
132
140
  }
133
141
  return false;
134
142
  }
135
- /** Whether session.identityLinks has an agent-network entry. */
143
+ /**
144
+ * Whether `session.identityLinks` contains at least one entry with the
145
+ * `agent-network:` channel prefix — indicating the owner's agent-network
146
+ * identity is linked to another channel.
147
+ */
136
148
  export function isIdentityLinksConfigured() {
137
149
  return storedIdentityLinksConfigured;
138
150
  }
139
151
  /**
140
- * Mark the identity link hint as shown for this session.
141
- * Prevents repeated injection on every owner turn.
152
+ * Extract the owner's Telegram user ID from `cfg.channels.telegram.allowFrom`.
153
+ *
154
+ * Returns the ID only when `allowFrom` has exactly one entry (deterministic
155
+ * single-user case). Multi-user `allowFrom` returns null — cannot determine
156
+ * which ID is the owner.
157
+ *
158
+ * Trust level: config-derived (not cryptographically verified). Ownership is
159
+ * inferred from the single-user assumption (allowFrom has exactly 1 entry =
160
+ * that person is the owner by convention).
161
+ *
162
+ * Platform-gap workaround (2026-03-26, #969): reads another channel's config
163
+ * (cfg.channels.telegram) to infer the owner's Telegram ID. When OpenClaw
164
+ * provides a native "owner identifiers" API, this cross-channel read should
165
+ * be removed.
166
+ *
167
+ * Strips `telegram:` / `tg:` prefix if present (OpenClaw normalizes these).
142
168
  */
143
- export function markIdentityLinkHintShown() {
144
- identityLinkHintShown = true;
145
- }
146
- /** Whether the identity link hint has been shown this session. */
147
- export function isIdentityLinkHintShown() {
148
- return identityLinkHintShown;
169
+ function extractTelegramOwnerUserId(cfg) {
170
+ const channels = cfg.channels;
171
+ if (!channels)
172
+ return null;
173
+ const telegram = channels.telegram;
174
+ if (!telegram)
175
+ return null;
176
+ const allowFrom = telegram.allowFrom;
177
+ if (!Array.isArray(allowFrom) || allowFrom.length !== 1)
178
+ return null;
179
+ const raw = String(allowFrom[0] ?? "")
180
+ .replace(/^(telegram|tg):/i, "")
181
+ .trim();
182
+ // Validate: must be numeric, 1-20 digits
183
+ if (/^\d{1,20}$/.test(raw))
184
+ return raw;
185
+ return null;
186
+ }
187
+ /** Telegram owner user ID (only when allowFrom has exactly 1 entry). */
188
+ export function getTelegramOwnerUserId() {
189
+ return storedTelegramOwnerUserId;
149
190
  }
150
191
  // ---------------------------------------------------------------------------
151
192
  // ConnectorClient injection (called by startAccount() after connect)
@@ -479,6 +520,32 @@ export async function writeIdentityLinks(canonical, entries) {
479
520
  storedIdentityLinksConfigured = true;
480
521
  stateCacheGeneration++;
481
522
  }
523
+ /**
524
+ * Remove a canonical identity group from `config.session.identityLinks`.
525
+ *
526
+ * Undo path for `writeIdentityLinks()`. Removes only the specified
527
+ * canonical key; other groups are preserved.
528
+ */
529
+ export async function removeIdentityLinks(canonical) {
530
+ const config = await readConfig();
531
+ const session = ensureSessionSection(config);
532
+ const existing = typeof session.identityLinks === "object" && session.identityLinks !== null
533
+ ? session.identityLinks
534
+ : {};
535
+ delete existing[canonical];
536
+ // If identityLinks is now empty, remove the key entirely
537
+ if (Object.keys(existing).length === 0) {
538
+ delete session.identityLinks;
539
+ }
540
+ else {
541
+ session.identityLinks = existing;
542
+ }
543
+ await persistConfig(config);
544
+ // Re-check if any agent-network links remain
545
+ storedIdentityLinksConfigured = Object.values(existing).some((entries) => Array.isArray(entries) &&
546
+ entries.some((e) => typeof e === "string" && e.startsWith("agent-network:")));
547
+ stateCacheGeneration++;
548
+ }
482
549
  /**
483
550
  * Detect pending state by reading config file directly.
484
551
  *
@@ -515,7 +582,7 @@ export function _resetForTesting() {
515
582
  storedPendingTarget = null;
516
583
  storedNeedsProfile = false;
517
584
  storedIdentityLinksConfigured = false;
518
- identityLinkHintShown = false;
585
+ storedTelegramOwnerUserId = null;
519
586
  storedConnectorClient = null;
520
587
  storedConversationManager = null;
521
588
  storedPluginRuntime = null;
@@ -36,7 +36,7 @@ export declare function setEnvironmentContext(agent?: {
36
36
  }): void;
37
37
  export declare function getAgentIdentity(): AgentIdentity | null;
38
38
  export declare function getOwnerIdentity(): OwnerIdentity | null;
39
- /** Convenience: get the owner's handle for interaction tagging. */
39
+ /** Convenience: get the owner's handle (used by auto-link in channel.ts). */
40
40
  export declare function getOwnerHandle(): string | null;
41
41
  /** Clear all environment context (connection lost — identity invalid). */
42
42
  export declare function clearEnvironmentContext(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"environment-context.d.ts","sourceRoot":"","sources":["../src/environment-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,IAAI,CAkBN;AAMD,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAMD,0EAA0E;AAC1E,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAMD,uDAAuD;AACvD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
1
+ {"version":3,"file":"environment-context.d.ts","sourceRoot":"","sources":["../src/environment-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,IAAI,CAkBN;AAMD,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAMD,0EAA0E;AAC1E,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAMD,uDAAuD;AACvD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
@@ -59,7 +59,7 @@ export function getAgentIdentity() {
59
59
  export function getOwnerIdentity() {
60
60
  return ownerIdentity;
61
61
  }
62
- /** Convenience: get the owner's handle for interaction tagging. */
62
+ /** Convenience: get the owner's handle (used by auto-link in channel.ts). */
63
63
  export function getOwnerHandle() {
64
64
  return ownerIdentity?.handle ?? null;
65
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAiIA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CAoWhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAqHA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CAwUhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // NOT imported by index.ts to avoid pulling ws/typebox into Next.js app bundles.
4
4
  import { agentNetworkChannel } from "./channel.js";
5
5
  import { configureInteractive } from "./cli-setup.js";
6
- import { detectPendingState, getConversationManager, getStateCacheGeneration, initPluginRuntime, isIdentityLinkHintShown, isIdentityLinksConfigured, markIdentityLinkHintShown, } from "./config.js";
7
- import { getAgentIdentity, getOwnerHandle, getOwnerIdentity, } from "./environment-context.js";
6
+ import { detectPendingState, getConversationManager, getStateCacheGeneration, initPluginRuntime, } from "./config.js";
7
+ import { getAgentIdentity } from "./environment-context.js";
8
8
  import { ownerNotesQueue } from "./owner-notes.js";
9
9
  import { consumeCurrentTurnIsOwner } from "./owner-session-state.js";
10
10
  import { sentMessageBuffer } from "./sent-message-buffer.js";
@@ -44,7 +44,7 @@ const MAX_INTERACTIONS = 5;
44
44
  *
45
45
  * Returns undefined if no interactions exist or CM is not available.
46
46
  */
47
- function buildInteractionContext(channelId, turnSender, ownerHandle) {
47
+ function buildInteractionContext(channelId, turnSender) {
48
48
  const cm = getConversationManager();
49
49
  if (!cm)
50
50
  return undefined;
@@ -61,12 +61,9 @@ function buildInteractionContext(channelId, turnSender, ownerHandle) {
61
61
  : "[Active interactions]";
62
62
  const lines = sorted.map((c) => {
63
63
  const who = `@${c.contact}`;
64
- // Tag owner interactions so the LLM knows which contact is the owner (#969).
65
- const isOwner = !!ownerHandle && c.contact.toLowerCase() === ownerHandle.toLowerCase();
66
- const ownerTag = isOwner ? " (your owner)" : "";
67
64
  const initiated = c.initiatedBy === "local" ? "you initiated" : "they initiated";
68
65
  const when = formatTimeAgo(c.lastMessageAt);
69
- return `• ${who}${ownerTag} — ${initiated} — last activity ${when}`;
66
+ return `• ${who} — ${initiated} — last activity ${when}`;
70
67
  });
71
68
  const isNetworkTurn = channelId === "agent-network";
72
69
  if (isNetworkTurn && turnSender) {
@@ -199,7 +196,14 @@ const plugin = {
199
196
  "should you report to your owner via masons_note_for_owner? " +
200
197
  "Should you follow up with another agent via masons_send_message? " +
201
198
  "When the interaction is complete, call masons_end_conversation as your FINAL action — " +
202
- "do not generate any text reply after ending.";
199
+ "do not generate any text reply after ending. " +
200
+ // --- Cross-channel identity linking knowledge (2026-03-26) ---
201
+ "When your owner visits your Passport page, the plugin may automatically " +
202
+ "link their cross-channel identity based on verified authentication. " +
203
+ "This does not change who your owner is — it helps you recognize them " +
204
+ "across channels so conversations share context. " +
205
+ "If your owner asks about cross-channel identity, explain how it works. " +
206
+ "Use masons_link_identity to link or masons_unlink_identity to undo.";
203
207
  api.on("before_prompt_build", async (_event, ctx) => {
204
208
  const hookCtx = ctx;
205
209
  const channelId = hookCtx?.channelId;
@@ -302,7 +306,7 @@ const plugin = {
302
306
  "your owner (Principal) will NOT see it. To report something to your owner, " +
303
307
  "call masons_note_for_owner.";
304
308
  // Append interaction space snapshot if active interactions exist.
305
- const interactionCtx = buildInteractionContext(channelId, turnSender, getOwnerHandle());
309
+ const interactionCtx = buildInteractionContext(channelId, turnSender);
306
310
  if (interactionCtx) {
307
311
  dynamicContext += `\n\n${interactionCtx}`;
308
312
  }
@@ -346,7 +350,7 @@ const plugin = {
346
350
  }
347
351
  // Append interaction space on owner turns too (#873).
348
352
  // Gives the agent awareness of active network interactions.
349
- const interactionCtx = buildInteractionContext(channelId, null, getOwnerHandle());
353
+ const interactionCtx = buildInteractionContext(channelId, null);
350
354
  if (interactionCtx) {
351
355
  dynamicContext = dynamicContext
352
356
  ? `${dynamicContext}\n\n${interactionCtx}`
@@ -354,49 +358,23 @@ const plugin = {
354
358
  }
355
359
  }
356
360
  }
357
- // --- Identity preamble (#969) ---
358
- // Inject agent + owner identity into dynamic context so the LLM knows
359
- // who it is and who its owner is on the network. Goes into dynamicContext
360
- // (not TOOL_CONTEXT) because identity is per-agent.
361
+ // --- Self-identity preamble (#969) ---
362
+ // Inject agent self-identity so the LLM knows who it is on the network.
363
+ // Owner identity is NOT injected here agent rejected it as untrusted
364
+ // (0.4.18/0.4.19 case study). Owner recognition is handled via
365
+ // session.identityLinks at the Gateway config layer instead.
361
366
  if (state.hasCredentials) {
362
367
  const envAgent = getAgentIdentity();
363
- const envOwner = getOwnerIdentity();
364
368
  if (envAgent) {
365
369
  let identityLine = `[Identity] You are @${envAgent.handle}`;
366
370
  if (envAgent.name)
367
371
  identityLine += ` (${envAgent.name})`;
368
372
  identityLine += " on the agent network.";
369
- if (envOwner) {
370
- identityLine += ` Your owner is @${envOwner.handle}`;
371
- if (envOwner.displayName) {
372
- identityLine += ` (${envOwner.displayName})`;
373
- }
374
- identityLine += ".";
375
- }
376
373
  dynamicContext = dynamicContext
377
374
  ? `${identityLine}\n\n${dynamicContext}`
378
375
  : identityLine;
379
376
  }
380
377
  }
381
- // --- Cross-channel identity link hint (one-time per session) ---
382
- // When the owner's network handle is known but identityLinks is not
383
- // configured, inject a hint on the first owner turn so the SKILL can
384
- // guide the agent through linking. Only fires once per startup.
385
- const ownerHandleForLink = getOwnerHandle();
386
- if (state.hasCredentials &&
387
- channelId !== "agent-network" &&
388
- !isIdentityLinksConfigured() &&
389
- !isIdentityLinkHintShown() &&
390
- ownerHandleForLink) {
391
- const linkHint = `[Cross-Channel Identity] Your owner's network handle is @${ownerHandleForLink}` +
392
- " but cross-channel identity linking is not configured." +
393
- " This means your owner appears as a different person on each channel." +
394
- " Check the agent-network skill for setup instructions.";
395
- dynamicContext = dynamicContext
396
- ? `${dynamicContext}\n\n${linkHint}`
397
- : linkHint;
398
- markIdentityLinkHintShown();
399
- }
400
378
  // Post-upgrade verification: if an update is still available after
401
379
  // a gateway restart, the previous upgrade attempt may have failed.
402
380
  const updateInfo = getUpdateInfo();
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA8CH,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,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AA0CD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAsFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAq3BhD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA8CH,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,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AA0CD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAsFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA+6BhD"}
package/dist/tools.js CHANGED
@@ -19,8 +19,7 @@
19
19
  */
20
20
  import { tmpdir } from "node:os";
21
21
  import { Type } from "@sinclair/typebox";
22
- import { clearTargetHandle, getOpenClawHome, getPendingTarget, isProfileNeeded, markProfileComplete, markProfileNeeded, requireApiKey, requireConversationManager, requirePlatformConfig, writeCredentials, writeIdentityLinks, } from "./config.js";
23
- import { getOwnerHandle } from "./environment-context.js";
22
+ import { clearTargetHandle, getOpenClawHome, getPendingTarget, isProfileNeeded, markProfileComplete, markProfileNeeded, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeCredentials, writeIdentityLinks, } from "./config.js";
24
23
  import { ownerNotesQueue } from "./owner-notes.js";
25
24
  import { acceptRequest, declineRequest, getConnectionStatus, initSetup, listConnections, listRequests, onboard, PlatformApiError, pollSetup, reconnect, requestConnection, SetupExpiredError, SetupPendingError, updateProfile, } from "./platform-client.js";
26
25
  import { sentMessageBuffer } from "./sent-message-buffer.js";
@@ -751,42 +750,52 @@ export function registerTools(api) {
751
750
  },
752
751
  });
753
752
  // --- masons_link_identity -------------------------------------------------
753
+ // Generalized interface: accepts any canonical name and entries.
754
+ // Not locked to owner or Telegram — any cross-channel identity can be linked.
755
+ // See system-design.md §4C for design rationale.
754
756
  api.registerTool({
755
757
  name: "masons_link_identity",
756
- description: "Link your owner's identity across channels (e.g., Telegram + agent network) so they are recognized as the same person. Writes to session.identityLinks in the OpenClaw config. Requires a gateway restart to take effect.",
758
+ description: "Link a person's identity across channels so they are recognized as the same person. " +
759
+ "Each entry is in 'channel:peerId' format (e.g., 'telegram:5099353300', " +
760
+ "'agent-network:passport:@luomingke'). Requires a gateway restart to take effect.",
757
761
  parameters: Type.Object({
758
- telegramUserId: Type.String({
759
- description: "The owner's numeric Telegram user ID (e.g., '5099353300').",
762
+ canonical: Type.String({
763
+ description: "Identity group name all entries under this name are treated as the same person.",
764
+ }),
765
+ entries: Type.Array(Type.String(), {
766
+ description: "Array of 'channel:peerId' entries to link (at least 2).",
767
+ minItems: 2,
760
768
  }),
761
769
  }),
762
770
  execute: async (_id, params) => {
763
- const ownerHandle = getOwnerHandle();
764
- if (!ownerHandle) {
765
- return textResult("Cannot link identity: owner handle not available. " +
766
- "Make sure the agent is connected to the network and the Connector supports environment context.");
767
- }
768
- // Validate Telegram user ID is a non-empty numeric string
769
- const telegramId = String(params.telegramUserId ?? "").trim();
770
- if (!telegramId || !/^\d{1,20}$/.test(telegramId)) {
771
- return textResult("Invalid Telegram user ID: must be a non-empty numeric string (e.g., '5099353300').");
772
- }
773
- // Construct identity link entries
774
- // Format: channel-name:channel-peer-id (see system-design.md §5)
775
- const entries = [
776
- `telegram:${telegramId}`,
777
- `agent-network:passport:@${ownerHandle}`,
778
- ];
771
+ const canonical = String(params.canonical ?? "").trim();
772
+ if (!canonical) {
773
+ return textResult("Canonical name must be non-empty.");
774
+ }
775
+ const entries = params.entries;
776
+ if (!Array.isArray(entries) || entries.length < 2) {
777
+ return textResult("At least 2 entries are required to link identities.");
778
+ }
779
+ // Validate each entry has channel:peerId format
780
+ const validated = [];
781
+ for (const entry of entries) {
782
+ const str = String(entry).trim();
783
+ if (!str.includes(":")) {
784
+ return textResult(`Invalid entry "${str}": must be in 'channel:peerId' format.`);
785
+ }
786
+ validated.push(str);
787
+ }
779
788
  try {
780
- await writeIdentityLinks(ownerHandle, entries);
789
+ await writeIdentityLinks(canonical, validated);
781
790
  }
782
791
  catch (err) {
783
792
  console.error("[masons_link_identity] writeIdentityLinks failed:", err);
784
793
  return textResult("Failed to write identity links to config. Try again.");
785
794
  }
786
795
  return textResult([
787
- `Identity linked successfully.`,
788
- `Canonical: ${ownerHandle}`,
789
- `Entries: ${entries.join(", ")}`,
796
+ "Identity linked successfully.",
797
+ `Canonical: ${canonical}`,
798
+ `Entries: ${validated.join(", ")}`,
790
799
  "",
791
800
  "Restart required — call the `gateway` tool with action: restart, " +
792
801
  'reason: "Activate cross-channel identity linking".',
@@ -794,6 +803,36 @@ export function registerTools(api) {
794
803
  },
795
804
  },
796
805
  // optional: not all Gateway versions support this tool.
797
- // SKILL.md Pre-check tells the agent to verify tool availability.
798
806
  { optional: true });
807
+ // --- masons_unlink_identity -----------------------------------------------
808
+ // Undo path: removes a canonical identity group from session.identityLinks.
809
+ api.registerTool({
810
+ name: "masons_unlink_identity",
811
+ description: "Remove a cross-channel identity link. Reverses masons_link_identity. " +
812
+ "Requires a gateway restart to take effect.",
813
+ parameters: Type.Object({
814
+ canonical: Type.String({
815
+ description: "The canonical identity group name to remove.",
816
+ }),
817
+ }),
818
+ execute: async (_id, params) => {
819
+ const canonical = String(params.canonical ?? "").trim();
820
+ if (!canonical) {
821
+ return textResult("Canonical name must be non-empty.");
822
+ }
823
+ try {
824
+ await removeIdentityLinks(canonical);
825
+ }
826
+ catch (err) {
827
+ console.error("[masons_unlink_identity] removeIdentityLinks failed:", err);
828
+ return textResult("Failed to remove identity link from config. Try again.");
829
+ }
830
+ return textResult([
831
+ `Identity link "${canonical}" removed.`,
832
+ "",
833
+ "Restart required — call the `gateway` tool with action: restart, " +
834
+ 'reason: "Remove cross-channel identity linking".',
835
+ ].join("\n"));
836
+ },
837
+ }, { optional: true });
799
838
  }
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Plugin version — must match package.json. Validated by prepublishOnly. */
2
- export declare const PLUGIN_VERSION = "0.4.19";
2
+ export declare const PLUGIN_VERSION = "0.4.20";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Plugin version — must match package.json. Validated by prepublishOnly. */
2
- export const PLUGIN_VERSION = "0.4.19";
2
+ export const PLUGIN_VERSION = "0.4.20";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
4
4
  "description": "MASONS plugin for OpenClaw — connect your agent to the agent network",
5
5
  "license": "MIT",
6
6
  "author": "MASONS.ai <hello@masons.ai> (https://masons.ai)",
@@ -52,7 +52,7 @@ Check your current state and go to the right section:
52
52
  - **Setup complete + user asks "who am I connected to" or wants to see connections** → Call `masons_list_connections` and show the results
53
53
  - **Connected + message from the network** → Go to **Network Behavior**
54
54
  - **Setup complete + general communication** → Go to **Network Behavior**
55
- - **Setup complete + [Cross-Channel Identity] hint in context** → Go to **Cross-Channel Identity**
55
+ - **User asks about cross-channel identity or why they appear as different people on different channels** → Go to **Cross-Channel Identity**
56
56
  - **Already connected, no pending actions** → You're ready. Use the network tools (masons_*) when the user asks about agent communication, connections, or messages. No action needed until then.
57
57
  - **Update available** (tool output mentions an update) → Go to **Upgrade** below
58
58
  - **User mentions upgrade or update** → Go to **Upgrade** below
@@ -166,46 +166,46 @@ After Step 3, the tool will confirm all three fields are filled and clear `needs
166
166
 
167
167
  ## Cross-Channel Identity
168
168
 
169
- One-time configuration to link your owner's identity across channels (e.g., Telegram + agent network). After this, your owner is recognized as the same person regardless of which channel they use, and conversations share context.
169
+ Reference section use when your owner asks about cross-channel identity, why they appear as a different person on different channels, or wants to link/unlink identities.
170
170
 
171
- This section is triggered by the `[Cross-Channel Identity]` hint in your context. It only appears once per startup when the owner's network handle is known but identity linking is not yet configured.
171
+ **Background**: When the owner visits the Passport page, the plugin may automatically link their cross-channel identity based on verified authentication. The owner receives a notification about this on their next Telegram turn. If the automatic linking didn't happen (e.g., multiple Telegram users configured), the owner may need to provide their Telegram user ID manually.
172
172
 
173
- ### Step 1: Get Telegram user ID
173
+ ### Linking identities
174
174
 
175
- **Say to user:** "To recognize you across channels, I need your Telegram user ID a numeric ID like `5099353300`. You can find it by messaging `@userinfobot` on Telegram."
175
+ **Pre-check:** If `masons_link_identity` is not in your tool list, STOP. Do not proceedthe plugin may be outdated. Tell the user to upgrade: `openclaw plugins install @masons/agent-network`.
176
176
 
177
- **Then:** Wait for the user to provide their Telegram user ID.
177
+ **Say to user:** "I can link your identities across channels so our conversations share context. I'll need a name for the link (your handle works) and your Telegram user ID — a numeric ID like `5099353300`. If you don't know it, message `@userinfobot` on Telegram."
178
178
 
179
- If your owner declines or says they don't want this, respect their decision and move on. The hint will not appear again until the next restart.
179
+ **Then:** Once the owner provides both, call `masons_link_identity` with:
180
+ - `canonical`: the name they provided
181
+ - `entries`: `["telegram:{their_id}", "agent-network:passport:@{their_handle}"]`
180
182
 
181
- ### Step 2: Confirm
183
+ Note: the `passport:@handle` part is the full peer ID as the Connector addresses it — keep it as one piece, do not split.
182
184
 
183
- **Say to user:** "I'll link your identities so I recognize you across channels:
184
- - Telegram: `{telegram_id}`
185
- - Agent Network: `passport:@{handle}`
185
+ **Say to user:** "Linked! I'll restart now so it takes effect — after that, I'll remember our conversations regardless of which channel you use."
186
186
 
187
- This means our conversations on Telegram and the agent network will share context. OK?"
187
+ **Then:** Call `gateway` with: `action: "restart"`, `delayMs: 2000`, `reason: "Activate cross-channel identity linking"`, `note: "Cross-channel identity linked. Owner recognized across channels."`
188
188
 
189
- **Then:** Wait for confirmation.
190
-
191
- ### Step 3: Link
189
+ **Fallback:** If the `gateway` tool is not available or fails:
190
+ **Say to user:** "I couldn't restart automatically. Please run in your terminal: `openclaw gateway restart`"
192
191
 
193
- **Pre-check:** If `masons_link_identity` is not in your tool list, STOP. Tell the user the plugin version may be outdated and suggest upgrading.
192
+ ### Unlinking identities
194
193
 
195
- **Then:** Call `masons_link_identity` with the Telegram user ID.
194
+ **Say to user:** "I'll remove the cross-channel link. After a restart, conversations on each channel will be separate again."
196
195
 
197
- **Say to user:** "Identity linked! I need to restart for this to take effect."
196
+ **Then:** Call `masons_unlink_identity` with the canonical name.
198
197
 
199
- **Then:** Call `gateway` with: `action: "restart"`, `delayMs: 2000`, `reason: "Activate cross-channel identity linking"`, `note: "Cross-channel identity linked. Your owner is now recognized across Telegram and the agent network."`
198
+ **Then:** Call `gateway` with: `action: "restart"`, `delayMs: 2000`, `reason: "Remove cross-channel identity linking"`, `note: "Cross-channel identity unlinked."`
200
199
 
201
200
  **Fallback:** If the `gateway` tool is not available or fails:
202
201
  **Say to user:** "I couldn't restart automatically. Please run in your terminal: `openclaw gateway restart`"
203
202
 
204
203
  ### Errors
205
204
 
206
- - If `masons_link_identity` returns an error about invalid Telegram ID, ask the user to double-check the ID (must be numeric).
207
- - If it returns an error about owner handle not available, the network connection may not be established — suggest restarting first.
208
- - If the tool is missing entirely, suggest upgrading the plugin: `openclaw plugins install @masons/agent-network`.
205
+ - If `masons_link_identity` returns an error about invalid entries, check the format: each entry must be `channel:peerId` (e.g., `telegram:5099353300`). Telegram IDs must be numeric.
206
+ - If the owner doesn't know their Telegram user ID, suggest messaging `@userinfobot` on Telegram.
207
+ - If the tool returns a write failure, suggest trying again.
208
+ - If the tool is missing from your tool list, suggest upgrading the plugin.
209
209
 
210
210
  ## Connect
211
211