@pingagent/sdk 0.1.12 → 0.1.14

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.
@@ -1,4 +1,6 @@
1
1
  import {
2
+ CollaborationEventManager,
3
+ CollaborationProjectionOutboxManager,
2
4
  ContactManager,
3
5
  LocalStore,
4
6
  PingAgentClient,
@@ -23,7 +25,7 @@ import {
23
25
  summarizeTrustPolicyAudit,
24
26
  updateStoredToken,
25
27
  upsertTrustPolicyRecommendation
26
- } from "./chunk-BLHMTUID.js";
28
+ } from "./chunk-MFKDD5X3.js";
27
29
 
28
30
  // src/web-server.ts
29
31
  import * as fs from "fs";
@@ -182,6 +184,7 @@ function getHostPanelHtml() {
182
184
  <div class="profile-list" id="profileList"></div>
183
185
  <div class="nav">
184
186
  <button id="navRuntime" class="active">Runtime</button>
187
+ <button id="navDecisions">Decisions</button>
185
188
  <button id="navPolicy">Policy</button>
186
189
  </div>
187
190
  <div class="link-row">
@@ -236,8 +239,39 @@ function getHostPanelHtml() {
236
239
  </div>
237
240
  </section>
238
241
 
242
+ <section id="decisionsPanel" class="panel">
243
+ <div class="grid two-col">
244
+ <div class="card">
245
+ <h2>Decision Inbox</h2>
246
+ <div id="decisionInboxSummary" class="muted small" style="margin-bottom:12px">Loading pending decisions\u2026</div>
247
+ <div class="audit-list" id="decisionInboxList"></div>
248
+ </div>
249
+ <div class="card">
250
+ <h2>Projection Delivery</h2>
251
+ <div id="projectionOutboxSummary" class="muted small" style="margin-bottom:12px">Loading projection delivery state\u2026</div>
252
+ <div class="audit-list" id="projectionOutboxList"></div>
253
+ </div>
254
+ </div>
255
+ </section>
256
+
239
257
  <section id="policyPanel" class="panel">
240
258
  <div class="grid policy-grid">
259
+ <div class="card">
260
+ <h2>Projection Policy</h2>
261
+ <div class="form-grid">
262
+ <label class="label">Preset</label>
263
+ <select id="projectionPreset">
264
+ <option value="quiet">quiet</option>
265
+ <option value="balanced">balanced</option>
266
+ <option value="strict">strict</option>
267
+ </select>
268
+ <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>
269
+ <div class="row-actions">
270
+ <button class="action-btn" id="saveProjectionPresetBtn">Save projection preset</button>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
241
275
  <div class="card">
242
276
  <h2>Policy Defaults</h2>
243
277
  <div class="form-grid">
@@ -357,7 +391,7 @@ function getHostPanelHtml() {
357
391
  return {
358
392
  profile: profile && profile.trim() ? profile.trim() : null,
359
393
  sessionKey: sessionKey && sessionKey.trim() ? sessionKey.trim() : null,
360
- view: view === 'policy' ? 'policy' : 'runtime',
394
+ view: view === 'policy' ? 'policy' : (view === 'decisions' ? 'decisions' : 'runtime'),
361
395
  };
362
396
  })();
363
397
 
@@ -367,6 +401,7 @@ function getHostPanelHtml() {
367
401
  profiles: [],
368
402
  overview: null,
369
403
  session: null,
404
+ decisions: null,
370
405
  policy: null,
371
406
  selectedSessionKey: initialQuery.sessionKey || null,
372
407
  detailMode: sessionStorage.getItem('pingagent_host_panel_detail_mode') || 'basic',
@@ -400,7 +435,7 @@ function getHostPanelHtml() {
400
435
  else url.searchParams.delete('profile');
401
436
  if (state.selectedSessionKey) url.searchParams.set('session_key', state.selectedSessionKey);
402
437
  else url.searchParams.delete('session_key');
403
- url.searchParams.set('view', state.currentTab === 'policy' ? 'policy' : 'runtime');
438
+ url.searchParams.set('view', state.currentTab === 'policy' ? 'policy' : (state.currentTab === 'decisions' ? 'decisions' : 'runtime'));
404
439
  history.replaceState(null, '', url.pathname + (url.search ? url.search : ''));
405
440
  }
406
441
 
@@ -479,8 +514,10 @@ function getHostPanelHtml() {
479
514
  function setTab(tab) {
480
515
  state.currentTab = tab;
481
516
  document.getElementById('navRuntime').classList.toggle('active', tab === 'runtime');
517
+ document.getElementById('navDecisions').classList.toggle('active', tab === 'decisions');
482
518
  document.getElementById('navPolicy').classList.toggle('active', tab === 'policy');
483
519
  document.getElementById('runtimePanel').classList.toggle('active', tab === 'runtime');
520
+ document.getElementById('decisionsPanel').classList.toggle('active', tab === 'decisions');
484
521
  document.getElementById('policyPanel').classList.toggle('active', tab === 'policy');
485
522
  syncUrlState();
486
523
  }
@@ -572,6 +609,109 @@ function getHostPanelHtml() {
572
609
  '</div>';
573
610
  }
574
611
 
612
+ function renderCollaborationEventsBlock(events, isAdvanced) {
613
+ if (!events || !events.length) {
614
+ return '<div class="empty">No collaboration events recorded yet. New external updates, runtime shifts, and decision points will appear here.</div>';
615
+ }
616
+ return events.map(function (event) {
617
+ const detail = event && event.detail ? event.detail : null;
618
+ const resolution = detail && detail.approval_resolution ? detail.approval_resolution : null;
619
+ const badges = [
620
+ '<span class="badge">' + esc(event.severity || 'info') + '</span>',
621
+ event.approval_required
622
+ ? '<span class="badge">' + esc(event.approval_status === 'pending' ? 'review' : event.approval_status) + '</span>'
623
+ : '',
624
+ event.target_human_session ? '<span class="badge">human thread</span>' : '',
625
+ ].filter(Boolean).join('');
626
+ const actions = event.approval_required && event.approval_status === 'pending'
627
+ ? '<div class="row-actions" style="margin-top:10px">' +
628
+ '<button class="action-btn collaboration-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="approved">Approve</button>' +
629
+ '<button class="danger-btn collaboration-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="rejected">Reject</button>' +
630
+ '</div>'
631
+ : '';
632
+ const resolutionLine = resolution
633
+ ? '<div class="muted small" style="margin-top:8px">resolution=' + esc(resolution.approval_status || event.approval_status || '(unknown)') +
634
+ ' \xB7 resolved_at=' + esc(fmtTs(resolution.resolved_at)) +
635
+ (resolution.resolved_by ? ' \xB7 resolved_by=' + esc(resolution.resolved_by) : '') +
636
+ (resolution.note ? ' \xB7 note=' + esc(resolution.note) : '') +
637
+ '</div>'
638
+ : '';
639
+ return '<div class="audit-row"><div class="top"><strong>' + esc(event.event_type) + '</strong>' + badges + '</div>' +
640
+ '<div class="muted small">' + esc(fmtTs(event.ts_ms)) + '</div>' +
641
+ '<div style="margin-top:8px">' + esc(event.summary || '(no summary)') + '</div>' +
642
+ '<div class="muted small" style="margin-top:8px">detail_ref=' + esc(detail && detail.detail_ref && detail.detail_ref.session_key ? detail.detail_ref.session_key : (event.session_key || '(none)')) + ' \xB7 conversation=' + esc(event.conversation_id || '(none)') + '</div>' +
643
+ resolutionLine +
644
+ actions +
645
+ (isAdvanced && detail ? '<pre style="margin-top:8px">' + esc(JSON.stringify(detail, null, 2)) + '</pre>' : '') +
646
+ '</div>';
647
+ }).join('');
648
+ }
649
+
650
+ function renderDecisionInbox() {
651
+ const data = state.decisions;
652
+ const pendingDecisions = data && Array.isArray(data.pendingDecisions) ? data.pendingDecisions : [];
653
+ const pendingOutbox = data && Array.isArray(data.projectionOutboxPending) ? data.projectionOutboxPending : [];
654
+ const failedOutbox = data && Array.isArray(data.projectionOutboxFailed) ? data.projectionOutboxFailed : [];
655
+ document.getElementById('decisionInboxSummary').textContent =
656
+ 'pending=' + pendingDecisions.length + ' \xB7 projection_pending=' + pendingOutbox.length + ' \xB7 projection_failed=' + failedOutbox.length;
657
+ document.getElementById('decisionInboxList').innerHTML = pendingDecisions.length
658
+ ? pendingDecisions.map(function (event) {
659
+ return '<div class="audit-row"><div class="top"><strong>' + esc(event.summary || '(no summary)') + '</strong>' +
660
+ '<span class="badge">' + esc(event.severity || 'warning') + '</span></div>' +
661
+ '<div class="muted small">' + esc(fmtTs(event.ts_ms)) + ' \xB7 session=' + esc(event.session_key || '(none)') + ' \xB7 target=' + esc(event.target_human_session || '(none)') + '</div>' +
662
+ '<div class="muted small" style="margin-top:8px">detail_ref=' + esc(event.conversation_id || '(none)') + '</div>' +
663
+ '<div class="row-actions">' +
664
+ '<button class="action-btn inbox-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="approved">Approve</button>' +
665
+ '<button class="danger-btn inbox-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="rejected">Reject</button>' +
666
+ '<button class="secondary-btn inbox-open-detail-btn" data-session-key="' + esc(event.session_key || '') + '">Open Detail</button>' +
667
+ '</div>' +
668
+ '</div>';
669
+ }).join('')
670
+ : '<div class="empty">No pending collaboration decisions.</div>';
671
+
672
+ const combinedOutbox = pendingOutbox.concat(failedOutbox);
673
+ document.getElementById('projectionOutboxSummary').textContent =
674
+ combinedOutbox.length
675
+ ? 'Human-thread projection uses a stable outbox. Failed rows stay visible until delivery recovers.'
676
+ : 'No undelivered human-thread projections.';
677
+ document.getElementById('projectionOutboxList').innerHTML = combinedOutbox.length
678
+ ? combinedOutbox.map(function (entry) {
679
+ return '<div class="audit-row"><div class="top"><strong>' + esc(entry.summary || '(no summary)') + '</strong>' +
680
+ '<span class="badge">' + esc(entry.status || 'pending') + '</span></div>' +
681
+ '<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>' +
682
+ '<div class="muted small" style="margin-top:8px">target=' + esc(entry.target_human_session || '(missing)') + ' \xB7 session=' + esc(entry.session_key || '(none)') + '</div>' +
683
+ (entry.last_error ? '<div style="margin-top:8px">' + esc(entry.last_error) + '</div>' : '') +
684
+ '</div>';
685
+ }).join('')
686
+ : '<div class="empty">No pending or failed projection deliveries.</div>';
687
+
688
+ document.querySelectorAll('.inbox-decision-btn').forEach(function (btn) {
689
+ btn.addEventListener('click', async function () {
690
+ const eventId = Number(btn.getAttribute('data-event-id'));
691
+ const decision = btn.getAttribute('data-decision');
692
+ if (!Number.isInteger(eventId) || !decision) return;
693
+ await api('/api/runtime/collaboration-decisions/resolve', {
694
+ method: 'POST',
695
+ headers: { 'Content-Type': 'application/json' },
696
+ body: JSON.stringify({ event_id: eventId, decision: decision }),
697
+ });
698
+ await refreshAll();
699
+ setTab('decisions');
700
+ });
701
+ });
702
+
703
+ document.querySelectorAll('.inbox-open-detail-btn').forEach(function (btn) {
704
+ btn.addEventListener('click', async function () {
705
+ const sessionKey = btn.getAttribute('data-session-key');
706
+ if (!sessionKey) return;
707
+ state.selectedSessionKey = sessionKey;
708
+ await loadSession(sessionKey);
709
+ renderOverview();
710
+ setTab('runtime');
711
+ });
712
+ });
713
+ }
714
+
575
715
  function getOverviewSessions() {
576
716
  return state.overview && Array.isArray(state.overview.sessions) ? state.overview.sessions : [];
577
717
  }
@@ -638,6 +778,7 @@ function getHostPanelHtml() {
638
778
  { label: 'Unread', value: overview.unreadTotal, sub: 'session-first inbox state' },
639
779
  { label: 'Tasks', value: overview.tasksTotal, sub: 'recent local task threads' },
640
780
  { label: 'Audit', value: overview.auditSummary.total_events, sub: 'policy / runtime audit events' },
781
+ { label: 'Collaboration', value: overview.collaborationSummary ? overview.collaborationSummary.total_events : 0, sub: overview.collaborationSummary ? ('pending_review=' + overview.collaborationSummary.pending_approvals) : 'projected external collaboration events' },
641
782
  { label: 'Recommendations', value: overview.recommendationSummary ? overview.recommendationSummary.total : overview.recommendations.length, sub: overview.recommendationSummary ? JSON.stringify(overview.recommendationSummary.by_status || {}) : 'learned policy suggestions' },
642
783
  { 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' },
643
784
  ];
@@ -783,6 +924,8 @@ conversation=' + result.conversation_id));
783
924
  const tasks = Array.isArray(detail.tasks) ? detail.tasks : [];
784
925
  const messages = Array.isArray(detail.messages) ? detail.messages : [];
785
926
  const auditEvents = Array.isArray(detail.auditEvents) ? detail.auditEvents : [];
927
+ const collaborationEvents = Array.isArray(detail.collaborationEvents) ? detail.collaborationEvents : [];
928
+ const pendingCollaborationEvents = Array.isArray(detail.pendingCollaborationEvents) ? detail.pendingCollaborationEvents : [];
786
929
  const recommendations = Array.isArray(detail.recommendations) ? detail.recommendations : [];
787
930
  const openRecommendation = recommendations.find(function (item) { return item.status === 'open'; }) || null;
788
931
  const reopenRecommendation = recommendations.find(function (item) { return item.status === 'dismissed' || item.status === 'superseded'; }) || null;
@@ -790,6 +933,10 @@ conversation=' + result.conversation_id));
790
933
  const bindingAlert = detail.bindingAlert || null;
791
934
  const activeWorkSession = detail.activeWorkSession || null;
792
935
  const summary = detail.sessionSummary || null;
936
+ const projectionPreset = detail.collaborationProjection && detail.collaborationProjection.preset
937
+ ? detail.collaborationProjection.preset
938
+ : 'balanced';
939
+ const projectionOutbox = Array.isArray(detail.projectionOutbox) ? detail.projectionOutbox : [];
793
940
  const isAdvanced = state.detailMode === 'advanced';
794
941
  const sessionLink = buildSessionLink(session.session_key);
795
942
  const summaryPills = [];
@@ -824,6 +971,9 @@ conversation=' + result.conversation_id));
824
971
  ? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(openRecommendation)) + '</div>'
825
972
  : (reopenRecommendation ? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(reopenRecommendation)) + '</div>' : '')) +
826
973
  (summaryPills.length ? '<div class="summary-pills">' + summaryPills.join('') + '</div>' : '') +
974
+ (pendingCollaborationEvents.length
975
+ ? '<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.</div></div>'
976
+ : '') +
827
977
  (bindingAlert
828
978
  ? '<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>'
829
979
  : '') +
@@ -919,6 +1069,11 @@ conversation=' + result.conversation_id));
919
1069
  return '<div class="message-row"><div class="muted small">' + esc(fmtTs(msg.ts_ms)) + ' \xB7 ' + esc(msg.direction) + ' \xB7 ' + esc(msg.schema) + '</div><div style="margin-top:8px">' + esc(messageSummary) + '</div></div>';
920
1070
  }).join('') : '<div class="empty">No local message history yet.</div>') +
