@pingagent/sdk 0.1.13 → 0.1.15
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 +312 -10
- package/dist/chunk-BCYHGKQE.js +4825 -0
- package/dist/chunk-MFKDD5X3.js +4235 -0
- package/dist/chunk-SAI2R63F.js +3923 -0
- package/dist/chunk-TWKCLIYT.js +4007 -0
- package/dist/index.d.ts +290 -18
- package/dist/index.js +39 -3
- package/dist/web-server.js +695 -6
- package/package.json +2 -2
package/bin/pingagent.js
CHANGED
|
@@ -24,12 +24,22 @@ import {
|
|
|
24
24
|
TaskThreadManager,
|
|
25
25
|
TaskHandoffManager,
|
|
26
26
|
TrustPolicyAuditManager,
|
|
27
|
+
CollaborationEventManager,
|
|
28
|
+
CollaborationProjectionOutboxManager,
|
|
29
|
+
OperatorSeenStateManager,
|
|
27
30
|
TrustRecommendationManager,
|
|
28
31
|
getTrustRecommendationActionLabel,
|
|
29
32
|
formatCapabilityCardSummary,
|
|
30
33
|
defaultTrustPolicyDoc,
|
|
31
34
|
normalizeTrustPolicyDoc,
|
|
32
35
|
upsertTrustPolicyRecommendation,
|
|
36
|
+
summarizeSinceLastSeen,
|
|
37
|
+
buildDeliveryTimeline,
|
|
38
|
+
buildProjectionPreview,
|
|
39
|
+
listPendingDecisionViews,
|
|
40
|
+
deriveTransportHealth,
|
|
41
|
+
readTransportPreference,
|
|
42
|
+
switchTransportPreference,
|
|
33
43
|
getActiveSessionFilePath,
|
|
34
44
|
getSessionMapFilePath,
|
|
35
45
|
getSessionBindingAlertsFilePath,
|
|
@@ -242,7 +252,16 @@ function formatSessionRow(session, selected) {
|
|
|
242
252
|
const trust = session.trust_state || 'unknown';
|
|
243
253
|
const unread = session.unread_count ?? 0;
|
|
244
254
|
const who = truncateLine(session.remote_did || session.conversation_id || 'unknown', 40);
|
|
245
|
-
|
|
255
|
+
const seen = session.since_last_seen || {};
|
|
256
|
+
const sinceCount = (seen.new_external_messages ?? 0)
|
|
257
|
+
+ (seen.new_conclusions ?? 0)
|
|
258
|
+
+ (seen.new_handoffs ?? 0)
|
|
259
|
+
+ (seen.new_decisions ?? 0)
|
|
260
|
+
+ (seen.new_failures ?? 0)
|
|
261
|
+
+ (seen.new_repairs ?? 0)
|
|
262
|
+
+ (seen.new_projection_failures ?? 0);
|
|
263
|
+
const sinceBadge = sinceCount > 0 ? ` new=${sinceCount}` : '';
|
|
264
|
+
return `${marker} ${who} [${trust}] unread=${unread}${sinceBadge}${reconnect}`;
|
|
246
265
|
}
|
|
247
266
|
|
|
248
267
|
function formatMessageRow(message) {
|
|
@@ -337,6 +356,7 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
337
356
|
const taskHandoffManager = new TaskHandoffManager(store);
|
|
338
357
|
const historyManager = new HistoryManager(store);
|
|
339
358
|
const auditManager = new TrustPolicyAuditManager(store);
|
|
359
|
+
const collaborationEventManager = new CollaborationEventManager(store);
|
|
340
360
|
const sessions = sessionManager.listRecentSessions(50);
|
|
341
361
|
const bindings = readSessionBindings();
|
|
342
362
|
const alerts = readSessionBindingAlerts();
|
|
@@ -344,6 +364,21 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
344
364
|
const alertByConversation = new Map(alerts.map((row) => [row.conversation_id, row]));
|
|
345
365
|
const activeChatSession = readCurrentActiveSessionKey();
|
|
346
366
|
const ingressRuntime = readIngressRuntimeStatus();
|
|
367
|
+
const transportPreference = readTransportPreference();
|
|
368
|
+
const transportHealth = deriveTransportHealth({
|
|
369
|
+
runtime_status: ingressRuntime
|
|
370
|
+
? {
|
|
371
|
+
...ingressRuntime,
|
|
372
|
+
preferred_transport_mode: transportPreference?.preferred_mode ?? ingressRuntime.preferred_transport_mode ?? 'bridge',
|
|
373
|
+
}
|
|
374
|
+
: {
|
|
375
|
+
receive_mode: 'webhook',
|
|
376
|
+
transport_mode: transportPreference?.preferred_mode ?? 'bridge',
|
|
377
|
+
preferred_transport_mode: transportPreference?.preferred_mode ?? 'bridge',
|
|
378
|
+
},
|
|
379
|
+
recent_events: collaborationEventManager.listRecent(30),
|
|
380
|
+
projection_outbox_failed: new CollaborationProjectionOutboxManager(store).listByStatus('failed', 20),
|
|
381
|
+
});
|
|
347
382
|
const desiredSelectedSessionKey =
|
|
348
383
|
(selectedSessionKey && sessions.some((session) => session.session_key === selectedSessionKey) ? selectedSessionKey : null) ??
|
|
349
384
|
sessionManager.getActiveSession()?.session_key ??
|
|
@@ -370,6 +405,13 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
370
405
|
const selectedAuditEvents = selectedSession
|
|
371
406
|
? auditManager.listBySession(selectedSession.session_key, 12)
|
|
372
407
|
: [];
|
|
408
|
+
const selectedCollaborationEvents = selectedSession
|
|
409
|
+
? collaborationEventManager.listBySession(selectedSession.session_key, 12)
|
|
410
|
+
: [];
|
|
411
|
+
const selectedPendingCollaborationEvents = selectedSession
|
|
412
|
+
? listPendingDecisionViews(store, 100).filter((event) => event.session_key === selectedSession.session_key).slice(0, 12)
|
|
413
|
+
: [];
|
|
414
|
+
const pendingCollaborationEventsGlobal = listPendingDecisionViews(store, 50);
|
|
373
415
|
const selectedMessages = selectedSession?.conversation_id
|
|
374
416
|
? historyManager.listRecent(selectedSession.conversation_id, 12)
|
|
375
417
|
: [];
|
|
@@ -393,26 +435,54 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
393
435
|
: [];
|
|
394
436
|
const unreadTotal = sessionsWithMeta.reduce((sum, session) => sum + (session.unread_count ?? 0), 0);
|
|
395
437
|
const alertSessions = sessionsWithMeta.filter((session) => !!session.binding_alert).length;
|
|
438
|
+
const sinceLastSeenGlobal = summarizeSinceLastSeen(store, {
|
|
439
|
+
operator_id: 'tui',
|
|
440
|
+
scope_type: 'global',
|
|
441
|
+
});
|
|
442
|
+
const sessionsWithSeen = sessionsWithMeta.map((session) => ({
|
|
443
|
+
...session,
|
|
444
|
+
since_last_seen: summarizeSinceLastSeen(store, {
|
|
445
|
+
operator_id: 'tui',
|
|
446
|
+
scope_type: 'session',
|
|
447
|
+
scope_key: session.session_key,
|
|
448
|
+
}),
|
|
449
|
+
}));
|
|
450
|
+
const selectedDeliveryTimeline = selectedSession
|
|
451
|
+
? buildDeliveryTimeline(store, selectedSession.session_key, 30)
|
|
452
|
+
: [];
|
|
453
|
+
const selectedProjectionPreview = selectedSession
|
|
454
|
+
? buildProjectionPreview(store, selectedSession.session_key, policy.doc.collaboration_projection?.preset || 'balanced', 5)
|
|
455
|
+
: null;
|
|
396
456
|
return {
|
|
397
457
|
identity,
|
|
398
458
|
runtimeMode,
|
|
399
459
|
activeChatSession,
|
|
400
460
|
ingressRuntime,
|
|
461
|
+
transportPreference,
|
|
462
|
+
transportHealth,
|
|
401
463
|
activeChatSessionFile: getActiveSessionFilePath(),
|
|
402
464
|
sessionMapPath: getSessionMapFilePath(),
|
|
403
465
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
404
466
|
policyPath: policy.path,
|
|
405
467
|
policyDoc: policy.doc,
|
|
406
|
-
|
|
468
|
+
projectionPreset: policy.doc.collaboration_projection?.preset || 'balanced',
|
|
469
|
+
sinceLastSeenGlobal,
|
|
470
|
+
sessions: sessionsWithSeen,
|
|
407
471
|
tasks: taskManager.listRecent(30).map((task) => ({
|
|
408
472
|
...task,
|
|
409
473
|
handoff: taskHandoffManager.get(task.task_id),
|
|
410
474
|
})),
|
|
411
475
|
auditEvents: auditManager.listRecent(40),
|
|
476
|
+
collaborationEvents: collaborationEventManager.listRecent(40),
|
|
412
477
|
selectedSession,
|
|
413
478
|
selectedSessionSummary,
|
|
414
479
|
selectedTasks,
|
|
415
480
|
selectedAuditEvents,
|
|
481
|
+
selectedCollaborationEvents,
|
|
482
|
+
selectedPendingCollaborationEvents,
|
|
483
|
+
selectedDeliveryTimeline,
|
|
484
|
+
selectedProjectionPreview,
|
|
485
|
+
pendingCollaborationEventsGlobal,
|
|
416
486
|
selectedMessages,
|
|
417
487
|
selectedHistoryPage,
|
|
418
488
|
selectedHistorySearchQuery: historySearchQuery.trim(),
|
|
@@ -432,7 +502,12 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
432
502
|
const selectedSummary = hostState.selectedSessionSummary || null;
|
|
433
503
|
const tasks = hostState.selectedTasks || [];
|
|
434
504
|
const auditEvents = hostState.selectedAuditEvents || [];
|
|
505
|
+
const collaborationEvents = hostState.selectedCollaborationEvents || [];
|
|
506
|
+
const pendingCollaborationEvents = hostState.selectedPendingCollaborationEvents || [];
|
|
507
|
+
const pendingCollaborationEventsGlobal = hostState.pendingCollaborationEventsGlobal || [];
|
|
435
508
|
const messages = hostState.selectedMessages || [];
|
|
509
|
+
const deliveryTimeline = hostState.selectedDeliveryTimeline || [];
|
|
510
|
+
const projectionPreview = hostState.selectedProjectionPreview || null;
|
|
436
511
|
const recommendations = hostState.selectedRecommendations || [];
|
|
437
512
|
const openRecommendation = recommendations.find((item) => item.status === 'open') || null;
|
|
438
513
|
const reopenRecommendation = recommendations.find((item) => item.status === 'dismissed' || item.status === 'superseded') || null;
|
|
@@ -442,6 +517,8 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
442
517
|
const view = uiState?.view || 'overview';
|
|
443
518
|
const selectedTaskIndex = Math.max(0, Math.min(uiState?.selectedTaskIndex || 0, Math.max(0, tasks.length - 1)));
|
|
444
519
|
const selectedTask = tasks[selectedTaskIndex] || null;
|
|
520
|
+
const selectedDecisionIndex = Math.max(0, Math.min(uiState?.selectedDecisionIndex || 0, Math.max(0, pendingCollaborationEventsGlobal.length - 1)));
|
|
521
|
+
const selectedDecision = pendingCollaborationEventsGlobal[selectedDecisionIndex] || null;
|
|
445
522
|
const statusCountdown = uiState?.statusExpiresAt
|
|
446
523
|
? ` (${Math.max(0, Math.ceil((uiState.statusExpiresAt - Date.now()) / 1000))}s)`
|
|
447
524
|
: '';
|
|
@@ -450,14 +527,18 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
450
527
|
|| hostState.ingressRuntime.receive_mode === 'polling_degraded'
|
|
451
528
|
|| !!hostState.ingressRuntime.hooks_last_error;
|
|
452
529
|
const ingressLabel = degraded ? 'Degraded' : 'Ready';
|
|
530
|
+
const transportHealth = hostState.transportHealth || { state: 'Ready', transport_mode: 'bridge', preferred_transport_mode: 'bridge', retry_queue_length: 0, consecutive_failures: 0 };
|
|
531
|
+
const sinceLastSeenGlobal = hostState.sinceLastSeenGlobal || {};
|
|
453
532
|
const lines = [
|
|
454
533
|
'PingAgent Host TUI',
|
|
455
534
|
`DID: ${hostState.identity.did}`,
|
|
456
535
|
`status=${formatStatusLine(uiState?.statusLevel || 'info', uiState?.statusMessage || '(ready)')}${statusTs}${statusCountdown}`,
|
|
457
536
|
`runtime_mode=${hostState.runtimeMode} receive_mode=${hostState.ingressRuntime?.receive_mode || 'webhook'} current_openclaw_chat=${hostState.activeChatSession || '(none)'}`,
|
|
458
537
|
`ingress=${ingressLabel}${degraded ? ' action=[f] fix-now' : ''}`,
|
|
538
|
+
`transport=${transportHealth.transport_mode} preferred=${transportHealth.preferred_transport_mode} state=${transportHealth.state} retry_queue=${transportHealth.retry_queue_length} failures=${transportHealth.consecutive_failures}`,
|
|
459
539
|
uiState?.publicLinkUrl ? `public_link=${uiState.publicLinkUrl}` : null,
|
|
460
|
-
`sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view}`,
|
|
540
|
+
`sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view} projection=${hostState.projectionPreset || 'balanced'}`,
|
|
541
|
+
`since_last_seen external=${sinceLastSeenGlobal.new_external_messages ?? 0} conclusions=${sinceLastSeenGlobal.new_conclusions ?? 0} decisions=${sinceLastSeenGlobal.new_decisions ?? 0} failures=${sinceLastSeenGlobal.new_failures ?? 0}`,
|
|
461
542
|
`policy=${hostState.policyPath}`,
|
|
462
543
|
`chat_link_map=${hostState.sessionMapPath}`,
|
|
463
544
|
`chat_link_alerts=${hostState.sessionBindingAlertsPath}`,
|
|
@@ -475,6 +556,7 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
475
556
|
lines.push('- a: approve selected pending contact');
|
|
476
557
|
lines.push('- m: mark selected session as read');
|
|
477
558
|
lines.push('- t: open task list view for selected session');
|
|
559
|
+
lines.push('- i: open global decision inbox');
|
|
478
560
|
lines.push('- x: cancel selected task (in task views)');
|
|
479
561
|
lines.push('- p: multiline reply prompt (detail view)');
|
|
480
562
|
lines.push('- S: edit carry-forward summary (detail view)');
|
|
@@ -483,12 +565,16 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
483
565
|
lines.push('- n / p: older / newer history page (history view)');
|
|
484
566
|
lines.push('- s or /: search local history (history view)');
|
|
485
567
|
lines.push('- y: dump task detail to stdout (task-detail view, choose json/plain)');
|
|
568
|
+
lines.push('- u: approve the selected collaboration decision');
|
|
569
|
+
lines.push('- U: reject the selected collaboration decision');
|
|
570
|
+
lines.push('- b: switch preferred transport to bridge');
|
|
571
|
+
lines.push('- c: switch preferred transport to channel');
|
|
486
572
|
lines.push('- f: repair OpenClaw hooks config');
|
|
487
573
|
lines.push('- A: apply first open trust recommendation for selected session');
|
|
488
574
|
lines.push('- D: dismiss current open recommendation');
|
|
489
575
|
lines.push('- R: reopen dismissed/superseded recommendation');
|
|
490
|
-
lines.push('-
|
|
491
|
-
lines.push('-
|
|
576
|
+
lines.push('- B: attach selected session to the current OpenClaw chat');
|
|
577
|
+
lines.push('- C: detach the selected chat link');
|
|
492
578
|
lines.push('- q: quit');
|
|
493
579
|
} else if (view === 'history') {
|
|
494
580
|
lines.push('Conversation History');
|
|
@@ -546,6 +632,17 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
546
632
|
lines.push('Tasks In Session');
|
|
547
633
|
lines.push(...tasks.map((task, idx) => `${idx === selectedTaskIndex ? '>' : ' '} ${task.task_id} [${task.status}] ${truncateLine(task.title || task.result_summary || '', 70)}`));
|
|
548
634
|
}
|
|
635
|
+
} else if (view === 'decisions') {
|
|
636
|
+
lines.push('Decision Inbox');
|
|
637
|
+
lines.push(`pending=${pendingCollaborationEventsGlobal.length}`);
|
|
638
|
+
lines.push('actions=[u] approve [U] reject [Enter/l] open-session [j/k] select [h/Esc] back');
|
|
639
|
+
lines.push('');
|
|
640
|
+
lines.push(...(pendingCollaborationEventsGlobal.length
|
|
641
|
+
? pendingCollaborationEventsGlobal.map((event, idx) => {
|
|
642
|
+
const marker = idx === selectedDecisionIndex ? '>' : ' ';
|
|
643
|
+
return `${marker} ${event.id} [${event.severity}${event.overdue ? ':overdue' : ''}] ${truncateLine(event.summary || '(none)', 88)} session=${event.session_key || '(none)'}`;
|
|
644
|
+
})
|
|
645
|
+
: ['- none']));
|
|
549
646
|
} else if (view === 'tasks') {
|
|
550
647
|
lines.push('Task Detail');
|
|
551
648
|
if (!selected) {
|
|
@@ -606,6 +703,15 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
606
703
|
} else {
|
|
607
704
|
lines.push('summary_objective=(none)');
|
|
608
705
|
}
|
|
706
|
+
if (pendingCollaborationEvents.length > 0) {
|
|
707
|
+
lines.push(`pending_collaboration_decisions=${pendingCollaborationEvents.length}`);
|
|
708
|
+
lines.push(`next_decision=${truncateLine(pendingCollaborationEvents[0].summary || '(none)', 100)}`);
|
|
709
|
+
if (pendingCollaborationEvents[0].overdue) {
|
|
710
|
+
lines.push(`next_decision_overdue=${Math.ceil((pendingCollaborationEvents[0].overdue_by_ms || 0) / 60000)}m`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const sessionSeen = selected.since_last_seen || {};
|
|
714
|
+
lines.push(`since_last_seen external=${sessionSeen.new_external_messages ?? 0} conclusions=${sessionSeen.new_conclusions ?? 0} decisions=${sessionSeen.new_decisions ?? 0} failures=${sessionSeen.new_failures ?? 0}`);
|
|
609
715
|
const actionBar = [
|
|
610
716
|
selected.trust_state === 'pending' ? '[a] approve' : null,
|
|
611
717
|
'[A] apply-rec',
|
|
@@ -615,10 +721,12 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
615
721
|
'[d] demo',
|
|
616
722
|
'[p] reply',
|
|
617
723
|
'[S] summary',
|
|
724
|
+
pendingCollaborationEvents.length ? '[u] approve-decision' : null,
|
|
725
|
+
pendingCollaborationEvents.length ? '[U] reject-decision' : null,
|
|
618
726
|
'[o] history',
|
|
619
727
|
'[t] tasks',
|
|
620
|
-
'[
|
|
621
|
-
'[
|
|
728
|
+
'[B] attach-chat',
|
|
729
|
+
'[C] detach-chat',
|
|
622
730
|
].filter(Boolean).join(' ');
|
|
623
731
|
lines.push(`actions=${actionBar}`);
|
|
624
732
|
lines.push('');
|
|
@@ -645,6 +753,25 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
645
753
|
lines.push(...(messages.length ? messages.map((message) => formatMessageRow(message)) : ['- none']));
|
|
646
754
|
}
|
|
647
755
|
lines.push('');
|
|
756
|
+
lines.push('Collaboration Feed');
|
|
757
|
+
lines.push(...(collaborationEvents.length
|
|
758
|
+
? collaborationEvents.map((event) => `- ${event.event_type} [${event.severity}${event.approval_required ? `:${event.approval_status}` : ''}] ${truncateLine(event.summary || '', 90)}`)
|
|
759
|
+
: ['- none']));
|
|
760
|
+
if (projectionPreview) {
|
|
761
|
+
lines.push('');
|
|
762
|
+
lines.push(`Projection Preview [preset=${projectionPreview.preset}]`);
|
|
763
|
+
lines.push(...(projectionPreview.recent.length
|
|
764
|
+
? projectionPreview.recent.map((entry) => `- ${entry.event_type} => ${entry.disposition} (${truncateLine(entry.reason, 70)})`)
|
|
765
|
+
: ['- none']));
|
|
766
|
+
}
|
|
767
|
+
if (view === 'detail') {
|
|
768
|
+
lines.push('');
|
|
769
|
+
lines.push('Delivery Timeline');
|
|
770
|
+
lines.push(...(deliveryTimeline.length
|
|
771
|
+
? deliveryTimeline.slice(0, 10).map((entry) => `- ${formatTs(entry.ts_ms, false)} ${entry.kind} ${truncateLine(entry.summary || '', 72)}`)
|
|
772
|
+
: ['- none']));
|
|
773
|
+
}
|
|
774
|
+
lines.push('');
|
|
648
775
|
lines.push('Audit');
|
|
649
776
|
lines.push(...(auditEvents.length
|
|
650
777
|
? auditEvents.map((event) => `- ${event.event_type} ${event.action || event.outcome || ''} ${truncateLine(event.explanation || '', 80)}`)
|
|
@@ -655,7 +782,7 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
655
782
|
}
|
|
656
783
|
|
|
657
784
|
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
|
|
785
|
+
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 transport->bridge c transport->channel B attach-chat C detach-chat ? help q quit');
|
|
659
786
|
return lines.join('\n');
|
|
660
787
|
}
|
|
661
788
|
|
|
@@ -667,6 +794,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
667
794
|
selectedSessionKey: null,
|
|
668
795
|
view: 'overview',
|
|
669
796
|
selectedTaskIndex: 0,
|
|
797
|
+
selectedDecisionIndex: 0,
|
|
670
798
|
statusMessage: '(ready)',
|
|
671
799
|
statusLevel: 'info',
|
|
672
800
|
statusExpiresAt: 0,
|
|
@@ -675,6 +803,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
675
803
|
historySearchQuery: '',
|
|
676
804
|
publicLinkUrl: '',
|
|
677
805
|
};
|
|
806
|
+
const seenState = {
|
|
807
|
+
globalView: null,
|
|
808
|
+
sessionKey: null,
|
|
809
|
+
};
|
|
678
810
|
|
|
679
811
|
const render = () => {
|
|
680
812
|
if (uiState.statusExpiresAt && Date.now() >= uiState.statusExpiresAt) {
|
|
@@ -717,7 +849,49 @@ async function runHostTui(identityPath, opts) {
|
|
|
717
849
|
return rendered.hostState;
|
|
718
850
|
};
|
|
719
851
|
|
|
852
|
+
const markSeen = (scopeType, scopeKey = null) => {
|
|
853
|
+
const store = openStore(identityPath);
|
|
854
|
+
try {
|
|
855
|
+
return new OperatorSeenStateManager(store).markSeen({
|
|
856
|
+
operator_id: 'tui',
|
|
857
|
+
scope_type: scopeType,
|
|
858
|
+
scope_key: scopeType === 'session' ? (scopeKey || null) : null,
|
|
859
|
+
last_seen_ts: Date.now(),
|
|
860
|
+
});
|
|
861
|
+
} finally {
|
|
862
|
+
store.close();
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const refreshSeenMarkers = (hostState, options = {}) => {
|
|
867
|
+
let changed = false;
|
|
868
|
+
const forceGlobal = !!options.forceGlobal;
|
|
869
|
+
const forceSession = !!options.forceSession;
|
|
870
|
+
if (uiState.view === 'overview' || uiState.view === 'decisions') {
|
|
871
|
+
if (forceGlobal || seenState.globalView !== uiState.view) {
|
|
872
|
+
markSeen('global');
|
|
873
|
+
seenState.globalView = uiState.view;
|
|
874
|
+
changed = true;
|
|
875
|
+
}
|
|
876
|
+
if (uiState.view === 'overview') seenState.sessionKey = null;
|
|
877
|
+
} else if (['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view)) {
|
|
878
|
+
const sessionKey = hostState.selectedSession?.session_key || null;
|
|
879
|
+
if (sessionKey && (forceSession || seenState.sessionKey !== sessionKey)) {
|
|
880
|
+
markSeen('session', sessionKey);
|
|
881
|
+
seenState.sessionKey = sessionKey;
|
|
882
|
+
changed = true;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return changed;
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const rerenderAfterSeenUpdate = (hostState, options = {}) => {
|
|
889
|
+
if (!refreshSeenMarkers(hostState, options)) return hostState;
|
|
890
|
+
return redraw();
|
|
891
|
+
};
|
|
892
|
+
|
|
720
893
|
let latestState = redraw();
|
|
894
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: true });
|
|
721
895
|
readline.emitKeypressEvents(process.stdin);
|
|
722
896
|
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
723
897
|
|
|
@@ -734,6 +908,50 @@ async function runHostTui(identityPath, opts) {
|
|
|
734
908
|
uiState.statusAt = Date.now();
|
|
735
909
|
};
|
|
736
910
|
|
|
911
|
+
const switchTransport = async (mode) => {
|
|
912
|
+
const label = mode === 'channel' ? 'channel' : 'bridge';
|
|
913
|
+
const confirmed = await confirmAction(`Switch preferred transport to ${label} and request a managed restart?`);
|
|
914
|
+
if (!confirmed) {
|
|
915
|
+
setStatus(`Transport switch to ${label} cancelled.`, 'warn');
|
|
916
|
+
latestState = redraw();
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
const result = switchTransportPreference(mode, {
|
|
921
|
+
updated_by: 'tui',
|
|
922
|
+
});
|
|
923
|
+
const store = openStore(identityPath);
|
|
924
|
+
try {
|
|
925
|
+
new CollaborationEventManager(store).record({
|
|
926
|
+
event_type: 'transport_switched',
|
|
927
|
+
severity: result.restarted ? 'notice' : 'warning',
|
|
928
|
+
summary: result.restarted
|
|
929
|
+
? `Preferred transport switched to ${label}. A managed restart was attempted.`
|
|
930
|
+
: `Preferred transport switched to ${label}. Restart is still required.`,
|
|
931
|
+
detail: {
|
|
932
|
+
preferred_mode: result.preferred_mode,
|
|
933
|
+
restart_required: result.restart_required,
|
|
934
|
+
restart_method: result.restart_method ?? null,
|
|
935
|
+
restart_error: result.restart_error ?? null,
|
|
936
|
+
preference_path: result.preference_path,
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
} finally {
|
|
940
|
+
store.close();
|
|
941
|
+
}
|
|
942
|
+
setStatus(
|
|
943
|
+
result.restarted
|
|
944
|
+
? `Preferred transport is now ${label}; managed restart attempted via ${result.restart_method || 'unknown method'}.`
|
|
945
|
+
: `Preferred transport saved as ${label}; restart still required.`,
|
|
946
|
+
result.restarted ? 'ok' : 'warn',
|
|
947
|
+
result.restarted ? 7000 : 9000,
|
|
948
|
+
);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
setStatus(`Transport switch failed: ${error?.message || 'unknown error'}`, 'err', 9000);
|
|
951
|
+
}
|
|
952
|
+
latestState = redraw();
|
|
953
|
+
};
|
|
954
|
+
|
|
737
955
|
const applySessionRecommendation = (selected) => {
|
|
738
956
|
if (!selected?.remote_did) return { ok: false, message: 'No remote DID for selected session.' };
|
|
739
957
|
const store = openStore(identityPath);
|
|
@@ -922,6 +1140,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
922
1140
|
const tasks = latestState.selectedTasks || [];
|
|
923
1141
|
if (!tasks.length) return;
|
|
924
1142
|
uiState.selectedTaskIndex = Math.min(tasks.length - 1, Math.max(0, uiState.selectedTaskIndex + delta));
|
|
1143
|
+
} else if (uiState.view === 'decisions') {
|
|
1144
|
+
const decisions = latestState.pendingCollaborationEventsGlobal || [];
|
|
1145
|
+
if (!decisions.length) return;
|
|
1146
|
+
uiState.selectedDecisionIndex = Math.min(decisions.length - 1, Math.max(0, uiState.selectedDecisionIndex + delta));
|
|
925
1147
|
} else if (uiState.view === 'history' && !uiState.historySearchQuery) {
|
|
926
1148
|
if (delta > 0 && latestState.selectedHistoryPage?.hasOlder) uiState.selectedHistoryPageIndex += 1;
|
|
927
1149
|
if (delta < 0 && latestState.selectedHistoryPage?.hasNewer) uiState.selectedHistoryPageIndex = Math.max(0, uiState.selectedHistoryPageIndex - 1);
|
|
@@ -936,6 +1158,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
936
1158
|
uiState.historySearchQuery = '';
|
|
937
1159
|
}
|
|
938
1160
|
latestState = redraw();
|
|
1161
|
+
latestState = rerenderAfterSeenUpdate(latestState, {
|
|
1162
|
+
forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions',
|
|
1163
|
+
forceSession: ['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view),
|
|
1164
|
+
});
|
|
939
1165
|
};
|
|
940
1166
|
|
|
941
1167
|
const jumpSelection = (target) => {
|
|
@@ -943,6 +1169,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
943
1169
|
const tasks = latestState.selectedTasks || [];
|
|
944
1170
|
if (!tasks.length) return;
|
|
945
1171
|
uiState.selectedTaskIndex = target;
|
|
1172
|
+
} else if (uiState.view === 'decisions') {
|
|
1173
|
+
const decisions = latestState.pendingCollaborationEventsGlobal || [];
|
|
1174
|
+
if (!decisions.length) return;
|
|
1175
|
+
uiState.selectedDecisionIndex = target;
|
|
946
1176
|
} else if (uiState.view === 'history') {
|
|
947
1177
|
uiState.selectedHistoryPageIndex = 0;
|
|
948
1178
|
} else {
|
|
@@ -954,6 +1184,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
954
1184
|
uiState.historySearchQuery = '';
|
|
955
1185
|
}
|
|
956
1186
|
latestState = redraw();
|
|
1187
|
+
latestState = rerenderAfterSeenUpdate(latestState, {
|
|
1188
|
+
forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions',
|
|
1189
|
+
forceSession: ['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view),
|
|
1190
|
+
});
|
|
957
1191
|
};
|
|
958
1192
|
|
|
959
1193
|
const stopInterval = () => {
|
|
@@ -989,23 +1223,34 @@ async function runHostTui(identityPath, opts) {
|
|
|
989
1223
|
if (_str === 'G') {
|
|
990
1224
|
const max = uiState.view === 'tasks' || uiState.view === 'task-detail'
|
|
991
1225
|
? Math.max(0, (latestState.selectedTasks || []).length - 1)
|
|
992
|
-
:
|
|
1226
|
+
: uiState.view === 'decisions'
|
|
1227
|
+
? Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)
|
|
1228
|
+
: Math.max(0, (latestState.sessions || []).length - 1);
|
|
993
1229
|
return jumpSelection(max);
|
|
994
1230
|
}
|
|
995
1231
|
if (key?.name === 'return' || key?.name === 'enter' || key?.name === 'l') {
|
|
996
1232
|
if (uiState.view === 'tasks') {
|
|
997
1233
|
uiState.view = 'task-detail';
|
|
1234
|
+
} else if (uiState.view === 'decisions') {
|
|
1235
|
+
const decision = (latestState.pendingCollaborationEventsGlobal || [])[Math.max(0, Math.min(uiState.selectedDecisionIndex || 0, Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)))] || null;
|
|
1236
|
+
if (decision?.session_key) {
|
|
1237
|
+
uiState.selectedSessionKey = decision.session_key;
|
|
1238
|
+
uiState.view = 'detail';
|
|
1239
|
+
}
|
|
998
1240
|
} else {
|
|
999
1241
|
uiState.view = 'detail';
|
|
1000
1242
|
}
|
|
1001
1243
|
latestState = redraw();
|
|
1244
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: uiState.view !== 'decisions' && uiState.view !== 'overview' });
|
|
1002
1245
|
return;
|
|
1003
1246
|
}
|
|
1004
1247
|
if (key?.name === 'escape' || key?.name === 'h') {
|
|
1005
1248
|
if (uiState.view === 'task-detail') uiState.view = 'tasks';
|
|
1006
1249
|
else if (uiState.view === 'history') uiState.view = 'detail';
|
|
1250
|
+
else if (uiState.view === 'decisions') uiState.view = 'overview';
|
|
1007
1251
|
else uiState.view = 'overview';
|
|
1008
1252
|
latestState = redraw();
|
|
1253
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions' });
|
|
1009
1254
|
return;
|
|
1010
1255
|
}
|
|
1011
1256
|
if (_str === '?') {
|
|
@@ -1042,6 +1287,15 @@ async function runHostTui(identityPath, opts) {
|
|
|
1042
1287
|
uiState.selectedHistoryPageIndex = 0;
|
|
1043
1288
|
setStatus('Opened task list view.', 'info');
|
|
1044
1289
|
latestState = redraw();
|
|
1290
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: true });
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (key?.name === 'i') {
|
|
1294
|
+
uiState.view = 'decisions';
|
|
1295
|
+
uiState.selectedDecisionIndex = 0;
|
|
1296
|
+
setStatus('Opened decision inbox.', 'info');
|
|
1297
|
+
latestState = redraw();
|
|
1298
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: true });
|
|
1045
1299
|
return;
|
|
1046
1300
|
}
|
|
1047
1301
|
if (key?.name === 'r') {
|
|
@@ -1083,6 +1337,45 @@ async function runHostTui(identityPath, opts) {
|
|
|
1083
1337
|
latestState = redraw();
|
|
1084
1338
|
return;
|
|
1085
1339
|
}
|
|
1340
|
+
if (key?.name === 'u' || _str === 'U') {
|
|
1341
|
+
const approvalStatus = _str === 'U' ? 'rejected' : 'approved';
|
|
1342
|
+
const pendingEvent = uiState.view === 'decisions'
|
|
1343
|
+
? (latestState.pendingCollaborationEventsGlobal || [])[Math.max(0, Math.min(uiState.selectedDecisionIndex || 0, Math.max(0, (latestState.pendingCollaborationEventsGlobal || []).length - 1)))] || null
|
|
1344
|
+
: (latestState.selectedPendingCollaborationEvents || [])[0];
|
|
1345
|
+
const selected = pendingEvent?.session_key
|
|
1346
|
+
? (latestState.sessions || []).find((session) => session.session_key === pendingEvent.session_key) || latestState.selectedSession
|
|
1347
|
+
: latestState.selectedSession;
|
|
1348
|
+
if (!pendingEvent || !selected) return;
|
|
1349
|
+
const confirmed = await confirmAction(`${approvalStatus === 'approved' ? 'Approve' : 'Reject'} collaboration decision ${pendingEvent.id}\nSession: ${selected.session_key}\nSummary: ${pendingEvent.summary}\nProceed?`);
|
|
1350
|
+
if (!confirmed) {
|
|
1351
|
+
setStatus('Collaboration decision cancelled.', 'warn');
|
|
1352
|
+
latestState = redraw();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const store = openStore(identityPath);
|
|
1356
|
+
try {
|
|
1357
|
+
const manager = new CollaborationEventManager(store);
|
|
1358
|
+
const result = manager.resolveApproval(pendingEvent.id, approvalStatus, {
|
|
1359
|
+
resolved_by: 'tui',
|
|
1360
|
+
});
|
|
1361
|
+
if (!result) {
|
|
1362
|
+
setStatus(`Decision ${pendingEvent.id} no longer exists.`, 'err');
|
|
1363
|
+
} else {
|
|
1364
|
+
setStatus(
|
|
1365
|
+
approvalStatus === 'approved'
|
|
1366
|
+
? `Approved collaboration decision ${pendingEvent.id}${result.projection_outbox ? ` · human-thread projection=${result.projection_outbox.status}` : ''}`
|
|
1367
|
+
: `Rejected collaboration decision ${pendingEvent.id}${result.projection_outbox ? ` · human-thread projection=${result.projection_outbox.status}` : ''}`,
|
|
1368
|
+
'ok',
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
setStatus(`Collaboration decision failed: ${error?.message || 'unknown error'}`, 'err', 9000);
|
|
1373
|
+
} finally {
|
|
1374
|
+
store.close();
|
|
1375
|
+
}
|
|
1376
|
+
latestState = redraw();
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1086
1379
|
if (_str === 'A') {
|
|
1087
1380
|
const selected = latestState.selectedSession;
|
|
1088
1381
|
if (!selected) return;
|
|
@@ -1185,6 +1478,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1185
1478
|
uiState.historySearchQuery = '';
|
|
1186
1479
|
setStatus('Opened local history view.', 'info');
|
|
1187
1480
|
latestState = redraw();
|
|
1481
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: true });
|
|
1188
1482
|
return;
|
|
1189
1483
|
}
|
|
1190
1484
|
if ((_str === '/' || key?.name === 's') && uiState.view === 'history') {
|
|
@@ -1197,6 +1491,14 @@ async function runHostTui(identityPath, opts) {
|
|
|
1197
1491
|
return;
|
|
1198
1492
|
}
|
|
1199
1493
|
if (key?.name === 'b') {
|
|
1494
|
+
await switchTransport('bridge');
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
if (key?.name === 'c') {
|
|
1498
|
+
await switchTransport('channel');
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
if (_str === 'B') {
|
|
1200
1502
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1201
1503
|
if (!selected?.conversation_id) return;
|
|
1202
1504
|
const current = latestState.activeChatSession || '(none)';
|
|
@@ -1216,7 +1518,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1216
1518
|
latestState = redraw();
|
|
1217
1519
|
return;
|
|
1218
1520
|
}
|
|
1219
|
-
if (
|
|
1521
|
+
if (_str === 'C') {
|
|
1220
1522
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1221
1523
|
if (!selected?.conversation_id) return;
|
|
1222
1524
|
removeSessionBinding(selected.conversation_id);
|