@pingagent/sdk 0.1.16 → 0.1.18

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.
@@ -5,15 +5,20 @@ import {
5
5
  HumanDeliveryBindingManager,
6
6
  LocalStore,
7
7
  NotificationIntentManager,
8
+ OpenClawExecutionAdapter,
8
9
  OperatorSeenStateManager,
9
10
  PingAgentClient,
11
+ SessionManager,
10
12
  TrustPolicyAuditManager,
11
13
  TrustRecommendationManager,
14
+ buildActionContract,
15
+ buildActionInboxSummary,
12
16
  buildDeliveryTimeline,
13
17
  buildProjectionPreview,
14
18
  decideContactPolicy,
15
19
  decideTaskPolicy,
16
20
  defaultTrustPolicyDoc,
21
+ deriveOpenClawAgentState,
17
22
  deriveTransportHealth,
18
23
  ensureTokenValid,
19
24
  getActiveSessionFilePath,
@@ -22,6 +27,7 @@ import {
22
27
  getTrustRecommendationActionLabel,
23
28
  listPendingDecisionViews,
24
29
  listRecentBindingsForSession,
30
+ listRuntimeAdapterDescriptors,
25
31
  loadIdentity,
26
32
  normalizeTrustPolicyDoc,
27
33
  readCurrentActiveSessionKey,
@@ -37,7 +43,7 @@ import {
37
43
  switchTransportPreference,
38
44
  updateStoredToken,
39
45
  upsertTrustPolicyRecommendation
40
- } from "./chunk-GU5W4KRD.js";
46
+ } from "./chunk-JWBNSM4N.js";
41
47
 
42
48
  // src/web-server.ts
43
49
  import * as fs from "fs";
@@ -196,7 +202,7 @@ function getHostPanelHtml() {
196
202
  <div class="profile-list" id="profileList"></div>
197
203
  <div class="nav">
198
204
  <button id="navRuntime" class="active">Runtime</button>
199
- <button id="navDecisions">Decisions</button>
205
+ <button id="navDecisions">Action Inbox</button>
200
206
  <button id="navPolicy">Policy</button>
201
207
  </div>
202
208
  <div class="link-row">
@@ -258,13 +264,13 @@ function getHostPanelHtml() {
258
264
  <section id="decisionsPanel" class="panel">
259
265
  <div class="grid two-col">
260
266
  <div class="card">
261
- <h2>Decision Inbox</h2>
262
- <div id="decisionInboxSummary" class="muted small" style="margin-bottom:12px">Loading pending decisions\u2026</div>
267
+ <h2>Action Inbox</h2>
268
+ <div id="decisionInboxSummary" class="muted small" style="margin-bottom:12px">Loading pending actions\u2026</div>
263
269
  <div class="audit-list" id="decisionInboxList"></div>
264
270
  </div>
265
271
  <div class="card">
266
- <h2>Projection Delivery</h2>
267
- <div id="projectionOutboxSummary" class="muted small" style="margin-bottom:12px">Loading projection delivery state\u2026</div>
272
+ <h2>Callback / Delivery</h2>
273
+ <div id="projectionOutboxSummary" class="muted small" style="margin-bottom:12px">Loading human-delivery state\u2026</div>
268
274
  <div class="audit-list" id="projectionOutboxList"></div>
269
275
  </div>
270
276
  </div>
@@ -281,7 +287,7 @@ function getHostPanelHtml() {
281
287
  <option value="balanced">balanced</option>
282
288
  <option value="strict">strict</option>
283
289
  </select>
284
- <div class="muted small">Balanced is the default: key conclusions, handoffs, repairs, and required decisions are pushed to the human thread; ordinary progress stays summary-first.</div>
290
+ <div class="muted small">Balanced is the default: key conclusions, handoffs, repairs, and required decisions become notification intents and are sent through bound-channel reply; ordinary progress stays summary-first.</div>
285
291
  <div class="row-actions">
286
292
  <button class="action-btn" id="saveProjectionPresetBtn">Save projection preset</button>
287
293
  </div>
@@ -573,9 +579,92 @@ function getHostPanelHtml() {
573
579
  }).join('');
574
580
  }
575
581
 
582
+ function renderActionInboxItems(items) {
583
+ if (!Array.isArray(items) || !items.length) {
584
+ return '<div class="empty">No pending actions right now.</div>';
585
+ }
586
+ return items.map(function (item) {
587
+ const actionContract = item.action_contract || {};
588
+ const badges = [
589
+ '<span class="badge">' + esc(item.status || 'pending_callback') + '</span>',
590
+ '<span class="badge">' + esc(item.risk_level || 'moderate') + '</span>',
591
+ item.overdue ? '<span class="badge alert">timed_out</span>' : '',
592
+ ].filter(Boolean).join('');
593
+ const actions = [];
594
+ if (item.source === 'decision' && item.event_id) {
595
+ actions.push('<button class="action-btn inbox-decision-btn" data-event-id="' + esc(item.event_id) + '" data-decision="approved">Approve</button>');
596
+ actions.push('<button class="danger-btn inbox-decision-btn" data-event-id="' + esc(item.event_id) + '" data-decision="rejected">Reject</button>');
597
+ }
598
+ if (item.source === 'notification_intent' && item.intent_id) {
599
+ actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="retry_intent" data-intent-id="' + esc(item.intent_id) + '">Retry</button>');
600
+ actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="re_resolve_intent_binding" data-intent-id="' + esc(item.intent_id) + '">Re-resolve</button>');
601
+ actions.push('<button class="danger-btn human-delivery-action-btn" data-action="cancel_intent" data-intent-id="' + esc(item.intent_id) + '">Cancel</button>');
602
+ }
603
+ if (item.source === 'external_escalation' && item.escalation_id) {
604
+ if (item.status === 'pending_approval') {
605
+ actions.push('<button class="action-btn openclaw-escalation-action-btn" data-action="approve" data-escalation-id="' + esc(item.escalation_id) + '">Approve</button>');
606
+ actions.push('<button class="danger-btn openclaw-escalation-action-btn" data-action="reject" data-escalation-id="' + esc(item.escalation_id) + '">Reject</button>');
607
+ } else {
608
+ actions.push('<button class="secondary-btn openclaw-escalation-action-btn" data-action="retry" data-escalation-id="' + esc(item.escalation_id) + '">Retry</button>');
609
+ }
610
+ actions.push('<button class="danger-btn openclaw-escalation-action-btn" data-action="cancel" data-escalation-id="' + esc(item.escalation_id) + '">Cancel</button>');
611
+ }
612
+ if (item.session_key) {
613
+ actions.push('<button class="secondary-btn inbox-open-detail-btn" data-session-key="' + esc(item.session_key || '') + '">Open Detail</button>');
614
+ }
615
+ const deadlineLine = item.deadline
616
+ ? '<div class="muted small" style="margin-top:8px">deadline=' + esc(fmtTs(item.deadline)) + '</div>'
617
+ : '';
618
+ const consequenceLine = item.consequence
619
+ ? '<div class="muted small" style="margin-top:8px">consequence=' + esc(item.consequence) + '</div>'
620
+ : '';
621
+ return '<div class="audit-row"><div class="top"><strong>' + esc(item.title || item.summary || '(no title)') + '</strong><div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end">' + badges + '</div></div>' +
622
+ '<div class="muted small">' + esc(item.source || 'runtime') + ' \xB7 required_action=' + esc(item.required_action || actionContract.required_action || '(review)') + (actionContract && actionContract.response_state ? ' \xB7 response_state=' + esc(actionContract.response_state) : '') + '</div>' +
623
+ '<div class="muted small" style="margin-top:8px">session=' + esc(item.session_key || '(none)') + ' \xB7 conversation=' + esc(item.conversation_id || '(none)') + '</div>' +
624
+ '<div style="margin-top:8px">' + esc(item.summary || '(no summary)') + '</div>' +
625
+ deadlineLine +
626
+ consequenceLine +
627
+ (actionContract && actionContract.callback_target
628
+ ? '<div class="muted small" style="margin-top:8px">callback_target=' + esc(actionContract.callback_target) + '</div>'
629
+ : '') +
630
+ (actionContract && actionContract.policy_scope && actionContract.policy_scope.designated_target
631
+ ? '<div class="muted small" style="margin-top:8px">designated_target=' + esc(actionContract.policy_scope.designated_target) + '</div>'
632
+ : '') +
633
+ (actions.length ? '<div class="row-actions" style="margin-top:10px">' + actions.join('') + '</div>' : '') +
634
+ '</div>';
635
+ }).join('');
636
+ }
637
+
638
+ function renderOpenClawAdapterExecution(adapter) {
639
+ if (!adapter) return '';
640
+ const escalations = Array.isArray(adapter.recent_escalations) ? adapter.recent_escalations : [];
641
+ const resumes = Array.isArray(adapter.recent_callback_resumes) ? adapter.recent_callback_resumes : [];
642
+ const escalationBlock = escalations.length
643
+ ? escalations.slice(0, 8).map(function (item) {
644
+ return '<div class="audit-row"><div class="top"><strong>Escalation #' + esc(item.id) + '</strong><span class="badge">' + esc(item.status || 'pending') + '</span></div>' +
645
+ '<div class="muted small">risk=' + esc(item.risk_level || 'moderate') + ' \xB7 token_required=' + esc(item.token_required ? 'true' : 'false') + ' \xB7 session=' + esc(item.session_key || '(none)') + '</div>' +
646
+ '<div style="margin-top:8px">' + esc(item.required_action || '(no action)') + '</div>' +
647
+ '</div>';
648
+ }).join('')
649
+ : '<div class="empty">No recent escalations.</div>';
650
+ const resumeBlock = resumes.length
651
+ ? resumes.slice(0, 8).map(function (item) {
652
+ return '<div class="audit-row"><div class="top"><strong>Callback #' + esc(item.id) + '</strong><span class="badge">' + esc(item.resume_delivery_status || 'pending') + '</span></div>' +
653
+ '<div class="muted small">response_state=' + esc(item.response_state || 'acknowledged') + ' \xB7 accepted=' + esc(item.accepted ? 'true' : 'false') + '</div>' +
654
+ (item.rejection_reason ? '<div class="muted small" style="margin-top:8px;color:#fecaca">' + esc(item.rejection_reason) + '</div>' : '') +
655
+ '</div>';
656
+ }).join('')
657
+ : '<div class="empty">No callback resumes yet.</div>';
658
+ return '<div style="margin-top:12px">' +
659
+ '<div class="label">OpenClaw Adapter Execution</div>' +
660
+ '<div class="muted small" style="margin-top:6px">pending_tokens=' + esc(adapter.token_required_pending || 0) + ' \xB7 blocked=' + esc(adapter.blocked_count || 0) + ' \xB7 failed=' + esc(adapter.failed_count || 0) + ' \xB7 expired=' + esc(adapter.expired_count || 0) + '</div>' +
661
+ '<div class="audit-list" style="margin-top:8px">' + escalationBlock + resumeBlock + '</div>' +
662
+ '</div>';
663
+ }
664
+
576
665
  function renderRecentBindings(bindings) {
577
666
  if (!Array.isArray(bindings) || !bindings.length) {
578
- return '<div class="empty">No human reply targets recorded yet. Once a person talks to OpenClaw from any channel, the explicit reply target will appear here.</div>';
667
+ return '<div class="empty">No external callback targets recorded yet. Once a person or designated target replies from any attached channel, the explicit target binding will appear here.</div>';
579
668
  }
580
669
  return bindings.map(function (binding) {
581
670
  const actions = binding.status === 'active'
@@ -634,6 +723,50 @@ function getHostPanelHtml() {
634
723
  });
635
724
  }
636
725
 
726
+ async function runOpenClawEscalationAction(action, escalationId) {
727
+ if (!action || !escalationId) return null;
728
+ if (action === 'retry') {
729
+ return api('/api/runtime-adapters/openclaw/escalations/' + encodeURIComponent(String(escalationId)) + '/retry', {
730
+ method: 'POST',
731
+ headers: { 'Content-Type': 'application/json' },
732
+ });
733
+ }
734
+ if (action === 'cancel') {
735
+ return api('/api/runtime-adapters/openclaw/escalations/' + encodeURIComponent(String(escalationId)) + '/cancel', {
736
+ method: 'POST',
737
+ headers: { 'Content-Type': 'application/json' },
738
+ });
739
+ }
740
+ if (action === 'approve' || action === 'reject') {
741
+ return api('/api/runtime-adapters/openclaw/callback-resumes', {
742
+ method: 'POST',
743
+ headers: { 'Content-Type': 'application/json' },
744
+ body: JSON.stringify({
745
+ external_escalation_id: escalationId,
746
+ response_state: action === 'approve' ? 'approved' : 'rejected',
747
+ resolution_source: 'host_panel',
748
+ message_ref: 'host_panel:' + action + ':' + String(escalationId),
749
+ }),
750
+ });
751
+ }
752
+ return null;
753
+ }
754
+
755
+ function wireOpenClawEscalationActionButtons(root) {
756
+ if (!root || !root.querySelectorAll) return;
757
+ root.querySelectorAll('.openclaw-escalation-action-btn').forEach(function (btn) {
758
+ btn.addEventListener('click', async function () {
759
+ const action = btn.getAttribute('data-action');
760
+ const escalationId = Number(btn.getAttribute('data-escalation-id'));
761
+ if (!action || !Number.isInteger(escalationId) || escalationId <= 0) return;
762
+ await runOpenClawEscalationAction(action, escalationId);
763
+ await refreshAll();
764
+ if (state.selectedSessionKey) await loadSession(state.selectedSessionKey);
765
+ setTab(state.tab || 'runtime');
766
+ });
767
+ });
768
+ }
769
+
637
770
  async function markSeen(scopeType, scopeKey) {
638
771
  return api('/api/runtime/seen', {
639
772
  method: 'POST',
@@ -866,7 +999,7 @@ function getHostPanelHtml() {
866
999
  event.approval_required
867
1000
  ? '<span class="badge">' + esc(event.approval_status === 'pending' ? 'review' : event.approval_status) + '</span>'
868
1001
  : '',
869
- event.target_human_session ? '<span class="badge">human thread</span>' : '',
1002
+ event.target_human_session ? '<span class="badge">human delivery</span>' : '',
870
1003
  ].filter(Boolean).join('');
871
1004
  const actions = event.approval_required && event.approval_status === 'pending'
872
1005
  ? '<div class="row-actions" style="margin-top:10px">' +
@@ -894,46 +1027,37 @@ function getHostPanelHtml() {
894
1027
 
895
1028
  function renderDecisionInbox() {
896
1029
  const data = state.decisions;
1030
+ const actionInbox = data && data.actionInbox ? data.actionInbox : { items: [], total: 0, pending_approval: 0, pending_callback: 0, pending_escalation: 0, blocked: 0, degraded: 0, timed_out: 0, high_risk: 0 };
897
1031
  const pendingDecisions = data && Array.isArray(data.pendingDecisions) ? data.pendingDecisions : [];
898
- const overdueDecisions = pendingDecisions.filter(function (event) { return !!event.overdue; });
899
1032
  const pendingOutbox = data && Array.isArray(data.projectionOutboxPending) ? data.projectionOutboxPending : [];
900
1033
  const failedOutbox = data && Array.isArray(data.projectionOutboxFailed) ? data.projectionOutboxFailed : [];
901
1034
  const pendingIntents = data && Array.isArray(data.notificationIntentsPending) ? data.notificationIntentsPending : [];
902
1035
  const unresolvedIntents = data && Array.isArray(data.notificationIntentsUnresolved) ? data.notificationIntentsUnresolved : [];
903
1036
  const failedIntents = data && Array.isArray(data.notificationIntentsFailed) ? data.notificationIntentsFailed : [];
904
1037
  const humanDelivery = data && data.humanDelivery ? data.humanDelivery : null;
1038
+ const openclawAdapter = data && data.openclawAdapter ? data.openclawAdapter : null;
905
1039
  const boundChannelReply = !!(humanDelivery && humanDelivery.mode === 'bound_channel_reply');
906
- document.getElementById('decisionInboxSummary').textContent = boundChannelReply
907
- ? ('pending=' + pendingDecisions.length + ' \xB7 overdue=' + overdueDecisions.length + ' \xB7 notify_pending=' + pendingIntents.length + ' \xB7 unresolved=' + unresolvedIntents.length + ' \xB7 notify_failed=' + failedIntents.length)
908
- : ('pending=' + pendingDecisions.length + ' \xB7 overdue=' + overdueDecisions.length + ' \xB7 projection_pending=' + pendingOutbox.length + ' \xB7 projection_failed=' + failedOutbox.length);
909
- document.getElementById('decisionInboxList').innerHTML = pendingDecisions.length
910
- ? pendingDecisions.map(function (event) {
911
- return '<div class="audit-row"><div class="top"><strong>' + esc(event.summary || '(no summary)') + '</strong>' +
912
- '<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end"><span class="badge">' + esc(event.severity || 'warning') + '</span>' +
913
- (event.overdue ? '<span class="badge alert">overdue</span>' : '') + '</div></div>' +
914
- '<div class="muted small">' + esc(fmtTs(event.ts_ms)) + ' \xB7 session=' + esc(event.session_key || '(none)') + ' \xB7 target=' + esc(event.target_human_session || '(none)') +
915
- (event.overdue ? ' \xB7 overdue_by=' + esc(Math.ceil((event.overdue_by_ms || 0) / 60000)) + 'm' : '') + '</div>' +
916
- '<div class="muted small" style="margin-top:8px">detail_ref=' + esc(event.conversation_id || '(none)') + '</div>' +
917
- '<div class="row-actions">' +
918
- '<button class="action-btn inbox-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="approved">Approve</button>' +
919
- '<button class="danger-btn inbox-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="rejected">Reject</button>' +
920
- '<button class="secondary-btn inbox-open-detail-btn" data-session-key="' + esc(event.session_key || '') + '">Open Detail</button>' +
921
- '</div>' +
922
- '</div>';
923
- }).join('')
924
- : '<div class="empty">No pending collaboration decisions.</div>';
1040
+ document.getElementById('decisionInboxSummary').textContent =
1041
+ 'total=' + (actionInbox.total || 0) +
1042
+ ' \xB7 approvals=' + (actionInbox.pending_approval || 0) +
1043
+ ' \xB7 callbacks=' + (actionInbox.pending_callback || 0) +
1044
+ ' \xB7 escalations=' + (actionInbox.pending_escalation || 0) +
1045
+ ' \xB7 blocked=' + (openclawAdapter ? (openclawAdapter.blocked_count || 0) : actionInbox.blocked || 0) +
1046
+ ' \xB7 timed_out=' + (actionInbox.timed_out || 0) +
1047
+ ' \xB7 high_risk=' + (actionInbox.high_risk || 0);
1048
+ document.getElementById('decisionInboxList').innerHTML = renderActionInboxItems(actionInbox.items);
925
1049
 
926
1050
  const combinedOutbox = pendingOutbox.concat(failedOutbox);
927
1051
  document.getElementById('projectionOutboxSummary').textContent = boundChannelReply
928
1052
  ? ('bound reply mode \xB7 pending=' + pendingIntents.length + ' \xB7 unresolved=' + unresolvedIntents.length + ' \xB7 failed=' + failedIntents.length)
929
1053
  : (combinedOutbox.length
930
- ? 'Human-thread projection uses a stable outbox. Failed rows stay visible until delivery recovers.'
931
- : 'No undelivered human-thread projections.');
1054
+ ? 'Legacy projection outbox remains visible for audit and compatibility. Failed rows stay visible until delivery recovers.'
1055
+ : 'No legacy projection-outbox rows need attention.');
932
1056
  document.getElementById('projectionOutboxList').innerHTML = boundChannelReply
933
1057
  ? renderHumanDeliveryIntents(
934
1058
  pendingIntents.concat(unresolvedIntents).concat(failedIntents),
935
1059
  'No pending, unresolved, or failed human-delivery intents.',
936
- )
1060
+ ) + renderOpenClawAdapterExecution(openclawAdapter)
937
1061
  : (combinedOutbox.length
938
1062
  ? combinedOutbox.map(function (entry) {
939
1063
  return '<div class="audit-row"><div class="top"><strong>' + esc(entry.summary || '(no summary)') + '</strong>' +
@@ -943,7 +1067,7 @@ function getHostPanelHtml() {
943
1067
  (entry.last_error ? '<div style="margin-top:8px">' + esc(entry.last_error) + '</div>' : '') +
944
1068
  '</div>';
945
1069
  }).join('')
946
- : '<div class="empty">No pending or failed projection deliveries.</div>');
1070
+ : '<div class="empty">No pending or failed legacy projection deliveries.</div>');
947
1071
 
948
1072
  document.querySelectorAll('.inbox-decision-btn').forEach(function (btn) {
949
1073
  btn.addEventListener('click', async function () {
@@ -971,6 +1095,7 @@ function getHostPanelHtml() {
971
1095
  });
972
1096
  });
973
1097
  wireHumanDeliveryActionButtons(document.getElementById('projectionOutboxList'));
1098
+ wireOpenClawEscalationActionButtons(document.getElementById('decisionInboxList'));
974
1099
  }
975
1100
 
976
1101
  function getOverviewSessions() {
@@ -1008,24 +1133,40 @@ function getHostPanelHtml() {
1008
1133
  if (!overview) return;
1009
1134
  syncSelectedSessionFromOverview();
1010
1135
  const ingressState = ingressStatusModel(overview);
1136
+ const agentState = overview.agentState || null;
1011
1137
  const transportHealth = overview.transportHealth || null;
1012
1138
  const sinceLastSeen = overview.sinceLastSeen || null;
1013
1139
  const humanDelivery = overview.humanDelivery || null;
1014
- const overdueDecisions = Array.isArray(overview.pendingDecisions)
1015
- ? overview.pendingDecisions.filter(function (event) { return !!event.overdue; })
1016
- : [];
1140
+ const actionInbox = overview.actionInbox || { total: 0, timed_out: 0, high_risk: 0 };
1141
+ const statusClass = agentState && (agentState.blocked || agentState.degraded)
1142
+ ? 'degraded'
1143
+ : ingressState.className;
1144
+ const statusLabel = agentState && agentState.state
1145
+ ? agentState.state
1146
+ : ingressState.label;
1147
+ const statusDetail = agentState && agentState.summary
1148
+ ? agentState.summary
1149
+ : ingressState.detail;
1017
1150
  document.getElementById('activationCard').innerHTML =
1018
1151
  '<div class="status-strip">' +
1019
1152
  '<div class="status-main">' +
1020
- '<h2>Activation</h2>' +
1021
- '<div class="status-state ' + ingressState.className + '">' + esc(ingressState.label) + '</div>' +
1022
- '<div class="muted small">' + esc(ingressState.detail) + '</div>' +
1153
+ '<h2>Agent Runtime</h2>' +
1154
+ '<div class="status-state ' + statusClass + '">' + esc(statusLabel) + '</div>' +
1155
+ '<div class="muted small">' + esc(statusDetail) + '</div>' +
1156
+ (agentState && agentState.next_action
1157
+ ? '<div class="muted small">next_action=' + esc(agentState.next_action) + '</div>'
1158
+ : '') +
1159
+ (agentState && agentState.reason
1160
+ ? '<div class="muted small">reason=' + esc(agentState.reason) + '</div>'
1161
+ : '') +
1023
1162
  '<div class="muted small">Public link: ' + esc(overview.publicSelf && overview.publicSelf.public_url ? overview.publicSelf.public_url : '(not ready yet)') + '</div>' +
1024
1163
  '<div class="summary-pills">' +
1025
- '<span class="pill">pending_decisions=' + esc((overview.pendingDecisions || []).length) + '</span>' +
1026
- '<span class="pill">overdue=' + esc(overdueDecisions.length) + '</span>' +
1027
- '<span class="pill">projection=' + esc(overview.collaborationProjection && overview.collaborationProjection.preset ? overview.collaborationProjection.preset : 'balanced') + '</span>' +
1164
+ '<span class="pill">action_inbox=' + esc(actionInbox.total || 0) + '</span>' +
1165
+ '<span class="pill">timed_out=' + esc(actionInbox.timed_out || 0) + '</span>' +
1166
+ '<span class="pill">high_risk=' + esc(actionInbox.high_risk || 0) + '</span>' +
1167
+ '<span class="pill">projection_policy=' + esc(overview.collaborationProjection && overview.collaborationProjection.preset ? overview.collaborationProjection.preset : 'balanced') + '</span>' +
1028
1168
  (humanDelivery ? '<span class="pill">human_delivery=' + esc(humanDelivery.mode || 'projection_outbox') + '</span>' : '') +
1169
+ (agentState && agentState.active_session_key ? '<span class="pill">active_session=ready</span>' : '') +
1029
1170
  '</div>' +
1030
1171
  '</div>' +
1031
1172
  '<div style="min-width:320px">' +
@@ -1053,23 +1194,29 @@ function getHostPanelHtml() {
1053
1194
  { label: 'Unread', value: overview.unreadTotal, sub: 'session-first inbox state' },
1054
1195
  { label: 'Tasks', value: overview.tasksTotal, sub: 'recent local task threads' },
1055
1196
  { label: 'Audit', value: overview.auditSummary.total_events, sub: 'policy / runtime audit events' },
1056
- { label: 'Collaboration', value: overview.collaborationSummary ? overview.collaborationSummary.total_events : 0, sub: overview.collaborationSummary ? ('pending_review=' + overview.collaborationSummary.pending_approvals) : 'projected external collaboration events' },
1057
- { label: 'Human Delivery', value: humanDelivery ? ((humanDelivery.pending_intents || 0) + '/' + (humanDelivery.active_bindings || 0)) : '-', sub: humanDelivery ? ('mode=' + (humanDelivery.mode || 'projection_outbox') + ' unresolved=' + (humanDelivery.unresolved_intents || 0) + ' failed=' + (humanDelivery.failed_intents || 0)) : 'binding-based human notification state' },
1197
+ { label: 'Action Inbox', value: actionInbox.total || 0, sub: 'high_risk=' + (actionInbox.high_risk || 0) + ' timed_out=' + (actionInbox.timed_out || 0) },
1198
+ { label: 'External Delivery', value: humanDelivery ? ((humanDelivery.pending_intents || 0) + '/' + (humanDelivery.active_bindings || 0)) : '-', sub: humanDelivery ? ('mode=' + (humanDelivery.mode || 'projection_outbox') + ' unresolved=' + (humanDelivery.unresolved_intents || 0) + ' failed=' + (humanDelivery.failed_intents || 0)) : 'binding-based callback delivery state' },
1058
1199
  { label: 'Recommendations', value: overview.recommendationSummary ? overview.recommendationSummary.total : overview.recommendations.length, sub: overview.recommendationSummary ? JSON.stringify(overview.recommendationSummary.by_status || {}) : 'learned policy suggestions' },
1059
1200
  { label: 'Public Link', value: overview.publicSelf && overview.publicSelf.public_url ? 'ready' : 'disabled', sub: overview.publicSelf && overview.publicSelf.public_url ? overview.publicSelf.public_url : 'create a hosted shareable profile link' },
1060
1201
  ];
1061
1202
  document.getElementById('statsGrid').innerHTML = stats.map(function (item) {
1062
1203
  return '<div class="card"><div class="label">' + esc(item.label) + '</div><div class="value">' + esc(item.value) + '</div><div class="muted small">' + esc(item.sub) + '</div></div>';
1063
1204
  }).join('');
1064
- document.getElementById('taskList').innerHTML = '<div class="task-row"><div class="top"><strong>Human Delivery Channels</strong><span class="badge">' + esc((humanDelivery && humanDelivery.supported_channels ? humanDelivery.supported_channels.length : 0) + '/' + (humanDelivery && humanDelivery.channel_capabilities ? humanDelivery.channel_capabilities.length : 0)) + '</span></div>' +
1065
- '<div class="muted small">Capabilities and canary state for explicit human-delivery replies.</div>' +
1205
+ document.getElementById('taskList').innerHTML = '<div class="task-row"><div class="top"><strong>External Callback Channels</strong><span class="badge">' + esc((humanDelivery && humanDelivery.supported_channels ? humanDelivery.supported_channels.length : 0) + '/' + (humanDelivery && humanDelivery.channel_capabilities ? humanDelivery.channel_capabilities.length : 0)) + '</span></div>' +
1206
+ '<div class="muted small">Capabilities and canary state for explicit external callback delivery.</div>' +
1066
1207
  '<div class="audit-list" style="margin-top:8px">' + renderHumanDeliveryCapabilities(humanDelivery) + '</div></div>';
1067
1208
 
1068
1209
  const toggleUnreadBtn = document.getElementById('toggleUnreadBtn');
1069
1210
  if (toggleUnreadBtn) toggleUnreadBtn.textContent = 'Unread only: ' + (state.showUnreadOnly ? 'on' : 'off');
1070
1211
  const sessions = getVisibleSessions();
1071
1212
  if (!sessions.length) {
1072
- document.getElementById('sessionList').innerHTML = '<div class="empty">' + (state.showUnreadOnly ? 'No unread sessions.' : 'No sessions yet.') + '</div>';
1213
+ document.getElementById('sessionList').innerHTML = '<div class="empty">' + (
1214
+ state.showUnreadOnly
1215
+ ? 'No unread sessions.'
1216
+ : (agentState && agentState.summary
1217
+ ? agentState.summary + ' Wait for inbound work or review the Action Inbox when callback, escalation, repair, or approval work appears.'
1218
+ : 'No sessions yet.')
1219
+ ) + '</div>';
1073
1220
  } else {
1074
1221
  document.getElementById('sessionList').innerHTML = sessions.map(function (session) {
1075
1222
  const active = session.session_key === state.selectedSessionKey ? ' active' : '';
@@ -1079,7 +1226,7 @@ function getHostPanelHtml() {
1079
1226
  ? '<span class="badge alert">new ' + esc(countSinceLastSeen(session.since_last_seen)) + '</span>'
1080
1227
  : ''),
1081
1228
  session.binding_alert
1082
- ? '<button type="button" class="badge alert rebind-badge-btn" data-session="' + esc(session.session_key) + '" data-conversation="' + esc(session.conversation_id || '') + '" data-bound-session="' + esc(session.mapped_work_session || '') + '" data-remote-did="' + esc(session.remote_did || '') + '" title="OpenClaw chat link needs attention">Needs reconnect</button>'
1229
+ ? '<button type="button" class="badge alert rebind-badge-btn" data-session="' + esc(session.session_key) + '" data-conversation="' + esc(session.conversation_id || '') + '" data-bound-session="' + esc(session.mapped_work_session || '') + '" data-remote-did="' + esc(session.remote_did || '') + '" title="Legacy compatibility route needs attention">Compatibility repair</button>'
1083
1230
  : '',
1084
1231
  ].filter(Boolean).join('');
1085
1232
  return '<div class="session-row' + active + '" data-session="' + esc(session.session_key) + '">' +
@@ -1087,7 +1234,7 @@ function getHostPanelHtml() {
1087
1234
  '<div class="muted small">unread=' + esc(session.unread_count) + ' \xB7 last=' + esc(fmtTs(session.last_remote_activity_at || session.updated_at)) + '</div>' +
1088
1235
  (state.detailMode === 'advanced'
1089
1236
  ? '<div class="muted small" style="margin-top:6px">conversation=' + esc(session.conversation_id || '(none)') + '</div>' +
1090
- '<div class="muted small">work_session=' + esc(session.mapped_work_session || '(unbound)') + (session.is_active_work_session ? ' \xB7 active_chat=true' : '') + '</div>'
1237
+ '<div class="muted small">compatibility_route=' + esc(session.mapped_work_session || '(unbound)') + (session.is_active_work_session ? ' \xB7 active_route=true' : '') + '</div>'
1091
1238
  : '') +
1092
1239
  '<div class="muted small" style="margin-top:6px">' + esc(session.last_message_preview || '(no preview)') + '</div>' +
1093
1240
  '</div>';
@@ -1252,8 +1399,8 @@ conversation=' + result.conversation_id));
1252
1399
  if (task && task.action) summaryPills.push('<span class="pill">task=' + esc(task.action) + '</span>');
1253
1400
  if (session.trust_state) summaryPills.push('<span class="pill">trust=' + esc(session.trust_state) + '</span>');
1254
1401
  if (Number(session.unread_count || 0) > 0) summaryPills.push('<span class="pill">unread=' + esc(session.unread_count) + '</span>');
1255
- if (binding && binding.session_key) summaryPills.push('<span class="pill">chat link attached</span>');
1256
- else if (activeWorkSession) summaryPills.push('<span class="pill">current OpenClaw chat available</span>');
1402
+ if (binding && binding.target_key) summaryPills.push('<span class="pill">human reply target ready</span>');
1403
+ if (isAdvanced && activeWorkSession) summaryPills.push('<span class="pill">compatibility route available</span>');
1257
1404
  const policyBlock = isAdvanced
1258
1405
  ? '<pre style="margin-top:8px">[Contact]\\naction=' + esc(contact.action) + '\\nsource=' + esc(contact.source) + (contact.matched_rule ? '\\nmatched_rule=' + esc(contact.matched_rule) : '') + '\\n' + esc(contact.explanation) + '\\n\\n[Task]\\naction=' + esc(task.action) + '\\nsource=' + esc(task.source) + (task.matched_rule ? '\\nmatched_rule=' + esc(task.matched_rule) : '') + '\\n' + esc(task.explanation) + '</pre>'
1259
1406
  : '<div class="summary-pills" style="margin-top:8px">' + summaryPills.join('') + '</div>' +
@@ -1271,8 +1418,8 @@ conversation=' + result.conversation_id));
1271
1418
  ? '<div style="margin-top:8px">' +
1272
1419
  '<div class="muted small">session=' + esc(session.session_key) + '</div>' +
1273
1420
  '<div class="muted small">conversation=' + esc(session.conversation_id || '(none)') + '</div>' +
1274
- '<div class="muted small">active_chat_session=' + esc(activeWorkSession || '(none)') + '</div>' +
1275
- '<div class="muted small">binding=' + esc(binding ? binding.session_key : '(unbound)') + '</div>' +
1421
+ '<div class="muted small">compatibility_active_session=' + esc(activeWorkSession || '(none)') + '</div>' +
1422
+ '<div class="muted small">reply_binding=' + esc(binding ? (binding.target_key || binding.session_key || '(bound)') : '(unbound)') + '</div>' +
1276
1423
  '</div>'
1277
1424
  : '') +
1278
1425
  (openRecommendation
@@ -1280,11 +1427,11 @@ conversation=' + result.conversation_id));
1280
1427
  : (reopenRecommendation ? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(reopenRecommendation)) + '</div>' : '')) +
1281
1428
  (summaryPills.length ? '<div class="summary-pills">' + summaryPills.join('') + '</div>' : '') +
1282
1429
  (pendingCollaborationEvents.length
1283
- ? '<div style="margin-top:10px;padding:10px 12px;border:1px solid #f59e0b;border-radius:10px;background:rgba(120,53,15,0.25);color:#fde68a"><strong>Decision pending</strong><div class="small" style="margin-top:6px">A collaboration update needs review before the human thread can be treated as fully current. Resolve it in the Collaboration Feed below.' +
1430
+ ? '<div style="margin-top:10px;padding:10px 12px;border:1px solid #f59e0b;border-radius:10px;background:rgba(120,53,15,0.25);color:#fde68a"><strong>Decision pending</strong><div class="small" style="margin-top:6px">A collaboration update needs review before this session is treated as settled. Resolve it in the Collaboration Feed below.' +
1284
1431
  (pendingCollaborationEvents[0].overdue ? ' This item is overdue.' : '') + '</div></div>'
1285
1432
  : '') +
1286
1433
  (bindingAlert
1287
- ? '<div style="margin-top:10px;padding:10px 12px;border:1px solid #ef4444;border-radius:10px;background:rgba(127,29,29,0.25);color:#fecaca"><strong>Needs reconnect</strong><div class="small" style="margin-top:6px">' + esc(isAdvanced ? (bindingAlert.message || 'Bound work session is missing. Rebind this PingAgent conversation to the current chat session.') : 'OpenClaw chat link is stale. Attach this PingAgent session to the current OpenClaw chat.') + '</div></div>'
1434
+ ? '<div style="margin-top:10px;padding:10px 12px;border:1px solid #ef4444;border-radius:10px;background:rgba(127,29,29,0.25);color:#fecaca"><strong>Compatibility route stale</strong><div class="small" style="margin-top:6px">' + esc(isAdvanced ? (bindingAlert.message || 'Legacy current-thread compatibility routing is missing. Repair it only if you intentionally depend on the compatibility path.') : 'Legacy current-thread compatibility routing is stale. Default human delivery continues to use bindings + notification intents.') + '</div></div>'
1288
1435
  : '') +
1289
1436
  '<div class="row-actions">' +
1290
1437
  (session.trust_state === 'pending'
@@ -1299,11 +1446,16 @@ conversation=' + result.conversation_id));
1299
1446
  (!openRecommendation && reopenRecommendation
1300
1447
  ? '<button class="secondary-btn reopen-session-recommendation-btn" data-session="' + esc(session.session_key) + '">Reopen</button>'
1301
1448
  : '') +
1302
- '<button class="action-btn bind-current-btn" data-conversation="' + esc(session.conversation_id || '') + '">Attach to Current Chat</button>' +
1303
1449
  '<button class="secondary-btn mark-read-btn" data-session="' + esc(session.session_key) + '">Mark read</button>' +
1304
1450
  '<button class="secondary-btn copy-session-link-btn" data-session="' + esc(session.session_key) + '">Copy Session Link</button>' +
1305
- '<button class="danger-btn clear-binding-btn" data-conversation="' + esc(session.conversation_id || '') + '">Detach Chat Link</button>' +
1451
+ (isAdvanced
1452
+ ? '<button class="action-btn bind-current-btn" data-conversation="' + esc(session.conversation_id || '') + '">Repair Compatibility Route</button>' +
1453
+ '<button class="danger-btn clear-binding-btn" data-conversation="' + esc(session.conversation_id || '') + '">Clear Compatibility Route</button>'
1454
+ : '') +
1306
1455
  '</div>' +
1456
+ (isAdvanced
1457
+ ? '<div class="muted small" style="margin-top:8px">Compatibility routing is repair-only. The default external callback path uses External Target Binding and Action / Callback Intent.</div>'
1458
+ : '') +
1307
1459
  '<div class="form-grid" style="margin-top:16px">' +
1308
1460
  '<label class="label">Reply in this session</label>' +
1309
1461
  '<textarea id="sessionReplyInput" placeholder="Send a text reply in this session"></textarea>' +
@@ -1391,9 +1543,9 @@ conversation=' + result.conversation_id));
1391
1543
  '</div>';
1392
1544
  }).join('') : '<div class="empty">No audit events for this session.</div>') +
1393
1545
  '</div></div>' +
1394
- '<div><div class="label">Human Thread Posture</div><div class="audit-list" style="margin-top:8px">' +
1546
+ '<div><div class="label">External Delivery Posture</div><div class="audit-list" style="margin-top:8px">' +
1395
1547
  '<div class="audit-row"><div class="top"><strong>Projection policy</strong><span class="badge">' + esc(projectionPreset) + '</span></div>' +
1396
- '<div class="muted small">The collaboration session keeps the raw transcript. The human thread receives concise updates, risk signals, decisions, and approval results through the projection outbox.</div>' +
1548
+ '<div class="muted small">The collaboration session keeps the raw transcript. External replies and human-visible updates now flow through External Target Binding and Action / Callback Intent. Projection outbox remains visible for audit and compatibility.</div>' +
1397
1549
  '<div style="margin-top:8px">Use this detail view to inspect every raw message, task, handoff, audit event, and runtime change before taking action.</div>' +
1398
1550
  (sinceLastSeen
1399
1551
  ? '<div class="summary-pills" style="margin-top:8px">' +
@@ -1406,7 +1558,7 @@ conversation=' + result.conversation_id));
1406
1558
  ? '<div class="muted small" style="margin-top:8px">transport=' + esc(transportHealth.transport_mode || 'bridge') + ' \xB7 state=' + esc(transportHealth.state || 'Ready') + ' \xB7 retry_queue=' + esc(transportHealth.retry_queue_length || 0) + '</div>'
1407
1559
  : '') +
1408
1560
  (humanDelivery
1409
- ? '<div class="muted small" style="margin-top:8px">human_delivery=' + esc(humanDelivery.mode || 'projection_outbox') + ' \xB7 unresolved=' + esc(humanDelivery.unresolved_intents || 0) + ' \xB7 failed=' + esc(humanDelivery.failed_intents || 0) + '</div>'
1561
+ ? '<div class="muted small" style="margin-top:8px">external_delivery=' + esc(humanDelivery.mode || 'projection_outbox') + ' \xB7 unresolved=' + esc(humanDelivery.unresolved_intents || 0) + ' \xB7 failed=' + esc(humanDelivery.failed_intents || 0) + '</div>'
1410
1562
  : '') +
1411
1563
  (projectionOutbox.length
1412
1564
  ? '<div class="muted small" style="margin-top:8px">outbox=' + esc(projectionOutbox[0].status || 'pending') + ' \xB7 target=' + esc(projectionOutbox[0].target_human_session || '(missing)') + '</div>'
@@ -1428,11 +1580,11 @@ conversation=' + result.conversation_id));
1428
1580
 
1429
1581
  el.innerHTML +=
1430
1582
  '<div class="grid two-col" style="margin-top:16px">' +
1431
- '<div><div class="label">Human Reply Targets</div><div class="audit-list" style="margin-top:8px">' +
1583
+ '<div><div class="label">External Target Bindings</div><div class="audit-list" style="margin-top:8px">' +
1432
1584
  renderRecentBindings(recentBindings) +
1433
1585
  '</div></div>' +
1434
- '<div><div class="label">Notification Intents</div><div class="audit-list" style="margin-top:8px">' +
1435
- renderHumanDeliveryIntents(recentNotificationIntents, 'No notification intents for this session yet.') +
1586
+ '<div><div class="label">Action / Callback Intents</div><div class="audit-list" style="margin-top:8px">' +
1587
+ renderHumanDeliveryIntents(recentNotificationIntents, 'No action or callback intents for this session yet.') +
1436
1588
  '</div></div>' +
1437
1589
  '</div>';
1438
1590
 
@@ -1586,7 +1738,7 @@ conversation=' + result.conversation_id));
1586
1738
  const previous = previousBinding || (state.session && state.session.binding ? state.session.binding.session_key : null) || '(unbound)';
1587
1739
  const targetRemoteDid = remoteDid || (state.session && state.session.session ? state.session.session.remote_did : null) || '(unknown)';
1588
1740
  const confirmed = window.confirm(
1589
- 'Attach this PingAgent session to the current OpenClaw chat?' +
1741
+ 'Repair the legacy current-thread compatibility route for this PingAgent session?' +
1590
1742
  '
1591
1743
 
1592
1744
  Conversation: ' + conversationId +
@@ -1594,9 +1746,9 @@ Conversation: ' + conversationId +
1594
1746
  Remote DID: ' + targetRemoteDid +
1595
1747
  '
1596
1748
 
1597
- Current OpenClaw chat: ' + (current || '(none)') +
1749
+ Current OpenClaw compatibility target: ' + (current || '(none)') +
1598
1750
  '
1599
- Previous chat link: ' + previous
1751
+ Previous compatibility route: ' + previous
1600
1752
  );
1601
1753
  if (!confirmed) return;
1602
1754
  await api('/api/runtime/session-bindings/bind-current', {
@@ -1974,6 +2126,9 @@ function resolvePath(p) {
1974
2126
  if (!p || !p.startsWith("~")) return p;
1975
2127
  return path.join(process.env.HOME || process.env.USERPROFILE || "", p.slice(1));
1976
2128
  }
2129
+ function isRecord(value) {
2130
+ return !!value && typeof value === "object" && !Array.isArray(value);
2131
+ }
1977
2132
  function findOpenClawInstallScript() {
1978
2133
  const explicit = process.env.PINGAGENT_OPENCLAW_INSTALL_BIN?.trim();
1979
2134
  if (explicit) return { kind: "script", cmd: process.execPath, args: [path.resolve(explicit)] };
@@ -2292,6 +2447,25 @@ function readHumanDeliveryState(storePath, limit = 12) {
2292
2447
  store.close();
2293
2448
  }
2294
2449
  }
2450
+ function readOpenClawAdapterState(storePath, limit = 20) {
2451
+ const store = new LocalStore(storePath);
2452
+ try {
2453
+ const adapter = new OpenClawExecutionAdapter(store);
2454
+ return adapter.buildOverview(limit);
2455
+ } finally {
2456
+ store.close();
2457
+ }
2458
+ }
2459
+ function buildOpenClawAgentStatePayload(input) {
2460
+ return deriveOpenClawAgentState({
2461
+ runtime_status: input.runtimeStatus ?? null,
2462
+ sessions: input.sessions ?? [],
2463
+ pending_decisions: Array.isArray(input.pendingDecisions) ? input.pendingDecisions.length : 0,
2464
+ human_delivery: input.humanDelivery ?? null,
2465
+ transport_health: input.transportHealth ?? null,
2466
+ blocked_reason: input.blockedReason ?? null
2467
+ });
2468
+ }
2295
2469
  function buildPolicyDecisionShape(identityPath, remoteDid, opts) {
2296
2470
  const policy = readTrustPolicyDoc(identityPath);
2297
2471
  const runtimeMode = opts?.runtimeMode ?? getRuntimeMode();
@@ -2342,9 +2516,9 @@ function formatRetentionLabel(ttlMs) {
2342
2516
  }
2343
2517
  function describeHostedTier(tier) {
2344
2518
  if (tier === "plus") return "shareable identity + higher relay + first alias";
2345
- if (tier === "pro") return "multi-identity communication + audit export";
2519
+ if (tier === "pro") return "multi-identity escalation + callback governance + audit export";
2346
2520
  if (tier === "enterprise") return "high-scale governance + operational controls";
2347
- return "free communication-first entry tier";
2521
+ return "free escalation-first entry tier";
2348
2522
  }
2349
2523
  async function buildRuntimeOverviewPayload(ctx) {
2350
2524
  const client = ctx.client;
@@ -2407,8 +2581,10 @@ async function buildRuntimeOverviewPayload(ctx) {
2407
2581
  const transportHealth = readTransportHealthState(ctx.storePath);
2408
2582
  const sinceLastSeen = readSinceLastSeenState(ctx.storePath, "host_panel", "global");
2409
2583
  const humanDelivery = readHumanDeliveryState(ctx.storePath, 20);
2584
+ const openclawAdapter = readOpenClawAdapterState(ctx.storePath, 20);
2410
2585
  const decisionStore = new LocalStore(ctx.storePath);
2411
2586
  let decisionViews = [];
2587
+ let actionInbox = null;
2412
2588
  let humanDeliveryDetails = null;
2413
2589
  try {
2414
2590
  decisionViews = listPendingDecisionViews(decisionStore, 100);
@@ -2423,6 +2599,21 @@ async function buildRuntimeOverviewPayload(ctx) {
2423
2599
  } finally {
2424
2600
  decisionStore.close();
2425
2601
  }
2602
+ const agentState = buildOpenClawAgentStatePayload({
2603
+ runtimeStatus: ingressRuntime,
2604
+ sessions,
2605
+ pendingDecisions: decisionViews,
2606
+ humanDelivery,
2607
+ transportHealth
2608
+ });
2609
+ actionInbox = buildActionInboxSummary({
2610
+ pending_decisions: decisionViews,
2611
+ notification_intents: humanDelivery?.recent_notification_intents ?? [],
2612
+ external_escalations: openclawAdapter.recent_escalations,
2613
+ agent_state: agentState,
2614
+ include_runtime: true,
2615
+ limit: 30
2616
+ });
2426
2617
  return {
2427
2618
  did: ctx.myDid,
2428
2619
  serverUrl: ctx.serverUrl,
@@ -2461,8 +2652,11 @@ async function buildRuntimeOverviewPayload(ctx) {
2461
2652
  collaborationSummary,
2462
2653
  projectionOutbox,
2463
2654
  transportHealth,
2655
+ agentState,
2656
+ actionInbox,
2464
2657
  sinceLastSeen,
2465
2658
  humanDelivery,
2659
+ openclawAdapter,
2466
2660
  humanDeliveryDetails,
2467
2661
  pendingDecisions: decisionViews,
2468
2662
  recentCollaborationEvents: collaborationEventManager.listRecent(20),
@@ -2538,6 +2732,8 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2538
2732
  let projectionPreview = null;
2539
2733
  let pendingDecisionViews = [];
2540
2734
  let humanDelivery = null;
2735
+ let actionInbox = null;
2736
+ let openclawAdapter = null;
2541
2737
  try {
2542
2738
  projectionOutbox = new CollaborationProjectionOutboxManager(outboxStore).listBySession(session.session_key, 20);
2543
2739
  notificationIntents = new NotificationIntentManager(outboxStore).listBySession(session.session_key, 20);
@@ -2556,9 +2752,25 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2556
2752
  );
2557
2753
  pendingDecisionViews = listPendingDecisionViews(outboxStore, 100).filter((event) => event.session_key === session.session_key).slice(0, 20);
2558
2754
  humanDelivery = summarizeHumanDelivery(outboxStore, 20);
2755
+ openclawAdapter = new OpenClawExecutionAdapter(outboxStore).buildOverview(50);
2559
2756
  } finally {
2560
2757
  outboxStore.close();
2561
2758
  }
2759
+ const agentState = buildOpenClawAgentStatePayload({
2760
+ runtimeStatus: readIngressRuntimeStatus(),
2761
+ sessions: sessionManager.listRecentSessions(50),
2762
+ pendingDecisions: pendingDecisionViews,
2763
+ humanDelivery,
2764
+ transportHealth: readTransportHealthState(ctx.storePath)
2765
+ });
2766
+ actionInbox = buildActionInboxSummary({
2767
+ pending_decisions: pendingDecisionViews,
2768
+ notification_intents: notificationIntents,
2769
+ external_escalations: (openclawAdapter?.recent_escalations ?? []).filter((item) => item.session_key === session.session_key),
2770
+ agent_state: agentState,
2771
+ include_runtime: false,
2772
+ limit: 20
2773
+ });
2562
2774
  return {
2563
2775
  session,
2564
2776
  sessionSummary: sessionSummaryManager.get(session.session_key),
@@ -2567,6 +2779,7 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2567
2779
  collaborationSummary: collaborationEventManager.summarize(200),
2568
2780
  ingressRuntime: readIngressRuntimeStatus(),
2569
2781
  transportHealth: readTransportHealthState(ctx.storePath),
2782
+ agentState,
2570
2783
  binding,
2571
2784
  bindingAlert,
2572
2785
  activeWorkSession,
@@ -2581,6 +2794,15 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2581
2794
  deliveryTimeline,
2582
2795
  projectionPreview,
2583
2796
  humanDelivery,
2797
+ openclawAdapter: openclawAdapter ? {
2798
+ ...openclawAdapter,
2799
+ recent_escalations: openclawAdapter.recent_escalations.filter((item) => item.session_key === session.session_key),
2800
+ recent_callback_resumes: openclawAdapter.recent_callback_resumes.filter((item) => {
2801
+ const payloadSessionKey = typeof item.detail?.session_key === "string" ? item.detail.session_key : null;
2802
+ return payloadSessionKey === session.session_key;
2803
+ })
2804
+ } : null,
2805
+ actionInbox,
2584
2806
  policyExplain: buildPolicyDecisionShape(ctx.identityPath, session.remote_did, { runtimeMode: getRuntimeMode() }),
2585
2807
  tasks: tasks.map((task) => ({
2586
2808
  ...task,
@@ -2829,14 +3051,32 @@ async function handleApi(pathname, req, ctx) {
2829
3051
  if (req.method === "GET") {
2830
3052
  const pendingDecisions = listPendingDecisionViews(store, 100);
2831
3053
  const intentManager = new NotificationIntentManager(store);
3054
+ const humanDelivery = summarizeHumanDelivery(store, 20);
3055
+ const openclawAdapter = new OpenClawExecutionAdapter(store).buildOverview(40);
3056
+ const actionInbox = buildActionInboxSummary({
3057
+ pending_decisions: pendingDecisions,
3058
+ notification_intents: humanDelivery.recent_notification_intents,
3059
+ external_escalations: openclawAdapter.recent_escalations,
3060
+ agent_state: buildOpenClawAgentStatePayload({
3061
+ runtimeStatus: readIngressRuntimeStatus(),
3062
+ sessions: new SessionManager(store).listRecentSessions(50),
3063
+ pendingDecisions,
3064
+ humanDelivery,
3065
+ transportHealth: readTransportHealthState(ctx.storePath)
3066
+ }),
3067
+ include_runtime: true,
3068
+ limit: 40
3069
+ });
2832
3070
  return {
2833
3071
  pendingDecisions,
3072
+ actionInbox,
2834
3073
  projectionOutboxPending: outboxManager.listByStatus("pending", 50),
2835
3074
  projectionOutboxFailed: outboxManager.listByStatus("failed", 50),
2836
3075
  notificationIntentsPending: intentManager.listByStatus("pending", 50).concat(intentManager.listByStatus("bound", 50)),
2837
3076
  notificationIntentsUnresolved: intentManager.listByStatus("unresolved", 50),
2838
3077
  notificationIntentsFailed: intentManager.listByStatus("failed", 50),
2839
- humanDelivery: summarizeHumanDelivery(store, 20)
3078
+ humanDelivery,
3079
+ openclawAdapter
2840
3080
  };
2841
3081
  }
2842
3082
  if (parts[2] === "resolve" && req.method === "POST") {
@@ -3350,6 +3590,96 @@ async function handleApi(pathname, req, ctx) {
3350
3590
  }
3351
3591
  }
3352
3592
  }
3593
+ if (parts[0] === "runtime-adapters") {
3594
+ if (req.method === "GET" && (!parts[1] || parts[1] === "overview")) {
3595
+ const openclaw = readOpenClawAdapterState(ctx.storePath, 20);
3596
+ return {
3597
+ adapters: listRuntimeAdapterDescriptors().map((adapter) => ({
3598
+ ...adapter,
3599
+ health: adapter.id === "openclaw" ? openclaw.health : null
3600
+ })),
3601
+ openclaw
3602
+ };
3603
+ }
3604
+ if (parts[1] === "openclaw") {
3605
+ if ((parts[2] === "overview" || !parts[2]) && req.method === "GET") {
3606
+ return readOpenClawAdapterState(ctx.storePath, 20);
3607
+ }
3608
+ const store = new LocalStore(ctx.storePath);
3609
+ try {
3610
+ const adapter = new OpenClawExecutionAdapter(store);
3611
+ if (parts[2] === "escalations" && req.method === "POST" && !parts[3]) {
3612
+ const body = await readBody(req);
3613
+ const actionContract = buildActionContract({
3614
+ body: isRecord(body?.action_contract) ? body.action_contract : body
3615
+ });
3616
+ const created = adapter.enqueueEscalation({
3617
+ run_id: typeof body?.run_id === "string" ? body.run_id : void 0,
3618
+ workflow_id: typeof body?.workflow_id === "string" ? body.workflow_id : void 0,
3619
+ session_key: String(body?.session_key ?? "").trim(),
3620
+ conversation_id: typeof body?.conversation_id === "string" ? body.conversation_id : void 0,
3621
+ source_event_id: Number.isInteger(body?.source_event_id) ? Number(body.source_event_id) : void 0,
3622
+ source_intent_id: Number.isInteger(body?.source_intent_id) ? Number(body.source_intent_id) : void 0,
3623
+ action_contract: actionContract,
3624
+ callback_target: typeof body?.callback_target === "string" ? body.callback_target : void 0,
3625
+ metadata: isRecord(body?.metadata) ? body.metadata : void 0
3626
+ });
3627
+ const dispatched = adapter.dispatchEscalation(created.escalation.id);
3628
+ return {
3629
+ ok: true,
3630
+ ...created,
3631
+ escalation: dispatched.escalation,
3632
+ notification_intent: dispatched.intent ?? created.notification_intent,
3633
+ dispatch: dispatched,
3634
+ overview: adapter.buildOverview(20)
3635
+ };
3636
+ }
3637
+ if (parts[2] === "callback-resumes" && req.method === "POST") {
3638
+ const body = await readBody(req);
3639
+ const resolutionSource = String(body?.resolution_source ?? "external").trim();
3640
+ const result = await adapter.resumeCallback({
3641
+ external_escalation_id: Number.isInteger(body?.external_escalation_id) ? Number(body.external_escalation_id) : void 0,
3642
+ run_id: typeof body?.run_id === "string" ? body.run_id : void 0,
3643
+ workflow_id: typeof body?.workflow_id === "string" ? body.workflow_id : void 0,
3644
+ response_state: String(body?.response_state ?? "acknowledged"),
3645
+ response_payload: isRecord(body?.response_payload) ? body.response_payload : void 0,
3646
+ message_ref: typeof body?.message_ref === "string" ? body.message_ref : void 0,
3647
+ callback_target: typeof body?.callback_target === "string" ? body.callback_target : void 0,
3648
+ capability_token: typeof body?.capability_token === "string" ? body.capability_token : void 0,
3649
+ binding_id: Number.isInteger(body?.binding_id) ? Number(body.binding_id) : void 0,
3650
+ trusted_local: resolutionSource === "host_panel" || resolutionSource === "mcp"
3651
+ });
3652
+ return {
3653
+ ok: true,
3654
+ ...result,
3655
+ overview: adapter.buildOverview(20)
3656
+ };
3657
+ }
3658
+ if (parts[2] === "escalations" && parts[3] && parts[4] === "retry" && req.method === "POST") {
3659
+ const escalationId = Number(parts[3]);
3660
+ if (!Number.isInteger(escalationId) || escalationId <= 0) throw new Error("valid escalation id is required");
3661
+ const retried = adapter.retryEscalation(escalationId);
3662
+ return {
3663
+ ok: true,
3664
+ ...retried,
3665
+ overview: adapter.buildOverview(20)
3666
+ };
3667
+ }
3668
+ if (parts[2] === "escalations" && parts[3] && parts[4] === "cancel" && req.method === "POST") {
3669
+ const escalationId = Number(parts[3]);
3670
+ if (!Number.isInteger(escalationId) || escalationId <= 0) throw new Error("valid escalation id is required");
3671
+ const canceled = adapter.cancelEscalation(escalationId);
3672
+ return {
3673
+ ok: true,
3674
+ escalation: canceled,
3675
+ overview: adapter.buildOverview(20)
3676
+ };
3677
+ }
3678
+ } finally {
3679
+ store.close();
3680
+ }
3681
+ }
3682
+ }
3353
3683
  if (parts[0] === "public") {
3354
3684
  if ((!parts[1] || parts[1] === "self") && req.method === "GET") {
3355
3685
  await maybeEnsureHostedPublicLink(ctx);