@masons/agent-network 0.6.17 → 0.6.19

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
@@ -1,9 +1,14 @@
1
- import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";
1
+ import type { ChannelPlugin, PluginRuntime } from "openclaw/plugin-sdk/channel-core";
2
2
  import type { AgentNetworkAccount } from "./config-schema.js";
3
3
  export declare function getOwnerPassportAddress(): string | null;
4
4
  export declare function consumeIdentityLinkingNudge(): boolean;
5
5
  export declare function _setNudgePendingForTesting(value: boolean): void;
6
- declare function withAgentNetworkSilentReplyPolicy(cfg: Record<string, unknown>): Record<string, unknown>;
6
+ export declare function buildServicesRetainedRuntimeSessionKey(buildAgentSessionKey: PluginRuntime["channel"]["routing"]["buildAgentSessionKey"], params: {
7
+ agentId: string;
8
+ accountId: string;
9
+ messageRef: string;
10
+ }): string;
11
+ declare function withAgentNetworkSilentReplyPolicy(cfg: Record<string, unknown>, servicesRetained?: boolean): Record<string, unknown>;
7
12
  export declare const _withAgentNetworkSilentReplyPolicyForTesting: typeof withAgentNetworkSilentReplyPolicy;
8
13
  export declare const agentNetworkChannel: ChannelPlugin<AgentNetworkAccount>;
9
14
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EAEd,MAAM,kCAAkC,CAAC;AAa1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAkD9D,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAOD,wBAAgB,2BAA2B,IAAI,OAAO,CAIrD;AAGD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/D;AAaD,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;AA+gBpC,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,mBAAmB,CAiRlE,CAAC"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACd,MAAM,kCAAkC,CAAC;AAa1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAuD9D,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAOD,wBAAgB,2BAA2B,IAAI,OAAO,CAIrD;AAGD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/D;AAeD,wBAAgB,sCAAsC,CACpD,oBAAoB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,EACjF,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,GACA,MAAM,CAiBR;AAED,iBAAS,iCAAiC,CACxC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,gBAAgB,UAAQ,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8CzB;AAED,eAAO,MAAM,4CAA4C,0CACtB,CAAC;AAokBpC,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,mBAAmB,CAuRlE,CAAC"}
package/dist/channel.js CHANGED
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import createDebug from "debug";
3
3
  import { clearConnectorClient, clearConversationManager, extractNetworkConfig, getDmScope, initConnectorClient, initConversationManager, initToolConfig, isIdentityLinksConfigured, requirePlatformConfig, requirePluginRuntime, } from "./config.js";
4
4
  import { ConnectorClient } from "./connector-client.js";
@@ -7,8 +7,10 @@ import { clearEnvironmentContext } from "./environment-context.js";
7
7
  import { extractHandleFromAddress } from "./handle-utils.js";
8
8
  import { clearOwnerState, isOwnerAddress, markOwnerAddress, setCurrentTurnIsOwner, setCurrentTurnOwnerSignal, } from "./owner-session-state.js";
9
9
  import { ServicesRetainedRecipientConsumer, } from "./services-retained-recipient.js";
10
+ import { isServicesRetainedReplyContext, runWithServicesRetainedReplyContext, } from "./services-retained-reply-context.js";
11
+ import { runWithServicesRetainedToolPolicy } from "./services-retained-tool-policy.js";
10
12
  import { createReplyTraceMetadata, ensureTraceMetadata, } from "./trace-metadata.js";
11
- import { setCurrentTurnReplyRoute, setCurrentTurnSender, setCurrentTurnSenderAddress, } from "./turn-context.js";
13
+ import { setCurrentTurnSender, setCurrentTurnSenderAddress, } from "./turn-context.js";
12
14
  import { checkForUpdate } from "./update-check.js";
13
15
  const dbg = createDebug("agent-network:channel");
14
16
  const SERVICES_RETAINED_INBOX_PEER = "agent-network:services-retained-node-inbox";
@@ -30,7 +32,23 @@ export function _setNudgePendingForTesting(value) {
30
32
  function isRecord(value) {
31
33
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
32
34
  }
33
- function withAgentNetworkSilentReplyPolicy(cfg) {
35
+ const SERVICES_RETAINED_TOOL_ALLOW = ["masons_note_for_owner"];
36
+ export function buildServicesRetainedRuntimeSessionKey(buildAgentSessionKey, params) {
37
+ const messageRefDigest = createHash("sha256")
38
+ .update(params.messageRef, "utf8")
39
+ .digest("hex");
40
+ return buildAgentSessionKey({
41
+ agentId: params.agentId,
42
+ channel: "agent-network",
43
+ accountId: params.accountId,
44
+ peer: {
45
+ kind: "direct",
46
+ id: `services-retained-${messageRefDigest}`,
47
+ },
48
+ dmScope: "per-account-channel-peer",
49
+ });
50
+ }
51
+ function withAgentNetworkSilentReplyPolicy(cfg, servicesRetained = false) {
34
52
  const surfaces = isRecord(cfg.surfaces) ? cfg.surfaces : {};
35
53
  const existingSurface = isRecord(surfaces["agent-network"])
36
54
  ? surfaces["agent-network"]
@@ -41,6 +59,8 @@ function withAgentNetworkSilentReplyPolicy(cfg) {
41
59
  const silentReplyRewrite = isRecord(existingSurface.silentReplyRewrite)
42
60
  ? existingSurface.silentReplyRewrite
43
61
  : {};
62
+ const tools = isRecord(cfg.tools) ? cfg.tools : {};
63
+ const messages = isRecord(cfg.messages) ? cfg.messages : {};
44
64
  return {
45
65
  ...cfg,
46
66
  surfaces: {
@@ -57,6 +77,18 @@ function withAgentNetworkSilentReplyPolicy(cfg) {
57
77
  },
58
78
  },
59
79
  },
80
+ ...(servicesRetained && {
81
+ messages: {
82
+ ...messages,
83
+ visibleReplies: "automatic",
84
+ },
85
+ tools: {
86
+ ...tools,
87
+ allow: [...SERVICES_RETAINED_TOOL_ALLOW],
88
+ alsoAllow: [],
89
+ toolSearch: false,
90
+ },
91
+ }),
60
92
  };
61
93
  }
62
94
  export const _withAgentNetworkSilentReplyPolicyForTesting = withAgentNetworkSilentReplyPolicy;
@@ -123,7 +155,7 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
123
155
  console.warn(`[agent-network:channel] invalid traceparent from=${fromAddress} traceId=${inboundTrace.traceId} spanName=receive`);
124
156
  }
125
157
  }
