@xfxstudio/claworld 0.2.5 → 0.2.7
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/installer/core.js +16 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +164 -12
- package/src/openclaw/plugin/config-schema.js +9 -1
- package/src/openclaw/plugin/register.js +151 -15
- package/src/openclaw/plugin/relay-client.js +502 -1
- package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
- package/src/openclaw/runtime/tool-contracts.js +40 -1
- package/src/openclaw/runtime/tool-inventory.js +3 -3
- package/src/openclaw/runtime/world-moderation-helper.js +9 -13
- package/src/product-shell/catalog/default-world-catalog.js +12 -258
- package/src/product-shell/contracts/world-manifest.js +0 -38
- package/src/product-shell/contracts/world-orchestration.js +0 -6
- package/src/product-shell/index.js +1 -5
- package/src/product-shell/membership/membership-service.js +24 -6
- package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -2
- package/src/product-shell/orchestration/world-conversation-text.js +0 -2
- package/src/product-shell/social/chat-request-routes.js +24 -1
- package/src/product-shell/social/chat-request-service.js +185 -15
- package/src/product-shell/worlds/world-admin-service.js +28 -120
- package/src/product-shell/worlds/world-authorization.js +20 -17
- package/src/product-shell/worlds/world-broadcast-service.js +2 -5
- package/src/product-shell/worlds/world-text.js +0 -2
- package/src/product-shell/results/result-service.js +0 -21
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
const DUPLICATE_CONNECTION_CLOSE_CODE = 4001;
|
|
16
16
|
const STALE_CONNECTION_CLOSE_CODE = 4002;
|
|
17
17
|
const TERMINAL_CLOSE_REASONS = new Set(['duplicate_connection_replaced', 'stale_connection']);
|
|
18
|
+
const DEFAULT_REPLY_ACK_TIMEOUT_MS = 5000;
|
|
18
19
|
|
|
19
20
|
function normalizeRelayWebSocketUrl(serverUrl) {
|
|
20
21
|
const parsed = new URL(serverUrl);
|
|
@@ -49,6 +50,90 @@ function buildInboundEnvelope(message = {}) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function normalizeOptionalText(value) {
|
|
54
|
+
if (value == null) return null;
|
|
55
|
+
const normalized = String(value).trim();
|
|
56
|
+
return normalized || null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
|
|
60
|
+
return createRuntimeBoundaryError({
|
|
61
|
+
code: 'relay_reply_ack_timeout',
|
|
62
|
+
category: 'transport',
|
|
63
|
+
status: 504,
|
|
64
|
+
message: `timed out waiting for relay reply acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
|
|
65
|
+
publicMessage: 'relay reply acknowledgement timed out',
|
|
66
|
+
recoverable: true,
|
|
67
|
+
context,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildReplyFallbackError({
|
|
72
|
+
deliveryId,
|
|
73
|
+
status,
|
|
74
|
+
body,
|
|
75
|
+
context = {},
|
|
76
|
+
} = {}) {
|
|
77
|
+
return createRuntimeBoundaryError({
|
|
78
|
+
code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_reply_fallback_failed',
|
|
79
|
+
category: status >= 500 ? 'runtime' : 'transport',
|
|
80
|
+
status: Number.isInteger(status) ? status : 502,
|
|
81
|
+
message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay reply fallback failed',
|
|
82
|
+
publicMessage: 'relay reply fallback failed',
|
|
83
|
+
recoverable: status >= 500,
|
|
84
|
+
context: {
|
|
85
|
+
deliveryId: normalizeOptionalText(deliveryId),
|
|
86
|
+
...context,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildKeepSilentAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
|
|
92
|
+
return createRuntimeBoundaryError({
|
|
93
|
+
code: 'relay_kept_silent_ack_timeout',
|
|
94
|
+
category: 'transport',
|
|
95
|
+
status: 504,
|
|
96
|
+
message: `timed out waiting for relay kept_silent acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
|
|
97
|
+
publicMessage: 'relay kept_silent acknowledgement timed out',
|
|
98
|
+
recoverable: true,
|
|
99
|
+
context,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildKeepSilentFallbackError({
|
|
104
|
+
deliveryId,
|
|
105
|
+
status,
|
|
106
|
+
body,
|
|
107
|
+
context = {},
|
|
108
|
+
} = {}) {
|
|
109
|
+
return createRuntimeBoundaryError({
|
|
110
|
+
code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_kept_silent_fallback_failed',
|
|
111
|
+
category: status >= 500 ? 'runtime' : 'transport',
|
|
112
|
+
status: Number.isInteger(status) ? status : 502,
|
|
113
|
+
message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay kept_silent fallback failed',
|
|
114
|
+
publicMessage: 'relay kept_silent fallback failed',
|
|
115
|
+
recoverable: status >= 500,
|
|
116
|
+
context: {
|
|
117
|
+
deliveryId: normalizeOptionalText(deliveryId),
|
|
118
|
+
...context,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isReplyAlreadyApplied(result = null, deliveryId = null) {
|
|
124
|
+
if (!result || result.status !== 409) return false;
|
|
125
|
+
if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
|
|
126
|
+
if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
|
|
127
|
+
return normalizeOptionalText(result.body?.delivery?.status) === 'replied';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isDeliveryKeptSilentAlreadyApplied(result = null, deliveryId = null) {
|
|
131
|
+
if (!result || result.status !== 409) return false;
|
|
132
|
+
if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
|
|
133
|
+
if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
|
|
134
|
+
return normalizeOptionalText(result.body?.delivery?.status) === 'kept_silent';
|
|
135
|
+
}
|
|
136
|
+
|
|
52
137
|
export class ClaworldRelayClient extends EventEmitter {
|
|
53
138
|
constructor({
|
|
54
139
|
logger = console,
|
|
@@ -479,7 +564,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
479
564
|
config,
|
|
480
565
|
agentId,
|
|
481
566
|
credential = null,
|
|
482
|
-
clientVersion = 'claworld-plugin/0.
|
|
567
|
+
clientVersion = 'claworld-plugin/0.2.7',
|
|
483
568
|
sessionTarget,
|
|
484
569
|
fallbackTarget,
|
|
485
570
|
} = {}) {
|
|
@@ -597,6 +682,419 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
597
682
|
});
|
|
598
683
|
}
|
|
599
684
|
|
|
685
|
+
sendKeepSilent({ deliveryId, sessionKey, reason = null, source = 'openclaw-autochain' } = {}) {
|
|
686
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
687
|
+
if (!normalizedDeliveryId) {
|
|
688
|
+
throw createRuntimeBoundaryError({
|
|
689
|
+
code: 'relay_delivery_id_required',
|
|
690
|
+
category: 'input',
|
|
691
|
+
status: 400,
|
|
692
|
+
message: 'deliveryId is required to mark relay delivery kept_silent',
|
|
693
|
+
publicMessage: 'deliveryId is required',
|
|
694
|
+
recoverable: true,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
const envelope = {
|
|
698
|
+
deliveryId: normalizedDeliveryId,
|
|
699
|
+
sessionKey: normalizeOptionalText(sessionKey) || null,
|
|
700
|
+
reason: normalizeOptionalText(reason) || 'no_renderable_reply',
|
|
701
|
+
source: normalizeOptionalText(source) || 'openclaw-autochain',
|
|
702
|
+
};
|
|
703
|
+
this.send({
|
|
704
|
+
type: 'kept_silent',
|
|
705
|
+
deliveryId: envelope.deliveryId,
|
|
706
|
+
sessionKey: envelope.sessionKey,
|
|
707
|
+
payload: {
|
|
708
|
+
reason: envelope.reason,
|
|
709
|
+
source: envelope.source,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
return envelope;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
waitForReplyAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
716
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
717
|
+
if (!normalizedDeliveryId) {
|
|
718
|
+
return Promise.reject(createRuntimeBoundaryError({
|
|
719
|
+
code: 'relay_delivery_id_required',
|
|
720
|
+
category: 'input',
|
|
721
|
+
status: 400,
|
|
722
|
+
message: 'deliveryId is required to wait for relay reply acknowledgement',
|
|
723
|
+
publicMessage: 'deliveryId is required',
|
|
724
|
+
recoverable: true,
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return new Promise((resolve, reject) => {
|
|
729
|
+
let settled = false;
|
|
730
|
+
let timeout = null;
|
|
731
|
+
|
|
732
|
+
const cleanup = () => {
|
|
733
|
+
if (timeout) clearTimeout(timeout);
|
|
734
|
+
this.off('reply.accepted', onReplyAccepted);
|
|
735
|
+
this.off('disconnect', onDisconnect);
|
|
736
|
+
this.off('close', onDisconnect);
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const settleResolve = (value) => {
|
|
740
|
+
if (settled) return;
|
|
741
|
+
settled = true;
|
|
742
|
+
cleanup();
|
|
743
|
+
resolve(value);
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
const settleReject = (error) => {
|
|
747
|
+
if (settled) return;
|
|
748
|
+
settled = true;
|
|
749
|
+
cleanup();
|
|
750
|
+
reject(error);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const onReplyAccepted = (message = {}) => {
|
|
754
|
+
const repliedDeliveryId = normalizeOptionalText(message?.data?.repliedDeliveryId);
|
|
755
|
+
if (repliedDeliveryId !== normalizedDeliveryId) return;
|
|
756
|
+
settleResolve(message);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const onDisconnect = (info = {}) => {
|
|
760
|
+
settleReject(createRuntimeBoundaryError({
|
|
761
|
+
code: 'relay_reply_ack_disconnected',
|
|
762
|
+
category: 'transport',
|
|
763
|
+
status: 502,
|
|
764
|
+
message: `relay websocket closed before reply acknowledgement for ${normalizedDeliveryId}`,
|
|
765
|
+
publicMessage: 'relay websocket closed before reply acknowledgement',
|
|
766
|
+
recoverable: true,
|
|
767
|
+
context: this.buildBoundaryContext({
|
|
768
|
+
stage: 'reply_ack_wait',
|
|
769
|
+
deliveryId: normalizedDeliveryId,
|
|
770
|
+
closeCode: info?.code ?? null,
|
|
771
|
+
closeReason: info?.reason || null,
|
|
772
|
+
}),
|
|
773
|
+
}));
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
this.on('reply.accepted', onReplyAccepted);
|
|
777
|
+
this.on('disconnect', onDisconnect);
|
|
778
|
+
this.on('close', onDisconnect);
|
|
779
|
+
|
|
780
|
+
timeout = setTimeout(() => {
|
|
781
|
+
settleReject(buildReplyAckTimeoutError({
|
|
782
|
+
deliveryId: normalizedDeliveryId,
|
|
783
|
+
timeoutMs,
|
|
784
|
+
context: this.buildBoundaryContext({
|
|
785
|
+
stage: 'reply_ack_wait',
|
|
786
|
+
deliveryId: normalizedDeliveryId,
|
|
787
|
+
}),
|
|
788
|
+
}));
|
|
789
|
+
}, timeoutMs);
|
|
790
|
+
if (typeof timeout.unref === 'function') timeout.unref();
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
|
|
795
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
796
|
+
if (!normalizedDeliveryId) {
|
|
797
|
+
return Promise.reject(createRuntimeBoundaryError({
|
|
798
|
+
code: 'relay_delivery_id_required',
|
|
799
|
+
category: 'input',
|
|
800
|
+
status: 400,
|
|
801
|
+
message: 'deliveryId is required to wait for relay kept_silent acknowledgement',
|
|
802
|
+
publicMessage: 'deliveryId is required',
|
|
803
|
+
recoverable: true,
|
|
804
|
+
}));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return new Promise((resolve, reject) => {
|
|
808
|
+
let settled = false;
|
|
809
|
+
let timeout = null;
|
|
810
|
+
|
|
811
|
+
const cleanup = () => {
|
|
812
|
+
if (timeout) clearTimeout(timeout);
|
|
813
|
+
this.off('kept_silent.accepted', onKeptSilentAccepted);
|
|
814
|
+
this.off('disconnect', onDisconnect);
|
|
815
|
+
this.off('close', onDisconnect);
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const settleResolve = (value) => {
|
|
819
|
+
if (settled) return;
|
|
820
|
+
settled = true;
|
|
821
|
+
cleanup();
|
|
822
|
+
resolve(value);
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const settleReject = (error) => {
|
|
826
|
+
if (settled) return;
|
|
827
|
+
settled = true;
|
|
828
|
+
cleanup();
|
|
829
|
+
reject(error);
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const onKeptSilentAccepted = (message = {}) => {
|
|
833
|
+
const keptSilentDeliveryId = normalizeOptionalText(message?.data?.keptSilentDeliveryId);
|
|
834
|
+
if (keptSilentDeliveryId !== normalizedDeliveryId) return;
|
|
835
|
+
settleResolve(message);
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const onDisconnect = (info = {}) => {
|
|
839
|
+
settleReject(createRuntimeBoundaryError({
|
|
840
|
+
code: 'relay_kept_silent_ack_disconnected',
|
|
841
|
+
category: 'transport',
|
|
842
|
+
status: 502,
|
|
843
|
+
message: `relay websocket closed before kept_silent acknowledgement for ${normalizedDeliveryId}`,
|
|
844
|
+
publicMessage: 'relay websocket closed before kept_silent acknowledgement',
|
|
845
|
+
recoverable: true,
|
|
846
|
+
context: this.buildBoundaryContext({
|
|
847
|
+
stage: 'kept_silent_ack_wait',
|
|
848
|
+
deliveryId: normalizedDeliveryId,
|
|
849
|
+
closeCode: info?.code ?? null,
|
|
850
|
+
closeReason: info?.reason || null,
|
|
851
|
+
}),
|
|
852
|
+
}));
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
this.on('kept_silent.accepted', onKeptSilentAccepted);
|
|
856
|
+
this.on('disconnect', onDisconnect);
|
|
857
|
+
this.on('close', onDisconnect);
|
|
858
|
+
|
|
859
|
+
timeout = setTimeout(() => {
|
|
860
|
+
settleReject(buildKeepSilentAckTimeoutError({
|
|
861
|
+
deliveryId: normalizedDeliveryId,
|
|
862
|
+
timeoutMs,
|
|
863
|
+
context: this.buildBoundaryContext({
|
|
864
|
+
stage: 'kept_silent_ack_wait',
|
|
865
|
+
deliveryId: normalizedDeliveryId,
|
|
866
|
+
}),
|
|
867
|
+
}));
|
|
868
|
+
}, timeoutMs);
|
|
869
|
+
if (typeof timeout.unref === 'function') timeout.unref();
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async replyToDeliveryHttp({ deliveryId, replyText, source = 'subagent' } = {}) {
|
|
874
|
+
const envelope = this.outbound.createReplyEnvelope({
|
|
875
|
+
deliveryId,
|
|
876
|
+
sessionKey: null,
|
|
877
|
+
replyText,
|
|
878
|
+
source,
|
|
879
|
+
});
|
|
880
|
+
const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(envelope.deliveryId)}/reply`, {
|
|
881
|
+
method: 'POST',
|
|
882
|
+
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
883
|
+
body: JSON.stringify({
|
|
884
|
+
fromAgentId: this.boundAgentId,
|
|
885
|
+
payload: {
|
|
886
|
+
...envelope.payload,
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
889
|
+
}, {
|
|
890
|
+
code: 'relay_reply_fallback_failed',
|
|
891
|
+
message: 'failed to submit relay reply fallback',
|
|
892
|
+
publicMessage: 'failed to submit relay reply fallback',
|
|
893
|
+
});
|
|
894
|
+
return {
|
|
895
|
+
...result,
|
|
896
|
+
envelope,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async sendReplyAndWaitForAck({
|
|
901
|
+
deliveryId,
|
|
902
|
+
sessionKey,
|
|
903
|
+
replyText,
|
|
904
|
+
source = 'subagent',
|
|
905
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
906
|
+
httpFallback = true,
|
|
907
|
+
} = {}) {
|
|
908
|
+
const envelope = this.sendReply({
|
|
909
|
+
deliveryId,
|
|
910
|
+
sessionKey,
|
|
911
|
+
replyText,
|
|
912
|
+
source,
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
try {
|
|
916
|
+
const ack = await this.waitForReplyAck({
|
|
917
|
+
deliveryId: envelope.deliveryId,
|
|
918
|
+
timeoutMs,
|
|
919
|
+
});
|
|
920
|
+
return {
|
|
921
|
+
ok: true,
|
|
922
|
+
envelope,
|
|
923
|
+
ack,
|
|
924
|
+
transport: 'websocket',
|
|
925
|
+
fallbackUsed: false,
|
|
926
|
+
};
|
|
927
|
+
} catch (error) {
|
|
928
|
+
if (!httpFallback) throw error;
|
|
929
|
+
|
|
930
|
+
this.logger.warn?.('[claworld:relay-client] reply websocket acknowledgement failed; attempting HTTP fallback', {
|
|
931
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
932
|
+
agentId: this.boundAgentId,
|
|
933
|
+
deliveryId: envelope.deliveryId,
|
|
934
|
+
sessionKey: envelope.sessionKey,
|
|
935
|
+
error: error?.message || String(error),
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
const fallbackResult = await this.replyToDeliveryHttp({
|
|
939
|
+
deliveryId: envelope.deliveryId,
|
|
940
|
+
replyText,
|
|
941
|
+
source,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
945
|
+
return {
|
|
946
|
+
ok: true,
|
|
947
|
+
envelope,
|
|
948
|
+
ack: {
|
|
949
|
+
event: 'reply.accepted',
|
|
950
|
+
data: fallbackResult.body,
|
|
951
|
+
},
|
|
952
|
+
transport: 'http',
|
|
953
|
+
fallbackUsed: true,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (isReplyAlreadyApplied(fallbackResult, envelope.deliveryId)) {
|
|
958
|
+
return {
|
|
959
|
+
ok: true,
|
|
960
|
+
envelope,
|
|
961
|
+
ack: {
|
|
962
|
+
event: 'reply.accepted',
|
|
963
|
+
data: {
|
|
964
|
+
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
965
|
+
repliedDeliveryId: envelope.deliveryId,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
transport: 'http-already-applied',
|
|
969
|
+
fallbackUsed: true,
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
throw buildReplyFallbackError({
|
|
974
|
+
deliveryId: envelope.deliveryId,
|
|
975
|
+
status: fallbackResult.status,
|
|
976
|
+
body: fallbackResult.body,
|
|
977
|
+
context: this.buildBoundaryContext({
|
|
978
|
+
stage: 'reply_fallback',
|
|
979
|
+
deliveryId: envelope.deliveryId,
|
|
980
|
+
sessionKey: envelope.sessionKey,
|
|
981
|
+
}),
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async keepDeliverySilentHttp({ deliveryId, reason = null, source = 'openclaw-autochain' } = {}) {
|
|
987
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
988
|
+
const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/kept-silent`, {
|
|
989
|
+
method: 'POST',
|
|
990
|
+
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
991
|
+
body: JSON.stringify({
|
|
992
|
+
fromAgentId: this.boundAgentId,
|
|
993
|
+
reason: normalizeOptionalText(reason) || 'no_renderable_reply',
|
|
994
|
+
source: normalizeOptionalText(source) || 'openclaw-autochain',
|
|
995
|
+
}),
|
|
996
|
+
}, {
|
|
997
|
+
code: 'relay_kept_silent_fallback_failed',
|
|
998
|
+
message: 'failed to submit relay kept_silent fallback',
|
|
999
|
+
publicMessage: 'failed to submit relay kept_silent fallback',
|
|
1000
|
+
});
|
|
1001
|
+
return {
|
|
1002
|
+
...result,
|
|
1003
|
+
envelope: {
|
|
1004
|
+
deliveryId: normalizedDeliveryId,
|
|
1005
|
+
reason: normalizeOptionalText(reason) || 'no_renderable_reply',
|
|
1006
|
+
source: normalizeOptionalText(source) || 'openclaw-autochain',
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
async sendKeepSilentAndWaitForAck({
|
|
1012
|
+
deliveryId,
|
|
1013
|
+
sessionKey,
|
|
1014
|
+
reason = null,
|
|
1015
|
+
source = 'openclaw-autochain',
|
|
1016
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1017
|
+
httpFallback = true,
|
|
1018
|
+
} = {}) {
|
|
1019
|
+
const envelope = this.sendKeepSilent({
|
|
1020
|
+
deliveryId,
|
|
1021
|
+
sessionKey,
|
|
1022
|
+
reason,
|
|
1023
|
+
source,
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
try {
|
|
1027
|
+
const ack = await this.waitForKeepSilentAck({
|
|
1028
|
+
deliveryId: envelope.deliveryId,
|
|
1029
|
+
timeoutMs,
|
|
1030
|
+
});
|
|
1031
|
+
return {
|
|
1032
|
+
ok: true,
|
|
1033
|
+
envelope,
|
|
1034
|
+
ack,
|
|
1035
|
+
transport: 'websocket',
|
|
1036
|
+
fallbackUsed: false,
|
|
1037
|
+
};
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
if (!httpFallback) throw error;
|
|
1040
|
+
|
|
1041
|
+
this.logger.warn?.('[claworld:relay-client] kept_silent websocket acknowledgement failed; attempting HTTP fallback', {
|
|
1042
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
1043
|
+
agentId: this.boundAgentId,
|
|
1044
|
+
deliveryId: envelope.deliveryId,
|
|
1045
|
+
sessionKey: envelope.sessionKey,
|
|
1046
|
+
error: error?.message || String(error),
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
const fallbackResult = await this.keepDeliverySilentHttp({
|
|
1050
|
+
deliveryId: envelope.deliveryId,
|
|
1051
|
+
reason: envelope.reason,
|
|
1052
|
+
source: envelope.source,
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1056
|
+
return {
|
|
1057
|
+
ok: true,
|
|
1058
|
+
envelope,
|
|
1059
|
+
ack: {
|
|
1060
|
+
event: 'kept_silent.accepted',
|
|
1061
|
+
data: fallbackResult.body,
|
|
1062
|
+
},
|
|
1063
|
+
transport: 'http',
|
|
1064
|
+
fallbackUsed: true,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (isDeliveryKeptSilentAlreadyApplied(fallbackResult, envelope.deliveryId)) {
|
|
1069
|
+
return {
|
|
1070
|
+
ok: true,
|
|
1071
|
+
envelope,
|
|
1072
|
+
ack: {
|
|
1073
|
+
event: 'kept_silent.accepted',
|
|
1074
|
+
data: {
|
|
1075
|
+
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1076
|
+
keptSilentDeliveryId: envelope.deliveryId,
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
transport: 'http-already-applied',
|
|
1080
|
+
fallbackUsed: true,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
throw buildKeepSilentFallbackError({
|
|
1085
|
+
deliveryId: envelope.deliveryId,
|
|
1086
|
+
status: fallbackResult.status,
|
|
1087
|
+
body: fallbackResult.body,
|
|
1088
|
+
context: this.buildBoundaryContext({
|
|
1089
|
+
stage: 'kept_silent_fallback',
|
|
1090
|
+
deliveryId: envelope.deliveryId,
|
|
1091
|
+
sessionKey: envelope.sessionKey,
|
|
1092
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
1093
|
+
}),
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
600
1098
|
async createChatRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
|
|
601
1099
|
const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
|
|
602
1100
|
method: 'GET',
|
|
@@ -618,6 +1116,9 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
618
1116
|
kickoffBrief: normalized.kickoffBrief || null,
|
|
619
1117
|
openingMessage: normalized.openingMessage || null,
|
|
620
1118
|
worldId: normalized.conversation?.worldId || null,
|
|
1119
|
+
requestContext: requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
1120
|
+
? requestContext
|
|
1121
|
+
: undefined,
|
|
621
1122
|
}),
|
|
622
1123
|
}, {
|
|
623
1124
|
code: 'relay_request_create_failed',
|
|
@@ -23,8 +23,7 @@ export function createDemoSessionBootstrap() {
|
|
|
23
23
|
'load demo world',
|
|
24
24
|
'create manual session',
|
|
25
25
|
'inject opening system message',
|
|
26
|
-
'wait for
|
|
27
|
-
'build canonical result',
|
|
26
|
+
'wait for conversation closure or manual stop',
|
|
28
27
|
],
|
|
29
28
|
status: 'planned',
|
|
30
29
|
};
|
|
@@ -76,7 +76,7 @@ function projectParticipantContextField(field = null) {
|
|
|
76
76
|
|
|
77
77
|
function projectWorldRole(worldRole, fallback = null) {
|
|
78
78
|
const normalized = normalizeText(worldRole, fallback);
|
|
79
|
-
return ['owner', '
|
|
79
|
+
return ['owner', 'member'].includes(normalized) ? normalized : fallback;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function projectWorldStats(stats = null) {
|
|
@@ -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
|
]);
|
|
8
8
|
|
|
@@ -22,6 +22,8 @@ export const CLAWORLD_WORLD_TOOL_NAMES = Object.freeze([
|
|
|
22
22
|
|
|
23
23
|
export const CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES = Object.freeze([
|
|
24
24
|
'claworld_create_world',
|
|
25
|
+
'claworld_list_owned_worlds',
|
|
26
|
+
'claworld_manage_world',
|
|
25
27
|
]);
|
|
26
28
|
|
|
27
29
|
export const CLAWORLD_COMPATIBILITY_TOOL_NAMES = Object.freeze([
|
|
@@ -35,8 +37,6 @@ export const CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES = Object.freeze([
|
|
|
35
37
|
'claworld_accept_friend_request',
|
|
36
38
|
'claworld_reject_friend_request',
|
|
37
39
|
'claworld_broadcast_world',
|
|
38
|
-
'claworld_list_owned_worlds',
|
|
39
|
-
'claworld_manage_world',
|
|
40
40
|
'claworld_resolve_agent',
|
|
41
41
|
]);
|
|
42
42
|
|
|
@@ -76,7 +76,7 @@ function normalizeWorldStats(stats = null) {
|
|
|
76
76
|
|
|
77
77
|
function normalizeWorldRole(worldRole, fallback = null) {
|
|
78
78
|
const normalized = normalizeText(worldRole, fallback);
|
|
79
|
-
return ['owner', '
|
|
79
|
+
return ['owner', 'member'].includes(normalized) ? normalized : fallback;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function normalizeManagedWorld(payload = {}) {
|
|
@@ -207,12 +207,11 @@ export async function createModeratedWorld({
|
|
|
207
207
|
const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
|
|
208
208
|
const created = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds`, {
|
|
209
209
|
method: 'POST',
|
|
210
|
-
headers: {
|
|
210
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
211
211
|
accept: 'application/json',
|
|
212
212
|
'content-type': 'application/json',
|
|
213
213
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
214
|
-
|
|
215
|
-
},
|
|
214
|
+
}),
|
|
216
215
|
body: JSON.stringify({
|
|
217
216
|
agentId: resolvedAgentId,
|
|
218
217
|
displayName,
|
|
@@ -259,11 +258,10 @@ export async function fetchOwnedWorlds({
|
|
|
259
258
|
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
260
259
|
requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
|
|
261
260
|
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
262
|
-
headers: {
|
|
261
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
263
262
|
accept: 'application/json',
|
|
264
263
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
265
|
-
|
|
266
|
-
},
|
|
264
|
+
}),
|
|
267
265
|
});
|
|
268
266
|
|
|
269
267
|
if (!result.ok) {
|
|
@@ -312,11 +310,10 @@ export async function manageModeratedWorld({
|
|
|
312
310
|
const requestUrl = new URL(`${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`);
|
|
313
311
|
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
314
312
|
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
315
|
-
headers: {
|
|
313
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
316
314
|
accept: 'application/json',
|
|
317
315
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
318
|
-
|
|
319
|
-
},
|
|
316
|
+
}),
|
|
320
317
|
});
|
|
321
318
|
|
|
322
319
|
if (!result.ok) {
|
|
@@ -334,12 +331,11 @@ export async function manageModeratedWorld({
|
|
|
334
331
|
|
|
335
332
|
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`, {
|
|
336
333
|
method: 'PATCH',
|
|
337
|
-
headers: {
|
|
334
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
338
335
|
accept: 'application/json',
|
|
339
336
|
'content-type': 'application/json',
|
|
340
337
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
341
|
-
|
|
342
|
-
},
|
|
338
|
+
}),
|
|
343
339
|
body: JSON.stringify({
|
|
344
340
|
agentId: resolvedAgentId,
|
|
345
341
|
...(changes && typeof changes === 'object' ? { changes } : {}),
|