@xfxstudio/claworld 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/openclaw/installer/core.js +16 -2
- package/src/openclaw/installer/doctor.js +63 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +189 -18
- package/src/openclaw/plugin/config-schema.js +9 -4
- package/src/openclaw/plugin/managed-config.js +110 -0
- package/src/openclaw/plugin/register.js +187 -61
- package/src/openclaw/plugin/relay-client.js +511 -0
- package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
- package/src/openclaw/runtime/tool-contracts.js +1 -1
- package/src/openclaw/runtime/tool-inventory.js +2 -1
- package/src/openclaw/runtime/world-moderation-helper.js +10 -12
- package/src/product-shell/catalog/default-world-catalog.js +3 -3
- package/src/product-shell/contracts/world-manifest.js +0 -24
- package/src/product-shell/contracts/world-orchestration.js +0 -4
- package/src/product-shell/index.js +0 -5
- 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-service.js +1 -0
- package/src/product-shell/worlds/world-admin-service.js +52 -32
- package/src/product-shell/worlds/world-authorization.js +15 -1
- package/src/product-shell/worlds/world-routes.js +1 -0
- 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,
|
|
@@ -597,6 +682,420 @@ 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
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
982
|
+
}),
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
async keepDeliverySilentHttp({ deliveryId, reason = null, source = 'openclaw-autochain' } = {}) {
|
|
988
|
+
const normalizedDeliveryId = normalizeOptionalText(deliveryId);
|
|
989
|
+
const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/kept-silent`, {
|
|
990
|
+
method: 'POST',
|
|
991
|
+
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
992
|
+
body: JSON.stringify({
|
|
993
|
+
fromAgentId: this.boundAgentId,
|
|
994
|
+
reason: normalizeOptionalText(reason) || 'no_renderable_reply',
|
|
995
|
+
source: normalizeOptionalText(source) || 'openclaw-autochain',
|
|
996
|
+
}),
|
|
997
|
+
}, {
|
|
998
|
+
code: 'relay_kept_silent_fallback_failed',
|
|
999
|
+
message: 'failed to submit relay kept_silent fallback',
|
|
1000
|
+
publicMessage: 'failed to submit relay kept_silent fallback',
|
|
1001
|
+
});
|
|
1002
|
+
return {
|
|
1003
|
+
...result,
|
|
1004
|
+
envelope: {
|
|
1005
|
+
deliveryId: normalizedDeliveryId,
|
|
1006
|
+
reason: normalizeOptionalText(reason) || 'no_renderable_reply',
|
|
1007
|
+
source: normalizeOptionalText(source) || 'openclaw-autochain',
|
|
1008
|
+
},
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
async sendKeepSilentAndWaitForAck({
|
|
1013
|
+
deliveryId,
|
|
1014
|
+
sessionKey,
|
|
1015
|
+
reason = null,
|
|
1016
|
+
source = 'openclaw-autochain',
|
|
1017
|
+
timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
|
|
1018
|
+
httpFallback = true,
|
|
1019
|
+
} = {}) {
|
|
1020
|
+
const envelope = this.sendKeepSilent({
|
|
1021
|
+
deliveryId,
|
|
1022
|
+
sessionKey,
|
|
1023
|
+
reason,
|
|
1024
|
+
source,
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
try {
|
|
1028
|
+
const ack = await this.waitForKeepSilentAck({
|
|
1029
|
+
deliveryId: envelope.deliveryId,
|
|
1030
|
+
timeoutMs,
|
|
1031
|
+
});
|
|
1032
|
+
return {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
envelope,
|
|
1035
|
+
ack,
|
|
1036
|
+
transport: 'websocket',
|
|
1037
|
+
fallbackUsed: false,
|
|
1038
|
+
};
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
if (!httpFallback) throw error;
|
|
1041
|
+
|
|
1042
|
+
this.logger.warn?.('[claworld:relay-client] kept_silent websocket acknowledgement failed; attempting HTTP fallback', {
|
|
1043
|
+
accountId: this.runtimeConfig?.accountId || null,
|
|
1044
|
+
agentId: this.boundAgentId,
|
|
1045
|
+
deliveryId: envelope.deliveryId,
|
|
1046
|
+
sessionKey: envelope.sessionKey,
|
|
1047
|
+
error: error?.message || String(error),
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const fallbackResult = await this.keepDeliverySilentHttp({
|
|
1051
|
+
deliveryId: envelope.deliveryId,
|
|
1052
|
+
reason: envelope.reason,
|
|
1053
|
+
source: envelope.source,
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
|
|
1057
|
+
return {
|
|
1058
|
+
ok: true,
|
|
1059
|
+
envelope,
|
|
1060
|
+
ack: {
|
|
1061
|
+
event: 'kept_silent.accepted',
|
|
1062
|
+
data: fallbackResult.body,
|
|
1063
|
+
},
|
|
1064
|
+
transport: 'http',
|
|
1065
|
+
fallbackUsed: true,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (isDeliveryKeptSilentAlreadyApplied(fallbackResult, envelope.deliveryId)) {
|
|
1070
|
+
return {
|
|
1071
|
+
ok: true,
|
|
1072
|
+
envelope,
|
|
1073
|
+
ack: {
|
|
1074
|
+
event: 'kept_silent.accepted',
|
|
1075
|
+
data: {
|
|
1076
|
+
...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
|
|
1077
|
+
keptSilentDeliveryId: envelope.deliveryId,
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
transport: 'http-already-applied',
|
|
1081
|
+
fallbackUsed: true,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
throw buildKeepSilentFallbackError({
|
|
1086
|
+
deliveryId: envelope.deliveryId,
|
|
1087
|
+
status: fallbackResult.status,
|
|
1088
|
+
body: fallbackResult.body,
|
|
1089
|
+
context: this.buildBoundaryContext({
|
|
1090
|
+
stage: 'kept_silent_fallback',
|
|
1091
|
+
deliveryId: envelope.deliveryId,
|
|
1092
|
+
sessionKey: envelope.sessionKey,
|
|
1093
|
+
fallbackFrom: error?.code || error?.message || null,
|
|
1094
|
+
}),
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
600
1099
|
async createChatRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
|
|
601
1100
|
const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
|
|
602
1101
|
method: 'GET',
|
|
@@ -650,6 +1149,18 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
650
1149
|
return result;
|
|
651
1150
|
}
|
|
652
1151
|
|
|
1152
|
+
async rejectChatRequest(requestId, { actorAgentId, ...options } = {}) {
|
|
1153
|
+
return await this.requestJson(`/v1/chat-requests/${requestId}/reject`, {
|
|
1154
|
+
method: 'POST',
|
|
1155
|
+
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
1156
|
+
body: JSON.stringify({ actorAgentId, ...options }),
|
|
1157
|
+
}, {
|
|
1158
|
+
code: 'relay_request_reject_failed',
|
|
1159
|
+
message: 'failed to reject relay chat request',
|
|
1160
|
+
publicMessage: 'failed to reject relay chat request',
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
653
1164
|
async deliverMessage({ fromAgentId, toAddress, payload = {}, conversation = {} } = {}) {
|
|
654
1165
|
return await this.requestJson('/v1/messages', {
|
|
655
1166
|
method: 'POST',
|
|
@@ -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
|
};
|
|
@@ -391,7 +391,7 @@ export function projectToolOwnedWorldsResponse(payload = {}, { accountId = null
|
|
|
391
391
|
? payload.items.map((world) => ({
|
|
392
392
|
worldId: world.worldId,
|
|
393
393
|
displayName: world.displayName,
|
|
394
|
-
|
|
394
|
+
worldContextText: normalizeText(world.worldContextText, null),
|
|
395
395
|
enabled: normalizeOptionalBoolean(world.enabled, null),
|
|
396
396
|
status: normalizeText(world.status, null),
|
|
397
397
|
worldRole: projectWorldRole(world.worldRole, null),
|
|
@@ -4,6 +4,7 @@ export const CLAWORLD_CHAT_REQUEST_TOOL_NAMES = Object.freeze([
|
|
|
4
4
|
'claworld_request_chat',
|
|
5
5
|
'claworld_list_chat_requests',
|
|
6
6
|
'claworld_accept_chat_request',
|
|
7
|
+
'claworld_reject_chat_request',
|
|
7
8
|
]);
|
|
8
9
|
|
|
9
10
|
export const CLAWORLD_BOOTSTRAP_TOOL_NAMES = Object.freeze([
|
|
@@ -22,11 +23,11 @@ export const CLAWORLD_WORLD_TOOL_NAMES = Object.freeze([
|
|
|
22
23
|
|
|
23
24
|
export const CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES = Object.freeze([
|
|
24
25
|
'claworld_create_world',
|
|
25
|
-
'claworld_list_owned_worlds',
|
|
26
26
|
'claworld_manage_world',
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
29
|
export const CLAWORLD_COMPATIBILITY_TOOL_NAMES = Object.freeze([
|
|
30
|
+
'claworld_list_owned_worlds',
|
|
30
31
|
'claworld_prepare_world_join',
|
|
31
32
|
'claworld_search_world',
|
|
32
33
|
]);
|
|
@@ -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) {
|
|
@@ -289,6 +287,7 @@ export async function manageModeratedWorld({
|
|
|
289
287
|
mode = 'get',
|
|
290
288
|
changes = null,
|
|
291
289
|
enabled = null,
|
|
290
|
+
status = null,
|
|
292
291
|
fetchImpl,
|
|
293
292
|
logger = console,
|
|
294
293
|
} = {}) {
|
|
@@ -312,11 +311,10 @@ export async function manageModeratedWorld({
|
|
|
312
311
|
const requestUrl = new URL(`${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`);
|
|
313
312
|
requestUrl.searchParams.set('agentId', resolvedAgentId);
|
|
314
313
|
const result = await fetchJson(fetchImpl, requestUrl.toString(), {
|
|
315
|
-
headers: {
|
|
314
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
316
315
|
accept: 'application/json',
|
|
317
316
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
318
|
-
|
|
319
|
-
},
|
|
317
|
+
}),
|
|
320
318
|
});
|
|
321
319
|
|
|
322
320
|
if (!result.ok) {
|
|
@@ -334,16 +332,16 @@ export async function manageModeratedWorld({
|
|
|
334
332
|
|
|
335
333
|
const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`, {
|
|
336
334
|
method: 'PATCH',
|
|
337
|
-
headers: {
|
|
335
|
+
headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
|
|
338
336
|
accept: 'application/json',
|
|
339
337
|
'content-type': 'application/json',
|
|
340
338
|
...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
|
|
341
|
-
|
|
342
|
-
},
|
|
339
|
+
}),
|
|
343
340
|
body: JSON.stringify({
|
|
344
341
|
agentId: resolvedAgentId,
|
|
345
342
|
...(changes && typeof changes === 'object' ? { changes } : {}),
|
|
346
343
|
...(enabled == null ? {} : { enabled }),
|
|
344
|
+
...(normalizeText(status, null) ? { status: normalizeText(status, null) } : {}),
|
|
347
345
|
}),
|
|
348
346
|
});
|
|
349
347
|
|
|
@@ -9,7 +9,7 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
9
9
|
lifecycle: 'prototype',
|
|
10
10
|
tags: ['dating', 'matching', 'a2a'],
|
|
11
11
|
worldContextText:
|
|
12
|
-
'世界:Dating Demo World [dating-demo-world]\n简介:A lightweight social world for proving world-scoped join, review, and chat request flow.\n互动规则:Clarify fit quickly, stay respectful, and stop once next steps are clear.\n禁止事项:Do not pressure, harass, manipulate, or ask for unsafe personal details
|
|
12
|
+
'世界:Dating Demo World [dating-demo-world]\n简介:A lightweight social world for proving world-scoped join, review, and chat request flow.\n互动规则:Clarify fit quickly, stay respectful, and stop once next steps are clear.\n禁止事项:Do not pressure, harass, manipulate, or ask for unsafe personal details.',
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
worldId: 'skill-handoff-world',
|
|
@@ -21,7 +21,7 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
21
21
|
lifecycle: 'prototype',
|
|
22
22
|
tags: ['skills', 'services', 'matching'],
|
|
23
23
|
worldContextText:
|
|
24
|
-
'世界:Skill Handoff World [skill-handoff-world]\n简介:A service marketplace world where agents screen delivery fit before moving to direct human contact.\n互动规则:Clarify scope, constraints, and whether a handoff is justified.\n禁止事项:Do not misrepresent capabilities or hide delivery blockers
|
|
24
|
+
'世界:Skill Handoff World [skill-handoff-world]\n简介:A service marketplace world where agents screen delivery fit before moving to direct human contact.\n互动规则:Clarify scope, constraints, and whether a handoff is justified.\n禁止事项:Do not misrepresent capabilities or hide delivery blockers.',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
worldId: 'job-match-world',
|
|
@@ -33,6 +33,6 @@ export const DEFAULT_WORLD_MANIFESTS = Object.freeze([
|
|
|
33
33
|
lifecycle: 'prototype',
|
|
34
34
|
tags: ['jobs', 'recruiting', 'screening'],
|
|
35
35
|
worldContextText:
|
|
36
|
-
'世界:Job Match World [job-match-world]\n简介:A recruiting world for agent-assisted screening before direct human contact.\n互动规则:Focus on fit, constraints, and whether a next step is justified.\n禁止事项:Do not fabricate experience, compensation, or hiring authority
|
|
36
|
+
'世界:Job Match World [job-match-world]\n简介:A recruiting world for agent-assisted screening before direct human contact.\n互动规则:Focus on fit, constraints, and whether a next step is justified.\n禁止事项:Do not fabricate experience, compensation, or hiring authority.',
|
|
37
37
|
},
|
|
38
38
|
]);
|