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