@masons/agent-network 0.4.13 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAsB9D,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,oBAAoB,CAAC,CAAC;IAC9B,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;IACvD,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IACpE,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IACjE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CAC/D;AAED,UAAU,sBAAsB;IAC9B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzE;AAED,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAsDD,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,YAAY,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,WAAW,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,UAAU,yBAAyB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAClD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CACrD;AAkED,eAAO,MAAM,mBAAmB,EAAE,yBAuZjC,CAAC"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAsB9D,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,oBAAoB,CAAC,CAAC;IAC9B,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;IACvD,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IACpE,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IACjE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CAC/D;AAED,UAAU,sBAAsB;IAC9B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzE;AAED,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAsDD,UAAU,qBAAqB,CAAC,CAAC,GAAG,OAAO;IACzC,YAAY,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,WAAW,CAAC,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,UAAU,yBAAyB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAClD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CACrD;AAkED,eAAO,MAAM,mBAAmB,EAAE,yBAkajC,CAAC"}
package/dist/channel.js CHANGED
@@ -244,9 +244,16 @@ export const agentNetworkChannel = {
244
244
  const text = payload.text;
245
245
  if (!text)
246
246
  return;
247
- // Use ConversationManager to send the reply.
248
- // Pass the MSTP address (senderId) directly so the CM
249
- // can match the registered conversation entry.
247
+ // Guard: if conversation was ended during this dispatch
248
+ // (LLM called masons_end_conversation mid-turn), suppress
249
+ // straggler text to prevent auto-creating a new session.
250
+ // endConversation() deletes the conversation entry — its
251
+ // absence is the signal. Normal inbound replies always
252
+ // have an entry (registerInbound creates it before dispatch).
253
+ if (!conversationManager.hasConversation(senderId)) {
254
+ dbg("deliver suppressed: conversation ended senderId=%s len=%d", senderId, text.length);
255
+ return;
256
+ }
250
257
  const deliverResult = await conversationManager.send(senderId, text);
251
258
  dbg("deliver reply senderId=%s status=%s contentLength=%d", senderId, deliverResult.status, text.length);
252
259
  },
