@pingagent/sdk 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pingagent.js +250 -11
- package/dist/chunk-BCYHGKQE.js +4825 -0
- package/dist/chunk-GU5W4KRD.js +5847 -0
- package/dist/chunk-HSVC3KDT.js +5611 -0
- package/dist/index.d.ts +415 -17
- package/dist/index.js +65 -3
- package/dist/web-server.js +597 -27
- package/package.json +3 -3
package/bin/pingagent.js
CHANGED
|
@@ -25,12 +25,24 @@ import {
|
|
|
25
25
|
TaskHandoffManager,
|
|
26
26
|
TrustPolicyAuditManager,
|
|
27
27
|
CollaborationEventManager,
|
|
28
|
+
CollaborationProjectionOutboxManager,
|
|
29
|
+
NotificationIntentManager,
|
|
30
|
+
OperatorSeenStateManager,
|
|
28
31
|
TrustRecommendationManager,
|
|
29
32
|
getTrustRecommendationActionLabel,
|
|
30
33
|
formatCapabilityCardSummary,
|
|
31
34
|
defaultTrustPolicyDoc,
|
|
32
35
|
normalizeTrustPolicyDoc,
|
|
33
36
|
upsertTrustPolicyRecommendation,
|
|
37
|
+
summarizeSinceLastSeen,
|
|
38
|
+
buildDeliveryTimeline,
|
|
39
|
+
buildProjectionPreview,
|
|
40
|
+
listPendingDecisionViews,
|
|
41
|
+
summarizeHumanDelivery,
|
|
42
|
+
listRecentBindingsForSession,
|
|
43
|
+
deriveTransportHealth,
|
|
44
|
+
readTransportPreference,
|
|
45
|
+
switchTransportPreference,
|
|
34
46
|
getActiveSessionFilePath,
|
|
35
47
|
getSessionMapFilePath,
|
|
36
48
|
getSessionBindingAlertsFilePath,
|
|
@@ -243,7 +255,16 @@ function formatSessionRow(session, selected) {
|
|
|
243
255
|
const trust = session.trust_state || 'unknown';
|
|
244
256
|
const unread = session.unread_count ?? 0;
|
|
245
257
|
const who = truncateLine(session.remote_did || session.conversation_id || 'unknown', 40);
|
|
246
|
-
|
|
258
|
+
const seen = session.since_last_seen || {};
|
|
259
|
+
const sinceCount = (seen.new_external_messages ?? 0)
|
|
260
|
+
+ (seen.new_conclusions ?? 0)
|
|
261
|
+
+ (seen.new_handoffs ?? 0)
|
|
262
|
+
+ (seen.new_decisions ?? 0)
|
|
263
|
+
+ (seen.new_failures ?? 0)
|
|
264
|
+
+ (seen.new_repairs ?? 0)
|
|
265
|
+
+ (seen.new_projection_failures ?? 0);
|
|
266
|
+
const sinceBadge = sinceCount > 0 ? ` new=${sinceCount}` : '';
|
|
267
|
+
return `${marker} ${who} [${trust}] unread=${unread}${sinceBadge}${reconnect}`;
|
|
247
268
|
}
|
|
248
269
|
|
|
249
270
|
function formatMessageRow(message) {
|
|
@@ -346,6 +367,21 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
346
367
|
const alertByConversation = new Map(alerts.map((row) => [row.conversation_id, row]));
|
|
347
368
|
const activeChatSession = readCurrentActiveSessionKey();
|
|
348
369
|
const ingressRuntime = readIngressRuntimeStatus();
|
|
370
|
+
const transportPreference = readTransportPreference();
|
|
371
|
+
const transportHealth = deriveTransportHealth({
|
|
372
|
+
runtime_status: ingressRuntime
|
|
373
|
+
? {
|
|
374
|
+
...ingressRuntime,
|
|
375
|
+
preferred_transport_mode: transportPreference?.preferred_mode ?? ingressRuntime.preferred_transport_mode ?? 'bridge',
|
|
376
|
+
}
|
|
377
|
+
: {
|
|
378
|
+
receive_mode: 'webhook',
|
|
379
|
+
transport_mode: transportPreference?.preferred_mode ?? 'bridge',
|
|
380
|
+
preferred_transport_mode: transportPreference?.preferred_mode ?? 'bridge',
|
|
381
|
+
},
|
|
382
|
+
recent_events: collaborationEventManager.listRecent(30),
|
|
383
|
+
projection_outbox_failed: new CollaborationProjectionOutboxManager(store).listByStatus('failed', 20),
|
|
384
|
+
});
|
|
349
385
|
const desiredSelectedSessionKey =
|
|
350
386
|
(selectedSessionKey && sessions.some((session) => session.session_key === selectedSessionKey) ? selectedSessionKey : null) ??
|
|
351
387
|
sessionManager.getActiveSession()?.session_key ??
|
|
@@ -376,9 +412,9 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
376
412
|
? collaborationEventManager.listBySession(selectedSession.session_key, 12)
|
|
377
413
|
: [];
|
|
378
414
|
const selectedPendingCollaborationEvents = selectedSession
|
|
379
|
-
?
|
|
415
|
+
? listPendingDecisionViews(store, 100).filter((event) => event.session_key === selectedSession.session_key).slice(0, 12)
|
|
380
416
|
: [];
|
|
381
|
-
const pendingCollaborationEventsGlobal =
|
|
417
|
+
const pendingCollaborationEventsGlobal = listPendingDecisionViews(store, 50);
|
|
382
418
|
const selectedMessages = selectedSession?.conversation_id
|
|
383
419
|
? historyManager.listRecent(selectedSession.conversation_id, 12)
|
|
384
420
|
: [];
|
|
@@ -402,18 +438,47 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
402
438
|
: [];
|
|
403
439
|
const unreadTotal = sessionsWithMeta.reduce((sum, session) => sum + (session.unread_count ?? 0), 0);
|
|
404
440
|
const alertSessions = sessionsWithMeta.filter((session) => !!session.binding_alert).length;
|
|
441
|
+
const sinceLastSeenGlobal = summarizeSinceLastSeen(store, {
|
|
442
|
+
operator_id: 'tui',
|
|
443
|
+
scope_type: 'global',
|
|
444
|
+
});
|
|
445
|
+
const sessionsWithSeen = sessionsWithMeta.map((session) => ({
|
|
446
|
+
...session,
|
|
447
|
+
since_last_seen: summarizeSinceLastSeen(store, {
|
|
448
|
+
operator_id: 'tui',
|
|
449
|
+
scope_type: 'session',
|
|
450
|
+
scope_key: session.session_key,
|
|
451
|
+
}),
|
|
452
|
+
}));
|
|
453
|
+
const selectedDeliveryTimeline = selectedSession
|
|
454
|
+
? buildDeliveryTimeline(store, selectedSession.session_key, 30)
|
|
455
|
+
: [];
|
|
456
|
+
const selectedProjectionPreview = selectedSession
|
|
457
|
+
? buildProjectionPreview(store, selectedSession.session_key, policy.doc.collaboration_projection?.preset || 'balanced', 5)
|
|
458
|
+
: null;
|
|
459
|
+
const humanDelivery = summarizeHumanDelivery(store, 20);
|
|
460
|
+
const selectedRecentBindings = selectedSession
|
|
461
|
+
? listRecentBindingsForSession(store, selectedSession.session_key, selectedSession.conversation_id, 12)
|
|
462
|
+
: [];
|
|
463
|
+
const selectedRecentNotificationIntents = selectedSession
|
|
464
|
+
? new NotificationIntentManager(store).listBySession(selectedSession.session_key, 12)
|
|
465
|
+
: [];
|
|
405
466
|
return {
|
|
406
467
|
identity,
|
|
407
468
|
runtimeMode,
|
|
408
469
|
activeChatSession,
|
|
409
470
|
ingressRuntime,
|
|
471
|
+
transportPreference,
|
|
472
|
+
transportHealth,
|
|
410
473
|
activeChatSessionFile: getActiveSessionFilePath(),
|
|
411
474
|
sessionMapPath: getSessionMapFilePath(),
|
|
412
475
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
413
476
|
policyPath: policy.path,
|
|
414
477
|
policyDoc: policy.doc,
|
|
415
478
|
projectionPreset: policy.doc.collaboration_projection?.preset || 'balanced',
|
|
416
|
-
|
|
479
|
+
humanDelivery,
|
|
480
|
+
sinceLastSeenGlobal,
|
|
481
|
+
sessions: sessionsWithSeen,
|
|
417
482
|
tasks: taskManager.listRecent(30).map((task) => ({
|
|
418
483
|
...task,
|
|
419
484
|
handoff: taskHandoffManager.get(task.task_id),
|
|
@@ -426,6 +491,10 @@ function buildHostState(identityPath, selectedSessionKey = null, historyPageInde
|
|
|
426
491
|
selectedAuditEvents,
|
|
427
492
|
selectedCollaborationEvents,
|
|
428
493
|
selectedPendingCollaborationEvents,
|
|
494
|
+
selectedDeliveryTimeline,
|
|
495
|
+
selectedProjectionPreview,
|
|
496
|
+
selectedRecentBindings,
|
|
497
|
+
selectedRecentNotificationIntents,
|
|
429
498
|
pendingCollaborationEventsGlobal,
|
|
430
499
|
selectedMessages,
|
|
431
500
|
selectedHistoryPage,
|
|
@@ -450,6 +519,10 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
450
519
|
const pendingCollaborationEvents = hostState.selectedPendingCollaborationEvents || [];
|
|
451
520
|
const pendingCollaborationEventsGlobal = hostState.pendingCollaborationEventsGlobal || [];
|
|
452
521
|
const messages = hostState.selectedMessages || [];
|
|
522
|
+
const deliveryTimeline = hostState.selectedDeliveryTimeline || [];
|
|
523
|
+
const projectionPreview = hostState.selectedProjectionPreview || null;
|
|
524
|
+
const recentBindings = hostState.selectedRecentBindings || [];
|
|
525
|
+
const recentNotificationIntents = hostState.selectedRecentNotificationIntents || [];
|
|
453
526
|
const recommendations = hostState.selectedRecommendations || [];
|
|
454
527
|
const openRecommendation = recommendations.find((item) => item.status === 'open') || null;
|
|
455
528
|
const reopenRecommendation = recommendations.find((item) => item.status === 'dismissed' || item.status === 'superseded') || null;
|
|
@@ -469,14 +542,33 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
469
542
|
|| hostState.ingressRuntime.receive_mode === 'polling_degraded'
|
|
470
543
|
|| !!hostState.ingressRuntime.hooks_last_error;
|
|
471
544
|
const ingressLabel = degraded ? 'Degraded' : 'Ready';
|
|
545
|
+
const transportHealth = hostState.transportHealth || { state: 'Ready', transport_mode: 'bridge', preferred_transport_mode: 'bridge', retry_queue_length: 0, consecutive_failures: 0 };
|
|
546
|
+
const humanDelivery = hostState.humanDelivery || {
|
|
547
|
+
mode: 'projection_outbox',
|
|
548
|
+
active_bindings: 0,
|
|
549
|
+
pending_intents: 0,
|
|
550
|
+
unresolved_intents: 0,
|
|
551
|
+
failed_intents: 0,
|
|
552
|
+
acknowledged_intents: 0,
|
|
553
|
+
channel_capabilities: [],
|
|
554
|
+
supported_channels: [],
|
|
555
|
+
unsupported_channels: [],
|
|
556
|
+
last_canary_ok: null,
|
|
557
|
+
last_canary_at: null,
|
|
558
|
+
};
|
|
559
|
+
const sinceLastSeenGlobal = hostState.sinceLastSeenGlobal || {};
|
|
472
560
|
const lines = [
|
|
473
561
|
'PingAgent Host TUI',
|
|
474
562
|
`DID: ${hostState.identity.did}`,
|
|
475
563
|
`status=${formatStatusLine(uiState?.statusLevel || 'info', uiState?.statusMessage || '(ready)')}${statusTs}${statusCountdown}`,
|
|
476
564
|
`runtime_mode=${hostState.runtimeMode} receive_mode=${hostState.ingressRuntime?.receive_mode || 'webhook'} current_openclaw_chat=${hostState.activeChatSession || '(none)'}`,
|
|
477
565
|
`ingress=${ingressLabel}${degraded ? ' action=[f] fix-now' : ''}`,
|
|
566
|
+
`transport=${transportHealth.transport_mode} preferred=${transportHealth.preferred_transport_mode} state=${transportHealth.state} retry_queue=${transportHealth.retry_queue_length} failures=${transportHealth.consecutive_failures}`,
|
|
567
|
+
`human_delivery mode=${humanDelivery.mode || 'projection_outbox'} active_bindings=${humanDelivery.active_bindings ?? 0} pending=${humanDelivery.pending_intents ?? 0} unresolved=${humanDelivery.unresolved_intents ?? 0} failed=${humanDelivery.failed_intents ?? 0} acked=${humanDelivery.acknowledged_intents ?? 0}`,
|
|
568
|
+
`human_delivery_channels supported=${(humanDelivery.supported_channels || []).join(',') || '(none)'} unsupported=${(humanDelivery.unsupported_channels || []).join(',') || '(none)'} canary_ok=${typeof humanDelivery.last_canary_ok === 'boolean' ? String(humanDelivery.last_canary_ok) : '(unknown)'} at=${humanDelivery.last_canary_at || '(none)'}`,
|
|
478
569
|
uiState?.publicLinkUrl ? `public_link=${uiState.publicLinkUrl}` : null,
|
|
479
570
|
`sessions=${sessions.length} unread_total=${hostState.unreadTotal ?? 0} alert_sessions=${hostState.alertSessions ?? 0} view=${view} projection=${hostState.projectionPreset || 'balanced'}`,
|
|
571
|
+
`since_last_seen external=${sinceLastSeenGlobal.new_external_messages ?? 0} conclusions=${sinceLastSeenGlobal.new_conclusions ?? 0} decisions=${sinceLastSeenGlobal.new_decisions ?? 0} failures=${sinceLastSeenGlobal.new_failures ?? 0}`,
|
|
480
572
|
`policy=${hostState.policyPath}`,
|
|
481
573
|
`chat_link_map=${hostState.sessionMapPath}`,
|
|
482
574
|
`chat_link_alerts=${hostState.sessionBindingAlertsPath}`,
|
|
@@ -505,12 +597,14 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
505
597
|
lines.push('- y: dump task detail to stdout (task-detail view, choose json/plain)');
|
|
506
598
|
lines.push('- u: approve the selected collaboration decision');
|
|
507
599
|
lines.push('- U: reject the selected collaboration decision');
|
|
600
|
+
lines.push('- b: switch preferred transport to bridge');
|
|
601
|
+
lines.push('- c: switch preferred transport to channel');
|
|
508
602
|
lines.push('- f: repair OpenClaw hooks config');
|
|
509
603
|
lines.push('- A: apply first open trust recommendation for selected session');
|
|
510
604
|
lines.push('- D: dismiss current open recommendation');
|
|
511
605
|
lines.push('- R: reopen dismissed/superseded recommendation');
|
|
512
|
-
lines.push('-
|
|
513
|
-
lines.push('-
|
|
606
|
+
lines.push('- B: attach selected session to the current OpenClaw chat');
|
|
607
|
+
lines.push('- C: detach the selected chat link');
|
|
514
608
|
lines.push('- q: quit');
|
|
515
609
|
} else if (view === 'history') {
|
|
516
610
|
lines.push('Conversation History');
|
|
@@ -576,7 +670,7 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
576
670
|
lines.push(...(pendingCollaborationEventsGlobal.length
|
|
577
671
|
? pendingCollaborationEventsGlobal.map((event, idx) => {
|
|
578
672
|
const marker = idx === selectedDecisionIndex ? '>' : ' ';
|
|
579
|
-
return `${marker} ${event.id} [${event.severity}] ${truncateLine(event.summary || '(none)', 88)} session=${event.session_key || '(none)'}`;
|
|
673
|
+
return `${marker} ${event.id} [${event.severity}${event.overdue ? ':overdue' : ''}] ${truncateLine(event.summary || '(none)', 88)} session=${event.session_key || '(none)'}`;
|
|
580
674
|
})
|
|
581
675
|
: ['- none']));
|
|
582
676
|
} else if (view === 'tasks') {
|
|
@@ -642,7 +736,12 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
642
736
|
if (pendingCollaborationEvents.length > 0) {
|
|
643
737
|
lines.push(`pending_collaboration_decisions=${pendingCollaborationEvents.length}`);
|
|
644
738
|
lines.push(`next_decision=${truncateLine(pendingCollaborationEvents[0].summary || '(none)', 100)}`);
|
|
739
|
+
if (pendingCollaborationEvents[0].overdue) {
|
|
740
|
+
lines.push(`next_decision_overdue=${Math.ceil((pendingCollaborationEvents[0].overdue_by_ms || 0) / 60000)}m`);
|
|
741
|
+
}
|
|
645
742
|
}
|
|
743
|
+
const sessionSeen = selected.since_last_seen || {};
|
|
744
|
+
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}`);
|
|
646
745
|
const actionBar = [
|
|
647
746
|
selected.trust_state === 'pending' ? '[a] approve' : null,
|
|
648
747
|
'[A] apply-rec',
|
|
@@ -656,8 +755,8 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
656
755
|
pendingCollaborationEvents.length ? '[U] reject-decision' : null,
|
|
657
756
|
'[o] history',
|
|
658
757
|
'[t] tasks',
|
|
659
|
-
'[
|
|
660
|
-
'[
|
|
758
|
+
'[B] attach-chat',
|
|
759
|
+
'[C] detach-chat',
|
|
661
760
|
].filter(Boolean).join(' ');
|
|
662
761
|
lines.push(`actions=${actionBar}`);
|
|
663
762
|
lines.push('');
|
|
@@ -688,6 +787,35 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
688
787
|
lines.push(...(collaborationEvents.length
|
|
689
788
|
? collaborationEvents.map((event) => `- ${event.event_type} [${event.severity}${event.approval_required ? `:${event.approval_status}` : ''}] ${truncateLine(event.summary || '', 90)}`)
|
|
690
789
|
: ['- none']));
|
|
790
|
+
if (projectionPreview) {
|
|
791
|
+
lines.push('');
|
|
792
|
+
lines.push(`Projection Preview [preset=${projectionPreview.preset}]`);
|
|
793
|
+
lines.push(...(projectionPreview.recent.length
|
|
794
|
+
? projectionPreview.recent.map((entry) => `- ${entry.event_type} => ${entry.disposition} (${truncateLine(entry.reason, 70)})`)
|
|
795
|
+
: ['- none']));
|
|
796
|
+
}
|
|
797
|
+
if (view === 'detail') {
|
|
798
|
+
lines.push('');
|
|
799
|
+
lines.push('Delivery Timeline');
|
|
800
|
+
lines.push(...(deliveryTimeline.length
|
|
801
|
+
? deliveryTimeline.slice(0, 10).map((entry) => `- ${formatTs(entry.ts_ms, false)} ${entry.kind} ${truncateLine(entry.summary || '', 72)}`)
|
|
802
|
+
: ['- none']));
|
|
803
|
+
lines.push('');
|
|
804
|
+
lines.push('Human Reply Targets');
|
|
805
|
+
lines.push(...(recentBindings.length
|
|
806
|
+
? recentBindings.slice(0, 6).map((binding) => `- ${binding.channel} -> ${truncateLine(binding.to || '(none)', 48)} [${binding.status}] owner=${truncateLine(binding.owner_ref || '(none)', 32)}`)
|
|
807
|
+
: ['- none']));
|
|
808
|
+
lines.push('');
|
|
809
|
+
lines.push('Notification Intents');
|
|
810
|
+
lines.push(...(recentNotificationIntents.length
|
|
811
|
+
? recentNotificationIntents.slice(0, 6).map((intent) => `- ${intent.intent_type} [${intent.status}] ${truncateLine(intent.summary || '', 72)}${intent.acknowledged_at ? ` ack=${formatTs(intent.acknowledged_at, false)}` : ''}`)
|
|
812
|
+
: ['- none']));
|
|
813
|
+
lines.push('');
|
|
814
|
+
lines.push('Channel Capability Canary');
|
|
815
|
+
lines.push(...((humanDelivery.channel_capabilities || []).length
|
|
816
|
+
? humanDelivery.channel_capabilities.slice(0, 8).map((capability) => `- ${capability.channel} configured=${capability.configured ? 'true' : 'false'} explicit_send=${capability.supports_explicit_send ? 'true' : 'false'} dry_run=${capability.supports_dry_run ? 'true' : 'false'} canary=${typeof capability.last_canary_ok === 'boolean' ? String(capability.last_canary_ok) : '(unknown)'}`)
|
|
817
|
+
: ['- none']));
|
|
818
|
+
}
|
|
691
819
|
lines.push('');
|
|
692
820
|
lines.push('Audit');
|
|
693
821
|
lines.push(...(auditEvents.length
|
|
@@ -699,7 +827,7 @@ function renderHostTuiScreen(hostState, uiState) {
|
|
|
699
827
|
}
|
|
700
828
|
|
|
701
829
|
lines.push('');
|
|
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
|
|
830
|
+
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');
|
|
703
831
|
return lines.join('\n');
|
|
704
832
|
}
|
|
705
833
|
|
|
@@ -720,6 +848,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
720
848
|
historySearchQuery: '',
|
|
721
849
|
publicLinkUrl: '',
|
|
722
850
|
};
|
|
851
|
+
const seenState = {
|
|
852
|
+
globalView: null,
|
|
853
|
+
sessionKey: null,
|
|
854
|
+
};
|
|
723
855
|
|
|
724
856
|
const render = () => {
|
|
725
857
|
if (uiState.statusExpiresAt && Date.now() >= uiState.statusExpiresAt) {
|
|
@@ -762,7 +894,49 @@ async function runHostTui(identityPath, opts) {
|
|
|
762
894
|
return rendered.hostState;
|
|
763
895
|
};
|
|
764
896
|
|
|
897
|
+
const markSeen = (scopeType, scopeKey = null) => {
|
|
898
|
+
const store = openStore(identityPath);
|
|
899
|
+
try {
|
|
900
|
+
return new OperatorSeenStateManager(store).markSeen({
|
|
901
|
+
operator_id: 'tui',
|
|
902
|
+
scope_type: scopeType,
|
|
903
|
+
scope_key: scopeType === 'session' ? (scopeKey || null) : null,
|
|
904
|
+
last_seen_ts: Date.now(),
|
|
905
|
+
});
|
|
906
|
+
} finally {
|
|
907
|
+
store.close();
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const refreshSeenMarkers = (hostState, options = {}) => {
|
|
912
|
+
let changed = false;
|
|
913
|
+
const forceGlobal = !!options.forceGlobal;
|
|
914
|
+
const forceSession = !!options.forceSession;
|
|
915
|
+
if (uiState.view === 'overview' || uiState.view === 'decisions') {
|
|
916
|
+
if (forceGlobal || seenState.globalView !== uiState.view) {
|
|
917
|
+
markSeen('global');
|
|
918
|
+
seenState.globalView = uiState.view;
|
|
919
|
+
changed = true;
|
|
920
|
+
}
|
|
921
|
+
if (uiState.view === 'overview') seenState.sessionKey = null;
|
|
922
|
+
} else if (['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view)) {
|
|
923
|
+
const sessionKey = hostState.selectedSession?.session_key || null;
|
|
924
|
+
if (sessionKey && (forceSession || seenState.sessionKey !== sessionKey)) {
|
|
925
|
+
markSeen('session', sessionKey);
|
|
926
|
+
seenState.sessionKey = sessionKey;
|
|
927
|
+
changed = true;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return changed;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const rerenderAfterSeenUpdate = (hostState, options = {}) => {
|
|
934
|
+
if (!refreshSeenMarkers(hostState, options)) return hostState;
|
|
935
|
+
return redraw();
|
|
936
|
+
};
|
|
937
|
+
|
|
765
938
|
let latestState = redraw();
|
|
939
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: true });
|
|
766
940
|
readline.emitKeypressEvents(process.stdin);
|
|
767
941
|
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
768
942
|
|
|
@@ -779,6 +953,50 @@ async function runHostTui(identityPath, opts) {
|
|
|
779
953
|
uiState.statusAt = Date.now();
|
|
780
954
|
};
|
|
781
955
|
|
|
956
|
+
const switchTransport = async (mode) => {
|
|
957
|
+
const label = mode === 'channel' ? 'channel' : 'bridge';
|
|
958
|
+
const confirmed = await confirmAction(`Switch preferred transport to ${label} and request a managed restart?`);
|
|
959
|
+
if (!confirmed) {
|
|
960
|
+
setStatus(`Transport switch to ${label} cancelled.`, 'warn');
|
|
961
|
+
latestState = redraw();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
const result = switchTransportPreference(mode, {
|
|
966
|
+
updated_by: 'tui',
|
|
967
|
+
});
|
|
968
|
+
const store = openStore(identityPath);
|
|
969
|
+
try {
|
|
970
|
+
new CollaborationEventManager(store).record({
|
|
971
|
+
event_type: 'transport_switched',
|
|
972
|
+
severity: result.restarted ? 'notice' : 'warning',
|
|
973
|
+
summary: result.restarted
|
|
974
|
+
? `Preferred transport switched to ${label}. A managed restart was attempted.`
|
|
975
|
+
: `Preferred transport switched to ${label}. Restart is still required.`,
|
|
976
|
+
detail: {
|
|
977
|
+
preferred_mode: result.preferred_mode,
|
|
978
|
+
restart_required: result.restart_required,
|
|
979
|
+
restart_method: result.restart_method ?? null,
|
|
980
|
+
restart_error: result.restart_error ?? null,
|
|
981
|
+
preference_path: result.preference_path,
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
} finally {
|
|
985
|
+
store.close();
|
|
986
|
+
}
|
|
987
|
+
setStatus(
|
|
988
|
+
result.restarted
|
|
989
|
+
? `Preferred transport is now ${label}; managed restart attempted via ${result.restart_method || 'unknown method'}.`
|
|
990
|
+
: `Preferred transport saved as ${label}; restart still required.`,
|
|
991
|
+
result.restarted ? 'ok' : 'warn',
|
|
992
|
+
result.restarted ? 7000 : 9000,
|
|
993
|
+
);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
setStatus(`Transport switch failed: ${error?.message || 'unknown error'}`, 'err', 9000);
|
|
996
|
+
}
|
|
997
|
+
latestState = redraw();
|
|
998
|
+
};
|
|
999
|
+
|
|
782
1000
|
const applySessionRecommendation = (selected) => {
|
|
783
1001
|
if (!selected?.remote_did) return { ok: false, message: 'No remote DID for selected session.' };
|
|
784
1002
|
const store = openStore(identityPath);
|
|
@@ -985,6 +1203,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
985
1203
|
uiState.historySearchQuery = '';
|
|
986
1204
|
}
|
|
987
1205
|
latestState = redraw();
|
|
1206
|
+
latestState = rerenderAfterSeenUpdate(latestState, {
|
|
1207
|
+
forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions',
|
|
1208
|
+
forceSession: ['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view),
|
|
1209
|
+
});
|
|
988
1210
|
};
|
|
989
1211
|
|
|
990
1212
|
const jumpSelection = (target) => {
|
|
@@ -1007,6 +1229,10 @@ async function runHostTui(identityPath, opts) {
|
|
|
1007
1229
|
uiState.historySearchQuery = '';
|
|
1008
1230
|
}
|
|
1009
1231
|
latestState = redraw();
|
|
1232
|
+
latestState = rerenderAfterSeenUpdate(latestState, {
|
|
1233
|
+
forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions',
|
|
1234
|
+
forceSession: ['detail', 'history', 'tasks', 'task-detail'].includes(uiState.view),
|
|
1235
|
+
});
|
|
1010
1236
|
};
|
|
1011
1237
|
|
|
1012
1238
|
const stopInterval = () => {
|
|
@@ -1060,6 +1286,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1060
1286
|
uiState.view = 'detail';
|
|
1061
1287
|
}
|
|
1062
1288
|
latestState = redraw();
|
|
1289
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: uiState.view !== 'decisions' && uiState.view !== 'overview' });
|
|
1063
1290
|
return;
|
|
1064
1291
|
}
|
|
1065
1292
|
if (key?.name === 'escape' || key?.name === 'h') {
|
|
@@ -1068,6 +1295,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1068
1295
|
else if (uiState.view === 'decisions') uiState.view = 'overview';
|
|
1069
1296
|
else uiState.view = 'overview';
|
|
1070
1297
|
latestState = redraw();
|
|
1298
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: uiState.view === 'overview' || uiState.view === 'decisions' });
|
|
1071
1299
|
return;
|
|
1072
1300
|
}
|
|
1073
1301
|
if (_str === '?') {
|
|
@@ -1104,6 +1332,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1104
1332
|
uiState.selectedHistoryPageIndex = 0;
|
|
1105
1333
|
setStatus('Opened task list view.', 'info');
|
|
1106
1334
|
latestState = redraw();
|
|
1335
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: true });
|
|
1107
1336
|
return;
|
|
1108
1337
|
}
|
|
1109
1338
|
if (key?.name === 'i') {
|
|
@@ -1111,6 +1340,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1111
1340
|
uiState.selectedDecisionIndex = 0;
|
|
1112
1341
|
setStatus('Opened decision inbox.', 'info');
|
|
1113
1342
|
latestState = redraw();
|
|
1343
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceGlobal: true });
|
|
1114
1344
|
return;
|
|
1115
1345
|
}
|
|
1116
1346
|
if (key?.name === 'r') {
|
|
@@ -1293,6 +1523,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1293
1523
|
uiState.historySearchQuery = '';
|
|
1294
1524
|
setStatus('Opened local history view.', 'info');
|
|
1295
1525
|
latestState = redraw();
|
|
1526
|
+
latestState = rerenderAfterSeenUpdate(latestState, { forceSession: true });
|
|
1296
1527
|
return;
|
|
1297
1528
|
}
|
|
1298
1529
|
if ((_str === '/' || key?.name === 's') && uiState.view === 'history') {
|
|
@@ -1305,6 +1536,14 @@ async function runHostTui(identityPath, opts) {
|
|
|
1305
1536
|
return;
|
|
1306
1537
|
}
|
|
1307
1538
|
if (key?.name === 'b') {
|
|
1539
|
+
await switchTransport('bridge');
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (key?.name === 'c') {
|
|
1543
|
+
await switchTransport('channel');
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (_str === 'B') {
|
|
1308
1547
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1309
1548
|
if (!selected?.conversation_id) return;
|
|
1310
1549
|
const current = latestState.activeChatSession || '(none)';
|
|
@@ -1324,7 +1563,7 @@ async function runHostTui(identityPath, opts) {
|
|
|
1324
1563
|
latestState = redraw();
|
|
1325
1564
|
return;
|
|
1326
1565
|
}
|
|
1327
|
-
if (
|
|
1566
|
+
if (_str === 'C') {
|
|
1328
1567
|
const selected = (latestState.sessions || []).find((session) => session.session_key === uiState.selectedSessionKey);
|
|
1329
1568
|
if (!selected?.conversation_id) return;
|
|
1330
1569
|
removeSessionBinding(selected.conversation_id);
|