agentgui 1.0.1117 → 1.0.1119
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/.gm/.embed-generation.code_chunks +1 -0
- package/.gm/.embed-generation.git_commit_vectors +1 -0
- package/.gm/.embed-generation.memories +1 -0
- package/.gm/.embed-generation.rssearch_vectors +1 -0
- package/.gm/prd.yml +10 -0
- package/AGENTS.md +1 -1
- package/lib/acp-http-protocol.js +91 -0
- package/lib/acp-sdk-manager.js +15 -2
- package/lib/claude-runner-agents.js +8 -1
- package/lib/http-handler.js +35 -566
- package/lib/http-routes/mutations.js +199 -0
- package/lib/http-routes/reads.js +212 -0
- package/lib/http-routes/shared.js +213 -0
- package/lib/ws-handlers/agents.js +137 -0
- package/lib/ws-handlers/chat.js +157 -0
- package/lib/ws-handlers/git.js +159 -0
- package/lib/ws-handlers/misc.js +36 -0
- package/lib/ws-handlers/shared.js +47 -0
- package/lib/ws-handlers/terminal-state.js +16 -0
- package/lib/ws-handlers-util.js +8 -526
- package/package.json +1 -1
- package/site/app/js/app.js +8 -103
- package/site/app/js/chat-persistence.js +123 -0
- package/site/app/vendor/anentrypoint-design/247420.css +1614 -488
- package/site/app/vendor/anentrypoint-design/247420.js +363 -77
- package/UX_OPTIMIZATION_SUMMARY.md +0 -238
- package/agentgui-after.png +0 -0
- package/agentgui-current.png +0 -0
- package/agentgui-final.png +0 -0
- package/agentgui-nobrand.png +0 -0
- package/agentgui-now.png +0 -0
- package/agentgui-v2.png +0 -0
- package/agentgui-v4.png +0 -0
- package/bash.exe.stackdump +0 -28
- package/design-reference.png +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const CHAT_KEY = 'agentgui.chat';
|
|
2
|
+
// Cap the persisted footprint: the last 100 messages, with any tool args/result
|
|
3
|
+
// payload over 4KB truncated in the SAVED copy only (in-memory state keeps the
|
|
4
|
+
// full payload). A quota failure is surfaced once instead of silently dropping
|
|
5
|
+
// persistence forever.
|
|
6
|
+
const PERSIST_MSG_CAP = 100;
|
|
7
|
+
const PERSIST_PART_CAP = 4096;
|
|
8
|
+
|
|
9
|
+
// An assistant turn with no content AND no parts is an empty shell (an aborted
|
|
10
|
+
// turn that produced nothing). The view already renders it as nothing; drop it
|
|
11
|
+
// from persisted/in-memory state so a restored chat never carries blank bubbles.
|
|
12
|
+
function isEmptyTurn(m) {
|
|
13
|
+
return m.role === 'assistant' && !m.content && !(Array.isArray(m.parts) && m.parts.length);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function trimPartForStorage(p) {
|
|
17
|
+
if (!p || typeof p !== 'object') return p;
|
|
18
|
+
let out = p;
|
|
19
|
+
for (const k of ['result', 'args']) {
|
|
20
|
+
const v = out[k];
|
|
21
|
+
if (v == null) continue;
|
|
22
|
+
let s;
|
|
23
|
+
if (typeof v === 'string') s = v;
|
|
24
|
+
else { try { s = JSON.stringify(v); } catch { s = String(v); } }
|
|
25
|
+
if (s.length > PERSIST_PART_CAP) {
|
|
26
|
+
if (out === p) out = { ...p };
|
|
27
|
+
out[k] = s.slice(0, PERSIST_PART_CAP);
|
|
28
|
+
out.truncatedForStorage = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function computeTotalCost(state) {
|
|
35
|
+
return (state.chat.messages || []).reduce((s, m) => s + (typeof m.costUsd === 'number' ? m.costUsd : 0), 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Factory: app.js calls this once at module load, passing the live `state`
|
|
39
|
+
// object (mutated by reference, same as every other app.js concern) and the
|
|
40
|
+
// shared helpers (announce/scheduleRender/lsGet/lsSet/lsRemove/debounce)
|
|
41
|
+
// already defined there. Returns the same function set app.js previously
|
|
42
|
+
// defined inline, wired to app.js's own state/render/announce.
|
|
43
|
+
export function createChatPersistence(state, { announce, scheduleRender, lsGet, lsRemove, debounce }) {
|
|
44
|
+
let _persistFailedOnce = false;
|
|
45
|
+
let _lastPersistTs = 0;
|
|
46
|
+
|
|
47
|
+
function persistChat() {
|
|
48
|
+
try {
|
|
49
|
+
const eligible = state.chat.messages.filter(m => !isEmptyTurn(m));
|
|
50
|
+
// On-screen transcript can exceed the cap; a reload only restores the last
|
|
51
|
+
// PERSIST_MSG_CAP turns - surface that once so it's not a silent surprise,
|
|
52
|
+
// same Alert pattern as the sibling quota-failure banner below.
|
|
53
|
+
const wasCapped = eligible.length > PERSIST_MSG_CAP;
|
|
54
|
+
if (wasCapped !== !!state.chat.persistTruncated) {
|
|
55
|
+
state.chat.persistTruncated = wasCapped;
|
|
56
|
+
scheduleRender();
|
|
57
|
+
}
|
|
58
|
+
const msgs = eligible
|
|
59
|
+
.slice(-PERSIST_MSG_CAP)
|
|
60
|
+
.map(m => ({ id: m.id, role: m.role, content: m.content, time: m.time, costUsd: m.costUsd, stopped: m.stopped || undefined, parts: Array.isArray(m.parts) ? m.parts.map(trimPartForStorage) : m.parts }));
|
|
61
|
+
const draft = (state.chat.draft || '').trim() ? state.chat.draft : '';
|
|
62
|
+
// Persist when there is a transcript OR a non-empty draft (a typed-but-not-
|
|
63
|
+
// sent message must survive a reload too).
|
|
64
|
+
if (!msgs.length && !draft) { lsRemove(CHAT_KEY); return; }
|
|
65
|
+
_lastPersistTs = Date.now();
|
|
66
|
+
localStorage.setItem(CHAT_KEY, JSON.stringify({ ts: _lastPersistTs, messages: msgs, draft, resumeSid: state.chat.resumeSid, totalCost: state.chat.totalCost || 0, agent: state.selectedAgent, model: state.selectedModel }));
|
|
67
|
+
} catch {
|
|
68
|
+
if (!_persistFailedOnce) {
|
|
69
|
+
_persistFailedOnce = true;
|
|
70
|
+
state.chat.persistError = true;
|
|
71
|
+
announce('chat too large to save locally - export it from settings');
|
|
72
|
+
scheduleRender();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const debouncedPersistDraft = debounce(persistChat, 500);
|
|
78
|
+
|
|
79
|
+
// Another GUI tab rewrote the shared chat key: never silently diverge - surface
|
|
80
|
+
// a banner offering to reload the newer copy (last-writer-wins with a ts guard).
|
|
81
|
+
window.addEventListener('storage', (e) => {
|
|
82
|
+
if (e.key !== CHAT_KEY || !e.newValue) return;
|
|
83
|
+
try {
|
|
84
|
+
const remote = JSON.parse(e.newValue);
|
|
85
|
+
if (remote && remote.ts && remote.ts > _lastPersistTs) {
|
|
86
|
+
state.chat.externalUpdate = true;
|
|
87
|
+
scheduleRender();
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function restoreChat() {
|
|
93
|
+
try {
|
|
94
|
+
const raw = lsGet(CHAT_KEY);
|
|
95
|
+
if (!raw) return;
|
|
96
|
+
const saved = JSON.parse(raw);
|
|
97
|
+
if (typeof saved.draft === 'string' && saved.draft.trim()) {
|
|
98
|
+
state.chat.draft = saved.draft;
|
|
99
|
+
// A restored draft is set programmatically, so Ctrl+Z has nothing in the
|
|
100
|
+
// browser's native undo history to act on - a one-time note explains why
|
|
101
|
+
// undo appears to do nothing right after a reload.
|
|
102
|
+
announce('draft restored - note: undo history does not carry over a reload');
|
|
103
|
+
}
|
|
104
|
+
if (typeof saved.totalCost === 'number') state.chat.totalCost = saved.totalCost;
|
|
105
|
+
if (Array.isArray(saved.messages) && saved.messages.length) {
|
|
106
|
+
state.chat.messages = saved.messages
|
|
107
|
+
.filter(m => !isEmptyTurn(m))
|
|
108
|
+
.map(m => ({ ...m, parts: Array.isArray(m.parts) ? m.parts : [] }));
|
|
109
|
+
// Prefer the per-message cost sum when present (it self-corrects after
|
|
110
|
+
// edit/retry truncation); the scalar is only the legacy fallback.
|
|
111
|
+
const derived = computeTotalCost(state);
|
|
112
|
+
if (derived) state.chat.totalCost = derived;
|
|
113
|
+
state.chat.resumeSid = saved.resumeSid || null;
|
|
114
|
+
// Restore the agent/model the transcript belongs to, so a restored chat
|
|
115
|
+
// isn't silently shown under whatever agent the picker defaulted to.
|
|
116
|
+
state.chat.restoredAgent = saved.agent || null;
|
|
117
|
+
state.chat.restoredModel = saved.model || null;
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { persistChat, restoreChat, debouncedPersistDraft, isEmptyTurn, computeTotalCost: () => computeTotalCost(state) };
|
|
123
|
+
}
|