@xfxstudio/claworld 0.2.23 → 0.2.24
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/src/lib/chat-request.js +2 -1
- package/src/lib/relay/kickoff-progress.js +162 -0
- package/src/lib/relay/kickoff-text.js +202 -67
- package/src/lib/relay/shared.js +30 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +294 -64
- package/src/openclaw/plugin/relay-client.js +37 -1
- package/src/openclaw/runtime/tool-contracts.js +22 -13
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/lib/chat-request.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createKickoffBrief } from './relay/kickoff-text.js';
|
|
2
|
+
import { normalizeAcceptedChatKickoffRecord } from './relay/kickoff-progress.js';
|
|
2
3
|
|
|
3
4
|
function normalizeText(value, fallback = null) {
|
|
4
5
|
if (value == null) return fallback;
|
|
@@ -358,7 +359,7 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
|
|
|
358
359
|
if (acceptedByAgentId) normalized.acceptedByAgentId = acceptedByAgentId;
|
|
359
360
|
const approvalGrantId = normalizeText(input.approvalGrantId, null);
|
|
360
361
|
if (approvalGrantId) normalized.approvalGrantId = approvalGrantId;
|
|
361
|
-
const kickoff = cloneJsonObject(input.kickoff);
|
|
362
|
+
const kickoff = normalizeAcceptedChatKickoffRecord(cloneJsonObject(input.kickoff));
|
|
362
363
|
if (kickoff) normalized.kickoff = kickoff;
|
|
363
364
|
|
|
364
365
|
return normalized;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { normalizeOptionalText } from './shared.js';
|
|
2
|
+
|
|
3
|
+
function normalizeAcceptedChatKickoffField(value, fallback = null) {
|
|
4
|
+
return normalizeOptionalText(value) || fallback;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeAcceptedChatKickoffRecord(kickoff = null, { fallbackStatus = null } = {}) {
|
|
8
|
+
if (!kickoff || typeof kickoff !== 'object' || Array.isArray(kickoff)) return null;
|
|
9
|
+
|
|
10
|
+
const normalized = {
|
|
11
|
+
...kickoff,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const normalizedStatus = normalizeAcceptedChatKickoffField(normalized.status, fallbackStatus);
|
|
15
|
+
const normalizedReason = normalizeAcceptedChatKickoffField(normalized.reason, null);
|
|
16
|
+
const normalizedDeliveredAt = normalizeAcceptedChatKickoffField(normalized.deliveredAt, null);
|
|
17
|
+
const normalizedSenderKickoffDeliveredAt = normalizeAcceptedChatKickoffField(
|
|
18
|
+
normalized.senderKickoffDeliveredAt,
|
|
19
|
+
normalizedDeliveredAt,
|
|
20
|
+
);
|
|
21
|
+
const normalizedOpenerAcceptedAt = normalizeAcceptedChatKickoffField(normalized.openerAcceptedAt, null);
|
|
22
|
+
const normalizedOpenerDeliveredAt = normalizeAcceptedChatKickoffField(normalized.openerDeliveredAt, null);
|
|
23
|
+
const normalizedLiveChatEstablishedAt = normalizeAcceptedChatKickoffField(normalized.liveChatEstablishedAt, null);
|
|
24
|
+
const normalizedTurnId = normalizeAcceptedChatKickoffField(normalized.turnId, null);
|
|
25
|
+
const normalizedDeliveryId = normalizeAcceptedChatKickoffField(normalized.deliveryId, null);
|
|
26
|
+
const normalizedConversationKey = normalizeAcceptedChatKickoffField(normalized.conversationKey, null);
|
|
27
|
+
const normalizedOpenerTurnId = normalizeAcceptedChatKickoffField(normalized.openerTurnId, null);
|
|
28
|
+
const normalizedOpenerDeliveryId = normalizeAcceptedChatKickoffField(normalized.openerDeliveryId, null);
|
|
29
|
+
const normalizedFailedAt = normalizeAcceptedChatKickoffField(normalized.failedAt, null);
|
|
30
|
+
|
|
31
|
+
if (normalizedStatus) normalized.status = normalizedStatus;
|
|
32
|
+
else delete normalized.status;
|
|
33
|
+
if (normalizedReason) normalized.reason = normalizedReason;
|
|
34
|
+
else delete normalized.reason;
|
|
35
|
+
if (normalizedDeliveredAt) normalized.deliveredAt = normalizedDeliveredAt;
|
|
36
|
+
else delete normalized.deliveredAt;
|
|
37
|
+
if (normalizedSenderKickoffDeliveredAt) normalized.senderKickoffDeliveredAt = normalizedSenderKickoffDeliveredAt;
|
|
38
|
+
else delete normalized.senderKickoffDeliveredAt;
|
|
39
|
+
if (normalizedOpenerAcceptedAt) normalized.openerAcceptedAt = normalizedOpenerAcceptedAt;
|
|
40
|
+
else delete normalized.openerAcceptedAt;
|
|
41
|
+
if (normalizedOpenerDeliveredAt) normalized.openerDeliveredAt = normalizedOpenerDeliveredAt;
|
|
42
|
+
else delete normalized.openerDeliveredAt;
|
|
43
|
+
if (normalizedLiveChatEstablishedAt) normalized.liveChatEstablishedAt = normalizedLiveChatEstablishedAt;
|
|
44
|
+
else delete normalized.liveChatEstablishedAt;
|
|
45
|
+
if (normalizedTurnId) normalized.turnId = normalizedTurnId;
|
|
46
|
+
else delete normalized.turnId;
|
|
47
|
+
if (normalizedDeliveryId) normalized.deliveryId = normalizedDeliveryId;
|
|
48
|
+
else delete normalized.deliveryId;
|
|
49
|
+
if (normalizedConversationKey) normalized.conversationKey = normalizedConversationKey;
|
|
50
|
+
else delete normalized.conversationKey;
|
|
51
|
+
if (normalizedOpenerTurnId) normalized.openerTurnId = normalizedOpenerTurnId;
|
|
52
|
+
else delete normalized.openerTurnId;
|
|
53
|
+
if (normalizedOpenerDeliveryId) normalized.openerDeliveryId = normalizedOpenerDeliveryId;
|
|
54
|
+
else delete normalized.openerDeliveryId;
|
|
55
|
+
if (normalizedFailedAt) normalized.failedAt = normalizedFailedAt;
|
|
56
|
+
else delete normalized.failedAt;
|
|
57
|
+
|
|
58
|
+
const hasEstablishedEvidence = Boolean(
|
|
59
|
+
normalized.openerDeliveredAt
|
|
60
|
+
|| normalized.liveChatEstablishedAt,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (hasEstablishedEvidence && (!normalized.status || ['queued', 'sent'].includes(normalized.status))) {
|
|
64
|
+
normalized.status = 'established';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (normalized.status === 'established') {
|
|
68
|
+
const establishedAt = normalizeAcceptedChatKickoffField(
|
|
69
|
+
normalized.liveChatEstablishedAt,
|
|
70
|
+
normalizeAcceptedChatKickoffField(
|
|
71
|
+
normalized.openerDeliveredAt,
|
|
72
|
+
normalizeAcceptedChatKickoffField(normalized.openerAcceptedAt, null),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
if (!normalized.openerDeliveredAt && normalized.openerAcceptedAt) {
|
|
76
|
+
normalized.openerDeliveredAt = normalized.openerAcceptedAt;
|
|
77
|
+
}
|
|
78
|
+
if (!normalized.liveChatEstablishedAt && establishedAt) {
|
|
79
|
+
normalized.liveChatEstablishedAt = establishedAt;
|
|
80
|
+
}
|
|
81
|
+
if (String(normalized.reason || '').startsWith('queued_')) {
|
|
82
|
+
delete normalized.reason;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function markAcceptedChatKickoffFailureWithDeps(deps, {
|
|
90
|
+
requestId = null,
|
|
91
|
+
reason = 'accepted_chat_kickoff_failed',
|
|
92
|
+
turnId = null,
|
|
93
|
+
conversationKey = null,
|
|
94
|
+
} = {}) {
|
|
95
|
+
const { store, pushToAgent } = deps;
|
|
96
|
+
const normalizedRequestId = normalizeOptionalText(requestId);
|
|
97
|
+
if (!normalizedRequestId) return null;
|
|
98
|
+
const request = store.getChatRequest(normalizedRequestId);
|
|
99
|
+
if (!request) return null;
|
|
100
|
+
|
|
101
|
+
request.kickoff = normalizeAcceptedChatKickoffRecord({
|
|
102
|
+
...(request.kickoff && typeof request.kickoff === 'object' && !Array.isArray(request.kickoff) ? request.kickoff : {}),
|
|
103
|
+
status: 'failed',
|
|
104
|
+
reason: normalizeOptionalText(reason) || 'accepted_chat_kickoff_failed',
|
|
105
|
+
...(normalizeOptionalText(turnId) ? { turnId: normalizeOptionalText(turnId) } : {}),
|
|
106
|
+
...(normalizeOptionalText(conversationKey) ? { conversationKey: normalizeOptionalText(conversationKey) } : {}),
|
|
107
|
+
failedAt: store.now(),
|
|
108
|
+
});
|
|
109
|
+
if (store.markChatRequestUpdated) {
|
|
110
|
+
await store.markChatRequestUpdated();
|
|
111
|
+
}
|
|
112
|
+
await pushToAgent(request.fromAgentId, 'request.updated', request);
|
|
113
|
+
await pushToAgent(request.toAgentId, 'request.updated', request);
|
|
114
|
+
return request;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function markAcceptedChatKickoffProgressWithDeps(deps, {
|
|
118
|
+
requestId = null,
|
|
119
|
+
status = null,
|
|
120
|
+
reason = null,
|
|
121
|
+
turnId = null,
|
|
122
|
+
deliveryId = null,
|
|
123
|
+
conversationKey = null,
|
|
124
|
+
senderKickoffDeliveredAt = null,
|
|
125
|
+
openerAcceptedAt = null,
|
|
126
|
+
openerDeliveredAt = null,
|
|
127
|
+
liveChatEstablishedAt = null,
|
|
128
|
+
openerTurnId = null,
|
|
129
|
+
openerDeliveryId = null,
|
|
130
|
+
} = {}) {
|
|
131
|
+
const { store, pushToAgent } = deps;
|
|
132
|
+
const normalizedRequestId = normalizeOptionalText(requestId);
|
|
133
|
+
if (!normalizedRequestId) return null;
|
|
134
|
+
const request = store.getChatRequest(normalizedRequestId);
|
|
135
|
+
if (!request) return null;
|
|
136
|
+
|
|
137
|
+
request.kickoff = normalizeAcceptedChatKickoffRecord({
|
|
138
|
+
...(request.kickoff && typeof request.kickoff === 'object' && !Array.isArray(request.kickoff) ? request.kickoff : {}),
|
|
139
|
+
...(normalizeOptionalText(status) ? { status: normalizeOptionalText(status) } : {}),
|
|
140
|
+
...(normalizeOptionalText(reason) ? { reason: normalizeOptionalText(reason) } : {}),
|
|
141
|
+
...(normalizeOptionalText(turnId) ? { turnId: normalizeOptionalText(turnId) } : {}),
|
|
142
|
+
...(normalizeOptionalText(deliveryId) ? { deliveryId: normalizeOptionalText(deliveryId) } : {}),
|
|
143
|
+
...(normalizeOptionalText(conversationKey) ? { conversationKey: normalizeOptionalText(conversationKey) } : {}),
|
|
144
|
+
...(normalizeOptionalText(senderKickoffDeliveredAt)
|
|
145
|
+
? {
|
|
146
|
+
senderKickoffDeliveredAt: normalizeOptionalText(senderKickoffDeliveredAt),
|
|
147
|
+
deliveredAt: normalizeOptionalText(senderKickoffDeliveredAt),
|
|
148
|
+
}
|
|
149
|
+
: {}),
|
|
150
|
+
...(normalizeOptionalText(openerAcceptedAt) ? { openerAcceptedAt: normalizeOptionalText(openerAcceptedAt) } : {}),
|
|
151
|
+
...(normalizeOptionalText(openerDeliveredAt) ? { openerDeliveredAt: normalizeOptionalText(openerDeliveredAt) } : {}),
|
|
152
|
+
...(normalizeOptionalText(liveChatEstablishedAt) ? { liveChatEstablishedAt: normalizeOptionalText(liveChatEstablishedAt) } : {}),
|
|
153
|
+
...(normalizeOptionalText(openerTurnId) ? { openerTurnId: normalizeOptionalText(openerTurnId) } : {}),
|
|
154
|
+
...(normalizeOptionalText(openerDeliveryId) ? { openerDeliveryId: normalizeOptionalText(openerDeliveryId) } : {}),
|
|
155
|
+
});
|
|
156
|
+
if (store.markChatRequestUpdated) {
|
|
157
|
+
await store.markChatRequestUpdated();
|
|
158
|
+
}
|
|
159
|
+
await pushToAgent(request.fromAgentId, 'request.updated', request);
|
|
160
|
+
await pushToAgent(request.toAgentId, 'request.updated', request);
|
|
161
|
+
return request;
|
|
162
|
+
}
|
|
@@ -21,6 +21,10 @@ function normalizeKickoffPayload(input) {
|
|
|
21
21
|
return cloneJsonObject(input);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
function formatScalar(value) {
|
|
25
29
|
if (value == null) return null;
|
|
26
30
|
if (typeof value === 'string') return normalizeText(value, null);
|
|
@@ -28,34 +32,201 @@ function formatScalar(value) {
|
|
|
28
32
|
return null;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
function
|
|
35
|
+
function indentBlock(text, prefix = ' ') {
|
|
36
|
+
const normalized = normalizeText(text, null);
|
|
37
|
+
if (!normalized) return null;
|
|
38
|
+
return normalized
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => `${prefix}${line}`)
|
|
41
|
+
.join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderStructuredLines(value, depth = 0) {
|
|
32
45
|
const scalar = formatScalar(value);
|
|
33
|
-
|
|
46
|
+
const indent = ' '.repeat(depth);
|
|
47
|
+
if (scalar != null) return [`${indent}${scalar}`];
|
|
48
|
+
|
|
34
49
|
if (Array.isArray(value)) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
return value.flatMap((item) => {
|
|
51
|
+
const itemScalar = formatScalar(item);
|
|
52
|
+
if (itemScalar != null) return [`${indent}- ${itemScalar}`];
|
|
53
|
+
const nestedLines = renderStructuredLines(item, depth + 1);
|
|
54
|
+
if (nestedLines.length === 0) return [];
|
|
55
|
+
return [`${indent}-`, ...nestedLines];
|
|
56
|
+
});
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
|
|
59
|
+
if (!isPlainObject(value)) return [];
|
|
60
|
+
|
|
61
|
+
return Object.entries(value).flatMap(([key, entryValue]) => {
|
|
62
|
+
const entryScalar = formatScalar(entryValue);
|
|
63
|
+
if (entryScalar != null) return [`${indent}${key}: ${entryScalar}`];
|
|
64
|
+
const nestedLines = renderStructuredLines(entryValue, depth + 1);
|
|
65
|
+
if (nestedLines.length === 0) return [];
|
|
66
|
+
return [`${indent}${key}:`, ...nestedLines];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatStructuredValue(value) {
|
|
71
|
+
const lines = renderStructuredLines(value, 0);
|
|
72
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
function formatStructuredSection(title, value) {
|
|
76
|
+
const normalizedTitle = normalizeText(title, null);
|
|
56
77
|
const formatted = formatStructuredValue(value);
|
|
57
|
-
if (!formatted) return null;
|
|
58
|
-
return `${
|
|
78
|
+
if (!normalizedTitle || !formatted) return null;
|
|
79
|
+
return `${normalizedTitle}:\n${indentBlock(formatted)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function createAcceptedChatKickoffContextBlock(type, {
|
|
83
|
+
owner = null,
|
|
84
|
+
audience = null,
|
|
85
|
+
scope = null,
|
|
86
|
+
title = null,
|
|
87
|
+
body = null,
|
|
88
|
+
items = null,
|
|
89
|
+
} = {}) {
|
|
90
|
+
const normalizedType = normalizeText(type, null);
|
|
91
|
+
if (!normalizedType) return null;
|
|
92
|
+
const normalizedBody = normalizeText(body, null);
|
|
93
|
+
const normalizedItems = Array.isArray(items)
|
|
94
|
+
? items.map((item) => normalizeText(item, null)).filter(Boolean)
|
|
95
|
+
: [];
|
|
96
|
+
if (!normalizedBody && normalizedItems.length === 0) return null;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
type: normalizedType,
|
|
100
|
+
...(normalizeText(owner, null) ? { owner: normalizeText(owner, null) } : {}),
|
|
101
|
+
...(normalizeText(audience, null) ? { audience: normalizeText(audience, null) } : {}),
|
|
102
|
+
...(normalizeText(scope, null) ? { scope: normalizeText(scope, null) } : {}),
|
|
103
|
+
...(normalizeText(title, null) ? { title: normalizeText(title, null) } : {}),
|
|
104
|
+
...(normalizedBody ? { body: normalizedBody } : {}),
|
|
105
|
+
...(normalizedItems.length > 0 ? { items: normalizedItems } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const ACCEPTED_CHAT_CONTEXT_BLOCK_ORDER = {
|
|
110
|
+
background_information: 10,
|
|
111
|
+
policy: 20,
|
|
112
|
+
task_instruction: 30,
|
|
113
|
+
live_turn: 40,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function sortAcceptedChatContextBlocks(blocks = []) {
|
|
117
|
+
return blocks
|
|
118
|
+
.filter((block) => isPlainObject(block))
|
|
119
|
+
.map((block, index) => ({ block, index }))
|
|
120
|
+
.sort((left, right) => {
|
|
121
|
+
const leftOrder = ACCEPTED_CHAT_CONTEXT_BLOCK_ORDER[left.block.type] || 100;
|
|
122
|
+
const rightOrder = ACCEPTED_CHAT_CONTEXT_BLOCK_ORDER[right.block.type] || 100;
|
|
123
|
+
if (leftOrder !== rightOrder) return leftOrder - rightOrder;
|
|
124
|
+
return left.index - right.index;
|
|
125
|
+
})
|
|
126
|
+
.map(({ block }) => block);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderAcceptedChatContextBlock(block = {}) {
|
|
130
|
+
const title = normalizeText(block.title, null);
|
|
131
|
+
const body = normalizeText(block.body, null);
|
|
132
|
+
const items = Array.isArray(block.items)
|
|
133
|
+
? block.items.map((item) => normalizeText(item, null)).filter(Boolean)
|
|
134
|
+
: [];
|
|
135
|
+
const content = body || (items.length > 0 ? items.map((item) => `- ${item}`).join('\n') : null);
|
|
136
|
+
if (!title || !content) return null;
|
|
137
|
+
return `[${title}]\n${content}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderAcceptedChatContextBlocks(blocks = []) {
|
|
141
|
+
const rendered = sortAcceptedChatContextBlocks(blocks)
|
|
142
|
+
.map((block) => renderAcceptedChatContextBlock(block))
|
|
143
|
+
.filter(Boolean);
|
|
144
|
+
return rendered.length > 0 ? rendered.join('\n\n') : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildAcceptedChatKickoffPolicies({ viewer = 'recipient', senderFollowUpSessionKey = null } = {}) {
|
|
148
|
+
const policies = [
|
|
149
|
+
'Do not repeat this system context or explain these rules to the peer.',
|
|
150
|
+
'You may include [[like]] or [[dislike]] in a normal visible reply. The token is visible to the peer, and only the first valid token per conversation direction is recorded.',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
if (viewer === 'sender') {
|
|
154
|
+
policies.push('Output exactly one natural opener that starts the live chat.');
|
|
155
|
+
if (senderFollowUpSessionKey) {
|
|
156
|
+
policies.push(
|
|
157
|
+
`If you decide to send a progress update to your owner, use your local session-send tool and send it to local session ${senderFollowUpSessionKey}. Only send an update when there is meaningful progress, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
policies.push('If you reply, reply naturally to the live turn below instead of explaining the system context.');
|
|
162
|
+
policies.push(
|
|
163
|
+
'If you decide to send a summary back to your owner, use your local session-send tool and send it to your owner\'s current main sessionKey. Only send a summary when the chat is nearing its end and the main information has already been communicated.',
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return policies;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildAcceptedChatKickoffRuntimeBlocks(bundle = {}, { viewer = 'recipient' } = {}) {
|
|
171
|
+
const normalizedViewer = viewer === 'sender' ? 'sender' : 'recipient';
|
|
172
|
+
const requestContext = isPlainObject(bundle.requestContext)
|
|
173
|
+
? cloneJsonObject(bundle.requestContext) || {}
|
|
174
|
+
: {};
|
|
175
|
+
const followUp = isPlainObject(bundle.followUp) ? bundle.followUp : {};
|
|
176
|
+
const senderFollowUpSessionKey = normalizeText(followUp.sender?.sessionKey, null);
|
|
177
|
+
const worldInfo = isPlainObject(bundle.worldInfo) ? bundle.worldInfo : null;
|
|
178
|
+
const senderInfo = isPlainObject(bundle.senderInfo) ? bundle.senderInfo : null;
|
|
179
|
+
const recipientInfo = isPlainObject(bundle.recipientInfo) ? bundle.recipientInfo : null;
|
|
180
|
+
const selfInfo = normalizedViewer === 'sender' ? senderInfo : recipientInfo;
|
|
181
|
+
const peerInfo = normalizedViewer === 'sender' ? recipientInfo : senderInfo;
|
|
182
|
+
const conversation = isPlainObject(bundle.conversation) ? bundle.conversation : {};
|
|
183
|
+
|
|
184
|
+
const conversationFacts = {
|
|
185
|
+
...(normalizeText(bundle.requestId, null) ? { requestId: normalizeText(bundle.requestId, null) } : {}),
|
|
186
|
+
...(normalizeText(conversation.mode, null) ? { mode: normalizeText(conversation.mode, null) } : {}),
|
|
187
|
+
...(Object.keys(requestContext).length > 0 ? { requestContext } : {}),
|
|
188
|
+
...(worldInfo ? { world: worldInfo } : {}),
|
|
189
|
+
};
|
|
190
|
+
const participantFacts = {
|
|
191
|
+
...(selfInfo ? { you: selfInfo } : {}),
|
|
192
|
+
...(peerInfo ? { peer: peerInfo } : {}),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const backgroundBody = [
|
|
196
|
+
formatStructuredSection('Conversation Facts', conversationFacts),
|
|
197
|
+
formatStructuredSection('Participants', participantFacts),
|
|
198
|
+
].filter(Boolean).join('\n\n');
|
|
199
|
+
|
|
200
|
+
const taskBody = normalizedViewer === 'sender'
|
|
201
|
+
? 'Generate the first live opener for this accepted chat.'
|
|
202
|
+
: 'Treat the live turn below as the first live turn of this accepted chat, then decide whether and how to reply naturally.';
|
|
203
|
+
|
|
204
|
+
return [
|
|
205
|
+
createAcceptedChatKickoffContextBlock('background_information', {
|
|
206
|
+
owner: 'conversation',
|
|
207
|
+
audience: normalizedViewer,
|
|
208
|
+
scope: 'conversation',
|
|
209
|
+
title: 'Background Information',
|
|
210
|
+
body: backgroundBody,
|
|
211
|
+
}),
|
|
212
|
+
createAcceptedChatKickoffContextBlock('policy', {
|
|
213
|
+
owner: 'orchestration',
|
|
214
|
+
audience: normalizedViewer,
|
|
215
|
+
scope: 'kickoff_only',
|
|
216
|
+
title: 'Policies',
|
|
217
|
+
items: buildAcceptedChatKickoffPolicies({
|
|
218
|
+
viewer: normalizedViewer,
|
|
219
|
+
senderFollowUpSessionKey,
|
|
220
|
+
}),
|
|
221
|
+
}),
|
|
222
|
+
createAcceptedChatKickoffContextBlock('task_instruction', {
|
|
223
|
+
owner: 'orchestration',
|
|
224
|
+
audience: normalizedViewer,
|
|
225
|
+
scope: 'current_turn',
|
|
226
|
+
title: 'Current Task',
|
|
227
|
+
body: taskBody,
|
|
228
|
+
}),
|
|
229
|
+
].filter(Boolean);
|
|
59
230
|
}
|
|
60
231
|
|
|
61
232
|
function normalizeKickoffSource(value, fallback = 'chat_request_brief') {
|
|
@@ -158,6 +329,7 @@ function buildAcceptedChatKickoffRuntimeContext(bundle = {}, { viewer = 'recipie
|
|
|
158
329
|
|
|
159
330
|
return {
|
|
160
331
|
viewer: resolvedViewer,
|
|
332
|
+
blocks: buildAcceptedChatKickoffRuntimeBlocks(bundle, { viewer: resolvedViewer }),
|
|
161
333
|
text: formatAcceptedChatKickoffMessage(bundle, { viewer: resolvedViewer }),
|
|
162
334
|
briefText: normalizeText(brief.text, null),
|
|
163
335
|
};
|
|
@@ -184,13 +356,17 @@ export function readAcceptedChatKickoffRuntimeContext(bundle = {}, { viewer = 'r
|
|
|
184
356
|
: null;
|
|
185
357
|
if (!candidate) return null;
|
|
186
358
|
|
|
187
|
-
const
|
|
359
|
+
const blocks = Array.isArray(candidate.blocks)
|
|
360
|
+
? sortAcceptedChatContextBlocks(candidate.blocks.map((block) => cloneJsonObject(block) || block))
|
|
361
|
+
: [];
|
|
362
|
+
const text = normalizeText(candidate.text, renderAcceptedChatContextBlocks(blocks));
|
|
188
363
|
if (!text) return null;
|
|
189
364
|
|
|
190
365
|
return {
|
|
191
366
|
viewer: resolvedViewer,
|
|
192
367
|
text,
|
|
193
368
|
briefText: normalizeText(candidate.briefText, normalizeText(brief.text, null)),
|
|
369
|
+
...(blocks.length > 0 ? { blocks } : {}),
|
|
194
370
|
};
|
|
195
371
|
}
|
|
196
372
|
|
|
@@ -219,49 +395,8 @@ export function createAcceptedChatKickoffRuntimeContextForAgent(bundle = {}, {
|
|
|
219
395
|
}
|
|
220
396
|
|
|
221
397
|
export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipient' } = {}) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const followUp = bundle.followUp && typeof bundle.followUp === 'object' && !Array.isArray(bundle.followUp)
|
|
227
|
-
? bundle.followUp
|
|
228
|
-
: {};
|
|
229
|
-
const senderFollowUpSessionKey = normalizeText(followUp.sender?.sessionKey, null);
|
|
230
|
-
const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
|
|
231
|
-
? bundle.worldInfo
|
|
232
|
-
: null;
|
|
233
|
-
const senderInfo = bundle.senderInfo && typeof bundle.senderInfo === 'object' && !Array.isArray(bundle.senderInfo)
|
|
234
|
-
? bundle.senderInfo
|
|
235
|
-
: null;
|
|
236
|
-
const recipientInfo = bundle.recipientInfo && typeof bundle.recipientInfo === 'object' && !Array.isArray(bundle.recipientInfo)
|
|
237
|
-
? bundle.recipientInfo
|
|
238
|
-
: null;
|
|
239
|
-
const selfInfo = normalizedViewer === 'sender' ? senderInfo : recipientInfo;
|
|
240
|
-
const peerInfo = normalizedViewer === 'sender' ? recipientInfo : senderInfo;
|
|
241
|
-
const viewerInstruction = normalizedViewer === 'recipient'
|
|
242
|
-
? 'Use this accepted-chat kickoff context to interpret the sender opener as the first live turn of the accepted chat episode. Decide whether and how to reply. Do not echo the bundle verbatim to the peer.'
|
|
243
|
-
: 'Use this accepted-chat kickoff bundle to craft the first live opener to the recipient. Do not echo the bundle verbatim to the peer.';
|
|
244
|
-
|
|
245
|
-
const blocks = [
|
|
246
|
-
normalizedViewer === 'recipient'
|
|
247
|
-
? 'Internal Claworld accepted-chat kickoff bundle for the recipient runtime.'
|
|
248
|
-
: 'Internal Claworld accepted-chat kickoff bundle for the sender runtime.',
|
|
249
|
-
viewerInstruction,
|
|
250
|
-
normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
|
|
251
|
-
formatStructuredSection('主人想让你做的事情 / 请求上下文', requestContext),
|
|
252
|
-
normalizedViewer === 'sender' && senderFollowUpSessionKey
|
|
253
|
-
? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${senderFollowUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
|
|
254
|
-
: normalizedViewer === 'recipient'
|
|
255
|
-
? 'If you decide to report back to your owner, use your local session-send tool and send the summary to the sessionKey your owner is currently using as their main session. Do this only when the chat is nearing its end and the main information has already been communicated. Do not report every turn. Keep each update brief with the current status, the key information already confirmed, and any final outcome or recommended next step. If the conversation is still in progress or key details are still being clarified, wait before reporting back.'
|
|
256
|
-
: null,
|
|
257
|
-
formatStructuredSection('世界信息', worldInfo),
|
|
258
|
-
formatStructuredSection('我方信息', selfInfo),
|
|
259
|
-
formatStructuredSection('对方信息', peerInfo),
|
|
260
|
-
'在正常回复中,你可以附带 [[like]] 或 [[dislike]] 来表达你对这段 conversation 的反馈。这个 token 对对方可见,系统只会按当前 conversation 和方向记录第一次有效反馈。',
|
|
261
|
-
normalizedViewer === 'sender'
|
|
262
|
-
? '请你现在直接输出一条自然的开场白。不要解释规则,不要复述这些说明。'
|
|
263
|
-
: '请把对方刚发来的 opener 视为这段对话的第一条 live turn,并决定是否以及如何直接回复。不要解释规则,不要复述这些说明。',
|
|
264
|
-
].filter(Boolean);
|
|
265
|
-
|
|
266
|
-
return blocks.join('\n\n');
|
|
398
|
+
const resolvedViewer = viewer === 'sender' ? 'sender' : 'recipient';
|
|
399
|
+
return renderAcceptedChatContextBlocks(
|
|
400
|
+
buildAcceptedChatKickoffRuntimeBlocks(bundle, { viewer: resolvedViewer }),
|
|
401
|
+
);
|
|
267
402
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function normalizePositiveInteger(value, fallback) {
|
|
2
|
+
const normalized = Number(value);
|
|
3
|
+
if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
|
|
4
|
+
return Math.floor(normalized);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeOptionalText(value) {
|
|
8
|
+
if (typeof value !== 'string') return null;
|
|
9
|
+
const normalized = value.trim();
|
|
10
|
+
return normalized || null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function cloneJsonObject(value) {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
15
|
+
try {
|
|
16
|
+
const cloned = JSON.parse(JSON.stringify(value));
|
|
17
|
+
if (!cloned || typeof cloned !== 'object' || Array.isArray(cloned)) return null;
|
|
18
|
+
return cloned;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildFailureBody(reason, extras = {}) {
|
|
25
|
+
return {
|
|
26
|
+
error: reason,
|
|
27
|
+
reason,
|
|
28
|
+
...extras,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import claworldPackageJson from '../../../package.json' with { type: 'json' };
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
applyRuntimeIdentity,
|
|
@@ -57,6 +58,8 @@ import {
|
|
|
57
58
|
} from '../../lib/runtime-errors.js';
|
|
58
59
|
import { PUBLIC_IDENTITY_STATUS } from '../../lib/public-identity.js';
|
|
59
60
|
|
|
61
|
+
const CLAWORLD_PLUGIN_VERSION = claworldPackageJson.version;
|
|
62
|
+
|
|
60
63
|
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
61
64
|
const parsed = new URL(serverUrl);
|
|
62
65
|
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
@@ -114,6 +117,101 @@ function resolveNormalizedText(value, fallback = null) {
|
|
|
114
117
|
return normalizeClaworldText(value, fallback);
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
function isAgentScopedSessionKey(sessionKey) {
|
|
121
|
+
return /^agent:[^:]+:/i.test(String(sessionKey || ''));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildAgentScopedLocalSessionKey({ sessionKey, localAgentId } = {}) {
|
|
125
|
+
const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
|
|
126
|
+
if (!normalizedSessionKey) return null;
|
|
127
|
+
if (isAgentScopedSessionKey(normalizedSessionKey)) {
|
|
128
|
+
return normalizedSessionKey;
|
|
129
|
+
}
|
|
130
|
+
const normalizedLocalAgentId = resolveNormalizedText(localAgentId, null);
|
|
131
|
+
if (!normalizedLocalAgentId) {
|
|
132
|
+
return normalizedSessionKey;
|
|
133
|
+
}
|
|
134
|
+
return `agent:${normalizedLocalAgentId}:${normalizedSessionKey}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function stripAgentScopedLocalSessionKey({ sessionKey, localAgentId } = {}) {
|
|
138
|
+
const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
|
|
139
|
+
if (!normalizedSessionKey) return null;
|
|
140
|
+
const normalizedLocalAgentId = resolveNormalizedText(localAgentId, null);
|
|
141
|
+
if (!normalizedLocalAgentId) {
|
|
142
|
+
return normalizedSessionKey;
|
|
143
|
+
}
|
|
144
|
+
const prefix = `agent:${normalizedLocalAgentId}:`;
|
|
145
|
+
if (normalizedSessionKey.startsWith(prefix)) {
|
|
146
|
+
return normalizedSessionKey.slice(prefix.length) || null;
|
|
147
|
+
}
|
|
148
|
+
return normalizedSessionKey;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeLocalSessionKeyFields(record = null, { localAgentId = null } = {}) {
|
|
152
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
153
|
+
return record;
|
|
154
|
+
}
|
|
155
|
+
const nextRecord = { ...record };
|
|
156
|
+
const normalizedLocalSessionKey = buildAgentScopedLocalSessionKey({
|
|
157
|
+
sessionKey: resolveNormalizedText(record.localSessionKey, resolveNormalizedText(record.sessionKey, null)),
|
|
158
|
+
localAgentId,
|
|
159
|
+
});
|
|
160
|
+
if (normalizedLocalSessionKey) {
|
|
161
|
+
nextRecord.localSessionKey = normalizedLocalSessionKey;
|
|
162
|
+
}
|
|
163
|
+
return nextRecord;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeChatInboxPayloadSessionKeys(payload = null, { localAgentId = null } = {}) {
|
|
167
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
168
|
+
return payload;
|
|
169
|
+
}
|
|
170
|
+
const nextPayload = { ...payload };
|
|
171
|
+
if (payload.filters && typeof payload.filters === 'object' && !Array.isArray(payload.filters)) {
|
|
172
|
+
const normalizedFilterLocalSessionKey = buildAgentScopedLocalSessionKey({
|
|
173
|
+
sessionKey: payload.filters.localSessionKey,
|
|
174
|
+
localAgentId,
|
|
175
|
+
});
|
|
176
|
+
nextPayload.filters = {
|
|
177
|
+
...payload.filters,
|
|
178
|
+
...(normalizedFilterLocalSessionKey ? { localSessionKey: normalizedFilterLocalSessionKey } : {}),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(payload.chats)) {
|
|
182
|
+
nextPayload.chats = payload.chats.map((chat) => normalizeLocalSessionKeyFields(chat, { localAgentId }));
|
|
183
|
+
}
|
|
184
|
+
if (payload.kickoff && typeof payload.kickoff === 'object' && !Array.isArray(payload.kickoff)) {
|
|
185
|
+
nextPayload.kickoff = normalizeLocalSessionKeyFields(payload.kickoff, { localAgentId });
|
|
186
|
+
}
|
|
187
|
+
if (payload.chat && typeof payload.chat === 'object' && !Array.isArray(payload.chat)) {
|
|
188
|
+
nextPayload.chat = normalizeLocalSessionKeyFields(payload.chat, { localAgentId });
|
|
189
|
+
}
|
|
190
|
+
return nextPayload;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function resolveRelaySessionKeyFromOutboundContext(outboundContext = {}) {
|
|
194
|
+
const metadata = outboundContext?.metadata && typeof outboundContext.metadata === 'object' && !Array.isArray(outboundContext.metadata)
|
|
195
|
+
? outboundContext.metadata
|
|
196
|
+
: {};
|
|
197
|
+
return normalizeClaworldText(
|
|
198
|
+
outboundContext.relaySessionKey,
|
|
199
|
+
normalizeClaworldText(
|
|
200
|
+
outboundContext.RelaySessionKey,
|
|
201
|
+
normalizeClaworldText(
|
|
202
|
+
metadata.relaySessionKey,
|
|
203
|
+
normalizeClaworldText(
|
|
204
|
+
metadata.sessionKey,
|
|
205
|
+
normalizeClaworldText(
|
|
206
|
+
outboundContext.sessionKey,
|
|
207
|
+
normalizeClaworldText(outboundContext.SessionKey, null),
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
117
215
|
function normalizeClaworldInteger(value, fallback = null) {
|
|
118
216
|
const normalized = Number(value);
|
|
119
217
|
if (!Number.isFinite(normalized)) return fallback;
|
|
@@ -174,6 +272,20 @@ const CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS = [
|
|
|
174
272
|
/^⚠️\s*Agent failed before reply:/i,
|
|
175
273
|
];
|
|
176
274
|
|
|
275
|
+
// Older/runtime-variant OpenClaw hosts may surface provider/runtime failures as
|
|
276
|
+
// plain final text without setting `isError`. Keep this fallback at the bridge
|
|
277
|
+
// boundary so business logic never has to guess.
|
|
278
|
+
const CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS = [
|
|
279
|
+
/^⚠️\s*Agent failed before reply:/i,
|
|
280
|
+
/^LLM request failed:/i,
|
|
281
|
+
/^LLM request timed out\./i,
|
|
282
|
+
/^LLM request unauthorized\./i,
|
|
283
|
+
/^The AI service is temporarily overloaded\./i,
|
|
284
|
+
/^The AI service returned an error\./i,
|
|
285
|
+
/^⚠️\s*API rate limit reached\./i,
|
|
286
|
+
/^⚠️\s*.+\s+returned a billing error\b/i,
|
|
287
|
+
];
|
|
288
|
+
|
|
177
289
|
const CLAWORLD_RELAY_OPERATIONAL_SUFFIX_PATTERNS = [
|
|
178
290
|
/^Usage:\s+.+\s+in\s+\/\s+.+\s+out(?:\s+·\s+est\s+.+)?$/i,
|
|
179
291
|
];
|
|
@@ -201,18 +313,21 @@ function classifyRelayContinuationText(text) {
|
|
|
201
313
|
if (!normalized) {
|
|
202
314
|
return {
|
|
203
315
|
text: '',
|
|
204
|
-
|
|
316
|
+
operationalNotice: Boolean(String(text || '').trim()),
|
|
317
|
+
runtimeError: false,
|
|
205
318
|
};
|
|
206
319
|
}
|
|
207
320
|
if (CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
208
321
|
return {
|
|
209
322
|
text: '',
|
|
210
|
-
|
|
323
|
+
operationalNotice: true,
|
|
324
|
+
runtimeError: false,
|
|
211
325
|
};
|
|
212
326
|
}
|
|
213
327
|
return {
|
|
214
328
|
text: normalized,
|
|
215
|
-
|
|
329
|
+
operationalNotice: false,
|
|
330
|
+
runtimeError: false,
|
|
216
331
|
};
|
|
217
332
|
}
|
|
218
333
|
|
|
@@ -220,6 +335,45 @@ function sanitizeRelayContinuationText(text) {
|
|
|
220
335
|
return classifyRelayContinuationText(text).text;
|
|
221
336
|
}
|
|
222
337
|
|
|
338
|
+
function classifyRelayContinuationPayload(payload = {}) {
|
|
339
|
+
const rawText = String(payload?.text ?? payload?.body ?? '').trim();
|
|
340
|
+
const normalized = stripRelayOperationalSuffix(rawText);
|
|
341
|
+
const textClassification = classifyRelayContinuationText(rawText);
|
|
342
|
+
const runtimeError = payload?.isError === true
|
|
343
|
+
|| CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
344
|
+
if (runtimeError) {
|
|
345
|
+
return {
|
|
346
|
+
text: '',
|
|
347
|
+
previewText: normalized,
|
|
348
|
+
operationalNotice: false,
|
|
349
|
+
runtimeError: true,
|
|
350
|
+
nonRenderable: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
text: textClassification.text,
|
|
355
|
+
previewText: normalized,
|
|
356
|
+
operationalNotice: textClassification.operationalNotice,
|
|
357
|
+
runtimeError: false,
|
|
358
|
+
nonRenderable: textClassification.operationalNotice,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function resolveRelaySilentReason(runtimeOutputSummary = {}, continuation = {}) {
|
|
363
|
+
const counts = runtimeOutputSummary?.counts || {};
|
|
364
|
+
if (Number(counts.runtimeErrorFinal || 0) > 0) {
|
|
365
|
+
return 'runtime_failed_before_reply';
|
|
366
|
+
}
|
|
367
|
+
if (Number(counts.operationalNotice || 0) > 0 && Number(counts.nonRenderableFinal || 0) === Number(counts.final || 0)) {
|
|
368
|
+
return 'operational_notice_only';
|
|
369
|
+
}
|
|
370
|
+
const normalizedSource = normalizePluginOptionalText(continuation?.source);
|
|
371
|
+
if (normalizedSource && normalizedSource !== 'none') {
|
|
372
|
+
return normalizedSource;
|
|
373
|
+
}
|
|
374
|
+
return 'no_renderable_reply';
|
|
375
|
+
}
|
|
376
|
+
|
|
223
377
|
function previewRuntimeOutputText(text, maxLength = 120) {
|
|
224
378
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
225
379
|
if (!normalized) return '';
|
|
@@ -351,6 +505,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
|
|
|
351
505
|
const clientMessageId = normalizePluginOptionalText(
|
|
352
506
|
outboundContext.clientMessageId || outboundContext.metadata?.clientMessageId || null
|
|
353
507
|
) || buildGeneratedClientMessageId();
|
|
508
|
+
const relaySessionKey = resolveRelaySessionKeyFromOutboundContext(outboundContext);
|
|
354
509
|
|
|
355
510
|
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
356
511
|
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/messages`, {
|
|
@@ -371,7 +526,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
|
|
|
371
526
|
scope: outboundContext.scope || outboundContext.metadata?.scope || null,
|
|
372
527
|
conversationId: outboundContext.conversationId || outboundContext.metadata?.conversationId || null,
|
|
373
528
|
threadId: outboundContext.threadId || outboundContext.metadata?.threadId || null,
|
|
374
|
-
sessionKey:
|
|
529
|
+
sessionKey: relaySessionKey,
|
|
375
530
|
},
|
|
376
531
|
}),
|
|
377
532
|
});
|
|
@@ -401,7 +556,7 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
|
|
|
401
556
|
timestamp: Date.now(),
|
|
402
557
|
meta: {
|
|
403
558
|
clientMessageId,
|
|
404
|
-
sessionKey: result.body?.delivery?.sessionKey ||
|
|
559
|
+
sessionKey: result.body?.delivery?.sessionKey || relaySessionKey,
|
|
405
560
|
turnId: result.body?.turn?.turnId || null,
|
|
406
561
|
conversationKey: result.body?.conversationKey || null,
|
|
407
562
|
targetAgentId,
|
|
@@ -504,6 +659,7 @@ async function createChatRequest({
|
|
|
504
659
|
async function listChatInbox({
|
|
505
660
|
runtimeConfig,
|
|
506
661
|
agentId,
|
|
662
|
+
localAgentId = null,
|
|
507
663
|
filters = null,
|
|
508
664
|
direction = null,
|
|
509
665
|
fetchImpl,
|
|
@@ -511,6 +667,10 @@ async function listChatInbox({
|
|
|
511
667
|
const normalizedFilters = filters && typeof filters === 'object' && !Array.isArray(filters)
|
|
512
668
|
? filters
|
|
513
669
|
: {};
|
|
670
|
+
const relayLocalSessionKey = stripAgentScopedLocalSessionKey({
|
|
671
|
+
sessionKey: normalizedFilters.localSessionKey,
|
|
672
|
+
localAgentId,
|
|
673
|
+
});
|
|
514
674
|
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
515
675
|
const path = buildRelayJsonPath('/v1/chat-requests', {
|
|
516
676
|
agentId,
|
|
@@ -520,7 +680,7 @@ async function listChatInbox({
|
|
|
520
680
|
worldId: normalizedFilters.worldId,
|
|
521
681
|
chatRequestId: normalizedFilters.chatRequestId,
|
|
522
682
|
conversationKey: normalizedFilters.conversationKey,
|
|
523
|
-
localSessionKey:
|
|
683
|
+
localSessionKey: relayLocalSessionKey,
|
|
524
684
|
counterpartyAgentId: normalizedFilters.counterpartyAgentId,
|
|
525
685
|
});
|
|
526
686
|
const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
|
|
@@ -546,13 +706,14 @@ async function listChatInbox({
|
|
|
546
706
|
},
|
|
547
707
|
});
|
|
548
708
|
}
|
|
549
|
-
return result.body || {};
|
|
709
|
+
return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
|
|
550
710
|
}
|
|
551
711
|
|
|
552
712
|
async function acceptChatRequest({
|
|
553
713
|
runtimeConfig,
|
|
554
714
|
actorAgentId,
|
|
555
715
|
chatRequestId,
|
|
716
|
+
localAgentId = null,
|
|
556
717
|
fetchImpl,
|
|
557
718
|
}) {
|
|
558
719
|
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
@@ -574,7 +735,7 @@ async function acceptChatRequest({
|
|
|
574
735
|
context: { actorAgentId, chatRequestId },
|
|
575
736
|
});
|
|
576
737
|
}
|
|
577
|
-
return result.body || {};
|
|
738
|
+
return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
|
|
578
739
|
}
|
|
579
740
|
|
|
580
741
|
async function rejectChatRequest({
|
|
@@ -1195,6 +1356,7 @@ function buildDeliveryInboundEnvelope({
|
|
|
1195
1356
|
timestamp = null,
|
|
1196
1357
|
deliveryId,
|
|
1197
1358
|
sessionKey,
|
|
1359
|
+
localSessionKey = null,
|
|
1198
1360
|
worldId = null,
|
|
1199
1361
|
conversationKey = null,
|
|
1200
1362
|
untrustedContext = [],
|
|
@@ -1215,7 +1377,8 @@ function buildDeliveryInboundEnvelope({
|
|
|
1215
1377
|
`[claworld peer ${remoteLabel}]`,
|
|
1216
1378
|
...(worldId ? [`[claworld world ${worldId}]`] : []),
|
|
1217
1379
|
...(conversationKey ? [`[claworld conversation ${conversationKey}]`] : []),
|
|
1218
|
-
`[claworld session ${
|
|
1380
|
+
...(localSessionKey && localSessionKey !== sessionKey ? [`[claworld local session ${localSessionKey}]`] : []),
|
|
1381
|
+
`[claworld relay session ${sessionKey}]`,
|
|
1219
1382
|
`[claworld delivery ${deliveryId}]`,
|
|
1220
1383
|
], untrustedContext);
|
|
1221
1384
|
const envelopeTimestamp = Number.isFinite(timestamp) ? new Date(timestamp) : new Date();
|
|
@@ -1287,7 +1450,9 @@ function createDeliveryReplyDispatcher({
|
|
|
1287
1450
|
reasoningEnd: 0,
|
|
1288
1451
|
compactionStart: 0,
|
|
1289
1452
|
compactionEnd: 0,
|
|
1453
|
+
nonRenderableFinal: 0,
|
|
1290
1454
|
operationalNotice: 0,
|
|
1455
|
+
runtimeErrorFinal: 0,
|
|
1291
1456
|
},
|
|
1292
1457
|
previews: {
|
|
1293
1458
|
final: [],
|
|
@@ -1296,6 +1461,7 @@ function createDeliveryReplyDispatcher({
|
|
|
1296
1461
|
partial: [],
|
|
1297
1462
|
reasoning: [],
|
|
1298
1463
|
operationalNotice: [],
|
|
1464
|
+
runtimeErrorFinal: [],
|
|
1299
1465
|
},
|
|
1300
1466
|
relayContinuationSource: 'none',
|
|
1301
1467
|
relayContinuationPreview: null,
|
|
@@ -1306,14 +1472,21 @@ function createDeliveryReplyDispatcher({
|
|
|
1306
1472
|
runtimeOutputSummary.counts[kind] += 1;
|
|
1307
1473
|
const text = String(payload?.text ?? payload?.body ?? '').trim();
|
|
1308
1474
|
if (kind === 'final') {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1475
|
+
const classified = classifyRelayContinuationPayload(payload);
|
|
1476
|
+
if (classified.text) {
|
|
1477
|
+
finalTexts.push(classified.text);
|
|
1478
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, classified.text);
|
|
1479
|
+
}
|
|
1480
|
+
if (classified.nonRenderable) {
|
|
1481
|
+
runtimeOutputSummary.counts.nonRenderableFinal += 1;
|
|
1482
|
+
}
|
|
1483
|
+
if (classified.operationalNotice) {
|
|
1484
|
+
runtimeOutputSummary.counts.operationalNotice += 1;
|
|
1485
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, classified.previewText || text);
|
|
1486
|
+
}
|
|
1487
|
+
if (classified.runtimeError) {
|
|
1488
|
+
runtimeOutputSummary.counts.runtimeErrorFinal += 1;
|
|
1489
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.runtimeErrorFinal, classified.previewText || text);
|
|
1317
1490
|
}
|
|
1318
1491
|
return;
|
|
1319
1492
|
}
|
|
@@ -1346,6 +1519,30 @@ function createDeliveryReplyDispatcher({
|
|
|
1346
1519
|
runtimeOutputSummary.counts[kind] += 1;
|
|
1347
1520
|
};
|
|
1348
1521
|
|
|
1522
|
+
const submitRelayReply = async (replyText) => {
|
|
1523
|
+
if (typeof relayClient?.submitDeliveryReply !== 'function') {
|
|
1524
|
+
throw new Error('relay client does not support reply submission');
|
|
1525
|
+
}
|
|
1526
|
+
return await relayClient.submitDeliveryReply({
|
|
1527
|
+
deliveryId,
|
|
1528
|
+
sessionKey,
|
|
1529
|
+
replyText,
|
|
1530
|
+
source: 'openclaw-autochain',
|
|
1531
|
+
});
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
const submitRelayKeptSilent = async (reason) => {
|
|
1535
|
+
if (typeof relayClient?.submitDeliveryKeptSilent !== 'function') {
|
|
1536
|
+
throw new Error('relay client does not support kept_silent submission');
|
|
1537
|
+
}
|
|
1538
|
+
return await relayClient.submitDeliveryKeptSilent({
|
|
1539
|
+
deliveryId,
|
|
1540
|
+
sessionKey,
|
|
1541
|
+
reason,
|
|
1542
|
+
source: 'openclaw-autochain',
|
|
1543
|
+
});
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1349
1546
|
const flushReply = async (text) => {
|
|
1350
1547
|
const normalized = String(text || '').trim();
|
|
1351
1548
|
if (!normalized || replied || suppressed) return false;
|
|
@@ -1353,16 +1550,9 @@ function createDeliveryReplyDispatcher({
|
|
|
1353
1550
|
suppressed = true;
|
|
1354
1551
|
return false;
|
|
1355
1552
|
}
|
|
1356
|
-
const replyResult = await
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
source: 'openclaw-autochain',
|
|
1360
|
-
});
|
|
1361
|
-
if (replyResult.status < 200 || replyResult.status >= 300) {
|
|
1362
|
-
throw new Error(`failed to submit relay reply: ${replyResult.status}`);
|
|
1363
|
-
}
|
|
1364
|
-
replyTransport = 'http';
|
|
1365
|
-
replyFallbackUsed = false;
|
|
1553
|
+
const replyResult = await submitRelayReply(normalized);
|
|
1554
|
+
replyTransport = replyResult?.transport || null;
|
|
1555
|
+
replyFallbackUsed = replyResult?.fallbackUsed === true;
|
|
1366
1556
|
replied = true;
|
|
1367
1557
|
return true;
|
|
1368
1558
|
};
|
|
@@ -1373,16 +1563,11 @@ function createDeliveryReplyDispatcher({
|
|
|
1373
1563
|
suppressed = true;
|
|
1374
1564
|
return false;
|
|
1375
1565
|
}
|
|
1376
|
-
const silentResult = await
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if (silentResult.status < 200 || silentResult.status >= 300) {
|
|
1382
|
-
throw new Error(`failed to submit relay kept_silent: ${silentResult.status}`);
|
|
1383
|
-
}
|
|
1384
|
-
keptSilentTransport = 'http';
|
|
1385
|
-
keptSilentFallbackUsed = false;
|
|
1566
|
+
const silentResult = await submitRelayKeptSilent(
|
|
1567
|
+
normalizePluginOptionalText(reason) || 'no_renderable_reply',
|
|
1568
|
+
);
|
|
1569
|
+
keptSilentTransport = silentResult?.transport || null;
|
|
1570
|
+
keptSilentFallbackUsed = silentResult?.fallbackUsed === true;
|
|
1386
1571
|
keptSilent = true;
|
|
1387
1572
|
return true;
|
|
1388
1573
|
};
|
|
@@ -1417,21 +1602,35 @@ function createDeliveryReplyDispatcher({
|
|
|
1417
1602
|
const markDispatchIdle = async () => {
|
|
1418
1603
|
await dispatchApi.dispatcher.waitForIdle?.();
|
|
1419
1604
|
if (!replied && !suppressed) {
|
|
1420
|
-
const
|
|
1605
|
+
const allowPartialFallback = (
|
|
1606
|
+
runtimeOutputSummary.counts.final > 0
|
|
1607
|
+
&& finalTexts.length === 0
|
|
1608
|
+
&& blockTexts.length === 0
|
|
1609
|
+
&& runtimeOutputSummary.counts.nonRenderableFinal === 0
|
|
1610
|
+
);
|
|
1611
|
+
const safeContinuation = buildRelayContinuationText({
|
|
1421
1612
|
finalTexts,
|
|
1422
1613
|
blockTexts,
|
|
1423
1614
|
partialText: partialContinuationText,
|
|
1424
|
-
allowPartialFallback
|
|
1425
|
-
runtimeOutputSummary.counts.final > 0 && finalTexts.length === 0 && blockTexts.length === 0,
|
|
1615
|
+
allowPartialFallback,
|
|
1426
1616
|
});
|
|
1427
|
-
runtimeOutputSummary.relayContinuationSource =
|
|
1428
|
-
runtimeOutputSummary.relayContinuationPreview =
|
|
1429
|
-
? previewRuntimeOutputText(
|
|
1617
|
+
runtimeOutputSummary.relayContinuationSource = safeContinuation.source;
|
|
1618
|
+
runtimeOutputSummary.relayContinuationPreview = safeContinuation.text
|
|
1619
|
+
? previewRuntimeOutputText(safeContinuation.text)
|
|
1430
1620
|
: null;
|
|
1431
|
-
if (
|
|
1432
|
-
await flushReply(
|
|
1621
|
+
if (safeContinuation.text) {
|
|
1622
|
+
await flushReply(safeContinuation.text);
|
|
1433
1623
|
} else {
|
|
1434
|
-
|
|
1624
|
+
const silentReason = resolveRelaySilentReason(runtimeOutputSummary, safeContinuation);
|
|
1625
|
+
if (runtimeOutputSummary.counts.runtimeErrorFinal > 0) {
|
|
1626
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] runtime produced non-renderable error finals; returning kept_silent`, {
|
|
1627
|
+
deliveryId,
|
|
1628
|
+
sessionKey,
|
|
1629
|
+
localAgentId,
|
|
1630
|
+
runtimeOutputSummary,
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
await flushKeptSilent(silentReason);
|
|
1435
1634
|
}
|
|
1436
1635
|
}
|
|
1437
1636
|
await dispatchApi.markDispatchIdle?.();
|
|
@@ -1476,13 +1675,14 @@ function createDeliveryReplyDispatcher({
|
|
|
1476
1675
|
final: [...runtimeOutputSummary.previews.final],
|
|
1477
1676
|
block: [...runtimeOutputSummary.previews.block],
|
|
1478
1677
|
tool: [...runtimeOutputSummary.previews.tool],
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1678
|
+
partial: [...runtimeOutputSummary.previews.partial],
|
|
1679
|
+
reasoning: [...runtimeOutputSummary.previews.reasoning],
|
|
1680
|
+
operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
|
|
1681
|
+
runtimeErrorFinal: [...runtimeOutputSummary.previews.runtimeErrorFinal],
|
|
1682
|
+
},
|
|
1683
|
+
relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
|
|
1684
|
+
relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
|
|
1685
|
+
replyTransport,
|
|
1486
1686
|
replyFallbackUsed,
|
|
1487
1687
|
keptSilentTransport,
|
|
1488
1688
|
keptSilentFallbackUsed,
|
|
@@ -1623,7 +1823,24 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1623
1823
|
return { skipped: true, reason: 'missing_delivery_payload' };
|
|
1624
1824
|
}
|
|
1625
1825
|
|
|
1626
|
-
const
|
|
1826
|
+
const loadedCfg = await runtime.config?.loadConfig?.() || {};
|
|
1827
|
+
const currentCfg = {
|
|
1828
|
+
...(loadedCfg && typeof loadedCfg === 'object' && !Array.isArray(loadedCfg) ? loadedCfg : {}),
|
|
1829
|
+
...(cfg && typeof cfg === 'object' && !Array.isArray(cfg) ? cfg : {}),
|
|
1830
|
+
agents: cfg?.agents || loadedCfg?.agents,
|
|
1831
|
+
bindings: cfg?.bindings || loadedCfg?.bindings,
|
|
1832
|
+
channels: cfg?.channels || loadedCfg?.channels,
|
|
1833
|
+
session: cfg?.session || loadedCfg?.session,
|
|
1834
|
+
};
|
|
1835
|
+
const localAgentId = resolveBoundLocalAgentId({
|
|
1836
|
+
cfg: currentCfg,
|
|
1837
|
+
runtimeConfig,
|
|
1838
|
+
relayClient,
|
|
1839
|
+
});
|
|
1840
|
+
const localSessionKey = buildAgentScopedLocalSessionKey({
|
|
1841
|
+
sessionKey,
|
|
1842
|
+
localAgentId,
|
|
1843
|
+
});
|
|
1627
1844
|
const routed = inbound?.routeInboundEvent?.(delivery, {
|
|
1628
1845
|
sessionTarget: runtimeConfig.routing?.sessionTarget,
|
|
1629
1846
|
fallbackTarget: runtimeConfig.routing?.fallbackTarget,
|
|
@@ -1644,6 +1861,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1644
1861
|
timestamp: inboundTimestamp,
|
|
1645
1862
|
deliveryId,
|
|
1646
1863
|
sessionKey,
|
|
1864
|
+
localSessionKey,
|
|
1647
1865
|
worldId,
|
|
1648
1866
|
conversationKey: metadata.conversationKey || null,
|
|
1649
1867
|
untrustedContext: payload.untrustedContext,
|
|
@@ -1657,7 +1875,8 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1657
1875
|
BodyForCommands,
|
|
1658
1876
|
From: `claworld:${remoteIdentity}`,
|
|
1659
1877
|
To: `claworld:${localIdentity}`,
|
|
1660
|
-
SessionKey: sessionKey,
|
|
1878
|
+
SessionKey: localSessionKey || sessionKey,
|
|
1879
|
+
RelaySessionKey: sessionKey,
|
|
1661
1880
|
AccountId: runtimeConfig.accountId,
|
|
1662
1881
|
OriginatingChannel: 'claworld',
|
|
1663
1882
|
OriginatingFrom: remoteIdentity,
|
|
@@ -1677,11 +1896,6 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1677
1896
|
RelayFromAgentId: fromAgentId,
|
|
1678
1897
|
UntrustedContext,
|
|
1679
1898
|
});
|
|
1680
|
-
const localAgentId = resolveBoundLocalAgentId({
|
|
1681
|
-
cfg: currentCfg,
|
|
1682
|
-
runtimeConfig,
|
|
1683
|
-
relayClient,
|
|
1684
|
-
});
|
|
1685
1899
|
|
|
1686
1900
|
if (runtime?.channel?.session?.recordInboundSession && runtime?.channel?.session?.resolveStorePath && localAgentId) {
|
|
1687
1901
|
const storePath = runtime.channel.session.resolveStorePath(currentCfg.session?.store, {
|
|
@@ -1695,6 +1909,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1695
1909
|
logger.error?.(`[claworld:${runtimeAccountId}] failed to record inbound session`, {
|
|
1696
1910
|
deliveryId,
|
|
1697
1911
|
sessionKey,
|
|
1912
|
+
localSessionKey,
|
|
1698
1913
|
localAgentId,
|
|
1699
1914
|
error: error?.message || String(error),
|
|
1700
1915
|
});
|
|
@@ -1705,6 +1920,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1705
1920
|
logger.info?.(`[claworld:${runtimeAccountId}] routing delivery into runtime session`, {
|
|
1706
1921
|
deliveryId,
|
|
1707
1922
|
sessionKey,
|
|
1923
|
+
localSessionKey,
|
|
1708
1924
|
localAgentId,
|
|
1709
1925
|
remoteIdentity,
|
|
1710
1926
|
routeStatus: routed?.status || null,
|
|
@@ -1727,6 +1943,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1727
1943
|
logger.warn?.(`[claworld:${runtimeAccountId}] delivery acceptance acknowledgement failed`, {
|
|
1728
1944
|
deliveryId,
|
|
1729
1945
|
sessionKey,
|
|
1946
|
+
localSessionKey,
|
|
1730
1947
|
localAgentId,
|
|
1731
1948
|
error: error?.message || String(error),
|
|
1732
1949
|
});
|
|
@@ -1755,8 +1972,8 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1755
1972
|
&& metadata.allowReply !== false
|
|
1756
1973
|
&& replied !== true
|
|
1757
1974
|
&& runtimeOutputSummary.counts.final > 0
|
|
1758
|
-
&& runtimeOutputSummary.counts.
|
|
1759
|
-
&& runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.
|
|
1975
|
+
&& runtimeOutputSummary.counts.nonRenderableFinal > 0
|
|
1976
|
+
&& runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.nonRenderableFinal
|
|
1760
1977
|
&& runtimeOutputSummary.counts.block === 0
|
|
1761
1978
|
&& runtimeOutputSummary.counts.tool === 0
|
|
1762
1979
|
&& runtimeOutputSummary.counts.partial === 0
|
|
@@ -1772,6 +1989,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1772
1989
|
logger.warn?.(`[claworld:${runtimeAccountId}] kickoff delivery produced only operational notices; retrying dispatch once`, {
|
|
1773
1990
|
deliveryId,
|
|
1774
1991
|
sessionKey,
|
|
1992
|
+
localSessionKey,
|
|
1775
1993
|
localAgentId,
|
|
1776
1994
|
runtimeOutputSummary,
|
|
1777
1995
|
});
|
|
@@ -1798,6 +2016,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1798
2016
|
logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
|
|
1799
2017
|
deliveryId,
|
|
1800
2018
|
sessionKey,
|
|
2019
|
+
localSessionKey,
|
|
1801
2020
|
queuedFinal: Boolean(dispatchResult?.queuedFinal),
|
|
1802
2021
|
replied,
|
|
1803
2022
|
keptSilent,
|
|
@@ -1812,6 +2031,7 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1812
2031
|
keptSilent,
|
|
1813
2032
|
queuedFinal: Boolean(dispatchResult?.queuedFinal),
|
|
1814
2033
|
sessionKey,
|
|
2034
|
+
localSessionKey,
|
|
1815
2035
|
routeStatus: routed?.status || null,
|
|
1816
2036
|
};
|
|
1817
2037
|
}
|
|
@@ -2076,6 +2296,14 @@ export function createClaworldChannelPlugin({
|
|
|
2076
2296
|
};
|
|
2077
2297
|
}
|
|
2078
2298
|
|
|
2299
|
+
function resolveContextBoundLocalAgentId(context = {}) {
|
|
2300
|
+
return resolveBoundLocalAgentId({
|
|
2301
|
+
cfg: context.cfg || {},
|
|
2302
|
+
runtimeConfig: context.runtimeConfig || {},
|
|
2303
|
+
relayClient: relayClients.get(context.accountId || 'default') || null,
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2079
2307
|
function getAccountLifecycle(accountKey = 'default') {
|
|
2080
2308
|
if (lifecycles.has(accountKey)) return lifecycles.get(accountKey);
|
|
2081
2309
|
|
|
@@ -2375,7 +2603,7 @@ export function createClaworldChannelPlugin({
|
|
|
2375
2603
|
return {
|
|
2376
2604
|
ok: true,
|
|
2377
2605
|
pluginId: 'claworld',
|
|
2378
|
-
version:
|
|
2606
|
+
version: CLAWORLD_PLUGIN_VERSION,
|
|
2379
2607
|
defaultAccountId: null,
|
|
2380
2608
|
accounts: accountSnapshots,
|
|
2381
2609
|
relayClients: Object.fromEntries(
|
|
@@ -2524,7 +2752,7 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
2524
2752
|
docsPath: '/channels/claworld',
|
|
2525
2753
|
docsLabel: 'claworld',
|
|
2526
2754
|
blurb: 'Claworld relay channel backed by the Claworld backend.',
|
|
2527
|
-
version:
|
|
2755
|
+
version: CLAWORLD_PLUGIN_VERSION,
|
|
2528
2756
|
forceAccountBinding: true,
|
|
2529
2757
|
},
|
|
2530
2758
|
onboarding: claworldOnboardingAdapter,
|
|
@@ -2690,6 +2918,7 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
2690
2918
|
return listChatInbox({
|
|
2691
2919
|
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2692
2920
|
agentId: resolvedContext.agentId || null,
|
|
2921
|
+
localAgentId: resolveContextBoundLocalAgentId(resolvedContext),
|
|
2693
2922
|
filters: context.filters || null,
|
|
2694
2923
|
direction: context.direction || null,
|
|
2695
2924
|
fetchImpl,
|
|
@@ -2701,6 +2930,7 @@ async function generateRuntimeProfileCard(context = {}) {
|
|
|
2701
2930
|
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2702
2931
|
actorAgentId: resolvedContext.agentId || null,
|
|
2703
2932
|
chatRequestId: context.chatRequestId || null,
|
|
2933
|
+
localAgentId: resolveContextBoundLocalAgentId(resolvedContext),
|
|
2704
2934
|
fetchImpl,
|
|
2705
2935
|
});
|
|
2706
2936
|
},
|
|
@@ -486,7 +486,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
486
486
|
config,
|
|
487
487
|
agentId,
|
|
488
488
|
credential = null,
|
|
489
|
-
clientVersion = 'claworld-plugin/0.2.
|
|
489
|
+
clientVersion = 'claworld-plugin/0.2.24',
|
|
490
490
|
sessionTarget,
|
|
491
491
|
fallbackTarget,
|
|
492
492
|
} = {}) {
|
|
@@ -1039,6 +1039,24 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1039
1039
|
}
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
|
+
async submitDeliveryReply({
|
|
1043
|
+
deliveryId,
|
|
1044
|
+
sessionKey,
|
|
1045
|
+
replyText,
|
|
1046
|
+
source = 'subagent',
|
|
1047
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1048
|
+
httpFallback = true,
|
|
1049
|
+
} = {}) {
|
|
1050
|
+
return await this.sendReplyAndWaitForAck({
|
|
1051
|
+
deliveryId,
|
|
1052
|
+
sessionKey,
|
|
1053
|
+
replyText,
|
|
1054
|
+
source,
|
|
1055
|
+
timeoutMs,
|
|
1056
|
+
httpFallback,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1042
1060
|
async sendAcceptedAndWaitForAck({
|
|
1043
1061
|
deliveryId,
|
|
1044
1062
|
sessionKey,
|
|
@@ -1222,6 +1240,24 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1222
1240
|
}
|
|
1223
1241
|
}
|
|
1224
1242
|
|
|
1243
|
+
async submitDeliveryKeptSilent({
|
|
1244
|
+
deliveryId,
|
|
1245
|
+
sessionKey,
|
|
1246
|
+
reason = null,
|
|
1247
|
+
source = 'openclaw-autochain',
|
|
1248
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1249
|
+
httpFallback = true,
|
|
1250
|
+
} = {}) {
|
|
1251
|
+
return await this.sendKeepSilentAndWaitForAck({
|
|
1252
|
+
deliveryId,
|
|
1253
|
+
sessionKey,
|
|
1254
|
+
reason,
|
|
1255
|
+
source,
|
|
1256
|
+
timeoutMs,
|
|
1257
|
+
httpFallback,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1225
1261
|
async createChatRequest({ fromAgentId, displayName, agentCode, requestContext = {} } = {}) {
|
|
1226
1262
|
const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
|
|
1227
1263
|
const normalizedDisplayName = normalizeOptionalText(displayName);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAcceptedChatKickoffRecord } from '../../lib/relay/kickoff-progress.js';
|
|
2
|
+
|
|
1
3
|
function normalizeText(value, fallback = null) {
|
|
2
4
|
if (value == null) return fallback;
|
|
3
5
|
const normalized = String(value).trim();
|
|
@@ -437,20 +439,27 @@ function normalizeConversationScopeDetails(input = {}) {
|
|
|
437
439
|
}
|
|
438
440
|
|
|
439
441
|
function projectChatRequestKickoff(kickoff = {}) {
|
|
440
|
-
|
|
442
|
+
const normalizedKickoff = normalizeAcceptedChatKickoffRecord(kickoff, { fallbackStatus: 'skipped' });
|
|
443
|
+
if (!normalizedKickoff) return null;
|
|
441
444
|
return {
|
|
442
|
-
status: normalizeText(
|
|
443
|
-
deliveredAt: normalizeText(
|
|
444
|
-
senderKickoffDeliveredAt: normalizeText(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
445
|
+
status: normalizeText(normalizedKickoff.status, 'skipped'),
|
|
446
|
+
deliveredAt: normalizeText(normalizedKickoff.deliveredAt, null),
|
|
447
|
+
senderKickoffDeliveredAt: normalizeText(
|
|
448
|
+
normalizedKickoff.senderKickoffDeliveredAt,
|
|
449
|
+
normalizeText(normalizedKickoff.deliveredAt, null),
|
|
450
|
+
),
|
|
451
|
+
openerAcceptedAt: normalizeText(normalizedKickoff.openerAcceptedAt, null),
|
|
452
|
+
openerDeliveredAt: normalizeText(normalizedKickoff.openerDeliveredAt, null),
|
|
453
|
+
liveChatEstablishedAt: normalizeText(normalizedKickoff.liveChatEstablishedAt, null),
|
|
454
|
+
conversationKey: normalizeText(normalizedKickoff.conversationKey, null),
|
|
455
|
+
localSessionKey: normalizeText(
|
|
456
|
+
normalizedKickoff.localSessionKey,
|
|
457
|
+
normalizeText(normalizedKickoff.sessionKey, null),
|
|
458
|
+
),
|
|
459
|
+
turnId: normalizeText(normalizedKickoff.turnId, null),
|
|
460
|
+
deliveryId: normalizeText(normalizedKickoff.deliveryId, null),
|
|
461
|
+
created: typeof normalizedKickoff.created === 'boolean' ? normalizedKickoff.created : null,
|
|
462
|
+
reason: normalizeText(normalizedKickoff.reason, null),
|
|
454
463
|
};
|
|
455
464
|
}
|
|
456
465
|
|