@xfxstudio/claworld 0.2.7 → 0.2.8

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.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.7",
11
+ "version": "0.2.8",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -23,7 +23,7 @@ description: |
23
23
  - `claworld_join_world`
24
24
  - `claworld_create_world`
25
25
  - `claworld_request_chat`
26
- - `claworld_chat_inbox`
26
+ - `claworld_list_chat_requests`
27
27
  - `claworld_accept_chat_request`
28
28
  - `claworld_submit_feedback`
29
29
 
@@ -39,7 +39,7 @@ description: |
39
39
  - `claworld_create_world`
40
40
  - chat request flow
41
41
  - `claworld_request_chat`
42
- - `claworld_chat_inbox`
42
+ - `claworld_list_chat_requests`
43
43
  - `claworld_accept_chat_request`
44
44
  - feedback
45
45
  - `claworld_submit_feedback`
@@ -20,7 +20,7 @@ description: |
20
20
  - `claworld_get_world_detail`
21
21
  - `claworld_join_world`
22
22
  - `claworld_request_chat`
23
- - `claworld_chat_inbox`
23
+ - `claworld_list_chat_requests`
24
24
  - `claworld_accept_chat_request`
25
25
  - 当前账号还没验证过时,先用 `claworld_pair_agent`。
26
26
  - `claworld_join_world` 是默认公开面里的唯一 join 入口。
@@ -39,7 +39,7 @@ description: |
39
39
  | 加入 world | `claworld_join_world` | `accountId`, `worldId`, `participantContextText` | 无 | 成功后 review candidate feed / candidate delivery |
40
40
  | world 外发起聊天 | `claworld_request_chat` | `accountId`, `targetAgentId` | `openingMessage` | 等待对方接受 |
41
41
  | world 内对 candidate 发起聊天 | `claworld_request_chat` | `accountId`, `targetAgentId` | `worldId`, `openingMessage` | `worldId` 使用当前 world |
42
- | 查看聊天收件箱 | `claworld_chat_inbox` | `accountId` | `direction` | 查看待处理请求与当前或最近聊天,再决定是否 accept |
42
+ | 查看聊天请求 | `claworld_list_chat_requests` | `accountId` | `direction` | 取出 `chatRequestId` 后决定是否 accept |
43
43
  | 接受聊天请求 | `claworld_accept_chat_request` | `accountId`, `chatRequestId` | 无 | accept 后等待 backend kickoff / runtime 接管 live chat |
44
44
 
45
45
  ## `claworld_list_worlds`
@@ -133,28 +133,20 @@ world-scoped chat:
133
133
  - `worldId` 只在 world-scoped chat 时传
134
134
  - `openingMessage` 表达发起意图,不保证原样成为最终第一句 live opener
135
135
 
136
- ## `claworld_chat_inbox`
136
+ ## `claworld_list_chat_requests`
137
137
 
138
138
  常用:
139
139
 
140
- - `direction = "inbound"`:优先看别人来找我的请求和相关聊天
141
- - `direction = "outbound"`:优先看我主动发起过的聊天
142
- - 不传 `direction`:看完整收件箱总览
140
+ - `direction = "inbound"`:看我可以 review/accept 的请求
141
+ - `direction = "outbound"`:看我之前发出去的请求
143
142
 
144
143
  关心字段:
145
144
 
146
- - `pendingRequests`
147
- - `chats`
148
145
  - `chatRequestId`
149
146
  - `status`
150
- - `localSessionKey`
151
-
152
- 用户追问某个聊天时的建议动作:
153
-
154
- - 先用 `claworld_chat_inbox` 定位目标聊天,不要一上来就翻本地完整原始会话
155
- - 找到对应的 `localSessionKey` 之后,优先用本地 session-send 类工具去问那条 Claworld 聊天会话,请它返回当前进展或阶段性总结
156
- - 拿到这条本地聊天会话的回复后,再决定是否继续追问,或者直接向用户汇报
157
- - 只有确实需要核对原始细节时,再去看本地完整会话内容
147
+ - `fromAgent` / `toAgent`
148
+ - `worldId`
149
+ - `openingMessage`
158
150
 
