@masons/agent-network 0.5.21 → 0.5.23

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 CHANGED
@@ -39,6 +39,8 @@ interface ChannelGatewayContext<T = unknown> {
39
39
  runtime: unknown;
40
40
  abortSignal: AbortSignal;
41
41
  }
42
+ declare function withAgentNetworkSilentReplyPolicy(cfg: Record<string, unknown>): Record<string, unknown>;
43
+ export declare const _withAgentNetworkSilentReplyPolicyForTesting: typeof withAgentNetworkSilentReplyPolicy;
42
44
  interface ChannelGatewayAdapter<T = unknown> {
43
45
  startAccount?(ctx: ChannelGatewayContext<T>): Promise<unknown>;
44
46
  stopAccount?(ctx: ChannelGatewayContext<T>): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAmC9D,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAOD,wBAAgB,2BAA2B,IAAI,OAAO,CAIrD;AAGD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/D;AAUD,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,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,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;AAuDD,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;AA4WD,eAAO,MAAM,mBAAmB,EAAE,yBAyNjC,CAAC"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAmC9D,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAOD,wBAAgB,2BAA2B,IAAI,OAAO,CAIrD;AAGD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/D;AAUD,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,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,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;AA2DD,iBAAS,iCAAiC,CACxC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BzB;AAED,eAAO,MAAM,4CAA4C,0CACtB,CAAC;AAEpC,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,YAAY,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,WAAW,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,UAAU,yBAAyB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAClD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CACrD;AA+WD,eAAO,MAAM,mBAAmB,EAAE,yBAyNjC,CAAC"}
