clay-server 2.26.0-beta.4 → 2.26.0-beta.5
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/lib/project-mate-interaction.js +760 -0
- package/lib/project-memory.js +677 -0
- package/lib/project.js +65 -1371
- package/lib/public/app.js +178 -1
- package/lib/public/css/title-bar.css +186 -0
- package/lib/sdk-bridge.js +19 -0
- package/lib/sdk-worker.js +13 -1
- package/lib/sessions.js +16 -1
- package/package.json +2 -2
package/lib/public/app.js
CHANGED
|
@@ -2599,6 +2599,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2599
2599
|
var contextMiniLabel = $("context-mini-label");
|
|
2600
2600
|
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
2601
2601
|
var headerContextEl = null;
|
|
2602
|
+
var richContextUsage = null;
|
|
2603
|
+
var ctxPopoverEl = null;
|
|
2604
|
+
var ctxPopoverVisible = false;
|
|
2602
2605
|
|
|
2603
2606
|
// Known context window sizes per model (fallback when SDK omits feature flag)
|
|
2604
2607
|
var KNOWN_CONTEXT_WINDOWS = {
|
|
@@ -2646,6 +2649,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2646
2649
|
headerContextEl.className = "header-context";
|
|
2647
2650
|
headerContextEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
2648
2651
|
statusArea.insertBefore(headerContextEl, statusArea.firstChild);
|
|
2652
|
+
headerContextEl.addEventListener("mouseenter", function() {
|
|
2653
|
+
if (richContextUsage) {
|
|
2654
|
+
showCtxPopover();
|
|
2655
|
+
}
|
|
2656
|
+
});
|
|
2657
|
+
headerContextEl.addEventListener("mouseleave", function() {
|
|
2658
|
+
ctxHoverTimer = setTimeout(hideCtxPopover, 120);
|
|
2659
|
+
});
|
|
2649
2660
|
}
|
|
2650
2661
|
if (headerContextEl) {
|
|
2651
2662
|
var hFill = headerContextEl.querySelector(".header-context-fill");
|
|
@@ -2653,7 +2664,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2653
2664
|
hFill.style.width = pct.toFixed(1) + "%";
|
|
2654
2665
|
hFill.className = "header-context-fill" + cls;
|
|
2655
2666
|
hLabel.textContent = pct.toFixed(0) + "%";
|
|
2656
|
-
|
|
2667
|
+
// Use data-tip as fallback when rich data is not yet loaded
|
|
2668
|
+
if (richContextUsage) {
|
|
2669
|
+
headerContextEl.removeAttribute("data-tip");
|
|
2670
|
+
} else {
|
|
2671
|
+
headerContextEl.dataset.tip = "Context window " + pct.toFixed(0) + "% used (" + formatTokens(used) + " / " + formatTokens(win) + " tokens)";
|
|
2672
|
+
}
|
|
2657
2673
|
}
|
|
2658
2674
|
}
|
|
2659
2675
|
contextUsedEl.textContent = formatTokens(used);
|
|
@@ -2718,9 +2734,158 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2718
2734
|
|
|
2719
2735
|
function resetContextData() {
|
|
2720
2736
|
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
2737
|
+
richContextUsage = null;
|
|
2738
|
+
hideCtxPopover();
|
|
2721
2739
|
updateContextPanel();
|
|
2722
2740
|
}
|
|
2723
2741
|
|
|
2742
|
+
// --- Rich context usage popover ---
|
|
2743
|
+
|
|
2744
|
+
var ctxHoverTimer = null;
|
|
2745
|
+
|
|
2746
|
+
function ensureCtxPopover() {
|
|
2747
|
+
if (ctxPopoverEl) return;
|
|
2748
|
+
ctxPopoverEl = document.createElement("div");
|
|
2749
|
+
ctxPopoverEl.className = "context-usage-popover hidden";
|
|
2750
|
+
// Keep popover open when hovering over it
|
|
2751
|
+
ctxPopoverEl.addEventListener("mouseenter", function() {
|
|
2752
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
2753
|
+
});
|
|
2754
|
+
ctxPopoverEl.addEventListener("mouseleave", function() {
|
|
2755
|
+
hideCtxPopover();
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
function showCtxPopover() {
|
|
2760
|
+
if (!headerContextEl || !richContextUsage) return;
|
|
2761
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
2762
|
+
ensureCtxPopover();
|
|
2763
|
+
headerContextEl.appendChild(ctxPopoverEl);
|
|
2764
|
+
renderCtxPopover();
|
|
2765
|
+
ctxPopoverEl.classList.remove("hidden");
|
|
2766
|
+
ctxPopoverVisible = true;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function hideCtxPopover() {
|
|
2770
|
+
if (!ctxPopoverEl) return;
|
|
2771
|
+
ctxPopoverEl.classList.add("hidden");
|
|
2772
|
+
ctxPopoverVisible = false;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// Categories to hide from the legend (noise, not actionable)
|
|
2776
|
+
var CTX_HIDDEN_CATS = { "Free space": 1, "Autocompact buffer": 1 };
|
|
2777
|
+
|
|
2778
|
+
function renderCtxPopover() {
|
|
2779
|
+
if (!ctxPopoverEl || !richContextUsage) return;
|
|
2780
|
+
var d = richContextUsage;
|
|
2781
|
+
var cats = d.categories || [];
|
|
2782
|
+
var total = d.totalTokens || 0;
|
|
2783
|
+
var max = d.maxTokens || 0;
|
|
2784
|
+
var pct = d.percentage != null ? d.percentage : (max > 0 ? (total / max) * 100 : 0);
|
|
2785
|
+
|
|
2786
|
+
var html = "";
|
|
2787
|
+
|
|
2788
|
+
// Header
|
|
2789
|
+
html += '<div class="ctx-pop-header">';
|
|
2790
|
+
html += '<span class="ctx-pop-model">' + escHtml(d.model || contextData.model || "-") + '</span>';
|
|
2791
|
+
html += '<span class="ctx-pop-pct">' + pct.toFixed(0) + '%';
|
|
2792
|
+
html += '<span class="ctx-pop-tokens">' + formatTokens(total) + ' / ' + formatTokens(max) + '</span>';
|
|
2793
|
+
html += '</span>';
|
|
2794
|
+
html += '</div>';
|
|
2795
|
+
|
|
2796
|
+
// Category emoji map
|
|
2797
|
+
var CTX_EMOJI = {
|
|
2798
|
+
"System prompt": "\ud83d\udcdc", "System tools": "\ud83d\udee0\ufe0f",
|
|
2799
|
+
"Memory files": "\ud83d\udcc1", "Skills": "\u26a1", "Messages": "\ud83d\udcac",
|
|
2800
|
+
"MCP tools": "\ud83d\udd0c", "Agents": "\ud83e\udd16", "Deferred tools": "\ud83d\udce6"
|
|
2801
|
+
};
|
|
2802
|
+
|
|
2803
|
+
// Stacked bar
|
|
2804
|
+
if (cats.length > 0 && max > 0) {
|
|
2805
|
+
html += '<div class="ctx-cat-bar">';
|
|
2806
|
+
for (var i = 0; i < cats.length; i++) {
|
|
2807
|
+
var cat = cats[i];
|
|
2808
|
+
if (cat.isDeferred || !cat.tokens || CTX_HIDDEN_CATS[cat.name]) continue;
|
|
2809
|
+
var w = Math.max(0.3, (cat.tokens / max) * 100);
|
|
2810
|
+
html += '<div style="width:' + w.toFixed(2) + '%;background:' + escHtml(cat.color) + '"></div>';
|
|
2811
|
+
}
|
|
2812
|
+
html += '</div>';
|
|
2813
|
+
|
|
2814
|
+
// Legend
|
|
2815
|
+
html += '<div class="ctx-cat-legend">';
|
|
2816
|
+
for (var j = 0; j < cats.length; j++) {
|
|
2817
|
+
var c = cats[j];
|
|
2818
|
+
if (c.isDeferred || !c.tokens || CTX_HIDDEN_CATS[c.name]) continue;
|
|
2819
|
+
var emoji = CTX_EMOJI[c.name] || "\ud83d\udcca";
|
|
2820
|
+
html += '<div class="ctx-cat-item">';
|
|
2821
|
+
html += '<span class="ctx-cat-name">' + em(emoji) + ' ' + escHtml(c.name) + '</span>';
|
|
2822
|
+
html += '<span class="ctx-cat-value">' + formatTokens(c.tokens) + '</span>';
|
|
2823
|
+
html += '</div>';
|
|
2824
|
+
}
|
|
2825
|
+
html += '</div>';
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// Message breakdown
|
|
2829
|
+
var mb = d.messageBreakdown;
|
|
2830
|
+
if (mb) {
|
|
2831
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
2832
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcac") + ' Messages</div>';
|
|
2833
|
+
if (mb.userMessageTokens) {
|
|
2834
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udc64") + ' User</span><span class="ctx-pop-row-value">' + formatTokens(mb.userMessageTokens) + '</span></div>';
|
|
2835
|
+
}
|
|
2836
|
+
if (mb.assistantMessageTokens) {
|
|
2837
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83e\udd16") + ' Assistant</span><span class="ctx-pop-row-value">' + formatTokens(mb.assistantMessageTokens) + '</span></div>';
|
|
2838
|
+
}
|
|
2839
|
+
if (mb.toolCallTokens) {
|
|
2840
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udee0\ufe0f") + ' Tool calls</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolCallTokens) + '</span></div>';
|
|
2841
|
+
}
|
|
2842
|
+
if (mb.toolResultTokens) {
|
|
2843
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udccb") + ' Tool results</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolResultTokens) + '</span></div>';
|
|
2844
|
+
}
|
|
2845
|
+
if (mb.attachmentTokens) {
|
|
2846
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcce") + ' Attachments</span><span class="ctx-pop-row-value">' + formatTokens(mb.attachmentTokens) + '</span></div>';
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// Memory files
|
|
2851
|
+
var mf = d.memoryFiles;
|
|
2852
|
+
if (mf && mf.length > 0) {
|
|
2853
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
2854
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcc1") + ' Memory Files</div>';
|
|
2855
|
+
var baseCount = {};
|
|
2856
|
+
for (var mc = 0; mc < mf.length; mc++) {
|
|
2857
|
+
var bn = mf[mc].path.split("/").pop() || mf[mc].path;
|
|
2858
|
+
baseCount[bn] = (baseCount[bn] || 0) + 1;
|
|
2859
|
+
}
|
|
2860
|
+
for (var mi = 0; mi < mf.length; mi++) {
|
|
2861
|
+
var fpath = mf[mi].path;
|
|
2862
|
+
var fname = fpath.split("/").pop() || fpath;
|
|
2863
|
+
if (baseCount[fname] > 1) {
|
|
2864
|
+
var parts = fpath.split("/");
|
|
2865
|
+
fname = parts.length >= 2 ? parts[parts.length - 2] + "/" + fname : fpath;
|
|
2866
|
+
}
|
|
2867
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcc4") + ' ' + escHtml(fname) + '</span><span class="ctx-pop-row-value">' + formatTokens(mf[mi].tokens) + '</span></div>';
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// Auto-compact note
|
|
2872
|
+
if (d.isAutoCompactEnabled && d.autoCompactThreshold) {
|
|
2873
|
+
html += '<div class="ctx-pop-note">' + em("\u267b\ufe0f") + ' Auto-compact at ' + formatTokens(d.autoCompactThreshold) + '</div>';
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
ctxPopoverEl.innerHTML = html;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
function escHtml(s) {
|
|
2880
|
+
var div = document.createElement("div");
|
|
2881
|
+
div.textContent = s;
|
|
2882
|
+
return div.innerHTML;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
function em(emoji) {
|
|
2886
|
+
return '<span class="ctx-emoji">' + emoji + '</span>';
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2724
2889
|
function resetContext() {
|
|
2725
2890
|
resetContextData();
|
|
2726
2891
|
// Keep view state, just reset data
|
|
@@ -4052,6 +4217,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4052
4217
|
|
|
4053
4218
|
case "history_done":
|
|
4054
4219
|
replayingHistory = false;
|
|
4220
|
+
// Restore cached rich context usage BEFORE updateContextPanel runs
|
|
4221
|
+
if (msg.contextUsage) {
|
|
4222
|
+
richContextUsage = msg.contextUsage;
|
|
4223
|
+
}
|
|
4055
4224
|
// Restore accurate context data from the last result in full history
|
|
4056
4225
|
if (msg.lastUsage || msg.lastModelUsage) {
|
|
4057
4226
|
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
|
|
@@ -4614,6 +4783,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4614
4783
|
accumulateContext(msg.cost, msg.usage, msg.modelUsage, msg.lastStreamInputTokens);
|
|
4615
4784
|
break;
|
|
4616
4785
|
|
|
4786
|
+
case "context_usage":
|
|
4787
|
+
if (msg.data && !replayingHistory) {
|
|
4788
|
+
richContextUsage = msg.data;
|
|
4789
|
+
if (headerContextEl) headerContextEl.removeAttribute("data-tip");
|
|
4790
|
+
if (ctxPopoverVisible) renderCtxPopover();
|
|
4791
|
+
}
|
|
4792
|
+
break;
|
|
4793
|
+
|
|
4617
4794
|
case "done":
|
|
4618
4795
|
setActivity(null);
|
|
4619
4796
|
stopThinking();
|
|
@@ -342,6 +342,7 @@
|
|
|
342
342
|
gap: 6px;
|
|
343
343
|
width: 80px;
|
|
344
344
|
cursor: default;
|
|
345
|
+
position: relative;
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
.header-context-bar {
|
|
@@ -370,6 +371,191 @@
|
|
|
370
371
|
white-space: nowrap;
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
/* --- Context usage popover --- */
|
|
375
|
+
.context-usage-popover {
|
|
376
|
+
position: absolute;
|
|
377
|
+
top: calc(100% + 10px);
|
|
378
|
+
right: -12px;
|
|
379
|
+
width: 320px;
|
|
380
|
+
background: var(--bg-alt);
|
|
381
|
+
border: 1px solid var(--border);
|
|
382
|
+
border-radius: 10px;
|
|
383
|
+
padding: 16px;
|
|
384
|
+
font-size: 12px;
|
|
385
|
+
color: var(--text-secondary);
|
|
386
|
+
box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.4);
|
|
387
|
+
z-index: 200;
|
|
388
|
+
animation: ctx-popover-in 0.12s ease-out;
|
|
389
|
+
line-height: 1.5;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.context-usage-popover.hidden { display: none; }
|
|
393
|
+
|
|
394
|
+
@keyframes ctx-popover-in {
|
|
395
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
396
|
+
to { opacity: 1; transform: translateY(0); }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Header */
|
|
400
|
+
.ctx-pop-header {
|
|
401
|
+
display: flex;
|
|
402
|
+
justify-content: space-between;
|
|
403
|
+
align-items: baseline;
|
|
404
|
+
margin-bottom: 12px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.ctx-pop-model {
|
|
408
|
+
font-size: 12px;
|
|
409
|
+
color: var(--text-muted);
|
|
410
|
+
font-family: "Roboto Mono", monospace;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.ctx-pop-pct {
|
|
414
|
+
font-size: 22px;
|
|
415
|
+
font-weight: 600;
|
|
416
|
+
color: var(--text);
|
|
417
|
+
font-family: "Roboto Mono", monospace;
|
|
418
|
+
letter-spacing: -0.5px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.ctx-pop-tokens {
|
|
422
|
+
font-size: 11px;
|
|
423
|
+
color: var(--text-muted);
|
|
424
|
+
font-family: "Roboto Mono", monospace;
|
|
425
|
+
margin-left: 6px;
|
|
426
|
+
font-weight: 400;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Stacked category bar */
|
|
430
|
+
.ctx-cat-bar {
|
|
431
|
+
display: flex;
|
|
432
|
+
height: 10px;
|
|
433
|
+
border-radius: 5px;
|
|
434
|
+
overflow: hidden;
|
|
435
|
+
background: var(--border);
|
|
436
|
+
margin-bottom: 12px;
|
|
437
|
+
gap: 1px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.ctx-cat-bar > div {
|
|
441
|
+
height: 100%;
|
|
442
|
+
min-width: 3px;
|
|
443
|
+
transition: width 0.3s ease;
|
|
444
|
+
border-radius: 2px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* Category legend */
|
|
448
|
+
.ctx-cat-legend {
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 1px;
|
|
452
|
+
margin-bottom: 4px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.ctx-cat-item {
|
|
456
|
+
display: flex;
|
|
457
|
+
align-items: center;
|
|
458
|
+
justify-content: space-between;
|
|
459
|
+
width: 100%;
|
|
460
|
+
font-size: 12px;
|
|
461
|
+
padding: 3px 0;
|
|
462
|
+
border-radius: 4px;
|
|
463
|
+
transition: background 0.1s ease;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.ctx-cat-item:hover {
|
|
467
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.ctx-emoji {
|
|
471
|
+
display: inline-block;
|
|
472
|
+
filter: grayscale(1);
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
transition: filter 0.15s ease;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.ctx-cat-item:hover .ctx-emoji,
|
|
478
|
+
.ctx-pop-row:hover .ctx-emoji,
|
|
479
|
+
.ctx-pop-note .ctx-emoji {
|
|
480
|
+
filter: grayscale(0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.ctx-cat-name {
|
|
484
|
+
color: var(--text-secondary);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.ctx-cat-value {
|
|
488
|
+
font-family: "Roboto Mono", monospace;
|
|
489
|
+
color: var(--text-muted);
|
|
490
|
+
font-size: 11px;
|
|
491
|
+
font-variant-numeric: tabular-nums;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Sections */
|
|
495
|
+
.ctx-pop-divider {
|
|
496
|
+
border: none;
|
|
497
|
+
height: 1px;
|
|
498
|
+
background: var(--border-subtle);
|
|
499
|
+
margin: 10px 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.ctx-pop-section-label {
|
|
503
|
+
font-size: 11px;
|
|
504
|
+
text-transform: uppercase;
|
|
505
|
+
color: var(--text-muted);
|
|
506
|
+
letter-spacing: 0.8px;
|
|
507
|
+
margin-bottom: 6px;
|
|
508
|
+
font-weight: 500;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.ctx-pop-row {
|
|
512
|
+
display: flex;
|
|
513
|
+
justify-content: space-between;
|
|
514
|
+
align-items: center;
|
|
515
|
+
font-size: 12px;
|
|
516
|
+
padding: 3px 0;
|
|
517
|
+
border-radius: 4px;
|
|
518
|
+
transition: background 0.1s ease;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.ctx-pop-row:hover {
|
|
522
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.ctx-pop-row-label {
|
|
526
|
+
color: var(--text-secondary);
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
overflow: hidden;
|
|
529
|
+
text-overflow: ellipsis;
|
|
530
|
+
margin-right: 12px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.ctx-pop-row-value {
|
|
534
|
+
font-family: "Roboto Mono", monospace;
|
|
535
|
+
color: var(--text-muted);
|
|
536
|
+
font-size: 11px;
|
|
537
|
+
flex-shrink: 0;
|
|
538
|
+
font-variant-numeric: tabular-nums;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.ctx-pop-note {
|
|
542
|
+
font-size: 11px;
|
|
543
|
+
color: var(--text-muted);
|
|
544
|
+
margin-top: 10px;
|
|
545
|
+
text-align: center;
|
|
546
|
+
padding: 4px 8px;
|
|
547
|
+
background: var(--border-subtle);
|
|
548
|
+
border-radius: 4px;
|
|
549
|
+
font-family: "Roboto Mono", monospace;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
@media (max-width: 400px) {
|
|
553
|
+
.context-usage-popover {
|
|
554
|
+
width: 260px;
|
|
555
|
+
right: -8px;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
373
559
|
/* --- Shared pill style for rate limit & fast mode --- */
|
|
374
560
|
.header-rate-limit,
|
|
375
561
|
.header-fast-mode {
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -188,6 +188,10 @@ function createSDKBridge(opts) {
|
|
|
188
188
|
sm.sendAndRecord(session, obj);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
function sendToSession(session, obj) {
|
|
192
|
+
sm.sendToSession(session, obj);
|
|
193
|
+
}
|
|
194
|
+
|
|
191
195
|
function processSDKMessage(session, parsed) {
|
|
192
196
|
// Timing: log key SDK milestones relative to query start
|
|
193
197
|
if (session._queryStartTs) {
|
|
@@ -435,6 +439,15 @@ function createSDKBridge(opts) {
|
|
|
435
439
|
session.isProcessing = false;
|
|
436
440
|
session.rateLimitResetsAt = null; // clear on success
|
|
437
441
|
onProcessingChanged();
|
|
442
|
+
// Fetch rich context usage breakdown (fire-and-forget, non-blocking)
|
|
443
|
+
if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
|
|
444
|
+
session.queryInstance.getContextUsage().then(function(ctxUsage) {
|
|
445
|
+
session.lastContextUsage = ctxUsage;
|
|
446
|
+
sendToSession(session, { type: "context_usage", data: ctxUsage });
|
|
447
|
+
}).catch(function(e) {
|
|
448
|
+
console.error("[sdk-bridge] getContextUsage failed (non-fatal):", e.message || e);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
438
451
|
var lastStreamInput = session.lastStreamInputTokens || null;
|
|
439
452
|
session.lastStreamInputTokens = null;
|
|
440
453
|
sendAndRecord(session, {
|
|
@@ -1196,6 +1209,11 @@ function createSDKBridge(opts) {
|
|
|
1196
1209
|
});
|
|
1197
1210
|
break;
|
|
1198
1211
|
|
|
1212
|
+
case "context_usage":
|
|
1213
|
+
session.lastContextUsage = msg.data;
|
|
1214
|
+
sendToSession(session, { type: "context_usage", data: msg.data });
|
|
1215
|
+
break;
|
|
1216
|
+
|
|
1199
1217
|
case "query_done":
|
|
1200
1218
|
console.log("[sdk-bridge] IPC query_done received, pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1201
1219
|
// Mark that we received a proper IPC completion, so the exit
|
|
@@ -1698,6 +1716,7 @@ function createSDKBridge(opts) {
|
|
|
1698
1716
|
for await (var msg of myQueryInstance) {
|
|
1699
1717
|
processSDKMessage(session, msg);
|
|
1700
1718
|
}
|
|
1719
|
+
// (getContextUsage moved to processSDKMessage result handler -- fire-and-forget)
|
|
1701
1720
|
// Stream ended normally after a task stop — no "result" message was sent,
|
|
1702
1721
|
// so the session is still marked as processing. Send interrupted feedback.
|
|
1703
1722
|
if (session.isProcessing && session.taskStopRequested) {
|
package/lib/sdk-worker.js
CHANGED
|
@@ -351,7 +351,19 @@ async function handleQueryStart(msg) {
|
|
|
351
351
|
}
|
|
352
352
|
sendToDaemon({ type: "sdk_event", event: event });
|
|
353
353
|
}
|
|
354
|
-
perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "),
|
|
354
|
+
perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "), fetching context usage");
|
|
355
|
+
// Fetch context usage breakdown before queryInstance is cleared
|
|
356
|
+
try {
|
|
357
|
+
if (queryInstance && typeof queryInstance.getContextUsage === "function") {
|
|
358
|
+
var ctxUsage = await queryInstance.getContextUsage();
|
|
359
|
+
sendToDaemon({ type: "context_usage", data: ctxUsage });
|
|
360
|
+
perf("context usage sent");
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Non-fatal: SDK may have already shut down
|
|
364
|
+
console.error("[sdk-worker] getContextUsage failed (non-fatal):", e.message);
|
|
365
|
+
}
|
|
366
|
+
perf("sending query_done");
|
|
355
367
|
sendToDaemon({ type: "query_done" });
|
|
356
368
|
} catch (err) {
|
|
357
369
|
var errMsg = err.message || String(err);
|
package/lib/sessions.js
CHANGED
|
@@ -366,7 +366,7 @@ function createSessionManager(opts) {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
_send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens });
|
|
369
|
+
_send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, contextUsage: session.lastContextUsage || null });
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
function switchSession(localId, targetWs, transform) {
|
|
@@ -492,6 +492,20 @@ function createSessionManager(opts) {
|
|
|
492
492
|
sessions.delete(localId);
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
function doSendToSession(session, obj) {
|
|
496
|
+
// Send to active clients without recording to history/disk (ephemeral data)
|
|
497
|
+
if (sendEach) {
|
|
498
|
+
var data = JSON.stringify(obj);
|
|
499
|
+
sendEach(function (ws) {
|
|
500
|
+
if (ws._clayActiveSession === session.localId && ws.readyState === 1) {
|
|
501
|
+
ws.send(data);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
} else if (session.localId === activeSessionId) {
|
|
505
|
+
send(obj);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
495
509
|
function doSendAndRecord(session, obj) {
|
|
496
510
|
session.history.push(obj);
|
|
497
511
|
appendToSessionFile(session, obj);
|
|
@@ -737,6 +751,7 @@ function createSessionManager(opts) {
|
|
|
737
751
|
saveSessionFile: saveSessionFile,
|
|
738
752
|
appendToSessionFile: appendToSessionFile,
|
|
739
753
|
sendAndRecord: doSendAndRecord,
|
|
754
|
+
sendToSession: doSendToSession,
|
|
740
755
|
findTurnBoundary: findTurnBoundary,
|
|
741
756
|
replayHistory: replayHistory,
|
|
742
757
|
searchSessions: searchSessions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "2.26.0-beta.
|
|
3
|
+
"version": "2.26.0-beta.5",
|
|
4
4
|
"description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clay-server": "./bin/cli.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"homepage": "https://github.com/chadbyte/claude-relay#readme",
|
|
37
37
|
"author": "Chad",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
39
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.92",
|
|
40
40
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
41
41
|
"nodemailer": "^6.10.1",
|
|
42
42
|
"qrcode-terminal": "^0.12.0",
|