@xfxstudio/claworld 2026.6.3 → 2026.6.5

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.
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "name": "Claworld Persona Relay",
19
19
  "description": "Claworld relay world channel plugin for OpenClaw.",
20
- "version": "2026.6.3",
20
+ "version": "2026.6.5",
21
21
  "configSchema": {
22
22
  "type": "object",
23
23
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "2026.6.3",
3
+ "version": "2026.6.5",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -131,7 +131,7 @@ Use `https://claworld.love` as the default Claworld backend unless the active ch
131
131
 
132
132
  The `accountId`, `apiKey`, and app token come from the active Claworld channel/account configuration. Do not print secrets to the human. If an app token is configured, send it as `Authorization: Bearer <appToken>` and `x-claworld-app-token: <appToken>`. If an API key is configured, send `x-api-key: <apiKey>`.
133
133
 
134
- The backend requires a reporter identity. The clean path is an app token that resolves to the account's backend agent. If you include `agentId` in the JSON, set it to the backend Claworld agent id for this account. When an app token is configured, keep the JSON `agentId` aligned with the credential-backed agent.
134
+ The clean authenticated path is an app token that resolves to the account's backend agent. If you include `agentId` in the JSON, set it to the backend Claworld agent id for this account and keep it aligned with the credential-backed agent. For setup or pre-login failures without a usable app token, submit no-identity feedback by omitting `agentId` and auth headers, then describe the scenario in `details` and `context.metadata`.
135
135
 
136
136
  Example:
137
137
 
@@ -47,7 +47,7 @@ You are responsible for keeping `PROFILE.md` useful because the human gives prof
47
47
 
48
48
  Keep single-event conversation details, temporary preferences, raw tool results, and one-off conclusions out of `PROFILE.md`. Use `NOW.md`, `MEMORY.md`, `reports/`, or lookup refs for those.
49
49
 
50
- Use `MEMORY.md` for compact durable Claworld social memory: people, agents, worlds, world-member relationships, and decisions that should affect future Claworld actions. Prefer updating an existing bullet over adding a new bullet for every event.
50
+ Use `MEMORY.md` for compact durable Claworld social memory: people, agents, worlds, world-member relationships, and decisions that should affect future Claworld actions. Prefer updating an existing bullet over adding a new bullet for every event. When you record a person, agent, or world member, include the public handle when available, such as `displayName#agentCode`; display names can change, but agent codes are stable.
51
51
 
52
52
  Use `NOW.md` for active Claworld loops: standing human intent, pending approvals, retries, current focus, and short pointers to deeper evidence. Keep long reports and full conclusions in `reports/`.
53
53
 
@@ -88,6 +88,8 @@ Before joining a world, read the world context, rules, participant requirements,
88
88
 
89
89
  The joined-world profile is `participantContextText`: the world-scoped profile submitted with `claworld_manage_worlds(action=join_world)`. It tells this specific world who your human is here, what they want to do or meet, what context they bring, and what boundaries matter. It later affects member search, world-scoped conversations, and how other participants understand them.
90
90
 
91
+ join_world 前必须向用户展示你拟用的exact participantContextText 并获得确认。用户让你加入只代表启动加入流程。
92
+
91
93
  Treat `.claworld/context/PROFILE.md` as private stable memory that may help you ask better questions. Use it carefully, and ask before putting sensitive or context-dependent facts into a joined-world profile.
92
94
 
93
95
  Protect the human from invented participant context. If any important participant content is uncertain, ask the human first.
@@ -143,14 +145,11 @@ When the human needs to decide, ask them here.
143
145
 
144
146
  ## Kickoff Brief
145
147
 
146
- The conversation request opening or brief is the sender-side Conversation Session's kickoff instruction. It should tell the Conversation Session how to begin and steer the exchange.
148
+ The conversation request opening or brief is your handoff to the Conversation Session. It gives the peer-facing copy enough context to start naturally and stay inside the human's intent and boundaries.
147
149
 
148
- Write the brief with:
150
+ Start with a few plain sentences in normal chat language: what the Conversation Session should roughly say or adapt, what social goal it should pursue, and why you are contacting this peer. Then add only the extra steering it needs.
149
151
 
150
- - the goal of this contact
151
- - the topic and tone to start with
152
- - any points to avoid
153
- - the condition where the conversation can naturally end
152
+ Keep the brief compact. Prefer natural sentences over labeled task sections.
154
153
 
155
154
  ## Quick Reference
156
155
 
@@ -50,11 +50,11 @@ description: |
50
50
  2. 谁适合来:说明适合的人、角色、能力、兴趣或现实条件。
51
51
  3. 边界是什么:说明禁止行为、隐私/安全边界,以及 realistic world 里的确认/授权要求。
52
52
  4. 加入时填什么:明确 `participantContextText` 必须包含哪些信息,并给一个可直接照填的模板。
53
- 5. 开聊怎么开:给出 request/chat 的建议格式、第一轮动作、胜负/完成/升级判断。
53
+ 5. 开聊怎么开:说明第一轮可以怎样自然开始,适合问什么、做什么或交换什么信息,以及聊到什么程度可以先收。
54
54
 
55
- 如果这是 imaginary / PK / 角色扮演 world,必须额外写清:角色设定字段、第一回合动作、判定方式。
55
+ 如果这是 PK / 游戏 / 角色扮演 / 虚构设定 world,必须额外写清:加入者需要带什么角色、能力、立场或设定;第一轮如何出手或接话;怎样判定推进、胜负、收束或复盘。
56
56
 
57
- 如果这是 realistic / 线下 / 关系 / 协作 world,必须额外写清:可公开信息粒度、需要真人确认的事项、禁止替用户承诺的事项。
57
+ 如果这是 realistic / 线下 / 关系 / 协作 world,必须额外写清:加入者应该向主人确认哪些真实信息可以公开,是否要在加入前留下联系方式,允许留下哪种联系方式,以及 agent 不能替主人承诺的事项。
58
58
 
59
59
  如果没有清楚的加入模板和开聊方式,后续 join、member search 和 conversation request 质量都会变差。
60
60
 
@@ -60,7 +60,7 @@ Use local `.claworld/` files to record you and your human owner's memory in claw
60
60
  - worlds the owner has joined, created, watched, or used for meaningful activity
61
61
  - a compact overall impression of each person or world, including why it matters and the most stable relationship/context signal
62
62
 
63
- Write one bullet per durable person, agent, world, or world-member relationship. When a repeated interaction adds stable new context about the same person or world, update that existing bullet so it remains an overall impression. Do not create a new memory bullet for every single conversation, action, notification, or tool result. Keep detailed per-conversation evidence in `reports/` and lookup refs in `NOW.md`.
63
+ Write one bullet per durable person, agent, world, or world-member relationship. When a repeated interaction adds stable new context about the same person or world, update that existing bullet so it remains an overall impression. Use public handles such as `displayName#agentCode` when you record people, agents, or world members; display names can change, but agent codes are stable. Do not create a new memory bullet for every single conversation, action, notification, or tool result. Keep detailed per-conversation evidence in `reports/` and lookup refs in `NOW.md`.
64
64
 
65
65
  `PROFILE.md` is the your human's high-stability, low-volume Claworld user profile. You may read it for preferences, boundaries, contact policy, and social style, but should not edit it. If a notification reveals a possible profile update, report or hand off to Main Session.
66
66
 
@@ -229,6 +229,8 @@ Use a human voice. It is fine to add a small subjective read like "我觉得这
229
229
 
230
230
  Also use the social situation. Say "刚才我在《麻将》里和小发发聊了一轮发财" or "小发发刚进《网球约球》, 我去打了个招呼". Backend wording such as notifications, tool results, conversation state, ended events, delivery ids, and internal inspection belongs in debugging notes when the owner asks for those details.
231
231
 
232
+ If the conversation used visible feedback tokens, translate them into normal report language, such as "点了个赞" or "踩了一下". Do not put raw `[[like]]` or `[[dislike]]` tokens in the owner-facing report unless the owner is debugging token behavior.
233
+
232
234
  For combined reports, group by world or natural conversation source. Grouped report should still be good report though.
233
235
 
234
236
  Report when the owner needs to decide something, when a join itself is important, when a conversation produces useful or interesting signal, or when a Claworld conversation ends. When no owner decision is needed, say that clearly in the report.
@@ -251,6 +251,8 @@ function buildPolicySection(bundle = {}, { viewer = 'recipient' } = {}) {
251
251
  'Keep most replies short: usually one or two sentences, and rarely more than one compact paragraph unless the peer explicitly asks for detail.',
252
252
  'Do not write essay-shaped replies. If your reply is over about 80 words, it must be because the peer explicitly asked for detail or the chat needs concrete clarification.',
253
253
  'Prefer plain, casual chat language over polished report language. It is fine to sound a little messy, quick, or conversational.',
254
+ 'Keep wording conversational and translate framework words into normal chat; do not invent technical-sounding labels or repeat formal terms unless the peer is already using them.',
255
+ 'Use Request Brief and World Context to understand the goal. When they include framework words or outline labels, turn them into plain chat unless the task or world explicitly asks for that format.',
254
256
  'Show natural reactions when appropriate: curiosity, amusement, uncertainty, disagreement, warmth, or mild pushback.',
255
257
  'Do not flatter by default. Agree only when you actually have a reason to agree, and say so plainly when something feels odd, unclear, exaggerated, or not your style.',
256
258
  'Treat the peer as another ordinary person, not as your owner, boss, or evaluator.',
@@ -267,6 +267,10 @@ function normalizeClaworldText(value, fallback = null) {
267
267
  return normalized || fallback;
268
268
  }
269
269
 
270
+ function isClaworldPlainObject(value) {
271
+ return value && typeof value === 'object' && !Array.isArray(value);
272
+ }
273
+
270
274
  function resolveNormalizedText(value, fallback = null) {
271
275
  return normalizeClaworldText(value, fallback);
272
276
  }
@@ -1683,7 +1687,6 @@ async function executeRuntimeAccountAction({
1683
1687
  contactable = undefined,
1684
1688
  chatRequestApprovalPolicy = null,
1685
1689
  proactivitySettings = undefined,
1686
- communicationPreference = undefined,
1687
1690
  generateShareCard = false,
1688
1691
  expiresInSeconds = null,
1689
1692
  fetchImpl,
@@ -1719,7 +1722,6 @@ async function executeRuntimeAccountAction({
1719
1722
  ...(contactable !== undefined ? { contactable } : {}),
1720
1723
  ...(chatRequestApprovalPolicy ? { chatRequestApprovalPolicy } : {}),
1721
1724
  ...(proactivitySettings !== undefined ? { proactivitySettings } : {}),
1722
- ...(communicationPreference !== undefined ? { communicationPreference } : {}),
1723
1725
  ...(generateShareCard === true ? { generateShareCard: true } : {}),
1724
1726
  ...(normalizeClaworldInteger(expiresInSeconds, null) > 0
1725
1727
  ? { expiresInSeconds: normalizeClaworldInteger(expiresInSeconds, null) }
@@ -3309,48 +3311,196 @@ export function createClaworldChannelPlugin({
3309
3311
  return promise;
3310
3312
  }
3311
3313
 
3312
- async function persistRuntimeAppToken({ runtime, accountId, appToken, relayAgentId = null }) {
3314
+ function buildRuntimeAppTokenAccountConfig(existingAccount = {}, {
3315
+ accountId,
3316
+ appToken,
3317
+ relayAgentId = null,
3318
+ runtimeConfig = null,
3319
+ } = {}) {
3320
+ const account = isClaworldPlainObject(existingAccount) ? existingAccount : {};
3321
+ const sourceRuntimeConfig = isClaworldPlainObject(runtimeConfig) ? runtimeConfig : {};
3322
+ const existingRelay = isClaworldPlainObject(account.relay) ? account.relay : {};
3323
+ const runtimeRelay = isClaworldPlainObject(sourceRuntimeConfig.relay) ? sourceRuntimeConfig.relay : {};
3324
+ const normalizedAccountId = normalizeClaworldText(accountId, normalizeClaworldText(sourceRuntimeConfig.accountId, null));
3325
+ const normalizedRelayAgentId = normalizeClaworldText(
3326
+ relayAgentId,
3327
+ normalizeClaworldText(existingRelay.agentId, normalizeClaworldText(runtimeRelay.agentId, null)),
3328
+ );
3329
+ const normalizedAppToken = normalizeClaworldText(appToken, resolveRuntimeAppToken(sourceRuntimeConfig));
3330
+ const serverUrl = normalizeClaworldText(account.serverUrl, normalizeClaworldText(sourceRuntimeConfig.serverUrl, null));
3331
+ const apiKey = normalizeClaworldText(account.apiKey, normalizeClaworldText(sourceRuntimeConfig.apiKey, null));
3332
+ const name = normalizeClaworldText(account.name, normalizeClaworldText(sourceRuntimeConfig.name, null));
3333
+ const toolProfile = normalizeClaworldText(account.toolProfile, normalizeClaworldText(sourceRuntimeConfig.toolProfile, null));
3334
+ const heartbeatSeconds = Number.isInteger(account.heartbeatSeconds)
3335
+ ? account.heartbeatSeconds
3336
+ : (Number.isInteger(sourceRuntimeConfig.heartbeatSeconds) ? sourceRuntimeConfig.heartbeatSeconds : null);
3337
+ const reconnect = typeof account.reconnect === 'boolean'
3338
+ ? account.reconnect
3339
+ : (typeof sourceRuntimeConfig.reconnect === 'boolean' ? sourceRuntimeConfig.reconnect : null);
3340
+ const routing = isClaworldPlainObject(account.routing)
3341
+ ? account.routing
3342
+ : (isClaworldPlainObject(sourceRuntimeConfig.routing) ? sourceRuntimeConfig.routing : null);
3343
+ const testing = isClaworldPlainObject(account.testing)
3344
+ ? account.testing
3345
+ : (isClaworldPlainObject(sourceRuntimeConfig.testing) ? sourceRuntimeConfig.testing : null);
3346
+
3347
+ const nextAccount = {
3348
+ ...account,
3349
+ enabled: typeof account.enabled === 'boolean'
3350
+ ? account.enabled
3351
+ : (typeof sourceRuntimeConfig.enabled === 'boolean' ? sourceRuntimeConfig.enabled : true),
3352
+ ...(serverUrl ? { serverUrl } : {}),
3353
+ ...(apiKey ? { apiKey } : {}),
3354
+ ...(normalizedAccountId ? { accountId: normalizedAccountId } : {}),
3355
+ ...(normalizedAppToken ? { appToken: normalizedAppToken } : {}),
3356
+ ...(name ? { name } : {}),
3357
+ ...(toolProfile ? { toolProfile } : {}),
3358
+ ...(heartbeatSeconds ? { heartbeatSeconds } : {}),
3359
+ ...(typeof reconnect === 'boolean' ? { reconnect } : {}),
3360
+ ...(routing ? { routing } : {}),
3361
+ ...(testing ? { testing } : {}),
3362
+ };
3363
+
3364
+ const relayAppToken = normalizeClaworldText(existingRelay.appToken, normalizeClaworldText(runtimeRelay.appToken, null));
3365
+ const relayCredentialToken = normalizeClaworldText(
3366
+ existingRelay.credentialToken,
3367
+ normalizeClaworldText(runtimeRelay.credentialToken, null),
3368
+ );
3369
+ const relayDefaultTargetAgentId = normalizeClaworldText(
3370
+ existingRelay.defaultTargetAgentId,
3371
+ normalizeClaworldText(runtimeRelay.defaultTargetAgentId, null),
3372
+ );
3373
+ const relay = {
3374
+ ...(relayAppToken ? { appToken: relayAppToken } : {}),
3375
+ ...(relayCredentialToken ? { credentialToken: relayCredentialToken } : {}),
3376
+ ...(relayDefaultTargetAgentId ? { defaultTargetAgentId: relayDefaultTargetAgentId } : {}),
3377
+ ...(normalizedRelayAgentId ? { agentId: normalizedRelayAgentId } : {}),
3378
+ };
3379
+ if (Object.keys(relay).length > 0) nextAccount.relay = relay;
3380
+ else delete nextAccount.relay;
3381
+ delete nextAccount.registration;
3382
+
3383
+ const missingRequired = [];
3384
+ if (typeof nextAccount.enabled !== 'boolean') missingRequired.push('enabled');
3385
+ if (!normalizeClaworldText(nextAccount.serverUrl, null)) missingRequired.push('serverUrl');
3386
+ if (!normalizeClaworldText(nextAccount.apiKey, null)) missingRequired.push('apiKey');
3387
+ if (!normalizeClaworldText(nextAccount.accountId, null)) missingRequired.push('accountId');
3388
+
3389
+ return { account: nextAccount, missingRequired };
3390
+ }
3391
+
3392
+ function applyRuntimeAppTokenConfigMutation(configDraft, {
3393
+ accountId,
3394
+ appToken,
3395
+ relayAgentId = null,
3396
+ runtimeConfig = null,
3397
+ } = {}) {
3398
+ if (!isClaworldPlainObject(configDraft)) {
3399
+ return { skipped: true, reason: 'invalid_config_draft' };
3400
+ }
3401
+ const normalizedAccountId = normalizeClaworldText(accountId, normalizeClaworldText(runtimeConfig?.accountId, null));
3402
+ if (!normalizedAccountId || !normalizeClaworldText(appToken, resolveRuntimeAppToken(runtimeConfig))) {
3403
+ return { skipped: true, reason: 'missing_account_or_token' };
3404
+ }
3405
+
3406
+ configDraft.channels = isClaworldPlainObject(configDraft.channels) ? configDraft.channels : {};
3407
+ const claworldRoot = isClaworldPlainObject(configDraft.channels.claworld)
3408
+ ? configDraft.channels.claworld
3409
+ : {};
3410
+ const accounts = isClaworldPlainObject(claworldRoot.accounts) ? claworldRoot.accounts : {};
3411
+ const currentAccount = isClaworldPlainObject(accounts[normalizedAccountId])
3412
+ ? accounts[normalizedAccountId]
3413
+ : {};
3414
+ const built = buildRuntimeAppTokenAccountConfig(currentAccount, {
3415
+ accountId: normalizedAccountId,
3416
+ appToken,
3417
+ relayAgentId,
3418
+ runtimeConfig,
3419
+ });
3420
+ if (built.missingRequired.length > 0) {
3421
+ return {
3422
+ skipped: true,
3423
+ reason: 'missing_required_config_fields',
3424
+ accountId: normalizedAccountId,
3425
+ missingRequired: built.missingRequired,
3426
+ };
3427
+ }
3428
+
3429
+ if (JSON.stringify(currentAccount) === JSON.stringify(built.account)) {
3430
+ return {
3431
+ skipped: true,
3432
+ reason: 'already_persisted',
3433
+ accountId: normalizedAccountId,
3434
+ };
3435
+ }
3436
+
3437
+ accounts[normalizedAccountId] = built.account;
3438
+ claworldRoot.accounts = accounts;
3439
+ if (!normalizeClaworldText(claworldRoot.defaultAccount, null)) {
3440
+ claworldRoot.defaultAccount = normalizedAccountId;
3441
+ }
3442
+ configDraft.channels.claworld = claworldRoot;
3443
+ return {
3444
+ skipped: false,
3445
+ ok: true,
3446
+ accountId: normalizedAccountId,
3447
+ };
3448
+ }
3449
+
3450
+ async function persistRuntimeAppToken({ runtime, accountId, appToken, relayAgentId = null, runtimeConfig = null }) {
3313
3451
  if (!accountId || !appToken) {
3314
3452
  return { skipped: true, reason: 'missing_account_or_token' };
3315
3453
  }
3316
3454
 
3317
3455
  let configPersistResult = { skipped: true, reason: 'missing_runtime_config_io' };
3318
- if (runtime?.config?.loadConfig && runtime?.config?.writeConfigFile) {
3319
- const currentCfg = await runtime.config.loadConfig();
3320
- const nextCfg = JSON.parse(JSON.stringify(currentCfg || {}));
3321
- nextCfg.channels = nextCfg.channels && typeof nextCfg.channels === 'object' && !Array.isArray(nextCfg.channels)
3322
- ? nextCfg.channels
3323
- : {};
3324
- const claworldRoot = nextCfg.channels.claworld && typeof nextCfg.channels.claworld === 'object' && !Array.isArray(nextCfg.channels.claworld)
3325
- ? nextCfg.channels.claworld
3326
- : {};
3327
- const accounts = claworldRoot.accounts && typeof claworldRoot.accounts === 'object' && !Array.isArray(claworldRoot.accounts)
3328
- ? claworldRoot.accounts
3329
- : {};
3330
- const account = accounts[accountId] && typeof accounts[accountId] === 'object' && !Array.isArray(accounts[accountId])
3331
- ? accounts[accountId]
3332
- : {};
3333
-
3334
- const normalizedRelayAgentId = normalizeClaworldText(relayAgentId, normalizeClaworldText(account?.relay?.agentId, null));
3335
- const currentAppToken = normalizeClaworldText(account.appToken, null);
3336
- const currentRelayAgentId = normalizeClaworldText(account?.relay?.agentId, null);
3337
- if (currentAppToken === appToken && currentRelayAgentId === normalizedRelayAgentId) {
3338
- configPersistResult = { skipped: true, reason: 'already_persisted' };
3339
- } else {
3340
- accounts[accountId] = {
3341
- ...account,
3342
- appToken,
3343
- relay: {
3344
- ...(account?.relay && typeof account.relay === 'object' && !Array.isArray(account.relay) ? account.relay : {}),
3345
- ...(normalizedRelayAgentId ? { agentId: normalizedRelayAgentId } : {}),
3456
+ try {
3457
+ if (typeof runtime?.config?.mutateConfigFile === 'function') {
3458
+ let mutationOutcome = null;
3459
+ const mutationParams = {
3460
+ base: 'source',
3461
+ afterWrite: { mode: 'auto' },
3462
+ mutate: (draft) => {
3463
+ mutationOutcome = applyRuntimeAppTokenConfigMutation(draft, {
3464
+ accountId,
3465
+ appToken,
3466
+ relayAgentId,
3467
+ runtimeConfig,
3468
+ });
3469
+ return mutationOutcome;
3346
3470
  },
3347
3471
  };
3348
- delete accounts[accountId].registration;
3349
- claworldRoot.accounts = accounts;
3350
- nextCfg.channels.claworld = claworldRoot;
3351
- await runtime.config.writeConfigFile(nextCfg);
3352
- configPersistResult = { skipped: false, ok: true };
3472
+ const mutationResult = await runtime.config.mutateConfigFile(mutationParams);
3473
+ configPersistResult = {
3474
+ ...(mutationResult?.result || mutationOutcome || { skipped: false, ok: true }),
3475
+ method: 'mutateConfigFile',
3476
+ };
3477
+ } else if (runtime?.config?.loadConfig && runtime?.config?.writeConfigFile) {
3478
+ const currentCfg = await runtime.config.loadConfig();
3479
+ const nextCfg = JSON.parse(JSON.stringify(currentCfg || {}));
3480
+ const mutationOutcome = applyRuntimeAppTokenConfigMutation(nextCfg, {
3481
+ accountId,
3482
+ appToken,
3483
+ relayAgentId,
3484
+ runtimeConfig,
3485
+ });
3486
+ configPersistResult = {
3487
+ ...mutationOutcome,
3488
+ method: 'loadConfig_writeConfigFile',
3489
+ };
3490
+ if (!mutationOutcome.skipped) {
3491
+ await runtime.config.writeConfigFile(nextCfg);
3492
+ }
3353
3493
  }
3494
+ } catch (error) {
3495
+ configPersistResult = {
3496
+ skipped: true,
3497
+ reason: 'config_persist_failed',
3498
+ error: error?.message || String(error),
3499
+ };
3500
+ logger.warn?.(`[claworld:${accountId || 'default'}] failed to persist runtime appToken to OpenClaw config`, {
3501
+ accountId,
3502
+ error: configPersistResult.error,
3503
+ });
3354
3504
  }
3355
3505
 
3356
3506
  let backupPersistResult = { skipped: true, reason: 'missing_runtime_config_loader' };
@@ -3358,6 +3508,9 @@ export function createClaworldChannelPlugin({
3358
3508
  backupPersistResult = await persistClaworldRuntimeBackup({
3359
3509
  runtime,
3360
3510
  accountId,
3511
+ runtimeConfig,
3512
+ appToken,
3513
+ relayAgentId,
3361
3514
  });
3362
3515
  } catch (error) {
3363
3516
  backupPersistResult = {
@@ -3399,8 +3552,10 @@ export function createClaworldChannelPlugin({
3399
3552
  };
3400
3553
  }
3401
3554
 
3555
+ const backupAgentId = normalizeClaworldText(backup?.agentId, null);
3402
3556
  const restoredRuntimeConfig = applyRuntimeIdentity(runtimeConfig, {
3403
3557
  appToken: backupToken,
3558
+ agentId: backupAgentId,
3404
3559
  });
3405
3560
 
3406
3561
  try {
@@ -3408,6 +3563,8 @@ export function createClaworldChannelPlugin({
3408
3563
  runtime,
3409
3564
  accountId,
3410
3565
  appToken: backupToken,
3566
+ relayAgentId: backupAgentId,
3567
+ runtimeConfig: restoredRuntimeConfig,
3411
3568
  });
3412
3569
  } catch (error) {
3413
3570
  logger.warn?.(`[claworld:${accountId || 'default'}] failed to persist restored runtime appToken`, {
@@ -3621,6 +3778,7 @@ export function createClaworldChannelPlugin({
3621
3778
  accountId: runtimeConfig.accountId,
3622
3779
  appToken: resolveRuntimeAppToken(runtimeConfig),
3623
3780
  relayAgentId: runtimeConfig.relay?.agentId || null,
3781
+ runtimeConfig,
3624
3782
  });
3625
3783
  if (!persisted.skipped || persisted.backup?.skipped === false) {
3626
3784
  logger.info?.(`[claworld:${runtimeAccountId}] persisted runtime binding state`, {
@@ -3874,6 +4032,7 @@ async function updateRuntimePublicIdentity(context = {}) {
3874
4032
  accountId: resolvedContext.accountId || boundRuntimeConfig.accountId || null,
3875
4033
  appToken: nextAppToken,
3876
4034
  relayAgentId: nextAgentId,
4035
+ runtimeConfig: boundRuntimeConfig,
3877
4036
  });
3878
4037
  } catch (error) {
3879
4038
  logger.warn?.('[claworld:profile] failed to persist activated runtime binding', {
@@ -4208,7 +4367,6 @@ async function generateRuntimeProfileCard(context = {}) {
4208
4367
  contactable: Object.prototype.hasOwnProperty.call(context, 'contactable') ? context.contactable : undefined,
4209
4368
  chatRequestApprovalPolicy: context.chatRequestApprovalPolicy || null,
4210
4369
  proactivitySettings: Object.prototype.hasOwnProperty.call(context, 'proactivitySettings') ? context.proactivitySettings : undefined,
4211
- communicationPreference: Object.prototype.hasOwnProperty.call(context, 'communicationPreference') ? context.communicationPreference : undefined,
4212
4370
  generateShareCard: context.generateShareCard === true,
4213
4371
  expiresInSeconds: context.expiresInSeconds ?? null,
4214
4372
  fetchImpl,
@@ -4592,7 +4750,6 @@ async function generateRuntimeProfileCard(context = {}) {
4592
4750
  contactable: Object.prototype.hasOwnProperty.call(context, 'contactable') ? context.contactable : undefined,
4593
4751
  chatRequestApprovalPolicy: context.chatRequestApprovalPolicy || null,
4594
4752
  proactivitySettings: Object.prototype.hasOwnProperty.call(context, 'proactivitySettings') ? context.proactivitySettings : undefined,
4595
- communicationPreference: Object.prototype.hasOwnProperty.call(context, 'communicationPreference') ? context.communicationPreference : undefined,
4596
4753
  generateShareCard: context.generateShareCard === true,
4597
4754
  expiresInSeconds: context.expiresInSeconds ?? null,
4598
4755
  fetchImpl,
@@ -490,10 +490,6 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
490
490
  description: 'Account-level proactive-management settings.',
491
491
  additionalProperties: true,
492
492
  }),
493
- communicationPreference: objectParam({
494
- description: 'Optional account-level communication preference settings.',
495
- additionalProperties: true,
496
- }),
497
493
  targetAgentId: stringParam({
498
494
  description: 'Target agent id for subscribe_person or unsubscribe_person.',
499
495
  minLength: 1,
@@ -583,7 +579,6 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
583
579
  contactable: params.contactable,
584
580
  chatRequestApprovalPolicy: params.chatRequestApprovalPolicy || null,
585
581
  proactivitySettings: params.proactivitySettings,
586
- communicationPreference: params.communicationPreference,
587
582
  generateShareCard,
588
583
  expiresInSeconds: params.expiresInSeconds ?? null,
589
584
  });
@@ -2061,14 +2056,16 @@ function buildRegisteredTools(api, plugin) {
2061
2056
  }));
2062
2057
  }
2063
2058
 
2064
- const cfg = await loadCurrentConfig(api);
2065
- const accountId = normalizeText(params.accountId, plugin.config.defaultAccountId(cfg) || null);
2066
- const runtimeConfig = plugin.config.resolveRuntimeConfig(cfg, accountId);
2059
+ const context = await resolveToolContext(api, plugin, params);
2060
+ const cfg = context.cfg || await loadCurrentConfig(api);
2061
+ const accountId = context.accountId;
2062
+ const runtimeConfig = context.runtimeConfig || plugin.config.resolveRuntimeConfig(cfg, accountId);
2067
2063
  const identityPayload = await plugin.runtime.productShell.profile.getPublicIdentity({
2064
+ ...context,
2068
2065
  cfg,
2069
2066
  accountId,
2070
2067
  runtimeConfig,
2071
- agentId: runtimeConfig.relay?.agentId || null,
2068
+ agentId: context.agentId || runtimeConfig.relay?.agentId || null,
2072
2069
  generateShareCard,
2073
2070
  expiresInSeconds: params.expiresInSeconds ?? null,
2074
2071
  });
@@ -8,6 +8,7 @@ import {
8
8
  setClaworldManagedRuntimeBackupState,
9
9
  stripClaworldManagedRuntimeConfig,
10
10
  } from './managed-config.js';
11
+ import { resolveRuntimeAppToken } from './account-identity.js';
11
12
 
12
13
  function normalizeText(value, fallback = null) {
13
14
  if (value == null) return fallback;
@@ -67,18 +68,53 @@ export async function persistClaworldRuntimeBackup({
67
68
  runtime = null,
68
69
  accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
69
70
  configPath = resolveDefaultOpenClawConfigPath(),
71
+ runtimeConfig = null,
72
+ appToken = null,
73
+ relayAgentId = null,
70
74
  } = {}) {
71
- if (!runtime?.config?.loadConfig) {
75
+ if (!runtime?.config?.loadConfig && !runtimeConfig && !appToken) {
72
76
  return { skipped: true, reason: 'missing_runtime_config_loader' };
73
77
  }
74
78
 
75
- const currentConfig = await runtime.config.loadConfig();
76
- const { backup } = stripClaworldManagedRuntimeConfig(currentConfig, {
79
+ let currentConfig = {};
80
+ if (runtime?.config?.loadConfig) {
81
+ try {
82
+ currentConfig = await runtime.config.loadConfig();
83
+ } catch (error) {
84
+ if (!runtimeConfig && !appToken) throw error;
85
+ currentConfig = {};
86
+ }
87
+ }
88
+ const { backup: configBackup } = stripClaworldManagedRuntimeConfig(currentConfig, {
77
89
  accountId,
78
90
  preserveBackup: true,
79
91
  });
92
+ const explicitRuntimeConfig = runtimeConfig && typeof runtimeConfig === 'object' && !Array.isArray(runtimeConfig)
93
+ ? runtimeConfig
94
+ : {};
95
+ const backup = {
96
+ ...configBackup,
97
+ version: configBackup?.version || 1,
98
+ accountId: normalizeText(configBackup?.accountId, normalizeText(explicitRuntimeConfig.accountId, accountId)),
99
+ agentId: normalizeText(relayAgentId, normalizeText(configBackup?.agentId, normalizeText(explicitRuntimeConfig?.relay?.agentId, null))),
100
+ serverUrl: normalizeText(configBackup?.serverUrl, normalizeText(explicitRuntimeConfig.serverUrl, null)),
101
+ apiKey: normalizeText(configBackup?.apiKey, normalizeText(explicitRuntimeConfig.apiKey, null)),
102
+ appToken: normalizeText(appToken, normalizeText(configBackup?.appToken, resolveRuntimeAppToken(explicitRuntimeConfig))),
103
+ displayName: normalizeText(
104
+ configBackup?.displayName,
105
+ normalizeText(explicitRuntimeConfig.name, normalizeText(explicitRuntimeConfig.registration?.displayName, null)),
106
+ ),
107
+ name: normalizeText(configBackup?.name, normalizeText(explicitRuntimeConfig.name, null)),
108
+ registrationDisplayName: normalizeText(
109
+ configBackup?.registrationDisplayName,
110
+ normalizeText(explicitRuntimeConfig.registration?.displayName, null),
111
+ ),
112
+ sessionDmScope: normalizeText(configBackup?.sessionDmScope, null),
113
+ toolProfile: normalizeText(configBackup?.toolProfile, null),
114
+ preservedAt: new Date().toISOString(),
115
+ };
80
116
 
81
- if (!backup?.appToken) {
117
+ if (!backup.appToken) {
82
118
  return { skipped: true, reason: 'missing_app_token', backup: null };
83
119
  }
84
120
 
@@ -1,5 +1,5 @@
1
1
  import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
2
- import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
2
+ import { buildRuntimeAuthHeaders, resolveRuntimeAppToken } from '../plugin/account-identity.js';
3
3
  import { collectFeedbackDiagnostics } from './feedback-diagnostics.js';
4
4
  import { fetchJson, normalizeRelayHttpBaseUrl } from './http-boundary.js';
5
5
 
@@ -44,13 +44,15 @@ export async function submitFeedbackReport({
44
44
  throw new Error('fetch is unavailable for claworld feedback helper');
45
45
  }
46
46
 
47
+ const normalizedContext = normalizeObject(context);
48
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
47
49
  const resolvedAgentId = normalizeText(agentId, null);
48
50
  if (!resolvedAgentId) {
49
51
  throw new Error('claworld feedback helper requires agentId');
50
52
  }
51
-
52
- const normalizedContext = normalizeObject(context);
53
- const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
53
+ if (!resolveRuntimeAppToken(resolvedRuntimeConfig)) {
54
+ throw new Error('claworld feedback helper requires appToken');
55
+ }
54
56
  const diagnostics = await collectFeedbackDiagnostics({
55
57
  cfg,
56
58
  runtime,
@@ -193,6 +193,7 @@ function buildClaworldManagementStartupPrompt(options = {}) {
193
193
  '',
194
194
  '## First Rule',
195
195
  'When you receive a Claworld notification, management wake, lifecycle event, or recurring Claworld management task, read the `claworld-management-session` skill before deciding what to do.',
196
+ 'A pre-compaction memory flush is a maintenance turn only. It does not satisfy, replace, or change any Claworld notification. After the flush finishes, handle the pending or next Claworld notification from scratch: read the Claworld management skill first, then decide accordingly.',
196
197
  '',
197
198
  '## What To Trust',
198
199
  'Use Claworld tools when you need current product facts: account state, public profiles, worlds, memberships, chat requests, conversation status, feedback, and delivery state.',