instar 1.3.818 → 1.3.820
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/dashboard/glance.js +504 -6
- package/dashboard/index.html +158 -652
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +498 -3
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +69 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/BackupManager.d.ts +10 -0
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +16 -0
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/BoundedJsonlAudit.d.ts +37 -0
- package/dist/core/BoundedJsonlAudit.d.ts.map +1 -0
- package/dist/core/BoundedJsonlAudit.js +87 -0
- package/dist/core/BoundedJsonlAudit.js.map +1 -0
- package/dist/core/JudgmentProvenanceLog.d.ts +144 -0
- package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -0
- package/dist/core/JudgmentProvenanceLog.js +301 -0
- package/dist/core/JudgmentProvenanceLog.js.map +1 -0
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +2 -0
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/OwnerDarkLadder.d.ts +108 -0
- package/dist/core/OwnerDarkLadder.d.ts.map +1 -0
- package/dist/core/OwnerDarkLadder.js +229 -0
- package/dist/core/OwnerDarkLadder.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +10 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +106 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SpawnAdmission.d.ts +218 -0
- package/dist/core/SpawnAdmission.d.ts.map +1 -0
- package/dist/core/SpawnAdmission.js +440 -0
- package/dist/core/SpawnAdmission.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +24 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
- package/dist/core/machineCoherenceManifest.js +30 -0
- package/dist/core/machineCoherenceManifest.js.map +1 -1
- package/dist/core/types.d.ts +87 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/DuplicateSessionReconciler.d.ts +225 -0
- package/dist/monitoring/DuplicateSessionReconciler.d.ts.map +1 -0
- package/dist/monitoring/DuplicateSessionReconciler.js +544 -0
- package/dist/monitoring/DuplicateSessionReconciler.js.map +1 -0
- package/dist/monitoring/FailureAnalyzer.d.ts.map +1 -1
- package/dist/monitoring/FailureAnalyzer.js +40 -1
- package/dist/monitoring/FailureAnalyzer.js.map +1 -1
- package/dist/monitoring/FailureLedger.d.ts +9 -0
- package/dist/monitoring/FailureLedger.d.ts.map +1 -1
- package/dist/monitoring/FailureLedger.js +21 -4
- package/dist/monitoring/FailureLedger.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +5 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +18 -3
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +9 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/fileRoutes.d.ts +16 -0
- package/dist/server/fileRoutes.d.ts.map +1 -1
- package/dist/server/fileRoutes.js +46 -0
- package/dist/server/fileRoutes.js.map +1 -1
- package/dist/server/middleware.d.ts +32 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +34 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +99 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +89 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lib/self-action-detect.mjs +2 -0
- package/skills/instar-dev/templates/side-effects-artifact.md +8 -0
- package/skills/spec-converge/SKILL.md +1 -0
- package/skills/spec-converge/scripts/write-convergence-tag.mjs +71 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.819.md +88 -0
- package/upgrades/1.3.820.md +25 -0
- package/upgrades/side-effects/glance-p4-sweep.md +112 -0
- package/upgrades/side-effects/ownership-gated-spawn-increment-1.md +166 -0
- package/upgrades/1.3.818.md +0 -25
package/dashboard/index.html
CHANGED
|
@@ -657,6 +657,10 @@
|
|
|
657
657
|
color: var(--text); cursor: pointer;
|
|
658
658
|
}
|
|
659
659
|
.glance-record-action:hover { border-color: var(--accent-dim); }
|
|
660
|
+
.glance-record-action { text-decoration: none; } /* also covers anchor actions */
|
|
661
|
+
/* Phase 4: a row of actions on a Layer-3 record (e.g. a secret's open/copy/cancel). */
|
|
662
|
+
.glance-record-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
|
|
663
|
+
.glance-record-actions .glance-record-action { margin-top: 0; }
|
|
660
664
|
/* Phase 3: the #1429 editable nickname field (in a machine's Layer-3 record) and
|
|
661
665
|
the warn-toned Layer-2 rows (a machine or safety check that needs a look). */
|
|
662
666
|
.glance-record-input {
|
|
@@ -3309,11 +3313,7 @@
|
|
|
3309
3313
|
</div>
|
|
3310
3314
|
</div>
|
|
3311
3315
|
<div class="tab-purpose">A live view of your open pull requests as they move through review and CI checks toward merge.</div>
|
|
3312
|
-
<div id="
|
|
3313
|
-
<div id="prPipelineEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
|
|
3314
|
-
No PR activity to display.
|
|
3315
|
-
</div>
|
|
3316
|
-
<div id="prPipelineList" style="display:flex;flex-direction:column;gap:12px"></div>
|
|
3316
|
+
<div id="prPipelineGlance" class="glance-root"></div>
|
|
3317
3317
|
</div>
|
|
3318
3318
|
|
|
3319
3319
|
<!-- Projects Tab — PROJECT-SCOPE-SPEC Phase 1.10 read-only view -->
|
|
@@ -3350,16 +3350,9 @@
|
|
|
3350
3350
|
</div>
|
|
3351
3351
|
</div>
|
|
3352
3352
|
<div class="tab-purpose">
|
|
3353
|
-
Long-running work that spans multiple phases
|
|
3354
|
-
<span style="color:#d65f5f">next-check-due</span>,
|
|
3355
|
-
<span style="color:#4a9a4a">ready-to-advance</span>,
|
|
3356
|
-
<span style="color:#888">stale (>7d)</span>.
|
|
3357
|
-
</div>
|
|
3358
|
-
<div id="initiativesDigest" style="display:none;padding:12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-dim)"></div>
|
|
3359
|
-
<div id="initiativesEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
|
|
3360
|
-
No initiatives yet. Ask your agent to create one, and it'll appear here.
|
|
3353
|
+
Long-running work that spans multiple phases — the headline says how many are in flight and how many need you; tap a tile to see which, tap one for the full record.
|
|
3361
3354
|
</div>
|
|
3362
|
-
<div id="
|
|
3355
|
+
<div id="initiativesGlance" class="glance-root"></div>
|
|
3363
3356
|
</div>
|
|
3364
3357
|
|
|
3365
3358
|
<!-- Commitments Tab (Open Promises) -->
|
|
@@ -3374,10 +3367,7 @@
|
|
|
3374
3367
|
<div class="tab-purpose">
|
|
3375
3368
|
Secret Drop — create one-time links so someone can hand you a password or API key safely, never pasted into chat. Each link expires and is destroyed the moment it's used.
|
|
3376
3369
|
</div>
|
|
3377
|
-
<div id="
|
|
3378
|
-
No pending secret requests.
|
|
3379
|
-
</div>
|
|
3380
|
-
<div id="secretsList" style="display:flex;flex-direction:column;gap:12px"></div>
|
|
3370
|
+
<div id="secretsGlance" class="glance-root"></div>
|
|
3381
3371
|
</div>
|
|
3382
3372
|
|
|
3383
3373
|
<div id="commitmentsPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
|
|
@@ -3402,30 +3392,7 @@
|
|
|
3402
3392
|
<div class="tab-purpose">
|
|
3403
3393
|
Token usage rolled up from Claude Code session transcripts. Last 24 hours unless noted.
|
|
3404
3394
|
</div>
|
|
3405
|
-
|
|
3406
|
-
<!-- Summary card -->
|
|
3407
|
-
<div id="tokensSummaryCard" style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3408
|
-
<div style="font-weight:600">Last 24h</div>
|
|
3409
|
-
<div id="tokensSummaryBody" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;font-size:13px">
|
|
3410
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3411
|
-
</div>
|
|
3412
|
-
</div>
|
|
3413
|
-
|
|
3414
|
-
<!-- Top sessions -->
|
|
3415
|
-
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3416
|
-
<div style="font-weight:600">Top Sessions</div>
|
|
3417
|
-
<div id="tokensSessionsBody" style="font-size:13px">
|
|
3418
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3419
|
-
</div>
|
|
3420
|
-
</div>
|
|
3421
|
-
|
|
3422
|
-
<!-- Orphans -->
|
|
3423
|
-
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3424
|
-
<div style="font-weight:600">Idle Sessions (> 30 min)</div>
|
|
3425
|
-
<div id="tokensOrphansBody" style="font-size:13px">
|
|
3426
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3427
|
-
</div>
|
|
3428
|
-
</div>
|
|
3395
|
+
<div id="tokensGlance" class="glance-root"></div>
|
|
3429
3396
|
</div>
|
|
3430
3397
|
|
|
3431
3398
|
<!-- Resource Usage tab — per-agent CPU + memory (ResourceLedger Phase B).
|
|
@@ -3438,30 +3405,7 @@
|
|
|
3438
3405
|
<div class="tab-purpose">
|
|
3439
3406
|
How much CPU and memory this agent is using right now — the server process and each running session — sampled continuously. Last hour unless noted.
|
|
3440
3407
|
</div>
|
|
3441
|
-
|
|
3442
|
-
<!-- Right-now headline (aggregate) -->
|
|
3443
|
-
<div id="resourcesHeadlineCard" style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3444
|
-
<div style="font-weight:600">Right now (all processes)</div>
|
|
3445
|
-
<div id="resourcesHeadlineBody" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;font-size:13px">
|
|
3446
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3447
|
-
</div>
|
|
3448
|
-
</div>
|
|
3449
|
-
|
|
3450
|
-
<!-- Per-source breakdown (server vs each session) -->
|
|
3451
|
-
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3452
|
-
<div style="font-weight:600">By process</div>
|
|
3453
|
-
<div id="resourcesSourcesBody" style="font-size:13px">
|
|
3454
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3455
|
-
</div>
|
|
3456
|
-
</div>
|
|
3457
|
-
|
|
3458
|
-
<!-- Recent trend (last samples of the aggregate) -->
|
|
3459
|
-
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3460
|
-
<div style="font-weight:600">Recent trend</div>
|
|
3461
|
-
<div id="resourcesTrendBody" style="font-size:13px">
|
|
3462
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3463
|
-
</div>
|
|
3464
|
-
</div>
|
|
3408
|
+
<div id="resourcesGlance" class="glance-root"></div>
|
|
3465
3409
|
</div>
|
|
3466
3410
|
|
|
3467
3411
|
<!-- Blockers tab — Blocker Ledger read surface, rebuilt on the glance template
|
|
@@ -3510,21 +3454,7 @@
|
|
|
3510
3454
|
only ever finds nothing is a quiet smoke detector; a row that's mostly skipped may be starved.
|
|
3511
3455
|
</div>
|
|
3512
3456
|
|
|
3513
|
-
|
|
3514
|
-
<div id="llmActivityHeadlineCard" style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3515
|
-
<div style="font-weight:600">All components (selected window)</div>
|
|
3516
|
-
<div id="llmActivityHeadlineBody" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px;font-size:13px">
|
|
3517
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3518
|
-
</div>
|
|
3519
|
-
</div>
|
|
3520
|
-
|
|
3521
|
-
<!-- Per-component breakdown -->
|
|
3522
|
-
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px">
|
|
3523
|
-
<div style="font-weight:600">By component</div>
|
|
3524
|
-
<div id="llmActivityBody" style="font-size:13px;overflow-x:auto">
|
|
3525
|
-
<div style="color:var(--text-dim)">Loading...</div>
|
|
3526
|
-
</div>
|
|
3527
|
-
</div>
|
|
3457
|
+
<div id="llmActivityGlance" class="glance-root"></div>
|
|
3528
3458
|
</div>
|
|
3529
3459
|
|
|
3530
3460
|
<!-- Routing Map tab — the read-only nature-axis routing map (FD11 readable canary,
|
|
@@ -4543,7 +4473,7 @@
|
|
|
4543
4473
|
renderSessionList();
|
|
4544
4474
|
|
|
4545
4475
|
// Persist session in URL for refresh
|
|
4546
|
-
|
|
4476
|
+
updateUrlState();
|
|
4547
4477
|
|
|
4548
4478
|
// Focus input
|
|
4549
4479
|
document.getElementById('termInput').focus();
|
|
@@ -4779,7 +4709,7 @@
|
|
|
4779
4709
|
document.getElementById('terminalView').style.display = 'none';
|
|
4780
4710
|
document.getElementById('noSession').style.display = 'flex';
|
|
4781
4711
|
renderSessionList();
|
|
4782
|
-
|
|
4712
|
+
updateUrlState(); // Clear session from URL
|
|
4783
4713
|
}
|
|
4784
4714
|
|
|
4785
4715
|
// ── Input ────────────────────────────────────────────────
|
|
@@ -5380,10 +5310,6 @@
|
|
|
5380
5310
|
display: ['flex'],
|
|
5381
5311
|
onActivate: () => {
|
|
5382
5312
|
if (typeof loadSecrets === 'function') loadSecrets();
|
|
5383
|
-
if (typeof startSecretsTicker === 'function') startSecretsTicker();
|
|
5384
|
-
},
|
|
5385
|
-
onDeactivate: () => {
|
|
5386
|
-
if (typeof stopSecretsTicker === 'function') stopSecretsTicker();
|
|
5387
5313
|
},
|
|
5388
5314
|
},
|
|
5389
5315
|
];
|
|
@@ -5470,7 +5396,7 @@
|
|
|
5470
5396
|
if (activeTab.onActivate) activeTab.onActivate();
|
|
5471
5397
|
}
|
|
5472
5398
|
|
|
5473
|
-
|
|
5399
|
+
updateUrlState();
|
|
5474
5400
|
}
|
|
5475
5401
|
|
|
5476
5402
|
// ── File Viewer ────────────────────────────────────────────
|
|
@@ -5796,71 +5722,29 @@
|
|
|
5796
5722
|
}
|
|
5797
5723
|
|
|
5798
5724
|
async function loadTokens() {
|
|
5799
|
-
const
|
|
5800
|
-
|
|
5801
|
-
const
|
|
5725
|
+
const glanceRoot = document.getElementById('tokensGlance');
|
|
5726
|
+
if (!glanceRoot) return;
|
|
5727
|
+
const glance = await loadGlanceModule();
|
|
5728
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
5802
5729
|
try {
|
|
5803
5730
|
const [summaryResp, sessionsResp, orphansResp] = await Promise.all([
|
|
5804
5731
|
apiFetch('/tokens/summary'),
|
|
5805
5732
|
apiFetch('/tokens/sessions?limit=10'),
|
|
5806
5733
|
apiFetch('/tokens/orphans?idleMs=' + (30 * 60 * 1000)),
|
|
5807
5734
|
]);
|
|
5808
|
-
const
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
<div><div style="color:var(--text-dim)">Output</div><div>${fmtTokens(s.totalOutput)}</div></div>
|
|
5813
|
-
<div><div style="color:var(--text-dim)">Cache read</div><div>${fmtTokens(s.totalCacheRead)}</div></div>
|
|
5814
|
-
<div><div style="color:var(--text-dim)">Cache create</div><div>${fmtTokens(s.totalCacheCreate)}</div></div>
|
|
5815
|
-
<div><div style="color:var(--text-dim)">Events</div><div>${fmtTokens(s.eventCount)}</div></div>
|
|
5816
|
-
<div><div style="color:var(--text-dim)">Sessions</div><div>${fmtTokens(s.sessionsActive)}</div></div>
|
|
5817
|
-
`;
|
|
5818
|
-
|
|
5819
|
-
const sessions = sessionsResp.sessions || [];
|
|
5820
|
-
if (sessions.length === 0) {
|
|
5821
|
-
sessionsEl.innerHTML = '<div style="color:var(--text-dim)">No sessions in window.</div>';
|
|
5822
|
-
} else {
|
|
5823
|
-
sessionsEl.innerHTML = `
|
|
5824
|
-
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
|
5825
|
-
<thead><tr style="text-align:left;color:var(--text-dim)">
|
|
5826
|
-
<th style="padding:6px 8px">Session</th>
|
|
5827
|
-
<th style="padding:6px 8px">Project</th>
|
|
5828
|
-
<th style="padding:6px 8px;text-align:right">Tokens</th>
|
|
5829
|
-
<th style="padding:6px 8px;text-align:right">Events</th>
|
|
5830
|
-
<th style="padding:6px 8px">Last activity</th>
|
|
5831
|
-
</tr></thead>
|
|
5832
|
-
<tbody>
|
|
5833
|
-
${sessions.map(r => `
|
|
5834
|
-
<tr style="border-top:1px solid var(--border)">
|
|
5835
|
-
<td style="padding:6px 8px;font-family:monospace">${escapeHtml((r.sessionId || '').slice(0, 8))}</td>
|
|
5836
|
-
<td style="padding:6px 8px">${escapeHtml(r.projectPath || '—')}</td>
|
|
5837
|
-
<td style="padding:6px 8px;text-align:right">${fmtTokens(r.totalTokens)}</td>
|
|
5838
|
-
<td style="padding:6px 8px;text-align:right">${fmtTokens(r.eventCount)}</td>
|
|
5839
|
-
<td style="padding:6px 8px">${fmtRelTime(r.lastTs)}</td>
|
|
5840
|
-
</tr>
|
|
5841
|
-
`).join('')}
|
|
5842
|
-
</tbody>
|
|
5843
|
-
</table>
|
|
5844
|
-
`;
|
|
5845
|
-
}
|
|
5846
|
-
|
|
5847
|
-
const orphans = orphansResp.orphans || [];
|
|
5848
|
-
if (orphans.length === 0) {
|
|
5849
|
-
orphansEl.innerHTML = '<div style="color:var(--text-dim)">No idle sessions.</div>';
|
|
5850
|
-
} else {
|
|
5851
|
-
orphansEl.innerHTML = orphans.map(o => `
|
|
5852
|
-
<div style="padding:6px 0;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-family:monospace;font-size:12px">
|
|
5853
|
-
<span>${escapeHtml((o.sessionId || '').slice(0, 12))}</span>
|
|
5854
|
-
<span style="color:var(--text-dim)">${escapeHtml(o.projectPath || '—')}</span>
|
|
5855
|
-
<span>${fmtRelTime(o.lastTs)}</span>
|
|
5856
|
-
</div>
|
|
5857
|
-
`).join('');
|
|
5858
|
-
}
|
|
5735
|
+
const spec = glance.tokensGlanceSpec(
|
|
5736
|
+
document, summaryResp, sessionsResp.sessions || [], orphansResp.orphans || [], { fmtTs: fmtRelTime },
|
|
5737
|
+
);
|
|
5738
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
5859
5739
|
} catch (err) {
|
|
5860
|
-
const
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5740
|
+
const m = err && err.message ? String(err.message) : String(err);
|
|
5741
|
+
glanceRoot.textContent = '';
|
|
5742
|
+
const note = document.createElement('div');
|
|
5743
|
+
note.className = 'glance-empty';
|
|
5744
|
+
note.textContent = /503|unavailable|not initialized/i.test(m)
|
|
5745
|
+
? 'Token tracking isn’t available on this agent yet.'
|
|
5746
|
+
: 'Could not load token usage right now — refresh to retry.';
|
|
5747
|
+
glanceRoot.appendChild(note);
|
|
5864
5748
|
}
|
|
5865
5749
|
}
|
|
5866
5750
|
|
|
@@ -5887,76 +5771,25 @@
|
|
|
5887
5771
|
return String(src || '—');
|
|
5888
5772
|
}
|
|
5889
5773
|
async function loadLlmActivity() {
|
|
5890
|
-
const
|
|
5891
|
-
|
|
5774
|
+
const glanceRoot = document.getElementById('llmActivityGlance');
|
|
5775
|
+
if (!glanceRoot) return;
|
|
5892
5776
|
const winSel = document.getElementById('llmActivityWindow');
|
|
5893
5777
|
const hours = Number((winSel && winSel.value) || 168);
|
|
5894
|
-
const
|
|
5895
|
-
|
|
5896
|
-
'<div style="font-size:18px;font-weight:600">' + escapeHtml(String(val)) + '</div></div>';
|
|
5897
|
-
const fmtNum = (n) => {
|
|
5898
|
-
n = Number(n) || 0;
|
|
5899
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
5900
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
|
|
5901
|
-
return String(n);
|
|
5902
|
-
};
|
|
5903
|
-
const pct = (num, den) => (den > 0 ? Math.round((100 * num) / den) + '%' : '—');
|
|
5778
|
+
const glance = await loadGlanceModule();
|
|
5779
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
5904
5780
|
try {
|
|
5905
5781
|
const data = await apiFetch('/metrics/features?sinceHours=' + hours);
|
|
5906
|
-
const
|
|
5907
|
-
|
|
5908
|
-
const realCalls = totals.realCalls || 0;
|
|
5909
|
-
headlineEl.innerHTML =
|
|
5910
|
-
card('Components', features.length) +
|
|
5911
|
-
card('Calls', totals.calls || 0) +
|
|
5912
|
-
card('Real calls', realCalls) +
|
|
5913
|
-
card('Acted', totals.fired || 0) +
|
|
5914
|
-
card('Fire rate', pct(totals.fired || 0, realCalls)) +
|
|
5915
|
-
card('Skipped', pct(totals.shed || 0, totals.calls || 0)) +
|
|
5916
|
-
card('Errors', totals.errors || 0) +
|
|
5917
|
-
card('Tokens', fmtNum((totals.tokensIn || 0) + (totals.tokensOut || 0)));
|
|
5918
|
-
|
|
5919
|
-
if (features.length === 0) {
|
|
5920
|
-
bodyEl.innerHTML = '<div style="color:var(--text-dim)">No LLM activity recorded in this window yet.</div>';
|
|
5921
|
-
return;
|
|
5922
|
-
}
|
|
5923
|
-
const th = (t, right) => '<th style="padding:6px 8px;' + (right ? 'text-align:right' : 'text-align:left') + '">' + t + '</th>';
|
|
5924
|
-
const td = (v, right) => '<td style="padding:6px 8px;' + (right ? 'text-align:right' : '') + '">' + v + '</td>';
|
|
5925
|
-
bodyEl.innerHTML =
|
|
5926
|
-
'<table style="width:100%;border-collapse:collapse;font-size:12px;min-width:760px">' +
|
|
5927
|
-
'<thead><tr style="color:var(--text-dim)">' +
|
|
5928
|
-
th('Component') + th('Provider') + th('Model') +
|
|
5929
|
-
th('Calls', true) + th('Real', true) + th('Acted', true) + th('Fire rate', true) +
|
|
5930
|
-
th('Skipped', true) + th('Errors', true) + th('Tokens', true) + th('Latency p50/p95', true) +
|
|
5931
|
-
'</tr></thead><tbody>' +
|
|
5932
|
-
features.map((f) => {
|
|
5933
|
-
const real = f.realCalls || 0;
|
|
5934
|
-
const provider = (f.frameworks && f.frameworks.length) ? f.frameworks.join(', ') : '—';
|
|
5935
|
-
const model = (f.models && f.models.length) ? f.models.join(', ') : '—';
|
|
5936
|
-
const tokens = (f.tokensIn || 0) + (f.tokensOut || 0);
|
|
5937
|
-
return '<tr style="border-top:1px solid var(--border)">' +
|
|
5938
|
-
td(escapeHtml(f.feature)) +
|
|
5939
|
-
td(escapeHtml(provider)) +
|
|
5940
|
-
td(escapeHtml(model)) +
|
|
5941
|
-
td(String(f.calls || 0), true) +
|
|
5942
|
-
td(String(real), true) +
|
|
5943
|
-
td(String(f.fired || 0), true) +
|
|
5944
|
-
td(pct(f.fired || 0, real), true) +
|
|
5945
|
-
td(pct(f.shed || 0, f.calls || 0), true) +
|
|
5946
|
-
td(String(f.errors || 0), true) +
|
|
5947
|
-
td(fmtNum(tokens), true) +
|
|
5948
|
-
td((f.p50LatencyMs || 0) + '/' + (f.p95LatencyMs || 0) + 'ms', true) +
|
|
5949
|
-
'</tr>';
|
|
5950
|
-
}).join('') +
|
|
5951
|
-
'</tbody></table>';
|
|
5782
|
+
const spec = glance.llmActivityGlanceSpec(document, data);
|
|
5783
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
5952
5784
|
} catch (err) {
|
|
5953
5785
|
const m = err && err.message ? String(err.message) : String(err);
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5786
|
+
glanceRoot.textContent = '';
|
|
5787
|
+
const note = document.createElement('div');
|
|
5788
|
+
note.className = 'glance-empty';
|
|
5789
|
+
note.textContent = /503|unavailable|not initialized/i.test(m)
|
|
5790
|
+
? 'LLM activity tracking isn’t available on this agent yet.'
|
|
5791
|
+
: 'Could not load LLM activity right now — refresh to retry.';
|
|
5792
|
+
glanceRoot.appendChild(note);
|
|
5960
5793
|
}
|
|
5961
5794
|
}
|
|
5962
5795
|
|
|
@@ -6032,77 +5865,23 @@
|
|
|
6032
5865
|
}
|
|
6033
5866
|
|
|
6034
5867
|
async function loadResources() {
|
|
6035
|
-
const
|
|
6036
|
-
|
|
6037
|
-
const
|
|
5868
|
+
const glanceRoot = document.getElementById('resourcesGlance');
|
|
5869
|
+
if (!glanceRoot) return;
|
|
5870
|
+
const glance = await loadGlanceModule();
|
|
5871
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
6038
5872
|
try {
|
|
6039
|
-
const
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
]);
|
|
6043
|
-
const sources = summaryResp.sources || [];
|
|
6044
|
-
const agg = sources.find(s => s.source === 'aggregate');
|
|
6045
|
-
if (agg) {
|
|
6046
|
-
headlineEl.innerHTML =
|
|
6047
|
-
'<div><div style="color:var(--text-dim)">CPU now</div><div style="font-size:18px;font-weight:600">' + escapeHtml(fmtCpu(agg.currentCpuPercent)) + '</div></div>' +
|
|
6048
|
-
'<div><div style="color:var(--text-dim)">Memory now (RSS)</div><div style="font-size:18px;font-weight:600">' + escapeHtml(fmtBytes(agg.currentRssBytes)) + '</div></div>' +
|
|
6049
|
-
'<div><div style="color:var(--text-dim)">Avg CPU (1h)</div><div>' + escapeHtml(fmtCpu(agg.avgCpuPercent)) + '</div></div>' +
|
|
6050
|
-
'<div><div style="color:var(--text-dim)">Peak CPU (1h)</div><div>' + escapeHtml(fmtCpu(agg.peakCpuPercent)) + '</div></div>' +
|
|
6051
|
-
'<div><div style="color:var(--text-dim)">Peak memory (1h)</div><div>' + escapeHtml(fmtBytes(agg.peakRssBytes)) + '</div></div>' +
|
|
6052
|
-
'<div><div style="color:var(--text-dim)">Samples</div><div>' + escapeHtml(String(summaryResp.sampleCount || 0)) + '</div></div>';
|
|
6053
|
-
} else {
|
|
6054
|
-
headlineEl.innerHTML = '<div style="color:var(--text-dim)">No samples collected yet — give it a minute.</div>';
|
|
6055
|
-
}
|
|
6056
|
-
|
|
6057
|
-
// Per-source breakdown (server + each session), aggregate listed last.
|
|
6058
|
-
const breakdown = sources.filter(s => s.source !== 'aggregate');
|
|
6059
|
-
if (breakdown.length === 0) {
|
|
6060
|
-
sourcesEl.innerHTML = '<div style="color:var(--text-dim)">No process samples in the last hour.</div>';
|
|
6061
|
-
} else {
|
|
6062
|
-
sourcesEl.innerHTML =
|
|
6063
|
-
'<table style="width:100%;border-collapse:collapse;font-size:13px">' +
|
|
6064
|
-
'<thead><tr style="text-align:left;color:var(--text-dim)">' +
|
|
6065
|
-
'<th style="padding:6px 8px">Process</th>' +
|
|
6066
|
-
'<th style="padding:6px 8px;text-align:right">CPU now</th>' +
|
|
6067
|
-
'<th style="padding:6px 8px;text-align:right">Avg CPU</th>' +
|
|
6068
|
-
'<th style="padding:6px 8px;text-align:right">Memory (RSS)</th>' +
|
|
6069
|
-
'<th style="padding:6px 8px;text-align:right">Peak CPU</th>' +
|
|
6070
|
-
'</tr></thead><tbody>' +
|
|
6071
|
-
breakdown.map(s =>
|
|
6072
|
-
'<tr style="border-top:1px solid var(--border)">' +
|
|
6073
|
-
'<td style="padding:6px 8px">' + escapeHtml(resourceSourceLabel(s.source)) + '</td>' +
|
|
6074
|
-
'<td style="padding:6px 8px;text-align:right">' + escapeHtml(fmtCpu(s.currentCpuPercent)) + '</td>' +
|
|
6075
|
-
'<td style="padding:6px 8px;text-align:right">' + escapeHtml(fmtCpu(s.avgCpuPercent)) + '</td>' +
|
|
6076
|
-
'<td style="padding:6px 8px;text-align:right">' + escapeHtml(fmtBytes(s.currentRssBytes)) + '</td>' +
|
|
6077
|
-
'<td style="padding:6px 8px;text-align:right">' + escapeHtml(fmtCpu(s.peakCpuPercent)) + '</td>' +
|
|
6078
|
-
'</tr>'
|
|
6079
|
-
).join('') +
|
|
6080
|
-
'</tbody></table>';
|
|
6081
|
-
}
|
|
6082
|
-
|
|
6083
|
-
// Recent trend: the last aggregate samples (newest first).
|
|
6084
|
-
const samples = samplesResp.samples || [];
|
|
6085
|
-
if (samples.length === 0) {
|
|
6086
|
-
trendEl.innerHTML = '<div style="color:var(--text-dim)">No trend yet.</div>';
|
|
6087
|
-
} else {
|
|
6088
|
-
trendEl.innerHTML = samples.map(s =>
|
|
6089
|
-
'<div style="padding:5px 0;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-size:12px">' +
|
|
6090
|
-
'<span style="color:var(--text-dim)">' + escapeHtml(fmtRelTime(s.ts)) + '</span>' +
|
|
6091
|
-
'<span>' + escapeHtml(fmtCpu(s.cpuPercent)) + ' CPU</span>' +
|
|
6092
|
-
'<span>' + escapeHtml(fmtBytes(s.rssBytes)) + ' RSS</span>' +
|
|
6093
|
-
'</div>'
|
|
6094
|
-
).join('');
|
|
6095
|
-
}
|
|
5873
|
+
const summaryResp = await apiFetch('/resources/summary?sinceHours=1');
|
|
5874
|
+
const spec = glance.resourcesGlanceSpec(document, summaryResp);
|
|
5875
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
6096
5876
|
} catch (err) {
|
|
6097
5877
|
const m = err && err.message ? String(err.message) : String(err);
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
trendEl.innerHTML = '';
|
|
5878
|
+
glanceRoot.textContent = '';
|
|
5879
|
+
const note = document.createElement('div');
|
|
5880
|
+
note.className = 'glance-empty';
|
|
5881
|
+
note.textContent = /503|unavailable|disabled|not initialized/i.test(m)
|
|
5882
|
+
? 'Resource tracking isn’t turned on for this agent yet.'
|
|
5883
|
+
: 'Could not load resource usage right now — refresh to retry.';
|
|
5884
|
+
glanceRoot.appendChild(note);
|
|
6106
5885
|
}
|
|
6107
5886
|
}
|
|
6108
5887
|
|
|
@@ -6536,7 +6315,7 @@
|
|
|
6536
6315
|
body.innerHTML = `<div class="file-empty"><div class="icon">📦</div><p>Binary file (${formatFileSize(data.size)})<br>Preview not available</p></div>`;
|
|
6537
6316
|
updateFileBadges(false);
|
|
6538
6317
|
editModifiedTimestamp = '';
|
|
6539
|
-
|
|
6318
|
+
updateUrlState();
|
|
6540
6319
|
return;
|
|
6541
6320
|
}
|
|
6542
6321
|
|
|
@@ -6553,7 +6332,7 @@
|
|
|
6553
6332
|
}
|
|
6554
6333
|
|
|
6555
6334
|
updateFileBadges(data.editable);
|
|
6556
|
-
|
|
6335
|
+
updateUrlState();
|
|
6557
6336
|
} catch (err) {
|
|
6558
6337
|
body.innerHTML = `<div class="file-error">Failed to load file: ${escapeHtml(err.message)}</div>`;
|
|
6559
6338
|
}
|
|
@@ -6727,7 +6506,7 @@
|
|
|
6727
6506
|
document.getElementById('fileContentHeader').style.display = 'none';
|
|
6728
6507
|
document.getElementById('fileContentBody').innerHTML =
|
|
6729
6508
|
'<div class="file-empty"><div class="icon">📂</div><p>Select a file to view its contents</p></div>';
|
|
6730
|
-
|
|
6509
|
+
updateUrlState();
|
|
6731
6510
|
}
|
|
6732
6511
|
|
|
6733
6512
|
function formatFileSize(bytes) {
|
|
@@ -7015,25 +6794,34 @@
|
|
|
7015
6794
|
|
|
7016
6795
|
// ── Deep linking via query params ──────────────────────────
|
|
7017
6796
|
|
|
7018
|
-
|
|
6797
|
+
// #1442 — record the active tab (and its extras) in the URL so a page refresh
|
|
6798
|
+
// restores the same tab. Before this, only Files wrote ?tab=; every other tab
|
|
6799
|
+
// deleted the param, so any refresh dumped the operator back on Sessions.
|
|
6800
|
+
// Named updateUrlState (was updateFileUrl) now that it is not Files-specific.
|
|
6801
|
+
function updateUrlState() {
|
|
7019
6802
|
const params = new URLSearchParams(window.location.search);
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
params.delete('path');
|
|
7026
|
-
}
|
|
7027
|
-
params.delete('session');
|
|
6803
|
+
// The default Sessions tab omits ?tab= to keep bare URLs clean (and so a
|
|
6804
|
+
// fresh visit with no query still lands on Sessions). Every other tab records
|
|
6805
|
+
// its id, so a refresh returns to it.
|
|
6806
|
+
if (currentTab && currentTab !== 'sessions') {
|
|
6807
|
+
params.set('tab', currentTab);
|
|
7028
6808
|
} else {
|
|
7029
6809
|
params.delete('tab');
|
|
6810
|
+
}
|
|
6811
|
+
// Files keeps its deep-link path — existing ?tab=files&path=… links must keep
|
|
6812
|
+
// working byte-for-byte.
|
|
6813
|
+
if (currentTab === 'files' && currentFilePath) {
|
|
6814
|
+
params.set('path', currentFilePath);
|
|
6815
|
+
} else {
|
|
7030
6816
|
params.delete('path');
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
6817
|
+
}
|
|
6818
|
+
// Persist the active streamed session in the URL (unchanged behavior — set
|
|
6819
|
+
// whenever a session is active except on Files; meaningful on Sessions,
|
|
6820
|
+
// harmless elsewhere).
|
|
6821
|
+
if (currentTab !== 'files' && activeSession) {
|
|
6822
|
+
params.set('session', activeSession);
|
|
6823
|
+
} else {
|
|
6824
|
+
params.delete('session');
|
|
7037
6825
|
}
|
|
7038
6826
|
const qs = params.toString();
|
|
7039
6827
|
const newUrl = window.location.pathname + (qs ? '?' + qs : '');
|
|
@@ -7046,6 +6834,11 @@
|
|
|
7046
6834
|
const filePath = params.get('path');
|
|
7047
6835
|
const sessionParam = params.get('session');
|
|
7048
6836
|
|
|
6837
|
+
// #1442 — restore any registered tab on refresh, not just Files. An unknown
|
|
6838
|
+
// or removed ?tab= value falls through to the default tab with no switchTab
|
|
6839
|
+
// and no console error (validated against TAB_REGISTRY). The session restore
|
|
6840
|
+
// below runs independently, so ?tab=X&session=Y both apply.
|
|
6841
|
+
const isKnownTab = !!tab && TAB_REGISTRY.some(t => t.id === tab);
|
|
7049
6842
|
if (tab === 'files') {
|
|
7050
6843
|
switchTab('files');
|
|
7051
6844
|
if (filePath) {
|
|
@@ -7066,7 +6859,11 @@
|
|
|
7066
6859
|
};
|
|
7067
6860
|
waitForTree();
|
|
7068
6861
|
}
|
|
7069
|
-
} else if (
|
|
6862
|
+
} else if (isKnownTab) {
|
|
6863
|
+
switchTab(tab);
|
|
6864
|
+
}
|
|
6865
|
+
|
|
6866
|
+
if (sessionParam) {
|
|
7070
6867
|
// Restore active session from URL — wait for session list to populate
|
|
7071
6868
|
let attempts = 0;
|
|
7072
6869
|
const waitForSessions = () => {
|
|
@@ -8100,96 +7897,39 @@
|
|
|
8100
7897
|
// - On 404 (prGate.phase='off' — commit 8), render a disabled
|
|
8101
7898
|
// placeholder rather than erroring.
|
|
8102
7899
|
async function loadPrPipeline() {
|
|
8103
|
-
const
|
|
8104
|
-
|
|
8105
|
-
const
|
|
8106
|
-
|
|
8107
|
-
// "never inject content as HTML" invariant trivially true.
|
|
8108
|
-
while (list.firstChild) list.removeChild(list.firstChild);
|
|
8109
|
-
notice.textContent = 'Loading…';
|
|
8110
|
-
empty.style.display = 'none';
|
|
7900
|
+
const glanceRoot = document.getElementById('prPipelineGlance');
|
|
7901
|
+
if (!glanceRoot) return;
|
|
7902
|
+
const glance = await loadGlanceModule();
|
|
7903
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
8111
7904
|
|
|
8112
|
-
//
|
|
8113
|
-
//
|
|
8114
|
-
|
|
8115
|
-
let res = null;
|
|
8116
|
-
let httpStatus = 0;
|
|
7905
|
+
// Raw fetch (not apiFetch) so we can distinguish 404 (gate disabled,
|
|
7906
|
+
// expected during phase=off) from other errors without throw-on-non-ok.
|
|
7907
|
+
let res = null, httpStatus = 0;
|
|
8117
7908
|
try {
|
|
8118
|
-
const raw = await fetch('/pr-gate/metrics', {
|
|
8119
|
-
headers: { 'Authorization': `Bearer ${token}` },
|
|
8120
|
-
});
|
|
7909
|
+
const raw = await fetch('/pr-gate/metrics', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
8121
7910
|
httpStatus = raw.status;
|
|
8122
7911
|
if (raw.ok) res = await raw.json();
|
|
8123
|
-
} catch {
|
|
8124
|
-
res = null;
|
|
8125
|
-
}
|
|
7912
|
+
} catch { res = null; }
|
|
8126
7913
|
|
|
8127
7914
|
if (httpStatus === 404 || (res && res.disabled === true)) {
|
|
8128
|
-
|
|
8129
|
-
|
|
7915
|
+
glanceRoot.textContent = '';
|
|
7916
|
+
const note = document.createElement('div');
|
|
7917
|
+
note.className = 'glance-empty';
|
|
7918
|
+
note.textContent = 'The auto-merge gate isn’t turned on for this agent yet.';
|
|
7919
|
+
glanceRoot.appendChild(note);
|
|
8130
7920
|
return;
|
|
8131
7921
|
}
|
|
8132
7922
|
if (!res) {
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
const phase = typeof res.phase === 'string' ? res.phase : 'unknown';
|
|
8139
|
-
notice.textContent = `Phase: ${phase}`;
|
|
8140
|
-
|
|
8141
|
-
const entries = Array.isArray(res.entries) ? res.entries : [];
|
|
8142
|
-
if (entries.length === 0) {
|
|
8143
|
-
empty.style.display = 'block';
|
|
7923
|
+
glanceRoot.textContent = '';
|
|
7924
|
+
const note = document.createElement('div');
|
|
7925
|
+
note.className = 'glance-empty';
|
|
7926
|
+
note.textContent = 'Could not load the merge gate right now — refresh to retry.';
|
|
7927
|
+
glanceRoot.appendChild(note);
|
|
8144
7928
|
return;
|
|
8145
7929
|
}
|
|
8146
7930
|
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
card.style.background = 'var(--panel)';
|
|
8150
|
-
card.style.padding = '12px';
|
|
8151
|
-
card.style.borderRadius = '6px';
|
|
8152
|
-
card.style.border = '1px solid var(--border)';
|
|
8153
|
-
|
|
8154
|
-
const header = document.createElement('div');
|
|
8155
|
-
header.style.display = 'flex';
|
|
8156
|
-
header.style.justifyContent = 'space-between';
|
|
8157
|
-
header.style.marginBottom = '6px';
|
|
8158
|
-
|
|
8159
|
-
const left = document.createElement('div');
|
|
8160
|
-
left.style.fontWeight = '600';
|
|
8161
|
-
const pr = typeof entry.pr_number === 'number' ? `PR #${entry.pr_number}` : 'PR #—';
|
|
8162
|
-
const sha = typeof entry.head_sha === 'string' ? ` @ ${entry.head_sha.slice(0, 8)}` : '';
|
|
8163
|
-
left.textContent = pr + sha;
|
|
8164
|
-
|
|
8165
|
-
const right = document.createElement('div');
|
|
8166
|
-
right.style.fontSize = '12px';
|
|
8167
|
-
right.style.color = entry.eligible ? 'var(--accent)' : 'var(--text-dim)';
|
|
8168
|
-
right.textContent = entry.eligible ? 'eligible' : 'not eligible';
|
|
8169
|
-
|
|
8170
|
-
header.appendChild(left);
|
|
8171
|
-
header.appendChild(right);
|
|
8172
|
-
card.appendChild(header);
|
|
8173
|
-
|
|
8174
|
-
if (typeof entry.reason === 'string' && entry.reason.length > 0) {
|
|
8175
|
-
const reason = document.createElement('div');
|
|
8176
|
-
reason.style.fontSize = '13px';
|
|
8177
|
-
reason.style.color = 'var(--text-dim)';
|
|
8178
|
-
reason.textContent = entry.reason;
|
|
8179
|
-
card.appendChild(reason);
|
|
8180
|
-
}
|
|
8181
|
-
|
|
8182
|
-
if (typeof entry.created_at === 'string') {
|
|
8183
|
-
const time = document.createElement('div');
|
|
8184
|
-
time.style.fontSize = '11px';
|
|
8185
|
-
time.style.color = 'var(--text-dim)';
|
|
8186
|
-
time.style.marginTop = '4px';
|
|
8187
|
-
time.textContent = entry.created_at;
|
|
8188
|
-
card.appendChild(time);
|
|
8189
|
-
}
|
|
8190
|
-
|
|
8191
|
-
list.appendChild(card);
|
|
8192
|
-
}
|
|
7931
|
+
const spec = glance.prPipelineGlanceSpec(document, res);
|
|
7932
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
8193
7933
|
}
|
|
8194
7934
|
|
|
8195
7935
|
// ── Projects Tab (PROJECT-SCOPE-SPEC Phase 1.10) ─────────────
|
|
@@ -8409,178 +8149,44 @@
|
|
|
8409
8149
|
// through textContent; no innerHTML. Matches the PR Pipeline tab's
|
|
8410
8150
|
// XSS-safe pattern.
|
|
8411
8151
|
async function loadInitiatives() {
|
|
8412
|
-
const
|
|
8413
|
-
const empty = document.getElementById('initiativesEmpty');
|
|
8414
|
-
const digestBox = document.getElementById('initiativesDigest');
|
|
8415
|
-
const filter = document.getElementById('initiativesFilter');
|
|
8152
|
+
const glanceRoot = document.getElementById('initiativesGlance');
|
|
8416
8153
|
const countBadge = document.getElementById('tabInitiativeCount');
|
|
8154
|
+
if (!glanceRoot) return;
|
|
8155
|
+
const glance = await loadGlanceModule();
|
|
8156
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
8417
8157
|
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8158
|
+
// Keep the status filter + showAll controls — they re-query and the glance
|
|
8159
|
+
// renders whatever comes back (behavior preserved).
|
|
8160
|
+
const filter = document.getElementById('initiativesFilter');
|
|
8161
|
+
const showAll = document.getElementById('initiativesShowAll');
|
|
8162
|
+
const params = [];
|
|
8163
|
+
if (filter && filter.value) params.push(`status=${encodeURIComponent(filter.value)}`);
|
|
8164
|
+
if (!showAll || !showAll.checked) { params.push('excludeKind=project'); params.push('excludeParented=true'); }
|
|
8165
|
+
const q = params.length ? '?' + params.join('&') : '';
|
|
8422
8166
|
|
|
8423
|
-
let itemsRes = null;
|
|
8424
|
-
let digestRes = null;
|
|
8167
|
+
let itemsRes = null, digestRes = null;
|
|
8425
8168
|
try {
|
|
8426
|
-
// PROJECT-SCOPE-SPEC Phase 1.10 — by default hide project-kind
|
|
8427
|
-
// records (shown in the Projects tab) and parented children
|
|
8428
|
-
// (shown inside their parent's card). Server-side filter so
|
|
8429
|
-
// we don't ship them over the wire.
|
|
8430
|
-
const showAll = document.getElementById('initiativesShowAll');
|
|
8431
|
-
const params = [];
|
|
8432
|
-
if (filter && filter.value) params.push(`status=${encodeURIComponent(filter.value)}`);
|
|
8433
|
-
if (!showAll || !showAll.checked) {
|
|
8434
|
-
params.push('excludeKind=project');
|
|
8435
|
-
params.push('excludeParented=true');
|
|
8436
|
-
}
|
|
8437
|
-
const q = params.length ? '?' + params.join('&') : '';
|
|
8438
8169
|
const [a, b] = await Promise.all([
|
|
8439
8170
|
apiFetch('/initiatives' + q).catch(() => null),
|
|
8440
8171
|
apiFetch('/initiatives/digest').catch(() => null),
|
|
8441
8172
|
]);
|
|
8442
|
-
itemsRes = a;
|
|
8443
|
-
|
|
8444
|
-
} catch { /* handled by null check below */ }
|
|
8445
|
-
|
|
8446
|
-
if (!itemsRes) {
|
|
8447
|
-
empty.style.display = 'block';
|
|
8448
|
-
empty.textContent = 'Initiative tracker unavailable.';
|
|
8449
|
-
if (countBadge) countBadge.textContent = '0';
|
|
8450
|
-
return;
|
|
8451
|
-
}
|
|
8173
|
+
itemsRes = a; digestRes = b;
|
|
8174
|
+
} catch { /* handled by the null check below */ }
|
|
8452
8175
|
|
|
8453
|
-
const items = Array.isArray(itemsRes.items) ? itemsRes.items : [];
|
|
8176
|
+
const items = (itemsRes && Array.isArray(itemsRes.items)) ? itemsRes.items : [];
|
|
8454
8177
|
if (countBadge) countBadge.textContent = String(items.length);
|
|
8455
8178
|
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
const
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
digestBox.appendChild(title);
|
|
8463
|
-
for (const d of digestRes.items) {
|
|
8464
|
-
const row = document.createElement('div');
|
|
8465
|
-
row.style.fontSize = '13px';
|
|
8466
|
-
row.style.padding = '3px 0';
|
|
8467
|
-
const colorMap = {
|
|
8468
|
-
'needs-user': '#e27d3b',
|
|
8469
|
-
'next-check-due': '#d65f5f',
|
|
8470
|
-
'ready-to-advance': '#4a9a4a',
|
|
8471
|
-
'stale': '#888',
|
|
8472
|
-
};
|
|
8473
|
-
const tag = document.createElement('span');
|
|
8474
|
-
tag.style.color = colorMap[d.reason] || 'var(--text-dim)';
|
|
8475
|
-
tag.style.marginRight = '6px';
|
|
8476
|
-
tag.textContent = `[${d.reason}]`;
|
|
8477
|
-
const label = document.createElement('span');
|
|
8478
|
-
label.textContent = `${d.title} — ${d.detail}`;
|
|
8479
|
-
row.appendChild(tag);
|
|
8480
|
-
row.appendChild(label);
|
|
8481
|
-
digestBox.appendChild(row);
|
|
8482
|
-
}
|
|
8483
|
-
digestBox.style.display = 'block';
|
|
8484
|
-
}
|
|
8485
|
-
|
|
8486
|
-
if (items.length === 0) {
|
|
8487
|
-
empty.style.display = 'block';
|
|
8488
|
-
empty.textContent = 'No initiatives to display. Create one via POST /initiatives.';
|
|
8179
|
+
if (!itemsRes) {
|
|
8180
|
+
glanceRoot.textContent = '';
|
|
8181
|
+
const note = document.createElement('div');
|
|
8182
|
+
note.className = 'glance-empty';
|
|
8183
|
+
note.textContent = 'The initiative tracker isn’t available on this agent yet.';
|
|
8184
|
+
glanceRoot.appendChild(note);
|
|
8489
8185
|
return;
|
|
8490
8186
|
}
|
|
8491
8187
|
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
card.style.background = 'var(--panel)';
|
|
8495
|
-
card.style.padding = '12px';
|
|
8496
|
-
card.style.borderRadius = '6px';
|
|
8497
|
-
card.style.border = '1px solid var(--border)';
|
|
8498
|
-
|
|
8499
|
-
const header = document.createElement('div');
|
|
8500
|
-
header.style.display = 'flex';
|
|
8501
|
-
header.style.justifyContent = 'space-between';
|
|
8502
|
-
header.style.alignItems = 'center';
|
|
8503
|
-
header.style.marginBottom = '6px';
|
|
8504
|
-
|
|
8505
|
-
const titleEl = document.createElement('div');
|
|
8506
|
-
titleEl.style.fontWeight = '600';
|
|
8507
|
-
titleEl.textContent = ini.title || ini.id || '(untitled)';
|
|
8508
|
-
header.appendChild(titleEl);
|
|
8509
|
-
|
|
8510
|
-
const status = document.createElement('span');
|
|
8511
|
-
status.style.fontSize = '11px';
|
|
8512
|
-
status.style.padding = '2px 8px';
|
|
8513
|
-
status.style.borderRadius = '999px';
|
|
8514
|
-
const statusColors = {
|
|
8515
|
-
active: 'var(--accent)',
|
|
8516
|
-
completed: '#4a9a4a',
|
|
8517
|
-
archived: 'var(--text-dim)',
|
|
8518
|
-
abandoned: '#d65f5f',
|
|
8519
|
-
};
|
|
8520
|
-
status.style.background = statusColors[ini.status] || 'var(--text-dim)';
|
|
8521
|
-
status.style.color = '#fff';
|
|
8522
|
-
status.textContent = ini.status || 'unknown';
|
|
8523
|
-
header.appendChild(status);
|
|
8524
|
-
card.appendChild(header);
|
|
8525
|
-
|
|
8526
|
-
if (ini.description) {
|
|
8527
|
-
const desc = document.createElement('div');
|
|
8528
|
-
desc.style.fontSize = '13px';
|
|
8529
|
-
desc.style.color = 'var(--text-dim)';
|
|
8530
|
-
desc.style.marginBottom = '8px';
|
|
8531
|
-
desc.textContent = ini.description;
|
|
8532
|
-
card.appendChild(desc);
|
|
8533
|
-
}
|
|
8534
|
-
|
|
8535
|
-
// Phase pills
|
|
8536
|
-
if (Array.isArray(ini.phases) && ini.phases.length > 0) {
|
|
8537
|
-
const phaseRow = document.createElement('div');
|
|
8538
|
-
phaseRow.style.display = 'flex';
|
|
8539
|
-
phaseRow.style.gap = '6px';
|
|
8540
|
-
phaseRow.style.flexWrap = 'wrap';
|
|
8541
|
-
phaseRow.style.marginBottom = '8px';
|
|
8542
|
-
const phaseColors = {
|
|
8543
|
-
pending: 'var(--text-dim)',
|
|
8544
|
-
'in-progress': 'var(--accent)',
|
|
8545
|
-
done: '#4a9a4a',
|
|
8546
|
-
blocked: '#d65f5f',
|
|
8547
|
-
};
|
|
8548
|
-
for (const p of ini.phases) {
|
|
8549
|
-
const pill = document.createElement('span');
|
|
8550
|
-
pill.style.fontSize = '11px';
|
|
8551
|
-
pill.style.padding = '2px 8px';
|
|
8552
|
-
pill.style.borderRadius = '4px';
|
|
8553
|
-
pill.style.border = `1px solid ${phaseColors[p.status] || 'var(--border)'}`;
|
|
8554
|
-
pill.style.color = phaseColors[p.status] || 'var(--text)';
|
|
8555
|
-
pill.textContent = `${p.name} · ${p.status}`;
|
|
8556
|
-
phaseRow.appendChild(pill);
|
|
8557
|
-
}
|
|
8558
|
-
card.appendChild(phaseRow);
|
|
8559
|
-
}
|
|
8560
|
-
|
|
8561
|
-
// Meta row
|
|
8562
|
-
const meta = document.createElement('div');
|
|
8563
|
-
meta.style.fontSize = '11px';
|
|
8564
|
-
meta.style.color = 'var(--text-dim)';
|
|
8565
|
-
const lastTouched = ini.lastTouchedAt ? ini.lastTouchedAt.slice(0, 10) : '';
|
|
8566
|
-
const needsUser = ini.needsUser ? ' · needs you' : '';
|
|
8567
|
-
const blockerCount = Array.isArray(ini.blockers) && ini.blockers.length > 0
|
|
8568
|
-
? ` · ${ini.blockers.length} blocker${ini.blockers.length === 1 ? '' : 's'}`
|
|
8569
|
-
: '';
|
|
8570
|
-
meta.textContent = `Last touched: ${lastTouched}${needsUser}${blockerCount}`;
|
|
8571
|
-
card.appendChild(meta);
|
|
8572
|
-
|
|
8573
|
-
if (ini.needsUser && ini.needsUserReason) {
|
|
8574
|
-
const reason = document.createElement('div');
|
|
8575
|
-
reason.style.fontSize = '12px';
|
|
8576
|
-
reason.style.marginTop = '4px';
|
|
8577
|
-
reason.style.color = '#e27d3b';
|
|
8578
|
-
reason.textContent = `→ ${ini.needsUserReason}`;
|
|
8579
|
-
card.appendChild(reason);
|
|
8580
|
-
}
|
|
8581
|
-
|
|
8582
|
-
list.appendChild(card);
|
|
8583
|
-
}
|
|
8188
|
+
const spec = glance.initiativesGlanceSpec(document, itemsRes, digestRes, { fmtTs: fmtRelTime });
|
|
8189
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
8584
8190
|
}
|
|
8585
8191
|
|
|
8586
8192
|
// ── Commitments Tab — glance floors F10/F11 (topic 29836) ────
|
|
@@ -8689,139 +8295,39 @@
|
|
|
8689
8295
|
}
|
|
8690
8296
|
}
|
|
8691
8297
|
|
|
8298
|
+
async function cancelSecretRequest(token) {
|
|
8299
|
+
try {
|
|
8300
|
+
await apiFetch(`/secrets/pending/${encodeURIComponent(token)}`, { method: 'DELETE' });
|
|
8301
|
+
await loadSecrets();
|
|
8302
|
+
} catch (err) {
|
|
8303
|
+
alert('Cancel failed: ' + (err && err.message ? err.message : String(err)));
|
|
8304
|
+
}
|
|
8305
|
+
}
|
|
8306
|
+
|
|
8692
8307
|
async function loadSecrets() {
|
|
8693
|
-
const
|
|
8694
|
-
|
|
8308
|
+
const glanceRoot = document.getElementById('secretsGlance');
|
|
8309
|
+
if (!glanceRoot) return;
|
|
8695
8310
|
const countBadge = document.getElementById('tabSecretCount');
|
|
8696
|
-
|
|
8697
|
-
|
|
8311
|
+
const glance = await loadGlanceModule();
|
|
8312
|
+
if (!glance) { glanceRoot.textContent = 'Loading the glance view failed — refresh to retry.'; return; }
|
|
8698
8313
|
|
|
8699
8314
|
let res = null;
|
|
8700
8315
|
try {
|
|
8701
8316
|
res = await apiFetch('/secrets/pending');
|
|
8702
|
-
} catch { /* fall through */ }
|
|
8317
|
+
} catch { /* fall through — an empty glance is the honest state */ }
|
|
8703
8318
|
|
|
8704
8319
|
const pending = (res && Array.isArray(res.pending)) ? res.pending : [];
|
|
8705
8320
|
secretsLastPending = pending;
|
|
8706
8321
|
|
|
8707
|
-
// Badge shows non-expired count.
|
|
8322
|
+
// Badge shows non-expired count (unchanged behavior).
|
|
8708
8323
|
const now = Date.now();
|
|
8709
8324
|
const liveCount = pending.filter(p => p.expiresAt > now).length;
|
|
8710
8325
|
if (countBadge) countBadge.textContent = String(liveCount);
|
|
8711
8326
|
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
}
|
|
8717
|
-
|
|
8718
|
-
const fmtTs = (ms) => {
|
|
8719
|
-
if (!ms) return '—';
|
|
8720
|
-
try { return new Date(ms).toLocaleString(); } catch { return String(ms); }
|
|
8721
|
-
};
|
|
8722
|
-
|
|
8723
|
-
for (const p of pending) {
|
|
8724
|
-
const card = document.createElement('div');
|
|
8725
|
-
card.style.cssText = 'padding:14px;border:1px solid var(--border);border-radius:6px;background:var(--bg-dim);display:flex;flex-direction:column;gap:8px';
|
|
8726
|
-
|
|
8727
|
-
const header = document.createElement('div');
|
|
8728
|
-
header.style.cssText = 'display:flex;justify-content:space-between;gap:12px;align-items:flex-start';
|
|
8729
|
-
const label = document.createElement('div');
|
|
8730
|
-
label.style.cssText = 'flex:1;font-weight:600;line-height:1.3';
|
|
8731
|
-
label.textContent = p.label || '(no label)';
|
|
8732
|
-
header.appendChild(label);
|
|
8733
|
-
|
|
8734
|
-
const badge = document.createElement('span');
|
|
8735
|
-
const expired = !!p.expired;
|
|
8736
|
-
badge.textContent = expired ? 'expired' : 'pending';
|
|
8737
|
-
badge.style.cssText = `font-size:11px;padding:3px 8px;border-radius:4px;background:${expired ? '#c66' : '#4a9a4a'};color:#fff;white-space:nowrap`;
|
|
8738
|
-
header.appendChild(badge);
|
|
8739
|
-
card.appendChild(header);
|
|
8740
|
-
|
|
8741
|
-
const meta = document.createElement('div');
|
|
8742
|
-
meta.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:6px;font-size:12px;color:var(--text-dim)';
|
|
8743
|
-
|
|
8744
|
-
const rows = [
|
|
8745
|
-
['token', (p.token || '').slice(0, 12) + '…'],
|
|
8746
|
-
['topic', p.topicId != null ? String(p.topicId) : '—'],
|
|
8747
|
-
['created', fmtTs(p.createdAt)],
|
|
8748
|
-
['expires', fmtTs(p.expiresAt)],
|
|
8749
|
-
];
|
|
8750
|
-
for (const [k, v] of rows) {
|
|
8751
|
-
const cell = document.createElement('div');
|
|
8752
|
-
const kEl = document.createElement('span');
|
|
8753
|
-
kEl.textContent = `${k}: `;
|
|
8754
|
-
kEl.style.opacity = '0.7';
|
|
8755
|
-
const vEl = document.createElement('span');
|
|
8756
|
-
vEl.textContent = v;
|
|
8757
|
-
vEl.style.color = 'var(--text)';
|
|
8758
|
-
cell.appendChild(kEl);
|
|
8759
|
-
cell.appendChild(vEl);
|
|
8760
|
-
meta.appendChild(cell);
|
|
8761
|
-
}
|
|
8762
|
-
|
|
8763
|
-
const countdownCell = document.createElement('div');
|
|
8764
|
-
const cLabel = document.createElement('span');
|
|
8765
|
-
cLabel.textContent = 'expires in: ';
|
|
8766
|
-
cLabel.style.opacity = '0.7';
|
|
8767
|
-
const cVal = document.createElement('span');
|
|
8768
|
-
cVal.id = `secretCountdown-${p.token}`;
|
|
8769
|
-
cVal.style.color = 'var(--text)';
|
|
8770
|
-
cVal.style.fontVariantNumeric = 'tabular-nums';
|
|
8771
|
-
cVal.textContent = formatCountdown(p.expiresAt - now);
|
|
8772
|
-
countdownCell.appendChild(cLabel);
|
|
8773
|
-
countdownCell.appendChild(cVal);
|
|
8774
|
-
meta.appendChild(countdownCell);
|
|
8775
|
-
|
|
8776
|
-
card.appendChild(meta);
|
|
8777
|
-
|
|
8778
|
-
// Links and actions.
|
|
8779
|
-
const actions = document.createElement('div');
|
|
8780
|
-
actions.style.cssText = 'display:flex;gap:8px;align-items:center;flex-wrap:wrap';
|
|
8781
|
-
|
|
8782
|
-
const url = p.tunnelUrl || p.localUrl;
|
|
8783
|
-
if (url) {
|
|
8784
|
-
const openBtn = document.createElement('a');
|
|
8785
|
-
openBtn.textContent = 'Open drop link';
|
|
8786
|
-
openBtn.href = url;
|
|
8787
|
-
openBtn.target = '_blank';
|
|
8788
|
-
openBtn.rel = 'noopener noreferrer';
|
|
8789
|
-
openBtn.style.cssText = 'padding:6px 10px;border:1px solid var(--border);border-radius:4px;text-decoration:none;color:var(--text);cursor:pointer';
|
|
8790
|
-
actions.appendChild(openBtn);
|
|
8791
|
-
|
|
8792
|
-
const copyBtn = document.createElement('button');
|
|
8793
|
-
copyBtn.textContent = 'Copy link';
|
|
8794
|
-
copyBtn.style.cssText = 'padding:6px 10px;cursor:pointer';
|
|
8795
|
-
copyBtn.addEventListener('click', async () => {
|
|
8796
|
-
try {
|
|
8797
|
-
await navigator.clipboard.writeText(url);
|
|
8798
|
-
copyBtn.textContent = 'Copied';
|
|
8799
|
-
setTimeout(() => { copyBtn.textContent = 'Copy link'; }, 1200);
|
|
8800
|
-
} catch {
|
|
8801
|
-
copyBtn.textContent = 'Copy failed';
|
|
8802
|
-
}
|
|
8803
|
-
});
|
|
8804
|
-
actions.appendChild(copyBtn);
|
|
8805
|
-
}
|
|
8806
|
-
|
|
8807
|
-
const cancelBtn = document.createElement('button');
|
|
8808
|
-
cancelBtn.textContent = 'Cancel';
|
|
8809
|
-
cancelBtn.style.cssText = 'padding:6px 10px;cursor:pointer';
|
|
8810
|
-
cancelBtn.addEventListener('click', async () => {
|
|
8811
|
-
cancelBtn.disabled = true;
|
|
8812
|
-
try {
|
|
8813
|
-
await apiFetch(`/secrets/pending/${encodeURIComponent(p.token)}`, { method: 'DELETE' });
|
|
8814
|
-
await loadSecrets();
|
|
8815
|
-
} catch (err) {
|
|
8816
|
-
cancelBtn.disabled = false;
|
|
8817
|
-
alert('Cancel failed: ' + (err && err.message ? err.message : String(err)));
|
|
8818
|
-
}
|
|
8819
|
-
});
|
|
8820
|
-
actions.appendChild(cancelBtn);
|
|
8821
|
-
|
|
8822
|
-
card.appendChild(actions);
|
|
8823
|
-
list.appendChild(card);
|
|
8824
|
-
}
|
|
8327
|
+
// The open / copy / cancel actions are preserved on each request's Layer-3
|
|
8328
|
+
// record (glance.js secretRecordNode) — behavior untouched, moved one tap down.
|
|
8329
|
+
const spec = glance.secretsGlanceSpec(document, { pending }, { now, onCancel: cancelSecretRequest });
|
|
8330
|
+
glance.renderGlance(document, glanceRoot, spec);
|
|
8825
8331
|
}
|
|
8826
8332
|
|
|
8827
8333
|
async function createTestSecretRequest() {
|