@pingagent/sdk 0.1.15 → 0.1.16
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/bin/pingagent.js +45 -0
- package/dist/chunk-GU5W4KRD.js +5847 -0
- package/dist/chunk-HSVC3KDT.js +5611 -0
- package/dist/index.d.ts +234 -1
- package/dist/index.js +31 -1
- package/dist/web-server.js +234 -19
- package/package.json +1 -1
package/dist/web-server.js
CHANGED
|
@@ -2,7 +2,9 @@ import {
|
|
|
2
2
|
CollaborationEventManager,
|
|
3
3
|
CollaborationProjectionOutboxManager,
|
|
4
4
|
ContactManager,
|
|
5
|
+
HumanDeliveryBindingManager,
|
|
5
6
|
LocalStore,
|
|
7
|
+
NotificationIntentManager,
|
|
6
8
|
OperatorSeenStateManager,
|
|
7
9
|
PingAgentClient,
|
|
8
10
|
TrustPolicyAuditManager,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
getSessionMapFilePath,
|
|
20
22
|
getTrustRecommendationActionLabel,
|
|
21
23
|
listPendingDecisionViews,
|
|
24
|
+
listRecentBindingsForSession,
|
|
22
25
|
loadIdentity,
|
|
23
26
|
normalizeTrustPolicyDoc,
|
|
24
27
|
readCurrentActiveSessionKey,
|
|
@@ -28,12 +31,13 @@ import {
|
|
|
28
31
|
readTransportPreference,
|
|
29
32
|
removeSessionBinding,
|
|
30
33
|
setSessionBinding,
|
|
34
|
+
summarizeHumanDelivery,
|
|
31
35
|
summarizeSinceLastSeen,
|
|
32
36
|
summarizeTrustPolicyAudit,
|
|
33
37
|
switchTransportPreference,
|
|
34
38
|
updateStoredToken,
|
|
35
39
|
upsertTrustPolicyRecommendation
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-GU5W4KRD.js";
|
|
37
41
|
|
|
38
42
|
// src/web-server.ts
|
|
39
43
|
import * as fs from "fs";
|
|
@@ -540,6 +544,96 @@ function getHostPanelHtml() {
|
|
|
540
544
|
}).join('');
|
|
541
545
|
}
|
|
542
546
|
|
|
547
|
+
function renderHumanDeliveryIntents(intents, emptyLabel) {
|
|
548
|
+
if (!Array.isArray(intents) || !intents.length) {
|
|
549
|
+
return '<div class="empty">' + esc(emptyLabel || 'No notification intents yet.') + '</div>';
|
|
550
|
+
}
|
|
551
|
+
return intents.map(function (intent) {
|
|
552
|
+
const bindingLabel = intent.resolved_binding_id ? ('binding=' + intent.resolved_binding_id) : 'binding=(unresolved)';
|
|
553
|
+
const ackLabel = intent.acknowledged_at
|
|
554
|
+
? (' \xB7 ack=' + fmtTs(intent.acknowledged_at))
|
|
555
|
+
: '';
|
|
556
|
+
const actions = [];
|
|
557
|
+
if (intent.status === 'failed' || intent.status === 'unresolved') {
|
|
558
|
+
actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="retry_intent" data-intent-id="' + esc(intent.id) + '">Retry</button>');
|
|
559
|
+
actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="re_resolve_intent_binding" data-intent-id="' + esc(intent.id) + '">Re-resolve</button>');
|
|
560
|
+
} else if (intent.status === 'pending' || intent.status === 'bound' || intent.status === 'sent') {
|
|
561
|
+
actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="re_resolve_intent_binding" data-intent-id="' + esc(intent.id) + '">Re-resolve</button>');
|
|
562
|
+
}
|
|
563
|
+
if (intent.status !== 'canceled' && intent.status !== 'acknowledged') {
|
|
564
|
+
actions.push('<button class="danger-btn human-delivery-action-btn" data-action="cancel_intent" data-intent-id="' + esc(intent.id) + '">Cancel</button>');
|
|
565
|
+
}
|
|
566
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(intent.title || intent.summary || '(no title)') + '</strong>' +
|
|
567
|
+
'<span class="badge">' + esc(intent.status || 'pending') + '</span></div>' +
|
|
568
|
+
'<div class="muted small">' + esc(fmtTs(intent.created_at || intent.updated_at)) + ' \xB7 type=' + esc(intent.intent_type || 'collaboration_update') + ' \xB7 ' + esc(bindingLabel) + esc(ackLabel) + '</div>' +
|
|
569
|
+
'<div style="margin-top:8px">' + esc(intent.summary || '(no summary)') + '</div>' +
|
|
570
|
+
(intent.last_error ? '<div class="muted small" style="margin-top:8px;color:#fecaca">' + esc(intent.last_error) + '</div>' : '') +
|
|
571
|
+
(actions.length ? '<div class="row-actions" style="margin-top:10px">' + actions.join('') + '</div>' : '') +
|
|
572
|
+
'</div>';
|
|
573
|
+
}).join('');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function renderRecentBindings(bindings) {
|
|
577
|
+
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>';
|
|
579
|
+
}
|
|
580
|
+
return bindings.map(function (binding) {
|
|
581
|
+
const actions = binding.status === 'active'
|
|
582
|
+
? '<div class="row-actions" style="margin-top:10px"><button class="secondary-btn human-delivery-action-btn" data-action="mark_binding_stale" data-binding-id="' + esc(binding.id) + '">Mark stale</button></div>'
|
|
583
|
+
: '';
|
|
584
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(binding.channel + ' -> ' + binding.to) + '</strong>' +
|
|
585
|
+
'<span class="badge">' + esc(binding.status || 'active') + '</span></div>' +
|
|
586
|
+
'<div class="muted small">' + esc(fmtTs(binding.last_inbound_at || binding.updated_at)) + ' \xB7 owner=' + esc(binding.owner_ref || '(none)') + '</div>' +
|
|
587
|
+
'<div class="muted small" style="margin-top:8px">session=' + esc(binding.source_session_key || '(none)') + ' \xB7 conversation=' + esc(binding.source_conversation_id || '(none)') + '</div>' +
|
|
588
|
+
actions +
|
|
589
|
+
'</div>';
|
|
590
|
+
}).join('');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function renderHumanDeliveryCapabilities(humanDelivery) {
|
|
594
|
+
const capabilities = Array.isArray(humanDelivery && humanDelivery.channel_capabilities) ? humanDelivery.channel_capabilities : [];
|
|
595
|
+
if (!capabilities.length) {
|
|
596
|
+
return '<div class="empty">No channel capability data yet.</div>';
|
|
597
|
+
}
|
|
598
|
+
return capabilities.map(function (cap) {
|
|
599
|
+
const supportLabel = cap.supports_explicit_send ? 'explicit-send' : 'unsupported';
|
|
600
|
+
const canary = cap.last_canary_at
|
|
601
|
+
? (' \xB7 canary=' + (cap.last_canary_ok ? 'ok' : 'failed') + ' @ ' + fmtTs(cap.last_canary_at))
|
|
602
|
+
: ' \xB7 canary=pending';
|
|
603
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(cap.channel) + '</strong><span class="badge">' + esc(supportLabel) + '</span></div>' +
|
|
604
|
+
'<div class="muted small">configured=' + esc(cap.configured ? 'true' : 'false') + ' \xB7 thread=' + esc(cap.supports_thread_id ? 'yes' : 'no') + ' \xB7 account=' + esc(cap.supports_account_id ? 'yes' : 'no') + ' \xB7 dry_run=' + esc(cap.supports_dry_run ? 'yes' : 'no') + esc(canary) + '</div>' +
|
|
605
|
+
(cap.last_canary_error ? '<div class="muted small" style="margin-top:8px;color:#fecaca">' + esc(cap.last_canary_error) + '</div>' : '') +
|
|
606
|
+
'</div>';
|
|
607
|
+
}).join('');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function runHumanDeliveryAction(action, payload) {
|
|
611
|
+
return api('/api/runtime/human-delivery/action', {
|
|
612
|
+
method: 'POST',
|
|
613
|
+
headers: { 'Content-Type': 'application/json' },
|
|
614
|
+
body: JSON.stringify(Object.assign({ action: action }, payload || {})),
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function wireHumanDeliveryActionButtons(root) {
|
|
619
|
+
if (!root || !root.querySelectorAll) return;
|
|
620
|
+
root.querySelectorAll('.human-delivery-action-btn').forEach(function (btn) {
|
|
621
|
+
btn.addEventListener('click', async function () {
|
|
622
|
+
const action = btn.getAttribute('data-action');
|
|
623
|
+
if (!action) return;
|
|
624
|
+
const intentId = btn.getAttribute('data-intent-id');
|
|
625
|
+
const bindingId = btn.getAttribute('data-binding-id');
|
|
626
|
+
await runHumanDeliveryAction(action, {
|
|
627
|
+
intent_id: intentId ? Number(intentId) : undefined,
|
|
628
|
+
binding_id: bindingId ? Number(bindingId) : undefined,
|
|
629
|
+
});
|
|
630
|
+
await refreshAll();
|
|
631
|
+
if (state.selectedSessionKey) await loadSession(state.selectedSessionKey);
|
|
632
|
+
setTab(state.tab || 'runtime');
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
543
637
|
async function markSeen(scopeType, scopeKey) {
|
|
544
638
|
return api('/api/runtime/seen', {
|
|
545
639
|
method: 'POST',
|
|
@@ -804,8 +898,14 @@ function getHostPanelHtml() {
|
|
|
804
898
|
const overdueDecisions = pendingDecisions.filter(function (event) { return !!event.overdue; });
|
|
805
899
|
const pendingOutbox = data && Array.isArray(data.projectionOutboxPending) ? data.projectionOutboxPending : [];
|
|
806
900
|
const failedOutbox = data && Array.isArray(data.projectionOutboxFailed) ? data.projectionOutboxFailed : [];
|
|
807
|
-
|
|
808
|
-
|
|
901
|
+
const pendingIntents = data && Array.isArray(data.notificationIntentsPending) ? data.notificationIntentsPending : [];
|
|
902
|
+
const unresolvedIntents = data && Array.isArray(data.notificationIntentsUnresolved) ? data.notificationIntentsUnresolved : [];
|
|
903
|
+
const failedIntents = data && Array.isArray(data.notificationIntentsFailed) ? data.notificationIntentsFailed : [];
|
|
904
|
+
const humanDelivery = data && data.humanDelivery ? data.humanDelivery : null;
|
|
905
|
+
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);
|
|
809
909
|
document.getElementById('decisionInboxList').innerHTML = pendingDecisions.length
|
|
810
910
|
? pendingDecisions.map(function (event) {
|
|
811
911
|
return '<div class="audit-row"><div class="top"><strong>' + esc(event.summary || '(no summary)') + '</strong>' +
|
|
@@ -824,20 +924,26 @@ function getHostPanelHtml() {
|
|
|
824
924
|
: '<div class="empty">No pending collaboration decisions.</div>';
|
|
825
925
|
|
|
826
926
|
const combinedOutbox = pendingOutbox.concat(failedOutbox);
|
|
827
|
-
document.getElementById('projectionOutboxSummary').textContent =
|
|
828
|
-
|
|
927
|
+
document.getElementById('projectionOutboxSummary').textContent = boundChannelReply
|
|
928
|
+
? ('bound reply mode \xB7 pending=' + pendingIntents.length + ' \xB7 unresolved=' + unresolvedIntents.length + ' \xB7 failed=' + failedIntents.length)
|
|
929
|
+
: (combinedOutbox.length
|
|
829
930
|
? 'Human-thread projection uses a stable outbox. Failed rows stay visible until delivery recovers.'
|
|
830
|
-
: 'No undelivered human-thread projections.';
|
|
831
|
-
document.getElementById('projectionOutboxList').innerHTML =
|
|
832
|
-
?
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
931
|
+
: 'No undelivered human-thread projections.');
|
|
932
|
+
document.getElementById('projectionOutboxList').innerHTML = boundChannelReply
|
|
933
|
+
? renderHumanDeliveryIntents(
|
|
934
|
+
pendingIntents.concat(unresolvedIntents).concat(failedIntents),
|
|
935
|
+
'No pending, unresolved, or failed human-delivery intents.',
|
|
936
|
+
)
|
|
937
|
+
: (combinedOutbox.length
|
|
938
|
+
? combinedOutbox.map(function (entry) {
|
|
939
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(entry.summary || '(no summary)') + '</strong>' +
|
|
940
|
+
'<span class="badge">' + esc(entry.status || 'pending') + '</span></div>' +
|
|
941
|
+
'<div class="muted small">' + esc(fmtTs(entry.created_at)) + ' \xB7 kind=' + esc(entry.projection_kind || 'collaboration_update') + ' \xB7 attempts=' + esc(entry.attempt_count || 0) + '</div>' +
|
|
942
|
+
'<div class="muted small" style="margin-top:8px">target=' + esc(entry.target_human_session || '(missing)') + ' \xB7 session=' + esc(entry.session_key || '(none)') + '</div>' +
|
|
943
|
+
(entry.last_error ? '<div style="margin-top:8px">' + esc(entry.last_error) + '</div>' : '') +
|
|
944
|
+
'</div>';
|
|
945
|
+
}).join('')
|
|
946
|
+
: '<div class="empty">No pending or failed projection deliveries.</div>');
|
|
841
947
|
|
|
842
948
|
document.querySelectorAll('.inbox-decision-btn').forEach(function (btn) {
|
|
843
949
|
btn.addEventListener('click', async function () {
|
|
@@ -864,6 +970,7 @@ function getHostPanelHtml() {
|
|
|
864
970
|
setTab('runtime');
|
|
865
971
|
});
|
|
866
972
|
});
|
|
973
|
+
wireHumanDeliveryActionButtons(document.getElementById('projectionOutboxList'));
|
|
867
974
|
}
|
|
868
975
|
|
|
869
976
|
function getOverviewSessions() {
|
|
@@ -903,6 +1010,7 @@ function getHostPanelHtml() {
|
|
|
903
1010
|
const ingressState = ingressStatusModel(overview);
|
|
904
1011
|
const transportHealth = overview.transportHealth || null;
|
|
905
1012
|
const sinceLastSeen = overview.sinceLastSeen || null;
|
|
1013
|
+
const humanDelivery = overview.humanDelivery || null;
|
|
906
1014
|
const overdueDecisions = Array.isArray(overview.pendingDecisions)
|
|
907
1015
|
? overview.pendingDecisions.filter(function (event) { return !!event.overdue; })
|
|
908
1016
|
: [];
|
|
@@ -917,6 +1025,7 @@ function getHostPanelHtml() {
|
|
|
917
1025
|
'<span class="pill">pending_decisions=' + esc((overview.pendingDecisions || []).length) + '</span>' +
|
|
918
1026
|
'<span class="pill">overdue=' + esc(overdueDecisions.length) + '</span>' +
|
|
919
1027
|
'<span class="pill">projection=' + esc(overview.collaborationProjection && overview.collaborationProjection.preset ? overview.collaborationProjection.preset : 'balanced') + '</span>' +
|
|
1028
|
+
(humanDelivery ? '<span class="pill">human_delivery=' + esc(humanDelivery.mode || 'projection_outbox') + '</span>' : '') +
|
|
920
1029
|
'</div>' +
|
|
921
1030
|
'</div>' +
|
|
922
1031
|
'<div style="min-width:320px">' +
|
|
@@ -945,12 +1054,16 @@ function getHostPanelHtml() {
|
|
|
945
1054
|
{ label: 'Tasks', value: overview.tasksTotal, sub: 'recent local task threads' },
|
|
946
1055
|
{ label: 'Audit', value: overview.auditSummary.total_events, sub: 'policy / runtime audit events' },
|
|
947
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' },
|
|
948
1058
|
{ label: 'Recommendations', value: overview.recommendationSummary ? overview.recommendationSummary.total : overview.recommendations.length, sub: overview.recommendationSummary ? JSON.stringify(overview.recommendationSummary.by_status || {}) : 'learned policy suggestions' },
|
|
949
1059
|
{ 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' },
|
|
950
1060
|
];
|
|
951
1061
|
document.getElementById('statsGrid').innerHTML = stats.map(function (item) {
|
|
952
1062
|
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>';
|
|
953
1063
|
}).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>' +
|
|
1066
|
+
'<div class="audit-list" style="margin-top:8px">' + renderHumanDeliveryCapabilities(humanDelivery) + '</div></div>';
|
|
954
1067
|
|
|
955
1068
|
const toggleUnreadBtn = document.getElementById('toggleUnreadBtn');
|
|
956
1069
|
if (toggleUnreadBtn) toggleUnreadBtn.textContent = 'Unread only: ' + (state.showUnreadOnly ? 'on' : 'off');
|
|
@@ -1022,7 +1135,7 @@ function getHostPanelHtml() {
|
|
|
1022
1135
|
});
|
|
1023
1136
|
|
|
1024
1137
|
const tasks = Array.isArray(overview.tasks) ? overview.tasks : [];
|
|
1025
|
-
document.getElementById('taskList').innerHTML
|
|
1138
|
+
document.getElementById('taskList').innerHTML += tasks.length
|
|
1026
1139
|
? tasks.map(function (task) {
|
|
1027
1140
|
return '<div class="task-row"><div class="top"><strong>' + esc(task.title || task.task_id) + '</strong><span class="badge">' + esc(task.status) + '</span></div>' +
|
|
1028
1141
|
'<div class="muted small">task_id=' + esc(task.task_id) + ' \xB7 session=' + esc(task.session_key) + '</div>' +
|
|
@@ -1031,7 +1144,7 @@ function getHostPanelHtml() {
|
|
|
1031
1144
|
(task.error_message ? '<div style="margin-top:8px;color:#fca5a5">' + esc(task.error_message) + '</div>' : '') +
|
|
1032
1145
|
renderHandoffBlock(task.handoff) +
|
|
1033
1146
|
'</div>';
|
|
1034
|
-
|
|
1147
|
+
}).join('')
|
|
1035
1148
|
: '<div class="empty">No recent task threads.</div>';
|
|
1036
1149
|
|
|
1037
1150
|
if (subscription) {
|
|
@@ -1125,10 +1238,13 @@ conversation=' + result.conversation_id));
|
|
|
1125
1238
|
? detail.collaborationProjection.preset
|
|
1126
1239
|
: 'balanced';
|
|
1127
1240
|
const projectionOutbox = Array.isArray(detail.projectionOutbox) ? detail.projectionOutbox : [];
|
|
1241
|
+
const recentBindings = Array.isArray(detail.recentBindings) ? detail.recentBindings : [];
|
|
1242
|
+
const recentNotificationIntents = Array.isArray(detail.recentNotificationIntents) ? detail.recentNotificationIntents : [];
|
|
1128
1243
|
const sinceLastSeen = detail.sinceLastSeen || null;
|
|
1129
1244
|
const deliveryTimeline = Array.isArray(detail.deliveryTimeline) ? detail.deliveryTimeline : [];
|
|
1130
1245
|
const projectionPreview = detail.projectionPreview || null;
|
|
1131
1246
|
const transportHealth = detail.transportHealth || null;
|
|
1247
|
+
const humanDelivery = detail.humanDelivery || null;
|
|
1132
1248
|
const isAdvanced = state.detailMode === 'advanced';
|
|
1133
1249
|
const sessionLink = buildSessionLink(session.session_key);
|
|
1134
1250
|
const summaryPills = [];
|
|
@@ -1289,6 +1405,9 @@ conversation=' + result.conversation_id));
|
|
|
1289
1405
|
(transportHealth
|
|
1290
1406
|
? '<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>'
|
|
1291
1407
|
: '') +
|
|
1408
|
+
(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>'
|
|
1410
|
+
: '') +
|
|
1292
1411
|
(projectionOutbox.length
|
|
1293
1412
|
? '<div class="muted small" style="margin-top:8px">outbox=' + esc(projectionOutbox[0].status || 'pending') + ' \xB7 target=' + esc(projectionOutbox[0].target_human_session || '(missing)') + '</div>'
|
|
1294
1413
|
: '<div class="muted small" style="margin-top:8px">outbox=clear</div>') +
|
|
@@ -1307,6 +1426,16 @@ conversation=' + result.conversation_id));
|
|
|
1307
1426
|
'</div></div>' +
|
|
1308
1427
|
'</div>';
|
|
1309
1428
|
|
|
1429
|
+
el.innerHTML +=
|
|
1430
|
+
'<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">' +
|
|
1432
|
+
renderRecentBindings(recentBindings) +
|
|
1433
|
+
'</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.') +
|
|
1436
|
+
'</div></div>' +
|
|
1437
|
+
'</div>';
|
|
1438
|
+
|
|
1310
1439
|
el.querySelectorAll('.approve-session-btn').forEach(function (btn) {
|
|
1311
1440
|
btn.addEventListener('click', async function () {
|
|
1312
1441
|
const sessionKey = btn.getAttribute('data-session');
|
|
@@ -1447,6 +1576,7 @@ conversation=' + result.conversation_id));
|
|
|
1447
1576
|
setTab('runtime');
|
|
1448
1577
|
});
|
|
1449
1578
|
}
|
|
1579
|
+
wireHumanDeliveryActionButtons(el);
|
|
1450
1580
|
}
|
|
1451
1581
|
|
|
1452
1582
|
async function promptBindCurrentChat(conversationId, previousBinding, remoteDid) {
|
|
@@ -2154,6 +2284,14 @@ function readSinceLastSeenState(storePath, operatorId, scopeType, scopeKey) {
|
|
|
2154
2284
|
store.close();
|
|
2155
2285
|
}
|
|
2156
2286
|
}
|
|
2287
|
+
function readHumanDeliveryState(storePath, limit = 12) {
|
|
2288
|
+
const store = new LocalStore(storePath);
|
|
2289
|
+
try {
|
|
2290
|
+
return summarizeHumanDelivery(store, limit);
|
|
2291
|
+
} finally {
|
|
2292
|
+
store.close();
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2157
2295
|
function buildPolicyDecisionShape(identityPath, remoteDid, opts) {
|
|
2158
2296
|
const policy = readTrustPolicyDoc(identityPath);
|
|
2159
2297
|
const runtimeMode = opts?.runtimeMode ?? getRuntimeMode();
|
|
@@ -2268,10 +2406,20 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
2268
2406
|
const projectionOutbox = readProjectionOutboxState(ctx.storePath, 20);
|
|
2269
2407
|
const transportHealth = readTransportHealthState(ctx.storePath);
|
|
2270
2408
|
const sinceLastSeen = readSinceLastSeenState(ctx.storePath, "host_panel", "global");
|
|
2409
|
+
const humanDelivery = readHumanDeliveryState(ctx.storePath, 20);
|
|
2271
2410
|
const decisionStore = new LocalStore(ctx.storePath);
|
|
2272
2411
|
let decisionViews = [];
|
|
2412
|
+
let humanDeliveryDetails = null;
|
|
2273
2413
|
try {
|
|
2274
2414
|
decisionViews = listPendingDecisionViews(decisionStore, 100);
|
|
2415
|
+
const bindingManager = new HumanDeliveryBindingManager(decisionStore);
|
|
2416
|
+
const intentManager = new NotificationIntentManager(decisionStore);
|
|
2417
|
+
humanDeliveryDetails = {
|
|
2418
|
+
unresolved_intents: intentManager.listByStatus("unresolved", 20),
|
|
2419
|
+
failed_intents: intentManager.listByStatus("failed", 20),
|
|
2420
|
+
acknowledged_intents: intentManager.listByStatus("acknowledged", 20),
|
|
2421
|
+
recent_bindings: bindingManager.listRecent(20)
|
|
2422
|
+
};
|
|
2275
2423
|
} finally {
|
|
2276
2424
|
decisionStore.close();
|
|
2277
2425
|
}
|
|
@@ -2314,6 +2462,8 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
2314
2462
|
projectionOutbox,
|
|
2315
2463
|
transportHealth,
|
|
2316
2464
|
sinceLastSeen,
|
|
2465
|
+
humanDelivery,
|
|
2466
|
+
humanDeliveryDetails,
|
|
2317
2467
|
pendingDecisions: decisionViews,
|
|
2318
2468
|
recentCollaborationEvents: collaborationEventManager.listRecent(20),
|
|
2319
2469
|
sessions: sessions.map((session) => ({
|
|
@@ -2381,12 +2531,17 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2381
2531
|
});
|
|
2382
2532
|
const outboxStore = new LocalStore(ctx.storePath);
|
|
2383
2533
|
let projectionOutbox = [];
|
|
2534
|
+
let notificationIntents = [];
|
|
2535
|
+
let recentBindings = [];
|
|
2384
2536
|
let sinceLastSeen = null;
|
|
2385
2537
|
let deliveryTimeline = [];
|
|
2386
2538
|
let projectionPreview = null;
|
|
2387
2539
|
let pendingDecisionViews = [];
|
|
2540
|
+
let humanDelivery = null;
|
|
2388
2541
|
try {
|
|
2389
2542
|
projectionOutbox = new CollaborationProjectionOutboxManager(outboxStore).listBySession(session.session_key, 20);
|
|
2543
|
+
notificationIntents = new NotificationIntentManager(outboxStore).listBySession(session.session_key, 20);
|
|
2544
|
+
recentBindings = listRecentBindingsForSession(outboxStore, session.session_key, session.conversation_id, 20);
|
|
2390
2545
|
sinceLastSeen = summarizeSinceLastSeen(outboxStore, {
|
|
2391
2546
|
operator_id: "host_panel",
|
|
2392
2547
|
scope_type: "session",
|
|
@@ -2400,6 +2555,7 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2400
2555
|
5
|
|
2401
2556
|
);
|
|
2402
2557
|
pendingDecisionViews = listPendingDecisionViews(outboxStore, 100).filter((event) => event.session_key === session.session_key).slice(0, 20);
|
|
2558
|
+
humanDelivery = summarizeHumanDelivery(outboxStore, 20);
|
|
2403
2559
|
} finally {
|
|
2404
2560
|
outboxStore.close();
|
|
2405
2561
|
}
|
|
@@ -2419,9 +2575,12 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2419
2575
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
2420
2576
|
collaborationProjection: policy.collaboration_projection,
|
|
2421
2577
|
projectionOutbox,
|
|
2578
|
+
recentBindings,
|
|
2579
|
+
recentNotificationIntents: notificationIntents,
|
|
2422
2580
|
sinceLastSeen,
|
|
2423
2581
|
deliveryTimeline,
|
|
2424
2582
|
projectionPreview,
|
|
2583
|
+
humanDelivery,
|
|
2425
2584
|
policyExplain: buildPolicyDecisionShape(ctx.identityPath, session.remote_did, { runtimeMode: getRuntimeMode() }),
|
|
2426
2585
|
tasks: tasks.map((task) => ({
|
|
2427
2586
|
...task,
|
|
@@ -2504,6 +2663,57 @@ async function handleApi(pathname, req, ctx) {
|
|
|
2504
2663
|
transportPreference: readTransportPreference()
|
|
2505
2664
|
};
|
|
2506
2665
|
}
|
|
2666
|
+
if (parts[1] === "human-delivery") {
|
|
2667
|
+
const store = new LocalStore(ctx.storePath);
|
|
2668
|
+
try {
|
|
2669
|
+
const bindingManager = new HumanDeliveryBindingManager(store);
|
|
2670
|
+
const intentManager = new NotificationIntentManager(store);
|
|
2671
|
+
if (req.method === "GET") {
|
|
2672
|
+
return {
|
|
2673
|
+
humanDelivery: summarizeHumanDelivery(store, 20),
|
|
2674
|
+
recentBindings: bindingManager.listRecent(50),
|
|
2675
|
+
unresolvedIntents: intentManager.listByStatus("unresolved", 50),
|
|
2676
|
+
failedIntents: intentManager.listByStatus("failed", 50),
|
|
2677
|
+
acknowledgedIntents: intentManager.listByStatus("acknowledged", 50)
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
if (req.method === "POST" && parts[2] === "action") {
|
|
2681
|
+
const body = await readBody(req);
|
|
2682
|
+
const action = String(body?.action ?? "").trim();
|
|
2683
|
+
const intentId = Number(body?.intent_id);
|
|
2684
|
+
const bindingId = Number(body?.binding_id);
|
|
2685
|
+
let intent = null;
|
|
2686
|
+
let binding = null;
|
|
2687
|
+
if (action === "retry_intent") {
|
|
2688
|
+
if (!Number.isInteger(intentId) || intentId <= 0) throw new Error("intent_id is required");
|
|
2689
|
+
intent = intentManager.markPending(intentId, { clear_error: true, clear_ack: false, clear_binding: false });
|
|
2690
|
+
} else if (action === "re_resolve_intent_binding") {
|
|
2691
|
+
if (!Number.isInteger(intentId) || intentId <= 0) throw new Error("intent_id is required");
|
|
2692
|
+
intent = intentManager.markPending(intentId, { clear_error: true, clear_ack: false, clear_binding: true });
|
|
2693
|
+
} else if (action === "cancel_intent") {
|
|
2694
|
+
if (!Number.isInteger(intentId) || intentId <= 0) throw new Error("intent_id is required");
|
|
2695
|
+
intent = intentManager.markCanceled(intentId, "manual_cancel");
|
|
2696
|
+
} else if (action === "mark_binding_stale") {
|
|
2697
|
+
if (!Number.isInteger(bindingId) || bindingId <= 0) throw new Error("binding_id is required");
|
|
2698
|
+
binding = bindingManager.markStale(bindingId);
|
|
2699
|
+
} else {
|
|
2700
|
+
throw new Error("action must be retry_intent, re_resolve_intent_binding, cancel_intent, or mark_binding_stale");
|
|
2701
|
+
}
|
|
2702
|
+
return {
|
|
2703
|
+
ok: true,
|
|
2704
|
+
action,
|
|
2705
|
+
intent,
|
|
2706
|
+
binding,
|
|
2707
|
+
humanDelivery: summarizeHumanDelivery(store, 20),
|
|
2708
|
+
unresolvedIntents: intentManager.listByStatus("unresolved", 50),
|
|
2709
|
+
failedIntents: intentManager.listByStatus("failed", 50),
|
|
2710
|
+
acknowledgedIntents: intentManager.listByStatus("acknowledged", 50)
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
} finally {
|
|
2714
|
+
store.close();
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2507
2717
|
if (parts[1] === "transport-switch" && req.method === "POST") {
|
|
2508
2718
|
const body = await readBody(req);
|
|
2509
2719
|
const mode = String(body?.mode ?? "").trim().toLowerCase();
|
|
@@ -2618,10 +2828,15 @@ async function handleApi(pathname, req, ctx) {
|
|
|
2618
2828
|
const outboxManager = new CollaborationProjectionOutboxManager(store);
|
|
2619
2829
|
if (req.method === "GET") {
|
|
2620
2830
|
const pendingDecisions = listPendingDecisionViews(store, 100);
|
|
2831
|
+
const intentManager = new NotificationIntentManager(store);
|
|
2621
2832
|
return {
|
|
2622
2833
|
pendingDecisions,
|
|
2623
2834
|
projectionOutboxPending: outboxManager.listByStatus("pending", 50),
|
|
2624
|
-
projectionOutboxFailed: outboxManager.listByStatus("failed", 50)
|
|
2835
|
+
projectionOutboxFailed: outboxManager.listByStatus("failed", 50),
|
|
2836
|
+
notificationIntentsPending: intentManager.listByStatus("pending", 50).concat(intentManager.listByStatus("bound", 50)),
|
|
2837
|
+
notificationIntentsUnresolved: intentManager.listByStatus("unresolved", 50),
|
|
2838
|
+
notificationIntentsFailed: intentManager.listByStatus("failed", 50),
|
|
2839
|
+
humanDelivery: summarizeHumanDelivery(store, 20)
|
|
2625
2840
|
};
|
|
2626
2841
|
}
|
|
2627
2842
|
if (parts[2] === "resolve" && req.method === "POST") {
|