@xfxstudio/claworld 0.1.4 → 0.2.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 (55) hide show
  1. package/README.md +12 -29
  2. package/openclaw.plugin.json +9 -33
  3. package/package.json +2 -10
  4. package/skills/claworld-help/SKILL.md +86 -160
  5. package/skills/claworld-join-and-chat/SKILL.md +107 -203
  6. package/skills/claworld-manage-worlds/SKILL.md +75 -392
  7. package/src/lib/chat-request.js +347 -0
  8. package/src/lib/{accepted-chat-kickoff.js → relay/kickoff-text.js} +67 -26
  9. package/src/openclaw/index.js +0 -5
  10. package/src/openclaw/installer/cli.js +14 -16
  11. package/src/openclaw/installer/core.js +13 -14
  12. package/src/openclaw/installer/doctor.js +69 -31
  13. package/src/openclaw/installer/workspace-contract.js +33 -9
  14. package/src/openclaw/plugin/claworld-channel-plugin.js +156 -625
  15. package/src/openclaw/plugin/config-schema.js +4 -16
  16. package/src/openclaw/plugin/managed-config.js +127 -75
  17. package/src/openclaw/plugin/onboarding.js +7 -3
  18. package/src/openclaw/plugin/register.js +40 -339
  19. package/src/openclaw/plugin/relay-client.js +112 -102
  20. package/src/openclaw/protocol/relay-event-protocol.js +34 -22
  21. package/src/openclaw/runtime/canonical-result-builder.js +15 -5
  22. package/src/openclaw/runtime/demo-session-bootstrap.js +0 -4
  23. package/src/openclaw/runtime/feedback-helper.js +3 -2
  24. package/src/openclaw/runtime/inbound-session-router.js +28 -20
  25. package/src/openclaw/runtime/outbound-session-bridge.js +21 -9
  26. package/src/openclaw/runtime/product-shell-helper.js +45 -637
  27. package/src/openclaw/runtime/runtime-path.js +2 -2
  28. package/src/openclaw/runtime/system-message-orchestrator.js +1 -1
  29. package/src/openclaw/runtime/tool-contracts.js +36 -258
  30. package/src/openclaw/runtime/world-moderation-helper.js +11 -65
  31. package/src/product-shell/catalog/default-world-catalog.js +15 -33
  32. package/src/product-shell/contracts/candidate-feed.js +40 -5
  33. package/src/product-shell/contracts/chat-request-approval-policy.js +3 -3
  34. package/src/product-shell/contracts/world-manifest.js +134 -161
  35. package/src/product-shell/contracts/world-orchestration.js +55 -326
  36. package/src/product-shell/feedback/feedback-routes.js +4 -3
  37. package/src/product-shell/feedback/feedback-service.js +11 -8
  38. package/src/product-shell/index.js +6 -7
  39. package/src/product-shell/matching/matchmaking-service.js +39 -5
  40. package/src/product-shell/membership/membership-service.js +125 -147
  41. package/src/product-shell/onboarding/onboarding-service.js +2 -2
  42. package/src/product-shell/orchestration/world-conversation-orchestrator.js +30 -0
  43. package/src/product-shell/orchestration/world-conversation-text.js +231 -0
  44. package/src/product-shell/results/result-service.js +9 -3
  45. package/src/product-shell/search/search-service.js +28 -1
  46. package/src/product-shell/social/chat-request-routes.js +0 -1
  47. package/src/product-shell/social/chat-request-service.js +1 -102
  48. package/src/product-shell/worlds/world-admin-service.js +86 -277
  49. package/src/product-shell/worlds/world-authorization.js +3 -5
  50. package/src/product-shell/worlds/world-routes.js +8 -38
  51. package/src/product-shell/worlds/world-service.js +3 -3
  52. package/src/product-shell/worlds/world-text.js +77 -0
  53. package/src/lib/runtime-guidance.js +0 -457
  54. package/src/openclaw/runtime/world-session-startup.js +0 -1
  55. package/src/product-shell/orchestration/session-orchestrator.js +0 -38
