@pingagent/sdk 0.1.19 → 0.1.20

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.
@@ -4,6 +4,7 @@ import {
4
4
  ContactManager,
5
5
  HumanDeliveryBindingManager,
6
6
  LocalStore,
7
+ NotificationDeliveryAttemptManager,
7
8
  NotificationIntentManager,
8
9
  OpenClawExecutionAdapter,
9
10
  OperatorSeenStateManager,
@@ -11,6 +12,7 @@ import {
11
12
  SessionManager,
12
13
  TrustPolicyAuditManager,
13
14
  TrustRecommendationManager,
15
+ VerifiedDeliveryTargetManager,
14
16
  buildActionContract,
15
17
  buildActionInboxSummary,
16
18
  buildDeliveryTimeline,
@@ -25,11 +27,16 @@ import {
25
27
  getSessionBindingAlertsFilePath,
26
28
  getSessionMapFilePath,
27
29
  getTrustRecommendationActionLabel,
30
+ importBindingCandidates,
31
+ listImportableBindingCandidates,
28
32
  listPendingDecisionViews,
29
33
  listRecentBindingsForSession,
30
34
  listRuntimeAdapterDescriptors,
31
35
  loadIdentity,
32
36
  normalizeTrustPolicyDoc,
37
+ normalizeVerifiedDeliveryTargetInput,
38
+ normalizeVerifiedDeliveryTargetUpdateInput,
39
+ promoteBindingToVerifiedTarget,
33
40
  readCurrentActiveSessionKey,
34
41
  readIngressRuntimeStatus,
35
42
  readSessionBindingAlerts,
@@ -43,7 +50,7 @@ import {
43
50
  switchTransportPreference,
44
51
  updateStoredToken,
45
52
  upsertTrustPolicyRecommendation
46
- } from "./chunk-JWBNSM4N.js";
53
+ } from "./chunk-M6XDTSQA.js";
47
54
 
48
55
  // src/web-server.ts
49
56
  import * as fs from "fs";
@@ -555,7 +562,9 @@ function getHostPanelHtml() {
555
562
  return '<div class="empty">' + esc(emptyLabel || 'No notification intents yet.') + '</div>';
556
563
  }
557
564
  return intents.map(function (intent) {
558
- const bindingLabel = intent.resolved_binding_id ? ('binding=' + intent.resolved_binding_id) : 'binding=(unresolved)';
565
+ const bindingLabel = intent.resolved_target_id
566
+ ? ('target=' + intent.resolved_target_id)
567
+ : (intent.resolved_binding_id ? ('binding=' + intent.resolved_binding_id) : 'binding=(unresolved)');
559
568
  const ackLabel = intent.acknowledged_at
560
569
  ? (' \xB7 ack=' + fmtTs(intent.acknowledged_at))
561
570
  : '';
@@ -679,6 +688,63 @@ function getHostPanelHtml() {
679
688
  }).join('');
680
689
  }
681
690
 
691
+ function renderVerifiedTargets(targets) {
692
+ if (!Array.isArray(targets) || !targets.length) {
693
+ return '<div class="empty">No verified explicit delivery targets yet. Promote a filtered binding candidate or create one from MCP to make notifications reliable.</div>';
694
+ }
695
+ return targets.map(function (target) {
696
+ const actions = [];
697
+ if (target.status !== 'disabled') {
698
+ actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="disable_verified_target" data-target-id="' + esc(target.id) + '">Disable</button>');
699
+ }
700
+ if (target.status === 'failing') {
701
+ actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="retest_verified_target" data-target-id="' + esc(target.id) + '">Retest</button>');
702
+ }
703
+ if (target.scope_type !== 'workspace_default' && target.route_kind === 'group') {
704
+ actions.push('<button class="secondary-btn human-delivery-action-btn" data-action="set_workspace_default_target" data-target-id="' + esc(target.id) + '">Use As Group Fallback</button>');
705
+ }
706
+ return '<div class="audit-row"><div class="top"><strong>' + esc(target.channel + ' -> ' + target.to_target) + '</strong>' +
707
+ '<span class="badge">' + esc(target.status || 'active') + '</span></div>' +
708
+ '<div class="muted small">' + esc(target.scope_type || 'owner') + ':' + esc(target.scope_key || '(none)') + ' \xB7 route=' + esc(target.route_kind || 'dm') + ' \xB7 priority=' + esc(target.priority || 0) + '</div>' +
709
+ '<div class="muted small" style="margin-top:8px">account=' + esc(target.account_id || '(none)') + ' \xB7 thread=' + esc(target.thread_id || '(none)') + '</div>' +
710
+ (target.last_error ? '<div class="muted small" style="margin-top:8px;color:#fecaca">' + esc(target.last_error) + '</div>' : '') +
711
+ (actions.length ? '<div class="row-actions" style="margin-top:10px">' + actions.join('') + '</div>' : '') +
712
+ '</div>';
713
+ }).join('');
714
+ }
715
+
716
+ function renderImportableBindingCandidates(candidates) {
717
+ if (!Array.isArray(candidates) || !candidates.length) {
718
+ return '<div class="empty">No importable binding candidates right now.</div>';
719
+ }
720
+ return candidates.map(function (candidate) {
721
+ const actions = candidate.already_verified_target_id
722
+ ? []
723
+ : ['<button class="secondary-btn human-delivery-action-btn" data-action="promote_binding_to_verified_target" data-binding-id="' + esc(candidate.binding_id) + '" data-scope-type="' + esc(candidate.scope_type) + '" data-scope-key="' + esc(candidate.scope_key) + '" data-route-kind="' + esc(candidate.route_kind) + '">Promote</button>'];
724
+ return '<div class="audit-row"><div class="top"><strong>' + esc(candidate.channel + ' -> ' + candidate.to_target) + '</strong>' +
725
+ '<span class="badge">' + esc(candidate.route_kind || 'dm') + '</span></div>' +
726
+ '<div class="muted small">' + esc(candidate.scope_type || 'owner') + ':' + esc(candidate.scope_key || '(none)') + ' \xB7 binding=' + esc(candidate.binding_id) + '</div>' +
727
+ (candidate.already_verified_target_id
728
+ ? '<div class="muted small" style="margin-top:8px">Already promoted as verified target #' + esc(candidate.already_verified_target_id) + '.</div>'
729
+ : '') +
730
+ (actions.length ? '<div class="row-actions" style="margin-top:10px">' + actions.join('') + '</div>' : '') +
731
+ '</div>';
732
+ }).join('');
733
+ }
734
+
735
+ function renderDeliveryAttempts(attempts) {
736
+ if (!Array.isArray(attempts) || !attempts.length) {
737
+ return '<div class="empty">No verified delivery attempts yet.</div>';
738
+ }
739
+ return attempts.map(function (attempt) {
740
+ return '<div class="audit-row"><div class="top"><strong>Intent #' + esc(attempt.intent_id) + ' -> Target #' + esc(attempt.target_id) + '</strong>' +
741
+ '<span class="badge">' + esc(attempt.status || 'pending') + '</span></div>' +
742
+ '<div class="muted small">attempt=' + esc(attempt.attempt_no || 0) + ' \xB7 updated=' + esc(fmtTs(attempt.updated_at || attempt.created_at)) + '</div>' +
743
+ (attempt.error_message ? '<div class="muted small" style="margin-top:8px;color:#fecaca">' + esc(attempt.error_message) + '</div>' : '') +
744
+ '</div>';
745
+ }).join('');
746
+ }
747
+
682
748
  function renderHumanDeliveryCapabilities(humanDelivery) {
683
749
  const capabilities = Array.isArray(humanDelivery && humanDelivery.channel_capabilities) ? humanDelivery.channel_capabilities : [];
684
750
  if (!capabilities.length) {
@@ -712,9 +778,17 @@ function getHostPanelHtml() {
712
778
  if (!action) return;
713
779
  const intentId = btn.getAttribute('data-intent-id');
714
780
  const bindingId = btn.getAttribute('data-binding-id');
781
+ const targetId = btn.getAttribute('data-target-id');
782
+ const scopeType = btn.getAttribute('data-scope-type');
783
+ const scopeKey = btn.getAttribute('data-scope-key');
784
+ const routeKind = btn.getAttribute('data-route-kind');
715
785
  await runHumanDeliveryAction(action, {
716
786
  intent_id: intentId ? Number(intentId) : undefined,
717
787
  binding_id: bindingId ? Number(bindingId) : undefined,
788
+ target_id: targetId ? Number(targetId) : undefined,
789
+ scope_type: scopeType || undefined,
790
+ scope_key: scopeKey || undefined,
791
+ route_kind: routeKind || undefined,
718
792
  });
719
793
  await refreshAll();
720
794
  if (state.selectedSessionKey) await loadSession(state.selectedSessionKey);
@@ -1036,7 +1110,7 @@ function getHostPanelHtml() {
1036
1110
  const failedIntents = data && Array.isArray(data.notificationIntentsFailed) ? data.notificationIntentsFailed : [];
1037
1111
  const humanDelivery = data && data.humanDelivery ? data.humanDelivery : null;
1038
1112
  const openclawAdapter = data && data.openclawAdapter ? data.openclawAdapter : null;
1039
- const boundChannelReply = !!(humanDelivery && humanDelivery.mode === 'bound_channel_reply');
1113
+ const intentBasedHumanDelivery = !!(humanDelivery && (humanDelivery.mode === 'bound_channel_reply' || humanDelivery.mode === 'verified_explicit_notify'));
1040
1114
  document.getElementById('decisionInboxSummary').textContent =
1041
1115
  'total=' + (actionInbox.total || 0) +
1042
1116
  ' \xB7 approvals=' + (actionInbox.pending_approval || 0) +
@@ -1048,12 +1122,12 @@ function getHostPanelHtml() {
1048
1122
  document.getElementById('decisionInboxList').innerHTML = renderActionInboxItems(actionInbox.items);
1049
1123
 
1050
1124
  const combinedOutbox = pendingOutbox.concat(failedOutbox);
1051
- document.getElementById('projectionOutboxSummary').textContent = boundChannelReply
1052
- ? ('bound reply mode \xB7 pending=' + pendingIntents.length + ' \xB7 unresolved=' + unresolvedIntents.length + ' \xB7 failed=' + failedIntents.length)
1125
+ document.getElementById('projectionOutboxSummary').textContent = intentBasedHumanDelivery
1126
+ ? ((humanDelivery && humanDelivery.mode === 'verified_explicit_notify' ? 'verified explicit notify' : 'bound reply mode') + ' \xB7 pending=' + pendingIntents.length + ' \xB7 unresolved=' + unresolvedIntents.length + ' \xB7 failed=' + failedIntents.length)
1053
1127
  : (combinedOutbox.length
1054
1128
  ? 'Legacy projection outbox remains visible for audit and compatibility. Failed rows stay visible until delivery recovers.'
1055
1129
  : 'No legacy projection-outbox rows need attention.');
1056
- document.getElementById('projectionOutboxList').innerHTML = boundChannelReply
1130
+ document.getElementById('projectionOutboxList').innerHTML = intentBasedHumanDelivery
1057
1131
  ? renderHumanDeliveryIntents(
1058
1132
  pendingIntents.concat(unresolvedIntents).concat(failedIntents),
1059
1133
  'No pending, unresolved, or failed human-delivery intents.',
@@ -1195,7 +1269,7 @@ function getHostPanelHtml() {
1195
1269
  { label: 'Tasks', value: overview.tasksTotal, sub: 'recent local task threads' },
1196
1270
  { label: 'Audit', value: overview.auditSummary.total_events, sub: 'policy / runtime audit events' },
1197
1271
  { 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' },
1272
+ { label: 'External Delivery', value: humanDelivery ? ((humanDelivery.pending_intents || 0) + '/' + (humanDelivery.active_verified_targets || 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' },
1199
1273
  { label: 'Recommendations', value: overview.recommendationSummary ? overview.recommendationSummary.total : overview.recommendations.length, sub: overview.recommendationSummary ? JSON.stringify(overview.recommendationSummary.by_status || {}) : 'learned policy suggestions' },
1200
1274
  { 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' },
1201
1275
  ];
@@ -1204,7 +1278,17 @@ function getHostPanelHtml() {
1204
1278
  }).join('');
1205
1279
  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
1280
  '<div class="muted small">Capabilities and canary state for explicit external callback delivery.</div>' +
1207
- '<div class="audit-list" style="margin-top:8px">' + renderHumanDeliveryCapabilities(humanDelivery) + '</div></div>';
1281
+ '<div class="audit-list" style="margin-top:8px">' + renderHumanDeliveryCapabilities(humanDelivery) + '</div></div>' +
1282
+ '<div class="task-row"><div class="top"><strong>Verified Explicit Targets</strong><span class="badge">' + esc(humanDelivery && humanDelivery.active_verified_targets != null ? humanDelivery.active_verified_targets : 0) + '</span></div>' +
1283
+ '<div class="muted small">Stable source of truth for reliable human notifications. DM-first, then group fallback.</div>' +
1284
+ '<div class="audit-list" style="margin-top:8px">' + renderVerifiedTargets(humanDelivery && humanDelivery.recent_verified_targets) + '</div></div>' +
1285
+ '<div class="task-row"><div class="top"><strong>Importable Binding Candidates</strong><span class="badge">' + esc(humanDelivery && humanDelivery.importable_binding_candidates ? humanDelivery.importable_binding_candidates.length : 0) + '</span></div>' +
1286
+ '<div class="muted small">Candidate addresses learned from real conversations. Promote only the targets you trust.</div>' +
1287
+ '<div class="row-actions" style="margin-top:10px"><button class="secondary-btn human-delivery-action-btn" data-action="import_binding_candidates">Import Candidates As Disabled</button></div>' +
1288
+ '<div class="audit-list" style="margin-top:8px">' + renderImportableBindingCandidates(humanDelivery && humanDelivery.importable_binding_candidates) + '</div></div>' +
1289
+ '<div class="task-row"><div class="top"><strong>Recent Delivery Attempts</strong><span class="badge">' + esc(humanDelivery && humanDelivery.recent_delivery_attempts ? humanDelivery.recent_delivery_attempts.length : 0) + '</span></div>' +
1290
+ '<div class="muted small">Audit trail for explicit notification attempts and fallback behavior.</div>' +
1291
+ '<div class="audit-list" style="margin-top:8px">' + renderDeliveryAttempts(humanDelivery && humanDelivery.recent_delivery_attempts) + '</div></div>';
1208
1292
 
1209
1293
  const toggleUnreadBtn = document.getElementById('toggleUnreadBtn');
1210
1294
  if (toggleUnreadBtn) toggleUnreadBtn.textContent = 'Unread only: ' + (state.showUnreadOnly ? 'on' : 'off');
@@ -1387,6 +1471,9 @@ conversation=' + result.conversation_id));
1387
1471
  const projectionOutbox = Array.isArray(detail.projectionOutbox) ? detail.projectionOutbox : [];
1388
1472
  const recentBindings = Array.isArray(detail.recentBindings) ? detail.recentBindings : [];
1389
1473
  const recentNotificationIntents = Array.isArray(detail.recentNotificationIntents) ? detail.recentNotificationIntents : [];
1474
+ const recentVerifiedTargets = Array.isArray(detail.recentVerifiedTargets) ? detail.recentVerifiedTargets : [];
1475
+ const recentDeliveryAttempts = Array.isArray(detail.recentDeliveryAttempts) ? detail.recentDeliveryAttempts : [];
1476
+ const importableBindingCandidates = Array.isArray(detail.importableBindingCandidates) ? detail.importableBindingCandidates : [];
1390
1477
  const sinceLastSeen = detail.sinceLastSeen || null;
1391
1478
  const deliveryTimeline = Array.isArray(detail.deliveryTimeline) ? detail.deliveryTimeline : [];
1392
1479
  const projectionPreview = detail.projectionPreview || null;
@@ -1588,6 +1675,27 @@ conversation=' + result.conversation_id));
1588
1675
  '</div></div>' +
1589
1676
  '</div>';
1590
1677
 
1678
+ el.innerHTML +=
1679
+ '<div class="grid two-col" style="margin-top:16px">' +
1680
+ '<div><div class="label">Verified Explicit Targets</div><div class="audit-list" style="margin-top:8px">' +
1681
+ renderVerifiedTargets(recentVerifiedTargets) +
1682
+ '</div></div>' +
1683
+ '<div><div class="label">Importable Binding Candidates</div><div class="audit-list" style="margin-top:8px">' +
1684
+ renderImportableBindingCandidates(importableBindingCandidates) +
1685
+ '</div></div>' +
1686
+ '</div>';
1687
+
1688
+ el.innerHTML +=
1689
+ '<div class="grid two-col" style="margin-top:16px">' +
1690
+ '<div><div class="label">Recent Delivery Attempts</div><div class="audit-list" style="margin-top:8px">' +
1691
+ renderDeliveryAttempts(recentDeliveryAttempts) +
1692
+ '</div></div>' +
1693
+ '<div><div class="label">Delivery Mode</div><div class="audit-list" style="margin-top:8px">' +
1694
+ '<div class="audit-row"><div class="top"><strong>Human delivery</strong><span class="badge">' + esc(humanDelivery && humanDelivery.mode ? humanDelivery.mode : 'projection_outbox') + '</span></div>' +
1695
+ '<div class="muted small">verified=' + esc(humanDelivery && humanDelivery.active_verified_targets != null ? humanDelivery.active_verified_targets : 0) + ' \xB7 unresolved=' + esc(humanDelivery && humanDelivery.unresolved_intents != null ? humanDelivery.unresolved_intents : 0) + ' \xB7 failed=' + esc(humanDelivery && humanDelivery.failed_intents != null ? humanDelivery.failed_intents : 0) + '</div></div>' +
1696
+ '</div></div>' +
1697
+ '</div>';
1698
+
1591
1699
  el.querySelectorAll('.approve-session-btn').forEach(function (btn) {
1592
1700
  btn.addEventListener('click', async function () {
1593
1701
  const sessionKey = btn.getAttribute('data-session');
@@ -2590,11 +2698,16 @@ async function buildRuntimeOverviewPayload(ctx) {
2590
2698
  decisionViews = listPendingDecisionViews(decisionStore, 100);
2591
2699
  const bindingManager = new HumanDeliveryBindingManager(decisionStore);
2592
2700
  const intentManager = new NotificationIntentManager(decisionStore);
2701
+ const targetManager = new VerifiedDeliveryTargetManager(decisionStore);
2702
+ const attemptManager = new NotificationDeliveryAttemptManager(decisionStore);
2593
2703
  humanDeliveryDetails = {
2594
2704
  unresolved_intents: intentManager.listByStatus("unresolved", 20),
2595
2705
  failed_intents: intentManager.listByStatus("failed", 20),
2596
2706
  acknowledged_intents: intentManager.listByStatus("acknowledged", 20),
2597
- recent_bindings: bindingManager.listRecent(20)
2707
+ recent_bindings: bindingManager.listRecent(20),
2708
+ recent_verified_targets: targetManager.listRecent(20),
2709
+ recent_delivery_attempts: attemptManager.listRecent(20),
2710
+ importable_binding_candidates: listImportableBindingCandidates(decisionStore, 20)
2598
2711
  };
2599
2712
  } finally {
2600
2713
  decisionStore.close();
@@ -2727,6 +2840,9 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2727
2840
  let projectionOutbox = [];
2728
2841
  let notificationIntents = [];
2729
2842
  let recentBindings = [];
2843
+ let recentVerifiedTargets = [];
2844
+ let recentDeliveryAttempts = [];
2845
+ let importableBindingCandidates = [];
2730
2846
  let sinceLastSeen = null;
2731
2847
  let deliveryTimeline = [];
2732
2848
  let projectionPreview = null;
@@ -2738,6 +2854,9 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2738
2854
  projectionOutbox = new CollaborationProjectionOutboxManager(outboxStore).listBySession(session.session_key, 20);
2739
2855
  notificationIntents = new NotificationIntentManager(outboxStore).listBySession(session.session_key, 20);
2740
2856
  recentBindings = listRecentBindingsForSession(outboxStore, session.session_key, session.conversation_id, 20);
2857
+ recentVerifiedTargets = new VerifiedDeliveryTargetManager(outboxStore).listRecent(20);
2858
+ recentDeliveryAttempts = new NotificationDeliveryAttemptManager(outboxStore).listRecent(20);
2859
+ importableBindingCandidates = listImportableBindingCandidates(outboxStore, 20);
2741
2860
  sinceLastSeen = summarizeSinceLastSeen(outboxStore, {
2742
2861
  operator_id: "host_panel",
2743
2862
  scope_type: "session",
@@ -2789,6 +2908,9 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
2789
2908
  collaborationProjection: policy.collaboration_projection,
2790
2909
  projectionOutbox,
2791
2910
  recentBindings,
2911
+ recentVerifiedTargets,
2912
+ recentDeliveryAttempts,
2913
+ importableBindingCandidates,
2792
2914
  recentNotificationIntents: notificationIntents,
2793
2915
  sinceLastSeen,
2794
2916
  deliveryTimeline,
@@ -2890,10 +3012,15 @@ async function handleApi(pathname, req, ctx) {
2890
3012
  try {
2891
3013
  const bindingManager = new HumanDeliveryBindingManager(store);
2892
3014
  const intentManager = new NotificationIntentManager(store);
3015
+ const targetManager = new VerifiedDeliveryTargetManager(store);
3016
+ const attemptManager = new NotificationDeliveryAttemptManager(store);
2893
3017
  if (req.method === "GET") {
2894
3018
  return {
2895
3019
  humanDelivery: summarizeHumanDelivery(store, 20),
2896
3020
  recentBindings: bindingManager.listRecent(50),
3021
+ recentVerifiedTargets: targetManager.listRecent(50),
3022
+ recentDeliveryAttempts: attemptManager.listRecent(50),
3023
+ importableBindingCandidates: listImportableBindingCandidates(store, 50),
2897
3024
  unresolvedIntents: intentManager.listByStatus("unresolved", 50),
2898
3025
  failedIntents: intentManager.listByStatus("failed", 50),
2899
3026
  acknowledgedIntents: intentManager.listByStatus("acknowledged", 50)
@@ -2904,8 +3031,11 @@ async function handleApi(pathname, req, ctx) {
2904
3031
  const action = String(body?.action ?? "").trim();
2905
3032
  const intentId = Number(body?.intent_id);
2906
3033
  const bindingId = Number(body?.binding_id);
3034
+ const targetId = Number(body?.target_id);
2907
3035
  let intent = null;
2908
3036
  let binding = null;
3037
+ let target = null;
3038
+ let importedTargets = [];
2909
3039
  if (action === "retry_intent") {
2910
3040
  if (!Number.isInteger(intentId) || intentId <= 0) throw new Error("intent_id is required");
2911
3041
  intent = intentManager.markPending(intentId, { clear_error: true, clear_ack: false, clear_binding: false });
@@ -2918,15 +3048,86 @@ async function handleApi(pathname, req, ctx) {
2918
3048
  } else if (action === "mark_binding_stale") {
2919
3049
  if (!Number.isInteger(bindingId) || bindingId <= 0) throw new Error("binding_id is required");
2920
3050
  binding = bindingManager.markStale(bindingId);
3051
+ } else if (action === "promote_binding_to_verified_target") {
3052
+ if (!Number.isInteger(bindingId) || bindingId <= 0) throw new Error("binding_id is required");
3053
+ target = promoteBindingToVerifiedTarget(store, {
3054
+ binding_id: bindingId,
3055
+ scope_type: body?.scope_type === "workspace_default" ? "workspace_default" : body?.scope_type === "owner" ? "owner" : void 0,
3056
+ scope_key: typeof body?.scope_key === "string" ? body.scope_key : void 0,
3057
+ route_kind: body?.route_kind === "group" ? "group" : body?.route_kind === "dm" ? "dm" : void 0,
3058
+ priority: Number.isFinite(Number(body?.priority)) ? Number(body.priority) : void 0,
3059
+ status: body?.status === "disabled" ? "disabled" : body?.status === "failing" ? "failing" : "active"
3060
+ });
3061
+ } else if (action === "create_verified_target") {
3062
+ const normalized = normalizeVerifiedDeliveryTargetInput({
3063
+ scope_type: body?.scope_type === "workspace_default" ? "workspace_default" : "owner",
3064
+ scope_key: typeof body?.scope_key === "string" ? body.scope_key : void 0,
3065
+ owner_ref: body?.scope_type === "owner" && typeof body?.scope_key === "string" ? body.scope_key : void 0,
3066
+ channel: typeof body?.channel === "string" ? body.channel : void 0,
3067
+ to_target: typeof body?.to_target === "string" ? body.to_target : void 0,
3068
+ account_id: typeof body?.account_id === "string" ? body.account_id : void 0,
3069
+ thread_id: typeof body?.thread_id === "string" || typeof body?.thread_id === "number" ? body.thread_id : void 0,
3070
+ route_kind: body?.route_kind === "group" ? "group" : body?.route_kind === "dm" ? "dm" : void 0,
3071
+ priority: Number.isFinite(Number(body?.priority)) ? Number(body.priority) : void 0,
3072
+ status: body?.status === "disabled" ? "disabled" : body?.status === "failing" ? "failing" : "active",
3073
+ provenance: "manual_create"
3074
+ });
3075
+ if (!normalized) throw new Error("channel/to_target is invalid or unsupported");
3076
+ target = targetManager.upsert(normalized);
3077
+ } else if (action === "update_verified_target") {
3078
+ if (!Number.isInteger(targetId) || targetId <= 0) throw new Error("target_id is required");
3079
+ const existing = targetManager.get(targetId);
3080
+ if (!existing) throw new Error("verified target not found");
3081
+ const patch = normalizeVerifiedDeliveryTargetUpdateInput(existing, {
3082
+ scope_type: body?.scope_type === "workspace_default" ? "workspace_default" : body?.scope_type === "owner" ? "owner" : void 0,
3083
+ scope_key: typeof body?.scope_key === "string" ? body.scope_key : void 0,
3084
+ channel: typeof body?.channel === "string" ? body.channel : void 0,
3085
+ to_target: typeof body?.to_target === "string" ? body.to_target : void 0,
3086
+ account_id: typeof body?.account_id === "string" ? body.account_id : void 0,
3087
+ thread_id: typeof body?.thread_id === "string" ? body.thread_id : void 0,
3088
+ route_kind: body?.route_kind === "group" ? "group" : body?.route_kind === "dm" ? "dm" : void 0,
3089
+ priority: Number.isFinite(Number(body?.priority)) ? Number(body.priority) : void 0,
3090
+ status: body?.status === "active" || body?.status === "disabled" || body?.status === "failing" ? body.status : void 0
3091
+ });
3092
+ if (!patch) throw new Error("channel/to_target is invalid or unsupported");
3093
+ target = targetManager.update(targetId, patch);
3094
+ } else if (action === "disable_verified_target") {
3095
+ if (!Number.isInteger(targetId) || targetId <= 0) throw new Error("target_id is required");
3096
+ target = targetManager.markDisabled(targetId);
3097
+ } else if (action === "retest_verified_target") {
3098
+ if (!Number.isInteger(targetId) || targetId <= 0) throw new Error("target_id is required");
3099
+ target = targetManager.markVerified(targetId);
3100
+ } else if (action === "set_workspace_default_target") {
3101
+ if (!Number.isInteger(targetId) || targetId <= 0) throw new Error("target_id is required");
3102
+ target = targetManager.update(targetId, {
3103
+ scope_type: "workspace_default",
3104
+ scope_key: "default",
3105
+ route_kind: "group",
3106
+ status: "active",
3107
+ provenance: "workspace_default"
3108
+ });
3109
+ } else if (action === "import_binding_candidates") {
3110
+ importedTargets = importBindingCandidates(store, {
3111
+ limit: 100,
3112
+ status: body?.status === "active" ? "active" : body?.status === "failing" ? "failing" : "disabled",
3113
+ scope_type: body?.scope_type === "workspace_default" ? "workspace_default" : body?.scope_type === "owner" ? "owner" : void 0,
3114
+ route_kind: body?.route_kind === "group" ? "group" : body?.route_kind === "dm" ? "dm" : void 0,
3115
+ provenance: "binding_import_bulk"
3116
+ });
2921
3117
  } else {
2922
- throw new Error("action must be retry_intent, re_resolve_intent_binding, cancel_intent, or mark_binding_stale");
3118
+ throw new Error("unsupported human delivery action");
2923
3119
  }
2924
3120
  return {
2925
3121
  ok: true,
2926
3122
  action,
2927
3123
  intent,
2928
3124
  binding,
3125
+ target,
3126
+ importedTargets,
2929
3127
  humanDelivery: summarizeHumanDelivery(store, 20),
3128
+ recentVerifiedTargets: targetManager.listRecent(50),
3129
+ recentDeliveryAttempts: attemptManager.listRecent(50),
3130
+ importableBindingCandidates: listImportableBindingCandidates(store, 50),
2930
3131
  unresolvedIntents: intentManager.listByStatus("unresolved", 50),
2931
3132
  failedIntents: intentManager.listByStatus("failed", 50),
2932
3133
  acknowledgedIntents: intentManager.listByStatus("acknowledged", 50)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pingagent/sdk",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -35,8 +35,8 @@
35
35
  "uuid": "^11.0.0",
36
36
  "ws": "^8.0.0",
37
37
  "@pingagent/protocol": "0.1.1",
38
- "@pingagent/schemas": "0.1.4",
39
- "@pingagent/a2a": "0.1.1"
38
+ "@pingagent/a2a": "0.1.1",
39
+ "@pingagent/schemas": "0.1.4"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/better-sqlite3": "^7.6.0",