921
1071
  '</div></div>' +
1072
+ '<div><div class="label">Collaboration Feed</div><div class="audit-list" style="margin-top:8px">' +
1073
+ renderCollaborationEventsBlock(collaborationEvents, isAdvanced) +
1074
+ '</div></div>' +
1075
+ '</div>' +
1076
+ '<div class="grid two-col" style="margin-top:16px">' +
922
1077
  '<div><div class="label">Policy Audit</div><div class="audit-list" style="margin-top:8px">' +
923
1078
  (auditEvents.length ? auditEvents.map(function (event) {
924
1079
  return '<div class="audit-row"><div class="top"><strong>' + esc(event.event_type) + '</strong><span class="badge">' + esc(event.action || event.outcome || '-') + '</span></div>' +
@@ -927,6 +1082,15 @@ conversation=' + result.conversation_id));
927
1082
  '</div>';
928
1083
  }).join('') : '<div class="empty">No audit events for this session.</div>') +
929
1084
  '</div></div>' +
1085
+ '<div><div class="label">Human Thread Posture</div><div class="audit-list" style="margin-top:8px">' +
1086
+ '<div class="audit-row"><div class="top"><strong>Projection policy</strong><span class="badge">' + esc(projectionPreset) + '</span></div>' +
1087
+ '<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>' +
1088
+ '<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>' +
1089
+ (projectionOutbox.length
1090
+ ? '<div class="muted small" style="margin-top:8px">outbox=' + esc(projectionOutbox[0].status || 'pending') + ' \xB7 target=' + esc(projectionOutbox[0].target_human_session || '(missing)') + '</div>'
1091
+ : '<div class="muted small" style="margin-top:8px">outbox=clear</div>') +
1092
+ '</div>' +
1093
+ '</div></div>' +
930
1094
  '</div>';
