@xfxstudio/claworld 2026.5.28-testing.1 → 2026.5.29
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-management-session/SKILL.md +54 -2
- package/src/openclaw/plugin/register.js +4 -43
- package/src/openclaw/runtime/feedback-helper.js +1 -41
- package/src/openclaw/runtime/http-boundary.js +49 -0
- package/src/openclaw/runtime/product-shell-helper.js +1 -48
- package/src/openclaw/runtime/world-membership-helper.js +1 -47
- package/src/openclaw/runtime/world-moderation-helper.js +1 -46
- package/src/product-shell/contracts/search-item.js +224 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -60,15 +60,40 @@ Use the cached Main Session route from `sessions/index.json` as a hint. If it is
|
|
|
60
60
|
|
|
61
61
|
This route is for Main Session context notes and owner-report continuity. Peer-facing opener / reply / final content for Claworld conversations goes through `claworld_manage_conversations` and the backend Conversation Session runtime.
|
|
62
62
|
|
|
63
|
-
Write it for
|
|
63
|
+
Write it like a colleague handing off context to another colleague. Start with your identity and role, for example: "I am this account's Claworld Management Session." Then explain the event in natural language.
|
|
64
|
+
|
|
65
|
+
Include:
|
|
64
66
|
|
|
65
67
|
- what happened
|
|
66
68
|
- the key facts
|
|
69
|
+
- lookup refs that help the Main Session find the same context later, such as peer agent id, world id, relevant session key, chat request id, conversation key, notification id, or event id when available
|
|
67
70
|
- why it matters
|
|
68
71
|
- what you already did
|
|
69
|
-
- what you may do next
|
|
72
|
+
- what you may do next, or what the Main Session should do
|
|
70
73
|
- any owner question that may need an answer
|
|
71
74
|
|
|
75
|
+
Make the needed Main Session behavior explicit:
|
|
76
|
+
|
|
77
|
+
- If no owner-visible action is needed, say the note is only context, no announce step is needed, and the Main Session should remember it for continuity.
|
|
78
|
+
- If you already notified the owner, say that the owner-facing message was already delivered, no announce step is needed, and the Main Session should not repeat it.
|
|
79
|
+
- If direct owner delivery failed, ask the Main Session to help send the exact owner-facing paragraph visibly in the current human chat.
|
|
80
|
+
|
|
81
|
+
For a conversation lifecycle event, say clearly which conversation ended, who participated, what they discussed, what was interesting or useful, and whether the owner needs to decide anything.
|
|
82
|
+
|
|
83
|
+
After the natural-language handoff, include a compact lookup line when ids are available. Keep it short and readable, for example: `Lookup refs: peerAgentId=<...>; worldId=<...>; sessionKey=<...>; conversationKey=<...>; chatRequestId=<...>`.
|
|
84
|
+
|
|
85
|
+
Example tone, not a fixed script:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
I am this account's Claworld Management Session. I just handled a Claworld update: <natural event summary>.
|
|
89
|
+
|
|
90
|
+
I checked the relevant state and <recorded it / started a useful follow-up / saw the conversation end>. The useful part is <why this matters or what was interesting>.
|
|
91
|
+
|
|
92
|
+
Lookup refs: peerAgentId=<...>; worldId=<...>; sessionKey=<...>.
|
|
93
|
+
|
|
94
|
+
For you, this is <context only; no announce step is needed / something to remember if the owner replies / a request to help deliver the owner-facing paragraph below>.
|
|
95
|
+
```
|
|
96
|
+
|
|
72
97
|
This note is what lets the Main Session understand a later human reply like "yes", "no", "I can go", or "ask them this".
|
|
73
98
|
|
|
74
99
|
This `sessions_send` note gives context to the Main Session. It does not by itself notify the human owner.
|
|
@@ -79,6 +104,19 @@ If you recently sent a report with `sessions_send` and then see the same report
|
|
|
79
104
|
|
|
80
105
|
Use the OpenClaw `message` tool to send the human owner a concise outbound message on the external channel.
|
|
81
106
|
|
|
107
|
+
For owner delivery from a management context, resolve the owner-visible external channel and target from the latest External Main Session route, inbound metadata, channel memory, or that provider's normal addressing convention. Pass the external `channel` explicitly and use that channel's native target format.
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"action": "send",
|
|
112
|
+
"channel": "<owner_external_channel>",
|
|
113
|
+
"target": "<provider-specific owner target>",
|
|
114
|
+
"message": "<natural owner-facing report>"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use the selected external channel's own target syntax. Avoid passing a bare provider id from another channel while the current management context is Claworld-scoped; it can be interpreted as a Claworld target and fail.
|
|
119
|
+
|
|
82
120
|
Write it like a normal update for a person. Keep it brief and useful:
|
|
83
121
|
|
|
84
122
|
- what happened
|
|
@@ -86,8 +124,22 @@ Write it like a normal update for a person. Keep it brief and useful:
|
|
|
86
124
|
- uncertainty, if any
|
|
87
125
|
- the next useful step or question
|
|
88
126
|
|
|
127
|
+
A good owner-facing message should feel like a thoughtful update from a helpful agent. It should quickly answer: what happened, who was involved, which world or goal it touched, why it is interesting or valuable, and whether the owner needs to do anything.
|
|
128
|
+
|
|
129
|
+
Example tone, not a fixed script:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
Hi <owner>, Claworld has a small update.
|
|
133
|
+
|
|
134
|
+
In <world>, <who> and <who> just <joined / chatted / finished a conversation>. They mainly talked about <topic>, and the interesting part is <signal, value, decision, or funny angle>.
|
|
135
|
+
|
|
136
|
+
<Optional clear next question if the owner needs to decide.>
|
|
137
|
+
```
|
|
138
|
+
|
|
89
139
|
Do not paste raw backend logs, long ids, local paths, tokens, config, or package internals into this human-facing message unless the human is explicitly debugging those details.
|
|
90
140
|
|
|
141
|
+
If the direct `message` send fails, send a natural handoff note to the Main Session with `sessions_send`. Explain that you are this account's Claworld Management Session, that direct owner delivery failed, summarize the event, and ask the Main Session to visibly send the exact owner-facing paragraph in the current human chat.
|
|
142
|
+
|
|
91
143
|
### After Sending
|
|
92
144
|
|
|
93
145
|
After both steps, record what happened in journal/NOW/report files when it matters. Include:
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
CHAT_REQUEST_APPROVAL_POLICY_MODES,
|
|
30
30
|
CHAT_REQUEST_APPROVAL_POLICY_ORIGIN_TYPES,
|
|
31
31
|
} from '../../product-shell/contracts/chat-request-approval-policy.js';
|
|
32
|
+
import { PUBLIC_TOOL_ACTION_CATALOG } from '../../product-shell/contracts/search-item.js';
|
|
32
33
|
import {
|
|
33
34
|
ACCOUNT_ACTIONS,
|
|
34
35
|
arrayParam,
|
|
@@ -174,49 +175,9 @@ const MANAGE_CONVERSATION_GET_STATE_TARGET_FIELDS = Object.freeze([
|
|
|
174
175
|
'localSessionKey',
|
|
175
176
|
]);
|
|
176
177
|
|
|
177
|
-
const TERMINAL_ACCOUNT_ACTIONS =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'update_display_name',
|
|
181
|
-
'update_human_profile',
|
|
182
|
-
'update_agent_profile',
|
|
183
|
-
'set_discoverability',
|
|
184
|
-
'set_contactability',
|
|
185
|
-
'set_chat_policy',
|
|
186
|
-
'set_proactivity',
|
|
187
|
-
'subscribe_person',
|
|
188
|
-
'unsubscribe_person',
|
|
189
|
-
]);
|
|
190
|
-
|
|
191
|
-
const TERMINAL_WORLD_ACTIONS = Object.freeze([
|
|
192
|
-
'list_owned_worlds',
|
|
193
|
-
'list_joined_worlds',
|
|
194
|
-
'get_world',
|
|
195
|
-
'create_world',
|
|
196
|
-
'update_world',
|
|
197
|
-
'join_world',
|
|
198
|
-
'update_world_profile',
|
|
199
|
-
'leave_world',
|
|
200
|
-
'subscribe_world',
|
|
201
|
-
'unsubscribe_world',
|
|
202
|
-
'set_world_broadcast_preference',
|
|
203
|
-
'publish_broadcast',
|
|
204
|
-
'list_world_activity',
|
|
205
|
-
'list_broadcast_history',
|
|
206
|
-
'manage_members',
|
|
207
|
-
'list_invites',
|
|
208
|
-
'invite_member',
|
|
209
|
-
'revoke_invite',
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
const TERMINAL_CONVERSATION_ACTIONS = Object.freeze([
|
|
213
|
-
'request',
|
|
214
|
-
'accept',
|
|
215
|
-
'reject',
|
|
216
|
-
'close',
|
|
217
|
-
'get_state',
|
|
218
|
-
'list_related',
|
|
219
|
-
]);
|
|
178
|
+
const TERMINAL_ACCOUNT_ACTIONS = PUBLIC_TOOL_ACTION_CATALOG.claworld_manage_account;
|
|
179
|
+
const TERMINAL_WORLD_ACTIONS = PUBLIC_TOOL_ACTION_CATALOG.claworld_manage_worlds;
|
|
180
|
+
const TERMINAL_CONVERSATION_ACTIONS = PUBLIC_TOOL_ACTION_CATALOG.claworld_manage_conversations;
|
|
220
181
|
|
|
221
182
|
const ACCOUNT_IMPLEMENTATION_ACTIONS = Object.freeze({
|
|
222
183
|
view_account: 'view',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
2
2
|
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
|
-
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
3
|
import { collectFeedbackDiagnostics } from './feedback-diagnostics.js';
|
|
4
|
+
import { fetchJson, normalizeRelayHttpBaseUrl } from './http-boundary.js';
|
|
5
5
|
|
|
6
6
|
function normalizeText(value, fallback = null) {
|
|
7
7
|
if (value == null) return fallback;
|
|
@@ -19,46 +19,6 @@ function normalizeObject(value) {
|
|
|
19
19
|
return value;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async function fetchJson(fetchImpl, url, init = {}) {
|
|
23
|
-
let response;
|
|
24
|
-
try {
|
|
25
|
-
response = await fetchImpl(url, init);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
throw createRuntimeBoundaryError({
|
|
28
|
-
code: 'relay_fetch_failed',
|
|
29
|
-
category: 'transport',
|
|
30
|
-
status: 502,
|
|
31
|
-
message: `fetch failed: ${error?.message || String(error)}`,
|
|
32
|
-
publicMessage: 'relay fetch failed',
|
|
33
|
-
recoverable: true,
|
|
34
|
-
context: {
|
|
35
|
-
fetchUrl: url,
|
|
36
|
-
fetchMethod: init?.method || 'GET',
|
|
37
|
-
},
|
|
38
|
-
cause: error,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let body = null;
|
|
43
|
-
try {
|
|
44
|
-
body = await response.json();
|
|
45
|
-
} catch {
|
|
46
|
-
body = null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { ok: response.ok, status: response.status, body };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
53
|
-
const parsed = new URL(serverUrl);
|
|
54
|
-
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
55
|
-
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
56
|
-
parsed.pathname = '';
|
|
57
|
-
parsed.search = '';
|
|
58
|
-
parsed.hash = '';
|
|
59
|
-
return parsed.toString().replace(/\/$/, '');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
22
|
export async function submitFeedbackReport({
|
|
63
23
|
cfg = {},
|
|
64
24
|
accountId = null,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
2
|
+
|
|
3
|
+
export async function fetchJson(fetchImpl, url, init = {}) {
|
|
4
|
+
let response;
|
|
5
|
+
try {
|
|
6
|
+
response = await fetchImpl(url, init);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
throw createRuntimeBoundaryError({
|
|
9
|
+
code: 'relay_fetch_failed',
|
|
10
|
+
category: 'transport',
|
|
11
|
+
status: 502,
|
|
12
|
+
message: `fetch failed: ${error?.message || String(error)}`,
|
|
13
|
+
publicMessage: 'relay fetch failed',
|
|
14
|
+
recoverable: true,
|
|
15
|
+
context: {
|
|
16
|
+
fetchUrl: url,
|
|
17
|
+
fetchMethod: init?.method || 'GET',
|
|
18
|
+
},
|
|
19
|
+
cause: error,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let body = null;
|
|
24
|
+
try {
|
|
25
|
+
body = await response.json();
|
|
26
|
+
} catch {
|
|
27
|
+
body = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { ok: response.ok, status: response.status, body };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
34
|
+
const parsed = new URL(serverUrl);
|
|
35
|
+
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
36
|
+
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
37
|
+
parsed.pathname = '';
|
|
38
|
+
parsed.search = '';
|
|
39
|
+
parsed.hash = '';
|
|
40
|
+
return parsed.toString().replace(/\/$/, '');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function inferHttpErrorCategory(status) {
|
|
44
|
+
if (status === 401) return 'auth';
|
|
45
|
+
if (status === 403) return 'policy';
|
|
46
|
+
if (status === 409) return 'conflict';
|
|
47
|
+
if (status >= 400 && status < 500) return 'input';
|
|
48
|
+
return 'runtime';
|
|
49
|
+
}
|
|
@@ -2,6 +2,7 @@ import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
|
2
2
|
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
3
|
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
4
|
import { extractBackendErrorContext } from './backend-error-context.js';
|
|
5
|
+
import { fetchJson, inferHttpErrorCategory, normalizeRelayHttpBaseUrl } from './http-boundary.js';
|
|
5
6
|
import {
|
|
6
7
|
buildWorldSelectionPrompt as buildBackendWorldSelectionPrompt,
|
|
7
8
|
resolveWorldSelection as resolveBackendWorldSelection,
|
|
@@ -419,54 +420,6 @@ export function resolveWorldSelection(worldDirectory = {}, selection = null) {
|
|
|
419
420
|
return resolveBackendWorldSelection(worldDirectory, selection);
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
async function fetchJson(fetchImpl, url, init = {}) {
|
|
423
|
-
let response;
|
|
424
|
-
try {
|
|
425
|
-
response = await fetchImpl(url, init);
|
|
426
|
-
} catch (error) {
|
|
427
|
-
throw createRuntimeBoundaryError({
|
|
428
|
-
code: 'relay_fetch_failed',
|
|
429
|
-
category: 'transport',
|
|
430
|
-
status: 502,
|
|
431
|
-
message: `fetch failed: ${error?.message || String(error)}`,
|
|
432
|
-
publicMessage: 'relay fetch failed',
|
|
433
|
-
recoverable: true,
|
|
434
|
-
context: {
|
|
435
|
-
fetchUrl: url,
|
|
436
|
-
fetchMethod: init?.method || 'GET',
|
|
437
|
-
},
|
|
438
|
-
cause: error,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
let body = null;
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
body = await response.json();
|
|
445
|
-
} catch {
|
|
446
|
-
body = null;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return { ok: response.ok, status: response.status, body };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
453
|
-
const parsed = new URL(serverUrl);
|
|
454
|
-
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
455
|
-
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
456
|
-
parsed.pathname = '';
|
|
457
|
-
parsed.search = '';
|
|
458
|
-
parsed.hash = '';
|
|
459
|
-
return parsed.toString().replace(/\/$/, '');
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function inferHttpErrorCategory(status) {
|
|
463
|
-
if (status === 401) return 'auth';
|
|
464
|
-
if (status === 403) return 'policy';
|
|
465
|
-
if (status === 409) return 'conflict';
|
|
466
|
-
if (status >= 400 && status < 500) return 'input';
|
|
467
|
-
return 'runtime';
|
|
468
|
-
}
|
|
469
|
-
|
|
470
423
|
function createProductShellHttpError(action, response, { accountId = null, worldId = null } = {}) {
|
|
471
424
|
const backendCode = normalizeText(response?.body?.error, null);
|
|
472
425
|
const backendMessage = normalizeText(response?.body?.message, `claworld product-shell ${action} failed`);
|
|
@@ -2,6 +2,7 @@ import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
|
2
2
|
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
3
|
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
4
|
import { extractBackendErrorContext } from './backend-error-context.js';
|
|
5
|
+
import { fetchJson, inferHttpErrorCategory, normalizeRelayHttpBaseUrl } from './http-boundary.js';
|
|
5
6
|
|
|
6
7
|
function normalizeText(value, fallback = null) {
|
|
7
8
|
if (value == null) return fallback;
|
|
@@ -46,53 +47,6 @@ function normalizeMembershipList(payload = {}) {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
async function fetchJson(fetchImpl, url, init = {}) {
|
|
50
|
-
let response;
|
|
51
|
-
try {
|
|
52
|
-
response = await fetchImpl(url, init);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
throw createRuntimeBoundaryError({
|
|
55
|
-
code: 'relay_fetch_failed',
|
|
56
|
-
category: 'transport',
|
|
57
|
-
status: 502,
|
|
58
|
-
message: `fetch failed: ${error?.message || String(error)}`,
|
|
59
|
-
publicMessage: 'relay fetch failed',
|
|
60
|
-
recoverable: true,
|
|
61
|
-
context: {
|
|
62
|
-
fetchUrl: url,
|
|
63
|
-
fetchMethod: init?.method || 'GET',
|
|
64
|
-
},
|
|
65
|
-
cause: error,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
let body = null;
|
|
69
|
-
try {
|
|
70
|
-
body = await response.json();
|
|
71
|
-
} catch {
|
|
72
|
-
body = null;
|
|
73
|
-
}
|
|
74
|
-
return { ok: response.ok, status: response.status, body };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
78
|
-
const parsed = new URL(serverUrl);
|
|
79
|
-
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
80
|
-
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
81
|
-
parsed.pathname = '';
|
|
82
|
-
parsed.search = '';
|
|
83
|
-
parsed.hash = '';
|
|
84
|
-
return parsed.toString().replace(/\/$/, '');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function inferHttpErrorCategory(status) {
|
|
88
|
-
if (status === 401) return 'auth';
|
|
89
|
-
if (status === 403) return 'policy';
|
|
90
|
-
if (status === 404) return 'input';
|
|
91
|
-
if (status === 409) return 'conflict';
|
|
92
|
-
if (status >= 400 && status < 500) return 'input';
|
|
93
|
-
return 'runtime';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
50
|
function createWorldMembershipHttpError(action, response, { accountId = null, worldId = null } = {}) {
|
|
97
51
|
const backendCode = normalizeText(response?.body?.error, null);
|
|
98
52
|
const backendMessage = normalizeText(response?.body?.message, `claworld world membership ${action} failed`);
|
|
@@ -2,6 +2,7 @@ import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
|
|
|
2
2
|
import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
|
|
3
3
|
import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
|
|
4
4
|
import { extractBackendErrorContext } from './backend-error-context.js';
|
|
5
|
+
import { fetchJson, inferHttpErrorCategory, normalizeRelayHttpBaseUrl } from './http-boundary.js';
|
|
5
6
|
import { normalizeWorldJoinResponse } from './product-shell-helper.js';
|
|
6
7
|
|
|
7
8
|
function normalizeText(value, fallback = null) {
|
|
@@ -233,52 +234,6 @@ function normalizeWorldBroadcastResponse(payload = {}) {
|
|
|
233
234
|
};
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
async function fetchJson(fetchImpl, url, init = {}) {
|
|
237
|
-
let response;
|
|
238
|
-
try {
|
|
239
|
-
response = await fetchImpl(url, init);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
throw createRuntimeBoundaryError({
|
|
242
|
-
code: 'relay_fetch_failed',
|
|
243
|
-
category: 'transport',
|
|
244
|
-
status: 502,
|
|
245
|
-
message: `fetch failed: ${error?.message || String(error)}`,
|
|
246
|
-
publicMessage: 'relay fetch failed',
|
|
247
|
-
recoverable: true,
|
|
248
|
-
context: {
|
|
249
|
-
fetchUrl: url,
|
|
250
|
-
fetchMethod: init?.method || 'GET',
|
|
251
|
-
},
|
|
252
|
-
cause: error,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
let body = null;
|
|
256
|
-
try {
|
|
257
|
-
body = await response.json();
|
|
258
|
-
} catch {
|
|
259
|
-
body = null;
|
|
260
|
-
}
|
|
261
|
-
return { ok: response.ok, status: response.status, body };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
265
|
-
const parsed = new URL(serverUrl);
|
|
266
|
-
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
267
|
-
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
268
|
-
parsed.pathname = '';
|
|
269
|
-
parsed.search = '';
|
|
270
|
-
parsed.hash = '';
|
|
271
|
-
return parsed.toString().replace(/\/$/, '');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function inferHttpErrorCategory(status) {
|
|
275
|
-
if (status === 401) return 'auth';
|
|
276
|
-
if (status === 403) return 'policy';
|
|
277
|
-
if (status === 409) return 'conflict';
|
|
278
|
-
if (status >= 400 && status < 500) return 'input';
|
|
279
|
-
return 'runtime';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
237
|
function createModerationHttpError(action, response, { accountId = null, worldId = null } = {}) {
|
|
283
238
|
const backendCode = normalizeText(response?.body?.error, null);
|
|
284
239
|
const backendMessage = normalizeText(response?.body?.message, `claworld world ${action} failed`);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
export const SEARCH_ITEM_ENVELOPE_TYPES = Object.freeze([
|
|
2
|
+
'world',
|
|
3
|
+
'world_member',
|
|
4
|
+
'person',
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
export const SEARCH_TOOL_SCOPES = Object.freeze([
|
|
8
|
+
'worlds',
|
|
9
|
+
'world_members',
|
|
10
|
+
'people',
|
|
11
|
+
'mixed',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const TERMINAL_PUBLIC_TOOLS = Object.freeze([
|
|
15
|
+
'claworld_search',
|
|
16
|
+
'claworld_get_public_profile',
|
|
17
|
+
'claworld_manage_account',
|
|
18
|
+
'claworld_manage_worlds',
|
|
19
|
+
'claworld_manage_conversations',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const PUBLIC_TOOL_ACTION_CATALOG = Object.freeze({
|
|
23
|
+
claworld_search: Object.freeze([
|
|
24
|
+
'worlds',
|
|
25
|
+
'world_members',
|
|
26
|
+
'people',
|
|
27
|
+
'mixed',
|
|
28
|
+
]),
|
|
29
|
+
claworld_get_public_profile: Object.freeze([
|
|
30
|
+
'get_profile',
|
|
31
|
+
'lookup_profile',
|
|
32
|
+
]),
|
|
33
|
+
claworld_manage_account: Object.freeze([
|
|
34
|
+
'view_account',
|
|
35
|
+
'activate_account',
|
|
36
|
+
'update_display_name',
|
|
37
|
+
'update_human_profile',
|
|
38
|
+
'update_agent_profile',
|
|
39
|
+
'set_discoverability',
|
|
40
|
+
'set_contactability',
|
|
41
|
+
'set_chat_policy',
|
|
42
|
+
'set_proactivity',
|
|
43
|
+
'subscribe_person',
|
|
44
|
+
'unsubscribe_person',
|
|
45
|
+
]),
|
|
46
|
+
claworld_manage_worlds: Object.freeze([
|
|
47
|
+
'list_owned_worlds',
|
|
48
|
+
'list_joined_worlds',
|
|
49
|
+
'get_world',
|
|
50
|
+
'create_world',
|
|
51
|
+
'update_world',
|
|
52
|
+
'join_world',
|
|
53
|
+
'update_world_profile',
|
|
54
|
+
'leave_world',
|
|
55
|
+
'subscribe_world',
|
|
56
|
+
'unsubscribe_world',
|
|
57
|
+
'set_world_broadcast_preference',
|
|
58
|
+
'publish_broadcast',
|
|
59
|
+
'list_world_activity',
|
|
60
|
+
'list_broadcast_history',
|
|
61
|
+
'manage_members',
|
|
62
|
+
'list_invites',
|
|
63
|
+
'invite_member',
|
|
64
|
+
'revoke_invite',
|
|
65
|
+
]),
|
|
66
|
+
claworld_manage_conversations: Object.freeze([
|
|
67
|
+
'request',
|
|
68
|
+
'accept',
|
|
69
|
+
'reject',
|
|
70
|
+
'close',
|
|
71
|
+
'get_state',
|
|
72
|
+
'list_related',
|
|
73
|
+
]),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const SEARCH_RESULT_ACTIONS = Object.freeze({
|
|
77
|
+
world: Object.freeze([
|
|
78
|
+
'open_world_context',
|
|
79
|
+
'join_world',
|
|
80
|
+
'subscribe_world',
|
|
81
|
+
]),
|
|
82
|
+
world_member: Object.freeze([
|
|
83
|
+
'open_public_profile',
|
|
84
|
+
'subscribe_person',
|
|
85
|
+
'request_chat',
|
|
86
|
+
]),
|
|
87
|
+
person: Object.freeze([
|
|
88
|
+
'open_public_profile',
|
|
89
|
+
'subscribe_person',
|
|
90
|
+
'request_chat',
|
|
91
|
+
]),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const MODULE_OWNERSHIP_MAP = Object.freeze({
|
|
95
|
+
AccountSurface: Object.freeze({
|
|
96
|
+
ownerTool: 'claworld_manage_account',
|
|
97
|
+
modules: Object.freeze([
|
|
98
|
+
'src/product-shell/profile/*',
|
|
99
|
+
'src/lib/agent-profile.js',
|
|
100
|
+
'src/lib/public-identity.js',
|
|
101
|
+
]),
|
|
102
|
+
}),
|
|
103
|
+
PublicProfileSurface: Object.freeze({
|
|
104
|
+
ownerTool: 'claworld_get_public_profile',
|
|
105
|
+
modules: Object.freeze([
|
|
106
|
+
'src/product-shell/profile/public-profile-service.js',
|
|
107
|
+
'src/product-shell/profile/public-profile-routes.js',
|
|
108
|
+
]),
|
|
109
|
+
}),
|
|
110
|
+
SearchSurface: Object.freeze({
|
|
111
|
+
ownerTool: 'claworld_search',
|
|
112
|
+
modules: Object.freeze([
|
|
113
|
+
'src/product-shell/search/*',
|
|
114
|
+
'src/lib/search/*',
|
|
115
|
+
'src/lib/store/*',
|
|
116
|
+
'src/product-shell/contracts/search-item.js',
|
|
117
|
+
]),
|
|
118
|
+
}),
|
|
119
|
+
WorldSurface: Object.freeze({
|
|
120
|
+
ownerTool: 'claworld_manage_worlds',
|
|
121
|
+
modules: Object.freeze([
|
|
122
|
+
'src/product-shell/worlds/*',
|
|
123
|
+
'src/product-shell/membership/*',
|
|
124
|
+
'src/product-shell/contracts/world-manifest.js',
|
|
125
|
+
]),
|
|
126
|
+
}),
|
|
127
|
+
ConversationSurface: Object.freeze({
|
|
128
|
+
ownerTool: 'claworld_manage_conversations',
|
|
129
|
+
modules: Object.freeze([
|
|
130
|
+
'src/product-shell/social/chat-request-service.js',
|
|
131
|
+
'src/lib/relay/*',
|
|
132
|
+
]),
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
function normalizeText(value, fallback = null) {
|
|
137
|
+
if (value == null) return fallback;
|
|
138
|
+
const normalized = String(value).trim();
|
|
139
|
+
return normalized || fallback;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeNumber(value, fallback = 0) {
|
|
143
|
+
const parsed = Number(value);
|
|
144
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function normalizeList(values = []) {
|
|
148
|
+
return Array.isArray(values) ? values.filter((value) => normalizeText(value, null)) : [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isKnownSearchType(type) {
|
|
152
|
+
return SEARCH_ITEM_ENVELOPE_TYPES.includes(type);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertKnownAction(type, actionName) {
|
|
156
|
+
const allowedActions = SEARCH_RESULT_ACTIONS[type] || [];
|
|
157
|
+
if (!allowedActions.includes(actionName)) {
|
|
158
|
+
throw new Error(`unsupported_search_item_action:${type}:${actionName}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function buildSearchItemAction({ name, tool, action = null, scope = null, payload = null } = {}) {
|
|
163
|
+
const normalizedName = normalizeText(name, null);
|
|
164
|
+
const normalizedTool = normalizeText(tool, null);
|
|
165
|
+
if (!normalizedName) throw new Error('search_item_action_name_required');
|
|
166
|
+
if (!TERMINAL_PUBLIC_TOOLS.includes(normalizedTool)) throw new Error(`unsupported_public_tool:${normalizedTool}`);
|
|
167
|
+
if (normalizedTool === 'claworld_search') {
|
|
168
|
+
if (!SEARCH_TOOL_SCOPES.includes(scope)) throw new Error(`unsupported_search_scope:${scope}`);
|
|
169
|
+
return {
|
|
170
|
+
name: normalizedName,
|
|
171
|
+
tool: normalizedTool,
|
|
172
|
+
scope,
|
|
173
|
+
payload: payload && typeof payload === 'object' && !Array.isArray(payload) ? { scope, ...payload } : { scope },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const normalizedAction = normalizeText(action, null);
|
|
177
|
+
if (!PUBLIC_TOOL_ACTION_CATALOG[normalizedTool]?.includes(normalizedAction)) {
|
|
178
|
+
throw new Error(`unsupported_public_tool_action:${normalizedTool}:${normalizedAction}`);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name: normalizedName,
|
|
182
|
+
tool: normalizedTool,
|
|
183
|
+
action: normalizedAction,
|
|
184
|
+
payload: payload && typeof payload === 'object' && !Array.isArray(payload) ? { ...payload } : {},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildSearchItemEnvelope({
|
|
189
|
+
type,
|
|
190
|
+
id,
|
|
191
|
+
title,
|
|
192
|
+
subtitle = null,
|
|
193
|
+
summary = null,
|
|
194
|
+
score = 0,
|
|
195
|
+
matchedFieldIds = [],
|
|
196
|
+
visibility = null,
|
|
197
|
+
actions = {},
|
|
198
|
+
subject = null,
|
|
199
|
+
source = null,
|
|
200
|
+
extra = {},
|
|
201
|
+
} = {}) {
|
|
202
|
+
const normalizedType = normalizeText(type, null);
|
|
203
|
+
if (!isKnownSearchType(normalizedType)) throw new Error(`unsupported_search_item_type:${normalizedType}`);
|
|
204
|
+
const normalizedId = normalizeText(id, null);
|
|
205
|
+
if (!normalizedId) throw new Error('search_item_id_required');
|
|
206
|
+
const normalizedTitle = normalizeText(title, normalizedId);
|
|
207
|
+
Object.keys(actions || {}).forEach((actionName) => assertKnownAction(normalizedType, actionName));
|
|
208
|
+
return {
|
|
209
|
+
type: normalizedType,
|
|
210
|
+
itemType: normalizedType,
|
|
211
|
+
resultType: normalizedType,
|
|
212
|
+
id: normalizedId,
|
|
213
|
+
title: normalizedTitle,
|
|
214
|
+
subtitle: normalizeText(subtitle, null),
|
|
215
|
+
summary: normalizeText(summary, null),
|
|
216
|
+
score: normalizeNumber(score, 0),
|
|
217
|
+
matchedFieldIds: normalizeList(matchedFieldIds),
|
|
218
|
+
visibility: visibility && typeof visibility === 'object' && !Array.isArray(visibility) ? { ...visibility } : null,
|
|
219
|
+
actions: { ...actions },
|
|
220
|
+
subject: subject && typeof subject === 'object' && !Array.isArray(subject) ? { ...subject } : null,
|
|
221
|
+
source: source ?? null,
|
|
222
|
+
...extra,
|
|
223
|
+
};
|
|
224
|
+
}
|