126
- if (!options.servicesRetained &&
158
+ if (options.servicesRetained === undefined &&
127
159
  isDuplicate(fromAddress, event.content, event.sentAt)) {
128
160
  dbg("duplicate message suppressed from=%s sentAt=%s", fromAddress, event.sentAt);
129
161
  return true;
@@ -172,7 +204,6 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
172
204
  (nodeRef ? deriveNodeName(nodeRef, fromAddress) : deriveSenderName(event));
173
205
  const replyAddress = options.routableReplyAddress ??
174
206
  (options.servicesRetained ? undefined : fromAddress);
175
- setCurrentTurnReplyRoute(replyAddress ? "routable" : "unavailable");
176
207
  if (replyAddress && !conversationManager.getContactByAddress(replyAddress)) {
177
208
  conversationManager.registerInbound(contact, replyAddress);
178
209
  }
@@ -208,13 +239,23 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
208
239
  accountId: ctx.accountId,
209
240
  peer: { kind: "direct", id: senderId },
210
241
  });
242
+ const runtimeSessionKey = options.servicesRetained
243
+ ? buildServicesRetainedRuntimeSessionKey(runtime.channel.routing.buildAgentSessionKey, {
244
+ agentId: route.agentId,
245
+ accountId: ctx.accountId,
246
+ messageRef: options.servicesRetained.messageRef,
247
+ })
248
+ : route.sessionKey;
211
249
  const msgCtx = runtime.channel.reply.finalizeInboundContext({
212
250
  Body: event.content,
213
251
  RawBody: event.content,
214
252
  CommandBody: event.content,
215
253
  From: senderId,
216
254
  To: senderId,
217
- SessionKey: route.sessionKey,
255
+ SessionKey: runtimeSessionKey,
256
+ ...(options.servicesRetained && {
257
+ RuntimePolicySessionKey: runtimeSessionKey,
258
+ }),
218
259
  AccountId: ctx.accountId,
219
260
  ChatType: "direct",
220
261
  SenderName: senderName,
@@ -244,18 +285,29 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
244
285
  });
245
286
  if (replyAddress)
246
287
  client.sendTyping(replyAddress, true);
247
- let replyDelivered = false;
288
+ let replyRouted = false;
289
+ const servicesRetainedReplyBlocks = [];
248
290
  let suppressedBlocks = 0;
249
291
  let dispatchError;
250
292
  try {
251
- await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
293
+ const dispatch = () => runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
252
294
  ctx: msgCtx,
253
- cfg: withAgentNetworkSilentReplyPolicy(ctx.cfg),
295
+ cfg: withAgentNetworkSilentReplyPolicy(ctx.cfg, options.servicesRetained !== undefined),
296
+ toolsAllow: options.servicesRetained
297
+ ? SERVICES_RETAINED_TOOL_ALLOW
298
+ : undefined,
254
299
  dispatcherOptions: {
255
300
  deliver: async (payload) => {
301
+ if (options.servicesRetained && payload.isError) {
302
+ throw new Error("services_retained_runtime_policy_unavailable");
303
+ }
256
304
  const text = payload.text;
257
305
  if (!text)
258
306
  return;
307
+ if (options.servicesRetained) {
308
+ servicesRetainedReplyBlocks.push(text);
309
+ return;
310
+ }
259
311
  if (!replyAddress) {
260
312
  suppressedBlocks++;
261
313
  return;
@@ -270,7 +322,7 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
270
322
  });
271
323
  const deliverResult = await conversationManager.send(replyAddress, text, replyTrace.metadata);
272
324
  if (deliverResult.status === "sent") {
273
- replyDelivered = true;
325
+ replyRouted = true;
274
326
  }
275
327
  else {
276
328
  console.warn(`[agent-network:${ctx.accountId}] reply send FAILED to=${senderId} code=${deliverResult.errorCode ?? "(none)"} error=${deliverResult.error ?? "(none)"}`);
@@ -283,14 +335,29 @@ async function handleAddressedMessage(event, conversationManager, client, ctx, o
283
335
  },
284
336
  },
285
337
  });
338
+ if (options.servicesRetained) {
339
+ await runWithServicesRetainedToolPolicy(runtimeSessionKey, () => runWithServicesRetainedReplyContext(dispatch));
340
+ }
341
+ else {
342
+ await dispatch();
343
+ }
286
344
  if (dispatchError)
287
345
  throw dispatchError;