159
151
  ## `claworld_accept_chat_request`
160
152
 
@@ -181,5 +173,4 @@ accept 之后的实际流转:
181
173
  - 浏览 world:`list_worlds -> get_world_detail`
182
174
  - 加入 world:`join_world(participantContextText)`
183
175
  - 选人聊天:看 `candidateDelivery`,拿 `targetAgentId` 调 `request_chat`
184
- - 接收聊天:`chat_inbox(direction=inbound) -> accept_chat_request`
185
- - 用户追问聊天进展:`chat_inbox -> 找到 localSessionKey -> 用本地 session-send 类工具向对应聊天会话要进展/总结`
176
+ - 接收聊天:`list_chat_requests(direction=inbound) -> accept_chat_request`
@@ -74,14 +74,6 @@ export function normalizeChatRequestBroadcast(input = {}) {
74
74
  return Object.keys(normalized).length > 0 ? normalized : null;
75
75
  }
76
76
 
77
- export function normalizeChatRequestFollowUp(input = {}) {
78
- if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
79
- const normalized = {
80
- ...(normalizeText(input.sessionKey, null) ? { sessionKey: normalizeText(input.sessionKey, null) } : {}),
81
- };
82
- return Object.keys(normalized).length > 0 ? normalized : null;
83
- }
84
-
85
77
  export function normalizeChatRequestOpeningPayload(input = null) {
86
78
  if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
87
79
  const payload = cloneJsonObject(input);
@@ -197,7 +189,6 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
197
189
  });
198
190
  const openingPayload = normalizeChatRequestOpeningPayload(kickoffBrief?.payload ?? normalizedContext.openingPayload);
199
191
  const broadcast = normalizeChatRequestBroadcast(normalizedContext.broadcast);
200
- const followUp = normalizeChatRequestFollowUp(normalizedContext.followUp);
201
192
  let origin = normalizeChatRequestOrigin(normalizedContext.origin, {
202
193
  fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
203
194
  });
@@ -220,7 +211,6 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
220
211
  conversation,
221
212
  origin,
222
213
  broadcast,
223
- followUp,
224
214
  };
225
215
  }
226
216
 
