repoburg 1.3.185 → 1.3.188

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.
Files changed (49) hide show
  1. package/agent-pane/backend/package-lock.json +2359 -0
  2. package/agent-pane/backend/package.json +29 -0
  3. package/agent-pane/backend/src/codex/codex-provider.ts +363 -0
  4. package/agent-pane/backend/src/codex/item-mapper.ts +59 -0
  5. package/agent-pane/backend/src/db/index.ts +91 -0
  6. package/agent-pane/backend/src/db/schema.ts +76 -0
  7. package/agent-pane/backend/src/index.ts +57 -0
  8. package/agent-pane/backend/src/routes/sessions.ts +134 -0
  9. package/agent-pane/backend/src/routes/turns.ts +60 -0
  10. package/agent-pane/backend/src/routes/workspaces.ts +110 -0
  11. package/agent-pane/backend/src/ws/index.ts +43 -0
  12. package/agent-pane/backend/tsconfig.json +23 -0
  13. package/agent-pane/frontend/index.html +18 -0
  14. package/agent-pane/frontend/package-lock.json +1948 -0
  15. package/agent-pane/frontend/package.json +28 -0
  16. package/agent-pane/frontend/src/App.tsx +44 -0
  17. package/agent-pane/frontend/src/AppInitializer.tsx +46 -0
  18. package/agent-pane/frontend/src/api/client.ts +41 -0
  19. package/agent-pane/frontend/src/api/sessions.ts +47 -0
  20. package/agent-pane/frontend/src/api/websocket.ts +149 -0
  21. package/agent-pane/frontend/src/api/workspaces.ts +24 -0
  22. package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +50 -0
  23. package/agent-pane/frontend/src/components/CommandInputBar.tsx +42 -0
  24. package/agent-pane/frontend/src/components/CommandPalette.tsx +207 -0
  25. package/agent-pane/frontend/src/components/DiffViewer.tsx +27 -0
  26. package/agent-pane/frontend/src/components/IdeWorkspace.tsx +25 -0
  27. package/agent-pane/frontend/src/components/NewSessionDialog.tsx +84 -0
  28. package/agent-pane/frontend/src/components/RenderArea.tsx +170 -0
  29. package/agent-pane/frontend/src/components/RightPanel.tsx +40 -0
  30. package/agent-pane/frontend/src/components/RuntimeIcon.tsx +16 -0
  31. package/agent-pane/frontend/src/components/SessionTabs.tsx +44 -0
  32. package/agent-pane/frontend/src/components/StatusBar.tsx +68 -0
  33. package/agent-pane/frontend/src/components/StatusDot.tsx +13 -0
  34. package/agent-pane/frontend/src/components/Toast.tsx +80 -0
  35. package/agent-pane/frontend/src/components/TurnContent.tsx +294 -0
  36. package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +350 -0
  37. package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +81 -0
  38. package/agent-pane/frontend/src/components/panels/LogsPanel.tsx +25 -0
  39. package/agent-pane/frontend/src/components/panels/MetricsPanel.tsx +82 -0
  40. package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +45 -0
  41. package/agent-pane/frontend/src/main.tsx +10 -0
  42. package/agent-pane/frontend/src/store/store.ts +635 -0
  43. package/agent-pane/frontend/src/styles/app.css +365 -0
  44. package/agent-pane/frontend/src/styles/tokens.css +62 -0
  45. package/agent-pane/frontend/src/types/index.ts +153 -0
  46. package/agent-pane/frontend/tsconfig.json +22 -0
  47. package/agent-pane/frontend/vite.config.ts +16 -0
  48. package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
  49. package/package.json +2 -1
