@xfxstudio/claworld 2026.5.27-testing.1 → 2026.5.28-testing.2
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/claworld-a2a-channel-agent/SKILL.md +7 -0
- package/skills/claworld-join-and-chat/SKILL.md +7 -0
- package/skills/claworld-management-session/SKILL.md +56 -2
- package/src/lib/relay/agent-readable-markdown.js +6 -1
- package/src/openclaw/index.js +5 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +429 -14
- package/src/openclaw/plugin/register.js +33 -54
- package/src/openclaw/runtime/feedback-helper.js +1 -41
- package/src/openclaw/runtime/http-boundary.js +49 -0
- package/src/openclaw/runtime/product-shell-helper.js +1 -48
- package/src/openclaw/runtime/working-memory.js +7 -0
- package/src/openclaw/runtime/world-membership-helper.js +1 -47
- package/src/openclaw/runtime/world-moderation-helper.js +1 -46
- package/src/product-shell/contracts/search-item.js +224 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -99,6 +100,125 @@ function normalizePluginOptionalText(value) {
|
|
|
99
100
|
return normalized || null;
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
function normalizeAssistantOutputTexts(value) {
|
|
104
|
+
const rawTexts = Array.isArray(value) ? value : [value];
|
|
105
|
+
const texts = [];
|
|
106
|
+
for (const rawText of rawTexts) {
|
|
107
|
+
const normalized = normalizePluginOptionalText(rawText);
|
|
108
|
+
if (normalized && !texts.includes(normalized)) texts.push(normalized);
|
|
109
|
+
}
|
|
110
|
+
return texts;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function pruneRecentAssistantOutputs(now = Date.now()) {
|
|
114
|
+
const pruneMap = (records) => {
|
|
115
|
+
for (const [key, record] of records.entries()) {
|
|
116
|
+
if (!record || now - Number(record.recordedAt || 0) > CLAWORLD_ASSISTANT_OUTPUT_TTL_MS) {
|
|
117
|
+
records.delete(key);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (records.size <= CLAWORLD_ASSISTANT_OUTPUT_MAX_RECORDS) return;
|
|
121
|
+
const sorted = [...records.entries()].sort((a, b) =>
|
|
122
|
+
Number(a[1]?.recordedAt || 0) - Number(b[1]?.recordedAt || 0),
|
|
123
|
+
);
|
|
124
|
+
for (const [key] of sorted.slice(0, Math.max(0, records.size - CLAWORLD_ASSISTANT_OUTPUT_MAX_RECORDS))) {
|
|
125
|
+
records.delete(key);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
pruneMap(recentAssistantOutputBySessionKey);
|
|
129
|
+
pruneMap(recentAssistantOutputBySessionId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function recordClaworldRuntimeAssistantOutput({
|
|
133
|
+
sessionKey = null,
|
|
134
|
+
sessionId = null,
|
|
135
|
+
runId = null,
|
|
136
|
+
assistantTexts = [],
|
|
137
|
+
timestamp = null,
|
|
138
|
+
recordedAt = Date.now(),
|
|
139
|
+
} = {}) {
|
|
140
|
+
const texts = normalizeAssistantOutputTexts(assistantTexts);
|
|
141
|
+
if (texts.length === 0) return false;
|
|
142
|
+
const normalizedSessionKey = normalizePluginOptionalText(sessionKey);
|
|
143
|
+
const normalizedSessionId = normalizePluginOptionalText(sessionId);
|
|
144
|
+
if (!normalizedSessionKey && !normalizedSessionId) return false;
|
|
145
|
+
const record = {
|
|
146
|
+
sessionKey: normalizedSessionKey,
|
|
147
|
+
sessionId: normalizedSessionId,
|
|
148
|
+
runId: normalizePluginOptionalText(runId),
|
|
149
|
+
assistantTexts: texts,
|
|
150
|
+
timestamp: normalizePluginOptionalText(timestamp),
|
|
151
|
+
recordedAt: Number.isFinite(Number(recordedAt)) ? Number(recordedAt) : Date.now(),
|
|
152
|
+
};
|
|
153
|
+
if (normalizedSessionKey) recentAssistantOutputBySessionKey.set(normalizedSessionKey, record);
|
|
154
|
+
if (normalizedSessionId) recentAssistantOutputBySessionId.set(normalizedSessionId, record);
|
|
155
|
+
pruneRecentAssistantOutputs(record.recordedAt);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readRecentAssistantOutputRecord({
|
|
160
|
+
sessionKeys = [],
|
|
161
|
+
sessionId = null,
|
|
162
|
+
afterMs = 0,
|
|
163
|
+
} = {}) {
|
|
164
|
+
pruneRecentAssistantOutputs();
|
|
165
|
+
const candidates = [];
|
|
166
|
+
for (const sessionKey of sessionKeys) {
|
|
167
|
+
const normalizedSessionKey = normalizePluginOptionalText(sessionKey);
|
|
168
|
+
if (!normalizedSessionKey) continue;
|
|
169
|
+
const record = recentAssistantOutputBySessionKey.get(normalizedSessionKey);
|
|
170
|
+
if (record) candidates.push(record);
|
|
171
|
+
}
|
|
172
|
+
const normalizedSessionId = normalizePluginOptionalText(sessionId);
|
|
173
|
+
if (normalizedSessionId) {
|
|
174
|
+
const record = recentAssistantOutputBySessionId.get(normalizedSessionId);
|
|
175
|
+
if (record) candidates.push(record);
|
|
176
|
+
}
|
|
177
|
+
return candidates
|
|
178
|
+
.filter((record) =>
|
|
179
|
+
Number(record?.recordedAt || 0) >= afterMs
|
|
180
|
+
&& normalizeAssistantOutputTexts(record?.assistantTexts).length > 0,
|
|
181
|
+
)
|
|
182
|
+
.sort((a, b) => Number(b.recordedAt || 0) - Number(a.recordedAt || 0))[0] || null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function waitForRecentAssistantOutputRecord({
|
|
186
|
+
sessionKeys = [],
|
|
187
|
+
sessionId = null,
|
|
188
|
+
afterMs = 0,
|
|
189
|
+
timeoutMs = CLAWORLD_ASSISTANT_OUTPUT_WAIT_MS,
|
|
190
|
+
} = {}) {
|
|
191
|
+
const startedAt = Date.now();
|
|
192
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
193
|
+
const record = readRecentAssistantOutputRecord({ sessionKeys, sessionId, afterMs });
|
|
194
|
+
if (record) return record;
|
|
195
|
+
await new Promise((resolve) => {
|
|
196
|
+
setTimeout(resolve, CLAWORLD_ASSISTANT_OUTPUT_POLL_MS);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function loadOpenClawReplyRuntime() {
|
|
203
|
+
if (!openClawReplyRuntimePromise) {
|
|
204
|
+
openClawReplyRuntimePromise = import('openclaw/plugin-sdk/reply-runtime')
|
|
205
|
+
.catch(async () => {
|
|
206
|
+
const argvPath = normalizePluginOptionalText(process.argv?.[1]);
|
|
207
|
+
if (!argvPath || !argvPath.endsWith('openclaw.mjs')) return null;
|
|
208
|
+
const runtimePath = path.join(path.dirname(argvPath), 'dist', 'plugin-sdk', 'reply-runtime.js');
|
|
209
|
+
return import(pathToFileURL(runtimePath).href).catch(() => null);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return openClawReplyRuntimePromise;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function resolveOpenClawReplyResolver(runtime = null) {
|
|
216
|
+
const directResolver = runtime?.channel?.reply?.getReplyFromConfig;
|
|
217
|
+
if (typeof directResolver === 'function') return directResolver;
|
|
218
|
+
const replyRuntime = await loadOpenClawReplyRuntime();
|
|
219
|
+
return typeof replyRuntime?.getReplyFromConfig === 'function' ? replyRuntime.getReplyFromConfig : null;
|
|
220
|
+
}
|
|
221
|
+
|
|
102
222
|
function requireClientMessageId(value = null) {
|
|
103
223
|
const normalized = normalizePluginOptionalText(value);
|
|
104
224
|
if (!normalized) {
|
|
@@ -112,6 +232,14 @@ function buildGeneratedClientMessageId() {
|
|
|
112
232
|
}
|
|
113
233
|
|
|
114
234
|
const DEFAULT_RELAY_HTTP_TIMEOUT_MS = 15_000;
|
|
235
|
+
const CLAWORLD_ASSISTANT_OUTPUT_TTL_MS = 60_000;
|
|
236
|
+
const CLAWORLD_ASSISTANT_OUTPUT_WAIT_MS = 750;
|
|
237
|
+
const CLAWORLD_ASSISTANT_OUTPUT_POLL_MS = 25;
|
|
238
|
+
const CLAWORLD_ASSISTANT_OUTPUT_MAX_RECORDS = 200;
|
|
239
|
+
|
|
240
|
+
const recentAssistantOutputBySessionKey = new Map();
|
|
241
|
+
const recentAssistantOutputBySessionId = new Map();
|
|
242
|
+
let openClawReplyRuntimePromise = null;
|
|
115
243
|
|
|
116
244
|
function buildRelayAgentSummary(item = {}) {
|
|
117
245
|
const normalizedAgentId = normalizeClaworldText(item?.agentId, null);
|
|
@@ -326,6 +454,7 @@ const CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS = [
|
|
|
326
454
|
/^↪️\s*Model Fallback:/i,
|
|
327
455
|
/^↪️\s*Model Fallback cleared:/i,
|
|
328
456
|
/^⚠️\s*Agent failed before reply:/i,
|
|
457
|
+
/^Sent the (?:reply|opener|Claworld reply)\.?$/i,
|
|
329
458
|
];
|
|
330
459
|
|
|
331
460
|
// Older/runtime-variant OpenClaw hosts may surface provider/runtime failures as
|
|
@@ -445,6 +574,87 @@ function appendRuntimeOutputPreview(previews, text) {
|
|
|
445
574
|
previews.push(preview);
|
|
446
575
|
}
|
|
447
576
|
|
|
577
|
+
function buildActiveDeliveryReplyKey({ accountId = null, targetAgentId = null } = {}) {
|
|
578
|
+
return [
|
|
579
|
+
normalizePluginOptionalText(accountId) || 'default',
|
|
580
|
+
normalizePluginOptionalText(targetAgentId) || '',
|
|
581
|
+
].join('\0');
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function resolveClaworldTargetAliases(value) {
|
|
585
|
+
const normalized = normalizeClaworldTarget(value) || normalizePluginOptionalText(value);
|
|
586
|
+
if (!normalized) return [];
|
|
587
|
+
const aliases = new Set([normalized]);
|
|
588
|
+
const atIndex = normalized.indexOf('@');
|
|
589
|
+
if (atIndex > 0) aliases.add(normalized.slice(0, atIndex));
|
|
590
|
+
return [...aliases].filter(Boolean);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function createActiveDeliveryReplyRegistry() {
|
|
594
|
+
const entriesByAccountTarget = new Map();
|
|
595
|
+
const entriesByAccount = new Map();
|
|
596
|
+
|
|
597
|
+
const addAccountEntry = (accountId, entry) => {
|
|
598
|
+
const accountKey = normalizePluginOptionalText(accountId) || 'default';
|
|
599
|
+
let set = entriesByAccount.get(accountKey);
|
|
600
|
+
if (!set) {
|
|
601
|
+
set = new Set();
|
|
602
|
+
entriesByAccount.set(accountKey, set);
|
|
603
|
+
}
|
|
604
|
+
set.add(entry);
|
|
605
|
+
return accountKey;
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const removeAccountEntry = (accountKey, entry) => {
|
|
609
|
+
const set = entriesByAccount.get(accountKey);
|
|
610
|
+
if (!set) return;
|
|
611
|
+
set.delete(entry);
|
|
612
|
+
if (set.size === 0) entriesByAccount.delete(accountKey);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
register(rawEntry = {}) {
|
|
617
|
+
const accountId = normalizePluginOptionalText(rawEntry.accountId) || 'default';
|
|
618
|
+
const targetAliases = resolveClaworldTargetAliases(rawEntry.targetAgentId);
|
|
619
|
+
const entry = {
|
|
620
|
+
...rawEntry,
|
|
621
|
+
accountId,
|
|
622
|
+
targetAgentId: targetAliases[0] || normalizePluginOptionalText(rawEntry.targetAgentId),
|
|
623
|
+
registeredAt: Date.now(),
|
|
624
|
+
};
|
|
625
|
+
const accountKey = addAccountEntry(accountId, entry);
|
|
626
|
+
const exactKeys = [];
|
|
627
|
+
for (const alias of targetAliases) {
|
|
628
|
+
const key = buildActiveDeliveryReplyKey({ accountId, targetAgentId: alias });
|
|
629
|
+
entriesByAccountTarget.set(key, entry);
|
|
630
|
+
exactKeys.push(key);
|
|
631
|
+
}
|
|
632
|
+
return () => {
|
|
633
|
+
for (const key of exactKeys) {
|
|
634
|
+
if (entriesByAccountTarget.get(key) === entry) entriesByAccountTarget.delete(key);
|
|
635
|
+
}
|
|
636
|
+
removeAccountEntry(accountKey, entry);
|
|
637
|
+
};
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
resolve({ accountId = null, to = null } = {}) {
|
|
641
|
+
const normalizedAccountId = normalizePluginOptionalText(accountId) || 'default';
|
|
642
|
+
const targetAliases = resolveClaworldTargetAliases(to);
|
|
643
|
+
for (const targetAlias of targetAliases) {
|
|
644
|
+
const exact = entriesByAccountTarget.get(buildActiveDeliveryReplyKey({
|
|
645
|
+
accountId: normalizedAccountId,
|
|
646
|
+
targetAgentId: targetAlias,
|
|
647
|
+
}));
|
|
648
|
+
if (exact) return exact;
|
|
649
|
+
}
|
|
650
|
+
if (targetAliases.length > 0) return null;
|
|
651
|
+
const accountEntries = entriesByAccount.get(normalizedAccountId);
|
|
652
|
+
if (!accountEntries || accountEntries.size !== 1) return null;
|
|
653
|
+
return [...accountEntries][0] || null;
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
448
658
|
function appendPartialContinuationChunk(currentText, chunk) {
|
|
449
659
|
const nextChunk = typeof chunk === 'string' ? chunk : '';
|
|
450
660
|
if (!nextChunk) return currentText;
|
|
@@ -456,6 +666,14 @@ function appendPartialContinuationChunk(currentText, chunk) {
|
|
|
456
666
|
return `${existing}${nextChunk}`;
|
|
457
667
|
}
|
|
458
668
|
|
|
669
|
+
function normalizeReplyResolverPayloads(result) {
|
|
670
|
+
if (!result) return [];
|
|
671
|
+
if (Array.isArray(result)) return result.filter((entry) => entry && typeof entry === 'object');
|
|
672
|
+
if (Array.isArray(result.payloads)) return result.payloads.filter((entry) => entry && typeof entry === 'object');
|
|
673
|
+
if (result && typeof result === 'object') return [result];
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
|
|
459
677
|
function buildRelayContinuationText({
|
|
460
678
|
finalTexts = [],
|
|
461
679
|
blockTexts = [],
|
|
@@ -2160,6 +2378,8 @@ function createDeliveryReplyDispatcher({
|
|
|
2160
2378
|
relayClient,
|
|
2161
2379
|
deliveryId,
|
|
2162
2380
|
sessionKey,
|
|
2381
|
+
localSessionKey = null,
|
|
2382
|
+
sessionId = null,
|
|
2163
2383
|
localAgentId = null,
|
|
2164
2384
|
allowReply = true,
|
|
2165
2385
|
logger,
|
|
@@ -2181,7 +2401,9 @@ function createDeliveryReplyDispatcher({
|
|
|
2181
2401
|
let keptSilentFallbackUsed = false;
|
|
2182
2402
|
const finalTexts = [];
|
|
2183
2403
|
const blockTexts = [];
|
|
2404
|
+
const replyResolverTexts = [];
|
|
2184
2405
|
let partialContinuationText = '';
|
|
2406
|
+
const dispatchStartedAt = Date.now();
|
|
2185
2407
|
const runtimeOutputSummary = {
|
|
2186
2408
|
counts: {
|
|
2187
2409
|
final: 0,
|
|
@@ -2197,6 +2419,10 @@ function createDeliveryReplyDispatcher({
|
|
|
2197
2419
|
nonRenderableFinal: 0,
|
|
2198
2420
|
operationalNotice: 0,
|
|
2199
2421
|
runtimeErrorFinal: 0,
|
|
2422
|
+
replyResolverPayload: 0,
|
|
2423
|
+
replyResolverNonRenderable: 0,
|
|
2424
|
+
assistantTextFallback: 0,
|
|
2425
|
+
messageToolReply: 0,
|
|
2200
2426
|
},
|
|
2201
2427
|
previews: {
|
|
2202
2428
|
final: [],
|
|
@@ -2206,6 +2432,9 @@ function createDeliveryReplyDispatcher({
|
|
|
2206
2432
|
reasoning: [],
|
|
2207
2433
|
operationalNotice: [],
|
|
2208
2434
|
runtimeErrorFinal: [],
|
|
2435
|
+
replyResolver: [],
|
|
2436
|
+
assistantTextFallback: [],
|
|
2437
|
+
messageToolReply: [],
|
|
2209
2438
|
},
|
|
2210
2439
|
relayContinuationSource: 'none',
|
|
2211
2440
|
relayContinuationPreview: null,
|
|
@@ -2246,6 +2475,60 @@ function createDeliveryReplyDispatcher({
|
|
|
2246
2475
|
}
|
|
2247
2476
|
};
|
|
2248
2477
|
|
|
2478
|
+
const recordReplyResolverPayloads = (result) => {
|
|
2479
|
+
for (const payload of normalizeReplyResolverPayloads(result)) {
|
|
2480
|
+
runtimeOutputSummary.counts.replyResolverPayload += 1;
|
|
2481
|
+
const classified = classifyRelayContinuationPayload(payload);
|
|
2482
|
+
const text = String(payload?.text ?? payload?.body ?? '').trim();
|
|
2483
|
+
if (classified.text) {
|
|
2484
|
+
if (!replyResolverTexts.includes(classified.text)) {
|
|
2485
|
+
replyResolverTexts.push(classified.text);
|
|
2486
|
+
}
|
|
2487
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.replyResolver, classified.text);
|
|
2488
|
+
}
|
|
2489
|
+
if (classified.nonRenderable) {
|
|
2490
|
+
runtimeOutputSummary.counts.replyResolverNonRenderable += 1;
|
|
2491
|
+
}
|
|
2492
|
+
if (classified.operationalNotice) {
|
|
2493
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, classified.previewText || text);
|
|
2494
|
+
}
|
|
2495
|
+
if (classified.runtimeError) {
|
|
2496
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.runtimeErrorFinal, classified.previewText || text);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
const resolveAssistantTextFallback = async () => {
|
|
2502
|
+
if (replyResolverTexts.length > 0) {
|
|
2503
|
+
return {
|
|
2504
|
+
source: 'reply_result',
|
|
2505
|
+
texts: [...replyResolverTexts],
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
const outputRecord = await waitForRecentAssistantOutputRecord({
|
|
2510
|
+
sessionKeys: [localSessionKey, sessionKey],
|
|
2511
|
+
sessionId,
|
|
2512
|
+
afterMs: dispatchStartedAt,
|
|
2513
|
+
});
|
|
2514
|
+
const assistantTexts = normalizeAssistantOutputTexts(outputRecord?.assistantTexts);
|
|
2515
|
+
if (assistantTexts.length === 0) {
|
|
2516
|
+
return {
|
|
2517
|
+
source: 'none',
|
|
2518
|
+
texts: [],
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
runtimeOutputSummary.counts.assistantTextFallback += assistantTexts.length;
|
|
2523
|
+
for (const text of assistantTexts) {
|
|
2524
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.assistantTextFallback, text);
|
|
2525
|
+
}
|
|
2526
|
+
return {
|
|
2527
|
+
source: 'assistant_text',
|
|
2528
|
+
texts: assistantTexts,
|
|
2529
|
+
};
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2249
2532
|
const recordRuntimeTextEvent = (kind, text) => {
|
|
2250
2533
|
if (!Object.prototype.hasOwnProperty.call(runtimeOutputSummary.counts, kind)) return;
|
|
2251
2534
|
runtimeOutputSummary.counts[kind] += 1;
|
|
@@ -2301,6 +2584,21 @@ function createDeliveryReplyDispatcher({
|
|
|
2301
2584
|
return true;
|
|
2302
2585
|
};
|
|
2303
2586
|
|
|
2587
|
+
const submitMessageToolReply = async ({ text } = {}) => {
|
|
2588
|
+
const normalized = sanitizeRelayContinuationText(text);
|
|
2589
|
+
if (!normalized) return false;
|
|
2590
|
+
runtimeOutputSummary.counts.messageToolReply += 1;
|
|
2591
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.messageToolReply, normalized);
|
|
2592
|
+
runtimeOutputSummary.relayContinuationSource = 'message_tool';
|
|
2593
|
+
runtimeOutputSummary.relayContinuationPreview = previewRuntimeOutputText(normalized);
|
|
2594
|
+
if (isExactNoReplyToken(normalized)) {
|
|
2595
|
+
runtimeOutputSummary.relayContinuationSource = 'message_tool_no_reply_token';
|
|
2596
|
+
runtimeOutputSummary.relayContinuationPreview = 'NO_REPLY';
|
|
2597
|
+
return await flushKeptSilent('no_reply_token');
|
|
2598
|
+
}
|
|
2599
|
+
return await flushReply(normalized);
|
|
2600
|
+
};
|
|
2601
|
+
|
|
2304
2602
|
const flushKeptSilent = async (reason = null) => {
|
|
2305
2603
|
if (replied || keptSilent || suppressed) return false;
|
|
2306
2604
|
if (allowReply === false) {
|
|
@@ -2346,18 +2644,39 @@ function createDeliveryReplyDispatcher({
|
|
|
2346
2644
|
const markDispatchIdle = async () => {
|
|
2347
2645
|
await dispatchApi.dispatcher.waitForIdle?.();
|
|
2348
2646
|
if (!replied && !suppressed) {
|
|
2647
|
+
let assistantTextFallback = {
|
|
2648
|
+
source: 'none',
|
|
2649
|
+
texts: [],
|
|
2650
|
+
};
|
|
2651
|
+
const shouldResolveAssistantTextFallback = (
|
|
2652
|
+
replyResolverTexts.length > 0
|
|
2653
|
+
|| runtimeOutputSummary.counts.assistantMessageStart > 0
|
|
2654
|
+
);
|
|
2655
|
+
if (finalTexts.length === 0 && blockTexts.length === 0 && shouldResolveAssistantTextFallback) {
|
|
2656
|
+
assistantTextFallback = await resolveAssistantTextFallback();
|
|
2657
|
+
}
|
|
2658
|
+
const continuationFinalTexts = finalTexts.length > 0
|
|
2659
|
+
? finalTexts
|
|
2660
|
+
: assistantTextFallback.texts;
|
|
2349
2661
|
const allowPartialFallback = (
|
|
2350
2662
|
runtimeOutputSummary.counts.final > 0
|
|
2351
|
-
&&
|
|
2663
|
+
&& continuationFinalTexts.length === 0
|
|
2352
2664
|
&& blockTexts.length === 0
|
|
2353
2665
|
&& runtimeOutputSummary.counts.nonRenderableFinal === 0
|
|
2354
2666
|
);
|
|
2355
2667
|
const safeContinuation = buildRelayContinuationText({
|
|
2356
|
-
finalTexts,
|
|
2668
|
+
finalTexts: continuationFinalTexts,
|
|
2357
2669
|
blockTexts,
|
|
2358
2670
|
partialText: partialContinuationText,
|
|
2359
2671
|
allowPartialFallback,
|
|
2360
2672
|
});
|
|
2673
|
+
if (
|
|
2674
|
+
safeContinuation.source === 'final'
|
|
2675
|
+
&& finalTexts.length === 0
|
|
2676
|
+
&& assistantTextFallback.source !== 'none'
|
|
2677
|
+
) {
|
|
2678
|
+
safeContinuation.source = assistantTextFallback.source;
|
|
2679
|
+
}
|
|
2361
2680
|
runtimeOutputSummary.relayContinuationSource = safeContinuation.source;
|
|
2362
2681
|
runtimeOutputSummary.relayContinuationPreview = safeContinuation.text
|
|
2363
2682
|
? previewRuntimeOutputText(safeContinuation.text)
|
|
@@ -2414,23 +2733,28 @@ function createDeliveryReplyDispatcher({
|
|
|
2414
2733
|
recordRuntimeLifecycle('compactionEnd');
|
|
2415
2734
|
},
|
|
2416
2735
|
},
|
|
2736
|
+
recordReplyResolverPayloads,
|
|
2417
2737
|
markDispatchIdle,
|
|
2418
2738
|
didReply: () => replied,
|
|
2419
2739
|
didKeepSilent: () => keptSilent,
|
|
2740
|
+
submitMessageToolReply,
|
|
2420
2741
|
getRuntimeOutputSummary: () => ({
|
|
2421
2742
|
counts: { ...runtimeOutputSummary.counts },
|
|
2422
2743
|
previews: {
|
|
2423
2744
|
final: [...runtimeOutputSummary.previews.final],
|
|
2424
2745
|
block: [...runtimeOutputSummary.previews.block],
|
|
2425
2746
|
tool: [...runtimeOutputSummary.previews.tool],
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2747
|
+
partial: [...runtimeOutputSummary.previews.partial],
|
|
2748
|
+
reasoning: [...runtimeOutputSummary.previews.reasoning],
|
|
2749
|
+
operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
|
|
2750
|
+
runtimeErrorFinal: [...runtimeOutputSummary.previews.runtimeErrorFinal],
|
|
2751
|
+
replyResolver: [...runtimeOutputSummary.previews.replyResolver],
|
|
2752
|
+
assistantTextFallback: [...runtimeOutputSummary.previews.assistantTextFallback],
|
|
2753
|
+
messageToolReply: [...runtimeOutputSummary.previews.messageToolReply],
|
|
2754
|
+
},
|
|
2755
|
+
relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
|
|
2756
|
+
relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
|
|
2757
|
+
replyTransport,
|
|
2434
2758
|
replyFallbackUsed,
|
|
2435
2759
|
keptSilentTransport,
|
|
2436
2760
|
keptSilentFallbackUsed,
|
|
@@ -2444,18 +2768,23 @@ async function runDeliveryReplyDispatch({
|
|
|
2444
2768
|
relayClient,
|
|
2445
2769
|
deliveryId,
|
|
2446
2770
|
sessionKey,
|
|
2771
|
+
localSessionKey,
|
|
2772
|
+
sessionId,
|
|
2447
2773
|
localAgentId,
|
|
2448
2774
|
allowReply,
|
|
2449
2775
|
logger,
|
|
2450
2776
|
runtimeAccountId,
|
|
2451
2777
|
inboundCtx,
|
|
2778
|
+
activeDeliveryReplies = null,
|
|
2452
2779
|
} = {}) {
|
|
2453
2780
|
const {
|
|
2454
2781
|
dispatcher,
|
|
2455
2782
|
replyOptions,
|
|
2783
|
+
recordReplyResolverPayloads,
|
|
2456
2784
|
markDispatchIdle,
|
|
2457
2785
|
didReply,
|
|
2458
2786
|
didKeepSilent,
|
|
2787
|
+
submitMessageToolReply,
|
|
2459
2788
|
getRuntimeOutputSummary,
|
|
2460
2789
|
} = createDeliveryReplyDispatcher({
|
|
2461
2790
|
runtime,
|
|
@@ -2463,19 +2792,59 @@ async function runDeliveryReplyDispatch({
|
|
|
2463
2792
|
relayClient,
|
|
2464
2793
|
deliveryId,
|
|
2465
2794
|
sessionKey,
|
|
2795
|
+
localSessionKey,
|
|
2796
|
+
sessionId,
|
|
2466
2797
|
localAgentId,
|
|
2467
2798
|
allowReply,
|
|
2468
2799
|
logger,
|
|
2469
2800
|
runtimeAccountId,
|
|
2470
2801
|
});
|
|
2471
2802
|
|
|
2472
|
-
const
|
|
2803
|
+
const baseReplyResolver = await resolveOpenClawReplyResolver(runtime);
|
|
2804
|
+
const replyResolver = baseReplyResolver
|
|
2805
|
+
? async (...args) => {
|
|
2806
|
+
const result = await baseReplyResolver(...args);
|
|
2807
|
+
recordReplyResolverPayloads(result);
|
|
2808
|
+
return result;
|
|
2809
|
+
}
|
|
2810
|
+
: undefined;
|
|
2811
|
+
|
|
2812
|
+
const dispatchParams = {
|
|
2473
2813
|
ctx: inboundCtx,
|
|
2474
2814
|
cfg: currentCfg,
|
|
2475
2815
|
dispatcher,
|
|
2476
2816
|
replyOptions,
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2817
|
+
};
|
|
2818
|
+
if (replyResolver) {
|
|
2819
|
+
dispatchParams.replyResolver = replyResolver;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
const shouldRegisterMessageToolCompat = (
|
|
2823
|
+
inboundCtx?.sessionKind === 'conversation'
|
|
2824
|
+
&& activeDeliveryReplies
|
|
2825
|
+
&& typeof activeDeliveryReplies.register === 'function'
|
|
2826
|
+
);
|
|
2827
|
+
const unregisterMessageToolCompat = shouldRegisterMessageToolCompat
|
|
2828
|
+
? activeDeliveryReplies.register({
|
|
2829
|
+
accountId: runtimeAccountId,
|
|
2830
|
+
localAgentId,
|
|
2831
|
+
targetAgentId: inboundCtx?.RelayFromAgentId || inboundCtx?.SenderId || inboundCtx?.OriginatingFrom || null,
|
|
2832
|
+
deliveryId,
|
|
2833
|
+
sessionKey,
|
|
2834
|
+
localSessionKey,
|
|
2835
|
+
sessionId,
|
|
2836
|
+
conversationKey: inboundCtx?.RelayConversationKey || inboundCtx?.conversationKey || null,
|
|
2837
|
+
submitMessageToolReply,
|
|
2838
|
+
})
|
|
2839
|
+
: null;
|
|
2840
|
+
|
|
2841
|
+
let dispatchResult;
|
|
2842
|
+
try {
|
|
2843
|
+
dispatchResult = await runtime.channel.reply.dispatchReplyFromConfig(dispatchParams);
|
|
2844
|
+
await markDispatchIdle();
|
|
2845
|
+
} finally {
|
|
2846
|
+
unregisterMessageToolCompat?.();
|
|
2847
|
+
}
|
|
2479
2848
|
|
|
2480
2849
|
return {
|
|
2481
2850
|
dispatchResult,
|
|
@@ -2529,6 +2898,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
2529
2898
|
runtime,
|
|
2530
2899
|
cfg,
|
|
2531
2900
|
inbound,
|
|
2901
|
+
activeDeliveryReplies = null,
|
|
2532
2902
|
}) {
|
|
2533
2903
|
const delivery = event?.delivery && typeof event.delivery === 'object' && !Array.isArray(event.delivery)
|
|
2534
2904
|
? event.delivery
|
|
@@ -2661,6 +3031,7 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
2661
3031
|
CommandAuthorized: commandAuthorized,
|
|
2662
3032
|
RelayDeliveryId: isRelayDelivery ? deliveryId : null,
|
|
2663
3033
|
RelayFromAgentId: fromAgentId,
|
|
3034
|
+
RelayConversationKey: metadata.conversationKey || null,
|
|
2664
3035
|
UntrustedContext,
|
|
2665
3036
|
});
|
|
2666
3037
|
|
|
@@ -2726,11 +3097,14 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
2726
3097
|
relayClient,
|
|
2727
3098
|
deliveryId,
|
|
2728
3099
|
sessionKey,
|
|
3100
|
+
localSessionKey,
|
|
3101
|
+
sessionId: sessionArtifacts.sessionId || null,
|
|
2729
3102
|
localAgentId,
|
|
2730
3103
|
allowReply,
|
|
2731
3104
|
logger,
|
|
2732
3105
|
runtimeAccountId,
|
|
2733
3106
|
inboundCtx,
|
|
3107
|
+
activeDeliveryReplies,
|
|
2734
3108
|
});
|
|
2735
3109
|
|
|
2736
3110
|
const shouldRetryKickoffDispatch = (
|
|
@@ -2772,11 +3146,14 @@ async function maybeBridgeRuntimeInboundEvent({
|
|
|
2772
3146
|
relayClient,
|
|
2773
3147
|
deliveryId,
|
|
2774
3148
|
sessionKey,
|
|
3149
|
+
localSessionKey,
|
|
3150
|
+
sessionId: sessionArtifacts.sessionId || null,
|
|
2775
3151
|
localAgentId,
|
|
2776
3152
|
allowReply,
|
|
2777
3153
|
logger,
|
|
2778
3154
|
runtimeAccountId,
|
|
2779
3155
|
inboundCtx,
|
|
3156
|
+
activeDeliveryReplies,
|
|
2780
3157
|
}));
|
|
2781
3158
|
}
|
|
2782
3159
|
|
|
@@ -2856,6 +3233,7 @@ export function createClaworldChannelPlugin({
|
|
|
2856
3233
|
const lifecycles = new Map();
|
|
2857
3234
|
const accountRuntimeContexts = new Map();
|
|
2858
3235
|
const accountBindingStates = new Map();
|
|
3236
|
+
const activeDeliveryReplies = createActiveDeliveryReplyRegistry();
|
|
2859
3237
|
|
|
2860
3238
|
function resolveAccountBindingKey(runtimeConfig = {}, fallbackAccountId = 'default') {
|
|
2861
3239
|
return String(runtimeConfig?.accountId || fallbackAccountId || 'default');
|
|
@@ -3290,6 +3668,7 @@ export function createClaworldChannelPlugin({
|
|
|
3290
3668
|
runtime: runtimeContext.runtime,
|
|
3291
3669
|
cfg: runtimeContext.cfg,
|
|
3292
3670
|
inbound,
|
|
3671
|
+
activeDeliveryReplies,
|
|
3293
3672
|
}).catch((error) => {
|
|
3294
3673
|
logger.error?.(`[claworld:${runtimeAccountId}] inbound bridge exception`, {
|
|
3295
3674
|
error: error?.message || String(error),
|
|
@@ -3692,8 +4071,44 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
3692
4071
|
deliveryMode: 'direct',
|
|
3693
4072
|
createReplyEnvelope: (params = {}) => outbound.createReplyEnvelope(params),
|
|
3694
4073
|
sendText: async (ctx = {}) => {
|
|
3695
|
-
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable for claworld outbound');
|
|
3696
4074
|
const resolvedContext = await resolveBoundRuntimeContext(ctx);
|
|
4075
|
+
const activeReply = activeDeliveryReplies.resolve({
|
|
4076
|
+
accountId: resolvedContext.accountId || ctx.accountId || null,
|
|
4077
|
+
to: ctx.to,
|
|
4078
|
+
});
|
|
4079
|
+
const activeReplyText = normalizeClaworldText(ctx.text, null);
|
|
4080
|
+
if (activeReply && activeReplyText) {
|
|
4081
|
+
const submitted = await activeReply.submitMessageToolReply?.({
|
|
4082
|
+
text: activeReplyText,
|
|
4083
|
+
to: ctx.to,
|
|
4084
|
+
});
|
|
4085
|
+
const clientMessageId = normalizePluginOptionalText(
|
|
4086
|
+
ctx.clientMessageId || ctx.metadata?.clientMessageId || null,
|
|
4087
|
+
) || buildGeneratedClientMessageId();
|
|
4088
|
+
logger.info?.(`[claworld:${resolvedContext.accountId || ctx.accountId || 'default'}] routed message tool send through active delivery reply`, {
|
|
4089
|
+
deliveryId: activeReply.deliveryId || null,
|
|
4090
|
+
sessionKey: activeReply.sessionKey || null,
|
|
4091
|
+
localSessionKey: activeReply.localSessionKey || null,
|
|
4092
|
+
targetAgentId: activeReply.targetAgentId || normalizeClaworldTarget(ctx.to) || null,
|
|
4093
|
+
submitted: submitted === true,
|
|
4094
|
+
});
|
|
4095
|
+
return {
|
|
4096
|
+
channel: 'claworld',
|
|
4097
|
+
messageId: activeReply.deliveryId || `delivery_${Date.now()}`,
|
|
4098
|
+
chatId: activeReply.targetAgentId || normalizeClaworldTarget(ctx.to) || ctx.to || null,
|
|
4099
|
+
timestamp: Date.now(),
|
|
4100
|
+
meta: {
|
|
4101
|
+
clientMessageId,
|
|
4102
|
+
sessionKey: activeReply.sessionKey || null,
|
|
4103
|
+
turnId: activeReply.deliveryId || null,
|
|
4104
|
+
conversationKey: activeReply.conversationKey || null,
|
|
4105
|
+
targetAgentId: activeReply.targetAgentId || normalizeClaworldTarget(ctx.to) || null,
|
|
4106
|
+
deliveryId: activeReply.deliveryId || null,
|
|
4107
|
+
routedVia: 'delivery_reply',
|
|
4108
|
+
},
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable for claworld outbound');
|
|
3697
4112
|
return deliverRelayMessage({
|
|
3698
4113
|
runtimeConfig: resolvedContext.runtimeConfig,
|
|
3699
4114
|
to: ctx.to,
|