@xfxstudio/claworld 0.1.4 → 0.2.0

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.
Files changed (55) hide show
  1. package/README.md +12 -29
  2. package/openclaw.plugin.json +9 -33
  3. package/package.json +2 -10
  4. package/skills/claworld-help/SKILL.md +86 -160
  5. package/skills/claworld-join-and-chat/SKILL.md +107 -203
  6. package/skills/claworld-manage-worlds/SKILL.md +75 -392
  7. package/src/lib/chat-request.js +347 -0
  8. package/src/lib/{accepted-chat-kickoff.js → relay/kickoff-text.js} +67 -26
  9. package/src/openclaw/index.js +0 -5
  10. package/src/openclaw/installer/cli.js +14 -16
  11. package/src/openclaw/installer/core.js +13 -14
  12. package/src/openclaw/installer/doctor.js +69 -31
  13. package/src/openclaw/installer/workspace-contract.js +33 -9
  14. package/src/openclaw/plugin/claworld-channel-plugin.js +156 -625
  15. package/src/openclaw/plugin/config-schema.js +4 -16
  16. package/src/openclaw/plugin/managed-config.js +127 -75
  17. package/src/openclaw/plugin/onboarding.js +7 -3
  18. package/src/openclaw/plugin/register.js +40 -339
  19. package/src/openclaw/plugin/relay-client.js +112 -102
  20. package/src/openclaw/protocol/relay-event-protocol.js +34 -22
  21. package/src/openclaw/runtime/canonical-result-builder.js +15 -5
  22. package/src/openclaw/runtime/demo-session-bootstrap.js +0 -4
  23. package/src/openclaw/runtime/feedback-helper.js +3 -2
  24. package/src/openclaw/runtime/inbound-session-router.js +28 -20
  25. package/src/openclaw/runtime/outbound-session-bridge.js +21 -9
  26. package/src/openclaw/runtime/product-shell-helper.js +45 -637
  27. package/src/openclaw/runtime/runtime-path.js +2 -2
  28. package/src/openclaw/runtime/system-message-orchestrator.js +1 -1
  29. package/src/openclaw/runtime/tool-contracts.js +36 -258
  30. package/src/openclaw/runtime/world-moderation-helper.js +11 -65
  31. package/src/product-shell/catalog/default-world-catalog.js +15 -33
  32. package/src/product-shell/contracts/candidate-feed.js +40 -5
  33. package/src/product-shell/contracts/chat-request-approval-policy.js +3 -3
  34. package/src/product-shell/contracts/world-manifest.js +134 -161
  35. package/src/product-shell/contracts/world-orchestration.js +55 -326
  36. package/src/product-shell/feedback/feedback-routes.js +4 -3
  37. package/src/product-shell/feedback/feedback-service.js +11 -8
  38. package/src/product-shell/index.js +6 -7
  39. package/src/product-shell/matching/matchmaking-service.js +39 -5
  40. package/src/product-shell/membership/membership-service.js +125 -147
  41. package/src/product-shell/onboarding/onboarding-service.js +2 -2
  42. package/src/product-shell/orchestration/world-conversation-orchestrator.js +30 -0
  43. package/src/product-shell/orchestration/world-conversation-text.js +231 -0
  44. package/src/product-shell/results/result-service.js +9 -3
  45. package/src/product-shell/search/search-service.js +28 -1
  46. package/src/product-shell/social/chat-request-routes.js +0 -1
  47. package/src/product-shell/social/chat-request-service.js +1 -102
  48. package/src/product-shell/worlds/world-admin-service.js +86 -277
  49. package/src/product-shell/worlds/world-authorization.js +3 -5
  50. package/src/product-shell/worlds/world-routes.js +8 -38
  51. package/src/product-shell/worlds/world-service.js +3 -3
  52. package/src/product-shell/worlds/world-text.js +77 -0
  53. package/src/lib/runtime-guidance.js +0 -457
  54. package/src/openclaw/runtime/world-session-startup.js +0 -1
  55. package/src/product-shell/orchestration/session-orchestrator.js +0 -38
@@ -5,6 +5,7 @@ import { buildRuntimeAuthHeaders } from './account-identity.js';
5
5
  import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
6
6
  import { createInboundSessionRouter } from '../runtime/inbound-session-router.js';
