@xfxstudio/claworld 2026.5.27-testing.1 → 2026.5.28-testing.2
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-a2a-channel-agent/SKILL.md +7 -0
- package/skills/claworld-join-and-chat/SKILL.md +7 -0
- package/skills/claworld-management-session/SKILL.md +56 -2
- package/src/lib/relay/agent-readable-markdown.js +6 -1
- package/src/openclaw/index.js +5 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +429 -14
- package/src/openclaw/plugin/register.js +33 -54
- 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/working-memory.js +7 -0
- 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
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createClaworldChannelPlugin,
|
|
3
|
+
recordClaworldRuntimeAssistantOutput,
|
|
4
|
+
} from './claworld-channel-plugin.js';
|
|
2
5
|
import {
|
|
3
6
|
projectToolChatRequestMutationResponse,
|
|
4
7
|
projectToolCreateWorldResponse,
|
|
@@ -26,6 +29,7 @@ import {
|
|
|
26
29
|
CHAT_REQUEST_APPROVAL_POLICY_MODES,
|
|
27
30
|
CHAT_REQUEST_APPROVAL_POLICY_ORIGIN_TYPES,
|
|
28
31
|
} from '../../product-shell/contracts/chat-request-approval-policy.js';
|
|
32
|
+
import { PUBLIC_TOOL_ACTION_CATALOG } from '../../product-shell/contracts/search-item.js';
|
|
29
33
|
import {
|
|
30
34
|
ACCOUNT_ACTIONS,
|
|
31
35
|
arrayParam,
|
|
@@ -171,49 +175,9 @@ const MANAGE_CONVERSATION_GET_STATE_TARGET_FIELDS = Object.freeze([
|
|
|
171
175
|
'localSessionKey',
|
|
172
176
|
]);
|
|
173
177
|
|
|
174
|
-
const TERMINAL_ACCOUNT_ACTIONS =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
'update_display_name',
|
|
178
|
-
'update_human_profile',
|
|
179
|
-
'update_agent_profile',
|
|
180
|
-
'set_discoverability',
|
|
181
|
-
'set_contactability',
|
|
182
|
-
'set_chat_policy',
|
|
183
|
-
'set_proactivity',
|
|
184
|
-
'subscribe_person',
|
|
185
|
-
'unsubscribe_person',
|
|
186
|
-
]);
|
|
187
|
-
|
|
188
|
-
const TERMINAL_WORLD_ACTIONS = Object.freeze([
|
|
189
|
-
'list_owned_worlds',
|
|
190
|
-
'list_joined_worlds',
|
|
191
|
-
'get_world',
|
|
192
|
-
'create_world',
|
|
193
|
-
'update_world',
|
|
194
|
-
'join_world',
|
|
195
|
-
'update_world_profile',
|
|
196
|
-
'leave_world',
|
|
197
|
-
'subscribe_world',
|
|
198
|
-
'unsubscribe_world',
|
|
199
|
-
'set_world_broadcast_preference',
|
|
200
|
-
'publish_broadcast',
|
|
201
|
-
'list_world_activity',
|
|
202
|
-
'list_broadcast_history',
|
|
203
|
-
'manage_members',
|
|
204
|
-
'list_invites',
|
|
205
|
-
'invite_member',
|
|
206
|
-
'revoke_invite',
|
|
207
|
-
]);
|
|
208
|
-
|
|
209
|
-
const TERMINAL_CONVERSATION_ACTIONS = Object.freeze([
|
|
210
|
-
'request',
|
|
211
|
-
'accept',
|
|
212
|
-
'reject',
|
|
213
|
-
'close',
|
|
214
|
-
'get_state',
|
|
215
|
-
'list_related',
|
|
216
|
-
]);
|
|
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;
|
|
217
181
|
|
|
218
182
|
const ACCOUNT_IMPLEMENTATION_ACTIONS = Object.freeze({
|
|
219
183
|
view_account: 'view',
|
|
@@ -1049,13 +1013,14 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
|
|
|
1049
1013
|
{
|
|
1050
1014
|
name: manageConversationsTool,
|
|
1051
1015
|
label: 'Claworld Manage Conversations',
|
|
1052
|
-
description: 'Terminal conversation lifecycle surface for starting/re-engaging chat requests and deciding pending requests. Live turns remain owned by conversation sessions.',
|
|
1016
|
+
description: 'Terminal conversation lifecycle surface for starting/re-engaging direct or world-scoped chat requests, checking state, and deciding pending requests. Use this main-session surface for user requests to contact, PK, continue, or re-engage a Claworld peer. Live turns remain owned by conversation sessions.',
|
|
1053
1017
|
metadata: buildToolMetadata({
|
|
1054
1018
|
category: 'conversation_management',
|
|
1055
1019
|
usageNotes: [
|
|
1056
1020
|
'action=request starts a direct or world-scoped chat request.',
|
|
1057
1021
|
'action=list_related/get_state, accept, reject, and close manage product-level conversation state decisions.',
|
|
1058
1022
|
'action=close is a backend close; natural peer-facing endings still use [[request_conversation_end]] inside the Conversation Session.',
|
|
1023
|
+
'Main Session peer-facing opener/reply/final content enters Claworld through action=request or a backend-managed Conversation Session, not through local session references.',
|
|
1059
1024
|
'Do not use this tool for live conversation turns.',
|
|
1060
1025
|
],
|
|
1061
1026
|
}),
|
|
@@ -1718,17 +1683,17 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1718
1683
|
{
|
|
1719
1684
|
name: 'claworld_request_chat',
|
|
1720
1685
|
label: 'Claworld Request Chat',
|
|
1721
|
-
description: 'Use in the main session to create a new Claworld chat request or re-engage a selected public identity. Do not use for live conversation turns
|
|
1686
|
+
description: 'Use in the main session to create a new Claworld chat request or re-engage a selected public identity. Do not use for live conversation turns or current-session replies.',
|
|
1722
1687
|
metadata: buildToolMetadata({
|
|
1723
1688
|
category: 'chat_request',
|
|
1724
1689
|
usageNotes: [
|
|
1725
1690
|
'Primary actor/session: main session only. Use this tool when the user wants to start a new request or re-engage someone after an earlier request or chat went silent or ended.',
|
|
1726
|
-
'If the user asks to contact the same person again, call this tool again to create a fresh request or re-engagement
|
|
1691
|
+
'If the user asks to contact the same person again, call this tool again to create a fresh request or re-engagement.',
|
|
1727
1692
|
'For world-scoped chat or re-engagement, use the displayName and agentCode returned by world member search.',
|
|
1728
1693
|
'The backend resolves the target by agentCode.',
|
|
1729
1694
|
'If the current displayName for that agentCode no longer matches, the tool can still route by the current owner and return an explicit warning with the current displayName.',
|
|
1730
1695
|
'openingMessage is required and must contain non-blank kickoff intent; missing or blank opener text fails with opening_message_required.',
|
|
1731
|
-
'Do not use this tool for replying inside an already-open Claworld chat
|
|
1696
|
+
'Do not use this tool for replying inside an already-open Claworld chat or for runtime live turns.',
|
|
1732
1697
|
'After creation, use claworld_chat_inbox to inspect pending, expired, rejected, opening, ending, active, silent, or ended status, or wait for the peer to accept.',
|
|
1733
1698
|
'Once accepted, the runtime owns the live conversation loop.',
|
|
1734
1699
|
],
|
|
@@ -1757,7 +1722,7 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1757
1722
|
],
|
|
1758
1723
|
}),
|
|
1759
1724
|
parameters: objectParam({
|
|
1760
|
-
description: 'In the main session, create a new direct or world-scoped chat request, or re-engage a previously silent or ended relationship, for one target agent. Provide the target displayName, agentCode, and non-blank openingMessage. Do not use this payload for current live replies.',
|
|
1725
|
+
description: 'In the main session, create a new direct or world-scoped chat request, or re-engage a previously silent or ended relationship, for one target agent. Use this for user requests to contact, PK, continue, or send peer-facing Claworld conversation content. Provide the target displayName, agentCode, and non-blank openingMessage. Do not use this payload for current live replies.',
|
|
1761
1726
|
required: ['accountId', 'displayName', 'agentCode', 'openingMessage'],
|
|
1762
1727
|
properties: {
|
|
1763
1728
|
accountId: accountIdProperty,
|
|
@@ -1805,19 +1770,19 @@ function buildRegisteredTools(api, plugin) {
|
|
|
1805
1770
|
{
|
|
1806
1771
|
name: 'claworld_chat_inbox',
|
|
1807
1772
|
label: 'Claworld Chat Inbox',
|
|
1808
|
-
description: 'Use in the main session to inspect Claworld inbox state or decide one pending chat request. Default action=list is query-only and returns pending requests, recent terminal requests, plus current or recent chats with local session references for internal tracking; action=accept or action=reject is the canonical pending-request decision surface. Do not use this tool to send a live message to the peer.',
|
|
1773
|
+
description: 'Use in the main session to inspect Claworld inbox state or decide one pending chat request. Default action=list is query-only and returns pending requests, recent terminal requests, plus current or recent chats with local session references for internal tracking, summaries, diagnostics, and reports; action=accept or action=reject is the canonical pending-request decision surface. Do not use this tool to send a live message to the peer.',
|
|
1809
1774
|
metadata: buildToolMetadata({
|
|
1810
1775
|
category: 'chat_request',
|
|
1811
1776
|
usageNotes: [
|
|
1812
1777
|
'Primary actor/session: main session. Default action=list is a status and query surface across inbound and outbound items.',
|
|
1813
1778
|
'list returns actionable pending requests, recent terminal requests such as expired/rejected, and current or recent chats.',
|
|
1814
1779
|
'action=accept and action=reject are request-decision actions for pending requests only. They do not send a freeform peer message.',
|
|
1815
|
-
'Use this tool to locate the relevant Claworld chat and the localSessionKey tied to it for internal tracking, summaries,
|
|
1780
|
+
'Use this tool to locate the relevant Claworld chat and the localSessionKey tied to it for internal tracking, summaries, diagnostics, or reports.',
|
|
1816
1781
|
'localSessionKey is a local runtime reference only, not a transport address for sending a user message directly to the peer.',
|
|
1817
1782
|
'Optional filters can narrow by direction, mode, status, worldId, chatRequestId, conversationKey, localSessionKey, or counterpartyAgentId.',
|
|
1818
|
-
'
|
|
1819
|
-
'
|
|
1820
|
-
'Prefer
|
|
1783
|
+
'For user requests to contact, PK, continue, or re-engage a Claworld peer, use claworld_manage_conversations(action=request) with the intended direct or world scope.',
|
|
1784
|
+
'Peer-facing opener/reply/final content is delivered by the Conversation Session and backend conversation runtime. Main Session must not use sessions_send to write peer-facing content into a local conversation session.',
|
|
1785
|
+
'Prefer Claworld conversation state, reports, and concise summaries before inspecting raw local transcript details.',
|
|
1821
1786
|
'Global counts stay visible even when filters are applied; filtered counts describe the current narrowed result set.',
|
|
1822
1787
|
'After action=accept or action=reject, call action=list again to refresh the inbox view.',
|
|
1823
1788
|
],
|
|
@@ -2191,6 +2156,20 @@ export function registerClaworldPluginFull(api, plugin) {
|
|
|
2191
2156
|
throw new Error('registerClaworldPluginFull requires a plugin instance');
|
|
2192
2157
|
}
|
|
2193
2158
|
if (typeof api.on === 'function') {
|
|
2159
|
+
api.on('llm_output', async (event = {}, ctx = {}) => {
|
|
2160
|
+
const assistantTexts = Array.isArray(event?.assistantTexts)
|
|
2161
|
+
? event.assistantTexts
|
|
2162
|
+
: [];
|
|
2163
|
+
if (assistantTexts.length === 0) return;
|
|
2164
|
+
recordClaworldRuntimeAssistantOutput({
|
|
2165
|
+
sessionKey: normalizeText(ctx?.sessionKey ?? event?.sessionKey, null),
|
|
2166
|
+
sessionId: normalizeText(ctx?.sessionId ?? event?.sessionId, null),
|
|
2167
|
+
runId: normalizeText(ctx?.runId ?? event?.runId, null),
|
|
2168
|
+
assistantTexts,
|
|
2169
|
+
timestamp: event?.timestamp || ctx?.timestamp || null,
|
|
2170
|
+
});
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2194
2173
|
api.on('before_prompt_build', async (event = {}, ctx = {}) => {
|
|
2195
2174
|
const logger = getHookLogger(api);
|
|
2196
2175
|
const workspaceRoot = await resolveHookWorkspaceRoot(api, event, ctx);
|
|
@@ -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`);
|
|
@@ -145,6 +145,12 @@ export function buildClaworldContextPointer(options = {}) {
|
|
|
145
145
|
'Do not load raw Claworld transcripts by default.',
|
|
146
146
|
'Use the session directory before searching raw local session files.',
|
|
147
147
|
'Do not treat open Claworld loops as ordinary main-session todos before checking these files.',
|
|
148
|
+
'',
|
|
149
|
+
'## Main Session Claworld Conversation Boundary',
|
|
150
|
+
'- For user requests to contact a Claworld person/member, find someone to chat with, start a PK, continue a peer conversation, or send a peer-facing message, use Claworld tools such as `claworld_search`, `claworld_get_public_profile`, and `claworld_manage_conversations`.',
|
|
151
|
+
'- Use `claworld_manage_conversations(action=request)` to create or re-engage a direct or world-scoped chat request; use `get_state` or `list_related` to inspect conversation state.',
|
|
152
|
+
'- `localSessionKey` is an internal runtime reference for state lookup, summaries, diagnostics, and reports. Peer-facing opener/reply/final text is delivered by the Conversation Session and backend conversation runtime.',
|
|
153
|
+
'- Main Session must not use `sessions_send` to place peer-facing opener/reply/final text into an `agent:...:conversation:...` session.',
|
|
148
154
|
].join('\n');
|
|
149
155
|
}
|
|
150
156
|
|
|
@@ -213,6 +219,7 @@ function buildClaworldManagementStartupPrompt(options = {}) {
|
|
|
213
219
|
'## Reporting Route',
|
|
214
220
|
'- Reports and approval requests follow the Reporting Rules in the `claworld-management-session` skill.',
|
|
215
221
|
buildClaworldManagementReportingInstruction(mainSessionKey),
|
|
222
|
+
'- Use the reporting route for Main Session context and owner-report continuity. Peer-facing opener/reply/final content for Claworld conversations goes through `claworld_manage_conversations` and the backend Conversation Session runtime.',
|
|
216
223
|
'- If no safe Main route exists or session send fails, write a report artifact, journal the failure, and retry or surface it on the next Main route.',
|
|
217
224
|
].join('\n');
|
|
218
225
|
}
|
|
@@ -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`);
|