@yemi33/minions 0.1.178 → 0.1.180
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 +34 -0
- package/dashboard/js/render-dispatch.js +41 -2
- package/dashboard/js/render-inbox.js +28 -6
- package/dashboard/js/render-kb.js +23 -2
- package/dashboard/js/render-meetings.js +3 -2
- package/dashboard/js/render-pinned.js +2 -1
- package/dashboard/js/render-pipelines.js +28 -5
- package/dashboard/js/render-plans.js +34 -10
- package/dashboard/js/render-prd.js +8 -5
- package/dashboard/js/render-prs.js +2 -1
- package/dashboard/js/render-schedules.js +7 -5
- package/dashboard/js/render-work-items.js +4 -2
- package/dashboard/js/state.js +5 -0
- package/dashboard/styles.css +2 -2
- package/engine/ado.js +14 -3
- package/engine/cleanup.js +1 -1
- package/engine/lifecycle.js +4 -0
- package/engine/meeting.js +1 -1
- package/engine/routing.js +1 -1
- package/engine/shared.js +10 -2
- package/engine/spawn-agent.js +1 -1
- package/engine.js +13 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.180 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/ado.js
|
|
8
|
+
- engine/cleanup.js
|
|
9
|
+
- engine/lifecycle.js
|
|
10
|
+
- engine/meeting.js
|
|
11
|
+
- engine/routing.js
|
|
12
|
+
- engine/shared.js
|
|
13
|
+
- engine/spawn-agent.js
|
|
14
|
+
|
|
15
|
+
### Dashboard
|
|
16
|
+
- dashboard/js/render-dispatch.js
|
|
17
|
+
- dashboard/js/render-inbox.js
|
|
18
|
+
- dashboard/js/render-kb.js
|
|
19
|
+
- dashboard/js/render-meetings.js
|
|
20
|
+
- dashboard/js/render-pinned.js
|
|
21
|
+
- dashboard/js/render-pipelines.js
|
|
22
|
+
- dashboard/js/render-plans.js
|
|
23
|
+
- dashboard/js/render-prd.js
|
|
24
|
+
- dashboard/js/render-prs.js
|
|
25
|
+
- dashboard/js/render-schedules.js
|
|
26
|
+
- dashboard/js/render-work-items.js
|
|
27
|
+
- dashboard/js/state.js
|
|
28
|
+
|
|
29
|
+
### Other
|
|
30
|
+
- test/unit.test.js
|
|
31
|
+
|
|
32
|
+
## 0.1.179 (2026-04-02)
|
|
33
|
+
|
|
34
|
+
### Dashboard
|
|
35
|
+
- dashboard/styles.css
|
|
36
|
+
|
|
3
37
|
## 0.1.178 (2026-04-02)
|
|
4
38
|
|
|
5
39
|
### Dashboard
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
// dashboard/js/render-dispatch.js — Engine status, dispatch, and log rendering extracted from dashboard.html
|
|
2
2
|
|
|
3
|
+
const COMPLETED_PER_PAGE = 20;
|
|
4
|
+
const LOG_PER_PAGE = 50;
|
|
5
|
+
let _completedPage = 0;
|
|
6
|
+
let _logPage = 0;
|
|
7
|
+
|
|
8
|
+
function _completedPrev() { if (_completedPage > 0) { _completedPage--; refresh(); } }
|
|
9
|
+
function _completedNext() { _completedPage++; refresh(); }
|
|
10
|
+
function _logPrev() { if (_logPage > 0) { _logPage--; refresh(); } }
|
|
11
|
+
function _logNext() { _logPage++; refresh(); }
|
|
12
|
+
|
|
3
13
|
function renderEngineStatus(engine) {
|
|
4
14
|
const badge = document.getElementById('engine-badge');
|
|
5
15
|
let state = engine?.state || 'stopped';
|
|
@@ -106,8 +116,14 @@ function renderDispatch(dispatch) {
|
|
|
106
116
|
completedCount.textContent = completed.length;
|
|
107
117
|
|
|
108
118
|
if (completed.length > 0) {
|
|
119
|
+
const totalCompPages = Math.ceil(completed.length / COMPLETED_PER_PAGE);
|
|
120
|
+
if (_completedPage >= totalCompPages) _completedPage = totalCompPages - 1;
|
|
121
|
+
if (_completedPage < 0) _completedPage = 0;
|
|
122
|
+
const compStart = _completedPage * COMPLETED_PER_PAGE;
|
|
123
|
+
const pageCompleted = completed.slice(compStart, compStart + COMPLETED_PER_PAGE);
|
|
124
|
+
|
|
109
125
|
completedEl.innerHTML = '<table class="pr-table"><thead><tr><th>ID</th><th>Type</th><th>Agent</th><th>Task</th><th>Result</th><th>Completed</th></tr></thead><tbody>' +
|
|
110
|
-
|
|
126
|
+
pageCompleted.map(d => {
|
|
111
127
|
const isError = d.result === 'error';
|
|
112
128
|
const agentId = (d.agent || '').toLowerCase();
|
|
113
129
|
const errorBtn = isError
|
|
@@ -122,6 +138,14 @@ function renderDispatch(dispatch) {
|
|
|
122
138
|
'<td class="pr-date">' + shortTime(d.completed_at) + '</td>' +
|
|
123
139
|
'</tr>';
|
|
124
140
|
}).join('') + '</tbody></table>';
|
|
141
|
+
if (completed.length > COMPLETED_PER_PAGE) {
|
|
142
|
+
completedEl.innerHTML += '<div class="pr-pager">' +
|
|
143
|
+
'<span class="pr-page-info">Showing ' + (compStart + 1) + ' to ' + Math.min(compStart + COMPLETED_PER_PAGE, completed.length) + ' of ' + completed.length + '</span>' +
|
|
144
|
+
'<div class="pr-pager-btns">' +
|
|
145
|
+
'<button class="pr-pager-btn ' + (_completedPage === 0 ? 'disabled' : '') + '" onclick="_completedPrev()">Prev</button>' +
|
|
146
|
+
'<button class="pr-pager-btn ' + (_completedPage >= totalCompPages - 1 ? 'disabled' : '') + '" onclick="_completedNext()">Next</button>' +
|
|
147
|
+
'</div></div>';
|
|
148
|
+
}
|
|
125
149
|
} else {
|
|
126
150
|
completedEl.innerHTML = '<p class="empty">No completed dispatches yet.</p>';
|
|
127
151
|
}
|
|
@@ -133,13 +157,28 @@ function renderEngineLog(log) {
|
|
|
133
157
|
el.innerHTML = '<div class="empty">No log entries yet.</div>';
|
|
134
158
|
return;
|
|
135
159
|
}
|
|
136
|
-
|
|
160
|
+
const reversed = log.slice().reverse();
|
|
161
|
+
const totalLogPages = Math.ceil(reversed.length / LOG_PER_PAGE);
|
|
162
|
+
if (_logPage >= totalLogPages) _logPage = totalLogPages - 1;
|
|
163
|
+
if (_logPage < 0) _logPage = 0;
|
|
164
|
+
const logStart = _logPage * LOG_PER_PAGE;
|
|
165
|
+
const pageLog = reversed.slice(logStart, logStart + LOG_PER_PAGE);
|
|
166
|
+
|
|
167
|
+
el.innerHTML = pageLog.map(e =>
|
|
137
168
|
'<div class="log-entry">' +
|
|
138
169
|
'<span class="log-ts">' + shortTime(e.timestamp) + '</span> ' +
|
|
139
170
|
'<span class="log-level-' + (e.level || 'info') + '">[' + (e.level || 'info') + ']</span> ' +
|
|
140
171
|
escHtml(e.message || '') +
|
|
141
172
|
'</div>'
|
|
142
173
|
).join('');
|
|
174
|
+
if (reversed.length > LOG_PER_PAGE) {
|
|
175
|
+
el.innerHTML += '<div class="pr-pager">' +
|
|
176
|
+
'<span class="pr-page-info">Showing ' + (logStart + 1) + ' to ' + Math.min(logStart + LOG_PER_PAGE, reversed.length) + ' of ' + reversed.length + '</span>' +
|
|
177
|
+
'<div class="pr-pager-btns">' +
|
|
178
|
+
'<button class="pr-pager-btn ' + (_logPage === 0 ? 'disabled' : '') + '" onclick="_logPrev()">Prev</button>' +
|
|
179
|
+
'<button class="pr-pager-btn ' + (_logPage >= totalLogPages - 1 ? 'disabled' : '') + '" onclick="_logNext()">Next</button>' +
|
|
180
|
+
'</div></div>';
|
|
181
|
+
}
|
|
143
182
|
}
|
|
144
183
|
|
|
145
184
|
function shortTime(t) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// render-inbox.js — Inbox and notes rendering functions extracted from dashboard.html
|
|
2
2
|
|
|
3
|
+
const INBOX_PER_PAGE = 15;
|
|
4
|
+
let _inboxPage = 0;
|
|
5
|
+
|
|
6
|
+
function _inboxPrev() { if (_inboxPage > 0) { _inboxPage--; renderInbox(inboxData); } }
|
|
7
|
+
function _inboxNext() { _inboxPage++; renderInbox(inboxData); }
|
|
8
|
+
|
|
3
9
|
function renderInbox(inbox) {
|
|
4
10
|
inbox = inbox.filter(function(item) { return !isDeleted('inbox:' + item.name); });
|
|
5
11
|
inboxData = inbox;
|
|
@@ -7,19 +13,35 @@ function renderInbox(inbox) {
|
|
|
7
13
|
const count = document.getElementById('inbox-count');
|
|
8
14
|
count.textContent = inbox.length;
|
|
9
15
|
if (!inbox.length) { list.innerHTML = '<p class="empty">No messages yet.</p>'; return; }
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
const totalInboxPages = Math.ceil(inbox.length / INBOX_PER_PAGE);
|
|
18
|
+
if (_inboxPage >= totalInboxPages) _inboxPage = totalInboxPages - 1;
|
|
19
|
+
if (_inboxPage < 0) _inboxPage = 0;
|
|
20
|
+
const inboxStart = _inboxPage * INBOX_PER_PAGE;
|
|
21
|
+
const pageInbox = inbox.slice(inboxStart, inboxStart + INBOX_PER_PAGE);
|
|
22
|
+
|
|
23
|
+
list.innerHTML = pageInbox.map((item, i) => {
|
|
24
|
+
const idx = inboxStart + i;
|
|
25
|
+
return `<div class="inbox-item" data-file="notes/inbox/${escHtml(item.name)}">
|
|
26
|
+
<div class="inbox-name" onclick="openModal(${idx})" style="cursor:pointer">
|
|
13
27
|
<span>${escHtml(item.name)}</span><span>${item.age}</span>
|
|
14
28
|
</div>
|
|
15
|
-
<div class="inbox-preview" onclick="openModal(${
|
|
29
|
+
<div class="inbox-preview" onclick="openModal(${idx})" style="cursor:pointer">${escHtml(item.content.slice(0,200))}</div>
|
|
16
30
|
<div style="display:flex;gap:6px;margin-top:6px;align-items:center">
|
|
17
31
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();promoteToKB('${escHtml(item.name)}')">Add to Knowledge Base</button>
|
|
18
32
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();openInboxInExplorer('${escHtml(item.name)}')">Open in Explorer</button>
|
|
19
33
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" onclick="event.stopPropagation();deleteInboxItem('${escHtml(item.name)}')">Delete</button>
|
|
20
34
|
</div>
|
|
21
|
-
</div
|
|
22
|
-
|
|
35
|
+
</div>`;
|
|
36
|
+
}).join('');
|
|
37
|
+
if (inbox.length > INBOX_PER_PAGE) {
|
|
38
|
+
list.innerHTML += '<div class="pr-pager">' +
|
|
39
|
+
'<span class="pr-page-info">Showing ' + (inboxStart + 1) + ' to ' + Math.min(inboxStart + INBOX_PER_PAGE, inbox.length) + ' of ' + inbox.length + '</span>' +
|
|
40
|
+
'<div class="pr-pager-btns">' +
|
|
41
|
+
'<button class="pr-pager-btn ' + (_inboxPage === 0 ? 'disabled' : '') + '" onclick="_inboxPrev()">Prev</button>' +
|
|
42
|
+
'<button class="pr-pager-btn ' + (_inboxPage >= totalInboxPages - 1 ? 'disabled' : '') + '" onclick="_inboxNext()">Next</button>' +
|
|
43
|
+
'</div></div>';
|
|
44
|
+
}
|
|
23
45
|
restoreNotifBadges();
|
|
24
46
|
}
|
|
25
47
|
|
|
@@ -13,6 +13,11 @@ const KB_CAT_ICONS = {
|
|
|
13
13
|
incidents: '\u{1F6A8}', 'api-notes': '\u{1F517}',
|
|
14
14
|
};
|
|
15
15
|
let _kbActiveTab = 'all';
|
|
16
|
+
const KB_PER_PAGE = 30;
|
|
17
|
+
let _kbPage = 0;
|
|
18
|
+
|
|
19
|
+
function _kbPrev() { if (_kbPage > 0) { _kbPage--; renderKnowledgeBase(); } }
|
|
20
|
+
function _kbNext() { _kbPage++; renderKnowledgeBase(); }
|
|
16
21
|
|
|
17
22
|
async function refreshKnowledgeBase() {
|
|
18
23
|
try {
|
|
@@ -67,7 +72,13 @@ function renderKnowledgeBase() {
|
|
|
67
72
|
return;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
const totalKbPages = Math.ceil(items.length / KB_PER_PAGE);
|
|
76
|
+
if (_kbPage >= totalKbPages) _kbPage = totalKbPages - 1;
|
|
77
|
+
if (_kbPage < 0) _kbPage = 0;
|
|
78
|
+
const kbStart = _kbPage * KB_PER_PAGE;
|
|
79
|
+
const pageItems = items.slice(kbStart, kbStart + KB_PER_PAGE);
|
|
80
|
+
|
|
81
|
+
listEl.innerHTML = pageItems.map(item => {
|
|
71
82
|
const icon = KB_CAT_ICONS[item.category] || '\u{1F4C4}';
|
|
72
83
|
const label = KB_CAT_LABELS[item.category] || item.category;
|
|
73
84
|
return '<div class="kb-item" data-file="knowledge/' + escHtml(item.category) + '/' + escHtml(item.file) + '" onclick="kbOpenItem(\'' + escHtml(item.category) + '\', \'' + escHtml(item.file) + '\')">' +
|
|
@@ -83,11 +94,20 @@ function renderKnowledgeBase() {
|
|
|
83
94
|
'</div>' +
|
|
84
95
|
'</div>';
|
|
85
96
|
}).join('');
|
|
97
|
+
if (items.length > KB_PER_PAGE) {
|
|
98
|
+
listEl.innerHTML += '<div class="pr-pager">' +
|
|
99
|
+
'<span class="pr-page-info">Showing ' + (kbStart + 1) + ' to ' + Math.min(kbStart + KB_PER_PAGE, items.length) + ' of ' + items.length + '</span>' +
|
|
100
|
+
'<div class="pr-pager-btns">' +
|
|
101
|
+
'<button class="pr-pager-btn ' + (_kbPage === 0 ? 'disabled' : '') + '" onclick="_kbPrev()">Prev</button>' +
|
|
102
|
+
'<button class="pr-pager-btn ' + (_kbPage >= totalKbPages - 1 ? 'disabled' : '') + '" onclick="_kbNext()">Next</button>' +
|
|
103
|
+
'</div></div>';
|
|
104
|
+
}
|
|
86
105
|
restoreNotifBadges();
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
function kbSetTab(tab) {
|
|
90
109
|
_kbActiveTab = tab;
|
|
110
|
+
_kbPage = 0;
|
|
91
111
|
renderKnowledgeBase();
|
|
92
112
|
}
|
|
93
113
|
|
|
@@ -143,10 +163,11 @@ function openCreateKbModal() {
|
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
async function submitKbEntry() {
|
|
166
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
|
|
146
167
|
const category = document.getElementById('kb-new-category').value;
|
|
147
168
|
const title = document.getElementById('kb-new-title').value;
|
|
148
169
|
const content = document.getElementById('kb-new-content').value;
|
|
149
|
-
if (!title || !content) { alert('Title and content are required'); return; }
|
|
170
|
+
if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } alert('Title and content are required'); return; }
|
|
150
171
|
try {
|
|
151
172
|
const res = await fetch('/api/knowledge', {
|
|
152
173
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -250,12 +250,13 @@ function openCreateMeetingModal() {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
async function _submitCreateMeeting() {
|
|
253
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Starting...'; }
|
|
253
254
|
const title = document.getElementById('mtg-title')?.value?.trim();
|
|
254
255
|
const agenda = document.getElementById('mtg-agenda')?.value?.trim();
|
|
255
|
-
if (!title || !agenda) { alert('Title and agenda required'); return; }
|
|
256
|
+
if (!title || !agenda) { if (btn) { btn.disabled = false; btn.textContent = 'Start Meeting'; } alert('Title and agenda required'); return; }
|
|
256
257
|
const checks = document.querySelectorAll('#mtg-participants input[type="checkbox"]:checked');
|
|
257
258
|
const participants = [...checks].map(c => c.value);
|
|
258
|
-
if (participants.length < 2) { alert('Select at least 2 participants'); return; }
|
|
259
|
+
if (participants.length < 2) { if (btn) { btn.disabled = false; btn.textContent = 'Start Meeting'; } alert('Select at least 2 participants'); return; }
|
|
259
260
|
try { closeModal(); } catch { /* expected */ }
|
|
260
261
|
showToast('cmd-toast', 'Meeting started with ' + participants.length + ' agents', true);
|
|
261
262
|
try {
|
|
@@ -34,10 +34,11 @@ function openPinNoteModal() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async function submitPinnedNote() {
|
|
37
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Pinning...'; }
|
|
37
38
|
const title = document.getElementById('pin-title').value;
|
|
38
39
|
const content = document.getElementById('pin-content').value;
|
|
39
40
|
const level = document.getElementById('pin-level').value;
|
|
40
|
-
if (!title || !content) { alert('Title and content required'); return; }
|
|
41
|
+
if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Pin Note'; } alert('Title and content required'); return; }
|
|
41
42
|
try { closeModal(); } catch { /* may not be open */ }
|
|
42
43
|
showToast('cmd-toast', 'Note pinned', true);
|
|
43
44
|
try {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// render-pipelines.js — Pipeline list, run detail, and create modal
|
|
2
2
|
|
|
3
3
|
let _pipelinesData = [];
|
|
4
|
+
const PIPELINES_PER_PAGE = 10;
|
|
5
|
+
let _pipelinesPage = 0;
|
|
6
|
+
|
|
7
|
+
function _pipelinesPrev() { if (_pipelinesPage > 0) { _pipelinesPage--; refresh(); } }
|
|
8
|
+
function _pipelinesNext() { _pipelinesPage++; refresh(); }
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Render clickable artifact links for a pipeline stage.
|
|
@@ -77,7 +82,24 @@ function renderPipelines(pipelines) {
|
|
|
77
82
|
}
|
|
78
83
|
countEl.textContent = pipelines.length;
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
const totalPipelinePages = Math.ceil(pipelines.length / PIPELINES_PER_PAGE);
|
|
86
|
+
if (_pipelinesPage >= totalPipelinePages) _pipelinesPage = totalPipelinePages - 1;
|
|
87
|
+
if (_pipelinesPage < 0) _pipelinesPage = 0;
|
|
88
|
+
const pipStart = _pipelinesPage * PIPELINES_PER_PAGE;
|
|
89
|
+
const pagePipelines = pipelines.slice(pipStart, pipStart + PIPELINES_PER_PAGE);
|
|
90
|
+
|
|
91
|
+
var pipelinePagerHtml = '';
|
|
92
|
+
if (pipelines.length > PIPELINES_PER_PAGE) {
|
|
93
|
+
pipelinePagerHtml = '<div class="pr-pager">' +
|
|
94
|
+
'<span class="pr-page-info">' + (pipStart + 1) + '-' + Math.min(pipStart + PIPELINES_PER_PAGE, pipelines.length) + ' of ' + pipelines.length + '</span>' +
|
|
95
|
+
'<div class="pr-pager-btns">' +
|
|
96
|
+
'<button class="pr-pager-btn ' + (_pipelinesPage === 0 ? 'disabled' : '') + '" onclick="_pipelinesPrev()">Prev</button>' +
|
|
97
|
+
'<button class="pr-pager-btn ' + (_pipelinesPage >= totalPipelinePages - 1 ? 'disabled' : '') + '" onclick="_pipelinesNext()">Next</button>' +
|
|
98
|
+
'</div>' +
|
|
99
|
+
'</div>';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
el.innerHTML = pagePipelines.map(function(p) {
|
|
81
103
|
const activeRun = (p.runs || []).find(function(r) { return r.status === 'running'; });
|
|
82
104
|
const lastRun = (p.runs || []).slice(-1)[0];
|
|
83
105
|
const statusColor = activeRun ? 'var(--blue)' : lastRun?.status === 'completed' ? 'var(--green)' : lastRun?.status === 'failed' ? 'var(--red)' : 'var(--muted)';
|
|
@@ -143,7 +165,7 @@ function renderPipelines(pipelines) {
|
|
|
143
165
|
'<div style="margin-top:6px;display:flex;gap:4px;align-items:center;flex-wrap:wrap">' + stageFlow + '</div>' +
|
|
144
166
|
progressHtml +
|
|
145
167
|
'</div>';
|
|
146
|
-
}).join('');
|
|
168
|
+
}).join('') + pipelinePagerHtml;
|
|
147
169
|
}
|
|
148
170
|
|
|
149
171
|
function openPipelineDetail(id) {
|
|
@@ -249,8 +271,8 @@ async function _togglePipelineEnabled(id, enabled, btn) {
|
|
|
249
271
|
try {
|
|
250
272
|
var res = await fetch('/api/pipelines/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, enabled: enabled }) });
|
|
251
273
|
if (res.ok) { showToast('cmd-toast', enabled ? 'Pipeline enabled' : 'Pipeline disabled', true); refresh(); }
|
|
252
|
-
else { alert('Failed'); }
|
|
253
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
274
|
+
else { alert('Failed'); refresh(); }
|
|
275
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
254
276
|
if (btn) { btn.textContent = enabled ? 'Disable' : 'Enable'; btn.style.pointerEvents = ''; }
|
|
255
277
|
}
|
|
256
278
|
|
|
@@ -349,12 +371,13 @@ function _updatePlCronPreview() {
|
|
|
349
371
|
}
|
|
350
372
|
|
|
351
373
|
async function _submitCreatePipeline() {
|
|
374
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
|
|
352
375
|
var id = document.getElementById('pl-id')?.value?.trim();
|
|
353
376
|
var title = document.getElementById('pl-title')?.value?.trim();
|
|
354
377
|
var useCron = document.getElementById('pl-use-cron')?.checked;
|
|
355
378
|
var cron = useCron ? (window._plComputedCron || '') : '';
|
|
356
379
|
var stagesRaw = document.getElementById('pl-stages')?.value?.trim();
|
|
357
|
-
if (!id || !title) { alert('ID and title required'); return; }
|
|
380
|
+
if (!id || !title) { if (btn) { btn.disabled = false; btn.textContent = 'Create Pipeline'; } alert('ID and title required'); return; }
|
|
358
381
|
var stages;
|
|
359
382
|
try { stages = JSON.parse(stagesRaw); } catch (e) { alert('Invalid JSON in stages: ' + e.message); return; }
|
|
360
383
|
if (!Array.isArray(stages) || stages.length === 0) { alert('Stages must be a non-empty array'); return; }
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// render-plans.js — Plan rendering functions extracted from dashboard.html
|
|
2
2
|
|
|
3
|
+
const PLANS_PER_PAGE = 10;
|
|
4
|
+
let _plansPage = 0;
|
|
5
|
+
|
|
6
|
+
function _plansPrev() { if (_plansPage > 0) { _plansPage--; refresh(); } }
|
|
7
|
+
function _plansNext() { _plansPage++; refresh(); }
|
|
8
|
+
|
|
3
9
|
function openCreatePlanModal() {
|
|
4
10
|
const projOpts = (typeof cmdProjects !== 'undefined' ? cmdProjects : []).map(p =>
|
|
5
11
|
'<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'
|
|
@@ -23,10 +29,11 @@ function openCreatePlanModal() {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
async function _submitCreatePlan() {
|
|
32
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
|
|
26
33
|
const title = document.getElementById('plan-new-title')?.value?.trim();
|
|
27
34
|
const content = document.getElementById('plan-new-content')?.value?.trim();
|
|
28
|
-
if (!title) { alert('Title is required'); return; }
|
|
29
|
-
if (!content) { alert('Plan content is required'); return; }
|
|
35
|
+
if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Create Plan'; } alert('Title is required'); return; }
|
|
36
|
+
if (!content) { if (btn) { btn.disabled = false; btn.textContent = 'Create Plan'; } alert('Plan content is required'); return; }
|
|
30
37
|
const project = document.getElementById('plan-new-project')?.value || '';
|
|
31
38
|
|
|
32
39
|
try {
|
|
@@ -90,6 +97,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
function renderPlans(plans) {
|
|
100
|
+
plans = plans.filter(function(p) { return !isDeleted('plan:' + p.file); });
|
|
93
101
|
const el = document.getElementById('plans-list');
|
|
94
102
|
const countEl = document.getElementById('plans-count');
|
|
95
103
|
countEl.textContent = plans.length;
|
|
@@ -256,7 +264,23 @@ function renderPlans(plans) {
|
|
|
256
264
|
'</div>';
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
|
|
267
|
+
const totalPlanPages = Math.ceil(activePlans.length / PLANS_PER_PAGE);
|
|
268
|
+
if (_plansPage >= totalPlanPages) _plansPage = totalPlanPages - 1;
|
|
269
|
+
if (_plansPage < 0) _plansPage = 0;
|
|
270
|
+
const plansStart = _plansPage * PLANS_PER_PAGE;
|
|
271
|
+
const pagePlans = activePlans.slice(plansStart, plansStart + PLANS_PER_PAGE);
|
|
272
|
+
|
|
273
|
+
let html = pagePlans.map(renderPlanCard).join('');
|
|
274
|
+
|
|
275
|
+
if (activePlans.length > PLANS_PER_PAGE) {
|
|
276
|
+
html += '<div class="pr-pager">' +
|
|
277
|
+
'<span class="pr-page-info">' + (plansStart + 1) + '-' + Math.min(plansStart + PLANS_PER_PAGE, activePlans.length) + ' of ' + activePlans.length + '</span>' +
|
|
278
|
+
'<div class="pr-pager-btns">' +
|
|
279
|
+
'<button class="pr-pager-btn ' + (_plansPage === 0 ? 'disabled' : '') + '" onclick="_plansPrev()">Prev</button>' +
|
|
280
|
+
'<button class="pr-pager-btn ' + (_plansPage >= totalPlanPages - 1 ? 'disabled' : '') + '" onclick="_plansNext()">Next</button>' +
|
|
281
|
+
'</div>' +
|
|
282
|
+
'</div>';
|
|
283
|
+
}
|
|
260
284
|
|
|
261
285
|
if (archivedPlans.length > 0) {
|
|
262
286
|
window._archivedPlans = archivedPlans;
|
|
@@ -493,21 +517,22 @@ async function planApprove(file, btn) {
|
|
|
493
517
|
async function planDelete(file) {
|
|
494
518
|
_stopPlanPoll();
|
|
495
519
|
if (!confirm('Delete plan "' + file + '"? This cannot be undone.')) return;
|
|
520
|
+
markDeleted('plan:' + file);
|
|
521
|
+
closeModal();
|
|
522
|
+
showToast('cmd-toast', 'Plan deleted', true);
|
|
496
523
|
try {
|
|
497
524
|
const res = await fetch('/api/plans/delete', {
|
|
498
525
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
499
526
|
body: JSON.stringify({ file })
|
|
500
527
|
});
|
|
501
528
|
if (res.ok) {
|
|
502
|
-
closeModal();
|
|
503
|
-
showToast('cmd-toast', 'Plan deleted', true);
|
|
504
|
-
refreshPlans();
|
|
505
529
|
refresh();
|
|
506
530
|
} else {
|
|
507
|
-
const d = await res.json();
|
|
508
|
-
alert('
|
|
531
|
+
const d = await res.json().catch(() => ({}));
|
|
532
|
+
alert('Delete failed: ' + (d.error || 'unknown'));
|
|
533
|
+
refresh(); // revert optimistic
|
|
509
534
|
}
|
|
510
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
535
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
511
536
|
}
|
|
512
537
|
|
|
513
538
|
async function planArchive(file, btn) {
|
|
@@ -528,7 +553,6 @@ async function planArchive(file, btn) {
|
|
|
528
553
|
if (d.archivedSource) msg += ' PRD + source plan (' + d.archivedSource + ')';
|
|
529
554
|
if (d.cancelledItems) msg += ', cancelled ' + d.cancelledItems + ' pending item(s)';
|
|
530
555
|
showToast('cmd-toast', msg, true);
|
|
531
|
-
refreshPlans();
|
|
532
556
|
refresh();
|
|
533
557
|
} else {
|
|
534
558
|
resetBtn();
|
|
@@ -598,6 +598,7 @@ async function prdItemEdit(source, itemId) {
|
|
|
598
598
|
}
|
|
599
599
|
|
|
600
600
|
async function prdItemSave(source, itemId) {
|
|
601
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
|
|
601
602
|
try {
|
|
602
603
|
const res = await fetch('/api/prd-items/update', {
|
|
603
604
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -610,20 +611,22 @@ async function prdItemSave(source, itemId) {
|
|
|
610
611
|
})
|
|
611
612
|
});
|
|
612
613
|
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Item updated', true); }
|
|
613
|
-
else { const d = await res.json(); alert('Failed: ' + (d.error || 'unknown')); }
|
|
614
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
614
|
+
else { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } const d = await res.json(); alert('Failed: ' + (d.error || 'unknown')); }
|
|
615
|
+
} catch (e) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Error: ' + e.message); }
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
async function prdItemRemove(source, itemId) {
|
|
618
619
|
if (!confirm('Remove item ' + itemId + '? This also cancels any pending work item.')) return;
|
|
620
|
+
closeModal();
|
|
621
|
+
showToast('cmd-toast', 'Item removed', true);
|
|
619
622
|
try {
|
|
620
623
|
const res = await fetch('/api/prd-items/remove', {
|
|
621
624
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
622
625
|
body: JSON.stringify({ source, itemId })
|
|
623
626
|
});
|
|
624
|
-
if (res.ok) {
|
|
625
|
-
else { const d = await res.json(); alert('
|
|
626
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
627
|
+
if (res.ok) { refresh(); }
|
|
628
|
+
else { const d = await res.json().catch(() => ({})); alert('Remove failed: ' + (d.error || 'unknown')); refresh(); }
|
|
629
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
627
630
|
}
|
|
628
631
|
|
|
629
632
|
async function prdItemRequeue(workItemId, source) {
|
|
@@ -123,8 +123,9 @@ function openAddPrModal() {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
async function _submitLinkPr() {
|
|
126
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Linking...'; }
|
|
126
127
|
const url = document.getElementById('pr-link-url')?.value?.trim();
|
|
127
|
-
if (!url) { alert('PR URL is required'); return; }
|
|
128
|
+
if (!url) { if (btn) { btn.disabled = false; btn.textContent = 'Link PR'; } alert('PR URL is required'); return; }
|
|
128
129
|
const title = document.getElementById('pr-link-title')?.value?.trim() || '';
|
|
129
130
|
const project = document.getElementById('pr-link-project')?.value || '';
|
|
130
131
|
const context = document.getElementById('pr-link-context')?.value || '';
|
|
@@ -75,7 +75,7 @@ function _generateScheduleId(title) {
|
|
|
75
75
|
.replace(/-+/g, '-')
|
|
76
76
|
.replace(/^-|-$/g, '')
|
|
77
77
|
.slice(0, 40);
|
|
78
|
-
const suffix = Math.random().toString(36).slice(2,
|
|
78
|
+
const suffix = Math.random().toString(36).slice(2, 10);
|
|
79
79
|
return (slug || 'task') + '-' + suffix;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -487,6 +487,7 @@ function openEditScheduleModal(id) {
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
async function submitSchedule(isEdit) {
|
|
490
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = isEdit ? 'Saving...' : 'Creating...'; }
|
|
490
491
|
_showScheduleError('');
|
|
491
492
|
const title = document.getElementById('sched-edit-title').value.trim();
|
|
492
493
|
const cron = window._schedComputedCron || '';
|
|
@@ -503,8 +504,9 @@ async function submitSchedule(isEdit) {
|
|
|
503
504
|
id = _generateScheduleId(title);
|
|
504
505
|
}
|
|
505
506
|
|
|
506
|
-
if (
|
|
507
|
-
if (!
|
|
507
|
+
function _resetSchedBtn() { if (btn) { btn.disabled = false; btn.textContent = isEdit ? 'Save Changes' : 'Create Schedule'; } }
|
|
508
|
+
if (!title) { _resetSchedBtn(); _showScheduleError('Title is required'); return; }
|
|
509
|
+
if (!cron) { _resetSchedBtn(); _showScheduleError('Schedule is required \u2014 select days and time, or use natural language'); return; }
|
|
508
510
|
|
|
509
511
|
const payload = { id, title, cron, type, priority, project: project || undefined, agent: agent || undefined, description: description || undefined, enabled: true };
|
|
510
512
|
const url = isEdit ? '/api/schedules/update' : '/api/schedules';
|
|
@@ -515,9 +517,9 @@ async function submitSchedule(isEdit) {
|
|
|
515
517
|
});
|
|
516
518
|
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', isEdit ? 'Schedule updated' : 'Schedule created', true); } else {
|
|
517
519
|
const d = await res.json().catch(() => ({}));
|
|
518
|
-
_showScheduleError((isEdit ? 'Update' : 'Create') + ' failed: ' + (d.error || 'unknown'));
|
|
520
|
+
_resetSchedBtn(); _showScheduleError((isEdit ? 'Update' : 'Create') + ' failed: ' + (d.error || 'unknown'));
|
|
519
521
|
}
|
|
520
|
-
} catch (e) { _showScheduleError('Error: ' + e.message); }
|
|
522
|
+
} catch (e) { _resetSchedBtn(); _showScheduleError('Error: ' + e.message); }
|
|
521
523
|
}
|
|
522
524
|
|
|
523
525
|
async function toggleScheduleEnabled(id, enabled) {
|
|
@@ -158,6 +158,7 @@ function editWorkItem(id, source) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
async function submitWorkItemEdit(id, source) {
|
|
161
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
|
|
161
162
|
const title = document.getElementById('wi-edit-title').value.trim();
|
|
162
163
|
const description = document.getElementById('wi-edit-desc').value;
|
|
163
164
|
const type = document.getElementById('wi-edit-type').value;
|
|
@@ -170,7 +171,7 @@ async function submitWorkItemEdit(id, source) {
|
|
|
170
171
|
});
|
|
171
172
|
const acRaw = document.getElementById('wi-edit-ac')?.value || '';
|
|
172
173
|
const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
|
|
173
|
-
if (!title) { alert('Title is required'); return; }
|
|
174
|
+
if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title is required'); return; }
|
|
174
175
|
try { closeModal(); } catch { /* may not be open */ }
|
|
175
176
|
showToast('cmd-toast', 'Work item updated', true);
|
|
176
177
|
try {
|
|
@@ -365,8 +366,9 @@ function openCreateWorkItemModal() {
|
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
async function _submitCreateWorkItem() {
|
|
369
|
+
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
|
|
368
370
|
const title = document.getElementById('wi-new-title')?.value?.trim();
|
|
369
|
-
if (!title) { alert('Title is required'); return; }
|
|
371
|
+
if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } alert('Title is required'); return; }
|
|
370
372
|
const desc = document.getElementById('wi-new-desc')?.value || '';
|
|
371
373
|
const type = document.getElementById('wi-new-type')?.value || 'implement';
|
|
372
374
|
const priority = document.getElementById('wi-new-priority')?.value || 'medium';
|
package/dashboard/js/state.js
CHANGED
|
@@ -15,6 +15,11 @@ function getPageFromUrl() {
|
|
|
15
15
|
let currentPage = getPageFromUrl();
|
|
16
16
|
|
|
17
17
|
function switchPage(page, pushState) {
|
|
18
|
+
// Clean up intervals and panels from previous page
|
|
19
|
+
try { _stopPlanPoll(); } catch {}
|
|
20
|
+
try { _stopMeetingPoll(); } catch {}
|
|
21
|
+
try { closeDetail(); } catch {}
|
|
22
|
+
|
|
18
23
|
currentPage = page;
|
|
19
24
|
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
20
25
|
const target = document.getElementById('page-' + page);
|
package/dashboard/styles.css
CHANGED
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
|
|
29
29
|
header {
|
|
30
30
|
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
31
|
-
padding: var(--space-6)
|
|
31
|
+
padding: var(--space-6) 14px; display: flex; align-items: center; justify-content: space-between;
|
|
32
32
|
position: sticky; top: 0; z-index: 100;
|
|
33
33
|
}
|
|
34
|
-
header h1 { font-size: var(--text-2xl); font-weight: 600; color: var(--blue); letter-spacing: 0.5px;
|
|
34
|
+
header h1 { font-size: var(--text-2xl); font-weight: 600; color: var(--blue); letter-spacing: 0.5px; }
|
|
35
35
|
header h1 span { color: var(--muted); font-weight: 400; font-size: var(--text-lg); margin-left: var(--space-4); }
|
|
36
36
|
.pulse { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--green); display: inline-block; margin-right: var(--space-3); animation: pulse 2s infinite; }
|
|
37
37
|
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
|
package/engine/ado.js
CHANGED
|
@@ -46,9 +46,20 @@ function getAdoToken() {
|
|
|
46
46
|
|
|
47
47
|
async function adoFetch(url, token, _retryCount = 0) {
|
|
48
48
|
const MAX_RETRIES = 1;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const timer = setTimeout(() => controller.abort(), 30000);
|
|
51
|
+
let res;
|
|
52
|
+
try {
|
|
53
|
+
res = await fetch(url, {
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
if (err.name === 'AbortError') throw new Error(`ADO API timeout (30s) for ${url.split('?')[0]}`);
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
clearTimeout(timer);
|
|
52
63
|
if (!res.ok) throw new Error(`ADO API ${res.status}: ${res.statusText}`);
|
|
53
64
|
const text = await res.text();
|
|
54
65
|
if (!text || text.trimStart().startsWith('<')) {
|
package/engine/cleanup.js
CHANGED
|
@@ -202,7 +202,7 @@ function runCleanup(config, verbose = false) {
|
|
|
202
202
|
for (const entry of wtEntries) {
|
|
203
203
|
if (entry.shouldClean) {
|
|
204
204
|
try {
|
|
205
|
-
exec(`git worktree remove "${entry.wtPath}" --force`, { cwd: root, stdio: 'pipe' });
|
|
205
|
+
exec(`git worktree remove "${entry.wtPath}" --force`, { cwd: root, stdio: 'pipe', timeout: 30000 });
|
|
206
206
|
cleaned.worktrees++;
|
|
207
207
|
if (verbose) console.log(` Removed worktree: ${entry.wtPath}`);
|
|
208
208
|
} catch (e) {
|
package/engine/lifecycle.js
CHANGED
|
@@ -817,13 +817,17 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
817
817
|
|
|
818
818
|
const teamsUrl = process.env.TEAMS_PLAN_FLOW_URL;
|
|
819
819
|
if (teamsUrl) {
|
|
820
|
+
const ac = new AbortController();
|
|
821
|
+
const t = setTimeout(() => ac.abort(), 5000);
|
|
820
822
|
try {
|
|
821
823
|
await fetch(teamsUrl, {
|
|
822
824
|
method: 'POST',
|
|
825
|
+
signal: ac.signal,
|
|
823
826
|
headers: { 'Content-Type': 'application/json' },
|
|
824
827
|
body: JSON.stringify({ text: `PR ${pr.id} merged: ${pr.title} (${project.name}) by ${pr.agent || 'unknown'}` })
|
|
825
828
|
});
|
|
826
829
|
} catch (err) { log('warn', `Teams post-merge notify failed: ${err.message}`); }
|
|
830
|
+
clearTimeout(t);
|
|
827
831
|
}
|
|
828
832
|
|
|
829
833
|
log('info', `Post-merge hooks completed for ${pr.id}`);
|
package/engine/meeting.js
CHANGED
package/engine/routing.js
CHANGED
|
@@ -136,7 +136,7 @@ function resolveAgent(workType, config, authorAgent = null) {
|
|
|
136
136
|
if (config.engine?.allowTempAgents) {
|
|
137
137
|
const tempId = `temp-${shared.uid()}`;
|
|
138
138
|
_claimedAgents.add(tempId);
|
|
139
|
-
tempAgents.set(tempId, { name: `Temp-${tempId.slice(5,
|
|
139
|
+
tempAgents.set(tempId, { name: `Temp-${tempId.slice(5, 13)}`, role: 'Temporary Agent', createdAt: ts() });
|
|
140
140
|
log('info', `Spawning temp agent ${tempId} — all permanent agents busy`);
|
|
141
141
|
return tempId;
|
|
142
142
|
}
|
package/engine/shared.js
CHANGED
|
@@ -68,7 +68,15 @@ function safeWrite(p, data) {
|
|
|
68
68
|
const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
69
69
|
const tmp = p + '.tmp.' + process.pid + '.' + (++_tmpCounter);
|
|
70
70
|
try {
|
|
71
|
-
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(tmp, content);
|
|
73
|
+
} catch (writeErr) {
|
|
74
|
+
if (writeErr.code === 'ENOSPC') {
|
|
75
|
+
try { fs.unlinkSync(tmp); } catch { /* cleanup */ }
|
|
76
|
+
throw new Error(`[ENOSPC] Disk full — cannot write ${path.basename(p)}`);
|
|
77
|
+
}
|
|
78
|
+
throw writeErr;
|
|
79
|
+
}
|
|
72
80
|
// Atomic rename — retry on Windows EPERM (file locking)
|
|
73
81
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
74
82
|
try {
|
|
@@ -168,7 +176,7 @@ function mutateJsonFileLocked(filePath, mutateFn, {
|
|
|
168
176
|
* Use for filenames that could collide (dispatch IDs, temp files, etc.)
|
|
169
177
|
*/
|
|
170
178
|
function uid() {
|
|
171
|
-
return Date.now().toString(36) + Math.random().toString(36).slice(2,
|
|
179
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
/**
|
package/engine/spawn-agent.js
CHANGED
|
@@ -38,7 +38,7 @@ for (const p of searchPaths) {
|
|
|
38
38
|
// Fallback: parse the shell wrapper
|
|
39
39
|
if (!claudeBin) {
|
|
40
40
|
try {
|
|
41
|
-
const which = exec('bash -c "which claude"', { encoding: 'utf8', env }).trim();
|
|
41
|
+
const which = exec('bash -c "which claude"', { encoding: 'utf8', env, timeout: 10000 }).trim();
|
|
42
42
|
const whichNative = which.replace(/^\/([a-zA-Z])\//, (_, d) => d.toUpperCase() + ':/').replace(/\//g, path.sep);
|
|
43
43
|
const wrapper = fs.readFileSync(whichNative, 'utf8');
|
|
44
44
|
const m = wrapper.match(/node_modules\/@anthropic-ai\/claude-code\/cli\.js/);
|
package/engine.js
CHANGED
|
@@ -2220,12 +2220,15 @@ let tickRunning = false;
|
|
|
2220
2220
|
async function tick() {
|
|
2221
2221
|
if (tickRunning) return; // prevent overlapping ticks
|
|
2222
2222
|
tickRunning = true;
|
|
2223
|
+
const tickStart = Date.now();
|
|
2223
2224
|
try {
|
|
2224
2225
|
await tickInner();
|
|
2225
2226
|
} catch (e) {
|
|
2226
2227
|
log('error', `Tick error: ${e.message}`);
|
|
2227
2228
|
} finally {
|
|
2228
2229
|
tickRunning = false;
|
|
2230
|
+
const elapsed = Date.now() - tickStart;
|
|
2231
|
+
if (elapsed > 30000) log('warn', `Slow tick: ${(elapsed / 1000).toFixed(1)}s`);
|
|
2229
2232
|
}
|
|
2230
2233
|
}
|
|
2231
2234
|
|
|
@@ -2243,9 +2246,9 @@ async function tickInner() {
|
|
|
2243
2246
|
tickCount++;
|
|
2244
2247
|
|
|
2245
2248
|
// 1. Check for timed-out agents, steering messages, and idle threshold
|
|
2246
|
-
checkTimeouts(config);
|
|
2247
|
-
checkSteering(config);
|
|
2248
|
-
checkIdleThreshold(config);
|
|
2249
|
+
try { checkTimeouts(config); } catch (e) { log('warn', `checkTimeouts: ${e.message}`); }
|
|
2250
|
+
try { checkSteering(config); } catch (e) { log('warn', `checkSteering: ${e.message}`); }
|
|
2251
|
+
try { checkIdleThreshold(config); } catch (e) { log('warn', `checkIdleThreshold: ${e.message}`); }
|
|
2249
2252
|
|
|
2250
2253
|
// 1b. Check for meeting round timeouts
|
|
2251
2254
|
try {
|
|
@@ -2260,11 +2263,11 @@ async function tickInner() {
|
|
|
2260
2263
|
}
|
|
2261
2264
|
|
|
2262
2265
|
// 2. Consolidate inbox
|
|
2263
|
-
consolidateInbox(config);
|
|
2266
|
+
try { consolidateInbox(config); } catch (e) { log('warn', `consolidateInbox: ${e.message}`); }
|
|
2264
2267
|
|
|
2265
2268
|
// 2.5. Periodic cleanup + MCP sync (every 10 ticks = ~5 minutes)
|
|
2266
2269
|
if (tickCount % 10 === 0) {
|
|
2267
|
-
runCleanup(config);
|
|
2270
|
+
try { runCleanup(config); } catch (e) { log('warn', `runCleanup: ${e.message}`); }
|
|
2268
2271
|
}
|
|
2269
2272
|
|
|
2270
2273
|
// 2.6. Poll PR status: build, review, merge (every 6 ticks = ~3 minutes)
|
|
@@ -2395,14 +2398,15 @@ async function tickInner() {
|
|
|
2395
2398
|
}
|
|
2396
2399
|
|
|
2397
2400
|
// 3. Discover new work from sources
|
|
2398
|
-
discoverWork(config);
|
|
2401
|
+
try { discoverWork(config); } catch (e) { log('warn', `discoverWork: ${e.message}`); }
|
|
2399
2402
|
|
|
2400
2403
|
// 4. Update snapshot
|
|
2401
|
-
updateSnapshot(config);
|
|
2404
|
+
try { updateSnapshot(config); } catch (e) { log('warn', `updateSnapshot: ${e.message}`); }
|
|
2402
2405
|
|
|
2403
2406
|
// 5. Process pending dispatches — auto-spawn agents
|
|
2404
|
-
|
|
2405
|
-
|
|
2407
|
+
let dispatch, activeCount;
|
|
2408
|
+
try { dispatch = getDispatch(); } catch (e) { log('warn', `getDispatch: ${e.message}`); return; }
|
|
2409
|
+
activeCount = (dispatch.active || []).length;
|
|
2406
2410
|
const maxConcurrent = config.engine?.maxConcurrent || 5;
|
|
2407
2411
|
|
|
2408
2412
|
if (activeCount >= maxConcurrent) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.180",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|