931
1095
 
932
1096
  el.querySelectorAll('.approve-session-btn').forEach(function (btn) {
@@ -1014,6 +1178,21 @@ conversation=' + result.conversation_id));
1014
1178
  setTab('runtime');
1015
1179
  });
1016
1180
  });
1181
+ el.querySelectorAll('.collaboration-decision-btn').forEach(function (btn) {
1182
+ btn.addEventListener('click', async function () {
1183
+ const eventId = Number(btn.getAttribute('data-event-id'));
1184
+ const decision = btn.getAttribute('data-decision');
1185
+ if (!Number.isInteger(eventId) || !decision) return;
1186
+ await api('/api/runtime/session/collaboration-decision', {
1187
+ method: 'POST',
1188
+ headers: { 'Content-Type': 'application/json' },
1189
+ body: JSON.stringify({ event_id: eventId, decision: decision }),
1190
+ });
1191
+ await refreshAll();
1192
+ if (state.selectedSessionKey) await loadSession(state.selectedSessionKey);
1193
+ setTab('runtime');
1194
+ });
1195
+ });
1017
1196
  const sendSessionReplyBtn = document.getElementById('sendSessionReplyBtn');
1018
1197
  if (sendSessionReplyBtn) {
1019
1198
  sendSessionReplyBtn.addEventListener('click', async function () {
@@ -1089,6 +1268,10 @@ Previous chat link: ' + previous
1089
1268
  const policy = state.policy;
1090
1269
  if (!policy) return;
1091
1270
  const profile = state.overview && state.overview.profile ? state.overview.profile : null;
1271
+ document.getElementById('projectionPreset').value =
1272
+ policy.doc && policy.doc.collaboration_projection && policy.doc.collaboration_projection.preset
1273
+ ? policy.doc.collaboration_projection.preset
1274
+ : 'balanced';
1092
1275
  document.getElementById('contactDefault').value = policy.doc.contact_policy.default_action;
1093
1276
  document.getElementById('taskDefault').value = policy.doc.task_policy.default_action;
1094
1277
  document.getElementById('profileDisplayName').value = profile && profile.display_name ? profile.display_name : '';
@@ -1282,17 +1465,24 @@ Previous chat link: ' + previous
1282
1465
  renderPolicy();
1283
1466
  }
1284
1467
 
1468
+ async function loadDecisions() {
1469
+ state.decisions = await api('/api/runtime/collaboration-decisions');
1470
+ renderDecisionInbox();
1471
+ }
1472
+
1285
1473
  async function refreshAll() {
1286
1474
  if (!state.selectedProfile && state.profiles.length > 1) {
1287
1475
  renderHeader();
1288
1476
  return;
1289
1477
  }
1290
1478
  await loadOverview();
1479
+ await loadDecisions();
1291
1480
  await loadPolicy();
1292
1481
  renderHeader();
1293
1482
  }
1294
1483
 
1295
1484
  document.getElementById('navRuntime').addEventListener('click', function () { setTab('runtime'); });
1485
+ document.getElementById('navDecisions').addEventListener('click', function () { setTab('decisions'); });
1296
1486
  document.getElementById('navPolicy').addEventListener('click', function () { setTab('policy'); });
1297
1487
  document.getElementById('toggleUnreadBtn').addEventListener('click', async function () {
1298
1488
  state.showUnreadOnly = !state.showUnreadOnly;
@@ -1331,6 +1521,15 @@ Previous chat link: ' + previous
1331
1521
  await refreshAll();
1332
1522
  setTab('policy');
1333
1523
  });
1524
+ document.getElementById('saveProjectionPresetBtn').addEventListener('click', async function () {
1525
+ await api('/api/runtime/policy/projection', {
1526
+ method: 'POST',
1527
+ headers: { 'Content-Type': 'application/json' },
1528
+ body: JSON.stringify({ preset: document.getElementById('projectionPreset').value }),
1529
+ });
1530
+ await refreshAll();
1531
+ setTab('policy');
1532
+ });
1334
1533
  document.getElementById('addRuleBtn').addEventListener('click', async function () {
1335
1534
  await api('/api/runtime/policy/rules', {
1336
1535
  method: 'POST',
@@ -1678,6 +1877,26 @@ function writeTrustPolicyDoc(identityPath, doc) {
1678
1877
  fs.writeFileSync(policyPath, JSON.stringify(normalizeTrustPolicyDoc(doc), null, 2), "utf-8");
1679
1878
  return policyPath;
1680
1879
  }
1880
+ function normalizeProjectionPreset(value) {
1881
+ if (value === "quiet" || value === "strict") return value;
1882
+ return "balanced";
1883
+ }
1884
+ function readProjectionOutboxState(storePath, limit = 20) {
1885
+ const store = new LocalStore(storePath);
1886
+ try {
1887
+ const manager = new CollaborationProjectionOutboxManager(store);
1888
+ const pending = manager.listByStatus("pending", limit);
1889
+ const failed = manager.listByStatus("failed", limit);
1890
+ return {
1891
+ pending,
1892
+ failed,
1893
+ total_pending: pending.length,
1894
+ total_failed: failed.length
1895
+ };
1896
+ } finally {
1897
+ store.close();
1898
+ }
1899
+ }
1681
1900
  function buildPolicyDecisionShape(identityPath, remoteDid, opts) {
1682
1901
  const policy = readTrustPolicyDoc(identityPath);
1683
1902
  const runtimeMode = opts?.runtimeMode ?? getRuntimeMode();
@@ -1741,7 +1960,8 @@ async function buildRuntimeOverviewPayload(ctx) {
1741
1960
  const taskManager = client.getTaskThreadManager();
1742
1961
  const taskHandoffManager = client.getTaskHandoffManager();
1743
1962
  const historyManager = client.getHistoryManager();
1744
- if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager) {
1963
+ const collaborationEventManager = client.getCollaborationEventManager();
1964
+ if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager || !collaborationEventManager) {
1745
1965
  throw new Error("Runtime overview requires a writable local store");
1746
1966
  }
1747
1967
  const sessions = sessionManager.listRecentSessions(24);
@@ -1787,6 +2007,8 @@ async function buildRuntimeOverviewPayload(ctx) {
1787
2007
  acc[session.trust_state] = (acc[session.trust_state] ?? 0) + 1;
1788
2008
  return acc;
1789
2009
  }, {});
2010
+ const collaborationSummary = collaborationEventManager.summarize(200);
2011
+ const projectionOutbox = readProjectionOutboxState(ctx.storePath, 20);
1790
2012
  return {
1791
2013
  did: ctx.myDid,
1792
2014
  serverUrl: ctx.serverUrl,
@@ -1816,18 +2038,23 @@ async function buildRuntimeOverviewPayload(ctx) {
1816
2038
  contact: policy.contact_policy.enabled ? policy.contact_policy.default_action : "disabled",
1817
2039
  task: policy.task_policy.enabled ? policy.task_policy.default_action : "disabled"
1818
2040
  },
2041
+ collaborationProjection: policy.collaboration_projection,
1819
2042
  sessionsTotal: sessions.length,
1820
2043
  tasksTotal: refreshedTasks.length,
1821
2044
  unreadTotal,
1822
2045
  trustCounts,
1823
2046
  recommendationSummary: recommendationState.summary,
2047
+ collaborationSummary,
2048
+ projectionOutbox,
2049
+ recentCollaborationEvents: collaborationEventManager.listRecent(20),
1824
2050
  sessions: sessions.map((session) => ({
1825
2051
  ...session,
1826
2052
  session_summary: sessionSummaryManager.get(session.session_key),
1827
2053
  mapped_work_session: session.conversation_id ? bindingByConversation.get(session.conversation_id) ?? null : null,
1828
2054
  binding_alert: session.conversation_id ? bindingAlertByConversation.get(session.conversation_id) ?? null : null,
1829
2055
  is_active_work_session: session.session_key === activeWorkSession,
1830
- latest_messages: session.conversation_id ? historyManager.listRecent(session.conversation_id, 3) : []
2056
+ latest_messages: session.conversation_id ? historyManager.listRecent(session.conversation_id, 3) : [],
2057
+ collaboration_events: collaborationEventManager.listBySession(session.session_key, 3)
1831
2058
  })),
1832
2059
  tasks: refreshedTasks.map((task) => ({
1833
2060
  ...task,
@@ -1847,7 +2074,8 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
1847
2074
  const taskManager = client.getTaskThreadManager();
1848
2075
  const taskHandoffManager = client.getTaskHandoffManager();
1849
2076
  const historyManager = client.getHistoryManager();
1850
- if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager) {
2077
+ const collaborationEventManager = client.getCollaborationEventManager();
2078
+ if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager || !collaborationEventManager) {
1851
2079
  throw new Error("Session overview requires a writable local store");
1852
2080
  }
1853
2081
  const session = sessionKey ? sessionManager.get(sessionKey) : sessionManager.getActiveSession() ?? sessionManager.listRecentSessions(1)[0] ?? null;
@@ -1881,9 +2109,19 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
1881
2109
  runtimeMode: getRuntimeMode(),
1882
2110
  limit: 20
1883
2111
  });
2112
+ const outboxStore = new LocalStore(ctx.storePath);
2113
+ let projectionOutbox = [];
2114
+ try {
2115
+ projectionOutbox = new CollaborationProjectionOutboxManager(outboxStore).listBySession(session.session_key, 20);
2116
+ } finally {
2117
+ outboxStore.close();
2118
+ }
1884
2119
  return {
1885
2120
  session,
1886
2121
  sessionSummary: sessionSummaryManager.get(session.session_key),
2122
+ collaborationEvents: collaborationEventManager.listBySession(session.session_key, 40),
2123
+ pendingCollaborationEvents: collaborationEventManager.listPendingBySession(session.session_key, 20),
2124
+ collaborationSummary: collaborationEventManager.summarize(200),
1887
2125
  ingressRuntime: readIngressRuntimeStatus(),
1888
2126
  binding,
1889
2127
  bindingAlert,
@@ -1891,6 +2129,8 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
1891
2129
  activeWorkSessionFile: getActiveSessionFilePath(),
1892
2130
  sessionMapPath: getSessionMapFilePath(),
1893
2131
  sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
2132
+ collaborationProjection: policy.collaboration_projection,
2133
+ projectionOutbox,
1894
2134
  policyExplain: buildPolicyDecisionShape(ctx.identityPath, session.remote_did, { runtimeMode: getRuntimeMode() }),
1895
2135
  tasks: tasks.map((task) => ({
1896
2136
  ...task,
@@ -2016,9 +2256,48 @@ async function handleApi(pathname, req, ctx) {
2016
2256
  receiveMode: readIngressRuntimeStatus()
2017
2257
  };
2018
2258
  }
2259
+ if (parts[1] === "collaboration-decisions") {
2260
+ const store = new LocalStore(ctx.storePath);
2261
+ try {
2262
+ const manager = new CollaborationEventManager(store);
2263
+ const outboxManager = new CollaborationProjectionOutboxManager(store);
2264
+ if (req.method === "GET") {
2265
+ const pendingDecisions = manager.listPending(100);
2266
+ return {
2267
+ pendingDecisions,
2268
+ projectionOutboxPending: outboxManager.listByStatus("pending", 50),
2269
+ projectionOutboxFailed: outboxManager.listByStatus("failed", 50)
2270
+ };
2271
+ }
2272
+ if (parts[2] === "resolve" && req.method === "POST") {
2273
+ const body = await readBody(req);
2274
+ const eventId = Number(body?.event_id);
2275
+ const decision = String(body?.decision ?? "").trim().toLowerCase();
2276
+ if (!Number.isInteger(eventId) || eventId <= 0) throw new Error("event_id is required");
2277
+ if (decision !== "approved" && decision !== "rejected") {
2278
+ throw new Error("decision must be approved or rejected");
2279
+ }
2280
+ const result = manager.resolveApproval(eventId, decision, {
2281
+ resolved_by: "host_panel",
2282
+ note: typeof body?.note === "string" ? body.note.trim() || void 0 : void 0
2283
+ });
2284
+ if (!result) throw new Error(`Collaboration event ${eventId} not found`);
2285
+ return {
2286
+ ok: true,
2287
+ event: result.updated,
2288
+ resolutionEvent: result.resolution_event,
2289
+ projectionOutbox: result.projection_outbox
2290
+ };
2291
+ }
2292
+ } finally {
2293
+ store.close();
2294
+ }
2295
+ }
2019
2296
  if (parts[1] === "session") {
2020
2297
  const sessionManager = ctx.client.getSessionManager();
2298
+ const collaborationEventManager = ctx.client.getCollaborationEventManager();
2021
2299
  if (!sessionManager) throw new Error("Session actions require a writable local store");
2300
+ if (!collaborationEventManager) throw new Error("Collaboration actions require a writable local store");
2022
2301
  if (parts[2] === "reply" && req.method === "POST") {
2023
2302
  const body = await readBody(req);
2024
2303
  const session = resolveSessionForInput(sessionManager, body);
@@ -2059,6 +2338,28 @@ async function handleApi(pathname, req, ctx) {
2059
2338
  session: updated
2060
2339
  };
2061
2340
  }
2341
+ if (parts[2] === "collaboration-decision" && req.method === "POST") {
2342
+ const body = await readBody(req);
2343
+ const eventId = Number(body?.event_id);
2344
+ const decision = String(body?.decision ?? "").trim().toLowerCase();
2345
+ if (!Number.isInteger(eventId) || eventId <= 0) throw new Error("event_id is required");
2346
+ if (decision !== "approved" && decision !== "rejected") {
2347
+ throw new Error("decision must be approved or rejected");
2348
+ }
2349
+ const result = collaborationEventManager.resolveApproval(eventId, decision, {
2350
+ resolved_by: "host_panel",
2351
+ note: typeof body?.note === "string" ? body.note.trim() || void 0 : void 0
2352
+ });
2353
+ if (!result) throw new Error(`Collaboration event ${eventId} not found`);
2354
+ const session = result.updated.session_key ? sessionManager.get(result.updated.session_key) : null;
2355
+ return {
2356
+ ok: true,
2357
+ event: result.updated,
2358
+ resolutionEvent: result.resolution_event,
2359
+ projectionOutbox: result.projection_outbox,
2360
+ session
2361
+ };
2362
+ }
2062
2363
  const url = new URL(req.url || "", "http://x");
2063
2364
  const sessionKey = url.searchParams.get("session_key");
2064
2365
  return buildSessionOverviewPayload(ctx, sessionKey);
@@ -2186,6 +2487,39 @@ async function handleApi(pathname, req, ctx) {
2186
2487
  }
2187
2488
  return { ok: true, path: savedPath, doc };
2188
2489
  }
2490
+ if (parts[2] === "projection") {
2491
+ if (req.method === "GET") {
2492
+ const doc = readTrustPolicyDoc(ctx.identityPath);
2493
+ return {
2494
+ path: policyPath,
2495
+ projection: doc.collaboration_projection
2496
+ };
2497
+ }
2498
+ if (req.method === "POST") {
2499
+ const body = await readBody(req);
2500
+ const doc = readTrustPolicyDoc(ctx.identityPath);
2501
+ doc.collaboration_projection = {
2502
+ preset: normalizeProjectionPreset(body?.preset)
2503
+ };
2504
+ const savedPath = writeTrustPolicyDoc(ctx.identityPath, doc);
2505
+ const auditStore = new LocalStore(ctx.storePath);
2506
+ try {
2507
+ new TrustPolicyAuditManager(auditStore).record({
2508
+ event_type: "policy_default_updated",
2509
+ policy_scope: "session",
2510
+ action: "collaboration_projection_updated",
2511
+ outcome: "saved",
2512
+ explanation: `Updated projection preset=${doc.collaboration_projection.preset}`,
2513
+ detail: {
2514
+ preset: doc.collaboration_projection.preset
2515
+ }
2516
+ });
2517
+ } finally {
2518
+ auditStore.close();
2519
+ }
2520
+ return { ok: true, path: savedPath, projection: doc.collaboration_projection, doc };
2521
+ }
2522
+ }
2189
2523
  if (parts[2] === "rules" && req.method === "POST") {
2190
2524
  const body = await readBody(req);
2191
2525
  const doc = readTrustPolicyDoc(ctx.identityPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pingagent/sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -36,7 +36,7 @@
36
36
  "ws": "^8.0.0",
37
37
  "@pingagent/protocol": "0.1.1",
38
38
  "@pingagent/a2a": "0.1.1",
39
- "@pingagent/schemas": "0.1.2"
39
+ "@pingagent/schemas": "0.1.4"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/better-sqlite3": "^7.6.0",