package/dist/channel.js CHANGED
@@ -24,6 +24,39 @@ export function consumeIdentityLinkingNudge() {
24
24
  export function _setNudgePendingForTesting(value) {
25
25
  identityLinkingNudgePending = value;
26
26
  }
27
+ function isRecord(value) {
28
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
29
+ }
30
+ function withAgentNetworkSilentReplyPolicy(cfg) {
31
+ const surfaces = isRecord(cfg.surfaces) ? cfg.surfaces : {};
32
+ const existingSurface = isRecord(surfaces["agent-network"])
33
+ ? surfaces["agent-network"]
34
+ : {};
35
+ const silentReply = isRecord(existingSurface.silentReply)
36
+ ? existingSurface.silentReply
37
+ : {};
38
+ const silentReplyRewrite = isRecord(existingSurface.silentReplyRewrite)
39
+ ? existingSurface.silentReplyRewrite
40
+ : {};
41
+ return {
42
+ ...cfg,
43
+ surfaces: {
44
+ ...surfaces,
45
+ "agent-network": {
46
+ ...existingSurface,
47
+ silentReply: {
48
+ ...silentReply,
49
+ direct: "allow",
50
+ },
51
+ silentReplyRewrite: {
52
+ ...silentReplyRewrite,
53
+ direct: false,
54
+ },
55
+ },
56
+ },
57
+ };
58
+ }
59
+ export const _withAgentNetworkSilentReplyPolicyForTesting = withAgentNetworkSilentReplyPolicy;
27
60
  function tryProjectNodeRef(metadata) {
28
61
  if (!metadata)
29
62
  return null;
@@ -179,7 +212,7 @@ async function handleAddressedMessage(event, conversationManager, client, ctx) {
179
212
  try {
180
213
  await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
181
214
  ctx: msgCtx,
182
- cfg: ctx.cfg,
215
+ cfg: withAgentNetworkSilentReplyPolicy(ctx.cfg),
183
216
  dispatcherOptions: {
184
217
  deliver: async (payload) => {
185
218
  const text = payload.text;
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,uBAAuB,CAAC;AAS7E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;IAchC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AAwBD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAGvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAoBpD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAoCjE,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IASvD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAkBxD,iBAAiB,IAAI,mBAAmB,EAAE;IAwB1C,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAiBzC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAwBtC,wBAAwB,IAAI,IAAI;IA+BhC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAwBvC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAQtC,OAAO,CAAC,uBAAuB;IA0B/B,gBAAgB,IAAI,IAAI;CAGzB"}
1
+ {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,uBAAuB,CAAC;AAS7E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;IAchC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AA4BD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAGvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAoBpD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAoCjE,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IASvD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAkBxD,iBAAiB,IAAI,mBAAmB,EAAE;IAwB1C,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAiBzC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAwBtC,wBAAwB,IAAI,IAAI;IAgChC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAyBvC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAQtC,OAAO,CAAC,uBAAuB;IA0B/B,gBAAgB,IAAI,IAAI;CAGzB"}
@@ -13,6 +13,9 @@ function formatErrorMessage(code, to) {
13
13
  const template = ERROR_MESSAGES[code] ?? `Send failed: ${code}`;
14
14
  return to ? template.replace(/\{to\}/g, to) : template;
15
15
  }
16
+ function normalizeHandleContact(contact) {
17
+ return contact.startsWith("@") ? contact.slice(1) : contact;
18
+ }
16
19
  export class ConversationManager {
17
20
  client;
18
21
  connectorHost;
@@ -101,14 +104,15 @@ export class ConversationManager {
101
104
  if (contact.startsWith("passport:")) {
102
105
  return contact;
103
106
  }
104
- if (this.conversations.has(contact)) {
105
- return contact;
107
+ const normalizedContact = normalizeHandleContact(contact);
108
+ if (this.conversations.has(normalizedContact)) {
109
+ return normalizedContact;
106
110
  }
107
111
  for (const [addr, entry] of this.conversations) {
108
- if (entry.contact === contact)
112
+ if (entry.contact === normalizedContact)
109
113
  return addr;
110
114
  }
111
- return `mstps://${this.connectorHost}/${contact}`;
115
+ return `mstps://${this.connectorHost}/${normalizedContact}`;
112
116
  }
113
117
  extractHandle(address) {
114
118
  return extractHandleFromAddress(address);
package/dist/plugin.js CHANGED
@@ -126,8 +126,8 @@ const plugin = {
126
126
  "After responding on the network, check: " +
127
127
  "should you report to your owner via masons_note_for_owner? " +
128
128
  "Should you follow up with another agent via masons_send_message? " +
129
- "When the interaction is complete, call masons_end_conversation as your FINAL action " +
130
- "do not generate any text reply after ending. " +
129
+ "When the interaction is complete, call masons_end_conversation as your FINAL local action; " +
130
+ "it does not close the Connector transport session, and you must not generate any text reply after ending. " +
131
131
  "";
132
132
  api.on("before_prompt_build", async (_event, ctx) => {
133
133
  const hookCtx = ctx;
package/dist/tools.js CHANGED
@@ -529,7 +529,7 @@ export function registerTools(api) {
529
529
  });
530
530
  api.registerTool({
531
531
  name: "masons_end_conversation",
532
- description: "End the conversation with a connected agent. This immediately closes the session do not send any text reply after calling this tool.",
532
+ description: "End the local Agent Network interaction with a connected agent. This is a silent terminal action: it does not close the Connector transport session, and no text reply should be sent after calling it.",
533
533
  parameters: Type.Object({
534
534
  contact: Type.String({
535
535
  description: "Handle (e.g. alice) or address of the agent to end the conversation with",
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.5.21";
1
+ export declare const PLUGIN_VERSION = "0.5.23";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.5.21";
1
+ export const PLUGIN_VERSION = "0.5.23";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "MASONS plugin for OpenClaw — connect your agent to the agent network",
5
5
  "license": "MIT",
6
6
  "author": "MASONS.ai <hello@masons.ai> (https://masons.ai)",
@@ -19,15 +19,6 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
- "scripts": {
23
- "build": "tsc",
24
- "dev": "tsc --watch",
25
- "test": "tsc -p test/tsconfig.json && node --test --loader ts-node/esm test/**/*.test.ts",
26
- "lint": "biome check",
27
- "format": "biome format --write",
28
- "prepublishOnly": "bash scripts/check-version.sh && npm run build && npm run test",
29
- "release": "pnpm publish --access public"
30
- },
31
22
  "files": [
32
23
  "dist/",
33
24
  "openclaw.plugin.json",
@@ -78,5 +69,13 @@
78
69
  "@types/ws": "^8",
79
70
  "ts-node": "^10",
80
71
  "typescript": "^5"
72
+ },
73
+ "scripts": {
74
+ "build": "tsc",
75
+ "dev": "tsc --watch",
76
+ "test": "tsc -p test/tsconfig.json && node --test --loader ts-node/esm test/**/*.test.ts",
77
+ "lint": "biome check",
78
+ "format": "biome format --write",
79
+ "release": "pnpm publish --access public"
81
80
  }
82
- }
81
+ }
@@ -1,89 +0,0 @@
1
- /**
2
- * Session Lifecycle — manages MSTP session state per contact.
3
- *
4
- * State machine: idle -> creating -> active -> recreating -> failed.
5
- * Internal key: MSTP address (not handle) — supports same handle on different Connectors.
6
- *
7
- * Responsibilities:
8
- * - Maps MSTP address to session state (sessionId, status, pending promise)
9
- * - Auto-creates sessions on demand via ensureSession()
10
- * - Deduplicates concurrent creates (second caller awaits first's promise)
11
- * - Registers inbound sessions from remote agents
12
- * - Invalidates all sessions on transport disconnect
13
- * - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
14
- *
15
- * @see docs/openclaw/session-abstraction-system-design.md §6.1
16
- */
17
- import type { ConnectorClient } from "./connector-client.js";
18
- export type SessionStatus = "idle" | "creating" | "active" | "recreating" | "failed";
19
- export interface SessionState {
20
- status: SessionStatus;
21
- sessionId: string | null;
22
- pendingRequestId: string | null;
23
- retryCount: number;
24
- /** Promise that resolves when session creation completes. Used for dedup. */
25
- createPromise: Promise<string> | null;
26
- /** Resolve function for the create promise. */
27
- createResolve: ((sessionId: string) => void) | null;
28
- /** Reject function for the create promise. */
29
- createReject: ((err: Error) => void) | null;
30
- }
31
- export declare class SessionLifecycle {
32
- private readonly client;
33
- /** Primary map: MSTP address -> session state */
34
- private readonly sessions;
35
- /** Reverse index: sessionId -> MSTP address */
36
- private readonly sessionToAddress;
37
- /** Configurable timeout for tests */
38
- private timeoutMs;
39
- constructor(client: ConnectorClient);
40
- /**
41
- * Ensure an active session exists for the given address.
42
- * Returns the sessionId. Creates a new session if none exists.
43
- * Concurrent callers get the same promise (dedup).
44
- */
45
- ensureSession(address: string): Promise<string>;
46
- /**
47
- * Get the active sessionId for an address, or null.
48
- */
49
- getSession(address: string): string | null;
50
- /**
51
- * Close the session for an address.
52
- */
53
- closeSession(address: string, reason?: string): void;
54
- /**
55
- * Register an inbound session (remote initiated).
56
- */
57
- registerInbound(sessionId: string, address: string): void;
58
- /**
59
- * Invalidate all sessions. Called on transport disconnect.
60
- * Conversations are preserved but sessions are cleared.
61
- */
62
- invalidateAll(): void;
63
- /**
64
- * Reverse lookup: sessionId -> MSTP address.
65
- */
66
- getAddressBySessionId(sessionId: string): string | undefined;
67
- /**
68
- * Get all session entries (for listing conversations).
69
- */
70
- listSessions(): Array<{
71
- address: string;
72
- status: SessionStatus;
73
- sessionId: string | null;
74
- }>;
75
- /**
76
- * Remove a session entry entirely (for endConversation).
77
- */
78
- removeSession(address: string): void;
79
- /** @internal Override timeout for tests. */
80
- _setTimeoutForTesting(ms: number): void;
81
- /** @internal Reset for test isolation. */
82
- _resetForTesting(): void;
83
- private subscribe;
84
- private createNewSession;
85
- private handleSessionCreated;
86
- private handleSessionEnded;
87
- private handleSessionError;
88
- }
89
- //# sourceMappingURL=session-lifecycle.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-lifecycle.d.ts","sourceRoot":"","sources":["../src/session-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACtC,+CAA+C;IAC/C,aAAa,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,8CAA8C;IAC9C,YAAY,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AAaD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D,qCAAqC;IACrC,OAAO,CAAC,SAAS,CAA6B;gBAElC,MAAM,EAAE,eAAe;IASnC;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB/C;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQ1C;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBpD;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBzD;;;OAGG;IACH,aAAa,IAAI,IAAI;IAqBrB;;OAEG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,YAAY,IAAI,KAAK,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;QACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;IAgBF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAcpC,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIvC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;IAmBxB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,gBAAgB;IA+GxB,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,kBAAkB;CA0B3B"}
@@ -1,348 +0,0 @@
1
- /**
2
- * Session Lifecycle — manages MSTP session state per contact.
3
- *
4
- * State machine: idle -> creating -> active -> recreating -> failed.
5
- * Internal key: MSTP address (not handle) — supports same handle on different Connectors.
6
- *
7
- * Responsibilities:
8
- * - Maps MSTP address to session state (sessionId, status, pending promise)
9
- * - Auto-creates sessions on demand via ensureSession()
10
- * - Deduplicates concurrent creates (second caller awaits first's promise)
11
- * - Registers inbound sessions from remote agents
12
- * - Invalidates all sessions on transport disconnect
13
- * - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
14
- *
15
- * @see docs/openclaw/session-abstraction-system-design.md §6.1
16
- */
17
- import createDebug from "debug";
18
- const dbg = createDebug("agent-network:session-lifecycle");
19
- // ---------------------------------------------------------------------------
20
- // Constants
21
- // ---------------------------------------------------------------------------
22
- const SESSION_CREATE_TIMEOUT_MS = 15_000;
23
- const MAX_RETRY_COUNT = 1;
24
- // ---------------------------------------------------------------------------
25
- // SessionLifecycle
26
- // ---------------------------------------------------------------------------
27
- export class SessionLifecycle {
28
- client;
29
- /** Primary map: MSTP address -> session state */
30
- sessions = new Map();
31
- /** Reverse index: sessionId -> MSTP address */
32
- sessionToAddress = new Map();
33
- /** Configurable timeout for tests */
34
- timeoutMs = SESSION_CREATE_TIMEOUT_MS;
35
- constructor(client) {
36
- this.client = client;
37
- this.subscribe();
38
- }
39
- // -------------------------------------------------------------------------
40
- // Public API
41
- // -------------------------------------------------------------------------
42
- /**
43
- * Ensure an active session exists for the given address.
44
- * Returns the sessionId. Creates a new session if none exists.
45
- * Concurrent callers get the same promise (dedup).
46
- */
47
- ensureSession(address) {
48
- const state = this.sessions.get(address);
49
- // Active session — return immediately
50
- if (state?.status === "active" && state.sessionId) {
51
- return Promise.resolve(state.sessionId);
52
- }
53
- // Creating/recreating — dedup: return existing promise
54
- if ((state?.status === "creating" || state?.status === "recreating") &&
55
- state.createPromise) {
56
- return state.createPromise;
57
- }
58
- // Idle, failed, or no state — create new session
59
- return this.createNewSession(address, state?.retryCount ?? 0);
60
- }
61
- /**
62
- * Get the active sessionId for an address, or null.
63
- */
64
- getSession(address) {
65
- const state = this.sessions.get(address);
66
- if (state?.status === "active" && state.sessionId) {
67
- return state.sessionId;
68
- }
69
- return null;
70
- }
71
- /**
72
- * Close the session for an address.
73
- */
74
- closeSession(address, reason) {
75
- const state = this.sessions.get(address);
76
- if (!state)
77
- return;
78
- if (state.sessionId) {
79
- this.client.endSession(state.sessionId, reason);
80
- this.sessionToAddress.delete(state.sessionId);
81
- }
82
- // Reject any pending create promise
83
- if (state.createReject) {
84
- state.createReject(new Error("Session closed by caller"));
85
- state.createResolve = null;
86
- state.createReject = null;
87
- state.createPromise = null;
88
- }
89
- this.sessions.delete(address);
90
- dbg("closeSession address=%s", address);
91
- }
92
- /**
93
- * Register an inbound session (remote initiated).
94
- */
95
- registerInbound(sessionId, address) {
96
- // If there's an existing session for this address, clean up
97
- const existing = this.sessions.get(address);
98
- if (existing?.sessionId && existing.sessionId !== sessionId) {
99
- this.sessionToAddress.delete(existing.sessionId);
100
- }
101
- const state = {
102
- status: "active",
103
- sessionId,
104
- pendingRequestId: null,
105
- retryCount: 0,
106
- createPromise: null,
107
- createResolve: null,
108
- createReject: null,
109
- };
110
- this.sessions.set(address, state);
111
- this.sessionToAddress.set(sessionId, address);
112
- dbg("registerInbound sessionId=%s address=%s", sessionId, address);
113
- }
114
- /**
115
- * Invalidate all sessions. Called on transport disconnect.
116
- * Conversations are preserved but sessions are cleared.
117
- */
118
- invalidateAll() {
119
- const count = this.sessions.size;
120
- for (const [address, state] of this.sessions) {
121
- // Reject pending creates
122
- if (state.createReject) {
123
- state.createReject(new Error("Transport disconnected"));
124
- state.createResolve = null;
125
- state.createReject = null;
126
- state.createPromise = null;
127
- }
128
- // Reset to idle — next send() will auto-create
129
- state.status = "idle";
130
- state.sessionId = null;
131
- state.pendingRequestId = null;
132
- state.retryCount = 0;
133
- this.sessions.set(address, state);
134
- }
135
- this.sessionToAddress.clear();
136
- dbg("invalidateAll cleared %d sessions", count);
137
- }
138
- /**
139
- * Reverse lookup: sessionId -> MSTP address.
140
- */
141
- getAddressBySessionId(sessionId) {
142
- return this.sessionToAddress.get(sessionId);
143
- }
144
- /**
145
- * Get all session entries (for listing conversations).
146
- */
147
- listSessions() {
148
- const entries = [];
149
- for (const [address, state] of this.sessions) {
150
- entries.push({
151
- address,
152
- status: state.status,
153
- sessionId: state.sessionId,
154
- });
155
- }
156
- return entries;
157
- }
158
- /**
159
- * Remove a session entry entirely (for endConversation).
160
- */
161
- removeSession(address) {
162
- const state = this.sessions.get(address);
163
- if (state?.sessionId) {
164
- this.sessionToAddress.delete(state.sessionId);
165
- }
166
- if (state?.createReject) {
167
- state.createReject(new Error("Session removed"));
168
- state.createResolve = null;
169
- state.createReject = null;
170
- state.createPromise = null;
171
- }
172
- this.sessions.delete(address);
173
- }
174
- /** @internal Override timeout for tests. */
175
- _setTimeoutForTesting(ms) {
176
- this.timeoutMs = ms;
177
- }
178
- /** @internal Reset for test isolation. */
179
- _resetForTesting() {
180
- // Reject pending creates silently
181
- for (const state of this.sessions.values()) {
182
- if (state.createReject) {
183
- state.createReject(new Error("Reset for testing"));
184
- state.createResolve = null;
185
- state.createReject = null;
186
- state.createPromise = null;
187
- }
188
- }
189
- this.sessions.clear();
190
- this.sessionToAddress.clear();
191
- this.timeoutMs = SESSION_CREATE_TIMEOUT_MS;
192
- }
193
- // -------------------------------------------------------------------------
194
- // Private
195
- // -------------------------------------------------------------------------
196
- subscribe() {
197
- this.client.on("session_created", (event) => {
198
- this.handleSessionCreated(event.sessionId, event.requestId, event.direction);
199
- });
200
- this.client.on("session_error", (sessionId, message) => {
201
- this.handleSessionError(sessionId, message);
202
- });
203
- this.client.on("session_ended", (event) => {
204
- this.handleSessionEnded(event.sessionId);
205
- });
206
- this.client.on("disconnected", () => {
207
- this.invalidateAll();
208
- });
209
- }
210
- createNewSession(address, previousRetryCount) {
211
- const status = previousRetryCount > 0 ? "recreating" : "creating";
212
- let createResolve = null;
213
- let createReject = null;
214
- const createPromise = new Promise((resolve, reject) => {
215
- createResolve = resolve;
216
- createReject = reject;
217
- });
218
- const { requestId, sent } = this.client.createSession(address);
219
- if (!sent) {
220
- const state = {
221
- status: "failed",
222
- sessionId: null,
223
- pendingRequestId: null,
224
- retryCount: previousRetryCount,
225
- createPromise: null,
226
- createResolve: null,
227
- createReject: null,
228
- };
229
- this.sessions.set(address, state);
230
- return Promise.reject(new Error("Failed to create session: transport unavailable"));
231
- }
232
- const state = {
233
- status,
234
- sessionId: null,
235
- pendingRequestId: requestId,
236
- retryCount: previousRetryCount,
237
- createPromise,
238
- createResolve,
239
- createReject,
240
- };
241
- this.sessions.set(address, state);
242
- // Timeout
243
- const timer = setTimeout(() => {
244
- const current = this.sessions.get(address);
245
- if (current &&
246
- current.pendingRequestId === requestId &&
247
- (current.status === "creating" || current.status === "recreating")) {
248
- dbg("session create timeout address=%s requestId=%s", address, requestId);
249
- // Can we retry?
250
- if (current.retryCount < MAX_RETRY_COUNT) {
251
- // Move to failed, then retry
252
- current.status = "failed";
253
- current.retryCount++;
254
- current.pendingRequestId = null;
255
- const retryResolve = current.createResolve;
256
- const retryReject = current.createReject;
257
- current.createPromise = null;
258
- current.createResolve = null;
259
- current.createReject = null;
260
- this.sessions.set(address, current);
261
- // Retry: create a new session, pipe result to original promise
262
- this.createNewSession(address, current.retryCount)
263
- .then((sessionId) => retryResolve?.(sessionId))
264
- .catch((err) => retryReject?.(err));
265
- }
266
- else {
267
- // Max retries exhausted
268
- current.status = "idle";
269
- current.pendingRequestId = null;
270
- current.retryCount = 0;
271
- if (current.createReject) {
272
- current.createReject(new Error("Session creation timed out. The remote agent may be unavailable."));
273
- }
274
- current.createPromise = null;
275
- current.createResolve = null;
276
- current.createReject = null;
277
- this.sessions.set(address, current);
278
- }
279
- }
280
- }, this.timeoutMs);
281
- // Attach cleanup to the promise to clear the timer if resolved early
282
- createPromise
283
- .then(() => clearTimeout(timer))
284
- .catch(() => clearTimeout(timer));
285
- dbg("createNewSession address=%s requestId=%s status=%s retry=%d", address, requestId, status, previousRetryCount);
286
- return createPromise;
287
- }
288
- handleSessionCreated(sessionId, requestId, direction) {
289
- // Outbound: match by requestId
290
- if (direction === "outbound" && requestId) {
291
- for (const [address, state] of this.sessions) {
292
- if (state.pendingRequestId === requestId &&
293
- (state.status === "creating" || state.status === "recreating")) {
294
- state.status = "active";
295
- state.sessionId = sessionId;
296
- state.pendingRequestId = null;
297
- this.sessionToAddress.set(sessionId, address);
298
- if (state.createResolve) {
299
- state.createResolve(sessionId);
300
- }
301
- state.createPromise = null;
302
- state.createResolve = null;
303
- state.createReject = null;
304
- dbg("session created (outbound) address=%s sessionId=%s", address, sessionId);
305
- return;
306
- }
307
- }
308
- }
309
- // Inbound sessions are registered externally via registerInbound()
310
- // from the channel adapter (which has the metadata to derive the address).
311
- // We don't handle inbound SESSION_CREATED here.
312
- }
313
- handleSessionEnded(sessionId) {
314
- const address = this.sessionToAddress.get(sessionId);
315
- if (!address)
316
- return;
317
- const state = this.sessions.get(address);
318
- if (!state)
319
- return;
320
- // Only clear if this session is the current one
321
- if (state.sessionId === sessionId) {
322
- state.status = "idle";
323
- state.sessionId = null;
324
- dbg("session ended address=%s sessionId=%s", address, sessionId);
325
- }
326
- this.sessionToAddress.delete(sessionId);
327
- }
328
- handleSessionError(sessionId, message) {
329
- const address = this.sessionToAddress.get(sessionId);
330
- if (!address)
331
- return;
332
- const state = this.sessions.get(address);
333
- if (!state)
334
- return;
335
- dbg("session error address=%s sessionId=%s message=%s", address, sessionId, message);
336
- // Session error on active session — mark for auto-recreate on next send().
337
- // Reset retryCount to 0: this is a new failure context (transport error),
338
- // not a continuation of a creation timeout. The next ensureSession() gets
339
- // a fresh retry budget.
340
- if (state.status === "active") {
341
- this.sessionToAddress.delete(sessionId);
342
- state.status = "idle";
343
- state.sessionId = null;
344
- state.retryCount = 0;
345
- dbg("auto-recreate queued address=%s", address);
346
- }
347
- }
348
- }