@xfxstudio/claworld 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +60 -0
  2. package/bin/claworld.mjs +9 -0
  3. package/index.js +51 -0
  4. package/openclaw.plugin.json +470 -0
  5. package/package.json +76 -0
  6. package/setup-entry.js +6 -0
  7. package/src/lib/accepted-chat-kickoff.js +192 -0
  8. package/src/lib/agent-address.js +46 -0
  9. package/src/lib/agent-profile.js +69 -0
  10. package/src/lib/http-auth.js +151 -0
  11. package/src/lib/policy.js +118 -0
  12. package/src/lib/runtime-errors.js +149 -0
  13. package/src/lib/runtime-guidance.js +458 -0
  14. package/src/openclaw/index.js +53 -0
  15. package/src/openclaw/installer/cli.js +349 -0
  16. package/src/openclaw/installer/constants.js +6 -0
  17. package/src/openclaw/installer/core.js +1548 -0
  18. package/src/openclaw/installer/doctor.js +690 -0
  19. package/src/openclaw/installer/workspace-contract.js +403 -0
  20. package/src/openclaw/plugin/account-identity.js +66 -0
  21. package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
  22. package/src/openclaw/plugin/config-schema.js +464 -0
  23. package/src/openclaw/plugin/lifecycle.js +114 -0
  24. package/src/openclaw/plugin/managed-config.js +648 -0
  25. package/src/openclaw/plugin/onboarding.js +291 -0
  26. package/src/openclaw/plugin/register.js +961 -0
  27. package/src/openclaw/plugin/relay-client.js +783 -0
  28. package/src/openclaw/plugin/runtime.js +12 -0
  29. package/src/openclaw/protocol/relay-event-protocol.js +31 -0
  30. package/src/openclaw/runtime/canonical-result-builder.js +116 -0
  31. package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
  32. package/src/openclaw/runtime/feedback-helper.js +145 -0
  33. package/src/openclaw/runtime/inbound-session-router.js +36 -0
  34. package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
  35. package/src/openclaw/runtime/product-shell-helper.js +1712 -0
  36. package/src/openclaw/runtime/runtime-path.js +19 -0
  37. package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
  38. package/src/openclaw/runtime/tool-contracts.js +714 -0
  39. package/src/openclaw/runtime/tool-inventory.js +92 -0
  40. package/src/openclaw/runtime/world-moderation-helper.js +415 -0
  41. package/src/openclaw/runtime/world-session-startup.js +1 -0
  42. package/src/product-shell/catalog/default-world-catalog.js +296 -0
  43. package/src/product-shell/contracts/candidate-feed.js +330 -0
  44. package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
  45. package/src/product-shell/contracts/world-manifest.js +435 -0
  46. package/src/product-shell/contracts/world-orchestration.js +1024 -0
  47. package/src/product-shell/feedback/feedback-contract.js +13 -0
  48. package/src/product-shell/feedback/feedback-routes.js +98 -0
  49. package/src/product-shell/feedback/feedback-service.js +254 -0
  50. package/src/product-shell/index.js +163 -0
  51. package/src/product-shell/matching/matchmaking-service.js +340 -0
  52. package/src/product-shell/membership/membership-service.js +277 -0
  53. package/src/product-shell/onboarding/onboarding-routes.js +37 -0
  54. package/src/product-shell/onboarding/onboarding-service.js +230 -0
  55. package/src/product-shell/orchestration/session-orchestrator.js +38 -0
  56. package/src/product-shell/results/result-service.js +15 -0
  57. package/src/product-shell/search/search-service.js +359 -0
  58. package/src/product-shell/social/chat-request-approval-policy.js +332 -0
  59. package/src/product-shell/social/chat-request-routes.js +108 -0
  60. package/src/product-shell/social/chat-request-service.js +632 -0
  61. package/src/product-shell/social/friend-routes.js +82 -0
  62. package/src/product-shell/social/friend-service.js +560 -0
  63. package/src/product-shell/social/social-routes.js +21 -0
  64. package/src/product-shell/social/social-service.js +140 -0
  65. package/src/product-shell/worlds/world-admin-service.js +705 -0
  66. package/src/product-shell/worlds/world-authorization.js +135 -0
  67. package/src/product-shell/worlds/world-broadcast-service.js +299 -0
  68. package/src/product-shell/worlds/world-routes.js +410 -0
  69. package/src/product-shell/worlds/world-service.js +89 -0