@@ -64,6 +64,14 @@ export declare class ConversationManager {
64
64
  * List all conversations with metadata.
65
65
  */
66
66
  listConversations(): ConversationSummary[];
67
+ /**
68
+ * Check if a conversation entry exists for a contact.
69
+ *
70
+ * Used by the deliver callback to detect post-end straggler text:
71
+ * endConversation() deletes the entry, so its absence signals that
72
+ * the LLM called masons_end_conversation during this dispatch.
73
+ */
74
+ hasConversation(contact: string): boolean;
67
75
  /**
68
76
  * End the conversation with a contact.
69
77
  * Closes the active session if one exists.
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;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;CACjC;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;AAMD,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAU1D;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAgDjE;;;OAGG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAyB1E;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ5D;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,iBAAiB,IAAI,mBAAmB,EAAE;IAe1C;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAkBtC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAYvC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUtC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;CAIzB"}
1
+ {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;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;CACjC;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;AAMD,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAU1D;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAgDjE;;;OAGG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAyB1E;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ5D;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,iBAAiB,IAAI,mBAAmB,EAAE;IAe1C;;;;;;OAMG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAKzC;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAkBtC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAYvC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUtC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;CAIzB"}
@@ -131,6 +131,17 @@ export class ConversationManager {
131
131
  }
132
132
  return result;
133
133
  }
134
+ /**
135
+ * Check if a conversation entry exists for a contact.
136
+ *
137
+ * Used by the deliver callback to detect post-end straggler text:
138
+ * endConversation() deletes the entry, so its absence signals that
139
+ * the LLM called masons_end_conversation during this dispatch.
140
+ */
141
+ hasConversation(contact) {
142
+ const address = this.resolveAddress(contact);
143
+ return this.conversations.has(address);
144
+ }
134
145
  /**
135
146
  * End the conversation with a contact.
136
147
  * Closes the active session if one exists.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAmHA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CA2RhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAoHA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CAgThC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -6,6 +6,7 @@ import { configureInteractive } from "./cli-setup.js";
6
6
  import { detectPendingState, getConversationManager, getStateCacheGeneration, initPluginRuntime, } from "./config.js";
7
7
  import { ownerNotesQueue } from "./owner-notes.js";
8
8
  import { consumeCurrentTurnIsOwner } from "./owner-session-state.js";
9
+ import { sentMessageBuffer } from "./sent-message-buffer.js";
9
10
  import { registerTools } from "./tools.js";
10
11
  import { consumeCurrentTurnSender } from "./turn-context.js";
11
12
  import { getUpdateInfo } from "./update-check.js";
@@ -192,8 +193,9 @@ const plugin = {
192
193
  "request action, not open-ended conversation. " +
193
194
  "After responding on the network, check: " +
194
195
  "should you report to your owner via masons_note_for_owner? " +
195
- "Should you end this interaction via masons_end_conversation? " +
196
- "Should you follow up with another agent via masons_send_message?";
196
+ "Should you follow up with another agent via masons_send_message? " +
197
+ "When the interaction is complete, call masons_end_conversation as your FINAL action — " +
198
+ "do not generate any text reply after ending.";
197
199
  api.on("before_prompt_build", async (_event, ctx) => {
198
200
  const hookCtx = ctx;
199
201
  const channelId = hookCtx?.channelId;
@@ -300,6 +302,24 @@ const plugin = {
300
302
  if (interactionCtx) {
301
303
  dynamicContext += `\n\n${interactionCtx}`;
302
304
  }
305
+ // Append sent message context — cross-session bridge (#918).
306
+ // When the LLM sent a message from the owner session via
307
+ // masons_send_message, inject the content here so the LLM
308
+ // knows what it previously said to this contact.
309
+ if (turnSender) {
310
+ const sentMsgs = sentMessageBuffer.getRecent(turnSender);
311
+ if (sentMsgs.length > 0) {
312
+ const lines = sentMsgs.map((m) => {
313
+ const preview = m.content.length > 100
314
+ ? `${m.content.slice(0, 100)}...`
315
+ : m.content;
316
+ return `• (${formatTimeAgo(m.timestamp)}): "${preview}"`;
317
+ });
318
+ dynamicContext +=
319
+ `\n\n[Your recent messages to @${turnSender}]\n` +
320
+ lines.join("\n");
321
+ }
322
+ }
303
323
  }
304
324
  }
305
325
  else {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sent Message Buffer — per-contact ring buffer of outgoing messages.
3
+ *
4
+ * When the LLM calls `masons_send_message`, the outgoing content is recorded
5
+ * here. On the next agent-network turn from that contact, `before_prompt_build`
6
+ * reads the buffer and injects the sent messages into `prependContext` so the
7
+ * LLM knows what it previously said.
8
+ *
9
+ * This is the reverse of `OwnerNotesQueue`:
10
+ * - OwnerNotesQueue: agent-network session → owner session
11
+ * - SentMessageBuffer: owner session → agent-network session
12
+ *
13
+ * Module-level singleton — shared between plugin.ts (read) and tools.ts (write).
14
+ *
15
+ * Lifecycle: in-memory only (D3). Lost on Gateway restart.
16
+ *
17
+ * @see docs/openclaw/interop-routing-system-design.md §7.1
18
+ */
19
+ export interface SentMessage {
20
+ /** The message content that was sent. */
21
+ content: string;
22
+ /** When the message was sent (Date.now()). */
23
+ timestamp: number;
24
+ }
25
+ export declare class SentMessageBuffer {
26
+ private entries;
27
+ /** Max messages retained per contact. */
28
+ static readonly MAX_PER_CONTACT = 5;
29
+ /** Messages older than this are evicted. */
30
+ static readonly TTL_MS: number;
31
+ /** Max content length stored per message. Longer content is truncated at record time. */
32
+ static readonly MAX_CONTENT_LENGTH = 500;
33
+ /**
34
+ * Record an outgoing message to a contact.
35
+ * Called by `masons_send_message` after successful send.
36
+ */
37
+ record(contact: string, content: string): void;
38
+ /**
39
+ * Get recent sent messages for a contact. Non-destructive — the same
40
+ * messages may be relevant across multiple turns with the same contact.
41
+ * Returns messages in chronological order (oldest first). Returns empty
42
+ * array if no messages exist or all are stale.
43
+ */
44
+ getRecent(contact: string): SentMessage[];
45
+ /**
46
+ * Clear all entries. For testing only.
47
+ */
48
+ clear(): void;
49
+ }
50
+ export declare const sentMessageBuffer: SentMessageBuffer;
51
+ //# sourceMappingURL=sent-message-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-message-buffer.d.ts","sourceRoot":"","sources":["../src/sent-message-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAoC;IAEnD,yCAAyC;IACzC,MAAM,CAAC,QAAQ,CAAC,eAAe,KAAK;IACpC,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,CAAC,MAAM,SAAkB;IACxC,yFAAyF;IACzF,MAAM,CAAC,QAAQ,CAAC,kBAAkB,OAAO;IAEzC;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IA2B9C;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE;IAoBzC;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAMD,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Sent Message Buffer — per-contact ring buffer of outgoing messages.
3
+ *
4
+ * When the LLM calls `masons_send_message`, the outgoing content is recorded
5
+ * here. On the next agent-network turn from that contact, `before_prompt_build`
6
+ * reads the buffer and injects the sent messages into `prependContext` so the
7
+ * LLM knows what it previously said.
8
+ *
9
+ * This is the reverse of `OwnerNotesQueue`:
10
+ * - OwnerNotesQueue: agent-network session → owner session
11
+ * - SentMessageBuffer: owner session → agent-network session
12
+ *
13
+ * Module-level singleton — shared between plugin.ts (read) and tools.ts (write).
14
+ *
15
+ * Lifecycle: in-memory only (D3). Lost on Gateway restart.
16
+ *
17
+ * @see docs/openclaw/interop-routing-system-design.md §7.1
18
+ */
19
+ // ---------------------------------------------------------------------------
20
+ // Buffer
21
+ // ---------------------------------------------------------------------------
22
+ export class SentMessageBuffer {
23
+ entries = new Map();
24
+ /** Max messages retained per contact. */
25
+ static MAX_PER_CONTACT = 5;
26
+ /** Messages older than this are evicted. */
27
+ static TTL_MS = 30 * 60 * 1000; // 30 minutes
28
+ /** Max content length stored per message. Longer content is truncated at record time. */
29
+ static MAX_CONTENT_LENGTH = 500;
30
+ /**
31
+ * Record an outgoing message to a contact.
32
+ * Called by `masons_send_message` after successful send.
33
+ */
34
+ record(contact, content) {
35
+ const now = Date.now();
36
+ const key = contact.toLowerCase();
37
+ let list = this.entries.get(key);
38
+ if (!list) {
39
+ list = [];
40
+ this.entries.set(key, list);
41
+ }
42
+ // Evict stale entries
43
+ const cutoff = now - SentMessageBuffer.TTL_MS;
44
+ const fresh = list.filter((m) => m.timestamp > cutoff);
45
+ // Cap at MAX_PER_CONTACT (drop oldest)
46
+ if (fresh.length >= SentMessageBuffer.MAX_PER_CONTACT) {
47
+ fresh.shift();
48
+ }
49
+ // Truncate content to prevent unbounded memory growth
50
+ const stored = content.length > SentMessageBuffer.MAX_CONTENT_LENGTH
51
+ ? `${content.slice(0, SentMessageBuffer.MAX_CONTENT_LENGTH)}...`
52
+ : content;
53
+ fresh.push({ content: stored, timestamp: now });
54
+ this.entries.set(key, fresh);
55
+ }
56
+ /**
57
+ * Get recent sent messages for a contact. Non-destructive — the same
58
+ * messages may be relevant across multiple turns with the same contact.
59
+ * Returns messages in chronological order (oldest first). Returns empty
60
+ * array if no messages exist or all are stale.
61
+ */
62
+ getRecent(contact) {
63
+ const key = contact.toLowerCase();
64
+ const list = this.entries.get(key);
65
+ if (!list)
66
+ return [];
67
+ const cutoff = Date.now() - SentMessageBuffer.TTL_MS;
68
+ const fresh = list.filter((m) => m.timestamp > cutoff);
69
+ if (fresh.length !== list.length) {
70
+ // Evict stale in place
71
+ if (fresh.length === 0) {
72
+ this.entries.delete(key);
73
+ }
74
+ else {
75
+ this.entries.set(key, fresh);
76
+ }
77
+ }
78
+ return fresh;
79
+ }
80
+ /**
81
+ * Clear all entries. For testing only.
82
+ */
83
+ clear() {
84
+ this.entries.clear();
85
+ }
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Singleton
89
+ // ---------------------------------------------------------------------------
90
+ export const sentMessageBuffer = new SentMessageBuffer();
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2CH,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AA0CD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAsFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAk2BhD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA4CH,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AA0CD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAsFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAs2BhD"}
package/dist/tools.js CHANGED
@@ -22,6 +22,7 @@ import { tmpdir } from "node:os";
22
22
  import { Type } from "@sinclair/typebox";
23
23
  import { clearTargetHandle, getOpenClawHome, getPendingTarget, isProfileNeeded, markProfileComplete, markProfileNeeded, requireApiKey, requireConversationManager, requirePlatformConfig, writeCredentials, } from "./config.js";
24
24
  import { ownerNotesQueue } from "./owner-notes.js";
25
+ import { sentMessageBuffer } from "./sent-message-buffer.js";
25
26
  import { acceptRequest, declineRequest, getConnectionStatus, initSetup, listConnections, listRequests, onboard, PlatformApiError, pollSetup, reconnect, requestConnection, SetupExpiredError, SetupPendingError, updateProfile, } from "./platform-client.js";
26
27
  import { fetchLatestVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
27
28
  // ---------------------------------------------------------------------------
@@ -605,14 +606,14 @@ export function registerTools(api) {
605
606
  const cm = requireConversationManager();
606
607
  const to = params.to;
607
608
  const content = params.content;
609
+ // Extract handle once — shared by pre-flight check and sent message buffer.
610
+ const handle = to.startsWith("mstps://") || to.startsWith("mstp://")
611
+ ? to.replace(/^mstps?:\/\/[^/]+\//, "").replace(/\/+$/, "")
612
+ : to;
608
613
  // --- Pre-flight: check connection status (advisory, not gate) ---
609
614
  // If the check fails (network error, API down), proceed anyway —
610
615
  // the Connector gate (Layer 2) is the authoritative enforcement.
611
616
  try {
612
- // Extract handle for connection check
613
- const handle = to.startsWith("mstps://") || to.startsWith("mstp://")
614
- ? to.replace(/^mstps?:\/\/[^/]+\//, "").replace(/\/+$/, "")
615
- : to;
616
617
  if (handle) {
617
618
  const cfg = requirePlatformConfig();
618
619
  const apiKey = requireApiKey();
@@ -630,6 +631,9 @@ export function registerTools(api) {
630
631
  }
631
632
  const result = await cm.send(to, content);
632
633
  if (result.status === "sent") {
634
+ // Record in sent message buffer so the agent-network session has
635
+ // context when the remote agent replies (cross-session bridge, #918).
636
+ sentMessageBuffer.record(handle, content);
633
637
  return textResult("Message sent.");
634
638
  }
635
639
  return textResult(result.error ??
@@ -639,7 +643,7 @@ export function registerTools(api) {
639
643
  // --- masons_end_conversation -----------------------------------------------
640
644
  api.registerTool({
641
645
  name: "masons_end_conversation",
642
- description: "End the conversation with a connected agent.",
646
+ description: "End the conversation with a connected agent. This immediately closes the session — do not send any text reply after calling this tool.",
643
647
  parameters: Type.Object({
644
648
  contact: Type.String({
645
649
  description: "Handle (e.g. alice) or address of the agent to end the conversation with",
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Plugin version — must match package.json. Validated by prepublishOnly. */
2
- export declare const PLUGIN_VERSION = "0.4.13";
2
+ export declare const PLUGIN_VERSION = "0.4.15";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Plugin version — must match package.json. Validated by prepublishOnly. */
2
- export const PLUGIN_VERSION = "0.4.13";
2
+ export const PLUGIN_VERSION = "0.4.15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
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,6 +19,15 @@
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
+ },
22
31
  "files": [
23
32
  "dist/",
24
33
  "openclaw.plugin.json",
@@ -68,13 +77,5 @@
68
77
  "@types/ws": "^8",
69
78
  "ts-node": "^10",
70
79
  "typescript": "^5"
71
- },
72
- "scripts": {
73
- "build": "tsc",
74
- "dev": "tsc --watch",
75
- "test": "tsc -p test/tsconfig.json && node --test --loader ts-node/esm test/**/*.test.ts",
76
- "lint": "biome check",
77
- "format": "biome format --write",
78
- "release": "pnpm publish --access public"
79
80
  }
80
- }
81
+ }