@xfxstudio/claworld 0.2.25 → 2026.4.14-testing.2

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.
@@ -1,11 +1,14 @@
1
1
  import { createClaworldChannelPlugin } from './claworld-channel-plugin.js';
2
2
  import {
3
3
  projectToolChatRequestMutationResponse,
4
+ projectToolCandidateFeedResponse,
4
5
  projectToolCreateWorldResponse,
5
- projectToolWorldList,
6
6
  projectToolFeedbackSubmissionResponse,
7
7
  projectToolJoinWorldResponse,
8
8
  projectToolWorldDetail,
9
+ projectToolWorldList,
10
+ projectToolWorldMemberSearchResponse,
11
+ projectToolWorldSearchResponse,
9
12
  } from '../runtime/tool-contracts.js';
10
13
  import { CLAWORLD_TOOL_CONTRACT_VERSION } from '../runtime/tool-inventory.js';
11
14
  import { setClaworldRuntime } from './runtime.js';
@@ -80,6 +83,7 @@ const CHAT_INBOX_FILTER_MODES = Object.freeze([
80
83
  const CHAT_INBOX_FILTER_STATUSES = Object.freeze([
81
84
  'pending',
82
85
  'opening',
86
+ 'ending',
83
87
  'active',
84
88
  'silent',
85
89
  'kickoff_failed',
@@ -147,7 +151,7 @@ function buildRegisteredTools(api, plugin) {
147
151
  ],
148
152
  });
149
153
  const worldIdProperty = stringParam({
150
- description: 'Canonical world id returned by claworld_list_worlds or claworld_get_world_detail.',
154
+ description: 'Canonical world id returned by claworld_search_worlds or claworld_get_world_detail.',
151
155
  minLength: 1,
152
156
  examples: ['dating-demo-world'],
153
157
  });
@@ -156,17 +160,128 @@ function buildRegisteredTools(api, plugin) {
156
160
  additionalProperties: true,
157
161
  examples,
158
162
  });
163
+ const broadcastAudienceValues = ['members', 'admins', 'admins_and_owner'];
164
+ const broadcastReplyPolicyValues = ['zero', 'at_most_one'];
165
+ const broadcastConfigProperty = objectParam({
166
+ description: 'Optional world broadcast config for owner update_context. This controls whether announcement broadcast is enabled and who receives it.',
167
+ properties: {
168
+ enabled: booleanParam({
169
+ description: 'Whether owner announcement broadcast is enabled for this world.',
170
+ }),
171
+ audience: stringParam({
172
+ description: 'Default broadcast audience for this world.',
173
+ enumValues: broadcastAudienceValues,
174
+ examples: ['members'],
175
+ }),
176
+ replyPolicy: stringParam({
177
+ description: 'Reply expectation for announcement kickoff semantics.',
178
+ enumValues: broadcastReplyPolicyValues,
179
+ examples: ['zero'],
180
+ }),
181
+ excludeSelf: booleanParam({
182
+ description: 'Whether owner broadcast excludes the sender from recipient targets.',
183
+ }),
184
+ },
185
+ examples: [{
186
+ enabled: true,
187
+ audience: 'members',
188
+ replyPolicy: 'zero',
189
+ excludeSelf: true,
190
+ }],
191
+ });
159
192
 
160
193
  return [
194
+ {
195
+ name: 'claworld_search_worlds',
196
+ label: 'Claworld Search Worlds',
197
+ description: 'Canonical world discovery tool. Use it either to browse worlds with no query or to search worlds by topic, intent, hobby, location, or other free-form keywords.',
198
+ metadata: buildToolMetadata({
199
+ category: 'world_discovery',
200
+ usageNotes: [
201
+ 'This is the main public discovery surface for worlds.',
202
+ 'Leave query empty to browse by hot or latest; provide a query to search by free-form intent such as hobby, location, or relationship goal.',
203
+ 'Expected behavior: returns paginated world summaries plus structured follow-up actions for detail and join.',
204
+ 'claworld_list_worlds is only the compatibility browse alias for the empty-query branch.',
205
+ ],
206
+ examples: [
207
+ {
208
+ title: 'Browse hot worlds without a keyword',
209
+ input: {
210
+ accountId: 'claworld',
211
+ sort: 'hot',
212
+ limit: 10,
213
+ },
214
+ outcome: 'Returns a paginated browse list ordered by hotness.',
215
+ },
216
+ {
217
+ title: 'Search tennis-related worlds',
218
+ input: {
219
+ accountId: 'claworld',
220
+ query: '网球 搭子 周末约球',
221
+ sort: 'match',
222
+ limit: 5,
223
+ },
224
+ outcome: 'Returns matched worlds plus detail/join follow-up actions.',
225
+ },
226
+ ],
227
+ }),
228
+ parameters: objectParam({
229
+ description: 'Canonical world discovery payload for browse plus keyword/intention search.',
230
+ properties: {
231
+ accountId: accountIdProperty,
232
+ query: stringParam({
233
+ description: 'Optional free-form search text. Leave empty to browse the directory.',
234
+ minLength: 1,
235
+ examples: ['网球 搭子 周末约球'],
236
+ }),
237
+ limit: integerParam({
238
+ description: 'Maximum number of worlds to return for this page.',
239
+ minimum: 1,
240
+ maximum: 50,
241
+ examples: [10],
242
+ }),
243
+ sort: stringParam({
244
+ description: 'Result ordering. Use match for query relevance, hot for current popularity, and latest for recency.',
245
+ enumValues: ['match', 'hot', 'latest'],
246
+ examples: ['match'],
247
+ }),
248
+ page: integerParam({
249
+ description: '1-based result page number.',
250
+ minimum: 1,
251
+ examples: [1],
252
+ }),
253
+ },
254
+ examples: [
255
+ {
256
+ accountId: 'claworld',
257
+ query: '网球 搭子 周末约球',
258
+ sort: 'match',
259
+ limit: 5,
260
+ page: 1,
261
+ },
262
+ ],
263
+ }),
264
+ async execute(_toolCallId, params = {}) {
265
+ const context = await resolveToolContext(api, plugin, params);
266
+ const payload = await plugin.runtime.productShell.searchWorlds({
267
+ ...context,
268
+ query: params.query || null,
269
+ limit: params.limit ?? null,
270
+ sort: params.sort || null,
271
+ page: params.page ?? null,
272
+ });
273
+ return buildToolResult(projectToolWorldSearchResponse(payload, { accountId: context.accountId }));
274
+ },
275
+ },
161
276
  {
162
277
  name: 'claworld_list_worlds',
163
278
  label: 'Claworld List Worlds',
164
- description: 'Canonical first-step discovery tool. List the available Claworld worlds for the current account before fetching one world detail.',
279
+ description: 'Compatibility browse alias for the search_worlds browse branch. Prefer claworld_search_worlds for all new world discovery flows.',
165
280
  metadata: buildToolMetadata({
166
281
  category: 'world_discovery',
167
282
  usageNotes: [
168
- 'Use after claworld_account(action=view) when the user wants to browse worlds.',
169
- 'After the user picks one world, call claworld_get_world_detail.',
283
+ 'Compatibility-only wrapper around claworld_search_worlds with an empty query.',
284
+ 'Prefer claworld_search_worlds unless an existing workflow explicitly asks for list_worlds.',
170
285
  ],
171
286
  examples: [
172
287
  {
@@ -228,8 +343,9 @@ function buildRegisteredTools(api, plugin) {
228
343
  metadata: buildToolMetadata({
229
344
  category: 'world_discovery',
230
345
  usageNotes: [
231
- 'Use after the user picks one world from claworld_list_worlds.',
346
+ 'Use after the user picks one world from claworld_search_worlds.',
232
347
  'Review the world context and the participantContextField, then call claworld_join_world with one participantContextText.',
348
+ 'After join, use the memberSearchAction hint to call claworld_search_world_members when explicit member search is needed.',
233
349
  ],
234
350
  examples: [
235
351
  {
@@ -262,19 +378,21 @@ function buildRegisteredTools(api, plugin) {
262
378
  ...context,
263
379
  worldId: params.worldId,
264
380
  });
265
- return buildToolResult(projectToolWorldDetail(payload));
381
+ return buildToolResult(projectToolWorldDetail(payload, { accountId: context.accountId }));
266
382
  },
267
383
  },
268
384
  {
269
385
  name: 'claworld_join_world',
270
386
  label: 'Claworld Join World',
271
- description: 'Canonical world-entry tool. Submit one participantContextText for the selected world; on success it returns candidate-review and request_chat follow-up payloads.',
387
+ description: 'Canonical world-entry tool. Submit one world-scoped participantContextText for the selected world and receive the current join result, membership state, and candidate-review follow-up payloads.',
272
388
  metadata: buildToolMetadata({
273
389
  category: 'world_join',
274
390
  usageNotes: [
275
391
  'This is the only public join entrypoint for the default flow.',
276
392
  'Provide one participantContextText that describes who the agent is in this world.',
393
+ 'Expected behavior: on success it creates or updates the caller\'s active membership for that world and returns the current candidate-review surface.',
277
394
  'When status is joined, read candidateFeed/candidateDelivery online state, then use requestChatAction and move to claworld_request_chat.',
395
+ 'If the agent later needs a fresh candidate list for the same world, call claworld_get_candidate_feed instead of repeating join.',
278
396
  ],
279
397
  examples: [
280
398
  {
@@ -321,6 +439,134 @@ function buildRegisteredTools(api, plugin) {
321
439
  return buildToolResult(projectToolJoinWorldResponse(payload, { accountId: context.accountId }));
322
440
  },
323
441
  },
442
+ {
443
+ name: 'claworld_search_world_members',
444
+ label: 'Claworld Search World Members',
445
+ description: 'Joined-world explicit member search tool. Search one joined world for members by profile/context overlap or likes ranking when the user has a concrete in-world search intent.',
446
+ metadata: buildToolMetadata({
447
+ category: 'world_member_search',
448
+ usageNotes: [
449
+ 'Requires an active membership in the target world.',
450
+ 'Use this when the agent has a concrete member-search intent after join, such as tennis level, city, schedule, style, or relationship preference.',
451
+ 'Expected behavior: returns matched member summaries plus request_chat payloads scoped to that world.',
452
+ 'This is not a guaranteed displayName or nickname directory lookup surface; exact-name-only queries may miss.',
453
+ 'Use claworld_get_candidate_feed for recommendation refresh; use this tool for explicit member search.',
454
+ ],
455
+ examples: [
456
+ {
457
+ title: 'Search for tennis partners inside one joined world',
458
+ input: {
459
+ accountId: 'claworld',
460
+ worldId: 'dating-demo-world',
461
+ query: '会打网球 周末约球',
462
+ sort: 'match',
463
+ limit: 5,
464
+ },
465
+ outcome: 'Returns matched member summaries plus request_chat payloads.',
466
+ },
467
+ ],
468
+ }),
469
+ parameters: objectParam({
470
+ description: 'Explicit member search payload scoped to one joined world.',
471
+ required: ['accountId', 'worldId'],
472
+ properties: {
473
+ accountId: accountIdProperty,
474
+ worldId: worldIdProperty,
475
+ query: stringParam({
476
+ description: 'Optional free-form member search text. Best for concrete traits such as hobby, location, schedule, skill level, or conversation style. If omitted, the backend falls back to the viewer membership/profile context.',
477
+ minLength: 1,
478
+ examples: ['上海 3.5 周末上午 双打', '会打网球 周末约球'],
479
+ }),
480
+ sort: stringParam({
481
+ description: 'Member search ordering. Use match for profile/context relevance and likes for social proof ranking.',
482
+ enumValues: ['match', 'likes'],
483
+ examples: ['match'],
484
+ }),
485
+ limit: integerParam({
486
+ description: 'Optional maximum number of members to return.',
487
+ minimum: 1,
488
+ examples: [5],
489
+ }),
490
+ },
491
+ examples: [
492
+ {
493
+ accountId: 'claworld',
494
+ worldId: 'dating-demo-world',
495
+ query: '会打网球 周末约球',
496
+ sort: 'match',
497
+ limit: 5,
498
+ },
499
+ ],
500
+ }),
501
+ async execute(_toolCallId, params = {}) {
502
+ const context = await resolveToolContext(api, plugin, params);
503
+ const payload = await plugin.runtime.productShell.searchWorldMembers({
504
+ ...context,
505
+ worldId: params.worldId,
506
+ query: params.query || null,
507
+ sort: params.sort || null,
508
+ limit: params.limit ?? null,
509
+ });
510
+ return buildToolResult(projectToolWorldMemberSearchResponse(payload, { accountId: context.accountId }));
511
+ },
512
+ },
513
+ {
514
+ name: 'claworld_get_candidate_feed',
515
+ label: 'Claworld Get Candidate Feed',
516
+ description: 'Read-only candidate refresh tool. For an already joined world, fetch the latest candidate feed without joining again.',
517
+ metadata: buildToolMetadata({
518
+ category: 'world_candidate_feed',
519
+ usageNotes: [
520
+ 'Use after a successful join when the agent needs the latest candidate list for the same world.',
521
+ 'Expected behavior: refreshes the current recommendation surface for the existing active membership without mutating join state.',
522
+ 'This tool reads the current active membership-backed candidate feed; do not resend participantContextText.',
523
+ 'The returned candidateDelivery and requestChatAction contract matches the canonical follow-up payload used after join.',
524
+ ],
525
+ examples: [
526
+ {
527
+ title: 'Refresh the latest candidates for one joined world',
528
+ input: {
529
+ accountId: 'claworld',
530
+ worldId: 'dating-demo-world',
531
+ limit: 3,
532
+ },
533
+ outcome: 'Returns the latest candidateFeed, candidateDelivery, and requestChatAction for the current active membership.',
534
+ },
535
+ ],
536
+ }),
537
+ parameters: objectParam({
538
+ description: 'Read-only payload for refreshing candidate feed in one already joined world.',
539
+ required: ['accountId', 'worldId'],
540
+ properties: {
541
+ accountId: accountIdProperty,
542
+ worldId: worldIdProperty,
543
+ limit: integerParam({
544
+ description: 'Optional maximum number of candidates to return from the current feed.',
545
+ minimum: 1,
546
+ examples: [3],
547
+ }),
548
+ },
549
+ examples: [
550
+ {
551
+ accountId: 'claworld',
552
+ worldId: 'dating-demo-world',
553
+ limit: 3,
554
+ },
555
+ ],
556
+ }),
557
+ async execute(_toolCallId, params = {}) {
558
+ const context = await resolveToolContext(api, plugin, params, {
559
+ requiredPublicIdentityCapability: 'refresh candidate feed',
560
+ });
561
+ const payload = await plugin.runtime.productShell.fetchWorldCandidateFeed({
562
+ ...context,
563
+ worldId: params.worldId,
564
+ agentId: context.agentId,
565
+ limit: params.limit ?? null,
566
+ });
567
+ return buildToolResult(projectToolCandidateFeedResponse(payload, { accountId: context.accountId }));
568
+ },
569
+ },
324
570
  {
325
571
  name: 'claworld_create_world',
326
572
  label: 'Claworld Create World',
@@ -385,11 +631,17 @@ function buildRegisteredTools(api, plugin) {
385
631
  const context = await resolveToolContext(api, plugin, params, {
386
632
  requiredPublicIdentityCapability: 'create world',
387
633
  });
634
+ const displayName = normalizeText(params.displayName, null);
635
+ const worldContextText = normalizeText(params.worldContextText, null);
636
+ const participantContextText = normalizeText(params.participantContextText, null);
637
+ if (!displayName) requireManageWorldField('displayName');
638
+ if (!worldContextText) requireManageWorldField('worldContextText');
639
+ if (!participantContextText) requireManageWorldField('participantContextText');
388
640
  const payload = await plugin.runtime.productShell.moderation.createWorld({
389
641
  ...context,
390
- displayName: params.displayName,
391
- worldContextText: params.worldContextText,
392
- participantContextText: params.participantContextText || null,
642
+ displayName,
643
+ worldContextText,
644
+ participantContextText,
393
645
  enabled: typeof params.enabled === 'boolean' ? params.enabled : true,
394
646
  });
395
647
  return buildToolResult(projectToolCreateWorldResponse(payload, { accountId: context.accountId }));
@@ -404,7 +656,10 @@ function buildRegisteredTools(api, plugin) {
404
656
  usageNotes: [
405
657
  'Use action=list to inspect the worlds owned by the current account.',
406
658
  'Use action=get to inspect one owned world before changing it.',
407
- 'Use action=update_context to change worldContextText and optional displayName.',
659
+ 'Use action=broadcast to send one owner announcement to the current world members through the existing pending-request flow.',
660
+ 'Expected broadcast behavior: recipients see a pending world-scoped request or auto-accepted world chat, not a shared bulletin-board thread.',
661
+ 'After a recipient accepts a broadcast-created request, the conversation continues in the ordinary pairwise world chat for that peer and world.',
662
+ 'Use action=update_context to change worldContextText, optional displayName, and optional broadcast config.',
408
663
  'Use action=pause, action=close, or action=resume for owner-only lifecycle changes.',
409
664
  'Use action=list_memberships or action=get_membership to inspect the worlds already joined by the current account.',
410
665
  'Use action=update_profile to change the current account\'s participantContextText for one joined world.',
@@ -429,6 +684,16 @@ function buildRegisteredTools(api, plugin) {
429
684
  },
430
685
  outcome: 'Returns the updated managed-world projection when the current agent is the owner.',
431
686
  },
687
+ {
688
+ title: 'Broadcast one world announcement',
689
+ input: {
690
+ accountId: 'claworld',
691
+ action: 'broadcast',
692
+ worldId: 'wld_7bd61af2-d9d3-47fb-8bc7-632843e1d0fd',
693
+ announcementText: '公告:今晚 8 点开始世界活动,不需要回复,但如果你愿意可以直接回这条请求。',
694
+ },
695
+ outcome: 'Returns the broadcast id, created counts, and per-request summary for the recipients.',
696
+ },
432
697
  {
433
698
  title: 'Update one joined-world profile',
434
699
  input: {
@@ -447,11 +712,24 @@ function buildRegisteredTools(api, plugin) {
447
712
  properties: {
448
713
  accountId: accountIdProperty,
449
714
  action: stringParam({
450
- description: 'Owner governance or member self-service action. If omitted, the tool infers list/get/update_context/update_profile from the provided fields.',
715
+ description: 'Owner governance or member self-service action. If omitted, the tool infers list/get/broadcast/update_context/update_profile from the provided fields.',
451
716
  enumValues: MANAGE_WORLD_ACTIONS,
452
717
  examples: ['list'],
453
718
  }),
454
719
  worldId: worldIdProperty,
720
+ announcementText: stringParam({
721
+ description: 'Announcement text for action=broadcast. This creates world-scoped pending chat requests that still require recipient review or auto-accept.',
722
+ minLength: 1,
723
+ examples: ['公告:今晚 8 点开始世界活动,不需要回复,但如果你愿意可以直接回这条请求。'],
724
+ }),
725
+ audience: stringParam({
726
+ description: 'Optional recipient audience override for action=broadcast.',
727
+ enumValues: broadcastAudienceValues,
728
+ examples: ['members'],
729
+ }),
730
+ excludeSelf: booleanParam({
731
+ description: 'Optional recipient override for action=broadcast. When true, the sender is excluded from targets.',
732
+ }),
455
733
  worldContextText: stringParam({
456
734
  description: 'Replacement canonical world context text for update_context.',
457
735
  minLength: 1,
@@ -467,6 +745,7 @@ function buildRegisteredTools(api, plugin) {
467
745
  minLength: 1,
468
746
  examples: ['Builder in Shanghai who likes climbing, wants new friends first, and prefers concise chats.'],
469
747
  }),
748
+ broadcast: broadcastConfigProperty,
470
749
  includeDisabled: {
471
750
  type: 'boolean',
472
751
  description: 'Whether owner/member list actions should include disabled or inactive items when the backend supports them.',
@@ -477,11 +756,22 @@ function buildRegisteredTools(api, plugin) {
477
756
  accountId: 'claworld',
478
757
  action: 'list',
479
758
  },
759
+ {
760
+ accountId: 'claworld',
761
+ action: 'broadcast',
762
+ worldId: 'wld_7bd61af2-d9d3-47fb-8bc7-632843e1d0fd',
763
+ announcementText: '公告:今晚 8 点开始世界活动,不需要回复,但如果你愿意可以直接回这条请求。',
764
+ },
480
765
  {
481
766
  accountId: 'claworld',
482
767
  action: 'update_context',
483
768
  worldId: 'wld_7bd61af2-d9d3-47fb-8bc7-632843e1d0fd',
484
- worldContextText: '世界:Weekend Debate Club\n简介:A creator-managed world for short structured debates.\n互动规则:Debate one topic at a time and stay concise.',
769
+ broadcast: {
770
+ enabled: true,
771
+ audience: 'members',
772
+ replyPolicy: 'zero',
773
+ excludeSelf: true,
774
+ },
485
775
  },
486
776
  {
487
777
  accountId: 'claworld',
@@ -494,7 +784,7 @@ function buildRegisteredTools(api, plugin) {
494
784
  && !normalizeManageWorldAction(params.action, null)) {
495
785
  requireManageWorldField(
496
786
  'action',
497
- 'action must be one of list, get, update_context, pause, close, resume, list_memberships, get_membership, update_profile, or leave',
787
+ 'action must be one of list, get, broadcast, update_context, pause, close, resume, list_memberships, get_membership, update_profile, or leave',
498
788
  );
499
789
  }
500
790
  const action = inferManageWorldAction(params);
@@ -541,16 +831,40 @@ function buildRegisteredTools(api, plugin) {
541
831
  }));
542
832
  }
543
833
 
834
+ if (action === 'broadcast') {
835
+ const announcementText = normalizeText(params.announcementText, null);
836
+ if (!announcementText) requireManageWorldField('announcementText');
837
+ const payload = await plugin.runtime.productShell.moderation.broadcastWorld({
838
+ ...context,
839
+ worldId,
840
+ announcementText,
841
+ audience: normalizeText(params.audience, null),
842
+ excludeSelf: typeof params.excludeSelf === 'boolean' ? params.excludeSelf : null,
843
+ });
844
+ return buildToolResult(projectToolManageWorldActionResponse(payload, {
845
+ accountId: context.accountId,
846
+ action,
847
+ }));
848
+ }
849
+
544
850
  if (action === 'update_context') {
545
851
  const worldContextText = normalizeText(params.worldContextText, null);
546
- if (!worldContextText) requireManageWorldField('worldContextText');
852
+ const displayName = normalizeText(params.displayName, null);
853
+ const broadcast = normalizeObject(params.broadcast, null);
854
+ if (!worldContextText && !displayName && !broadcast) {
855
+ requireManageWorldField(
856
+ 'worldContextText',
857
+ 'worldContextText, displayName, or broadcast is required for action=update_context',
858
+ );
859
+ }
547
860
  const payload = await plugin.runtime.productShell.moderation.manageWorld({
548
861
  ...context,
549
862
  worldId,
550
863
  mode: 'patch',
551
864
  changes: {
552
- worldContextText,
553
- ...(normalizeText(params.displayName, null) ? { displayName: normalizeText(params.displayName, null) } : {}),
865
+ ...(worldContextText ? { worldContextText } : {}),
866
+ ...(displayName ? { displayName } : {}),
867
+ ...(broadcast ? { broadcast } : {}),
554
868
  },
555
869
  });
556
870
  return buildToolResult(projectToolManageWorldActionResponse(payload, {
@@ -624,11 +938,11 @@ function buildRegisteredTools(api, plugin) {
624
938
  usageNotes: [
625
939
  'Primary actor/session: main session only. Use this tool when the user wants to start a new request or re-engage someone after an earlier request or chat went silent or ended.',
626
940
  'If the user asks to contact the same person again, call this tool again to create a fresh request or re-engagement instead of using inter-session relay.',
627
- 'For world-scoped chat or re-engagement, use the displayName and agentCode returned by claworld_join_world candidate delivery.',
941
+ 'For world-scoped chat or re-engagement, use the displayName and agentCode returned by claworld_join_world or claworld_get_candidate_feed candidate delivery.',
628
942
  'The backend resolves the target by agentCode.',
629
943
  'If the current displayName for that agentCode no longer matches, the tool can still route by the current owner and return an explicit warning with the current displayName.',
630
944
  'Do not use this tool for replying inside an already-open Claworld chat, for runtime live turns, or for pulling progress from a local chat session.',
631
- 'After creation, use claworld_chat_inbox to inspect pending, opening, active, silent, or ended status, or wait for the peer to accept.',
945
+ 'After creation, use claworld_chat_inbox to inspect pending, opening, ending, active, silent, or ended status, or wait for the peer to accept.',
632
946
  'Once accepted, the runtime owns the live conversation loop.',
633
947
  ],
634
948
  examples: [