@xfxstudio/claworld 0.2.8 → 0.2.10-beta.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 (50) hide show
  1. package/README.md +1 -1
  2. package/openclaw.plugin.json +7 -63
  3. package/package.json +6 -2
  4. package/skills/claworld-help/SKILL.md +7 -3
  5. package/skills/claworld-join-and-chat/SKILL.md +38 -9
  6. package/skills/claworld-manage-worlds/SKILL.md +81 -10
  7. package/src/lib/agent-profile.js +8 -3
  8. package/src/lib/chat-request.js +19 -1
  9. package/src/lib/policy.js +2 -6
  10. package/src/lib/public-identity.js +175 -0
  11. package/src/lib/relay/kickoff-text.js +7 -1
  12. package/src/openclaw/installer/cli.js +46 -1
  13. package/src/openclaw/installer/constants.js +1 -0
  14. package/src/openclaw/installer/core.js +234 -3
  15. package/src/openclaw/installer/doctor.js +2 -2
  16. package/src/openclaw/plugin/account-identity.js +1 -2
  17. package/src/openclaw/plugin/claworld-channel-plugin.js +302 -266
  18. package/src/openclaw/plugin/config-schema.js +9 -23
  19. package/src/openclaw/plugin/managed-config.js +284 -79
  20. package/src/openclaw/plugin/onboarding.js +22 -42
  21. package/src/openclaw/plugin/register.js +144 -25
  22. package/src/openclaw/plugin/relay-client.js +237 -18
  23. package/src/openclaw/runtime/backend-error-context.js +91 -0
  24. package/src/openclaw/runtime/feedback-helper.js +1 -2
  25. package/src/openclaw/runtime/product-shell-helper.js +43 -9
  26. package/src/openclaw/runtime/tool-contracts.js +65 -3
  27. package/src/openclaw/runtime/tool-inventory.js +8 -1
  28. package/src/openclaw/runtime/world-moderation-helper.js +3 -19
  29. package/src/product-shell/contracts/candidate-feed.js +7 -0
  30. package/src/product-shell/contracts/world-manifest.js +0 -1
  31. package/src/product-shell/contracts/world-orchestration.js +10 -1
  32. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
  33. package/src/product-shell/feedback/feedback-routes.js +0 -1
  34. package/src/product-shell/feedback/feedback-service.js +4 -9
  35. package/src/product-shell/index.js +40 -7
  36. package/src/product-shell/matching/matchmaking-service.js +22 -1
  37. package/src/product-shell/membership/membership-service.js +5 -1
  38. package/src/product-shell/onboarding/onboarding-service.js +10 -21
  39. package/src/product-shell/profile/public-identity-routes.js +60 -0
  40. package/src/product-shell/profile/public-identity-service.js +190 -0
  41. package/src/product-shell/search/search-service.js +9 -2
  42. package/src/product-shell/social/chat-request-routes.js +4 -1
  43. package/src/product-shell/social/chat-request-service.js +184 -22
  44. package/src/product-shell/social/friend-routes.js +1 -1
  45. package/src/product-shell/social/friend-service.js +16 -19
  46. package/src/product-shell/social/social-routes.js +2 -2
  47. package/src/product-shell/social/social-service.js +31 -35
  48. package/src/product-shell/worlds/world-admin-service.js +31 -10
  49. package/src/product-shell/worlds/world-broadcast-service.js +2 -2
  50. package/src/lib/agent-address.js +0 -46
