@masons/agent-network 0.5.14 → 0.5.16
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 +0 -7
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +3 -174
- package/dist/cli-setup.d.ts +0 -109
- package/dist/cli-setup.d.ts.map +1 -1
- package/dist/cli-setup.js +16 -570
- package/dist/config-fs.d.ts +4 -0
- package/dist/config-fs.d.ts.map +1 -0
- package/dist/config-fs.js +23 -0
- package/dist/config-schema.js +2 -2
- package/dist/config.d.ts +2 -210
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -334
- package/dist/connector-client.d.ts +0 -32
- package/dist/connector-client.d.ts.map +1 -1
- package/dist/connector-client.js +1 -89
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -3
- package/dist/conversation-manager.d.ts +0 -106
- package/dist/conversation-manager.d.ts.map +1 -1
- package/dist/conversation-manager.js +2 -131
- package/dist/environment-context.d.ts +0 -24
- package/dist/environment-context.d.ts.map +1 -1
- package/dist/environment-context.js +0 -42
- package/dist/handle-utils.d.ts +0 -14
- package/dist/handle-utils.d.ts.map +1 -1
- package/dist/handle-utils.js +0 -14
- package/dist/index.js +0 -9
- package/dist/owner-notes.d.ts +0 -33
- package/dist/owner-notes.d.ts.map +1 -1
- package/dist/owner-notes.js +2 -41
- package/dist/owner-session-state.d.ts +0 -26
- package/dist/owner-session-state.d.ts.map +1 -1
- package/dist/owner-session-state.js +0 -37
- package/dist/platform-client.d.ts +13 -202
- package/dist/platform-client.d.ts.map +1 -1
- package/dist/platform-client.js +22 -171
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -167
- package/dist/sent-message-buffer.d.ts +0 -36
- package/dist/sent-message-buffer.d.ts.map +1 -1
- package/dist/sent-message-buffer.js +1 -45
- package/dist/tools.d.ts +0 -28
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +36 -240
- package/dist/turn-context.d.ts +0 -45
- package/dist/turn-context.d.ts.map +1 -1
- package/dist/turn-context.js +0 -57
- package/dist/types.d.ts +0 -67
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -7
- package/dist/update-cache.d.ts +0 -17
- package/dist/update-cache.d.ts.map +1 -1
- package/dist/update-cache.js +1 -21
- package/dist/update-check.d.ts +1 -40
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +7 -66
- package/dist/version.d.ts +1 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -2
- package/openclaw.plugin.json +94 -3
- package/package.json +11 -10
- package/skills/agent-network/SKILL.md +21 -47
- package/skills/agent-network/references/troubleshooting.md +5 -5
package/dist/config.js
CHANGED
|
@@ -1,73 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
* Config & runtime bridge — mediates shared state between channel.ts and tools.ts.
|
|
3
|
-
*
|
|
4
|
-
* Read/write separation architecture:
|
|
5
|
-
* - **Reads**: `initToolConfig(ctx.cfg)` injects Host-provided config into
|
|
6
|
-
* module-level variables. Tools read from these vars via guard functions.
|
|
7
|
-
* - **Writes**: 3 fs functions write to `openclaw.json`, signaling the Host
|
|
8
|
-
* (Gateway monitors file changes for hot-reload / restart).
|
|
9
|
-
* - **Runtime**: `initConnectorClient(client)` injects the live WebSocket
|
|
10
|
-
* client after connection is established. Conversation tools read it via
|
|
11
|
-
* `requireConnectorClient()`.
|
|
12
|
-
*
|
|
13
|
-
* `ctx.cfg` is the Host's authoritative value for this process lifecycle.
|
|
14
|
-
* Plugin does not re-parse the config file for reads.
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { persistConfig, readConfig } from "./config-fs.js";
|
|
20
2
|
import { DEFAULT_API_HOST, } from "./platform-client.js";
|
|
21
|
-
|
|
22
|
-
// Module-level state (lifecycle-scoped — set by initToolConfig, read by tools)
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Initialize with defaults so setup tools work BEFORE startAccount() runs.
|
|
25
|
-
// On first install (no credentials), startAccount() never runs because
|
|
26
|
-
// listAccountIds() returns []. But the LLM setup tools (masons_setup_init etc.)
|
|
27
|
-
// still need apiHost to call the Platform API. Without this default, tools
|
|
28
|
-
// throw "Channel must be started first" on first run.
|
|
29
|
-
// Config state (from Host's openclaw.json — set by initToolConfig)
|
|
3
|
+
export { getOpenClawHome } from "./config-fs.js";
|
|
30
4
|
let platformConfig = { apiHost: DEFAULT_API_HOST };
|
|
31
5
|
let storedApiKey = null;
|
|
32
6
|
let storedPendingTarget = null;
|
|
33
7
|
let storedNeedsProfile = false;
|
|
34
|
-
// Cross-channel identity state (from cfg.session — set by initToolConfig)
|
|
35
8
|
let storedIdentityLinksConfigured = false;
|
|
36
|
-
// dmScope from cfg.session.dmScope — determines notification + linking behavior.
|
|
37
|
-
// "main" (default): all DMs share one session. Identity linking has no effect.
|
|
38
|
-
// "per-peer": identity linking merges sessions across channels.
|
|
39
|
-
// "per-channel-peer" / "per-account-channel-peer": identity linking normalizes
|
|
40
|
-
// peerId but sessions stay separate per channel.
|
|
41
9
|
let storedDmScope = "main";
|
|
42
|
-
// State cache generation — incremented when config-modifying functions write
|
|
43
|
-
// to disk (writeCredentials, clearTargetHandle, markProfileNeeded, markProfileComplete).
|
|
44
|
-
// before_prompt_build compares its cached generation to this value; mismatch
|
|
45
|
-
// triggers a re-read of detectPendingState() from disk.
|
|
46
10
|
let stateCacheGeneration = 0;
|
|
47
|
-
/**
|
|
48
|
-
* Current state cache generation. Incremented by config-modifying functions.
|
|
49
|
-
* Used by before_prompt_build to detect when cached state needs a refresh.
|
|
50
|
-
*/
|
|
51
11
|
export function getStateCacheGeneration() {
|
|
52
12
|
return stateCacheGeneration;
|
|
53
13
|
}
|
|
54
|
-
// Runtime state (live connection — set by startAccount, cleared by stopAccount)
|
|
55
14
|
let storedConnectorClient = null;
|
|
56
15
|
let storedConversationManager = null;
|
|
57
|
-
// PluginRuntime state (set by register() in plugin.ts, used by channel.ts)
|
|
58
|
-
// Typed as unknown to avoid circular imports — channel.ts casts to PluginRuntime.
|
|
59
16
|
let storedPluginRuntime = null;
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Config navigation — shared between channel.ts and config.ts
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
/**
|
|
64
|
-
* Extract the `channels.agent-network` section from the full OpenClaw config.
|
|
65
|
-
*
|
|
66
|
-
* OpenClaw Gateway passes the ENTIRE config (all of openclaw.json) to
|
|
67
|
-
* channel adapter methods and startAccount(). This helper navigates to
|
|
68
|
-
* the agent-network-specific section.
|
|
69
|
-
*
|
|
70
|
-
*/
|
|
71
17
|
export function extractNetworkConfig(cfg) {
|
|
72
18
|
const channels = cfg.channels;
|
|
73
19
|
if (!channels)
|
|
@@ -77,15 +23,6 @@ export function extractNetworkConfig(cfg) {
|
|
|
77
23
|
return null;
|
|
78
24
|
return section;
|
|
79
25
|
}
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// Config injection (called by startAccount() in channel.ts)
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
/**
|
|
84
|
-
* Inject Host-provided config into module-level variables.
|
|
85
|
-
*
|
|
86
|
-
* Called by `startAccount()` each time Gateway starts or hot-reloads.
|
|
87
|
-
* Ensures tools always read the Host's current config values.
|
|
88
|
-
*/
|
|
89
26
|
export function initToolConfig(cfg) {
|
|
90
27
|
const networkCfg = extractNetworkConfig(cfg);
|
|
91
28
|
platformConfig = {
|
|
@@ -93,7 +30,6 @@ export function initToolConfig(cfg) {
|
|
|
93
30
|
? networkCfg.apiHost
|
|
94
31
|
: DEFAULT_API_HOST,
|
|
95
32
|
};
|
|
96
|
-
// Extract API key from channels.agent-network.accounts.default.token
|
|
97
33
|
const accounts = networkCfg?.accounts;
|
|
98
34
|
const defaultAccount = accounts?.default;
|
|
99
35
|
storedApiKey =
|
|
@@ -103,36 +39,11 @@ export function initToolConfig(cfg) {
|
|
|
103
39
|
? networkCfg.pendingTarget
|
|
104
40
|
: null;
|
|
105
41
|
storedNeedsProfile = networkCfg?.needsProfile === true;
|
|
106
|
-
// --- Cross-channel identity state (cfg.session, not cfg.channels) ---
|
|
107
|
-
// Decision: 2026-03-26, during cross-channel identity linking design (#969, #986).
|
|
108
|
-
//
|
|
109
|
-
// Reads Gateway-level session config to detect:
|
|
110
|
-
// 1. Whether identityLinks already contains agent-network entries
|
|
111
|
-
// 2. The dmScope mode (determines notification + linking behavior)
|
|
112
|
-
//
|
|
113
|
-
// These are NOT agent-network-specific — any channel plugin would need
|
|
114
|
-
// similar capabilities. They live here because only one channel plugin
|
|
115
|
-
// exists today. When OpenClaw provides native cross-channel identity
|
|
116
|
-
// management, this should migrate.
|
|
117
42
|
storedIdentityLinksConfigured = hasAgentNetworkIdentityLink(cfg);
|
|
118
43
|
const sessionCfg = cfg.session;
|
|
119
44
|
storedDmScope =
|
|
120
45
|
typeof sessionCfg?.dmScope === "string" ? sessionCfg.dmScope : "main";
|
|
121
46
|
}
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// Cross-channel identity detection
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
/**
|
|
126
|
-
* Check if any `session.identityLinks` entry includes an agent-network peerId.
|
|
127
|
-
*
|
|
128
|
-
* Scans all canonical identity groups for entries containing
|
|
129
|
-
* `"agent-network:"` prefix, which indicates the owner's agent-network
|
|
130
|
-
* identity has been linked to another channel.
|
|
131
|
-
*
|
|
132
|
-
* Reads `cfg.session.identityLinks` — this is Gateway-level config,
|
|
133
|
-
* not channel-level. Legitimate because Gateway passes the full config
|
|
134
|
-
* via `ctx.cfg` to `startAccount()`.
|
|
135
|
-
*/
|
|
136
47
|
function hasAgentNetworkIdentityLink(cfg) {
|
|
137
48
|
const session = cfg.session;
|
|
138
49
|
const links = session?.identityLinks;
|
|
@@ -146,210 +57,69 @@ function hasAgentNetworkIdentityLink(cfg) {
|
|
|
146
57
|
}
|
|
147
58
|
return false;
|
|
148
59
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Whether `session.identityLinks` contains at least one entry with the
|
|
151
|
-
* `agent-network:` channel prefix — indicating the owner's agent-network
|
|
152
|
-
* identity is linked to another channel.
|
|
153
|
-
*/
|
|
154
60
|
export function isIdentityLinksConfigured() {
|
|
155
61
|
return storedIdentityLinksConfigured;
|
|
156
62
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Current dmScope mode from Gateway config (`cfg.session.dmScope`).
|
|
159
|
-
*
|
|
160
|
-
* Determines how the plugin handles cross-channel identity:
|
|
161
|
-
* - "main" (default): all DMs share one session. Identity linking has no effect.
|
|
162
|
-
* - "per-peer": identity linking merges sessions across channels.
|
|
163
|
-
* - "per-channel-peer" / "per-account-channel-peer": identity linking normalizes
|
|
164
|
-
* peerId but sessions stay separate per channel.
|
|
165
|
-
*/
|
|
166
63
|
export function getDmScope() {
|
|
167
64
|
return storedDmScope;
|
|
168
65
|
}
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// ConnectorClient injection (called by startAccount() after connect)
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
/**
|
|
173
|
-
* Inject the live ConnectorClient after WebSocket connection is established.
|
|
174
|
-
*
|
|
175
|
-
* Called by `startAccount()` AFTER `client.connect()` resolves — ensures
|
|
176
|
-
* tools never receive a client with a null WebSocket.
|
|
177
|
-
*
|
|
178
|
-
* Phase 1 supports a single account. The double-init guard makes this
|
|
179
|
-
* assumption explicit: a second call without `clearConnectorClient()` throws.
|
|
180
|
-
*/
|
|
181
66
|
export function initConnectorClient(client) {
|
|
182
67
|
if (storedConnectorClient !== null) {
|
|
183
68
|
throw new Error("ConnectorClient already initialized. Multi-account not yet supported.");
|
|
184
69
|
}
|
|
185
70
|
storedConnectorClient = client;
|
|
186
71
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Clear the stored ConnectorClient reference.
|
|
189
|
-
*
|
|
190
|
-
* Called by `stopAccount()` and at `startAccount()` entry (defensive).
|
|
191
|
-
* NOT called by the abort handler — see #935.
|
|
192
|
-
*/
|
|
193
72
|
export function clearConnectorClient() {
|
|
194
73
|
storedConnectorClient = null;
|
|
195
74
|
}
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
// ConversationManager injection (called by startAccount() after connect)
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
/**
|
|
200
|
-
* Inject the live ConversationManager after WebSocket connection is established.
|
|
201
|
-
*
|
|
202
|
-
* Called by `startAccount()` AFTER `client.connect()` resolves — ensures
|
|
203
|
-
* tools always get a manager backed by a connected client.
|
|
204
|
-
*/
|
|
205
75
|
export function initConversationManager(manager) {
|
|
206
76
|
storedConversationManager = manager;
|
|
207
77
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Clear the stored ConversationManager reference.
|
|
210
|
-
*
|
|
211
|
-
* Called by `stopAccount()` and at `startAccount()` entry (defensive).
|
|
212
|
-
* NOT called by the abort handler — in-flight tools need the stale CM
|
|
213
|
-
* to graceful-fail via disconnected WebSocket instead of throwing (#935).
|
|
214
|
-
*/
|
|
215
78
|
export function clearConversationManager() {
|
|
216
79
|
storedConversationManager = null;
|
|
217
80
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Get the live ConversationManager. Throws if not initialized.
|
|
220
|
-
*/
|
|
221
81
|
export function requireConversationManager() {
|
|
222
82
|
if (!storedConversationManager) {
|
|
223
83
|
throw new Error("Not connected to the agent network. Wait for the connection to be established.");
|
|
224
84
|
}
|
|
225
85
|
return storedConversationManager;
|
|
226
86
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Get the live ConversationManager, or null if not yet initialized.
|
|
229
|
-
*
|
|
230
|
-
* Non-throwing variant for use in before_prompt_build where the connection
|
|
231
|
-
* may not be established yet (e.g., during setup flow). Callers must handle
|
|
232
|
-
* the null case gracefully.
|
|
233
|
-
*/
|
|
234
87
|
export function getConversationManager() {
|
|
235
88
|
return storedConversationManager;
|
|
236
89
|
}
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
// PluginRuntime injection (called by register() in plugin.ts)
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
/**
|
|
241
|
-
* Store the OpenClaw PluginRuntime for inbound message dispatch.
|
|
242
|
-
*
|
|
243
|
-
* Called by `register(api)` in plugin.ts — captures `api.runtime` which
|
|
244
|
-
* provides `runtime.channel.*` (routing, reply, session) used by the
|
|
245
|
-
* 4-step inbound dispatch flow in channel.ts.
|
|
246
|
-
*
|
|
247
|
-
* Idempotent: overwrites on re-call. Gateway may hot-reload plugins,
|
|
248
|
-
* calling `register()` again without a teardown counterpart.
|
|
249
|
-
*/
|
|
250
90
|
export function initPluginRuntime(runtime) {
|
|
251
91
|
storedPluginRuntime = runtime;
|
|
252
92
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Clear the stored PluginRuntime reference.
|
|
255
|
-
*/
|
|
256
93
|
export function clearPluginRuntime() {
|
|
257
94
|
storedPluginRuntime = null;
|
|
258
95
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Get the stored PluginRuntime. Throws if not initialized.
|
|
261
|
-
*
|
|
262
|
-
* Callers cast the result to `PluginRuntime` (defined in channel.ts)
|
|
263
|
-
* at the usage site. Typed as `unknown` here to avoid circular imports.
|
|
264
|
-
*/
|
|
265
96
|
export function requirePluginRuntime() {
|
|
266
97
|
if (storedPluginRuntime == null) {
|
|
267
98
|
throw new Error("Plugin runtime not initialized. register() must be called first.");
|
|
268
99
|
}
|
|
269
100
|
return storedPluginRuntime;
|
|
270
101
|
}
|
|
271
|
-
// ---------------------------------------------------------------------------
|
|
272
|
-
// Guard functions (used by tools — fail-fast if not initialized)
|
|
273
|
-
// ---------------------------------------------------------------------------
|
|
274
|
-
/**
|
|
275
|
-
* Get platform config. Always returns a valid config — defaults are set
|
|
276
|
-
* at module load time, and overwritten by `initToolConfig()` when
|
|
277
|
-
* `startAccount()` runs.
|
|
278
|
-
*
|
|
279
|
-
* This ensures setup tools work on first install (before credentials
|
|
280
|
-
* exist and before `startAccount()` has run).
|
|
281
|
-
*/
|
|
282
102
|
export function requirePlatformConfig() {
|
|
283
103
|
return platformConfig;
|
|
284
104
|
}
|
|
285
|
-
/**
|
|
286
|
-
* Get stored API key. Throws if no credentials are configured.
|
|
287
|
-
*/
|
|
288
105
|
export function requireApiKey() {
|
|
289
106
|
if (!storedApiKey) {
|
|
290
|
-
throw new Error("No
|
|
107
|
+
throw new Error("No runtime key configured. Complete setup first.");
|
|
291
108
|
}
|
|
292
109
|
return storedApiKey;
|
|
293
110
|
}
|
|
294
|
-
/**
|
|
295
|
-
* Get pending connection target handle, or null if none.
|
|
296
|
-
*/
|
|
297
111
|
export function getPendingTarget() {
|
|
298
112
|
return storedPendingTarget;
|
|
299
113
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Whether the agent needs profile completion before connecting.
|
|
302
|
-
*
|
|
303
|
-
* True after onboard (new agent creation), cleared after all required
|
|
304
|
-
* profile fields are filled. Used by connection tools to gate requests
|
|
305
|
-
* until the agent has a visible identity.
|
|
306
|
-
*/
|
|
307
114
|
export function isProfileNeeded() {
|
|
308
115
|
return storedNeedsProfile;
|
|
309
116
|
}
|
|
310
|
-
/**
|
|
311
|
-
* Get the live ConnectorClient. Throws if not initialized.
|
|
312
|
-
*
|
|
313
|
-
* Note: The returned client may be in a reconnecting state (WebSocket
|
|
314
|
-
* temporarily null after a drop). Callers must handle `false` returns
|
|
315
|
-
* from send methods (`sendMessage`, `createSession`, `endSession`).
|
|
316
|
-
*/
|
|
317
117
|
export function requireConnectorClient() {
|
|
318
118
|
if (!storedConnectorClient) {
|
|
319
119
|
throw new Error("Not connected to the agent network. Wait for the connection to be established.");
|
|
320
120
|
}
|
|
321
121
|
return storedConnectorClient;
|
|
322
122
|
}
|
|
323
|
-
// ---------------------------------------------------------------------------
|
|
324
|
-
// Config path resolution
|
|
325
|
-
// ---------------------------------------------------------------------------
|
|
326
|
-
/** Resolve the OpenClaw home directory (`$OPENCLAW_HOME` or `~/.openclaw`). */
|
|
327
|
-
export function getOpenClawHome() {
|
|
328
|
-
return process.env.OPENCLAW_HOME || join(homedir(), ".openclaw");
|
|
329
|
-
}
|
|
330
|
-
function getConfigPath() {
|
|
331
|
-
return join(getOpenClawHome(), "openclaw.json");
|
|
332
|
-
}
|
|
333
|
-
// ---------------------------------------------------------------------------
|
|
334
|
-
// Internal read/write helpers
|
|
335
|
-
// ---------------------------------------------------------------------------
|
|
336
|
-
async function readConfig() {
|
|
337
|
-
try {
|
|
338
|
-
const raw = await readFile(getConfigPath(), "utf-8");
|
|
339
|
-
return JSON.parse(raw);
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
return {}; // File doesn't exist yet
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
async function persistConfig(config) {
|
|
346
|
-
const configPath = getConfigPath();
|
|
347
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
348
|
-
await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Get or create the `channels.agent-network` section in config.
|
|
352
|
-
*/
|
|
353
123
|
function ensureNetworkSection(config) {
|
|
354
124
|
if (typeof config.channels !== "object" || config.channels === null) {
|
|
355
125
|
config.channels = {};
|
|
@@ -361,21 +131,20 @@ function ensureNetworkSection(config) {
|
|
|
361
131
|
}
|
|
362
132
|
return channels["agent-network"];
|
|
363
133
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
134
|
+
export async function readExistingConnectorUrl() {
|
|
135
|
+
const config = await readConfig();
|
|
136
|
+
const networkCfg = extractNetworkConfig(config);
|
|
137
|
+
const accounts = networkCfg?.accounts;
|
|
138
|
+
const defaultAccount = accounts?.default;
|
|
139
|
+
const connectorUrl = defaultAccount?.connectorUrl;
|
|
140
|
+
return typeof connectorUrl === "string" && connectorUrl.length > 0
|
|
141
|
+
? connectorUrl
|
|
142
|
+
: undefined;
|
|
143
|
+
}
|
|
374
144
|
export async function writeCredentials(creds, apiHost, idpBaseUrl) {
|
|
375
145
|
const config = await readConfig();
|
|
376
146
|
const section = ensureNetworkSection(config);
|
|
377
147
|
section.enabled = true;
|
|
378
|
-
// Write accounts.default
|
|
379
148
|
if (typeof section.accounts !== "object" || section.accounts === null) {
|
|
380
149
|
section.accounts = {};
|
|
381
150
|
}
|
|
@@ -384,30 +153,17 @@ export async function writeCredentials(creds, apiHost, idpBaseUrl) {
|
|
|
384
153
|
connectorUrl: creds.connectorUrl,
|
|
385
154
|
token: creds.token,
|
|
386
155
|
};
|
|
387
|
-
// Write apiHost if provided
|
|
388
156
|
if (apiHost) {
|
|
389
157
|
section.apiHost = apiHost;
|
|
158
|
+
platformConfig = { apiHost };
|
|
390
159
|
}
|
|
391
|
-
// Write idpBaseUrl if provided (Better Auth IdP URL for device flow).
|
|
392
|
-
// Added in 0.5.1 alongside the plugin.auth.login hook (#1264 follow-up).
|
|
393
160
|
if (idpBaseUrl) {
|
|
394
161
|
section.idpBaseUrl = idpBaseUrl;
|
|
395
162
|
}
|
|
396
163
|
await persistConfig(config);
|
|
397
|
-
// Update module-level var — deliberate exception to read/write separation,
|
|
398
|
-
// same pattern as clearTargetHandle(). Without this, tools fail with
|
|
399
|
-
// "No API key configured" until Gateway restarts and re-runs initToolConfig().
|
|
400
164
|
storedApiKey = creds.token;
|
|
401
165
|
stateCacheGeneration++;
|
|
402
166
|
}
|
|
403
|
-
/**
|
|
404
|
-
* Write pending connection target handle.
|
|
405
|
-
*
|
|
406
|
-
* Used in the invitation flow: when an agent visits another agent's page
|
|
407
|
-
* and installs the plugin, the target handle is persisted here before
|
|
408
|
-
* restart. After restart, Layer B (`detectPendingState`) reads it back
|
|
409
|
-
* and injects continuity context so the agent knows who to connect to.
|
|
410
|
-
*/
|
|
411
167
|
export async function writeTargetHandle(handle) {
|
|
412
168
|
const config = await readConfig();
|
|
413
169
|
const section = ensureNetworkSection(config);
|
|
@@ -415,31 +171,14 @@ export async function writeTargetHandle(handle) {
|
|
|
415
171
|
await persistConfig(config);
|
|
416
172
|
stateCacheGeneration++;
|
|
417
173
|
}
|
|
418
|
-
/**
|
|
419
|
-
* Clear pending connection target from config file AND module-level variable.
|
|
420
|
-
*
|
|
421
|
-
* This is a deliberate exception to the read/write separation pattern:
|
|
422
|
-
* we also update `storedPendingTarget` in memory to avoid stale reads
|
|
423
|
-
* within the same session (since `initToolConfig()` only runs at startup).
|
|
424
|
-
*/
|
|
425
174
|
export async function clearTargetHandle() {
|
|
426
175
|
const config = await readConfig();
|
|
427
176
|
const section = ensureNetworkSection(config);
|
|
428
177
|
delete section.pendingTarget;
|
|
429
178
|
await persistConfig(config);
|
|
430
|
-
// Update module-level var — deliberate exception to read/write separation
|
|
431
179
|
storedPendingTarget = null;
|
|
432
180
|
stateCacheGeneration++;
|
|
433
181
|
}
|
|
434
|
-
// ---------------------------------------------------------------------------
|
|
435
|
-
// Profile state (set after onboard, cleared after profile update)
|
|
436
|
-
// ---------------------------------------------------------------------------
|
|
437
|
-
/**
|
|
438
|
-
* Mark that this agent needs profile completion.
|
|
439
|
-
*
|
|
440
|
-
* Called after onboard (new agent creation) — the agent has an empty profile.
|
|
441
|
-
* The before_prompt_build hook reads this to inject profile generation context.
|
|
442
|
-
*/
|
|
443
182
|
export async function markProfileNeeded() {
|
|
444
183
|
const config = await readConfig();
|
|
445
184
|
const section = ensureNetworkSection(config);
|
|
@@ -448,14 +187,6 @@ export async function markProfileNeeded() {
|
|
|
448
187
|
storedNeedsProfile = true;
|
|
449
188
|
stateCacheGeneration++;
|
|
450
189
|
}
|
|
451
|
-
/**
|
|
452
|
-
* Mark that profile completion is done.
|
|
453
|
-
*
|
|
454
|
-
* Called after successful masons_update_profile — clears the needsProfile flag
|
|
455
|
-
* so the before_prompt_build hook no longer injects profile context.
|
|
456
|
-
* Also updates the in-memory `storedNeedsProfile` flag so tool-level gates
|
|
457
|
-
* (connection request / accept) take effect immediately without a restart.
|
|
458
|
-
*/
|
|
459
190
|
export async function markProfileComplete() {
|
|
460
191
|
const config = await readConfig();
|
|
461
192
|
const section = ensureNetworkSection(config);
|
|
@@ -464,64 +195,27 @@ export async function markProfileComplete() {
|
|
|
464
195
|
storedNeedsProfile = false;
|
|
465
196
|
stateCacheGeneration++;
|
|
466
197
|
}
|
|
467
|
-
// ---------------------------------------------------------------------------
|
|
468
|
-
// Cross-channel identity link write (writes to config.session, not config.channels)
|
|
469
|
-
// ---------------------------------------------------------------------------
|
|
470
|
-
/**
|
|
471
|
-
* Get or create the top-level `session` section in config.
|
|
472
|
-
*
|
|
473
|
-
* Unlike `ensureNetworkSection()` which targets `channels.agent-network`,
|
|
474
|
-
* this targets `config.session` — a Gateway-level config section.
|
|
475
|
-
* Used only by `writeIdentityLinks()`.
|
|
476
|
-
*/
|
|
477
198
|
function ensureSessionSection(config) {
|
|
478
199
|
if (typeof config.session !== "object" || config.session === null) {
|
|
479
200
|
config.session = {};
|
|
480
201
|
}
|
|
481
202
|
return config.session;
|
|
482
203
|
}
|
|
483
|
-
/**
|
|
484
|
-
* Write cross-channel identity links to `config.session.identityLinks`.
|
|
485
|
-
*
|
|
486
|
-
* **Entry-level additive merge** (2026-03-26, #986 redesign):
|
|
487
|
-
* New entries are merged INTO the existing canonical group, deduped,
|
|
488
|
-
* lowercase-normalized. Never overwrites other canonical groups, and
|
|
489
|
-
* never removes existing entries within the same group.
|
|
490
|
-
*
|
|
491
|
-
* This allows one-channel-at-a-time linking: first call adds Telegram,
|
|
492
|
-
* second call adds Feishu — without losing the Telegram entry.
|
|
493
|
-
*
|
|
494
|
-
* This writes to `session.*` (Gateway-level), not `channels.agent-network.*`.
|
|
495
|
-
* Justified because cross-channel identity is inherently a Gateway concern,
|
|
496
|
-
* and the Gateway provides the full config to plugins via `ctx.cfg`.
|
|
497
|
-
*
|
|
498
|
-
* Follows the same readConfig → merge → persistConfig pattern as
|
|
499
|
-
* writeCredentials() and writeTargetHandle().
|
|
500
|
-
*/
|
|
501
204
|
export async function writeIdentityLinks(canonical, entries) {
|
|
502
205
|
const config = await readConfig();
|
|
503
206
|
const session = ensureSessionSection(config);
|
|
504
|
-
// Merge into existing identityLinks (never overwrite other groups)
|
|
505
207
|
const existing = typeof session.identityLinks === "object" && session.identityLinks !== null
|
|
506
208
|
? session.identityLinks
|
|
507
209
|
: {};
|
|
508
|
-
// Entry-level additive merge: combine new entries into existing group
|
|
509
210
|
const current = existing[canonical] ?? [];
|
|
510
211
|
const normalized = entries.map((e) => e.toLowerCase());
|
|
511
212
|
const merged = [...new Set([...current, ...normalized])];
|
|
512
213
|
existing[canonical] = merged;
|
|
513
214
|
session.identityLinks = existing;
|
|
514
215
|
await persistConfig(config);
|
|
515
|
-
// Update module-level state — same exception pattern as writeCredentials
|
|
516
216
|
storedIdentityLinksConfigured = true;
|
|
517
217
|
stateCacheGeneration++;
|
|
518
218
|
}
|
|
519
|
-
/**
|
|
520
|
-
* Remove a canonical identity group from `config.session.identityLinks`.
|
|
521
|
-
*
|
|
522
|
-
* Undo path for `writeIdentityLinks()`. Removes only the specified
|
|
523
|
-
* canonical key; other groups are preserved.
|
|
524
|
-
*/
|
|
525
219
|
export async function removeIdentityLinks(canonical) {
|
|
526
220
|
const config = await readConfig();
|
|
527
221
|
const session = ensureSessionSection(config);
|
|
@@ -529,7 +223,6 @@ export async function removeIdentityLinks(canonical) {
|
|
|
529
223
|
? session.identityLinks
|
|
530
224
|
: {};
|
|
531
225
|
delete existing[canonical];
|
|
532
|
-
// If identityLinks is now empty, remove the key entirely
|
|
533
226
|
if (Object.keys(existing).length === 0) {
|
|
534
227
|
delete session.identityLinks;
|
|
535
228
|
}
|
|
@@ -537,18 +230,10 @@ export async function removeIdentityLinks(canonical) {
|
|
|
537
230
|
session.identityLinks = existing;
|
|
538
231
|
}
|
|
539
232
|
await persistConfig(config);
|
|
540
|
-
// Re-check if any agent-network links remain
|
|
541
233
|
storedIdentityLinksConfigured = Object.values(existing).some((entries) => Array.isArray(entries) &&
|
|
542
234
|
entries.some((e) => typeof e === "string" && e.startsWith("agent-network:")));
|
|
543
235
|
stateCacheGeneration++;
|
|
544
236
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Detect pending state by reading config file directly.
|
|
547
|
-
*
|
|
548
|
-
* Unlike `initToolConfig()` which is called by `startAccount()`, this reads
|
|
549
|
-
* from disk — available even when the channel hasn't started (no credentials).
|
|
550
|
-
* Used by Layer B (before_prompt_build hook) for per-turn context injection.
|
|
551
|
-
*/
|
|
552
237
|
export async function detectPendingState() {
|
|
553
238
|
const config = await readConfig();
|
|
554
239
|
const networkCfg = extractNetworkConfig(config);
|
|
@@ -564,14 +249,9 @@ export async function detectPendingState() {
|
|
|
564
249
|
const pendingTarget = typeof networkCfg.pendingTarget === "string"
|
|
565
250
|
? networkCfg.pendingTarget
|
|
566
251
|
: null;
|
|
567
|
-
// needsProfile is explicitly true only when set by onboard flow
|
|
568
252
|
const needsProfile = networkCfg.needsProfile === true;
|
|
569
253
|
return { hasCredentials, pendingTarget, needsProfile };
|
|
570
254
|
}
|
|
571
|
-
// ---------------------------------------------------------------------------
|
|
572
|
-
// Test-only reset (prefix _ = internal)
|
|
573
|
-
// ---------------------------------------------------------------------------
|
|
574
|
-
/** @internal Reset module state for test isolation. */
|
|
575
255
|
export function _resetForTesting() {
|
|
576
256
|
platformConfig = { apiHost: DEFAULT_API_HOST };
|
|
577
257
|
storedApiKey = null;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { type AddressedMessageEvent, type DeliveryPendingEvent, type DeliveryStatusEvent, type SendAckEvent, type StructuredErrorEvent } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Error with machine-readable `code` from Connector structured errors.
|
|
5
|
-
* Thrown by `send()` when the Connector responds with ERROR { code }.
|
|
6
|
-
*/
|
|
7
3
|
export declare class ConnectorError extends Error {
|
|
8
4
|
readonly code: string;
|
|
9
5
|
readonly to?: string;
|
|
@@ -30,31 +26,13 @@ export declare class ConnectorClient extends EventEmitter {
|
|
|
30
26
|
private registerResolve;
|
|
31
27
|
private registerReject;
|
|
32
28
|
private registerTimer;
|
|
33
|
-
/** Delivery cursor — highest recipientSeq received from Connector (#1133).
|
|
34
|
-
* Sent on REGISTER so the Connector can fill any gap. In-memory only —
|
|
35
|
-
* survives Connector restarts (Plugin stays alive), lost on Plugin restart
|
|
36
|
-
* (graceful degradation: no gap fill, same as pre-#1133 behavior). */
|
|
37
29
|
private lastKnownSeq;
|
|
38
|
-
/** Pending SEND_ACK correlation map for address-based sends. */
|
|
39
30
|
private pendingSends;
|
|
40
31
|
constructor(url: string, token: string);
|
|
41
32
|
connect(): Promise<void>;
|
|
42
33
|
disconnect(): void;
|
|
43
|
-
/**
|
|
44
|
-
* Send a message to a routable address. Returns a Promise that resolves
|
|
45
|
-
* with the SEND_ACK from the Connector, or rejects with a ConnectorError
|
|
46
|
-
* (structured error code) or a plain Error (timeout / disconnect).
|
|
47
|
-
*
|
|
48
|
-
* The messageId is generated internally and used for ACK correlation.
|
|
49
|
-
*/
|
|
50
34
|
send(to: string, content: string, contentType?: string, metadata?: Record<string, unknown>): Promise<SendAckEvent>;
|
|
51
|
-
/**
|
|
52
|
-
* Send a typing indicator to a routable address.
|
|
53
|
-
*/
|
|
54
35
|
sendTyping(to: string, isTyping: boolean): boolean;
|
|
55
|
-
/**
|
|
56
|
-
* Acknowledge delivery of stored messages up to a timestamp.
|
|
57
|
-
*/
|
|
58
36
|
ackDelivery(upTo: string): boolean;
|
|
59
37
|
on<K extends keyof ConnectorClientEvents>(event: K, listener: ConnectorClientEvents[K]): this;
|
|
60
38
|
on(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
@@ -70,18 +48,9 @@ export declare class ConnectorClient extends EventEmitter {
|
|
|
70
48
|
private resolveRegister;
|
|
71
49
|
private rejectRegister;
|
|
72
50
|
private handleMessage;
|
|
73
|
-
/** Dispatch inbound address-based events. */
|
|
74
51
|
private dispatchAddressed;
|
|
75
52
|
private handleRegisterAck;
|
|
76
|
-
/**
|
|
77
|
-
* Resolve the pending send promise matching this SEND_ACK.
|
|
78
|
-
* Also emits `send_ack` for listeners that want to observe all ACKs.
|
|
79
|
-
*/
|
|
80
53
|
private handleSendAck;
|
|
81
|
-
/**
|
|
82
|
-
* Structured ERROR: message-scoped errors reject pending sends,
|
|
83
|
-
* connection-scoped errors emit generic event.
|
|
84
|
-
*/
|
|
85
54
|
private handleStructuredError;
|
|
86
55
|
private handleClose;
|
|
87
56
|
private handleError;
|
|
@@ -90,7 +59,6 @@ export declare class ConnectorClient extends EventEmitter {
|
|
|
90
59
|
private nextExponentialBackoff;
|
|
91
60
|
private clearReconnectTimer;
|
|
92
61
|
private cleanupConnection;
|
|
93
|
-
/** Send an event over the WebSocket. */
|
|
94
62
|
private sendEvent;
|
|
95
63
|
}
|
|
96
64
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector-client.d.ts","sourceRoot":"","sources":["../src/connector-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,EAEL,KAAK,qBAAqB,EAI1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAWxB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"connector-client.d.ts","sourceRoot":"","sources":["../src/connector-client.ts"],"names":[],"mappings":"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"}
|