agentopia 1.0.0
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/.claude/settings.local.json +28 -0
- package/dist/app.d.ts +10 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +121 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/db/database.d.ts +5 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +39 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +621 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/auth.d.ts +13 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +733 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/agents.d.ts +3 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +1058 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/issues.d.ts +4 -0
- package/dist/routes/issues.d.ts.map +1 -0
- package/dist/routes/issues.js +946 -0
- package/dist/routes/issues.js.map +1 -0
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/knowledge.d.ts.map +1 -0
- package/dist/routes/knowledge.js +117 -0
- package/dist/routes/knowledge.js.map +1 -0
- package/dist/routes/memories.d.ts +3 -0
- package/dist/routes/memories.d.ts.map +1 -0
- package/dist/routes/memories.js +115 -0
- package/dist/routes/memories.js.map +1 -0
- package/dist/routes/messages.d.ts +3 -0
- package/dist/routes/messages.d.ts.map +1 -0
- package/dist/routes/messages.js +130 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +754 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/templates.d.ts +3 -0
- package/dist/routes/templates.d.ts.map +1 -0
- package/dist/routes/templates.js +117 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/ui.d.ts +3 -0
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +38 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/services/agent-hierarchy.d.ts +14 -0
- package/dist/services/agent-hierarchy.d.ts.map +1 -0
- package/dist/services/agent-hierarchy.js +58 -0
- package/dist/services/agent-hierarchy.js.map +1 -0
- package/dist/services/agent-issue-batch.d.ts +17 -0
- package/dist/services/agent-issue-batch.d.ts.map +1 -0
- package/dist/services/agent-issue-batch.js +57 -0
- package/dist/services/agent-issue-batch.js.map +1 -0
- package/dist/services/controller.d.ts +4 -0
- package/dist/services/controller.d.ts.map +1 -0
- package/dist/services/controller.js +237 -0
- package/dist/services/controller.js.map +1 -0
- package/dist/services/langgraph-runner.d.ts +33 -0
- package/dist/services/langgraph-runner.d.ts.map +1 -0
- package/dist/services/langgraph-runner.js +478 -0
- package/dist/services/langgraph-runner.js.map +1 -0
- package/dist/services/orchestrator.d.ts +9 -0
- package/dist/services/orchestrator.d.ts.map +1 -0
- package/dist/services/orchestrator.js +116 -0
- package/dist/services/orchestrator.js.map +1 -0
- package/dist/services/pre-controller.d.ts +7 -0
- package/dist/services/pre-controller.d.ts.map +1 -0
- package/dist/services/pre-controller.js +101 -0
- package/dist/services/pre-controller.js.map +1 -0
- package/dist/services/process-manager.d.ts +67 -0
- package/dist/services/process-manager.d.ts.map +1 -0
- package/dist/services/process-manager.js +938 -0
- package/dist/services/process-manager.js.map +1 -0
- package/dist/services/project-permissions.d.ts +84 -0
- package/dist/services/project-permissions.d.ts.map +1 -0
- package/dist/services/project-permissions.js +129 -0
- package/dist/services/project-permissions.js.map +1 -0
- package/dist/services/scheduler.d.ts +6 -0
- package/dist/services/scheduler.d.ts.map +1 -0
- package/dist/services/scheduler.js +300 -0
- package/dist/services/scheduler.js.map +1 -0
- package/dist/services/system-prompt.d.ts +3 -0
- package/dist/services/system-prompt.d.ts.map +1 -0
- package/dist/services/system-prompt.js +285 -0
- package/dist/services/system-prompt.js.map +1 -0
- package/dist/services/terminal.d.ts +18 -0
- package/dist/services/terminal.d.ts.map +1 -0
- package/dist/services/terminal.js +222 -0
- package/dist/services/terminal.js.map +1 -0
- package/dist/services/websocket.d.ts +15 -0
- package/dist/services/websocket.d.ts.map +1 -0
- package/dist/services/websocket.js +204 -0
- package/dist/services/websocket.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/env.ini +18 -0
- package/package.json +38 -0
- package/project_id +0 -0
- package/public/admin-users.html +188 -0
- package/public/agent.html +199 -0
- package/public/css/issues.css +275 -0
- package/public/css/style.css +1299 -0
- package/public/index.html +166 -0
- package/public/issue.html +76 -0
- package/public/js/agent.js +19 -0
- package/public/js/common.js +735 -0
- package/public/js/dashboard.js +772 -0
- package/public/js/files-panel.js +703 -0
- package/public/js/interactive-terminal.js +201 -0
- package/public/js/issue-renderer.js +559 -0
- package/public/js/issue.js +57 -0
- package/public/js/project.js +2425 -0
- package/public/js/terminal.js +564 -0
- package/public/project.html +430 -0
- package/public/terminal.html +67 -0
- package/public/vendor/marked.js +74 -0
- package/public/vendor/xterm-addon-fit.js +2 -0
- package/public/vendor/xterm.css +209 -0
- package/public/vendor/xterm.js +2 -0
- package/send_message_and_update_issue.js +65 -0
- package/tsconfig.json +19 -0
- package/update_round2_and_create_round3.js +284 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
const agentId = window.location.pathname.split('/').pop();
|
|
2
|
+
let term = null;
|
|
3
|
+
let fitAddon = null;
|
|
4
|
+
let lastLogId = 0;
|
|
5
|
+
|
|
6
|
+
// ─── Activity Summary ───
|
|
7
|
+
const MAX_ACTIVITIES = 20;
|
|
8
|
+
const activities = [];
|
|
9
|
+
let lastActivityIsActive = false;
|
|
10
|
+
|
|
11
|
+
function parseToolActivity(content) {
|
|
12
|
+
// Match [Tool: ToolName] {json_input}
|
|
13
|
+
const m = content.match(/^\[Tool: (\w+)\]\s*(.*)$/);
|
|
14
|
+
if (!m) return null;
|
|
15
|
+
const tool = m[1];
|
|
16
|
+
let detail = '';
|
|
17
|
+
try {
|
|
18
|
+
const input = JSON.parse(m[2]);
|
|
19
|
+
switch (tool) {
|
|
20
|
+
case 'Read':
|
|
21
|
+
detail = 'Read file <code>' + escHtml(basename(input.file_path || '')) + '</code>';
|
|
22
|
+
break;
|
|
23
|
+
case 'Edit':
|
|
24
|
+
detail = 'Edit file <code>' + escHtml(basename(input.file_path || '')) + '</code>';
|
|
25
|
+
break;
|
|
26
|
+
case 'Write':
|
|
27
|
+
detail = 'Write file <code>' + escHtml(basename(input.file_path || '')) + '</code>';
|
|
28
|
+
break;
|
|
29
|
+
case 'Bash':
|
|
30
|
+
detail = 'Run command <code>' + escHtml((input.command || '').slice(0, 60)) + '</code>';
|
|
31
|
+
break;
|
|
32
|
+
case 'Grep':
|
|
33
|
+
detail = 'Search <code>' + escHtml((input.pattern || '').slice(0, 40)) + '</code>';
|
|
34
|
+
break;
|
|
35
|
+
case 'Glob':
|
|
36
|
+
detail = 'Find files <code>' + escHtml((input.pattern || '').slice(0, 40)) + '</code>';
|
|
37
|
+
break;
|
|
38
|
+
case 'Agent':
|
|
39
|
+
detail = 'Delegate subtask ' + escHtml((input.description || '').slice(0, 50));
|
|
40
|
+
break;
|
|
41
|
+
case 'WebFetch':
|
|
42
|
+
detail = 'Fetch URL <code>' + escHtml((input.url || '').slice(0, 50)) + '</code>';
|
|
43
|
+
break;
|
|
44
|
+
case 'WebSearch':
|
|
45
|
+
detail = 'Web search <code>' + escHtml((input.query || '').slice(0, 40)) + '</code>';
|
|
46
|
+
break;
|
|
47
|
+
case 'NotebookEdit':
|
|
48
|
+
detail = 'Edit notebook <code>' + escHtml(basename(input.notebook_path || '')) + '</code>';
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
detail = 'Invoke tool ' + escHtml(tool);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
detail = 'Invoke tool ' + escHtml(tool);
|
|
55
|
+
}
|
|
56
|
+
return { tool, detail };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const TOOL_ICONS = {
|
|
60
|
+
Read: '📖', Edit: '✏️', Write: '📝', Bash: '⚡', Grep: '🔍',
|
|
61
|
+
Glob: '📁', Agent: '🤖', WebFetch: '🌐', WebSearch: '🔎', NotebookEdit: '📓',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function addActivity(tool, detail) {
|
|
65
|
+
// Mark previous active item as done
|
|
66
|
+
if (lastActivityIsActive && activities.length > 0) {
|
|
67
|
+
activities[activities.length - 1].active = false;
|
|
68
|
+
}
|
|
69
|
+
activities.push({ tool, detail, time: new Date(), active: true });
|
|
70
|
+
if (activities.length > MAX_ACTIVITIES) activities.shift();
|
|
71
|
+
lastActivityIsActive = true;
|
|
72
|
+
renderActivities();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function completeLastActivity() {
|
|
76
|
+
if (lastActivityIsActive && activities.length > 0) {
|
|
77
|
+
activities[activities.length - 1].active = false;
|
|
78
|
+
lastActivityIsActive = false;
|
|
79
|
+
renderActivities();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderActivities() {
|
|
84
|
+
const panel = document.getElementById('activity-panel');
|
|
85
|
+
const list = document.getElementById('activity-list');
|
|
86
|
+
const count = document.getElementById('activity-count');
|
|
87
|
+
if (!panel || !list) return;
|
|
88
|
+
|
|
89
|
+
if (activities.length === 0) {
|
|
90
|
+
panel.style.display = 'none';
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
panel.style.display = '';
|
|
94
|
+
count.textContent = activities.length + ' activities';
|
|
95
|
+
|
|
96
|
+
// Render newest first
|
|
97
|
+
list.innerHTML = activities.slice().reverse().map(a => {
|
|
98
|
+
const icon = TOOL_ICONS[a.tool] || '🔧';
|
|
99
|
+
const cls = a.active ? 'activity-item active' : 'activity-item';
|
|
100
|
+
const timeStr = a.time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
101
|
+
return `<div class="${cls}"><span class="activity-icon">${icon}</span><span class="activity-text">${a.detail}</span><span class="activity-time">${timeStr}</span></div>`;
|
|
102
|
+
}).join('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function escHtml(s) {
|
|
106
|
+
const d = document.createElement('div');
|
|
107
|
+
d.textContent = s;
|
|
108
|
+
return d.innerHTML;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function basename(p) {
|
|
112
|
+
return p ? p.split('/').pop() : '';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function initTerminal() {
|
|
116
|
+
const cs = getComputedStyle(document.documentElement);
|
|
117
|
+
term = new Terminal({
|
|
118
|
+
theme: {
|
|
119
|
+
background: cs.getPropertyValue('--bg').trim() || '#fdf6e3',
|
|
120
|
+
foreground: cs.getPropertyValue('--fg').trim() || '#073642',
|
|
121
|
+
cursor: cs.getPropertyValue('--accent').trim() || '#268bd2',
|
|
122
|
+
selectionBackground: cs.getPropertyValue('--selected-bg').trim() || '#e8dcc8',
|
|
123
|
+
},
|
|
124
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
125
|
+
fontSize: 13,
|
|
126
|
+
scrollback: 10000,
|
|
127
|
+
convertEol: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
fitAddon = new FitAddon.FitAddon();
|
|
131
|
+
term.loadAddon(fitAddon);
|
|
132
|
+
term.open(document.getElementById('terminal'));
|
|
133
|
+
fitAddon.fit();
|
|
134
|
+
window.addEventListener('resize', () => fitAddon.fit());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function writeLog(log, skipActivity) {
|
|
138
|
+
if (log.stream === 'stdin') {
|
|
139
|
+
const preview = log.content.replace(/\n/g, ' ').slice(0, 100);
|
|
140
|
+
term.writeln('\x1b[36m--- Input Prompt (' + log.content.length + ' chars) ---\x1b[0m');
|
|
141
|
+
term.writeln('\x1b[2m' + preview + '...\x1b[0m');
|
|
142
|
+
term.writeln('\x1b[36m--- Output ---\x1b[0m');
|
|
143
|
+
} else if (log.stream === 'cost') {
|
|
144
|
+
return; // Don't display cost data in terminal
|
|
145
|
+
} else if (log.stream === 'stderr') {
|
|
146
|
+
// Skip proxychains noise
|
|
147
|
+
if (log.content.includes('proxychains')) return;
|
|
148
|
+
term.writeln('\x1b[31m' + log.content.trimEnd() + '\x1b[0m');
|
|
149
|
+
} else {
|
|
150
|
+
// Skip proxychains noise in stdout too
|
|
151
|
+
if (log.content.includes('Executing through proxy:') || log.content.includes('Port 7897')) return;
|
|
152
|
+
|
|
153
|
+
// Parse activity from tool calls (only for real-time, not history)
|
|
154
|
+
if (!skipActivity) {
|
|
155
|
+
const trimmed = log.content.trim();
|
|
156
|
+
if (trimmed.startsWith('[Tool: ')) {
|
|
157
|
+
const parsed = parseToolActivity(trimmed);
|
|
158
|
+
if (parsed) addActivity(parsed.tool, parsed.detail);
|
|
159
|
+
} else if (trimmed.startsWith('[Result]')) {
|
|
160
|
+
completeLastActivity();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Use writeln per line for reliable display
|
|
165
|
+
const lines = log.content.split('\n');
|
|
166
|
+
lines.forEach((line, i) => {
|
|
167
|
+
if (i < lines.length - 1 || line) {
|
|
168
|
+
term.writeln(line);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
term.scrollToBottom();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Poll logs via HTTP (real-time, every 1 second) ───
|
|
176
|
+
|
|
177
|
+
async function pollLogs() {
|
|
178
|
+
try {
|
|
179
|
+
const res = await fetch(`/api/agents/${agentId}/logs?limit=200`, { headers: apiHeaders() });
|
|
180
|
+
if (!res.ok) return;
|
|
181
|
+
const logs = await res.json();
|
|
182
|
+
if (!logs.length) return;
|
|
183
|
+
|
|
184
|
+
const newLogs = logs.filter(l => l.id > lastLogId);
|
|
185
|
+
if (newLogs.length > 0) {
|
|
186
|
+
// Display oldest first
|
|
187
|
+
newLogs.reverse().forEach(writeLog);
|
|
188
|
+
lastLogId = Math.max(...logs.map(l => l.id));
|
|
189
|
+
}
|
|
190
|
+
} catch (e) { console.error('Failed to poll logs', e); }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Initial load: fetch history, grouped by run
|
|
194
|
+
async function loadHistory() {
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch(`/api/agents/${agentId}/logs?limit=500`, { headers: apiHeaders() });
|
|
197
|
+
if (!res.ok) return;
|
|
198
|
+
const logs = await res.json();
|
|
199
|
+
if (!logs.length) {
|
|
200
|
+
term.writeln('\x1b[2m(no logs yet)\x1b[0m');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
lastLogId = Math.max(...logs.map(l => l.id));
|
|
204
|
+
|
|
205
|
+
// Group by run_id, display with separators
|
|
206
|
+
// Parse activities from last run only (most recent context)
|
|
207
|
+
logs.reverse();
|
|
208
|
+
let currentRun = null;
|
|
209
|
+
let runIndex = 0;
|
|
210
|
+
const lastRunId = logs.length > 0 ? logs[logs.length - 1].run_id : null;
|
|
211
|
+
logs.forEach(log => {
|
|
212
|
+
if (log.run_id !== currentRun) {
|
|
213
|
+
currentRun = log.run_id;
|
|
214
|
+
runIndex++;
|
|
215
|
+
if (runIndex > 1) term.writeln('');
|
|
216
|
+
term.writeln('\x1b[33m━━━ Run #' + runIndex + ' ━━━\x1b[0m');
|
|
217
|
+
}
|
|
218
|
+
// Parse activities from last run for initial context
|
|
219
|
+
const isLastRun = log.run_id === lastRunId;
|
|
220
|
+
if (isLastRun && log.stream === 'stdout') {
|
|
221
|
+
const trimmed = log.content.trim();
|
|
222
|
+
if (trimmed.startsWith('[Tool: ')) {
|
|
223
|
+
const parsed = parseToolActivity(trimmed);
|
|
224
|
+
if (parsed) {
|
|
225
|
+
activities.push({ tool: parsed.tool, detail: parsed.detail, time: parseServerDate(log.created_at) || new Date(), active: false });
|
|
226
|
+
if (activities.length > MAX_ACTIVITIES) activities.shift();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
writeLog(log, true);
|
|
231
|
+
});
|
|
232
|
+
renderActivities();
|
|
233
|
+
term.writeln('\r\n\x1b[36m--- End of history ---\x1b[0m\r\n');
|
|
234
|
+
} catch (e) { console.error('Failed to load history', e); }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Agent Info ───
|
|
238
|
+
|
|
239
|
+
async function loadAgentInfo() {
|
|
240
|
+
try {
|
|
241
|
+
const res = await fetch(`/api/agents/${agentId}`, { headers: apiHeaders() });
|
|
242
|
+
if (!res.ok) return;
|
|
243
|
+
const agent = await res.json();
|
|
244
|
+
|
|
245
|
+
document.getElementById('agent-name').textContent = agent.name;
|
|
246
|
+
document.getElementById('agent-title').textContent = agent.name;
|
|
247
|
+
document.getElementById('agent-role').textContent = agent.role || '-';
|
|
248
|
+
document.getElementById('agent-type').textContent = agent.is_controller ? 'Controller' : 'Worker';
|
|
249
|
+
document.getElementById('agent-status-text').textContent = agent.status + (agent.pid ? ` (PID: ${agent.pid})` : '');
|
|
250
|
+
document.getElementById('agent-started').textContent = formatLocalDateTime(agent.started_at);
|
|
251
|
+
document.getElementById('agent-status').textContent = agent.status;
|
|
252
|
+
document.getElementById('agent-status').className = `status-badge status-${agent.status}`;
|
|
253
|
+
document.title = `Argus - ${agent.name}`;
|
|
254
|
+
window.currentAgentState = agent;
|
|
255
|
+
if (window.AgentFiles && typeof window.AgentFiles.setAgent === 'function') {
|
|
256
|
+
window.AgentFiles.setAgent(agent);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
document.getElementById('project-link').href = `/projects/${agent.project_id}`;
|
|
260
|
+
// Load project name for breadcrumb
|
|
261
|
+
if (!window._projectLoaded) {
|
|
262
|
+
window._projectLoaded = true;
|
|
263
|
+
fetch(`/api/projects/${agent.project_id}`, { headers: apiHeaders() })
|
|
264
|
+
.then(r => r.ok ? r.json() : null)
|
|
265
|
+
.then(p => { if (p) document.getElementById('project-link').textContent = p.name; })
|
|
266
|
+
.catch((e) => { console.error('Failed to load project name', e); });
|
|
267
|
+
}
|
|
268
|
+
document.getElementById('btn-start').style.display = agent.status === 'running' ? 'none' : '';
|
|
269
|
+
document.getElementById('btn-stop').style.display = agent.status === 'running' ? '' : 'none';
|
|
270
|
+
document.getElementById('thinking-indicator').style.display = agent.status === 'running' ? '' : 'none';
|
|
271
|
+
const retryBtn = document.getElementById('btn-retry');
|
|
272
|
+
if (retryBtn) {
|
|
273
|
+
retryBtn.style.display = (agent.status === 'error' && agent.last_prompt) ? '' : 'none';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!window._instructionsLoaded) {
|
|
277
|
+
document.getElementById('agent-instructions').value = agent.custom_instructions || '';
|
|
278
|
+
document.getElementById('agent-workdir').value = agent.working_directory || '';
|
|
279
|
+
const maxRunsEl = document.getElementById('agent-maxruns');
|
|
280
|
+
if (maxRunsEl) maxRunsEl.value = agent.session_max_runs ?? 10;
|
|
281
|
+
const maxTokensEl = document.getElementById('agent-maxtokens');
|
|
282
|
+
if (maxTokensEl) maxTokensEl.value = agent.session_max_tokens ?? 200000;
|
|
283
|
+
const resumeTimeoutEl = document.getElementById('agent-resumetimeout');
|
|
284
|
+
if (resumeTimeoutEl) resumeTimeoutEl.value = agent.session_resume_timeout ?? 300;
|
|
285
|
+
window._instructionsLoaded = true;
|
|
286
|
+
}
|
|
287
|
+
} catch (e) { console.error('Failed to load agent info', e); }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const refreshAgentInfo = loadAgentInfo;
|
|
291
|
+
|
|
292
|
+
// ─── Actions ───
|
|
293
|
+
|
|
294
|
+
async function quickStart() {
|
|
295
|
+
const btn = document.getElementById('btn-start');
|
|
296
|
+
await withLoading(btn, async () => {
|
|
297
|
+
const res = await fetch(`/api/agents/${agentId}/start`, {
|
|
298
|
+
method: 'POST', headers: apiHeaders(), body: JSON.stringify({}),
|
|
299
|
+
});
|
|
300
|
+
if (res.ok) { loadAgentInfo(); showToast('Agent started', 'success'); }
|
|
301
|
+
else { const err = await res.json(); showToast(err.error || 'Failed to start', 'error'); }
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function saveWorkdir() {
|
|
306
|
+
const val = document.getElementById('agent-workdir').value.trim();
|
|
307
|
+
const btn = document.querySelector('button[onclick="saveWorkdir()"]');
|
|
308
|
+
await withLoading(btn, async () => {
|
|
309
|
+
const res = await fetch(`/api/agents/${agentId}`, {
|
|
310
|
+
method: 'PUT', headers: apiHeaders(), body: JSON.stringify({ working_directory: val || null }),
|
|
311
|
+
});
|
|
312
|
+
if (res.ok) {
|
|
313
|
+
showToast('Saved', 'success');
|
|
314
|
+
loadAgentInfo();
|
|
315
|
+
if (window.AgentFiles && typeof window.AgentFiles.handleWorkingDirectoryChange === 'function') {
|
|
316
|
+
window.AgentFiles.handleWorkingDirectoryChange();
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
showToast('Failed to save', 'error');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function saveMaxRuns() {
|
|
325
|
+
const raw = parseInt(document.getElementById('agent-maxruns').value);
|
|
326
|
+
const val = Number.isNaN(raw) ? 10 : raw;
|
|
327
|
+
const btn = document.querySelector('button[onclick="saveMaxRuns()"]');
|
|
328
|
+
await withLoading(btn, async () => {
|
|
329
|
+
const res = await fetch(`/api/agents/${agentId}`, {
|
|
330
|
+
method: 'PUT', headers: apiHeaders(), body: JSON.stringify({ session_max_runs: Math.max(1, val) }),
|
|
331
|
+
});
|
|
332
|
+
if (res.ok) showToast('Saved', 'success');
|
|
333
|
+
else showToast('Failed to save', 'error');
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function saveMaxTokens() {
|
|
338
|
+
const raw = parseInt(document.getElementById('agent-maxtokens').value);
|
|
339
|
+
const val = Number.isNaN(raw) ? 0 : raw;
|
|
340
|
+
const btn = document.querySelector('button[onclick="saveMaxTokens()"]');
|
|
341
|
+
await withLoading(btn, async () => {
|
|
342
|
+
const res = await fetch(`/api/agents/${agentId}`, {
|
|
343
|
+
method: 'PUT', headers: apiHeaders(), body: JSON.stringify({ session_max_tokens: Math.max(0, val) }),
|
|
344
|
+
});
|
|
345
|
+
if (res.ok) showToast('Saved', 'success');
|
|
346
|
+
else showToast('Failed to save', 'error');
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function saveResumeTimeout() {
|
|
351
|
+
const raw = parseInt(document.getElementById('agent-resumetimeout').value);
|
|
352
|
+
const val = Number.isNaN(raw) ? 300 : raw;
|
|
353
|
+
const btn = document.querySelector('button[onclick="saveResumeTimeout()"]');
|
|
354
|
+
await withLoading(btn, async () => {
|
|
355
|
+
const res = await fetch(`/api/agents/${agentId}`, {
|
|
356
|
+
method: 'PUT', headers: apiHeaders(), body: JSON.stringify({ session_resume_timeout: Math.max(0, val) }),
|
|
357
|
+
});
|
|
358
|
+
if (res.ok) showToast('Saved', 'success');
|
|
359
|
+
else showToast('Failed to save', 'error');
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function showStartModal() { document.getElementById('startModal').classList.add('active'); }
|
|
364
|
+
function hideModal() { document.getElementById('startModal').classList.remove('active'); }
|
|
365
|
+
|
|
366
|
+
async function startAgent() {
|
|
367
|
+
const btn = document.querySelector('#startModal button[onclick="startAgent()"]');
|
|
368
|
+
await withLoading(btn, async () => {
|
|
369
|
+
const prompt = document.getElementById('start-prompt').value.trim();
|
|
370
|
+
const body = prompt ? { prompt } : {};
|
|
371
|
+
const res = await fetch(`/api/agents/${agentId}/start`, {
|
|
372
|
+
method: 'POST', headers: apiHeaders(), body: JSON.stringify(body),
|
|
373
|
+
});
|
|
374
|
+
if (res.ok) { hideModal(); loadAgentInfo(); showToast('Agent started', 'success'); }
|
|
375
|
+
else { const err = await res.json(); showToast(err.error || 'Failed to start', 'error'); }
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function retryAgent() {
|
|
380
|
+
const btn = document.getElementById('btn-retry');
|
|
381
|
+
await withLoading(btn, async () => {
|
|
382
|
+
const res = await fetch(`/api/agents/${agentId}/retry`, {
|
|
383
|
+
method: 'POST', headers: apiHeaders(), body: JSON.stringify({}),
|
|
384
|
+
});
|
|
385
|
+
if (res.ok) { loadAgentInfo(); showToast('Agent retried', 'success'); }
|
|
386
|
+
else { const err = await res.json().catch(() => ({})); showToast(err.error || 'Failed to retry', 'error'); }
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function stopAgent() {
|
|
391
|
+
if (!await showConfirm('Stop this agent?')) return;
|
|
392
|
+
const btn = document.getElementById('btn-stop');
|
|
393
|
+
await withLoading(btn, async () => {
|
|
394
|
+
const res = await fetch(`/api/agents/${agentId}/stop`, { method: 'POST', headers: apiHeaders(), body: '{}' });
|
|
395
|
+
if (res.ok) { showToast('Agent stopped', 'success'); } else { const e = await res.json().catch(() => ({})); showToast(e.error || 'Failed to stop', 'error'); }
|
|
396
|
+
loadAgentInfo();
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function clearTerminal() { if (term) { term.clear(); } }
|
|
401
|
+
|
|
402
|
+
function openTerminal() {
|
|
403
|
+
window.location.href = `/terminal?agentId=${agentId}&newSession=true`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function saveInstructions() {
|
|
407
|
+
const btn = document.querySelector('button[onclick="saveInstructions()"]');
|
|
408
|
+
await withLoading(btn, async () => {
|
|
409
|
+
const val = document.getElementById('agent-instructions').value;
|
|
410
|
+
const res = await fetch(`/api/agents/${agentId}`, {
|
|
411
|
+
method: 'PUT', headers: apiHeaders(), body: JSON.stringify({ custom_instructions: val }),
|
|
412
|
+
});
|
|
413
|
+
if (res.ok) showToast('Saved', 'success');
|
|
414
|
+
else showToast('Failed to save', 'error');
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function loadSystemPromptPreview() {
|
|
419
|
+
const el = document.getElementById('system-prompt-preview');
|
|
420
|
+
if (el.textContent) return;
|
|
421
|
+
try {
|
|
422
|
+
const res = await fetch(`/api/agents/${agentId}/system-prompt`, { headers: apiHeaders() });
|
|
423
|
+
if (res.ok) { const data = await res.json(); el.textContent = data.prompt; }
|
|
424
|
+
} catch (e) { console.error('Failed to load system prompt', e); }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function toggleSystemPrompt() {
|
|
428
|
+
const el = document.getElementById('system-prompt-preview');
|
|
429
|
+
if (el.style.display === 'none') { el.style.display = ''; loadSystemPromptPreview(); }
|
|
430
|
+
else { el.style.display = 'none'; }
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ─── Messages ───
|
|
434
|
+
|
|
435
|
+
let allAgents = [];
|
|
436
|
+
|
|
437
|
+
async function loadMessages() {
|
|
438
|
+
try {
|
|
439
|
+
const res = await fetch(`/api/agents/${agentId}/messages?limit=30`, { headers: apiHeaders() });
|
|
440
|
+
if (!res.ok) return;
|
|
441
|
+
const data = await res.json();
|
|
442
|
+
const messages = data.messages || [];
|
|
443
|
+
const unread = messages.filter(m => m.status === 'unread').length;
|
|
444
|
+
|
|
445
|
+
const badge = document.getElementById('unread-badge');
|
|
446
|
+
if (unread > 0) {
|
|
447
|
+
badge.textContent = unread;
|
|
448
|
+
badge.style.display = '';
|
|
449
|
+
} else {
|
|
450
|
+
badge.style.display = 'none';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const container = document.getElementById('messages-list');
|
|
454
|
+
if (messages.length === 0) {
|
|
455
|
+
container.innerHTML = '<div style="color:var(--text-secondary);font-size:13px;padding:8px">No messages</div>';
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
container.innerHTML = messages.map(function(m) {
|
|
459
|
+
var isUnread = m.status === 'unread';
|
|
460
|
+
var date = m.created_at ? new Date(m.created_at + (m.created_at.includes('Z') ? '' : 'Z')).toLocaleString() : '';
|
|
461
|
+
return '<div style="padding:8px 12px;border-bottom:1px solid var(--border);' + (isUnread ? 'background:var(--selected-bg);' : '') + 'cursor:pointer" onclick="toggleMessageDetail(this,\'' + m.id + '\')">' +
|
|
462
|
+
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
463
|
+
'<span style="font-weight:' + (isUnread ? '600' : '400') + ';font-size:13px">' +
|
|
464
|
+
(isUnread ? '<span style="color:var(--accent);margin-right:4px">●</span>' : '') +
|
|
465
|
+
(m.from_name || m.from_agent_id.slice(0,8)) +
|
|
466
|
+
(m.subject ? ': ' + esc(m.subject) : '') +
|
|
467
|
+
'</span>' +
|
|
468
|
+
'<span style="font-size:11px;color:var(--text-secondary)">' + timeAgo(m.created_at) + '</span>' +
|
|
469
|
+
'</div>' +
|
|
470
|
+
'<div class="msg-detail" style="display:none;margin-top:8px;padding:8px;background:var(--bg);border-radius:4px;font-size:12px;white-space:pre-wrap">' + esc(m.body) + '</div>' +
|
|
471
|
+
'</div>';
|
|
472
|
+
}).join('');
|
|
473
|
+
} catch(e) {}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function toggleMessageDetail(el, msgId) {
|
|
477
|
+
var detail = el.querySelector('.msg-detail');
|
|
478
|
+
if (!detail) return;
|
|
479
|
+
var visible = detail.style.display !== 'none';
|
|
480
|
+
detail.style.display = visible ? 'none' : '';
|
|
481
|
+
if (!visible) {
|
|
482
|
+
// Mark as read
|
|
483
|
+
fetch('/api/agents/' + agentId + '/messages/' + msgId, {
|
|
484
|
+
method: 'PUT', headers: apiHeaders()
|
|
485
|
+
}).then(function() { setTimeout(loadMessages, 500); });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function markAllRead() {
|
|
490
|
+
await fetch('/api/agents/' + agentId + '/messages/read-all', { method: 'POST', headers: apiHeaders() });
|
|
491
|
+
showToast('All messages marked as read', 'success');
|
|
492
|
+
loadMessages();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function showSendMessage() {
|
|
496
|
+
// Load agents if not loaded
|
|
497
|
+
if (allAgents.length === 0) {
|
|
498
|
+
try {
|
|
499
|
+
var aRes = await fetch('/api/agents/' + agentId, { headers: apiHeaders() });
|
|
500
|
+
var me = await aRes.json();
|
|
501
|
+
var pRes = await fetch('/api/projects/' + me.project_id + '/agents', { headers: apiHeaders() });
|
|
502
|
+
allAgents = await pRes.json();
|
|
503
|
+
} catch(e) {}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
var existing = document.getElementById('send-msg-dialog');
|
|
507
|
+
if (existing) { existing.remove(); return; }
|
|
508
|
+
var opts = allAgents.filter(function(a) { return a.id !== agentId; }).map(function(a) {
|
|
509
|
+
return '<option value="' + a.id + '">' + esc(a.name) + '</option>';
|
|
510
|
+
}).join('');
|
|
511
|
+
|
|
512
|
+
var div = document.createElement('div');
|
|
513
|
+
div.id = 'send-msg-dialog';
|
|
514
|
+
div.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--header-bg);border:1px solid var(--border);border-radius:8px;padding:16px;z-index:200;box-shadow:0 4px 12px rgba(0,0,0,0.3);min-width:350px';
|
|
515
|
+
div.innerHTML =
|
|
516
|
+
'<div style="font-weight:600;margin-bottom:12px">Send Message</div>' +
|
|
517
|
+
'<div style="margin-bottom:8px"><label style="font-size:12px;color:var(--text-secondary)">To</label>' +
|
|
518
|
+
'<select id="msg-to" style="width:100%;padding:4px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-size:12px;margin-top:2px">' + opts + '</select></div>' +
|
|
519
|
+
'<div style="margin-bottom:8px"><label style="font-size:12px;color:var(--text-secondary)">Subject</label>' +
|
|
520
|
+
'<input type="text" id="msg-subject" style="width:100%;padding:4px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-size:12px;margin-top:2px"></div>' +
|
|
521
|
+
'<div style="margin-bottom:12px"><label style="font-size:12px;color:var(--text-secondary)">Message</label>' +
|
|
522
|
+
'<textarea id="msg-body" rows="4" style="width:100%;padding:4px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-size:12px;margin-top:2px;font-family:inherit;resize:vertical"></textarea></div>' +
|
|
523
|
+
'<div style="display:flex;gap:8px;justify-content:flex-end">' +
|
|
524
|
+
'<button class="btn btn-sm" onclick="document.getElementById(\'send-msg-dialog\').remove()">Cancel</button>' +
|
|
525
|
+
'<button class="btn btn-sm btn-primary" onclick="sendMessage()">Send</button>' +
|
|
526
|
+
'</div>';
|
|
527
|
+
document.body.appendChild(div);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function sendMessage() {
|
|
531
|
+
var to = document.getElementById('msg-to').value;
|
|
532
|
+
var subject = document.getElementById('msg-subject').value;
|
|
533
|
+
var body = document.getElementById('msg-body').value;
|
|
534
|
+
if (!to || !body) { showToast('Recipient and message are required', 'error'); return; }
|
|
535
|
+
try {
|
|
536
|
+
var res = await fetch('/api/agents/' + agentId + '/messages/send', {
|
|
537
|
+
method: 'POST', headers: apiHeaders(),
|
|
538
|
+
body: JSON.stringify({ to: to, subject: subject, body: body })
|
|
539
|
+
});
|
|
540
|
+
if (res.ok) {
|
|
541
|
+
showToast('Message sent', 'success');
|
|
542
|
+
var dialog = document.getElementById('send-msg-dialog');
|
|
543
|
+
if (dialog) dialog.remove();
|
|
544
|
+
loadMessages();
|
|
545
|
+
} else {
|
|
546
|
+
var err = await res.json();
|
|
547
|
+
showToast(err.error || 'Failed to send', 'error');
|
|
548
|
+
}
|
|
549
|
+
} catch(e) { showToast('Network error', 'error'); }
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ─── Init ───
|
|
553
|
+
|
|
554
|
+
initTerminal();
|
|
555
|
+
loadAgentInfo();
|
|
556
|
+
loadHistory();
|
|
557
|
+
loadMessages();
|
|
558
|
+
|
|
559
|
+
// Poll every 1 second for new logs (real-time feel)
|
|
560
|
+
setInterval(pollLogs, 1000);
|
|
561
|
+
// Refresh agent info every 3 seconds
|
|
562
|
+
setInterval(loadAgentInfo, 3000);
|
|
563
|
+
// Refresh messages every 10 seconds
|
|
564
|
+
setInterval(loadMessages, 10000);
|