projax 3.3.69 → 3.3.71

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 (44) hide show
  1. package/dist/agent-runner.d.ts +60 -0
  2. package/dist/agent-runner.js +382 -0
  3. package/dist/api/database.d.ts +38 -1
  4. package/dist/api/database.d.ts.map +1 -1
  5. package/dist/api/database.js +327 -0
  6. package/dist/api/database.js.map +1 -1
  7. package/dist/api/routes/agents.d.ts +4 -0
  8. package/dist/api/routes/agents.d.ts.map +1 -0
  9. package/dist/api/routes/agents.js +375 -0
  10. package/dist/api/routes/agents.js.map +1 -0
  11. package/dist/api/routes/index.d.ts.map +1 -1
  12. package/dist/api/routes/index.js +4 -0
  13. package/dist/api/routes/index.js.map +1 -1
  14. package/dist/api/routes/todos.d.ts +4 -0
  15. package/dist/api/routes/todos.d.ts.map +1 -0
  16. package/dist/api/routes/todos.js +595 -0
  17. package/dist/api/routes/todos.js.map +1 -0
  18. package/dist/api/types.d.ts +67 -0
  19. package/dist/api/types.d.ts.map +1 -1
  20. package/dist/core/database.d.ts +47 -0
  21. package/dist/core/database.js +48 -0
  22. package/dist/core-bridge.d.ts +1 -1
  23. package/dist/electron/core/database.d.ts +47 -0
  24. package/dist/electron/core/database.js +48 -0
  25. package/dist/electron/renderer/assets/index-6afBeDFD.js +66 -0
  26. package/dist/electron/renderer/assets/index-Bd3aFi7B.css +1 -0
  27. package/dist/electron/renderer/index.html +2 -2
  28. package/dist/index.js +6 -175
  29. package/dist/octopus-cli.d.ts +2 -0
  30. package/dist/octopus-cli.js +45 -0
  31. package/dist/octopus-show-tui.d.ts +1 -0
  32. package/dist/octopus-show-tui.js +802 -0
  33. package/dist/octopus-tui.d.ts +1 -0
  34. package/dist/octopus-tui.js +115 -0
  35. package/dist/prxi.js +373 -63
  36. package/dist/prxi.tsx +472 -65
  37. package/package.json +7 -2
  38. package/dist/electron/renderer/assets/index-BjZn_mEF.js +0 -66
  39. package/dist/electron/renderer/assets/index-CZmDxbJO.js +0 -66
  40. package/dist/electron/renderer/assets/index-Cd1mTO3s.js +0 -66
  41. package/dist/electron/renderer/assets/index-Cj4QON0s.js +0 -66
  42. package/dist/electron/renderer/assets/index-CmtZriN5.js +0 -66
  43. package/dist/electron/renderer/assets/index-DJDPi0kp.css +0 -1
  44. package/dist/electron/renderer/assets/index-DfocdjIj.css +0 -1
package/dist/prxi.tsx CHANGED
@@ -22,8 +22,21 @@ import {
22
22
  removeProject,
23
23
  getCurrentBranch,
24
24
  Project,
25
+ Agent,
26
+ AgentCliType,
27
+ RunningAgent,
25
28
  } from './core-bridge';
26
29
  import { getProjectScripts, getRunningProcessesClean, runScript, runScriptInBackground, stopScript } from './script-runner';
30
+ import {
31
+ getAgentsByProject,
32
+ getRunningAgents,
33
+ launchAgentInTerminal,
34
+ createAgent,
35
+ updateAgent,
36
+ deleteAgent,
37
+ CLI_DISPLAY_NAMES,
38
+ VALID_CLI_TYPES,
39
+ } from './agent-runner';
27
40
  import { spawn } from 'child_process';
28
41
  import * as path from 'path';
29
42
  import * as os from 'os';
@@ -68,10 +81,36 @@ function truncateText(text: string, maxLength: number): string {
68
81
  }
69
82
 
70
83
  // Type definitions for views and filters
71
- type ViewType = 'projects' | 'workspaces' | 'processes' | 'settings';
84
+ type ViewType = 'projects' | 'tasks' | 'agents' | 'processes' | 'workspaces' | 'settings';
72
85
  type FilterType = 'all' | 'name' | 'path' | 'ports' | 'tags' | 'running';
