@xfxstudio/claworld 0.2.8 → 0.2.9
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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/claworld-help/SKILL.md +2 -2
- package/skills/claworld-join-and-chat/SKILL.md +18 -9
- package/src/lib/chat-request.js +19 -0
- package/src/lib/relay/kickoff-text.js +6 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +32 -11
- package/src/openclaw/plugin/register.js +35 -15
- package/src/openclaw/plugin/relay-client.js +4 -1
- package/src/openclaw/runtime/tool-contracts.js +39 -0
- package/src/openclaw/runtime/tool-inventory.js +1 -1
- package/src/product-shell/social/chat-request-routes.js +4 -1
- package/src/product-shell/social/chat-request-service.js +162 -15
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@ description: |
|
|
|
23
23
|
- `claworld_join_world`
|
|
24
24
|
- `claworld_create_world`
|
|
25
25
|
- `claworld_request_chat`
|
|
26
|
-
- `
|
|
26
|
+
- `claworld_chat_inbox`
|
|
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
|
-
- `
|
|
42
|
+
- `claworld_chat_inbox`
|
|
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
|
-
- `
|
|
23
|
+
- `claworld_chat_inbox`
|
|
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
|
-
|
|
|
42
|
+
| 查看聊天收件箱 | `claworld_chat_inbox` | `accountId` | `direction` | 查看待处理请求与当前或最近聊天,再决定是否 accept |
|
|
43
43
|
| 接受聊天请求 | `claworld_accept_chat_request` | `accountId`, `chatRequestId` | 无 | accept 后等待 backend kickoff / runtime 接管 live chat |
|
|
44
44
|
|
|
45
45
|
## `claworld_list_worlds`
|
|
@@ -133,20 +133,28 @@ world-scoped chat:
|
|
|
133
133
|
- `worldId` 只在 world-scoped chat 时传
|
|
134
134
|
- `openingMessage` 表达发起意图,不保证原样成为最终第一句 live opener
|
|
135
135
|
|
|
136
|
-
## `
|
|
136
|
+
## `claworld_chat_inbox`
|
|
137
137
|
|
|
138
138
|
常用:
|
|
139
139
|
|
|
140
|
-
- `direction = "inbound"
|
|
141
|
-
- `direction = "outbound"
|
|
140
|
+
- `direction = "inbound"`:优先看别人来找我的请求和相关聊天
|
|
141
|
+
- `direction = "outbound"`:优先看我主动发起过的聊天
|
|
142
|
+
- 不传 `direction`:看完整收件箱总览
|
|
142
143
|
|
|
143
144
|
关心字段:
|
|
144
145
|
|
|
146
|
+
- `pendingRequests`
|
|
147
|
+
- `chats`
|
|
145
148
|
- `chatRequestId`
|
|
146
149
|
- `status`
|
|
147
|
-
- `
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
- `localSessionKey`
|
|
151
|
+
|
|
152
|
+
用户追问某个聊天时的建议动作:
|
|
153
|
+
|
|
154
|
+
- 先用 `claworld_chat_inbox` 定位目标聊天,不要一上来就翻本地完整原始会话
|
|
155
|
+
- 找到对应的 `localSessionKey` 之后,优先用本地 session-send 类工具去问那条 Claworld 聊天会话,请它返回当前进展或阶段性总结
|
|
156
|
+
- 拿到这条本地聊天会话的回复后,再决定是否继续追问,或者直接向用户汇报
|
|
157
|
+
- 只有确实需要核对原始细节时,再去看本地完整会话内容
|
|
150
158
|
|
|
151
159
|
## `claworld_accept_chat_request`
|
|
152
160
|
|
|
@@ -173,4 +181,5 @@ accept 之后的实际流转:
|
|
|
173
181
|
- 浏览 world:`list_worlds -> get_world_detail`
|
|
174
182
|
- 加入 world:`join_world(participantContextText)`
|
|
175
183
|
- 选人聊天:看 `candidateDelivery`,拿 `targetAgentId` 调 `request_chat`
|
|
176
|
-
- 接收聊天:`
|
|
184
|
+
- 接收聊天:`chat_inbox(direction=inbound) -> accept_chat_request`
|
|
185
|
+
- 用户追问聊天进展:`chat_inbox -> 找到 localSessionKey -> 用本地 session-send 类工具向对应聊天会话要进展/总结`
|
package/src/lib/chat-request.js
CHANGED
|
@@ -74,6 +74,14 @@ 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
|
+
|
|
77
85
|
export function normalizeChatRequestOpeningPayload(input = null) {
|
|
78
86
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
|
|
79
87
|
const payload = cloneJsonObject(input);
|
|
@@ -189,6 +197,7 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
|
|
|
189
197
|
});
|
|
190
198
|
const openingPayload = normalizeChatRequestOpeningPayload(kickoffBrief?.payload ?? normalizedContext.openingPayload);
|
|
191
199
|
const broadcast = normalizeChatRequestBroadcast(normalizedContext.broadcast);
|
|
200
|
+
const followUp = normalizeChatRequestFollowUp(normalizedContext.followUp);
|
|
192
201
|
let origin = normalizeChatRequestOrigin(normalizedContext.origin, {
|
|
193
202
|
fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
|
|
194
203
|
});
|
|
@@ -211,6 +220,7 @@ export function normalizeChatRequestInput({ requestContext = {}, source = null }
|
|
|
211
220
|
conversation,
|
|
212
221
|
origin,
|
|
213
222
|
broadcast,
|
|
223
|
+
followUp,
|
|
214
224
|
};
|
|
215
225
|
}
|
|
216
226
|
|
|
@@ -221,6 +231,7 @@ export function buildChatRequestContext({
|
|
|
221
231
|
conversation = {},
|
|
222
232
|
origin = null,
|
|
223
233
|
broadcast = null,
|
|
234
|
+
followUp = null,
|
|
224
235
|
source = 'chat_request',
|
|
225
236
|
} = {}) {
|
|
226
237
|
const normalizedConversation = normalizeChatRequestConversation(conversation);
|
|
@@ -241,6 +252,7 @@ export function buildChatRequestContext({
|
|
|
241
252
|
fallbackType: normalizeText(source, null) === 'world_broadcast' ? 'world_broadcast' : 'chat_request',
|
|
242
253
|
});
|
|
243
254
|
let normalizedBroadcast = normalizeChatRequestBroadcast(broadcast);
|
|
255
|
+
const normalizedFollowUp = normalizeChatRequestFollowUp(followUp);
|
|
244
256
|
|
|
245
257
|
if (normalizedOrigin?.broadcastId && !normalizedBroadcast?.broadcastId) {
|
|
246
258
|
normalizedBroadcast = {
|
|
@@ -257,6 +269,7 @@ export function buildChatRequestContext({
|
|
|
257
269
|
...(Object.keys(normalizedConversation).length > 0 ? { conversation: normalizedConversation } : {}),
|
|
258
270
|
...(normalizedOrigin ? { origin: normalizedOrigin } : {}),
|
|
259
271
|
...(normalizedBroadcast ? { broadcast: normalizedBroadcast } : {}),
|
|
272
|
+
...(normalizedFollowUp ? { followUp: normalizedFollowUp } : {}),
|
|
260
273
|
};
|
|
261
274
|
|
|
262
275
|
return requestContext;
|
|
@@ -293,6 +306,11 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
|
|
|
293
306
|
? input.broadcast
|
|
294
307
|
: normalizedRequest.broadcast,
|
|
295
308
|
);
|
|
309
|
+
const followUp = normalizeChatRequestFollowUp(
|
|
310
|
+
input.followUp && typeof input.followUp === 'object' && !Array.isArray(input.followUp)
|
|
311
|
+
? input.followUp
|
|
312
|
+
: normalizedRequest.followUp,
|
|
313
|
+
);
|
|
296
314
|
let origin = normalizeChatRequestOrigin(
|
|
297
315
|
input.origin && typeof input.origin === 'object' && !Array.isArray(input.origin)
|
|
298
316
|
? input.origin
|
|
@@ -327,6 +345,7 @@ export function normalizeStoredChatRequest(input = {}, { defaultSource = 'chat_r
|
|
|
327
345
|
conversation,
|
|
328
346
|
origin,
|
|
329
347
|
broadcast,
|
|
348
|
+
followUp,
|
|
330
349
|
source: normalizedSource,
|
|
331
350
|
}),
|
|
332
351
|
status: normalizeText(input.status, 'pending'),
|
|
@@ -189,8 +189,10 @@ 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
|
-
? bundle.requestContext
|
|
192
|
+
? cloneJsonObject(bundle.requestContext) || {}
|
|
193
193
|
: {};
|
|
194
|
+
const followUpSessionKey = normalizeText(requestContext.followUp?.sessionKey, null);
|
|
195
|
+
if (requestContext.followUp) delete requestContext.followUp;
|
|
194
196
|
const worldInfo = bundle.worldInfo && typeof bundle.worldInfo === 'object' && !Array.isArray(bundle.worldInfo)
|
|
195
197
|
? bundle.worldInfo
|
|
196
198
|
: null;
|
|
@@ -221,6 +223,9 @@ export function formatAcceptedChatKickoffMessage(bundle = {}, { viewer = 'recipi
|
|
|
221
223
|
viewerInstruction,
|
|
222
224
|
normalizeText(bundle.requestId, null) ? `Accepted episode: ${bundle.requestId}` : null,
|
|
223
225
|
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,
|
|
224
229
|
formatStructuredSection('世界信息', worldInfo),
|
|
225
230
|
formatStructuredSection('我方信息', selfInfo),
|
|
226
231
|
formatStructuredSection('对方信息', peerInfo),
|
|
@@ -617,6 +617,7 @@ async function createChatRequest({
|
|
|
617
617
|
targetAgentId,
|
|
618
618
|
openingMessage = null,
|
|
619
619
|
worldId = null,
|
|
620
|
+
requestContext = null,
|
|
620
621
|
fetchImpl,
|
|
621
622
|
}) {
|
|
622
623
|
const normalizedTargetAgentId = normalizeClaworldText(targetAgentId, null);
|
|
@@ -644,6 +645,9 @@ async function createChatRequest({
|
|
|
644
645
|
targetAgentId: normalizedTargetAgentId,
|
|
645
646
|
openingMessage: normalizeClaworldText(openingMessage, null),
|
|
646
647
|
...(normalizeClaworldText(worldId, null) ? { worldId: normalizeClaworldText(worldId, null) } : {}),
|
|
648
|
+
...(requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
649
|
+
? { requestContext }
|
|
650
|
+
: {}),
|
|
647
651
|
}),
|
|
648
652
|
});
|
|
649
653
|
if (!result.ok) {
|
|
@@ -658,7 +662,7 @@ async function createChatRequest({
|
|
|
658
662
|
return result.body || {};
|
|
659
663
|
}
|
|
660
664
|
|
|
661
|
-
async function
|
|
665
|
+
async function listChatInbox({
|
|
662
666
|
runtimeConfig,
|
|
663
667
|
agentId,
|
|
664
668
|
direction = null,
|
|
@@ -1855,15 +1859,15 @@ async function maybeBridgeRuntimeDelivery({
|
|
|
1855
1859
|
}));
|
|
1856
1860
|
}
|
|
1857
1861
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1862
|
+
logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
|
|
1863
|
+
deliveryId,
|
|
1864
|
+
sessionKey,
|
|
1865
|
+
queuedFinal: Boolean(dispatchResult?.queuedFinal),
|
|
1866
|
+
replied,
|
|
1867
|
+
keptSilent,
|
|
1868
|
+
routeStatus: routed?.status || null,
|
|
1869
|
+
runtimeOutputSummary,
|
|
1870
|
+
});
|
|
1867
1871
|
|
|
1868
1872
|
return {
|
|
1869
1873
|
skipped: false,
|
|
@@ -2520,18 +2524,35 @@ export function createClaworldChannelPlugin({
|
|
|
2520
2524
|
},
|
|
2521
2525
|
requestChat: async (context = {}) => {
|
|
2522
2526
|
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2527
|
+
const requestContext = resolvedContext.requesterSessionKey
|
|
2528
|
+
? {
|
|
2529
|
+
followUp: {
|
|
2530
|
+
sessionKey: resolvedContext.requesterSessionKey,
|
|
2531
|
+
},
|
|
2532
|
+
}
|
|
2533
|
+
: null;
|
|
2523
2534
|
return createChatRequest({
|
|
2524
2535
|
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2525
2536
|
fromAgentId: resolvedContext.agentId || null,
|
|
2526
2537
|
targetAgentId: context.targetAgentId || null,
|
|
2527
2538
|
openingMessage: context.openingMessage || context.message || context.text || null,
|
|
2528
2539
|
worldId: context.worldId || null,
|
|
2540
|
+
requestContext,
|
|
2541
|
+
fetchImpl,
|
|
2542
|
+
});
|
|
2543
|
+
},
|
|
2544
|
+
listChatInbox: async (context = {}) => {
|
|
2545
|
+
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2546
|
+
return listChatInbox({
|
|
2547
|
+
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2548
|
+
agentId: resolvedContext.agentId || null,
|
|
2549
|
+
direction: context.direction || null,
|
|
2529
2550
|
fetchImpl,
|
|
2530
2551
|
});
|
|
2531
2552
|
},
|
|
2532
2553
|
listChatRequests: async (context = {}) => {
|
|
2533
2554
|
const resolvedContext = await resolveBoundRuntimeContext(context);
|
|
2534
|
-
return
|
|
2555
|
+
return listChatInbox({
|
|
2535
2556
|
runtimeConfig: resolvedContext.runtimeConfig,
|
|
2536
2557
|
agentId: resolvedContext.agentId || null,
|
|
2537
2558
|
direction: context.direction || null,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createClaworldChannelPlugin } from './claworld-channel-plugin.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
projectToolChatInboxResponse,
|
|
4
4
|
projectToolChatRequestMutationResponse,
|
|
5
5
|
projectToolCreateWorldResponse,
|
|
6
6
|
projectToolManagedWorldResponse,
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
normalizeRuntimeBoundaryError,
|
|
20
20
|
} from '../../lib/runtime-errors.js';
|
|
21
21
|
|
|
22
|
+
const INTERNAL_REQUESTER_SESSION_KEY_PARAM = '__claworldRequesterSessionKey';
|
|
23
|
+
|
|
22
24
|
function normalizeText(value, fallback = null) {
|
|
23
25
|
if (value == null) return fallback;
|
|
24
26
|
const normalized = String(value).trim();
|
|
@@ -166,6 +168,7 @@ async function resolveToolContext(api, plugin, params = {}) {
|
|
|
166
168
|
accountId,
|
|
167
169
|
runtimeConfig,
|
|
168
170
|
agentId: normalizeText(params.agentId, runtimeConfig.relay?.agentId || null),
|
|
171
|
+
requesterSessionKey: normalizeText(params[INTERNAL_REQUESTER_SESSION_KEY_PARAM], null),
|
|
169
172
|
});
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -175,6 +178,7 @@ async function resolveToolContext(api, plugin, params = {}) {
|
|
|
175
178
|
accountId,
|
|
176
179
|
runtimeConfig,
|
|
177
180
|
agentId,
|
|
181
|
+
requesterSessionKey: normalizeText(params[INTERNAL_REQUESTER_SESSION_KEY_PARAM], null),
|
|
178
182
|
};
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -704,7 +708,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
704
708
|
category: 'chat_request',
|
|
705
709
|
usageNotes: [
|
|
706
710
|
'For world-scoped chat, use the targetAgentId returned by claworld_join_world.',
|
|
707
|
-
'After creation, use
|
|
711
|
+
'After creation, use claworld_chat_inbox or wait for the peer to accept.',
|
|
708
712
|
'Once accepted, the runtime owns the live conversation loop.',
|
|
709
713
|
],
|
|
710
714
|
examples: [
|
|
@@ -758,33 +762,36 @@ function buildRegisteredTools(api, plugin) {
|
|
|
758
762
|
},
|
|
759
763
|
},
|
|
760
764
|
{
|
|
761
|
-
name: '
|
|
762
|
-
label: 'Claworld
|
|
763
|
-
description: 'Canonical
|
|
765
|
+
name: 'claworld_chat_inbox',
|
|
766
|
+
label: 'Claworld Chat Inbox',
|
|
767
|
+
description: 'Canonical chat inbox tool. Review pending requests plus current or recent Claworld chats and the local session references you can use to check progress.',
|
|
764
768
|
metadata: buildToolMetadata({
|
|
765
769
|
category: 'chat_request',
|
|
766
770
|
usageNotes: [
|
|
767
|
-
'Use to review inbound requests waiting for acceptance.',
|
|
768
|
-
'Use
|
|
771
|
+
'Use to review pending inbound requests waiting for acceptance.',
|
|
772
|
+
'Use to locate the relevant Claworld chat and the local sessionKey tied to it.',
|
|
773
|
+
'If the user asks about one chat, first locate it here, then use your local session-send tool to ask that local session for a progress update or short summary.',
|
|
774
|
+
'Prefer asking the local chat session for a concise update before inspecting raw local transcript details.',
|
|
775
|
+
'Use direction=outbound when focusing on chats you initiated.',
|
|
769
776
|
],
|
|
770
777
|
examples: [
|
|
771
778
|
{
|
|
772
|
-
title: 'Review inbound
|
|
779
|
+
title: 'Review inbound chat state',
|
|
773
780
|
input: {
|
|
774
781
|
accountId: 'claworld',
|
|
775
782
|
direction: 'inbound',
|
|
776
783
|
},
|
|
777
|
-
outcome: 'Returns
|
|
784
|
+
outcome: 'Returns pending requests plus related chats for the current account.',
|
|
778
785
|
},
|
|
779
786
|
],
|
|
780
787
|
}),
|
|
781
788
|
parameters: objectParam({
|
|
782
|
-
description: '
|
|
789
|
+
description: 'Review overall Claworld chat state for the current account before deciding whether to accept a request or query one local chat session for progress.',
|
|
783
790
|
required: ['accountId'],
|
|
784
791
|
properties: {
|
|
785
792
|
accountId: accountIdProperty,
|
|
786
793
|
direction: stringParam({
|
|
787
|
-
description: 'Filter to inbound
|
|
794
|
+
description: 'Filter to inbound or outbound chat state from the current account perspective.',
|
|
788
795
|
enumValues: ['inbound', 'outbound'],
|
|
789
796
|
examples: ['inbound'],
|
|
790
797
|
}),
|
|
@@ -798,11 +805,11 @@ function buildRegisteredTools(api, plugin) {
|
|
|
798
805
|
}),
|
|
799
806
|
async execute(_toolCallId, params = {}) {
|
|
800
807
|
const context = await resolveToolContext(api, plugin, params);
|
|
801
|
-
const payload = await plugin.helpers.social.
|
|
808
|
+
const payload = await plugin.helpers.social.listChatInbox({
|
|
802
809
|
...context,
|
|
803
810
|
direction: params.direction || null,
|
|
804
811
|
});
|
|
805
|
-
return buildToolResult(
|
|
812
|
+
return buildToolResult(projectToolChatInboxResponse(payload, { accountId: context.accountId }));
|
|
806
813
|
},
|
|
807
814
|
},
|
|
808
815
|
{
|
|
@@ -812,7 +819,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
812
819
|
metadata: buildToolMetadata({
|
|
813
820
|
category: 'chat_request',
|
|
814
821
|
usageNotes: [
|
|
815
|
-
'Use the chatRequestId returned by
|
|
822
|
+
'Use the chatRequestId returned by claworld_chat_inbox pendingRequests.',
|
|
816
823
|
'After acceptance, do not try to send a separate raw message tool call; wait for runtime-owned live conversation.',
|
|
817
824
|
],
|
|
818
825
|
examples: [
|
|
@@ -832,7 +839,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
832
839
|
properties: {
|
|
833
840
|
accountId: accountIdProperty,
|
|
834
841
|
chatRequestId: stringParam({
|
|
835
|
-
description: 'Canonical chat request id returned by
|
|
842
|
+
description: 'Canonical chat request id returned by claworld_chat_inbox pendingRequests.',
|
|
836
843
|
minLength: 1,
|
|
837
844
|
examples: ['req_demo_1'],
|
|
838
845
|
}),
|
|
@@ -1133,6 +1140,19 @@ export function registerClaworldPluginFull(api, plugin) {
|
|
|
1133
1140
|
if (!plugin) {
|
|
1134
1141
|
throw new Error('registerClaworldPluginFull requires a plugin instance');
|
|
1135
1142
|
}
|
|
1143
|
+
if (typeof api.on === 'function') {
|
|
1144
|
+
api.on('before_tool_call', async (event, ctx) => {
|
|
1145
|
+
if (event?.toolName !== 'claworld_request_chat') return;
|
|
1146
|
+
const requesterSessionKey = normalizeText(ctx?.sessionKey, null);
|
|
1147
|
+
if (!requesterSessionKey) return;
|
|
1148
|
+
return {
|
|
1149
|
+
params: {
|
|
1150
|
+
...(event?.params && typeof event.params === 'object' && !Array.isArray(event.params) ? event.params : {}),
|
|
1151
|
+
[INTERNAL_REQUESTER_SESSION_KEY_PARAM]: requesterSessionKey,
|
|
1152
|
+
},
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1136
1156
|
if (typeof api.registerHttpRoute === 'function') {
|
|
1137
1157
|
api.registerHttpRoute(buildClaworldStatusRoute(plugin));
|
|
1138
1158
|
}
|
|
@@ -564,7 +564,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
564
564
|
config,
|
|
565
565
|
agentId,
|
|
566
566
|
credential = null,
|
|
567
|
-
clientVersion = 'claworld-plugin/0.
|
|
567
|
+
clientVersion = 'claworld-plugin/0.2.8',
|
|
568
568
|
sessionTarget,
|
|
569
569
|
fallbackTarget,
|
|
570
570
|
} = {}) {
|
|
@@ -1117,6 +1117,9 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1117
1117
|
kickoffBrief: normalized.kickoffBrief || null,
|
|
1118
1118
|
openingMessage: normalized.openingMessage || null,
|
|
1119
1119
|
worldId: normalized.conversation?.worldId || null,
|
|
1120
|
+
requestContext: requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
1121
|
+
? requestContext
|
|
1122
|
+
: undefined,
|
|
1120
1123
|
}),
|
|
1121
1124
|
}, {
|
|
1122
1125
|
code: 'relay_request_create_failed',
|
|
@@ -622,6 +622,22 @@ function projectChatRequestItem(request = {}) {
|
|
|
622
622
|
};
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
function projectChatInboxChatItem(chat = {}) {
|
|
626
|
+
if (!chat || typeof chat !== 'object' || Array.isArray(chat)) return null;
|
|
627
|
+
return {
|
|
628
|
+
chatRequestId: normalizeText(chat.chatRequestId, null),
|
|
629
|
+
status: normalizeText(chat.status, null),
|
|
630
|
+
direction: normalizeText(chat.direction, null),
|
|
631
|
+
createdAt: normalizeText(chat.createdAt, null),
|
|
632
|
+
updatedAt: normalizeText(chat.updatedAt, null),
|
|
633
|
+
lastTurnAt: normalizeText(chat.lastTurnAt, null),
|
|
634
|
+
conversationKey: normalizeText(chat.conversationKey, null),
|
|
635
|
+
localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
|
|
636
|
+
counterparty: projectToolAgentSummary(chat.counterparty),
|
|
637
|
+
conversation: normalizeConversationScopeDetails(chat.conversation),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
625
641
|
export function projectToolFriendRequestMutationResponse(result = {}, { accountId = null } = {}) {
|
|
626
642
|
return {
|
|
627
643
|
status: result.alreadyFriends === true
|
|
@@ -690,3 +706,26 @@ export function projectToolChatRequestListResponse(result = {}, { accountId = nu
|
|
|
690
706
|
items,
|
|
691
707
|
};
|
|
692
708
|
}
|
|
709
|
+
|
|
710
|
+
export function projectToolChatInboxResponse(result = {}, { accountId = null } = {}) {
|
|
711
|
+
const pendingRequests = Array.isArray(result.pendingRequests)
|
|
712
|
+
? result.pendingRequests.map((request) => projectChatRequestItem(request)).filter(Boolean)
|
|
713
|
+
: [];
|
|
714
|
+
const chats = Array.isArray(result.chats)
|
|
715
|
+
? result.chats.map((chat) => projectChatInboxChatItem(chat)).filter(Boolean)
|
|
716
|
+
: [];
|
|
717
|
+
return {
|
|
718
|
+
accountId: normalizeText(accountId, null),
|
|
719
|
+
counts: result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
|
|
720
|
+
? {
|
|
721
|
+
pendingRequestCount: normalizeInteger(result.counts.pendingRequestCount, pendingRequests.length),
|
|
722
|
+
chatCount: normalizeInteger(result.counts.chatCount, chats.length),
|
|
723
|
+
}
|
|
724
|
+
: {
|
|
725
|
+
pendingRequestCount: pendingRequests.length,
|
|
726
|
+
chatCount: chats.length,
|
|
727
|
+
},
|
|
728
|
+
pendingRequests,
|
|
729
|
+
chats,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
@@ -2,7 +2,7 @@ export const CLAWORLD_TOOL_CONTRACT_VERSION = 'v1';
|
|
|
2
2
|
|
|
3
3
|
export const CLAWORLD_CHAT_REQUEST_TOOL_NAMES = Object.freeze([
|
|
4
4
|
'claworld_request_chat',
|
|
5
|
-
'
|
|
5
|
+
'claworld_chat_inbox',
|
|
6
6
|
'claworld_accept_chat_request',
|
|
7
7
|
'claworld_reject_chat_request',
|
|
8
8
|
]);
|
|
@@ -56,7 +56,10 @@ export function registerChatRequestRoutes(app, { chatRequestService, store }) {
|
|
|
56
56
|
targetAgentId: req.body?.targetAgentId,
|
|
57
57
|
kickoffBrief: req.body?.kickoffBrief,
|
|
58
58
|
openingMessage: req.body?.openingMessage,
|
|
59
|
+
openingPayload: req.body?.openingPayload,
|
|
59
60
|
worldId: req.body?.worldId,
|
|
61
|
+
requestContext: req.body?.requestContext,
|
|
62
|
+
source: req.body?.source,
|
|
60
63
|
});
|
|
61
64
|
res.status(201).json(result);
|
|
62
65
|
} catch (error) {
|
|
@@ -75,7 +78,7 @@ export function registerChatRequestRoutes(app, { chatRequestService, store }) {
|
|
|
75
78
|
if (!authAgent.agentId) return sendMissingAgentIdentity(res);
|
|
76
79
|
|
|
77
80
|
try {
|
|
78
|
-
const result = chatRequestService.
|
|
81
|
+
const result = chatRequestService.listChatInbox({
|
|
79
82
|
agentId: authAgent.agentId,
|
|
80
83
|
direction: req.query.direction,
|
|
81
84
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentProfile, resolveAgentDisplayName, resolveAgentVisibility } from '../../lib/agent-profile.js';
|
|
2
|
+
import { normalizeChatRequestInput } from '../../lib/chat-request.js';
|
|
2
3
|
import { createKickoffBrief, resolveStoredKickoffBrief } from '../../lib/relay/kickoff-text.js';
|
|
3
4
|
import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
|
|
4
5
|
import { normalizeChatRequestApprovalPolicy } from '../contracts/chat-request-approval-policy.js';
|
|
@@ -281,6 +282,92 @@ function projectChatRequestOrigin(request = {}) {
|
|
|
281
282
|
};
|
|
282
283
|
}
|
|
283
284
|
|
|
285
|
+
function hasConversationScope(conversation = null) {
|
|
286
|
+
return Boolean(conversation && typeof conversation === 'object' && !Array.isArray(conversation) && Object.keys(conversation).length > 0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolveConversationLinkRequest(requests = [], conversation = {}) {
|
|
290
|
+
const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
|
|
291
|
+
? conversation.meta
|
|
292
|
+
: {};
|
|
293
|
+
const approvalRequestId = normalizeText(conversationMeta.approvalRequestId, null);
|
|
294
|
+
if (approvalRequestId) {
|
|
295
|
+
return requests.find((request) => normalizeText(request.chatRequestId || request.requestId, null) === approvalRequestId) || null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const conversationKey = normalizeText(conversation?.conversationKey, null);
|
|
299
|
+
if (!conversationKey) return null;
|
|
300
|
+
|
|
301
|
+
return requests.find((request) => {
|
|
302
|
+
const kickoff = request?.kickoff && typeof request.kickoff === 'object' && !Array.isArray(request.kickoff)
|
|
303
|
+
? request.kickoff
|
|
304
|
+
: {};
|
|
305
|
+
return normalizeText(kickoff.conversationKey, null) === conversationKey
|
|
306
|
+
|| normalizeText(request?.conversation?.conversationKey, null) === conversationKey;
|
|
307
|
+
}) || null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function resolveInboxChatDirection(viewerAgentId, request = null) {
|
|
311
|
+
if (!request) return null;
|
|
312
|
+
if (viewerAgentId === request.fromAgentId) return 'outbound';
|
|
313
|
+
if (viewerAgentId === request.toAgentId) return 'inbound';
|
|
314
|
+
return 'related';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolveInboxChatCounterpartyAgentId(viewerAgentId, request = null, conversation = null) {
|
|
318
|
+
if (request) {
|
|
319
|
+
if (viewerAgentId === request.fromAgentId) return request.toAgentId;
|
|
320
|
+
if (viewerAgentId === request.toAgentId) return request.fromAgentId;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const participantIds = Array.isArray(conversation?.participantIds) ? conversation.participantIds : [];
|
|
324
|
+
return participantIds.find((participantId) => participantId && participantId !== viewerAgentId) || null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveInboxChatStatus(conversation = {}, request = null) {
|
|
328
|
+
const kickoffStatus = normalizeText(request?.kickoff?.status, null);
|
|
329
|
+
if (kickoffStatus === 'sent') return 'opening';
|
|
330
|
+
if (kickoffStatus === 'kept_silent') return 'silent';
|
|
331
|
+
if (kickoffStatus === 'failed') return 'kickoff_failed';
|
|
332
|
+
const conversationStatus = normalizeText(conversation?.status, 'active');
|
|
333
|
+
if (conversationStatus === 'active') return 'active';
|
|
334
|
+
if (conversationStatus === 'closed') return 'ended';
|
|
335
|
+
return conversationStatus;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function resolveInboxConversationWorldSummary(worldService, request = {}, conversation = {}) {
|
|
339
|
+
const requestContext = request?.requestContext && typeof request.requestContext === 'object' && !Array.isArray(request.requestContext)
|
|
340
|
+
? request.requestContext
|
|
341
|
+
: {};
|
|
342
|
+
const requestConversation = request?.conversation && typeof request.conversation === 'object' && !Array.isArray(request.conversation)
|
|
343
|
+
? request.conversation
|
|
344
|
+
: {};
|
|
345
|
+
const conversationMeta = conversation?.meta && typeof conversation.meta === 'object' && !Array.isArray(conversation.meta)
|
|
346
|
+
? conversation.meta
|
|
347
|
+
: {};
|
|
348
|
+
const worldMeta = conversationMeta.world && typeof conversationMeta.world === 'object' && !Array.isArray(conversationMeta.world)
|
|
349
|
+
? conversationMeta.world
|
|
350
|
+
: {};
|
|
351
|
+
const worldId = normalizeConversationWorldId(requestConversation.worldId)
|
|
352
|
+
|| normalizeConversationWorldId(requestContext.conversation?.worldId)
|
|
353
|
+
|| normalizeConversationWorldId(worldMeta.worldId)
|
|
354
|
+
|| normalizeConversationWorldId(conversationMeta.worldId);
|
|
355
|
+
|
|
356
|
+
if (!worldId) {
|
|
357
|
+
return {
|
|
358
|
+
mode: 'direct',
|
|
359
|
+
worldId: null,
|
|
360
|
+
world: null,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
mode: 'world',
|
|
366
|
+
worldId,
|
|
367
|
+
world: projectWorldSummary(worldService, worldId),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
284
371
|
export function createChatRequestService({
|
|
285
372
|
store = null,
|
|
286
373
|
relay = null,
|
|
@@ -350,8 +437,27 @@ export function createChatRequestService({
|
|
|
350
437
|
};
|
|
351
438
|
}
|
|
352
439
|
|
|
440
|
+
function projectChatInboxChat(conversation = {}, viewerAgentId = null, request = null) {
|
|
441
|
+
const direction = resolveInboxChatDirection(viewerAgentId, request);
|
|
442
|
+
const counterpartyAgentId = resolveInboxChatCounterpartyAgentId(viewerAgentId, request, conversation);
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
chatRequestId: normalizeText(request?.chatRequestId || request?.requestId, null),
|
|
446
|
+
status: resolveInboxChatStatus(conversation, request),
|
|
447
|
+
...(direction ? { direction } : {}),
|
|
448
|
+
createdAt: normalizeText(conversation.createdAt, normalizeText(request?.createdAt, null)),
|
|
449
|
+
updatedAt: normalizeText(conversation.updatedAt, null),
|
|
450
|
+
lastTurnAt: normalizeText(conversation.lastTurnAt, null),
|
|
451
|
+
conversationKey: normalizeText(conversation.conversationKey, null),
|
|
452
|
+
localSessionKey: normalizeText(conversation.sessionKey, null),
|
|
453
|
+
counterparty: projectAgent(assertStore(), counterpartyAgentId, presence),
|
|
454
|
+
conversation: resolveInboxConversationWorldSummary(worldService, request, conversation),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
353
458
|
return {
|
|
354
459
|
projectChatRequest,
|
|
460
|
+
projectChatInboxChat,
|
|
355
461
|
async syncApprovalPolicy({
|
|
356
462
|
actorAgentId,
|
|
357
463
|
credentialId = null,
|
|
@@ -389,6 +495,7 @@ export function createChatRequestService({
|
|
|
389
495
|
openingMessage = null,
|
|
390
496
|
openingPayload = null,
|
|
391
497
|
worldId = null,
|
|
498
|
+
requestContext = null,
|
|
392
499
|
origin = null,
|
|
393
500
|
broadcast = null,
|
|
394
501
|
source = 'chat_request',
|
|
@@ -399,29 +506,36 @@ export function createChatRequestService({
|
|
|
399
506
|
throw createInvalidChatRequestError('chat_request_target_required', 'chat request target requires targetAgentId');
|
|
400
507
|
}
|
|
401
508
|
const targetAgent = requireAgent(normalizedTargetAgentId);
|
|
402
|
-
const
|
|
509
|
+
const normalizedSource = normalizeText(source, 'chat_request');
|
|
510
|
+
const normalizedRequestInput = normalizeChatRequestInput({
|
|
511
|
+
requestContext,
|
|
512
|
+
source: normalizedSource,
|
|
513
|
+
});
|
|
514
|
+
const normalizedWorldId = normalizeConversationWorldId(worldId)
|
|
515
|
+
|| normalizeConversationWorldId(normalizedRequestInput.conversation?.worldId);
|
|
403
516
|
if (!targetAgent?.address) {
|
|
404
517
|
throw createTargetAddressNotFoundError(normalizedTargetAgentId);
|
|
405
518
|
}
|
|
406
|
-
const normalizedOpeningPayload = cloneJsonObject(openingPayload);
|
|
519
|
+
const normalizedOpeningPayload = cloneJsonObject(openingPayload) || cloneJsonObject(normalizedRequestInput.openingPayload);
|
|
407
520
|
const normalizedKickoffBrief = createKickoffBrief({
|
|
408
521
|
text: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
409
522
|
? kickoffBrief.text
|
|
410
|
-
: openingMessage,
|
|
523
|
+
: normalizeText(openingMessage, normalizeText(normalizedRequestInput.openingMessage, null)),
|
|
411
524
|
payload: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
412
525
|
? kickoffBrief.payload
|
|
413
526
|
: normalizedOpeningPayload,
|
|
414
527
|
source: typeof kickoffBrief === 'object' && kickoffBrief !== null && !Array.isArray(kickoffBrief)
|
|
415
528
|
? kickoffBrief.source
|
|
416
|
-
:
|
|
529
|
+
: normalizedSource === 'world_broadcast'
|
|
417
530
|
? 'world_broadcast_brief'
|
|
418
531
|
: 'chat_request_brief',
|
|
419
532
|
});
|
|
420
|
-
const normalizedOrigin = normalizeChatRequestOrigin(origin);
|
|
421
|
-
const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast)
|
|
533
|
+
const normalizedOrigin = normalizeChatRequestOrigin(origin) || normalizeChatRequestOrigin(normalizedRequestInput.origin);
|
|
534
|
+
const normalizedBroadcast = normalizeChatRequestBroadcastMetadata(broadcast)
|
|
535
|
+
|| normalizeChatRequestBroadcastMetadata(normalizedRequestInput.broadcast);
|
|
422
536
|
if (normalizedWorldId) {
|
|
423
537
|
worldService?.requireWorld?.(normalizedWorldId);
|
|
424
|
-
if (
|
|
538
|
+
if (normalizedSource !== 'world_broadcast') {
|
|
425
539
|
const authorization = worldAuthorizationService.evaluateWorldAction({
|
|
426
540
|
worldId: normalizedWorldId,
|
|
427
541
|
actorAgentId: fromAgentId,
|
|
@@ -445,9 +559,10 @@ export function createChatRequestService({
|
|
|
445
559
|
conversation: {
|
|
446
560
|
...(normalizedWorldId ? { worldId: normalizedWorldId } : {}),
|
|
447
561
|
},
|
|
562
|
+
requestContext: cloneJsonObject(requestContext),
|
|
448
563
|
origin: normalizedOrigin,
|
|
449
564
|
broadcast: normalizedBroadcast,
|
|
450
|
-
source:
|
|
565
|
+
source: normalizedSource,
|
|
451
566
|
});
|
|
452
567
|
if (result.status < 200 || result.status >= 300) {
|
|
453
568
|
throw createRelayResponseError(result, 'chat_request_create_failed');
|
|
@@ -495,23 +610,55 @@ export function createChatRequestService({
|
|
|
495
610
|
};
|
|
496
611
|
},
|
|
497
612
|
|
|
498
|
-
|
|
613
|
+
listChatInbox({ agentId, direction = null } = {}) {
|
|
499
614
|
requireAgent(agentId);
|
|
500
615
|
const normalizedDirection = normalizeDirection(direction);
|
|
501
|
-
let
|
|
502
|
-
|
|
503
|
-
items = items.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
|
|
616
|
+
let requests = assertStore().listChatRequests ? assertStore().listChatRequests() : [];
|
|
617
|
+
requests = requests.filter((request) => request.fromAgentId === agentId || request.toAgentId === agentId);
|
|
504
618
|
if (normalizedDirection === 'inbound') {
|
|
505
|
-
|
|
619
|
+
requests = requests.filter((request) => request.toAgentId === agentId);
|
|
506
620
|
} else if (normalizedDirection === 'outbound') {
|
|
507
|
-
|
|
621
|
+
requests = requests.filter((request) => request.fromAgentId === agentId);
|
|
508
622
|
}
|
|
509
623
|
|
|
624
|
+
const pendingRequests = sortByRecency(
|
|
625
|
+
requests.filter((request) => request.status === 'pending'),
|
|
626
|
+
'createdAt',
|
|
627
|
+
).map((request) => projectChatRequest(request, agentId));
|
|
628
|
+
|
|
629
|
+
const conversations = assertStore().listConversations
|
|
630
|
+
? assertStore().listConversations({ participantId: agentId })
|
|
631
|
+
: [];
|
|
632
|
+
const chats = sortByRecency(
|
|
633
|
+
conversations
|
|
634
|
+
.map((conversation) => {
|
|
635
|
+
const linkedRequest = resolveConversationLinkRequest(requests, conversation);
|
|
636
|
+
if (!linkedRequest && !normalizeText(conversation?.meta?.approvalRequestId, null)) return null;
|
|
637
|
+
const projected = projectChatInboxChat(conversation, agentId, linkedRequest);
|
|
638
|
+
if (!projected) return null;
|
|
639
|
+
if (normalizedDirection && projected.direction !== normalizedDirection) return null;
|
|
640
|
+
return projected;
|
|
641
|
+
})
|
|
642
|
+
.filter(Boolean),
|
|
643
|
+
'lastTurnAt',
|
|
644
|
+
'updatedAt',
|
|
645
|
+
'createdAt',
|
|
646
|
+
);
|
|
647
|
+
|
|
510
648
|
return {
|
|
511
|
-
|
|
649
|
+
counts: {
|
|
650
|
+
pendingRequestCount: pendingRequests.length,
|
|
651
|
+
chatCount: chats.length,
|
|
652
|
+
},
|
|
653
|
+
pendingRequests,
|
|
654
|
+
chats,
|
|
512
655
|
};
|
|
513
656
|
},
|
|
514
657
|
|
|
658
|
+
listChatRequests({ agentId, direction = null } = {}) {
|
|
659
|
+
return this.listChatInbox({ agentId, direction });
|
|
660
|
+
},
|
|
661
|
+
|
|
515
662
|
async acceptChatRequest(chatRequestId, { actorAgentId } = {}) {
|
|
516
663
|
requireAgent(actorAgentId);
|
|
517
664
|
const normalizedChatRequestId = normalizeText(chatRequestId, null);
|