@yemi33/minions 0.1.16 → 0.1.18
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 +21 -0
- package/dashboard/js/refresh.js +1 -0
- package/dashboard/js/render-inbox.js +30 -0
- package/dashboard/js/render-kb.js +39 -0
- package/dashboard/js/render-pinned.js +53 -0
- package/dashboard/js/render-work-items.js +46 -3
- package/dashboard/pages/home.html +5 -0
- package/dashboard/pages/inbox.html +2 -2
- package/dashboard-build.js +1 -1
- package/dashboard.js +91 -3
- package/engine.js +31 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.18 (2026-03-28)
|
|
4
|
+
|
|
5
|
+
### Dashboard
|
|
6
|
+
- dashboard/js/render-inbox.js
|
|
7
|
+
|
|
8
|
+
## 0.1.17 (2026-03-28)
|
|
9
|
+
|
|
10
|
+
### Engine
|
|
11
|
+
- engine.js
|
|
12
|
+
|
|
13
|
+
### Dashboard
|
|
14
|
+
- dashboard-build.js
|
|
15
|
+
- dashboard.js
|
|
16
|
+
- dashboard/js/refresh.js
|
|
17
|
+
- dashboard/js/render-inbox.js
|
|
18
|
+
- dashboard/js/render-kb.js
|
|
19
|
+
- dashboard/js/render-pinned.js
|
|
20
|
+
- dashboard/js/render-work-items.js
|
|
21
|
+
- dashboard/pages/home.html
|
|
22
|
+
- dashboard/pages/inbox.html
|
|
23
|
+
|
|
3
24
|
## 0.1.16 (2026-03-28)
|
|
4
25
|
|
|
5
26
|
### Dashboard
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -38,6 +38,7 @@ async function refresh() {
|
|
|
38
38
|
renderSkills(data.skills || []);
|
|
39
39
|
renderMcpServers(data.mcpServers || []);
|
|
40
40
|
renderSchedules(data.schedules || []);
|
|
41
|
+
renderPinned(data.pinned || []);
|
|
41
42
|
// Update sidebar counts
|
|
42
43
|
const swi = document.getElementById('sidebar-wi');
|
|
43
44
|
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
@@ -145,6 +145,36 @@ async function openInboxInExplorer(name) {
|
|
|
145
145
|
} catch {}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function openQuickNoteModal() {
|
|
149
|
+
document.getElementById('modal-title').textContent = 'Quick Note';
|
|
150
|
+
document.getElementById('modal-body').innerHTML =
|
|
151
|
+
'<div style="display:flex;flex-direction:column;gap:12px">' +
|
|
152
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Title' +
|
|
153
|
+
'<input id="note-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="Decision, observation, or context..."></label>' +
|
|
154
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Content' +
|
|
155
|
+
'<textarea id="note-content" rows="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Write your note... Agents will see this after consolidation."></textarea></label>' +
|
|
156
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px">' +
|
|
157
|
+
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
158
|
+
'<button onclick="submitQuickNote()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save Note</button>' +
|
|
159
|
+
'</div>' +
|
|
160
|
+
'</div>';
|
|
161
|
+
document.getElementById('modal').classList.add('open');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function submitQuickNote() {
|
|
165
|
+
const title = document.getElementById('note-title').value;
|
|
166
|
+
const content = document.getElementById('note-content').value;
|
|
167
|
+
if (!title && !content) { alert('Title or content required'); return; }
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch('/api/notes', {
|
|
170
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: JSON.stringify({ title: title || 'Quick note', what: content || title })
|
|
172
|
+
});
|
|
173
|
+
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Note saved to inbox', true); }
|
|
174
|
+
else { const d = await res.json(); alert('Error: ' + (d.error || 'unknown')); }
|
|
175
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
176
|
+
}
|
|
177
|
+
|
|
148
178
|
async function doPromoteToKB(name, category) {
|
|
149
179
|
try {
|
|
150
180
|
const res = await fetch('/api/inbox/promote-kb', {
|
|
@@ -106,6 +106,45 @@ async function kbSweep() {
|
|
|
106
106
|
setTimeout(() => { btn.textContent = origText; btn.style.color = 'var(--muted)'; btn.disabled = false; }, 3000);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function openCreateKbModal() {
|
|
110
|
+
document.getElementById('modal-title').textContent = 'New Knowledge Base Entry';
|
|
111
|
+
document.getElementById('modal-body').innerHTML =
|
|
112
|
+
'<div style="display:flex;flex-direction:column;gap:12px">' +
|
|
113
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Category' +
|
|
114
|
+
'<select id="kb-new-category" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)">' +
|
|
115
|
+
'<option value="architecture">Architecture</option>' +
|
|
116
|
+
'<option value="conventions">Conventions</option>' +
|
|
117
|
+
'<option value="project-notes">Project Notes</option>' +
|
|
118
|
+
'<option value="build-reports">Build Reports</option>' +
|
|
119
|
+
'<option value="reviews">Reviews</option>' +
|
|
120
|
+
'</select></label>' +
|
|
121
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Title' +
|
|
122
|
+
'<input id="kb-new-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="Entry title"></label>' +
|
|
123
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Content' +
|
|
124
|
+
'<textarea id="kb-new-content" rows="8" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Write your knowledge entry..."></textarea></label>' +
|
|
125
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px">' +
|
|
126
|
+
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
127
|
+
'<button onclick="submitKbEntry()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
|
|
128
|
+
'</div>' +
|
|
129
|
+
'</div>';
|
|
130
|
+
document.getElementById('modal').classList.add('open');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function submitKbEntry() {
|
|
134
|
+
const category = document.getElementById('kb-new-category').value;
|
|
135
|
+
const title = document.getElementById('kb-new-title').value;
|
|
136
|
+
const content = document.getElementById('kb-new-content').value;
|
|
137
|
+
if (!title || !content) { alert('Title and content are required'); return; }
|
|
138
|
+
try {
|
|
139
|
+
const res = await fetch('/api/knowledge', {
|
|
140
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
body: JSON.stringify({ category, title, content })
|
|
142
|
+
});
|
|
143
|
+
if (res.ok) { closeModal(); refreshKnowledgeBase(); showToast('cmd-toast', 'KB entry created', true); }
|
|
144
|
+
else { const d = await res.json(); alert('Error: ' + (d.error || 'unknown')); }
|
|
145
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
146
|
+
}
|
|
147
|
+
|
|
109
148
|
async function kbOpenItem(category, file) {
|
|
110
149
|
try {
|
|
111
150
|
const content = await fetch('/api/knowledge/' + category + '/' + encodeURIComponent(file)).then(r => r.text());
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// dashboard/js/render-pinned.js — Pinned context notes rendering and management
|
|
2
|
+
|
|
3
|
+
function renderPinned(entries) {
|
|
4
|
+
const el = document.getElementById('pinned-content');
|
|
5
|
+
if (!el) return;
|
|
6
|
+
if (!entries || entries.length === 0) {
|
|
7
|
+
el.innerHTML = '<p class="empty">No pinned notes. Pin important context that all agents should see.</p>';
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
el.innerHTML = entries.map(e =>
|
|
11
|
+
'<div style="padding:8px 12px;margin-bottom:6px;background:var(--surface2);border-left:3px solid ' +
|
|
12
|
+
(e.level === 'critical' ? 'var(--red)' : e.level === 'warning' ? 'var(--yellow)' : 'var(--blue)') +
|
|
13
|
+
';border-radius:4px">' +
|
|
14
|
+
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
15
|
+
'<strong style="font-size:var(--text-md)">' + escHtml(e.title) + '</strong>' +
|
|
16
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="removePinnedNote(\'' + escHtml(e.title) + '\')">Unpin</button>' +
|
|
17
|
+
'</div>' +
|
|
18
|
+
'<div style="font-size:var(--text-sm);color:var(--muted);margin-top:4px">' + escHtml(e.content.slice(0, 200)) + '</div>' +
|
|
19
|
+
'</div>'
|
|
20
|
+
).join('');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function openPinNoteModal() {
|
|
24
|
+
document.getElementById('modal-title').textContent = 'Pin a Note';
|
|
25
|
+
document.getElementById('modal-body').innerHTML =
|
|
26
|
+
'<div style="display:flex;flex-direction:column;gap:12px">' +
|
|
27
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Title<input id="pin-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="e.g. API freeze until Friday"></label>' +
|
|
28
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Content<textarea id="pin-content" rows="4" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Context all agents should see..."></textarea></label>' +
|
|
29
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Level<select id="pin-level" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)"><option value="info">Info</option><option value="warning">Warning</option><option value="critical">Critical</option></select></label>' +
|
|
30
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px"><button onclick="closeModal()" class="pr-pager-btn">Cancel</button><button onclick="submitPinnedNote()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Pin</button></div>' +
|
|
31
|
+
'</div>';
|
|
32
|
+
document.getElementById('modal').classList.add('open');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function submitPinnedNote() {
|
|
36
|
+
const title = document.getElementById('pin-title').value;
|
|
37
|
+
const content = document.getElementById('pin-content').value;
|
|
38
|
+
const level = document.getElementById('pin-level').value;
|
|
39
|
+
if (!title || !content) { alert('Title and content required'); return; }
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch('/api/pinned', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, content, level }) });
|
|
42
|
+
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Note pinned', true); }
|
|
43
|
+
else { const d = await res.json(); alert('Error: ' + (d.error || 'unknown')); }
|
|
44
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function removePinnedNote(title) {
|
|
48
|
+
if (!confirm('Unpin "' + title + '"?')) return;
|
|
49
|
+
try {
|
|
50
|
+
await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
|
|
51
|
+
refresh();
|
|
52
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
53
|
+
}
|
|
@@ -33,9 +33,14 @@ function wiRow(item) {
|
|
|
33
33
|
'</td>' +
|
|
34
34
|
'<td>' + prLink + '</td>' +
|
|
35
35
|
'<td><span class="pr-date">' + shortTime(item.created) + '</span></td>' +
|
|
36
|
+
'<td style="white-space:nowrap;font-size:9px;color:var(--muted)">' +
|
|
37
|
+
(item.references && item.references.length ? '<span title="' + item.references.length + ' reference(s)" style="margin-right:4px">🔗' + item.references.length + '</span>' : '') +
|
|
38
|
+
(item.acceptanceCriteria && item.acceptanceCriteria.length ? '<span title="' + item.acceptanceCriteria.length + ' acceptance criteria">☑' + item.acceptanceCriteria.length + '</span>' : '') +
|
|
39
|
+
'</td>' +
|
|
36
40
|
'<td style="white-space:nowrap">' +
|
|
37
41
|
((item.status === 'pending' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();editWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Edit work item">✎</button>' : '') +
|
|
38
42
|
((item.status === 'done' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--muted);border-color:var(--border);margin-right:4px" onclick="event.stopPropagation();archiveWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Archive work item">📦</button>' : '') +
|
|
43
|
+
((item.status === 'done' || item.status === 'failed') && !item._humanFeedback ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-right:4px" onclick="event.stopPropagation();feedbackWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Give feedback">👍👎</button>' : (item._humanFeedback ? '<span style="font-size:9px" title="Feedback given">' + (item._humanFeedback.rating === 'up' ? '👍' : '👎') + '</span> ' : '')) +
|
|
39
44
|
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Delete work item and kill agent">✕</button>' +
|
|
40
45
|
'</td>' +
|
|
41
46
|
'</tr>';
|
|
@@ -65,7 +70,7 @@ function renderWorkItems(items) {
|
|
|
65
70
|
const start = wiPage * WI_PER_PAGE;
|
|
66
71
|
const pageItems = items.slice(start, start + WI_PER_PAGE);
|
|
67
72
|
|
|
68
|
-
let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th></tr></thead><tbody>';
|
|
73
|
+
let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>';
|
|
69
74
|
html += pageItems.map(wiRow).join('');
|
|
70
75
|
html += '</tbody></table></div>';
|
|
71
76
|
|
|
@@ -113,6 +118,12 @@ function editWorkItem(id, source) {
|
|
|
113
118
|
'<select id="wi-edit-agent" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)"><option value="">Auto</option>' + agentOpts + '</select>' +
|
|
114
119
|
'</label>' +
|
|
115
120
|
'</div>' +
|
|
121
|
+
'<label style="color:var(--text);font-size:var(--text-md)">References (one per line: url | title | type)' +
|
|
122
|
+
'<textarea id="wi-edit-refs" rows="3" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit;resize:vertical">' + escHtml((item.references || []).map(function(r) { return r.url + ' | ' + (r.title || '') + ' | ' + (r.type || 'link'); }).join('\n')) + '</textarea>' +
|
|
123
|
+
'</label>' +
|
|
124
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Acceptance Criteria (one per line)' +
|
|
125
|
+
'<textarea id="wi-edit-ac" rows="3" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit;resize:vertical">' + escHtml((item.acceptanceCriteria || []).join('\n')) + '</textarea>' +
|
|
126
|
+
'</label>' +
|
|
116
127
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
117
128
|
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
118
129
|
'<button onclick="submitWorkItemEdit(\'' + escHtml(id) + '\',\'' + escHtml(source || '') + '\')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
|
|
@@ -127,11 +138,18 @@ async function submitWorkItemEdit(id, source) {
|
|
|
127
138
|
const type = document.getElementById('wi-edit-type').value;
|
|
128
139
|
const priority = document.getElementById('wi-edit-priority').value;
|
|
129
140
|
const agent = document.getElementById('wi-edit-agent').value;
|
|
141
|
+
const refsRaw = document.getElementById('wi-edit-refs')?.value || '';
|
|
142
|
+
const references = refsRaw.split('\n').filter(function(l) { return l.trim(); }).map(function(l) {
|
|
143
|
+
var parts = l.split('|').map(function(s) { return s.trim(); });
|
|
144
|
+
return { url: parts[0], title: parts[1] || parts[0], type: parts[2] || 'link' };
|
|
145
|
+
});
|
|
146
|
+
const acRaw = document.getElementById('wi-edit-ac')?.value || '';
|
|
147
|
+
const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
|
|
130
148
|
if (!title) { alert('Title is required'); return; }
|
|
131
149
|
try {
|
|
132
150
|
const res = await fetch('/api/work-items/update', {
|
|
133
151
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
134
|
-
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent })
|
|
152
|
+
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria })
|
|
135
153
|
});
|
|
136
154
|
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Work item updated', true); } else {
|
|
137
155
|
const d = await res.json();
|
|
@@ -208,9 +226,34 @@ async function retryWorkItem(id, source) {
|
|
|
208
226
|
function wiPrev() { if (wiPage > 0) { wiPage--; renderWorkItems(allWorkItems); } }
|
|
209
227
|
function wiNext() { const tp = Math.ceil(allWorkItems.length / WI_PER_PAGE); if (wiPage < tp-1) { wiPage++; renderWorkItems(allWorkItems); } }
|
|
210
228
|
|
|
229
|
+
function feedbackWorkItem(id, source) {
|
|
230
|
+
document.getElementById('modal-title').textContent = 'Feedback on ' + id;
|
|
231
|
+
document.getElementById('modal-body').innerHTML =
|
|
232
|
+
'<div style="display:flex;flex-direction:column;gap:16px;align-items:center">' +
|
|
233
|
+
'<div style="display:flex;gap:24px">' +
|
|
234
|
+
'<button onclick="submitFeedback(\'' + escHtml(id) + '\',\'' + escHtml(source) + '\',\'up\')" style="font-size:40px;background:none;border:2px solid var(--border);border-radius:12px;padding:16px 24px;cursor:pointer;transition:all 0.2s" onmouseover="this.style.borderColor=\'var(--green)\'" onmouseout="this.style.borderColor=\'var(--border)\'">👍</button>' +
|
|
235
|
+
'<button onclick="submitFeedback(\'' + escHtml(id) + '\',\'' + escHtml(source) + '\',\'down\')" style="font-size:40px;background:none;border:2px solid var(--border);border-radius:12px;padding:16px 24px;cursor:pointer;transition:all 0.2s" onmouseover="this.style.borderColor=\'var(--red)\'" onmouseout="this.style.borderColor=\'var(--border)\'">👎</button>' +
|
|
236
|
+
'</div>' +
|
|
237
|
+
'<textarea id="feedback-comment" rows="3" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-family:inherit;resize:vertical" placeholder="Optional: what was good or needs improvement?"></textarea>' +
|
|
238
|
+
'</div>';
|
|
239
|
+
document.getElementById('modal').classList.add('open');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function submitFeedback(id, source, rating) {
|
|
243
|
+
const comment = document.getElementById('feedback-comment')?.value || '';
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch('/api/work-items/feedback', {
|
|
246
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
247
|
+
body: JSON.stringify({ id, source, rating, comment })
|
|
248
|
+
});
|
|
249
|
+
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Feedback saved — agents will learn from it', true); }
|
|
250
|
+
else { const d = await res.json(); alert('Error: ' + (d.error || 'unknown')); }
|
|
251
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
252
|
+
}
|
|
253
|
+
|
|
211
254
|
function openAllWorkItems() {
|
|
212
255
|
document.getElementById('modal-title').textContent = 'All Work Items (' + allWorkItems.length + ')';
|
|
213
|
-
const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th></tr></thead><tbody>' +
|
|
256
|
+
const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>' +
|
|
214
257
|
allWorkItems.map(wiRow).join('') + '</tbody></table></div>';
|
|
215
258
|
document.getElementById('modal-body').innerHTML = html;
|
|
216
259
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
<span style="color:var(--blue);font-weight:600">Command Center</span>
|
|
13
13
|
<span>Ask anything, dispatch work, manage plans — powered by Sonnet</span>
|
|
14
14
|
<button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
|
|
15
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);border-color:var(--green)" onclick="openQuickNoteModal()">+ Note</button>
|
|
15
16
|
</div>
|
|
16
17
|
<div class="cmd-toast" id="cmd-toast"></div>
|
|
17
18
|
</section>
|
|
@@ -19,6 +20,10 @@
|
|
|
19
20
|
<h2>Minions Members <span style="font-size:10px;color:var(--border);font-weight:400;text-transform:none;letter-spacing:0">click for details</span></h2>
|
|
20
21
|
<div class="agents" id="agents-grid">Loading...</div>
|
|
21
22
|
</section>
|
|
23
|
+
<section>
|
|
24
|
+
<h2>Pinned Context <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openPinNoteModal()">+ Pin</button></h2>
|
|
25
|
+
<div id="pinned-content"><p class="empty">No pinned notes.</p></div>
|
|
26
|
+
</section>
|
|
22
27
|
<section>
|
|
23
28
|
<h2>Dispatch Queue</h2>
|
|
24
29
|
<div class="dispatch-stats" id="dispatch-stats"></div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<section>
|
|
2
|
-
<h2>Notes Inbox <span class="count" id="inbox-count">0</span> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
|
|
2
|
+
<h2>Notes Inbox <span class="count" id="inbox-count">0</span> <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openQuickNoteModal()">+ Note</button> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
|
|
3
3
|
<div class="inbox-list" id="inbox-list">Loading...</div>
|
|
4
4
|
</section>
|
|
5
5
|
<section>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div id="notes-list">Loading...</div>
|
|
8
8
|
</section>
|
|
9
9
|
<section>
|
|
10
|
-
<h2>Knowledge Base <span class="count" id="kb-count">0</span> <button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
|
|
10
|
+
<h2>Knowledge Base <span class="count" id="kb-count">0</span> <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openCreateKbModal()">+ New</button><button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
|
|
11
11
|
<div class="kb-tabs" id="kb-tabs"></div>
|
|
12
12
|
<div class="kb-list" id="kb-list"><p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p></div>
|
|
13
13
|
</section>
|
package/dashboard-build.js
CHANGED
|
@@ -32,7 +32,7 @@ function buildDashboardHtml() {
|
|
|
32
32
|
'utils', 'state', 'detail-panel', 'live-stream',
|
|
33
33
|
'render-agents', 'render-dispatch', 'render-work-items', 'render-prd',
|
|
34
34
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
35
|
-
'render-other', 'render-schedules',
|
|
35
|
+
'render-other', 'render-schedules', 'render-pinned',
|
|
36
36
|
'command-parser', 'command-input', 'command-center', 'command-history',
|
|
37
37
|
'modal', 'modal-qa', 'settings', 'refresh'
|
|
38
38
|
];
|
package/dashboard.js
CHANGED
|
@@ -76,7 +76,7 @@ function buildDashboardHtml() {
|
|
|
76
76
|
'utils', 'state', 'detail-panel', 'live-stream',
|
|
77
77
|
'render-agents', 'render-dispatch', 'render-work-items', 'render-prd',
|
|
78
78
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
79
|
-
'render-other', 'render-schedules',
|
|
79
|
+
'render-other', 'render-schedules', 'render-pinned',
|
|
80
80
|
'command-parser', 'command-input', 'command-center', 'command-history',
|
|
81
81
|
'modal', 'modal-qa', 'settings', 'refresh'
|
|
82
82
|
];
|
|
@@ -132,6 +132,17 @@ function getMcpServers() {
|
|
|
132
132
|
} catch { return []; }
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function parsePinnedEntries(content) {
|
|
136
|
+
if (!content) return [];
|
|
137
|
+
const entries = [];
|
|
138
|
+
const regex = /###\s*(🔴\s*|🟡\s*)?(.+)\n\n([\s\S]*?)(?=\n\n###|\n\n\*Pinned|$)/g;
|
|
139
|
+
let m;
|
|
140
|
+
while ((m = regex.exec(content)) !== null) {
|
|
141
|
+
entries.push({ level: m[1]?.includes('🔴') ? 'critical' : m[1]?.includes('🟡') ? 'warning' : 'info', title: m[2].trim(), content: m[3].trim() });
|
|
142
|
+
}
|
|
143
|
+
return entries;
|
|
144
|
+
}
|
|
145
|
+
|
|
135
146
|
let _statusCache = null;
|
|
136
147
|
let _statusCacheTs = 0;
|
|
137
148
|
const STATUS_CACHE_TTL = 10000; // 10s — reduces expensive aggregation frequency; mutations call invalidateStatusCache()
|
|
@@ -166,6 +177,7 @@ function getStatus() {
|
|
|
166
177
|
const runs = shared.safeJson(path.join(MINIONS_DIR, 'engine', 'schedule-runs.json')) || {};
|
|
167
178
|
return scheds.map(s => ({ ...s, _lastRun: runs[s.id] || null }));
|
|
168
179
|
})(),
|
|
180
|
+
pinned: (() => { try { return parsePinnedEntries(safeRead(path.join(MINIONS_DIR, 'pinned.md'))); } catch { return []; } })(),
|
|
169
181
|
projects: PROJECTS.map(p => ({ name: p.name, path: p.localPath, description: p.description || '' })),
|
|
170
182
|
initialized: !!(CONFIG.agents && Object.keys(CONFIG.agents).length > 0),
|
|
171
183
|
installId: safeRead(path.join(MINIONS_DIR, '.install-id')).trim() || null,
|
|
@@ -900,6 +912,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
900
912
|
if (body.scope) item.scope = body.scope;
|
|
901
913
|
if (body.agent) item.agent = body.agent;
|
|
902
914
|
if (body.agents) item.agents = body.agents;
|
|
915
|
+
if (body.references) item.references = body.references;
|
|
916
|
+
if (body.acceptanceCriteria) item.acceptanceCriteria = body.acceptanceCriteria;
|
|
903
917
|
items.push(item);
|
|
904
918
|
safeWrite(wiPath, items);
|
|
905
919
|
return jsonReply(res, 200, { ok: true, id });
|
|
@@ -936,6 +950,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
936
950
|
if (type !== undefined) item.type = type;
|
|
937
951
|
if (priority !== undefined) item.priority = priority;
|
|
938
952
|
if (agent !== undefined) item.agent = agent || null;
|
|
953
|
+
if (body.references !== undefined) item.references = body.references;
|
|
954
|
+
if (body.acceptanceCriteria !== undefined) item.acceptanceCriteria = body.acceptanceCriteria;
|
|
939
955
|
item.updatedAt = new Date().toISOString();
|
|
940
956
|
|
|
941
957
|
safeWrite(wiPath, items);
|
|
@@ -2738,12 +2754,69 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2738
2754
|
{ method: 'GET', path: '/api/health', desc: 'Lightweight health check for monitoring', handler: handleHealth },
|
|
2739
2755
|
|
|
2740
2756
|
// Work items
|
|
2741
|
-
{ method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: 'title, type?, description?, priority?, project?, agent?, agents?, scope?', handler: handleWorkItemsCreate },
|
|
2742
|
-
{ method: 'POST', path: '/api/work-items/update', desc: 'Edit a pending/failed work item', params: 'id, source?, title?, description?, type?, priority?, agent?', handler: handleWorkItemsUpdate },
|
|
2757
|
+
{ method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: 'title, type?, description?, priority?, project?, agent?, agents?, scope?, references?, acceptanceCriteria?', handler: handleWorkItemsCreate },
|
|
2758
|
+
{ method: 'POST', path: '/api/work-items/update', desc: 'Edit a pending/failed work item', params: 'id, source?, title?, description?, type?, priority?, agent?, references?, acceptanceCriteria?', handler: handleWorkItemsUpdate },
|
|
2743
2759
|
{ method: 'POST', path: '/api/work-items/retry', desc: 'Reset a failed/dispatched item to pending', params: 'id, source?', handler: handleWorkItemsRetry },
|
|
2744
2760
|
{ method: 'POST', path: '/api/work-items/delete', desc: 'Remove a work item, kill agent, clear dispatch', params: 'id, source?', handler: handleWorkItemsDelete },
|
|
2745
2761
|
{ method: 'POST', path: '/api/work-items/archive', desc: 'Move a completed/failed work item to archive', params: 'id, source?', handler: handleWorkItemsArchive },
|
|
2746
2762
|
{ method: 'GET', path: '/api/work-items/archive', desc: 'List archived work items', handler: handleWorkItemsArchiveList },
|
|
2763
|
+
{ method: 'POST', path: '/api/work-items/feedback', desc: 'Add human feedback on completed work', params: 'id, rating, comment?', handler: async (req, res) => {
|
|
2764
|
+
const body = await readBody(req);
|
|
2765
|
+
const { id, source, rating, comment } = body;
|
|
2766
|
+
if (!id || !rating) return jsonReply(res, 400, { error: 'id and rating required' });
|
|
2767
|
+
const projects = shared.getProjects(CONFIG);
|
|
2768
|
+
const paths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
2769
|
+
for (const p of projects) paths.push(shared.projectWorkItemsPath(p));
|
|
2770
|
+
for (const wiPath of paths) {
|
|
2771
|
+
const items = JSON.parse(safeRead(wiPath) || '[]');
|
|
2772
|
+
const item = items.find(i => i.id === id);
|
|
2773
|
+
if (!item) continue;
|
|
2774
|
+
item._humanFeedback = { rating, comment: comment || '', at: new Date().toISOString() };
|
|
2775
|
+
safeWrite(wiPath, items);
|
|
2776
|
+
const agent = item.dispatched_to || item.agent || 'unknown';
|
|
2777
|
+
const feedbackNote = '# Human Feedback on ' + id + '\n\n' +
|
|
2778
|
+
'**Rating:** ' + (rating === 'up' ? '👍 Good' : '👎 Needs improvement') + '\n' +
|
|
2779
|
+
'**Item:** ' + (item.title || id) + '\n' +
|
|
2780
|
+
'**Agent:** ' + agent + '\n' +
|
|
2781
|
+
(comment ? '**Feedback:** ' + comment + '\n' : '');
|
|
2782
|
+
const inboxPath = path.join(MINIONS_DIR, 'notes', 'inbox', agent + '-feedback-' + new Date().toISOString().slice(0, 10) + '-' + shared.uid().slice(0, 4) + '.md');
|
|
2783
|
+
safeWrite(inboxPath, feedbackNote);
|
|
2784
|
+
invalidateStatusCache();
|
|
2785
|
+
return jsonReply(res, 200, { ok: true });
|
|
2786
|
+
}
|
|
2787
|
+
return jsonReply(res, 404, { error: 'Work item not found' });
|
|
2788
|
+
}},
|
|
2789
|
+
|
|
2790
|
+
// Pinned notes
|
|
2791
|
+
{ method: 'GET', path: '/api/pinned', desc: 'Get pinned notes', handler: async (req, res) => {
|
|
2792
|
+
const content = safeRead(path.join(MINIONS_DIR, 'pinned.md'));
|
|
2793
|
+
return jsonReply(res, 200, { content, entries: parsePinnedEntries(content) });
|
|
2794
|
+
}},
|
|
2795
|
+
{ method: 'POST', path: '/api/pinned', desc: 'Add a pinned note', params: 'title, content, level?', handler: async (req, res) => {
|
|
2796
|
+
const body = await readBody(req);
|
|
2797
|
+
const { title, content, level } = body;
|
|
2798
|
+
if (!title || !content) return jsonReply(res, 400, { error: 'title and content required' });
|
|
2799
|
+
const pinnedPath = path.join(MINIONS_DIR, 'pinned.md');
|
|
2800
|
+
const existing = safeRead(pinnedPath);
|
|
2801
|
+
const levelTag = level === 'critical' ? '🔴 ' : level === 'warning' ? '🟡 ' : '';
|
|
2802
|
+
const entry = '\n\n### ' + levelTag + title + '\n\n' + content + '\n\n*Pinned by human on ' + new Date().toISOString().slice(0, 10) + '*';
|
|
2803
|
+
safeWrite(pinnedPath, (existing || '# Pinned Context\n\nCritical notes visible to all agents.') + entry);
|
|
2804
|
+
invalidateStatusCache();
|
|
2805
|
+
return jsonReply(res, 200, { ok: true });
|
|
2806
|
+
}},
|
|
2807
|
+
{ method: 'POST', path: '/api/pinned/remove', desc: 'Remove a pinned note by title', params: 'title', handler: async (req, res) => {
|
|
2808
|
+
const body = await readBody(req);
|
|
2809
|
+
const { title } = body;
|
|
2810
|
+
if (!title) return jsonReply(res, 400, { error: 'title required' });
|
|
2811
|
+
const pinnedPath = path.join(MINIONS_DIR, 'pinned.md');
|
|
2812
|
+
let content = safeRead(pinnedPath);
|
|
2813
|
+
if (!content) return jsonReply(res, 404, { error: 'No pinned notes' });
|
|
2814
|
+
const regex = new RegExp('\\n\\n###\\s*(?:🔴\\s*|🟡\\s*)?' + title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\n[\\s\\S]*?(?=\\n\\n###|$)', 'i');
|
|
2815
|
+
content = content.replace(regex, '');
|
|
2816
|
+
safeWrite(pinnedPath, content);
|
|
2817
|
+
invalidateStatusCache();
|
|
2818
|
+
return jsonReply(res, 200, { ok: true });
|
|
2819
|
+
}},
|
|
2747
2820
|
|
|
2748
2821
|
// Notes
|
|
2749
2822
|
{ method: 'POST', path: '/api/notes', desc: 'Write a note to inbox for consolidation', params: 'title, what, why?, author?', handler: handleNotesCreate },
|
|
@@ -2780,6 +2853,21 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2780
2853
|
|
|
2781
2854
|
// Knowledge base
|
|
2782
2855
|
{ method: 'GET', path: '/api/knowledge', desc: 'List all knowledge base entries grouped by category', handler: handleKnowledgeList },
|
|
2856
|
+
{ method: 'POST', path: '/api/knowledge', desc: 'Create a knowledge base entry', params: 'category, title, content', handler: async (req, res) => {
|
|
2857
|
+
const body = await readBody(req);
|
|
2858
|
+
const { category, title, content } = body;
|
|
2859
|
+
if (!category || !title || !content) return jsonReply(res, 400, { error: 'category, title, and content required' });
|
|
2860
|
+
const validCategories = ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews'];
|
|
2861
|
+
if (!validCategories.includes(category)) return jsonReply(res, 400, { error: 'Invalid category. Must be: ' + validCategories.join(', ') });
|
|
2862
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60);
|
|
2863
|
+
const filePath = path.join(MINIONS_DIR, 'knowledge', category, slug + '.md');
|
|
2864
|
+
const dir = path.dirname(filePath);
|
|
2865
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
2866
|
+
const header = '# ' + title + '\n\n*Created by human teammate on ' + new Date().toISOString().slice(0, 10) + '*\n\n';
|
|
2867
|
+
safeWrite(filePath, header + content);
|
|
2868
|
+
invalidateStatusCache();
|
|
2869
|
+
return jsonReply(res, 200, { ok: true, path: filePath });
|
|
2870
|
+
}},
|
|
2783
2871
|
{ method: 'POST', path: '/api/knowledge/sweep', desc: 'Deduplicate, consolidate, and reorganize knowledge base', handler: handleKnowledgeSweep },
|
|
2784
2872
|
{ method: 'GET', path: /^\/api\/knowledge\/([^/]+)\/([^?]+)/, desc: 'Read a specific knowledge base entry', handler: handleKnowledgeRead },
|
|
2785
2873
|
|
package/engine.js
CHANGED
|
@@ -357,6 +357,13 @@ function renderPlaybook(type, vars) {
|
|
|
357
357
|
return null;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
// Inject pinned context (always visible to agents)
|
|
361
|
+
let pinnedContent = '';
|
|
362
|
+
try { pinnedContent = fs.readFileSync(path.join(MINIONS_DIR, 'pinned.md'), 'utf8'); } catch {}
|
|
363
|
+
if (pinnedContent) {
|
|
364
|
+
content += '\n\n---\n\n## Pinned Context (CRITICAL — READ FIRST)\n\n' + pinnedContent;
|
|
365
|
+
}
|
|
366
|
+
|
|
360
367
|
// Inject team notes context
|
|
361
368
|
const notes = getNotes();
|
|
362
369
|
if (notes) {
|
|
@@ -2675,6 +2682,14 @@ function discoverFromWorkItems(config, project) {
|
|
|
2675
2682
|
};
|
|
2676
2683
|
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch {}
|
|
2677
2684
|
|
|
2685
|
+
// Inject references and acceptance criteria
|
|
2686
|
+
const refs = (item.references || []).map(r =>
|
|
2687
|
+
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
2688
|
+
).join('\n');
|
|
2689
|
+
vars.references = refs ? '## References\n\n' + refs : '';
|
|
2690
|
+
const ac = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
2691
|
+
vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
|
|
2692
|
+
|
|
2678
2693
|
// Inject ask-specific variables for the ask playbook
|
|
2679
2694
|
if (workType === 'ask') {
|
|
2680
2695
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
@@ -2962,6 +2977,14 @@ function discoverCentralWorkItems(config) {
|
|
|
2962
2977
|
project_path: ap?.localPath || '',
|
|
2963
2978
|
};
|
|
2964
2979
|
|
|
2980
|
+
// Inject references and acceptance criteria
|
|
2981
|
+
const fanRefs = (item.references || []).map(r =>
|
|
2982
|
+
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
2983
|
+
).join('\n');
|
|
2984
|
+
vars.references = fanRefs ? '## References\n\n' + fanRefs : '';
|
|
2985
|
+
const fanAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
2986
|
+
vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
|
|
2987
|
+
|
|
2965
2988
|
if (workType === 'ask') {
|
|
2966
2989
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
2967
2990
|
vars.task_id = item.id;
|
|
@@ -3029,6 +3052,14 @@ function discoverCentralWorkItems(config) {
|
|
|
3029
3052
|
};
|
|
3030
3053
|
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch {}
|
|
3031
3054
|
|
|
3055
|
+
// Inject references and acceptance criteria
|
|
3056
|
+
const normRefs = (item.references || []).map(r =>
|
|
3057
|
+
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
3058
|
+
).join('\n');
|
|
3059
|
+
vars.references = normRefs ? '## References\n\n' + normRefs : '';
|
|
3060
|
+
const normAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
3061
|
+
vars.acceptance_criteria = normAc ? '## Acceptance Criteria\n\n' + normAc : '';
|
|
3062
|
+
|
|
3032
3063
|
// Inject plan-specific variables for the plan playbook
|
|
3033
3064
|
if (workType === 'plan') {
|
|
3034
3065
|
// Ensure plans directory exists before agent tries to write
|
package/package.json
CHANGED