agentgui 1.0.1110 → 1.0.1112
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/gm.db +0 -0
- package/.gm/prd.yml +0 -76
- package/AGENTS.md +1 -5
- package/lib/ws-handlers-util.js +63 -0
- package/package.json +4 -2
- package/scripts/capture-screenshots.mjs +206 -0
- package/site/app/js/app.js +125 -35
- package/site/app/js/backend.js +19 -0
- package/site/app/vendor/anentrypoint-design/247420.css +454 -1
- package/site/app/vendor/anentrypoint-design/247420.js +25 -25
package/site/app/js/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as B from './backend.js';
|
|
|
3
3
|
|
|
4
4
|
installStyles().catch(() => {});
|
|
5
5
|
|
|
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 } = C;
|
|
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, ModelsConfig, PluginsConfig } = C;
|
|
7
7
|
|
|
8
8
|
// One duration/bytes vocabulary across every surface: prefer the kit's shared
|
|
9
9
|
// formatters (exported alongside the components), fall back to the local
|
|
@@ -44,9 +44,13 @@ const state = {
|
|
|
44
44
|
eventsLimit: 300, // how many of the most-recent events to render; grows via "load older"
|
|
45
45
|
files: { path: '', segments: [], entries: [], roots: [], loading: false, error: null, preview: null, sort: 'name', sortDir: 'asc', filter: '' },
|
|
46
46
|
git: { loading: false, error: null, diff: '', commits: [], worktrees: [], files: [], file: '', worktreeBusy: false },
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
47
|
+
// Models tab: composed agent/provider availability (models.availability WS
|
|
48
|
+
// handler), fed into the design SDK's ModelsConfig component.
|
|
49
|
+
models: { data: null, loading: false, error: null, selectedProviderId: null },
|
|
50
|
+
// Plugins tab: agentgui has no freddie-style plugin host - the closest real
|
|
51
|
+
// extensibility surface is the discovered agent-CLI registry (agents.list),
|
|
52
|
+
// adapted to PluginsConfig's {name,surfaces,requires,enabled,status} shape.
|
|
53
|
+
plugins: { selected: null },
|
|
50
54
|
};
|
|
51
55
|
|
|
52
56
|
// Two-step arm controls auto-reset after this delay so an accidental first click
|
|
@@ -173,12 +177,6 @@ function lsGet(k) { try { return localStorage.getItem(k); } catch { return null;
|
|
|
173
177
|
function lsSet(k, v) { try { localStorage.setItem(k, v); } catch {} }
|
|
174
178
|
function lsRemove(k) { try { localStorage.removeItem(k); } catch {} }
|
|
175
179
|
|
|
176
|
-
function dismissOnboarding() {
|
|
177
|
-
state.showOnboarding = false;
|
|
178
|
-
lsSet('agentgui.onboarded', '1');
|
|
179
|
-
render();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
180
|
// A single visually-hidden aria-live region for transient announcements (tab
|
|
183
181
|
// changes, etc.) so screen-reader users hear context that's otherwise conveyed
|
|
184
182
|
// only by focus movement or color.
|
|
@@ -326,6 +324,24 @@ function navTo(tab, { writeHash: doWriteHash = true, push = true } = {}) {
|
|
|
326
324
|
// filesMain (which runs during render) - a render-time fetch is fragile under
|
|
327
325
|
// a double-render and re-enters render() while building the tree.
|
|
328
326
|
if (tab === 'files' && !state.files.path && !state.files.loading && !state.files.error) loadDir('');
|
|
327
|
+
// Chat's @-mention autocomplete (mentionFiles) reuses state.files.entries -
|
|
328
|
+
// seed it from the root listing on first chat visit so mentions work
|
|
329
|
+
// without requiring a prior Files-tab visit. Uses the same confined
|
|
330
|
+
// B.listDir the Files tab itself calls; failure is silent (mentionFiles
|
|
331
|
+
// just stays empty, same as never having visited Files).
|
|
332
|
+
if (tab === 'chat' && !state.files.entries.length && !state.files.loading && !state.files.error) {
|
|
333
|
+
B.listDir(state.backend, '').then((j) => {
|
|
334
|
+
if (!state.files.entries.length) state.files.entries = j.entries || [];
|
|
335
|
+
if (!state.files.roots.length) state.files.roots = j.roots || [];
|
|
336
|
+
render();
|
|
337
|
+
}).catch(() => {});
|
|
338
|
+
}
|
|
339
|
+
// Models tab: composed agent/provider availability, fetched on first visit
|
|
340
|
+
// (and via the ModelsConfig 'refresh' action thereafter).
|
|
341
|
+
if (tab === 'models' && !state.models.data && !state.models.loading && !state.models.error) loadModelsAvailability();
|
|
342
|
+
// Plugins tab reuses state.agents (agents.list) - same lazy load as chat's
|
|
343
|
+
// agent picker, so visiting Plugins directly (deep-link/reload) still works.
|
|
344
|
+
if (tab === 'plugins' && !state.agents.length && !state.agentsLoading && !state.agentsError) loadAgents();
|
|
329
345
|
// popstate calls navTo with writeHash:false so it never replaceState-clobbers
|
|
330
346
|
// the entry it popped; user-initiated navigation pushes so Back walks tabs.
|
|
331
347
|
if (doWriteHash) writeHash({ push });
|
|
@@ -656,6 +672,10 @@ function view() {
|
|
|
656
672
|
} else if (state.tab === 'settings') {
|
|
657
673
|
// Same word as the rail item - location chrome must not fork vocabulary.
|
|
658
674
|
crumbLeaf = 'settings';
|
|
675
|
+
} else if (state.tab === 'models') {
|
|
676
|
+
crumbLeaf = 'models';
|
|
677
|
+
} else if (state.tab === 'plugins') {
|
|
678
|
+
crumbLeaf = 'plugins';
|
|
659
679
|
}
|
|
660
680
|
const crumb = Crumb({ trail: crumbTrail, leaf: crumbLeaf, right: [dot] });
|
|
661
681
|
|
|
@@ -678,18 +698,7 @@ function view() {
|
|
|
678
698
|
h('div', { key: 'sc-body', class: 'ds-alert-body' }, ShortcutList({ shortcuts: SHORTCUTS })),
|
|
679
699
|
] }))
|
|
680
700
|
: null;
|
|
681
|
-
|
|
682
|
-
// other guidance is installHint (zero-agents case) or the calm chat empty
|
|
683
|
-
// state (agents-but-no-conversation case); neither explains History/Files/
|
|
684
|
-
// Live exist at all. Dismissed once, ever, via localStorage.
|
|
685
|
-
const onboardingBanner = state.showOnboarding
|
|
686
|
-
? Alert({
|
|
687
|
-
key: 'onboarding', kind: 'info', title: 'Welcome to AgentGUI',
|
|
688
|
-
onDismiss: dismissOnboarding,
|
|
689
|
-
children: 'Chat with an agent here. History holds every past conversation, Files browses and edits project directories, Live shows every running session at once, and Settings covers connection, appearance, and keyboard shortcuts.',
|
|
690
|
-
})
|
|
691
|
-
: null;
|
|
692
|
-
const main = h('div', { id: 'agentgui-main', role: 'region', 'aria-label': 'main content', 'data-chat-scroll': '', class: 'agentgui-main agentgui-main-' + state.tab }, [shortcutsHint, onboardingBanner, ...mainContent()].filter(Boolean));
|
|
701
|
+
const main = h('div', { id: 'agentgui-main', role: 'region', 'aria-label': 'main content', 'data-chat-scroll': '', class: 'agentgui-main agentgui-main-' + state.tab }, [shortcutsHint, ...mainContent()].filter(Boolean));
|
|
693
702
|
|
|
694
703
|
// Claude-Desktop three-column shell: a persistent left rail (workspace nav), an
|
|
695
704
|
// optional sessions column (chat + history share the conversation list), the
|
|
@@ -749,6 +758,8 @@ function workspaceRail() {
|
|
|
749
758
|
{ key: 'files', label: 'Files', icon: 'folder', active: state.tab === 'files', onClick: () => navTo('files') },
|
|
750
759
|
{ key: 'live', label: 'Live', icon: 'activity', active: state.tab === 'live', count: liveCount || null, rail: liveHasError ? 'flame' : undefined, onClick: () => navTo('live') },
|
|
751
760
|
{ key: 'git', label: 'Git', icon: 'branch', active: state.tab === 'git', onClick: () => navTo('git') },
|
|
761
|
+
{ key: 'models', label: 'Models', icon: 'circle-dot', active: state.tab === 'models', onClick: () => navTo('models') },
|
|
762
|
+
{ key: 'plugins', label: 'Plugins', icon: 'link', active: state.tab === 'plugins', onClick: () => navTo('plugins') },
|
|
752
763
|
{ key: 'settings', label: 'Settings', icon: 'settings', active: state.tab === 'settings', onClick: () => navTo('settings') },
|
|
753
764
|
];
|
|
754
765
|
return WorkspaceRail({
|
|
@@ -953,6 +964,8 @@ function mainContent() {
|
|
|
953
964
|
if (state.tab === 'files') return filesMain();
|
|
954
965
|
if (state.tab === 'live') return liveMain();
|
|
955
966
|
if (state.tab === 'git') return gitMain();
|
|
967
|
+
if (state.tab === 'models') return modelsMain();
|
|
968
|
+
if (state.tab === 'plugins') return pluginsMain();
|
|
956
969
|
return settingsMain();
|
|
957
970
|
}
|
|
958
971
|
|
|
@@ -1055,6 +1068,61 @@ function gitMain() {
|
|
|
1055
1068
|
];
|
|
1056
1069
|
}
|
|
1057
1070
|
|
|
1071
|
+
// Models tab: agentgui's real model surface composed by the models.availability
|
|
1072
|
+
// WS handler (agent-CLI registry availability + provider key presence), fed
|
|
1073
|
+
// into the design SDK's ModelsConfig component. Not freddie's probed per-mode
|
|
1074
|
+
// matrix - see lib/ws-handlers-util.js models.availability for the mapping.
|
|
1075
|
+
function modelsMain() {
|
|
1076
|
+
const m = state.models;
|
|
1077
|
+
return [
|
|
1078
|
+
PageHeader({ compact: true, dense: true, title: 'Models', lede: 'Discovered agent CLIs, their models, and provider key presence on this server.' }),
|
|
1079
|
+
ModelsConfig({
|
|
1080
|
+
data: m.data,
|
|
1081
|
+
loading: m.loading,
|
|
1082
|
+
error: m.error,
|
|
1083
|
+
selectedProviderId: m.selectedProviderId,
|
|
1084
|
+
onSelectProvider: (id) => { state.models.selectedProviderId = id; render(); },
|
|
1085
|
+
onRefresh: () => loadModelsAvailability(),
|
|
1086
|
+
}),
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Plugins tab: agentgui has no freddie-style plugin host (~150 discoverable
|
|
1091
|
+
// plugin.js files) - its actual extensibility surface is the discovered
|
|
1092
|
+
// agent-CLI registry (lib/agent-discovery.js + lib/claude-runner-agents.js
|
|
1093
|
+
// AgentRegistry, the same data agents.list already exposes to the chat
|
|
1094
|
+
// picker). Adapted onto PluginsConfig's shape: name=agent id, surfaces=
|
|
1095
|
+
// protocol (cli|acp), requires=[] (agents have no dependency graph), enabled=
|
|
1096
|
+
// available (the CLI was actually found on this server), status=install hint
|
|
1097
|
+
// when missing. There is no enable/disable action (agentgui does not gate
|
|
1098
|
+
// which agent CLIs are usable) so onToggle re-checks availability instead of
|
|
1099
|
+
// pretending to flip a setting that doesn't exist.
|
|
1100
|
+
function pluginsMain() {
|
|
1101
|
+
const list = (state.agents || []).map((a) => ({
|
|
1102
|
+
name: a.id,
|
|
1103
|
+
version: undefined,
|
|
1104
|
+
surfaces: a.protocol || 'cli',
|
|
1105
|
+
requires: [],
|
|
1106
|
+
source: a.npxPackage ? ('npx ' + a.npxPackage) : undefined,
|
|
1107
|
+
enabled: a.available !== false,
|
|
1108
|
+
status: a.available !== false ? 'loaded' : (a.npxInstallable ? 'installable via npx' : 'not found'),
|
|
1109
|
+
}));
|
|
1110
|
+
return [
|
|
1111
|
+
PageHeader({ compact: true, dense: true, title: 'Plugins', lede: 'Discovered agent CLIs — agentgui\'s real extensibility surface (no freddie-style plugin host).' }),
|
|
1112
|
+
PluginsConfig({
|
|
1113
|
+
plugins: list,
|
|
1114
|
+
selected: state.plugins.selected,
|
|
1115
|
+
loading: !!state.agentsLoading,
|
|
1116
|
+
error: state.agentsError,
|
|
1117
|
+
onSelect: (name) => { state.plugins.selected = name; render(); },
|
|
1118
|
+
// No real enable/disable exists - re-run discovery so a just-installed
|
|
1119
|
+
// CLI's availability reflects immediately instead of a fake toggle.
|
|
1120
|
+
onToggle: () => { withBusy(null, () => loadAgents(), 'checking…'); },
|
|
1121
|
+
onReload: () => loadAgents(),
|
|
1122
|
+
}),
|
|
1123
|
+
];
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1058
1126
|
// --- files (folder browser) ---
|
|
1059
1127
|
async function loadDir(dirPath, { fromHash = false } = {}) {
|
|
1060
1128
|
state.files = state.files || {};
|
|
@@ -1235,9 +1303,7 @@ async function runBulkDelete() {
|
|
|
1235
1303
|
state.files.dialog = null;
|
|
1236
1304
|
restoreFileDialogFocus(d._trigger);
|
|
1237
1305
|
marked.clear(); state.files._lastMarkIdx = null;
|
|
1238
|
-
|
|
1239
|
-
announce(successMsg);
|
|
1240
|
-
toast({ message: successMsg, kind: 'success' });
|
|
1306
|
+
announce('deleted ' + okCount + ' ' + (okCount === 1 ? 'entry' : 'entries'));
|
|
1241
1307
|
// Patch the visible list immediately instead of waiting on a second full
|
|
1242
1308
|
// directory round-trip - the server already confirmed every deletion.
|
|
1243
1309
|
const gone = new Set(targets.map((e) => e.path || e.name));
|
|
@@ -1287,9 +1353,7 @@ async function runBulkMove(destDir) {
|
|
|
1287
1353
|
state.files.dialog = null;
|
|
1288
1354
|
restoreFileDialogFocus(d._trigger);
|
|
1289
1355
|
marked.clear(); state.files._lastMarkIdx = null;
|
|
1290
|
-
|
|
1291
|
-
announce(successMsg);
|
|
1292
|
-
toast({ message: successMsg, kind: 'success' });
|
|
1356
|
+
announce('moved ' + okCount + ' ' + (okCount === 1 ? 'entry' : 'entries'));
|
|
1293
1357
|
// Moved entries leave the current dir - patch them out immediately rather
|
|
1294
1358
|
// than waiting on a second full directory round-trip.
|
|
1295
1359
|
const gone = new Set(targets.map((e) => e.path || e.name));
|
|
@@ -1325,11 +1389,8 @@ async function runFileMutation(fn, doneMsg, patch) {
|
|
|
1325
1389
|
restoreFileDialogFocus(d._trigger);
|
|
1326
1390
|
announce(doneMsg);
|
|
1327
1391
|
// A soft-delete's trashId (if this mutation was a delete) rides the
|
|
1328
|
-
// return value straight to the undo-toast caller
|
|
1329
|
-
// dedicated real-undo-action UI, not a generic dismiss-only confirmation,
|
|
1330
|
-
// so it stays separate from toast() rather than being replaced by it.
|
|
1392
|
+
// return value straight to the undo-toast caller.
|
|
1331
1393
|
if (result && result.trashId) offerUndoDelete(result.trashId, doneMsg);
|
|
1332
|
-
else toast({ message: doneMsg, kind: 'success' });
|
|
1333
1394
|
if (patch) {
|
|
1334
1395
|
// Patch the visible list immediately, matching the bulk-delete/move
|
|
1335
1396
|
// pattern, instead of stalling the dialog on a second full round-trip.
|
|
@@ -2483,6 +2544,16 @@ function chatMain() {
|
|
|
2483
2544
|
messages: state.chat.messages,
|
|
2484
2545
|
busy: state.chat.busy,
|
|
2485
2546
|
draft: state.chat.draft,
|
|
2547
|
+
// Scroll-position overview strip alongside the thread.
|
|
2548
|
+
showMinimap: true,
|
|
2549
|
+
// @-mention file autocomplete: reuse the Files tab's own data source
|
|
2550
|
+
// (state.files.entries, populated by loadDir/listDir) rather than a new
|
|
2551
|
+
// fetch. Scoped to whatever directory Files last listed - if the user
|
|
2552
|
+
// hasn't opened Files yet this is empty and the composer simply shows
|
|
2553
|
+
// no mention suggestions until they do (no fake/synthetic file list).
|
|
2554
|
+
mentionFiles: (state.files.entries || [])
|
|
2555
|
+
.filter((e) => e && e.path)
|
|
2556
|
+
.map((e) => ({ path: e.path, isDir: e.type === 'dir' })),
|
|
2486
2557
|
// Idle never reads 'resuming…' (nothing is in flight - the continuation
|
|
2487
2558
|
// fact lives in the composer context line and banner); a remotely-stopped
|
|
2488
2559
|
// turn reads 'stopped', not a normal finish.
|
|
@@ -3817,9 +3888,9 @@ function settingsMain() {
|
|
|
3817
3888
|
render();
|
|
3818
3889
|
},
|
|
3819
3890
|
}),
|
|
3820
|
-
state.backendStatus === 'connecting' ? h('
|
|
3821
|
-
state.backendStatus === 'ok' ? h('
|
|
3822
|
-
state.backendStatus === 'failed' ? h('
|
|
3891
|
+
state.backendStatus === 'connecting' ? h('p', { key: 'bst-connecting', class: 't-meta', role: 'status' }, 'connecting…') : null,
|
|
3892
|
+
state.backendStatus === 'ok' ? h('p', { key: 'bst-ok', class: 't-meta', role: 'status' }, 'connected') : null,
|
|
3893
|
+
state.backendStatus === 'failed' ? h('p', { key: 'bst-failed', class: 't-meta field-error', role: 'alert' }, 'connection failed - check the URL') : null,
|
|
3823
3894
|
(state.confirmingBackend !== undefined && state.confirmingBackend === state.backendDraft && isValid && state.backendDraft !== state.backend)
|
|
3824
3895
|
? h('p', { key: 'bcw', class: 't-meta field-error', role: 'alert' }, 'changing backend discards this browser\'s chat transcript - press save again to confirm') : null,
|
|
3825
3896
|
healthSummary(),
|
|
@@ -4266,6 +4337,25 @@ async function loadAgents() {
|
|
|
4266
4337
|
}
|
|
4267
4338
|
}
|
|
4268
4339
|
|
|
4340
|
+
// Models tab data load: models.availability WS handler composes agent
|
|
4341
|
+
// registry + provider key presence into ModelsConfig's expected shape. See
|
|
4342
|
+
// lib/ws-handlers-util.js for exactly what each field means in agentgui (no
|
|
4343
|
+
// freddie-style per-mode probe matrix - one real 'cli' mode per model).
|
|
4344
|
+
async function loadModelsAvailability() {
|
|
4345
|
+
state.models.loading = true;
|
|
4346
|
+
state.models.error = null;
|
|
4347
|
+
render();
|
|
4348
|
+
try {
|
|
4349
|
+
state.models.data = await B.getModelsAvailability(state.backend);
|
|
4350
|
+
state.models.loading = false;
|
|
4351
|
+
render();
|
|
4352
|
+
} catch (e) {
|
|
4353
|
+
state.models.loading = false;
|
|
4354
|
+
state.models.error = errText(e) || 'failed to load model availability';
|
|
4355
|
+
render();
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4269
4359
|
// Boot-time automatic retries with backoff when the first agents fetch fails
|
|
4270
4360
|
// (the server may still be warming up).
|
|
4271
4361
|
async function retryLoadAgents() {
|
package/site/app/js/backend.js
CHANGED
|
@@ -428,6 +428,25 @@ export async function listAgentModels(base, agentId) {
|
|
|
428
428
|
} catch { return []; }
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
// Composed model-availability view for the Models tab (ModelsConfig
|
|
432
|
+
// component): per-agent CLI availability + models, in the shape ModelsConfig
|
|
433
|
+
// expects ({timestamp, providers, sampler, summary}). See lib/ws-handlers-
|
|
434
|
+
// util.js models.availability for what each field really means in agentgui
|
|
435
|
+
// (no probed per-mode matrix like freddie - one real 'cli' mode per model).
|
|
436
|
+
export async function getModelsAvailability(base) {
|
|
437
|
+
return wsCall(base, 'models.availability', {});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Provider API-key configs (masked) for the Models tab's provider auth info -
|
|
441
|
+
// backs the existing Settings 'keys' surface, reused here as-is.
|
|
442
|
+
export async function getAuthConfigs(base) {
|
|
443
|
+
return wsCall(base, 'auth.configs', {});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function saveAuthConfig(base, providerId, apiKey, defaultModel) {
|
|
447
|
+
return wsCall(base, 'auth.save', { providerId, apiKey, defaultModel });
|
|
448
|
+
}
|
|
449
|
+
|
|
431
450
|
// ---------- Git / worktrees (WS) ----------
|
|
432
451
|
|
|
433
452
|
export async function gitStatus(base, { cwd } = {}) {
|