@@ -0,0 +1,81 @@
1
+ import { useStore } from '../../store/store.ts';
2
+
3
+ export default function ChangesPanel() {
4
+ const { activeSessionId, sessionDetails, changesScope, setChangesScope, activeTurnIndex, setSelectedChangePath } = useStore();
5
+ const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
6
+ const turns = detail?.turns ?? [];
7
+
8
+ // Collect file_change items
9
+ type ChangeEntry = { path: string; kind: string };
10
+ const entries: ChangeEntry[] = [];
11
+
12
+ if (changesScope === 'session') {
13
+ turns.forEach((turn) => {
14
+ turn.items.forEach((item) => {
15
+ if (item.type === 'file_change' && item.data?.changes) {
16
+ const changes = item.data.changes as Array<{ path: string; kind: string }>;
17
+ changes.forEach((c) => entries.push({ path: c.path, kind: c.kind }));
18
+ }
19
+ });
20
+ });
21
+ } else {
22
+ // Current turn only
23
+ const turn = turns[activeTurnIndex];
24
+ if (turn) {
25
+ turn.items.forEach((item) => {
26
+ if (item.type === 'file_change' && item.data?.changes) {
27
+ const changes = item.data.changes as Array<{ path: string; kind: string }>;
28
+ changes.forEach((c) => entries.push({ path: c.path, kind: c.kind }));
29
+ }
30
+ });
31
+ }
32
+ }
33
+
34
+ // Deduplicate by path (keep last occurrence)
35
+ const seen = new Map<string, ChangeEntry>();
36
+ for (const e of entries) seen.set(e.path, e);
37
+ const deduped = Array.from(seen.values());
38
+
39
+ return (
40
+ <>
41
+ <div className="changes-scope-toggle">
42
+ <button
43
+ className={`scope-btn ${changesScope === 'session' ? 'active' : ''}`}
44
+ onClick={() => setChangesScope('session')}
45
+ >Session</button>
46
+ <button
47
+ className={`scope-btn ${changesScope === 'turngroup' ? 'active' : ''}`}
48
+ onClick={() => setChangesScope('turngroup')}
49
+ >Current Turn</button>
50
+ </div>
51
+
52
+ {deduped.length === 0 ? (
53
+ <div className="empty-state">No file changes in this scope</div>
54
+ ) : (
55
+ <div className="right-section">
56
+ <div className="right-section-title">Changed Files ({deduped.length})</div>
57
+ {deduped.map((entry) => {
58
+ const statusClass = entry.kind === 'add' ? 'fc-add' : entry.kind === 'delete' ? 'fc-del' : 'fc-mod';
59
+ const statusChar = entry.kind === 'add' ? 'A' : entry.kind === 'delete' ? 'D' : 'M';
60
+ const fileName = entry.path.split('/').pop() || entry.path;
61
+ const dir = entry.path.replace('/' + fileName, '');
62
+
63
+ return (
64
+ <div
65
+ key={entry.path}
66
+ className="file-change clickable"
67
+ onClick={() => setSelectedChangePath(entry.path)}
68
+ >
69
+ <span className={`fc-status ${statusClass}`}>{statusChar}</span>
70
+ <span className="fc-name">
71
+ {dir && <span style={{ color: 'var(--text-muted)' }}>{dir}/</span>}
72
+ {fileName}
73
+ </span>
74
+ </div>
75
+ );
76
+ })}
77
+ </div>
78
+ )}
79
+ </>
80
+ );
81
+ }
@@ -0,0 +1,25 @@
1
+ import { useStore } from '../../store/store.ts';
2
+
3
+ export default function LogsPanel() {
4
+ const eventLog = useStore((s) => s.eventLog);
5
+
6
+ if (eventLog.length === 0) {
7
+ return <div className="empty-state">No events logged yet</div>;
8
+ }
9
+
10
+ // Show in reverse chronological order (newest first)
11
+ const logs = [...eventLog].reverse();
12
+
13
+ return (
14
+ <div className="right-section">
15
+ <div className="right-section-title">Event Log</div>
16
+ {logs.map((entry, i) => (
17
+ <div key={i} className="log-entry">
18
+ <span className="log-time">{entry.time}</span>
19
+ <span className={`log-level log-${entry.level.toLowerCase()}`}>{entry.level}</span>
20
+ <span className="log-msg">{entry.message}</span>
21
+ </div>
22
+ ))}
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,82 @@
1
+ import { useStore } from '../../store/store.ts';
2
+ import { statusToDot, statusToLabel, formatDuration } from '../../types/index.ts';
3
+ import StatusDot from '../StatusDot.tsx';
4
+
5
+ export default function MetricsPanel() {
6
+ const { activeSessionId, sessions, sessionDetails } = useStore();
7
+ const session = sessions.find((s) => s.id === activeSessionId);
8
+ const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
9
+
10
+ if (!session) {
11
+ return <div className="empty-state">No session selected</div>;
12
+ }
13
+
14
+ const isRunning = session.is_running === 1;
15
+ const dot = statusToDot(session.status, isRunning);
16
+ const label = statusToLabel(session.status, isRunning);
17
+
18
+ // Aggregate token usage from all completed turns
19
+ let totalIn = 0;
20
+ let totalOut = 0;
21
+ let totalReasoning = 0;
22
+ if (detail) {
23
+ for (const turn of detail.turns) {
24
+ if (turn.token_usage) {
25
+ totalIn += turn.token_usage.input_tokens;
26
+ totalOut += turn.token_usage.output_tokens;
27
+ totalReasoning += turn.token_usage.reasoning_output_tokens;
28
+ }
29
+ }
30
+ }
31
+
32
+ const turnCount = detail?.turns.length ?? 0;
33
+
34
+ // Calculate duration from first turn created_at to last turn updated_at (or now if running)
35
+ let duration = '—';
36
+ if (detail && detail.turns.length > 0) {
37
+ const firstTurn = detail.turns[0];
38
+ const lastTurn = detail.turns[detail.turns.length - 1];
39
+ const endISO = isRunning ? null : lastTurn.updated_at;
40
+ duration = formatDuration(firstTurn.created_at, endISO);
41
+ }
42
+
43
+ // Repo name = basename of cwd
44
+ const repoName = session.cwd ? session.cwd.split('/').pop() || session.cwd : '—';
45
+
46
+ return (
47
+ <>
48
+ <div className="right-section">
49
+ <div className="right-section-title">Session</div>
50
+ <div className="metric-row"><span className="metric-label">Runtime</span><span className="metric-val">Codex</span></div>
51
+ <div className="metric-row"><span className="metric-label">Model</span><span className="metric-val">{session.model || 'default'}</span></div>
52
+ <div className="metric-row"><span className="metric-label">Repo</span><span className="metric-val" title={session.cwd || undefined}>{repoName}</span></div>
53
+ <div className="metric-row"><span className="metric-label">Turns</span><span className="metric-val">{turnCount}</span></div>
54
+ <div className="metric-row"><span className="metric-label">Duration</span><span className="metric-val">{duration}</span></div>
55
+ <div className="metric-row">
56
+ <span className="metric-label">Status</span>
57
+ <span className="metric-val">
58
+ <span className="badge badge-running">
59
+ <StatusDot status={dot} size={5} /> {label}
60
+ </span>
61
+ </span>
62
+ </div>
63
+ </div>
64
+
65
+ <div className="right-section">
66
+ <div className="right-section-title">Resources</div>
67
+ <div className="metric-row"><span className="metric-label">Tokens In</span><span className="metric-val">{totalIn.toLocaleString()}</span></div>
68
+ <div className="metric-row"><span className="metric-label">Tokens Out</span><span className="metric-val">{totalOut.toLocaleString()}</span></div>
69
+ {totalReasoning > 0 && (
70
+ <div className="metric-row"><span className="metric-label">Reasoning</span><span className="metric-val">{totalReasoning.toLocaleString()}</span></div>
71
+ )}
72
+ <div className="metric-row"><span className="metric-label">Cost</span><span className="metric-val" style={{ color: 'var(--amber)' }}>—</span></div>
73
+ </div>
74
+
75
+ <div className="right-section">
76
+ <div className="right-section-title">Metadata</div>
77
+ <div className="metric-row"><span className="metric-label">Created</span><span className="metric-val" style={{ fontSize: '11px' }}>{new Date(session.created_at).toLocaleString()}</span></div>
78
+ <div className="metric-row"><span className="metric-label">Thread ID</span><span className="metric-val" style={{ fontSize: '10px', color: 'var(--text-muted)' }}>{session.external_thread_id ? session.external_thread_id.slice(0, 16) + '…' : '—'}</span></div>
79
+ </div>
80
+ </>
81
+ );
82
+ }
@@ -0,0 +1,45 @@
1
+ import { useStore } from '../../store/store.ts';
2
+ import { User } from 'lucide-react';
3
+
4
+ export default function TurnsPanel() {
5
+ const { activeSessionId, sessionDetails, activeTurnIndex, setActiveTurnIndex } = useStore();
6
+ const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
7
+ const turns = detail?.turns ?? [];
8
+
9
+ if (turns.length === 0) {
10
+ return <div className="empty-state">No turns yet</div>;
11
+ }
12
+
13
+ function handleClick(idx: number) {
14
+ setActiveTurnIndex(idx);
15
+ // Scroll render area to turn position
16
+ requestAnimationFrame(() => {
17
+ document.getElementById(`turn-${idx}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
18
+ });
19
+ }
20
+
21
+ return (
22
+ <div className="right-section">
23
+ <div className="right-section-title">Conversation</div>
24
+ {turns.map((turn, idx) => {
25
+ const isActive = idx === activeTurnIndex;
26
+ const preview = turn.user_prompt.length > 50
27
+ ? turn.user_prompt.slice(0, 50) + '…'
28
+ : turn.user_prompt;
29
+
30
+ return (
31
+ <div
32
+ key={turn.id}
33
+ className={`turn-item ${isActive ? 'active' : ''}`}
34
+ onClick={() => handleClick(idx)}
35
+ >
36
+ <span className="turn-item-icon" style={{ background: 'var(--amber)', color: '#fff' }}>
37
+ <User size={12} />
38
+ </span>
39
+ <span className="turn-item-label">{preview}</span>
40
+ </div>
41
+ );
42
+ })}
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App.tsx';
4
+ import './styles/app.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ );