@pingagent/sdk 0.1.13 → 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.
package/bin/pingagent.js CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  TaskThreadManager,
25
25
  TaskHandoffManager,
26
26
  TrustPolicyAuditManager,
27
+ CollaborationEventManager,
27
28
  TrustRecommendationManager,
28
29
  getTrustRecommendationActionLabel,
29
30
  formatCapabilityCardSummary,
@@ -337,6 +338,7 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
337
338
  const taskHandoffManager = new TaskHandoffManager(store);
338
339
  const historyManager = new HistoryManager(store);
339
340
  const auditManager = new TrustPolicyAuditManager(store);
341
+ const collaborationEventManager = new CollaborationEventManager(store);
340
342
  const sessions = sessionManager.listRecentSessions(50);
341
343
  const bindings = readSessionBindings();
342
344
  const alerts = readSessionBindingAlerts();
@@ -370,6 +372,13 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
370
372
  const selectedAuditEvents = selectedSession
371
373
  ? auditManager.listBySession(selectedSession.session_key, 12)
372
374
  : [];
375
+ const selectedCollaborationEvents = selectedSession
376
+ ? collaborationEventManager.listBySession(selectedSession.session_key, 12)
377
+ : [];
378
+ const selectedPendingCollaborationEvents = selectedSession
379
+ ? collaborationEventManager.listPendingBySession(selectedSession.session_key, 12)
380
+ : [];
381
+ const pendingCollaborationEventsGlobal = collaborationEventManager.listPending(50);
373
382
  const selectedMessages = selectedSession?.conversation_id
374
383
  ? historyManager.listRecent(selectedSession.conversation_id, 12)
375
384
  : [];
@@ -403,16 +412,21 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
403
412
  sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
404
413
  policyPath: policy.path,
405
414
  policyDoc: policy.doc,
415
+ projectionPreset: policy.doc.collaboration_projection?.preset || 'balanced',
406
416
  sessions: sessionsWithMeta,
407
417
  tasks: taskManager.listRecent(30).map((task) => ({
408
418
  ...task,
409
419
  handoff: taskHandoffManager.get(task.task_id),
410
420
  })),
411
421
  auditEvents: auditManager.listRecent(40),
422
+ collaborationEvents: collaborationEventManager.listRecent(40),
412
423
  selectedSession,
413
424
  selectedSessionSummary,
414
425
  selectedTasks,
415
426
  selectedAuditEvents,
427
+ selectedCollaborationEvents,
428
+ selectedPendingCollaborationEvents,
429
+ pendingCollaborationEventsGlobal,
416
430
  selectedMessages,
417
431
  selectedHistoryPage,
418
432
  selectedHistorySearchQuery: historySearchQuery.trim(),
