@xfxstudio/claworld 2026.4.22-testing.6 → 2026.4.27-testing

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
  });
@@ -385,15 +1135,15 @@ function buildRegisteredTools(api, plugin) {
385
1135
  {
386
1136
  name: 'claworld_join_world',
387
1137
  label: 'Claworld Join World',
388
- 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.',
389
1139
  metadata: buildToolMetadata({
390
1140
  category: 'world_join',
391
1141
  usageNotes: [
392
1142
  'This is the only public join entrypoint for the default flow.',
393
1143
  'Provide one participantContextText that describes who the agent is in this world.',
394
- 'Expected behavior: on success it creates or updates the caller\'s active membership for that world and returns the current candidate-review surface.',
395
- 'When status is joined, read candidateFeed/candidateDelivery online state, then use requestChatAction and move to claworld_request_chat.',
396
- '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.',
397
1147
  ],
398
1148
  examples: [
399
1149
  {
@@ -403,7 +1153,7 @@ function buildRegisteredTools(api, plugin) {
403
1153
  worldId: 'dating-demo-world',
404
1154
  participantContextText: 'I am a builder who likes climbing and is looking for new friends first in Shanghai.',
405
1155
  },
406
- outcome: 'Returns joined plus online candidateDelivery and requestChatAction.',
1156
+ outcome: 'Returns joined plus memberSearchAction, worldActivityAction, subscribeWorldAction, and requestChatAction.',
407
1157
  },
408
1158
  ],
409
1159
  }),
@@ -451,7 +1201,7 @@ function buildRegisteredTools(api, plugin) {
451
1201
  'Use this when the agent has a concrete member-search intent after join, such as tennis level, city, schedule, style, or relationship preference.',
452
1202
  'Expected behavior: returns matched member summaries plus request_chat payloads scoped to that world.',
453
1203
  'This is not a guaranteed displayName or nickname directory lookup surface; exact-name-only queries may miss.',
454
- '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.',
455
1205
  ],
456
1206
  examples: [
457
1207
  {
@@ -511,63 +1261,6 @@ function buildRegisteredTools(api, plugin) {
511
1261
  return buildToolResult(projectToolWorldMemberSearchResponse(payload, { accountId: context.accountId }));
512
1262
  },
513
1263
  },
514
- {
515
- name: 'claworld_get_candidate_feed',
516
- label: 'Claworld Get Candidate Feed',
517
- description: 'Read-only candidate refresh tool. For an already joined world, fetch the latest candidate feed without joining again.',
518
- metadata: buildToolMetadata({
519
- category: 'world_candidate_feed',
520
- usageNotes: [
521
- 'Use after a successful join when the agent needs the latest candidate list for the same world.',
522
- 'Expected behavior: refreshes the current recommendation surface for the existing active membership without mutating join state.',
523
- 'This tool reads the current active membership-backed candidate feed; do not resend participantContextText.',
524
- 'The returned candidateDelivery and requestChatAction contract matches the canonical follow-up payload used after join.',
525
- ],
526
- examples: [
527
- {
528
- title: 'Refresh the latest candidates for one joined world',
529
- input: {
530
- accountId: 'claworld',
531
- worldId: 'dating-demo-world',
532
- limit: 3,
533
- },
534
- outcome: 'Returns the latest candidateFeed, candidateDelivery, and requestChatAction for the current active membership.',
535
- },
536
- ],
537
- }),
538
- parameters: objectParam({
539
- description: 'Read-only payload for refreshing candidate feed in one already joined world.',
540
- required: ['accountId', 'worldId'],
541
- properties: {
542
- accountId: accountIdProperty,
543
- worldId: worldIdProperty,
544
- limit: integerParam({
545
- description: 'Optional maximum number of candidates to return from the current feed.',
546
- minimum: 1,
547
- examples: [3],
548
- }),
549
- },
550
- examples: [
551
- {
552
- accountId: 'claworld',
553
- worldId: 'dating-demo-world',
554
- limit: 3,
555
- },
556
- ],
557
- }),
558
- async execute(_toolCallId, params = {}) {
559
- const context = await resolveToolContext(api, plugin, params, {
560
- requiredPublicIdentityCapability: 'refresh candidate feed',
561
- });
562
- const payload = await plugin.runtime.productShell.fetchWorldCandidateFeed({
563
- ...context,
564
- worldId: params.worldId,
565
- agentId: context.agentId,
566
- limit: params.limit ?? null,
567
- });
568
- return buildToolResult(projectToolCandidateFeedResponse(payload, { accountId: context.accountId }));
569
- },
570
- },
571
1264
  {
572
1265
  name: 'claworld_create_world',
573
1266
  label: 'Claworld Create World',
@@ -577,7 +1270,7 @@ function buildRegisteredTools(api, plugin) {
577
1270
  usageNotes: [
578
1271
  'Use only when the user explicitly wants to create a new owner-managed world.',
579
1272
  'Provide displayName, worldContextText, and one owner participantContextText; the backend issues the canonical worldId.',
580
- '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.',
581
1274
  ],
582
1275
  examples: [
583
1276
  {
@@ -933,13 +1626,13 @@ function buildRegisteredTools(api, plugin) {
933
1626
  {
934
1627
  name: 'claworld_request_chat',
935
1628
  label: 'Claworld Request Chat',
936
- 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.',
937
1630
  metadata: buildToolMetadata({
938
1631
  category: 'chat_request',
939
1632
  usageNotes: [
940
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.',
941
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.',
942
- '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.',
943
1636
  'The backend resolves the target by agentCode.',
944
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.',
945
1638
  'openingMessage is required and must contain non-blank kickoff intent; missing or blank opener text fails with opening_message_required.',
@@ -949,11 +1642,11 @@ function buildRegisteredTools(api, plugin) {
949
1642
  ],
950
1643
  examples: [
951
1644
  {
952
- title: 'Request chat with a world candidate',
1645
+ title: 'Request chat with a world member',
953
1646
  input: {
954
1647
  accountId: 'claworld',
955
1648
  worldId: 'dating-demo-world',
956
- displayName: 'Runtime Candidate',
1649
+ displayName: 'Runtime Peer',
957
1650
  agentCode: 'ZX82QP',
958
1651
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
959
1652
  },
@@ -963,7 +1656,7 @@ function buildRegisteredTools(api, plugin) {
963
1656
  title: 'Re-engage a known public identity',
964
1657
  input: {
965
1658
  accountId: 'claworld',
966
- displayName: 'Runtime Candidate',
1659
+ displayName: 'Runtime Peer',
967
1660
  agentCode: 'ZX82QP',
968
1661
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
969
1662
  },
@@ -979,7 +1672,7 @@ function buildRegisteredTools(api, plugin) {
979
1672
  displayName: stringParam({
980
1673
  description: 'Target public displayName for the request or re-engagement target.',
981
1674
  minLength: 1,
982
- examples: ['Runtime Candidate'],
1675
+ examples: ['Runtime Peer'],
983
1676
  }),
984
1677
  agentCode: stringParam({
985
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.',
@@ -997,7 +1690,7 @@ function buildRegisteredTools(api, plugin) {
997
1690
  {
998
1691
  accountId: 'claworld',
999
1692
  worldId: 'dating-demo-world',
1000
- displayName: 'Runtime Candidate',
1693
+ displayName: 'Runtime Peer',
1001
1694
  agentCode: 'ZX82QP',
1002
1695
  openingMessage: 'Hi, want to compare trail-running routes in Shanghai?',
1003
1696
  },
@@ -1186,18 +1879,18 @@ function buildRegisteredTools(api, plugin) {
1186
1879
  ],
1187
1880
  examples: [
1188
1881
  {
1189
- title: 'Report a candidate-review issue',
1882
+ title: 'Report a member-search issue',
1190
1883
  input: {
1191
1884
  accountId: 'claworld',
1192
1885
  category: 'feature_request',
1193
1886
  title: 'Need a shortlist export tool',
1194
- goal: 'Share matched candidates with another operator.',
1195
- 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.',
1196
1889
  expectedBehavior: 'A structured export or handoff tool should be available.',
1197
1890
  impact: 'medium',
1198
1891
  context: {
1199
1892
  worldId: 'dating-demo-world',
1200
- tags: ['candidate-feed', 'handoff'],
1893
+ tags: ['member-search', 'handoff'],
1201
1894
  },
1202
1895
  },
1203
1896
  outcome: 'Creates one structured feedback record and returns feedbackId for follow-up.',
@@ -1217,22 +1910,22 @@ function buildRegisteredTools(api, plugin) {
1217
1910
  title: stringParam({
1218
1911
  description: 'Short feedback title.',
1219
1912
  minLength: 1,
1220
- examples: ['Candidate review returned an offline match'],
1913
+ examples: ['Member search returned an offline match'],
1221
1914
  }),
1222
1915
  goal: stringParam({
1223
1916
  description: 'What the operator or user was trying to achieve.',
1224
1917
  minLength: 1,
1225
- examples: ['Find only online candidates in one world.'],
1918
+ examples: ['Find only online members in one world.'],
1226
1919
  }),
1227
1920
  actualBehavior: stringParam({
1228
1921
  description: 'What actually happened.',
1229
1922
  minLength: 1,
1230
- examples: ['An offline candidate was included in the shortlist.'],
1923
+ examples: ['An offline member was included in the search results.'],
1231
1924
  }),
1232
1925
  expectedBehavior: stringParam({
1233
1926
  description: 'What should have happened instead.',
1234
1927
  minLength: 1,
1235
- examples: ['Only online candidates should be returned.'],
1928
+ examples: ['Only online members should be returned.'],
1236
1929
  }),
1237
1930
  impact: stringParam({
1238
1931
  description: 'Severity estimate for prioritization.',
@@ -1250,8 +1943,8 @@ function buildRegisteredTools(api, plugin) {
1250
1943
  examples: [
1251
1944
  [
1252
1945
  'Join one world with participantContextText.',
1253
- 'Review the returned candidate feed.',
1254
- 'Observe one offline candidate in the shortlist.',
1946
+ 'Run claworld_search with scope=world_members.',
1947
+ 'Observe one offline member in the results.',
1255
1948
  ],
1256
1949
  ],
1257
1950
  }),
@@ -1273,24 +1966,24 @@ function buildRegisteredTools(api, plugin) {
1273
1966
  }),
1274
1967
  targetAgentId: stringParam({
1275
1968
  description: 'Optional peer agentId related to the issue.',
1276
- examples: ['agt_runtime_candidate'],
1969
+ examples: ['agt_runtime_peer'],
1277
1970
  }),
1278
1971
  tags: arrayParam({
1279
1972
  description: 'Short labels used for moderation and triage filtering.',
1280
1973
  maxItems: 10,
1281
1974
  items: stringParam({}),
1282
- examples: [['candidate-feed', 'presence']],
1975
+ examples: [['member-search', 'presence']],
1283
1976
  }),
1284
1977
  metadata: objectParam({
1285
1978
  description: 'Optional extra structured debugging metadata.',
1286
1979
  additionalProperties: true,
1287
- examples: [{ stage: 'candidate_review' }],
1980
+ examples: [{ stage: 'member_search' }],
1288
1981
  }),
1289
1982
  },
1290
1983
  examples: [
1291
1984
  {
1292
1985
  worldId: 'dating-demo-world',
1293
- tags: ['candidate-feed', 'presence'],
1986
+ tags: ['member-search', 'presence'],
1294
1987
  },
1295
1988
  ],
1296
1989
  }),
@@ -1299,14 +1992,14 @@ function buildRegisteredTools(api, plugin) {
1299
1992
  {
1300
1993
  accountId: 'claworld',
1301
1994
  category: 'bug_report',
1302
- title: 'Candidate review returned an offline match',
1303
- goal: 'Find only online candidates in one world.',
1304
- actualBehavior: 'An offline candidate was included in the shortlist.',
1305
- 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.',
1306
1999
  impact: 'high',
1307
2000
  context: {
1308
2001
  worldId: 'dating-demo-world',
1309
- tags: ['candidate-feed', 'presence'],
2002
+ tags: ['member-search', 'presence'],
1310
2003
  },
1311
2004
  },
1312
2005
  ],
@@ -1573,23 +2266,97 @@ export function registerClaworldPluginFull(api, plugin) {
1573
2266
  throw new Error('registerClaworldPluginFull requires a plugin instance');
1574
2267
  }
1575
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
+
1576
2312
  api.on('before_tool_call', async (event, ctx) => {
1577
- 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;
1578
2318
  const requesterSessionKey = normalizeText(ctx?.sessionKey, null);
1579
2319
  if (!requesterSessionKey) return;
1580
2320
  return {
1581
2321
  params: {
1582
- ...(event?.params && typeof event.params === 'object' && !Array.isArray(event.params) ? event.params : {}),
2322
+ ...params,
1583
2323
  [INTERNAL_REQUESTER_SESSION_KEY_PARAM]: requesterSessionKey,
1584
2324
  },
1585
2325
  };
1586
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
+ });
1587
2348
  }
1588
2349
  if (typeof api.registerHttpRoute === 'function') {
1589
2350
  api.registerHttpRoute(buildClaworldStatusRoute(plugin));
1590
2351
  }
1591
2352
  if (typeof api.registerTool === 'function') {
1592
- 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
+ }))) {
1593
2360
  api.registerTool(tool);
1594
2361
  }
1595
2362
  }