7
7
  import { createOutboundSessionBridge } from '../runtime/outbound-session-bridge.js';
8
+ import { normalizeChatRequestInput } from '../../lib/chat-request.js';
8
9
  import {
9
10
  buildPublicErrorPayload,
10
11
  createRuntimeBoundaryError,
@@ -33,61 +34,19 @@ function normalizeRelayWebSocketUrl(serverUrl) {
33
34
 
34
35
  function buildInboundEnvelope(message = {}) {
35
36
  const data = message.data || {};
36
- const sessionId = data.sessionId || data.requestId || null;
37
- const eventId = data.turnId || data.requestId || data.sessionId || null;
38
-
39
- switch (message.event) {
40
- case 'request.received':
41
- return {
42
- session_id: sessionId,
43
- event_id: eventId,
44
- from_agent_id: data.fromAgentId || data.fromAgent?.agentId || null,
45
- type: 'system_message',
46
- payload: { kind: 'request.received', ...data },
47
- };
48
- case 'session.created':
49
- return {
50
- session_id: sessionId,
51
- event_id: eventId,
52
- from_agent_id: data.fromAgentId || data.targetAgentId || null,
53
- type: 'system_message',
54
- payload: { kind: 'session.created', ...data },
55
- };
56
- case 'turn.deliver':
57
- return {
58
- session_id: sessionId,
59
- event_id: eventId,
60
- from_agent_id: data.fromAgentId || data.fromAgent?.agentId || null,
61
- type: 'message',
62
- payload: data.payload || {},
63
- };
64
- case 'turn.reply':
65
- return {
66
- session_id: sessionId,
67
- event_id: eventId,
68
- from_agent_id: data.fromAgentId || data.fromAgent?.agentId || null,
69
- type: 'message',
70
- payload: data.payload || {},
71
- };
72
- case 'turn.timeout':
73
- return {
74
- session_id: sessionId,
75
- event_id: eventId,
76
- from_agent_id: data.fromAgentId || null,
77
- type: 'system_message',
78
- payload: { kind: 'turn.timeout', ...data },
79
- };
80
- case 'session.terminated':
81
- return {
82
- session_id: sessionId,
83
- event_id: eventId,
84
- from_agent_id: data.actorAgentId || null,
85
- type: 'session_end',
86
- payload: { kind: 'session.terminated', ...data },
87
- };
88
- default:
89
- return null;
90
- }
37
+ if (message.event !== 'delivery') return null;
38
+ const metadata = data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)
39
+ ? { ...data.metadata }
40
+ : {};
41
+ return {
42
+ eventType: data.eventType || 'delivery',
43
+ deliveryId: data.deliveryId || null,
44
+ sessionKey: data.sessionKey || null,
45
+ payload: data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
46
+ ? { ...data.payload }
47
+ : {},
48
+ metadata,
49
+ };
91
50
  }
92
51
 
93
52
  export class ClaworldRelayClient extends EventEmitter {
@@ -118,6 +77,7 @@ export class ClaworldRelayClient extends EventEmitter {
118
77
  this.reconnectAttempts = 0;
119
78
  this.manualClose = false;
120
79
  this.lastDisconnectInfo = null;
80
+ this.acceptedChatRequests = new Map();
121
81
  }
122
82
 
123
83
  buildBoundaryContext(extra = null) {
@@ -176,9 +136,9 @@ export class ClaworldRelayClient extends EventEmitter {
176
136
  fallbackTarget: fallbackTarget || this.runtimeConfig.routing.fallbackTarget,
177
137
  });
178
138
  const runtimeEvent = {
179
- relayEvent: message.event,
139
+ eventType: inboundEnvelope.eventType,
180
140
  protocol: described,
181
- inboundEnvelope,
141
+ delivery: inboundEnvelope,
182
142
  route,
183
143
  raw: message,
184
144
  };
@@ -325,12 +285,14 @@ export class ClaworldRelayClient extends EventEmitter {
325
285
  accountId: this.runtimeConfig.accountId,
326
286
  agentId,
327
287
  clientVersion,
288
+ bridgeProtocol: this.protocol.version,
328
289
  });
329
290
  this.send({
330
291
  type: 'auth',
331
292
  agentId,
332
293
  credential,
333
294
  clientVersion,
295
+ bridgeProtocol: this.protocol.version,
334
296
  });
335
297
  });
