agentgui 1.0.1116 → 1.0.1118
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 +15 -3342
- package/AGENTS.md +1 -1
- 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 -463
- package/package.json +1 -1
- package/site/app/js/app.js +126 -104
- package/site/app/js/backend.js +19 -0
- 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/.gm/browser-config.json +0 -1
- package/.gm/claim-audit-fired +0 -1
- package/.gm/disciplines/agentgui/mem-1779277005143-0-1352.json +0 -1
- package/.gm/disciplines/agentgui/memories/.flat-export-done +0 -0
- package/.gm/disciplines/agentgui/memories/mem-c07fa3b0836edbe2-1352.md +0 -8
- package/.gm/gm.db +0 -0
- package/.gm/memories/.flat-export-done +0 -1
- package/.gm/memories/mem-0131ff4869d86085-370.md +0 -10
- package/.gm/memories/mem-04049d11cb8c1661-2135.md +0 -8
- package/.gm/memories/mem-069ff10372ed1770-1375.md +0 -8
- package/.gm/memories/mem-08a4a9e050a4deff-3048.md +0 -8
- package/.gm/memories/mem-09597ee0df9c9224-965.md +0 -8
- package/.gm/memories/mem-0993d156f571b619-1059.md +0 -8
- package/.gm/memories/mem-0b681bf2340c8df1-3170.md +0 -8
- package/.gm/memories/mem-0c444abde86202f6-1681.md +0 -8
- package/.gm/memories/mem-0d1408fa048b9923-1159.md +0 -8
- package/.gm/memories/mem-0dad5d82fec829ba-887.md +0 -8
- package/.gm/memories/mem-10a776effb7ce3a6-935.md +0 -8
- package/.gm/memories/mem-10d264b4f8e66fb4-1492.md +0 -8
- package/.gm/memories/mem-118c35ac67541d98-2909.md +0 -8
- package/.gm/memories/mem-119cf70dd19aa0a5-2225.md +0 -8
- package/.gm/memories/mem-15a07cd761b337c4-4320.md +0 -8
- package/.gm/memories/mem-15c73aca528535cb-2183.md +0 -8
- package/.gm/memories/mem-166f52796185e0e7-1487.md +0 -8
- package/.gm/memories/mem-1a148cf9c3126df5-3963.md +0 -8
- package/.gm/memories/mem-1a459001058f618e-567.md +0 -8
- package/.gm/memories/mem-1af3e13f7618e668-2146.md +0 -8
- package/.gm/memories/mem-1fd31d6873e9c164-794.md +0 -8
- package/.gm/memories/mem-21f551be092fd77d-2340.md +0 -8
- package/.gm/memories/mem-231ff5de003f20f0-586.md +0 -8
- package/.gm/memories/mem-2567393d3adb03ab-347.md +0 -8
- package/.gm/memories/mem-27faf41cdd050c80-1261.md +0 -8
- package/.gm/memories/mem-28889e43e8ce64cc-3663.md +0 -8
- package/.gm/memories/mem-2f4fd06515abf439-2562.md +0 -8
- package/.gm/memories/mem-34a749430303c5f7-544.md +0 -8
- package/.gm/memories/mem-3602d11d7bce9446-1057.md +0 -8
- package/.gm/memories/mem-38b7e24dbcba2f3e-639.md +0 -8
- package/.gm/memories/mem-39a7cc2f887a90b1-1079.md +0 -8
- package/.gm/memories/mem-40d6169c8759061d-806.md +0 -8
- package/.gm/memories/mem-4420e6d00e5c8632-2318.md +0 -8
- package/.gm/memories/mem-447e5a54ddc9357a-654.md +0 -8
- package/.gm/memories/mem-455b3beb4afabe14-591.md +0 -8
- package/.gm/memories/mem-4845ee594759151c-560.md +0 -8
- package/.gm/memories/mem-4a1254c60b868b88-374.md +0 -8
- package/.gm/memories/mem-4bafafa47a0e4b6c-3237.md +0 -8
- package/.gm/memories/mem-4c06c45b4d31bc60-1202.md +0 -8
- package/.gm/memories/mem-4dfac801d0378448-3676.md +0 -8
- package/.gm/memories/mem-4ecd3b713e384170-122.md +0 -10
- package/.gm/memories/mem-51d8992a3c6f6c9c-1073.md +0 -8
- package/.gm/memories/mem-539eee7e8c6c10ee-2557.md +0 -8
- package/.gm/memories/mem-559b8b2abc4b1091-1665.md +0 -8
- package/.gm/memories/mem-59dd73a463e54948-871.md +0 -8
- package/.gm/memories/mem-5b99b882ffe1a3c2-2262.md +0 -8
- package/.gm/memories/mem-5feb252a5244149c-1791.md +0 -8
- package/.gm/memories/mem-6224f8d185797f2b-1047.md +0 -8
- package/.gm/memories/mem-6434ee8261a6a6a2-3126.md +0 -8
- package/.gm/memories/mem-66b0947b946f3437-1155.md +0 -8
- package/.gm/memories/mem-6bdf783a1f6be7c2-1062.md +0 -8
- package/.gm/memories/mem-6d49f08bebac54c9-744.md +0 -8
- package/.gm/memories/mem-70d96acb736af2d7-685.md +0 -8
- package/.gm/memories/mem-70de7d5e71fcd439-2680.md +0 -8
- package/.gm/memories/mem-713614f12d7029da-1577.md +0 -8
- package/.gm/memories/mem-720d76c88e64f6ee-1967.md +0 -8
- package/.gm/memories/mem-7535f34f430fc196-1340.md +0 -8
- package/.gm/memories/mem-861a588d2773681a-1311.md +0 -8
- package/.gm/memories/mem-8af7980a92e09959-891.md +0 -8
- package/.gm/memories/mem-8b438ef4c609b7af-401.md +0 -8
- package/.gm/memories/mem-8f59884039787ef3-743.md +0 -8
- package/.gm/memories/mem-92b199c2024ee1aa-2513.md +0 -8
- package/.gm/memories/mem-9329105b05b9c0f6-324.md +0 -10
- package/.gm/memories/mem-94f30cec3c283f97-782.md +0 -10
- package/.gm/memories/mem-956d4813319f65a3-1710.md +0 -8
- package/.gm/memories/mem-96fffbb6615e031b-1263.md +0 -8
- package/.gm/memories/mem-993ab31f7a10aefa-718.md +0 -8
- package/.gm/memories/mem-a3e440db285f655a-995.md +0 -8
- package/.gm/memories/mem-a52933cd6495e6f5-2624.md +0 -8
- package/.gm/memories/mem-a86426ac44138c96-1606.md +0 -8
- package/.gm/memories/mem-a95b1d49d71133ce-1007.md +0 -8
- package/.gm/memories/mem-a9968a186368b022-1150.md +0 -8
- package/.gm/memories/mem-aaeae38d8eb01809-1076.md +0 -8
- package/.gm/memories/mem-ade01d9c02677c55-1557.md +0 -8
- package/.gm/memories/mem-b06b8c85b81663e6-1250.md +0 -8
- package/.gm/memories/mem-b451e5e17e555202-1490.md +0 -8
- package/.gm/memories/mem-b64ad00dcb6f909e-937.md +0 -8
- package/.gm/memories/mem-b7d6018ceca55b6f-2220.md +0 -8
- package/.gm/memories/mem-bdf466854032cac9-1509.md +0 -8
- package/.gm/memories/mem-c0482039e6ab4c50-2047.md +0 -8
- package/.gm/memories/mem-c4aaec5c4e6f80c2-920.md +0 -8
- package/.gm/memories/mem-c66976ddacb4fbe8-1454.md +0 -8
- package/.gm/memories/mem-c7c5bc263850c332-1844.md +0 -8
- package/.gm/memories/mem-cda639547932c796-713.md +0 -8
- package/.gm/memories/mem-cdf04ff6a6a0aa6f-671.md +0 -8
- package/.gm/memories/mem-cf8472c84d9e04c4-1883.md +0 -8
- package/.gm/memories/mem-cfd4d894370c63c3-1288.md +0 -8
- package/.gm/memories/mem-d060480435f92eec-1146.md +0 -8
- package/.gm/memories/mem-d347c921b79e6d44-1099.md +0 -8
- package/.gm/memories/mem-d3c8ea6c861bfbb8-1457.md +0 -8
- package/.gm/memories/mem-d5e928c552cd87f9-214.md +0 -10
- package/.gm/memories/mem-d63bc3b8a0269844-406.md +0 -10
- package/.gm/memories/mem-d9c19257abc2fa5e-1061.md +0 -8
- package/.gm/memories/mem-d9f26358a7cd3bb8-866.md +0 -8
- package/.gm/memories/mem-da1c91acf63d81d3-863.md +0 -8
- package/.gm/memories/mem-dc1ac3b04d22317e-664.md +0 -8
- package/.gm/memories/mem-dceeae20bc56d407-711.md +0 -8
- package/.gm/memories/mem-dd98b21c0896dc04-1557.md +0 -8
- package/.gm/memories/mem-df74674eec7745ac-178.md +0 -10
- package/.gm/memories/mem-dfe3b466fd5d1199-802.md +0 -8
- package/.gm/memories/mem-e0f9286aed2d5a06-1116.md +0 -8
- package/.gm/memories/mem-e1950b064099b7be-1895.md +0 -8
- package/.gm/memories/mem-e1d5431ae2542872-961.md +0 -8
- package/.gm/memories/mem-e490a181de6469fc-487.md +0 -8
- package/.gm/memories/mem-e72c6efbb27326fb-392.md +0 -8
- package/.gm/memories/mem-e943b58115a02b4c-593.md +0 -8
- package/.gm/memories/mem-e9bc5f9ed6edbcf7-1580.md +0 -8
- package/.gm/memories/mem-eae1e383927854fb-2649.md +0 -8
- package/.gm/memories/mem-ebbe6c0694c526f0-1392.md +0 -8
- package/.gm/memories/mem-ebf85becc23410b0-1633.md +0 -8
- package/.gm/memories/mem-ee0596fa17c7102e-362.md +0 -10
- package/.gm/memories/mem-ee4450fbc311b583-1361.md +0 -8
- package/.gm/memories/mem-ee8858162518f144-1252.md +0 -8
- package/.gm/memories/mem-f3297e82df622649-1166.md +0 -8
- package/.gm/memories/mem-f3f317c083d979ee-1210.md +0 -8
- package/.gm/memories/mem-f714a86465fba158-2041.md +0 -8
- package/.gm/memories/mem-f786bc1626d92845-204.md +0 -10
- package/.gm/memories/mem-f9f0dc8895b21e18-1217.md +0 -8
- package/.gm/memories/mem-fb3b2a7395800041-525.md +0 -8
- package/.gm/memories/mem-fc5753f92ee7d654-966.md +0 -8
- package/.gm/memories/mem-fe3faa1652f07239-480.md +0 -8
- package/.gm/memories/mem-ff8669e527e5c427-2788.md +0 -8
- package/.gm/mutables.yml +0 -53
- 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
package/site/app/js/app.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { h, mount, installStyles, components as C } from 'anentrypoint-design';
|
|
2
2
|
import * as B from './backend.js';
|
|
3
|
+
import { createChatPersistence } from './chat-persistence.js';
|
|
3
4
|
|
|
4
5
|
installStyles().catch(() => {});
|
|
5
6
|
|
|
6
|
-
const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, IconButton, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton, flashComposerNote, toast, withBusy, GitStatusPanel, GitDiffView, WorktreeSwitcher, Badge } = C;
|
|
7
|
+
const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, IconButton, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton, flashComposerNote, toast, withBusy, GitStatusPanel, GitDiffView, WorktreeSwitcher, Badge, ModelsConfig, PluginsConfig } = C;
|
|
7
8
|
|
|
8
9
|
// One duration/bytes vocabulary across every surface: prefer the kit's shared
|
|
9
10
|
// formatters (exported alongside the components), fall back to the local
|
|
@@ -52,6 +53,13 @@ const state = {
|
|
|
52
53
|
// One-time welcome banner naming what each tab is for. Shown until
|
|
53
54
|
// dismissed once, ever - a returning user has already learned the tabs.
|
|
54
55
|
showOnboarding: lsGet('agentgui.onboarded') !== '1',
|
|
56
|
+
// Models tab: composed agent/provider availability (models.availability WS
|
|
57
|
+
// handler), fed into the design SDK's ModelsConfig component.
|
|
58
|
+
models: { data: null, loading: false, error: null, selectedProviderId: null },
|
|
59
|
+
// Plugins tab: agentgui has no freddie-style plugin host - the closest real
|
|
60
|
+
// extensibility surface is the discovered agent-CLI registry (agents.list),
|
|
61
|
+
// adapted to PluginsConfig's {name,surfaces,requires,enabled,status} shape.
|
|
62
|
+
plugins: { selected: null },
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
// Two-step arm controls auto-reset after this delay so an accidental first click
|
|
@@ -331,6 +339,24 @@ function navTo(tab, { writeHash: doWriteHash = true, push = true } = {}) {
|
|
|
331
339
|
// filesMain (which runs during render) - a render-time fetch is fragile under
|
|
332
340
|
// a double-render and re-enters render() while building the tree.
|
|
333
341
|
if (tab === 'files' && !state.files.path && !state.files.loading && !state.files.error) loadDir('');
|
|
342
|
+
// Chat's @-mention autocomplete (mentionFiles) reuses state.files.entries -
|
|
343
|
+
// seed it from the root listing on first chat visit so mentions work
|
|
344
|
+
// without requiring a prior Files-tab visit. Uses the same confined
|
|
345
|
+
// B.listDir the Files tab itself calls; failure is silent (mentionFiles
|
|
346
|
+
// just stays empty, same as never having visited Files).
|
|
347
|
+
if (tab === 'chat' && !state.files.entries.length && !state.files.loading && !state.files.error) {
|
|
348
|
+
B.listDir(state.backend, '').then((j) => {
|
|
349
|
+
if (!state.files.entries.length) state.files.entries = j.entries || [];
|
|
350
|
+
if (!state.files.roots.length) state.files.roots = j.roots || [];
|
|
351
|
+
render();
|
|
352
|
+
}).catch(() => {});
|
|
353
|
+
}
|
|
354
|
+
// Models tab: composed agent/provider availability, fetched on first visit
|
|
355
|
+
// (and via the ModelsConfig 'refresh' action thereafter).
|
|
356
|
+
if (tab === 'models' && !state.models.data && !state.models.loading && !state.models.error) loadModelsAvailability();
|
|
357
|
+
// Plugins tab reuses state.agents (agents.list) - same lazy load as chat's
|
|
358
|
+
// agent picker, so visiting Plugins directly (deep-link/reload) still works.
|
|
359
|
+
if (tab === 'plugins' && !state.agents.length && !state.agentsLoading && !state.agentsError) loadAgents();
|
|
334
360
|
// popstate calls navTo with writeHash:false so it never replaceState-clobbers
|
|
335
361
|
// the entry it popped; user-initiated navigation pushes so Back walks tabs.
|
|
336
362
|
if (doWriteHash) writeHash({ push });
|
|
@@ -675,6 +701,10 @@ function view() {
|
|
|
675
701
|
} else if (state.tab === 'settings') {
|
|
676
702
|
// Same word as the rail item - location chrome must not fork vocabulary.
|
|
677
703
|
crumbLeaf = 'settings';
|
|
704
|
+
} else if (state.tab === 'models') {
|
|
705
|
+
crumbLeaf = 'models';
|
|
706
|
+
} else if (state.tab === 'plugins') {
|
|
707
|
+
crumbLeaf = 'plugins';
|
|
678
708
|
}
|
|
679
709
|
const crumb = Crumb({ trail: crumbTrail, leaf: crumbLeaf, right: [dot] });
|
|
680
710
|
|
|
@@ -773,6 +803,8 @@ function workspaceRail() {
|
|
|
773
803
|
{ key: 'files', label: 'Files', icon: 'folder', active: state.tab === 'files', onClick: () => navTo('files') },
|
|
774
804
|
{ key: 'live', label: 'Live', icon: 'activity', active: state.tab === 'live', count: liveCount || null, rail: liveHasError ? 'flame' : undefined, onClick: () => navTo('live') },
|
|
775
805
|
{ key: 'git', label: 'Git', icon: 'branch', active: state.tab === 'git', onClick: () => navTo('git') },
|
|
806
|
+
{ key: 'models', label: 'Models', icon: 'circle-dot', active: state.tab === 'models', onClick: () => navTo('models') },
|
|
807
|
+
{ key: 'plugins', label: 'Plugins', icon: 'link', active: state.tab === 'plugins', onClick: () => navTo('plugins') },
|
|
776
808
|
{ key: 'settings', label: 'Settings', icon: 'settings', active: state.tab === 'settings', onClick: () => navTo('settings') },
|
|
777
809
|
];
|
|
778
810
|
return WorkspaceRail({
|
|
@@ -977,6 +1009,8 @@ function mainContent() {
|
|
|
977
1009
|
if (state.tab === 'files') return filesMain();
|
|
978
1010
|
if (state.tab === 'live') return liveMain();
|
|
979
1011
|
if (state.tab === 'git') return gitMain();
|
|
1012
|
+
if (state.tab === 'models') return modelsMain();
|
|
1013
|
+
if (state.tab === 'plugins') return pluginsMain();
|
|
980
1014
|
return settingsMain();
|
|
981
1015
|
}
|
|
982
1016
|
|
|
@@ -1080,6 +1114,61 @@ function gitMain() {
|
|
|
1080
1114
|
];
|
|
1081
1115
|
}
|
|
1082
1116
|
|
|
1117
|
+
// Models tab: agentgui's real model surface composed by the models.availability
|
|
1118
|
+
// WS handler (agent-CLI registry availability + provider key presence), fed
|
|
1119
|
+
// into the design SDK's ModelsConfig component. Not freddie's probed per-mode
|
|
1120
|
+
// matrix - see lib/ws-handlers-util.js models.availability for the mapping.
|
|
1121
|
+
function modelsMain() {
|
|
1122
|
+
const m = state.models;
|
|
1123
|
+
return [
|
|
1124
|
+
PageHeader({ compact: true, dense: true, title: 'Models', lede: 'Discovered agent CLIs, their models, and provider key presence on this server.' }),
|
|
1125
|
+
ModelsConfig({
|
|
1126
|
+
data: m.data,
|
|
1127
|
+
loading: m.loading,
|
|
1128
|
+
error: m.error,
|
|
1129
|
+
selectedProviderId: m.selectedProviderId,
|
|
1130
|
+
onSelectProvider: (id) => { state.models.selectedProviderId = id; render(); },
|
|
1131
|
+
onRefresh: () => loadModelsAvailability(),
|
|
1132
|
+
}),
|
|
1133
|
+
];
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Plugins tab: agentgui has no freddie-style plugin host (~150 discoverable
|
|
1137
|
+
// plugin.js files) - its actual extensibility surface is the discovered
|
|
1138
|
+
// agent-CLI registry (lib/agent-discovery.js + lib/claude-runner-agents.js
|
|
1139
|
+
// AgentRegistry, the same data agents.list already exposes to the chat
|
|
1140
|
+
// picker). Adapted onto PluginsConfig's shape: name=agent id, surfaces=
|
|
1141
|
+
// protocol (cli|acp), requires=[] (agents have no dependency graph), enabled=
|
|
1142
|
+
// available (the CLI was actually found on this server), status=install hint
|
|
1143
|
+
// when missing. There is no enable/disable action (agentgui does not gate
|
|
1144
|
+
// which agent CLIs are usable) so onToggle re-checks availability instead of
|
|
1145
|
+
// pretending to flip a setting that doesn't exist.
|
|
1146
|
+
function pluginsMain() {
|
|
1147
|
+
const list = (state.agents || []).map((a) => ({
|
|
1148
|
+
name: a.id,
|
|
1149
|
+
version: undefined,
|
|
1150
|
+
surfaces: a.protocol || 'cli',
|
|
1151
|
+
requires: [],
|
|
1152
|
+
source: a.npxPackage ? ('npx ' + a.npxPackage) : undefined,
|
|
1153
|
+
enabled: a.available !== false,
|
|
1154
|
+
status: a.available !== false ? 'loaded' : (a.npxInstallable ? 'installable via npx' : 'not found'),
|
|
1155
|
+
}));
|
|
1156
|
+
return [
|
|
1157
|
+
PageHeader({ compact: true, dense: true, title: 'Plugins', lede: 'Discovered agent CLIs — agentgui\'s real extensibility surface (no freddie-style plugin host).' }),
|
|
1158
|
+
PluginsConfig({
|
|
1159
|
+
plugins: list,
|
|
1160
|
+
selected: state.plugins.selected,
|
|
1161
|
+
loading: !!state.agentsLoading,
|
|
1162
|
+
error: state.agentsError,
|
|
1163
|
+
onSelect: (name) => { state.plugins.selected = name; render(); },
|
|
1164
|
+
// No real enable/disable exists - re-run discovery so a just-installed
|
|
1165
|
+
// CLI's availability reflects immediately instead of a fake toggle.
|
|
1166
|
+
onToggle: () => { withBusy(null, () => loadAgents(), 'checking…'); },
|
|
1167
|
+
onReload: () => loadAgents(),
|
|
1168
|
+
}),
|
|
1169
|
+
];
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1083
1172
|
// --- files (folder browser) ---
|
|
1084
1173
|
async function loadDir(dirPath, { fromHash = false } = {}) {
|
|
1085
1174
|
state.files = state.files || {};
|
|
@@ -2508,6 +2597,16 @@ function chatMain() {
|
|
|
2508
2597
|
messages: state.chat.messages,
|
|
2509
2598
|
busy: state.chat.busy,
|
|
2510
2599
|
draft: state.chat.draft,
|
|
2600
|
+
// Scroll-position overview strip alongside the thread.
|
|
2601
|
+
showMinimap: true,
|
|
2602
|
+
// @-mention file autocomplete: reuse the Files tab's own data source
|
|
2603
|
+
// (state.files.entries, populated by loadDir/listDir) rather than a new
|
|
2604
|
+
// fetch. Scoped to whatever directory Files last listed - if the user
|
|
2605
|
+
// hasn't opened Files yet this is empty and the composer simply shows
|
|
2606
|
+
// no mention suggestions until they do (no fake/synthetic file list).
|
|
2607
|
+
mentionFiles: (state.files.entries || [])
|
|
2608
|
+
.filter((e) => e && e.path)
|
|
2609
|
+
.map((e) => ({ path: e.path, isDir: e.type === 'dir' })),
|
|
2511
2610
|
// Idle never reads 'resuming…' (nothing is in flight - the continuation
|
|
2512
2611
|
// fact lives in the composer context line and banner); a remotely-stopped
|
|
2513
2612
|
// turn reads 'stopped', not a normal finish.
|
|
@@ -2818,109 +2917,13 @@ function cancelChat() {
|
|
|
2818
2917
|
render();
|
|
2819
2918
|
}
|
|
2820
2919
|
|
|
2821
|
-
|
|
2822
|
-
//
|
|
2823
|
-
//
|
|
2824
|
-
//
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
}
|
|
2828
|
-
// Cap the persisted footprint: the last 100 messages, with any tool args/result
|
|
2829
|
-
// payload over 4KB truncated in the SAVED copy only (in-memory state keeps the
|
|
2830
|
-
// full payload). A quota failure is surfaced once instead of silently dropping
|
|
2831
|
-
// persistence forever.
|
|
2832
|
-
const PERSIST_MSG_CAP = 100;
|
|
2833
|
-
const PERSIST_PART_CAP = 4096;
|
|
2834
|
-
let _persistFailedOnce = false;
|
|
2835
|
-
let _lastPersistTs = 0;
|
|
2836
|
-
function trimPartForStorage(p) {
|
|
2837
|
-
if (!p || typeof p !== 'object') return p;
|
|
2838
|
-
let out = p;
|
|
2839
|
-
for (const k of ['result', 'args']) {
|
|
2840
|
-
const v = out[k];
|
|
2841
|
-
if (v == null) continue;
|
|
2842
|
-
let s;
|
|
2843
|
-
if (typeof v === 'string') s = v;
|
|
2844
|
-
else { try { s = JSON.stringify(v); } catch { s = String(v); } }
|
|
2845
|
-
if (s.length > PERSIST_PART_CAP) {
|
|
2846
|
-
if (out === p) out = { ...p };
|
|
2847
|
-
out[k] = s.slice(0, PERSIST_PART_CAP);
|
|
2848
|
-
out.truncatedForStorage = true;
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
return out;
|
|
2852
|
-
}
|
|
2853
|
-
function persistChat() {
|
|
2854
|
-
try {
|
|
2855
|
-
const eligible = state.chat.messages.filter(m => !isEmptyTurn(m));
|
|
2856
|
-
// On-screen transcript can exceed the cap; a reload only restores the last
|
|
2857
|
-
// PERSIST_MSG_CAP turns - surface that once so it's not a silent surprise,
|
|
2858
|
-
// same Alert pattern as the sibling quota-failure banner below.
|
|
2859
|
-
const wasCapped = eligible.length > PERSIST_MSG_CAP;
|
|
2860
|
-
if (wasCapped !== !!state.chat.persistTruncated) {
|
|
2861
|
-
state.chat.persistTruncated = wasCapped;
|
|
2862
|
-
scheduleRender();
|
|
2863
|
-
}
|
|
2864
|
-
const msgs = eligible
|
|
2865
|
-
.slice(-PERSIST_MSG_CAP)
|
|
2866
|
-
.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 }));
|
|
2867
|
-
const draft = (state.chat.draft || '').trim() ? state.chat.draft : '';
|
|
2868
|
-
// Persist when there is a transcript OR a non-empty draft (a typed-but-not-
|
|
2869
|
-
// sent message must survive a reload too).
|
|
2870
|
-
if (!msgs.length && !draft) { lsRemove(CHAT_KEY); return; }
|
|
2871
|
-
_lastPersistTs = Date.now();
|
|
2872
|
-
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 }));
|
|
2873
|
-
} catch {
|
|
2874
|
-
if (!_persistFailedOnce) {
|
|
2875
|
-
_persistFailedOnce = true;
|
|
2876
|
-
state.chat.persistError = true;
|
|
2877
|
-
announce('chat too large to save locally - export it from settings');
|
|
2878
|
-
scheduleRender();
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
const debouncedPersistDraft = debounce(persistChat, 500);
|
|
2883
|
-
// Another GUI tab rewrote the shared chat key: never silently diverge - surface
|
|
2884
|
-
// a banner offering to reload the newer copy (last-writer-wins with a ts guard).
|
|
2885
|
-
window.addEventListener('storage', (e) => {
|
|
2886
|
-
if (e.key !== CHAT_KEY || !e.newValue) return;
|
|
2887
|
-
try {
|
|
2888
|
-
const remote = JSON.parse(e.newValue);
|
|
2889
|
-
if (remote && remote.ts && remote.ts > _lastPersistTs) {
|
|
2890
|
-
state.chat.externalUpdate = true;
|
|
2891
|
-
scheduleRender();
|
|
2892
|
-
}
|
|
2893
|
-
} catch {}
|
|
2894
|
-
});
|
|
2895
|
-
function restoreChat() {
|
|
2896
|
-
try {
|
|
2897
|
-
const raw = lsGet(CHAT_KEY);
|
|
2898
|
-
if (!raw) return;
|
|
2899
|
-
const saved = JSON.parse(raw);
|
|
2900
|
-
if (typeof saved.draft === 'string' && saved.draft.trim()) {
|
|
2901
|
-
state.chat.draft = saved.draft;
|
|
2902
|
-
// A restored draft is set programmatically, so Ctrl+Z has nothing in the
|
|
2903
|
-
// browser's native undo history to act on - a one-time note explains why
|
|
2904
|
-
// undo appears to do nothing right after a reload.
|
|
2905
|
-
announce('draft restored - note: undo history does not carry over a reload');
|
|
2906
|
-
}
|
|
2907
|
-
if (typeof saved.totalCost === 'number') state.chat.totalCost = saved.totalCost;
|
|
2908
|
-
if (Array.isArray(saved.messages) && saved.messages.length) {
|
|
2909
|
-
state.chat.messages = saved.messages
|
|
2910
|
-
.filter(m => !isEmptyTurn(m))
|
|
2911
|
-
.map(m => ({ ...m, parts: Array.isArray(m.parts) ? m.parts : [] }));
|
|
2912
|
-
// Prefer the per-message cost sum when present (it self-corrects after
|
|
2913
|
-
// edit/retry truncation); the scalar is only the legacy fallback.
|
|
2914
|
-
const derived = computeTotalCost();
|
|
2915
|
-
if (derived) state.chat.totalCost = derived;
|
|
2916
|
-
state.chat.resumeSid = saved.resumeSid || null;
|
|
2917
|
-
// Restore the agent/model the transcript belongs to, so a restored chat
|
|
2918
|
-
// isn't silently shown under whatever agent the picker defaulted to.
|
|
2919
|
-
state.chat.restoredAgent = saved.agent || null;
|
|
2920
|
-
state.chat.restoredModel = saved.model || null;
|
|
2921
|
-
}
|
|
2922
|
-
} catch {}
|
|
2923
|
-
}
|
|
2920
|
+
// Chat transcript localStorage persistence - extracted to chat-persistence.js
|
|
2921
|
+
// (vertical slice per AGENTS.md SOLID/Clean-Architecture preferences). The
|
|
2922
|
+
// factory closes over the SAME live `state` object every other app.js concern
|
|
2923
|
+
// mutates by reference, so every call site below (persistChat/restoreChat/
|
|
2924
|
+
// debouncedPersistDraft/isEmptyTurn) keeps working unchanged.
|
|
2925
|
+
const { persistChat, restoreChat, debouncedPersistDraft, isEmptyTurn } =
|
|
2926
|
+
createChatPersistence(state, { announce, scheduleRender, lsGet, lsRemove, debounce });
|
|
2924
2927
|
|
|
2925
2928
|
// Flatten a message's content (string content or md/text/tool parts) to plain
|
|
2926
2929
|
// text for the copy action.
|
|
@@ -4351,6 +4354,25 @@ async function loadAgents() {
|
|
|
4351
4354
|
}
|
|
4352
4355
|
}
|
|
4353
4356
|
|
|
4357
|
+
// Models tab data load: models.availability WS handler composes agent
|
|
4358
|
+
// registry + provider key presence into ModelsConfig's expected shape. See
|
|
4359
|
+
// lib/ws-handlers-util.js for exactly what each field means in agentgui (no
|
|
4360
|
+
// freddie-style per-mode probe matrix - one real 'cli' mode per model).
|
|
4361
|
+
async function loadModelsAvailability() {
|
|
4362
|
+
state.models.loading = true;
|
|
4363
|
+
state.models.error = null;
|
|
4364
|
+
render();
|
|
4365
|
+
try {
|
|
4366
|
+
state.models.data = await B.getModelsAvailability(state.backend);
|
|
4367
|
+
state.models.loading = false;
|
|
4368
|
+
render();
|
|
4369
|
+
} catch (e) {
|
|
4370
|
+
state.models.loading = false;
|
|
4371
|
+
state.models.error = errText(e) || 'failed to load model availability';
|
|
4372
|
+
render();
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4354
4376
|
// Boot-time automatic retries with backoff when the first agents fetch fails
|
|
4355
4377
|
// (the server may still be warming up).
|
|
4356
4378
|
async function retryLoadAgents() {
|
package/site/app/js/backend.js
CHANGED
|
@@ -435,6 +435,25 @@ export async function listAgentModels(base, agentId) {
|
|
|
435
435
|
} catch { return []; }
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
// Composed model-availability view for the Models tab (ModelsConfig
|
|
439
|
+
// component): per-agent CLI availability + models, in the shape ModelsConfig
|
|
440
|
+
// expects ({timestamp, providers, sampler, summary}). See lib/ws-handlers-
|
|
441
|
+
// util.js models.availability for what each field really means in agentgui
|
|
442
|
+
// (no probed per-mode matrix like freddie - one real 'cli' mode per model).
|
|
443
|
+
export async function getModelsAvailability(base) {
|
|
444
|
+
return wsCall(base, 'models.availability', {});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Provider API-key configs (masked) for the Models tab's provider auth info -
|
|
448
|
+
// backs the existing Settings 'keys' surface, reused here as-is.
|
|
449
|
+
export async function getAuthConfigs(base) {
|
|
450
|
+
return wsCall(base, 'auth.configs', {});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function saveAuthConfig(base, providerId, apiKey, defaultModel) {
|
|
454
|
+
return wsCall(base, 'auth.save', { providerId, apiKey, defaultModel });
|
|
455
|
+
}
|
|
456
|
+
|
|
438
457
|
// ---------- Git / worktrees (WS) ----------
|
|
439
458
|
|
|
440
459
|
export async function gitStatus(base, { cwd } = {}) {
|
|
@@ -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
|
+
}
|