@yemi33/minions 0.1.641 → 0.1.643

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 CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.641 (2026-04-08)
3
+ ## 0.1.643 (2026-04-09)
4
+
5
+ ### Features
6
+ - track notes, plans, and PRDs as work item artifacts
7
+
8
+ ## 0.1.642 (2026-04-09)
9
+
10
+ ### Features
11
+ - show artifact links in work item detail modal
4
12
 
5
13
  ### Fixes
6
14
  - clear _retryCount on successful completion
@@ -372,9 +372,11 @@ async function _abortPipeline(id, btn) {
372
372
  try {
373
373
  var res = await fetch('/api/pipelines/abort', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
374
374
  if (res.ok) {
375
- showToast('cmd-toast', 'Pipeline run aborted', true);
375
+ var d = await res.json().catch(function() { return {}; });
376
+ showToast('cmd-toast', 'Pipeline aborted — ' + (d.cancelledWorkItems || 0) + ' work items cancelled', true);
376
377
  if (btn) { btn.textContent = '\u2713 Aborted'; btn.style.color = 'var(--red)'; }
377
- setTimeout(function() { openPipelineDetail(id); }, 1500);
378
+ refresh();
379
+ openPipelineDetail(id);
378
380
  } else { var d = await res.json().catch(function() { return {}; }); alert('Abort failed: ' + (d.error || 'unknown')); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
379
381
  } catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
380
382
  }
@@ -444,6 +444,20 @@ function openWorkItemDetail(id) {
444
444
  if (item.references?.length) html += field('References', item.references.map(r => '<a href="' + escHtml(r.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(r.title || r.url) + '</a>' + (r.type ? ' <span style="color:var(--muted);font-size:10px">(' + escHtml(r.type) + ')</span>' : '')).join('<br>'));
445
445
  if (item._humanFeedback) html += field('Human Feedback', (item._humanFeedback.rating === 'up' ? '👍' : '👎') + (item._humanFeedback.comment ? ' — ' + escHtml(item._humanFeedback.comment) : ''));
446
446
  if (item._pr) html += field('Pull Request', '<a href="' + escHtml(item._prUrl || '#') + '" target="_blank" style="color:var(--blue)">' + escHtml(item._pr) + '</a>');
447
+
448
+ // Artifacts — output log, branch, skills, etc.
449
+ var arts = item._artifacts || {};
450
+ var artPills = '';
451
+ var pillStyle = 'display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:10px;font-size:10px;cursor:pointer;background:var(--surface2);border:1px solid var(--border);color:var(--text)';
452
+ if (arts.outputLog) artPills += '<span onclick="viewAgentOutput(\'' + escHtml(arts.outputLog) + '\')" style="' + pillStyle + '">📄 Output Log</span> ';
453
+ if (arts.branch) artPills += '<span style="' + pillStyle + ';cursor:default">🌿 ' + escHtml(arts.branch) + '</span> ';
454
+ if (arts.plan) artPills += '<span onclick="planView(\'' + escHtml(arts.plan) + '\')" style="' + pillStyle + '">📋 Plan</span> ';
455
+ if (arts.prd) artPills += '<span onclick="planView(\'' + escHtml(arts.prd) + '\')" style="' + pillStyle + '">📄 PRD</span> ';
456
+ if (arts.sourcePlan) artPills += '<span onclick="planView(\'' + escHtml(arts.sourcePlan) + '\')" style="' + pillStyle + '">📋 Source Plan</span> ';
457
+ if (arts.notes && arts.notes.length > 0) arts.notes.forEach(function(n) { artPills += '<span onclick="closeModal();switchPage(\'inbox\')" style="' + pillStyle + '">📝 ' + escHtml(n.replace(/\.md$/, '').slice(0, 30)) + '</span> '; });
458
+ if (arts.skills && arts.skills.length > 0) arts.skills.forEach(function(s) { artPills += '<span onclick="openSkill(\'' + escHtml(s) + '\',\'minions\',\'\')" style="' + pillStyle + '">⚙ ' + escHtml(s) + '</span> '; });
459
+ if (artPills) html += field('Artifacts', '<div style="display:flex;flex-wrap:wrap;gap:4px">' + artPills + '</div>');
460
+
447
461
  if (item._totalCostUsd != null) html += field('Cumulative Cost', '$' + Number(item._totalCostUsd).toFixed(4));
448
462
  if (item._totalInputTokens) html += field('Total Input Tokens', Number(item._totalInputTokens).toLocaleString());
449
463
  if (item._totalOutputTokens) html += field('Total Output Tokens', Number(item._totalOutputTokens).toLocaleString());
@@ -466,4 +480,20 @@ function openAllWorkItems() {
466
480
  document.getElementById('modal').classList.add('open');
467
481
  }
468
482
 
469
- window.MinionsWork = { wiRow, renderWorkItems, editWorkItem, submitWorkItemEdit, deleteWorkItem, archiveWorkItem, toggleWorkItemArchive, retryWorkItem, wiPrev, wiNext, feedbackWorkItem, submitFeedback, openCreateWorkItemModal, openWorkItemDetail, openAllWorkItems };
483
+ function viewAgentOutput(logPath) {
484
+ document.getElementById('modal-title').textContent = 'Agent Output';
485
+ document.getElementById('modal-body').innerHTML = '<p style="color:var(--muted)">Loading...</p>';
486
+ document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
487
+ document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
488
+ document.getElementById('modal').classList.add('open');
489
+ fetch('/api/agent-output?file=' + encodeURIComponent(logPath))
490
+ .then(function(r) { return r.text(); })
491
+ .then(function(content) {
492
+ document.getElementById('modal-body').textContent = content;
493
+ })
494
+ .catch(function() {
495
+ document.getElementById('modal-body').innerHTML = '<p style="color:var(--red)">Failed to load output.</p>';
496
+ });
497
+ }
498
+
499
+ window.MinionsWork = { wiRow, renderWorkItems, editWorkItem, submitWorkItemEdit, deleteWorkItem, archiveWorkItem, toggleWorkItemArchive, retryWorkItem, wiPrev, wiNext, feedbackWorkItem, submitFeedback, openCreateWorkItemModal, openWorkItemDetail, openAllWorkItems, viewAgentOutput };
package/dashboard.js CHANGED
@@ -3883,7 +3883,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3883
3883
 
3884
3884
  { method: 'POST', path: '/api/plans/create', desc: 'Create a plan from user-provided content', params: 'title, content, project?', handler: async (req, res) => {
3885
3885
  const body = await readBody(req);
3886
- const { title, content, project: projectName } = body;
3886
+ const { title, content, project: projectName, meetingId } = body;
3887
3887
  if (!title || !content) return jsonReply(res, 400, { error: 'title and content required' });
3888
3888
 
3889
3889
  const plansDir = path.join(MINIONS_DIR, 'plans');
@@ -3895,6 +3895,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3895
3895
 
3896
3896
  const header = `# ${title}\n\n` +
3897
3897
  (projectName ? `**Project:** ${projectName}\n` : '') +
3898
+ (meetingId ? `**Source Meeting:** ${meetingId}\n` : '') +
3898
3899
  `**Created:** ${date}\n**By:** human teammate\n\n---\n\n`;
3899
3900
  safeWrite(filePath, header + content);
3900
3901
 
@@ -3935,6 +3936,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3935
3936
  { method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live(?:\?.*)?$/, desc: 'Tail live output for a working agent', params: 'tail? (bytes, default 8192)', handler: handleAgentLive },
3936
3937
  { method: 'GET', path: /^\/api\/agent\/([\w-]+)\/output(?:\?.*)?$/, desc: 'Fetch final output.log for an agent', handler: handleAgentOutput },
3937
3938
  { method: 'GET', path: /^\/api\/agent\/([\w-]+)$/, desc: 'Get detailed agent info', handler: handleAgentDetail },
3939
+ { method: 'GET', path: '/api/agent-output', desc: 'Read agent output log file', params: 'file', handler: async (req, res) => {
3940
+ const file = new URL(req.url, 'http://localhost').searchParams.get('file');
3941
+ if (!file || file.includes('..') || file.includes('\0') || !file.startsWith('agents/')) return jsonReply(res, 400, { error: 'invalid file' });
3942
+ const content = safeRead(path.join(MINIONS_DIR, file));
3943
+ if (content === null) return jsonReply(res, 404, { error: 'not found' });
3944
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
3945
+ res.setHeader('Cache-Control', 'no-cache');
3946
+ res.end(content);
3947
+ } },
3938
3948
 
3939
3949
  // Knowledge base
3940
3950
  { method: 'GET', path: '/api/knowledge', desc: 'List all knowledge base entries grouped by category', handler: handleKnowledgeList },
@@ -4062,8 +4072,25 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4062
4072
  const run = getActiveRun(body.id);
4063
4073
  if (!run) return jsonReply(res, 404, { error: 'No active run to abort' });
4064
4074
  completeRun(body.id, run.runId, 'failed');
4075
+ // Cancel pending/active work items and dispatches spawned by this run
4076
+ let cancelled = 0;
4077
+ const wiPaths = [path.join(MINIONS_DIR, 'work-items.json'), ...PROJECTS.map(p => shared.projectWorkItemsPath(p))];
4078
+ for (const wiPath of wiPaths) {
4079
+ try {
4080
+ mutateWorkItems(wiPath, items => {
4081
+ for (const w of items) {
4082
+ if (w._pipelineRun === run.runId && w.status !== shared.WI_STATUS.DONE && w.status !== shared.WI_STATUS.CANCELLED) {
4083
+ w.status = shared.WI_STATUS.CANCELLED;
4084
+ w._cancelledBy = 'pipeline-abort';
4085
+ cancelled++;
4086
+ }
4087
+ }
4088
+ });
4089
+ } catch {}
4090
+ }
4091
+ const dispatchCleaned = cleanDispatchEntries(d => d.meta?.item?._pipelineRun === run.runId);
4065
4092
  invalidateStatusCache();
4066
- return jsonReply(res, 200, { ok: true, runId: run.runId });
4093
+ return jsonReply(res, 200, { ok: true, runId: run.runId, cancelledWorkItems: cancelled, cancelledDispatches: dispatchCleaned });
4067
4094
  }},
4068
4095
  { method: 'POST', path: '/api/pipelines/retrigger', desc: 'Abort active run (if any) and start a new one', params: 'id', handler: async (req, res) => {
4069
4096
  const body = await readBody(req);
package/engine.js CHANGED
@@ -786,6 +786,38 @@ async function spawnAgent(dispatchItem, config) {
786
786
 
787
787
  log('info', `Agent ${agentId} completed. Output saved to ${archivePath}`);
788
788
 
789
+ // Track artifacts on the work item for dashboard display
790
+ if (dispatchItem.meta?.item?.id) {
791
+ try {
792
+ const artWiPath = resolveWorkItemPath(dispatchItem.meta);
793
+ if (artWiPath) {
794
+ // Collect inbox notes written by this agent today
795
+ const _artToday = shared.dateStamp();
796
+ const _artInboxDir = path.join(MINIONS_DIR, 'notes', 'inbox');
797
+ let _artNotes = [];
798
+ try { _artNotes = shared.safeReadDir(_artInboxDir).filter(f => f.includes(agentId) && f.includes(_artToday)); } catch {}
799
+
800
+ mutateJsonFileLocked(artWiPath, data => {
801
+ if (!Array.isArray(data)) return data;
802
+ const wi = data.find(i => i.id === dispatchItem.meta.item.id);
803
+ if (!wi) return data;
804
+ const arts = wi._artifacts || {};
805
+ arts.outputLog = `agents/${agentId}/output-${id}.log`;
806
+ if (dispatchItem.meta.branch) arts.branch = dispatchItem.meta.branch;
807
+ if (wi._pr) arts.pr = wi._pr;
808
+ if (wi._prUrl) arts.prUrl = wi._prUrl;
809
+ if (_artNotes.length > 0) arts.notes = _artNotes;
810
+ // Track plan/PRD artifacts from dispatch metadata
811
+ if (dispatchItem.meta.item?.planFile) arts.plan = dispatchItem.meta.item.planFile;
812
+ if (dispatchItem.meta.item?._prdFilename) arts.prd = dispatchItem.meta.item._prdFilename;
813
+ if (dispatchItem.meta.item?.sourcePlan) arts.sourcePlan = dispatchItem.meta.item.sourcePlan;
814
+ wi._artifacts = arts;
815
+ return data;
816
+ });
817
+ }
818
+ } catch (err) { log('warn', `Artifact tracking: ${err.message}`); }
819
+ }
820
+
789
821
  // Clean up temp agent directory
790
822
  if (tempAgents.has(agentId)) {
791
823
  tempAgents.delete(agentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.641",
3
+ "version": "0.1.643",
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"