@yemi33/minions 0.1.11 → 0.1.13
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/CHANGELOG.md +60 -0
- package/dashboard/js/command-center.js +377 -0
- package/dashboard/js/command-history.js +70 -0
- package/dashboard/js/command-input.js +268 -0
- package/dashboard/js/command-parser.js +129 -0
- package/dashboard/js/detail-panel.js +98 -0
- package/dashboard/js/live-stream.js +69 -0
- package/dashboard/js/modal-qa.js +268 -0
- package/dashboard/js/modal.js +131 -0
- package/dashboard/js/refresh.js +59 -0
- package/dashboard/js/render-agents.js +17 -0
- package/dashboard/js/render-dispatch.js +148 -0
- package/dashboard/js/render-inbox.js +126 -0
- package/dashboard/js/render-kb.js +107 -0
- package/dashboard/js/render-other.js +181 -0
- package/dashboard/js/render-plans.js +304 -0
- package/dashboard/js/render-prd.js +469 -0
- package/dashboard/js/render-prs.js +94 -0
- package/dashboard/js/render-schedules.js +158 -0
- package/dashboard/js/render-skills.js +89 -0
- package/dashboard/js/render-work-items.js +219 -0
- package/dashboard/js/settings.js +135 -0
- package/dashboard/js/state.js +84 -0
- package/dashboard/js/utils.js +39 -0
- package/dashboard/layout.html +123 -0
- package/dashboard/pages/engine.html +12 -0
- package/dashboard/pages/home.html +31 -0
- package/dashboard/pages/inbox.html +17 -0
- package/dashboard/pages/plans.html +4 -0
- package/dashboard/pages/prd.html +5 -0
- package/dashboard/pages/prs.html +4 -0
- package/dashboard/pages/schedule.html +10 -0
- package/dashboard/pages/work.html +5 -0
- package/dashboard/styles.css +598 -0
- package/dashboard-build.js +51 -0
- package/dashboard.html +179 -107
- package/dashboard.js +51 -1
- package/engine/ado.js +14 -0
- package/engine/cli.js +11 -0
- package/engine/github.js +14 -0
- package/engine/lifecycle.js +25 -29
- package/engine.js +106 -19
- package/package.json +1 -1
- package/routing.md +1 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// command-input.js — Command input UI functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
// Auto-resize textarea
|
|
4
|
+
function cmdAutoResize() {
|
|
5
|
+
const ta = document.getElementById('cmd-input');
|
|
6
|
+
ta.style.height = 'auto';
|
|
7
|
+
ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function cmdUpdateHighlight() {
|
|
11
|
+
const text = document.getElementById('cmd-input').value;
|
|
12
|
+
const hl = document.getElementById('cmd-highlight');
|
|
13
|
+
if (!text) { hl.innerHTML = ''; return; }
|
|
14
|
+
// Escape HTML then wrap tokens with highlight spans
|
|
15
|
+
let html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
16
|
+
html = html.replace(/(\/(?:plan|note|decide)\b)/gi, '<span class="hl-cmd">$1</span>');
|
|
17
|
+
html = html.replace(/(@\w+)/g, '<span class="hl-mention">$1</span>');
|
|
18
|
+
html = html.replace(/(![a-z]+\b)/gi, '<span class="hl-priority">$1</span>');
|
|
19
|
+
html = html.replace(/(#\S+)/g, '<span class="hl-project">$1</span>');
|
|
20
|
+
html = html.replace(/(--(?:stack|parallel)\b)/gi, '<span class="hl-flag">$1</span>');
|
|
21
|
+
hl.innerHTML = html + '\n'; // trailing newline prevents layout shift
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function syncHighlightScroll() {
|
|
25
|
+
const ta = document.getElementById('cmd-input');
|
|
26
|
+
const hl = document.getElementById('cmd-highlight');
|
|
27
|
+
hl.scrollTop = ta.scrollTop;
|
|
28
|
+
hl.scrollLeft = ta.scrollLeft;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cmdInputChanged() {
|
|
32
|
+
cmdAutoResize();
|
|
33
|
+
cmdUpdateHighlight();
|
|
34
|
+
cmdRenderMeta();
|
|
35
|
+
|
|
36
|
+
// Check for @ mention or # project trigger
|
|
37
|
+
const input = document.getElementById('cmd-input');
|
|
38
|
+
const cursor = input.selectionStart;
|
|
39
|
+
const before = input.value.slice(0, cursor);
|
|
40
|
+
const atMatch = before.match(/@(\w*)$/);
|
|
41
|
+
const hashMatch = before.match(/#(\w*)$/);
|
|
42
|
+
if (atMatch) {
|
|
43
|
+
cmdShowMentions(atMatch[1]);
|
|
44
|
+
} else if (hashMatch) {
|
|
45
|
+
cmdShowProjects(hashMatch[1]);
|
|
46
|
+
} else {
|
|
47
|
+
cmdHidePopup();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cmdKeyDown(e) {
|
|
52
|
+
const popup = document.getElementById('cmd-mention-popup');
|
|
53
|
+
const isPopupVisible = popup.classList.contains('visible');
|
|
54
|
+
|
|
55
|
+
// Navigate mention popup with arrow keys
|
|
56
|
+
if (isPopupVisible) {
|
|
57
|
+
const items = popup.querySelectorAll('.cmd-mention-item');
|
|
58
|
+
if (items.length === 0) return;
|
|
59
|
+
|
|
60
|
+
if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
cmdMentionIdx = (cmdMentionIdx + 1) % items.length;
|
|
64
|
+
items.forEach((el, i) => el.classList.toggle('active', i === cmdMentionIdx));
|
|
65
|
+
items[cmdMentionIdx]?.scrollIntoView({ block: 'nearest' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
cmdMentionIdx = (cmdMentionIdx - 1 + items.length) % items.length;
|
|
72
|
+
items.forEach((el, i) => el.classList.toggle('active', i === cmdMentionIdx));
|
|
73
|
+
items[cmdMentionIdx]?.scrollIntoView({ block: 'nearest' });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (e.key === 'Enter' && !e.ctrlKey) {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
const active = items[cmdMentionIdx >= 0 ? cmdMentionIdx : 0];
|
|
80
|
+
if (active) cmdInsertPopupItem(active.dataset.id);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (e.key === 'Escape') {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
cmdHidePopup();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ArrowUp/ArrowDown to navigate command history (only when no popup visible)
|
|
91
|
+
if (e.key === 'ArrowUp' && !isPopupVisible) {
|
|
92
|
+
const input = document.getElementById('cmd-input');
|
|
93
|
+
// Only intercept if cursor is at start of input (or input is single-line)
|
|
94
|
+
if (input.selectionStart === 0 || !input.value.includes('\n')) {
|
|
95
|
+
const history = cmdGetHistory();
|
|
96
|
+
if (history.length === 0) return;
|
|
97
|
+
if (_cmdHistoryIdx === -1) _cmdHistoryDraft = input.value; // Save current draft
|
|
98
|
+
if (_cmdHistoryIdx < history.length - 1) {
|
|
99
|
+
_cmdHistoryIdx++;
|
|
100
|
+
input.value = history[_cmdHistoryIdx].text;
|
|
101
|
+
cmdAutoResize();
|
|
102
|
+
cmdRenderMeta();
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
// Move cursor to end
|
|
105
|
+
setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (e.key === 'ArrowDown' && !isPopupVisible) {
|
|
111
|
+
const input = document.getElementById('cmd-input');
|
|
112
|
+
if (input.selectionStart === input.value.length || !input.value.includes('\n')) {
|
|
113
|
+
if (_cmdHistoryIdx >= 0) {
|
|
114
|
+
_cmdHistoryIdx--;
|
|
115
|
+
const history = cmdGetHistory();
|
|
116
|
+
input.value = _cmdHistoryIdx >= 0 ? history[_cmdHistoryIdx].text : (_cmdHistoryDraft || '');
|
|
117
|
+
cmdAutoResize();
|
|
118
|
+
cmdRenderMeta();
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ctrl+Enter to submit
|
|
127
|
+
if (e.key === 'Enter' && e.ctrlKey) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
cmdSubmit();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function cmdSubmit() {
|
|
134
|
+
const input = document.getElementById('cmd-input');
|
|
135
|
+
const raw = input.value.trim();
|
|
136
|
+
if (!raw) return showToast('cmd-toast', 'Type something first', false);
|
|
137
|
+
|
|
138
|
+
// Route to Command Center panel
|
|
139
|
+
input.value = '';
|
|
140
|
+
if (!_ccOpen) toggleCommandCenter();
|
|
141
|
+
document.getElementById('cc-input').value = raw;
|
|
142
|
+
ccSend();
|
|
143
|
+
cmdSaveHistory(raw, 'cc');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Render the parsed meta chips below input
|
|
148
|
+
function cmdRenderMeta() {
|
|
149
|
+
const el = document.getElementById('cmd-meta');
|
|
150
|
+
const input = document.getElementById('cmd-input').value;
|
|
151
|
+
if (!input.trim()) { el.innerHTML = ''; return; }
|
|
152
|
+
|
|
153
|
+
const parsed = cmdParseInput(input);
|
|
154
|
+
let chips = [];
|
|
155
|
+
|
|
156
|
+
// Intent chip
|
|
157
|
+
if (parsed.intent === 'plan') {
|
|
158
|
+
const strategy = parsed.branchStrategy || 'parallel';
|
|
159
|
+
const stratLabel = strategy === 'parallel' ? 'parallel branches' : 'stacked';
|
|
160
|
+
chips.push('<span class="cmd-chip" style="background:var(--purple,#a855f7);color:#fff">Plan → PRD → Dispatch (' + stratLabel + ')</span>');
|
|
161
|
+
} else {
|
|
162
|
+
const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'plan': 'Plan' };
|
|
163
|
+
chips.push('<span class="cmd-chip intent">' + intentLabels[parsed.intent] + '</span>');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Type chip (only for work items)
|
|
167
|
+
if (parsed.intent === 'work-item') {
|
|
168
|
+
chips.push('<span class="cmd-chip">' + parsed.type + '</span>');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Priority chip
|
|
172
|
+
chips.push('<span class="cmd-chip priority-' + parsed.priority + '">' + parsed.priority + ' priority</span>');
|
|
173
|
+
|
|
174
|
+
// Agent chips
|
|
175
|
+
if (parsed.fanout) {
|
|
176
|
+
chips.push('<span class="cmd-chip fanout">@everyone (fan-out)</span>');
|
|
177
|
+
}
|
|
178
|
+
for (const agentId of parsed.agents) {
|
|
179
|
+
const agent = cmdAgents.find(a => a.id === agentId);
|
|
180
|
+
if (agent) {
|
|
181
|
+
chips.push('<span class="cmd-chip agent-chip">' + agent.emoji + ' @' + agent.name + '</span>');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Project chip(s)
|
|
186
|
+
if (parsed.projects.length > 0) {
|
|
187
|
+
parsed.projects.forEach(p => chips.push('<span class="cmd-chip project-chip">#' + escHtml(p) + '</span>'));
|
|
188
|
+
} else if (parsed.project) {
|
|
189
|
+
chips.push('<span class="cmd-chip project-chip">#' + escHtml(parsed.project) + '</span>');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
el.innerHTML = chips.join('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Autocomplete popup (shared for @mentions and #projects)
|
|
196
|
+
let cmdPopupMode = ''; // '@' or '#'
|
|
197
|
+
|
|
198
|
+
function cmdShowMentions(query) {
|
|
199
|
+
cmdPopupMode = '@';
|
|
200
|
+
const popup = document.getElementById('cmd-mention-popup');
|
|
201
|
+
const q = query.toLowerCase();
|
|
202
|
+
let items = [];
|
|
203
|
+
|
|
204
|
+
// Always show @everyone option
|
|
205
|
+
items.push({ id: 'everyone', name: 'everyone', emoji: '\u{1F4E2}', role: 'Fan-out to all agents' });
|
|
206
|
+
|
|
207
|
+
for (const a of cmdAgents) {
|
|
208
|
+
if (!q || a.id.includes(q) || a.name.toLowerCase().includes(q) || a.role.toLowerCase().includes(q)) {
|
|
209
|
+
items.push(a);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (items.length === 0) { popup.classList.remove('visible'); return; }
|
|
214
|
+
|
|
215
|
+
cmdMentionIdx = 0;
|
|
216
|
+
popup.innerHTML = items.map((a, i) =>
|
|
217
|
+
'<div class="cmd-mention-item' + (i === 0 ? ' active' : '') + '" data-id="' + a.id + '" onclick="cmdInsertPopupItem(\'' + escHtml(a.id) + '\')">' +
|
|
218
|
+
'<span class="mention-emoji">' + a.emoji + '</span>' +
|
|
219
|
+
'<span class="mention-name">@' + escHtml(a.name) + '</span>' +
|
|
220
|
+
'<span class="mention-role">' + escHtml(a.role) + '</span>' +
|
|
221
|
+
'</div>'
|
|
222
|
+
).join('');
|
|
223
|
+
popup.classList.add('visible');
|
|
224
|
+
popup.scrollTop = 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cmdShowProjects(query) {
|
|
228
|
+
cmdPopupMode = '#';
|
|
229
|
+
const popup = document.getElementById('cmd-mention-popup');
|
|
230
|
+
const q = query.toLowerCase();
|
|
231
|
+
let items = cmdProjects.filter(p => !q || p.name.toLowerCase().includes(q));
|
|
232
|
+
|
|
233
|
+
if (items.length === 0) { popup.classList.remove('visible'); return; }
|
|
234
|
+
|
|
235
|
+
cmdMentionIdx = 0;
|
|
236
|
+
popup.innerHTML = items.map((p, i) =>
|
|
237
|
+
'<div class="cmd-mention-item' + (i === 0 ? ' active' : '') + '" data-id="' + escHtml(p.name) + '" onclick="cmdInsertPopupItem(\'' + escHtml(p.name) + '\')">' +
|
|
238
|
+
'<span class="mention-emoji">\u{1F4C1}</span>' +
|
|
239
|
+
'<span class="mention-name" style="color:var(--green)">#' + escHtml(p.name) + '</span>' +
|
|
240
|
+
'<span class="mention-role">' + escHtml(p.description.slice(0, 50)) + (p.description.length > 50 ? '...' : '') + '</span>' +
|
|
241
|
+
'</div>'
|
|
242
|
+
).join('');
|
|
243
|
+
popup.classList.add('visible');
|
|
244
|
+
popup.scrollTop = 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function cmdHidePopup() {
|
|
248
|
+
document.getElementById('cmd-mention-popup').classList.remove('visible');
|
|
249
|
+
cmdMentionIdx = -1;
|
|
250
|
+
cmdPopupMode = '';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function cmdInsertPopupItem(id) {
|
|
254
|
+
const input = document.getElementById('cmd-input');
|
|
255
|
+
const val = input.value;
|
|
256
|
+
const cursor = input.selectionStart;
|
|
257
|
+
const before = val.slice(0, cursor);
|
|
258
|
+
const trigger = cmdPopupMode; // '@' or '#'
|
|
259
|
+
const triggerIdx = before.lastIndexOf(trigger);
|
|
260
|
+
if (triggerIdx === -1) return;
|
|
261
|
+
const after = val.slice(cursor);
|
|
262
|
+
input.value = before.slice(0, triggerIdx) + trigger + id + ' ' + after;
|
|
263
|
+
input.focus();
|
|
264
|
+
const newPos = triggerIdx + id.length + 2;
|
|
265
|
+
input.setSelectionRange(newPos, newPos);
|
|
266
|
+
cmdHidePopup();
|
|
267
|
+
cmdRenderMeta();
|
|
268
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// command-parser.js — Command parsing functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let cmdAgents = []; // [{id, name, emoji, role}]
|
|
4
|
+
let cmdProjects = []; // [{name, description}]
|
|
5
|
+
let cmdMentionIdx = -1; // active mention autocomplete index
|
|
6
|
+
|
|
7
|
+
function cmdUpdateAgentList(agents) {
|
|
8
|
+
cmdAgents = (agents || []).map(a => ({ id: a.id, name: a.name, emoji: a.emoji, role: a.role }));
|
|
9
|
+
}
|
|
10
|
+
function cmdUpdateProjectList(projects) {
|
|
11
|
+
cmdProjects = (projects || []).map(p => ({ name: p.name, description: p.description || '' }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function showToast(id, msg, ok) {
|
|
15
|
+
const el = document.getElementById(id);
|
|
16
|
+
el.className = 'cmd-toast ' + (ok ? 'success' : 'error');
|
|
17
|
+
el.textContent = msg;
|
|
18
|
+
setTimeout(() => { el.className = 'cmd-toast'; }, 4000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function detectWorkItemType(text) {
|
|
22
|
+
const t = text.toLowerCase();
|
|
23
|
+
const patterns = [
|
|
24
|
+
{ type: 'ask', words: ['explain', 'why does', 'why is', 'what does', 'how do i', 'how do you', 'what\'s the', 'tell me', 'can you explain', 'walk me through'] },
|
|
25
|
+
{ type: 'explore', words: ['explore', 'investigate', 'understand', 'analyze', 'audit', 'document', 'architecture', 'how does', 'what is', 'look into', 'research', 'survey', 'map out', 'codebase', 'make a note of', 'find out'] },
|
|
26
|
+
{ type: 'fix', words: ['fix', 'bug', 'broken', 'crash', 'error', 'issue', 'patch', 'repair', 'resolve', 'regression', 'failing', 'doesn\'t work', 'not working'] },
|
|
27
|
+
{ type: 'review', words: ['code review', 'check pr', 'look at pr', 'audit code', 'inspect', 'review pr', 'review pull request'] },
|
|
28
|
+
{ type: 'explore', words: ['review the plan', 'review plan', 'review the design', 'review design', 'review the doc', 'look at the plan', 'check the plan', 'read the plan', 'review it', 'review this'] },
|
|
29
|
+
{ type: 'review', words: ['review'] },
|
|
30
|
+
{ type: 'test', words: ['test', 'write tests', 'add tests', 'unit test', 'e2e test', 'coverage', 'testing', 'build', 'run locally', 'localhost', 'start the', 'spin up', 'verify', 'check if it works'] },
|
|
31
|
+
];
|
|
32
|
+
for (const { type, words } of patterns) {
|
|
33
|
+
if (words.some(w => t.includes(w))) return type;
|
|
34
|
+
}
|
|
35
|
+
return 'implement';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Parse the unified input into structured intent
|
|
39
|
+
function cmdParseInput(raw) {
|
|
40
|
+
let text = raw.trim();
|
|
41
|
+
const result = {
|
|
42
|
+
intent: 'work-item', // 'work-item' | 'note' | 'plan'
|
|
43
|
+
agents: [], // assigned agent IDs
|
|
44
|
+
fanout: false,
|
|
45
|
+
priority: 'medium',
|
|
46
|
+
project: '', // primary project (for work items, plans)
|
|
47
|
+
projects: [], // multi-project list (for PRD items)
|
|
48
|
+
title: '',
|
|
49
|
+
description: '',
|
|
50
|
+
type: '', // work item type (auto-detected)
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Detect /stop, /cancel, /kill
|
|
54
|
+
if (/^\/(?:stop|cancel|kill)\b/i.test(text) || /^(stop|cancel|kill|abort|halt)\s+/i.test(text)) {
|
|
55
|
+
result.intent = 'cancel';
|
|
56
|
+
text = text.replace(/^\/(?:stop|cancel|kill)\s*/i, '').replace(/^(stop|cancel|kill|abort|halt)\s+/i, '').trim();
|
|
57
|
+
// Try to resolve agent name from remaining text
|
|
58
|
+
var cancelAgent = cmdAgents.find(function(a) { return text.toLowerCase().includes(a.id) || text.toLowerCase().includes(a.name.toLowerCase()); });
|
|
59
|
+
if (cancelAgent) result.agents = [cancelAgent.id];
|
|
60
|
+
result.cancelTask = text;
|
|
61
|
+
result.title = 'Cancel: ' + text;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Detect /decide, /note, or natural "remember" keyword
|
|
66
|
+
const rememberPattern = /^(remember|remember that|don't forget|note that|keep in mind)\b/i;
|
|
67
|
+
if (/^\/decide\b/i.test(text) || /^\/note\b/i.test(text) || rememberPattern.test(text)) {
|
|
68
|
+
result.intent = 'note';
|
|
69
|
+
text = text.replace(/^\/decide\s*/i, '').replace(/^\/note\s*/i, '').replace(rememberPattern, '').trim();
|
|
70
|
+
} else if (/^\/plan\b/i.test(text) || /^(make a plan|plan out|plan for|plan how|create a plan|design a plan|come up with a plan|draft a plan|write a plan)\b/i.test(text)) {
|
|
71
|
+
result.intent = 'plan';
|
|
72
|
+
text = text.replace(/^\/plan\s*/i, '').replace(/^(make a plan for|plan out how|plan for how|plan how|create a plan for|design a plan for|come up with a plan for|draft a plan for|write a plan for|make a plan|plan out|create a plan|design a plan|come up with a plan|draft a plan|write a plan)\s*/i, '').trim();
|
|
73
|
+
// Extract branch strategy flag
|
|
74
|
+
if (/--parallel\b/i.test(text)) {
|
|
75
|
+
result.branchStrategy = 'parallel';
|
|
76
|
+
text = text.replace(/--parallel\b/i, '').trim();
|
|
77
|
+
} else if (/--stack\b/i.test(text)) {
|
|
78
|
+
result.branchStrategy = 'shared-branch';
|
|
79
|
+
text = text.replace(/--stack\b/i, '').trim();
|
|
80
|
+
} else {
|
|
81
|
+
result.branchStrategy = 'parallel'; // default — items without depends_on get independent branches
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Extract @mentions
|
|
86
|
+
const mentionRe = /@(\w+)/g;
|
|
87
|
+
let m;
|
|
88
|
+
while ((m = mentionRe.exec(text)) !== null) {
|
|
89
|
+
const name = m[1].toLowerCase();
|
|
90
|
+
if (name === 'everyone' || name === 'all') {
|
|
91
|
+
result.fanout = true;
|
|
92
|
+
} else {
|
|
93
|
+
const agent = cmdAgents.find(a => a.id === name || a.name.toLowerCase() === name);
|
|
94
|
+
if (agent && !result.agents.includes(agent.id)) result.agents.push(agent.id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Remove @mentions from text
|
|
98
|
+
text = text.replace(/@\w+/g, '').trim();
|
|
99
|
+
|
|
100
|
+
// Extract !priority
|
|
101
|
+
if (/!high\b/i.test(text)) { result.priority = 'high'; text = text.replace(/!high\b/i, '').trim(); }
|
|
102
|
+
else if (/!low\b/i.test(text)) { result.priority = 'low'; text = text.replace(/!low\b/i, '').trim(); }
|
|
103
|
+
else if (/!urgent\b/i.test(text)) { result.priority = 'high'; text = text.replace(/!urgent\b/i, '').trim(); }
|
|
104
|
+
|
|
105
|
+
// Extract #project(s)
|
|
106
|
+
const projRe = /#(\S+)/g;
|
|
107
|
+
while ((m = projRe.exec(text)) !== null) {
|
|
108
|
+
const pname = m[1];
|
|
109
|
+
const proj = cmdProjects.find(p => p.name.toLowerCase() === pname.toLowerCase());
|
|
110
|
+
if (proj) {
|
|
111
|
+
result.project = proj.name; // last match for backward compat (work items, plans)
|
|
112
|
+
if (!result.projects.includes(proj.name)) result.projects.push(proj.name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
text = text.replace(/#\S+/g, '').trim();
|
|
116
|
+
|
|
117
|
+
// Clean up extra whitespace
|
|
118
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
119
|
+
|
|
120
|
+
// Split first line as title, rest as description
|
|
121
|
+
const lines = text.split('\n');
|
|
122
|
+
result.title = lines[0] || '';
|
|
123
|
+
result.description = lines.slice(1).join('\n').trim();
|
|
124
|
+
|
|
125
|
+
// Auto-detect work type
|
|
126
|
+
result.type = detectWorkItemType(result.title + ' ' + result.description);
|
|
127
|
+
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// dashboard/js/detail-panel.js — Agent detail panel extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
function closeDetail() {
|
|
4
|
+
document.getElementById('detail-overlay').classList.remove('open');
|
|
5
|
+
document.getElementById('detail-panel').classList.remove('open');
|
|
6
|
+
currentAgentId = null;
|
|
7
|
+
stopLiveStream();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function renderDetailTabs(detail) {
|
|
11
|
+
const isWorking = detail.statusData?.status === 'working';
|
|
12
|
+
const tabs = [
|
|
13
|
+
...(isWorking ? [{ id: 'live', label: 'Live Output' }] : []),
|
|
14
|
+
{ id: 'thought-process', label: 'Thought Process' },
|
|
15
|
+
{ id: 'charter', label: 'Charter' },
|
|
16
|
+
{ id: 'history', label: 'History' },
|
|
17
|
+
{ id: 'output', label: 'Output Log' },
|
|
18
|
+
];
|
|
19
|
+
document.getElementById('detail-tabs').innerHTML = tabs.map(t =>
|
|
20
|
+
'<div class="detail-tab ' + (t.id === currentTab ? 'active' : '') + '" onclick="switchTab(\'' + t.id + '\')">' + t.label + '</div>'
|
|
21
|
+
).join('');
|
|
22
|
+
|
|
23
|
+
document.getElementById('detail-panel').dataset.detail = JSON.stringify(detail);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function switchTab(tabId) {
|
|
27
|
+
currentTab = tabId;
|
|
28
|
+
const detail = JSON.parse(document.getElementById('detail-panel').dataset.detail || '{}');
|
|
29
|
+
renderDetailTabs(detail);
|
|
30
|
+
renderDetailContent(detail, tabId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderDetailContent(detail, tab) {
|
|
34
|
+
const el = document.getElementById('detail-content');
|
|
35
|
+
|
|
36
|
+
if (tab === 'thought-process') {
|
|
37
|
+
let html = '';
|
|
38
|
+
|
|
39
|
+
if (detail.statusData) {
|
|
40
|
+
html += '<h4>Current Status</h4><div class="section">';
|
|
41
|
+
html += 'Status: <span style="color:var(--' + (detail.statusData.status === 'working' ? 'yellow' : detail.statusData.status === 'done' ? 'green' : 'muted') + ')">' + (detail.statusData.status || 'idle').toUpperCase() + '</span>\n';
|
|
42
|
+
if (detail.statusData.task) html += 'Task: ' + escHtml(detail.statusData.task) + '\n';
|
|
43
|
+
if (detail.statusData.started_at) html += 'Started: ' + detail.statusData.started_at + '\n';
|
|
44
|
+
if (detail.statusData.completed_at) html += 'Completed: ' + detail.statusData.completed_at + '\n';
|
|
45
|
+
html += '</div>';
|
|
46
|
+
if (detail.statusData.resultSummary) {
|
|
47
|
+
html += '<h4>Last Result</h4><div class="section" style="border-left:3px solid var(--green);padding-left:12px">' + escHtml(detail.statusData.resultSummary) + '</div>';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (detail.inboxContents && detail.inboxContents.length > 0) {
|
|
52
|
+
html += '<h4>Notes & Findings (' + detail.inboxContents.length + ')</h4>';
|
|
53
|
+
detail.inboxContents.forEach(item => {
|
|
54
|
+
html += '<div class="section"><strong style="color:var(--purple)">' + escHtml(item.name) + '</strong>\n\n' + escHtml(item.content) + '</div>';
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
html += '<h4>Notes & Findings</h4><div class="section" style="color:var(--muted);font-style:italic">No notes or findings written yet.</div>';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (detail.outputLog) {
|
|
61
|
+
html += '<h4>Latest Output</h4><div class="section">' + escHtml(detail.outputLog) + '</div>';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
el.innerHTML = html;
|
|
65
|
+
} else if (tab === 'live') {
|
|
66
|
+
el.innerHTML = '<div class="section" id="live-output" style="max-height:60vh;overflow-y:auto;font-size:11px;line-height:1.6">Loading live output...</div>' +
|
|
67
|
+
'<div style="margin-top:8px;display:flex;gap:8px;align-items:center">' +
|
|
68
|
+
'<span class="pulse"></span><span id="live-status-label" style="font-size:11px;color:var(--green)">Streaming live</span>' +
|
|
69
|
+
'<button class="pr-pager-btn" onclick="refreshLiveOutput()" style="font-size:10px">Refresh now</button>' +
|
|
70
|
+
'</div>';
|
|
71
|
+
startLiveStream(currentAgentId);
|
|
72
|
+
} else if (tab === 'charter') {
|
|
73
|
+
el.innerHTML = '<div class="section">' + escHtml(detail.charter || 'No charter found.') + '</div>';
|
|
74
|
+
} else if (tab === 'history') {
|
|
75
|
+
let html = '';
|
|
76
|
+
// Recent dispatch results
|
|
77
|
+
if (detail.recentDispatches && detail.recentDispatches.length > 0) {
|
|
78
|
+
html += '<h4>Recent Dispatches</h4><table class="pr-table" style="margin-bottom:16px"><thead><tr><th>Task</th><th>Type</th><th>Result</th><th>Completed</th></tr></thead><tbody>';
|
|
79
|
+
detail.recentDispatches.forEach(d => {
|
|
80
|
+
const isError = d.result === 'error';
|
|
81
|
+
const color = isError ? 'var(--red)' : 'var(--green)';
|
|
82
|
+
const reason = d.reason ? ' title="' + escHtml(d.reason) + '"' : '';
|
|
83
|
+
html += '<tr>' +
|
|
84
|
+
'<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(d.task) + '">' + escHtml(d.task.slice(0, 80)) + '</td>' +
|
|
85
|
+
'<td><span class="dispatch-type ' + d.type + '">' + escHtml(d.type) + '</span></td>' +
|
|
86
|
+
'<td style="color:' + color + '"' + reason + '>' + escHtml(d.result) + (isError && d.reason ? ' <span style="font-size:10px;color:var(--muted)">(' + escHtml(d.reason.slice(0, 50)) + ')</span>' : '') + '</td>' +
|
|
87
|
+
'<td style="font-size:10px;color:var(--muted)">' + (d.completed_at ? new Date(d.completed_at).toLocaleString() : '') + '</td>' +
|
|
88
|
+
'</tr>';
|
|
89
|
+
});
|
|
90
|
+
html += '</tbody></table>';
|
|
91
|
+
}
|
|
92
|
+
// Raw history.md
|
|
93
|
+
html += '<h4>Task History</h4><div class="section">' + escHtml(detail.history || 'No history yet.') + '</div>';
|
|
94
|
+
el.innerHTML = html;
|
|
95
|
+
} else if (tab === 'output') {
|
|
96
|
+
el.innerHTML = '<div class="section">' + escHtml(detail.outputLog || 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// dashboard/js/live-stream.js — Live streaming and polling extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let livePollingInterval = null;
|
|
4
|
+
let liveEventSource = null;
|
|
5
|
+
|
|
6
|
+
function startLiveStream(agentId) {
|
|
7
|
+
stopLiveStream();
|
|
8
|
+
if (!agentId) return;
|
|
9
|
+
|
|
10
|
+
const outputEl = document.getElementById('live-output');
|
|
11
|
+
if (outputEl) outputEl.textContent = '';
|
|
12
|
+
|
|
13
|
+
liveEventSource = new EventSource('/api/agent/' + agentId + '/live-stream');
|
|
14
|
+
|
|
15
|
+
liveEventSource.onmessage = function(e) {
|
|
16
|
+
try {
|
|
17
|
+
const chunk = JSON.parse(e.data);
|
|
18
|
+
const el = document.getElementById('live-output');
|
|
19
|
+
if (el) {
|
|
20
|
+
const wasAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;
|
|
21
|
+
el.textContent += chunk;
|
|
22
|
+
if (wasAtBottom) el.scrollTop = el.scrollHeight;
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
liveEventSource.addEventListener('done', function() {
|
|
28
|
+
stopLiveStream();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
liveEventSource.onerror = function() {
|
|
32
|
+
// Fall back to polling on SSE error
|
|
33
|
+
stopLiveStream();
|
|
34
|
+
startLivePolling();
|
|
35
|
+
const label = document.getElementById('live-status-label');
|
|
36
|
+
if (label) label.textContent = 'Auto-refreshing every 3s';
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stopLiveStream() {
|
|
41
|
+
if (liveEventSource) {
|
|
42
|
+
liveEventSource.close();
|
|
43
|
+
liveEventSource = null;
|
|
44
|
+
}
|
|
45
|
+
stopLivePolling();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function startLivePolling() {
|
|
49
|
+
stopLivePolling();
|
|
50
|
+
refreshLiveOutput();
|
|
51
|
+
livePollingInterval = setInterval(refreshLiveOutput, 3000);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function stopLivePolling() {
|
|
55
|
+
if (livePollingInterval) { clearInterval(livePollingInterval); livePollingInterval = null; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function refreshLiveOutput() {
|
|
59
|
+
if (!currentAgentId || currentTab !== 'live') { stopLivePolling(); return; }
|
|
60
|
+
try {
|
|
61
|
+
const text = await fetch('/api/agent/' + currentAgentId + '/live?tail=16384').then(r => r.text());
|
|
62
|
+
const el = document.getElementById('live-output');
|
|
63
|
+
if (el) {
|
|
64
|
+
const wasAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 50;
|
|
65
|
+
el.textContent = text;
|
|
66
|
+
if (wasAtBottom) el.scrollTop = el.scrollHeight;
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|