agentgui 1.0.1096 → 1.0.1097

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 CHANGED
Binary file
@@ -324,6 +324,26 @@ export function register(router, deps) {
324
324
  } catch (e) { err(500, e.message); }
325
325
  });
326
326
 
327
+ // --- git.status: changed-file list (staged/unstaged/untracked) for
328
+ // GitStatusPanel. Porcelain=v1 line format "XY PATH" (rename: "XY OLD -> NEW"),
329
+ // X = index state, Y = worktree state, '?'/' ' = untouched/untracked.
330
+ router.handle('git.status', async (p) => {
331
+ const cwd = resolveGitCwd(p, STARTUP_CWD);
332
+ try {
333
+ const { stdout } = await execFileP('git', ['status', '--porcelain=v1'], { cwd });
334
+ const files = [];
335
+ for (const line of stdout.split('\n')) {
336
+ if (!line) continue;
337
+ const x = line[0], y = line[1];
338
+ const path = line.slice(3);
339
+ if (x === '?' && y === '?') { files.push({ path, status: '?' }); continue; }
340
+ if (x !== ' ' && x !== '?') files.push({ path, status: x, staged: true });
341
+ else if (y !== ' ' && y !== '?') files.push({ path, status: y, staged: false });
342
+ }
343
+ return { files };
344
+ } catch (e) { err(500, (e.stderr || e.message || 'git status failed').trim()); }
345
+ });
346
+
327
347
  // --- git.diff: unified diff for a single file or the whole working tree.
328
348
  // p.cwd (optional) is confined via confineToRoots/fsAllowRoots, same as
329
349
  // chat.sendMessage; p.file (optional) is validated to reject flags/shell
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1096",
3
+ "version": "1.0.1097",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -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 } = 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 } = 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
@@ -43,10 +43,7 @@ const state = {
43
43
  activeTimer: null,
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
- // Minimal git/worktree panel state. The richer visual component
47
- // (GitDiffView/WorktreeSwitcher) lives in anentrypoint-design; this is a
48
- // plain list/text render until the vendored SDK bundle picks those up.
49
- git: { loading: false, error: null, diff: '', commits: [], worktrees: [], file: '' },
46
+ git: { loading: false, error: null, diff: '', commits: [], worktrees: [], files: [], file: '', worktreeBusy: false },
50
47
  };
51
48
 
52
49
  // Two-step arm controls auto-reset after this delay so an accidental first click
@@ -939,20 +936,19 @@ function mainContent() {
939
936
  return settingsMain();
940
937
  }
941
938
 
