@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.
Files changed (66) hide show
  1. package/dist/channel.d.ts +0 -7
  2. package/dist/channel.d.ts.map +1 -1
  3. package/dist/channel.js +3 -174
  4. package/dist/cli-setup.d.ts +0 -109
  5. package/dist/cli-setup.d.ts.map +1 -1
  6. package/dist/cli-setup.js +16 -570
  7. package/dist/config-fs.d.ts +4 -0
  8. package/dist/config-fs.d.ts.map +1 -0
  9. package/dist/config-fs.js +23 -0
  10. package/dist/config-schema.js +2 -2
  11. package/dist/config.d.ts +2 -210
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +14 -334
  14. package/dist/connector-client.d.ts +0 -32
  15. package/dist/connector-client.d.ts.map +1 -1
  16. package/dist/connector-client.js +1 -89
  17. package/dist/constants.d.ts +0 -1
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +2 -3
  20. package/dist/conversation-manager.d.ts +0 -106
  21. package/dist/conversation-manager.d.ts.map +1 -1
  22. package/dist/conversation-manager.js +2 -131
  23. package/dist/environment-context.d.ts +0 -24
  24. package/dist/environment-context.d.ts.map +1 -1
  25. package/dist/environment-context.js +0 -42
  26. package/dist/handle-utils.d.ts +0 -14
  27. package/dist/handle-utils.d.ts.map +1 -1
  28. package/dist/handle-utils.js +0 -14
  29. package/dist/index.js +0 -9
  30. package/dist/owner-notes.d.ts +0 -33
  31. package/dist/owner-notes.d.ts.map +1 -1
  32. package/dist/owner-notes.js +2 -41
  33. package/dist/owner-session-state.d.ts +0 -26
  34. package/dist/owner-session-state.d.ts.map +1 -1
  35. package/dist/owner-session-state.js +0 -37
  36. package/dist/platform-client.d.ts +13 -202
  37. package/dist/platform-client.d.ts.map +1 -1
  38. package/dist/platform-client.js +22 -171
  39. package/dist/plugin.d.ts +5 -0
  40. package/dist/plugin.d.ts.map +1 -1
  41. package/dist/plugin.js +3 -167
  42. package/dist/sent-message-buffer.d.ts +0 -36
  43. package/dist/sent-message-buffer.d.ts.map +1 -1
  44. package/dist/sent-message-buffer.js +1 -45
  45. package/dist/tools.d.ts +0 -28
  46. package/dist/tools.d.ts.map +1 -1
  47. package/dist/tools.js +36 -240
  48. package/dist/turn-context.d.ts +0 -45
  49. package/dist/turn-context.d.ts.map +1 -1
  50. package/dist/turn-context.js +0 -57
  51. package/dist/types.d.ts +0 -67
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/types.js +0 -7
  54. package/dist/update-cache.d.ts +0 -17
  55. package/dist/update-cache.d.ts.map +1 -1
  56. package/dist/update-cache.js +1 -21
  57. package/dist/update-check.d.ts +1 -40
  58. package/dist/update-check.d.ts.map +1 -1
  59. package/dist/update-check.js +7 -66
  60. package/dist/version.d.ts +1 -2
  61. package/dist/version.d.ts.map +1 -1
  62. package/dist/version.js +1 -2
  63. package/openclaw.plugin.json +94 -3
  64. package/package.json +11 -10
  65. package/skills/agent-network/SKILL.md +21 -47
  66. 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 API key configured. Complete setup first.");
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
- * Write credentials + apiHost (+ optional idpBaseUrl) to `openclaw.json`.
366
- *
367
- * Atomic: all fields are written in a single persistConfig() call to avoid
368
- * partial-write race conditions.
369
- *
370
- * `idpBaseUrl` is written only when provided, mirroring `apiHost`'s pattern —
371
- * omitted values leave any existing field in place (useful when callers want
372
- * to refresh credentials without overriding IdP URL).
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":"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"}
1
+ {"version":3,"file":"connector-client.d.ts","sourceRoot":"","sources":["../src/connector-client.ts"],"names":[],"mappings":"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"}