@@ -432,6 +446,9 @@ function renderHostTuiScreen(hostState, uiState) {
432
446
  const selectedSummary = hostState.selectedSessionSummary || null;
433
447
  const tasks = hostState.selectedTasks || [];
434
448
  const auditEvents = hostState.selectedAuditEvents || [];
449
+ const collaborationEvents = hostState.selectedCollaborationEvents || [];
450
+ const pendingCollaborationEvents = hostState.selectedPendingCollaborationEvents || [];
451
+ const pendingCollaborationEventsGlobal = hostState.pendingCollaborationEventsGlobal || [];
435
452
  const messages = hostState.selectedMessages || [];
436
453
  const recommendations = hostState.selectedRecommendations || [];
437
454
  const openRecommendation = recommendations.find((item) => item.status === 'open') || null;
@@ -442,6 +459,8 @@ function renderHostTuiScreen(hostState, uiState) {
442
459
  const view = uiState?.view || 'overview';
443
460
  const selectedTaskIndex = Math.max(0, Math.min(uiState?.selectedTaskIndex || 0, Math.max(0, tasks.length - 1)));
444
461
  const selectedTask = tasks[selectedTaskIndex] || null;
462
+ const selectedDecisionIndex = Math.max(0, Math.min(uiState?.selectedDecisionIndex || 0, Math.max(0, pendingCollaborationEventsGlobal.length - 1)));
463
+ const selectedDecision = pendingCollaborationEventsGlobal[selectedDecisionIndex] || null;
445
464
  const statusCountdown = uiState?.statusExpiresAt
446
465
  ? ` (${Math.max(0, Math.ceil((uiState.statusExpiresAt - Date.now()) / 1000))}s)`
447
466
  : '';
@@ -457,7 +476,7 @@ function renderHostTuiScreen(hostState, uiState) {
457
476
  `runtime_mode=${hostState.runtimeMode} receive_mode=${hostState.ingressRuntime?.receive_mode || 'webhook'} current_openclaw_chat=${hostState.activeChatSession || '(none)'}`,
458
477
  `ingress=${ingressLabel}${degraded ? ' action=[f] fix-now' : ''}`,
459
478
  uiState?.publicLinkUrl ? `public_link=${uiState.publicLinkUrl}` : null,
460
- `sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view}`,
479
+ `sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view} projection=${hostState.projectionPreset || 'balanced'}`,
461
480
  `policy=${hostState.policyPath}`,
462
481
  `chat_link_map=${hostState.sessionMapPath}`,
463
482
  `chat_link_alerts=${hostState.sessionBindingAlertsPath}`,
@@ -475,6 +494,7 @@ function renderHostTuiScreen(hostState, uiState) {
475
494
  lines.push('- a: approve selected pending contact');
476
495
  lines.push('- m: mark selected session as read');
477
496
  lines.push('- t: open task list view for selected session');
497
+ lines.push('- i: open global decision inbox');
478
498
  lines.push('- x: cancel selected task (in task views)');
479
499
  lines.push('- p: multiline reply prompt (detail view)');
480
500
  lines.push('- S: edit carry-forward summary (detail view)');
@@ -483,6 +503,8 @@ function renderHostTuiScreen(hostState, uiState) {
483
503
  lines.push('- n / p: older / newer history page (history view)');
484
504
  lines.push('- s or /: search local history (history view)');
485
505
  lines.push('- y: dump task detail to stdout (task-detail view, choose json/plain)');
506
+ lines.push('- u: approve the selected collaboration decision');
507
+ lines.push('- U: reject the selected collaboration decision');
486
508
  lines.push('- f: repair OpenClaw hooks config');
487
509
  lines.push('- A: apply first open trust recommendation for selected session');
488
510
  lines.push('- D: dismiss current open recommendation');
@@ -546,6 +568,17 @@ function renderHostTuiScreen(hostState, uiState) {
546
568
  lines.push('Tasks In Session');
547
569
  lines.push(...tasks.map((task, idx) => `${idx === selectedTaskIndex ? '>' : ' '} ${task.task_id} [${task.status}] ${truncateLine(task.title || task.result_summary || '', 70)}`));
548
570
  }
571
+ } else if (view === 'decisions') {
572
+ lines.push('Decision Inbox');
573
+ lines.push(`pending=${pendingCollaborationEventsGlobal.length}`);
574
+ lines.push('actions=[u] approve [U] reject [Enter/l] open-session [j/k] select [h/Esc] back');
575
+ lines.push('');
576
+ lines.push(...(pendingCollaborationEventsGlobal.length
577
+ ? pendingCollaborationEventsGlobal.map((event, idx) => {
578
+ const marker = idx === selectedDecisionIndex ? '>' : ' ';
579
+ return `${marker} ${event.id} [${event.severity}] ${truncateLine(event.summary || '(none)', 88)} session=${event.session_key || '(none)'}`;
580
+ })
581
+ : ['- none']));
549
582
  } else if (view === 'tasks') {
550
583
  lines.push('Task Detail');
551
584
  if (!selected) {
@@ -606,6 +639,10 @@ function renderHostTuiScreen(hostState, uiState) {
606
639
  } else {
607
640
  lines.push('summary_objective=(none)');
608
641
  }
642
+ if (pendingCollaborationEvents.length > 0) {
643
+ lines.push(`pending_collaboration_decisions=${pendingCollaborationEvents.length}`);
644
+ lines.push(`next_decision=${truncateLine(pendingCollaborationEvents[0].summary || '(none)', 100)}`);
645
+ }
609
646
  const actionBar = [
610
647
  selected.trust_state === 'pending' ? '[a] approve' : null,
611
648
  '[A] apply-rec',
@@ -615,6 +652,8 @@ function renderHostTuiScreen(hostState, uiState) {
615
652
  '[d] demo',
616
653
  '[p] reply',
617
654
  '[S] summary',
655
+ pendingCollaborationEvents.length ? '[u] approve-decision' : null,
656
+ pendingCollaborationEvents.length ? '[U] reject-decision' : null,
618
657
  '[o] history',
619
658
  '[t] tasks',
620
659
  '[b] attach-chat',
@@ -645,6 +684,11 @@ function renderHostTuiScreen(hostState, uiState) {
645
684
  lines.push(...(messages.length ? messages.map((message) => formatMessageRow(message)) : ['- none']));
646
685
  }
647
686
  lines.push('');
687
+ lines.push('Collaboration Feed');
688
+ lines.push(...(collaborationEvents.length
689
+ ? collaborationEvents.map((event) => `- ${event.event_type} [${event.severity}${event.approval_required ? `:${event.approval_status}` : ''}] ${truncateLine(event.summary || '', 90)}`)
690
+ : ['- none']));
691
+ lines.push('');
648
692
  lines.push('Audit');
649
693
  lines.push(...(auditEvents.length
650
694
  ? auditEvents.map((event) => `- ${event.event_type} ${event.action || event.outcome || ''} ${truncateLine(event.explanation || '', 80)}`)
@@ -655,7 +699,7 @@ function renderHostTuiScreen(hostState, uiState) {
655
699
  }
656
700
 
657
701
  lines.push('');
658
- lines.push('Keys: ↑/↓ or j/k select Enter/l open Esc/h back g/G jump r refresh a approve A apply-rec D dismiss-rec R reopen-rec d demo m read p reply o history s search t tasks x cancel-task y dump f fix-hooks b attach-chat c detach-chat ? help q quit');
702
+ lines.push('Keys: ↑/↓ or j/k select Enter/l open Esc/h back g/G jump r refresh a approve u/U decide A apply-rec D dismiss-rec R reopen-rec d demo m read p reply o history s search t tasks x cancel-task y dump f fix-hooks b attach-chat c detach-chat ? help q quit');
659
703
  return lines.join('\n');
660
704
  }
661
705
 
@@ -667,6 +711,7 @@ async function runHostTui(identityPath, opts) {
667
711
  selectedSessionKey: null,
668
712
  view: 'overview',
669
713
  selectedTaskIndex: 0,
714
+ selectedDecisionIndex: 0,
670
715
  statusMessage: '(ready)',
671
716
  statusLevel: 'info',
672
717
  statusExpiresAt: 0,
@@ -922,6 +967,10 @@ async function runHostTui(identityPath, opts) {
922
967
  const tasks = latestState.selectedTasks || [];
923
968
  if (!tasks.length) return;
924
969
  uiState.selectedTaskIndex = Math.min(tasks.length - 1, Math.max(0, uiState.selectedTaskIndex + delta));
970
+ } else if (uiState.view === 'decisions') {
971
+ const decisions = latestState.pendingCollaborationEventsGlobal || [];
972
+ if (!decisions.length) return;
973
+ uiState.selectedDecisionIndex = Math.min(decisions.length - 1, Math.max(0, uiState.selectedDecisionIndex + delta));
925
974
  } else if (uiState.view === 'history' && !uiState.historySearchQuery) {
926
975
  if (delta > 0 && latestState.selectedHistoryPage?.hasOlder) uiState.selectedHistoryPageIndex += 1;
927
976
  if (delta < 0 && latestState.selectedHistoryPage?.hasNewer) uiState.selectedHistoryPageIndex = Math.max(0, uiState.selectedHistoryPageIndex - 1);
@@ -943,6 +992,10 @@ async function runHostTui(identityPath, opts) {
943
992
  const tasks = latestState.selectedTasks || [];
944
993
  if (!tasks.length) return;
945
994
  uiState.selectedTaskIndex = target;
995
+ } else if (uiState.view === 'decisions') {
996
+ const decisions = latestState.pendingCollaborationEventsGlobal || [];
997
+ if (!decisions.length) return;
998
+ uiState.selectedDecisionIndex = target;
946
999
  } else if (uiState.view === 'history') {
947
1000
  uiState.selectedHistoryPageIndex = 0;
948
1001
  } else {
@@ -989,12 +1042,20 @@ async function runHostTui(identityPath, opts) {
989
1042
  if (_str === 'G') {
990
1043
  const max = uiState.view === 'tasks' || uiState.view === 'task-detail'
991
1044
  ? Math.max(0, (latestState.selectedTasks || []).length - 1)
992
- : Math.max(0, (latestState.sessions || []).length - 1);
1045
+ : uiState.view === 'decisions'
1046
+ ? Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)
1047
+ : Math.max(0, (latestState.sessions || []).length - 1);
993
1048
  return jumpSelection(max);
994
1049
  }
995
1050
  if (key?.name === 'return' || key?.name === 'enter' || key?.name === 'l') {
996
1051
  if (uiState.view === 'tasks') {
997
1052
  uiState.view = 'task-detail';
1053
+ } else if (uiState.view === 'decisions') {
1054
+ const decision = (latestState.pendingCollaborationEventsGlobal || [])[Math.max(0, Math.min(uiState.selectedDecisionIndex || 0, Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)))] || null;
1055
+ if (decision?.session_key) {
1056
+ uiState.selectedSessionKey = decision.session_key;
1057
+ uiState.view = 'detail';
1058
+ }
998
1059
  } else {
999
1060
  uiState.view = 'detail';
1000
1061
  }
@@ -1004,6 +1065,7 @@ async function runHostTui(identityPath, opts) {
1004
1065
  if (key?.name === 'escape' || key?.name === 'h') {
1005
1066
  if (uiState.view === 'task-detail') uiState.view = 'tasks';
1006
1067
  else if (uiState.view === 'history') uiState.view = 'detail';
1068
+ else if (uiState.view === 'decisions') uiState.view = 'overview';
1007
1069
  else uiState.view = 'overview';
1008
1070
  latestState = redraw();
1009
1071
  return;
@@ -1044,6 +1106,13 @@ async function runHostTui(identityPath, opts) {
1044
1106
  latestState = redraw();
1045
1107
  return;
1046
1108
  }
1109
+ if (key?.name === 'i') {
1110
+ uiState.view = 'decisions';
1111
+ uiState.selectedDecisionIndex = 0;
1112
+ setStatus('Opened decision inbox.', 'info');
1113
+ latestState = redraw();
1114
+ return;
1115
+ }
1047
1116
  if (key?.name === 'r') {
1048
1117
  setStatus('Refreshed.', 'info');
1049
1118
  latestState = redraw();
@@ -1083,6 +1152,45 @@ async function runHostTui(identityPath, opts) {
1083
1152
  latestState = redraw();
1084
1153
  return;
1085
1154
  }
1155
+ if (key?.name === 'u' || _str === 'U') {
1156
+ const approvalStatus = _str === 'U' ? 'rejected' : 'approved';
1157
+ const pendingEvent = uiState.view === 'decisions'
1158
+ ? (latestState.pendingCollaborationEventsGlobal || [])[Math.max(0, Math.min(uiState.selectedDecisionIndex || 0, Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)))] || null
1159
+ : (latestState.selectedPendingCollaborationEvents || [])[0];
1160
+ const selected = pendingEvent?.session_key
1161
+ ? (latestState.sessions || []).find((session) => session.session_key === pendingEvent.session_key) || latestState.selectedSession
1162
+ : latestState.selectedSession;
1163
+ if (!pendingEvent || !selected) return;
1164
+ const confirmed = await confirmAction(`${approvalStatus === 'approved' ? 'Approve' : 'Reject'} collaboration decision ${pendingEvent.id}\nSession: ${selected.session_key}\nSummary: ${pendingEvent.summary}\nProceed?`);
1165
+ if (!confirmed) {
1166
+ setStatus('Collaboration decision cancelled.', 'warn');
1167
+ latestState = redraw();
1168
+ return;
1169
+ }
1170
+ const store = openStore(identityPath);
1171
+ try {
1172
+ const manager = new CollaborationEventManager(store);
1173
+ const result = manager.resolveApproval(pendingEvent.id, approvalStatus, {
1174
+ resolved_by: 'tui',
1175
+ });
1176
+ if (!result) {
1177
+ setStatus(`Decision ${pendingEvent.id} no longer exists.`, 'err');
1178
+ } else {
1179
+ setStatus(
1180
+ approvalStatus === 'approved'
1181
+ ? `Approved collaboration decision ${pendingEvent.id}${result.projection_outbox ? ` · human-thread projection=${result.projection_outbox.status}` : ''}`
1182
+ : `Rejected collaboration decision ${pendingEvent.id}${result.projection_outbox ? ` · human-thread projection=${result.projection_outbox.status}` : ''}`,
1183
+ 'ok',
1184
+ );
1185
+ }
1186
+ } catch (error) {
1187
+ setStatus(`Collaboration decision failed: ${error?.message || 'unknown error'}`, 'err', 9000);
1188
+ } finally {
1189
+ store.close();
1190
+ }
1191
+ latestState = redraw();
1192
+ return;
1193
+ }
1086
1194
  if (_str === 'A') {
1087
1195
  const selected = latestState.selectedSession;
1088
1196
  if (!selected) return;