agentgui 1.0.928 → 1.0.930
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/AGENTS.md +10 -0
- package/package.json +1 -1
- package/site/app/js/app.js +69 -2
package/AGENTS.md
CHANGED
|
@@ -104,3 +104,13 @@ Paired changes:
|
|
|
104
104
|
- better-sqlite3 bumped ^12.6.2 → ^12.9.0
|
|
105
105
|
|
|
106
106
|
bun start was already working (bun has native sqlite support via `bun:sqlite`). The node path was broken due to the missing native binding. Only compile from source fixes the node path.
|
|
107
|
+
|
|
108
|
+
## webjsx applyDiff Array Keying (2026-05-04)
|
|
109
|
+
|
|
110
|
+
**Children arrays that mix keyed VElements with raw text/numbers crash in `vendor/webjsx/applyDiff.js`.**
|
|
111
|
+
|
|
112
|
+
`webjsx` requires all siblings in an array prop (children, right, left, breadcrumbs, etc.) to either ALL be VElements with keys or ALL be non-keyed. Passing `Crumb({ right: [h('span', { key: 'a' }, 'A'), h('span', { key: 'b' }, 'B'), '● connected'] })` crashes on render with `TypeError: Cannot read properties of undefined (reading 'key')` at line 247420.js when the render loop tries to read `.key` on the bare string.
|
|
113
|
+
|
|
114
|
+
Fix: wrap any bare strings as keyed VElements: `h('span', { key: 'dot' }, '● connected')`. Rule: if ANY sibling in an array has a key, ALL siblings must be VElements (no raw strings or numbers).
|
|
115
|
+
|
|
116
|
+
Surfaced 2026-05-04 while validating the chat surface in `site/app/`. Fix applied to crumbRight construction in `site/app/js/app.js` view().
|
package/package.json
CHANGED
package/site/app/js/app.js
CHANGED
|
@@ -18,9 +18,16 @@ const state = {
|
|
|
18
18
|
events: [],
|
|
19
19
|
searchQ: '',
|
|
20
20
|
searchHits: null,
|
|
21
|
+
live: { es: null, connected: false, lastEventTs: 0, error: null, eventCount: 0 },
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
let render;
|
|
25
|
+
let renderScheduled = false;
|
|
26
|
+
function scheduleRender() {
|
|
27
|
+
if (renderScheduled) return;
|
|
28
|
+
renderScheduled = true;
|
|
29
|
+
requestAnimationFrame(() => { renderScheduled = false; render(); });
|
|
30
|
+
}
|
|
24
31
|
|
|
25
32
|
function timeNow() {
|
|
26
33
|
const d = new Date();
|
|
@@ -28,14 +35,74 @@ function timeNow() {
|
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
function navTo(tab) {
|
|
38
|
+
const prev = state.tab;
|
|
31
39
|
state.tab = tab;
|
|
32
|
-
if (tab === 'history')
|
|
40
|
+
if (tab === 'history') {
|
|
41
|
+
refreshHistory();
|
|
42
|
+
openLiveStream();
|
|
43
|
+
} else if (prev === 'history') {
|
|
44
|
+
closeLiveStream();
|
|
45
|
+
}
|
|
33
46
|
render();
|
|
34
47
|
}
|
|
35
48
|
|
|
49
|
+
function openLiveStream() {
|
|
50
|
+
if (state.live.es) return;
|
|
51
|
+
state.live.error = null;
|
|
52
|
+
state.live.connected = false;
|
|
53
|
+
try {
|
|
54
|
+
state.live.es = B.streamHistory(state.backend, (kind, data) => {
|
|
55
|
+
state.live.lastEventTs = Date.now();
|
|
56
|
+
state.live.eventCount++;
|
|
57
|
+
if (kind === 'hello') {
|
|
58
|
+
state.live.connected = true;
|
|
59
|
+
} else if (kind === 'event' && data) {
|
|
60
|
+
if (state.selectedSid && data.sid === state.selectedSid) {
|
|
61
|
+
state.events.push(data);
|
|
62
|
+
}
|
|
63
|
+
const sess = state.sessions.find(s => s.sid === data.sid);
|
|
64
|
+
if (sess) {
|
|
65
|
+
sess.events = (sess.events || 0) + 1;
|
|
66
|
+
sess.last = data.ts || Date.now();
|
|
67
|
+
if (data.type === 'tool_use') sess.tools = (sess.tools || 0) + 1;
|
|
68
|
+
if (data.isError) sess.errors = (sess.errors || 0) + 1;
|
|
69
|
+
} else {
|
|
70
|
+
refreshHistory();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} else if (kind === 'conversation') {
|
|
74
|
+
refreshHistory();
|
|
75
|
+
return;
|
|
76
|
+
} else if (kind === 'error' && data) {
|
|
77
|
+
state.live.error = data.error || 'stream error';
|
|
78
|
+
}
|
|
79
|
+
scheduleRender();
|
|
80
|
+
});
|
|
81
|
+
state.live.es.addEventListener('error', () => {
|
|
82
|
+
state.live.connected = false;
|
|
83
|
+
state.live.error = 'connection lost';
|
|
84
|
+
scheduleRender();
|
|
85
|
+
});
|
|
86
|
+
} catch (e) {
|
|
87
|
+
state.live.error = e.message;
|
|
88
|
+
state.live.es = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function closeLiveStream() {
|
|
93
|
+
if (!state.live.es) return;
|
|
94
|
+
try { state.live.es.close(); } catch {}
|
|
95
|
+
state.live.es = null;
|
|
96
|
+
state.live.connected = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
36
99
|
function view() {
|
|
37
100
|
const ok = state.health.status === 'ok';
|
|
38
|
-
const
|
|
101
|
+
const liveActive = state.tab === 'history' && state.live.connected && (Date.now() - state.live.lastEventTs < 30000);
|
|
102
|
+
const dotText = state.tab === 'history'
|
|
103
|
+
? (state.live.error ? '○ stream offline' : (liveActive ? '● live · ' + state.live.eventCount : (state.live.connected ? '● live' : '◌ connecting…')))
|
|
104
|
+
: (ok ? '● connected' : '○ offline');
|
|
105
|
+
const dot = h('span', { key: 'dot' }, dotText);
|
|
39
106
|
|
|
40
107
|
const topbar = Topbar({
|
|
41
108
|
brand: 'agentgui',
|