@masons/agent-network 0.4.18 → 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.
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +55 -2
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +163 -0
- package/dist/environment-context.d.ts +1 -1
- package/dist/environment-context.d.ts.map +1 -1
- package/dist/environment-context.js +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +18 -21
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +87 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/agent-network/SKILL.md +44 -0
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"
|
|
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,6 +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
|
+
/**
|
|
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.
|
|
45
|
+
*/
|
|
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;
|
|
41
49
|
/**
|
|
42
50
|
* Inject the live ConnectorClient after WebSocket connection is established.
|
|
43
51
|
*
|
|
@@ -183,6 +191,27 @@ export declare function markProfileNeeded(): Promise<void>;
|
|
|
183
191
|
* (connection request / accept) take effect immediately without a restart.
|
|
184
192
|
*/
|
|
185
193
|
export declare function markProfileComplete(): Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Write cross-channel identity links to `config.session.identityLinks`.
|
|
196
|
+
*
|
|
197
|
+
* Merges a new canonical identity group into the existing identityLinks
|
|
198
|
+
* map. Never overwrites other canonical groups.
|
|
199
|
+
*
|
|
200
|
+
* This writes to `session.*` (Gateway-level), not `channels.agent-network.*`.
|
|
201
|
+
* Justified because cross-channel identity is inherently a Gateway concern,
|
|
202
|
+
* and the Gateway provides the full config to plugins via `ctx.cfg`.
|
|
203
|
+
*
|
|
204
|
+
* Follows the same readConfig → merge → persistConfig pattern as
|
|
205
|
+
* writeCredentials() and writeTargetHandle().
|
|
206
|
+
*/
|
|
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>;
|
|
186
215
|
/** Pending state detected from config file — used for restart continuity. */
|
|
187
216
|
export interface PendingState {
|
|
188
217
|
/** Whether valid credentials exist (connectorUrl + token) */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -31,6 +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 + cfg.channels.telegram — set by initToolConfig)
|
|
35
|
+
let storedIdentityLinksConfigured = false;
|
|
36
|
+
let storedTelegramOwnerUserId = null;
|
|
34
37
|
// State cache generation — incremented when config-modifying functions write
|
|
35
38
|
// to disk (writeCredentials, clearTargetHandle, markProfileNeeded, markProfileComplete).
|
|
36
39
|
// before_prompt_build compares its cached generation to this value; mismatch
|
|
@@ -95,6 +98,95 @@ export function initToolConfig(cfg) {
|
|
|
95
98
|
? networkCfg.pendingTarget
|
|
96
99
|
: null;
|
|
97
100
|
storedNeedsProfile = networkCfg?.needsProfile === true;
|
|
101
|
+
// --- Cross-channel identity state (cfg.session, not cfg.channels) ---
|
|
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
|
|
106
|
+
// agent-network identity is linked to other channels. Used by
|
|
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.
|
|
113
|
+
storedIdentityLinksConfigured = hasAgentNetworkIdentityLink(cfg);
|
|
114
|
+
storedTelegramOwnerUserId = extractTelegramOwnerUserId(cfg);
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Cross-channel identity detection
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Check if any `session.identityLinks` entry includes an agent-network peerId.
|
|
121
|
+
*
|
|
122
|
+
* Scans all canonical identity groups for entries containing
|
|
123
|
+
* `"agent-network:"` prefix, which indicates the owner's agent-network
|
|
124
|
+
* identity has been linked to another channel.
|
|
125
|
+
*
|
|
126
|
+
* Reads `cfg.session.identityLinks` — this is Gateway-level config,
|
|
127
|
+
* not channel-level. Legitimate because Gateway passes the full config
|
|
128
|
+
* via `ctx.cfg` to `startAccount()`.
|
|
129
|
+
*/
|
|
130
|
+
function hasAgentNetworkIdentityLink(cfg) {
|
|
131
|
+
const session = cfg.session;
|
|
132
|
+
const links = session?.identityLinks;
|
|
133
|
+
if (!links)
|
|
134
|
+
return false;
|
|
135
|
+
for (const entries of Object.values(links)) {
|
|
136
|
+
if (Array.isArray(entries) &&
|
|
137
|
+
entries.some((e) => typeof e === "string" && e.startsWith("agent-network:"))) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
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
|
+
*/
|
|
148
|
+
export function isIdentityLinksConfigured() {
|
|
149
|
+
return storedIdentityLinksConfigured;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
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).
|
|
168
|
+
*/
|
|
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;
|
|
98
190
|
}
|
|
99
191
|
// ---------------------------------------------------------------------------
|
|
100
192
|
// ConnectorClient injection (called by startAccount() after connect)
|
|
@@ -385,6 +477,75 @@ export async function markProfileComplete() {
|
|
|
385
477
|
storedNeedsProfile = false;
|
|
386
478
|
stateCacheGeneration++;
|
|
387
479
|
}
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
// Cross-channel identity link write (writes to config.session, not config.channels)
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
/**
|
|
484
|
+
* Get or create the top-level `session` section in config.
|
|
485
|
+
*
|
|
486
|
+
* Unlike `ensureNetworkSection()` which targets `channels.agent-network`,
|
|
487
|
+
* this targets `config.session` — a Gateway-level config section.
|
|
488
|
+
* Used only by `writeIdentityLinks()`.
|
|
489
|
+
*/
|
|
490
|
+
function ensureSessionSection(config) {
|
|
491
|
+
if (typeof config.session !== "object" || config.session === null) {
|
|
492
|
+
config.session = {};
|
|
493
|
+
}
|
|
494
|
+
return config.session;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Write cross-channel identity links to `config.session.identityLinks`.
|
|
498
|
+
*
|
|
499
|
+
* Merges a new canonical identity group into the existing identityLinks
|
|
500
|
+
* map. Never overwrites other canonical groups.
|
|
501
|
+
*
|
|
502
|
+
* This writes to `session.*` (Gateway-level), not `channels.agent-network.*`.
|
|
503
|
+
* Justified because cross-channel identity is inherently a Gateway concern,
|
|
504
|
+
* and the Gateway provides the full config to plugins via `ctx.cfg`.
|
|
505
|
+
*
|
|
506
|
+
* Follows the same readConfig → merge → persistConfig pattern as
|
|
507
|
+
* writeCredentials() and writeTargetHandle().
|
|
508
|
+
*/
|
|
509
|
+
export async function writeIdentityLinks(canonical, entries) {
|
|
510
|
+
const config = await readConfig();
|
|
511
|
+
const session = ensureSessionSection(config);
|
|
512
|
+
// Merge into existing identityLinks (never overwrite other groups)
|
|
513
|
+
const existing = typeof session.identityLinks === "object" && session.identityLinks !== null
|
|
514
|
+
? session.identityLinks
|
|
515
|
+
: {};
|
|
516
|
+
existing[canonical] = entries;
|
|
517
|
+
session.identityLinks = existing;
|
|
518
|
+
await persistConfig(config);
|
|
519
|
+
// Update module-level state — same exception pattern as writeCredentials
|
|
520
|
+
storedIdentityLinksConfigured = true;
|
|
521
|
+
stateCacheGeneration++;
|
|
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
|
+
}
|
|
388
549
|
/**
|
|
389
550
|
* Detect pending state by reading config file directly.
|
|
390
551
|
*
|
|
@@ -420,6 +581,8 @@ export function _resetForTesting() {
|
|
|
420
581
|
storedApiKey = null;
|
|
421
582
|
storedPendingTarget = null;
|
|
422
583
|
storedNeedsProfile = false;
|
|
584
|
+
storedIdentityLinksConfigured = false;
|
|
585
|
+
storedTelegramOwnerUserId = null;
|
|
423
586
|
storedConnectorClient = null;
|
|
424
587
|
storedConversationManager = null;
|
|
425
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
|
|
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,
|
|
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
|
|
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
|
}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { agentNetworkChannel } from "./channel.js";
|
|
5
5
|
import { configureInteractive } from "./cli-setup.js";
|
|
6
6
|
import { detectPendingState, getConversationManager, getStateCacheGeneration, initPluginRuntime, } from "./config.js";
|
|
7
|
-
import { getAgentIdentity
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
353
|
+
const interactionCtx = buildInteractionContext(channelId, null);
|
|
350
354
|
if (interactionCtx) {
|
|
351
355
|
dynamicContext = dynamicContext
|
|
352
356
|
? `${dynamicContext}\n\n${interactionCtx}`
|
|
@@ -354,25 +358,18 @@ const plugin = {
|
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
|
-
// ---
|
|
358
|
-
// Inject agent
|
|
359
|
-
//
|
|
360
|
-
// (
|
|
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;
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
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,7 +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, } from "./config.js";
|
|
22
|
+
import { clearTargetHandle, getOpenClawHome, getPendingTarget, isProfileNeeded, markProfileComplete, markProfileNeeded, removeIdentityLinks, requireApiKey, requireConversationManager, requirePlatformConfig, writeCredentials, writeIdentityLinks, } from "./config.js";
|
|
23
23
|
import { ownerNotesQueue } from "./owner-notes.js";
|
|
24
24
|
import { acceptRequest, declineRequest, getConnectionStatus, initSetup, listConnections, listRequests, onboard, PlatformApiError, pollSetup, reconnect, requestConnection, SetupExpiredError, SetupPendingError, updateProfile, } from "./platform-client.js";
|
|
25
25
|
import { sentMessageBuffer } from "./sent-message-buffer.js";
|
|
@@ -749,4 +749,90 @@ export function registerTools(api) {
|
|
|
749
749
|
].join("\n"));
|
|
750
750
|
},
|
|
751
751
|
});
|
|
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.
|
|
756
|
+
api.registerTool({
|
|
757
|
+
name: "masons_link_identity",
|
|
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.",
|
|
761
|
+
parameters: Type.Object({
|
|
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,
|
|
768
|
+
}),
|
|
769
|
+
}),
|
|
770
|
+
execute: async (_id, params) => {
|
|
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
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
await writeIdentityLinks(canonical, validated);
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
console.error("[masons_link_identity] writeIdentityLinks failed:", err);
|
|
793
|
+
return textResult("Failed to write identity links to config. Try again.");
|
|
794
|
+
}
|
|
795
|
+
return textResult([
|
|
796
|
+
"Identity linked successfully.",
|
|
797
|
+
`Canonical: ${canonical}`,
|
|
798
|
+
`Entries: ${validated.join(", ")}`,
|
|
799
|
+
"",
|
|
800
|
+
"Restart required — call the `gateway` tool with action: restart, " +
|
|
801
|
+
'reason: "Activate cross-channel identity linking".',
|
|
802
|
+
].join("\n"));
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
// optional: not all Gateway versions support this tool.
|
|
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 });
|
|
752
838
|
}
|
package/dist/version.d.ts
CHANGED
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.
|
|
2
|
+
export const PLUGIN_VERSION = "0.4.20";
|
package/package.json
CHANGED
|
@@ -52,6 +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
|
+
- **User asks about cross-channel identity or why they appear as different people on different channels** → Go to **Cross-Channel Identity**
|
|
55
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.
|
|
56
57
|
- **Update available** (tool output mentions an update) → Go to **Upgrade** below
|
|
57
58
|
- **User mentions upgrade or update** → Go to **Upgrade** below
|
|
@@ -163,6 +164,49 @@ After Step 3, the tool will confirm all three fields are filled and clear `needs
|
|
|
163
164
|
- If there is a pending connection target (`pendingTarget` in config) → Go to **Connect**
|
|
164
165
|
- Otherwise → Let the user know they're live on the network and ready to connect with others.
|
|
165
166
|
|
|
167
|
+
## Cross-Channel Identity
|
|
168
|
+
|
|
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
|
+
|
|
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
|
+
|
|
173
|
+
### Linking identities
|
|
174
|
+
|
|
175
|
+
**Pre-check:** If `masons_link_identity` is not in your tool list, STOP. Do not proceed — the plugin may be outdated. Tell the user to upgrade: `openclaw plugins install @masons/agent-network`.
|
|
176
|
+
|
|
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
|
+
|
|
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}"]`
|
|
182
|
+
|
|
183
|
+
Note: the `passport:@handle` part is the full peer ID as the Connector addresses it — keep it as one piece, do not split.
|
|
184
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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`"
|
|
191
|
+
|
|
192
|
+
### Unlinking identities
|
|
193
|
+
|
|
194
|
+
**Say to user:** "I'll remove the cross-channel link. After a restart, conversations on each channel will be separate again."
|
|
195
|
+
|
|
196
|
+
**Then:** Call `masons_unlink_identity` with the canonical name.
|
|
197
|
+
|
|
198
|
+
**Then:** Call `gateway` with: `action: "restart"`, `delayMs: 2000`, `reason: "Remove cross-channel identity linking"`, `note: "Cross-channel identity unlinked."`
|
|
199
|
+
|
|
200
|
+
**Fallback:** If the `gateway` tool is not available or fails:
|
|
201
|
+
**Say to user:** "I couldn't restart automatically. Please run in your terminal: `openclaw gateway restart`"
|
|
202
|
+
|
|
203
|
+
### Errors
|
|
204
|
+
|
|
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
|
+
|
|
166
210
|
## Connect
|
|
167
211
|
|
|
168
212
|
Use this section whenever the user wants to connect to a specific agent.
|