@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,98 @@
1
+ const DEFAULT_MODE = 'manual_review';
2
+ const SUPPORTED_MODES = Object.freeze([
3
+ DEFAULT_MODE,
4
+ 'world_only',
5
+ 'trusted_only',
6
+ 'trusted_or_world',
7
+ 'open',
8
+ ]);
9
+ const SUPPORTED_ORIGIN_TYPES = Object.freeze([
10
+ 'chat_request',
11
+ 'world_broadcast',
12
+ ]);
13
+
14
+ function normalizeText(value, fallback = null) {
15
+ if (value == null) return fallback;
16
+ const normalized = String(value).trim();
17
+ return normalized || fallback;
18
+ }
19
+
20
+ function normalizeToken(value, fallback = null) {
21
+ const normalized = normalizeText(value, fallback);
22
+ return normalized ? normalized.toLowerCase().replace(/[\s-]+/g, '_') : fallback;
23
+ }
24
+
25
+ function uniqueStrings(values = [], normalizer = (value) => normalizeText(value, null)) {
26
+ const seen = new Set();
27
+ const items = [];
28
+ for (const value of Array.isArray(values) ? values : []) {
29
+ const normalized = normalizer(value);
30
+ if (!normalized || seen.has(normalized)) continue;
31
+ seen.add(normalized);
32
+ items.push(normalized);
33
+ }
34
+ return items;
35
+ }
36
+
37
+ export const CHAT_REQUEST_APPROVAL_POLICY_SCHEMA_VERSION = 1;
38
+ export const CHAT_REQUEST_APPROVAL_POLICY_MODES = SUPPORTED_MODES;
39
+ export const CHAT_REQUEST_APPROVAL_POLICY_ORIGIN_TYPES = SUPPORTED_ORIGIN_TYPES;
40
+ export const DEFAULT_CHAT_REQUEST_APPROVAL_POLICY_MODE = DEFAULT_MODE;
41
+
42
+ export function normalizeChatRequestApprovalMode(value, fallback = DEFAULT_MODE) {
43
+ const normalized = normalizeToken(value, null);
44
+ switch (normalized) {
45
+ case 'manual':
46
+ case 'manual_review':
47
+ case 'review':
48
+ return DEFAULT_MODE;
49
+ case 'world':
50
+ case 'world_only':
51
+ return 'world_only';
52
+ case 'trusted':
53
+ case 'trusted_only':
54
+ return 'trusted_only';
55
+ case 'trusted_or_world':
56
+ case 'world_or_trusted':
57
+ return 'trusted_or_world';
58
+ case 'open':
59
+ case 'auto_accept':
60
+ case 'all':
61
+ return 'open';
62
+ default:
63
+ return SUPPORTED_MODES.includes(fallback) ? fallback : DEFAULT_MODE;
64
+ }
65
+ }
66
+
67
+ export function normalizeChatRequestApprovalOriginType(value, fallback = null) {
68
+ const normalized = normalizeToken(value, fallback);
69
+ return SUPPORTED_ORIGIN_TYPES.includes(normalized) ? normalized : fallback;
70
+ }
71
+
72
+ export function normalizeChatRequestApprovalBlocks(value = {}) {
73
+ const candidate = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
74
+ return {
75
+ originTypes: uniqueStrings(candidate.originTypes, (entry) => normalizeChatRequestApprovalOriginType(entry, null)).sort(),
76
+ worldIds: uniqueStrings(candidate.worldIds, (entry) => normalizeText(entry, null)).sort(),
77
+ };
78
+ }
79
+
80
+ export function normalizeChatRequestApprovalPolicy(
81
+ value = {},
82
+ { fallbackMode = DEFAULT_MODE, legacyAutoAccept = null } = {},
83
+ ) {
84
+ const candidate = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
85
+ const normalizedLegacyAutoAccept = typeof legacyAutoAccept === 'boolean' ? legacyAutoAccept : null;
86
+ const mode = normalizeText(candidate.mode, null)
87
+ ? normalizeChatRequestApprovalMode(candidate.mode, fallbackMode)
88
+ : normalizedLegacyAutoAccept === true
89
+ ? 'open'
90
+ : normalizedLegacyAutoAccept === false
91
+ ? DEFAULT_MODE
92
+ : normalizeChatRequestApprovalMode(fallbackMode, DEFAULT_MODE);
93
+
94
+ return {
95
+ mode,
96
+ blocks: normalizeChatRequestApprovalBlocks(candidate.blocks),
97
+ };
98
+ }
@@ -0,0 +1,435 @@
1
+ import { projectCandidateFeedModel } from './candidate-feed.js';
2
+ import { buildWorldSessionStartupText } from './world-orchestration.js';
3
+
4
+ function normalizeText(value, fallback = null) {
5
+ if (value == null) return fallback;
6
+ const normalized = String(value).trim();
7
+ return normalized || fallback;
8
+ }
9
+
10
+ function normalizeStringList(values = []) {
11
+ if (!Array.isArray(values)) return [];
12
+ return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
13
+ }
14
+
15
+ function normalizeWorldEligibility(value, fallback = 'active') {
16
+ const normalized = normalizeText(value, fallback);
17
+ if (normalized === 'joined') return 'joined';
18
+ return 'active';
19
+ }
20
+
21
+ function normalizeBroadcastAudience(value, fallback = 'members') {
22
+ const normalized = normalizeText(value, fallback);
23
+ if (normalized === 'admins') return 'admins';
24
+ if (normalized === 'admins_and_owner') return 'admins_and_owner';
25
+ return 'members';
26
+ }
27
+
28
+ function normalizeBroadcastReplyPolicy(value, fallback = 'zero') {
29
+ const normalized = normalizeText(value, fallback);
30
+ if (normalized === 'at_most_one') return 'at_most_one';
31
+ return 'zero';
32
+ }
33
+
34
+ function normalizeBroadcastConfig(broadcast = {}, { enabled = false } = {}) {
35
+ const normalized = broadcast && typeof broadcast === 'object' && !Array.isArray(broadcast)
36
+ ? broadcast
37
+ : {};
38
+
39
+ return {
40
+ enabled: normalized.enabled === true || enabled === true,
41
+ audience: normalizeBroadcastAudience(normalized.audience, 'members'),
42
+ replyPolicy: normalizeBroadcastReplyPolicy(normalized.replyPolicy, 'zero'),
43
+ excludeSelf: normalized.excludeSelf !== false,
44
+ };
45
+ }
46
+
47
+ function normalizeField(field = {}, index = 0, { required = false } = {}) {
48
+ const fieldId = normalizeText(field.fieldId || field.id, `field_${index + 1}`);
49
+ return {
50
+ fieldId,
51
+ label: normalizeText(field.label, fieldId),
52
+ type: normalizeText(field.type, 'string'),
53
+ source: normalizeText(field.source, 'profile'),
54
+ required: field.required === true || required,
55
+ description: normalizeText(field.description, null),
56
+ examples: normalizeStringList(field.examples),
57
+ constraints: field.constraints && typeof field.constraints === 'object' ? field.constraints : {},
58
+ };
59
+ }
60
+
61
+ function normalizeRole(role = {}, index = 0) {
62
+ const roleId = normalizeText(role.roleId || role.id, `role_${index + 1}`);
63
+ return {
64
+ roleId,
65
+ label: normalizeText(role.label, roleId),
66
+ objective: normalizeText(role.objective, null),
67
+ promptSummary: normalizeText(role.promptSummary, null),
68
+ };
69
+ }
70
+
71
+ function normalizeExampleSignals(exampleSignals = {}) {
72
+ const normalizeGroup = (key) => (Array.isArray(exampleSignals[key]) ? exampleSignals[key] : []);
73
+ return {
74
+ intentSignals: normalizeGroup('intentSignals'),
75
+ conversationSignals: normalizeGroup('conversationSignals'),
76
+ agentSignals: normalizeGroup('agentSignals'),
77
+ };
78
+ }
79
+
80
+ function normalizeJoinSchema(joinSchema = {}) {
81
+ const requiredFields = Array.isArray(joinSchema.requiredFields)
82
+ ? joinSchema.requiredFields.map((field, index) => normalizeField(field, index, { required: true }))
83
+ : [];
84
+ const optionalFields = Array.isArray(joinSchema.optionalFields)
85
+ ? joinSchema.optionalFields.map((field, index) => normalizeField(field, index, { required: false }))
86
+ : [];
87
+
88
+ return {
89
+ requiredFields,
90
+ optionalFields,
91
+ hints: normalizeStringList(joinSchema.hints),
92
+ };
93
+ }
94
+
95
+ function normalizeSearchSchema(searchSchema = {}, joinSchema = {}) {
96
+ const fieldLookup = new Map(
97
+ [...joinSchema.requiredFields, ...joinSchema.optionalFields].map((field) => [field.fieldId, field]),
98
+ );
99
+ const rawInputFieldIds = Array.isArray(searchSchema.inputFieldIds)
100
+ ? searchSchema.inputFieldIds
101
+ : (Array.isArray(searchSchema.inputFields)
102
+ ? searchSchema.inputFields.map((field) => (typeof field === 'string' ? field : field.fieldId || field.id))
103
+ : joinSchema.requiredFields.map((field) => field.fieldId));
104
+ const inputFieldIds = normalizeStringList(rawInputFieldIds);
105
+ const inputFields = inputFieldIds.map((fieldId, index) => {
106
+ const referenced = fieldLookup.get(fieldId);
107
+ if (referenced) return { ...referenced };
108
+ return normalizeField({ fieldId, label: fieldId }, index, { required: false });
109
+ });
110
+
111
+ return {
112
+ mode: normalizeText(searchSchema.mode, 'membership_profile_search'),
113
+ inputFieldIds,
114
+ inputFields,
115
+ resultFields: normalizeStringList(
116
+ searchSchema.resultFields || [
117
+ 'agentId',
118
+ 'playerId',
119
+ 'membershipId',
120
+ 'worldId',
121
+ 'displayName',
122
+ 'headline',
123
+ 'address',
124
+ 'online',
125
+ 'matchedFieldIds',
126
+ 'score',
127
+ 'profileSummary',
128
+ ],
129
+ ),
130
+ onlineOnly: searchSchema.onlineOnly !== false,
131
+ defaultLimit: Number.isFinite(Number(searchSchema.defaultLimit))
132
+ ? Math.max(1, Math.trunc(Number(searchSchema.defaultLimit)))
133
+ : 10,
134
+ summary: normalizeText(
135
+ searchSchema.summary,
136
+ 'Search active online world members by world-profile overlap before opening a conversation.',
137
+ ),
138
+ hints: normalizeStringList(searchSchema.hints),
139
+ };
140
+ }
141
+
142
+ export function normalizeWorldManifest(manifest = {}, index = 0) {
143
+ const worldId = normalizeText(manifest.worldId || manifest.id, `world_${index + 1}`);
144
+ const joinSchema = normalizeJoinSchema(manifest.joinSchema);
145
+ const searchSchema = normalizeSearchSchema(manifest.searchSchema, joinSchema);
146
+ const defaultInteractionRules = normalizeText(manifest.sessionTemplate?.worldRules?.openingText, null);
147
+ const defaultProhibitedRules = normalizeText(manifest.sessionTemplate?.raiseHandPolicy?.summary, null);
148
+
149
+ return {
150
+ worldId,
151
+ slug: normalizeText(manifest.slug, worldId),
152
+ displayName: normalizeText(manifest.displayName, worldId),
153
+ summary: normalizeText(manifest.summary, null),
154
+ description: normalizeText(manifest.description, normalizeText(manifest.summary, null)),
155
+ category: normalizeText(manifest.category, 'general'),
156
+ lifecycle: normalizeText(manifest.lifecycle, 'scaffold'),
157
+ tags: normalizeStringList(manifest.tags),
158
+ interactionRules: normalizeText(manifest.interactionRules, defaultInteractionRules),
159
+ prohibitedRules: normalizeText(manifest.prohibitedRules, defaultProhibitedRules),
160
+ ratingRules: normalizeText(
161
+ manifest.ratingRules,
162
+ 'After the interaction ends, each agent should rate the other participant from 1 to 10 based on fit, clarity, and rule compliance.',
163
+ ),
164
+ creatorAgentId: normalizeText(manifest.creatorAgentId, null),
165
+ adminAgentIds: normalizeStringList(manifest.adminAgentIds),
166
+ eligibility: normalizeWorldEligibility(manifest.eligibility, 'active'),
167
+ broadcast: normalizeBroadcastConfig(manifest.broadcast, {
168
+ enabled: manifest.broadcast?.enabled === true,
169
+ }),
170
+ status: normalizeText(manifest.status, manifest.enabled === true ? 'enabled' : 'draft'),
171
+ enabled: manifest.enabled === true,
172
+ schemaVersion: Number.isFinite(Number(manifest.schemaVersion)) ? Math.max(1, Math.trunc(Number(manifest.schemaVersion))) : 1,
173
+ createdAt: normalizeText(manifest.createdAt, null),
174
+ updatedAt: normalizeText(manifest.updatedAt, null),
175
+ metrics: manifest.metrics && typeof manifest.metrics === 'object'
176
+ ? {
177
+ totalConversationCount: Number.isFinite(Number(manifest.metrics.totalConversationCount))
178
+ ? Math.max(0, Math.trunc(Number(manifest.metrics.totalConversationCount)))
179
+ : 0,
180
+ }
181
+ : { totalConversationCount: 0 },
182
+ roles: Array.isArray(manifest.roles)
183
+ ? manifest.roles.map((role, roleIndex) => normalizeRole(role, roleIndex))
184
+ : [],
185
+ joinSchema,
186
+ searchSchema,
187
+ matching: {
188
+ mode: normalizeText(manifest.matching?.mode, 'manual_review'),
189
+ cadence: normalizeText(manifest.matching?.cadence, 'on_demand'),
190
+ strategySummary: normalizeText(manifest.matching?.strategySummary, null),
191
+ candidateSources: normalizeStringList(manifest.matching?.candidateSources),
192
+ },
193
+ sessionTemplate: {
194
+ mode: normalizeText(manifest.sessionTemplate?.mode, 'a2a'),
195
+ maxTurns: Number.isFinite(Number(manifest.sessionTemplate?.maxTurns))
196
+ ? Math.max(1, Number(manifest.sessionTemplate.maxTurns))
197
+ : 6,
198
+ turnTimeoutMs: Number.isFinite(Number(manifest.sessionTemplate?.turnTimeoutMs))
199
+ ? Math.max(1000, Number(manifest.sessionTemplate.turnTimeoutMs))
200
+ : 60_000,
201
+ raiseHandPolicy: {
202
+ mode: normalizeText(manifest.sessionTemplate?.raiseHandPolicy?.mode, 'dual_raise_hand'),
203
+ summary: normalizeText(manifest.sessionTemplate?.raiseHandPolicy?.summary, null),
204
+ },
205
+ worldRules: {
206
+ openingText: normalizeText(manifest.sessionTemplate?.worldRules?.openingText, null),
207
+ turnMessageRules: Array.isArray(manifest.sessionTemplate?.worldRules?.turnMessageRules)
208
+ ? manifest.sessionTemplate.worldRules.turnMessageRules
209
+ : [],
210
+ convergence: manifest.sessionTemplate?.worldRules?.convergence || {},
211
+ stateChangeMessages: manifest.sessionTemplate?.worldRules?.stateChangeMessages || {},
212
+ },
213
+ },
214
+ resultContract: {
215
+ schemaId: normalizeText(manifest.resultContract?.schemaId, `${worldId}.result.v1`),
216
+ outputs: normalizeStringList(manifest.resultContract?.outputs),
217
+ successCriteria: normalizeStringList(manifest.resultContract?.successCriteria),
218
+ exampleSignals: normalizeExampleSignals(manifest.resultContract?.exampleSignals),
219
+ },
220
+ meta: {
221
+ status: normalizeText(manifest.meta?.status, 'scaffold_ready'),
222
+ persistence: normalizeText(manifest.meta?.persistence, 'in_memory_catalog'),
223
+ },
224
+ };
225
+ }
226
+
227
+ export function projectWorldCard(world) {
228
+ const agentSummary = {
229
+ displayName: world.displayName,
230
+ summary: world.summary,
231
+ category: world.category,
232
+ requiredFieldCount: world.joinSchema.requiredFields.length,
233
+ matchingMode: world.matching.mode,
234
+ sessionMode: world.sessionTemplate.mode,
235
+ };
236
+
237
+ return {
238
+ worldId: world.worldId,
239
+ slug: world.slug,
240
+ displayName: agentSummary.displayName,
241
+ summary: agentSummary.summary,
242
+ category: agentSummary.category,
243
+ lifecycle: world.lifecycle,
244
+ tags: world.tags,
245
+ requiredFieldCount: agentSummary.requiredFieldCount,
246
+ roleCount: world.roles.length,
247
+ matchingMode: agentSummary.matchingMode,
248
+ sessionMode: agentSummary.sessionMode,
249
+ createdAt: world.createdAt || null,
250
+ updatedAt: world.updatedAt || null,
251
+ status: world.meta.status,
252
+ agentSummary,
253
+ };
254
+ }
255
+
256
+ export function projectJoinPlan(world) {
257
+ return {
258
+ worldId: world.worldId,
259
+ requiredFields: world.joinSchema.requiredFields,
260
+ optionalFields: world.joinSchema.optionalFields,
261
+ hints: world.joinSchema.hints,
262
+ nextAction: 'collect_profile_fields_then_call_join',
263
+ };
264
+ }
265
+
266
+ function projectFieldGuide(fields = [], { required = false } = {}) {
267
+ return fields.map((field) => ({
268
+ fieldId: field.fieldId,
269
+ label: field.label,
270
+ required,
271
+ type: field.type,
272
+ source: field.source,
273
+ description: field.description,
274
+ examples: field.examples,
275
+ constraints: field.constraints,
276
+ }));
277
+ }
278
+
279
+ export function projectSearchModel(world) {
280
+ return {
281
+ modelId: `${world.worldId}.search.v1`,
282
+ worldId: world.worldId,
283
+ mode: world.searchSchema.mode,
284
+ previewRoute: `/v1/worlds/${world.worldId}/search`,
285
+ inputFieldIds: world.searchSchema.inputFieldIds,
286
+ inputFields: projectFieldGuide(world.searchSchema.inputFields, { required: false }),
287
+ resultFields: world.searchSchema.resultFields,
288
+ viewerRequirement: 'active_membership',
289
+ onlineOnly: world.searchSchema.onlineOnly,
290
+ defaultLimit: world.searchSchema.defaultLimit,
291
+ summary: world.searchSchema.summary,
292
+ hints: world.searchSchema.hints,
293
+ status: 'phase1_world_search',
294
+ };
295
+ }
296
+
297
+ export function projectWorldDetail(world) {
298
+ const joinPlan = projectJoinPlan(world);
299
+ const requiredFieldGuide = projectFieldGuide(world.joinSchema.requiredFields, { required: true });
300
+ const optionalFieldGuide = projectFieldGuide(world.joinSchema.optionalFields, { required: false });
301
+ const allFieldGuide = [...requiredFieldGuide, ...optionalFieldGuide];
302
+ const candidateFeedOverview = projectCandidateFeedModel(world);
303
+ const searchSchema = projectSearchModel(world);
304
+ const runtimeStartupContext = {
305
+ worldId: world.worldId,
306
+ status: 'ready',
307
+ source: 'product_shell_session_startup',
308
+ text: buildWorldSessionStartupText({
309
+ worldId: world.worldId,
310
+ displayName: world.displayName,
311
+ summary: world.summary,
312
+ interactionRules: world.interactionRules,
313
+ prohibitedRules: world.prohibitedRules,
314
+ ratingRules: world.ratingRules,
315
+ sessionMode: world.sessionTemplate.mode,
316
+ sessionOverview: {
317
+ worldId: world.worldId,
318
+ mode: world.sessionTemplate.mode,
319
+ maxTurns: world.sessionTemplate.maxTurns,
320
+ turnTimeoutMs: world.sessionTemplate.turnTimeoutMs,
321
+ raiseHandPolicy: world.sessionTemplate.raiseHandPolicy,
322
+ openingText: world.sessionTemplate.worldRules.openingText,
323
+ convergence: world.sessionTemplate.worldRules.convergence,
324
+ },
325
+ }),
326
+ };
327
+
328
+ return {
329
+ world: world,
330
+ agentSummary: {
331
+ displayName: world.displayName,
332
+ summary: world.summary,
333
+ category: world.category,
334
+ requiredFieldCount: world.joinSchema.requiredFields.length,
335
+ optionalFieldCount: world.joinSchema.optionalFields.length,
336
+ matchingMode: world.matching.mode,
337
+ sessionMode: world.sessionTemplate.mode,
338
+ },
339
+ joinSchema: {
340
+ worldId: world.worldId,
341
+ requiredFields: world.joinSchema.requiredFields,
342
+ optionalFields: world.joinSchema.optionalFields,
343
+ requiredFieldIds: requiredFieldGuide.map((field) => field.fieldId),
344
+ optionalFieldIds: optionalFieldGuide.map((field) => field.fieldId),
345
+ requiredFieldCount: requiredFieldGuide.length,
346
+ optionalFieldCount: optionalFieldGuide.length,
347
+ hints: world.joinSchema.hints,
348
+ nextAction: joinPlan.nextAction,
349
+ },
350
+ fieldGuide: {
351
+ required: requiredFieldGuide,
352
+ optional: optionalFieldGuide,
353
+ allFields: allFieldGuide,
354
+ },
355
+ sessionOverview: {
356
+ worldId: world.worldId,
357
+ mode: world.sessionTemplate.mode,
358
+ maxTurns: world.sessionTemplate.maxTurns,
359
+ turnTimeoutMs: world.sessionTemplate.turnTimeoutMs,
360
+ raiseHandPolicy: world.sessionTemplate.raiseHandPolicy,
361
+ openingText: world.sessionTemplate.worldRules.openingText,
362
+ convergence: world.sessionTemplate.worldRules.convergence,
363
+ turnMessageRules: world.sessionTemplate.worldRules.turnMessageRules,
364
+ expectationSummary:
365
+ `Expect a ${world.sessionTemplate.mode} session with up to ${world.sessionTemplate.maxTurns} turns and ` +
366
+ `a ${world.sessionTemplate.turnTimeoutMs}ms turn timeout before the world asks both agents to converge.`,
367
+ status: 'scaffold_ready',
368
+ },
369
+ matchingOverview: {
370
+ worldId: world.worldId,
371
+ mode: world.matching.mode,
372
+ cadence: world.matching.cadence,
373
+ strategySummary: world.matching.strategySummary,
374
+ candidateSources: world.matching.candidateSources,
375
+ inputFields: requiredFieldGuide.map((field) => field.fieldId),
376
+ optionalContextFields: optionalFieldGuide.map((field) => field.fieldId),
377
+ searchMode: searchSchema.mode,
378
+ deliveryMode: candidateFeedOverview.deliveryMode,
379
+ status: 'scaffold_ready',
380
+ },
381
+ searchSchema,
382
+ candidateFeedOverview,
383
+ joinPlan,
384
+ runtimeStartupContext,
385
+ };
386
+ }
387
+
388
+ export function projectSessionStartupContext(world) {
389
+ const runtimeStartupContext = {
390
+ worldId: world.worldId,
391
+ status: 'ready',
392
+ source: 'product_shell_session_startup',
393
+ text: buildWorldSessionStartupText({
394
+ worldId: world.worldId,
395
+ displayName: world.displayName,
396
+ summary: world.summary,
397
+ interactionRules: world.interactionRules,
398
+ prohibitedRules: world.prohibitedRules,
399
+ ratingRules: world.ratingRules,
400
+ sessionMode: world.sessionTemplate.mode,
401
+ sessionOverview: {
402
+ worldId: world.worldId,
403
+ mode: world.sessionTemplate.mode,
404
+ maxTurns: world.sessionTemplate.maxTurns,
405
+ turnTimeoutMs: world.sessionTemplate.turnTimeoutMs,
406
+ raiseHandPolicy: world.sessionTemplate.raiseHandPolicy,
407
+ openingText: world.sessionTemplate.worldRules.openingText,
408
+ convergence: world.sessionTemplate.worldRules.convergence,
409
+ },
410
+ }),
411
+ };
412
+
413
+ return {
414
+ worldId: world.worldId,
415
+ displayName: world.displayName,
416
+ summary: world.summary,
417
+ interactionRules: world.interactionRules,
418
+ prohibitedRules: world.prohibitedRules,
419
+ ratingRules: world.ratingRules,
420
+ sessionMode: world.sessionTemplate.mode,
421
+ sessionOverview: {
422
+ worldId: world.worldId,
423
+ mode: world.sessionTemplate.mode,
424
+ maxTurns: world.sessionTemplate.maxTurns,
425
+ turnTimeoutMs: world.sessionTemplate.turnTimeoutMs,
426
+ raiseHandPolicy: world.sessionTemplate.raiseHandPolicy,
427
+ openingText: world.sessionTemplate.worldRules.openingText,
428
+ convergence: world.sessionTemplate.worldRules.convergence,
429
+ turnMessageRules: world.sessionTemplate.worldRules.turnMessageRules,
430
+ },
431
+ status: 'ready',
432
+ source: 'product_shell_session_startup',
433
+ runtimeStartupContext,
434
+ };
435
+ }