942
- // --- git (diff/log + worktree list) ---
943
- // Minimal panel: loads git.log + worktree.list on entry, git.diff on demand.
944
- // A richer visual component (GitDiffView/WorktreeSwitcher) is being built in
945
- // anentrypoint-design in parallel; swap this for those exports once the
946
- // vendored SDK bundle (site/app/vendor/anentrypoint-design) is rebuilt to
947
- // include them - it does not yet (checked: no GitDiffView/WorktreeSwitcher
948
- // in the vendored 247420.js as of this change).
939
+ // --- git (status/diff/log + worktree list) ---
940
+ // Uses anentrypoint-design's GitStatusPanel/GitDiffView/WorktreeSwitcher
941
+ // (ported from pi-web's BranchNavigator concept) once the vendored SDK
942
+ // bundle carries them - see components.js barrel export.
949
943
  async function loadGitPanel() {
950
944
  state.git.loading = true; state.git.error = null; render();
951
945
  try {
952
- const [commits, worktrees] = await Promise.all([
946
+ const [files, commits, worktrees] = await Promise.all([
947
+ B.gitStatus(state.backend, {}),
953
948
  B.gitLog(state.backend, { limit: 20 }),
954
949
  B.worktreeList(state.backend, {}),
955
950
  ]);
951
+ state.git.files = files;
956
952
  state.git.commits = commits;
957
953
  state.git.worktrees = worktrees;
958
954
  } catch (e) {
@@ -973,19 +969,59 @@ async function loadGitDiff(file) {
973
969
  state.git.diffLoading = false; render();
974
970
  }
975
971
 
972
+ async function createWorktree(path, branch, newBranch) {
973
+ state.git.worktreeBusy = true; render();
974
+ try {
975
+ await B.worktreeCreate(state.backend, { path, branch, newBranch });
976
+ state.git.wtDialog = null;
977
+ await loadGitPanel();
978
+ } catch (e) {
979
+ if (state.git.wtDialog) { state.git.wtDialog.error = e.message || 'Could not create worktree.'; }
980
+ else state.git.error = e.message || 'Could not create worktree.';
981
+ }
982
+ state.git.worktreeBusy = false; render();
983
+ }
984
+
985
+ function gitWorktreeDialog() {
986
+ const d = state.git.wtDialog;
987
+ if (!d) return null;
988
+ return PromptDialog({
989
+ title: 'New worktree', value: d.value ?? '', placeholder: 'relative path, e.g. ../myrepo-feature',
990
+ error: d.error || null, busy: !!state.git.worktreeBusy,
991
+ confirmLabel: state.git.worktreeBusy ? 'creating…' : 'create', cancelLabel: 'cancel',
992
+ onCancel: () => { state.git.wtDialog = null; render(); },
993
+ onInput: (v) => { d.value = v; },
994
+ onConfirm: (v) => {
995
+ if (!v) { d.error = 'enter a worktree path'; render(); return; }
996
+ // New worktree checks out a branch named after the path's basename -
997
+ // matches WorktreeSwitcher's "new worktree" action, which collects no
998
+ // separate branch field (host owns the create flow per its own docs).
999
+ createWorktree(v, undefined, v.split('/').filter(Boolean).pop());
1000
+ },
1001
+ });
1002
+ }
1003
+
976
1004
  function gitMain() {
977
- if (!state.git.loading && !state.git.commits.length && !state.git.worktrees.length && !state.git.error) loadGitPanel();
1005
+ if (!state.git.loading && !state.git.files.length && !state.git.commits.length && !state.git.worktrees.length && !state.git.error) loadGitPanel();
978
1006
  const g = state.git;
979
1007
  return [
980
1008
  PageHeader({ compact: true, dense: true, title: 'Git', lede: 'Working-tree diff, recent commits, and worktrees.' }),
981
1009
  g.error ? Alert({ key: 'git-err', kind: 'error', title: 'Git error', children: g.error }) : null,
982
1010
  Row({ key: 'git-actions', children: [
983
1011
  Btn({ key: 'refresh', onClick: () => loadGitPanel(), children: g.loading ? 'loading…' : 'refresh' }),
984
- Btn({ key: 'diff-all', onClick: () => loadGitDiff(''), children: g.diffLoading ? 'loading diff…' : 'load full diff' }),
1012
+ WorktreeSwitcher({
1013
+ key: 'wts',
1014
+ worktrees: g.worktrees,
1015
+ current: (g.worktrees.find(w => w.branch) || {}).branch,
1016
+ onSwitch: (w) => loadGitDiff(''),
1017
+ onCreate: () => { state.git.wtDialog = { value: '', error: null }; render(); },
1018
+ }),
985
1019
  ] }),
1020
+ Panel({ id: 'git-status', title: 'changed files', children:
1021
+ GitStatusPanel({ files: g.files, active: g.file, onFileClick: (f) => loadGitDiff(f.path) }) }),
986
1022
  Panel({ id: 'git-diff', title: g.file ? ('diff: ' + g.file) : 'diff', children:
987
1023
  g.diffError ? h('p', { key: 'de', class: 't-meta field-error' }, g.diffError)
988
- : h('pre', { key: 'dp', class: 'git-diff-pre' }, g.diff || '(no diff loaded)') }),
1024
+ : GitDiffView({ diff: g.diff || '', filename: g.file }) }),
989
1025
  Panel({ id: 'git-log', title: 'recent commits', children:
990
1026
  !g.commits.length ? h('p', { key: 'nc', class: 't-meta' }, g.loading ? 'loading…' : 'no commits')
991
1027
  : h('ul', { key: 'cl', class: 'git-commit-list' }, g.commits.map(c =>
@@ -995,13 +1031,7 @@ function gitMain() {
995
1031
  h('span', { key: 'm', class: 't-meta' }, ' — ' + (c.author || '') + ' · ' + (c.date || '')),
996
1032
  Btn({ key: 'view', small: true, onClick: () => loadGitDiff(''), children: 'view' }),
997
1033
  ]))) }),
998
- Panel({ id: 'git-worktrees', title: 'worktrees', children:
999
- !g.worktrees.length ? h('p', { key: 'nw', class: 't-meta' }, g.loading ? 'loading…' : 'no worktrees')
1000
- : h('ul', { key: 'wl', class: 'git-worktree-list' }, g.worktrees.map((w, i) =>
1001
- h('li', { key: w.path || i }, [
1002
- h('code', { key: 'p' }, w.path || ''),
1003
- ' ' + (w.branch || (w.detached ? '(detached)' : '')),
1004
- ]))) }),
1034
+ gitWorktreeDialog(),
1005
1035
  ];
1006
1036
  }
1007
1037
 
@@ -430,6 +430,11 @@ export async function listAgentModels(base, agentId) {
430
430
 
431
431
  // ---------- Git / worktrees (WS) ----------
432
432
 
433
+ export async function gitStatus(base, { cwd } = {}) {
434
+ const { files } = await wsCall(base, 'git.status', { cwd });
435
+ return files || [];
436
+ }
437
+
433
438
  export async function gitDiff(base, { cwd, file, staged } = {}) {
434
439
  return wsCall(base, 'git.diff', { cwd, file, staged });
435
440
  }