@@ -0,0 +1,175 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ export const PUBLIC_IDENTITY_STATUS = Object.freeze({
4
+ PENDING: 'pending',
5
+ READY: 'ready',
6
+ });
7
+
8
+ export const PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH = 40;
9
+ export const PUBLIC_IDENTITY_CODE_LENGTH = 6;
10
+ const PUBLIC_IDENTITY_CODE_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
11
+
12
+ function normalizeText(value, fallback = null) {
13
+ if (value == null) return fallback;
14
+ const normalized = String(value).trim();
15
+ return normalized || fallback;
16
+ }
17
+
18
+ function normalizeIsoTimestamp(value, fallback = null) {
19
+ const normalized = normalizeText(value, null);
20
+ if (!normalized) return fallback;
21
+ const timestamp = Date.parse(normalized);
22
+ return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : fallback;
23
+ }
24
+
25
+ function cloneObject(value, fallback = {}) {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return { ...fallback };
27
+ return { ...value };
28
+ }
29
+
30
+ export function normalizePublicDisplayName(value, { fallback = null } = {}) {
31
+ const normalized = normalizeText(value, fallback);
32
+ if (!normalized) return fallback;
33
+ return normalized.slice(0, PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH);
34
+ }
35
+
36
+ export function validatePublicDisplayName(value) {
37
+ const normalized = normalizeText(value, null);
38
+ if (!normalized) {
39
+ return {
40
+ ok: false,
41
+ code: 'display_name_required',
42
+ message: 'displayName is required',
43
+ };
44
+ }
45
+ if (normalized.length > PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH) {
46
+ return {
47
+ ok: false,
48
+ code: 'display_name_too_long',
49
+ message: `displayName must be ${PUBLIC_IDENTITY_DISPLAY_NAME_MAX_LENGTH} characters or fewer`,
50
+ };
51
+ }
52
+ if (normalized.includes('#')) {
53
+ return {
54
+ ok: false,
55
+ code: 'display_name_reserved_character',
56
+ message: 'displayName must not include #',
57
+ };
58
+ }
59
+ if (/[\r\n\t]/.test(normalized)) {
60
+ return {
61
+ ok: false,
62
+ code: 'display_name_invalid_whitespace',
63
+ message: 'displayName must not include line breaks or tabs',
64
+ };
65
+ }
66
+ if (/[\u0000-\u001F\u007F]/.test(normalized)) {
67
+ return {
68
+ ok: false,
69
+ code: 'display_name_invalid_character',
70
+ message: 'displayName contains unsupported control characters',
71
+ };
72
+ }
73
+ return {
74
+ ok: true,
75
+ value: normalized,
76
+ };
77
+ }
78
+
79
+ export function generatePublicIdentityCode({ length = PUBLIC_IDENTITY_CODE_LENGTH } = {}) {
80
+ const targetLength = Number.isInteger(length) && length > 0 ? length : PUBLIC_IDENTITY_CODE_LENGTH;
81
+ const bytes = randomBytes(targetLength);
82
+ let output = '';
83
+ for (let index = 0; index < targetLength; index += 1) {
84
+ output += PUBLIC_IDENTITY_CODE_ALPHABET[bytes[index] % PUBLIC_IDENTITY_CODE_ALPHABET.length];
85
+ }
86
+ return output;
87
+ }
88
+
89
+ export function formatPublicIdentityDisplay({ displayName = null, code = null } = {}) {
90
+ const normalizedDisplayName = normalizeText(displayName, null);
91
+ const normalizedCode = normalizeText(code, null);
92
+ if (!normalizedDisplayName || !normalizedCode) return null;
93
+ return `${normalizedDisplayName}#${normalizedCode}`;
94
+ }
95
+
96
+ export function parsePublicIdentityDisplay(value) {
97
+ const normalized = normalizeText(value, null);
98
+ if (!normalized) return null;
99
+ const hashIndex = normalized.lastIndexOf('#');
100
+ if (hashIndex <= 0 || hashIndex >= normalized.length - 1) return null;
101
+ const displayName = normalizeText(normalized.slice(0, hashIndex), null);
102
+ const code = normalizeText(normalized.slice(hashIndex + 1), null)?.toUpperCase() || null;
103
+ if (!displayName || !code) return null;
104
+ if (displayName.includes('#')) return null;
105
+ return {
106
+ displayName,
107
+ code,
108
+ identity: `${displayName}#${code}`,
109
+ };
110
+ }
111
+
112
+ export function buildPublicIdentityRecord(input = {}, {
113
+ fallbackDisplayName = null,
114
+ statusFallback = PUBLIC_IDENTITY_STATUS.PENDING,
115
+ now = null,
116
+ } = {}) {
117
+ const source = cloneObject(input);
118
+ const normalizedDisplayName = normalizePublicDisplayName(
119
+ source.displayName,
120
+ { fallback: normalizePublicDisplayName(fallbackDisplayName, { fallback: null }) },
121
+ );
122
+ const normalizedCode = normalizeText(source.code, null)?.toUpperCase() || null;
123
+ const normalizedStatus = normalizeText(source.status, null);
124
+ const resolvedStatus = normalizedStatus === PUBLIC_IDENTITY_STATUS.READY
125
+ ? PUBLIC_IDENTITY_STATUS.READY
126
+ : normalizedStatus === PUBLIC_IDENTITY_STATUS.PENDING
127
+ ? PUBLIC_IDENTITY_STATUS.PENDING
128
+ : (normalizedCode && normalizedDisplayName ? PUBLIC_IDENTITY_STATUS.READY : statusFallback);
129
+ const fallbackTimestamp = normalizeIsoTimestamp(now, null);
130
+ const confirmedAt = normalizeIsoTimestamp(source.confirmedAt, null)
131
+ || (resolvedStatus === PUBLIC_IDENTITY_STATUS.READY ? fallbackTimestamp : null);
132
+ const updatedAt = normalizeIsoTimestamp(source.updatedAt, fallbackTimestamp);
133
+ return {
134
+ displayName: normalizedDisplayName,
135
+ code: normalizedCode,
136
+ status: resolvedStatus,
137
+ confirmedAt,
138
+ updatedAt,
139
+ };
140
+ }
141
+
142
+ export function resolvePublicIdentity(agent = {}) {
143
+ const publicIdentity = buildPublicIdentityRecord(agent?.publicIdentity, {
144
+ fallbackDisplayName: agent?.displayName || agent?.agentId || null,
145
+ now: agent?.createdAt || null,
146
+ });
147
+ return {
148
+ ...publicIdentity,
149
+ displayIdentity: formatPublicIdentityDisplay(publicIdentity),
150
+ };
151
+ }
152
+
153
+ export function isPublicIdentityReady(agent = {}) {
154
+ return resolvePublicIdentity(agent).status === PUBLIC_IDENTITY_STATUS.READY;
155
+ }
156
+
157
+ export function buildPublicIdentityMissingFields(agent = {}) {
158
+ const publicIdentity = resolvePublicIdentity(agent);
159
+ const missingFields = [];
160
+ if (!publicIdentity.displayName) {
161
+ missingFields.push({
162
+ fieldId: 'displayName',
163
+ label: 'Public Name',
164
+ description: 'A public display name used in Claworld identity surfaces.',
165
+ });
166
+ }
167
+ if (!publicIdentity.code) {
168
+ missingFields.push({
169
+ fieldId: 'code',
170
+ label: 'Public Code',
171
+ description: 'A system-generated unique suffix used in the public identity.',
172
+ });
173
+ }
174
+ return missingFields;
175
+ }
@@ -189,8 +189,10 @@ export function createAcceptedChatKickoffRuntimeContextForAgent(bundle = {}, {
189
189
  export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipient' } = {}) {
190
190
  const normalizedViewer = viewer === 'sender' ? 'sender' : 'recipient';
191
191
  const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
192
- ? bundle.requestContext
192
+ ? cloneJsonObject(bundle.requestContext) || {}
193
193
  : {};
194
+ const followUpSessionKey = normalizeText(requestContext.followUp?.sessionKey, null);
195
+ if (requestContext.followUp) delete requestContext.followUp;
194
196
  const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
195
197
  ? bundle.worldInfo
196
198
  : null;
@@ -221,9 +223,13 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
221
223
  viewerInstruction,
222
224
  normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
223
225
  formatStructuredSection('主人想让你做的事情 / 请求上下文', requestContext),
226
+ normalizedViewer === 'sender' && followUpSessionKey
227
+ ? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${followUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
228
+ : null,
224
229
  formatStructuredSection('世界信息', worldInfo),
225
230
  formatStructuredSection('我方信息', selfInfo),
226
231
  formatStructuredSection('对方信息', peerInfo),
232
+ '在正常回复中,你可以附带 [[like]] 或 [[dislike]] 来表达你对这段 conversation 的反馈。这个 token 对对方可见,系统只会按当前 conversation 和方向记录第一次有效反馈。',
227
233
  normalizedViewer === 'sender'
228
234
  ? '请你现在直接输出一条自然的开场白。不要解释规则,不要复述这些说明。'
229
235
  : '请把对方刚发来的 opener 视为这段对话的第一条 live turn,并决定是否以及如何直接回复。不要解释规则,不要复述这些说明。',
@@ -8,6 +8,7 @@ import {
8
8
  CLAWORLD_INSTALLER_BIN_NAME,
9
9
  CLAWORLD_INSTALLER_COMMAND,
10
10
  CLAWORLD_INSTALLER_PACKAGE_NAME,
11
+ CLAWORLD_UNINSTALL_COMMAND,
11
12
  CLAWORLD_UPDATE_COMMAND,
12
13
  } from './constants.js';
13
14
  import { formatClaworldDoctorReport, runClaworldDoctor } from './doctor.js';
@@ -15,6 +16,7 @@ import {
15
16
  DEFAULT_OPENCLAW_BIN,
16
17
  DEFAULT_OPENCLAW_CONFIG_PATH,
17
18
  runClaworldInstallerInstall,
19
+ runClaworldInstallerUninstall,
18
20
  runClaworldInstallerUpdate,
19
21
  } from './core.js';
20
22
 
@@ -38,6 +40,7 @@ Commands:
38
40
  install Run the installer-first Claworld setup flow
39
41
  doctor Validate the managed Claworld install health
40
42
  update Update the tracked Claworld plugin, refresh managed state, and run doctor
43
+ uninstall Safely remove the managed Claworld runtime and uninstall the plugin
41
44
  upgrade Alias for update
42
45
  help Show this help
43
46
 
@@ -74,6 +77,12 @@ Doctor options:
74
77
  --account-id <id> Managed Claworld account id (default: claworld)
75
78
  --agent-id <id> Local OpenClaw agent id bound to claworld (default: main)
76
79
  --workspace <path> Optional dedicated workspace path override
80
+ Uninstall options:
81
+ --config <path> OpenClaw config path (default: ${DEFAULT_OPENCLAW_CONFIG_PATH})
82
+ --state-dir <path> Optional OPENCLAW_STATE_DIR for OpenClaw commands
83
+ --openclaw-bin <path> OpenClaw CLI binary (default: ${DEFAULT_OPENCLAW_BIN})
84
+ --account-id <id> Managed Claworld account id (default: claworld)
85
+ --agent-id <id> Local OpenClaw agent id bound to claworld (default: main)
77
86
  --json Print machine-readable result
78
87
  --help, -h Show this help
79
88
 
@@ -81,6 +90,7 @@ Canonical commands:
81
90
  ${CLAWORLD_INSTALLER_COMMAND}
82
91
  ${CLAWORLD_DOCTOR_COMMAND}
83
92
  ${CLAWORLD_UPDATE_COMMAND}
93
+ ${CLAWORLD_UNINSTALL_COMMAND}
84
94
  `);
85
95
  }
86
96
 
@@ -132,6 +142,13 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
132
142
  agentId: 'main',
133
143
  workspace: null,
134
144
  },
145
+ uninstall: {
146
+ openclawBin: env.OPENCLAW_BIN || DEFAULT_OPENCLAW_BIN,
147
+ configPath: expandUserPath(env.OPENCLAW_CONFIG_PATH || DEFAULT_OPENCLAW_CONFIG_PATH, homeDir),
148
+ stateDir: env.OPENCLAW_STATE_DIR ? expandUserPath(env.OPENCLAW_STATE_DIR, homeDir) : null,
149
+ accountId: 'claworld',
150
+ agentId: 'main',
151
+ },
135
152
  };
136
153
 
137
154
  if (argv.length === 0) {
@@ -149,18 +166,21 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
149
166
  options.install.configPath = expandUserPath(nextValue(remaining, index), homeDir);
150
167
  options.update.configPath = options.install.configPath;
151
168
  options.doctor.configPath = options.install.configPath;
169
+ options.uninstall.configPath = options.install.configPath;
152
170
  index += 1;
153
171
  break;
154
172
  case '--state-dir':
155
173
  options.install.stateDir = expandUserPath(nextValue(remaining, index), homeDir);
156
174
  options.update.stateDir = options.install.stateDir;
157
175
  options.doctor.stateDir = options.install.stateDir;
176
+ options.uninstall.stateDir = options.install.stateDir;
158
177
  index += 1;
159
178
  break;
160
179
  case '--openclaw-bin':
161
180
  options.install.openclawBin = nextValue(remaining, index);
162
181
  options.update.openclawBin = options.install.openclawBin;
163
182
  options.doctor.openclawBin = options.install.openclawBin;
183
+ options.uninstall.openclawBin = options.install.openclawBin;
164
184
  index += 1;
165
185
  break;
166
186
  case '--server-url':
@@ -178,12 +198,14 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
178
198
  options.install.accountId = nextValue(remaining, index);
179
199
  options.update.accountId = options.install.accountId;
180
200
  options.doctor.accountId = options.install.accountId;
201
+ options.uninstall.accountId = options.install.accountId;
181
202
  index += 1;
182
203
  break;
183
204
  case '--agent-id':
184
205
  options.install.agentId = nextValue(remaining, index);
185
206
  options.update.agentId = options.install.agentId;
186
207
  options.doctor.agentId = options.install.agentId;
208
+ options.uninstall.agentId = options.install.agentId;
187
209
  index += 1;
188
210
  break;
189
211
  case '--workspace':
@@ -226,7 +248,7 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
226
248
  }
227
249
  }
228
250
 
229
- if (!['install', 'update', 'doctor', 'help'].includes(options.command)) {
251
+ if (!['install', 'update', 'uninstall', 'doctor', 'help'].includes(options.command)) {
230
252
  throw new Error(`Unknown command: ${options.command}`);
231
253
  }
232
254
  if (options.command === 'install' && !['npm', 'link', 'copy', 'skip'].includes(options.install.pluginInstallMode)) {
@@ -304,6 +326,19 @@ function printUpdateSummary(result, doctorResult) {
304
326
  console.log('');
305
327
  }
306
328
 
329
+ function printUninstallSummary(result) {
330
+ console.log('');
331
+ console.log('Claworld uninstall complete');
332
+ console.log('===========================');
333
+ console.log(`Managed account: ${result.transformed?.backup?.accountId || 'claworld'}`);
334
+ console.log(`OpenClaw version: ${result.host?.version || '(unknown)'}`);
335
+ console.log(`Plugin action: ${result.plugin?.action || 'unknown'}`);
336
+ console.log(`Runtime refresh: ${result.runtimeRefresh?.action || 'unknown'}`);
337
+ console.log(`Config path: ${result.configPath}`);
338
+ console.log(`Backup path: ${result.backupPath || '(unchanged or new file)'}`);
339
+ console.log('');
340
+ }
341
+
307
342
  export async function runInstallerCli(argv = process.argv.slice(2), env = process.env) {
308
343
  const parsed = parseInstallerCliArgs(argv, env);
309
344
  if (parsed.command === 'help') {
@@ -347,6 +382,16 @@ export async function runInstallerCli(argv = process.argv.slice(2), env = proces
347
382
  };
348
383
  }
349
384
 
385
+ if (parsed.command === 'uninstall') {
386
+ const result = await runClaworldInstallerUninstall(parsed.uninstall);
387
+ if (parsed.json) {
388
+ console.log(JSON.stringify(result, null, 2));
389
+ } else {
390
+ printUninstallSummary(result);
391
+ }
392
+ return result;
393
+ }
394
+
350
395
  const result = await runClaworldDoctor(parsed.doctor);
351
396
  if (parsed.json) {
352
397
  console.log(JSON.stringify(result, null, 2));
@@ -3,4 +3,5 @@ export const CLAWORLD_INSTALLER_PACKAGE_NAME = '@xfxstudio/claworld';
3
3
  export const CLAWORLD_INSTALLER_COMMAND = 'npx -y @xfxstudio/claworld install';
4
4
  export const CLAWORLD_DOCTOR_COMMAND = 'npx -y @xfxstudio/claworld doctor';
5
5
  export const CLAWORLD_UPDATE_COMMAND = 'npx -y @xfxstudio/claworld update';
6
+ export const CLAWORLD_UNINSTALL_COMMAND = 'npx -y @xfxstudio/claworld uninstall';
6
7
  export const CLAWORLD_OPENCLAW_MIN_HOST_VERSION = '>=2026.3.22';