346
+ if (options.servicesRetained && servicesRetainedReplyBlocks.length > 0) {
347
+ const replyResult = await options.servicesRetained.reply(servicesRetainedReplyBlocks.join("\n\n"));
348
+ if (replyResult.status === "staged") {
349
+ replyRouted = true;
350
+ }
351
+ else {
352
+ throw new Error(`${replyResult.errorCode}: ${replyResult.error}`);
353
+ }
354
+ }
288
355
  if (suppressedBlocks > 0) {
289
356
  console.warn(options.servicesRetained
290
357
  ? `[agent-network:${ctx.accountId}] reply not routed for Services-retained message: canonical envelope has no reply address blocks=${suppressedBlocks}`
291
358
  : `[agent-network:${ctx.accountId}] reply suppressed (conversation ended) from=${senderId} blocks=${suppressedBlocks}`);
292
359
  }
293
- else if (!replyDelivered) {
360
+ else if (!replyRouted) {
294
361
  console.log(`[agent-network:${ctx.accountId}] dispatch completed without in-turn reply text from=${senderId} (tool-path reply or silence)`);
295
362
  }
296
363
  }
@@ -372,6 +439,9 @@ export const agentNetworkChannel = {
372
439
  outbound: {
373
440
  deliveryMode: "direct",
374
441
  async sendText(ctx) {
442
+ if (isServicesRetainedReplyContext()) {
443
+ throw new Error("services_retained_generic_send_forbidden");
444
+ }
375
445
  const state = ctx.accountId ? accounts.get(ctx.accountId) : undefined;
376
446
  if (!state)
377
447
  return { channel: "agent-network", messageId: "" };
@@ -417,7 +487,10 @@ export const agentNetworkChannel = {
417
487
  runtimePeerId: SERVICES_RETAINED_INBOX_PEER,
418
488
  senderName: servicesRetainedSenderName(message),
419
489
  messageSid: `an:${message.envelope.message_ref}`,
420
- servicesRetained: true,
490
+ servicesRetained: {
491
+ messageRef: message.envelope.message_ref,
492
+ reply: message.reply,
493
+ },
421
494
  }),
422
495
  });