73
86
  type SortType = 'name-asc' | 'name-desc' | 'recent' | 'oldest' | 'running';
74
87
 
88
+ // Task types
89
+ interface TodoTask {
90
+ id: number;
91
+ list_id: number;
92
+ title: string;
93
+ status: 'pending' | 'in_progress' | 'completed' | 'blocked';
94
+ priority: 'low' | 'medium' | 'high' | 'urgent';
95
+ assignee_agent_id: number | null;
96
+ worktree_path: string | null;
97
+ }
98
+
99
+ // Task status config
100
+ const taskStatusConfig: Record<string, { color: string; icon: string }> = {
101
+ pending: { color: colors.textTertiary, icon: '○' },
102
+ in_progress: { color: colors.accentOrange, icon: '◐' },
103
+ completed: { color: colors.accentGreen, icon: '●' },
104
+ blocked: { color: '#f85149', icon: '✗' },
105
+ };
106
+
107
+ const taskPriorityColors: Record<string, string> = {
108
+ low: colors.textTertiary,
109
+ medium: '#d29922',
110
+ high: colors.accentOrange,
111
+ urgent: '#f85149',
112
+ };
113
+
75
114
  const FILTER_TYPES: FilterType[] = ['all', 'name', 'path', 'ports', 'tags', 'running'];
76
115
  const SORT_TYPES: SortType[] = ['name-asc', 'name-desc', 'recent', 'oldest', 'running'];
77
116
 
@@ -138,9 +177,11 @@ const HelpModal: React.FC<HelpModalProps> = ({ onClose }) => {
138
177
  <Text> </Text>
139
178
  <Text color={colors.accentCyan}>View Navigation:</Text>
140
179
  <Text> 1 Projects view</Text>
141
- <Text> 2 Workspaces view</Text>
142
- <Text> 3 Global processes view</Text>
143
- <Text> 4 Settings</Text>
180
+ <Text> 2 Tasks view (requires selected project)</Text>
181
+ <Text> 3 Agents view (requires selected project)</Text>
182
+ <Text> 4 Processes view</Text>
183
+ <Text> 5 Workspaces view</Text>
184
+ <Text> 6 Settings</Text>
144
185
  <Text> T Toggle terminal output panel</Text>
145
186
  <Text> </Text>
146
187
  <Text color={colors.accentCyan}>Projects View - Navigation:</Text>
@@ -694,10 +735,10 @@ const ProjectDetailsComponent: React.FC<ProjectDetailsProps> = ({
694
735
  .then(res => {
695
736
  if (res.ok) setNpmPackage(pkg.name);
696
737
  })
697
- .catch(() => {});
738
+ .catch(() => { /* Ignore fetch errors */ });
698
739
  }
699
740
  }
700
- } catch {}
741
+ } catch (e) { /* Ignore parsing or file read errors */ }
701
742
  }, [project]);
702
743
 
703
744
  if (!project) {
@@ -1207,6 +1248,23 @@ const App: React.FC = () => {
1207
1248
  'chrome', 'firefox', 'safari', 'edge', 'custom',
1208
1249
  ];
1209
1250
 
1251
+ // Agents state
1252
+ const [agents, setAgents] = useState<Agent[]>([]);
1253
+ const [selectedAgentIndex, setSelectedAgentIndex] = useState(0);
1254
+ const [runningAgentsList, setRunningAgentsList] = useState<RunningAgent[]>([]);
1255
+ const [showAddAgentModal, setShowAddAgentModal] = useState(false);
1256
+ const [editingAgent, setEditingAgent] = useState<Agent | null>(null);
1257
+ const [newAgentName, setNewAgentName] = useState('');
1258
+ const [newAgentCliType, setNewAgentCliType] = useState<AgentCliType>('claude');
1259
+ const [newAgentCliTypeIndex, setNewAgentCliTypeIndex] = useState(0);
1260
+ const [agentInputField, setAgentInputField] = useState<'name' | 'cli_type' | 'model' | 'api_key'>('name');
1261
+ const [newAgentModel, setNewAgentModel] = useState('');
1262
+ const [newAgentApiKey, setNewAgentApiKey] = useState('');
1263
+
1264
+ // Tasks state
1265
+ const [tasks, setTasks] = useState<TodoTask[]>([]);
1266
+ const [selectedTaskIdx, setSelectedTaskIdx] = useState(0);
1267
+
1210
1268
  // Get terminal dimensions
1211
1269
  const terminalHeight = process.stdout.rows || 24;
1212
1270
  const availableHeight = terminalHeight - 4; // Subtract status bar (increased for view indicator)
@@ -1227,14 +1285,23 @@ const App: React.FC = () => {
1227
1285
  // Use defaults
1228
1286
  }
1229
1287
 
1230
- // Refresh running processes and git branches every 5 seconds
1288
+ // Refresh running processes, git branches, and running agents every 5 seconds
1231
1289
  const interval = setInterval(() => {
1232
1290
  loadRunningProcesses();
1291
+ loadRunningAgentsList();
1233
1292
  }, 5000);
1234
1293
 
1235
1294
  return () => clearInterval(interval);
1236
1295
  }, []);
