@pingagent/sdk 0.1.14 → 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 +205 -11
- package/dist/chunk-BCYHGKQE.js +4825 -0
- package/dist/index.d.ts +184 -19
- package/dist/index.js +35 -3
- package/dist/web-server.js +365 -10
- package/package.json +3 -3
package/dist/web-server.js
CHANGED
|
@@ -3,29 +3,37 @@ import {
|
|
|
3
3
|
CollaborationProjectionOutboxManager,
|
|
4
4
|
ContactManager,
|
|
5
5
|
LocalStore,
|
|
6
|
+
OperatorSeenStateManager,
|
|
6
7
|
PingAgentClient,
|
|
7
8
|
TrustPolicyAuditManager,
|
|
8
9
|
TrustRecommendationManager,
|
|
10
|
+
buildDeliveryTimeline,
|
|
11
|
+
buildProjectionPreview,
|
|
9
12
|
decideContactPolicy,
|
|
10
13
|
decideTaskPolicy,
|
|
11
14
|
defaultTrustPolicyDoc,
|
|
15
|
+
deriveTransportHealth,
|
|
12
16
|
ensureTokenValid,
|
|
13
17
|
getActiveSessionFilePath,
|
|
14
18
|
getSessionBindingAlertsFilePath,
|
|
15
19
|
getSessionMapFilePath,
|
|
16
20
|
getTrustRecommendationActionLabel,
|
|
21
|
+
listPendingDecisionViews,
|
|
17
22
|
loadIdentity,
|
|
18
23
|
normalizeTrustPolicyDoc,
|
|
19
24
|
readCurrentActiveSessionKey,
|
|
20
25
|
readIngressRuntimeStatus,
|
|
21
26
|
readSessionBindingAlerts,
|
|
22
27
|
readSessionBindings,
|
|
28
|
+
readTransportPreference,
|
|
23
29
|
removeSessionBinding,
|
|
24
30
|
setSessionBinding,
|
|
31
|
+
summarizeSinceLastSeen,
|
|
25
32
|
summarizeTrustPolicyAudit,
|
|
33
|
+
switchTransportPreference,
|
|
26
34
|
updateStoredToken,
|
|
27
35
|
upsertTrustPolicyRecommendation
|
|
28
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-BCYHGKQE.js";
|
|
29
37
|
|
|
30
38
|
// src/web-server.ts
|
|
31
39
|
import * as fs from "fs";
|
|
@@ -210,6 +218,10 @@ function getHostPanelHtml() {
|
|
|
210
218
|
|
|
211
219
|
<section id="runtimePanel" class="panel active">
|
|
212
220
|
<div class="card" id="activationCard" style="margin-bottom:16px"></div>
|
|
221
|
+
<div class="grid two-col" style="margin-bottom:16px">
|
|
222
|
+
<div class="card" id="transportHealthCard"></div>
|
|
223
|
+
<div class="card" id="sinceLastSeenCard"></div>
|
|
224
|
+
</div>
|
|
213
225
|
<div class="grid stats" id="statsGrid"></div>
|
|
214
226
|
<div class="grid runtime-layout" style="margin-top:16px">
|
|
215
227
|
<div class="card">
|
|
@@ -269,6 +281,7 @@ function getHostPanelHtml() {
|
|
|
269
281
|
<div class="row-actions">
|
|
270
282
|
<button class="action-btn" id="saveProjectionPresetBtn">Save projection preset</button>
|
|
271
283
|
</div>
|
|
284
|
+
<div id="projectionPolicyPreview" class="muted small" style="margin-top:12px"></div>
|
|
272
285
|
</div>
|
|
273
286
|
</div>
|
|
274
287
|
|
|
@@ -407,6 +420,10 @@ function getHostPanelHtml() {
|
|
|
407
420
|
detailMode: sessionStorage.getItem('pingagent_host_panel_detail_mode') || 'basic',
|
|
408
421
|
showUnreadOnly: false,
|
|
409
422
|
};
|
|
423
|
+
const seenMarkers = {
|
|
424
|
+
globalTab: null,
|
|
425
|
+
sessionKey: null,
|
|
426
|
+
};
|
|
410
427
|
|
|
411
428
|
function esc(value) {
|
|
412
429
|
return String(value == null ? '' : value)
|
|
@@ -429,6 +446,130 @@ function getHostPanelHtml() {
|
|
|
429
446
|
try { return new Date(value).toLocaleString(); } catch { return String(value); }
|
|
430
447
|
}
|
|
431
448
|
|
|
449
|
+
function countSinceLastSeen(summary) {
|
|
450
|
+
if (!summary) return 0;
|
|
451
|
+
return Number(summary.new_external_messages || 0)
|
|
452
|
+
+ Number(summary.new_conclusions || 0)
|
|
453
|
+
+ Number(summary.new_handoffs || 0)
|
|
454
|
+
+ Number(summary.new_decisions || 0)
|
|
455
|
+
+ Number(summary.new_failures || 0)
|
|
456
|
+
+ Number(summary.new_repairs || 0)
|
|
457
|
+
+ Number(summary.new_projection_failures || 0);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function renderSinceLastSeenSummary(summary) {
|
|
461
|
+
if (!summary) return '<div class="empty">No seen-state yet.</div>';
|
|
462
|
+
const count = countSinceLastSeen(summary);
|
|
463
|
+
return '' +
|
|
464
|
+
'<div class="status-main">' +
|
|
465
|
+
'<h2>Since Last Seen</h2>' +
|
|
466
|
+
'<div class="muted small">Per operator view. Opening runtime, detail, or decisions resets the matching anchor instead of forcing every refresh to zero out the feed.</div>' +
|
|
467
|
+
'<div class="summary-pills">' +
|
|
468
|
+
'<span class="pill">new=' + esc(count) + '</span>' +
|
|
469
|
+
'<span class="pill">external=' + esc(summary.new_external_messages || 0) + '</span>' +
|
|
470
|
+
'<span class="pill">conclusions=' + esc(summary.new_conclusions || 0) + '</span>' +
|
|
471
|
+
'<span class="pill">handoffs=' + esc(summary.new_handoffs || 0) + '</span>' +
|
|
472
|
+
'<span class="pill">decisions=' + esc(summary.new_decisions || 0) + '</span>' +
|
|
473
|
+
'<span class="pill">failures=' + esc(summary.new_failures || 0) + '</span>' +
|
|
474
|
+
'<span class="pill">repairs=' + esc(summary.new_repairs || 0) + '</span>' +
|
|
475
|
+
'<span class="pill">projection=' + esc(summary.new_projection_failures || 0) + '</span>' +
|
|
476
|
+
'</div>' +
|
|
477
|
+
'<div class="muted small" style="margin-top:8px">latest=' + esc(fmtTs(summary.latest_ts)) + '</div>' +
|
|
478
|
+
'</div>';
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function renderTransportHealthCard(transportHealth) {
|
|
482
|
+
const health = transportHealth || {};
|
|
483
|
+
const stateLabel = health.state || 'Ready';
|
|
484
|
+
const stateClass = stateLabel === 'Degraded' || stateLabel === 'Switching Recommended' ? 'degraded' : 'ready';
|
|
485
|
+
const current = health.transport_mode || 'bridge';
|
|
486
|
+
const preferred = health.preferred_transport_mode || current;
|
|
487
|
+
return '' +
|
|
488
|
+
'<div class="status-strip">' +
|
|
489
|
+
'<div class="status-main">' +
|
|
490
|
+
'<h2>Transport Health</h2>' +
|
|
491
|
+
'<div class="status-state ' + esc(stateClass) + '">' + esc(stateLabel) + '</div>' +
|
|
492
|
+
'<div class="summary-pills">' +
|
|
493
|
+
'<span class="pill">current=' + esc(current) + '</span>' +
|
|
494
|
+
'<span class="pill">preferred=' + esc(preferred) + '</span>' +
|
|
495
|
+
'<span class="pill">retry_queue=' + esc(health.retry_queue_length || 0) + '</span>' +
|
|
496
|
+
'<span class="pill">failures=' + esc(health.consecutive_failures || 0) + '</span>' +
|
|
497
|
+
'</div>' +
|
|
498
|
+
'<div class="muted small" style="margin-top:8px">last inbound=' + esc(fmtTs(health.last_inbound_at)) + ' \xB7 last outbound=' + esc(fmtTs(health.last_outbound_at)) + '</div>' +
|
|
499
|
+
'<div class="muted small">last degraded=' + esc(fmtTs(health.last_degraded_at)) + ' \xB7 last repaired=' + esc(fmtTs(health.last_repaired_at)) + '</div>' +
|
|
500
|
+
(health.last_error ? '<div style="margin-top:8px;color:#fecaca">' + esc(health.last_error) + '</div>' : '<div class="muted small" style="margin-top:8px">No recent transport errors.</div>') +
|
|
501
|
+
'</div>' +
|
|
502
|
+
'<div style="min-width:240px">' +
|
|
503
|
+
'<div class="label">Actions</div>' +
|
|
504
|
+
'<div class="row-actions" style="margin-top:8px">' +
|
|
505
|
+
'<button class="secondary-btn transport-switch-btn" data-mode="bridge" style="width:auto">Switch to bridge</button>' +
|
|
506
|
+
'<button class="secondary-btn transport-switch-btn" data-mode="channel" style="width:auto">Switch to channel</button>' +
|
|
507
|
+
'</div>' +
|
|
508
|
+
(health.state === 'Switching Recommended'
|
|
509
|
+
? '<div class="muted small" style="margin-top:10px">Assist Then Switch is recommending a move back to bridge. The preference file already captures the desired fallback path.</div>'
|
|
510
|
+
: '<div class="muted small" style="margin-top:10px">Managed restart keeps the transport layer swappable without changing the human-facing model.</div>') +
|
|
511
|
+
'</div>' +
|
|
512
|
+
'</div>';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function renderProjectionPreviewBlock(preview) {
|
|
516
|
+
if (!preview) return '<div class="empty">No projection preview available for the selected session yet.</div>';
|
|
517
|
+
const detailOnly = Array.isArray(preview.description && preview.description.detail_only) ? preview.description.detail_only : [];
|
|
518
|
+
const immediate = Array.isArray(preview.description && preview.description.immediate) ? preview.description.immediate : [];
|
|
519
|
+
return '' +
|
|
520
|
+
'<div class="muted small">Preset <strong>' + esc(preview.preset) + '</strong> projects <code>' + esc(immediate.join(', ') || 'none') + '</code> immediately and keeps <code>' + esc(detailOnly.join(', ') || 'none') + '</code> in detail-only mode.</div>' +
|
|
521
|
+
'<div class="audit-list" style="margin-top:10px">' +
|
|
522
|
+
(Array.isArray(preview.recent) && preview.recent.length
|
|
523
|
+
? preview.recent.map(function (entry) {
|
|
524
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(entry.event_type) + '</strong><span class="badge">' + esc(entry.disposition) + '</span></div>' +
|
|
525
|
+
'<div class="muted small">' + esc(entry.reason || '(no reason)') + '</div>' +
|
|
526
|
+
'</div>';
|
|
527
|
+
}).join('')
|
|
528
|
+
: '<div class="empty">No recent collaboration events to preview.</div>') +
|
|
529
|
+
'</div>';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function renderDeliveryTimeline(timeline) {
|
|
533
|
+
if (!Array.isArray(timeline) || !timeline.length) {
|
|
534
|
+
return '<div class="empty">No delivery timeline yet.</div>';
|
|
535
|
+
}
|
|
536
|
+
return timeline.map(function (entry) {
|
|
537
|
+
return '<div class="audit-row"><div class="top"><strong>' + esc(entry.kind || 'event') + '</strong><span class="badge">' + esc(fmtTs(entry.ts_ms)) + '</span></div>' +
|
|
538
|
+
'<div style="margin-top:8px">' + esc(entry.summary || '(no summary)') + '</div>' +
|
|
539
|
+
'</div>';
|
|
540
|
+
}).join('');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function markSeen(scopeType, scopeKey) {
|
|
544
|
+
return api('/api/runtime/seen', {
|
|
545
|
+
method: 'POST',
|
|
546
|
+
headers: { 'Content-Type': 'application/json' },
|
|
547
|
+
body: JSON.stringify({
|
|
548
|
+
operator_id: 'host_panel',
|
|
549
|
+
scope_type: scopeType,
|
|
550
|
+
scope_key: scopeType === 'session' ? scopeKey : null,
|
|
551
|
+
}),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function maybeMarkGlobalSeen(tab, force) {
|
|
556
|
+
if (tab !== 'runtime' && tab !== 'decisions') return;
|
|
557
|
+
if (!force && seenMarkers.globalTab === tab) return;
|
|
558
|
+
try {
|
|
559
|
+
await markSeen('global', null);
|
|
560
|
+
seenMarkers.globalTab = tab;
|
|
561
|
+
} catch {}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function maybeMarkSessionSeen(sessionKey, force) {
|
|
565
|
+
if (!sessionKey) return;
|
|
566
|
+
if (!force && seenMarkers.sessionKey === sessionKey) return;
|
|
567
|
+
try {
|
|
568
|
+
await markSeen('session', sessionKey);
|
|
569
|
+
seenMarkers.sessionKey = sessionKey;
|
|
570
|
+
} catch {}
|
|
571
|
+
}
|
|
572
|
+
|
|
432
573
|
function syncUrlState() {
|
|
433
574
|
const url = new URL(window.location.href);
|
|
434
575
|
if (state.selectedProfile) url.searchParams.set('profile', state.selectedProfile);
|
|
@@ -512,6 +653,7 @@ function getHostPanelHtml() {
|
|
|
512
653
|
}
|
|
513
654
|
|
|
514
655
|
function setTab(tab) {
|
|
656
|
+
const previous = state.currentTab;
|
|
515
657
|
state.currentTab = tab;
|
|
516
658
|
document.getElementById('navRuntime').classList.toggle('active', tab === 'runtime');
|
|
517
659
|
document.getElementById('navDecisions').classList.toggle('active', tab === 'decisions');
|
|
@@ -519,7 +661,16 @@ function getHostPanelHtml() {
|
|
|
519
661
|
document.getElementById('runtimePanel').classList.toggle('active', tab === 'runtime');
|
|
520
662
|
document.getElementById('decisionsPanel').classList.toggle('active', tab === 'decisions');
|
|
521
663
|
document.getElementById('policyPanel').classList.toggle('active', tab === 'policy');
|
|
664
|
+
if (previous !== tab && tab !== 'runtime') {
|
|
665
|
+
seenMarkers.sessionKey = null;
|
|
666
|
+
}
|
|
522
667
|
syncUrlState();
|
|
668
|
+
if (previous !== tab) {
|
|
669
|
+
void maybeMarkGlobalSeen(tab, true);
|
|
670
|
+
if (tab === 'runtime' && state.selectedSessionKey) {
|
|
671
|
+
void maybeMarkSessionSeen(state.selectedSessionKey, true);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
523
674
|
}
|
|
524
675
|
|
|
525
676
|
function renderHeader() {
|
|
@@ -650,15 +801,18 @@ function getHostPanelHtml() {
|
|
|
650
801
|
function renderDecisionInbox() {
|
|
651
802
|
const data = state.decisions;
|
|
652
803
|
const pendingDecisions = data && Array.isArray(data.pendingDecisions) ? data.pendingDecisions : [];
|
|
804
|
+
const overdueDecisions = pendingDecisions.filter(function (event) { return !!event.overdue; });
|
|
653
805
|
const pendingOutbox = data && Array.isArray(data.projectionOutboxPending) ? data.projectionOutboxPending : [];
|
|
654
806
|
const failedOutbox = data && Array.isArray(data.projectionOutboxFailed) ? data.projectionOutboxFailed : [];
|
|
655
807
|
document.getElementById('decisionInboxSummary').textContent =
|
|
656
|
-
'pending=' + pendingDecisions.length + ' \xB7 projection_pending=' + pendingOutbox.length + ' \xB7 projection_failed=' + failedOutbox.length;
|
|
808
|
+
'pending=' + pendingDecisions.length + ' \xB7 overdue=' + overdueDecisions.length + ' \xB7 projection_pending=' + pendingOutbox.length + ' \xB7 projection_failed=' + failedOutbox.length;
|
|
657
809
|
document.getElementById('decisionInboxList').innerHTML = pendingDecisions.length
|
|
658
810
|
? pendingDecisions.map(function (event) {
|
|
659
811
|
return '<div class="audit-row"><div class="top"><strong>' + esc(event.summary || '(no summary)') + '</strong>' +
|
|
660
|
-
'<span class="badge">' + esc(event.severity || 'warning') + '</span
|
|
661
|
-
'<
|
|
812
|
+
'<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end"><span class="badge">' + esc(event.severity || 'warning') + '</span>' +
|
|
813
|
+
(event.overdue ? '<span class="badge alert">overdue</span>' : '') + '</div></div>' +
|
|
814
|
+
'<div class="muted small">' + esc(fmtTs(event.ts_ms)) + ' \xB7 session=' + esc(event.session_key || '(none)') + ' \xB7 target=' + esc(event.target_human_session || '(none)') +
|
|
815
|
+
(event.overdue ? ' \xB7 overdue_by=' + esc(Math.ceil((event.overdue_by_ms || 0) / 60000)) + 'm' : '') + '</div>' +
|
|
662
816
|
'<div class="muted small" style="margin-top:8px">detail_ref=' + esc(event.conversation_id || '(none)') + '</div>' +
|
|
663
817
|
'<div class="row-actions">' +
|
|
664
818
|
'<button class="action-btn inbox-decision-btn" data-event-id="' + esc(event.id) + '" data-decision="approved">Approve</button>' +
|
|
@@ -747,6 +901,11 @@ function getHostPanelHtml() {
|
|
|
747
901
|
if (!overview) return;
|
|
748
902
|
syncSelectedSessionFromOverview();
|
|
749
903
|
const ingressState = ingressStatusModel(overview);
|
|
904
|
+
const transportHealth = overview.transportHealth || null;
|
|
905
|
+
const sinceLastSeen = overview.sinceLastSeen || null;
|
|
906
|
+
const overdueDecisions = Array.isArray(overview.pendingDecisions)
|
|
907
|
+
? overview.pendingDecisions.filter(function (event) { return !!event.overdue; })
|
|
908
|
+
: [];
|
|
750
909
|
document.getElementById('activationCard').innerHTML =
|
|
751
910
|
'<div class="status-strip">' +
|
|
752
911
|
'<div class="status-main">' +
|
|
@@ -754,6 +913,11 @@ function getHostPanelHtml() {
|
|
|
754
913
|
'<div class="status-state ' + ingressState.className + '">' + esc(ingressState.label) + '</div>' +
|
|
755
914
|
'<div class="muted small">' + esc(ingressState.detail) + '</div>' +
|
|
756
915
|
'<div class="muted small">Public link: ' + esc(overview.publicSelf && overview.publicSelf.public_url ? overview.publicSelf.public_url : '(not ready yet)') + '</div>' +
|
|
916
|
+
'<div class="summary-pills">' +
|
|
917
|
+
'<span class="pill">pending_decisions=' + esc((overview.pendingDecisions || []).length) + '</span>' +
|
|
918
|
+
'<span class="pill">overdue=' + esc(overdueDecisions.length) + '</span>' +
|
|
919
|
+
'<span class="pill">projection=' + esc(overview.collaborationProjection && overview.collaborationProjection.preset ? overview.collaborationProjection.preset : 'balanced') + '</span>' +
|
|
920
|
+
'</div>' +
|
|
757
921
|
'</div>' +
|
|
758
922
|
'<div style="min-width:320px">' +
|
|
759
923
|
'<div class="label">Quick Start</div>' +
|
|
@@ -768,6 +932,8 @@ function getHostPanelHtml() {
|
|
|
768
932
|
'</div>' +
|
|
769
933
|
'</div>' +
|
|
770
934
|
'</div>';
|
|
935
|
+
document.getElementById('transportHealthCard').innerHTML = renderTransportHealthCard(transportHealth);
|
|
936
|
+
document.getElementById('sinceLastSeenCard').innerHTML = renderSinceLastSeenSummary(sinceLastSeen);
|
|
771
937
|
const subscription = overview.subscription || null;
|
|
772
938
|
const stats = [
|
|
773
939
|
{ label: 'Plan', value: subscription ? subscription.tier : 'ghost', sub: subscription ? subscription.summary : 'subscription unavailable' },
|
|
@@ -796,6 +962,9 @@ function getHostPanelHtml() {
|
|
|
796
962
|
const active = session.session_key === state.selectedSessionKey ? ' active' : '';
|
|
797
963
|
const badges = [
|
|
798
964
|
'<span class="badge ' + esc(session.trust_state) + '">' + esc(session.trust_state) + '</span>',
|
|
965
|
+
(countSinceLastSeen(session.since_last_seen) > 0
|
|
966
|
+
? '<span class="badge alert">new ' + esc(countSinceLastSeen(session.since_last_seen)) + '</span>'
|
|
967
|
+
: ''),
|
|
799
968
|
session.binding_alert
|
|
800
969
|
? '<button type="button" class="badge alert rebind-badge-btn" data-session="' + esc(session.session_key) + '" data-conversation="' + esc(session.conversation_id || '') + '" data-bound-session="' + esc(session.mapped_work_session || '') + '" data-remote-did="' + esc(session.remote_did || '') + '" title="OpenClaw chat link needs attention">Needs reconnect</button>'
|
|
801
970
|
: '',
|
|
@@ -833,6 +1002,25 @@ function getHostPanelHtml() {
|
|
|
833
1002
|
});
|
|
834
1003
|
}
|
|
835
1004
|
|
|
1005
|
+
document.querySelectorAll('.transport-switch-btn').forEach(function (btn) {
|
|
1006
|
+
btn.addEventListener('click', async function () {
|
|
1007
|
+
const mode = btn.getAttribute('data-mode');
|
|
1008
|
+
if (mode !== 'bridge' && mode !== 'channel') return;
|
|
1009
|
+
const confirmed = window.confirm('Switch preferred transport to ' + mode + ' and request a managed restart?');
|
|
1010
|
+
if (!confirmed) return;
|
|
1011
|
+
const result = await api('/api/runtime/transport-switch', {
|
|
1012
|
+
method: 'POST',
|
|
1013
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1014
|
+
body: JSON.stringify({ mode: mode }),
|
|
1015
|
+
});
|
|
1016
|
+
window.alert(result && result.result && result.result.restarted
|
|
1017
|
+
? ('Preferred transport switched to ' + mode + '. Managed restart was attempted.')
|
|
1018
|
+
: ('Preferred transport switched to ' + mode + '. Restart is still required.'));
|
|
1019
|
+
await refreshAll();
|
|
1020
|
+
setTab('runtime');
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
1023
|
+
|
|
836
1024
|
const tasks = Array.isArray(overview.tasks) ? overview.tasks : [];
|
|
837
1025
|
document.getElementById('taskList').innerHTML = tasks.length
|
|
838
1026
|
? tasks.map(function (task) {
|
|
@@ -937,6 +1125,10 @@ conversation=' + result.conversation_id));
|
|
|
937
1125
|
? detail.collaborationProjection.preset
|
|
938
1126
|
: 'balanced';
|
|
939
1127
|
const projectionOutbox = Array.isArray(detail.projectionOutbox) ? detail.projectionOutbox : [];
|
|
1128
|
+
const sinceLastSeen = detail.sinceLastSeen || null;
|
|
1129
|
+
const deliveryTimeline = Array.isArray(detail.deliveryTimeline) ? detail.deliveryTimeline : [];
|
|
1130
|
+
const projectionPreview = detail.projectionPreview || null;
|
|
1131
|
+
const transportHealth = detail.transportHealth || null;
|
|
940
1132
|
const isAdvanced = state.detailMode === 'advanced';
|
|
941
1133
|
const sessionLink = buildSessionLink(session.session_key);
|
|
942
1134
|
const summaryPills = [];
|
|
@@ -972,7 +1164,8 @@ conversation=' + result.conversation_id));
|
|
|
972
1164
|
: (reopenRecommendation ? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(reopenRecommendation)) + '</div>' : '')) +
|
|
973
1165
|
(summaryPills.length ? '<div class="summary-pills">' + summaryPills.join('') + '</div>' : '') +
|
|
974
1166
|
(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
|
|
1167
|
+
? '<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.' +
|
|
1168
|
+
(pendingCollaborationEvents[0].overdue ? ' This item is overdue.' : '') + '</div></div>'
|
|
976
1169
|
: '') +
|
|
977
1170
|
(bindingAlert
|
|
978
1171
|
? '<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>'
|
|
@@ -1084,15 +1277,36 @@ conversation=' + result.conversation_id));
|
|
|
1084
1277
|
'</div></div>' +
|
|
1085
1278
|
'<div><div class="label">Human Thread Posture</div><div class="audit-list" style="margin-top:8px">' +
|
|
1086
1279
|
'<div class="audit-row"><div class="top"><strong>Projection policy</strong><span class="badge">' + esc(projectionPreset) + '</span></div>' +
|
|
1087
|
-
|
|
1088
|
-
|
|
1280
|
+
'<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>' +
|
|
1281
|
+
'<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>' +
|
|
1282
|
+
(sinceLastSeen
|
|
1283
|
+
? '<div class="summary-pills" style="margin-top:8px">' +
|
|
1284
|
+
'<span class="pill">new=' + esc(countSinceLastSeen(sinceLastSeen)) + '</span>' +
|
|
1285
|
+
'<span class="pill">decisions=' + esc(sinceLastSeen.new_decisions || 0) + '</span>' +
|
|
1286
|
+
'<span class="pill">failures=' + esc(sinceLastSeen.new_failures || 0) + '</span>' +
|
|
1287
|
+
'</div>'
|
|
1288
|
+
: '') +
|
|
1289
|
+
(transportHealth
|
|
1290
|
+
? '<div class="muted small" style="margin-top:8px">transport=' + esc(transportHealth.transport_mode || 'bridge') + ' \xB7 state=' + esc(transportHealth.state || 'Ready') + ' \xB7 retry_queue=' + esc(transportHealth.retry_queue_length || 0) + '</div>'
|
|
1291
|
+
: '') +
|
|
1089
1292
|
(projectionOutbox.length
|
|
1090
1293
|
? '<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
1294
|
: '<div class="muted small" style="margin-top:8px">outbox=clear</div>') +
|
|
1295
|
+
'<div style="margin-top:12px"><div class="label">Projection Preview</div><div style="margin-top:8px">' + renderProjectionPreviewBlock(projectionPreview) + '</div></div>' +
|
|
1092
1296
|
'</div>' +
|
|
1093
1297
|
'</div></div>' +
|
|
1094
1298
|
'</div>';
|
|
1095
1299
|
|
|
1300
|
+
el.innerHTML +=
|
|
1301
|
+
'<div class="grid two-col" style="margin-top:16px">' +
|
|
1302
|
+
'<div><div class="label">Since Last Seen</div><div class="audit-list" style="margin-top:8px">' +
|
|
1303
|
+
'<div class="audit-row">' + renderSinceLastSeenSummary(sinceLastSeen) + '</div>' +
|
|
1304
|
+
'</div></div>' +
|
|
1305
|
+
'<div><div class="label">Delivery Timeline</div><div class="audit-list" style="margin-top:8px">' +
|
|
1306
|
+
renderDeliveryTimeline(deliveryTimeline) +
|
|
1307
|
+
'</div></div>' +
|
|
1308
|
+
'</div>';
|
|
1309
|
+
|
|
1096
1310
|
el.querySelectorAll('.approve-session-btn').forEach(function (btn) {
|
|
1097
1311
|
btn.addEventListener('click', async function () {
|
|
1098
1312
|
const sessionKey = btn.getAttribute('data-session');
|
|
@@ -1272,6 +1486,7 @@ Previous chat link: ' + previous
|
|
|
1272
1486
|
policy.doc && policy.doc.collaboration_projection && policy.doc.collaboration_projection.preset
|
|
1273
1487
|
? policy.doc.collaboration_projection.preset
|
|
1274
1488
|
: 'balanced';
|
|
1489
|
+
document.getElementById('projectionPolicyPreview').innerHTML = renderProjectionPreviewBlock(state.session && state.session.projectionPreview ? state.session.projectionPreview : null);
|
|
1275
1490
|
document.getElementById('contactDefault').value = policy.doc.contact_policy.default_action;
|
|
1276
1491
|
document.getElementById('taskDefault').value = policy.doc.task_policy.default_action;
|
|
1277
1492
|
document.getElementById('profileDisplayName').value = profile && profile.display_name ? profile.display_name : '';
|
|
@@ -1443,6 +1658,7 @@ Previous chat link: ' + previous
|
|
|
1443
1658
|
syncSelectedSessionFromOverview();
|
|
1444
1659
|
renderHeader();
|
|
1445
1660
|
renderOverview();
|
|
1661
|
+
if (state.currentTab === 'runtime') await maybeMarkGlobalSeen('runtime');
|
|
1446
1662
|
if (state.selectedSessionKey) {
|
|
1447
1663
|
await loadSession(state.selectedSessionKey);
|
|
1448
1664
|
} else {
|
|
@@ -1454,10 +1670,14 @@ Previous chat link: ' + previous
|
|
|
1454
1670
|
|
|
1455
1671
|
async function loadSession(sessionKey) {
|
|
1456
1672
|
if (!sessionKey) return;
|
|
1673
|
+
const previousSessionKey = state.selectedSessionKey;
|
|
1457
1674
|
state.selectedSessionKey = sessionKey;
|
|
1458
1675
|
state.session = await api('/api/runtime/session?session_key=' + encodeURIComponent(sessionKey));
|
|
1459
1676
|
syncUrlState();
|
|
1460
1677
|
renderSession();
|
|
1678
|
+
if (state.currentTab === 'runtime') {
|
|
1679
|
+
await maybeMarkSessionSeen(sessionKey, previousSessionKey !== sessionKey);
|
|
1680
|
+
}
|
|
1461
1681
|
}
|
|
1462
1682
|
|
|
1463
1683
|
async function loadPolicy() {
|
|
@@ -1468,6 +1688,7 @@ Previous chat link: ' + previous
|
|
|
1468
1688
|
async function loadDecisions() {
|
|
1469
1689
|
state.decisions = await api('/api/runtime/collaboration-decisions');
|
|
1470
1690
|
renderDecisionInbox();
|
|
1691
|
+
if (state.currentTab === 'decisions') await maybeMarkGlobalSeen('decisions');
|
|
1471
1692
|
}
|
|
1472
1693
|
|
|
1473
1694
|
async function refreshAll() {
|
|
@@ -1897,6 +2118,42 @@ function readProjectionOutboxState(storePath, limit = 20) {
|
|
|
1897
2118
|
store.close();
|
|
1898
2119
|
}
|
|
1899
2120
|
}
|
|
2121
|
+
function readTransportHealthState(storePath) {
|
|
2122
|
+
const store = new LocalStore(storePath);
|
|
2123
|
+
try {
|
|
2124
|
+
const eventManager = new CollaborationEventManager(store);
|
|
2125
|
+
const outboxManager = new CollaborationProjectionOutboxManager(store);
|
|
2126
|
+
const runtimeStatus = readIngressRuntimeStatus();
|
|
2127
|
+
const preference = readTransportPreference();
|
|
2128
|
+
const normalizedStatus = runtimeStatus ? {
|
|
2129
|
+
...runtimeStatus,
|
|
2130
|
+
preferred_transport_mode: preference?.preferred_mode ?? runtimeStatus.preferred_transport_mode ?? "bridge"
|
|
2131
|
+
} : {
|
|
2132
|
+
receive_mode: "webhook",
|
|
2133
|
+
transport_mode: preference?.preferred_mode ?? "bridge",
|
|
2134
|
+
preferred_transport_mode: preference?.preferred_mode ?? "bridge"
|
|
2135
|
+
};
|
|
2136
|
+
return deriveTransportHealth({
|
|
2137
|
+
runtime_status: normalizedStatus,
|
|
2138
|
+
recent_events: eventManager.listRecent(30),
|
|
2139
|
+
projection_outbox_failed: outboxManager.listByStatus("failed", 20)
|
|
2140
|
+
});
|
|
2141
|
+
} finally {
|
|
2142
|
+
store.close();
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function readSinceLastSeenState(storePath, operatorId, scopeType, scopeKey) {
|
|
2146
|
+
const store = new LocalStore(storePath);
|
|
2147
|
+
try {
|
|
2148
|
+
return summarizeSinceLastSeen(store, {
|
|
2149
|
+
operator_id: operatorId,
|
|
2150
|
+
scope_type: scopeType,
|
|
2151
|
+
scope_key: scopeKey
|
|
2152
|
+
});
|
|
2153
|
+
} finally {
|
|
2154
|
+
store.close();
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
1900
2157
|
function buildPolicyDecisionShape(identityPath, remoteDid, opts) {
|
|
1901
2158
|
const policy = readTrustPolicyDoc(identityPath);
|
|
1902
2159
|
const runtimeMode = opts?.runtimeMode ?? getRuntimeMode();
|
|
@@ -2009,6 +2266,15 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
2009
2266
|
}, {});
|
|
2010
2267
|
const collaborationSummary = collaborationEventManager.summarize(200);
|
|
2011
2268
|
const projectionOutbox = readProjectionOutboxState(ctx.storePath, 20);
|
|
2269
|
+
const transportHealth = readTransportHealthState(ctx.storePath);
|
|
2270
|
+
const sinceLastSeen = readSinceLastSeenState(ctx.storePath, "host_panel", "global");
|
|
2271
|
+
const decisionStore = new LocalStore(ctx.storePath);
|
|
2272
|
+
let decisionViews = [];
|
|
2273
|
+
try {
|
|
2274
|
+
decisionViews = listPendingDecisionViews(decisionStore, 100);
|
|
2275
|
+
} finally {
|
|
2276
|
+
decisionStore.close();
|
|
2277
|
+
}
|
|
2012
2278
|
return {
|
|
2013
2279
|
did: ctx.myDid,
|
|
2014
2280
|
serverUrl: ctx.serverUrl,
|
|
@@ -2046,6 +2312,9 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
2046
2312
|
recommendationSummary: recommendationState.summary,
|
|
2047
2313
|
collaborationSummary,
|
|
2048
2314
|
projectionOutbox,
|
|
2315
|
+
transportHealth,
|
|
2316
|
+
sinceLastSeen,
|
|
2317
|
+
pendingDecisions: decisionViews,
|
|
2049
2318
|
recentCollaborationEvents: collaborationEventManager.listRecent(20),
|
|
2050
2319
|
sessions: sessions.map((session) => ({
|
|
2051
2320
|
...session,
|
|
@@ -2054,7 +2323,8 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
2054
2323
|
binding_alert: session.conversation_id ? bindingAlertByConversation.get(session.conversation_id) ?? null : null,
|
|
2055
2324
|
is_active_work_session: session.session_key === activeWorkSession,
|
|
2056
2325
|
latest_messages: session.conversation_id ? historyManager.listRecent(session.conversation_id, 3) : [],
|
|
2057
|
-
collaboration_events: collaborationEventManager.listBySession(session.session_key, 3)
|
|
2326
|
+
collaboration_events: collaborationEventManager.listBySession(session.session_key, 3),
|
|
2327
|
+
since_last_seen: readSinceLastSeenState(ctx.storePath, "host_panel", "session", session.session_key)
|
|
2058
2328
|
})),
|
|
2059
2329
|
tasks: refreshedTasks.map((task) => ({
|
|
2060
2330
|
...task,
|
|
@@ -2111,8 +2381,25 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2111
2381
|
});
|
|
2112
2382
|
const outboxStore = new LocalStore(ctx.storePath);
|
|
2113
2383
|
let projectionOutbox = [];
|
|
2384
|
+
let sinceLastSeen = null;
|
|
2385
|
+
let deliveryTimeline = [];
|
|
2386
|
+
let projectionPreview = null;
|
|
2387
|
+
let pendingDecisionViews = [];
|
|
2114
2388
|
try {
|
|
2115
2389
|
projectionOutbox = new CollaborationProjectionOutboxManager(outboxStore).listBySession(session.session_key, 20);
|
|
2390
|
+
sinceLastSeen = summarizeSinceLastSeen(outboxStore, {
|
|
2391
|
+
operator_id: "host_panel",
|
|
2392
|
+
scope_type: "session",
|
|
2393
|
+
scope_key: session.session_key
|
|
2394
|
+
});
|
|
2395
|
+
deliveryTimeline = buildDeliveryTimeline(outboxStore, session.session_key, 40);
|
|
2396
|
+
projectionPreview = buildProjectionPreview(
|
|
2397
|
+
outboxStore,
|
|
2398
|
+
session.session_key,
|
|
2399
|
+
policy.collaboration_projection.preset,
|
|
2400
|
+
5
|
|
2401
|
+
);
|
|
2402
|
+
pendingDecisionViews = listPendingDecisionViews(outboxStore, 100).filter((event) => event.session_key === session.session_key).slice(0, 20);
|
|
2116
2403
|
} finally {
|
|
2117
2404
|
outboxStore.close();
|
|
2118
2405
|
}
|
|
@@ -2120,9 +2407,10 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2120
2407
|
session,
|
|
2121
2408
|
sessionSummary: sessionSummaryManager.get(session.session_key),
|
|
2122
2409
|
collaborationEvents: collaborationEventManager.listBySession(session.session_key, 40),
|
|
2123
|
-
pendingCollaborationEvents:
|
|
2410
|
+
pendingCollaborationEvents: pendingDecisionViews,
|
|
2124
2411
|
collaborationSummary: collaborationEventManager.summarize(200),
|
|
2125
2412
|
ingressRuntime: readIngressRuntimeStatus(),
|
|
2413
|
+
transportHealth: readTransportHealthState(ctx.storePath),
|
|
2126
2414
|
binding,
|
|
2127
2415
|
bindingAlert,
|
|
2128
2416
|
activeWorkSession,
|
|
@@ -2131,6 +2419,9 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
2131
2419
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
2132
2420
|
collaborationProjection: policy.collaboration_projection,
|
|
2133
2421
|
projectionOutbox,
|
|
2422
|
+
sinceLastSeen,
|
|
2423
|
+
deliveryTimeline,
|
|
2424
|
+
projectionPreview,
|
|
2134
2425
|
policyExplain: buildPolicyDecisionShape(ctx.identityPath, session.remote_did, { runtimeMode: getRuntimeMode() }),
|
|
2135
2426
|
tasks: tasks.map((task) => ({
|
|
2136
2427
|
...task,
|
|
@@ -2207,6 +2498,70 @@ async function handleApi(pathname, req, ctx) {
|
|
|
2207
2498
|
status: readIngressRuntimeStatus()
|
|
2208
2499
|
};
|
|
2209
2500
|
}
|
|
2501
|
+
if (parts[1] === "transport-health" && req.method === "GET") {
|
|
2502
|
+
return {
|
|
2503
|
+
transportHealth: readTransportHealthState(ctx.storePath),
|
|
2504
|
+
transportPreference: readTransportPreference()
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
if (parts[1] === "transport-switch" && req.method === "POST") {
|
|
2508
|
+
const body = await readBody(req);
|
|
2509
|
+
const mode = String(body?.mode ?? "").trim().toLowerCase();
|
|
2510
|
+
if (mode !== "bridge" && mode !== "channel") throw new Error("mode must be bridge or channel");
|
|
2511
|
+
const result = switchTransportPreference(mode, {
|
|
2512
|
+
updated_by: "host_panel"
|
|
2513
|
+
});
|
|
2514
|
+
const store = new LocalStore(ctx.storePath);
|
|
2515
|
+
try {
|
|
2516
|
+
new CollaborationEventManager(store).record({
|
|
2517
|
+
event_type: "transport_switched",
|
|
2518
|
+
severity: result.restarted ? "notice" : "warning",
|
|
2519
|
+
summary: result.restarted ? `Preferred transport switched to ${mode}. A managed restart was attempted.` : `Preferred transport switched to ${mode}. Restart is still required.`,
|
|
2520
|
+
detail: {
|
|
2521
|
+
preferred_mode: result.preferred_mode,
|
|
2522
|
+
restart_required: result.restart_required,
|
|
2523
|
+
restart_method: result.restart_method ?? null,
|
|
2524
|
+
restart_error: result.restart_error ?? null,
|
|
2525
|
+
preference_path: result.preference_path
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
} finally {
|
|
2529
|
+
store.close();
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
ok: true,
|
|
2533
|
+
result,
|
|
2534
|
+
transportHealth: readTransportHealthState(ctx.storePath)
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
if (parts[1] === "seen" && req.method === "POST") {
|
|
2538
|
+
const body = await readBody(req);
|
|
2539
|
+
const operatorId = String(body?.operator_id ?? "host_panel").trim();
|
|
2540
|
+
const scopeType = String(body?.scope_type ?? "global").trim();
|
|
2541
|
+
const scopeKey = typeof body?.scope_key === "string" ? body.scope_key.trim() : void 0;
|
|
2542
|
+
if (!["host_panel", "tui", "mcp"].includes(operatorId)) throw new Error("operator_id must be host_panel, tui, or mcp");
|
|
2543
|
+
if (scopeType !== "global" && scopeType !== "session") throw new Error("scope_type must be global or session");
|
|
2544
|
+
const store = new LocalStore(ctx.storePath);
|
|
2545
|
+
try {
|
|
2546
|
+
const seen = new OperatorSeenStateManager(store).markSeen({
|
|
2547
|
+
operator_id: operatorId,
|
|
2548
|
+
scope_type: scopeType,
|
|
2549
|
+
scope_key: scopeType === "session" ? scopeKey : null,
|
|
2550
|
+
last_seen_ts: Date.now()
|
|
2551
|
+
});
|
|
2552
|
+
return {
|
|
2553
|
+
ok: true,
|
|
2554
|
+
seen,
|
|
2555
|
+
summary: summarizeSinceLastSeen(store, {
|
|
2556
|
+
operator_id: operatorId,
|
|
2557
|
+
scope_type: scopeType,
|
|
2558
|
+
scope_key: scopeType === "session" ? scopeKey : null
|
|
2559
|
+
})
|
|
2560
|
+
};
|
|
2561
|
+
} finally {
|
|
2562
|
+
store.close();
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2210
2565
|
if (parts[1] === "demo" && req.method === "POST") {
|
|
2211
2566
|
const body = await readBody(req);
|
|
2212
2567
|
const preset = typeof body?.preset === "string" ? body.preset.trim().toLowerCase() : "";
|
|
@@ -2262,7 +2617,7 @@ async function handleApi(pathname, req, ctx) {
|
|
|
2262
2617
|
const manager = new CollaborationEventManager(store);
|
|
2263
2618
|
const outboxManager = new CollaborationProjectionOutboxManager(store);
|
|
2264
2619
|
if (req.method === "GET") {
|
|
2265
|
-
const pendingDecisions =
|
|
2620
|
+
const pendingDecisions = listPendingDecisionViews(store, 100);
|
|
2266
2621
|
return {
|
|
2267
2622
|
pendingDecisions,
|
|
2268
2623
|
projectionOutboxPending: outboxManager.listByStatus("pending", 50),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pingagent/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"uuid": "^11.0.0",
|
|
36
36
|
"ws": "^8.0.0",
|
|
37
37
|
"@pingagent/protocol": "0.1.1",
|
|
38
|
-
"@pingagent/
|
|
39
|
-
"@pingagent/
|
|
38
|
+
"@pingagent/schemas": "0.1.4",
|
|
39
|
+
"@pingagent/a2a": "0.1.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "^7.6.0",
|