@@ -0,0 +1,231 @@
1
+ const DEFAULT_TEMPLATE_REFS = {
2
+ opening: 'world.conversation.opening',
3
+ convergence: 'world.conversation.convergence',
4
+ stateChanged: 'world.conversation.state_changed',
5
+ };
6
+
7
+ function normalizeText(value, fallback = null) {
8
+ if (value == null) return fallback;
9
+ const normalized = String(value).trim();
10
+ return normalized || fallback;
11
+ }
12
+
13
+ function normalizeInteger(value, fallback = null) {
14
+ const normalized = Number(value);
15
+ if (!Number.isFinite(normalized)) return fallback;
16
+ return Math.trunc(normalized);
17
+ }
18
+
19
+ function normalizePositiveInteger(value, fallback = null) {
20
+ const normalized = normalizeInteger(value, fallback);
21
+ if (normalized == null || normalized <= 0) return fallback;
22
+ return normalized;
23
+ }
24
+
25
+ function normalizeTurnRule(rule = {}, index = 0) {
26
+ return {
27
+ id: rule.id || `turn_rule_${index + 1}`,
28
+ trigger: rule.trigger || 'turn_threshold',
29
+ atTurn: Number.isFinite(Number(rule.atTurn)) ? Math.max(0, Number(rule.atTurn)) : null,
30
+ visibility: rule.visibility || 'both',
31
+ role: rule.role || 'system',
32
+ templateRef: rule.templateRef || `world.turn.rule.${index + 1}`,
33
+ text: rule.text || null,
34
+ once: rule.once !== false,
35
+ };
36
+ }
37
+
38
+ function buildMessage({
39
+ conversationId = null,
40
+ trigger,
41
+ role = 'system',
42
+ visibility = 'both',
43
+ templateRef = null,
44
+ text = null,
45
+ metadata = {},
46
+ }) {
47
+ return {
48
+ conversationId: normalizeText(conversationId, null),
49
+ trigger,
50
+ role,
51
+ visibility,
52
+ templateRef,
53
+ text,
54
+ metadata,
55
+ };
56
+ }
57
+
58
+ function formatConversationOverview(detail = {}) {
59
+ const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
60
+ ? detail.conversationOverview
61
+ : {};
62
+ const mode = normalizeText(detail.conversationMode || conversationOverview.mode, null);
63
+ const parts = [];
64
+
65
+ if (mode) parts.push(`${mode} mode`);
66
+
67
+ return parts.length > 0 ? parts.join(', ') : null;
68
+ }
69
+
70
+ export function buildWorldConversationContextEvent(detail = {}) {
71
+ const worldId = normalizeText(detail.worldId || detail.world?.worldId, null);
72
+ if (!worldId) return null;
73
+ const worldContextText = normalizeText(
74
+ detail.world?.worldContextText,
75
+ normalizeText(detail.worldContextText, null),
76
+ );
77
+ if (worldContextText) return worldContextText;
78
+
79
+ const displayName = normalizeText(detail.displayName || detail.world?.displayName || detail.worldDisplayName, worldId);
80
+ const summary = normalizeText(detail.summary || detail.world?.summary, null);
81
+ const sessionSummary = formatConversationOverview(detail);
82
+ const conversationOverview = detail.conversationOverview && typeof detail.conversationOverview === 'object'
83
+ ? detail.conversationOverview
84
+ : {};
85
+ const openingText = normalizeText(conversationOverview.openingText, null);
86
+ const convergenceText = normalizeText(conversationOverview.convergence?.text, null);
87
+ const interactionRules = normalizeText(detail.interactionRules, null);
88
+ const prohibitedRules = normalizeText(detail.prohibitedRules, null);
89
+ const ratingRules = normalizeText(detail.ratingRules, null);
90
+
91
+ const lines = [
92
+ 'Internal Claworld world context for this conversation.',
93
+ 'Do not acknowledge, paraphrase, or announce this setup to the peer unless it is directly relevant to their message.',
94
+ `World: ${displayName} [${worldId}]`,
95
+ summary ? `Summary: ${summary}` : null,
96
+ sessionSummary ? `Conversation overview: ${sessionSummary}` : null,
97
+ 'Interruption handling: prefer reconnect/resume. Temporary silence or reconnect churn is not the normal way to close a round.',
98
+ openingText ? `Opening focus: ${openingText}` : null,
99
+ interactionRules ? `Interaction rules: ${interactionRules}` : null,
100
+ prohibitedRules ? `Prohibited rules: ${prohibitedRules}` : null,
101
+ ratingRules ? `Rating rules: ${ratingRules}` : null,
102
+ convergenceText ? `Convergence rule: ${convergenceText}` : null,
103
+ 'Apply these world rules symmetrically when responding in this conversation.',
104
+ ].filter(Boolean);
105
+
106
+ return lines.join('\n');
107
+ }
108
+
109
+ export function createSystemMessageOrchestrator({ templateRefs = DEFAULT_TEMPLATE_REFS } = {}) {
110
+ return {
111
+ supportedTriggers: ['conversation_started', 'turn_threshold', 'convergence', 'state_changed'],
112
+ describeRuleShape() {
113
+ return {
114
+ opening_system_message: 'optional text/template ref',
115
+ turn_message_rules: [
116
+ {
117
+ id: 'turn_nudge_2',
118
+ trigger: 'turn_threshold',
119
+ atTurn: 2,
120
+ visibility: 'both',
121
+ role: 'system',
122
+ templateRef: 'world.turn.nudge',
123
+ once: true,
124
+ },
125
+ ],
126
+ convergence_message: {
127
+ whenRemainingTurnsLTE: 1,
128
+ templateRef: templateRefs.convergence,
129
+ },
130
+ state_change_messages: {
131
+ active_to_review: templateRefs.stateChanged,
132
+ },
133
+ };
134
+ },
135
+ planMessages({
136
+ conversationId = null,
137
+ trigger = 'conversation_started',
138
+ turnIndex = 0,
139
+ remainingTurns = null,
140
+ worldRules = {},
141
+ previousState = null,
142
+ nextState = null,
143
+ emittedRuleIds = [],
144
+ } = {}) {
145
+ const resolvedConversationId = normalizeText(conversationId, null);
146
+ const messages = [];
147
+ const emitted = new Set(emittedRuleIds);
148
+
149
+ if (trigger === 'conversation_started' && worldRules.openingSystemMessage !== false) {
150
+ messages.push(
151
+ buildMessage({
152
+ conversationId: resolvedConversationId,
153
+ trigger,
154
+ templateRef: worldRules.openingTemplateRef || templateRefs.opening,
155
+ text: worldRules.openingText || null,
156
+ metadata: { phase: 'opening' },
157
+ }),
158
+ );
159
+ }
160
+
161
+ const rules = Array.isArray(worldRules.turnMessageRules)
162
+ ? worldRules.turnMessageRules.map((rule, index) => normalizeTurnRule(rule, index))
163
+ : [];
164
+ if (trigger === 'turn_threshold') {
165
+ for (const rule of rules) {
166
+ if (rule.trigger !== 'turn_threshold') continue;
167
+ if (rule.atTurn == null || turnIndex < rule.atTurn) continue;
168
+ if (rule.once && emitted.has(rule.id)) continue;
169
+ messages.push(
170
+ buildMessage({
171
+ conversationId: resolvedConversationId,
172
+ trigger,
173
+ role: rule.role,
174
+ visibility: rule.visibility,
175
+ templateRef: rule.templateRef,
176
+ text: rule.text,
177
+ metadata: { ruleId: rule.id, atTurn: rule.atTurn },
178
+ }),
179
+ );
180
+ emitted.add(rule.id);
181
+ }
182
+ }
183
+
184
+ const convergenceThreshold = Number.isFinite(Number(worldRules.convergence?.whenRemainingTurnsLTE))
185
+ ? Number(worldRules.convergence.whenRemainingTurnsLTE)
186
+ : 1;
187
+ if (
188
+ trigger === 'convergence' &&
189
+ remainingTurns != null &&
190
+ Number(remainingTurns) <= convergenceThreshold
191
+ ) {
192
+ messages.push(
193
+ buildMessage({
194
+ conversationId: resolvedConversationId,
195
+ trigger,
196
+ templateRef: worldRules.convergence?.templateRef || templateRefs.convergence,
197
+ text: worldRules.convergence?.text || null,
198
+ metadata: { remainingTurns: Number(remainingTurns) },
199
+ }),
200
+ );
201
+ }
202
+
203
+ if (trigger === 'state_changed' && previousState !== nextState && nextState) {
204
+ const stateKey = `${previousState || 'unknown'}_to_${nextState}`;
205
+ messages.push(
206
+ buildMessage({
207
+ conversationId: resolvedConversationId,
208
+ trigger,
209
+ templateRef:
210
+ worldRules.stateChangeMessages?.[stateKey]?.templateRef || templateRefs.stateChanged,
211
+ text: worldRules.stateChangeMessages?.[stateKey]?.text || null,
212
+ metadata: {
213
+ previousState,
214
+ nextState,
215
+ stateKey,
216
+ },
217
+ }),
218
+ );
219
+ }
220
+
221
+ return {
222
+ conversationId: resolvedConversationId,
223
+ turnIndex,
224
+ trigger,
225
+ emittedRuleIds: [...emitted],
226
+ messages,
227
+ status: messages.length > 0 ? 'planned' : 'noop',
228
+ };
229
+ },
230
+ };
231
+ }
@@ -3,13 +3,19 @@ import { createCanonicalResultBuilder } from '../../openclaw/runtime/canonical-r
3
3
  export function createResultService({ builder = createCanonicalResultBuilder() } = {}) {
4
4
  return {
5
5
  schema: builder.schema,
6
- preview({ world, sessionId = 'ses_preview' } = {}) {
7
- return builder.build({
8
- sessionId,
6
+ previewConversation({ world, conversationKey = 'cnv_preview' } = {}) {
7
+ const preview = builder.build({
8
+ conversationId: conversationKey,
9
9
  intentSignals: world.resultContract.exampleSignals.intentSignals,
10
10
  conversationSignals: world.resultContract.exampleSignals.conversationSignals,
11
11
  agentSignals: world.resultContract.exampleSignals.agentSignals,
12
12
  });
13
+ const rest = { ...preview };
14
+ delete rest.conversationId;
15
+ return {
16
+ ...rest,
17
+ conversationKey,
18
+ };
13
19
  },
14
20
  };
15
21
  }
@@ -37,6 +37,16 @@ function normalizeComparableText(value) {
37
37
  return normalizeText(value, null)?.toLowerCase() || null;
38
38
  }
39
39
 
40
+ function tokenizeText(value) {
41
+ return [...new Set(
42
+ String(value || '')
43
+ .toLowerCase()
44
+ .split(/[^a-z0-9\u4e00-\u9fff]+/i)
45
+ .map((entry) => entry.trim())
46
+ .filter((entry) => entry.length >= 2),
47
+ )];
48
+ }
49
+
40
50
  function normalizeSearchLimit(limit, fallback = 10) {
41
51
  const normalized = normalizeInteger(limit, fallback);
42
52
  if (normalized <= 0) return fallback;
@@ -133,6 +143,23 @@ function compareField(field, queryValue, candidateValue, worldId) {
133
143
 
134
144
  const weights = resolveFieldWeight(worldId, field);
135
145
 
146
+ if (field.fieldId === 'participantContextText') {
147
+ const queryTokens = tokenizeText(queryValue);
148
+ const candidateTokens = tokenizeText(candidateValue);
149
+ const sharedValues = queryTokens.filter((entry) => candidateTokens.includes(entry));
150
+ if (sharedValues.length === 0) return null;
151
+
152
+ return {
153
+ fieldId: field.fieldId,
154
+ label: field.label,
155
+ matchType: 'overlap',
156
+ queryValue: normalizeText(queryValue, ''),
157
+ candidateValue: normalizeText(candidateValue, ''),
158
+ sharedValues,
159
+ contribution: Math.min(sharedValues.length * 8, 48),
160
+ };
161
+ }
162
+
136
163
  if (field.type === 'string[]') {
137
164
  const queryItems = normalizeStringList(queryValue);
138
165
  const candidateItems = normalizeStringList(candidateValue);
@@ -180,7 +207,7 @@ function projectSummaryField(field, profile = {}) {
180
207
  function projectProfileSummary(world, profile = {}, agent = null) {
181
208
  return {
182
209
  displayName: normalizeText(agent?.displayName, null),
183
- headline: normalizeText(profile.headline, null),
210
+ headline: normalizeText(profile.participantContextText, normalizeText(profile.headline, null)),
184
211
  requiredFields: world.joinSchema.requiredFields
185
212
  .map((field) => projectSummaryField(field, profile))
186
213
  .filter(Boolean),
@@ -57,7 +57,6 @@ export function registerChatRequestRoutes(app, { chatRequestService, store }) {
57
57
  kickoffBrief: req.body?.kickoffBrief,
58
58
  openingMessage: req.body?.openingMessage,
59
59
  worldId: req.body?.worldId,
60
- episodePolicy: req.body?.episodePolicy,
61
60
  });
62
61
  res.status(201).json(result);
63
62
  } catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
2
- import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/accepted-chat-kickoff.js';
2
+ import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/relay/kickoff-text.js';
3
3
  import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
4
4
  import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
5
5
  import {
@@ -39,87 +39,6 @@ function normalizeConversationWorldId(value) {
39
39
  return normalizeText(value, null);
40
40
  }
41
41
 
42
- function parseEpisodePolicy(episodePolicy = null) {
43
- if (episodePolicy == null) return { value: null, error: null };
44
- if (!episodePolicy || typeof episodePolicy !== 'object' || Array.isArray(episodePolicy)) {
45
- return {
46
- value: null,
47
- error: {
48
- code: 'chat_request_episode_policy_invalid',
49
- message: 'episodePolicy must be an object',
50
- },
51
- };
52
- }
53
-
54
- const hasMaxTurns = Object.prototype.hasOwnProperty.call(episodePolicy, 'maxTurns');
55
- const hasTurnTimeoutMs = Object.prototype.hasOwnProperty.call(episodePolicy, 'turnTimeoutMs');
56
- const hasRaiseHandPolicy = Object.prototype.hasOwnProperty.call(episodePolicy, 'raiseHandPolicy');
57
-
58
- const maxTurns = !hasMaxTurns
59
- ? null
60
- : episodePolicy.maxTurns === null
61
- ? null
62
- : normalizePositiveInteger(episodePolicy.maxTurns, null);
63
- if (hasMaxTurns && episodePolicy.maxTurns != null && maxTurns == null) {
64
- return {
65
- value: null,
66
- error: {
67
- code: 'chat_request_episode_policy_invalid',
68
- message: 'episodePolicy.maxTurns must be a positive integer',
69
- },
70
- };
71
- }
72
-
73
- const turnTimeoutMs = !hasTurnTimeoutMs
74
- ? null
75
- : episodePolicy.turnTimeoutMs === null
76
- ? null
77
- : normalizePositiveInteger(episodePolicy.turnTimeoutMs, null);
78
- if (hasTurnTimeoutMs && episodePolicy.turnTimeoutMs != null && turnTimeoutMs == null) {
79
- return {
80
- value: null,
81
- error: {
82
- code: 'chat_request_episode_policy_invalid',
83
- message: 'episodePolicy.turnTimeoutMs must be a positive integer',
84
- },
85
- };
86
- }
87
-
88
- if (
89
- hasRaiseHandPolicy
90
- && episodePolicy.raiseHandPolicy != null
91
- && (
92
- typeof episodePolicy.raiseHandPolicy !== 'object'
93
- || Array.isArray(episodePolicy.raiseHandPolicy)
94
- )
95
- ) {
96
- return {
97
- value: null,
98
- error: {
99
- code: 'chat_request_episode_policy_invalid',
100
- message: 'episodePolicy.raiseHandPolicy must be an object',
101
- },
102
- };
103
- }
104
-
105
- const raiseHandPolicy = cloneJsonObject(episodePolicy.raiseHandPolicy);
106
- const value = {
107
- ...(maxTurns != null ? { maxTurns } : {}),
108
- ...(turnTimeoutMs != null ? { turnTimeoutMs } : {}),
109
- ...(raiseHandPolicy ? { raiseHandPolicy } : {}),
110
- };
111
-
112
- return {
113
- value: Object.keys(value).length > 0 ? value : null,
114
- error: null,
115
- };
116
- }
117
-
118
- function projectEpisodePolicy(conversation = {}) {
119
- const parsed = parseEpisodePolicy(conversation);
120
- return parsed.error ? null : parsed.value;
121
- }
122
-
123
42
  function sortByRecency(items = [], ...fieldNames) {
124
43
  return items.slice().sort((left, right) => {
125
44
  for (const fieldName of fieldNames) {
@@ -267,8 +186,6 @@ function projectKickoff(kickoff = {}) {
267
186
  return {
268
187
  status: normalizeText(kickoff.status, 'skipped'),
269
188
  deliveredAt: normalizeText(kickoff.deliveredAt, null),
270
- relaySessionPreparedAt: normalizeText(kickoff.relaySessionPreparedAt, null),
271
- acceptedRoundPreparedAt: normalizeText(kickoff.acceptedRoundPreparedAt, null),
272
189
  senderKickoffDeliveredAt: normalizeText(kickoff.senderKickoffDeliveredAt, normalizeText(kickoff.deliveredAt, null)),
273
190
  openerAcceptedAt: normalizeText(kickoff.openerAcceptedAt, null),
274
191
  openerDeliveredAt: normalizeText(kickoff.openerDeliveredAt, null),
@@ -399,8 +316,6 @@ export function createChatRequestService({
399
316
  : {};
400
317
  const worldId = normalizeConversationWorldId(conversation.worldId)
401
318
  || normalizeConversationWorldId(requestContext.conversation?.worldId);
402
- const episodePolicy = projectEpisodePolicy(conversation)
403
- || projectEpisodePolicy(requestContext.conversation || {});
404
319
  const counterpartyAgentId =
405
320
  viewerAgentId === request.fromAgentId
406
321
  ? request.toAgentId
@@ -430,7 +345,6 @@ export function createChatRequestService({
430
345
  mode: worldId ? 'world' : 'direct',
431
346
  worldId,
432
347
  world: projectWorldSummary(worldService, worldId),
433
- ...(episodePolicy ? { episodePolicy } : {}),
434
348
  },
435
349
  };
436
350
  }
@@ -474,7 +388,6 @@ export function createChatRequestService({
474
388
  openingMessage = null,
475
389
  openingPayload = null,
476
390
  worldId = null,
477
- episodePolicy = null,
478
391
  origin = null,
479
392
  broadcast = null,
480
393
  source = 'chat_request',
@@ -505,20 +418,7 @@ export function createChatRequestService({
505
418
  });
506
419
  const normalizedOrigin = normalizeChatRequestOrigin(origin);
507
420
  const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast);
508
- const normalizedEpisodePolicy = parseEpisodePolicy(episodePolicy);
509
- if (normalizedEpisodePolicy.error) {
510
- throw createInvalidChatRequestError(
511
- normalizedEpisodePolicy.error.code,
512
- normalizedEpisodePolicy.error.message,
513
- );
514
- }
515
421
  if (normalizedWorldId) {
516
- if (normalizedEpisodePolicy.value) {
517
- throw createInvalidChatRequestError(
518
- 'world_chat_request_episode_policy_not_supported',
519
- 'world-scoped chat requests inherit episode policy from the world template',
520
- );
521
- }
522
422
  worldService?.requireWorld?.(normalizedWorldId);
523
423
  if (normalizeText(source, 'chat_request') !== 'world_broadcast') {
524
424
  const authorization = worldAuthorizationService.evaluateWorldAction({
@@ -543,7 +443,6 @@ export function createChatRequestService({
543
443
  openingPayload: cloneJsonObject(normalizedKickoffBrief?.payload) || normalizedOpeningPayload,
544
444
  conversation: {
545
445
  ...(normalizedWorldId ? { worldId: normalizedWorldId } : {}),
546
- ...(normalizedEpisodePolicy.value || {}),
547
446
  },
548
447
  origin: normalizedOrigin,
549
448
  broadcast: normalizedBroadcast,