@@ -0,0 +1,648 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import {
4
+ CLAWORLD_MINIMAL_OPENCLAW_TOOL_NAMES,
5
+ CLAWORLD_OPTIONAL_WORLD_HELPER_TOOL_NAMES,
6
+ CLAWORLD_PUBLIC_TOOL_NAMES,
7
+ CLAWORLD_READ_ONLY_OPENCLAW_TOOL_NAMES,
8
+ CLAWORLD_TOOL_PROFILES,
9
+ } from '../runtime/tool-inventory.js';
10
+ import {
11
+ DEFAULT_CHAT_REQUEST_APPROVAL_POLICY_MODE,
12
+ normalizeChatRequestApprovalMode,
13
+ } from '../../product-shell/contracts/chat-request-approval-policy.js';
14
+
15
+ export const DEFAULT_CLAWORLD_SERVER_URL = 'https://claworld.zy-sj.com';
16
+ export const DEFAULT_CLAWORLD_API_KEY = 'local-test';
17
+ export const DEFAULT_CLAWORLD_AGENT_ID = 'claworld';
18
+ export const DEFAULT_CLAWORLD_ACCOUNT_ID = 'claworld';
19
+ export const DEFAULT_CLAWORLD_TOOL_PROFILE = 'default';
20
+ export const DEFAULT_CLAWORLD_DM_SCOPE = 'per-channel-peer';
21
+ export const DEFAULT_CLAWORLD_APPROVAL_MODE = DEFAULT_CHAT_REQUEST_APPROVAL_POLICY_MODE;
22
+
23
+ export const TOOL_PROFILES = CLAWORLD_TOOL_PROFILES;
24
+
25
+ export function normalizeText(value, fallback = null) {
26
+ if (value == null) return fallback;
27
+ const normalized = String(value).trim();
28
+ return normalized || fallback;
29
+ }
30
+
31
+ export function titleCase(value) {
32
+ const normalized = normalizeText(value, '') || '';
33
+ if (!normalized) return 'Claworld';
34
+ return normalized
35
+ .split(/[^A-Za-z0-9]+/)
36
+ .filter(Boolean)
37
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
38
+ .join(' ');
39
+ }
40
+
41
+ export function ensureObject(value) {
42
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
43
+ return value;
44
+ }
45
+
46
+ function normalizeRegistrationAgentCode(value, fallback = null) {
47
+ const normalized = normalizeText(value, fallback);
48
+ return normalized ? normalized.toLowerCase() : fallback;
49
+ }
50
+
51
+ export function expandUserPath(input, homeDir = os.homedir()) {
52
+ const normalized = normalizeText(input, null);
53
+ if (!normalized) return normalized;
54
+ if (normalized === '~') return homeDir;
55
+ if (normalized.startsWith('~/')) return path.join(homeDir, normalized.slice(2));
56
+ return normalized;
57
+ }
58
+
59
+ function asStringArray(value) {
60
+ if (value == null) return [];
61
+ if (Array.isArray(value)) {
62
+ return value
63
+ .map((item) => normalizeText(item, null))
64
+ .filter(Boolean);
65
+ }
66
+ const normalized = normalizeText(value, null);
67
+ return normalized ? [normalized] : [];
68
+ }
69
+
70
+ function uniqueStrings(values = []) {
71
+ const seen = new Set();
72
+ const result = [];
73
+ for (const value of values) {
74
+ const normalized = normalizeText(value, null);
75
+ if (!normalized || seen.has(normalized)) continue;
76
+ seen.add(normalized);
77
+ result.push(normalized);
78
+ }
79
+ return result;
80
+ }
81
+
82
+ function inferRelayDomain(defaultToAddress = null) {
83
+ const normalized = normalizeText(defaultToAddress, null);
84
+ if (!normalized) return 'relay.local';
85
+ const atIndex = normalized.indexOf('@');
86
+ if (atIndex <= 0 || atIndex >= normalized.length - 1) return 'relay.local';
87
+ return normalized.slice(atIndex + 1).trim().toLowerCase() || 'relay.local';
88
+ }
89
+
90
+ function findAgentIndex(agentList = [], agentId) {
91
+ return agentList.findIndex((item) => ensureObject(item).id === agentId);
92
+ }
93
+
94
+ function findAgentEntry(config = {}, agentId) {
95
+ const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
96
+ return list
97
+ .map((item) => ensureObject(item))
98
+ .find((item) => item.id === agentId) || null;
99
+ }
100
+
101
+ function findManagedAccountEntry(config = {}, accountId) {
102
+ const claworldRoot = ensureObject(config?.channels?.claworld);
103
+ const accounts = ensureObject(claworldRoot.accounts);
104
+ if (accounts[accountId]) return ensureObject(accounts[accountId]);
105
+ if (normalizeText(claworldRoot.accountId, null) === accountId) return claworldRoot;
106
+ return {};
107
+ }
108
+
109
+ function buildManagedAgentEntry(options = {}) {
110
+ const managedSkills = resolveManagedAgentSkills({ toolProfile: options.toolProfile });
111
+ return {
112
+ id: options.agentId,
113
+ workspace: options.workspace,
114
+ ...(managedSkills === undefined ? {} : { skills: managedSkills }),
115
+ ...(options.agentDirExplicit && options.agentDir ? { agentDir: options.agentDir } : {}),
116
+ };
117
+ }
118
+
119
+ function buildManagedAccountEntry(options = {}) {
120
+ const base = {
121
+ enabled: true,
122
+ serverUrl: options.serverUrl,
123
+ apiKey: options.apiKey,
124
+ accountId: options.accountId,
125
+ toolProfile: options.toolProfile,
126
+ name: normalizeText(options.name, normalizeText(options.displayName, null)),
127
+ approval: {
128
+ mode: normalizeChatRequestApprovalMode(options.approvalMode, DEFAULT_CLAWORLD_APPROVAL_MODE),
129
+ },
130
+ };
131
+
132
+ if (options.appToken) {
133
+ base.appToken = options.appToken;
134
+ } else {
135
+ base.registration = {
136
+ enabled: true,
137
+ agentCode: options.registrationAgentCode,
138
+ displayName: options.displayName,
139
+ };
140
+ }
141
+
142
+ if (!options.defaultToAddress) {
143
+ return base;
144
+ }
145
+
146
+ return {
147
+ ...base,
148
+ relay: {
149
+ defaultToAddress: options.defaultToAddress,
150
+ },
151
+ };
152
+ }
153
+
154
+ function buildMergedAccountEntry(existingAccount = {}, options = {}) {
155
+ const existingApproval = ensureObject(existingAccount.approval);
156
+ const existingRelay = ensureObject(existingAccount.relay);
157
+ const existingRegistration = ensureObject(existingAccount.registration);
158
+ const merged = {
159
+ ...existingAccount,
160
+ enabled: true,
161
+ serverUrl: options.serverUrl,
162
+ apiKey: options.apiKey,
163
+ accountId: options.accountId,
164
+ toolProfile: options.toolProfile,
165
+ name: normalizeText(options.name, normalizeText(existingAccount.name, normalizeText(options.displayName, null))),
166
+ approval: {
167
+ ...existingApproval,
168
+ mode: normalizeChatRequestApprovalMode(options.approvalMode, DEFAULT_CLAWORLD_APPROVAL_MODE),
169
+ },
170
+ ...(options.defaultToAddress
171
+ ? {
172
+ relay: {
173
+ ...existingRelay,
174
+ defaultToAddress: options.defaultToAddress,
175
+ },
176
+ }
177
+ : existingAccount.relay
178
+ ? { relay: existingRelay }
179
+ : {}),
180
+ };
181
+
182
+ if (options.appToken) {
183
+ return {
184
+ ...merged,
185
+ appToken: options.appToken,
186
+ };
187
+ }
188
+
189
+ return {
190
+ ...merged,
191
+ registration: {
192
+ ...existingRegistration,
193
+ enabled: true,
194
+ agentCode: options.registrationAgentCode,
195
+ displayName: options.displayName,
196
+ },
197
+ };
198
+ }
199
+
200
+ export function canonicalRelayAgentCode(registrationAgentCode, defaultToAddress = null) {
201
+ const normalizedAgentCode = normalizeRegistrationAgentCode(registrationAgentCode, null);
202
+ if (!normalizedAgentCode) return null;
203
+ if (normalizedAgentCode.includes('@')) return normalizedAgentCode;
204
+ return `${normalizedAgentCode}@${inferRelayDomain(defaultToAddress)}`;
205
+ }
206
+
207
+ export function normalizeClaworldToolProfile(toolProfile = DEFAULT_CLAWORLD_TOOL_PROFILE) {
208
+ const normalized = normalizeText(toolProfile, DEFAULT_CLAWORLD_TOOL_PROFILE);
209
+ if (normalized === 'world') return 'default';
210
+ return normalized;
211
+ }
212
+
213
+ export function inferClaworldToolProfile(config = {}) {
214
+ const allow = new Set(asStringArray(config?.tools?.allow));
215
+ if (allow.has('*')) return 'full';
216
+ if (CLAWORLD_READ_ONLY_OPENCLAW_TOOL_NAMES.some((toolName) => allow.has(toolName))) {
217
+ return 'default';
218
+ }
219
+ if (CLAWORLD_PUBLIC_TOOL_NAMES.some((toolName) => allow.has(toolName))) {
220
+ return 'minimal';
221
+ }
222
+ return DEFAULT_CLAWORLD_TOOL_PROFILE;
223
+ }
224
+
225
+ function resolveStoredClaworldToolProfile(account = {}) {
226
+ const persistedToolProfile = normalizeText(account?.toolProfile, null);
227
+ return persistedToolProfile
228
+ ? normalizeClaworldToolProfile(persistedToolProfile)
229
+ : null;
230
+ }
231
+
232
+ function resolveManagedToolProfile({
233
+ cfg = {},
234
+ existingAccount = {},
235
+ explicitToolProfile = null,
236
+ } = {}) {
237
+ const normalizedExplicitToolProfile = normalizeText(explicitToolProfile, null);
238
+ if (normalizedExplicitToolProfile) {
239
+ return normalizeClaworldToolProfile(normalizedExplicitToolProfile);
240
+ }
241
+
242
+ const persistedToolProfile = resolveStoredClaworldToolProfile(existingAccount);
243
+ if (persistedToolProfile) {
244
+ return persistedToolProfile;
245
+ }
246
+
247
+ const inferredToolProfile = inferClaworldToolProfile(cfg);
248
+ if (inferredToolProfile === 'minimal') {
249
+ const allow = new Set(asStringArray(cfg?.tools?.allow));
250
+ const hasMinimalCoreAnchor = CLAWORLD_MINIMAL_OPENCLAW_TOOL_NAMES
251
+ .some((toolName) => allow.has(toolName));
252
+ if (!hasMinimalCoreAnchor) {
253
+ // Legacy managed installs stored only the old public claworld allowlist.
254
+ // Treat that shape as the historical default/world profile during refresh.
255
+ return DEFAULT_CLAWORLD_TOOL_PROFILE;
256
+ }
257
+ }
258
+ return normalizeClaworldToolProfile(inferredToolProfile);
259
+ }
260
+
261
+ export function resolveToolNames({ toolProfile = DEFAULT_CLAWORLD_TOOL_PROFILE } = {}) {
262
+ const normalizedProfile = normalizeClaworldToolProfile(toolProfile);
263
+ const baseProfile = TOOL_PROFILES[normalizedProfile];
264
+ if (!baseProfile) {
265
+ throw new Error(`Unsupported tool profile: ${toolProfile}`);
266
+ }
267
+ return [...baseProfile];
268
+ }
269
+
270
+ export function resolveManagedAgentSkills({ toolProfile = DEFAULT_CLAWORLD_TOOL_PROFILE } = {}) {
271
+ const normalizedProfile = normalizeClaworldToolProfile(toolProfile);
272
+ return normalizedProfile === 'full' ? undefined : [];
273
+ }
274
+
275
+ function describeToolAllowEntries(toolNames = []) {
276
+ if (toolNames.length === 1) {
277
+ return toolNames[0] === '*' ? '1 entry (includes *)' : '1 entry';
278
+ }
279
+ return toolNames.includes('*')
280
+ ? `${toolNames.length} entries (includes *)`
281
+ : `${toolNames.length} entries`;
282
+ }
283
+
284
+ export function buildWorkspaceAgentsContent({
285
+ agentId,
286
+ accountId,
287
+ registrationAgentCode,
288
+ appToken = null,
289
+ defaultToAddress = null,
290
+ } = {}) {
291
+ const canonicalCode = canonicalRelayAgentCode(registrationAgentCode, defaultToAddress);
292
+ const identityLine = canonicalCode
293
+ ? `- canonical relay agentCode after bootstrap: \`${canonicalCode}\``
294
+ : appToken
295
+ ? '- relay identity is resolved from the configured appToken at runtime'
296
+ : '- relay identity is resolved during runtime bootstrap';
297
+
298
+ return `# Claworld Channel Agent
299
+
300
+ This workspace is dedicated to the OpenClaw \`claworld\` channel.
301
+
302
+ Routing contract:
303
+
304
+ - local OpenClaw agent id: \`${agentId}\`
305
+ - claworld account id: \`${accountId}\`
306
+ ${registrationAgentCode ? `- registration.agentCode: \`${registrationAgentCode}\`` : '- credential mode: appToken/manual binding'}
307
+ ${identityLine}
308
+
309
+ Operating rules:
310
+
311
+ - keep this workspace focused on Claworld relay and world interactions
312
+ - use explicit \`claworld_*\` tools when they are available
313
+ - do not treat this workspace as the user's general-purpose main agent
314
+ - do not treat this as a separately bootstrapped OpenClaw persona
315
+ `;
316
+ }
317
+
318
+ export function buildWorkspaceMemoryContent({
319
+ agentId,
320
+ accountId,
321
+ registrationAgentCode,
322
+ appToken = null,
323
+ defaultToAddress = null,
324
+ } = {}) {
325
+ const canonicalCode = canonicalRelayAgentCode(registrationAgentCode, defaultToAddress);
326
+ const identityLine = canonicalCode
327
+ ? `- relay identity: \`${canonicalCode}\``
328
+ : appToken
329
+ ? '- relay identity: resolved from appToken at runtime'
330
+ : '- relay identity: assigned during runtime bootstrap';
331
+
332
+ return `# Claworld Memory
333
+
334
+ - workspace owner: \`${agentId}\`
335
+ - claworld account: \`${accountId}\`
336
+ ${identityLine}
337
+
338
+ Use this file for durable Claworld-specific notes only.
339
+
340
+ - keep world rules, pairing context, and recurring counterpart preferences here when they help future Claworld sessions
341
+ - do not duplicate a full standalone OpenClaw bootstrap/persona here
342
+ - prefer channel/world-specific memory over general-purpose assistant memory
343
+ `;
344
+ }
345
+
346
+ export function resolveDefaultManagedWorkspace(agentId = DEFAULT_CLAWORLD_AGENT_ID) {
347
+ return `~/.openclaw/workspace-${agentId}`;
348
+ }
349
+
350
+ export function resolveDefaultManagedRegistrationAgentCode(accountId = DEFAULT_CLAWORLD_ACCOUNT_ID) {
351
+ return null;
352
+ }
353
+
354
+ export function resolveDefaultManagedDisplayName(accountId = DEFAULT_CLAWORLD_ACCOUNT_ID) {
355
+ return `${titleCase(accountId)} Channel Agent`;
356
+ }
357
+
358
+ function resolveStoredApprovalMode(existingAccount = {}) {
359
+ const existingApproval = ensureObject(existingAccount.approval);
360
+ if (normalizeText(existingApproval.mode, null)) {
361
+ return normalizeChatRequestApprovalMode(existingApproval.mode, DEFAULT_CLAWORLD_APPROVAL_MODE);
362
+ }
363
+ if (typeof existingApproval.autoAccept === 'boolean') {
364
+ return existingApproval.autoAccept ? 'open' : DEFAULT_CLAWORLD_APPROVAL_MODE;
365
+ }
366
+ return DEFAULT_CLAWORLD_APPROVAL_MODE;
367
+ }
368
+
369
+ export function resolveClaworldManagedRuntimeOptions({
370
+ cfg = {},
371
+ accountId = null,
372
+ input = {},
373
+ overrides = {},
374
+ } = {}) {
375
+ const resolvedAccountId = normalizeText(accountId, DEFAULT_CLAWORLD_ACCOUNT_ID);
376
+ const agentId = normalizeText(overrides.agentId, resolvedAccountId);
377
+ const existingAgent = findAgentEntry(cfg, agentId);
378
+ const existingAccount = findManagedAccountEntry(cfg, resolvedAccountId);
379
+ const replaceManagedRuntime = overrides.replaceManagedRuntime !== false;
380
+ const defaultWorkspace = resolveDefaultManagedWorkspace(agentId);
381
+ const workspace = normalizeText(
382
+ overrides.workspace,
383
+ replaceManagedRuntime
384
+ ? defaultWorkspace
385
+ : normalizeText(existingAgent?.workspace, defaultWorkspace),
386
+ );
387
+ const serverUrl = normalizeText(
388
+ overrides.serverUrl,
389
+ normalizeText(input.httpUrl, normalizeText(input.url, DEFAULT_CLAWORLD_SERVER_URL)),
390
+ );
391
+ const apiKey = normalizeText(overrides.apiKey, DEFAULT_CLAWORLD_API_KEY);
392
+ const explicitAppToken = normalizeText(
393
+ overrides.appToken,
394
+ normalizeText(input.appToken, null),
395
+ );
396
+ const explicitRegistrationAgentCode = normalizeRegistrationAgentCode(
397
+ overrides.registrationAgentCode,
398
+ normalizeRegistrationAgentCode(input.code, null),
399
+ );
400
+ const appToken = explicitRegistrationAgentCode && !explicitAppToken
401
+ ? null
402
+ : normalizeText(
403
+ explicitAppToken,
404
+ normalizeText(
405
+ existingAccount.appToken,
406
+ normalizeText(
407
+ existingAccount?.relay?.appToken,
408
+ normalizeText(existingAccount?.relay?.credentialToken, null),
409
+ ),
410
+ ),
411
+ );
412
+ const displayName = normalizeText(
413
+ overrides.displayName,
414
+ normalizeText(input.name, resolveDefaultManagedDisplayName(resolvedAccountId)),
415
+ );
416
+ const name = normalizeText(overrides.name, displayName);
417
+ const explicitToolProfile = normalizeText(
418
+ overrides.toolProfile,
419
+ normalizeText(input.toolProfile, null),
420
+ );
421
+ const existingRegistrationAgentCode = normalizeRegistrationAgentCode(
422
+ existingAccount?.registration?.agentCode,
423
+ normalizeRegistrationAgentCode(existingAccount?.localAgent?.agentCode, null),
424
+ );
425
+ const registrationAgentCode = appToken && !explicitRegistrationAgentCode
426
+ ? null
427
+ : normalizeRegistrationAgentCode(
428
+ explicitRegistrationAgentCode,
429
+ normalizeRegistrationAgentCode(
430
+ normalizeRegistrationAgentCode(
431
+ resolveDefaultManagedRegistrationAgentCode(resolvedAccountId),
432
+ existingRegistrationAgentCode,
433
+ ),
434
+ ),
435
+ );
436
+ const approvalMode = normalizeChatRequestApprovalMode(
437
+ normalizeText(overrides.approvalMode, null),
438
+ typeof overrides.autoAccept === 'boolean'
439
+ ? (overrides.autoAccept ? 'open' : DEFAULT_CLAWORLD_APPROVAL_MODE)
440
+ : resolveStoredApprovalMode(existingAccount),
441
+ );
442
+
443
+ return {
444
+ repoRoot: normalizeText(overrides.repoRoot, null),
445
+ agentId,
446
+ accountId: resolvedAccountId,
447
+ workspace,
448
+ agentDir: normalizeText(overrides.agentDir, null),
449
+ agentDirExplicit: overrides.agentDirExplicit === true,
450
+ serverUrl,
451
+ apiKey,
452
+ appToken,
453
+ registrationAgentCode,
454
+ displayName,
455
+ name,
456
+ defaultToAddress: normalizeText(overrides.defaultToAddress, null),
457
+ toolProfile: resolveManagedToolProfile({
458
+ cfg,
459
+ existingAccount,
460
+ explicitToolProfile,
461
+ }),
462
+ approvalMode,
463
+ sessionDmScope: normalizeText(
464
+ overrides.sessionDmScope,
465
+ DEFAULT_CLAWORLD_DM_SCOPE,
466
+ ),
467
+ replaceManagedRuntime,
468
+ preserveDefaultAccount: overrides.preserveDefaultAccount === true,
469
+ forceDefaultAccount: overrides.forceDefaultAccount === true,
470
+ pluginInstallMode: normalizeText(overrides.pluginInstallMode, 'skip'),
471
+ installPlugin: overrides.installPlugin !== false,
472
+ };
473
+ }
474
+
475
+ export function applyClaworldManagedRuntimeConfig(inputConfig = {}, options = {}) {
476
+ const config = JSON.parse(JSON.stringify(ensureObject(inputConfig)));
477
+ const toolProfile = normalizeClaworldToolProfile(options.toolProfile);
478
+ const toolNames = resolveToolNames({ toolProfile });
479
+ const summary = [];
480
+ const replaceManagedRuntime = options.replaceManagedRuntime !== false;
481
+ const preserveDefaultAccount = options.preserveDefaultAccount === true;
482
+ const sessionDmScope = normalizeText(options.sessionDmScope, DEFAULT_CLAWORLD_DM_SCOPE);
483
+
484
+ if (!options.appToken && !normalizeText(options.registrationAgentCode, null)) {
485
+ throw new Error('claworld registration agentCode is required when appToken is absent');
486
+ }
487
+
488
+ config.tools = ensureObject(config.tools);
489
+ const managedOptionalHelperTools = new Set(CLAWORLD_OPTIONAL_WORLD_HELPER_TOOL_NAMES);
490
+ const existingAllow = asStringArray(config.tools.allow);
491
+ const removedHelperTools = existingAllow.filter((toolName) => managedOptionalHelperTools.has(toolName));
492
+ config.tools.allow = uniqueStrings([
493
+ ...existingAllow.filter((toolName) => !managedOptionalHelperTools.has(toolName)),
494
+ ...toolNames,
495
+ ]);
496
+ if (removedHelperTools.length > 0) {
497
+ summary.push(`tools.allow removed optional helper entries (${removedHelperTools.join(',')})`);
498
+ }
499
+ summary.push(`tools.allow updated for ${toolProfile} profile (${describeToolAllowEntries(toolNames)})`);
500
+
501
+ config.session = ensureObject(config.session);
502
+ if (!Object.prototype.hasOwnProperty.call(config.session, 'dmScope')) {
503
+ config.session.dmScope = sessionDmScope;
504
+ summary.push(`session.dmScope set to ${sessionDmScope}`);
505
+ }
506
+
507
+ config.agents = ensureObject(config.agents);
508
+ const existingAgentList = Array.isArray(config.agents.list) ? [...config.agents.list] : [];
509
+ const managedAgentEntry = buildManagedAgentEntry(options);
510
+ if (replaceManagedRuntime) {
511
+ const removedAgentEntries = existingAgentList.filter((item) => ensureObject(item).id === options.agentId).length;
512
+ config.agents.list = [
513
+ ...existingAgentList.filter((item) => ensureObject(item).id !== options.agentId),
514
+ managedAgentEntry,
515
+ ];
516
+ summary.push(
517
+ removedAgentEntries > 0
518
+ ? `replaced managed agent entry ${options.agentId}`
519
+ : `added workspace-scoped agent entry ${options.agentId}`,
520
+ );
521
+ } else {
522
+ const agentIndex = findAgentIndex(existingAgentList, options.agentId);
523
+ if (agentIndex >= 0) {
524
+ const existingAgent = ensureObject(existingAgentList[agentIndex]);
525
+ const existingAgentDir = normalizeText(existingAgent.agentDir, null);
526
+ const managedSkills = resolveManagedAgentSkills({ toolProfile: options.toolProfile });
527
+ const keepExistingAgentDir = Boolean(
528
+ existingAgentDir
529
+ && (
530
+ options.agentDirExplicit
531
+ || existingAgentDir !== normalizeText(options.agentDir, null)
532
+ ),
533
+ );
534
+ const nextAgentEntry = {
535
+ ...existingAgent,
536
+ id: options.agentId,
537
+ workspace: normalizeText(existingAgent.workspace, options.workspace),
538
+ ...(keepExistingAgentDir ? { agentDir: existingAgentDir } : {}),
539
+ ...(managedSkills === undefined ? {} : { skills: managedSkills }),
540
+ };
541
+ if (!keepExistingAgentDir) {
542
+ delete nextAgentEntry.agentDir;
543
+ }
544
+ if (managedSkills === undefined) {
545
+ delete nextAgentEntry.skills;
546
+ }
547
+ existingAgentList[agentIndex] = nextAgentEntry;
548
+ summary.push(`updated existing agent entry ${options.agentId}`);
549
+ } else {
550
+ existingAgentList.push(managedAgentEntry);
551
+ summary.push(`added workspace-scoped agent entry ${options.agentId}`);
552
+ }
553
+ config.agents.list = existingAgentList;
554
+ }
555
+
556
+ config.channels = ensureObject(config.channels);
557
+ const existingClaworldRoot = ensureObject(config.channels.claworld);
558
+ const claworldRoot = replaceManagedRuntime ? {} : { ...existingClaworldRoot };
559
+ const existingDefaultAccount = normalizeText(existingClaworldRoot.defaultAccount, null);
560
+ const existingAccounts = ensureObject(existingClaworldRoot.accounts);
561
+ const targetExistingAccount = ensureObject(existingAccounts[options.accountId]);
562
+ const nextAccounts = { ...existingAccounts };
563
+ nextAccounts[options.accountId] = replaceManagedRuntime
564
+ ? buildManagedAccountEntry(options)
565
+ : buildMergedAccountEntry(targetExistingAccount, options);
566
+ claworldRoot.accounts = nextAccounts;
567
+ const shouldKeepDefaultAccount = preserveDefaultAccount
568
+ && existingDefaultAccount
569
+ && Object.prototype.hasOwnProperty.call(nextAccounts, existingDefaultAccount);
570
+ if (!shouldKeepDefaultAccount || options.forceDefaultAccount) {
571
+ claworldRoot.defaultAccount = options.accountId;
572
+ summary.push(`channels.claworld.defaultAccount set to ${options.accountId}`);
573
+ } else {
574
+ claworldRoot.defaultAccount = existingDefaultAccount;
575
+ summary.push(`channels.claworld.defaultAccount preserved as ${existingDefaultAccount}`);
576
+ }
577
+ config.channels.claworld = claworldRoot;
578
+ summary.push(
579
+ replaceManagedRuntime
580
+ ? `replaced managed channels.claworld.accounts.${options.accountId}`
581
+ : `configured channels.claworld.accounts.${options.accountId}`,
582
+ );
583
+
584
+ const existingBindings = Array.isArray(config.bindings) ? [...config.bindings] : [];
585
+ const remainingBindings = existingBindings.filter((binding) => {
586
+ const candidate = ensureObject(binding);
587
+ const match = ensureObject(candidate.match);
588
+ const bindingChannel = normalizeText(match.channel, null);
589
+ const bindingAccountId = normalizeText(match.accountId, null);
590
+ const bindingAgentId = normalizeText(candidate.agentId, null);
591
+
592
+ if (bindingChannel === 'claworld' && bindingAccountId === options.accountId) return false;
593
+ if (bindingChannel === 'claworld' && bindingAgentId === options.agentId) return false;
594
+ return true;
595
+ });
596
+ remainingBindings.push({
597
+ agentId: options.agentId,
598
+ match: {
599
+ channel: 'claworld',
600
+ accountId: options.accountId,
601
+ },
602
+ });
603
+ config.bindings = remainingBindings;
604
+ summary.push(
605
+ replaceManagedRuntime
606
+ ? `replaced claworld binding for ${options.accountId}`
607
+ : `reconciled claworld binding for ${options.accountId}`,
608
+ );
609
+
610
+ return {
611
+ config,
612
+ toolNames,
613
+ summary,
614
+ canonicalAgentCode: canonicalRelayAgentCode(
615
+ options.registrationAgentCode,
616
+ options.defaultToAddress,
617
+ ),
618
+ };
619
+ }
620
+
621
+ export function applyClaworldBootstrapConfig(inputConfig = {}, options = {}) {
622
+ const config = JSON.parse(JSON.stringify(ensureObject(inputConfig)));
623
+ const summary = [];
624
+
625
+ config.plugins = ensureObject(config.plugins);
626
+ config.plugins.allow = uniqueStrings([...asStringArray(config.plugins.allow), 'claworld']);
627
+ config.plugins.entries = ensureObject(config.plugins.entries);
628
+ config.plugins.entries.claworld = {
629
+ ...ensureObject(config.plugins.entries.claworld),
630
+ enabled: true,
631
+ };
632
+ config.plugins.load = ensureObject(config.plugins.load);
633
+ const shouldEnsureLoadPath = options.pluginInstallMode === 'link' || options.installPlugin === false;
634
+ if (shouldEnsureLoadPath) {
635
+ config.plugins.load.paths = uniqueStrings([
636
+ ...asStringArray(config.plugins.load.paths),
637
+ options.repoRoot,
638
+ ]);
639
+ summary.push(`plugins.load.paths includes ${options.repoRoot}`);
640
+ }
641
+ summary.push('plugins.allow includes claworld');
642
+
643
+ const runtimeResult = applyClaworldManagedRuntimeConfig(config, options);
644
+ return {
645
+ ...runtimeResult,
646
+ summary: [...summary, ...runtimeResult.summary],
647
+ };
648
+ }