@pingagent/sdk 0.1.10 → 0.1.11
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 +878 -6
- package/dist/chunk-6OA4F66H.js +3193 -0
- package/dist/chunk-BLHMTUID.js +3610 -0
- package/dist/chunk-HCQ7CEDE.js +3556 -0
- package/dist/chunk-R3D7LOGB.js +3553 -0
- package/dist/chunk-SMDQYV7Z.js +3173 -0
- package/dist/chunk-YBNFPOKO.js +3553 -0
- package/dist/index.d.ts +229 -8
- package/dist/index.js +21 -1
- package/dist/web-server.js +732 -13
- package/package.json +3 -3
package/dist/web-server.js
CHANGED
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
getActiveSessionFilePath,
|
|
12
12
|
getSessionBindingAlertsFilePath,
|
|
13
13
|
getSessionMapFilePath,
|
|
14
|
+
getTrustRecommendationActionLabel,
|
|
14
15
|
loadIdentity,
|
|
15
16
|
normalizeTrustPolicyDoc,
|
|
16
17
|
readCurrentActiveSessionKey,
|
|
18
|
+
readIngressRuntimeStatus,
|
|
17
19
|
readSessionBindingAlerts,
|
|
18
20
|
readSessionBindings,
|
|
19
21
|
removeSessionBinding,
|
|
@@ -21,12 +23,13 @@ import {
|
|
|
21
23
|
summarizeTrustPolicyAudit,
|
|
22
24
|
updateStoredToken,
|
|
23
25
|
upsertTrustPolicyRecommendation
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-BLHMTUID.js";
|
|
25
27
|
|
|
26
28
|
// src/web-server.ts
|
|
27
29
|
import * as fs from "fs";
|
|
28
30
|
import * as http from "http";
|
|
29
31
|
import * as path from "path";
|
|
32
|
+
import { spawnSync } from "child_process";
|
|
30
33
|
import { SCHEMA_TEXT } from "@pingagent/schemas";
|
|
31
34
|
|
|
32
35
|
// src/host-panel-html.ts
|
|
@@ -75,6 +78,30 @@ function getHostPanelHtml() {
|
|
|
75
78
|
padding: 16px;
|
|
76
79
|
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22);
|
|
77
80
|
}
|
|
81
|
+
.status-strip {
|
|
82
|
+
display: flex;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
gap: 16px;
|
|
85
|
+
align-items: flex-start;
|
|
86
|
+
flex-wrap: wrap;
|
|
87
|
+
}
|
|
88
|
+
.status-main { display: grid; gap: 8px; }
|
|
89
|
+
.status-main h2 { margin: 0; font-size: 18px; }
|
|
90
|
+
.status-state {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 8px;
|
|
94
|
+
width: fit-content;
|
|
95
|
+
padding: 6px 12px;
|
|
96
|
+
border-radius: 999px;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
letter-spacing: 0.08em;
|
|
99
|
+
text-transform: uppercase;
|
|
100
|
+
border: 1px solid #334155;
|
|
101
|
+
}
|
|
102
|
+
.status-state.ready { background: rgba(34, 197, 94, 0.12); color: #86efac; border-color: rgba(34, 197, 94, 0.35); }
|
|
103
|
+
.status-state.degraded { background: rgba(248, 113, 113, 0.14); color: #fecaca; border-color: rgba(248, 113, 113, 0.45); }
|
|
104
|
+
.quickstart-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
|
|
78
105
|
.card h2, .card h3 { margin: 0 0 12px; font-size: 16px; }
|
|
79
106
|
.stats .value { font-size: 28px; font-weight: 700; margin-top: 6px; }
|
|
80
107
|
.runtime-layout { grid-template-columns: minmax(260px, 340px) minmax(0, 1fr); align-items: start; }
|
|
@@ -155,11 +182,17 @@ function getHostPanelHtml() {
|
|
|
155
182
|
</div>
|
|
156
183
|
<div class="header-actions">
|
|
157
184
|
<span class="pill" id="runtimeModePill">runtime_mode=bridge</span>
|
|
185
|
+
<span class="pill" id="receiveModePill">receive_mode=webhook</span>
|
|
158
186
|
<span class="pill" id="policyPathPill">policy=\u2026</span>
|
|
187
|
+
<button class="secondary-btn" id="fixHooksBtn" style="width:auto">Fix OpenClaw Hooks</button>
|
|
188
|
+
<button class="secondary-btn" id="publicLinkBtn" style="width:auto">Public Link</button>
|
|
189
|
+
<button class="secondary-btn" id="contactCardBtn" style="width:auto">Contact Card</button>
|
|
190
|
+
<a class="secondary-btn" href="/a/demo" target="_blank" rel="noreferrer" style="width:auto">Try Demo Agent</a>
|
|
159
191
|
</div>
|
|
160
192
|
</div>
|
|
161
193
|
|
|
162
194
|
<section id="runtimePanel" class="panel active">
|
|
195
|
+
<div class="card" id="activationCard" style="margin-bottom:16px"></div>
|
|
163
196
|
<div class="grid stats" id="statsGrid"></div>
|
|
164
197
|
<div class="grid runtime-layout" style="margin-top:16px">
|
|
165
198
|
<div class="card">
|
|
@@ -253,6 +286,40 @@ function getHostPanelHtml() {
|
|
|
253
286
|
<div class="audit-list" id="policyAuditList"></div>
|
|
254
287
|
</div>
|
|
255
288
|
</div>
|
|
289
|
+
|
|
290
|
+
<div class="card" style="margin-top:16px">
|
|
291
|
+
<h2>Profile + Capability Card</h2>
|
|
292
|
+
<div class="form-grid">
|
|
293
|
+
<label class="label">Display name</label>
|
|
294
|
+
<input id="profileDisplayName" placeholder="Agent name">
|
|
295
|
+
<label class="label">Bio</label>
|
|
296
|
+
<textarea id="profileBio" placeholder="What this agent does"></textarea>
|
|
297
|
+
<label class="label">Tags (comma separated)</label>
|
|
298
|
+
<input id="profileTags" placeholder="coding, devops">
|
|
299
|
+
<label class="label">Legacy capabilities (comma separated)</label>
|
|
300
|
+
<input id="profileCapabilities" placeholder="coding, testing">
|
|
301
|
+
<label class="label">Capability summary</label>
|
|
302
|
+
<textarea id="capabilityCardSummary" placeholder="Short machine-readable summary"></textarea>
|
|
303
|
+
<label class="label">Accepts new work</label>
|
|
304
|
+
<select id="capabilityCardAcceptsNewWork">
|
|
305
|
+
<option value="">(unspecified)</option>
|
|
306
|
+
<option value="true">true</option>
|
|
307
|
+
<option value="false">false</option>
|
|
308
|
+
</select>
|
|
309
|
+
<label class="label">Preferred contact mode</label>
|
|
310
|
+
<select id="capabilityCardContactMode">
|
|
311
|
+
<option value="">(unspecified)</option>
|
|
312
|
+
<option value="dm">dm</option>
|
|
313
|
+
<option value="task">task</option>
|
|
314
|
+
<option value="either">either</option>
|
|
315
|
+
</select>
|
|
316
|
+
<label class="label">Capability entries JSON</label>
|
|
317
|
+
<textarea id="capabilityCardItems" placeholder='[{"id":"coding","label":"Coding","accepts_tasks":true}]'></textarea>
|
|
318
|
+
<div class="row-actions">
|
|
319
|
+
<button class="action-btn" id="saveProfileBtn">Save Profile</button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
256
323
|
</section>
|
|
257
324
|
</main>
|
|
258
325
|
</div>
|
|
@@ -277,6 +344,13 @@ function getHostPanelHtml() {
|
|
|
277
344
|
.replace(/'/g, ''');
|
|
278
345
|
}
|
|
279
346
|
|
|
347
|
+
function parseCsvList(value) {
|
|
348
|
+
return String(value == null ? '' : value)
|
|
349
|
+
.split(',')
|
|
350
|
+
.map(function (item) { return item.trim(); })
|
|
351
|
+
.filter(Boolean);
|
|
352
|
+
}
|
|
353
|
+
|
|
280
354
|
function fmtTs(value) {
|
|
281
355
|
if (!value) return '-';
|
|
282
356
|
try { return new Date(value).toLocaleString(); } catch { return String(value); }
|
|
@@ -338,20 +412,117 @@ function getHostPanelHtml() {
|
|
|
338
412
|
const profileLabel = state.selectedProfile ? 'profile=' + state.selectedProfile : 'Select profile';
|
|
339
413
|
const title = overview ? ('Host Panel \xB7 ' + overview.did) : 'PingAgent Host Panel';
|
|
340
414
|
const tier = overview && overview.subscription ? overview.subscription.tier : null;
|
|
415
|
+
const receiveMode = overview && overview.ingressRuntime ? overview.ingressRuntime.receive_mode : 'webhook';
|
|
341
416
|
document.getElementById('headerTitle').textContent = title;
|
|
342
417
|
document.getElementById('headerSubtitle').textContent = overview
|
|
343
418
|
? (profileLabel + ' \xB7 ' + overview.serverUrl + (tier ? (' \xB7 tier=' + tier) : '') + ' \xB7 sessions=' + overview.sessionsTotal + ' \xB7 unread=' + overview.unreadTotal)
|
|
344
419
|
: profileLabel;
|
|
345
420
|
document.getElementById('runtimeModePill').textContent = overview ? ('runtime_mode=' + overview.runtimeMode) : 'runtime_mode=\u2026';
|
|
421
|
+
document.getElementById('receiveModePill').textContent = 'receive_mode=' + receiveMode;
|
|
346
422
|
document.getElementById('policyPathPill').textContent = overview ? ('policy=' + overview.trustPolicyPath) : 'policy=\u2026';
|
|
423
|
+
document.getElementById('fixHooksBtn').textContent = receiveMode === 'polling_degraded' || (overview && overview.ingressRuntime && overview.ingressRuntime.hooks_last_error) ? 'Fix now' : 'Fix OpenClaw Hooks';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function ingressStatusModel(overview) {
|
|
427
|
+
const ingress = overview && overview.ingressRuntime ? overview.ingressRuntime : null;
|
|
428
|
+
const degraded = !ingress || ingress.receive_mode === 'polling_degraded' || !!ingress.hooks_last_error;
|
|
429
|
+
return {
|
|
430
|
+
degraded: degraded,
|
|
431
|
+
label: degraded ? 'Degraded' : 'Ready',
|
|
432
|
+
className: degraded ? 'degraded' : 'ready',
|
|
433
|
+
detail: degraded
|
|
434
|
+
? (ingress && ingress.hooks_last_error ? ingress.hooks_last_error : 'Webhook ingress is degraded. Fix hooks or keep running on polling fallback.')
|
|
435
|
+
: 'Webhook ingress is healthy. New inbound messages land on the main runtime path.',
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function recommendationActionLabel(item) {
|
|
440
|
+
if (!item) return 'Apply Recommendation';
|
|
441
|
+
if (item.status === 'dismissed' || item.status === 'superseded') return 'Reopen';
|
|
442
|
+
if (item.status === 'applied') return 'Applied';
|
|
443
|
+
if (item.primary_action_label) return item.primary_action_label;
|
|
444
|
+
if (item.policy === 'contact' && item.action === 'approve') return 'Approve + remember sender';
|
|
445
|
+
if (item.policy === 'contact' && item.action === 'manual') return 'Keep contact manual';
|
|
446
|
+
if (item.policy === 'contact' && item.action === 'reject') return 'Block this sender';
|
|
447
|
+
if (item.policy === 'task' && item.action === 'bridge') return 'Keep tasks manual';
|
|
448
|
+
if (item.policy === 'task' && item.action === 'execute') return 'Allow tasks from this sender';
|
|
449
|
+
if (item.policy === 'task' && item.action === 'deny') return 'Block tasks from this sender';
|
|
450
|
+
return 'Apply Recommendation';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function formatCapabilityCardEditor(card) {
|
|
454
|
+
if (!card) return '';
|
|
455
|
+
try {
|
|
456
|
+
return JSON.stringify(Array.isArray(card.capabilities) ? card.capabilities : [], null, 2);
|
|
457
|
+
} catch {
|
|
458
|
+
return '';
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function renderSummaryBlock(summary) {
|
|
463
|
+
if (!summary) {
|
|
464
|
+
return '<div class="empty">No carry-forward summary yet. Save one below to make delegation and session continuation cheaper.</div>';
|
|
465
|
+
}
|
|
466
|
+
const parts = [
|
|
467
|
+
summary.objective ? '<div><span class="label">Objective</span><div style="margin-top:6px">' + esc(summary.objective) + '</div></div>' : '',
|
|
468
|
+
summary.context ? '<div><span class="label">Context</span><div style="margin-top:6px">' + esc(summary.context) + '</div></div>' : '',
|
|
469
|
+
summary.constraints ? '<div><span class="label">Constraints</span><div style="margin-top:6px">' + esc(summary.constraints) + '</div></div>' : '',
|
|
470
|
+
summary.decisions ? '<div><span class="label">Decisions</span><div style="margin-top:6px">' + esc(summary.decisions) + '</div></div>' : '',
|
|
471
|
+
summary.open_questions ? '<div><span class="label">Open Questions</span><div style="margin-top:6px">' + esc(summary.open_questions) + '</div></div>' : '',
|
|
472
|
+
summary.next_action ? '<div><span class="label">Next Action</span><div style="margin-top:6px">' + esc(summary.next_action) + '</div></div>' : '',
|
|
473
|
+
summary.handoff_ready_text ? '<div><span class="label">Handoff Ready</span><pre style="margin-top:6px">' + esc(summary.handoff_ready_text) + '</pre></div>' : '',
|
|
474
|
+
].filter(Boolean);
|
|
475
|
+
parts.push('<div class="muted small">updated=' + esc(fmtTs(summary.updated_at)) + '</div>');
|
|
476
|
+
return parts.join('');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function renderHandoffBlock(handoff) {
|
|
480
|
+
if (!handoff) return '';
|
|
481
|
+
return '' +
|
|
482
|
+
'<div style="margin-top:8px;padding:10px 12px;border:1px solid #1d4ed8;border-radius:10px;background:rgba(30,64,175,0.18)">' +
|
|
483
|
+
'<div class="label">Handoff</div>' +
|
|
484
|
+
(handoff.objective ? '<div class="muted small" style="margin-top:6px">objective=' + esc(handoff.objective) + '</div>' : '') +
|
|
485
|
+
(handoff.priority ? '<div class="muted small">priority=' + esc(handoff.priority) + '</div>' : '') +
|
|
486
|
+
(handoff.success_criteria ? '<div class="muted small">success=' + esc(handoff.success_criteria) + '</div>' : '') +
|
|
487
|
+
(handoff.callback_session_key ? '<div class="muted small">callback=' + esc(handoff.callback_session_key) + '</div>' : '') +
|
|
488
|
+
(handoff.delegated_by || handoff.delegated_to
|
|
489
|
+
? '<div class="muted small">delegated_by=' + esc(handoff.delegated_by || '(unknown)') + ' \xB7 delegated_to=' + esc(handoff.delegated_to || '(unknown)') + '</div>'
|
|
490
|
+
: '') +
|
|
491
|
+
(handoff.carry_forward_summary
|
|
492
|
+
? '<pre style="margin-top:8px">' + esc(handoff.carry_forward_summary) + '</pre>'
|
|
493
|
+
: '') +
|
|
494
|
+
'</div>';
|
|
347
495
|
}
|
|
348
496
|
|
|
349
497
|
function renderOverview() {
|
|
350
498
|
const overview = state.overview;
|
|
351
499
|
if (!overview) return;
|
|
500
|
+
const ingressState = ingressStatusModel(overview);
|
|
501
|
+
document.getElementById('activationCard').innerHTML =
|
|
502
|
+
'<div class="status-strip">' +
|
|
503
|
+
'<div class="status-main">' +
|
|
504
|
+
'<h2>Activation</h2>' +
|
|
505
|
+
'<div class="status-state ' + ingressState.className + '">' + esc(ingressState.label) + '</div>' +
|
|
506
|
+
'<div class="muted small">' + esc(ingressState.detail) + '</div>' +
|
|
507
|
+
'<div class="muted small">Public link: ' + esc(overview.publicSelf && overview.publicSelf.public_url ? overview.publicSelf.public_url : '(not ready yet)') + '</div>' +
|
|
508
|
+
'</div>' +
|
|
509
|
+
'<div style="min-width:320px">' +
|
|
510
|
+
'<div class="label">Quick Start</div>' +
|
|
511
|
+
'<div class="quickstart-row">' +
|
|
512
|
+
'<button class="action-btn demo-preset-btn" data-preset="hello">Demo: hello</button>' +
|
|
513
|
+
'<button class="secondary-btn demo-preset-btn" data-preset="delegate">Demo: delegate</button>' +
|
|
514
|
+
'<button class="secondary-btn demo-preset-btn" data-preset="trust">Demo: trust</button>' +
|
|
515
|
+
'</div>' +
|
|
516
|
+
'<div class="quickstart-row">' +
|
|
517
|
+
'<button class="secondary-btn" id="copyPublicLinkBtn">Copy Public Link</button>' +
|
|
518
|
+
'<button class="secondary-btn" id="makeContactCardBtn">Share Contact Card</button>' +
|
|
519
|
+
'</div>' +
|
|
520
|
+
'</div>' +
|
|
521
|
+
'</div>';
|
|
352
522
|
const subscription = overview.subscription || null;
|
|
353
523
|
const stats = [
|
|
354
524
|
{ label: 'Plan', value: subscription ? subscription.tier : 'ghost', sub: subscription ? subscription.summary : 'subscription unavailable' },
|
|
525
|
+
{ label: 'Ingress', value: overview.ingressRuntime ? overview.ingressRuntime.receive_mode : 'webhook', sub: overview.ingressRuntime && overview.ingressRuntime.reason ? overview.ingressRuntime.reason : 'OpenClaw ingress receive mode' },
|
|
355
526
|
{ label: 'Relay', value: subscription ? (subscription.usage.relay_today + '/' + subscription.usage.relay_limit) : '-', sub: subscription ? ('retention=' + subscription.retention_label) : 'daily relay usage' },
|
|
356
527
|
{ label: 'Alias', value: subscription ? (subscription.usage.alias_count + '/' + subscription.usage.alias_limit) : '-', sub: subscription ? (subscription.audit_export_allowed ? 'audit export enabled' : 'audit export off on this plan') : 'identity limits' },
|
|
357
528
|
{ label: 'Sessions', value: overview.sessionsTotal, sub: JSON.stringify(overview.trustCounts || {}) },
|
|
@@ -359,6 +530,7 @@ function getHostPanelHtml() {
|
|
|
359
530
|
{ label: 'Tasks', value: overview.tasksTotal, sub: 'recent local task threads' },
|
|
360
531
|
{ label: 'Audit', value: overview.auditSummary.total_events, sub: 'policy / runtime audit events' },
|
|
361
532
|
{ label: 'Recommendations', value: overview.recommendationSummary ? overview.recommendationSummary.total : overview.recommendations.length, sub: overview.recommendationSummary ? JSON.stringify(overview.recommendationSummary.by_status || {}) : 'learned policy suggestions' },
|
|
533
|
+
{ 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' },
|
|
362
534
|
];
|
|
363
535
|
document.getElementById('statsGrid').innerHTML = stats.map(function (item) {
|
|
364
536
|
return '<div class="card"><div class="label">' + esc(item.label) + '</div><div class="value">' + esc(item.value) + '</div><div class="muted small">' + esc(item.sub) + '</div></div>';
|
|
@@ -416,6 +588,7 @@ function getHostPanelHtml() {
|
|
|
416
588
|
'<div class="muted small">updated=' + esc(fmtTs(task.updated_at)) + '</div>' +
|
|
417
589
|
(task.result_summary ? '<div style="margin-top:8px">' + esc(task.result_summary) + '</div>' : '') +
|
|
418
590
|
(task.error_message ? '<div style="margin-top:8px;color:#fca5a5">' + esc(task.error_message) + '</div>' : '') +
|
|
591
|
+
renderHandoffBlock(task.handoff) +
|
|
419
592
|
'</div>';
|
|
420
593
|
}).join('')
|
|
421
594
|
: '<div class="empty">No recent task threads.</div>';
|
|
@@ -433,6 +606,56 @@ function getHostPanelHtml() {
|
|
|
433
606
|
: '') +
|
|
434
607
|
'</div>' + document.getElementById('taskList').innerHTML;
|
|
435
608
|
}
|
|
609
|
+
|
|
610
|
+
document.querySelectorAll('.demo-preset-btn').forEach(function (btn) {
|
|
611
|
+
btn.addEventListener('click', async function () {
|
|
612
|
+
const preset = btn.getAttribute('data-preset');
|
|
613
|
+
const result = await api('/api/runtime/demo', {
|
|
614
|
+
method: 'POST',
|
|
615
|
+
headers: { 'Content-Type': 'application/json' },
|
|
616
|
+
body: JSON.stringify({ preset: preset }),
|
|
617
|
+
});
|
|
618
|
+
window.alert(result.sent
|
|
619
|
+
? ('Demo agent messaged.
|
|
620
|
+
|
|
621
|
+
conversation=' + result.conversation_id)
|
|
622
|
+
: ('Demo agent ready.
|
|
623
|
+
|
|
624
|
+
conversation=' + result.conversation_id));
|
|
625
|
+
await refreshAll();
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
const copyPublicLinkBtn = document.getElementById('copyPublicLinkBtn');
|
|
629
|
+
if (copyPublicLinkBtn) {
|
|
630
|
+
copyPublicLinkBtn.addEventListener('click', async function () {
|
|
631
|
+
const url = overview.publicSelf && overview.publicSelf.public_url ? overview.publicSelf.public_url : '';
|
|
632
|
+
if (!url) {
|
|
633
|
+
window.alert('No public link yet. Use Public Link or refresh after hosted auto-create.');
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
await navigator.clipboard.writeText(url);
|
|
638
|
+
window.alert('Copied public link:
|
|
639
|
+
' + url);
|
|
640
|
+
} catch {
|
|
641
|
+
window.prompt('Copy public link', url);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const makeContactCardBtn = document.getElementById('makeContactCardBtn');
|
|
646
|
+
if (makeContactCardBtn) {
|
|
647
|
+
makeContactCardBtn.addEventListener('click', async function () {
|
|
648
|
+
const result = await api('/api/public/contact-card', {
|
|
649
|
+
method: 'POST',
|
|
650
|
+
headers: { 'Content-Type': 'application/json' },
|
|
651
|
+
body: JSON.stringify({
|
|
652
|
+
target_did: overview && overview.did ? overview.did : undefined,
|
|
653
|
+
}),
|
|
654
|
+
});
|
|
655
|
+
window.alert('Contact card ready:
|
|
656
|
+
' + (result.share_url || '(missing share_url)'));
|
|
657
|
+
});
|
|
658
|
+
}
|
|
436
659
|
}
|
|
437
660
|
|
|
438
661
|
function renderSession() {
|
|
@@ -449,9 +672,12 @@ function getHostPanelHtml() {
|
|
|
449
672
|
const messages = Array.isArray(detail.messages) ? detail.messages : [];
|
|
450
673
|
const auditEvents = Array.isArray(detail.auditEvents) ? detail.auditEvents : [];
|
|
451
674
|
const recommendations = Array.isArray(detail.recommendations) ? detail.recommendations : [];
|
|
675
|
+
const openRecommendation = recommendations.find(function (item) { return item.status === 'open'; }) || null;
|
|
676
|
+
const reopenRecommendation = recommendations.find(function (item) { return item.status === 'dismissed' || item.status === 'superseded'; }) || null;
|
|
452
677
|
const binding = detail.binding || null;
|
|
453
678
|
const bindingAlert = detail.bindingAlert || null;
|
|
454
679
|
const activeWorkSession = detail.activeWorkSession || null;
|
|
680
|
+
const summary = detail.sessionSummary || null;
|
|
455
681
|
|
|
456
682
|
el.innerHTML = '' +
|
|
457
683
|
'<div class="two-col">' +
|
|
@@ -464,10 +690,22 @@ function getHostPanelHtml() {
|
|
|
464
690
|
'<div class="muted small">last activity=' + esc(fmtTs(session.last_remote_activity_at || session.updated_at)) + '</div>' +
|
|
465
691
|
'<div class="muted small" style="margin-top:8px">active_chat_session=' + esc(activeWorkSession || '(none)') + '</div>' +
|
|
466
692
|
'<div class="muted small">binding=' + esc(binding ? binding.session_key : '(unbound)') + '</div>' +
|
|
693
|
+
(openRecommendation
|
|
694
|
+
? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(openRecommendation)) + '</div>'
|
|
695
|
+
: (reopenRecommendation ? '<div class="muted small" style="margin-top:8px">trust_action=' + esc(recommendationActionLabel(reopenRecommendation)) + '</div>' : '')) +
|
|
467
696
|
(bindingAlert
|
|
468
697
|
? '<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 rebind</strong><div class="small" style="margin-top:6px">' + esc(bindingAlert.message || 'Bound work session is missing. Rebind this PingAgent conversation to the current chat session.') + '</div></div>'
|
|
469
698
|
: '') +
|
|
470
699
|
'<div class="row-actions">' +
|
|
700
|
+
(openRecommendation
|
|
701
|
+
? '<button class="action-btn apply-session-recommendation-btn" data-session="' + esc(session.session_key) + '">' + esc(recommendationActionLabel(openRecommendation)) + '</button>'
|
|
702
|
+
: '') +
|
|
703
|
+
(openRecommendation
|
|
704
|
+
? '<button class="danger-btn dismiss-session-recommendation-btn" data-session="' + esc(session.session_key) + '">Dismiss</button>'
|
|
705
|
+
: '') +
|
|
706
|
+
(!openRecommendation && reopenRecommendation
|
|
707
|
+
? '<button class="secondary-btn reopen-session-recommendation-btn" data-session="' + esc(session.session_key) + '">Reopen</button>'
|
|
708
|
+
: '') +
|
|
471
709
|
'<button class="action-btn bind-current-btn" data-conversation="' + esc(session.conversation_id || '') + '">Bind Current Chat</button>' +
|
|
472
710
|
'<button class="danger-btn clear-binding-btn" data-conversation="' + esc(session.conversation_id || '') + '">Clear Binding</button>' +
|
|
473
711
|
'</div>' +
|
|
@@ -477,6 +715,27 @@ function getHostPanelHtml() {
|
|
|
477
715
|
'<pre style="margin-top:8px">[Contact]\\naction=' + esc(contact.action) + '\\nsource=' + esc(contact.source) + (contact.matched_rule ? '\\nmatched_rule=' + esc(contact.matched_rule) : '') + '\\n' + esc(contact.explanation) + '\\n\\n[Task]\\naction=' + esc(task.action) + '\\nsource=' + esc(task.source) + (task.matched_rule ? '\\nmatched_rule=' + esc(task.matched_rule) : '') + '\\n' + esc(task.explanation) + '</pre>' +
|
|
478
716
|
'</div>' +
|
|
479
717
|
'</div>' +
|
|
718
|
+
'<div class="grid two-col" style="margin-top:16px">' +
|
|
719
|
+
'<div>' +
|
|
720
|
+
'<div class="label">Carry-Forward Summary</div>' +
|
|
721
|
+
'<div style="margin-top:8px">' + renderSummaryBlock(summary) + '</div>' +
|
|
722
|
+
'</div>' +
|
|
723
|
+
'<div>' +
|
|
724
|
+
'<div class="label">Update Summary</div>' +
|
|
725
|
+
'<div class="form-grid" style="margin-top:8px">' +
|
|
726
|
+
'<input id="sessionSummaryObjective" placeholder="Objective" value="' + esc(summary && summary.objective ? summary.objective : '') + '">' +
|
|
727
|
+
'<textarea id="sessionSummaryContext" placeholder="Context">' + esc(summary && summary.context ? summary.context : '') + '</textarea>' +
|
|
728
|
+
'<textarea id="sessionSummaryConstraints" placeholder="Constraints">' + esc(summary && summary.constraints ? summary.constraints : '') + '</textarea>' +
|
|
729
|
+
'<textarea id="sessionSummaryDecisions" placeholder="Decisions">' + esc(summary && summary.decisions ? summary.decisions : '') + '</textarea>' +
|
|
730
|
+
'<textarea id="sessionSummaryOpenQuestions" placeholder="Open questions">' + esc(summary && summary.open_questions ? summary.open_questions : '') + '</textarea>' +
|
|
731
|
+
'<textarea id="sessionSummaryNextAction" placeholder="Next action">' + esc(summary && summary.next_action ? summary.next_action : '') + '</textarea>' +
|
|
732
|
+
'<textarea id="sessionSummaryHandoff" placeholder="Handoff-ready summary">' + esc(summary && summary.handoff_ready_text ? summary.handoff_ready_text : '') + '</textarea>' +
|
|
733
|
+
'<div class="row-actions">' +
|
|
734
|
+
'<button class="action-btn" id="saveSessionSummaryBtn">Save Summary</button>' +
|
|
735
|
+
'</div>' +
|
|
736
|
+
'</div>' +
|
|
737
|
+
'</div>' +
|
|
738
|
+
'</div>' +
|
|
480
739
|
'<div class="grid two-col" style="margin-top:16px">' +
|
|
481
740
|
'<div><div class="label">Task Threads</div><div class="task-list" style="margin-top:8px">' +
|
|
482
741
|
(tasks.length ? tasks.map(function (taskItem) {
|
|
@@ -484,15 +743,26 @@ function getHostPanelHtml() {
|
|
|
484
743
|
'<div class="muted small">updated=' + esc(fmtTs(taskItem.updated_at)) + '</div>' +
|
|
485
744
|
(taskItem.result_summary ? '<div style="margin-top:8px">' + esc(taskItem.result_summary) + '</div>' : '') +
|
|
486
745
|
(taskItem.error_message ? '<div style="margin-top:8px;color:#fca5a5">' + esc(taskItem.error_message) + '</div>' : '') +
|
|
746
|
+
renderHandoffBlock(taskItem.handoff) +
|
|
487
747
|
'</div>';
|
|
488
748
|
}).join('') : '<div class="empty">No tasks in this session.</div>') +
|
|
489
749
|
'</div></div>' +
|
|
490
750
|
'<div><div class="label">Learned Recommendations</div><div class="recommendation-list" style="margin-top:8px">' +
|
|
491
751
|
(recommendations.length ? recommendations.map(function (item) {
|
|
752
|
+
const actionButton = item.status === 'open'
|
|
753
|
+
? '<button class="action-btn apply-session-recommendation-btn" data-session="' + esc(session.session_key) + '">' + esc(recommendationActionLabel(item)) + '</button>'
|
|
754
|
+
: '';
|
|
755
|
+
const dismissButton = item.status === 'open'
|
|
756
|
+
? '<button class="danger-btn dismiss-session-recommendation-btn" data-session="' + esc(session.session_key) + '">Dismiss</button>'
|
|
757
|
+
: '';
|
|
758
|
+
const reopenButton = (item.status === 'dismissed' || item.status === 'superseded')
|
|
759
|
+
? '<button class="secondary-btn reopen-session-recommendation-btn" data-session="' + esc(session.session_key) + '">Reopen</button>'
|
|
760
|
+
: '';
|
|
492
761
|
return '<div class="recommendation-row"><div class="top"><strong>' + esc(item.policy) + '</strong><span class="badge">' + esc(item.status + ' \xB7 ' + item.action) + '</span></div>' +
|
|
493
762
|
'<div class="muted small">current=' + esc(item.current_action) + ' \xB7 confidence=' + esc(item.confidence) + '</div>' +
|
|
494
763
|
'<div class="muted small">match=' + esc(item.match) + '</div>' +
|
|
495
764
|
'<div style="margin-top:8px">' + esc(item.reason) + '</div>' +
|
|
765
|
+
'<div class="row-actions">' + actionButton + dismissButton + reopenButton + '</div>' +
|
|
496
766
|
'</div>';
|
|
497
767
|
}).join('') : '<div class="empty">No learned recommendation for this session.</div>') +
|
|
498
768
|
'</div></div>' +
|
|
@@ -534,6 +804,60 @@ function getHostPanelHtml() {
|
|
|
534
804
|
setTab('runtime');
|
|
535
805
|
});
|
|
536
806
|
});
|
|
807
|
+
el.querySelectorAll('.apply-session-recommendation-btn').forEach(function (btn) {
|
|
808
|
+
btn.addEventListener('click', async function () {
|
|
809
|
+
await api('/api/runtime/policy/recommendations/apply-session', {
|
|
810
|
+
method: 'POST',
|
|
811
|
+
headers: { 'Content-Type': 'application/json' },
|
|
812
|
+
body: JSON.stringify({ session_key: btn.getAttribute('data-session') }),
|
|
813
|
+
});
|
|
814
|
+
await refreshAll();
|
|
815
|
+
setTab('runtime');
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
el.querySelectorAll('.dismiss-session-recommendation-btn').forEach(function (btn) {
|
|
819
|
+
btn.addEventListener('click', async function () {
|
|
820
|
+
await api('/api/runtime/policy/recommendations/dismiss-session', {
|
|
821
|
+
method: 'POST',
|
|
822
|
+
headers: { 'Content-Type': 'application/json' },
|
|
823
|
+
body: JSON.stringify({ session_key: btn.getAttribute('data-session') }),
|
|
824
|
+
});
|
|
825
|
+
await refreshAll();
|
|
826
|
+
setTab('runtime');
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
el.querySelectorAll('.reopen-session-recommendation-btn').forEach(function (btn) {
|
|
830
|
+
btn.addEventListener('click', async function () {
|
|
831
|
+
await api('/api/runtime/policy/recommendations/reopen-session', {
|
|
832
|
+
method: 'POST',
|
|
833
|
+
headers: { 'Content-Type': 'application/json' },
|
|
834
|
+
body: JSON.stringify({ session_key: btn.getAttribute('data-session') }),
|
|
835
|
+
});
|
|
836
|
+
await refreshAll();
|
|
837
|
+
setTab('runtime');
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
const saveSessionSummaryBtn = document.getElementById('saveSessionSummaryBtn');
|
|
841
|
+
if (saveSessionSummaryBtn) {
|
|
842
|
+
saveSessionSummaryBtn.addEventListener('click', async function () {
|
|
843
|
+
await api('/api/runtime/session-summary', {
|
|
844
|
+
method: 'POST',
|
|
845
|
+
headers: { 'Content-Type': 'application/json' },
|
|
846
|
+
body: JSON.stringify({
|
|
847
|
+
session_key: session.session_key,
|
|
848
|
+
objective: document.getElementById('sessionSummaryObjective').value.trim(),
|
|
849
|
+
context: document.getElementById('sessionSummaryContext').value.trim(),
|
|
850
|
+
constraints: document.getElementById('sessionSummaryConstraints').value.trim(),
|
|
851
|
+
decisions: document.getElementById('sessionSummaryDecisions').value.trim(),
|
|
852
|
+
open_questions: document.getElementById('sessionSummaryOpenQuestions').value.trim(),
|
|
853
|
+
next_action: document.getElementById('sessionSummaryNextAction').value.trim(),
|
|
854
|
+
handoff_ready_text: document.getElementById('sessionSummaryHandoff').value.trim(),
|
|
855
|
+
}),
|
|
856
|
+
});
|
|
857
|
+
await refreshAll();
|
|
858
|
+
setTab('runtime');
|
|
859
|
+
});
|
|
860
|
+
}
|
|
537
861
|
}
|
|
538
862
|
|
|
539
863
|
async function promptBindCurrentChat(conversationId, previousBinding, remoteDid) {
|
|
@@ -568,8 +892,23 @@ Previous binding: ' + previous
|
|
|
568
892
|
function renderPolicy() {
|
|
569
893
|
const policy = state.policy;
|
|
570
894
|
if (!policy) return;
|
|
895
|
+
const profile = state.overview && state.overview.profile ? state.overview.profile : null;
|
|
571
896
|
document.getElementById('contactDefault').value = policy.doc.contact_policy.default_action;
|
|
572
897
|
document.getElementById('taskDefault').value = policy.doc.task_policy.default_action;
|
|
898
|
+
document.getElementById('profileDisplayName').value = profile && profile.display_name ? profile.display_name : '';
|
|
899
|
+
document.getElementById('profileBio').value = profile && profile.bio ? profile.bio : '';
|
|
900
|
+
document.getElementById('profileTags').value = profile && Array.isArray(profile.tags) ? profile.tags.join(', ') : '';
|
|
901
|
+
document.getElementById('profileCapabilities').value = profile && Array.isArray(profile.capabilities) ? profile.capabilities.join(', ') : '';
|
|
902
|
+
document.getElementById('capabilityCardSummary').value = profile && profile.capability_card && profile.capability_card.summary ? profile.capability_card.summary : '';
|
|
903
|
+
document.getElementById('capabilityCardAcceptsNewWork').value =
|
|
904
|
+
profile && profile.capability_card && typeof profile.capability_card.accepts_new_work === 'boolean'
|
|
905
|
+
? String(profile.capability_card.accepts_new_work)
|
|
906
|
+
: '';
|
|
907
|
+
document.getElementById('capabilityCardContactMode').value =
|
|
908
|
+
profile && profile.capability_card && profile.capability_card.preferred_contact_mode
|
|
909
|
+
? profile.capability_card.preferred_contact_mode
|
|
910
|
+
: '';
|
|
911
|
+
document.getElementById('capabilityCardItems').value = formatCapabilityCardEditor(profile && profile.capability_card ? profile.capability_card : null);
|
|
573
912
|
|
|
574
913
|
const rules = [];
|
|
575
914
|
policy.doc.contact_policy.rules.forEach(function (rule) {
|
|
@@ -610,7 +949,7 @@ Previous binding: ' + previous
|
|
|
610
949
|
if (!list.length) return '';
|
|
611
950
|
return '<div><div class="label" style="margin-bottom:8px">' + esc(status) + '</div>' + list.map(function (item) {
|
|
612
951
|
const applyButton = status !== 'applied'
|
|
613
|
-
? '<button class="action-btn apply-recommendation-btn" data-recommendation-id="' + esc(item.id) + '">
|
|
952
|
+
? '<button class="action-btn apply-recommendation-btn" data-recommendation-id="' + esc(item.id) + '">' + esc(recommendationActionLabel(item)) + '</button>'
|
|
614
953
|
: '';
|
|
615
954
|
const dismissButton = status === 'open'
|
|
616
955
|
? '<button class="danger-btn dismiss-recommendation-btn" data-recommendation-id="' + esc(item.id) + '">Dismiss</button>'
|
|
@@ -672,6 +1011,45 @@ Previous binding: ' + previous
|
|
|
672
1011
|
: '<div class="empty">No audit events yet.</div>';
|
|
673
1012
|
|
|
674
1013
|
updateRuleActionOptions();
|
|
1014
|
+
|
|
1015
|
+
const saveProfileBtn = document.getElementById('saveProfileBtn');
|
|
1016
|
+
if (saveProfileBtn) {
|
|
1017
|
+
saveProfileBtn.onclick = async function () {
|
|
1018
|
+
let capabilityItems = [];
|
|
1019
|
+
const rawItems = document.getElementById('capabilityCardItems').value.trim();
|
|
1020
|
+
if (rawItems) {
|
|
1021
|
+
try {
|
|
1022
|
+
const parsed = JSON.parse(rawItems);
|
|
1023
|
+
if (!Array.isArray(parsed)) throw new Error('Capability entries JSON must be an array.');
|
|
1024
|
+
capabilityItems = parsed;
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
window.alert(err && err.message ? err.message : 'Invalid capability entries JSON');
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const acceptsNewWorkValue = document.getElementById('capabilityCardAcceptsNewWork').value;
|
|
1031
|
+
const capabilityCard = {
|
|
1032
|
+
version: '1',
|
|
1033
|
+
summary: document.getElementById('capabilityCardSummary').value.trim() || undefined,
|
|
1034
|
+
accepts_new_work: acceptsNewWorkValue === '' ? undefined : acceptsNewWorkValue === 'true',
|
|
1035
|
+
preferred_contact_mode: document.getElementById('capabilityCardContactMode').value || undefined,
|
|
1036
|
+
capabilities: capabilityItems,
|
|
1037
|
+
};
|
|
1038
|
+
await api('/api/profile', {
|
|
1039
|
+
method: 'POST',
|
|
1040
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1041
|
+
body: JSON.stringify({
|
|
1042
|
+
display_name: document.getElementById('profileDisplayName').value.trim() || undefined,
|
|
1043
|
+
bio: document.getElementById('profileBio').value.trim() || undefined,
|
|
1044
|
+
tags: parseCsvList(document.getElementById('profileTags').value),
|
|
1045
|
+
capabilities: parseCsvList(document.getElementById('profileCapabilities').value),
|
|
1046
|
+
capability_card: capabilityCard,
|
|
1047
|
+
}),
|
|
1048
|
+
});
|
|
1049
|
+
await refreshAll();
|
|
1050
|
+
setTab('policy');
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
675
1053
|
}
|
|
676
1054
|
|
|
677
1055
|
function updateRuleActionOptions() {
|
|
@@ -758,6 +1136,46 @@ Previous binding: ' + previous
|
|
|
758
1136
|
document.getElementById('simulateOutput').textContent = JSON.stringify(result, null, 2);
|
|
759
1137
|
setTab('policy');
|
|
760
1138
|
});
|
|
1139
|
+
document.getElementById('fixHooksBtn').addEventListener('click', async function () {
|
|
1140
|
+
const confirmed = window.confirm('Repair OpenClaw hooks config now? A timestamped backup of openclaw.json will be written first.');
|
|
1141
|
+
if (!confirmed) return;
|
|
1142
|
+
const result = await api('/api/runtime/openclaw/fix-hooks', { method: 'POST' });
|
|
1143
|
+
window.alert((result.ok ? 'Hooks repaired.
|
|
1144
|
+
|
|
1145
|
+
' : 'Hooks repair reported an error.
|
|
1146
|
+
|
|
1147
|
+
') + (result.stdout || result.stderr || 'No output'));
|
|
1148
|
+
await refreshAll();
|
|
1149
|
+
});
|
|
1150
|
+
document.getElementById('publicLinkBtn').addEventListener('click', async function () {
|
|
1151
|
+
const suggested = state.overview && state.overview.publicSelf && state.overview.publicSelf.public_slug
|
|
1152
|
+
? state.overview.publicSelf.public_slug
|
|
1153
|
+
: '';
|
|
1154
|
+
const slug = window.prompt('Public share slug (leave empty to use the recommended value):', suggested);
|
|
1155
|
+
if (slug == null) return;
|
|
1156
|
+
const result = await api('/api/public/link', {
|
|
1157
|
+
method: 'POST',
|
|
1158
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1159
|
+
body: JSON.stringify({ slug: slug.trim() || undefined, enabled: true }),
|
|
1160
|
+
});
|
|
1161
|
+
window.alert('Public link ready:
|
|
1162
|
+
' + (result.public_url || '(missing public_url)'));
|
|
1163
|
+
await refreshAll();
|
|
1164
|
+
});
|
|
1165
|
+
document.getElementById('contactCardBtn').addEventListener('click', async function () {
|
|
1166
|
+
const intro = window.prompt('Optional intro note for this contact card:', '');
|
|
1167
|
+
if (intro == null) return;
|
|
1168
|
+
const result = await api('/api/public/contact-card', {
|
|
1169
|
+
method: 'POST',
|
|
1170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1171
|
+
body: JSON.stringify({
|
|
1172
|
+
target_did: state.overview && state.overview.did ? state.overview.did : undefined,
|
|
1173
|
+
intro_note: intro.trim() || undefined,
|
|
1174
|
+
}),
|
|
1175
|
+
});
|
|
1176
|
+
window.alert('Contact card ready:
|
|
1177
|
+
' + (result.share_url || '(missing share_url)'));
|
|
1178
|
+
});
|
|
761
1179
|
|
|
762
1180
|
async function init() {
|
|
763
1181
|
await loadProfiles();
|
|
@@ -781,6 +1199,29 @@ function resolvePath(p) {
|
|
|
781
1199
|
if (!p || !p.startsWith("~")) return p;
|
|
782
1200
|
return path.join(process.env.HOME || process.env.USERPROFILE || "", p.slice(1));
|
|
783
1201
|
}
|
|
1202
|
+
function findOpenClawInstallScript() {
|
|
1203
|
+
const explicit = process.env.PINGAGENT_OPENCLAW_INSTALL_BIN?.trim();
|
|
1204
|
+
if (explicit) return { kind: "script", cmd: process.execPath, args: [path.resolve(explicit)] };
|
|
1205
|
+
const repoScript = path.resolve(process.cwd(), "packages", "openclaw-install", "install.mjs");
|
|
1206
|
+
if (fs.existsSync(repoScript)) return { kind: "script", cmd: process.execPath, args: [repoScript] };
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
function runOpenClawInstall(args) {
|
|
1210
|
+
const resolved = findOpenClawInstallScript();
|
|
1211
|
+
if (!resolved) {
|
|
1212
|
+
return { ok: false, stdout: "", stderr: "OpenClaw installer script not found locally. Set PINGAGENT_OPENCLAW_INSTALL_BIN." };
|
|
1213
|
+
}
|
|
1214
|
+
const result = spawnSync(resolved.cmd, [...resolved.args, ...args], {
|
|
1215
|
+
encoding: "utf-8",
|
|
1216
|
+
env: process.env
|
|
1217
|
+
});
|
|
1218
|
+
return {
|
|
1219
|
+
ok: result.status === 0,
|
|
1220
|
+
stdout: String(result.stdout ?? ""),
|
|
1221
|
+
stderr: String(result.stderr ?? ""),
|
|
1222
|
+
status: result.status ?? 1
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
784
1225
|
function listProfiles(rootDir) {
|
|
785
1226
|
const root = resolvePath(rootDir);
|
|
786
1227
|
const profiles = [];
|
|
@@ -839,6 +1280,30 @@ function listProfiles(rootDir) {
|
|
|
839
1280
|
return profiles;
|
|
840
1281
|
}
|
|
841
1282
|
var DEFAULT_SERVER_URL = "https://pingagent.chat";
|
|
1283
|
+
var OFFICIAL_HOSTED_ORIGIN = new URL(DEFAULT_SERVER_URL).origin;
|
|
1284
|
+
var autoPublicLinkAttempts = /* @__PURE__ */ new Set();
|
|
1285
|
+
function normalizeOrigin(input) {
|
|
1286
|
+
try {
|
|
1287
|
+
return new URL(String(input ?? "")).origin;
|
|
1288
|
+
} catch {
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
function isOfficialHostedServer(serverUrl) {
|
|
1293
|
+
return normalizeOrigin(serverUrl) === OFFICIAL_HOSTED_ORIGIN;
|
|
1294
|
+
}
|
|
1295
|
+
async function maybeEnsureHostedPublicLink(ctx) {
|
|
1296
|
+
if (!isOfficialHostedServer(ctx.serverUrl)) return;
|
|
1297
|
+
const key = `${ctx.identityPath}:${normalizeOrigin(ctx.serverUrl)}`;
|
|
1298
|
+
if (autoPublicLinkAttempts.has(key)) return;
|
|
1299
|
+
autoPublicLinkAttempts.add(key);
|
|
1300
|
+
try {
|
|
1301
|
+
const current = await ctx.client.getPublicSelf().catch(() => ({ ok: false }));
|
|
1302
|
+
if (current.ok && current.data?.public_slug) return;
|
|
1303
|
+
await ctx.client.createPublicLink({ enabled: true }).catch(() => ({ ok: false }));
|
|
1304
|
+
} catch {
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
842
1307
|
async function getContextForProfile(profile, defaultServerUrl) {
|
|
843
1308
|
const identity = loadIdentity(profile.identityPath);
|
|
844
1309
|
const serverUrl = identity.serverUrl ?? defaultServerUrl ?? DEFAULT_SERVER_URL;
|
|
@@ -1044,11 +1509,14 @@ function describeHostedTier(tier) {
|
|
|
1044
1509
|
}
|
|
1045
1510
|
async function buildRuntimeOverviewPayload(ctx) {
|
|
1046
1511
|
const client = ctx.client;
|
|
1512
|
+
await maybeEnsureHostedPublicLink(ctx);
|
|
1047
1513
|
await client.listConversations({ type: "dm" });
|
|
1048
1514
|
const sessionManager = client.getSessionManager();
|
|
1515
|
+
const sessionSummaryManager = client.getSessionSummaryManager();
|
|
1049
1516
|
const taskManager = client.getTaskThreadManager();
|
|
1517
|
+
const taskHandoffManager = client.getTaskHandoffManager();
|
|
1050
1518
|
const historyManager = client.getHistoryManager();
|
|
1051
|
-
if (!sessionManager || !taskManager || !historyManager) {
|
|
1519
|
+
if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager) {
|
|
1052
1520
|
throw new Error("Runtime overview requires a writable local store");
|
|
1053
1521
|
}
|
|
1054
1522
|
const sessions = sessionManager.listRecentSessions(24);
|
|
@@ -1071,6 +1539,10 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
1071
1539
|
const runtimeMode = getRuntimeMode();
|
|
1072
1540
|
const subRes = await client.getSubscription().catch(() => ({ ok: false }));
|
|
1073
1541
|
const subscription = subRes.ok && subRes.data ? subRes.data : null;
|
|
1542
|
+
const publicSelfRes = await client.getPublicSelf().catch(() => ({ ok: false }));
|
|
1543
|
+
const publicSelf = publicSelfRes.ok && publicSelfRes.data ? publicSelfRes.data : null;
|
|
1544
|
+
const profileRes = await client.getProfile().catch(() => ({ ok: false }));
|
|
1545
|
+
const profile = profileRes.ok && profileRes.data ? profileRes.data : null;
|
|
1074
1546
|
const recommendationState = syncTrustRecommendations(ctx.storePath, {
|
|
1075
1547
|
policyDoc: policy,
|
|
1076
1548
|
sessions,
|
|
@@ -1082,6 +1554,7 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
1082
1554
|
const unreadTotal = sessions.reduce((sum, session) => sum + session.unread_count, 0);
|
|
1083
1555
|
const sessionBindings = readSessionBindings();
|
|
1084
1556
|
const sessionBindingAlerts = readSessionBindingAlerts();
|
|
1557
|
+
const ingressRuntime = readIngressRuntimeStatus();
|
|
1085
1558
|
const activeWorkSession = readCurrentActiveSessionKey();
|
|
1086
1559
|
const bindingByConversation = new Map(sessionBindings.map((row) => [row.conversation_id, row.session_key]));
|
|
1087
1560
|
const bindingAlertByConversation = new Map(sessionBindingAlerts.map((row) => [row.conversation_id, row]));
|
|
@@ -1096,6 +1569,7 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
1096
1569
|
trustPolicyPath: getTrustPolicyPath(ctx.identityPath),
|
|
1097
1570
|
activeWorkSessionFile: getActiveSessionFilePath(),
|
|
1098
1571
|
activeWorkSession,
|
|
1572
|
+
ingressRuntime,
|
|
1099
1573
|
sessionMapPath: getSessionMapFilePath(),
|
|
1100
1574
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
1101
1575
|
sessionBindings,
|
|
@@ -1111,6 +1585,8 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
1111
1585
|
retention_label: formatRetentionLabel(subscription.limits.store_forward_ttl_ms),
|
|
1112
1586
|
audit_export_allowed: !!subscription.limits.audit_export_allowed
|
|
1113
1587
|
} : null,
|
|
1588
|
+
publicSelf,
|
|
1589
|
+
profile,
|
|
1114
1590
|
policyDefaults: {
|
|
1115
1591
|
contact: policy.contact_policy.enabled ? policy.contact_policy.default_action : "disabled",
|
|
1116
1592
|
task: policy.task_policy.enabled ? policy.task_policy.default_action : "disabled"
|
|
@@ -1122,22 +1598,31 @@ async function buildRuntimeOverviewPayload(ctx) {
|
|
|
1122
1598
|
recommendationSummary: recommendationState.summary,
|
|
1123
1599
|
sessions: sessions.map((session) => ({
|
|
1124
1600
|
...session,
|
|
1601
|
+
session_summary: sessionSummaryManager.get(session.session_key),
|
|
1125
1602
|
mapped_work_session: session.conversation_id ? bindingByConversation.get(session.conversation_id) ?? null : null,
|
|
1126
1603
|
binding_alert: session.conversation_id ? bindingAlertByConversation.get(session.conversation_id) ?? null : null,
|
|
1127
1604
|
is_active_work_session: session.session_key === activeWorkSession,
|
|
1128
1605
|
latest_messages: session.conversation_id ? historyManager.listRecent(session.conversation_id, 3) : []
|
|
1129
1606
|
})),
|
|
1130
|
-
tasks: refreshedTasks
|
|
1607
|
+
tasks: refreshedTasks.map((task) => ({
|
|
1608
|
+
...task,
|
|
1609
|
+
handoff: taskHandoffManager.get(task.task_id)
|
|
1610
|
+
})),
|
|
1131
1611
|
auditSummary,
|
|
1132
|
-
recommendations: recommendationState.recommendations
|
|
1612
|
+
recommendations: recommendationState.recommendations.map((item) => ({
|
|
1613
|
+
...item,
|
|
1614
|
+
primary_action_label: getTrustRecommendationActionLabel(item)
|
|
1615
|
+
}))
|
|
1133
1616
|
};
|
|
1134
1617
|
}
|
|
1135
1618
|
async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
1136
1619
|
const client = ctx.client;
|
|
1137
1620
|
const sessionManager = client.getSessionManager();
|
|
1621
|
+
const sessionSummaryManager = client.getSessionSummaryManager();
|
|
1138
1622
|
const taskManager = client.getTaskThreadManager();
|
|
1623
|
+
const taskHandoffManager = client.getTaskHandoffManager();
|
|
1139
1624
|
const historyManager = client.getHistoryManager();
|
|
1140
|
-
if (!sessionManager || !taskManager || !historyManager) {
|
|
1625
|
+
if (!sessionManager || !sessionSummaryManager || !taskManager || !taskHandoffManager || !historyManager) {
|
|
1141
1626
|
throw new Error("Session overview requires a writable local store");
|
|
1142
1627
|
}
|
|
1143
1628
|
const session = sessionKey ? sessionManager.get(sessionKey) : sessionManager.getActiveSession() ?? sessionManager.listRecentSessions(1)[0] ?? null;
|
|
@@ -1173,6 +1658,8 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
1173
1658
|
});
|
|
1174
1659
|
return {
|
|
1175
1660
|
session,
|
|
1661
|
+
sessionSummary: sessionSummaryManager.get(session.session_key),
|
|
1662
|
+
ingressRuntime: readIngressRuntimeStatus(),
|
|
1176
1663
|
binding,
|
|
1177
1664
|
bindingAlert,
|
|
1178
1665
|
activeWorkSession,
|
|
@@ -1180,10 +1667,16 @@ async function buildSessionOverviewPayload(ctx, sessionKey) {
|
|
|
1180
1667
|
sessionMapPath: getSessionMapFilePath(),
|
|
1181
1668
|
sessionBindingAlertsPath: getSessionBindingAlertsFilePath(),
|
|
1182
1669
|
policyExplain: buildPolicyDecisionShape(ctx.identityPath, session.remote_did, { runtimeMode: getRuntimeMode() }),
|
|
1183
|
-
tasks
|
|
1670
|
+
tasks: tasks.map((task) => ({
|
|
1671
|
+
...task,
|
|
1672
|
+
handoff: taskHandoffManager.get(task.task_id)
|
|
1673
|
+
})),
|
|
1184
1674
|
messages,
|
|
1185
1675
|
auditEvents,
|
|
1186
|
-
recommendations: recommendationState.recommendations.filter((item) => item.remote_did === session.remote_did)
|
|
1676
|
+
recommendations: recommendationState.recommendations.filter((item) => item.remote_did === session.remote_did).map((item) => ({
|
|
1677
|
+
...item,
|
|
1678
|
+
primary_action_label: getTrustRecommendationActionLabel(item)
|
|
1679
|
+
}))
|
|
1187
1680
|
};
|
|
1188
1681
|
}
|
|
1189
1682
|
async function handleApi(pathname, req, ctx) {
|
|
@@ -1196,9 +1689,96 @@ async function handleApi(pathname, req, ctx) {
|
|
|
1196
1689
|
return { did: myDid, serverUrl };
|
|
1197
1690
|
}
|
|
1198
1691
|
if (parts[0] === "runtime") {
|
|
1692
|
+
if (parts[1] === "session-summary") {
|
|
1693
|
+
const sessionManager = client.getSessionManager();
|
|
1694
|
+
const sessionSummaryManager = client.getSessionSummaryManager();
|
|
1695
|
+
if (!sessionManager || !sessionSummaryManager) throw new Error("Session summary requires a writable local store");
|
|
1696
|
+
const url = new URL(req.url || "", "http://x");
|
|
1697
|
+
const body = req.method === "POST" ? await readBody(req) : null;
|
|
1698
|
+
const sessionKey = String(
|
|
1699
|
+
body?.session_key ?? url.searchParams.get("session_key") ?? ""
|
|
1700
|
+
).trim();
|
|
1701
|
+
const conversationId = String(
|
|
1702
|
+
body?.conversation_id ?? url.searchParams.get("conversation_id") ?? ""
|
|
1703
|
+
).trim();
|
|
1704
|
+
const remoteDid = String(
|
|
1705
|
+
body?.remote_did ?? url.searchParams.get("remote_did") ?? ""
|
|
1706
|
+
).trim();
|
|
1707
|
+
const session = sessionKey ? sessionManager.get(sessionKey) : conversationId ? sessionManager.getByConversationId(conversationId) : remoteDid ? sessionManager.listRecentSessions(100).find((item) => item.remote_did === remoteDid) ?? null : sessionManager.getActiveSession() ?? sessionManager.listRecentSessions(1)[0] ?? null;
|
|
1708
|
+
if (!session) throw new Error("No session available");
|
|
1709
|
+
if (req.method === "GET") {
|
|
1710
|
+
return { session, summary: sessionSummaryManager.get(session.session_key) };
|
|
1711
|
+
}
|
|
1712
|
+
const summary = sessionSummaryManager.upsert({
|
|
1713
|
+
session_key: session.session_key,
|
|
1714
|
+
objective: typeof body?.objective === "string" ? body.objective : void 0,
|
|
1715
|
+
context: typeof body?.context === "string" ? body.context : void 0,
|
|
1716
|
+
constraints: typeof body?.constraints === "string" ? body.constraints : void 0,
|
|
1717
|
+
decisions: typeof body?.decisions === "string" ? body.decisions : void 0,
|
|
1718
|
+
open_questions: typeof body?.open_questions === "string" ? body.open_questions : void 0,
|
|
1719
|
+
next_action: typeof body?.next_action === "string" ? body.next_action : void 0,
|
|
1720
|
+
handoff_ready_text: typeof body?.handoff_ready_text === "string" ? body.handoff_ready_text : void 0
|
|
1721
|
+
});
|
|
1722
|
+
return { ok: true, session, summary };
|
|
1723
|
+
}
|
|
1199
1724
|
if (!parts[1] || parts[1] === "overview") {
|
|
1200
1725
|
return buildRuntimeOverviewPayload(ctx);
|
|
1201
1726
|
}
|
|
1727
|
+
if (parts[1] === "receive-mode" && req.method === "GET") {
|
|
1728
|
+
return {
|
|
1729
|
+
runtimeStatusPath: path.resolve(process.env.IM_INGRESS_RUNTIME_STATUS_FILE || ""),
|
|
1730
|
+
status: readIngressRuntimeStatus()
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
if (parts[1] === "demo" && req.method === "POST") {
|
|
1734
|
+
const body = await readBody(req);
|
|
1735
|
+
const preset = typeof body?.preset === "string" ? body.preset.trim().toLowerCase() : "";
|
|
1736
|
+
const presetMessages = {
|
|
1737
|
+
hello: "Hello",
|
|
1738
|
+
delegate: "Please show me how task delegation works in PingAgent.",
|
|
1739
|
+
trust: "Show me how trust decisions and recommendations work."
|
|
1740
|
+
};
|
|
1741
|
+
const resolved = await client.resolveAlias("pingagent/demo");
|
|
1742
|
+
if (!resolved.ok || !resolved.data?.did) {
|
|
1743
|
+
throw new Error(resolved.error?.message ?? "Failed to resolve demo agent");
|
|
1744
|
+
}
|
|
1745
|
+
const convo = await client.openConversation(resolved.data.did);
|
|
1746
|
+
if (!convo.ok || !convo.data?.conversation_id) {
|
|
1747
|
+
throw new Error(convo.error?.message ?? "Failed to open demo conversation");
|
|
1748
|
+
}
|
|
1749
|
+
const message = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : presetMessages[preset] ?? "";
|
|
1750
|
+
if (!message) {
|
|
1751
|
+
return {
|
|
1752
|
+
ok: true,
|
|
1753
|
+
did: resolved.data.did,
|
|
1754
|
+
conversation_id: convo.data.conversation_id,
|
|
1755
|
+
preset: preset || null,
|
|
1756
|
+
sent: false
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
const sendRes = await client.sendMessage(convo.data.conversation_id, SCHEMA_TEXT, { text: message });
|
|
1760
|
+
if (!sendRes.ok) {
|
|
1761
|
+
throw new Error(sendRes.error?.message ?? "Failed to send demo message");
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
ok: true,
|
|
1765
|
+
did: resolved.data.did,
|
|
1766
|
+
conversation_id: convo.data.conversation_id,
|
|
1767
|
+
preset: preset || null,
|
|
1768
|
+
sent: true,
|
|
1769
|
+
message_id: sendRes.data?.message_id ?? null
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
if (parts[1] === "openclaw" && parts[2] === "fix-hooks" && req.method === "POST") {
|
|
1773
|
+
const result = runOpenClawInstall(["fix-hooks"]);
|
|
1774
|
+
return {
|
|
1775
|
+
ok: result.ok,
|
|
1776
|
+
stdout: result.stdout,
|
|
1777
|
+
stderr: result.stderr,
|
|
1778
|
+
status: result.status,
|
|
1779
|
+
receiveMode: readIngressRuntimeStatus()
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1202
1782
|
if (parts[1] === "session") {
|
|
1203
1783
|
const url = new URL(req.url || "", "http://x");
|
|
1204
1784
|
const sessionKey = url.searchParams.get("session_key");
|
|
@@ -1290,7 +1870,10 @@ async function handleApi(pathname, req, ctx) {
|
|
|
1290
1870
|
auditSummary: summarizeTrustPolicyAudit(auditEvents),
|
|
1291
1871
|
auditEvents,
|
|
1292
1872
|
recommendationSummary: recommendationState.summary,
|
|
1293
|
-
recommendations: recommendationState.recommendations
|
|
1873
|
+
recommendations: recommendationState.recommendations.map((item) => ({
|
|
1874
|
+
...item,
|
|
1875
|
+
primary_action_label: getTrustRecommendationActionLabel(item)
|
|
1876
|
+
}))
|
|
1294
1877
|
};
|
|
1295
1878
|
} finally {
|
|
1296
1879
|
auditStore.close();
|
|
@@ -1488,6 +2071,141 @@ async function handleApi(pathname, req, ctx) {
|
|
|
1488
2071
|
}
|
|
1489
2072
|
};
|
|
1490
2073
|
}
|
|
2074
|
+
if (parts[2] === "recommendations" && parts[3] === "apply-session" && req.method === "POST") {
|
|
2075
|
+
const body = await readBody(req);
|
|
2076
|
+
const sessionKey = typeof body?.session_key === "string" ? body.session_key : ctx.client.getSessionManager()?.getActiveSession()?.session_key ?? "";
|
|
2077
|
+
if (!sessionKey) throw new Error("session_key is required");
|
|
2078
|
+
const session = ctx.client.getSessionManager()?.get(sessionKey);
|
|
2079
|
+
if (!session?.remote_did) throw new Error("No active session recommendation target");
|
|
2080
|
+
const doc = readTrustPolicyDoc(ctx.identityPath);
|
|
2081
|
+
const sharedStore = new LocalStore(ctx.storePath);
|
|
2082
|
+
try {
|
|
2083
|
+
const auditManager = new TrustPolicyAuditManager(sharedStore);
|
|
2084
|
+
const recommendationManager = new TrustRecommendationManager(sharedStore);
|
|
2085
|
+
const auditEvents = auditManager.listRecent(200);
|
|
2086
|
+
recommendationManager.sync({
|
|
2087
|
+
policyDoc: doc,
|
|
2088
|
+
sessions: sessionManager.listRecentSessions(100),
|
|
2089
|
+
tasks: taskManager.listRecent(100),
|
|
2090
|
+
auditEvents,
|
|
2091
|
+
runtimeMode,
|
|
2092
|
+
limit: 50
|
|
2093
|
+
});
|
|
2094
|
+
const recommendation = recommendationManager.list({ remoteDid: session.remote_did, status: "open", limit: 1 })[0];
|
|
2095
|
+
if (!recommendation) throw new Error("No open recommendation for this session");
|
|
2096
|
+
const nextDoc = upsertTrustPolicyRecommendation(doc, recommendation);
|
|
2097
|
+
const savedPath = writeTrustPolicyDoc(ctx.identityPath, nextDoc);
|
|
2098
|
+
const stored = recommendationManager.apply(recommendation.id) ?? recommendation;
|
|
2099
|
+
auditManager.record({
|
|
2100
|
+
event_type: "recommendation_applied",
|
|
2101
|
+
policy_scope: recommendation.policy,
|
|
2102
|
+
remote_did: recommendation.remote_did,
|
|
2103
|
+
action: String(recommendation.action),
|
|
2104
|
+
outcome: "recommendation_applied",
|
|
2105
|
+
explanation: recommendation.reason,
|
|
2106
|
+
matched_rule: recommendation.match,
|
|
2107
|
+
detail: { recommendation_id: recommendation.id, session_key: sessionKey }
|
|
2108
|
+
});
|
|
2109
|
+
return { ok: true, path: savedPath, recommendation: stored, doc: nextDoc };
|
|
2110
|
+
} finally {
|
|
2111
|
+
sharedStore.close();
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
if (parts[2] === "recommendations" && parts[3] === "dismiss-session" && req.method === "POST") {
|
|
2115
|
+
const body = await readBody(req);
|
|
2116
|
+
const sessionKey = typeof body?.session_key === "string" ? body.session_key : ctx.client.getSessionManager()?.getActiveSession()?.session_key ?? "";
|
|
2117
|
+
if (!sessionKey) throw new Error("session_key is required");
|
|
2118
|
+
const session = ctx.client.getSessionManager()?.get(sessionKey);
|
|
2119
|
+
if (!session?.remote_did) throw new Error("No active session recommendation target");
|
|
2120
|
+
const sharedStore = new LocalStore(ctx.storePath);
|
|
2121
|
+
try {
|
|
2122
|
+
const recommendationManager = new TrustRecommendationManager(sharedStore);
|
|
2123
|
+
const recommendation = recommendationManager.list({ remoteDid: session.remote_did, status: "open", limit: 1 })[0];
|
|
2124
|
+
if (!recommendation) throw new Error("No open recommendation for this session");
|
|
2125
|
+
const stored = recommendationManager.dismiss(recommendation.id) ?? recommendation;
|
|
2126
|
+
new TrustPolicyAuditManager(sharedStore).record({
|
|
2127
|
+
event_type: "recommendation_dismissed",
|
|
2128
|
+
policy_scope: recommendation.policy,
|
|
2129
|
+
remote_did: recommendation.remote_did,
|
|
2130
|
+
action: String(recommendation.action),
|
|
2131
|
+
outcome: "recommendation_dismissed",
|
|
2132
|
+
explanation: recommendation.reason,
|
|
2133
|
+
matched_rule: recommendation.match,
|
|
2134
|
+
detail: { recommendation_id: recommendation.id, session_key: sessionKey }
|
|
2135
|
+
});
|
|
2136
|
+
return { ok: true, recommendation: stored };
|
|
2137
|
+
} finally {
|
|
2138
|
+
sharedStore.close();
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
if (parts[2] === "recommendations" && parts[3] === "reopen-session" && req.method === "POST") {
|
|
2142
|
+
const body = await readBody(req);
|
|
2143
|
+
const sessionKey = typeof body?.session_key === "string" ? body.session_key : ctx.client.getSessionManager()?.getActiveSession()?.session_key ?? "";
|
|
2144
|
+
if (!sessionKey) throw new Error("session_key is required");
|
|
2145
|
+
const session = ctx.client.getSessionManager()?.get(sessionKey);
|
|
2146
|
+
if (!session?.remote_did) throw new Error("No active session recommendation target");
|
|
2147
|
+
const sharedStore = new LocalStore(ctx.storePath);
|
|
2148
|
+
try {
|
|
2149
|
+
const recommendationManager = new TrustRecommendationManager(sharedStore);
|
|
2150
|
+
const recommendation = recommendationManager.list({ remoteDid: session.remote_did, status: ["dismissed", "superseded"], limit: 1 })[0];
|
|
2151
|
+
if (!recommendation) throw new Error("No dismissed or superseded recommendation for this session");
|
|
2152
|
+
const stored = recommendationManager.reopen(recommendation.id) ?? recommendation;
|
|
2153
|
+
new TrustPolicyAuditManager(sharedStore).record({
|
|
2154
|
+
event_type: "recommendation_reopened",
|
|
2155
|
+
policy_scope: recommendation.policy,
|
|
2156
|
+
remote_did: recommendation.remote_did,
|
|
2157
|
+
action: String(recommendation.action),
|
|
2158
|
+
outcome: "recommendation_reopened",
|
|
2159
|
+
explanation: recommendation.reason,
|
|
2160
|
+
matched_rule: recommendation.match,
|
|
2161
|
+
detail: { recommendation_id: recommendation.id, session_key: sessionKey }
|
|
2162
|
+
});
|
|
2163
|
+
return { ok: true, recommendation: stored };
|
|
2164
|
+
} finally {
|
|
2165
|
+
sharedStore.close();
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
if (parts[0] === "public") {
|
|
2171
|
+
if ((!parts[1] || parts[1] === "self") && req.method === "GET") {
|
|
2172
|
+
await maybeEnsureHostedPublicLink(ctx);
|
|
2173
|
+
const res = await client.getPublicSelf();
|
|
2174
|
+
if (!res.ok) throw new Error(res.error?.message ?? "Failed to get public link state");
|
|
2175
|
+
return res.data;
|
|
2176
|
+
}
|
|
2177
|
+
if (parts[1] === "link" && req.method === "POST") {
|
|
2178
|
+
const body = await readBody(req);
|
|
2179
|
+
const res = await client.createPublicLink({
|
|
2180
|
+
slug: typeof body?.slug === "string" ? body.slug : void 0,
|
|
2181
|
+
enabled: typeof body?.enabled === "boolean" ? body.enabled : void 0
|
|
2182
|
+
});
|
|
2183
|
+
if (!res.ok) throw new Error(res.error?.message ?? "Failed to create public link");
|
|
2184
|
+
return res.data;
|
|
2185
|
+
}
|
|
2186
|
+
if (parts[1] === "contact-card" && req.method === "POST") {
|
|
2187
|
+
const body = await readBody(req);
|
|
2188
|
+
const res = await client.createContactCard({
|
|
2189
|
+
target_did: typeof body?.target_did === "string" ? body.target_did : void 0,
|
|
2190
|
+
referrer_did: typeof body?.referrer_did === "string" ? body.referrer_did : void 0,
|
|
2191
|
+
intro_note: typeof body?.intro_note === "string" ? body.intro_note : void 0,
|
|
2192
|
+
message_template: typeof body?.message_template === "string" ? body.message_template : void 0
|
|
2193
|
+
});
|
|
2194
|
+
if (!res.ok) throw new Error(res.error?.message ?? "Failed to create contact card");
|
|
2195
|
+
return res.data;
|
|
2196
|
+
}
|
|
2197
|
+
if (parts[1] === "task-share" && req.method === "POST") {
|
|
2198
|
+
const body = await readBody(req);
|
|
2199
|
+
const res = await client.createTaskShare({
|
|
2200
|
+
task_id: typeof body?.task_id === "string" ? body.task_id : void 0,
|
|
2201
|
+
title: typeof body?.title === "string" ? body.title : void 0,
|
|
2202
|
+
status: typeof body?.status === "string" ? body.status : void 0,
|
|
2203
|
+
summary: String(body?.summary ?? "").trim(),
|
|
2204
|
+
conversation_id: typeof body?.conversation_id === "string" ? body.conversation_id : void 0,
|
|
2205
|
+
redacted_metadata: body?.redacted_metadata && typeof body.redacted_metadata === "object" ? body.redacted_metadata : void 0
|
|
2206
|
+
});
|
|
2207
|
+
if (!res.ok) throw new Error(res.error?.message ?? "Failed to create task share");
|
|
2208
|
+
return res.data;
|
|
1491
2209
|
}
|
|
1492
2210
|
}
|
|
1493
2211
|
if (parts[0] === "profile") {
|
|
@@ -1499,11 +2217,12 @@ async function handleApi(pathname, req, ctx) {
|
|
|
1499
2217
|
if (req.method === "POST") {
|
|
1500
2218
|
const body = await readBody(req);
|
|
1501
2219
|
const profile = {};
|
|
1502
|
-
if (body?.display_name
|
|
1503
|
-
if (body?.bio
|
|
1504
|
-
if (Array.isArray(body?.capabilities)) profile.capabilities = body.capabilities;
|
|
2220
|
+
if (typeof body?.display_name === "string") profile.display_name = body.display_name;
|
|
2221
|
+
if (typeof body?.bio === "string") profile.bio = body.bio;
|
|
2222
|
+
if (Array.isArray(body?.capabilities)) profile.capabilities = body.capabilities.map((value) => String(value ?? "").trim()).filter(Boolean);
|
|
1505
2223
|
else if (typeof body?.capabilities === "string") profile.capabilities = body.capabilities.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1506
|
-
if (
|
|
2224
|
+
if (body?.capability_card && typeof body.capability_card === "object") profile.capability_card = body.capability_card;
|
|
2225
|
+
if (Array.isArray(body?.tags)) profile.tags = body.tags.map((value) => String(value ?? "").trim()).filter(Boolean);
|
|
1507
2226
|
else if (typeof body?.tags === "string") profile.tags = body.tags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1508
2227
|
if (typeof body?.discoverable === "boolean") profile.discoverable = body.discoverable;
|
|
1509
2228
|
const res = await client.updateProfile(profile);
|