336
298
 
@@ -517,7 +479,7 @@ export class ClaworldRelayClient extends EventEmitter {
517
479
  config,
518
480
  agentId,
519
481
  credential = null,
520
- clientVersion = 'claworld-plugin/0.1.4',
482
+ clientVersion = 'claworld-plugin/0.1.5',
521
483
  sessionTarget,
522
484
  fallbackTarget,
523
485
  } = {}) {
@@ -608,47 +570,84 @@ export class ClaworldRelayClient extends EventEmitter {
608
570
  this.send({ type: 'heartbeat' });
609
571
  }
610
572
 
611
- sendTurnReply({ sessionId, messageId = null, replyText, source = 'subagent', control = null } = {}) {
612
- const envelope = this.outbound.createReplyEnvelope({ sessionId, messageId, replyText, source, control });
573
+ sendReply({ deliveryId, sessionKey, replyText, source = 'subagent' } = {}) {
574
+ const envelope = this.outbound.createReplyEnvelope({
575
+ deliveryId,
576
+ sessionKey,
577
+ replyText,
578
+ source,
579
+ });
613
580
  this.send({
614
- type: 'turn.reply',
615
- sessionId,
581
+ type: 'reply',
582
+ deliveryId: envelope.deliveryId,
583
+ sessionKey: envelope.sessionKey,
616
584
  payload: {
617
- text: envelope.reply_text,
618
- source: envelope.source,
619
- trace: envelope.trace,
620
- ...(envelope.control ? { control: envelope.control } : {}),
585
+ ...envelope.payload,
621
586
  },
622
587
  });
623
588
  return envelope;
624
589
  }
625
590
 
626
- terminateSession({ sessionId, reason = 'client_terminate' } = {}) {
627
- this.send({ type: 'session.terminate', sessionId, reason });
591
+ replyToDelivery({ deliveryId, sessionKey, replyText, source = 'subagent' } = {}) {
592
+ return this.sendReply({
593
+ deliveryId,
594
+ sessionKey,
595
+ replyText,
596
+ source,
597
+ });
628
598
  }
629
599
 
630
- async createSessionRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
631
- return await this.requestJson('/v1/session-requests', {
600
+ async createChatRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
601
+ const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
602
+ method: 'GET',
603
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig),
604
+ }, {
605
+ code: 'relay_target_resolve_failed',
606
+ message: 'failed to resolve relay target',
607
+ publicMessage: 'failed to resolve relay target',
608
+ });
609
+ if (target.status >= 400) return target;
610
+
611
+ const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
612
+ return await this.requestJson('/v1/chat-requests', {
632
613
  method: 'POST',
633
614
  headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
634
- body: JSON.stringify({ fromAgentId, toAddress, requestContext }),
615
+ body: JSON.stringify({
616
+ fromAgentId,
617
+ targetAgentId: target.body?.agentId || target.body?.id || null,
618
+ kickoffBrief: normalized.kickoffBrief || null,
619
+ openingMessage: normalized.openingMessage || null,
620
+ worldId: normalized.conversation?.worldId || null,
621
+ }),
635
622
  }, {
636
623
  code: 'relay_request_create_failed',
637
- message: 'failed to create relay session request',
638
- publicMessage: 'failed to create relay session request',
624
+ message: 'failed to create relay chat request',
625
+ publicMessage: 'failed to create relay chat request',
639
626
  });
640
627
  }
641
628
 
642
- async acceptSessionRequest(requestId, { actorAgentId, ...options } = {}) {
643
- return await this.requestJson(`/v1/session-requests/${requestId}/accept`, {
629
+ async acceptChatRequest(requestId, { actorAgentId, ...options } = {}) {
630
+ const result = await this.requestJson(`/v1/chat-requests/${requestId}/accept`, {
644
631
  method: 'POST',
645
632
  headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
646
633
  body: JSON.stringify({ actorAgentId, ...options }),
647
634
  }, {
648
635
  code: 'relay_request_accept_failed',
649
- message: 'failed to accept relay session request',
650
- publicMessage: 'failed to accept relay session request',
636
+ message: 'failed to accept relay chat request',
637
+ publicMessage: 'failed to accept relay chat request',
651
638
  });
639
+ if (result.status < 400 && requestId) {
640
+ const kickoff = result.body?.kickoff && typeof result.body.kickoff === 'object' && !Array.isArray(result.body.kickoff)
641
+ ? { ...result.body.kickoff }
642
+ : null;
643
+ this.acceptedChatRequests.set(requestId, {
644
+ requestId,
645
+ sessionKey: kickoff?.sessionKey || null,
646
+ conversationKey: kickoff?.conversationKey || null,
647
+ kickoff,
648
+ });
649
+ }
650
+ return result;
652
651
  }
653
652
 
654
653
  async deliverMessage({ fromAgentId, toAddress, payload = {}, conversation = {} } = {}) {
@@ -663,18 +662,6 @@ export class ClaworldRelayClient extends EventEmitter {
663
662
  });
664
663
  }
665
664
 
666
- async createTurn({ sessionId, fromAgentId, payload = {} } = {}) {
667
- return await this.requestJson(`/v1/sessions/${sessionId}/turns`, {
668
- method: 'POST',
669
- headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
670
- body: JSON.stringify({ fromAgentId, payload }),
671
- }, {
672
- code: 'relay_turn_create_failed',
673
- message: 'failed to create relay turn',
674
- publicMessage: 'failed to create relay turn',
675
- });
676
- }
677
-
678
665
  waitFor(eventNameOrPredicate, timeoutMs = 8000) {
679
666
  const isPredicate = typeof eventNameOrPredicate === 'function';
680
667
  const predicate = isPredicate
@@ -700,7 +687,7 @@ export class ClaworldRelayClient extends EventEmitter {
700
687
  });
701
688
  }
702
689
 
703
- async establishSession({ fromAgentId, toAddress, requestContext = {}, openingPayload = {} } = {}) {
690
+ async establishConversation({ fromAgentId, toAddress, requestContext = {}, openingPayload = {} } = {}) {
704
691
  const normalizedRequestContext = requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
705
692
  ? { ...requestContext }
706
693
  : {};
@@ -714,32 +701,55 @@ export class ClaworldRelayClient extends EventEmitter {
714
701
  }
715
702
  }
716
703
 
717
- const requestResult = await this.createSessionRequest({
704
+ const requestResult = await this.createChatRequest({
718
705
  fromAgentId,
719
706
  toAddress,
720
707
  requestContext: normalizedRequestContext,
721
708
  });
722
709
  if (requestResult.status !== 201) {
723
- throw new Error(`failed to create session request: ${JSON.stringify(requestResult.body)}`);
710
+ throw new Error(`failed to create chat request: ${JSON.stringify(requestResult.body)}`);
724
711
  }
725
712
  const requestId = requestResult.body.requestId;
726
713
  return {
727
714
  requestId,
728
- sessionId: null,
729
- openApprovedSession: async ({ timeoutMs = 15000 } = {}) => {
730
- const acceptedEvent = await this.waitFor(
731
- (event) => event.event === 'request.updated'
732
- && event.data?.requestId === requestId
733
- && event.data?.status === 'accepted',
715
+ sessionKey: null,
716
+ conversationKey: null,
717
+ openAcceptedConversation: async ({ timeoutMs = 15000 } = {}) => {
718
+ const acceptedRequest = this.acceptedChatRequests.get(requestId) || null;
719
+ if (acceptedRequest?.kickoff || acceptedRequest?.conversationKey || acceptedRequest?.sessionKey) {
720
+ return {
721
+ requestId,
722
+ sessionKey: acceptedRequest?.sessionKey || acceptedRequest?.kickoff?.sessionKey || null,
723
+ conversationKey: acceptedRequest?.conversationKey || acceptedRequest?.kickoff?.conversationKey || null,
724
+ kickoff: acceptedRequest?.kickoff || null,
725
+ delivery: null,
726
+ };
727
+ }
728
+ const deliveryEvent = await this.waitFor(
729
+ (event) => event.event === 'delivery'
730
+ && (
731
+ event.data?.metadata?.kickoffRequestId === requestId
732
+ ),
734
733
  timeoutMs,
735
734
  );
736
- const kickoff = acceptedEvent?.data?.kickoff && typeof acceptedEvent.data.kickoff === 'object'
737
- ? acceptedEvent.data.kickoff
738
- : null;
735
+ const delivery = deliveryEvent?.data && typeof deliveryEvent.data === 'object' && !Array.isArray(deliveryEvent.data)
736
+ ? deliveryEvent.data
737
+ : {};
738
+ const metadata = delivery.metadata && typeof delivery.metadata === 'object' && !Array.isArray(delivery.metadata)
739
+ ? delivery.metadata
740
+ : {};
741
+ const kickoff = {
742
+ status: 'delivered',
743
+ deliveryId: delivery.deliveryId || null,
744
+ sessionKey: delivery.sessionKey || metadata.sessionKey || null,
745
+ conversationKey: delivery.conversationKey || null,
746
+ };
739
747
  return {
740
748
  requestId,
741
- sessionId: kickoff?.sessionId || null,
749
+ sessionKey: kickoff.sessionKey,
750
+ conversationKey: kickoff.conversationKey,
742
751
  kickoff,
752
+ delivery: deliveryEvent,
743
753
  };
744
754
  },
745
755
  };
@@ -1,31 +1,43 @@
1
- const MINIMAL_EVENT_TYPES = [
2
- 'message',
3
- 'raise_hand',
4
- 'session_end',
5
- 'session_result_ready',
6
- 'system_message',
7
- 'ack',
8
- ];
1
+ export const CLAWORLD_PLUGIN_BRIDGE_PROTOCOL = 'claworld.delivery_reply.v1';
2
+
3
+ const DELIVERY_EVENT_TYPE = 'delivery';
4
+
5
+ function normalizeText(value, fallback = null) {
6
+ if (value == null) return fallback;
7
+ const normalized = String(value).trim();
8
+ return normalized || fallback;
9
+ }
10
+
11
+ function normalizePayload(payload = null) {
12
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return {};
13
+ return { ...payload };
14
+ }
9
15
 
10
16
  export function createRelayEventProtocol() {
11
17
  return {
12
- version: 'claworld.a2a.v0.scaffold',
13
- requiredEnvelopeFields: ['event_id', 'session_id', 'type', 'payload', 'ts'],
14
- eventTypes: MINIMAL_EVENT_TYPES,
18
+ version: CLAWORLD_PLUGIN_BRIDGE_PROTOCOL,
19
+ eventTypes: [DELIVERY_EVENT_TYPE],
20
+ requiredEnvelopeFields: ['eventType', 'deliveryId', 'sessionKey', 'payload'],
15
21
  describeEvent(event = {}) {
22
+ const payload = normalizePayload(event.payload);
23
+ const missing = [];
24
+ if (normalizeText(event.eventType, null) !== DELIVERY_EVENT_TYPE) {
25
+ missing.push('eventType');
26
+ }
27
+ if (!normalizeText(event.deliveryId, null)) {
28
+ missing.push('deliveryId');
29
+ }
30
+ if (!normalizeText(event.sessionKey, null)) {
31
+ missing.push('sessionKey');
32
+ }
33
+ if (!normalizeText(payload.text, null)) {
34
+ missing.push('payload.text');
35
+ }
16
36
  return {
17
- ok: MINIMAL_EVENT_TYPES.includes(event.type),
18
- missing: this.requiredEnvelopeFields.filter((field) => event[field] == null),
19
- role: event.role || inferRoleFromType(event.type),
37
+ ok: missing.length === 0,
38
+ missing,
39
+ role: 'delivery',
20
40
  };
21
41
  },
22
42
  };
23
43
  }
24
-
25
- function inferRoleFromType(type) {
26
- if (type === 'system_message') return 'system';
27
- if (type === 'raise_hand') return 'control';
28
- if (type === 'message') return 'agent';
29
- if (type === 'session_result_ready') return 'system';
30
- return 'control';
31
- }
@@ -34,12 +34,16 @@ function buildSummary({ matchScore, riskCount, intentCount, conversationCount, a
34
34
  }
35
35
 
36
36
  function pickRecommendation({ matchScore, normalizedSignals, risks }) {
37
- const raisedHands = normalizedSignals.filter((signal) => signal.type === 'raise_hand').length;
37
+ const readySignals = normalizedSignals.filter(
38
+ (signal) => signal.type === 'next_step_ready'
39
+ || signal.type === 'human_handoff_ready'
40
+ || signal.type === 'conversation_complete',
41
+ ).length;
38
42
  const hardBlock = normalizedSignals.some(
39
43
  (signal) => signal.type === 'block' || signal.metadata?.hardBlock === true || signal.risk >= 0.85,
40
44
  );
41
45
  if (hardBlock) return 'pass';
42
- if (raisedHands >= 2 && (matchScore == null || matchScore >= 0.45) && risks.length <= 1) {
46
+ if (readySignals >= 2 && (matchScore == null || matchScore >= 0.45) && risks.length <= 1) {
43
47
  return 'continue';
44
48
  }
45
49
  if (matchScore != null && matchScore >= 0.65 && risks.length === 0) {
@@ -58,9 +62,15 @@ export function createCanonicalResultBuilder() {
58
62
  summary: 'string',
59
63
  risks: 'string[]',
60
64
  recommendation: 'continue|pass|review',
61
- evidence: 'SessionMessage[]',
65
+ evidence: 'ConversationMessage[]',
62
66
  },
63
- build({ sessionId, intentSignals = [], conversationSignals = [], agentSignals = [] } = {}) {
67
+ build({
68
+ conversationId = null,
69
+ intentSignals = [],
70
+ conversationSignals = [],
71
+ agentSignals = [],
72
+ } = {}) {
73
+ const resolvedConversationId = conversationId || null;
64
74
  const normalizedIntentSignals = intentSignals.map((signal, index) =>
65
75
  normalizeSignal(signal, index, 'intent'),
66
76
  );
@@ -92,7 +102,7 @@ export function createCanonicalResultBuilder() {
92
102
  }));
93
103
 
94
104
  return {
95
- sessionId,
105
+ conversationId: resolvedConversationId,
96
106
  match_score: matchScore,
97
107
  summary: buildSummary({
98
108
  matchScore,
@@ -4,23 +4,19 @@ export function createDemoSessionBootstrap() {
4
4
  users: ['demo-a', 'demo-b'],
5
5
  world: 'dating-demo-world',
6
6
  allowDemoToken: true,
7
- maxTurns: 6,
8
7
  },
9
8
  createLaunchPlan({
10
9
  sessionGoal = 'run one stable A2A demo loop',
11
10
  users,
12
11
  world,
13
- maxTurns,
14
12
  openingMessage = 'You are entering demo mode. Try to complete one stable match loop.',
15
13
  } = {}) {
16
14
  const resolvedUsers = Array.isArray(users) && users.length > 0 ? users : this.defaults.users;
17
15
  const resolvedWorld = world || this.defaults.world;
18
- const resolvedMaxTurns = Number.isFinite(Number(maxTurns)) ? Number(maxTurns) : this.defaults.maxTurns;
19
16
  return {
20
17
  sessionGoal,
21
18
  world: resolvedWorld,
22
19
  users: resolvedUsers,
23
- maxTurns: resolvedMaxTurns,
24
20
  openingMessage,
25
21
  steps: [
26
22
  'load demo users',
@@ -110,8 +110,9 @@ export async function submitFeedbackReport({
110
110
  reproductionSteps,
111
111
  context: {
112
112
  worldId: normalizeText(normalizedContext.worldId, null),
113
- sessionId: normalizeText(normalizedContext.sessionId, null),
114
- roundId: normalizeText(normalizedContext.roundId, null),
113
+ conversationKey: normalizeText(normalizedContext.conversationKey, null),
114
+ turnId: normalizeText(normalizedContext.turnId, null),
115
+ deliveryId: normalizeText(normalizedContext.deliveryId, null),
115
116
  targetAgentId: normalizeText(normalizedContext.targetAgentId, null),
116
117
  targetAgentCode: normalizeText(normalizedContext.targetAgentCode, null),
117
118
  tags: normalizeStringList(normalizedContext.tags),
@@ -1,35 +1,43 @@
1
1
  import { OPENCLAW_RUNTIME_PATH, createRuntimePathTrace } from './runtime-path.js';
2
2
 
3
- function normalizeSessionTarget(value, fallback) {
4
- const candidate = typeof value === 'string' ? value.trim() : '';
5
- return candidate || fallback;
3
+ function normalizeText(value, fallback = null) {
4
+ if (value == null) return fallback;
5
+ const normalized = String(value).trim();
6
+ return normalized || fallback;
7
+ }
8
+
9
+ function normalizePayload(payload = null) {
10
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return {};
11
+ return { ...payload };
6
12
  }
7
13
 
8
14
  export function createInboundSessionRouter() {
9
15
  return {
10
16
  runtimePath: OPENCLAW_RUNTIME_PATH,
11
17
  routeInboundEvent(event = {}, options = {}) {
12
- const target = normalizeSessionTarget(options.sessionTarget, 'subagent');
13
- const fallbackTarget = normalizeSessionTarget(options.fallbackTarget, 'mainagent');
14
- const route = options.route && typeof options.route === 'object' ? options.route : null;
18
+ const eventType = normalizeText(event.eventType || event.type, null);
19
+ const deliveryId = normalizeText(event.deliveryId || event.event_id || event.eventId, null);
20
+ const sessionKey = normalizeText(event.sessionKey, null);
21
+ const payload = normalizePayload(event.payload);
15
22
  return {
16
- action: 'route_inbound_event',
17
- target,
18
- fallbackTarget,
19
- sessionId: event.session_id || null,
20
- eventId: event.event_id || null,
21
- fromAgentId: event.from_agent_id || null,
22
- eventType: event.type || null,
23
- route,
24
- sessionKey: route?.sessionKey || null,
25
- accountId: route?.accountId || null,
26
- preserveFields: ['session_id', 'event_id', 'from_agent_id'],
23
+ action: 'route_delivery',
24
+ target: normalizeText(options.sessionTarget, 'mainagent'),
25
+ fallbackTarget: normalizeText(options.fallbackTarget, 'mainagent'),
26
+ eventType,
27
+ deliveryId,
28
+ sessionKey,
29
+ payload,
30
+ metadata: event.metadata && typeof event.metadata === 'object' && !Array.isArray(event.metadata)
31
+ ? { ...event.metadata }
32
+ : {},
27
33
  trace: createRuntimePathTrace({
28
- sessionId: event.session_id || null,
29
- eventId: event.event_id || null,
34
+ sessionKey,
35
+ eventId: deliveryId,
30
36
  direction: 'inbound',
31
37
  }),
32
- status: route?.sessionKey ? 'resolved' : 'unresolved',
38
+ status: eventType === 'delivery' && deliveryId && sessionKey && normalizeText(payload.text, null)
39
+ ? 'resolved'
40
+ : 'invalid',
33
41
  };
34
42
  },
35
43
  };
@@ -1,16 +1,28 @@
1
- import { createRuntimePathTrace } from './runtime-path.js';
1
+ function normalizeText(value, fallback = null) {
2
+ if (value == null) return fallback;
3
+ const normalized = String(value).trim();
4
+ return normalized || fallback;
5
+ }
2
6
 
3
7
  export function createOutboundSessionBridge() {
4
8
  return {
5
- createReplyEnvelope({ sessionId, messageId = null, replyText, source = 'subagent', control = null } = {}) {
9
+ createReplyEnvelope({
10
+ deliveryId,
11
+ sessionKey,
12
+ replyText,
13
+ source = 'subagent',
14
+ } = {}) {
15
+ const normalizedReplyText = normalizeText(replyText, '');
16
+ const normalizedDeliveryId = normalizeText(deliveryId, null);
17
+ const normalizedSessionKey = normalizeText(sessionKey, null);
6
18
  return {
7
- session_id: sessionId,
8
- message_id: messageId,
9
- reply_text: replyText,
10
- source,
11
- control: control && typeof control === 'object' && !Array.isArray(control) ? { ...control } : null,
12
- trace: createRuntimePathTrace({ sessionId, eventId: messageId, direction: 'outbound' }),
13
- status: 'scaffold_only',
19
+ eventType: 'reply',
20
+ deliveryId: normalizedDeliveryId,
21
+ sessionKey: normalizedSessionKey,
22
+ payload: {
23
+ text: normalizedReplyText,
24
+ source: normalizeText(source, 'subagent'),
25
+ },
14
26
  };
15
27
  },
16
28
  };