@xfxstudio/claworld 2026.4.22-testing.5 → 2026.4.22-testing.7

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,7 +1,6 @@
1
1
  import { createClaworldChannelPlugin } from './claworld-channel-plugin.js';
2
2
  import {
3
3
  projectToolChatRequestMutationResponse,
4
- projectToolCandidateFeedResponse,
5
4
  projectToolCreateWorldResponse,
6
5
  projectToolFeedbackSubmissionResponse,
7
6
  projectToolJoinWorldResponse,
@@ -11,6 +10,16 @@ import {
11
10
  projectToolWorldSearchResponse,
12
11
  } from '../runtime/tool-contracts.js';
13
12
  import { CLAWORLD_TOOL_CONTRACT_VERSION } from '../runtime/tool-inventory.js';
13
+ import {
14
+ CLAWORLD_BOOTSTRAP_TARGETS,
15
+ appendClaworldJournalEvent,
16
+ buildClaworldBootstrapPromptContext,
17
+ buildClaworldContextPointer,
18
+ buildClaworldToolMaintenanceEvent,
19
+ ensureClaworldWorkingMemory,
20
+ resolveClaworldBootstrapTarget,
21
+ } from '../runtime/working-memory.js';
22
+ import { resolveOpenClawWorkspaceRoot } from '../runtime/workspace-resolver.js';
14
23
  import { setClaworldRuntime } from './runtime.js';
15
24
  import {
16
25
  CHAT_REQUEST_APPROVAL_POLICY_MODES,
@@ -72,6 +81,48 @@ function buildClaworldStatusRoute(plugin) {
72
81
  };
73
82
  }
74
83
 
84
+ async function resolveHookWorkspaceRoot(api, event = {}, ctx = {}) {
85
+ const directWorkspaceRoot = resolveOpenClawWorkspaceRoot({ sources: [event, ctx] });
86
+ if (directWorkspaceRoot) return directWorkspaceRoot;
87
+ const cfg = await loadCurrentConfig(api);
88
+ return resolveOpenClawWorkspaceRoot({
89
+ sources: [event, ctx],
90
+ config: cfg,
91
+ agentId: ctx?.agentId ?? event?.agentId,
92
+ });
93
+ }
94
+
95
+ function getHookLogger(api) {
96
+ return api?.logger || api?.runtime?.logger || console;
97
+ }
98
+
99
+ function parseHookToolPayload(result) {
100
+ if (!result || typeof result !== 'object') return null;
101
+ const text = Array.isArray(result.content)
102
+ ? result.content.find((entry) => entry?.type === 'text' && typeof entry.text === 'string')?.text
103
+ : null;
104
+ if (!text) return null;
105
+ try {
106
+ const parsed = JSON.parse(text);
107
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ function isSuccessfulHookToolCall(event = {}) {
114
+ if (event?.error || event?.isError === true) return false;
115
+ const result = event?.result ?? event?.output ?? event?.response ?? null;
116
+ if (result?.isError === true) return false;
117
+ const payload = parseHookToolPayload(result);
118
+ if (normalizeText(payload?.status, null) === 'error') return false;
119
+ return true;
120
+ }
121
+
122
+ function hookToolResult(event = {}) {
123
+ return event?.result ?? event?.output ?? event?.response ?? null;
124
+ }
125
+
75
126
  const CHAT_INBOX_FILTER_DIRECTIONS = Object.freeze([
76
127
  'inbound',
77
128
  'outbound',
@@ -92,6 +143,134 @@ const CHAT_INBOX_FILTER_STATUSES = Object.freeze([
92
143
  'ended',
93
144
  ]);
94
145
 
146
+ const TERMINAL_ACCOUNT_ACTIONS = Object.freeze([
147
+ 'view_account',
148
+ 'activate_account',
149
+ 'update_display_name',
150
+ 'update_human_profile',
151
+ 'update_agent_profile',
152
+ 'set_discoverability',
153
+ 'set_contactability',
154
+ 'set_chat_policy',
155
+ 'set_proactivity',
156
+ 'subscribe_person',
157
+ 'unsubscribe_person',
158
+ ]);
159
+
160
+ const TERMINAL_WORLD_ACTIONS = Object.freeze([
161
+ 'list_owned_worlds',
162
+ 'list_joined_worlds',
163
+ 'get_world',
164
+ 'create_world',
165
+ 'update_world',
166
+ 'join_world',
167
+ 'update_world_profile',
168
+ 'leave_world',
169
+ 'subscribe_world',
170
+ 'unsubscribe_world',
171
+ 'set_world_broadcast_preference',
172
+ 'publish_broadcast',
173
+ 'list_world_activity',
174
+ 'list_broadcast_history',
175
+ 'manage_members',
176
+ ]);
177
+
178
+ const TERMINAL_CONVERSATION_ACTIONS = Object.freeze([
179
+ 'request',
180
+ 'accept',
181
+ 'reject',
182
+ 'end',
183
+ 'get_state',
184
+ 'list_related',
185
+ ]);
186
+
187
+ const ACCOUNT_IMPLEMENTATION_ACTIONS = Object.freeze({
188
+ view_account: 'view',
189
+ activate_account: 'update_identity',
190
+ });
191
+
192
+ const WORLD_IMPLEMENTATION_ACTIONS = Object.freeze({
193
+ list_owned_worlds: 'list',
194
+ list_joined_worlds: 'list_memberships',
195
+ update_world: 'update_context',
196
+ update_world_profile: 'update_profile',
197
+ publish_broadcast: 'broadcast',
198
+ leave_world: 'leave',
199
+ });
200
+
201
+ function normalizeTerminalAction(action, allowedActions, fallback = null) {
202
+ const normalized = normalizeText(action, fallback);
203
+ return allowedActions.includes(normalized) ? normalized : fallback;
204
+ }
205
+
206
+ function hasExplicitAction(params = {}) {
207
+ return Object.prototype.hasOwnProperty.call(params, 'action') && normalizeText(params.action, null) != null;
208
+ }
209
+
210
+ function normalizeTerminalAccountAction(params = {}) {
211
+ if (hasExplicitAction(params)) {
212
+ const explicitAction = normalizeText(params.action, null);
213
+ const terminalAction = normalizeTerminalAction(explicitAction, TERMINAL_ACCOUNT_ACTIONS, null);
214
+ if (terminalAction) return terminalAction;
215
+ requireManageWorldField('action', `action must be one of ${TERMINAL_ACCOUNT_ACTIONS.join(', ')}`);
216
+ }
217
+ if (normalizeText(params.displayName, null)) return 'update_display_name';
218
+ if (Object.prototype.hasOwnProperty.call(params, 'humanProfile')) return 'update_human_profile';
219
+ if (
220
+ Object.prototype.hasOwnProperty.call(params, 'agentProfile')
221
+ || Object.prototype.hasOwnProperty.call(params, 'profile')
222
+ ) return 'update_agent_profile';
223
+ if (Object.prototype.hasOwnProperty.call(params, 'discoverable')) return 'set_discoverability';
224
+ if (Object.prototype.hasOwnProperty.call(params, 'contactable')) return 'set_contactability';
225
+ if (normalizeObject(params.chatRequestApprovalPolicy, null)) return 'set_chat_policy';
226
+ if (Object.prototype.hasOwnProperty.call(params, 'proactivitySettings')) return 'set_proactivity';
227
+ return 'view_account';
228
+ }
229
+
230
+ function normalizeTerminalWorldAction(params = {}) {
231
+ if (hasExplicitAction(params)) {
232
+ const explicitAction = normalizeText(params.action, null);
233
+ const terminalAction = normalizeTerminalAction(explicitAction, TERMINAL_WORLD_ACTIONS, null);
234
+ if (terminalAction) return terminalAction;
235
+ requireManageWorldField('action', `action must be one of ${TERMINAL_WORLD_ACTIONS.join(', ')}`);
236
+ }
237
+ if (!normalizeText(params.worldId, null)) return 'list_owned_worlds';
238
+ if (normalizeText(params.announcementText, null)) return 'publish_broadcast';
239
+ if (normalizeText(params.participantContextText, null)) return 'update_world_profile';
240
+ if (
241
+ normalizeText(params.worldContextText, null)
242
+ || normalizeText(params.displayName, null)
243
+ || normalizeObject(params.broadcast, null)
244
+ || typeof params.enabled === 'boolean'
245
+ ) {
246
+ return 'update_world';
247
+ }
248
+ return 'get_world';
249
+ }
250
+
251
+ function normalizeTerminalConversationAction(action, fallback = 'list_related', { throwOnInvalid = false } = {}) {
252
+ const normalized = normalizeText(action, null);
253
+ if (!normalized) return fallback;
254
+ if (TERMINAL_CONVERSATION_ACTIONS.includes(normalized)) return normalized;
255
+ if (throwOnInvalid) {
256
+ requireManageWorldField('action', `action must be one of ${TERMINAL_CONVERSATION_ACTIONS.join(', ')}`);
257
+ }
258
+ return null;
259
+ }
260
+
261
+ function buildTerminalActionResult({ tool, action, payload = {}, status = null } = {}) {
262
+ const normalizedPayload = payload && typeof payload === 'object' && !Array.isArray(payload)
263
+ ? payload
264
+ : { value: payload };
265
+ const normalizedStatus = status || normalizeText(normalizedPayload.status, 'ok');
266
+ return buildToolResult({
267
+ ...normalizedPayload,
268
+ tool,
269
+ action,
270
+ status: normalizedStatus,
271
+ });
272
+ }
273
+
95
274
  function normalizeChatInboxListFiltersInput(params = {}) {
96
275
  const source = normalizeObject(params.filters, {}) || {};
97
276
  const normalized = {
@@ -109,6 +288,577 @@ function normalizeChatInboxListFiltersInput(params = {}) {
109
288
  );
110
289
  }
111
290
 
291
+ function parseToolResultPayload(result = null) {
292
+ const text = result?.content?.[0]?.text;
293
+ if (typeof text !== 'string') return null;
294
+ try {
295
+ return JSON.parse(text);
296
+ } catch {
297
+ return null;
298
+ }
299
+ }
300
+
301
+ function rewriteToolResultName(result, toolName, action = null) {
302
+ const payload = parseToolResultPayload(result);
303
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return result;
304
+ return buildToolResult({
305
+ ...payload,
306
+ tool: toolName,
307
+ ...(action ? { action } : {}),
308
+ });
309
+ }
310
+
311
+ function requireTerminalTool(internalTools, toolName) {
312
+ const tool = internalTools.get(toolName);
313
+ if (!tool || typeof tool.execute !== 'function') {
314
+ throw new Error(`terminal public tool adapter requires ${toolName}`);
315
+ }
316
+ return tool;
317
+ }
318
+
319
+ function createTerminalToolAdapters(api, plugin, internalTools) {
320
+ const accountIdProperty = stringParam({
321
+ description: 'Claworld account id to execute the tool against.',
322
+ minLength: 1,
323
+ examples: ['claworld'],
324
+ });
325
+ const worldIdProperty = stringParam({
326
+ description: 'Canonical world id.',
327
+ minLength: 1,
328
+ examples: ['dating-demo-world'],
329
+ });
330
+ const searchTool = 'claworld_search';
331
+ const manageWorldsTool = 'claworld_manage_worlds';
332
+ const manageConversationsTool = 'claworld_manage_conversations';
333
+ const accountTool = 'claworld_manage_account';
334
+ const publicProfileTool = 'claworld_get_public_profile';
335
+
336
+ return [
337
+ {
338
+ name: accountTool,
339
+ label: 'Claworld Manage Account',
340
+ description: 'Terminal account surface for readiness, public identity, global profile, share-card generation, and account-level chat policy.',
341
+ metadata: buildToolMetadata({
342
+ category: 'account',
343
+ usageNotes: [
344
+ 'Use this owner-facing account surface for activation, profile, policy, and subscription decisions.',
345
+ 'Use action=view_account for readiness; update_display_name, update_agent_profile, or set_chat_policy for common account mutations.',
346
+ 'Use subscribe_person or unsubscribe_person when a search/profile result exposes a person subscription target.',
347
+ ],
348
+ }),
349
+ parameters: objectParam({
350
+ description: 'Terminal account management payload.',
351
+ required: ['accountId'],
352
+ properties: {
353
+ accountId: accountIdProperty,
354
+ action: stringParam({
355
+ description: 'Account action.',
356
+ enumValues: TERMINAL_ACCOUNT_ACTIONS,
357
+ examples: ['view_account', 'update_display_name', 'set_chat_policy'],
358
+ }),
359
+ displayName: stringParam({
360
+ description: 'Public-facing display name for activate_account or update_display_name.',
361
+ minLength: 1,
362
+ examples: ['Moza', '小发发'],
363
+ }),
364
+ profile: stringParam({
365
+ description: 'Optional global profile text used when updating the agent profile.',
366
+ examples: ['喜欢慢节奏介绍,也愿意先让 agent 做初步认识。🙂'],
367
+ }),
368
+ humanProfile: stringParam({
369
+ description: 'Owner-facing human profile text for update_human_profile.',
370
+ examples: ['周末在上海,喜欢网球和安静咖啡馆。'],
371
+ }),
372
+ agentProfile: stringParam({
373
+ description: 'Agent-facing profile/personality text for update_agent_profile.',
374
+ examples: ['偏主动但会先确认边界,擅长总结和约局。'],
375
+ }),
376
+ discoverable: booleanParam({ description: 'Whether this account can appear in global people search.' }),
377
+ contactable: booleanParam({ description: 'Whether this account can receive direct public-profile chat requests.' }),
378
+ chatRequestApprovalPolicy: objectParam({
379
+ description: 'Backend-managed inbound chat-request policy for this account.',
380
+ additionalProperties: true,
381
+ }),
382
+ proactivitySettings: objectParam({
383
+ description: 'Account-level proactive-management settings.',
384
+ additionalProperties: true,
385
+ }),
386
+ communicationPreference: objectParam({
387
+ description: 'Optional account-level communication preference settings.',
388
+ additionalProperties: true,
389
+ }),
390
+ targetAgentId: stringParam({
391
+ description: 'Target agent id for subscribe_person or unsubscribe_person.',
392
+ minLength: 1,
393
+ examples: ['agt_alice'],
394
+ }),
395
+ targetId: stringParam({
396
+ description: 'Generic target id for subscription actions.',
397
+ minLength: 1,
398
+ }),
399
+ subscriptionId: stringParam({
400
+ description: 'Existing subscription id for unsubscribe_person.',
401
+ minLength: 1,
402
+ examples: ['sub_123'],
403
+ }),
404
+ generateShareCard: booleanParam({
405
+ description: 'When true, include a temporary public identity card when supported.',
406
+ }),
407
+ expiresInSeconds: integerParam({
408
+ description: 'Optional temporary share-card TTL in seconds.',
409
+ minimum: 1,
410
+ examples: [7200],
411
+ }),
412
+ },
413
+ }),
414
+ async execute(toolCallId, params = {}) {
415
+ const action = normalizeTerminalAccountAction(params);
416
+ const subscriptionTargetId = normalizeText(params.targetAgentId, normalizeText(params.targetId, null));
417
+ if (action === 'subscribe_person') {
418
+ if (!subscriptionTargetId) requireManageWorldField('targetAgentId', 'targetAgentId is required for action=subscribe_person');
419
+ const context = await resolveToolContext(api, plugin, params, {
420
+ requiredPublicIdentityCapability: 'subscribe person',
421
+ });
422
+ const payload = await plugin.runtime.productShell.subscriptions.createSubscription({
423
+ ...context,
424
+ targetType: 'person',
425
+ targetId: subscriptionTargetId,
426
+ });
427
+ return buildTerminalActionResult({ tool: accountTool, action, payload });
428
+ }
429
+ if (action === 'unsubscribe_person') {
430
+ const context = await resolveToolContext(api, plugin, params, {
431
+ requiredPublicIdentityCapability: 'unsubscribe person',
432
+ });
433
+ const payload = await plugin.runtime.productShell.subscriptions.deleteSubscription({
434
+ ...context,
435
+ subscriptionId: params.subscriptionId || null,
436
+ targetType: subscriptionTargetId ? 'person' : null,
437
+ targetId: subscriptionTargetId,
438
+ });
439
+ return buildTerminalActionResult({ tool: accountTool, action, payload });
440
+ }
441
+
442
+ const implementationAction = ACCOUNT_IMPLEMENTATION_ACTIONS[action] || null;
443
+ if (implementationAction) {
444
+ const implementationParams = {
445
+ ...params,
446
+ action: implementationAction,
447
+ ...(action === 'update_agent_profile' && params.agentProfile !== undefined
448
+ ? { profile: params.agentProfile }
449
+ : {}),
450
+ };
451
+ const result = await requireTerminalTool(internalTools, 'claworld_account').execute(toolCallId, implementationParams);
452
+ return rewriteToolResultName(
453
+ result,
454
+ accountTool,
455
+ action,
456
+ );
457
+ }
458
+
459
+ if (typeof plugin.runtime.productShell.profile.executeAccountAction !== 'function') {
460
+ requireManageWorldField('action', `action=${action} requires the account management runtime adapter`);
461
+ }
462
+ const context = await resolveToolContext(api, plugin, params, {
463
+ requiredPublicIdentityCapability: action === 'activate_account' ? null : 'manage account',
464
+ });
465
+ const generateShareCard = typeof params.generateShareCard === 'boolean'
466
+ ? params.generateShareCard
467
+ : action === 'update_display_name';
468
+ const payload = await plugin.runtime.productShell.profile.executeAccountAction({
469
+ ...context,
470
+ action,
471
+ displayName: params.displayName || null,
472
+ profile: params.profile,
473
+ humanProfile: params.humanProfile,
474
+ agentProfile: params.agentProfile,
475
+ discoverable: params.discoverable,
476
+ contactable: params.contactable,
477
+ chatRequestApprovalPolicy: params.chatRequestApprovalPolicy || null,
478
+ proactivitySettings: params.proactivitySettings,
479
+ communicationPreference: params.communicationPreference,
480
+ generateShareCard,
481
+ expiresInSeconds: params.expiresInSeconds ?? null,
482
+ });
483
+ return buildTerminalActionResult({ tool: accountTool, action, payload });
484
+ },
485
+ },
486
+ {
487
+ name: searchTool,
488
+ label: 'Claworld Search',
489
+ description: 'Terminal search surface for worlds, world members, and people. Use scope=worlds for world discovery and scope=world_members for in-world member search.',
490
+ metadata: buildToolMetadata({
491
+ category: 'search',
492
+ usageNotes: [
493
+ 'scope=worlds searches or browses visible worlds.',
494
+ 'scope=world_members searches members in an authorized world.',
495
+ 'scope=people searches globally discoverable/contactable public identities.',
496
+ 'scope=mixed combines world, optional world-member, and global people search results in one SearchItemEnvelope list.',
497
+ ],
498
+ }),
499
+ parameters: objectParam({
500
+ description: 'Terminal Claworld search payload.',
501
+ properties: {
502
+ accountId: accountIdProperty,
503
+ scope: stringParam({
504
+ description: 'Search scope.',
505
+ enumValues: ['worlds', 'world_members', 'people', 'mixed'],
506
+ examples: ['mixed'],
507
+ }),
508
+ worldId: worldIdProperty,
509
+ query: stringParam({
510
+ description: 'Optional search text.',
511
+ minLength: 1,
512
+ examples: ['网球 搭子 周末约球'],
513
+ }),
514
+ sort: stringParam({
515
+ description: 'Sort mode for the selected scope.',
516
+ enumValues: ['match', 'hot', 'latest', 'likes'],
517
+ examples: ['match'],
518
+ }),
519
+ limit: integerParam({
520
+ description: 'Maximum result count.',
521
+ minimum: 1,
522
+ maximum: 50,
523
+ examples: [10],
524
+ }),
525
+ page: integerParam({
526
+ description: '1-based page for world search.',
527
+ minimum: 1,
528
+ examples: [1],
529
+ }),
530
+ },
531
+ examples: [
532
+ { accountId: 'claworld', scope: 'worlds', query: '网球', sort: 'match', limit: 5 },
533
+ { accountId: 'claworld', scope: 'world_members', worldId: 'dating-demo-world', query: '上海 周末', limit: 5 },
534
+ { accountId: 'claworld', scope: 'people', query: 'Moza', limit: 5 },
535
+ { accountId: 'claworld', scope: 'mixed', query: '网球 Moza', limit: 5 },
536
+ ],
537
+ }),
538
+ async execute(toolCallId, params = {}) {
539
+ const context = await resolveToolContext(api, plugin, params);
540
+ const scope = normalizeText(params.scope, params.worldId ? 'world_members' : 'mixed');
541
+ if (scope === 'world_members' && !normalizeText(params.worldId, null)) {
542
+ requireManageWorldField('worldId', 'worldId is required for scope=world_members');
543
+ }
544
+ if (!['worlds', 'world_members', 'people', 'mixed'].includes(scope)) {
545
+ requireManageWorldField('scope', 'scope must be one of worlds, world_members, people, or mixed');
546
+ }
547
+ const payload = await plugin.runtime.productShell.search({
548
+ ...context,
549
+ scope,
550
+ worldId: params.worldId || null,
551
+ query: params.query || null,
552
+ sort: params.sort || null,
553
+ limit: params.limit ?? null,
554
+ page: params.page ?? null,
555
+ });
556
+ return buildToolResult({ tool: searchTool, ...payload });
557
+ },
558
+ },
559
+ {
560
+ name: publicProfileTool,
561
+ label: 'Claworld Get Public Profile',
562
+ description: 'Read the current account public profile/readiness projection. Target public-profile lookup is exposed as a terminal tool name and remains backend-fact based.',
563
+ metadata: buildToolMetadata({
564
+ category: 'public_profile',
565
+ usageNotes: [
566
+ 'Use this for public identity readiness and share-card fetches.',
567
+ 'It intentionally does not expose notification or runtime-inbox internals.',
568
+ ],
569
+ }),
570
+ parameters: objectParam({
571
+ description: 'Public profile lookup payload.',
572
+ properties: {
573
+ accountId: accountIdProperty,
574
+ agentCode: stringParam({
575
+ description: 'Optional public code for a future target lookup route.',
576
+ minLength: 1,
577
+ examples: ['ZX82QP'],
578
+ }),
579
+ displayName: stringParam({
580
+ description: 'Optional display name paired with agentCode for a future target lookup route.',
581
+ minLength: 1,
582
+ examples: ['Runtime Peer'],
583
+ }),
584
+ generateShareCard: booleanParam({
585
+ description: 'When true, include the current account share-card when available.',
586
+ }),
587
+ expiresInSeconds: integerParam({
588
+ description: 'Optional temporary share-card TTL in seconds.',
589
+ minimum: 1,
590
+ examples: [7200],
591
+ }),
592
+ },
593
+ }),
594
+ async execute(toolCallId, params = {}) {
595
+ const result = await requireTerminalTool(internalTools, 'claworld_account').execute(toolCallId, {
596
+ ...params,
597
+ action: 'view',
598
+ });
599
+ return rewriteToolResultName(result, publicProfileTool);
600
+ },
601
+ },
602
+ {
603
+ name: manageWorldsTool,
604
+ label: 'Claworld Manage Worlds',
605
+ description: 'Terminal world surface for browsing selected world details, joining, creation, owner governance, broadcast, and membership self-service.',
606
+ metadata: buildToolMetadata({
607
+ category: 'world_management',
608
+ usageNotes: [
609
+ 'action=join_world joins a visible world with world-scoped profile text.',
610
+ 'action=create_world creates an owner-managed world.',
611
+ 'Owner governance and member self-service actions use terminal action names such as update_world, publish_broadcast, and update_world_profile.',
612
+ 'Subscription, activity, and member-list actions are backed by the product-shell terminal routes.',
613
+ 'Retired feed-style recommendations are not exposed as terminal public actions.',
614
+ ],
615
+ }),
616
+ parameters: objectParam({
617
+ description: 'Terminal world management payload.',
618
+ required: ['accountId'],
619
+ properties: {
620
+ accountId: accountIdProperty,
621
+ action: stringParam({
622
+ description: 'World action.',
623
+ enumValues: TERMINAL_WORLD_ACTIONS,
624
+ examples: ['join_world'],
625
+ }),
626
+ worldId: worldIdProperty,
627
+ displayName: stringParam({ description: 'World display name for create/update.', minLength: 1 }),
628
+ worldContextText: stringParam({ description: 'Canonical world context text for create/update.', minLength: 1 }),
629
+ participantContextText: stringParam({ description: 'World-scoped profile text for join/create/update_world_profile.', minLength: 1 }),
630
+ announcementText: stringParam({ description: 'Broadcast text for action=publish_broadcast.', minLength: 1 }),
631
+ audience: stringParam({ description: 'Broadcast audience override.', enumValues: ['members', 'admins', 'admins_and_owner'] }),
632
+ excludeSelf: booleanParam({ description: 'Whether broadcast excludes the sender.' }),
633
+ includeDisabled: booleanParam({ description: 'Whether list actions include disabled rows.' }),
634
+ enabled: booleanParam({ description: 'Whether create/resume should enable the world.' }),
635
+ broadcastEnabled: booleanParam({ description: 'Whether a world subscription should receive broadcasts.' }),
636
+ broadcast: objectParam({ description: 'Optional broadcast config for update_world or set_world_broadcast_preference.', additionalProperties: true }),
637
+ subscriptionId: stringParam({ description: 'Existing subscription id for unsubscribe_world.', minLength: 1 }),
638
+ limit: integerParam({ description: 'Maximum rows for activity/member listing actions.', minimum: 1, maximum: 100 }),
639
+ status: stringParam({ description: 'Optional membership/subscription status filter.', minLength: 1 }),
640
+ },
641
+ }),
642
+ async execute(toolCallId, params = {}) {
643
+ const action = normalizeTerminalWorldAction(params);
644
+ if (action === 'join_world') {
645
+ const result = await requireTerminalTool(internalTools, 'claworld_join_world').execute(toolCallId, params);
646
+ return rewriteToolResultName(result, manageWorldsTool, action);
647
+ }
648
+ if (action === 'create_world') {
649
+ const result = await requireTerminalTool(internalTools, 'claworld_create_world').execute(toolCallId, params);
650
+ return rewriteToolResultName(result, manageWorldsTool, action);
651
+ }
652
+ if (action === 'get_world') {
653
+ const result = await requireTerminalTool(internalTools, 'claworld_get_world_detail').execute(toolCallId, {
654
+ ...params,
655
+ action: 'get_world_detail',
656
+ });
657
+ return rewriteToolResultName(
658
+ result,
659
+ manageWorldsTool,
660
+ action,
661
+ );
662
+ }
663
+ if (action === 'subscribe_world' || action === 'set_world_broadcast_preference') {
664
+ const worldId = normalizeText(params.worldId, null);
665
+ if (!worldId) requireManageWorldField('worldId');
666
+ const context = await resolveToolContext(api, plugin, params, {
667
+ requiredPublicIdentityCapability: 'subscribe world',
668
+ });
669
+ const payload = await plugin.runtime.productShell.subscriptions.createSubscription({
670
+ ...context,
671
+ targetType: 'world',
672
+ targetId: worldId,
673
+ broadcastEnabled: params.broadcastEnabled !== false,
674
+ });
675
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
676
+ }
677
+ if (action === 'unsubscribe_world') {
678
+ const worldId = normalizeText(params.worldId, null);
679
+ const context = await resolveToolContext(api, plugin, params, {
680
+ requiredPublicIdentityCapability: 'unsubscribe world',
681
+ });
682
+ const payload = await plugin.runtime.productShell.subscriptions.deleteSubscription({
683
+ ...context,
684
+ subscriptionId: params.subscriptionId || null,
685
+ targetType: worldId ? 'world' : null,
686
+ targetId: worldId,
687
+ });
688
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
689
+ }
690
+ if (action === 'list_world_activity' || action === 'list_broadcast_history') {
691
+ const worldId = normalizeText(params.worldId, null);
692
+ if (!worldId) requireManageWorldField('worldId');
693
+ const context = await resolveToolContext(api, plugin, params, {
694
+ requiredPublicIdentityCapability: 'list world activity',
695
+ });
696
+ const payload = await plugin.runtime.productShell.activity.listWorldActivity({
697
+ ...context,
698
+ worldId,
699
+ limit: params.limit ?? null,
700
+ });
701
+ const filteredPayload = action === 'list_broadcast_history' && Array.isArray(payload?.items)
702
+ ? {
703
+ ...payload,
704
+ items: payload.items.filter((item) => /broadcast/i.test(String(item.activityType || item.type || ''))),
705
+ }
706
+ : payload;
707
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload: filteredPayload });
708
+ }
709
+ if (action === 'manage_members') {
710
+ const worldId = normalizeText(params.worldId, null);
711
+ if (!worldId) requireManageWorldField('worldId');
712
+ const context = await resolveToolContext(api, plugin, params, {
713
+ requiredPublicIdentityCapability: 'manage members',
714
+ });
715
+ const payload = await plugin.runtime.productShell.membership.listWorldMembers({
716
+ ...context,
717
+ worldId,
718
+ status: params.status || null,
719
+ limit: params.limit ?? null,
720
+ });
721
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
722
+ }
723
+ if (
724
+ action === 'update_world'
725
+ && typeof params.enabled === 'boolean'
726
+ && !normalizeText(params.worldContextText, null)
727
+ && !normalizeText(params.displayName, null)
728
+ && !normalizeObject(params.broadcast, null)
729
+ ) {
730
+ const worldId = normalizeText(params.worldId, null);
731
+ if (!worldId) requireManageWorldField('worldId');
732
+ const context = await resolveToolContext(api, plugin, params, {
733
+ requiredPublicIdentityCapability: 'manage worlds',
734
+ });
735
+ const payload = await plugin.runtime.productShell.moderation.manageWorld({
736
+ ...context,
737
+ worldId,
738
+ mode: 'patch',
739
+ status: params.enabled ? 'enabled' : 'paused',
740
+ enabled: params.enabled,
741
+ });
742
+ return buildTerminalActionResult({
743
+ tool: manageWorldsTool,
744
+ action,
745
+ payload: projectToolManageWorldActionResponse(payload, {
746
+ accountId: context.accountId,
747
+ action: 'get',
748
+ }),
749
+ });
750
+ }
751
+
752
+ const implementationAction = WORLD_IMPLEMENTATION_ACTIONS[action] || action;
753
+ const implementationParams = {
754
+ ...params,
755
+ action: implementationAction,
756
+ };
757
+ const result = await requireTerminalTool(internalTools, 'claworld_manage_world').execute(toolCallId, implementationParams);
758
+ return rewriteToolResultName(
759
+ result,
760
+ manageWorldsTool,
761
+ action,
762
+ );
763
+ },
764
+ },
765
+ {
766
+ name: manageConversationsTool,
767
+ label: 'Claworld Manage Conversations',
768
+ description: 'Terminal conversation lifecycle surface for starting/re-engaging chat requests and deciding pending requests. Live turns remain owned by conversation sessions.',
769
+ metadata: buildToolMetadata({
770
+ category: 'conversation_management',
771
+ usageNotes: [
772
+ 'action=request starts a direct or world-scoped chat request.',
773
+ 'action=list_related/get_state, accept, reject, and end manage product-level conversation state decisions.',
774
+ 'Do not use this tool for live conversation turns.',
775
+ ],
776
+ }),
777
+ parameters: objectParam({
778
+ description: 'Terminal conversation management payload.',
779
+ required: ['accountId'],
780
+ properties: {
781
+ accountId: accountIdProperty,
782
+ action: stringParam({
783
+ description: 'Conversation action.',
784
+ enumValues: TERMINAL_CONVERSATION_ACTIONS,
785
+ examples: ['request'],
786
+ }),
787
+ displayName: stringParam({ description: 'Target public display name for request.', minLength: 1 }),
788
+ agentCode: stringParam({ description: 'Target public agent code for request.', minLength: 1 }),
789
+ openingMessage: stringParam({ description: 'Request/re-engagement kickoff message.', minLength: 1 }),
790
+ worldId: worldIdProperty,
791
+ filters: objectParam({ description: 'List filters.', additionalProperties: true }),
792
+ chatRequestId: stringParam({ description: 'Request id for accept/reject.', minLength: 1 }),
793
+ conversationKey: stringParam({ description: 'Conversation key for get_state/end.', minLength: 1 }),
794
+ localSessionKey: stringParam({ description: 'Local conversation session key for get_state/end guidance.', minLength: 1 }),
795
+ endingMessage: stringParam({ description: 'Optional final peer-facing message to pair with the conversation-end control token.', minLength: 1 }),
796
+ },
797
+ }),
798
+ async execute(toolCallId, params = {}) {
799
+ const action = normalizeTerminalConversationAction(params.action, 'list_related', { throwOnInvalid: true });
800
+ if (action === 'request') {
801
+ const result = await requireTerminalTool(internalTools, 'claworld_request_chat').execute(toolCallId, {
802
+ ...params,
803
+ action: 'request_chat',
804
+ });
805
+ return rewriteToolResultName(result, manageConversationsTool, action);
806
+ }
807
+ if (['list_related', 'get_state', 'accept', 'reject'].includes(action)) {
808
+ const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
809
+ ...params,
810
+ action: ['list_related', 'get_state'].includes(action) ? 'list' : action,
811
+ });
812
+ return rewriteToolResultName(result, manageConversationsTool, action);
813
+ }
814
+ if (action === 'end') {
815
+ const conversationKey = normalizeText(params.conversationKey, null);
816
+ const localSessionKey = normalizeText(params.localSessionKey, null);
817
+ if (!conversationKey && !localSessionKey) {
818
+ requireManageWorldField('conversationKey', 'conversationKey or localSessionKey is required for action=end');
819
+ }
820
+ const filters = {
821
+ ...(normalizeObject(params.filters, {}) || {}),
822
+ ...(conversationKey ? { conversationKey } : {}),
823
+ ...(localSessionKey ? { localSessionKey } : {}),
824
+ };
825
+ const result = await requireTerminalTool(internalTools, 'claworld_chat_inbox').execute(toolCallId, {
826
+ ...params,
827
+ action: 'list',
828
+ filters,
829
+ });
830
+ const parsed = JSON.parse(result.content?.[0]?.text || '{}');
831
+ return buildTerminalActionResult({
832
+ tool: manageConversationsTool,
833
+ action,
834
+ payload: {
835
+ ...parsed,
836
+ status: 'conversation_session_required',
837
+ ending: {
838
+ status: 'conversation_session_required',
839
+ controlToken: '[[request_conversation_end]]',
840
+ conversationKey,
841
+ localSessionKey,
842
+ endingMessage: normalizeText(params.endingMessage, null),
843
+ instruction:
844
+ 'Send one final peer-facing reply from the Conversation Session and include [[request_conversation_end]] to request a formal close.',
845
+ },
846
+ requiresUserDecision: false,
847
+ nextAction: 'send_final_conversation_session_reply_with_request_conversation_end',
848
+ },
849
+ });
850
+ }
851
+ requireManageWorldField('action', `action must be one of ${TERMINAL_CONVERSATION_ACTIONS.join(', ')}`);
852
+ return buildToolResult({ status: 'error', tool: manageConversationsTool });
853
+ },
854
+ },
855
+ {
856
+ ...requireTerminalTool(internalTools, 'claworld_submit_feedback'),
857
+ description: 'Submit structured operator or developer feedback about the terminal Claworld flow.',
858
+ },
859
+ ];
860
+ }
861
+
112
862
  function buildRegisteredTools(api, plugin) {
113
863
  const accountIdProperty = stringParam({
114
864
  description: 'Claworld account id to execute the tool against. In managed installs this is usually the dedicated claworld account.',
@@ -153,7 +903,7 @@ function buildRegisteredTools(api, plugin) {
153
903
  ],
154
904
  });
155
905
  const worldIdProperty = stringParam({
156
- description: 'Canonical world id returned by claworld_search_worlds or claworld_get_world_detail.',
906
+ description: 'Canonical world id returned by claworld_search(scope=worlds) or claworld_manage_worlds(action=get_world).',
157
907
  minLength: 1,
158
908
  examples: ['dating-demo-world'],
159
909
  });
@@ -203,7 +953,6 @@ function buildRegisteredTools(api, plugin) {
203
953
  'This is the main public discovery surface for worlds.',
204
954
  '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.',
205
955
  'Expected behavior: returns paginated world summaries plus structured follow-up actions for detail and join.',
206
- 'claworld_list_worlds is only the compatibility browse alias for the empty-query branch.',
207
956
  ],
208
957
  examples: [
209
958
  {
@@ -278,12 +1027,12 @@ function buildRegisteredTools(api, plugin) {
278
1027
  {
279
1028
  name: 'claworld_list_worlds',
280
1029
  label: 'Claworld List Worlds',
281
- description: 'Compatibility browse alias for the search_worlds browse branch. Prefer claworld_search_worlds for all new world discovery flows.',
1030
+ description: 'Browse worlds without a query. Use claworld_search_worlds when the user supplies topic, intent, hobby, location, or other keywords.',
282
1031
  metadata: buildToolMetadata({
283
1032
  category: 'world_discovery',
284
1033
  usageNotes: [
285
- 'Compatibility-only wrapper around claworld_search_worlds with an empty query.',
286
- 'Prefer claworld_search_worlds unless an existing workflow explicitly asks for list_worlds.',
1034
+ 'This tool returns the no-query world browse result.',
1035
+ 'Use claworld_search_worlds when the user supplies topic, intent, hobby, location, or other keywords.',
287
1036
  ],
288
1037
  examples: [
289
1038
  {
@@ -386,15 +1135,15 @@ function buildRegisteredTools(api, plugin) {
386
1135
  {
387
1136
  name: 'claworld_join_world',
388
1137
  label: 'Claworld Join World',
389
- 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.',
1138
+ description: 'Canonical world-entry tool. Submit one world-scoped participantContextText for the selected world and receive the current join result, membership state, and terminal member-discovery follow-up actions.',
390
1139
  metadata: buildToolMetadata({
391
1140
  category: 'world_join',
392
1141
  usageNotes: [
393
1142
  'This is the only public join entrypoint for the default flow.',
394
1143
  'Provide one participantContextText that describes who the agent is in this world.',
395
- 'Expected behavior: on success it creates or updates the caller\'s active membership for that world and returns the current candidate-review surface.',
396
- 'When status is joined, read candidateFeed/candidateDelivery online state, then use requestChatAction and move to claworld_request_chat.',
397
- 'If the agent later needs a fresh candidate list for the same world, call claworld_get_candidate_feed instead of repeating join.',
1144
+ 'Expected behavior: on success it creates or updates the caller\'s active membership for that world and returns member-search, activity, subscription, and optional request-chat follow-up actions.',
1145
+ 'When status is joined, use memberSearchAction or worldActivityAction before requestChatAction unless a target member is already known.',
1146
+ 'If the agent later needs fresh member suggestions for the same world, call claworld_search_world_members instead of repeating join.',
398
1147
  ],
399
1148
  examples: [
400
1149
  {
@@ -404,7 +1153,7 @@ function buildRegisteredTools(api, plugin) {
404
1153
  worldId: 'dating-demo-world',
405
1154
  participantContextText: 'I am a builder who likes climbing and is looking for new friends first in Shanghai.',
406
1155
  },
407
- outcome: 'Returns joined plus online candidateDelivery and requestChatAction.',
1156
+ outcome: 'Returns joined plus memberSearchAction, worldActivityAction, subscribeWorldAction, and requestChatAction.',
408
1157
  },
409
1158
  ],
410
1159
  }),
@@ -452,7 +1201,7 @@ function buildRegisteredTools(api, plugin) {
452
1201
  'Use this when the agent has a concrete member-search intent after join, such as tennis level, city, schedule, style, or relationship preference.',
453
1202
  'Expected behavior: returns matched member summaries plus request_chat payloads scoped to that world.',
454
1203
  'This is not a guaranteed displayName or nickname directory lookup surface; exact-name-only queries may miss.',
455
- 'Use claworld_get_candidate_feed for recommendation refresh; use this tool for explicit member search.',
1204
+ 'Use this tool for both recommendation refresh and explicit member search within an active world membership.',
456
1205
  ],
457
1206
  examples: [
458
1207
  {
@@ -512,63 +1261,6 @@ function buildRegisteredTools(api, plugin) {
512
1261
  return buildToolResult(projectToolWorldMemberSearchResponse(payload, { accountId: context.accountId }));
513
1262
  },
514
1263
  },
515
- {
516
- name: 'claworld_get_candidate_feed',
517
- label: 'Claworld Get Candidate Feed',
518
- description: 'Read-only candidate refresh tool. For an already joined world, fetch the latest candidate feed without joining again.',
519
- metadata: buildToolMetadata({
520
- category: 'world_candidate_feed',
521
- usageNotes: [
522
- 'Use after a successful join when the agent needs the latest candidate list for the same world.',
523
- 'Expected behavior: refreshes the current recommendation surface for the existing active membership without mutating join state.',
524
- 'This tool reads the current active membership-backed candidate feed; do not resend participantContextText.',
525
- 'The returned candidateDelivery and requestChatAction contract matches the canonical follow-up payload used after join.',
526
- ],
527
- examples: [
528
- {
529
- title: 'Refresh the latest candidates for one joined world',
530
- input: {
531
- accountId: 'claworld',
532
- worldId: 'dating-demo-world',
533
- limit: 3,
534
- },
535
- outcome: 'Returns the latest candidateFeed, candidateDelivery, and requestChatAction for the current active membership.',
536
- },
537
- ],
538
- }),
539
- parameters: objectParam({
540
- description: 'Read-only payload for refreshing candidate feed in one already joined world.',
541
- required: ['accountId', 'worldId'],
542
- properties: {
543
- accountId: accountIdProperty,
544
- worldId: worldIdProperty,
545
- limit: integerParam({
546
- description: 'Optional maximum number of candidates to return from the current feed.',
547
- minimum: 1,
548
- examples: [3],
549
- }),
550
- },
551
- examples: [
552
- {
553
- accountId: 'claworld',
554
- worldId: 'dating-demo-world',
555
- limit: 3,
556
- },
557
- ],
558
- }),
559
- async execute(_toolCallId, params = {}) {
560
- const context = await resolveToolContext(api, plugin, params, {
561
- requiredPublicIdentityCapability: 'refresh candidate feed',
562
- });
563
- const payload = await plugin.runtime.productShell.fetchWorldCandidateFeed({
564
- ...context,
565
- worldId: params.worldId,
566
- agentId: context.agentId,
567
- limit: params.limit ?? null,
568
- });
569
- return buildToolResult(projectToolCandidateFeedResponse(payload, { accountId: context.accountId }));
570
- },
571
- },
572
1264
  {
573
1265
  name: 'claworld_create_world',
574
1266
  label: 'Claworld Create World',
@@ -578,7 +1270,7 @@ function buildRegisteredTools(api, plugin) {
578
1270
  usageNotes: [
579
1271
  'Use only when the user explicitly wants to create a new owner-managed world.',
580
1272
  'Provide displayName, worldContextText, and one owner participantContextText; the backend issues the canonical worldId.',
581
- 'The response keeps the managed world fields and also returns ownerJoin with the canonical join/candidate follow-up payload.',
1273
+ 'The response keeps the managed world fields and returns ownerJoin with the canonical member-search, activity, and subscription follow-up payload.',
582
1274
  ],
583
1275
  examples: [
584
1276
  {
@@ -934,13 +1626,13 @@ function buildRegisteredTools(api, plugin) {
934
1626
  {
935
1627
  name: 'claworld_request_chat',
936
1628
  label: 'Claworld Request Chat',
937
- description: 'Use in the main session to create a new Claworld chat request or re-engage a selected candidate or known public identity. Do not use for live conversation turns, current-session replies, or progress relay inside an already-open Claworld chat runtime.',
1629
+ description: 'Use in the main session to create a new Claworld chat request or re-engage a selected public identity. Do not use for live conversation turns, current-session replies, or progress relay inside an already-open Claworld chat runtime.',
938
1630
  metadata: buildToolMetadata({
939
1631
  category: 'chat_request',
940
1632
  usageNotes: [
941
1633
  '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.',
942
1634
  '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.',
943
- 'For world-scoped chat or re-engagement, use the displayName and agentCode returned by claworld_join_world or claworld_get_candidate_feed candidate delivery.',
1635
+ 'For world-scoped chat or re-engagement, use the displayName and agentCode returned by world member search.',
944
1636
  'The backend resolves the target by agentCode.',
945
1637
  '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.',
946
1638
  'openingMessage is required and must contain non-blank kickoff intent; missing or blank opener text fails with opening_message_required.',
@@ -950,11 +1642,11 @@ function buildRegisteredTools(api, plugin) {
950
1642
  ],
951
1643
  examples: [
952
1644
  {
953
- title: 'Request chat with a world candidate',
1645
+ title: 'Request chat with a world member',
954
1646
  input: {
955
1647
  accountId: 'claworld',
956
1648
  worldId: 'dating-demo-world',
957
- displayName: 'Runtime Candidate',
1649
+ displayName: 'Runtime Peer',
958
1650
  agentCode: 'ZX82QP',
959
1651
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
960
1652
  },
@@ -964,7 +1656,7 @@ function buildRegisteredTools(api, plugin) {
964
1656
  title: 'Re-engage a known public identity',
965
1657
  input: {
966
1658
  accountId: 'claworld',
967
- displayName: 'Runtime Candidate',
1659
+ displayName: 'Runtime Peer',
968
1660
  agentCode: 'ZX82QP',
969
1661
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
970
1662
  },
@@ -980,7 +1672,7 @@ function buildRegisteredTools(api, plugin) {
980
1672
  displayName: stringParam({
981
1673
  description: 'Target public displayName for the request or re-engagement target.',
982
1674
  minLength: 1,
983
- examples: ['Runtime Candidate'],
1675
+ examples: ['Runtime Peer'],
984
1676
  }),
985
1677
  agentCode: stringParam({
986
1678
  description: 'Target public agentCode. The backend resolves the target by this code and verifies the displayName still matches. Use public identity, not local session references.',
@@ -998,7 +1690,7 @@ function buildRegisteredTools(api, plugin) {
998
1690
  {
999
1691
  accountId: 'claworld',
1000
1692
  worldId: 'dating-demo-world',
1001
- displayName: 'Runtime Candidate',
1693
+ displayName: 'Runtime Peer',
1002
1694
  agentCode: 'ZX82QP',
1003
1695
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
1004
1696
  },
@@ -1187,18 +1879,18 @@ function buildRegisteredTools(api, plugin) {
1187
1879
  ],
1188
1880
  examples: [
1189
1881
  {
1190
- title: 'Report a candidate-review issue',
1882
+ title: 'Report a member-search issue',
1191
1883
  input: {
1192
1884
  accountId: 'claworld',
1193
1885
  category: 'feature_request',
1194
1886
  title: 'Need a shortlist export tool',
1195
- goal: 'Share matched candidates with another operator.',
1196
- actualBehavior: 'No export tool is available after reviewing candidates.',
1887
+ goal: 'Share matched members with another operator.',
1888
+ actualBehavior: 'No export tool is available after reviewing member-search results.',
1197
1889
  expectedBehavior: 'A structured export or handoff tool should be available.',
1198
1890
  impact: 'medium',
1199
1891
  context: {
1200
1892
  worldId: 'dating-demo-world',
1201
- tags: ['candidate-feed', 'handoff'],
1893
+ tags: ['member-search', 'handoff'],
1202
1894
  },
1203
1895
  },
1204
1896
  outcome: 'Creates one structured feedback record and returns feedbackId for follow-up.',
@@ -1218,22 +1910,22 @@ function buildRegisteredTools(api, plugin) {
1218
1910
  title: stringParam({
1219
1911
  description: 'Short feedback title.',
1220
1912
  minLength: 1,
1221
- examples: ['Candidate review returned an offline match'],
1913
+ examples: ['Member search returned an offline match'],
1222
1914
  }),
1223
1915
  goal: stringParam({
1224
1916
  description: 'What the operator or user was trying to achieve.',
1225
1917
  minLength: 1,
1226
- examples: ['Find only online candidates in one world.'],
1918
+ examples: ['Find only online members in one world.'],
1227
1919
  }),
1228
1920
  actualBehavior: stringParam({
1229
1921
  description: 'What actually happened.',
1230
1922
  minLength: 1,
1231
- examples: ['An offline candidate was included in the shortlist.'],
1923
+ examples: ['An offline member was included in the search results.'],
1232
1924
  }),
1233
1925
  expectedBehavior: stringParam({
1234
1926
  description: 'What should have happened instead.',
1235
1927
  minLength: 1,
1236
- examples: ['Only online candidates should be returned.'],
1928
+ examples: ['Only online members should be returned.'],
1237
1929
  }),
1238
1930
  impact: stringParam({
1239
1931
  description: 'Severity estimate for prioritization.',
@@ -1251,8 +1943,8 @@ function buildRegisteredTools(api, plugin) {
1251
1943
  examples: [
1252
1944
  [
1253
1945
  'Join one world with participantContextText.',
1254
- 'Review the returned candidate feed.',
1255
- 'Observe one offline candidate in the shortlist.',
1946
+ 'Run claworld_search with scope=world_members.',
1947
+ 'Observe one offline member in the results.',
1256
1948
  ],
1257
1949
  ],
1258
1950
  }),
@@ -1274,24 +1966,24 @@ function buildRegisteredTools(api, plugin) {
1274
1966
  }),
1275
1967
  targetAgentId: stringParam({
1276
1968
  description: 'Optional peer agentId related to the issue.',
1277
- examples: ['agt_runtime_candidate'],
1969
+ examples: ['agt_runtime_peer'],
1278
1970
  }),
1279
1971
  tags: arrayParam({
1280
1972
  description: 'Short labels used for moderation and triage filtering.',
1281
1973
  maxItems: 10,
1282
1974
  items: stringParam({}),
1283
- examples: [['candidate-feed', 'presence']],
1975
+ examples: [['member-search', 'presence']],
1284
1976
  }),
1285
1977
  metadata: objectParam({
1286
1978
  description: 'Optional extra structured debugging metadata.',
1287
1979
  additionalProperties: true,
1288
- examples: [{ stage: 'candidate_review' }],
1980
+ examples: [{ stage: 'member_search' }],
1289
1981
  }),
1290
1982
  },
1291
1983
  examples: [
1292
1984
  {
1293
1985
  worldId: 'dating-demo-world',
1294
- tags: ['candidate-feed', 'presence'],
1986
+ tags: ['member-search', 'presence'],
1295
1987
  },
1296
1988
  ],
1297
1989
  }),
@@ -1300,14 +1992,14 @@ function buildRegisteredTools(api, plugin) {
1300
1992
  {
1301
1993
  accountId: 'claworld',
1302
1994
  category: 'bug_report',
1303
- title: 'Candidate review returned an offline match',
1304
- goal: 'Find only online candidates in one world.',
1305
- actualBehavior: 'An offline candidate was included in the shortlist.',
1306
- expectedBehavior: 'Only online candidates should be returned.',
1995
+ title: 'Member search returned an offline match',
1996
+ goal: 'Find only online members in one world.',
1997
+ actualBehavior: 'An offline member was included in the search results.',
1998
+ expectedBehavior: 'Only online members should be returned.',
1307
1999
  impact: 'high',
1308
2000
  context: {
1309
2001
  worldId: 'dating-demo-world',
1310
- tags: ['candidate-feed', 'presence'],
2002
+ tags: ['member-search', 'presence'],
1311
2003
  },
1312
2004
  },
1313
2005
  ],
@@ -1574,23 +2266,97 @@ export function registerClaworldPluginFull(api, plugin) {
1574
2266
  throw new Error('registerClaworldPluginFull requires a plugin instance');
1575
2267
  }
1576
2268
  if (typeof api.on === 'function') {
2269
+ api.on('before_prompt_build', async (event = {}, ctx = {}) => {
2270
+ const logger = getHookLogger(api);
2271
+ const workspaceRoot = await resolveHookWorkspaceRoot(api, event, ctx);
2272
+ const bootstrapContext = { ...event, ...ctx, workspaceRoot };
2273
+ const bootstrapTarget = resolveClaworldBootstrapTarget(bootstrapContext);
2274
+ try {
2275
+ if (workspaceRoot) {
2276
+ await ensureClaworldWorkingMemory(workspaceRoot);
2277
+ }
2278
+ } catch (error) {
2279
+ logger?.warn?.('[claworld:working-memory] unable to ensure workspace memory', error);
2280
+ }
2281
+ try {
2282
+ const injection = await buildClaworldBootstrapPromptContext(
2283
+ bootstrapContext,
2284
+ { workspaceRoot },
2285
+ );
2286
+ if (!injection?.appendSystemContext) return;
2287
+ logger?.info?.('[claworld:working-memory] prompt bootstrap', {
2288
+ target: injection.target,
2289
+ channel: injection.context?.channel || null,
2290
+ sessionKey: injection.context?.sessionKey || null,
2291
+ sessionType: injection.context?.sessionType || null,
2292
+ files: injection.files,
2293
+ pointerInjected: injection.pointerInjected,
2294
+ fallbackFiles: injection.fallbackFiles,
2295
+ omittedFiles: injection.omittedFiles,
2296
+ truncated: injection.truncated,
2297
+ });
2298
+ return {
2299
+ appendSystemContext: injection.appendSystemContext,
2300
+ };
2301
+ } catch (error) {
2302
+ logger?.warn?.('[claworld:working-memory] unable to build prompt bootstrap context', error);
2303
+ }
2304
+ if (bootstrapTarget === CLAWORLD_BOOTSTRAP_TARGETS.MAIN) {
2305
+ return {
2306
+ appendSystemContext: buildClaworldContextPointer(),
2307
+ };
2308
+ }
2309
+ return;
2310
+ });
2311
+
1577
2312
  api.on('before_tool_call', async (event, ctx) => {
1578
- if (event?.toolName !== 'claworld_request_chat') return;
2313
+ if (event?.toolName !== 'claworld_manage_conversations') return;
2314
+ const params = event?.params && typeof event.params === 'object' && !Array.isArray(event.params)
2315
+ ? event.params
2316
+ : {};
2317
+ if (normalizeTerminalConversationAction(params.action, null) !== 'request') return;
1579
2318
  const requesterSessionKey = normalizeText(ctx?.sessionKey, null);
1580
2319
  if (!requesterSessionKey) return;
1581
2320
  return {
1582
2321
  params: {
1583
- ...(event?.params && typeof event.params === 'object' && !Array.isArray(event.params) ? event.params : {}),
2322
+ ...params,
1584
2323
  [INTERNAL_REQUESTER_SESSION_KEY_PARAM]: requesterSessionKey,
1585
2324
  },
1586
2325
  };
1587
2326
  });
2327
+
2328
+ api.on('after_tool_call', async (event = {}, ctx = {}) => {
2329
+ if (!isSuccessfulHookToolCall(event)) return;
2330
+ const toolName = normalizeText(event?.toolName ?? ctx?.toolName, null);
2331
+ if (!toolName || !toolName.startsWith('claworld_')) return;
2332
+ const logger = getHookLogger(api);
2333
+ try {
2334
+ const workspaceRoot = await resolveHookWorkspaceRoot(api, event, ctx);
2335
+ if (!workspaceRoot) return;
2336
+ const maintenanceEvent = buildClaworldToolMaintenanceEvent({
2337
+ toolName,
2338
+ params: event?.params || {},
2339
+ result: hookToolResult(event),
2340
+ timestamp: event?.timestamp || ctx?.timestamp || null,
2341
+ });
2342
+ if (!maintenanceEvent) return;
2343
+ await appendClaworldJournalEvent(workspaceRoot, maintenanceEvent);
2344
+ } catch (error) {
2345
+ logger?.warn?.('[claworld:working-memory] unable to append tool event', error);
2346
+ }
2347
+ });
1588
2348
  }
1589
2349
  if (typeof api.registerHttpRoute === 'function') {
1590
2350
  api.registerHttpRoute(buildClaworldStatusRoute(plugin));
1591
2351
  }
1592
2352
  if (typeof api.registerTool === 'function') {
1593
- for (const tool of buildRegisteredTools(api, plugin)) {
2353
+ const internalTools = new Map(
2354
+ buildRegisteredTools(api, plugin).map((tool) => [tool.name, tool]),
2355
+ );
2356
+ for (const tool of createTerminalToolAdapters(api, plugin, internalTools).map((terminalTool) => ({
2357
+ ...terminalTool,
2358
+ execute: withToolErrorBoundary(terminalTool.name, terminalTool.execute),
2359
+ }))) {
1594
2360
  api.registerTool(tool);
1595
2361
  }
1596
2362
  }