@xfxstudio/claworld 0.1.0
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/README.md +60 -0
- package/bin/claworld.mjs +9 -0
- package/index.js +51 -0
- package/openclaw.plugin.json +470 -0
- package/package.json +76 -0
- package/setup-entry.js +6 -0
- package/src/lib/accepted-chat-kickoff.js +192 -0
- package/src/lib/agent-address.js +46 -0
- package/src/lib/agent-profile.js +69 -0
- package/src/lib/http-auth.js +151 -0
- package/src/lib/policy.js +118 -0
- package/src/lib/runtime-errors.js +149 -0
- package/src/lib/runtime-guidance.js +458 -0
- package/src/openclaw/index.js +53 -0
- package/src/openclaw/installer/cli.js +349 -0
- package/src/openclaw/installer/constants.js +6 -0
- package/src/openclaw/installer/core.js +1548 -0
- package/src/openclaw/installer/doctor.js +690 -0
- package/src/openclaw/installer/workspace-contract.js +403 -0
- package/src/openclaw/plugin/account-identity.js +66 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
- package/src/openclaw/plugin/config-schema.js +464 -0
- package/src/openclaw/plugin/lifecycle.js +114 -0
- package/src/openclaw/plugin/managed-config.js +648 -0
- package/src/openclaw/plugin/onboarding.js +291 -0
- package/src/openclaw/plugin/register.js +961 -0
- package/src/openclaw/plugin/relay-client.js +783 -0
- package/src/openclaw/plugin/runtime.js +12 -0
- package/src/openclaw/protocol/relay-event-protocol.js +31 -0
- package/src/openclaw/runtime/canonical-result-builder.js +116 -0
- package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
- package/src/openclaw/runtime/feedback-helper.js +145 -0
- package/src/openclaw/runtime/inbound-session-router.js +36 -0
- package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
- package/src/openclaw/runtime/product-shell-helper.js +1712 -0
- package/src/openclaw/runtime/runtime-path.js +19 -0
- package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
- package/src/openclaw/runtime/tool-contracts.js +714 -0
- package/src/openclaw/runtime/tool-inventory.js +92 -0
- package/src/openclaw/runtime/world-moderation-helper.js +415 -0
- package/src/openclaw/runtime/world-session-startup.js +1 -0
- package/src/product-shell/catalog/default-world-catalog.js +296 -0
- package/src/product-shell/contracts/candidate-feed.js +330 -0
- package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
- package/src/product-shell/contracts/world-manifest.js +435 -0
- package/src/product-shell/contracts/world-orchestration.js +1024 -0
- package/src/product-shell/feedback/feedback-contract.js +13 -0
- package/src/product-shell/feedback/feedback-routes.js +98 -0
- package/src/product-shell/feedback/feedback-service.js +254 -0
- package/src/product-shell/index.js +163 -0
- package/src/product-shell/matching/matchmaking-service.js +340 -0
- package/src/product-shell/membership/membership-service.js +277 -0
- package/src/product-shell/onboarding/onboarding-routes.js +37 -0
- package/src/product-shell/onboarding/onboarding-service.js +230 -0
- package/src/product-shell/orchestration/session-orchestrator.js +38 -0
- package/src/product-shell/results/result-service.js +15 -0
- package/src/product-shell/search/search-service.js +359 -0
- package/src/product-shell/social/chat-request-approval-policy.js +332 -0
- package/src/product-shell/social/chat-request-routes.js +108 -0
- package/src/product-shell/social/chat-request-service.js +632 -0
- package/src/product-shell/social/friend-routes.js +82 -0
- package/src/product-shell/social/friend-service.js +560 -0
- package/src/product-shell/social/social-routes.js +21 -0
- package/src/product-shell/social/social-service.js +140 -0
- package/src/product-shell/worlds/world-admin-service.js +705 -0
- package/src/product-shell/worlds/world-authorization.js +135 -0
- package/src/product-shell/worlds/world-broadcast-service.js +299 -0
- package/src/product-shell/worlds/world-routes.js +410 -0
- package/src/product-shell/worlds/world-service.js +89 -0
|
@@ -0,0 +1,3118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyRuntimeIdentity,
|
|
3
|
+
buildRuntimeAuthHeaders,
|
|
4
|
+
normalizeRuntimeRegistration,
|
|
5
|
+
resolveRuntimeAppToken,
|
|
6
|
+
} from './account-identity.js';
|
|
7
|
+
import {
|
|
8
|
+
claworldChannelConfigJsonSchema,
|
|
9
|
+
claworldChannelConfigSchema,
|
|
10
|
+
defaultClaworldAccountId,
|
|
11
|
+
inspectClaworldChannelAccount,
|
|
12
|
+
listClaworldAccountIds,
|
|
13
|
+
resolveClaworldChannelAccount,
|
|
14
|
+
resolveClaworldRuntimeConfig,
|
|
15
|
+
validateClaworldChannelConfig,
|
|
16
|
+
} from './config-schema.js';
|
|
17
|
+
import {
|
|
18
|
+
claworldOnboardingAdapter,
|
|
19
|
+
claworldSetupAdapter,
|
|
20
|
+
} from './onboarding.js';
|
|
21
|
+
import { createClaworldLifecycleManager } from './lifecycle.js';
|
|
22
|
+
import { createClaworldRelayClient } from './relay-client.js';
|
|
23
|
+
import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
|
|
24
|
+
import { createInboundSessionRouter } from '../runtime/inbound-session-router.js';
|
|
25
|
+
import { createOutboundSessionBridge } from '../runtime/outbound-session-bridge.js';
|
|
26
|
+
import { createCanonicalResultBuilder } from '../runtime/canonical-result-builder.js';
|
|
27
|
+
import { createDemoSessionBootstrap } from '../runtime/demo-session-bootstrap.js';
|
|
28
|
+
import {
|
|
29
|
+
createModeratedWorld,
|
|
30
|
+
fetchOwnedWorlds,
|
|
31
|
+
manageModeratedWorld,
|
|
32
|
+
} from '../runtime/world-moderation-helper.js';
|
|
33
|
+
import { submitFeedbackReport } from '../runtime/feedback-helper.js';
|
|
34
|
+
import {
|
|
35
|
+
buildWorldSelectionPrompt,
|
|
36
|
+
buildCandidateDeliverySummary,
|
|
37
|
+
buildPostSetupWorldDirectory,
|
|
38
|
+
buildRequiredFieldExplanation,
|
|
39
|
+
broadcastWorld,
|
|
40
|
+
fetchWorldSearch,
|
|
41
|
+
fetchWorldCandidateFeed,
|
|
42
|
+
fetchWorldJoinCheck,
|
|
43
|
+
fetchWorldDetail,
|
|
44
|
+
joinWorld,
|
|
45
|
+
submitWorldJoin,
|
|
46
|
+
submitWorldSearch,
|
|
47
|
+
resolveWorldJoinFlow,
|
|
48
|
+
resolveWorldSelection,
|
|
49
|
+
resolveWorldProfileCollectionFlow,
|
|
50
|
+
resolveWorldSelectionFlow,
|
|
51
|
+
} from '../runtime/product-shell-helper.js';
|
|
52
|
+
import { getClaworldRuntime } from './runtime.js';
|
|
53
|
+
import {
|
|
54
|
+
createRuntimeBoundaryError,
|
|
55
|
+
normalizeRuntimeBoundaryError,
|
|
56
|
+
serializeRuntimeBoundaryError,
|
|
57
|
+
} from '../../lib/runtime-errors.js';
|
|
58
|
+
import {
|
|
59
|
+
ACCEPTED_CHAT_KICKOFF_PAYLOAD_KIND,
|
|
60
|
+
readAcceptedChatKickoffRuntimeContext,
|
|
61
|
+
resolveAcceptedChatKickoffViewer,
|
|
62
|
+
} from '../../lib/accepted-chat-kickoff.js';
|
|
63
|
+
|
|
64
|
+
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
65
|
+
const parsed = new URL(serverUrl);
|
|
66
|
+
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
67
|
+
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
68
|
+
parsed.pathname = '';
|
|
69
|
+
parsed.search = '';
|
|
70
|
+
parsed.hash = '';
|
|
71
|
+
return parsed.toString().replace(/\/$/, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inferRelayDomain(runtimeConfig = {}) {
|
|
75
|
+
const defaultToAddress = String(runtimeConfig.relay?.defaultToAddress || '').trim();
|
|
76
|
+
const atIndex = defaultToAddress.indexOf('@');
|
|
77
|
+
if (atIndex > 0 && atIndex < defaultToAddress.length - 1) {
|
|
78
|
+
return defaultToAddress.slice(atIndex + 1).trim().toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
return 'relay.local';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const RELAY_LOCAL_AGENT_CODE_PATTERN = /^[A-Za-z0-9._:+~-]+$/;
|
|
84
|
+
const RELAY_CANONICAL_AGENT_CODE_PATTERN = /^[A-Za-z0-9._:+~-]+@[A-Za-z0-9._:+~-]+$/;
|
|
85
|
+
|
|
86
|
+
function normalizeRelayDomainName(value, fallback = null) {
|
|
87
|
+
const normalized = normalizeClaworldText(value, fallback)?.toLowerCase() || null;
|
|
88
|
+
if (!normalized) return fallback;
|
|
89
|
+
if (!/^[a-z0-9._:+~-]+$/.test(normalized)) return fallback;
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeRelayLocalAgentCode(value, fallback = null) {
|
|
94
|
+
const normalized = normalizeClaworldText(value, fallback)?.toLowerCase() || null;
|
|
95
|
+
if (!normalized) return fallback;
|
|
96
|
+
if (!RELAY_LOCAL_AGENT_CODE_PATTERN.test(normalized)) return fallback;
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseCanonicalRelayAgentCode(value) {
|
|
101
|
+
const normalized = normalizeClaworldText(value, null)?.toLowerCase() || null;
|
|
102
|
+
if (!normalized || !RELAY_CANONICAL_AGENT_CODE_PATTERN.test(normalized)) return null;
|
|
103
|
+
const atIndex = normalized.indexOf('@');
|
|
104
|
+
if (atIndex <= 0 || atIndex >= normalized.length - 1) return null;
|
|
105
|
+
const relayLocalCode = normalizeRelayLocalAgentCode(normalized.slice(0, atIndex), null);
|
|
106
|
+
const domain = normalizeRelayDomainName(normalized.slice(atIndex + 1), null);
|
|
107
|
+
if (!relayLocalCode || !domain) return null;
|
|
108
|
+
return {
|
|
109
|
+
agentCode: `${relayLocalCode}@${domain}`,
|
|
110
|
+
relayLocalCode,
|
|
111
|
+
domain,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildCanonicalRelayAgentCode(relayLocalCode, runtimeConfig = {}, domainOverride = null) {
|
|
116
|
+
const normalizedRelayLocalCode = normalizeRelayLocalAgentCode(relayLocalCode, null);
|
|
117
|
+
const domain = normalizeRelayDomainName(domainOverride, null)
|
|
118
|
+
|| normalizeRelayDomainName(inferRelayDomain(runtimeConfig), null);
|
|
119
|
+
if (!normalizedRelayLocalCode || !domain) return null;
|
|
120
|
+
return `${normalizedRelayLocalCode}@${domain}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeCanonicalRelayAgentCode(value, runtimeConfig = {}) {
|
|
124
|
+
const parsed = parseCanonicalRelayAgentCode(value);
|
|
125
|
+
if (parsed) return parsed.agentCode;
|
|
126
|
+
const relayLocalCode = normalizeRelayLocalAgentCode(value, null);
|
|
127
|
+
if (!relayLocalCode) return null;
|
|
128
|
+
return buildCanonicalRelayAgentCode(relayLocalCode, runtimeConfig);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeRelayIdentityInput({ runtimeConfig = {}, agentId = null, agentCode = null, address = null } = {}) {
|
|
132
|
+
const normalizedAgentId = normalizeClaworldText(agentId, null);
|
|
133
|
+
const normalizedAddress = normalizeClaworldText(resolveRelayAddress(address || agentCode || '', runtimeConfig), null)?.toLowerCase() || null;
|
|
134
|
+
const parsedCanonicalAgentCode = parseCanonicalRelayAgentCode(agentCode)
|
|
135
|
+
|| parseCanonicalRelayAgentCode(normalizedAddress);
|
|
136
|
+
const relayLocalCode = normalizeRelayLocalAgentCode(agentCode, null)
|
|
137
|
+
|| parsedCanonicalAgentCode?.relayLocalCode
|
|
138
|
+
|| null;
|
|
139
|
+
const domain = parsedCanonicalAgentCode?.domain
|
|
140
|
+
|| normalizeRelayDomainName(normalizedAddress?.split('@')[1], null)
|
|
141
|
+
|| normalizeRelayDomainName(inferRelayDomain(runtimeConfig), null);
|
|
142
|
+
const normalizedAgentCode = parsedCanonicalAgentCode?.agentCode
|
|
143
|
+
|| (relayLocalCode ? buildCanonicalRelayAgentCode(relayLocalCode, runtimeConfig, domain) : null)
|
|
144
|
+
|| null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
agentId: normalizedAgentId,
|
|
148
|
+
agentCode: normalizedAgentCode,
|
|
149
|
+
relayLocalCode,
|
|
150
|
+
address: normalizedAddress || normalizedAgentCode || null,
|
|
151
|
+
domain,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildRelayAgentSummary(item = {}, runtimeConfig = {}) {
|
|
156
|
+
const normalizedAgentId = normalizeClaworldText(item?.agentId, null);
|
|
157
|
+
const relayLocalCode = normalizeRelayLocalAgentCode(item?.agentCode, null);
|
|
158
|
+
const address = normalizeClaworldText(item?.address, null)?.toLowerCase()
|
|
159
|
+
|| buildCanonicalRelayAgentCode(relayLocalCode, runtimeConfig, item?.domain)
|
|
160
|
+
|| null;
|
|
161
|
+
const parsedCanonicalAgentCode = parseCanonicalRelayAgentCode(address);
|
|
162
|
+
const domain = normalizeRelayDomainName(item?.domain, null)
|
|
163
|
+
|| parsedCanonicalAgentCode?.domain
|
|
164
|
+
|| normalizeRelayDomainName(inferRelayDomain(runtimeConfig), null);
|
|
165
|
+
const canonicalAgentCode = parsedCanonicalAgentCode?.agentCode
|
|
166
|
+
|| buildCanonicalRelayAgentCode(relayLocalCode, runtimeConfig, domain)
|
|
167
|
+
|| null;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
agentId: normalizedAgentId,
|
|
171
|
+
agentCode: canonicalAgentCode,
|
|
172
|
+
relayLocalCode: relayLocalCode || parsedCanonicalAgentCode?.relayLocalCode || null,
|
|
173
|
+
domain,
|
|
174
|
+
address: address || canonicalAgentCode || null,
|
|
175
|
+
displayName: normalizeClaworldText(item?.displayName, null),
|
|
176
|
+
discoverable: typeof item?.discoverable === 'boolean' ? item.discoverable : null,
|
|
177
|
+
contactable: typeof item?.contactable === 'boolean' ? item.contactable : null,
|
|
178
|
+
online: typeof item?.online === 'boolean' ? item.online : null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function normalizeClaworldTarget(raw) {
|
|
183
|
+
if (typeof raw !== 'string') return undefined;
|
|
184
|
+
let value = raw.trim();
|
|
185
|
+
if (!value) return undefined;
|
|
186
|
+
value = value.replace(/^claworld:/i, '').replace(/^user:/i, '').replace(/^@/, '').trim();
|
|
187
|
+
return value || undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resolveRelayAddress(to, runtimeConfig = {}) {
|
|
191
|
+
const normalized = normalizeClaworldTarget(typeof to === 'string' ? to : '');
|
|
192
|
+
const fallback = normalizeClaworldTarget(runtimeConfig.relay?.defaultToAddress || '');
|
|
193
|
+
const candidate = normalized || fallback;
|
|
194
|
+
if (!candidate) return '';
|
|
195
|
+
if (candidate.includes('@')) return candidate.toLowerCase();
|
|
196
|
+
return `${candidate.toLowerCase()}@${inferRelayDomain(runtimeConfig)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeClaworldText(value, fallback = null) {
|
|
200
|
+
if (value == null) return fallback;
|
|
201
|
+
const normalized = String(value).trim();
|
|
202
|
+
return normalized || fallback;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveNormalizedText(value, fallback = null) {
|
|
206
|
+
return normalizeClaworldText(value, fallback);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizeClaworldInteger(value, fallback = null) {
|
|
210
|
+
const normalized = Number(value);
|
|
211
|
+
if (!Number.isFinite(normalized)) return fallback;
|
|
212
|
+
return Math.trunc(normalized);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function shouldAuthorizeBridgedCommand({ runtimeConfig = {}, relayEvent, incomingText }) {
|
|
216
|
+
if (runtimeConfig.testing?.allowBridgedCommandDispatch !== true) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (relayEvent !== 'turn.deliver') {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return typeof incomingText === 'string' && incomingText.trim().startsWith('/');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const CLAWORLD_RAISE_HAND_DIRECTIVE = '[[CLAWORLD_RAISE_HAND]]';
|
|
226
|
+
const CLAWORLD_RAISE_HAND_DIRECTIVE_PATTERN = /\[\[\s*CLAWORLD_RAISE_HAND\s*\]\]/gi;
|
|
227
|
+
|
|
228
|
+
function extractControlledReply(text) {
|
|
229
|
+
let raiseHand = false;
|
|
230
|
+
const sanitized = String(text || '')
|
|
231
|
+
.replace(CLAWORLD_RAISE_HAND_DIRECTIVE_PATTERN, () => {
|
|
232
|
+
raiseHand = true;
|
|
233
|
+
return ' ';
|
|
234
|
+
})
|
|
235
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
236
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
237
|
+
.trim();
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
text: sanitized || (raiseHand ? 'I am ready to conclude this round.' : ''),
|
|
241
|
+
control: raiseHand ? { type: 'raise_hand', raiseHand: true } : null,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS = [
|
|
246
|
+
/^🧭\s*New session:\s+\S+/i,
|
|
247
|
+
/^🧹\s*Auto-compaction complete(?:\s*\(count \d+\))?\.$/i,
|
|
248
|
+
/^↪️\s*Model Fallback:/i,
|
|
249
|
+
/^↪️\s*Model Fallback cleared:/i,
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const CLAWORLD_RELAY_OPERATIONAL_SUFFIX_PATTERNS = [
|
|
253
|
+
/^Usage:\s+.+\s+in\s+\/\s+.+\s+out(?:\s+·\s+est\s+.+)?$/i,
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const CLAWORLD_RUNTIME_OUTPUT_PREVIEW_LIMIT = 3;
|
|
257
|
+
|
|
258
|
+
function stripRelayOperationalSuffix(text) {
|
|
259
|
+
const lines = String(text || '').split('\n');
|
|
260
|
+
while (lines.length > 0) {
|
|
261
|
+
const lastLine = String(lines[lines.length - 1] || '').trim();
|
|
262
|
+
if (!lastLine) {
|
|
263
|
+
lines.pop();
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (!CLAWORLD_RELAY_OPERATIONAL_SUFFIX_PATTERNS.some((pattern) => pattern.test(lastLine))) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
lines.pop();
|
|
270
|
+
}
|
|
271
|
+
return lines.join('\n').trim();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function classifyRelayContinuationText(text) {
|
|
275
|
+
const normalized = stripRelayOperationalSuffix(text);
|
|
276
|
+
if (!normalized) {
|
|
277
|
+
return {
|
|
278
|
+
text: '',
|
|
279
|
+
operational: Boolean(String(text || '').trim()),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
283
|
+
return {
|
|
284
|
+
text: '',
|
|
285
|
+
operational: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
text: normalized,
|
|
290
|
+
operational: false,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function sanitizeRelayContinuationText(text) {
|
|
295
|
+
return classifyRelayContinuationText(text).text;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function previewRuntimeOutputText(text, maxLength = 120) {
|
|
299
|
+
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
300
|
+
if (!normalized) return '';
|
|
301
|
+
if (normalized.length <= maxLength) return normalized;
|
|
302
|
+
return `${normalized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function appendRuntimeOutputPreview(previews, text) {
|
|
306
|
+
const preview = previewRuntimeOutputText(text);
|
|
307
|
+
if (!preview) return;
|
|
308
|
+
if (previews.includes(preview)) return;
|
|
309
|
+
if (previews.length >= CLAWORLD_RUNTIME_OUTPUT_PREVIEW_LIMIT) return;
|
|
310
|
+
previews.push(preview);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function buildRelayContinuationText({ finalTexts = [], blockTexts = [] } = {}) {
|
|
314
|
+
const sanitizedFinalTexts = finalTexts
|
|
315
|
+
.map((entry) => sanitizeRelayContinuationText(entry))
|
|
316
|
+
.filter(Boolean);
|
|
317
|
+
if (sanitizedFinalTexts.length > 0) {
|
|
318
|
+
return {
|
|
319
|
+
text: sanitizedFinalTexts.join('\n\n').trim(),
|
|
320
|
+
source: 'final',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const sanitizedBlockTexts = blockTexts
|
|
324
|
+
.map((entry) => sanitizeRelayContinuationText(entry))
|
|
325
|
+
.filter(Boolean);
|
|
326
|
+
if (sanitizedBlockTexts.length > 0) {
|
|
327
|
+
return {
|
|
328
|
+
text: sanitizedBlockTexts.join('\n').trim(),
|
|
329
|
+
source: 'block',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
text: '',
|
|
334
|
+
source: 'none',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function resolveContinuationState(turnData = {}) {
|
|
339
|
+
const continuation = turnData?.continuation;
|
|
340
|
+
if (!continuation || typeof continuation !== 'object' || Array.isArray(continuation)) {
|
|
341
|
+
return { allowed: true, reason: null, roundStatus: null };
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
allowed: continuation.allowed !== false,
|
|
345
|
+
reason: normalizeClaworldText(continuation.reason, null),
|
|
346
|
+
roundStatus: normalizeClaworldText(continuation.roundStatus, null),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resolveBackendGuidanceMessages(payload = {}) {
|
|
351
|
+
const guidance = payload?.guidance;
|
|
352
|
+
if (!guidance || typeof guidance !== 'object' || Array.isArray(guidance)) return [];
|
|
353
|
+
const messages = Array.isArray(guidance.messages) ? guidance.messages : [];
|
|
354
|
+
return messages
|
|
355
|
+
.map((message, index) => {
|
|
356
|
+
const text = normalizeClaworldText(message?.text, null);
|
|
357
|
+
if (!text) return null;
|
|
358
|
+
return {
|
|
359
|
+
text,
|
|
360
|
+
contextKey:
|
|
361
|
+
normalizeClaworldText(message?.contextKey, null)
|
|
362
|
+
|| `claworld:backend-guidance:${normalizeClaworldText(message?.kind, 'message')}:${index}`,
|
|
363
|
+
};
|
|
364
|
+
})
|
|
365
|
+
.filter(Boolean);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function enqueueBackendGuidance({
|
|
369
|
+
runtime,
|
|
370
|
+
sessionKey,
|
|
371
|
+
payload,
|
|
372
|
+
} = {}) {
|
|
373
|
+
if (!runtime?.system?.enqueueSystemEvent || !sessionKey) {
|
|
374
|
+
return { skipped: true, reason: 'missing_runtime_or_session', count: 0 };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const messages = resolveBackendGuidanceMessages(payload);
|
|
378
|
+
if (messages.length === 0) {
|
|
379
|
+
return { skipped: true, reason: 'no_backend_guidance', count: 0 };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (const message of messages) {
|
|
383
|
+
runtime.system.enqueueSystemEvent(message.text, {
|
|
384
|
+
sessionKey,
|
|
385
|
+
contextKey: message.contextKey,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
skipped: false,
|
|
391
|
+
reason: null,
|
|
392
|
+
count: messages.length,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function buildClaworldDirectoryEntries(config = {}, accountId = null) {
|
|
397
|
+
const accountIds = listClaworldAccountIds(config);
|
|
398
|
+
const currentAccountId = String(accountId || '').trim();
|
|
399
|
+
const entries = [];
|
|
400
|
+
|
|
401
|
+
for (const id of accountIds) {
|
|
402
|
+
const account = inspectClaworldChannelAccount(config, id);
|
|
403
|
+
if (!account?.enabled || !account?.configured) continue;
|
|
404
|
+
const normalizedId = String(account.accountId || id || '').trim();
|
|
405
|
+
if (!normalizedId) continue;
|
|
406
|
+
if (currentAccountId && normalizedId === currentAccountId) continue;
|
|
407
|
+
entries.push({
|
|
408
|
+
id: resolveRelayAddress(normalizedId, account),
|
|
409
|
+
name: normalizedId,
|
|
410
|
+
handle: normalizedId,
|
|
411
|
+
rank: 100,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const current = inspectClaworldChannelAccount(config, currentAccountId || null);
|
|
416
|
+
const defaultTarget = resolveRelayAddress(current?.relay?.defaultToAddress || '', current || {});
|
|
417
|
+
if (defaultTarget) {
|
|
418
|
+
const localPart = defaultTarget.split('@')[0] || defaultTarget;
|
|
419
|
+
if (!entries.some((entry) => entry.id === defaultTarget)) {
|
|
420
|
+
entries.push({ id: defaultTarget, name: localPart, handle: localPart, rank: 50 });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return entries;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger, outboundContext = {}, messagePayload = null }) {
|
|
428
|
+
const fromAgentId = runtimeConfig.relay?.agentId;
|
|
429
|
+
if (!fromAgentId) throw new Error('claworld relay.agentId is required for outbound send');
|
|
430
|
+
|
|
431
|
+
const toAddress = resolveRelayAddress(to, runtimeConfig);
|
|
432
|
+
if (!toAddress) throw new Error('claworld outbound target is required');
|
|
433
|
+
|
|
434
|
+
const normalizedText = normalizeClaworldText(text, null);
|
|
435
|
+
const payload = messagePayload && typeof messagePayload === 'object'
|
|
436
|
+
? { ...messagePayload }
|
|
437
|
+
: {};
|
|
438
|
+
|
|
439
|
+
if (!normalizeClaworldText(payload.text, null) && normalizedText) {
|
|
440
|
+
payload.text = normalizedText;
|
|
441
|
+
}
|
|
442
|
+
if (!normalizeClaworldText(payload.text, null)) {
|
|
443
|
+
throw new Error('claworld outbound text is required');
|
|
444
|
+
}
|
|
445
|
+
payload.source = normalizeClaworldText(payload.source, 'openclaw-claworld');
|
|
446
|
+
payload.accountId = normalizeClaworldText(payload.accountId, runtimeConfig.accountId);
|
|
447
|
+
|
|
448
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
449
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/messages`, {
|
|
450
|
+
method: 'POST',
|
|
451
|
+
headers: {
|
|
452
|
+
'content-type': 'application/json',
|
|
453
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
454
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
455
|
+
},
|
|
456
|
+
body: JSON.stringify({
|
|
457
|
+
fromAgentId,
|
|
458
|
+
toAddress,
|
|
459
|
+
payload,
|
|
460
|
+
conversation: {
|
|
461
|
+
conversationKey: outboundContext.conversationKey || outboundContext.metadata?.conversationKey || null,
|
|
462
|
+
worldId: outboundContext.worldId || outboundContext.metadata?.worldId || null,
|
|
463
|
+
scope: outboundContext.scope || outboundContext.sessionScope || outboundContext.metadata?.scope || null,
|
|
464
|
+
conversationId: outboundContext.conversationId || outboundContext.metadata?.conversationId || null,
|
|
465
|
+
threadId: outboundContext.threadId || outboundContext.metadata?.threadId || null,
|
|
466
|
+
sessionKey: outboundContext.sessionKey || outboundContext.SessionKey || null,
|
|
467
|
+
},
|
|
468
|
+
}),
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
if (!result.ok) {
|
|
472
|
+
logger.error?.('[claworld:outbound] session request failed', { status: result.status, body: result.body });
|
|
473
|
+
throw createRuntimeBoundaryError({
|
|
474
|
+
code: 'relay_message_delivery_failed',
|
|
475
|
+
category: 'transport',
|
|
476
|
+
status: result.status >= 500 ? 502 : result.status,
|
|
477
|
+
message: `claworld outbound failed: ${result.status}`,
|
|
478
|
+
publicMessage: 'claworld outbound message delivery failed',
|
|
479
|
+
recoverable: true,
|
|
480
|
+
context: {
|
|
481
|
+
accountId: runtimeConfig.accountId || null,
|
|
482
|
+
fromAgentId,
|
|
483
|
+
toAddress,
|
|
484
|
+
status: result.status,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
channel: 'claworld',
|
|
491
|
+
messageId: result.body?.turn?.turnId || `turn_${Date.now()}`,
|
|
492
|
+
chatId: toAddress,
|
|
493
|
+
timestamp: Date.now(),
|
|
494
|
+
meta: {
|
|
495
|
+
sessionId: result.body?.session?.sessionId || null,
|
|
496
|
+
turnId: result.body?.turn?.turnId || null,
|
|
497
|
+
conversationKey: result.body?.conversationKey || null,
|
|
498
|
+
toAddress,
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function buildRelayJsonPath(pathname, query = {}) {
|
|
504
|
+
const search = new URLSearchParams();
|
|
505
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
506
|
+
const normalized = resolveNormalizedText(value, null);
|
|
507
|
+
if (normalized) search.set(key, normalized);
|
|
508
|
+
});
|
|
509
|
+
const encoded = search.toString();
|
|
510
|
+
return encoded ? `${pathname}?${encoded}` : pathname;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function createRelayRouteError({
|
|
514
|
+
result,
|
|
515
|
+
runtimeConfig,
|
|
516
|
+
code,
|
|
517
|
+
publicMessage,
|
|
518
|
+
message,
|
|
519
|
+
context = {},
|
|
520
|
+
}) {
|
|
521
|
+
throw createRuntimeBoundaryError({
|
|
522
|
+
code,
|
|
523
|
+
category: 'transport',
|
|
524
|
+
status: result?.status >= 500 ? 502 : result?.status || 502,
|
|
525
|
+
message: message || publicMessage,
|
|
526
|
+
publicMessage,
|
|
527
|
+
recoverable: true,
|
|
528
|
+
context: {
|
|
529
|
+
accountId: runtimeConfig.accountId || null,
|
|
530
|
+
httpStatus: result?.status || null,
|
|
531
|
+
backendCode: result?.body?.error || null,
|
|
532
|
+
backendMessage: result?.body?.message || null,
|
|
533
|
+
...context,
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function createFriendRequest({
|
|
539
|
+
runtimeConfig,
|
|
540
|
+
fromAgentId,
|
|
541
|
+
toAddress,
|
|
542
|
+
message = null,
|
|
543
|
+
metadata = {},
|
|
544
|
+
fetchImpl,
|
|
545
|
+
}) {
|
|
546
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
547
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/friend-requests`, {
|
|
548
|
+
method: 'POST',
|
|
549
|
+
headers: {
|
|
550
|
+
'content-type': 'application/json',
|
|
551
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
552
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
553
|
+
},
|
|
554
|
+
body: JSON.stringify({
|
|
555
|
+
fromAgentId,
|
|
556
|
+
toAddress,
|
|
557
|
+
message: normalizeClaworldText(message, null),
|
|
558
|
+
metadata: metadata && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : {},
|
|
559
|
+
}),
|
|
560
|
+
});
|
|
561
|
+
if (!result.ok) {
|
|
562
|
+
createRelayRouteError({
|
|
563
|
+
result,
|
|
564
|
+
runtimeConfig,
|
|
565
|
+
code: 'friend_request_failed',
|
|
566
|
+
publicMessage: 'failed to create friend request',
|
|
567
|
+
context: { fromAgentId, toAddress },
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return result.body || {};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function listFriendRequests({
|
|
574
|
+
runtimeConfig,
|
|
575
|
+
agentId,
|
|
576
|
+
direction = null,
|
|
577
|
+
status = null,
|
|
578
|
+
fetchImpl,
|
|
579
|
+
}) {
|
|
580
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
581
|
+
const path = buildRelayJsonPath('/v1/friend-requests', {
|
|
582
|
+
agentId,
|
|
583
|
+
direction,
|
|
584
|
+
status,
|
|
585
|
+
});
|
|
586
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
|
|
587
|
+
method: 'GET',
|
|
588
|
+
headers: {
|
|
589
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
590
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
if (!result.ok) {
|
|
594
|
+
createRelayRouteError({
|
|
595
|
+
result,
|
|
596
|
+
runtimeConfig,
|
|
597
|
+
code: 'friend_request_list_failed',
|
|
598
|
+
publicMessage: 'failed to list friend requests',
|
|
599
|
+
context: { agentId, direction, status },
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return result.body || {};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function acceptFriendRequest({
|
|
606
|
+
runtimeConfig,
|
|
607
|
+
actorAgentId,
|
|
608
|
+
friendRequestId,
|
|
609
|
+
fetchImpl,
|
|
610
|
+
}) {
|
|
611
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
612
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/friend-requests/${encodeURIComponent(friendRequestId)}/accept`, {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: {
|
|
615
|
+
'content-type': 'application/json',
|
|
616
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
617
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
618
|
+
},
|
|
619
|
+
body: JSON.stringify({ actorAgentId }),
|
|
620
|
+
});
|
|
621
|
+
if (!result.ok) {
|
|
622
|
+
createRelayRouteError({
|
|
623
|
+
result,
|
|
624
|
+
runtimeConfig,
|
|
625
|
+
code: 'friend_request_accept_failed',
|
|
626
|
+
publicMessage: 'failed to accept friend request',
|
|
627
|
+
context: { actorAgentId, friendRequestId },
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return result.body || {};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function rejectFriendRequest({
|
|
634
|
+
runtimeConfig,
|
|
635
|
+
actorAgentId,
|
|
636
|
+
friendRequestId,
|
|
637
|
+
fetchImpl,
|
|
638
|
+
}) {
|
|
639
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
640
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/friend-requests/${encodeURIComponent(friendRequestId)}/reject`, {
|
|
641
|
+
method: 'POST',
|
|
642
|
+
headers: {
|
|
643
|
+
'content-type': 'application/json',
|
|
644
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
645
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
646
|
+
},
|
|
647
|
+
body: JSON.stringify({ actorAgentId }),
|
|
648
|
+
});
|
|
649
|
+
if (!result.ok) {
|
|
650
|
+
createRelayRouteError({
|
|
651
|
+
result,
|
|
652
|
+
runtimeConfig,
|
|
653
|
+
code: 'friend_request_reject_failed',
|
|
654
|
+
publicMessage: 'failed to reject friend request',
|
|
655
|
+
context: { actorAgentId, friendRequestId },
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return result.body || {};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function createChatRequest({
|
|
662
|
+
runtimeConfig,
|
|
663
|
+
fromAgentId,
|
|
664
|
+
targetAgentId,
|
|
665
|
+
openingMessage = null,
|
|
666
|
+
worldId = null,
|
|
667
|
+
episodePolicy = null,
|
|
668
|
+
fetchImpl,
|
|
669
|
+
}) {
|
|
670
|
+
const normalizedTargetAgentId = normalizeClaworldText(targetAgentId, null);
|
|
671
|
+
if (!normalizedTargetAgentId) {
|
|
672
|
+
throw createRuntimeBoundaryError({
|
|
673
|
+
code: 'tool_input_invalid',
|
|
674
|
+
category: 'input',
|
|
675
|
+
status: 400,
|
|
676
|
+
message: 'claworld chat request target requires targetAgentId',
|
|
677
|
+
publicMessage: 'claworld chat request target requires targetAgentId',
|
|
678
|
+
recoverable: true,
|
|
679
|
+
context: { field: 'targetAgentId' },
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
683
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests`, {
|
|
684
|
+
method: 'POST',
|
|
685
|
+
headers: {
|
|
686
|
+
'content-type': 'application/json',
|
|
687
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
688
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
689
|
+
},
|
|
690
|
+
body: JSON.stringify({
|
|
691
|
+
fromAgentId,
|
|
692
|
+
targetAgentId: normalizedTargetAgentId,
|
|
693
|
+
openingMessage: normalizeClaworldText(openingMessage, null),
|
|
694
|
+
...(normalizeClaworldText(worldId, null) ? { worldId: normalizeClaworldText(worldId, null) } : {}),
|
|
695
|
+
...(episodePolicy && typeof episodePolicy === 'object' && !Array.isArray(episodePolicy)
|
|
696
|
+
? { episodePolicy }
|
|
697
|
+
: {}),
|
|
698
|
+
}),
|
|
699
|
+
});
|
|
700
|
+
if (!result.ok) {
|
|
701
|
+
createRelayRouteError({
|
|
702
|
+
result,
|
|
703
|
+
runtimeConfig,
|
|
704
|
+
code: 'chat_request_create_failed',
|
|
705
|
+
publicMessage: 'failed to create chat request',
|
|
706
|
+
context: { fromAgentId, targetAgentId: normalizedTargetAgentId },
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
return result.body || {};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async function listChatRequests({
|
|
713
|
+
runtimeConfig,
|
|
714
|
+
agentId,
|
|
715
|
+
direction = null,
|
|
716
|
+
fetchImpl,
|
|
717
|
+
}) {
|
|
718
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
719
|
+
const path = buildRelayJsonPath('/v1/chat-requests', {
|
|
720
|
+
agentId,
|
|
721
|
+
direction,
|
|
722
|
+
});
|
|
723
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}${path}`, {
|
|
724
|
+
method: 'GET',
|
|
725
|
+
headers: {
|
|
726
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
727
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
if (!result.ok) {
|
|
731
|
+
createRelayRouteError({
|
|
732
|
+
result,
|
|
733
|
+
runtimeConfig,
|
|
734
|
+
code: 'chat_request_list_failed',
|
|
735
|
+
publicMessage: 'failed to list chat requests',
|
|
736
|
+
context: { agentId, direction },
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return result.body || {};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async function acceptChatRequest({
|
|
743
|
+
runtimeConfig,
|
|
744
|
+
actorAgentId,
|
|
745
|
+
chatRequestId,
|
|
746
|
+
fetchImpl,
|
|
747
|
+
}) {
|
|
748
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
749
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests/${encodeURIComponent(chatRequestId)}/accept`, {
|
|
750
|
+
method: 'POST',
|
|
751
|
+
headers: {
|
|
752
|
+
'content-type': 'application/json',
|
|
753
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
754
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
755
|
+
},
|
|
756
|
+
body: JSON.stringify({ actorAgentId }),
|
|
757
|
+
});
|
|
758
|
+
if (!result.ok) {
|
|
759
|
+
createRelayRouteError({
|
|
760
|
+
result,
|
|
761
|
+
runtimeConfig,
|
|
762
|
+
code: 'chat_request_accept_failed',
|
|
763
|
+
publicMessage: 'failed to accept chat request',
|
|
764
|
+
context: { actorAgentId, chatRequestId },
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
return result.body || {};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function syncChatRequestApprovalPolicy({
|
|
771
|
+
runtimeConfig,
|
|
772
|
+
fetchImpl,
|
|
773
|
+
logger,
|
|
774
|
+
}) {
|
|
775
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
776
|
+
let result;
|
|
777
|
+
try {
|
|
778
|
+
result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests/approval-policy`, {
|
|
779
|
+
method: 'PUT',
|
|
780
|
+
headers: {
|
|
781
|
+
'content-type': 'application/json',
|
|
782
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
783
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
784
|
+
},
|
|
785
|
+
body: JSON.stringify({
|
|
786
|
+
accountId: runtimeConfig.accountId || null,
|
|
787
|
+
approval: runtimeConfig.approval || {},
|
|
788
|
+
}),
|
|
789
|
+
});
|
|
790
|
+
} catch (error) {
|
|
791
|
+
logger.warn?.(`[claworld:${runtimeConfig.accountId || 'default'}] approval policy sync failed`, {
|
|
792
|
+
error: error?.message || String(error),
|
|
793
|
+
code: error?.code || null,
|
|
794
|
+
category: error?.category || null,
|
|
795
|
+
});
|
|
796
|
+
return {
|
|
797
|
+
ok: false,
|
|
798
|
+
status: error?.status || null,
|
|
799
|
+
body: null,
|
|
800
|
+
error,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (!result.ok) {
|
|
804
|
+
logger.warn?.(`[claworld:${runtimeConfig.accountId || 'default'}] approval policy sync failed`, {
|
|
805
|
+
status: result.status,
|
|
806
|
+
body: result.body,
|
|
807
|
+
});
|
|
808
|
+
return {
|
|
809
|
+
ok: false,
|
|
810
|
+
status: result.status,
|
|
811
|
+
body: result.body,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
logger.info?.(`[claworld:${runtimeConfig.accountId || 'default'}] approval policy sync ok`, {
|
|
816
|
+
mode: result.body?.approvalPolicy?.policy?.mode || runtimeConfig.approval?.mode || null,
|
|
817
|
+
syncedAt: result.body?.approvalPolicy?.syncedAt || null,
|
|
818
|
+
});
|
|
819
|
+
return {
|
|
820
|
+
ok: true,
|
|
821
|
+
status: result.status,
|
|
822
|
+
body: result.body,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function waitForAbort(signal) {
|
|
827
|
+
return new Promise((resolve) => {
|
|
828
|
+
if (!signal) return resolve({ reason: 'missing_abort_signal' });
|
|
829
|
+
if (signal.aborted) return resolve({ reason: 'already_aborted' });
|
|
830
|
+
signal.addEventListener('abort', () => resolve({ reason: 'abort_signal' }), { once: true });
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function waitForRelayClientClose(relayClient) {
|
|
835
|
+
return new Promise((resolve) => {
|
|
836
|
+
if (!relayClient?.once) return resolve({ reason: 'missing_relay_client' });
|
|
837
|
+
relayClient.once('close', (info = {}) => {
|
|
838
|
+
resolve({
|
|
839
|
+
reason: info.reason || 'relay_client_closed',
|
|
840
|
+
close: info,
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function isNonRecoverableBootstrapHoldError(error) {
|
|
847
|
+
return Boolean(
|
|
848
|
+
error
|
|
849
|
+
&& error.recoverable === false
|
|
850
|
+
&& ['auth', 'bootstrap', 'config', 'conflict', 'input', 'policy'].includes(error.category),
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function buildBootstrapHoldMessage(error, runtimeConfig = {}) {
|
|
855
|
+
if (error?.code === 'agent_code_conflict') {
|
|
856
|
+
const requestedAgentCode = normalizeClaworldText(
|
|
857
|
+
error?.context?.requestedAgentCode,
|
|
858
|
+
normalizeRuntimeRegistration(runtimeConfig).agentCode || null,
|
|
859
|
+
);
|
|
860
|
+
return requestedAgentCode
|
|
861
|
+
? `Claworld setup blocked: relay agent code ${requestedAgentCode} already exists. Add the bound appToken for this account or choose a different registration.agentCode.`
|
|
862
|
+
: 'Claworld setup blocked: relay agent code already exists. Add the bound appToken for this account or choose a different registration.agentCode.';
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const publicMessage = normalizeClaworldText(error?.publicMessage, null);
|
|
866
|
+
const fallbackMessage = normalizeClaworldText(error?.message, 'relay binding bootstrap failed');
|
|
867
|
+
return `Claworld setup blocked: ${publicMessage || fallbackMessage}`;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function summarizeObjectShape(value) {
|
|
871
|
+
if (!value || typeof value !== 'object') return { type: typeof value };
|
|
872
|
+
const keys = Object.keys(value).sort();
|
|
873
|
+
return {
|
|
874
|
+
type: Array.isArray(value) ? 'array' : 'object',
|
|
875
|
+
keys,
|
|
876
|
+
hasChannelsClaworld: Boolean(value.channels?.claworld),
|
|
877
|
+
hasAccounts: Boolean(value.accounts && typeof value.accounts === 'object'),
|
|
878
|
+
accountId: value.accountId || null,
|
|
879
|
+
configured: typeof value.configured === 'boolean' ? value.configured : null,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function hasRelayDispatchSurface(runtime) {
|
|
884
|
+
return Boolean(
|
|
885
|
+
runtime?.channel?.routing?.resolveAgentRoute
|
|
886
|
+
&& runtime?.channel?.reply?.finalizeInboundContext
|
|
887
|
+
&& (
|
|
888
|
+
runtime?.channel?.reply?.dispatchReplyFromConfig
|
|
889
|
+
|| runtime?.channel?.reply?.createReplyDispatcherWithTyping
|
|
890
|
+
)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function resolvePluginRuntimeCandidate(contextRuntime) {
|
|
895
|
+
let globalRuntime = null;
|
|
896
|
+
try {
|
|
897
|
+
globalRuntime = getClaworldRuntime();
|
|
898
|
+
} catch {
|
|
899
|
+
globalRuntime = null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (hasRelayDispatchSurface(contextRuntime)) {
|
|
903
|
+
return {
|
|
904
|
+
runtime: contextRuntime,
|
|
905
|
+
runtimeSource: 'context.runtime',
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (hasRelayDispatchSurface(globalRuntime)) {
|
|
910
|
+
return {
|
|
911
|
+
runtime: globalRuntime,
|
|
912
|
+
runtimeSource: 'global_runtime',
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (contextRuntime) {
|
|
917
|
+
return {
|
|
918
|
+
runtime: contextRuntime,
|
|
919
|
+
runtimeSource: 'context.runtime',
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (globalRuntime) {
|
|
924
|
+
return {
|
|
925
|
+
runtime: globalRuntime,
|
|
926
|
+
runtimeSource: 'global_runtime',
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
runtime: null,
|
|
932
|
+
runtimeSource: 'unavailable',
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function resolveRuntimeConfigSource(context = {}) {
|
|
937
|
+
const runtimeCfg = context.cfg && typeof context.cfg === 'object' ? context.cfg : null;
|
|
938
|
+
const accountId = context.accountId || context.account?.accountId || null;
|
|
939
|
+
|
|
940
|
+
if (runtimeCfg) {
|
|
941
|
+
return {
|
|
942
|
+
sourceType: 'root_cfg',
|
|
943
|
+
configSource: runtimeCfg,
|
|
944
|
+
runtimeConfig: resolveClaworldRuntimeConfig(runtimeCfg, accountId),
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const accountLike = context.account || context.config || null;
|
|
949
|
+
if (accountLike) {
|
|
950
|
+
return {
|
|
951
|
+
sourceType: 'resolved_account',
|
|
952
|
+
configSource: accountLike,
|
|
953
|
+
runtimeConfig: resolveClaworldRuntimeConfig(accountLike, accountId),
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return {
|
|
958
|
+
sourceType: 'empty',
|
|
959
|
+
configSource: {},
|
|
960
|
+
runtimeConfig: resolveClaworldRuntimeConfig({}, accountId),
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function fetchJson(fetchImpl, url, init = {}) {
|
|
965
|
+
try {
|
|
966
|
+
const response = await fetchImpl(url, init);
|
|
967
|
+
let body = null;
|
|
968
|
+
try {
|
|
969
|
+
body = await response.json();
|
|
970
|
+
} catch {
|
|
971
|
+
body = null;
|
|
972
|
+
}
|
|
973
|
+
return { ok: response.ok, status: response.status, body };
|
|
974
|
+
} catch (error) {
|
|
975
|
+
throw createRuntimeBoundaryError({
|
|
976
|
+
code: 'relay_fetch_failed',
|
|
977
|
+
category: 'transport',
|
|
978
|
+
status: 502,
|
|
979
|
+
message: `fetch failed: ${error?.message || String(error)}`,
|
|
980
|
+
publicMessage: 'relay fetch failed',
|
|
981
|
+
recoverable: true,
|
|
982
|
+
context: {
|
|
983
|
+
fetchUrl: url,
|
|
984
|
+
fetchMethod: init?.method || 'GET',
|
|
985
|
+
fetchHeaders: init?.headers || null,
|
|
986
|
+
},
|
|
987
|
+
cause: error,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
async function registerRelayBinding({ runtimeConfig, fetchImpl, logger }) {
|
|
993
|
+
if (typeof fetchImpl !== 'function') {
|
|
994
|
+
throw new Error('fetch is unavailable for relay registration');
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
998
|
+
const registration = normalizeRuntimeRegistration(runtimeConfig);
|
|
999
|
+
const registerResult = await fetchJson(fetchImpl, `${baseUrl}/v1/agents/register`, {
|
|
1000
|
+
method: 'POST',
|
|
1001
|
+
headers: buildRuntimeAuthHeaders(runtimeConfig, {
|
|
1002
|
+
accept: 'application/json',
|
|
1003
|
+
'content-type': 'application/json',
|
|
1004
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
1005
|
+
}),
|
|
1006
|
+
body: JSON.stringify({
|
|
1007
|
+
...(registration.enabled ? { agentCode: registration.agentCode } : {}),
|
|
1008
|
+
...(registration.displayName ? { displayName: registration.displayName } : {}),
|
|
1009
|
+
}),
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
if (!registerResult.ok) {
|
|
1013
|
+
logger.error?.('[claworld:bootstrap] register relay agent failed', {
|
|
1014
|
+
accountId: runtimeConfig.accountId,
|
|
1015
|
+
status: registerResult.status,
|
|
1016
|
+
body: registerResult.body,
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
const errorCode = normalizeClaworldText(registerResult.body?.error, 'relay_agent_registration_failed');
|
|
1020
|
+
const category = registerResult.status === 401
|
|
1021
|
+
? 'auth'
|
|
1022
|
+
: registerResult.status === 403
|
|
1023
|
+
? 'policy'
|
|
1024
|
+
: registerResult.status === 409
|
|
1025
|
+
? 'conflict'
|
|
1026
|
+
: registerResult.status >= 500
|
|
1027
|
+
? 'transport'
|
|
1028
|
+
: 'config';
|
|
1029
|
+
const publicMessage = errorCode === 'agent_code_conflict'
|
|
1030
|
+
? 'relay agent code already exists; reuse its appToken or choose a different registration.agentCode'
|
|
1031
|
+
: errorCode === 'agent_code_mismatch'
|
|
1032
|
+
? 'configured relay agent code does not match the provided appToken'
|
|
1033
|
+
: 'relay registration failed';
|
|
1034
|
+
|
|
1035
|
+
throw createRuntimeBoundaryError({
|
|
1036
|
+
code: errorCode,
|
|
1037
|
+
category,
|
|
1038
|
+
status: registerResult.status,
|
|
1039
|
+
message: `failed to register relay agent: ${registerResult.status}`,
|
|
1040
|
+
publicMessage,
|
|
1041
|
+
recoverable: registerResult.status >= 500,
|
|
1042
|
+
context: {
|
|
1043
|
+
accountId: runtimeConfig.accountId || null,
|
|
1044
|
+
requestedAgentCode: normalizeClaworldText(
|
|
1045
|
+
registerResult.body?.requestedAgentCode,
|
|
1046
|
+
registration.agentCode || null,
|
|
1047
|
+
),
|
|
1048
|
+
authenticatedAgentCode: normalizeClaworldText(registerResult.body?.authenticatedAgentCode, null),
|
|
1049
|
+
authenticatedAddress: normalizeClaworldText(registerResult.body?.authenticatedAddress, null),
|
|
1050
|
+
conflictingAgentId: normalizeClaworldText(registerResult.body?.agent?.agentId, null),
|
|
1051
|
+
conflictingAddress: normalizeClaworldText(registerResult.body?.agent?.address, null),
|
|
1052
|
+
appTokenConfigured: Boolean(resolveRuntimeAppToken(runtimeConfig)),
|
|
1053
|
+
},
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const registeredAgent = registerResult.body?.agent || null;
|
|
1058
|
+
const appToken = normalizeClaworldText(registerResult.body?.appToken, resolveRuntimeAppToken(runtimeConfig));
|
|
1059
|
+
if (!registeredAgent?.agentId || !appToken) {
|
|
1060
|
+
throw new Error('relay registration did not produce relay agent binding');
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return {
|
|
1064
|
+
bindingSource: normalizeClaworldText(
|
|
1065
|
+
registerResult.body?.bindingSource,
|
|
1066
|
+
resolveRuntimeAppToken(runtimeConfig) ? 'provided_app_token' : 'created_agent_app_token',
|
|
1067
|
+
),
|
|
1068
|
+
runtimeConfig: applyRuntimeIdentity(runtimeConfig, {
|
|
1069
|
+
agentId: registeredAgent.agentId,
|
|
1070
|
+
appToken,
|
|
1071
|
+
}),
|
|
1072
|
+
relayAgent: buildRelayAgentSummary(registeredAgent, runtimeConfig),
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
async function fetchRelayAgents({ runtimeConfig, fetchImpl, logger }) {
|
|
1077
|
+
if (typeof fetchImpl !== 'function') {
|
|
1078
|
+
throw new Error('fetch is unavailable for relay agent lookup');
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
|
|
1082
|
+
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/agents`, {
|
|
1083
|
+
headers: {
|
|
1084
|
+
accept: 'application/json',
|
|
1085
|
+
...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
|
|
1086
|
+
...buildRuntimeAuthHeaders(runtimeConfig),
|
|
1087
|
+
},
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
if (!result.ok) {
|
|
1091
|
+
logger.warn?.('[claworld:pairing] relay agent lookup failed', {
|
|
1092
|
+
accountId: runtimeConfig.accountId || null,
|
|
1093
|
+
status: result.status,
|
|
1094
|
+
body: result.body,
|
|
1095
|
+
});
|
|
1096
|
+
throw new Error(`relay agent lookup failed: ${result.status}`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return Array.isArray(result.body?.items) ? result.body.items : [];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
async function resolveRelayAgentSummary({
|
|
1103
|
+
runtimeConfig,
|
|
1104
|
+
fetchImpl,
|
|
1105
|
+
logger,
|
|
1106
|
+
agentId = null,
|
|
1107
|
+
agentCode = null,
|
|
1108
|
+
address = null,
|
|
1109
|
+
}) {
|
|
1110
|
+
const requestedIdentity = normalizeRelayIdentityInput({
|
|
1111
|
+
runtimeConfig,
|
|
1112
|
+
agentId,
|
|
1113
|
+
agentCode,
|
|
1114
|
+
address,
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
try {
|
|
1118
|
+
const items = await fetchRelayAgents({ runtimeConfig, fetchImpl, logger });
|
|
1119
|
+
const match = items
|
|
1120
|
+
.map((item) => buildRelayAgentSummary(item, runtimeConfig))
|
|
1121
|
+
.find((item) => {
|
|
1122
|
+
if (requestedIdentity.agentId && item.agentId === requestedIdentity.agentId) return true;
|
|
1123
|
+
if (requestedIdentity.agentCode && item.agentCode === requestedIdentity.agentCode) return true;
|
|
1124
|
+
if (requestedIdentity.address && item.address === requestedIdentity.address) return true;
|
|
1125
|
+
if (requestedIdentity.relayLocalCode && item.relayLocalCode === requestedIdentity.relayLocalCode) return true;
|
|
1126
|
+
return false;
|
|
1127
|
+
}) || null;
|
|
1128
|
+
|
|
1129
|
+
if (match) {
|
|
1130
|
+
return {
|
|
1131
|
+
...match,
|
|
1132
|
+
resolved: true,
|
|
1133
|
+
resolutionSource: requestedIdentity.agentId && match.agentId === requestedIdentity.agentId
|
|
1134
|
+
? 'agentId'
|
|
1135
|
+
: requestedIdentity.agentCode && match.agentCode === requestedIdentity.agentCode
|
|
1136
|
+
? 'agentCode'
|
|
1137
|
+
: requestedIdentity.address && match.address === requestedIdentity.address
|
|
1138
|
+
? 'address'
|
|
1139
|
+
: 'relayLocalCode',
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
} catch {
|
|
1143
|
+
// Fallback below keeps pairing/send tools usable even when lookup fails.
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const canInferFallbackAgentCode = Boolean(
|
|
1147
|
+
requestedIdentity.agentCode
|
|
1148
|
+
|| requestedIdentity.address
|
|
1149
|
+
|| requestedIdentity.relayLocalCode,
|
|
1150
|
+
);
|
|
1151
|
+
const fallbackAgentCode = requestedIdentity.agentCode
|
|
1152
|
+
|| (canInferFallbackAgentCode ? buildCanonicalRelayAgentCode(runtimeConfig.localAgent?.agentCode, runtimeConfig) : null)
|
|
1153
|
+
|| (canInferFallbackAgentCode ? buildCanonicalRelayAgentCode(runtimeConfig.accountId, runtimeConfig) : null);
|
|
1154
|
+
const parsedFallbackAgentCode = parseCanonicalRelayAgentCode(fallbackAgentCode);
|
|
1155
|
+
|
|
1156
|
+
return {
|
|
1157
|
+
agentId: requestedIdentity.agentId,
|
|
1158
|
+
agentCode: fallbackAgentCode,
|
|
1159
|
+
relayLocalCode: requestedIdentity.relayLocalCode
|
|
1160
|
+
|| parsedFallbackAgentCode?.relayLocalCode
|
|
1161
|
+
|| normalizeRelayLocalAgentCode(runtimeConfig.localAgent?.agentCode, null)
|
|
1162
|
+
|| null,
|
|
1163
|
+
domain: requestedIdentity.domain
|
|
1164
|
+
|| parsedFallbackAgentCode?.domain
|
|
1165
|
+
|| normalizeRelayDomainName(inferRelayDomain(runtimeConfig), null),
|
|
1166
|
+
address: requestedIdentity.address || fallbackAgentCode || null,
|
|
1167
|
+
displayName: normalizeClaworldText(runtimeConfig.localAgent?.displayName, null),
|
|
1168
|
+
discoverable: null,
|
|
1169
|
+
contactable: null,
|
|
1170
|
+
online: null,
|
|
1171
|
+
resolved: false,
|
|
1172
|
+
resolutionSource: 'fallback',
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async function ensureAgentPairing({ runtimeConfig, fetchImpl, logger }) {
|
|
1177
|
+
const binding = await ensureRelayBinding({ runtimeConfig, fetchImpl, logger });
|
|
1178
|
+
const pairedRuntimeConfig = binding.runtimeConfig;
|
|
1179
|
+
const relayAgentId = normalizeClaworldText(pairedRuntimeConfig.relay?.agentId, null);
|
|
1180
|
+
const relayAgentCode = normalizeRelayLocalAgentCode(
|
|
1181
|
+
pairedRuntimeConfig.localAgent?.agentCode,
|
|
1182
|
+
normalizeRelayLocalAgentCode(pairedRuntimeConfig.accountId, null),
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
if (!relayAgentId) {
|
|
1186
|
+
return {
|
|
1187
|
+
status: 'unpaired',
|
|
1188
|
+
reason: 'missing_app_token_or_registration',
|
|
1189
|
+
bindingSource: binding.bindingSource,
|
|
1190
|
+
runtimeConfig: pairedRuntimeConfig,
|
|
1191
|
+
relayAgent: await resolveRelayAgentSummary({
|
|
1192
|
+
runtimeConfig: pairedRuntimeConfig,
|
|
1193
|
+
fetchImpl,
|
|
1194
|
+
logger,
|
|
1195
|
+
agentId: relayAgentId,
|
|
1196
|
+
agentCode: relayAgentCode,
|
|
1197
|
+
}),
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
return {
|
|
1202
|
+
status: 'paired',
|
|
1203
|
+
reason: null,
|
|
1204
|
+
bindingSource: binding.bindingSource,
|
|
1205
|
+
runtimeConfig: pairedRuntimeConfig,
|
|
1206
|
+
relayAgent: await resolveRelayAgentSummary({
|
|
1207
|
+
runtimeConfig: pairedRuntimeConfig,
|
|
1208
|
+
fetchImpl,
|
|
1209
|
+
logger,
|
|
1210
|
+
agentId: relayAgentId,
|
|
1211
|
+
agentCode: relayAgentCode,
|
|
1212
|
+
}),
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
async function fetchPostSetupWorldDirectory({ cfg, accountId, runtimeConfig, limit = null, sort = null, page = null, fetchImpl, logger }) {
|
|
1217
|
+
if (typeof fetchImpl !== 'function') {
|
|
1218
|
+
throw new Error('fetch is unavailable for claworld product-shell helper');
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg || {}, accountId || null);
|
|
1222
|
+
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
1223
|
+
const requestUrl = new URL(`${baseUrl}/v1/worlds`);
|
|
1224
|
+
if (limit != null) requestUrl.searchParams.set('limit', String(limit));
|
|
1225
|
+
if (sort) requestUrl.searchParams.set('sort', String(sort));
|
|
1226
|
+
if (page != null) requestUrl.searchParams.set('page', String(page));
|
|
1227
|
+
const worlds = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
1228
|
+
headers: {
|
|
1229
|
+
accept: 'application/json',
|
|
1230
|
+
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
1231
|
+
...buildRuntimeAuthHeaders(resolvedRuntimeConfig),
|
|
1232
|
+
},
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
if (!worlds.ok) {
|
|
1236
|
+
logger.error?.('[claworld:product-shell] world directory fetch failed', {
|
|
1237
|
+
status: worlds.status,
|
|
1238
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1239
|
+
body: worlds.body,
|
|
1240
|
+
});
|
|
1241
|
+
throw new Error(`claworld product-shell world fetch failed: ${worlds.status}`);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
return buildPostSetupWorldDirectory(worlds.body, {
|
|
1245
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
async function ensureRelayBinding({ runtimeConfig, fetchImpl, logger }) {
|
|
1250
|
+
const normalizedRuntimeConfig = applyRuntimeIdentity(runtimeConfig);
|
|
1251
|
+
const registration = normalizeRuntimeRegistration(normalizedRuntimeConfig);
|
|
1252
|
+
const appToken = resolveRuntimeAppToken(normalizedRuntimeConfig);
|
|
1253
|
+
|
|
1254
|
+
if (appToken && normalizedRuntimeConfig.relay?.agentId) {
|
|
1255
|
+
return {
|
|
1256
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1257
|
+
bindingSource: 'configured_app_token',
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (!appToken && !registration.enabled) {
|
|
1262
|
+
return { runtimeConfig: normalizedRuntimeConfig, bindingSource: 'no_registration_context' };
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
logger.info?.('[claworld:bootstrap] ensureRelayBinding start', {
|
|
1266
|
+
accountId: normalizedRuntimeConfig.accountId,
|
|
1267
|
+
serverUrl: normalizedRuntimeConfig.serverUrl,
|
|
1268
|
+
appTokenConfigured: Boolean(appToken),
|
|
1269
|
+
registrationEnabled: registration.enabled,
|
|
1270
|
+
agentCode: registration.agentCode || null,
|
|
1271
|
+
hasRelayAgentId: Boolean(normalizedRuntimeConfig.relay?.agentId),
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
return await registerRelayBinding({
|
|
1275
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1276
|
+
fetchImpl,
|
|
1277
|
+
logger,
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function resolveSessionCreatedWorldId(event = {}) {
|
|
1282
|
+
const session = event?.raw?.data && typeof event.raw.data === 'object' ? event.raw.data : {};
|
|
1283
|
+
const metadata = session.metadata && typeof session.metadata === 'object' ? session.metadata : {};
|
|
1284
|
+
const candidateWorldId = [
|
|
1285
|
+
metadata.worldId,
|
|
1286
|
+
metadata.worldContext?.worldId,
|
|
1287
|
+
session.worldContext?.worldId,
|
|
1288
|
+
session.worldId,
|
|
1289
|
+
session.session?.metadata?.worldId,
|
|
1290
|
+
session.session?.metadata?.worldContext?.worldId,
|
|
1291
|
+
session.session?.worldContext?.worldId,
|
|
1292
|
+
session.session?.worldId,
|
|
1293
|
+
event?.inboundEnvelope?.metadata?.worldId,
|
|
1294
|
+
event?.inboundEnvelope?.metadata?.worldContext?.worldId,
|
|
1295
|
+
event?.inboundEnvelope?.metadata?.session?.worldId,
|
|
1296
|
+
event?.inboundEnvelope?.metadata?.session?.metadata?.worldId,
|
|
1297
|
+
event?.inboundEnvelope?.metadata?.session?.metadata?.worldContext?.worldId,
|
|
1298
|
+
].map((value) => resolveNormalizedText(value, null)).find(Boolean);
|
|
1299
|
+
return candidateWorldId || null;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function resolveTurnWorldId(event = {}) {
|
|
1303
|
+
const turnData = event?.raw?.data && typeof event.raw.data === 'object' ? event.raw.data : {};
|
|
1304
|
+
const metadata = turnData.metadata && typeof turnData.metadata === 'object' ? turnData.metadata : {};
|
|
1305
|
+
const candidateWorldId = [
|
|
1306
|
+
metadata.worldId,
|
|
1307
|
+
metadata.worldContext?.worldId,
|
|
1308
|
+
turnData.worldContext?.worldId,
|
|
1309
|
+
turnData.worldId,
|
|
1310
|
+
turnData.session?.metadata?.worldId,
|
|
1311
|
+
turnData.session?.metadata?.worldContext?.worldId,
|
|
1312
|
+
turnData.session?.worldContext?.worldId,
|
|
1313
|
+
turnData.session?.worldId,
|
|
1314
|
+
event?.inboundEnvelope?.metadata?.worldId,
|
|
1315
|
+
event?.inboundEnvelope?.metadata?.worldContext?.worldId,
|
|
1316
|
+
event?.inboundEnvelope?.metadata?.session?.worldId,
|
|
1317
|
+
event?.inboundEnvelope?.metadata?.session?.metadata?.worldId,
|
|
1318
|
+
event?.inboundEnvelope?.metadata?.session?.metadata?.worldContext?.worldId,
|
|
1319
|
+
].map((value) => resolveNormalizedText(value, null)).find(Boolean);
|
|
1320
|
+
return candidateWorldId || null;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function resolveAcceptedChatKickoffBundle(event = {}) {
|
|
1324
|
+
const turnData = event?.raw?.data && typeof event.raw.data === 'object' ? event.raw.data : {};
|
|
1325
|
+
if (turnData.acceptedChatKickoff && typeof turnData.acceptedChatKickoff === 'object' && !Array.isArray(turnData.acceptedChatKickoff)) {
|
|
1326
|
+
return turnData.acceptedChatKickoff;
|
|
1327
|
+
}
|
|
1328
|
+
const payload = turnData.payload && typeof turnData.payload === 'object' && !Array.isArray(turnData.payload)
|
|
1329
|
+
? turnData.payload
|
|
1330
|
+
: {};
|
|
1331
|
+
if (payload.kickoffBundle && typeof payload.kickoffBundle === 'object' && !Array.isArray(payload.kickoffBundle)) {
|
|
1332
|
+
return payload.kickoffBundle;
|
|
1333
|
+
}
|
|
1334
|
+
return null;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function resolveAcceptedChatKickoffRuntimeContext(event = {}, { bundle = null, localAgentId = null, fallback = 'recipient' } = {}) {
|
|
1338
|
+
const turnData = event?.raw?.data && typeof event.raw.data === 'object' ? event.raw.data : {};
|
|
1339
|
+
if (
|
|
1340
|
+
turnData.acceptedChatKickoffRuntimeContext
|
|
1341
|
+
&& typeof turnData.acceptedChatKickoffRuntimeContext === 'object'
|
|
1342
|
+
&& !Array.isArray(turnData.acceptedChatKickoffRuntimeContext)
|
|
1343
|
+
) {
|
|
1344
|
+
return turnData.acceptedChatKickoffRuntimeContext;
|
|
1345
|
+
}
|
|
1346
|
+
const payload = turnData.payload && typeof turnData.payload === 'object' && !Array.isArray(turnData.payload)
|
|
1347
|
+
? turnData.payload
|
|
1348
|
+
: {};
|
|
1349
|
+
if (
|
|
1350
|
+
payload.acceptedChatKickoffRuntimeContext
|
|
1351
|
+
&& typeof payload.acceptedChatKickoffRuntimeContext === 'object'
|
|
1352
|
+
&& !Array.isArray(payload.acceptedChatKickoffRuntimeContext)
|
|
1353
|
+
) {
|
|
1354
|
+
return payload.acceptedChatKickoffRuntimeContext;
|
|
1355
|
+
}
|
|
1356
|
+
if (!bundle) return null;
|
|
1357
|
+
return readAcceptedChatKickoffRuntimeContext(bundle, {
|
|
1358
|
+
viewer: resolveAcceptedChatKickoffViewer(bundle, {
|
|
1359
|
+
localAgentId,
|
|
1360
|
+
fallback,
|
|
1361
|
+
}),
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function buildWorldAwareSessionKey(sessionKey, worldId = null) {
|
|
1366
|
+
if (!worldId) return sessionKey;
|
|
1367
|
+
return `${sessionKey}:world:${worldId}`;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function buildSessionWorldCacheKey(runtimeAccountId, sessionId) {
|
|
1371
|
+
const accountId = resolveNormalizedText(runtimeAccountId, 'default');
|
|
1372
|
+
if (!sessionId || !accountId) return null;
|
|
1373
|
+
return `${accountId}:${sessionId}`;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function resolveSessionCreatedPeerAgentId(session = {}, localRelayAgentId = null) {
|
|
1377
|
+
const agentAId = normalizeClaworldText(session.agentAId, null);
|
|
1378
|
+
const agentBId = normalizeClaworldText(session.agentBId, null);
|
|
1379
|
+
const localAgentId = normalizeClaworldText(localRelayAgentId, null);
|
|
1380
|
+
|
|
1381
|
+
if (localAgentId && agentAId === localAgentId) return agentBId;
|
|
1382
|
+
if (localAgentId && agentBId === localAgentId) return agentAId;
|
|
1383
|
+
return agentAId || agentBId || null;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
async function maybeInjectWorldSessionStartupContext({
|
|
1387
|
+
runtime,
|
|
1388
|
+
cfg,
|
|
1389
|
+
runtimeConfig,
|
|
1390
|
+
runtimeAccountId,
|
|
1391
|
+
event,
|
|
1392
|
+
logger,
|
|
1393
|
+
sessionIdToWorldId,
|
|
1394
|
+
} = {}) {
|
|
1395
|
+
const worldCache = sessionIdToWorldId || null;
|
|
1396
|
+
if (!runtime?.system?.enqueueSystemEvent || !runtime?.channel?.routing?.resolveAgentRoute) {
|
|
1397
|
+
return { skipped: true, reason: 'missing_runtime_system_or_routing' };
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const session = event?.raw?.data && typeof event.raw.data === 'object' ? event.raw.data : {};
|
|
1401
|
+
const worldId = resolveSessionCreatedWorldId(event);
|
|
1402
|
+
if (!worldId) {
|
|
1403
|
+
return { skipped: true, reason: 'not_world_scoped' };
|
|
1404
|
+
}
|
|
1405
|
+
const sessionLookupKey = buildSessionWorldCacheKey(runtimeAccountId, session.sessionId);
|
|
1406
|
+
if (sessionLookupKey && worldCache) {
|
|
1407
|
+
worldCache.set(sessionLookupKey, worldId);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const peerAgentId = resolveSessionCreatedPeerAgentId(session, runtimeConfig?.relay?.agentId || null);
|
|
1411
|
+
if (!peerAgentId) {
|
|
1412
|
+
return { skipped: true, reason: 'peer_agent_unresolved' };
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const currentCfg = cfg || await runtime.config?.loadConfig?.() || {};
|
|
1416
|
+
const routePeerId = resolveRelayAddress(peerAgentId, runtimeConfig);
|
|
1417
|
+
const route = runtime.channel.routing.resolveAgentRoute({
|
|
1418
|
+
cfg: currentCfg,
|
|
1419
|
+
channel: 'claworld',
|
|
1420
|
+
accountId: runtimeConfig.accountId,
|
|
1421
|
+
peer: { kind: 'direct', id: routePeerId || peerAgentId },
|
|
1422
|
+
}) || {};
|
|
1423
|
+
const sessionKey = route.sessionKey || `agent:${route.agentId || runtimeConfig.accountId}:main`;
|
|
1424
|
+
const worldSessionKey = buildWorldAwareSessionKey(sessionKey, worldId);
|
|
1425
|
+
const enqueueResult = enqueueBackendGuidance({
|
|
1426
|
+
runtime,
|
|
1427
|
+
sessionKey: worldSessionKey,
|
|
1428
|
+
payload: session,
|
|
1429
|
+
});
|
|
1430
|
+
if (enqueueResult.skipped) {
|
|
1431
|
+
return { skipped: true, reason: enqueueResult.reason, worldId, sessionKey: worldSessionKey };
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
logger.info?.(`[claworld:${runtimeAccountId}] injected backend-authored world startup guidance`, {
|
|
1435
|
+
worldId,
|
|
1436
|
+
sessionId: session.sessionId || null,
|
|
1437
|
+
sessionKey: worldSessionKey,
|
|
1438
|
+
routePeerId: routePeerId || peerAgentId,
|
|
1439
|
+
guidanceCount: enqueueResult.count,
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
return {
|
|
1443
|
+
skipped: false,
|
|
1444
|
+
ok: true,
|
|
1445
|
+
worldId,
|
|
1446
|
+
sessionKey: worldSessionKey,
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function buildRelayInboundEnvelope({
|
|
1451
|
+
runtime,
|
|
1452
|
+
currentCfg,
|
|
1453
|
+
runtimeAccountId,
|
|
1454
|
+
remoteIdentity,
|
|
1455
|
+
incomingText,
|
|
1456
|
+
contextText = null,
|
|
1457
|
+
commandText = null,
|
|
1458
|
+
sessionId,
|
|
1459
|
+
turnId,
|
|
1460
|
+
relayEvent,
|
|
1461
|
+
mode = 'direct',
|
|
1462
|
+
worldId = null,
|
|
1463
|
+
conversationKey = null,
|
|
1464
|
+
}) {
|
|
1465
|
+
const envelopeOptions = runtime?.channel?.reply?.resolveEnvelopeFormatOptions
|
|
1466
|
+
? runtime.channel.reply.resolveEnvelopeFormatOptions(currentCfg)
|
|
1467
|
+
: undefined;
|
|
1468
|
+
const bodyText = [
|
|
1469
|
+
String(contextText || '').trim(),
|
|
1470
|
+
String(incomingText || '').trim(),
|
|
1471
|
+
].filter(Boolean).join('\n\n');
|
|
1472
|
+
const remoteLabel = String(remoteIdentity || 'unknown-peer').trim() || 'unknown-peer';
|
|
1473
|
+
const systemLines = [
|
|
1474
|
+
`[claworld peer ${remoteLabel}]`,
|
|
1475
|
+
`[claworld mode ${mode}]`,
|
|
1476
|
+
...(worldId ? [`[claworld world ${worldId}]`] : []),
|
|
1477
|
+
...(conversationKey ? [`[claworld conversation ${conversationKey}]`] : []),
|
|
1478
|
+
`[claworld session ${sessionId}]`,
|
|
1479
|
+
`[claworld turn ${turnId}]`,
|
|
1480
|
+
`[relay event ${relayEvent}]`,
|
|
1481
|
+
];
|
|
1482
|
+
const rawBody = `${bodyText}\n\n${systemLines.join('\n')}`;
|
|
1483
|
+
const normalizedCommandText = String(commandText || '').trim();
|
|
1484
|
+
const commandBody = normalizedCommandText || rawBody;
|
|
1485
|
+
|
|
1486
|
+
if (runtime?.channel?.reply?.formatAgentEnvelope) {
|
|
1487
|
+
return {
|
|
1488
|
+
Body: runtime.channel.reply.formatAgentEnvelope({
|
|
1489
|
+
channel: 'Claworld',
|
|
1490
|
+
from: remoteLabel,
|
|
1491
|
+
timestamp: new Date(),
|
|
1492
|
+
envelope: envelopeOptions,
|
|
1493
|
+
body: rawBody,
|
|
1494
|
+
}),
|
|
1495
|
+
RawBody: rawBody,
|
|
1496
|
+
CommandBody: commandBody,
|
|
1497
|
+
ContextLines: systemLines,
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
return {
|
|
1502
|
+
Body: `${remoteLabel}: ${rawBody}`,
|
|
1503
|
+
RawBody: rawBody,
|
|
1504
|
+
CommandBody: commandBody,
|
|
1505
|
+
ContextLines: systemLines,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function createRelayReplyDispatcher({
|
|
1510
|
+
runtime,
|
|
1511
|
+
currentCfg,
|
|
1512
|
+
route,
|
|
1513
|
+
relayClient,
|
|
1514
|
+
sessionId,
|
|
1515
|
+
turnId,
|
|
1516
|
+
relayEvent,
|
|
1517
|
+
localRelayAgentId,
|
|
1518
|
+
allowRelayContinuation = true,
|
|
1519
|
+
logger,
|
|
1520
|
+
runtimeAccountId,
|
|
1521
|
+
}) {
|
|
1522
|
+
const prefixContext = runtime?.channel?.reply?.createReplyPrefixContext
|
|
1523
|
+
? runtime.channel.reply.createReplyPrefixContext({ cfg: currentCfg, agentId: route.agentId })
|
|
1524
|
+
: { responsePrefix: '', responsePrefixContextProvider: () => ({}) };
|
|
1525
|
+
const humanDelay = runtime?.channel?.reply?.resolveHumanDelayConfig
|
|
1526
|
+
? runtime.channel.reply.resolveHumanDelayConfig(currentCfg, route.agentId)
|
|
1527
|
+
: undefined;
|
|
1528
|
+
|
|
1529
|
+
let replied = false;
|
|
1530
|
+
let suppressed = false;
|
|
1531
|
+
const finalTexts = [];
|
|
1532
|
+
const blockTexts = [];
|
|
1533
|
+
const runtimeOutputSummary = {
|
|
1534
|
+
counts: {
|
|
1535
|
+
final: 0,
|
|
1536
|
+
block: 0,
|
|
1537
|
+
tool: 0,
|
|
1538
|
+
partial: 0,
|
|
1539
|
+
reasoning: 0,
|
|
1540
|
+
toolStart: 0,
|
|
1541
|
+
assistantMessageStart: 0,
|
|
1542
|
+
reasoningEnd: 0,
|
|
1543
|
+
compactionStart: 0,
|
|
1544
|
+
compactionEnd: 0,
|
|
1545
|
+
operationalNotice: 0,
|
|
1546
|
+
},
|
|
1547
|
+
previews: {
|
|
1548
|
+
final: [],
|
|
1549
|
+
block: [],
|
|
1550
|
+
tool: [],
|
|
1551
|
+
partial: [],
|
|
1552
|
+
reasoning: [],
|
|
1553
|
+
operationalNotice: [],
|
|
1554
|
+
},
|
|
1555
|
+
relayContinuationSource: 'none',
|
|
1556
|
+
relayContinuationPreview: null,
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
const recordRuntimePayload = (kind, payload = {}) => {
|
|
1560
|
+
if (!Object.prototype.hasOwnProperty.call(runtimeOutputSummary.counts, kind)) return;
|
|
1561
|
+
runtimeOutputSummary.counts[kind] += 1;
|
|
1562
|
+
const text = String(payload?.text ?? payload?.body ?? '').trim();
|
|
1563
|
+
if (kind === 'final') {
|
|
1564
|
+
if (text) {
|
|
1565
|
+
finalTexts.push(text);
|
|
1566
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, text);
|
|
1567
|
+
const classified = classifyRelayContinuationText(text);
|
|
1568
|
+
if (classified.operational) {
|
|
1569
|
+
runtimeOutputSummary.counts.operationalNotice += 1;
|
|
1570
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, text);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (kind === 'block') {
|
|
1576
|
+
if (text) {
|
|
1577
|
+
blockTexts.push(text);
|
|
1578
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.block, text);
|
|
1579
|
+
}
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (kind === 'tool') {
|
|
1583
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.tool, text);
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
const recordRuntimeTextEvent = (kind, text) => {
|
|
1588
|
+
if (!Object.prototype.hasOwnProperty.call(runtimeOutputSummary.counts, kind)) return;
|
|
1589
|
+
runtimeOutputSummary.counts[kind] += 1;
|
|
1590
|
+
if (kind === 'partial') {
|
|
1591
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.partial, text);
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
if (kind === 'reasoning') {
|
|
1595
|
+
appendRuntimeOutputPreview(runtimeOutputSummary.previews.reasoning, text);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
const recordRuntimeLifecycle = (kind) => {
|
|
1600
|
+
if (!Object.prototype.hasOwnProperty.call(runtimeOutputSummary.counts, kind)) return;
|
|
1601
|
+
runtimeOutputSummary.counts[kind] += 1;
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
const flushReply = async (text) => {
|
|
1605
|
+
const controlledReply = extractControlledReply(text);
|
|
1606
|
+
const normalized = String(controlledReply.text || '').trim();
|
|
1607
|
+
if ((!normalized && !controlledReply.control) || replied || suppressed) return false;
|
|
1608
|
+
if (allowRelayContinuation === false) {
|
|
1609
|
+
suppressed = true;
|
|
1610
|
+
return false;
|
|
1611
|
+
}
|
|
1612
|
+
if (relayEvent === 'turn.reply') {
|
|
1613
|
+
if (!localRelayAgentId) {
|
|
1614
|
+
throw new Error('missing local relay agent id for relay continuation');
|
|
1615
|
+
}
|
|
1616
|
+
const result = await relayClient.createTurn({
|
|
1617
|
+
sessionId,
|
|
1618
|
+
fromAgentId: localRelayAgentId,
|
|
1619
|
+
payload: {
|
|
1620
|
+
text: normalized,
|
|
1621
|
+
source: 'openclaw-autochain',
|
|
1622
|
+
...(controlledReply.control ? { control: controlledReply.control } : {}),
|
|
1623
|
+
},
|
|
1624
|
+
});
|
|
1625
|
+
if (result.status !== 201) {
|
|
1626
|
+
throw new Error(`failed to create follow-up relay turn: ${result.status}`);
|
|
1627
|
+
}
|
|
1628
|
+
} else {
|
|
1629
|
+
relayClient.sendTurnReply({
|
|
1630
|
+
sessionId,
|
|
1631
|
+
messageId: turnId,
|
|
1632
|
+
replyText: normalized,
|
|
1633
|
+
source: 'openclaw-autochain',
|
|
1634
|
+
...(controlledReply.control ? { control: controlledReply.control } : {}),
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
replied = true;
|
|
1638
|
+
return true;
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
const dispatchApi = runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
1642
|
+
responsePrefix: prefixContext.responsePrefix,
|
|
1643
|
+
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
1644
|
+
humanDelay,
|
|
1645
|
+
deliver: async (payload = {}, info = {}) => {
|
|
1646
|
+
if (info?.kind === 'final') {
|
|
1647
|
+
recordRuntimePayload('final', payload);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (info?.kind === 'block') {
|
|
1651
|
+
recordRuntimePayload('block', payload);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (info?.kind === 'tool') {
|
|
1655
|
+
recordRuntimePayload('tool', payload);
|
|
1656
|
+
}
|
|
1657
|
+
},
|
|
1658
|
+
onError: (error, info) => {
|
|
1659
|
+
logger.error?.(`[claworld:${runtimeAccountId}] turn bridge dispatch error`, {
|
|
1660
|
+
sessionId,
|
|
1661
|
+
turnId,
|
|
1662
|
+
kind: info?.kind || null,
|
|
1663
|
+
error: error?.message || String(error),
|
|
1664
|
+
});
|
|
1665
|
+
},
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
const markDispatchIdle = async () => {
|
|
1669
|
+
if (!replied && !suppressed) {
|
|
1670
|
+
const continuation = buildRelayContinuationText({ finalTexts, blockTexts });
|
|
1671
|
+
runtimeOutputSummary.relayContinuationSource = continuation.source;
|
|
1672
|
+
runtimeOutputSummary.relayContinuationPreview = continuation.text
|
|
1673
|
+
? previewRuntimeOutputText(continuation.text)
|
|
1674
|
+
: null;
|
|
1675
|
+
const continuationText = continuation.text;
|
|
1676
|
+
if (continuationText) {
|
|
1677
|
+
await flushReply(continuationText);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
await dispatchApi.markDispatchIdle?.();
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
return {
|
|
1684
|
+
dispatcher: dispatchApi.dispatcher,
|
|
1685
|
+
replyOptions: {
|
|
1686
|
+
...dispatchApi.replyOptions,
|
|
1687
|
+
onPartialReply: async (payload = {}) => {
|
|
1688
|
+
recordRuntimeTextEvent('partial', payload?.text);
|
|
1689
|
+
},
|
|
1690
|
+
onReasoningStream: async (payload = {}) => {
|
|
1691
|
+
recordRuntimeTextEvent('reasoning', payload?.text);
|
|
1692
|
+
},
|
|
1693
|
+
onReasoningEnd: async () => {
|
|
1694
|
+
recordRuntimeLifecycle('reasoningEnd');
|
|
1695
|
+
},
|
|
1696
|
+
onAssistantMessageStart: async () => {
|
|
1697
|
+
recordRuntimeLifecycle('assistantMessageStart');
|
|
1698
|
+
},
|
|
1699
|
+
onToolStart: async () => {
|
|
1700
|
+
recordRuntimeLifecycle('toolStart');
|
|
1701
|
+
},
|
|
1702
|
+
onCompactionStart: async () => {
|
|
1703
|
+
recordRuntimeLifecycle('compactionStart');
|
|
1704
|
+
},
|
|
1705
|
+
onCompactionEnd: async () => {
|
|
1706
|
+
recordRuntimeLifecycle('compactionEnd');
|
|
1707
|
+
},
|
|
1708
|
+
},
|
|
1709
|
+
markDispatchIdle,
|
|
1710
|
+
didReply: () => replied,
|
|
1711
|
+
getRuntimeOutputSummary: () => ({
|
|
1712
|
+
counts: { ...runtimeOutputSummary.counts },
|
|
1713
|
+
previews: {
|
|
1714
|
+
final: [...runtimeOutputSummary.previews.final],
|
|
1715
|
+
block: [...runtimeOutputSummary.previews.block],
|
|
1716
|
+
tool: [...runtimeOutputSummary.previews.tool],
|
|
1717
|
+
partial: [...runtimeOutputSummary.previews.partial],
|
|
1718
|
+
reasoning: [...runtimeOutputSummary.previews.reasoning],
|
|
1719
|
+
operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
|
|
1720
|
+
},
|
|
1721
|
+
relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
|
|
1722
|
+
relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
|
|
1723
|
+
}),
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
async function maybeBridgeDeliveredTurn({
|
|
1728
|
+
relayClient,
|
|
1729
|
+
runtimeConfig,
|
|
1730
|
+
runtimeAccountId,
|
|
1731
|
+
event,
|
|
1732
|
+
logger,
|
|
1733
|
+
runtime,
|
|
1734
|
+
cfg,
|
|
1735
|
+
inbound,
|
|
1736
|
+
sessionIdToWorldId,
|
|
1737
|
+
}) {
|
|
1738
|
+
const worldCache = sessionIdToWorldId || null;
|
|
1739
|
+
const relayEvent = event?.relayEvent || 'turn.deliver';
|
|
1740
|
+
const turnData = event?.raw?.data || {};
|
|
1741
|
+
const sessionId = turnData.sessionId || event?.inboundEnvelope?.session_id || null;
|
|
1742
|
+
const turnId = turnData.turnId || event?.inboundEnvelope?.event_id || null;
|
|
1743
|
+
const kickoffBundle = resolveAcceptedChatKickoffBundle(event);
|
|
1744
|
+
const payload = turnData.payload && typeof turnData.payload === 'object' && !Array.isArray(turnData.payload)
|
|
1745
|
+
? turnData.payload
|
|
1746
|
+
: {};
|
|
1747
|
+
const localRelayAgentId = runtimeConfig.relay?.agentId || relayClient?.boundAgentId || null;
|
|
1748
|
+
const kickoffRuntimeContext = resolveAcceptedChatKickoffRuntimeContext(event, {
|
|
1749
|
+
bundle: kickoffBundle,
|
|
1750
|
+
localAgentId: localRelayAgentId,
|
|
1751
|
+
fallback: relayEvent === 'turn.deliver' ? 'sender' : 'recipient',
|
|
1752
|
+
});
|
|
1753
|
+
const incomingText = turnData.payload?.text
|
|
1754
|
+
|| event?.inboundEnvelope?.payload?.text
|
|
1755
|
+
|| (
|
|
1756
|
+
payload.kind === ACCEPTED_CHAT_KICKOFF_PAYLOAD_KIND
|
|
1757
|
+
? kickoffRuntimeContext?.text || ''
|
|
1758
|
+
: ''
|
|
1759
|
+
);
|
|
1760
|
+
const fromAgentId = turnData.fromAgentId || event?.inboundEnvelope?.from_agent_id || null;
|
|
1761
|
+
const conversationKey = turnData.conversationKey || null;
|
|
1762
|
+
const bridgedCommandText = payload.kind === ACCEPTED_CHAT_KICKOFF_PAYLOAD_KIND
|
|
1763
|
+
? kickoffRuntimeContext?.briefText || kickoffBundle?.request?.brief?.text || incomingText
|
|
1764
|
+
: incomingText;
|
|
1765
|
+
const kickoffContextText = relayEvent === 'turn.reply'
|
|
1766
|
+
&& kickoffBundle?.kickoffTurnId
|
|
1767
|
+
&& kickoffBundle.kickoffTurnId === turnId
|
|
1768
|
+
? kickoffRuntimeContext?.text || null
|
|
1769
|
+
: null;
|
|
1770
|
+
const senderId = fromAgentId || 'unknown-peer';
|
|
1771
|
+
if (
|
|
1772
|
+
!runtime?.channel?.routing?.resolveAgentRoute
|
|
1773
|
+
|| !runtime?.channel?.reply?.finalizeInboundContext
|
|
1774
|
+
|| !runtime?.channel?.reply?.dispatchReplyFromConfig
|
|
1775
|
+
|| !runtime?.channel?.reply?.createReplyDispatcherWithTyping
|
|
1776
|
+
) {
|
|
1777
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] skipping relay bridge: missing runtime bridge hooks`, {
|
|
1778
|
+
relayEvent,
|
|
1779
|
+
sessionId,
|
|
1780
|
+
turnId,
|
|
1781
|
+
});
|
|
1782
|
+
return { skipped: true, reason: 'missing_runtime_bridge_hooks' };
|
|
1783
|
+
}
|
|
1784
|
+
if (!sessionId || !turnId || !incomingText) {
|
|
1785
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] skipping relay bridge: missing turn payload`, {
|
|
1786
|
+
relayEvent,
|
|
1787
|
+
sessionId,
|
|
1788
|
+
turnId,
|
|
1789
|
+
hasIncomingText: Boolean(incomingText),
|
|
1790
|
+
});
|
|
1791
|
+
return { skipped: true, reason: 'missing_turn_payload' };
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const currentCfg = cfg || await runtime.config?.loadConfig?.() || {};
|
|
1795
|
+
const localAddress = resolveRelayAddress(runtimeConfig.accountId || '', runtimeConfig);
|
|
1796
|
+
const remoteAddress = resolveRelayAddress(senderId, runtimeConfig);
|
|
1797
|
+
const routePeerId = remoteAddress || senderId;
|
|
1798
|
+
const worldId = resolveTurnWorldId(event);
|
|
1799
|
+
const sessionLookupKey = buildSessionWorldCacheKey(runtimeAccountId, sessionId);
|
|
1800
|
+
const resolvedWorldId = worldId || (sessionLookupKey ? worldCache?.get(sessionLookupKey) : null);
|
|
1801
|
+
if (sessionLookupKey && resolvedWorldId && worldCache) {
|
|
1802
|
+
worldCache.set(sessionLookupKey, resolvedWorldId);
|
|
1803
|
+
}
|
|
1804
|
+
const route = runtime.channel.routing.resolveAgentRoute({
|
|
1805
|
+
cfg: currentCfg,
|
|
1806
|
+
channel: 'claworld',
|
|
1807
|
+
accountId: runtimeConfig.accountId,
|
|
1808
|
+
peer: { kind: 'direct', id: routePeerId },
|
|
1809
|
+
}) || {};
|
|
1810
|
+
const routed = inbound?.routeInboundEvent?.(event?.inboundEnvelope || {}, {
|
|
1811
|
+
sessionTarget: runtimeConfig.routing?.sessionTarget,
|
|
1812
|
+
fallbackTarget: runtimeConfig.routing?.fallbackTarget,
|
|
1813
|
+
route,
|
|
1814
|
+
}) || null;
|
|
1815
|
+
|
|
1816
|
+
const sessionKey = route.sessionKey || `agent:${route.agentId || runtimeConfig.accountId}:main`;
|
|
1817
|
+
const worldSessionKey = buildWorldAwareSessionKey(sessionKey, resolvedWorldId);
|
|
1818
|
+
const commandAuthorized = shouldAuthorizeBridgedCommand({
|
|
1819
|
+
runtimeConfig,
|
|
1820
|
+
relayEvent,
|
|
1821
|
+
incomingText: bridgedCommandText,
|
|
1822
|
+
});
|
|
1823
|
+
const { Body, RawBody, CommandBody, ContextLines } = buildRelayInboundEnvelope({
|
|
1824
|
+
runtime,
|
|
1825
|
+
currentCfg,
|
|
1826
|
+
runtimeAccountId,
|
|
1827
|
+
remoteIdentity: routePeerId,
|
|
1828
|
+
incomingText,
|
|
1829
|
+
contextText: kickoffContextText,
|
|
1830
|
+
commandText: bridgedCommandText,
|
|
1831
|
+
sessionId,
|
|
1832
|
+
turnId,
|
|
1833
|
+
relayEvent,
|
|
1834
|
+
mode: resolvedWorldId ? 'world' : 'direct',
|
|
1835
|
+
worldId: resolvedWorldId,
|
|
1836
|
+
conversationKey,
|
|
1837
|
+
});
|
|
1838
|
+
const inboundCtx = runtime.channel.reply.finalizeInboundContext({
|
|
1839
|
+
Body,
|
|
1840
|
+
RawBody,
|
|
1841
|
+
CommandBody,
|
|
1842
|
+
BodyForAgent: RawBody,
|
|
1843
|
+
BodyForCommands: CommandBody,
|
|
1844
|
+
From: routePeerId ? `claworld:${routePeerId}` : `claworld:${senderId}`,
|
|
1845
|
+
To: localAddress ? `claworld:${localAddress}` : `claworld:${runtimeConfig.accountId}`,
|
|
1846
|
+
SessionKey: worldSessionKey,
|
|
1847
|
+
AccountId: runtimeConfig.accountId,
|
|
1848
|
+
OriginatingChannel: 'claworld',
|
|
1849
|
+
OriginatingFrom: routePeerId || senderId,
|
|
1850
|
+
// Match OpenClaw DM channel semantics: `To` is the local account surface,
|
|
1851
|
+
// while `OriginatingTo` is the remote peer to reply back to.
|
|
1852
|
+
OriginatingTo: routePeerId || localAddress || runtimeConfig.accountId,
|
|
1853
|
+
ChatType: 'direct',
|
|
1854
|
+
SenderName: routePeerId || senderId,
|
|
1855
|
+
SenderId: routePeerId || senderId,
|
|
1856
|
+
MessageId: turnId,
|
|
1857
|
+
Provider: 'claworld',
|
|
1858
|
+
Surface: 'claworld',
|
|
1859
|
+
ConversationLabel: routePeerId || senderId,
|
|
1860
|
+
Timestamp: Date.now(),
|
|
1861
|
+
MessageSid: turnId,
|
|
1862
|
+
WasMentioned: false,
|
|
1863
|
+
CommandAuthorized: commandAuthorized,
|
|
1864
|
+
RelaySessionId: sessionId,
|
|
1865
|
+
RelayTurnId: turnId,
|
|
1866
|
+
RelayFromAgentId: fromAgentId,
|
|
1867
|
+
UntrustedContext: ContextLines,
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
enqueueBackendGuidance({
|
|
1871
|
+
runtime,
|
|
1872
|
+
sessionKey: worldSessionKey,
|
|
1873
|
+
payload: turnData,
|
|
1874
|
+
});
|
|
1875
|
+
|
|
1876
|
+
logger.info?.(`[claworld:${runtimeAccountId}] bridging ${relayEvent} into agent session`, {
|
|
1877
|
+
sessionId,
|
|
1878
|
+
turnId,
|
|
1879
|
+
routePeerId,
|
|
1880
|
+
routeSessionKey: worldSessionKey,
|
|
1881
|
+
routeStatus: routed?.status || null,
|
|
1882
|
+
bodyPreview: String(Body || '').slice(0, 240),
|
|
1883
|
+
rawBodyPreview: String(RawBody || '').slice(0, 240),
|
|
1884
|
+
inboundAccountId: inboundCtx?.AccountId || null,
|
|
1885
|
+
inboundTo: inboundCtx?.To || null,
|
|
1886
|
+
inboundFrom: inboundCtx?.From || null,
|
|
1887
|
+
originatingTo: inboundCtx?.OriginatingTo || null,
|
|
1888
|
+
originatingFrom: inboundCtx?.OriginatingFrom || null,
|
|
1889
|
+
commandAuthorized,
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
const { dispatcher, replyOptions, markDispatchIdle, didReply, getRuntimeOutputSummary } = createRelayReplyDispatcher({
|
|
1893
|
+
runtime,
|
|
1894
|
+
currentCfg,
|
|
1895
|
+
route,
|
|
1896
|
+
relayClient,
|
|
1897
|
+
sessionId,
|
|
1898
|
+
turnId,
|
|
1899
|
+
relayEvent,
|
|
1900
|
+
localRelayAgentId,
|
|
1901
|
+
allowRelayContinuation: resolveContinuationState(turnData).allowed !== false,
|
|
1902
|
+
logger,
|
|
1903
|
+
runtimeAccountId,
|
|
1904
|
+
});
|
|
1905
|
+
const dispatchResult = await runtime.channel.reply.dispatchReplyFromConfig({
|
|
1906
|
+
ctx: inboundCtx,
|
|
1907
|
+
cfg: currentCfg,
|
|
1908
|
+
dispatcher,
|
|
1909
|
+
replyOptions,
|
|
1910
|
+
});
|
|
1911
|
+
await markDispatchIdle();
|
|
1912
|
+
const replied = didReply();
|
|
1913
|
+
const runtimeOutputSummary = getRuntimeOutputSummary();
|
|
1914
|
+
|
|
1915
|
+
logger.info?.(`[claworld:${runtimeAccountId}] relay message bridge completed`, {
|
|
1916
|
+
sessionId,
|
|
1917
|
+
turnId,
|
|
1918
|
+
relayEvent,
|
|
1919
|
+
queuedFinal: Boolean(dispatchResult?.queuedFinal),
|
|
1920
|
+
replied,
|
|
1921
|
+
routeSessionKey: worldSessionKey,
|
|
1922
|
+
routeStatus: routed?.status || null,
|
|
1923
|
+
runtimeOutputSummary,
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
return {
|
|
1927
|
+
skipped: false,
|
|
1928
|
+
ok: true,
|
|
1929
|
+
replied,
|
|
1930
|
+
queuedFinal: Boolean(dispatchResult?.queuedFinal),
|
|
1931
|
+
routeSessionKey: worldSessionKey,
|
|
1932
|
+
routeStatus: routed?.status || null,
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
export function createClaworldChannelPlugin({
|
|
1937
|
+
logger = console,
|
|
1938
|
+
relayClientFactory = createClaworldRelayClient,
|
|
1939
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1940
|
+
} = {}) {
|
|
1941
|
+
const protocol = createRelayEventProtocol();
|
|
1942
|
+
const inbound = createInboundSessionRouter();
|
|
1943
|
+
const outbound = createOutboundSessionBridge();
|
|
1944
|
+
const results = createCanonicalResultBuilder();
|
|
1945
|
+
const demo = createDemoSessionBootstrap();
|
|
1946
|
+
const relayClients = new Map();
|
|
1947
|
+
const lifecycles = new Map();
|
|
1948
|
+
const accountRuntimeContexts = new Map();
|
|
1949
|
+
const sessionIdToWorldId = new Map();
|
|
1950
|
+
|
|
1951
|
+
async function persistRuntimeAppToken({ runtime, accountId, appToken }) {
|
|
1952
|
+
if (!runtime?.config?.loadConfig || !runtime?.config?.writeConfigFile) {
|
|
1953
|
+
return { skipped: true, reason: 'missing_runtime_config_io' };
|
|
1954
|
+
}
|
|
1955
|
+
if (!accountId || !appToken) {
|
|
1956
|
+
return { skipped: true, reason: 'missing_account_or_token' };
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
const currentCfg = await runtime.config.loadConfig();
|
|
1960
|
+
const nextCfg = JSON.parse(JSON.stringify(currentCfg || {}));
|
|
1961
|
+
nextCfg.channels = nextCfg.channels && typeof nextCfg.channels === 'object' && !Array.isArray(nextCfg.channels)
|
|
1962
|
+
? nextCfg.channels
|
|
1963
|
+
: {};
|
|
1964
|
+
const claworldRoot = nextCfg.channels.claworld && typeof nextCfg.channels.claworld === 'object' && !Array.isArray(nextCfg.channels.claworld)
|
|
1965
|
+
? nextCfg.channels.claworld
|
|
1966
|
+
: {};
|
|
1967
|
+
const accounts = claworldRoot.accounts && typeof claworldRoot.accounts === 'object' && !Array.isArray(claworldRoot.accounts)
|
|
1968
|
+
? claworldRoot.accounts
|
|
1969
|
+
: {};
|
|
1970
|
+
const account = accounts[accountId] && typeof accounts[accountId] === 'object' && !Array.isArray(accounts[accountId])
|
|
1971
|
+
? accounts[accountId]
|
|
1972
|
+
: {};
|
|
1973
|
+
|
|
1974
|
+
if (normalizeClaworldText(account.appToken, null) === appToken) {
|
|
1975
|
+
return { skipped: true, reason: 'already_persisted' };
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
accounts[accountId] = {
|
|
1979
|
+
...account,
|
|
1980
|
+
appToken,
|
|
1981
|
+
};
|
|
1982
|
+
claworldRoot.accounts = accounts;
|
|
1983
|
+
nextCfg.channels.claworld = claworldRoot;
|
|
1984
|
+
await runtime.config.writeConfigFile(nextCfg);
|
|
1985
|
+
return { skipped: false, ok: true };
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
async function resolveBoundRuntimeContext(context = {}) {
|
|
1989
|
+
const cfg = context.cfg || {};
|
|
1990
|
+
const accountId = context.accountId || null;
|
|
1991
|
+
const runtimeContext = accountRuntimeContexts.get(accountId || 'default') || null;
|
|
1992
|
+
let runtimeConfig = runtimeContext?.runtimeConfig || context.runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
|
|
1993
|
+
if (runtimeContext?.runtimeConfig && !runtimeContext?.deferredFailure) {
|
|
1994
|
+
return {
|
|
1995
|
+
...context,
|
|
1996
|
+
cfg: runtimeContext.cfg || cfg,
|
|
1997
|
+
accountId: runtimeConfig.accountId || accountId || null,
|
|
1998
|
+
runtimeConfig,
|
|
1999
|
+
agentId: context.agentId || runtimeConfig.relay?.agentId || null,
|
|
2000
|
+
bindingSource: 'runtime_context',
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
const binding = await ensureRelayBinding({ runtimeConfig, fetchImpl, logger });
|
|
2004
|
+
runtimeConfig = binding.runtimeConfig;
|
|
2005
|
+
return {
|
|
2006
|
+
...context,
|
|
2007
|
+
cfg,
|
|
2008
|
+
accountId: runtimeConfig.accountId || accountId || null,
|
|
2009
|
+
runtimeConfig,
|
|
2010
|
+
agentId: context.agentId || runtimeConfig.relay?.agentId || null,
|
|
2011
|
+
bindingSource: binding.bindingSource,
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function clearSessionWorldCache(runtimeAccountId = 'default') {
|
|
2016
|
+
const accountId = resolveNormalizedText(runtimeAccountId, 'default');
|
|
2017
|
+
const prefix = `${accountId}:`;
|
|
2018
|
+
Array.from(sessionIdToWorldId.keys()).forEach((cacheKey) => {
|
|
2019
|
+
if (cacheKey.startsWith(prefix)) {
|
|
2020
|
+
sessionIdToWorldId.delete(cacheKey);
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function getAccountLifecycle(accountKey = 'default') {
|
|
2026
|
+
if (lifecycles.has(accountKey)) return lifecycles.get(accountKey);
|
|
2027
|
+
|
|
2028
|
+
const lifecycle = createClaworldLifecycleManager({
|
|
2029
|
+
logger,
|
|
2030
|
+
connect: async (context = {}) => {
|
|
2031
|
+
const runtimeAccountId = String(context.accountId || context.account?.accountId || accountKey);
|
|
2032
|
+
|
|
2033
|
+
logger.info?.(`[claworld:${runtimeAccountId}] startAccount invoked`, {
|
|
2034
|
+
accountId: context.accountId || null,
|
|
2035
|
+
hasAbortSignal: Boolean(context.abortSignal),
|
|
2036
|
+
hasAccount: Boolean(context.account),
|
|
2037
|
+
hasConfig: Boolean(context.config),
|
|
2038
|
+
hasCfg: Boolean(context.cfg),
|
|
2039
|
+
accountShape: summarizeObjectShape(context.account),
|
|
2040
|
+
configShape: summarizeObjectShape(context.config),
|
|
2041
|
+
cfgShape: summarizeObjectShape(context.cfg),
|
|
2042
|
+
});
|
|
2043
|
+
|
|
2044
|
+
const { sourceType, configSource, runtimeConfig: initialRuntimeConfig } = resolveRuntimeConfigSource(context);
|
|
2045
|
+
let runtimeConfig = initialRuntimeConfig;
|
|
2046
|
+
logger.info?.(`[claworld:${runtimeAccountId}] resolved runtime config source`, {
|
|
2047
|
+
sourceType,
|
|
2048
|
+
configSourceShape: summarizeObjectShape(configSource),
|
|
2049
|
+
runtimeConfigShape: summarizeObjectShape(runtimeConfig),
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
const validation = validateClaworldChannelConfig(configSource, context.accountId);
|
|
2053
|
+
if (!validation.ok && sourceType !== 'root_cfg') {
|
|
2054
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] non-root runtime source would not validate as full cfg`, {
|
|
2055
|
+
sourceType,
|
|
2056
|
+
errors: validation.errors,
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
let binding;
|
|
2061
|
+
try {
|
|
2062
|
+
binding = await ensureRelayBinding({ runtimeConfig, fetchImpl, logger });
|
|
2063
|
+
} catch (error) {
|
|
2064
|
+
const normalized = normalizeRuntimeBoundaryError(error, {
|
|
2065
|
+
code: 'claworld_relay_binding_failed',
|
|
2066
|
+
category: 'bootstrap',
|
|
2067
|
+
message: 'claworld relay binding bootstrap failed',
|
|
2068
|
+
publicMessage: 'claworld relay binding bootstrap failed',
|
|
2069
|
+
recoverable: true,
|
|
2070
|
+
context: {
|
|
2071
|
+
accountId: runtimeAccountId,
|
|
2072
|
+
stage: 'ensureRelayBinding',
|
|
2073
|
+
},
|
|
2074
|
+
});
|
|
2075
|
+
if (!isNonRecoverableBootstrapHoldError(normalized)) {
|
|
2076
|
+
throw normalized;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
const { runtime: deferredRuntime } = resolvePluginRuntimeCandidate(context.runtime || null);
|
|
2080
|
+
const deferredFailure = serializeRuntimeBoundaryError(normalized);
|
|
2081
|
+
const holdMessage = buildBootstrapHoldMessage(normalized, runtimeConfig);
|
|
2082
|
+
accountRuntimeContexts.set(accountKey, {
|
|
2083
|
+
runtime: deferredRuntime,
|
|
2084
|
+
cfg: context.cfg || null,
|
|
2085
|
+
runtimeConfig,
|
|
2086
|
+
deferredFailure,
|
|
2087
|
+
deferredErrorMessage: holdMessage,
|
|
2088
|
+
});
|
|
2089
|
+
context.setStatus?.({
|
|
2090
|
+
accountId: runtimeAccountId,
|
|
2091
|
+
configured: false,
|
|
2092
|
+
connected: false,
|
|
2093
|
+
running: false,
|
|
2094
|
+
restartPending: false,
|
|
2095
|
+
lastError: holdMessage,
|
|
2096
|
+
});
|
|
2097
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] relay binding requires operator setup; holding runtime`, {
|
|
2098
|
+
code: normalized.code,
|
|
2099
|
+
category: normalized.category,
|
|
2100
|
+
status: normalized.status,
|
|
2101
|
+
requestedAgentCode: normalized.context?.requestedAgentCode || null,
|
|
2102
|
+
});
|
|
2103
|
+
return {
|
|
2104
|
+
startedDeferred: true,
|
|
2105
|
+
reason: 'bootstrap_setup_required',
|
|
2106
|
+
runtimeConfig,
|
|
2107
|
+
deferredFailure,
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
runtimeConfig = binding.runtimeConfig;
|
|
2111
|
+
logger.info?.(`[claworld:${runtimeAccountId}] relay binding ready`, {
|
|
2112
|
+
bindingSource: binding.bindingSource,
|
|
2113
|
+
relayAgentId: runtimeConfig.relay?.agentId || null,
|
|
2114
|
+
hasAppToken: Boolean(resolveRuntimeAppToken(runtimeConfig)),
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
const relayAgentId = context.agentId || runtimeConfig.relay?.agentId || null;
|
|
2118
|
+
const relayCredential = context.credential || (resolveRuntimeAppToken(runtimeConfig)
|
|
2119
|
+
? { type: 'agent_token', token: resolveRuntimeAppToken(runtimeConfig) }
|
|
2120
|
+
: null);
|
|
2121
|
+
|
|
2122
|
+
if (!relayAgentId) {
|
|
2123
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] missing relay runtime context; deferring connect`);
|
|
2124
|
+
return { startedDeferred: true, reason: 'missing_runtime_context', runtimeConfig };
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
const runtimeResolution = resolvePluginRuntimeCandidate(context.runtime || null);
|
|
2128
|
+
const pluginRuntime = runtimeResolution.runtime;
|
|
2129
|
+
const runtimeSource = runtimeResolution.runtimeSource;
|
|
2130
|
+
|
|
2131
|
+
logger.info?.(`[claworld:${runtimeAccountId}] runtime surface resolved`, {
|
|
2132
|
+
runtimeSource,
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
if (binding.bindingSource !== 'configured_app_token') {
|
|
2136
|
+
try {
|
|
2137
|
+
const persisted = await persistRuntimeAppToken({
|
|
2138
|
+
runtime: pluginRuntime,
|
|
2139
|
+
accountId: runtimeConfig.accountId,
|
|
2140
|
+
appToken: resolveRuntimeAppToken(runtimeConfig),
|
|
2141
|
+
});
|
|
2142
|
+
if (!persisted.skipped) {
|
|
2143
|
+
logger.info?.(`[claworld:${runtimeAccountId}] persisted runtime appToken`, {
|
|
2144
|
+
accountId: runtimeConfig.accountId,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
} catch (error) {
|
|
2148
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] failed to persist runtime appToken`, {
|
|
2149
|
+
error: error?.message || String(error),
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
await syncChatRequestApprovalPolicy({
|
|
2155
|
+
runtimeConfig,
|
|
2156
|
+
fetchImpl,
|
|
2157
|
+
logger,
|
|
2158
|
+
});
|
|
2159
|
+
|
|
2160
|
+
accountRuntimeContexts.set(accountKey, {
|
|
2161
|
+
runtime: pluginRuntime,
|
|
2162
|
+
cfg: context.cfg || null,
|
|
2163
|
+
runtimeConfig,
|
|
2164
|
+
deferredFailure: null,
|
|
2165
|
+
deferredErrorMessage: null,
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
const relayClient = relayClientFactory({ logger, inbound, outbound, protocol });
|
|
2169
|
+
relayClients.set(accountKey, relayClient);
|
|
2170
|
+
|
|
2171
|
+
relayClient.on?.('close', (info = {}) => {
|
|
2172
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] relay websocket closed`, info);
|
|
2173
|
+
});
|
|
2174
|
+
relayClient.on?.('runtime_event', (event) => {
|
|
2175
|
+
logger.debug?.(`[claworld:${runtimeAccountId}] inbound relay event`, {
|
|
2176
|
+
relayEvent: event?.relayEvent,
|
|
2177
|
+
target: event?.route?.target || null,
|
|
2178
|
+
sessionId: event?.inboundEnvelope?.session_id || null,
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
if (event?.relayEvent === 'session.created') {
|
|
2182
|
+
const runtimeContext = accountRuntimeContexts.get(accountKey) || {};
|
|
2183
|
+
maybeInjectWorldSessionStartupContext({
|
|
2184
|
+
runtime: runtimeContext.runtime,
|
|
2185
|
+
cfg: runtimeContext.cfg,
|
|
2186
|
+
runtimeConfig,
|
|
2187
|
+
runtimeAccountId,
|
|
2188
|
+
event,
|
|
2189
|
+
logger,
|
|
2190
|
+
sessionIdToWorldId,
|
|
2191
|
+
}).catch((error) => {
|
|
2192
|
+
logger.error?.(`[claworld:${runtimeAccountId}] world startup injection exception`, {
|
|
2193
|
+
error: error?.message || String(error),
|
|
2194
|
+
});
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
if (event?.relayEvent === 'turn.deliver') {
|
|
2199
|
+
const runtimeContext = accountRuntimeContexts.get(accountKey) || {};
|
|
2200
|
+
maybeBridgeDeliveredTurn({
|
|
2201
|
+
relayClient,
|
|
2202
|
+
runtimeConfig,
|
|
2203
|
+
runtimeAccountId,
|
|
2204
|
+
event,
|
|
2205
|
+
logger,
|
|
2206
|
+
runtime: runtimeContext.runtime,
|
|
2207
|
+
cfg: runtimeContext.cfg,
|
|
2208
|
+
inbound,
|
|
2209
|
+
sessionIdToWorldId,
|
|
2210
|
+
}).catch((error) => {
|
|
2211
|
+
logger.error?.(`[claworld:${runtimeAccountId}] turn bridge exception`, {
|
|
2212
|
+
error: error?.message || String(error),
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
if (event?.relayEvent === 'turn.reply') {
|
|
2218
|
+
const runtimeContext = accountRuntimeContexts.get(accountKey) || {};
|
|
2219
|
+
maybeBridgeDeliveredTurn({
|
|
2220
|
+
relayClient,
|
|
2221
|
+
runtimeConfig,
|
|
2222
|
+
runtimeAccountId,
|
|
2223
|
+
event,
|
|
2224
|
+
logger,
|
|
2225
|
+
runtime: runtimeContext.runtime,
|
|
2226
|
+
cfg: runtimeContext.cfg,
|
|
2227
|
+
inbound,
|
|
2228
|
+
sessionIdToWorldId,
|
|
2229
|
+
}).catch((error) => {
|
|
2230
|
+
logger.error?.(`[claworld:${runtimeAccountId}] reply bridge exception`, {
|
|
2231
|
+
error: error?.message || String(error),
|
|
2232
|
+
});
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
if (context.autoConnect === false) {
|
|
2238
|
+
logger.info?.(`[claworld:${runtimeAccountId}] lifecycle started in deferred mode (autoConnect=false)`);
|
|
2239
|
+
return { startedDeferred: true, runtimeConfig, relayClient };
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
logger.info?.(`[claworld:${runtimeAccountId}] connecting relay websocket ...`);
|
|
2243
|
+
await relayClient.connect({
|
|
2244
|
+
config: runtimeConfig,
|
|
2245
|
+
agentId: relayAgentId,
|
|
2246
|
+
credential: relayCredential,
|
|
2247
|
+
clientVersion: context.clientVersion,
|
|
2248
|
+
sessionTarget: context.sessionTarget || runtimeConfig.routing?.sessionTarget,
|
|
2249
|
+
fallbackTarget: context.fallbackTarget || runtimeConfig.routing?.fallbackTarget,
|
|
2250
|
+
});
|
|
2251
|
+
logger.info?.(`[claworld:${runtimeAccountId}] auth ok`);
|
|
2252
|
+
return relayClient;
|
|
2253
|
+
},
|
|
2254
|
+
disconnect: async ({ reason }) => {
|
|
2255
|
+
const relayClient = relayClients.get(accountKey);
|
|
2256
|
+
if (relayClient) {
|
|
2257
|
+
await relayClient.close(reason);
|
|
2258
|
+
relayClients.delete(accountKey);
|
|
2259
|
+
}
|
|
2260
|
+
accountRuntimeContexts.delete(accountKey);
|
|
2261
|
+
clearSessionWorldCache(accountKey);
|
|
2262
|
+
},
|
|
2263
|
+
});
|
|
2264
|
+
|
|
2265
|
+
lifecycles.set(accountKey, lifecycle);
|
|
2266
|
+
return lifecycle;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
async function runAccountLifecycle(context = {}) {
|
|
2270
|
+
const accountKey = String(context.accountId || context.account?.accountId || 'default');
|
|
2271
|
+
const lifecycle = getAccountLifecycle(accountKey);
|
|
2272
|
+
const started = await lifecycle.start(context);
|
|
2273
|
+
const runtimeAccountId = String(
|
|
2274
|
+
context.accountId
|
|
2275
|
+
|| context.account?.accountId
|
|
2276
|
+
|| started?.connection?.runtimeConfig?.accountId
|
|
2277
|
+
|| accountKey
|
|
2278
|
+
);
|
|
2279
|
+
|
|
2280
|
+
if (!context.abortSignal) {
|
|
2281
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] no abortSignal; startAccount will behave as a short-lived start call`);
|
|
2282
|
+
return started;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
if (context.abortSignal.aborted) {
|
|
2286
|
+
logger.info?.(`[claworld:${runtimeAccountId}] abort already signaled before runtime entered steady state`);
|
|
2287
|
+
await lifecycle.stop('abort_before_run');
|
|
2288
|
+
return started;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
const startedDeferred = Boolean(started?.connection?.startedDeferred);
|
|
2292
|
+
logger.info?.(
|
|
2293
|
+
`[claworld:${runtimeAccountId}] account runtime started; waiting for ${
|
|
2294
|
+
startedDeferred ? 'abort while setup is deferred' : 'abort'
|
|
2295
|
+
}`,
|
|
2296
|
+
);
|
|
2297
|
+
const stopReason = startedDeferred
|
|
2298
|
+
? await waitForAbort(context.abortSignal)
|
|
2299
|
+
: await Promise.race([
|
|
2300
|
+
waitForAbort(context.abortSignal),
|
|
2301
|
+
waitForRelayClientClose(started?.connection),
|
|
2302
|
+
]);
|
|
2303
|
+
|
|
2304
|
+
if (stopReason?.reason === 'abort_signal') {
|
|
2305
|
+
logger.info?.(`[claworld:${runtimeAccountId}] abort signal received, stopping`);
|
|
2306
|
+
await lifecycle.stop('abort_signal');
|
|
2307
|
+
return started;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
logger.warn?.(`[claworld:${runtimeAccountId}] account runtime ended before abort`, stopReason);
|
|
2311
|
+
await lifecycle.stop(stopReason?.reason || 'runtime_stopped');
|
|
2312
|
+
return started;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
function createStatusSnapshot() {
|
|
2316
|
+
const configuredAccounts = new Set([
|
|
2317
|
+
...listClaworldAccountIds({ channels: { claworld: { accounts: {} } } }),
|
|
2318
|
+
...Array.from(accountRuntimeContexts.keys()),
|
|
2319
|
+
...Array.from(relayClients.keys()),
|
|
2320
|
+
...Array.from(lifecycles.keys()),
|
|
2321
|
+
]);
|
|
2322
|
+
|
|
2323
|
+
const accountSnapshots = Object.fromEntries(
|
|
2324
|
+
Array.from(configuredAccounts).map((accountId) => {
|
|
2325
|
+
const lifecycle = lifecycles.get(accountId)?.snapshot?.() || null;
|
|
2326
|
+
const relayClient = relayClients.get(accountId)?.snapshot?.() || null;
|
|
2327
|
+
const runtimeContext = accountRuntimeContexts.get(accountId) || null;
|
|
2328
|
+
const deferredFailure = runtimeContext?.deferredFailure || null;
|
|
2329
|
+
const connected = Boolean(relayClient && relayClient.connectionState === 'authenticated');
|
|
2330
|
+
const lastFailure = lifecycle?.lastStartFailure || deferredFailure || null;
|
|
2331
|
+
const lastError = runtimeContext?.deferredErrorMessage || lifecycle?.lastStartError || deferredFailure?.message || null;
|
|
2332
|
+
const setupBlocked = isNonRecoverableBootstrapHoldError(lastFailure);
|
|
2333
|
+
const degraded = Boolean(lastError) || Boolean(relayClient && relayClient.connectionState === 'error');
|
|
2334
|
+
return [accountId, {
|
|
2335
|
+
configured: !setupBlocked,
|
|
2336
|
+
enabled: true,
|
|
2337
|
+
connected,
|
|
2338
|
+
degraded,
|
|
2339
|
+
hasRuntime: Boolean(runtimeContext?.runtime),
|
|
2340
|
+
hasCfg: Boolean(runtimeContext?.cfg),
|
|
2341
|
+
lifecycle,
|
|
2342
|
+
relayClient,
|
|
2343
|
+
lastError,
|
|
2344
|
+
lastFailure,
|
|
2345
|
+
}];
|
|
2346
|
+
}),
|
|
2347
|
+
);
|
|
2348
|
+
|
|
2349
|
+
return {
|
|
2350
|
+
ok: true,
|
|
2351
|
+
pluginId: 'claworld',
|
|
2352
|
+
version: '0.3.0',
|
|
2353
|
+
defaultAccountId: null,
|
|
2354
|
+
accounts: accountSnapshots,
|
|
2355
|
+
relayClients: Object.fromEntries(
|
|
2356
|
+
Array.from(relayClients.entries()).map(([accountId, client]) => [accountId, client?.snapshot?.() || null]),
|
|
2357
|
+
),
|
|
2358
|
+
lifecycles: Object.fromEntries(
|
|
2359
|
+
Array.from(lifecycles.entries()).map(([accountId, lifecycle]) => [accountId, lifecycle.snapshot()]),
|
|
2360
|
+
),
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
return {
|
|
2365
|
+
id: 'claworld',
|
|
2366
|
+
meta: {
|
|
2367
|
+
id: 'claworld',
|
|
2368
|
+
label: 'Claworld',
|
|
2369
|
+
selectionLabel: 'Claworld Persona Relay',
|
|
2370
|
+
detailLabel: 'Claworld A2A Relay Channel',
|
|
2371
|
+
docsPath: '/channels/claworld',
|
|
2372
|
+
docsLabel: 'claworld',
|
|
2373
|
+
blurb: 'Persona/Claworld relay channel backed by persona-backend.',
|
|
2374
|
+
version: '0.3.0',
|
|
2375
|
+
forceAccountBinding: true,
|
|
2376
|
+
},
|
|
2377
|
+
onboarding: claworldOnboardingAdapter,
|
|
2378
|
+
capabilities: {
|
|
2379
|
+
chatTypes: ['direct'],
|
|
2380
|
+
polls: false,
|
|
2381
|
+
threads: false,
|
|
2382
|
+
reactions: false,
|
|
2383
|
+
media: false,
|
|
2384
|
+
edit: false,
|
|
2385
|
+
reply: true,
|
|
2386
|
+
},
|
|
2387
|
+
agentPrompt: {
|
|
2388
|
+
messageToolHints: () => [
|
|
2389
|
+
'- Claworld targets are canonical identity handles like `alice@robin`.',
|
|
2390
|
+
'- Omit `target` to keep replying inside the current A2A session when the runtime already inferred the peer.',
|
|
2391
|
+
'- Use explicit targets like `user:alice@robin` when you want to open a new relay session to another remote OpenClaw agent.',
|
|
2392
|
+
],
|
|
2393
|
+
},
|
|
2394
|
+
reload: { configPrefixes: ['channels.claworld'] },
|
|
2395
|
+
configSchema: {
|
|
2396
|
+
schema: claworldChannelConfigJsonSchema,
|
|
2397
|
+
},
|
|
2398
|
+
config: {
|
|
2399
|
+
schema: claworldChannelConfigSchema,
|
|
2400
|
+
validate: validateClaworldChannelConfig,
|
|
2401
|
+
listAccountIds: (cfg) => listClaworldAccountIds(cfg),
|
|
2402
|
+
defaultAccountId: (cfg) => defaultClaworldAccountId(cfg),
|
|
2403
|
+
inspectAccount: (cfg, accountId) => inspectClaworldChannelAccount(cfg, accountId),
|
|
2404
|
+
resolveAccount: (cfg, accountId) => resolveClaworldChannelAccount(cfg, accountId),
|
|
2405
|
+
resolveRuntimeConfig: (cfg, accountId) => resolveClaworldRuntimeConfig(cfg, accountId),
|
|
2406
|
+
isConfigured: (account, cfg) => {
|
|
2407
|
+
if (account?.configured != null) return Boolean(account.configured);
|
|
2408
|
+
return inspectClaworldChannelAccount(account || cfg || {}).configured;
|
|
2409
|
+
},
|
|
2410
|
+
describeAccount: (account, cfg) => {
|
|
2411
|
+
if (account?.configured != null) return account;
|
|
2412
|
+
return inspectClaworldChannelAccount(account || cfg || {});
|
|
2413
|
+
},
|
|
2414
|
+
},
|
|
2415
|
+
setup: claworldSetupAdapter,
|
|
2416
|
+
messaging: {
|
|
2417
|
+
normalizeTarget: (raw) => normalizeClaworldTarget(raw) ?? undefined,
|
|
2418
|
+
targetResolver: {
|
|
2419
|
+
looksLikeId: (raw, normalized) => {
|
|
2420
|
+
const value = String(normalized || raw || '').trim();
|
|
2421
|
+
if (!value) return false;
|
|
2422
|
+
if (value.includes('@')) return RELAY_CANONICAL_AGENT_CODE_PATTERN.test(value);
|
|
2423
|
+
return /^[a-z0-9._:+~-]+$/i.test(value) || /^agt_[a-z0-9_-]+$/i.test(value);
|
|
2424
|
+
},
|
|
2425
|
+
hint: '<agent@relay-domain|agentId>',
|
|
2426
|
+
},
|
|
2427
|
+
},
|
|
2428
|
+
directory: {
|
|
2429
|
+
self: async ({ cfg, accountId } = {}) => {
|
|
2430
|
+
const account = inspectClaworldChannelAccount(cfg || {}, accountId || null);
|
|
2431
|
+
const address = resolveRelayAddress(account?.accountId || '', account || {});
|
|
2432
|
+
if (!account?.configured || !address) return null;
|
|
2433
|
+
return {
|
|
2434
|
+
id: address,
|
|
2435
|
+
name: account.accountId,
|
|
2436
|
+
handle: account.accountId,
|
|
2437
|
+
};
|
|
2438
|
+
},
|
|
2439
|
+
listPeers: async ({ cfg, accountId } = {}) => buildClaworldDirectoryEntries(cfg || {}, accountId || null),
|
|
2440
|
+
listGroups: async () => [],
|
|
2441
|
+
},
|
|
2442
|
+
status: {
|
|
2443
|
+
getSnapshot: () => createStatusSnapshot(),
|
|
2444
|
+
},
|
|
2445
|
+
gateway: {
|
|
2446
|
+
startAccount: async (context = {}) => {
|
|
2447
|
+
context.log?.info?.(`starting claworld[${context.accountId || context.account?.accountId || 'default'}]`);
|
|
2448
|
+
return runAccountLifecycle({
|
|
2449
|
+
...context,
|
|
2450
|
+
config: context.cfg || context.config || context.account || {},
|
|
2451
|
+
});
|
|
2452
|
+
},
|
|
2453
|
+
stopAccount: async (context = {}) => {
|
|
2454
|
+
const accountKey = String(context.accountId || context.account?.accountId || 'default');
|
|
2455
|
+
const lifecycle = getAccountLifecycle(accountKey);
|
|
2456
|
+
return lifecycle.stop('stop_account');
|
|
2457
|
+
},
|
|
2458
|
+
start: (context = {}) => runAccountLifecycle(context),
|
|
2459
|
+
stop: async (reasonOrContext = 'manual_stop') => {
|
|
2460
|
+
if (typeof reasonOrContext === 'object' && reasonOrContext !== null) {
|
|
2461
|
+
const accountKey = String(reasonOrContext.accountId || reasonOrContext.account?.accountId || 'default');
|
|
2462
|
+
return getAccountLifecycle(accountKey).stop(reasonOrContext.reason || 'manual_stop');
|
|
2463
|
+
}
|
|
2464
|
+
const entries = Array.from(lifecycles.entries());
|
|
2465
|
+
await Promise.all(entries.map(([, lifecycle]) => lifecycle.stop(reasonOrContext)));
|
|
2466
|
+
return { started: false, stopped: true, reason: reasonOrContext };
|
|
2467
|
+
},
|
|
2468
|
+
reconnect: async (context = {}) => {
|
|
2469
|
+
const accountKey = String(context.accountId || context.account?.accountId || 'default');
|
|
2470
|
+
return getAccountLifecycle(accountKey).reconnect(context);
|
|
2471
|
+
},
|
|
2472
|
+
snapshot: () => ({
|
|
2473
|
+
lifecycles: Object.fromEntries(Array.from(lifecycles.entries()).map(([accountId, lifecycle]) => [accountId, lifecycle.snapshot()])),
|
|
2474
|
+
relayClients: Object.fromEntries(Array.from(relayClients.entries()).map(([accountId, client]) => [accountId, client?.snapshot?.() || null])),
|
|
2475
|
+
}),
|
|
2476
|
+
},
|
|
2477
|
+
inbound: {
|
|
2478
|
+
routeRelayEvent: (event = {}, options = {}) => {
|
|
2479
|
+
const envelope = {
|
|
2480
|
+
session_id: event.session_id || event.sessionId || null,
|
|
2481
|
+
event_id: event.event_id || event.eventId || null,
|
|
2482
|
+
from_agent_id: event.from_agent_id || event.fromAgentId || null,
|
|
2483
|
+
type: event.type || 'message',
|
|
2484
|
+
payload: event.payload || {},
|
|
2485
|
+
};
|
|
2486
|
+
return inbound.routeInboundEvent(envelope, options);
|
|
2487
|
+
},
|
|
2488
|
+
},
|
|
2489
|
+
outbound: {
|
|
2490
|
+
deliveryMode: 'direct',
|
|
2491
|
+
createReplyEnvelope: (params = {}) => outbound.createReplyEnvelope(params),
|
|
2492
|
+
sendText: async (ctx = {}) => {
|
|
2493
|
+
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable for claworld outbound');
|
|
2494
|
+
const resolvedContext = await resolveBoundRuntimeContext(ctx);
|
|
2495
|
+
return deliverRelayMessage({
|
|
2496
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2497
|
+
to: ctx.to,
|
|
2498
|
+
text: ctx.text,
|
|
2499
|
+
fetchImpl,
|
|
2500
|
+
logger,
|
|
2501
|
+
outboundContext: ctx,
|
|
2502
|
+
});
|
|
2503
|
+
},
|
|
2504
|
+
},
|
|
2505
|
+
helpers: {
|
|
2506
|
+
resolveToolRuntimeContext: resolveBoundRuntimeContext,
|
|
2507
|
+
pairing: {
|
|
2508
|
+
ensureAgentPairing: async (context = {}) => ensureAgentPairing({
|
|
2509
|
+
runtimeConfig: context.runtimeConfig || resolveClaworldRuntimeConfig(context.cfg || {}, context.accountId || null),
|
|
2510
|
+
fetchImpl,
|
|
2511
|
+
logger,
|
|
2512
|
+
}),
|
|
2513
|
+
resolveAgentIdentity: async (context = {}) => resolveRelayAgentSummary({
|
|
2514
|
+
runtimeConfig: context.runtimeConfig || resolveClaworldRuntimeConfig(context.cfg || {}, context.accountId || null),
|
|
2515
|
+
fetchImpl,
|
|
2516
|
+
logger,
|
|
2517
|
+
agentId: context.agentId || context.targetAgentId || null,
|
|
2518
|
+
agentCode: context.agentCode || context.targetAgentCode || null,
|
|
2519
|
+
address: context.address || null,
|
|
2520
|
+
}),
|
|
2521
|
+
},
|
|
2522
|
+
social: {
|
|
2523
|
+
sendFriendRequest: async (context = {}) => {
|
|
2524
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2525
|
+
return createFriendRequest({
|
|
2526
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2527
|
+
fromAgentId: resolvedContext.agentId || null,
|
|
2528
|
+
toAddress: context.toAddress || null,
|
|
2529
|
+
message: context.message || null,
|
|
2530
|
+
metadata: context.metadata || {},
|
|
2531
|
+
fetchImpl,
|
|
2532
|
+
});
|
|
2533
|
+
},
|
|
2534
|
+
listFriendRequests: async (context = {}) => {
|
|
2535
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2536
|
+
return listFriendRequests({
|
|
2537
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2538
|
+
agentId: resolvedContext.agentId || null,
|
|
2539
|
+
direction: context.direction || null,
|
|
2540
|
+
status: context.status || null,
|
|
2541
|
+
fetchImpl,
|
|
2542
|
+
});
|
|
2543
|
+
},
|
|
2544
|
+
acceptFriendRequest: async (context = {}) => {
|
|
2545
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2546
|
+
return acceptFriendRequest({
|
|
2547
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2548
|
+
actorAgentId: resolvedContext.agentId || null,
|
|
2549
|
+
friendRequestId: context.friendRequestId || null,
|
|
2550
|
+
fetchImpl,
|
|
2551
|
+
});
|
|
2552
|
+
},
|
|
2553
|
+
rejectFriendRequest: async (context = {}) => {
|
|
2554
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2555
|
+
return rejectFriendRequest({
|
|
2556
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2557
|
+
actorAgentId: resolvedContext.agentId || null,
|
|
2558
|
+
friendRequestId: context.friendRequestId || null,
|
|
2559
|
+
fetchImpl,
|
|
2560
|
+
});
|
|
2561
|
+
},
|
|
2562
|
+
requestChat: async (context = {}) => {
|
|
2563
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2564
|
+
return createChatRequest({
|
|
2565
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2566
|
+
fromAgentId: resolvedContext.agentId || null,
|
|
2567
|
+
targetAgentId: context.targetAgentId || null,
|
|
2568
|
+
openingMessage: context.openingMessage || context.message || context.text || null,
|
|
2569
|
+
worldId: context.worldId || null,
|
|
2570
|
+
episodePolicy: context.episodePolicy || null,
|
|
2571
|
+
fetchImpl,
|
|
2572
|
+
});
|
|
2573
|
+
},
|
|
2574
|
+
listChatRequests: async (context = {}) => {
|
|
2575
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2576
|
+
return listChatRequests({
|
|
2577
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2578
|
+
agentId: resolvedContext.agentId || null,
|
|
2579
|
+
direction: context.direction || null,
|
|
2580
|
+
fetchImpl,
|
|
2581
|
+
});
|
|
2582
|
+
},
|
|
2583
|
+
acceptChatRequest: async (context = {}) => {
|
|
2584
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2585
|
+
return acceptChatRequest({
|
|
2586
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2587
|
+
actorAgentId: resolvedContext.agentId || null,
|
|
2588
|
+
chatRequestId: context.chatRequestId || null,
|
|
2589
|
+
fetchImpl,
|
|
2590
|
+
});
|
|
2591
|
+
},
|
|
2592
|
+
},
|
|
2593
|
+
postSetup: {
|
|
2594
|
+
fetchWorldDirectory: async (context = {}) => {
|
|
2595
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2596
|
+
return fetchPostSetupWorldDirectory({
|
|
2597
|
+
cfg: resolvedContext.cfg || {},
|
|
2598
|
+
accountId: resolvedContext.accountId || null,
|
|
2599
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2600
|
+
limit: context.limit ?? null,
|
|
2601
|
+
sort: context.sort || null,
|
|
2602
|
+
page: context.page ?? null,
|
|
2603
|
+
fetchImpl,
|
|
2604
|
+
logger,
|
|
2605
|
+
});
|
|
2606
|
+
},
|
|
2607
|
+
fetchWorldDetail: async (context = {}) => {
|
|
2608
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2609
|
+
return fetchWorldDetail({
|
|
2610
|
+
cfg: resolvedContext.cfg || {},
|
|
2611
|
+
accountId: resolvedContext.accountId || null,
|
|
2612
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2613
|
+
worldId: context.worldId || null,
|
|
2614
|
+
fetchImpl,
|
|
2615
|
+
logger,
|
|
2616
|
+
});
|
|
2617
|
+
},
|
|
2618
|
+
fetchWorldJoinCheck: async (context = {}) => {
|
|
2619
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2620
|
+
return fetchWorldJoinCheck({
|
|
2621
|
+
cfg: resolvedContext.cfg || {},
|
|
2622
|
+
accountId: resolvedContext.accountId || null,
|
|
2623
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2624
|
+
worldId: context.worldId || null,
|
|
2625
|
+
profile: context.profile || {},
|
|
2626
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? null,
|
|
2627
|
+
fetchImpl,
|
|
2628
|
+
logger,
|
|
2629
|
+
});
|
|
2630
|
+
},
|
|
2631
|
+
joinWorld: async (context = {}) => {
|
|
2632
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2633
|
+
return joinWorld({
|
|
2634
|
+
cfg: resolvedContext.cfg || {},
|
|
2635
|
+
accountId: resolvedContext.accountId || null,
|
|
2636
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2637
|
+
worldId: context.worldId || null,
|
|
2638
|
+
agentId: resolvedContext.agentId || null,
|
|
2639
|
+
profile: context.profile || {},
|
|
2640
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2641
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? null,
|
|
2642
|
+
fetchImpl,
|
|
2643
|
+
logger,
|
|
2644
|
+
});
|
|
2645
|
+
},
|
|
2646
|
+
submitWorldJoin: async (context = {}) => {
|
|
2647
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2648
|
+
return submitWorldJoin({
|
|
2649
|
+
cfg: resolvedContext.cfg || {},
|
|
2650
|
+
accountId: resolvedContext.accountId || null,
|
|
2651
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2652
|
+
worldId: context.worldId || null,
|
|
2653
|
+
agentId: resolvedContext.agentId || null,
|
|
2654
|
+
profile: context.profile || {},
|
|
2655
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2656
|
+
profileUpdate: context.profileUpdate ?? context.profilePatch ?? null,
|
|
2657
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2658
|
+
fetchImpl,
|
|
2659
|
+
logger,
|
|
2660
|
+
});
|
|
2661
|
+
},
|
|
2662
|
+
fetchWorldCandidateFeed: async (context = {}) => {
|
|
2663
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2664
|
+
return fetchWorldCandidateFeed({
|
|
2665
|
+
cfg: resolvedContext.cfg || {},
|
|
2666
|
+
accountId: resolvedContext.accountId || null,
|
|
2667
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2668
|
+
worldId: context.worldId || null,
|
|
2669
|
+
agentId: resolvedContext.agentId || null,
|
|
2670
|
+
limit: context.limit ?? context.candidateLimit ?? null,
|
|
2671
|
+
fetchImpl,
|
|
2672
|
+
logger,
|
|
2673
|
+
});
|
|
2674
|
+
},
|
|
2675
|
+
fetchWorldSearch: async (context = {}) => {
|
|
2676
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2677
|
+
return fetchWorldSearch({
|
|
2678
|
+
cfg: resolvedContext.cfg || {},
|
|
2679
|
+
accountId: resolvedContext.accountId || null,
|
|
2680
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2681
|
+
worldId: context.worldId || null,
|
|
2682
|
+
agentId: resolvedContext.agentId || null,
|
|
2683
|
+
query: context.query || {},
|
|
2684
|
+
limit: context.limit ?? null,
|
|
2685
|
+
fetchImpl,
|
|
2686
|
+
logger,
|
|
2687
|
+
});
|
|
2688
|
+
},
|
|
2689
|
+
submitWorldSearch: async (context = {}) => {
|
|
2690
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2691
|
+
return submitWorldSearch({
|
|
2692
|
+
cfg: resolvedContext.cfg || {},
|
|
2693
|
+
accountId: resolvedContext.accountId || null,
|
|
2694
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2695
|
+
worldId: context.worldId || null,
|
|
2696
|
+
agentId: resolvedContext.agentId || null,
|
|
2697
|
+
query: context.query || {},
|
|
2698
|
+
limit: context.limit ?? null,
|
|
2699
|
+
fetchImpl,
|
|
2700
|
+
logger,
|
|
2701
|
+
});
|
|
2702
|
+
},
|
|
2703
|
+
resolveWorldSelection: (context = {}) => resolveWorldSelection(
|
|
2704
|
+
context.worldDirectory || {},
|
|
2705
|
+
context.selection ?? context.userChoice ?? null,
|
|
2706
|
+
),
|
|
2707
|
+
buildCandidateDeliverySummary,
|
|
2708
|
+
buildRequiredFieldExplanation,
|
|
2709
|
+
resolveWorldProfileCollectionFlow: async (context = {}) => {
|
|
2710
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2711
|
+
return resolveWorldProfileCollectionFlow({
|
|
2712
|
+
cfg: resolvedContext.cfg || {},
|
|
2713
|
+
accountId: resolvedContext.accountId || null,
|
|
2714
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2715
|
+
worldId: context.worldId || null,
|
|
2716
|
+
worldDetail: context.worldDetail || null,
|
|
2717
|
+
profile: context.profile || {},
|
|
2718
|
+
profileUpdate: context.profileUpdate ?? context.profilePatch ?? null,
|
|
2719
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2720
|
+
fetchImpl,
|
|
2721
|
+
logger,
|
|
2722
|
+
});
|
|
2723
|
+
},
|
|
2724
|
+
resolveWorldJoinFlow: async (context = {}) => {
|
|
2725
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2726
|
+
return resolveWorldJoinFlow({
|
|
2727
|
+
cfg: resolvedContext.cfg || {},
|
|
2728
|
+
accountId: resolvedContext.accountId || null,
|
|
2729
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2730
|
+
worldId: context.worldId || null,
|
|
2731
|
+
worldDetail: context.worldDetail || null,
|
|
2732
|
+
agentId: resolvedContext.agentId || null,
|
|
2733
|
+
profile: context.profile || {},
|
|
2734
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2735
|
+
limit: context.limit ?? context.candidateLimit ?? null,
|
|
2736
|
+
fetchImpl,
|
|
2737
|
+
logger,
|
|
2738
|
+
});
|
|
2739
|
+
},
|
|
2740
|
+
resolveWorldSelectionFlow: async (context = {}) => {
|
|
2741
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2742
|
+
return resolveWorldSelectionFlow({
|
|
2743
|
+
cfg: resolvedContext.cfg || {},
|
|
2744
|
+
accountId: resolvedContext.accountId || null,
|
|
2745
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2746
|
+
worldDirectory: context.worldDirectory || null,
|
|
2747
|
+
selection: context.selection ?? context.userChoice ?? null,
|
|
2748
|
+
profile: context.profile || {},
|
|
2749
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2750
|
+
fetchImpl,
|
|
2751
|
+
logger,
|
|
2752
|
+
});
|
|
2753
|
+
},
|
|
2754
|
+
},
|
|
2755
|
+
moderation: {
|
|
2756
|
+
createWorld: async (context = {}) => {
|
|
2757
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2758
|
+
return createModeratedWorld({
|
|
2759
|
+
cfg: resolvedContext.cfg || {},
|
|
2760
|
+
accountId: resolvedContext.accountId || null,
|
|
2761
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2762
|
+
agentId: resolvedContext.agentId || null,
|
|
2763
|
+
adminAgentIds: context.adminAgentIds || [],
|
|
2764
|
+
eligibility: context.eligibility || 'active',
|
|
2765
|
+
...(Object.prototype.hasOwnProperty.call(context, 'broadcast') ? { broadcast: context.broadcast } : {}),
|
|
2766
|
+
displayName: context.displayName || null,
|
|
2767
|
+
summary: context.summary || null,
|
|
2768
|
+
description: context.description || null,
|
|
2769
|
+
entryProfileSchema: context.entryProfileSchema || {},
|
|
2770
|
+
sessionTemplate: context.sessionTemplate || null,
|
|
2771
|
+
interactionRules: context.interactionRules || null,
|
|
2772
|
+
prohibitedRules: context.prohibitedRules || null,
|
|
2773
|
+
ratingRules: context.ratingRules || null,
|
|
2774
|
+
enabled: context.enabled === true,
|
|
2775
|
+
fetchImpl,
|
|
2776
|
+
logger,
|
|
2777
|
+
});
|
|
2778
|
+
},
|
|
2779
|
+
listOwnedWorlds: async (context = {}) => {
|
|
2780
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2781
|
+
return fetchOwnedWorlds({
|
|
2782
|
+
cfg: resolvedContext.cfg || {},
|
|
2783
|
+
accountId: resolvedContext.accountId || null,
|
|
2784
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2785
|
+
agentId: resolvedContext.agentId || null,
|
|
2786
|
+
includeDisabled: context.includeDisabled !== false,
|
|
2787
|
+
fetchImpl,
|
|
2788
|
+
logger,
|
|
2789
|
+
});
|
|
2790
|
+
},
|
|
2791
|
+
manageWorld: async (context = {}) => {
|
|
2792
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2793
|
+
return manageModeratedWorld({
|
|
2794
|
+
cfg: resolvedContext.cfg || {},
|
|
2795
|
+
accountId: resolvedContext.accountId || null,
|
|
2796
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2797
|
+
agentId: resolvedContext.agentId || null,
|
|
2798
|
+
worldId: context.worldId || null,
|
|
2799
|
+
mode: context.mode || 'get',
|
|
2800
|
+
changes: context.changes || null,
|
|
2801
|
+
enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
|
|
2802
|
+
fetchImpl,
|
|
2803
|
+
logger,
|
|
2804
|
+
});
|
|
2805
|
+
},
|
|
2806
|
+
},
|
|
2807
|
+
},
|
|
2808
|
+
runtime: {
|
|
2809
|
+
protocol,
|
|
2810
|
+
inbound,
|
|
2811
|
+
outbound,
|
|
2812
|
+
results,
|
|
2813
|
+
demo,
|
|
2814
|
+
productShell: {
|
|
2815
|
+
fetchWorldDirectory: async (context = {}) => {
|
|
2816
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2817
|
+
return fetchPostSetupWorldDirectory({
|
|
2818
|
+
cfg: resolvedContext.cfg || {},
|
|
2819
|
+
accountId: resolvedContext.accountId || null,
|
|
2820
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2821
|
+
limit: context.limit ?? null,
|
|
2822
|
+
sort: context.sort || null,
|
|
2823
|
+
page: context.page ?? null,
|
|
2824
|
+
fetchImpl,
|
|
2825
|
+
logger,
|
|
2826
|
+
});
|
|
2827
|
+
},
|
|
2828
|
+
buildWorldSelectionPrompt,
|
|
2829
|
+
fetchWorldDetail: async (context = {}) => {
|
|
2830
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2831
|
+
return fetchWorldDetail({
|
|
2832
|
+
cfg: resolvedContext.cfg || {},
|
|
2833
|
+
accountId: resolvedContext.accountId || null,
|
|
2834
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2835
|
+
worldId: context.worldId || null,
|
|
2836
|
+
fetchImpl,
|
|
2837
|
+
logger,
|
|
2838
|
+
});
|
|
2839
|
+
},
|
|
2840
|
+
fetchWorldJoinCheck: async (context = {}) => {
|
|
2841
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2842
|
+
return fetchWorldJoinCheck({
|
|
2843
|
+
cfg: resolvedContext.cfg || {},
|
|
2844
|
+
accountId: resolvedContext.accountId || null,
|
|
2845
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2846
|
+
worldId: context.worldId || null,
|
|
2847
|
+
profile: context.profile || {},
|
|
2848
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? null,
|
|
2849
|
+
fetchImpl,
|
|
2850
|
+
logger,
|
|
2851
|
+
});
|
|
2852
|
+
},
|
|
2853
|
+
joinWorld: async (context = {}) => {
|
|
2854
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2855
|
+
return joinWorld({
|
|
2856
|
+
cfg: resolvedContext.cfg || {},
|
|
2857
|
+
accountId: resolvedContext.accountId || null,
|
|
2858
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2859
|
+
worldId: context.worldId || null,
|
|
2860
|
+
agentId: resolvedContext.agentId || null,
|
|
2861
|
+
profile: context.profile || {},
|
|
2862
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2863
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? null,
|
|
2864
|
+
fetchImpl,
|
|
2865
|
+
logger,
|
|
2866
|
+
});
|
|
2867
|
+
},
|
|
2868
|
+
submitWorldJoin: async (context = {}) => {
|
|
2869
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2870
|
+
return submitWorldJoin({
|
|
2871
|
+
cfg: resolvedContext.cfg || {},
|
|
2872
|
+
accountId: resolvedContext.accountId || null,
|
|
2873
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2874
|
+
worldId: context.worldId || null,
|
|
2875
|
+
agentId: resolvedContext.agentId || null,
|
|
2876
|
+
profile: context.profile || {},
|
|
2877
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2878
|
+
profileUpdate: context.profileUpdate ?? context.profilePatch ?? null,
|
|
2879
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2880
|
+
fetchImpl,
|
|
2881
|
+
logger,
|
|
2882
|
+
});
|
|
2883
|
+
},
|
|
2884
|
+
fetchWorldCandidateFeed: async (context = {}) => {
|
|
2885
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2886
|
+
return fetchWorldCandidateFeed({
|
|
2887
|
+
cfg: resolvedContext.cfg || {},
|
|
2888
|
+
accountId: resolvedContext.accountId || null,
|
|
2889
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2890
|
+
worldId: context.worldId || null,
|
|
2891
|
+
agentId: resolvedContext.agentId || null,
|
|
2892
|
+
limit: context.limit ?? context.candidateLimit ?? null,
|
|
2893
|
+
fetchImpl,
|
|
2894
|
+
logger,
|
|
2895
|
+
});
|
|
2896
|
+
},
|
|
2897
|
+
fetchWorldSearch: async (context = {}) => {
|
|
2898
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2899
|
+
return fetchWorldSearch({
|
|
2900
|
+
cfg: resolvedContext.cfg || {},
|
|
2901
|
+
accountId: resolvedContext.accountId || null,
|
|
2902
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2903
|
+
worldId: context.worldId || null,
|
|
2904
|
+
agentId: resolvedContext.agentId || null,
|
|
2905
|
+
query: context.query || {},
|
|
2906
|
+
limit: context.limit ?? null,
|
|
2907
|
+
fetchImpl,
|
|
2908
|
+
logger,
|
|
2909
|
+
});
|
|
2910
|
+
},
|
|
2911
|
+
submitWorldSearch: async (context = {}) => {
|
|
2912
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2913
|
+
return submitWorldSearch({
|
|
2914
|
+
cfg: resolvedContext.cfg || {},
|
|
2915
|
+
accountId: resolvedContext.accountId || null,
|
|
2916
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2917
|
+
worldId: context.worldId || null,
|
|
2918
|
+
agentId: resolvedContext.agentId || null,
|
|
2919
|
+
query: context.query || {},
|
|
2920
|
+
limit: context.limit ?? null,
|
|
2921
|
+
fetchImpl,
|
|
2922
|
+
logger,
|
|
2923
|
+
});
|
|
2924
|
+
},
|
|
2925
|
+
broadcastWorld: async (context = {}) => {
|
|
2926
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2927
|
+
return broadcastWorld({
|
|
2928
|
+
cfg: resolvedContext.cfg || {},
|
|
2929
|
+
accountId: resolvedContext.accountId || null,
|
|
2930
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2931
|
+
worldId: context.worldId || null,
|
|
2932
|
+
agentId: resolvedContext.agentId || null,
|
|
2933
|
+
message: context.message || null,
|
|
2934
|
+
payload: context.payload || {},
|
|
2935
|
+
audience: context.audience || null,
|
|
2936
|
+
replyPolicy: context.replyPolicy || null,
|
|
2937
|
+
...(Object.prototype.hasOwnProperty.call(context, 'excludeSelf') ? { excludeSelf: context.excludeSelf } : {}),
|
|
2938
|
+
conversation: context.conversation || {},
|
|
2939
|
+
fetchImpl,
|
|
2940
|
+
logger,
|
|
2941
|
+
});
|
|
2942
|
+
},
|
|
2943
|
+
resolveWorldSelection,
|
|
2944
|
+
buildCandidateDeliverySummary,
|
|
2945
|
+
buildRequiredFieldExplanation,
|
|
2946
|
+
resolveWorldProfileCollectionFlow: async (context = {}) => {
|
|
2947
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2948
|
+
return resolveWorldProfileCollectionFlow({
|
|
2949
|
+
cfg: resolvedContext.cfg || {},
|
|
2950
|
+
accountId: resolvedContext.accountId || null,
|
|
2951
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2952
|
+
worldId: context.worldId || null,
|
|
2953
|
+
worldDetail: context.worldDetail || null,
|
|
2954
|
+
profile: context.profile || {},
|
|
2955
|
+
profileUpdate: context.profileUpdate ?? context.profilePatch ?? null,
|
|
2956
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2957
|
+
fetchImpl,
|
|
2958
|
+
logger,
|
|
2959
|
+
});
|
|
2960
|
+
},
|
|
2961
|
+
resolveWorldJoinFlow: async (context = {}) => {
|
|
2962
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2963
|
+
return resolveWorldJoinFlow({
|
|
2964
|
+
cfg: resolvedContext.cfg || {},
|
|
2965
|
+
accountId: resolvedContext.accountId || null,
|
|
2966
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2967
|
+
worldId: context.worldId || null,
|
|
2968
|
+
worldDetail: context.worldDetail || null,
|
|
2969
|
+
agentId: resolvedContext.agentId || null,
|
|
2970
|
+
profile: context.profile || {},
|
|
2971
|
+
profileSnapshot: context.profileSnapshot ?? null,
|
|
2972
|
+
limit: context.limit ?? context.candidateLimit ?? null,
|
|
2973
|
+
fetchImpl,
|
|
2974
|
+
logger,
|
|
2975
|
+
});
|
|
2976
|
+
},
|
|
2977
|
+
resolveWorldSelectionFlow: async (context = {}) => {
|
|
2978
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2979
|
+
return resolveWorldSelectionFlow({
|
|
2980
|
+
cfg: resolvedContext.cfg || {},
|
|
2981
|
+
accountId: resolvedContext.accountId || null,
|
|
2982
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2983
|
+
worldDirectory: context.worldDirectory || null,
|
|
2984
|
+
selection: context.selection ?? context.userChoice ?? null,
|
|
2985
|
+
profile: context.profile || {},
|
|
2986
|
+
maxFieldsPerStep: context.maxFieldsPerStep ?? context.maxPromptFields ?? 1,
|
|
2987
|
+
fetchImpl,
|
|
2988
|
+
logger,
|
|
2989
|
+
});
|
|
2990
|
+
},
|
|
2991
|
+
feedback: {
|
|
2992
|
+
submitFeedback: async (context = {}) => {
|
|
2993
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2994
|
+
return submitFeedbackReport({
|
|
2995
|
+
cfg: resolvedContext.cfg || {},
|
|
2996
|
+
accountId: resolvedContext.accountId || null,
|
|
2997
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
2998
|
+
agentId: resolvedContext.agentId || null,
|
|
2999
|
+
category: context.category || null,
|
|
3000
|
+
title: context.title || null,
|
|
3001
|
+
goal: context.goal || null,
|
|
3002
|
+
actualBehavior: context.actualBehavior || null,
|
|
3003
|
+
expectedBehavior: context.expectedBehavior || null,
|
|
3004
|
+
impact: context.impact || null,
|
|
3005
|
+
details: context.details || null,
|
|
3006
|
+
reproductionSteps: context.reproductionSteps || [],
|
|
3007
|
+
context: context.context || {},
|
|
3008
|
+
fetchImpl,
|
|
3009
|
+
logger,
|
|
3010
|
+
toolCallId: context.toolCallId || null,
|
|
3011
|
+
pluginVersion: context.pluginVersion || null,
|
|
3012
|
+
toolContractVersion: context.toolContractVersion || null,
|
|
3013
|
+
});
|
|
3014
|
+
},
|
|
3015
|
+
},
|
|
3016
|
+
moderation: {
|
|
3017
|
+
createWorld: async (context = {}) => {
|
|
3018
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
3019
|
+
return createModeratedWorld({
|
|
3020
|
+
cfg: resolvedContext.cfg || {},
|
|
3021
|
+
accountId: resolvedContext.accountId || null,
|
|
3022
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
3023
|
+
agentId: resolvedContext.agentId || null,
|
|
3024
|
+
adminAgentIds: context.adminAgentIds || [],
|
|
3025
|
+
eligibility: context.eligibility || 'active',
|
|
3026
|
+
...(Object.prototype.hasOwnProperty.call(context, 'broadcast') ? { broadcast: context.broadcast } : {}),
|
|
3027
|
+
displayName: context.displayName || null,
|
|
3028
|
+
summary: context.summary || null,
|
|
3029
|
+
description: context.description || null,
|
|
3030
|
+
entryProfileSchema: context.entryProfileSchema || {},
|
|
3031
|
+
sessionTemplate: context.sessionTemplate || null,
|
|
3032
|
+
interactionRules: context.interactionRules || null,
|
|
3033
|
+
prohibitedRules: context.prohibitedRules || null,
|
|
3034
|
+
ratingRules: context.ratingRules || null,
|
|
3035
|
+
enabled: context.enabled === true,
|
|
3036
|
+
fetchImpl,
|
|
3037
|
+
logger,
|
|
3038
|
+
});
|
|
3039
|
+
},
|
|
3040
|
+
listOwnedWorlds: async (context = {}) => {
|
|
3041
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
3042
|
+
return fetchOwnedWorlds({
|
|
3043
|
+
cfg: resolvedContext.cfg || {},
|
|
3044
|
+
accountId: resolvedContext.accountId || null,
|
|
3045
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
3046
|
+
agentId: resolvedContext.agentId || null,
|
|
3047
|
+
includeDisabled: context.includeDisabled !== false,
|
|
3048
|
+
fetchImpl,
|
|
3049
|
+
logger,
|
|
3050
|
+
});
|
|
3051
|
+
},
|
|
3052
|
+
manageWorld: async (context = {}) => {
|
|
3053
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
3054
|
+
return manageModeratedWorld({
|
|
3055
|
+
cfg: resolvedContext.cfg || {},
|
|
3056
|
+
accountId: resolvedContext.accountId || null,
|
|
3057
|
+
runtimeConfig: resolvedContext.runtimeConfig || null,
|
|
3058
|
+
agentId: resolvedContext.agentId || null,
|
|
3059
|
+
worldId: context.worldId || null,
|
|
3060
|
+
mode: context.mode || 'get',
|
|
3061
|
+
changes: context.changes || null,
|
|
3062
|
+
enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
|
|
3063
|
+
fetchImpl,
|
|
3064
|
+
logger,
|
|
3065
|
+
});
|
|
3066
|
+
},
|
|
3067
|
+
},
|
|
3068
|
+
},
|
|
3069
|
+
createRelayClient: (options = {}) => relayClientFactory({ logger, inbound, outbound, protocol, ...options }),
|
|
3070
|
+
},
|
|
3071
|
+
internals: {
|
|
3072
|
+
protocol,
|
|
3073
|
+
inbound,
|
|
3074
|
+
outbound,
|
|
3075
|
+
results,
|
|
3076
|
+
demo,
|
|
3077
|
+
lifecycles,
|
|
3078
|
+
getRelayClient: (accountId = 'default') => relayClients.get(accountId) || null,
|
|
3079
|
+
},
|
|
3080
|
+
async start(context = {}) {
|
|
3081
|
+
return runAccountLifecycle(context);
|
|
3082
|
+
},
|
|
3083
|
+
async stop(reason) {
|
|
3084
|
+
return this.gateway.stop(reason);
|
|
3085
|
+
},
|
|
3086
|
+
async reconnect(context = {}) {
|
|
3087
|
+
return this.gateway.reconnect(context);
|
|
3088
|
+
},
|
|
3089
|
+
describe() {
|
|
3090
|
+
return {
|
|
3091
|
+
id: this.id,
|
|
3092
|
+
meta: this.meta,
|
|
3093
|
+
capabilities: this.capabilities,
|
|
3094
|
+
configSchema: this.configSchema,
|
|
3095
|
+
runtimePath: inbound.runtimePath,
|
|
3096
|
+
lifecycle: this.gateway.snapshot(),
|
|
3097
|
+
relayClient: relayClients.get('default')?.snapshot?.() || null,
|
|
3098
|
+
status: createStatusSnapshot(),
|
|
3099
|
+
readyFor: [
|
|
3100
|
+
'plugin manifest validation',
|
|
3101
|
+
'config inspection',
|
|
3102
|
+
'native channel setup/onboarding',
|
|
3103
|
+
'relay websocket adapter tests',
|
|
3104
|
+
'manual relay-agent binding',
|
|
3105
|
+
'minimal outbound session-request send',
|
|
3106
|
+
],
|
|
3107
|
+
notImplemented: [
|
|
3108
|
+
'真实 OpenClaw sdk import/types 对齐',
|
|
3109
|
+
'ack/retry persistence',
|
|
3110
|
+
'console + demo wiring',
|
|
3111
|
+
],
|
|
3112
|
+
};
|
|
3113
|
+
},
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
export const claworldChannelPluginScaffold = createClaworldChannelPlugin;
|
|
3118
|
+
export { normalizeRelayHttpBaseUrl };
|