agentgui 1.0.975 → 1.0.976
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/package.json
CHANGED
package/site/app/js/app.js
CHANGED
|
@@ -131,7 +131,8 @@ function scheduleStreamRender() {
|
|
|
131
131
|
requestAnimationFrame(() => {
|
|
132
132
|
streamRenderScheduled = false;
|
|
133
133
|
render();
|
|
134
|
-
|
|
134
|
+
// Kit AgentChat's IntersectionObserver sentinel handles streaming auto-scroll;
|
|
135
|
+
// calling scrollChatToBottom here forces a synchronous scrollHeight layout reflow.
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
|
|
@@ -262,6 +263,7 @@ function navTo(tab, { writeHash: doWriteHash = true, push = true } = {}) {
|
|
|
262
263
|
// Leaving chat clears any pending new-chat confirmation so it doesn't linger
|
|
263
264
|
// as a stale banner when the user returns.
|
|
264
265
|
if (prev === 'chat' && tab !== 'chat') state.confirmingNewChat = false;
|
|
266
|
+
if (prev !== tab) state.confirmingClearData = false;
|
|
265
267
|
state.tab = tab;
|
|
266
268
|
// Live history SSE feeds both the History tab (event log) and the Live
|
|
267
269
|
// dashboard (per-session activity tally + stream-health signal); open it on
|
|
@@ -367,7 +369,11 @@ let _liveTick = null;
|
|
|
367
369
|
function startLiveTick() {
|
|
368
370
|
if (_liveTick) return;
|
|
369
371
|
_liveTick = setInterval(() => {
|
|
370
|
-
if (state.tab === 'live' && Array.isArray(state.active) && state.active.length)
|
|
372
|
+
if (state.tab === 'live' && Array.isArray(state.active) && state.active.length) {
|
|
373
|
+
scheduleRender();
|
|
374
|
+
} else if (state.tab !== 'live' && !(Array.isArray(state.active) && state.active.length)) {
|
|
375
|
+
clearInterval(_liveTick); _liveTick = null;
|
|
376
|
+
}
|
|
371
377
|
}, 1000);
|
|
372
378
|
}
|
|
373
379
|
async function stopActiveChat(sid) {
|
|
@@ -512,9 +518,9 @@ function view() {
|
|
|
512
518
|
const liveActive = state.tab === 'history' && state.live.connected && (Date.now() - state.live.lastEventTs < 30000);
|
|
513
519
|
const dotLabel = state.tab === 'history'
|
|
514
520
|
? (state.live.error
|
|
515
|
-
? state.live.error + (state.live.reconnects ? ' · ' + state.live.reconnects + ' reconnects' : '')
|
|
516
|
-
: (liveActive ? 'live · ' + state.live.eventCount : (state.live.connected ? 'live' : 'connecting…')))
|
|
517
|
-
: (ok ? (state.health.ws === 'reconnecting' ? 'connecting' : 'connected') : 'offline');
|
|
521
|
+
? 'stream: ' + state.live.error + (state.live.reconnects ? ' · ' + state.live.reconnects + ' reconnects' : '')
|
|
522
|
+
: (liveActive ? 'stream: live · ' + state.live.eventCount : (state.live.connected ? 'stream: live' : 'stream: connecting…')))
|
|
523
|
+
: (ok ? (state.health.ws === 'reconnecting' ? 'connecting…' : 'connected') : 'offline');
|
|
518
524
|
const dotLive = state.tab === 'history' ? (liveActive || state.live.connected) : ok;
|
|
519
525
|
// The status dot is drawn entirely by CSS (.status-dot::before) - a small
|
|
520
526
|
// colored disc, real product design, not a text glyph. State drives its colour
|
|
@@ -1050,7 +1056,7 @@ function fileDialog() {
|
|
|
1050
1056
|
return PromptDialog({
|
|
1051
1057
|
title: 'Rename ' + d.file.name, value: d.file.name, placeholder: 'new name',
|
|
1052
1058
|
error: d.error || null, busy: !!d.busy,
|
|
1053
|
-
confirmLabel: d.busy ? 'renaming
|
|
1059
|
+
confirmLabel: d.busy ? 'renaming…' : 'rename', cancelLabel: 'cancel',
|
|
1054
1060
|
onCancel: closeFileDialog,
|
|
1055
1061
|
onConfirm: (v) => {
|
|
1056
1062
|
// Every confirm press produces visible feedback - never a silent no-op.
|
|
@@ -1067,7 +1073,7 @@ function fileDialog() {
|
|
|
1067
1073
|
? 'Delete this folder and everything inside it? This cannot be undone.'
|
|
1068
1074
|
: 'Delete this file? This cannot be undone.',
|
|
1069
1075
|
error: d.error || null, busy: !!d.busy,
|
|
1070
|
-
confirmLabel: d.busy ? 'deleting
|
|
1076
|
+
confirmLabel: d.busy ? 'deleting…' : 'delete', cancelLabel: 'cancel', destructive: true,
|
|
1071
1077
|
onCancel: closeFileDialog,
|
|
1072
1078
|
onConfirm: () => runFileMutation(() => B.deleteEntry(state.backend, d.file.path, isDir), 'deleted ' + d.file.name),
|
|
1073
1079
|
});
|
|
@@ -1081,7 +1087,7 @@ function fileDialog() {
|
|
|
1081
1087
|
? 'Folders are deleted with everything inside them. '
|
|
1082
1088
|
: '') + 'This cannot be undone.',
|
|
1083
1089
|
error: d.error || null, busy: !!d.busy,
|
|
1084
|
-
confirmLabel: d.busy ? 'deleting
|
|
1090
|
+
confirmLabel: d.busy ? 'deleting…' : 'delete ' + n, cancelLabel: 'cancel', destructive: true,
|
|
1085
1091
|
onCancel: closeFileDialog,
|
|
1086
1092
|
onConfirm: runBulkDelete,
|
|
1087
1093
|
});
|
|
@@ -1092,7 +1098,7 @@ function fileDialog() {
|
|
|
1092
1098
|
title: 'Move ' + n + ' selected ' + (n === 1 ? 'entry' : 'entries'),
|
|
1093
1099
|
value: state.files.path || '', placeholder: 'destination folder path',
|
|
1094
1100
|
error: d.error || null, busy: !!d.busy,
|
|
1095
|
-
confirmLabel: d.busy ? 'moving
|
|
1101
|
+
confirmLabel: d.busy ? 'moving…' : 'move ' + n, cancelLabel: 'cancel',
|
|
1096
1102
|
onCancel: closeFileDialog,
|
|
1097
1103
|
onConfirm: (v) => {
|
|
1098
1104
|
if (!v) { d.error = 'enter a destination folder'; render(); return; }
|
|
@@ -1105,7 +1111,7 @@ function fileDialog() {
|
|
|
1105
1111
|
return PromptDialog({
|
|
1106
1112
|
title: 'New folder', value: '', placeholder: 'folder name',
|
|
1107
1113
|
error: d.error || null, busy: !!d.busy,
|
|
1108
|
-
confirmLabel: d.busy ? 'creating
|
|
1114
|
+
confirmLabel: d.busy ? 'creating…' : 'create', cancelLabel: 'cancel',
|
|
1109
1115
|
onCancel: closeFileDialog,
|
|
1110
1116
|
onConfirm: (v) => {
|
|
1111
1117
|
if (!v) { d.error = 'enter a folder name'; render(); return; }
|
|
@@ -2495,7 +2501,7 @@ function historyMain() {
|
|
|
2495
2501
|
const meta = SessionMeta({
|
|
2496
2502
|
items: [
|
|
2497
2503
|
sess && sess.cwd ? { label: 'cwd', value: sess.cwd, title: sess.cwd } : null,
|
|
2498
|
-
sessionDuration() ? { label: 'duration', value:
|
|
2504
|
+
(() => { const dur = sessionDuration(); return dur ? { label: 'duration', value: dur } : null; })(),
|
|
2499
2505
|
{ label: 'session id', value: state.selectedSid.slice(0, 8) + '…', title: state.selectedSid, onCopy: () => copyText(state.selectedSid, 'session id copied') },
|
|
2500
2506
|
// Spelled counter vocabulary in the detail strip (events/turns/tools/
|
|
2501
2507
|
// errors); the abbreviated 'ev/tools/err' triple stays compact-row-only.
|
|
@@ -2571,7 +2577,7 @@ function historyMain() {
|
|
|
2571
2577
|
}
|
|
2572
2578
|
return {
|
|
2573
2579
|
key,
|
|
2574
|
-
code: String(
|
|
2580
|
+
code: String(total - shown.length + i + 1).padStart(4, '0'),
|
|
2575
2581
|
rail,
|
|
2576
2582
|
expanded, // disclosure state -> kit Row sets aria-expanded
|
|
2577
2583
|
highlight: q || undefined,
|
|
@@ -2811,7 +2817,9 @@ function historySide() {
|
|
|
2811
2817
|
function isValidUrl(s) {
|
|
2812
2818
|
if (!s) return true; // blank = same-origin is valid
|
|
2813
2819
|
try {
|
|
2814
|
-
|
|
2820
|
+
// Only add http:// prefix for schemeless inputs (no ://); inputs with an
|
|
2821
|
+
// explicit non-http scheme (ftp://, ws://) must fail the protocol check.
|
|
2822
|
+
const u = new URL(s.includes('://') ? s : 'http://' + s);
|
|
2815
2823
|
return u.protocol === 'http:' || u.protocol === 'https:'; // reject ftp:/ws:/etc
|
|
2816
2824
|
} catch { return false; }
|
|
2817
2825
|
}
|
|
@@ -2821,12 +2829,15 @@ function isValidUrl(s) {
|
|
|
2821
2829
|
// becomes the same string we validated. Blank stays blank (same-origin).
|
|
2822
2830
|
function normalizeBackend(s) {
|
|
2823
2831
|
if (!s) return '';
|
|
2824
|
-
try { return new URL(s.
|
|
2832
|
+
try { return new URL(s.includes('://') ? s : 'http://' + s).origin; }
|
|
2825
2833
|
catch { return s; }
|
|
2826
2834
|
}
|
|
2827
2835
|
|
|
2828
2836
|
async function saveBackend() {
|
|
2829
|
-
if (!isValidUrl(state.backendDraft)
|
|
2837
|
+
if (!isValidUrl(state.backendDraft)) return;
|
|
2838
|
+
// Re-submitting the current URL (e.g. after a failed health check) re-runs
|
|
2839
|
+
// the health probe and shows connecting… so the user gets visible feedback.
|
|
2840
|
+
if (state.backendDraft === state.backend) { state.backendStatus = 'connecting'; render(); await recheckHealth(); return; }
|
|
2830
2841
|
// Switching backend orphans the local chat transcript (it belongs to the old
|
|
2831
2842
|
// server's sessions). Confirm once if there's a transcript to lose - and the
|
|
2832
2843
|
// confirmation binds to the EXACT value confirmed: editing the URL after
|
|
@@ -2856,7 +2867,7 @@ function healthSummary() {
|
|
|
2856
2867
|
// (connected/offline/connecting) so the same state reads the same word
|
|
2857
2868
|
// everywhere, instead of the raw health.status ('ok'/'down').
|
|
2858
2869
|
const connWord = hh.status === 'ok' ? 'connected'
|
|
2859
|
-
: hh.status === 'unknown' ? 'connecting'
|
|
2870
|
+
: hh.status === 'unknown' ? 'connecting…'
|
|
2860
2871
|
: 'offline';
|
|
2861
2872
|
bits.push([connWord, 'Backend connection status']);
|
|
2862
2873
|
if (hh.version) bits.push(['v' + hh.version, 'Server version']);
|
|
@@ -2918,7 +2929,7 @@ function settingsMain() {
|
|
|
2918
2929
|
key: 'savebtn',
|
|
2919
2930
|
type: 'submit',
|
|
2920
2931
|
primary: true,
|
|
2921
|
-
disabled: !isValid || state.
|
|
2932
|
+
disabled: !isValid || state.backendStatus === 'connecting',
|
|
2922
2933
|
onClick: (e) => { e.preventDefault(); saveBackend(); },
|
|
2923
2934
|
children: state.backendStatus === 'connecting' ? 'connecting…' : 'save + reconnect',
|
|
2924
2935
|
title: isValid ? 'Save backend URL and reconnect' : 'Fix URL format first',
|
|
@@ -3066,7 +3077,7 @@ function agentsPanel() {
|
|
|
3066
3077
|
// Rail tone keeps its GUI-wide meaning: green=ok/selected,
|
|
3067
3078
|
// flame=error/unavailable. Selection is shown via `active`, not by
|
|
3068
3079
|
// borrowing purple (purple is reserved for subagents).
|
|
3069
|
-
rail: (a.id === state.selectedAgent
|
|
3080
|
+
rail: !avail ? 'flame' : (a.id === state.selectedAgent ? 'green' : undefined),
|
|
3070
3081
|
active: a.id === state.selectedAgent,
|
|
3071
3082
|
// Non-installable agents are genuinely inert: mark them disabled (no
|
|
3072
3083
|
// click, no button role) instead of looking clickable but doing nothing.
|
package/site/app/js/backend.js
CHANGED
|
@@ -377,6 +377,7 @@ export async function* streamChat(base, { model, messages, signal, agentId, resu
|
|
|
377
377
|
} else if (ev.type === 'streaming_progress') {
|
|
378
378
|
const block = ev.block;
|
|
379
379
|
if (block?.type === 'text' && block.text) push({ type: 'text', text: block.text });
|
|
380
|
+
else if (block?.type === 'thinking' && block.thinking) push({ type: 'thinking', text: block.thinking });
|
|
380
381
|
else if (block?.type === 'tool_use') push({ type: 'tool', block });
|
|
381
382
|
else if (block?.type === 'tool_result') push({ type: 'tool_result', block });
|
|
382
383
|
else if (block?.type === 'result') push({ type: 'result', block });
|
|
@@ -498,12 +498,12 @@
|
|
|
498
498
|
.ds-247420 ::selection { background: color-mix(in oklab, var(--accent) 24%, var(--bg-2)); color: var(--fg); }
|
|
499
499
|
|
|
500
500
|
/* Every root has a CQ container so fluid type can resolve to a meaningful inline-size. */
|
|
501
|
-
.ds-247420 .app, .ds-247420
|
|
501
|
+
.ds-247420 .app, .ds-247420 .ds-stage {
|
|
502
502
|
container-type: inline-size;
|
|
503
503
|
overflow-x: clip;
|
|
504
504
|
min-width: 0;
|
|
505
505
|
}
|
|
506
|
-
.ds-247420 .app
|
|
506
|
+
.ds-247420 .app * { min-width: 0; }
|
|
507
507
|
|
|
508
508
|
/* ============================================================
|
|
509
509
|
Typography
|