423
496
  const state = {
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export declare function buildInteractionContext(channelId: string | undefined, turnSender: string | null, turnSenderAddress: string | null, turnReplyRoute?: "routable" | "unavailable" | null): string | undefined;
1
+ import { evaluateServicesRetainedToolPolicy } from "./services-retained-tool-policy.js";
2
+ export declare function buildInteractionContext(channelId: string | undefined, turnSender: string | null, turnSenderAddress: string | null, servicesRetained?: boolean): string | undefined;
2
3
  interface OpenClawPluginApi {
3
4
  runtime: unknown;
4
5
  registerChannel(opts: {
@@ -8,6 +9,11 @@ interface OpenClawPluginApi {
8
9
  optional?: boolean;
9
10
  name?: string;
10
11
  }): void;
12
+ registerTrustedToolPolicy(policy: {
13
+ id: string;
14
+ description: string;
15
+ evaluate: typeof evaluateServicesRetainedToolPolicy;
16
+ }): void;
11
17
  on(event: string, handler: (...args: unknown[]) => unknown): void;
12
18
  }
13
19
  declare const plugin: {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAsFA,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,cAAc,GAAE,UAAU,GAAG,aAAa,GAAG,IAAW,GACvD,MAAM,GAAG,SAAS,CA+EpB;AAKD,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,IAAI,CAAC;IACR,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAaI,iBAAiB;CA2YhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,kCAAkC,EAEnC,MAAM,oCAAoC,CAAC;AAgE5C,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,gBAAgB,UAAQ,GACvB,MAAM,GAAG,SAAS,CA+EpB;AAKD,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CACV,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,IAAI,CAAC;IACR,yBAAyB,CAAC,MAAM,EAAE;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,OAAO,kCAAkC,CAAC;KACrD,GAAG,IAAI,CAAC;IACT,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAaI,iBAAiB;CAiZhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/plugin.js CHANGED
@@ -6,8 +6,10 @@ import { getAgentIdentity } from "./environment-context.js";
6
6
  import { ownerNotesQueue } from "./owner-notes.js";
7
7
  import { consumeCurrentTurnIsOwner, consumeCurrentTurnOwnerSignal, } from "./owner-session-state.js";
8
8
  import { sentMessageBuffer } from "./sent-message-buffer.js";
9
+ import { isServicesRetainedReplyContext } from "./services-retained-reply-context.js";
10
+ import { evaluateServicesRetainedToolPolicy, SERVICES_RETAINED_TOOL_POLICY_ID, } from "./services-retained-tool-policy.js";
9
11
  import { registerTools } from "./tools.js";
10
- import { consumeCurrentTurnReplyRoute, consumeCurrentTurnSender, consumeCurrentTurnSenderAddress, setCurrentTurnChannelId, setCurrentTurnIsOwnerForTools, setCurrentTurnOwnerSignalForTools, } from "./turn-context.js";
12
+ import { consumeCurrentTurnSender, consumeCurrentTurnSenderAddress, setCurrentTurnChannelId, setCurrentTurnIsOwnerForTools, setCurrentTurnOwnerSignalForTools, } from "./turn-context.js";
11
13
  import { getUpdateInfo } from "./update-check.js";
12
14
  import { PLUGIN_VERSION } from "./version.js";
13
15
  function formatTimeAgo(timestamp) {
@@ -38,7 +40,7 @@ function drainAndFormatNotes(reportInstruction) {
38
40
  reportInstruction);
39
41
  }
40
42
  const MAX_INTERACTIONS = 5;
41
- export function buildInteractionContext(channelId, turnSender, turnSenderAddress, turnReplyRoute = null) {
43
+ export function buildInteractionContext(channelId, turnSender, turnSenderAddress, servicesRetained = false) {
42
44
  const cm = getConversationManager();
43
45
  if (!cm)
44
46
  return undefined;
@@ -71,8 +73,8 @@ export function buildInteractionContext(channelId, turnSender, turnSenderAddress
71
73
  : turnSender;
72
74
  return (`${header}\n${lines.join("\n")}\n` +
73
75
  `[This turn] From ${from}. ${initiated}\n` +
74
- (turnReplyRoute === "unavailable"
75
- ? "No reply route is available for this message. "
76
+ (servicesRetained
77
+ ? "Your text reply uses the Services-retained reply authority for this message. "
76
78
  : "Your text reply goes to this sender. ") +
77
79
  "To report to your owner: masons_note_for_owner.");
78
80
  }
@@ -94,6 +96,11 @@ const plugin = {
94
96
  configSchema: pluginManifest.configSchema,
95
97
  register(api) {
96
98
  initPluginRuntime(api.runtime);
99
+ api.registerTrustedToolPolicy({
100
+ id: SERVICES_RETAINED_TOOL_POLICY_ID,
101
+ description: "Deny every tool except the owner note during Services-retained recipient turns.",
102
+ evaluate: evaluateServicesRetainedToolPolicy,
103
+ });
97
104
  registerTools(api);
98
105
  try {
99
106
  api.registerChannel({
@@ -145,7 +152,7 @@ const plugin = {
145
152
  const turnOwnerSignal = consumeCurrentTurnOwnerSignal();
146
153
  const turnSender = consumeCurrentTurnSender();
147
154
  const turnSenderAddress = consumeCurrentTurnSenderAddress();
148
- const turnReplyRoute = consumeCurrentTurnReplyRoute();
155
+ const servicesRetainedReply = isServicesRetainedReplyContext();
149
156
  setCurrentTurnChannelId(channelId ?? null);
150
157
  setCurrentTurnIsOwnerForTools(turnIsOwner);
151
158
  setCurrentTurnOwnerSignalForTools(turnOwnerSignal);
@@ -211,16 +218,15 @@ const plugin = {
211
218
  }
212
219
  }
213
220
  else {
214
- dynamicContext =
215
- turnReplyRoute === "unavailable"
216
- ? "[Agent Network Services-retained message] This canonical message carries no reply address. " +
217
- "A plain text reply will NOT be routed to its sender. Do not infer or fabricate an address or Session. " +
218
- "Treat the message as inbound information; to report something to your owner, call masons_note_for_owner."
219
- : "[Agent Network Routing] This turn was triggered by a message " +
220
- "on the agent network. Your text reply will be sent to that sender " +
221
- "your owner (Principal) will NOT see it. To report something to your owner, " +
222
- "call masons_note_for_owner.";
223
- const interactionCtx = buildInteractionContext(channelId, turnSender, turnSenderAddress, turnReplyRoute);
221
+ dynamicContext = servicesRetainedReply
222
+ ? "[Agent Network — Services-retained message] Reply normally with plain text. " +
223
+ "The Host will use this message's Services-owned opaque reply authority; do not call masons_send_message, infer an address, or fabricate a Session for this reply. " +
224
+ "To report something separately to your owner, call masons_note_for_owner."
225
+ : "[Agent Network Routing] This turn was triggered by a message " +
226
+ "on the agent network. Your text reply will be sent to that sender — " +
227
+ "your owner (Principal) will NOT see it. To report something to your owner, " +
228
+ "call masons_note_for_owner.";
229
+ const interactionCtx = buildInteractionContext(channelId, turnSender, turnSenderAddress, servicesRetainedReply);
224
230
  if (interactionCtx) {
225
231
  dynamicContext += `\n\n${interactionCtx}`;
226
232
  }
@@ -2,7 +2,17 @@ import { type RuntimeExactTargetPresentationEnvelopeV1 } from "./_vendor/runtime
2
2
  export interface ServicesRetainedRecipientPresentation {
3
3
  contentType: string;
4
4
  envelope: RuntimeExactTargetPresentationEnvelopeV1;
5
+ reply: ServicesRetainedRecipientReply;
5
6
  }
7
+ export type ServicesRetainedRecipientReplyResult = {
8
+ status: "staged";
9
+ } | {
10
+ status: "failed";
11
+ errorCode: string;
12
+ error: string;
13
+ retryable: boolean;
14
+ };
15
+ export type ServicesRetainedRecipientReply = (content: string) => Promise<ServicesRetainedRecipientReplyResult>;
6
16
  export interface ServicesRetainedRecipientLogger {
7
17
  info(message: string): void;
8
18
  warn(message: string): void;
@@ -30,6 +40,7 @@ export declare class ServicesRetainedRecipientConsumer {
30
40
  private presentedOffer;
31
41
  private acquirePermanentlyBlocked;
32
42
  private consumptionPermanentlyBlocked;
43
+ private replyPermanentlyBlocked;
33
44
  private consecutiveHeartbeatNotFound;
34
45
  private stopped;
35
46
  private activationPromise;
@@ -45,7 +56,11 @@ export declare class ServicesRetainedRecipientConsumer {
45
56
  requestDrain(): Promise<void>;
46
57
  private activate;
47
58
  private drain;
59
+ private advancePresentedOffer;
48
60
  private consumePresentedOffer;
61
+ private createReplyAction;
62
+ private submitReplyAttempt;
63
+ private submitReply;
49
64
  private scheduleHeartbeat;
50
65
  private heartbeat;
51
66
  private recoverEndpoint;
@@ -1 +1 @@
1
- {"version":3,"file":"services-retained-recipient.d.ts","sourceRoot":"","sources":["../src/services-retained-recipient.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,wCAAwC,EAI9C,MAAM,2CAA2C,CAAC;AAKnD,MAAM,WAAW,qCAAqC;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,wCAAwC,CAAC;CACpD;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,OAAO,EAAE,qCAAqC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,MAAM,EAAE,+BAA+B,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAaD,qBAAa,iCAAiC;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IAEjD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,6BAA6B,CAAS;IAC9C,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,oBAAoB,CAA8C;IAC1E,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,EAAE,gCAAgC;IAc/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAerB,QAAQ;YAoDR,KAAK;YA0EL,qBAAqB;IAwDnC,OAAO,CAAC,iBAAiB;YAcX,SAAS;IAgCvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,mBAAmB;CAI5B"}
1
+ {"version":3,"file":"services-retained-recipient.d.ts","sourceRoot":"","sources":["../src/services-retained-recipient.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,wCAAwC,EAM9C,MAAM,2CAA2C,CAAC;AAKnD,MAAM,WAAW,qCAAqC;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,wCAAwC,CAAC;IACnD,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,MAAM,MAAM,oCAAoC,GAC5C;IACE,MAAM,EAAE,QAAQ,CAAC;CAClB,GACD;IACE,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEN,MAAM,MAAM,8BAA8B,GAAG,CAC3C,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,oCAAoC,CAAC,CAAC;AAiBnD,MAAM,WAAW,+BAA+B;IAC9C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,OAAO,EAAE,qCAAqC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,MAAM,EAAE,+BAA+B,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAeD,qBAAa,iCAAiC;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IAEjD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,6BAA6B,CAAS;IAC9C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,oBAAoB,CAA8C;IAC1E,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,EAAE,gCAAgC;IAc/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAerB,QAAQ;YAoDR,KAAK;YAsFL,qBAAqB;YAkCrB,qBAAqB;IAuDnC,OAAO,CAAC,iBAAiB;YAgCX,kBAAkB;YAkClB,WAAW;IAqFzB,OAAO,CAAC,iBAAiB;YAcX,SAAS;IAgCvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,mBAAmB;CAI5B"}
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { acquireServicesRetainedNodeRecipientMessage, consumeServicesRetainedNodeRecipientMessage, heartbeatRuntimeEndpoint, RuntimeAdapterApiError, registerRuntimeEndpoint, unregisterRuntimeEndpoint, } from "./_vendor/runtime-adapter-client/index.js";
2
+ import { acquireServicesRetainedNodeRecipientMessage, consumeServicesRetainedNodeRecipientMessage, heartbeatRuntimeEndpoint, RuntimeAdapterApiError, registerRuntimeEndpoint, replyServicesRetainedNodeRecipientMessage, unregisterRuntimeEndpoint, } from "./_vendor/runtime-adapter-client/index.js";
3
3
  const RETRY_DELAY_MS = 5_000;
4
4
  const MIN_HEARTBEAT_DELAY_MS = 1_000;
5
5
  export class ServicesRetainedRecipientConsumer {
@@ -14,6 +14,7 @@ export class ServicesRetainedRecipientConsumer {
14
14
  presentedOffer = null;
15
15
  acquirePermanentlyBlocked = false;
16
16
  consumptionPermanentlyBlocked = false;
17
+ replyPermanentlyBlocked = false;
17
18
  consecutiveHeartbeatNotFound = 0;
18
19
  stopped = true;
19
20
  activationPromise = null;
@@ -73,6 +74,7 @@ export class ServicesRetainedRecipientConsumer {
73
74
  this.presentedOffer = null;
74
75
  this.acquirePermanentlyBlocked = false;
75
76
  this.consumptionPermanentlyBlocked = false;
77
+ this.replyPermanentlyBlocked = false;
76
78
  });
77
79
  return this.stopPromise;
78
80
  }
@@ -139,9 +141,11 @@ export class ServicesRetainedRecipientConsumer {
139
141
  this.drainRequested = false;
140
142
  const endpoint = this.endpoint;
141
143
  if (this.presentedOffer) {
142
- if (this.consumptionPermanentlyBlocked)
144
+ if (this.consumptionPermanentlyBlocked ||
145
+ this.replyPermanentlyBlocked) {
143
146
  return;
144
- if (!(await this.consumePresentedOffer(endpoint)))
147
+ }
148
+ if (!(await this.advancePresentedOffer(endpoint)))
145
149
  return;
146
150
  continue;
147
151
  }
@@ -174,29 +178,65 @@ export class ServicesRetainedRecipientConsumer {
174
178
  }
175
179
  return;
176
180
  }
181
+ const offer = {
182
+ messageRef: acquired.message_ref,
183
+ replyHandle: acquired.reply_handle,
184
+ consumed: false,
185
+ replyAttempt: null,
186
+ };
187
+ this.presentedOffer = offer;
188
+ this.consumptionPermanentlyBlocked = false;
189
+ this.replyPermanentlyBlocked = false;
177
190
  let presented = false;
178
191
  try {
179
192
  presented = await this.present({
180
193
  contentType: acquired.content_type,
181
194
  envelope: acquired.envelope,
195
+ reply: this.createReplyAction(offer),
182
196
  });
183
197
  }
184
198
  catch (error) {
185
199
  this.logger.error(`OpenClaw inbound presentation failed message_ref=${acquired.message_ref}`, error);
186
200
  }
187
201
  if (!presented) {
202
+ if (this.presentedOffer === offer)
203
+ this.presentedOffer = null;
188
204
  this.scheduleDrainRetry();
189
205
  return;
190
206
  }
191
- this.presentedOffer = {
192
- messageRef: acquired.message_ref,
193
- replyHandle: acquired.reply_handle,
194
- };
195
- this.consumptionPermanentlyBlocked = false;
196
- if (!(await this.consumePresentedOffer(endpoint)))
207
+ if (!(await this.advancePresentedOffer(endpoint)))
197
208
  return;
198
209
  }
199
210
  }
211
+ async advancePresentedOffer(endpoint) {
212
+ const offer = this.presentedOffer;
213
+ if (!offer)
214
+ return true;
215
+ if (!offer.consumed && !(await this.consumePresentedOffer(endpoint))) {
216
+ return false;
217
+ }
218
+ if (this.presentedOffer !== offer)
219
+ return true;
220
+ if (!offer.replyAttempt) {
221
+ this.presentedOffer = null;
222
+ this.drainRequested = true;
223
+ return true;
224
+ }
225
+ const result = await this.submitReplyAttempt(endpoint, offer);
226
+ if (result.status === "sent") {
227
+ this.presentedOffer = null;
228
+ this.replyPermanentlyBlocked = false;
229
+ this.drainRequested = true;
230
+ return true;
231
+ }
232
+ this.logger.error(`Services-retained reply refused message_ref=${offer.messageRef} code=${result.errorCode} retryable=${String(result.retryable)}`);
233
+ if (result.retryable) {
234
+ this.scheduleDrainRetry();
235
+ return false;
236
+ }
237
+ this.replyPermanentlyBlocked = true;
238
+ return false;
239
+ }
200
240
  async consumePresentedOffer(endpoint) {
201
241
  const offer = this.presentedOffer;
202
242
  if (!offer)
@@ -211,9 +251,8 @@ export class ServicesRetainedRecipientConsumer {
211
251
  });
212
252
  if (outcome.ok) {
213
253
  this.logger.info(`Services-retained message consumed message_ref=${offer.messageRef} status=${outcome.status}`);
214
- this.presentedOffer = null;
254
+ offer.consumed = true;
215
255
  this.consumptionPermanentlyBlocked = false;
216
- this.drainRequested = true;
217
256
  return true;
218
257
  }
219
258
  const reason = "providerStatus" in outcome ? outcome.reason : undefined;
@@ -236,6 +275,124 @@ export class ServicesRetainedRecipientConsumer {
236
275
  this.logger.error(`Services-retained consumption stopped on permanent rejection message_ref=${offer.messageRef}${reason ? ` reason=${reason}` : ""}`);
237
276
  return false;
238
277
  }
278
+ createReplyAction(offer) {
279
+ return async (content) => {
280
+ if (this.presentedOffer !== offer) {
281
+ return {
282
+ status: "failed",
283
+ errorCode: "reply_handle_stale",
284
+ error: "Services-retained reply authority is no longer active",
285
+ retryable: false,
286
+ };
287
+ }
288
+ if (offer.replyAttempt && offer.replyAttempt.content !== content) {
289
+ return {
290
+ status: "failed",
291
+ errorCode: "reply_handle_conflict",
292
+ error: "Services-retained reply authority was already used with different content",
293
+ retryable: false,
294
+ };
295
+ }
296
+ offer.replyAttempt ??= {
297
+ content,
298
+ requestId: randomUUID(),
299
+ inFlight: null,
300
+ terminalResult: null,
301
+ };
302
+ return { status: "staged" };
303
+ };
304
+ }
305
+ async submitReplyAttempt(endpoint, offer) {
306
+ const attempt = offer.replyAttempt;
307
+ if (!attempt) {
308
+ return {
309
+ status: "failed",
310
+ errorCode: "reply_unavailable",
311
+ error: "Services-retained reply content was not staged",
312
+ retryable: false,
313
+ };
314
+ }
315
+ if (attempt.terminalResult)
316
+ return attempt.terminalResult;
317
+ if (attempt.inFlight)
318
+ return attempt.inFlight;
319
+ attempt.inFlight = this.submitReply(endpoint, offer.replyHandle, attempt.requestId, attempt.content).then((result) => {
320
+ if (result.status === "sent" || !result.retryable) {
321
+ attempt.terminalResult = result;
322
+ }
323
+ return result;
324
+ });
325
+ try {
326
+ return await attempt.inFlight;
327
+ }
328
+ finally {
329
+ attempt.inFlight = null;
330
+ }
331
+ }
332
+ async submitReply(endpoint, replyHandle, replyRequestId, content) {
333
+ let failure;
334
+ for (let attempt = 0; attempt < 2; attempt++) {
335
+ let outcome;
336
+ try {
337
+ outcome = await replyServicesRetainedNodeRecipientMessage(this.clientConfig, this.runtimeKey, {
338
+ type: "runtime_node_recipient_message_reply",
339
+ version: 1,
340
+ endpoint_id: endpoint.id,
341
+ endpoint_nonce: endpoint.nonce,
342
+ reply_handle: replyHandle,
343
+ reply_request_id: replyRequestId,
344
+ content,
345
+ content_type: "text/plain",
346
+ });
347
+ }
348
+ catch (error) {
349
+ const errorCode = error instanceof RuntimeAdapterApiError
350
+ ? error.code
351
+ : "services_retained_reply_failed";
352
+ return {
353
+ status: "failed",
354
+ errorCode,
355
+ error: errorText(error),
356
+ retryable: false,
357
+ };
358
+ }
359
+ if (outcome.ok) {
360
+ this.logger.info(`Services-retained reply ${outcome.status} message_ref=${outcome.messageRef}`);
361
+ return {
362
+ status: "sent",
363
+ messageRef: outcome.messageRef,
364
+ replayed: outcome.status === "already_emitted",
365
+ };
366
+ }
367
+ failure = outcome;
368
+ if (outcome.terminal || attempt === 1)
369
+ break;
370
+ }
371
+ if (!failure) {
372
+ return {
373
+ status: "failed",
374
+ errorCode: "services_retained_reply_failed",
375
+ error: "Services-retained reply did not produce an outcome",
376
+ retryable: false,
377
+ };
378
+ }
379
+ const reason = "reason" in failure && typeof failure.reason === "string"
380
+ ? failure.reason
381
+ : "assignmentFailureCategory" in failure
382
+ ? failure.assignmentFailureCategory
383
+ : undefined;
384
+ const errorCode = reason ?? "services_retained_reply_failed";
385
+ const detail = "detail" in failure && typeof failure.detail === "string"
386
+ ? failure.detail
387
+ : errorCode;
388
+ this.logger.warn(`Services-retained reply failed code=${errorCode} terminal=${String(failure.terminal)} detail=${detail}`);
389
+ return {
390
+ status: "failed",
391
+ errorCode,
392
+ error: detail,
393
+ retryable: !failure.terminal,
394
+ };
395
+ }
239
396
  scheduleHeartbeat() {
240
397
  this.clearHeartbeatTimer();
241
398
  if (this.stopped || !this.endpoint)
@@ -0,0 +1,3 @@
1
+ export declare function runWithServicesRetainedReplyContext<T>(callback: () => T): T;
2
+ export declare function isServicesRetainedReplyContext(): boolean;
3
+ //# sourceMappingURL=services-retained-reply-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services-retained-reply-context.d.ts","sourceRoot":"","sources":["../src/services-retained-reply-context.ts"],"names":[],"mappings":"AAIA,wBAAgB,mCAAmC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAE3E;AAED,wBAAgB,8BAA8B,IAAI,OAAO,CAExD"}
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const servicesRetainedReplyContext = new AsyncLocalStorage();
3
+ export function runWithServicesRetainedReplyContext(callback) {
4
+ return servicesRetainedReplyContext.run(true, callback);
5
+ }
6
+ export function isServicesRetainedReplyContext() {
7
+ return servicesRetainedReplyContext.getStore() === true;
8
+ }
@@ -0,0 +1,11 @@
1
+ export declare const SERVICES_RETAINED_TOOL_POLICY_ID = "agent-network-services-retained-turn-v1";
2
+ export declare function runWithServicesRetainedToolPolicy<T>(sessionKey: string, callback: () => Promise<T> | T): Promise<T>;
3
+ export declare function evaluateServicesRetainedToolPolicy(event: {
4
+ toolName: string;
5
+ }, ctx: {
6
+ sessionKey?: string;
7
+ }): {
8
+ allow: false;
9
+ reason: string;
10
+ } | undefined;
11
+ //# sourceMappingURL=services-retained-tool-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services-retained-tool-policy.d.ts","sourceRoot":"","sources":["../src/services-retained-tool-policy.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gCAAgC,4CACF,CAAC;AAE5C,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAeZ;AAED,wBAAgB,kCAAkC,CAChD,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC3B,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAY9C"}
@@ -0,0 +1,30 @@
1
+ const SERVICES_RETAINED_OWNER_NOTE_TOOL = "masons_note_for_owner";
2
+ const activeSessionRefCounts = new Map();
3
+ export const SERVICES_RETAINED_TOOL_POLICY_ID = "agent-network-services-retained-turn-v1";
4
+ export async function runWithServicesRetainedToolPolicy(sessionKey, callback) {
5
+ activeSessionRefCounts.set(sessionKey, (activeSessionRefCounts.get(sessionKey) ?? 0) + 1);
6
+ try {
7
+ return await callback();
8
+ }
9
+ finally {
10
+ const remaining = (activeSessionRefCounts.get(sessionKey) ?? 1) - 1;
11
+ if (remaining > 0) {
12
+ activeSessionRefCounts.set(sessionKey, remaining);
13
+ }
14
+ else {
15
+ activeSessionRefCounts.delete(sessionKey);
16
+ }
17
+ }
18
+ }
19
+ export function evaluateServicesRetainedToolPolicy(event, ctx) {
20
+ if (!ctx.sessionKey || !activeSessionRefCounts.has(ctx.sessionKey)) {
21
+ return undefined;
22
+ }
23
+ if (event.toolName === SERVICES_RETAINED_OWNER_NOTE_TOOL) {
24
+ return undefined;
25
+ }
26
+ return {
27
+ allow: false,
28
+ reason: "Services-retained recipient turns permit only the owner-note capability.",
29
+ };
30
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAuEA,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,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;AAE3E,UAAU,OAAO;IACf,YAAY,CACV,IAAI,EAAE,cAAc,GAAG,WAAW,EAWlC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC7D,IAAI,CAAC;CACT;AAoDD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAwSD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAs2BhD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAwEA,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,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;AAE3E,UAAU,OAAO;IACf,YAAY,CACV,IAAI,EAAE,cAAc,GAAG,WAAW,EAWlC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC7D,IAAI,CAAC;CACT;AAoDD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAwSD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA22BhD"}
package/dist/tools.js CHANGED
@@ -7,6 +7,7 @@ import { extractHandleFromAddress } from "./handle-utils.js";
7
7
  import { ownerNotesQueue } from "./owner-notes.js";
8
8
  import { acceptRequest, declineRequest, listConnections, listRequests, PlatformApiError, PlatformNetworkError, requestConnection, updateProfile, } from "./platform-client.js";
9
9
  import { sentMessageBuffer } from "./sent-message-buffer.js";
10
+ import { isServicesRetainedReplyContext } from "./services-retained-reply-context.js";
10
11
  import { getCurrentTurnChannelId, getCurrentTurnIsOwnerNonConsuming, getCurrentTurnOwnerSignal, } from "./turn-context.js";
11
12
  import { getLatestPublishedVersion, getPluginVersion, getUpdateInfo, } from "./update-check.js";
12
13
  const PROFILE_FIELDS = new Set(["name", "scope", "about", "audience"]);
@@ -584,6 +585,9 @@ export function registerTools(api) {
584
585
  }),
585
586
  }),
586
587
  execute: withUpdateNotice(async (_id, params) => {
588
+ if (isServicesRetainedReplyContext()) {
589
+ return textResult("This Services-retained message must be answered with an ordinary plain-text reply. The Host will apply its opaque reply authority; masons_send_message cannot be used as a fallback for this turn.");
590
+ }
587
591
  const cm = requireConversationManager();
588
592
  const to = params.to;
589
593
  const content = params.content;
@@ -1,13 +1,11 @@
1
1
  import type { OwnerSignal } from "./owner-session-state.js";
2
2
  export declare function setCurrentTurnSender(contact: string | null): void;
3
3
  export declare function setCurrentTurnSenderAddress(address: string | null): void;
4
- export declare function setCurrentTurnReplyRoute(route: "routable" | "unavailable"): void;
5
4
  export declare function setCurrentTurnChannelId(channelId: string | null): void;
6
5
  export declare function setCurrentTurnIsOwnerForTools(value: boolean): void;
7
6
  export declare function setCurrentTurnOwnerSignalForTools(signal: OwnerSignal): void;
8
7
  export declare function consumeCurrentTurnSender(): string | null;
9
8
  export declare function consumeCurrentTurnSenderAddress(): string | null;
10
- export declare function consumeCurrentTurnReplyRoute(): "routable" | "unavailable" | null;
11
9
  export declare function getCurrentTurnChannelId(): string | null;
12
10
  export declare function getCurrentTurnIsOwnerNonConsuming(): boolean;
13
11
  export declare function getCurrentTurnOwnerSignal(): OwnerSignal;
@@ -1 +1 @@
1
- {"version":3,"file":"turn-context.d.ts","sourceRoot":"","sources":["../src/turn-context.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA0C5D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjE;AAUD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExE;AAGD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,GAAG,aAAa,GAChC,IAAI,CAEN;AAOD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtE;AAOD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElE;AAUD,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAE3E;AAUD,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAIxD;AAGD,wBAAgB,+BAA+B,IAAI,MAAM,GAAG,IAAI,CAI/D;AAGD,wBAAgB,4BAA4B,IACxC,UAAU,GACV,aAAa,GACb,IAAI,CAIP;AAMD,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAMD,wBAAgB,iCAAiC,IAAI,OAAO,CAE3D;AAOD,wBAAgB,yBAAyB,IAAI,WAAW,CAEvD"}
1
+ {"version":3,"file":"turn-context.d.ts","sourceRoot":"","sources":["../src/turn-context.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAyC5D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjE;AAUD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExE;AAOD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtE;AAOD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElE;AAUD,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAE3E;AAUD,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAIxD;AAGD,wBAAgB,+BAA+B,IAAI,MAAM,GAAG,IAAI,CAI/D;AAMD,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAMD,wBAAgB,iCAAiC,IAAI,OAAO,CAE3D;AAOD,wBAAgB,yBAAyB,IAAI,WAAW,CAEvD"}
@@ -1,6 +1,5 @@
1
1
  let currentTurnSender = null;
2
2
  let currentTurnSenderAddress = null;
3
- let currentTurnReplyRoute = null;
4
3
  let currentTurnChannelId = null;
5
4
  let currentTurnIsOwner = false;
6
5
  let currentTurnOwnerSignalForTools = null;
@@ -10,9 +9,6 @@ export function setCurrentTurnSender(contact) {
10
9
  export function setCurrentTurnSenderAddress(address) {
11
10
  currentTurnSenderAddress = address;
12
11
  }
13
- export function setCurrentTurnReplyRoute(route) {
14
- currentTurnReplyRoute = route;
15
- }
16
12
  export function setCurrentTurnChannelId(channelId) {
17
13
  currentTurnChannelId = channelId;
18
14
  }
@@ -32,11 +28,6 @@ export function consumeCurrentTurnSenderAddress() {
32
28
  currentTurnSenderAddress = null;
33
29
  return value;
34
30
  }
35
- export function consumeCurrentTurnReplyRoute() {
36
- const value = currentTurnReplyRoute;
37
- currentTurnReplyRoute = null;
38
- return value;
39
- }
40
31
  export function getCurrentTurnChannelId() {
41
32
  return currentTurnChannelId;
42
33
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.6.17";
1
+ export declare const PLUGIN_VERSION = "0.6.19";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.6.17";
1
+ export const PLUGIN_VERSION = "0.6.19";
@@ -3,6 +3,7 @@
3
3
  "channels": ["agent-network"],
4
4
  "skills": ["skills/agent-network"],
5
5
  "contracts": {
6
+ "trustedToolPolicies": ["agent-network-services-retained-turn-v1"],
6
7
  "tools": [
7
8
  "masons_setup",
8
9
  "masons_update_profile",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/agent-network",
3
- "version": "0.6.17",
3
+ "version": "0.6.19",
4
4
  "description": "MASONS Agent Network — OpenClaw channel plugin for connecting agent runtimes to the Agent Network over MSTP.",
5
5
  "license": "MIT",
6
6
  "author": "MASONS.ai <hello@masons.ai> (https://masons.ai)",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "openclaw": {
40
40
  "compat": {
41
- "pluginApi": ">=2026.3.28"
41
+ "pluginApi": ">=2026.6.11"
42
42
  },
43
43
  "extensions": [
44
44
  "./dist/plugin.js"