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