@xfxstudio/claworld 2026.4.14-testing.1 → 2026.4.16-testing.1

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