agentgui 1.0.959 → 1.0.961
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/.claude/workflows/gui-claude-code-parity.js +88 -0
- package/.claude/workflows/gui-logic-predictability.js +88 -0
- package/AGENTS.md +27 -9
- package/PUNCHLIST-LOGIC.md +405 -0
- package/PUNCHLIST-PARITY.md +36 -0
- package/lib/broadcast.js +1 -0
- package/lib/ws-handlers-util.js +42 -4
- package/package.json +1 -1
- package/server.js +27 -0
- package/site/app/js/app.js +676 -137
- package/site/app/js/backend.js +25 -1
- package/site/app/vendor/anentrypoint-design/247420.css +363 -50
- package/site/app/vendor/anentrypoint-design/247420.js +15 -13
package/lib/ws-handlers-util.js
CHANGED
|
@@ -18,6 +18,20 @@ const SUB_AGENT_MAP = {
|
|
|
18
18
|
export function register(router, deps) {
|
|
19
19
|
const { queries, wsOptimizer, broadcastSync, getProviderConfigs, saveProviderConfig, STARTUP_CWD, discoveredAgents, subscriptionIndex, activeChats } = deps;
|
|
20
20
|
|
|
21
|
+
// Short-lived per-session terminal-event buffer (finding 35): a turn that
|
|
22
|
+
// completes/errors/cancels while a client ws is down would otherwise be a
|
|
23
|
+
// fire-and-forget broadcast the client never sees, hanging it busy forever.
|
|
24
|
+
// A re-subscribing client gets the buffered terminal frame replayed.
|
|
25
|
+
const TERMINAL_TTL_MS = 60000;
|
|
26
|
+
const terminalEvents = new Map(); // sessionId -> terminal event
|
|
27
|
+
function recordTerminal(sessionId, event) {
|
|
28
|
+
terminalEvents.set(sessionId, event);
|
|
29
|
+
const t = setTimeout(() => {
|
|
30
|
+
if (terminalEvents.get(sessionId) === event) terminalEvents.delete(sessionId);
|
|
31
|
+
}, TERMINAL_TTL_MS);
|
|
32
|
+
if (typeof t.unref === 'function') t.unref();
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
// --- agents.list: enumerate registered ACP agents + claude-code ---
|
|
22
36
|
router.handle('agents.list', () => {
|
|
23
37
|
const agents = registry.list().map(a => ({
|
|
@@ -55,7 +69,11 @@ export function register(router, deps) {
|
|
|
55
69
|
subscriptionIndex.get(sid).add(ws);
|
|
56
70
|
ws.subscriptions = ws.subscriptions || new Set();
|
|
57
71
|
ws.subscriptions.add(sid);
|
|
58
|
-
|
|
72
|
+
// Replay a buffered terminal frame (complete/error/cancelled) so a client
|
|
73
|
+
// that re-subscribes after a ws drop learns the turn already ended.
|
|
74
|
+
const term = terminalEvents.get(sid);
|
|
75
|
+
if (term) { try { wsOptimizer.sendToClient(ws, { ...term, replayed: true }); } catch {} }
|
|
76
|
+
return { subscribed: true, sessionId: sid, replayedTerminal: !!term };
|
|
59
77
|
});
|
|
60
78
|
|
|
61
79
|
// --- chat.sendMessage: start a one-shot streaming chat with an agent.
|
|
@@ -88,6 +106,9 @@ export function register(router, deps) {
|
|
|
88
106
|
|
|
89
107
|
const ctrl = { aborted: false, proc: null, agentId, model, cwd, startedAt: Date.now() };
|
|
90
108
|
activeChats.set(sessionId, ctrl);
|
|
109
|
+
// Push-driven hint so clients refresh active-session state without
|
|
110
|
+
// waiting for the 3s chat.active poll (finding 48).
|
|
111
|
+
broadcastSync({ type: 'chat_active_changed', reason: 'started', sessionId, agentId, timestamp: Date.now() });
|
|
91
112
|
|
|
92
113
|
// Fire-and-forget. Errors broadcast as streaming_error.
|
|
93
114
|
(async () => {
|
|
@@ -127,11 +148,20 @@ export function register(router, deps) {
|
|
|
127
148
|
onPid: () => {}, onProcess: (proc) => { ctrl.proc = proc; },
|
|
128
149
|
};
|
|
129
150
|
await runClaudeWithStreaming(content, cwd, agentId, config);
|
|
130
|
-
|
|
151
|
+
if (!ctrl.aborted) {
|
|
152
|
+
const ev = { type: 'streaming_complete', sessionId, claudeSessionId: ctrl.claudeSessionId || null, agentId, eventCount, timestamp: Date.now() };
|
|
153
|
+
recordTerminal(sessionId, ev);
|
|
154
|
+
broadcastSync(ev);
|
|
155
|
+
}
|
|
131
156
|
} catch (e) {
|
|
132
|
-
|
|
157
|
+
if (!ctrl.aborted) {
|
|
158
|
+
const ev = { type: 'streaming_error', sessionId, claudeSessionId: ctrl.claudeSessionId || null, agentId, error: e.message || String(e), recoverable: false, timestamp: Date.now() };
|
|
159
|
+
recordTerminal(sessionId, ev);
|
|
160
|
+
broadcastSync(ev);
|
|
161
|
+
}
|
|
133
162
|
} finally {
|
|
134
163
|
activeChats.delete(sessionId);
|
|
164
|
+
broadcastSync({ type: 'chat_active_changed', reason: 'ended', sessionId, agentId, timestamp: Date.now() });
|
|
135
165
|
}
|
|
136
166
|
})();
|
|
137
167
|
|
|
@@ -142,7 +172,7 @@ export function register(router, deps) {
|
|
|
142
172
|
router.handle('chat.active', () => {
|
|
143
173
|
const sessions = [];
|
|
144
174
|
for (const [sid, c] of activeChats) {
|
|
145
|
-
sessions.push({ sessionId: sid, agentId: c.agentId || null, model: c.model || null, cwd: c.cwd || null, startedAt: c.startedAt || null, pid: c.proc?.pid || null });
|
|
175
|
+
sessions.push({ sessionId: sid, claudeSessionId: c.claudeSessionId || null, agentId: c.agentId || null, model: c.model || null, cwd: c.cwd || null, startedAt: c.startedAt || null, pid: c.proc?.pid || null });
|
|
146
176
|
}
|
|
147
177
|
return { sessions };
|
|
148
178
|
});
|
|
@@ -154,8 +184,16 @@ export function register(router, deps) {
|
|
|
154
184
|
const ctrl = activeChats.get(sid);
|
|
155
185
|
if (!ctrl) return { cancelled: false, reason: 'not-found' };
|
|
156
186
|
ctrl.aborted = true;
|
|
187
|
+
// Broadcast the terminal 'cancelled' frame BEFORE killing the proc so a
|
|
188
|
+
// remote cancellation (other tab, dashboard stop-all) does not read as a
|
|
189
|
+
// normal completion (finding 44). streaming_cancelled is in BROADCAST_TYPES
|
|
190
|
+
// so every connected client sees it; it is also buffered for re-subscribers.
|
|
191
|
+
const ev = { type: 'streaming_cancelled', sessionId: sid, claudeSessionId: ctrl.claudeSessionId || null, agentId: ctrl.agentId || null, cancelled: true, timestamp: Date.now() };
|
|
192
|
+
recordTerminal(sid, ev);
|
|
193
|
+
broadcastSync(ev);
|
|
157
194
|
try { ctrl.proc?.kill?.(); } catch {}
|
|
158
195
|
activeChats.delete(sid);
|
|
196
|
+
broadcastSync({ type: 'chat_active_changed', reason: 'cancelled', sessionId: sid, agentId: ctrl.agentId || null, timestamp: Date.now() });
|
|
159
197
|
return { cancelled: true };
|
|
160
198
|
});
|
|
161
199
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -70,6 +70,33 @@ const staticDir = path.join(rootDir, 'site', 'app');
|
|
|
70
70
|
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
|
|
71
71
|
|
|
72
72
|
const expressApp = createExpressApp({ queries, BASE_URL });
|
|
73
|
+
|
|
74
|
+
// ccsniff's /v1/history/sessions/:sid/events returns ALL events with no
|
|
75
|
+
// paging. Shim (mounted BEFORE the ccsniff router; node_modules untouched):
|
|
76
|
+
// when ?limit= is present, slice the most-recent window server-side and
|
|
77
|
+
// report { total, offset } so the client can page older on demand via
|
|
78
|
+
// ?before= (slice end index, exclusive; defaults to total).
|
|
79
|
+
function historyEventsLimitShim(req, res, next) {
|
|
80
|
+
if (!/\/v1\/history\/sessions\/[^/]+\/events$/.test(req.path)) return next();
|
|
81
|
+
const limit = parseInt(req.query.limit, 10);
|
|
82
|
+
if (!Number.isFinite(limit) || limit <= 0) return next();
|
|
83
|
+
const origJson = res.json.bind(res);
|
|
84
|
+
res.json = (body) => {
|
|
85
|
+
try {
|
|
86
|
+
if (body && Array.isArray(body.events)) {
|
|
87
|
+
const total = body.events.length;
|
|
88
|
+
const before = parseInt(req.query.before, 10);
|
|
89
|
+
const end = (Number.isFinite(before) && before >= 0) ? Math.min(before, total) : total;
|
|
90
|
+
const start = Math.max(0, end - limit);
|
|
91
|
+
body = { ...body, events: body.events.slice(start, end), total, offset: start };
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
return origJson(body);
|
|
95
|
+
};
|
|
96
|
+
next();
|
|
97
|
+
}
|
|
98
|
+
expressApp.use(historyEventsLimitShim);
|
|
99
|
+
|
|
73
100
|
try {
|
|
74
101
|
const historyRouter = await createHistoryRouter({ projectsDir: process.env.CLAUDE_PROJECTS_DIR });
|
|
75
102
|
expressApp.use('/', historyRouter);
|