1237
1296
 
1297
+ // Load agents when switching to agents view or when selected project changes
1298
+ useEffect(() => {
1299
+ const currentProject = projects.length > 0 ? projects[selectedIndex] : null;
1300
+ if (currentView === 'agents' && currentProject) {
1301
+ loadAgentsForProject(currentProject.id);
1302
+ }
1303
+ }, [currentView, projects, selectedIndex]);
1304
+
1238
1305
  // Load git branches when projects change
1239
1306
  useEffect(() => {
1240
1307
  if (allProjects.length > 0) {
@@ -1265,12 +1332,12 @@ const App: React.FC = () => {
1265
1332
  setDetailSelectedScript(0); // Reset selected script
1266
1333
  }, [selectedIndex]);
1267
1334
 
1268
- // Load workspaces when switching to workspaces view
1269
- useEffect(() => {
1270
- if (currentView === 'workspaces' && workspaces.length === 0) {
1271
- loadWorkspacesFromApi();
1272
- }
1273
- }, [currentView]);
1335
+ // Load workspaces when switching to workspaces view (legacy - kept for future use)
1336
+ // useEffect(() => {
1337
+ // if (currentView === 'workspaces' && workspaces.length === 0) {
1338
+ // loadWorkspacesFromApi();
1339
+ // }
1340
+ // }, [currentView]);
1274
1341
 
1275
1342
  // Update scroll offset when selected index changes
1276
1343
  useEffect(() => {
@@ -1301,6 +1368,27 @@ const App: React.FC = () => {
1301
1368
  }
1302
1369
  };
1303
1370
 
1371
+ const loadAgentsForProject = (projectId: number) => {
1372
+ try {
1373
+ const projectAgents = getAgentsByProject(projectId);
1374
+ setAgents(projectAgents);
1375
+ if (selectedAgentIndex >= projectAgents.length) {
1376
+ setSelectedAgentIndex(Math.max(0, projectAgents.length - 1));
1377
+ }
1378
+ } catch (error) {
1379
+ setAgents([]);
1380
+ }
1381
+ };
1382
+
1383
+ const loadRunningAgentsList = () => {
1384
+ try {
1385
+ const running = getRunningAgents();
1386
+ setRunningAgentsList(running);
1387
+ } catch (error) {
1388
+ setRunningAgentsList([]);
1389
+ }
1390
+ };
1391
+
1304
1392
  const loadProjects = () => {
1305
1393
  const loadedProjects = getAllProjects();
1306
1394
  setAllProjects(loadedProjects);
@@ -1729,62 +1817,135 @@ const App: React.FC = () => {
1729
1817
  return;
1730
1818
  }
1731
1819
 
1732
- // Handle navigation in workspaces view
1733
- if (currentView === 'workspaces') {
1734
- if (key.upArrow || input === 'k') {
1735
- setSelectedWorkspaceIndex((prev) => Math.max(0, prev - 1));
1736
- return;
1737
- }
1738
- if (key.downArrow || input === 'j') {
1739
- setSelectedWorkspaceIndex((prev) => Math.min(workspaces.length - 1, prev + 1));
1820
+ // Tasks view navigation is handled in renderTasksView
1821
+
1822
+ // Handle navigation in agents view
1823
+ if (currentView === 'agents') {
1824
+ // Handle add agent modal
1825
+ if (showAddAgentModal) {
1826
+ if (key.escape) {
1827
+ setShowAddAgentModal(false);
1828
+ setNewAgentName('');
1829
+ setNewAgentModel('');
1830
+ setNewAgentApiKey('');
1831
+ setNewAgentCliTypeIndex(0);
1832
+ setAgentInputField('name');
1833
+ return;
1834
+ }
1835
+ if (key.tab) {
1836
+ // Cycle through input fields
1837
+ const fields: Array<'name' | 'cli_type' | 'model' | 'api_key'> = ['name', 'cli_type', 'model', 'api_key'];
1838
+ const currentIdx = fields.indexOf(agentInputField);
1839
+ setAgentInputField(fields[(currentIdx + 1) % fields.length]);
1840
+ return;
1841
+ }
1842
+ if (agentInputField === 'cli_type') {
1843
+ if (key.upArrow || input === 'k') {
1844
+ setNewAgentCliTypeIndex(prev => Math.max(0, prev - 1));
1845
+ setNewAgentCliType(VALID_CLI_TYPES[Math.max(0, newAgentCliTypeIndex - 1)]);
1846
+ return;
1847
+ }
1848
+ if (key.downArrow || input === 'j') {
1849
+ setNewAgentCliTypeIndex(prev => Math.min(VALID_CLI_TYPES.length - 1, prev + 1));
1850
+ setNewAgentCliType(VALID_CLI_TYPES[Math.min(VALID_CLI_TYPES.length - 1, newAgentCliTypeIndex + 1)]);
1851
+ return;
1852
+ }
1853
+ }
1854
+ if (key.return && agentInputField === 'api_key' && newAgentName.trim()) {
1855
+ // Create agent
1856
+ if (selectedProject) {
1857
+ const agent = createAgent(selectedProject.id, {
1858
+ name: newAgentName.trim(),
1859
+ cli_type: newAgentCliType,
1860
+ model: newAgentModel.trim() || null,
1861
+ api_key: newAgentApiKey.trim() || null,
1862
+ });
1863
+ if (agent) {
1864
+ loadAgentsForProject(selectedProject.id);
1865
+ setShowAddAgentModal(false);
1866
+ setNewAgentName('');
1867
+ setNewAgentModel('');
1868
+ setNewAgentApiKey('');
1869
+ setNewAgentCliTypeIndex(0);
1870
+ setAgentInputField('name');
1871
+ } else {
1872
+ setError('Failed to create agent');
1873
+ }
1874
+ }
1875
+ return;
1876
+ }
1877
+ // Handle text input
1878
+ if (agentInputField === 'name') {
1879
+ if (key.backspace || key.delete) {
1880
+ setNewAgentName(prev => prev.slice(0, -1));
1881
+ } else if (input && input.length === 1 && !key.ctrl && !key.meta) {
1882
+ setNewAgentName(prev => prev + input);
1883
+ }
1884
+ return;
1885
+ }
1886
+ if (agentInputField === 'model') {
1887
+ if (key.backspace || key.delete) {
1888
+ setNewAgentModel(prev => prev.slice(0, -1));
1889
+ } else if (input && input.length === 1 && !key.ctrl && !key.meta) {
1890
+ setNewAgentModel(prev => prev + input);
1891
+ }
1892
+ return;
1893
+ }
1894
+ if (agentInputField === 'api_key') {
1895
+ if (key.backspace || key.delete) {
1896
+ setNewAgentApiKey(prev => prev.slice(0, -1));
1897
+ } else if (input && input.length === 1 && !key.ctrl && !key.meta) {
1898
+ setNewAgentApiKey(prev => prev + input);
1899
+ }
1900
+ return;
1901
+ }
1740
1902
  return;
1741
1903
  }
1742
- }
1743
1904
 
1744
- // Handle navigation in processes view
1745
- if (currentView === 'processes') {
1905
+ // Navigation
1746
1906
  if (key.upArrow || input === 'k') {
1747
- setSelectedProcessIndex((prev) => Math.max(0, prev - 1));
1907
+ setSelectedAgentIndex(prev => Math.max(0, prev - 1));
1748
1908
  return;
1749
1909
  }
1750
1910
  if (key.downArrow || input === 'j') {
1751
- setSelectedProcessIndex((prev) => Math.min(runningProcesses.length - 1, prev + 1));
1911
+ setSelectedAgentIndex(prev => Math.min(agents.length - 1, prev + 1));
1752
1912
  return;
1753
1913
  }
1754
- if (input === 'x' && runningProcesses.length > 0) {
1755
- // Stop the selected process
1756
- const selectedProc = runningProcesses[selectedProcessIndex];
1757
- if (!selectedProc) return;
1758
- setIsLoading(true);
1759
- setLoadingMessage(`Stopping process ${selectedProc.pid}...`);
1760
- setTimeout(async () => {
1761
- try {
1762
- await stopScript(selectedProc.pid);
1763
- await loadRunningProcesses();
1764
- setIsLoading(false);
1765
- } catch (err) {
1766
- setIsLoading(false);
1767
- setError(err instanceof Error ? err.message : String(err));
1914
+ // Add agent
1915
+ if (input === 'a') {
1916
+ setShowAddAgentModal(true);
1917
+ setAgentInputField('name');
1918
+ return;
1919
+ }
1920
+ // Launch agent
1921
+ if (key.return || input === 'l') {
1922
+ const agent = agents[selectedAgentIndex];
1923
+ if (agent && selectedProject) {
1924
+ const success = launchAgentInTerminal(agent, selectedProject.path);
1925
+ if (success) {
1926
+ setError(null);
1927
+ } else {
1928
+ setError('Failed to launch agent');
1768
1929
  }
1769
- }, 100);
1930
+ }
1770
1931
  return;
1771
1932
  }
1772
- if (input === 'X' && runningProcesses.length > 0) {
1773
- // Stop ALL processes
1774
- setIsLoading(true);
1775
- setLoadingMessage('Stopping all processes...');
1776
- setTimeout(async () => {
1777
- try {
1778
- for (const proc of runningProcesses) {
1779
- await stopScript(proc.pid);
1780
- }
1781
- await loadRunningProcesses();
1782
- setIsLoading(false);
1783
- } catch (err) {
1784
- setIsLoading(false);
1785
- setError(err instanceof Error ? err.message : String(err));
1933
+ // Delete agent
1934
+ if (input === 'd') {
1935
+ const agent = agents[selectedAgentIndex];
1936
+ if (agent) {
1937
+ const success = deleteAgent(agent.id);
1938
+ if (success && selectedProject) {
1939
+ loadAgentsForProject(selectedProject.id);
1940
+ } else {
1941
+ setError('Failed to delete agent');
1786
1942
  }
1787
- }, 100);
1943
+ }
1944
+ return;
1945
+ }
1946
+ // Back to projects
1947
+ if (key.escape) {
1948
+ setCurrentView('projects');
1788
1949
  return;
1789
1950
  }
1790
1951
  }
@@ -1835,14 +1996,27 @@ const App: React.FC = () => {
1835
1996
  return;
1836
1997
  }
1837
1998
  if (input === '2') {
1838
- setCurrentView('workspaces');
1999
+ setCurrentView('tasks');
1839
2000
  return;
1840
2001
  }
1841
2002
  if (input === '3') {
1842
- setCurrentView('processes');
2003
+ if (selectedProject) {
2004
+ setCurrentView('agents');
2005
+ loadAgentsForProject(selectedProject.id);
2006
+ } else {
2007
+ setError('Select a project first to manage agents');
2008
+ }
1843
2009
  return;
1844
2010
  }
1845
2011
  if (input === '4') {
2012
+ setCurrentView('processes');
2013
+ return;
2014
+ }
2015
+ if (input === '5') {
2016
+ setCurrentView('workspaces');
2017
+ return;
2018
+ }
2019
+ if (input === '6') {
1846
2020
  setCurrentView('settings');
1847
2021
  return;
1848
2022
  }
@@ -2557,6 +2731,229 @@ const App: React.FC = () => {
2557
2731
  );
2558
2732
  };
2559
2733
 
2734
+ // Load tasks when switching to tasks view
2735
+ useEffect(() => {
2736
+ if (currentView === 'tasks' && selectedProject) {
2737
+ loadTasksForProject();
2738
+ }
2739
+ }, [currentView, selectedProject]);
2740
+
2741
+ const loadTasksForProject = async () => {
2742
+ if (!selectedProject) return;
2743
+ try {
2744
+ const ports = [38124, 38125, 38126];
2745
+ let apiBaseUrl = '';
2746
+ for (const port of ports) {
2747
+ try {
2748
+ const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(500) });
2749
+ if (res.ok) { apiBaseUrl = `http://localhost:${port}/api`; break; }
2750
+ } catch (e) { /* Ignore API port check errors */ }
2751
+ }
2752
+ if (!apiBaseUrl) return;
2753
+
2754
+ // Get todo lists
2755
+ const listsRes = await fetch(`${apiBaseUrl}/projects/${selectedProject.id}/todo-lists`);
2756
+ const lists = listsRes.ok ? (await listsRes.json()) as Array<{ id: number }> : [];
2757
+
2758
+ // Get tasks for each list
2759
+ const allTasks: TodoTask[] = [];
2760
+ for (const list of lists) {
2761
+ const tasksRes = await fetch(`${apiBaseUrl}/todo-lists/${list.id}/tasks`);
2762
+ if (tasksRes.ok) {
2763
+ const listTasks = (await tasksRes.json()) as TodoTask[];
2764
+ allTasks.push(...listTasks);
2765
+ }
2766
+ }
2767
+ } catch (e) { /* Ignore task loading errors */ }
2768
+ };
2769
+
2770
+ // Render Tasks view
2771
+ const renderTasksView = () => {
2772
+ if (!selectedProject) {
2773
+ return (
2774
+ <Box flexDirection="column" padding={2}>
2775
+ <Text bold color={colors.accentCyan}>Tasks</Text>
2776
+ <Text> </Text>
2777
+ <Text color={colors.textTertiary}>Select a project first to view tasks</Text>
2778
+ </Box>
2779
+ );
2780
+ }
2781
+
2782
+ return (
2783
+ <Box flexDirection="column" padding={1} height={availableHeight}>
2784
+ <Text bold color={colors.accentCyan}>
2785
+ Tasks for {selectedProject.name} ({tasks.length})
2786
+ </Text>
2787
+ <Text> </Text>
2788
+
2789
+ {tasks.length === 0 ? (
2790
+ <Box flexDirection="column">
2791
+ <Text color={colors.textTertiary}>No tasks found</Text>
2792
+ <Text> </Text>
2793
+ <Text color={colors.textSecondary}>Press 'a' to add a task</Text>
2794
+ </Box>
2795
+ ) : (
2796
+ <Box flexDirection="column">
2797
+ {tasks.map((task, idx) => {
2798
+ const isSelected = idx === selectedTaskIdx;
2799
+ const cfg = taskStatusConfig[task.status];
2800
+ return (
2801
+ <Box key={task.id}>
2802
+ <Text color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
2803
+ {isSelected ? '▶ ' : ' '}
2804
+ <Text color={cfg.color}>{cfg.icon}</Text> {truncateText(task.title, 40)}
2805
+ </Text>
2806
+ {task.worktree_path && <Text color={colors.accentGreen}> 🌿</Text>}
2807
+ </Box>
2808
+ );
2809
+ })}
2810
+ </Box>
2811
+ )}
2812
+
2813
+ <Text> </Text>
2814
+ <Text color={colors.textTertiary}>
2815
+ ↑↓/jk: navigate | a: add task | Space: toggle | d: delete
2816
+ </Text>
2817
+ </Box>
2818
+ );
2819
+ };
2820
+
2821
+ // Render Agents view
2822
+ const renderAgentsView = () => {
2823
+ const selectedAgent = agents[selectedAgentIndex];
2824
+ const isAgentRunning = selectedAgent
2825
+ ? runningAgentsList.some(r => r.agentId === selectedAgent.id)
2826
+ : false;
2827
+
2828
+ return (
2829
+ <Box flexDirection="column" padding={1} height={availableHeight}>
2830
+ {/* Add Agent Modal */}
2831
+ {showAddAgentModal && (
2832
+ <Box
2833
+ flexDirection="column"
2834
+ borderStyle="round"
2835
+ borderColor={colors.accentCyan}
2836
+ padding={1}
2837
+ width={60}
2838
+ >
2839
+ <Text bold color={colors.accentCyan}>Add New Agent</Text>
2840
+ <Text> </Text>
2841
+ <Box>
2842
+ <Text color={agentInputField === 'name' ? colors.accentCyan : colors.textSecondary}>
2843
+ Name: {agentInputField === 'name' ? '▶ ' : ' '}
2844
+ </Text>
2845
+ <Text color={colors.textPrimary}>{newAgentName || '_'}</Text>
2846
+ </Box>
2847
+ <Text> </Text>
2848
+ <Text color={agentInputField === 'cli_type' ? colors.accentCyan : colors.textSecondary}>
2849
+ CLI Type: {agentInputField === 'cli_type' ? '▶' : ''}
2850
+ </Text>
2851
+ {VALID_CLI_TYPES.map((cliType, idx) => (
2852
+ <Text
2853
+ key={cliType}
2854
+ color={idx === newAgentCliTypeIndex ? colors.accentGreen : colors.textTertiary}
2855
+ >
2856
+ {idx === newAgentCliTypeIndex ? ' ● ' : ' ○ '}
2857
+ {CLI_DISPLAY_NAMES[cliType]}
2858
+ </Text>
2859
+ ))}
2860
+ <Text> </Text>
2861
+ <Box>
2862
+ <Text color={agentInputField === 'model' ? colors.accentCyan : colors.textSecondary}>
2863
+ Model (optional): {agentInputField === 'model' ? '▶ ' : ' '}
2864
+ </Text>
2865
+ <Text color={colors.textPrimary}>{newAgentModel || '_'}</Text>
2866
+ </Box>
2867
+ <Text> </Text>
2868
+ <Box>
2869
+ <Text color={agentInputField === 'api_key' ? colors.accentCyan : colors.textSecondary}>
2870
+ API Key (optional): {agentInputField === 'api_key' ? '▶ ' : ' '}
2871
+ </Text>
2872
+ <Text color={colors.textPrimary}>
2873
+ {newAgentApiKey ? '*'.repeat(Math.min(newAgentApiKey.length, 20)) : '_'}
2874
+ </Text>
2875
+ </Box>
2876
+ <Text> </Text>
2877
+ <Text color={colors.textTertiary}>Tab: next field | ↑↓: select CLI | Enter: create | Esc: cancel</Text>
2878
+ </Box>
2879
+ )}
2880
+
2881
+ {!showAddAgentModal && (
2882
+ <>
2883
+ <Text bold color={colors.accentCyan}>
2884
+ Agents for {selectedProject?.name || 'No Project'} ({agents.length})
2885
+ </Text>
2886
+ <Text> </Text>
2887
+
2888
+ {agents.length === 0 ? (
2889
+ <Box flexDirection="column">
2890
+ <Text color={colors.textTertiary}>No agents configured for this project.</Text>
2891
+ <Text> </Text>
2892
+ <Text color={colors.textSecondary}>Press 'a' to add an agent.</Text>
2893
+ </Box>
2894
+ ) : (
2895
+ <Box flexDirection="row" height={availableHeight - 6}>
2896
+ {/* Agent List */}
2897
+ <Box flexDirection="column" width="40%" borderStyle="round" borderColor={colors.borderColor} padding={1}>
2898
+ {agents.map((agent, index) => {
2899
+ const isSelected = index === selectedAgentIndex;
2900
+ const running = runningAgentsList.some(r => r.agentId === agent.id);
2901
+ return (
2902
+ <Box key={agent.id}>
2903
+ <Text color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
2904
+ {isSelected ? '▶ ' : ' '}
2905
+ {running ? '● ' : '○ '}
2906
+ {truncateText(agent.name, 25)}
2907
+ </Text>
2908
+ </Box>
2909
+ );
2910
+ })}
2911
+ </Box>
2912
+ <Box width={1} />
2913
+ {/* Agent Details */}
2914
+ <Box flexDirection="column" width="60%" borderStyle="round" borderColor={colors.borderColor} padding={1}>
2915
+ {selectedAgent && (
2916
+ <>
2917
+ <Text bold color={colors.accentCyan}>{selectedAgent.name}</Text>
2918
+ <Text> </Text>
2919
+ <Text color={colors.textSecondary}>
2920
+ Type: <Text color={colors.textPrimary}>{CLI_DISPLAY_NAMES[selectedAgent.cli_type]}</Text>
2921
+ </Text>
2922
+ {selectedAgent.model && (
2923
+ <Text color={colors.textSecondary}>
2924
+ Model: <Text color={colors.textPrimary}>{selectedAgent.model}</Text>
2925
+ </Text>
2926
+ )}
2927
+ {selectedAgent.api_key && (
2928
+ <Text color={colors.textSecondary}>
2929
+ API Key: <Text color={colors.accentGreen}>configured</Text>
2930
+ </Text>
2931
+ )}
2932
+ {selectedAgent.system_prompt && (
2933
+ <Text color={colors.textSecondary}>
2934
+ System Prompt: <Text color={colors.textPrimary}>{truncateText(selectedAgent.system_prompt, 40)}</Text>
2935
+ </Text>
2936
+ )}
2937
+ <Text> </Text>
2938
+ <Text color={isAgentRunning ? colors.accentGreen : colors.textTertiary}>
2939
+ Status: {isAgentRunning ? 'Running' : 'Stopped'}
2940
+ </Text>
2941
+ </>
2942
+ )}
2943
+ </Box>
2944
+ </Box>
2945
+ )}
2946
+
2947
+ <Text> </Text>
2948
+ <Text color={colors.textTertiary}>
2949
+ ↑↓/jk: navigate | a: add agent | Enter/l: launch | d: delete | Esc: back to projects
2950
+ </Text>
2951
+ </>
2952
+ )}
2953
+ </Box>
2954
+ );
2955
+ };
2956
+
2560
2957
  return (
2561
2958
  <Box flexDirection="column" height={terminalHeight}>
2562
2959
  {/* View indicator bar */}
@@ -2565,29 +2962,39 @@ const App: React.FC = () => {
2565
2962
  [1] Projects
2566
2963
  </Text>
2567
2964
  <Text> </Text>
2568
- <Text color={currentView === 'workspaces' ? colors.accentCyan : colors.textTertiary}>
2569
- [2] Workspaces
2965
+ <Text color={currentView === 'tasks' ? colors.accentCyan : colors.textTertiary}>
2966
+ [2] Tasks
2967
+ </Text>
2968
+ <Text> </Text>
2969
+ <Text color={currentView === 'agents' ? colors.accentCyan : colors.textTertiary}>
2970
+ [3] Agents
2570
2971
  </Text>
2571
2972
  <Text> </Text>
2572
2973
  <Text color={currentView === 'processes' ? colors.accentCyan : colors.textTertiary}>
2573
- [3] Processes
2974
+ [4] Processes
2975
+ </Text>
2976
+ <Text> </Text>
2977
+ <Text color={currentView === 'workspaces' ? colors.accentCyan : colors.textTertiary}>
2978
+ [5] Workspaces
2574
2979
  </Text>
2575
2980
  <Text> </Text>
2576
2981
  <Text color={currentView === 'settings' ? colors.accentCyan : colors.textTertiary}>
2577
- [4] Settings
2982
+ [6] Settings
2578
2983
  </Text>
2579
- {showTerminalPanel && (
2984
+ {runningAgentsList.length > 0 && (
2580
2985
  <>
2581
2986
  <Text> | </Text>
2582
- <Text color={colors.accentGreen}>Terminal [T]</Text>
2987
+ <Text color={colors.accentGreen}>● {runningAgentsList.length} running</Text>
2583
2988
  </>
2584
2989
  )}
2585
2990
  </Box>
2586
2991
 
2587
2992
  {/* Main content based on current view */}
2588
2993
  {currentView === 'projects' && renderProjectsView()}
2589
- {currentView === 'workspaces' && renderWorkspacesView()}
2994
+ {currentView === 'tasks' && renderTasksView()}
2995
+ {currentView === 'agents' && renderAgentsView()}
2590
2996
  {currentView === 'processes' && renderProcessesView()}
2997
+ {currentView === 'workspaces' && renderWorkspacesView()}
2591
2998
  {currentView === 'settings' && renderSettingsView()}
2592
2999
 
2593
3000
  {/* Status bar */}