@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.
@@ -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-BCYHGKQE.js";
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
- document.getElementById('decisionInboxSummary').textContent =
808
- 'pending=' + pendingDecisions.length + ' \xB7 overdue=' + overdueDecisions.length + ' \xB7 projection_pending=' + pendingOutbox.length + ' \xB7 projection_failed=' + failedOutbox.length;
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
- combinedOutbox.length
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 = combinedOutbox.length
832
- ? combinedOutbox.map(function (entry) {
833
- return '<div class="audit-row"><div class="top"><strong>' + esc(entry.summary || '(no summary)') + '</strong>' +
834
- '<span class="badge">' + esc(entry.status || 'pending') + '</span></div>' +
835
- '<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>' +
836
- '<div class="muted small" style="margin-top:8px">target=' + esc(entry.target_human_session || '(missing)') + ' \xB7 session=' + esc(entry.session_key || '(none)') + '</div>' +
837
- (entry.last_error ? '<div style="margin-top:8px">' + esc(entry.last_error) + '</div>' : '') +
838
- '</div>';
839
- }).join('')
840
- : '<div class="empty">No pending or failed projection deliveries.</div>';
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 = tasks.length
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
- }).join('')
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") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pingagent/sdk",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"