@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 +7 -2
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +85 -12
- package/dist/plugin.d.ts +7 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +21 -15
- package/dist/services-retained-recipient.d.ts +15 -0
- package/dist/services-retained-recipient.d.ts.map +1 -1
- package/dist/services-retained-recipient.js +168 -11
- package/dist/services-retained-reply-context.d.ts +3 -0
- package/dist/services-retained-reply-context.d.ts.map +1 -0
- package/dist/services-retained-reply-context.js +8 -0
- package/dist/services-retained-tool-policy.d.ts +11 -0
- package/dist/services-retained-tool-policy.d.ts.map +1 -0
- package/dist/services-retained-tool-policy.js +30 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +4 -0
- package/dist/turn-context.d.ts +0 -2
- package/dist/turn-context.d.ts.map +1 -1
- package/dist/turn-context.js +0 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +2 -2
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
|
|
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 {};
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
288
|
+
let replyRouted = false;
|
|
289
|
+
const servicesRetainedReplyBlocks = [];
|
|
248
290
|
let suppressedBlocks = 0;
|
|
249
291
|
let dispatchError;
|
|
250
292
|
try {
|
|
251
|
-
|
|
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
|
-
|
|
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 (!
|
|
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:
|
|
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
|
-
|
|
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: {
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
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 {
|
|
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,
|
|
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
|
-
(
|
|
75
|
-
? "
|
|
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
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"
|
|
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;
|
package/dist/turn-context.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/turn-context.js
CHANGED
|
@@ -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.
|
|
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.
|
|
1
|
+
export const PLUGIN_VERSION = "0.6.19";
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masons/agent-network",
|
|
3
|
-
"version": "0.6.
|
|
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.
|
|
41
|
+
"pluginApi": ">=2026.6.11"
|
|
42
42
|
},
|
|
43
43
|
"extensions": [
|
|
44
44
|
"./dist/plugin.js"
|