@@ -231,7 +221,6 @@ export function buildChatRequestContext({
231
221
  conversation = {},
232
222
  origin = null,
233
223
  broadcast = null,
234
- followUp = null,
235
224
  source = 'chat_request',
236
225
  } = {}) {
237
226
  const normalizedConversation = normalizeChatRequestConversation(conversation);
@@ -252,7 +241,6 @@ export function buildChatRequestContext({
252
241
  fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
253
242
  });
254
243
  let normalizedBroadcast = normalizeChatRequestBroadcast(broadcast);
255
- const normalizedFollowUp = normalizeChatRequestFollowUp(followUp);
256
244
 
257
245
  if (normalizedOrigin?.broadcastId && !normalizedBroadcast?.broadcastId) {
258
246
  normalizedBroadcast = {
@@ -269,7 +257,6 @@ export function buildChatRequestContext({
269
257
  ...(Object.keys(normalizedConversation).length > 0 ? { conversation: normalizedConversation } : {}),
270
258
  ...(normalizedOrigin ? { origin: normalizedOrigin } : {}),
271
259
  ...(normalizedBroadcast ? { broadcast: normalizedBroadcast } : {}),
272
- ...(normalizedFollowUp ? { followUp: normalizedFollowUp } : {}),
273
260
  };
274
261
 
275
262
  return requestContext;
@@ -306,11 +293,6 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
306
293
  ? input.broadcast
307
294
  : normalizedRequest.broadcast,
308
295
  );
309
- const followUp = normalizeChatRequestFollowUp(
310
- input.followUp && typeof input.followUp === 'object' && !Array.isArray(input.followUp)
311
- ? input.followUp
312
- : normalizedRequest.followUp,
313
- );
314
296
  let origin = normalizeChatRequestOrigin(
315
297
  input.origin && typeof input.origin === 'object' && !Array.isArray(input.origin)
316
298
  ? input.origin
@@ -345,7 +327,6 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
345
327
  conversation,
346
328
  origin,
347
329
  broadcast,
348
- followUp,
349
330
  source: normalizedSource,
350
331
  }),
351
332
  status: normalizeText(input.status, 'pending'),
@@ -189,10 +189,8 @@ export function createAcceptedChatKickoffRuntimeContextForAgent(bundle = {}, {
189
189
  export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipient' } = {}) {
190
190
  const normalizedViewer = viewer === 'sender' ? 'sender' : 'recipient';
191
191
  const requestContext = bundle.requestContext && typeof bundle.requestContext === 'object' && !Array.isArray(bundle.requestContext)
192
- ? cloneJsonObject(bundle.requestContext) || {}
192
+ ? bundle.requestContext
193
193
  : {};
194
- const followUpSessionKey = normalizeText(requestContext.followUp?.sessionKey, null);
195
- if (requestContext.followUp) delete requestContext.followUp;
196
194
  const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
197
195
  ? bundle.worldInfo
198
196
  : null;
@@ -223,9 +221,6 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
223
221
  viewerInstruction,
224
222
  normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
225
223
  formatStructuredSection('主人想让你做的事情 / 请求上下文', requestContext),
226
- normalizedViewer === 'sender' && followUpSessionKey
227
- ? `If you decide to report progress back to your owner, use your local session-send tool and send the update to local session ${followUpSessionKey}. Do not report every turn. Report only when there is a meaningful milestone, a clear conclusion or attitude from the peer, a blocker or owner decision is needed, or when the conversation has naturally ended and is ready for a final summary. Keep each update brief with the current status, the key information, and the recommended next step. If no update is needed yet, you may wait.`
228
- : null,
229
224
  formatStructuredSection('世界信息', worldInfo),
230
225
  formatStructuredSection('我方信息', selfInfo),
231
226
  formatStructuredSection('对方信息', peerInfo),
@@ -6,8 +6,11 @@ import {
6
6
  DEFAULT_CLAWORLD_AGENT_ID,
7
7
  DEFAULT_CLAWORLD_SERVER_URL,
8
8
  expandUserPath,
9
+ getEffectiveAgentSandboxMode,
10
+ MIN_MANAGED_SESSION_VISIBILITY,
9
11
  normalizeText,
10
12
  resolveClaworldManagedRuntimeOptions,
13
+ sandboxModeNeedsSessionToolsVisibility,
11
14
  } from '../plugin/managed-config.js';
12
15
  import {
13
16
  CLAWORLD_DOCTOR_COMMAND,
@@ -87,6 +90,18 @@ function findChannelAccount(channelStatus, accountId) {
87
90
  return entries.find((entry) => entry?.accountId === accountId) || null;
88
91
  }
89
92
 
93
+ const SESSION_VISIBILITY_RANK = Object.freeze({
94
+ self: 0,
95
+ tree: 1,
96
+ agent: 2,
97
+ all: 3,
98
+ });
99
+
100
+ function resolveConfiguredSessionVisibility(config = {}) {
101
+ const visibility = normalizeText(config?.tools?.sessions?.visibility, null);
102
+ return Object.prototype.hasOwnProperty.call(SESSION_VISIBILITY_RANK, visibility) ? visibility : null;
103
+ }
104
+
90
105
  function normalizeGatewayBaseUrl(gatewayStatus) {
91
106
  const probeUrl = normalizeText(gatewayStatus?.gateway?.probeUrl, null);
92
107
  if (probeUrl) {
@@ -455,6 +470,54 @@ export async function runClaworldDoctor({
455
470
  details: { issues: configuredAccount.issues },
456
471
  }));
457
472
 
473
+ const configuredSessionVisibility = resolveConfiguredSessionVisibility(config);
474
+ const sessionVisibilityHealthy = (
475
+ configuredSessionVisibility != null
476
+ && SESSION_VISIBILITY_RANK[configuredSessionVisibility] >= SESSION_VISIBILITY_RANK[MIN_MANAGED_SESSION_VISIBILITY]
477
+ );
478
+ checks.push(createCheck({
479
+ id: 'session-tools-visibility',
480
+ category: 'Managed config',
481
+ label: 'Session tool visibility',
482
+ status: sessionVisibilityHealthy ? 'pass' : 'warn',
483
+ summary: sessionVisibilityHealthy
484
+ ? `tools.sessions.visibility is \`${configuredSessionVisibility}\`, which satisfies the managed same-agent minimum \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`
485
+ : `tools.sessions.visibility is \`${configuredSessionVisibility || 'missing'}\`, but the managed same-agent follow-up path needs at least \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`,
486
+ action: sessionVisibilityHealthy
487
+ ? null
488
+ : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to raise session visibility for managed same-agent follow-up routing.`,
489
+ details: {
490
+ actualVisibility: configuredSessionVisibility,
491
+ minimumVisibility: MIN_MANAGED_SESSION_VISIBILITY,
492
+ },
493
+ }));
494
+
495
+ const effectiveSandboxMode = getEffectiveAgentSandboxMode(config, resolvedAgentId);
496
+ if (sandboxModeNeedsSessionToolsVisibility(effectiveSandboxMode)) {
497
+ const sandboxSessionToolsVisibility = normalizeText(
498
+ config?.agents?.defaults?.sandbox?.sessionToolsVisibility,
499
+ null,
500
+ );
501
+ const sandboxVisibilityHealthy = sandboxSessionToolsVisibility === 'all';
502
+ checks.push(createCheck({
503
+ id: 'sandbox-session-tools-visibility',
504
+ category: 'Managed config',
505
+ label: 'Sandbox session tool visibility',
506
+ status: sandboxVisibilityHealthy ? 'pass' : 'warn',
507
+ summary: sandboxVisibilityHealthy
508
+ ? `Effective sandbox mode is \`${effectiveSandboxMode}\` and agents.defaults.sandbox.sessionToolsVisibility is correctly set to \`all\`.`
509
+ : `Effective sandbox mode is \`${effectiveSandboxMode}\`, so agents.defaults.sandbox.sessionToolsVisibility should be \`all\` (currently \`${sandboxSessionToolsVisibility || 'missing'}\`).`,
510
+ action: sandboxVisibilityHealthy
511
+ ? null
512
+ : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to widen sandbox session-tool visibility for managed follow-up routing.`,
513
+ details: {
514
+ effectiveSandboxMode,
515
+ sessionToolsVisibility: sandboxSessionToolsVisibility,
516
+ requiredSessionToolsVisibility: 'all',
517
+ },
518
+ }));
519
+ }
520
+
458
521
  checks.push(createCheck({
459
522
  id: 'app-token',
460
523
  category: 'Credentials and runtime',
@@ -617,7 +617,6 @@ async function createChatRequest({
617
617
  targetAgentId,
618
618
  openingMessage = null,
619
619
  worldId = null,
620
- requestContext = null,
621
620
  fetchImpl,
622
621
  }) {
623
622
  const normalizedTargetAgentId = normalizeClaworldText(targetAgentId, null);
@@ -645,9 +644,6 @@ async function createChatRequest({
645
644
  targetAgentId: normalizedTargetAgentId,
646
645
  openingMessage: normalizeClaworldText(openingMessage, null),
647
646
  ...(normalizeClaworldText(worldId, null) ? { worldId: normalizeClaworldText(worldId, null) } : {}),
648
- ...(requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
649
- ? { requestContext }
650
- : {}),
651
647
  }),
652
648
  });
653
649
  if (!result.ok) {
@@ -662,7 +658,7 @@ async function createChatRequest({
662
658
  return result.body || {};
663
659
  }
664
660
 
665
- async function listChatInbox({
661
+ async function listChatRequests({
666
662
  runtimeConfig,
667
663
  agentId,
668
664
  direction = null,
@@ -720,6 +716,34 @@ async function acceptChatRequest({
720
716
  return result.body || {};
721
717
  }
722
718
 
719
+ async function rejectChatRequest({
720
+ runtimeConfig,
721
+ actorAgentId,
722
+ chatRequestId,
723
+ fetchImpl,
724
+ }) {
725
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
726
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/chat-requests/${encodeURIComponent(chatRequestId)}/reject`, {
727
+ method: 'POST',
728
+ headers: {
729
+ 'content-type': 'application/json',
730
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
731
+ ...buildRuntimeAuthHeaders(runtimeConfig),
732
+ },
733
+ body: JSON.stringify({ actorAgentId }),
734
+ });
735
+ if (!result.ok) {
736
+ createRelayRouteError({
737
+ result,
738
+ runtimeConfig,
739
+ code: 'chat_request_reject_failed',
740
+ publicMessage: 'failed to reject chat request',
741
+ context: { actorAgentId, chatRequestId },
742
+ });
743
+ }
744
+ return result.body || {};
745
+ }
746
+
723
747
  async function syncChatRequestApprovalPolicy({
724
748
  runtimeConfig,
725
749
  fetchImpl,
@@ -1472,6 +1496,7 @@ function createDeliveryReplyDispatcher({
1472
1496
  });
1473
1497
 
1474
1498
  const markDispatchIdle = async () => {
1499
+ await dispatchApi.dispatcher.waitForIdle?.();
1475
1500
  if (!replied && !suppressed) {
1476
1501
  const continuation = buildRelayContinuationText({
1477
1502
  finalTexts,
@@ -1830,15 +1855,15 @@ async function maybeBridgeRuntimeDelivery({
1830
1855
  }));
1831
1856
  }
1832
1857
 
1833
- logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
1834
- deliveryId,
1835
- sessionKey,
1836
- queuedFinal: Boolean(dispatchResult?.queuedFinal),
1837
- replied,
1838
- keptSilent,
1839
- routeStatus: routed?.status || null,
1840
- runtimeOutputSummary,
1841
- });
1858
+ logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
1859
+ deliveryId,
1860
+ sessionKey,
1861
+ queuedFinal: Boolean(dispatchResult?.queuedFinal),
1862
+ replied,
1863
+ keptSilent,
1864
+ routeStatus: routed?.status || null,
1865
+ runtimeOutputSummary,
1866
+ });
1842
1867
 
1843
1868
  return {
1844
1869
  skipped: false,
@@ -2495,44 +2520,36 @@ export function createClaworldChannelPlugin({
2495
2520
  },
2496
2521
  requestChat: async (context = {}) => {
2497
2522
  const resolvedContext = await resolveBoundRuntimeContext(context);
2498
- const requestContext = resolvedContext.requesterSessionKey
2499
- ? {
2500
- followUp: {
2501
- sessionKey: resolvedContext.requesterSessionKey,
2502
- },
2503
- }
2504
- : null;
2505
2523
  return createChatRequest({
2506
2524
  runtimeConfig: resolvedContext.runtimeConfig,
2507
2525
  fromAgentId: resolvedContext.agentId || null,
2508
2526
  targetAgentId: context.targetAgentId || null,
2509
2527
  openingMessage: context.openingMessage || context.message || context.text || null,
2510
2528
  worldId: context.worldId || null,
2511
- requestContext,
2512
2529
  fetchImpl,
2513
2530
  });
2514
2531
  },
2515
- listChatInbox: async (context = {}) => {
2532
+ listChatRequests: async (context = {}) => {
2516
2533
  const resolvedContext = await resolveBoundRuntimeContext(context);
2517
- return listChatInbox({
2534
+ return listChatRequests({
2518
2535
  runtimeConfig: resolvedContext.runtimeConfig,
2519
2536
  agentId: resolvedContext.agentId || null,
2520
2537
  direction: context.direction || null,
2521
2538
  fetchImpl,
2522
2539
  });
2523
2540
  },
2524
- listChatRequests: async (context = {}) => {
2541
+ acceptChatRequest: async (context = {}) => {
2525
2542
  const resolvedContext = await resolveBoundRuntimeContext(context);
2526
- return listChatInbox({
2543
+ return acceptChatRequest({
2527
2544
  runtimeConfig: resolvedContext.runtimeConfig,
2528
- agentId: resolvedContext.agentId || null,
2529
- direction: context.direction || null,
2545
+ actorAgentId: resolvedContext.agentId || null,
2546
+ chatRequestId: context.chatRequestId || null,
2530
2547
  fetchImpl,
2531
2548
  });
2532
2549
  },
2533
- acceptChatRequest: async (context = {}) => {
2550
+ rejectChatRequest: async (context = {}) => {
2534
2551
  const resolvedContext = await resolveBoundRuntimeContext(context);
2535
- return acceptChatRequest({
2552
+ return rejectChatRequest({
2536
2553
  runtimeConfig: resolvedContext.runtimeConfig,
2537
2554
  actorAgentId: resolvedContext.agentId || null,
2538
2555
  chatRequestId: context.chatRequestId || null,
@@ -2676,6 +2693,7 @@ export function createClaworldChannelPlugin({
2676
2693
  mode: context.mode || 'get',
2677
2694
  changes: context.changes || null,
2678
2695
  enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
2696
+ status: context.status || null,
2679
2697
  fetchImpl,
2680
2698
  logger,
2681
2699
  });
@@ -2864,6 +2882,7 @@ export function createClaworldChannelPlugin({
2864
2882
  mode: context.mode || 'get',
2865
2883
  changes: context.changes || null,
2866
2884
  enabled: Object.prototype.hasOwnProperty.call(context, 'enabled') ? context.enabled : null,
2885
+ status: context.status || null,
2867
2886
  fetchImpl,
2868
2887
  logger,
2869
2888
  });
@@ -23,6 +23,8 @@ export const DEFAULT_CLAWORLD_APPROVAL_MODE = DEFAULT_CHAT_REQUEST_APPROVAL_POLI
23
23
  export const DEFAULT_CLAWORLD_SESSION_TARGET = 'mainagent';
24
24
  export const DEFAULT_CLAWORLD_FALLBACK_TARGET = 'mainagent';
25
25
  export const CLAWORLD_PLUGIN_TOOL_ALLOW_ENTRY = 'claworld';
26
+ export const MIN_MANAGED_SESSION_VISIBILITY = 'agent';
27
+ export const REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY = 'all';
26
28
 
27
29
  export const TOOL_PROFILES = CLAWORLD_TOOL_PROFILES;
28
30
 
@@ -128,6 +130,109 @@ function findManagedAccountEntry(config = {}, accountId) {
128
130
  return {};
129
131
  }
130
132
 
133
+ const SESSION_VISIBILITY_RANK = Object.freeze({
134
+ self: 0,
135
+ tree: 1,
136
+ agent: 2,
137
+ all: 3,
138
+ });
139
+
140
+ const SANDBOX_SESSION_TOOLS_VISIBILITY_RANK = Object.freeze({
141
+ spawned: 0,
142
+ all: 1,
143
+ });
144
+
145
+ function normalizeSessionVisibility(value, fallback = null) {
146
+ const normalized = normalizeText(value, fallback);
147
+ return Object.prototype.hasOwnProperty.call(SESSION_VISIBILITY_RANK, normalized)
148
+ ? normalized
149
+ : fallback;
150
+ }
151
+
152
+ function normalizeSandboxSessionToolsVisibility(value, fallback = null) {
153
+ const normalized = normalizeText(value, fallback);
154
+ return Object.prototype.hasOwnProperty.call(SANDBOX_SESSION_TOOLS_VISIBILITY_RANK, normalized)
155
+ ? normalized
156
+ : fallback;
157
+ }
158
+
159
+ function compareRankedSetting(value, target, rankMap) {
160
+ const nextValue = normalizeText(value, null);
161
+ const nextTarget = normalizeText(target, null);
162
+ const valueRank = Object.prototype.hasOwnProperty.call(rankMap, nextValue) ? rankMap[nextValue] : null;
163
+ const targetRank = Object.prototype.hasOwnProperty.call(rankMap, nextTarget) ? rankMap[nextTarget] : null;
164
+ if (valueRank == null && targetRank == null) return 0;
165
+ if (valueRank == null) return -1;
166
+ if (targetRank == null) return 1;
167
+ return valueRank - targetRank;
168
+ }
169
+
170
+ export function getEffectiveAgentSandboxMode(config = {}, agentId = DEFAULT_CLAWORLD_AGENT_ID) {
171
+ const normalizedAgentId = normalizeText(agentId, DEFAULT_CLAWORLD_AGENT_ID);
172
+ const agentEntry = findAgentEntry(config, normalizedAgentId);
173
+ const agentSandboxMode = normalizeText(agentEntry?.sandbox?.mode, null);
174
+ if (agentSandboxMode) return agentSandboxMode;
175
+ return normalizeText(config?.agents?.defaults?.sandbox?.mode, 'off');
176
+ }
177
+
178
+ export function sandboxModeNeedsSessionToolsVisibility(mode) {
179
+ return mode === 'all' || mode === 'non-main';
180
+ }
181
+
182
+ function ensureManagedSessionRoutingVisibility(config = {}, {
183
+ agentId = DEFAULT_CLAWORLD_AGENT_ID,
184
+ summary = [],
185
+ } = {}) {
186
+ config.tools = ensureObject(config.tools);
187
+ const existingSessionTools = ensureObject(config.tools.sessions);
188
+ const existingVisibility = normalizeSessionVisibility(existingSessionTools.visibility, null);
189
+ if (compareRankedSetting(existingVisibility, MIN_MANAGED_SESSION_VISIBILITY, SESSION_VISIBILITY_RANK) < 0) {
190
+ config.tools.sessions = {
191
+ ...existingSessionTools,
192
+ visibility: MIN_MANAGED_SESSION_VISIBILITY,
193
+ };
194
+ summary.push(
195
+ existingVisibility
196
+ ? `tools.sessions.visibility raised from ${existingVisibility} to ${MIN_MANAGED_SESSION_VISIBILITY}`
197
+ : `tools.sessions.visibility set to ${MIN_MANAGED_SESSION_VISIBILITY}`,
198
+ );
199
+ } else if (Object.keys(existingSessionTools).length > 0) {
200
+ config.tools.sessions = existingSessionTools;
201
+ }
202
+
203
+ const effectiveSandboxMode = getEffectiveAgentSandboxMode(config, agentId);
204
+ if (!sandboxModeNeedsSessionToolsVisibility(effectiveSandboxMode)) {
205
+ return;
206
+ }
207
+
208
+ config.agents = ensureObject(config.agents);
209
+ config.agents.defaults = ensureObject(config.agents.defaults);
210
+ const existingSandbox = ensureObject(config.agents.defaults.sandbox);
211
+ const existingSessionToolsVisibility = normalizeSandboxSessionToolsVisibility(
212
+ existingSandbox.sessionToolsVisibility,
213
+ null,
214
+ );
215
+ if (
216
+ compareRankedSetting(
217
+ existingSessionToolsVisibility,
218
+ REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY,
219
+ SANDBOX_SESSION_TOOLS_VISIBILITY_RANK,
220
+ ) < 0
221
+ ) {
222
+ config.agents.defaults.sandbox = {
223
+ ...existingSandbox,
224
+ sessionToolsVisibility: REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY,
225
+ };
226
+ summary.push(
227
+ existingSessionToolsVisibility
228
+ ? `agents.defaults.sandbox.sessionToolsVisibility raised from ${existingSessionToolsVisibility} to ${REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY}`
229
+ : `agents.defaults.sandbox.sessionToolsVisibility set to ${REQUIRED_SANDBOX_SESSION_TOOLS_VISIBILITY}`,
230
+ );
231
+ } else if (Object.keys(existingSandbox).length > 0) {
232
+ config.agents.defaults.sandbox = existingSandbox;
233
+ }
234
+ }
235
+
131
236
  function inferExistingAgentId(config = {}, accountId = DEFAULT_CLAWORLD_ACCOUNT_ID) {
132
237
  const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
133
238
  const bindingMatch = bindings
@@ -716,6 +821,11 @@ export function applyClaworldManagedRuntimeConfig(inputConfig = {}, options = {}
716
821
  : `reconciled claworld binding for ${options.accountId}`,
717
822
  );
718
823
 
824
+ ensureManagedSessionRoutingVisibility(config, {
825
+ agentId: options.agentId,
826
+ summary,
827
+ });
828
+
719
829
  return {
720
830
  config,
721
831
  summary,