@yemi33/minions 0.1.81 → 0.1.83

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,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.83 (2026-03-31)
4
+
5
+ ### Engine
6
+ - engine/lifecycle.js
7
+
8
+ ## 0.1.82 (2026-03-31)
9
+
10
+ ### Engine
11
+ - engine/lifecycle.js
12
+
13
+ ### Dashboard
14
+ - dashboard.js
15
+ - dashboard/js/command-center.js
16
+
17
+ ### Playbooks
18
+ - implement.md
19
+
3
20
  ## 0.1.81 (2026-03-31)
4
21
 
5
22
  ### Dashboard
@@ -384,6 +384,45 @@ async function ccExecuteAction(action) {
384
384
  }
385
385
  break;
386
386
  }
387
+ case 'schedule': {
388
+ const url = action._update ? '/api/schedules/update' : '/api/schedules';
389
+ const res = await fetch(url, {
390
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
391
+ body: JSON.stringify({
392
+ id: action.id, title: action.title, cron: action.cron,
393
+ type: action.workType || 'implement',
394
+ project: action.project, agent: action.agent,
395
+ description: action.description, priority: action.priority,
396
+ enabled: action.enabled !== false,
397
+ })
398
+ });
399
+ if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Schedule create failed'); }
400
+ status.innerHTML = '&#10003; Schedule ' + (action._update ? 'updated' : 'created') + ': <strong>' + escHtml(action.id) + '</strong>';
401
+ status.style.color = 'var(--green)';
402
+ break;
403
+ }
404
+ case 'delete-schedule': {
405
+ const res = await fetch('/api/schedules/delete', {
406
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
407
+ body: JSON.stringify({ id: action.id })
408
+ });
409
+ if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Schedule delete failed'); }
410
+ status.innerHTML = '&#10003; Deleted schedule: <strong>' + escHtml(action.id) + '</strong>';
411
+ status.style.color = 'var(--orange)';
412
+ break;
413
+ }
414
+ case 'create-meeting': {
415
+ const res = await fetch('/api/meetings', {
416
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
417
+ body: JSON.stringify({ topic: action.topic, agents: action.agents, rounds: action.rounds, project: action.project })
418
+ });
419
+ if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Meeting create failed'); }
420
+ const d = await res.json();
421
+ status.innerHTML = '&#10003; Meeting started: <strong>' + escHtml(action.topic) + '</strong>' + (d.id ? ' (' + escHtml(d.id) + ')' : '');
422
+ status.style.color = 'var(--green)';
423
+ wakeEngine();
424
+ break;
425
+ }
387
426
  default:
388
427
  status.innerHTML = '? Unknown action: ' + escHtml(action.type);
389
428
  status.style.color = 'var(--muted)';
package/dashboard.js CHANGED
@@ -371,6 +371,9 @@ Available action types:
371
371
  - **plan-edit**: Revise/edit a plan .md file. Fields: file (plan .md filename from plans/), instruction (what to change).
372
372
  - **execute-plan**: Execute an existing plan .md file. Fields: file (plan .md filename), project (optional)
373
373
  - **file-edit**: Edit any minions file via LLM. Fields: file (path relative to minions dir), instruction (what to change).
374
+ - **schedule**: Create or update a scheduled task. Fields: id (unique slug), title, cron (3-field: minute hour dayOfWeek), workType (implement/test/explore/ask/review/fix), project (optional), agent (optional), description (optional), priority (optional), enabled (default true). Example cron: "0 9 2" = every Tuesday at 9am.
375
+ - **delete-schedule**: Delete a scheduled task. Fields: id.
376
+ - **create-meeting**: Start a team meeting. Fields: topic, agents (array of agent IDs), rounds (optional, default 3), project (optional).
374
377
 
375
378
  ## Rules
376
379
 
@@ -419,7 +422,8 @@ ${projects}
419
422
  ### Scheduled Tasks
420
423
  ${schedSummary}
421
424
 
422
- To discover all available dashboard APIs, fetch GET http://localhost:7331/api/routes — it returns every endpoint with method, path, description, and accepted parameters.
425
+ ### Dashboard API (all endpoints)
426
+ ${_getApiRoutesSummary()}
423
427
 
424
428
  For details on any of the above, use your tools to read files under \`${MINIONS_DIR}\`.`;
425
429
  }
@@ -446,6 +450,16 @@ function parseCCActions(text) {
446
450
  return { text: displayText, actions };
447
451
  }
448
452
 
453
+ // ── API routes reference for CC — populated by server setup ──────────────────
454
+ let _apiRoutesRef = null; // set to ROUTES array once server initializes
455
+ function _getApiRoutesSummary() {
456
+ if (!_apiRoutesRef) return '(API routes not yet loaded — fetch GET /api/routes to discover endpoints)';
457
+ return _apiRoutesRef
458
+ .filter(r => r.path !== '/api/routes' && typeof r.path === 'string')
459
+ .map(r => `- \`${r.method} ${r.path}\` — ${r.desc}${r.params ? ' | Params: ' + r.params : ''}`)
460
+ .join('\n');
461
+ }
462
+
449
463
  // ── Shared LLM call core — used by CC panel and doc modals ──────────────────
450
464
 
451
465
  // Session store for doc modals — keyed by filePath or title, persisted to disk
@@ -3310,6 +3324,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3310
3324
  { method: 'POST', path: '/api/settings/routing', desc: 'Update routing.md', params: 'content', handler: handleSettingsRouting },
3311
3325
  ];
3312
3326
 
3327
+ // Expose routes to CC preamble builder (once, on first request)
3328
+ if (!_apiRoutesRef) _apiRoutesRef = ROUTES;
3329
+
3313
3330
  // ── Route Dispatcher ────────────────────────────────────────────────────────
3314
3331
 
3315
3332
  const pathname = req.url.split('?')[0];
@@ -545,11 +545,10 @@ function syncPrsFromOutput(output, agentId, meta, config) {
545
545
 
546
546
  const projects = shared.getProjects(config);
547
547
  const defaultProject = (meta?.project?.name && projects.find(p => p.name === meta.project.name)) || projects[0];
548
- if (!defaultProject) return 0;
548
+ const useCentral = !defaultProject;
549
549
 
550
550
  // Match each PR to its correct project by finding which repo URL appears near the PR number in output
551
551
  function resolveProjectForPr(prId) {
552
- // Look for the PR URL in output to determine which ADO project it belongs to
553
552
  for (const p of projects) {
554
553
  if (!p.prUrlBase) continue;
555
554
  const urlFragment = p.prUrlBase.replace(/pullrequest\/$/, '');
@@ -561,21 +560,32 @@ function syncPrsFromOutput(output, agentId, meta, config) {
561
560
  return defaultProject;
562
561
  }
563
562
 
563
+ // Extract PR URL directly from agent output — no manual construction
564
+ function extractPrUrl(prId) {
565
+ const ghMatch = output.match(new RegExp(`https?://github\\.com/[^\\s"'\\)\\]]*?/pull/${prId}(?:[^\\s"'\\)\\]]*)`, 'i'));
566
+ if (ghMatch) return ghMatch[0].replace(/[.,;:]+$/, '');
567
+ const adoMatch = output.match(new RegExp(`https?://(?:dev\\.azure\\.com|[^/]+\\.visualstudio\\.com)[^\\s"'\\)\\]]*?pullrequest/${prId}(?:[^\\s"'\\)\\]]*)`, 'i'));
568
+ if (adoMatch) return adoMatch[0].replace(/[.,;:]+$/, '');
569
+ return '';
570
+ }
571
+
564
572
  const agentName = config.agents?.[agentId]?.name || agentId;
565
573
  let added = 0;
566
- // Track which project PR files need writing
567
- const dirtyProjects = new Map(); // projectName -> { project, prs, prPath }
574
+ const centralPrPath = path.join(MINIONS_DIR, 'pull-requests.json');
575
+ // Track which PR files need writing keyed by target name
576
+ const dirtyTargets = new Map(); // name -> { prs, prPath }
568
577
 
569
578
  for (const prId of prMatches) {
570
579
  const fullId = `PR-${prId}`;
571
- const targetProject = resolveProjectForPr(prId);
572
- const prPath = shared.projectPrPath(targetProject);
580
+ const targetProject = useCentral ? null : resolveProjectForPr(prId);
581
+ const targetName = targetProject ? targetProject.name : '_central';
582
+ const prPath = targetProject ? shared.projectPrPath(targetProject) : centralPrPath;
573
583
 
574
- // Load PRs for this project (cache per project)
575
- if (!dirtyProjects.has(targetProject.name)) {
576
- dirtyProjects.set(targetProject.name, { project: targetProject, prs: safeJson(prPath) || [], prPath });
584
+ // Load PRs for this target (cache per target)
585
+ if (!dirtyTargets.has(targetName)) {
586
+ dirtyTargets.set(targetName, { prs: safeJson(prPath) || [], prPath });
577
587
  }
578
- const entry = dirtyProjects.get(targetProject.name);
588
+ const entry = dirtyTargets.get(targetName);
579
589
  if (entry.prs.some(p => p.id === fullId || String(p.id).includes(prId))) continue;
580
590
 
581
591
  let title = meta?.item?.title || '';
@@ -592,7 +602,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
592
602
  reviewStatus: 'pending',
593
603
  status: 'active',
594
604
  created: e.dateStamp(),
595
- url: targetProject.prUrlBase ? targetProject.prUrlBase + prId : '',
605
+ url: extractPrUrl(prId),
596
606
  prdItems: meta?.item?.id ? [meta.item.id] : [],
597
607
  sourcePlan: meta?.item?.sourcePlan || '',
598
608
  itemType: meta?.item?.itemType || ''
@@ -601,9 +611,9 @@ function syncPrsFromOutput(output, agentId, meta, config) {
601
611
  added++;
602
612
  }
603
613
 
604
- for (const [name, entry] of dirtyProjects) {
614
+ for (const [name, entry] of dirtyTargets) {
605
615
  shared.safeWrite(entry.prPath, entry.prs);
606
- e.log('info', `Synced PR(s) from ${agentName}'s output to ${name}/pull-requests.json`);
616
+ e.log('info', `Synced PR(s) from ${agentName}'s output to ${name === '_central' ? 'central' : name}/pull-requests.json`);
607
617
  }
608
618
  return added;
609
619
  }
@@ -1136,8 +1146,8 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1136
1146
  const projects = shared.getProjects(config);
1137
1147
  const existingPrFound = Object.values(getPrLinks()).includes(meta.item.id);
1138
1148
  if (!existingPrFound) {
1139
- e.log('warn', `Agent completed implement task ${meta.item.id} but no PR was created`);
1140
- // Set noPr flag on the work item so the dashboard can surface this
1149
+ e.log('warn', `Agent completed implement task ${meta.item.id} but no PR was created — reverting to failed for retry`);
1150
+ // Revert to failed so auto-retry can re-attempt with PR creation
1141
1151
  let wiPath;
1142
1152
  if (meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout') {
1143
1153
  wiPath = path.join(MINIONS_DIR, 'work-items.json');
@@ -1149,6 +1159,18 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1149
1159
  const wi = items.find(i => i.id === meta.item.id);
1150
1160
  if (wi) {
1151
1161
  wi.noPr = true;
1162
+ wi.failReason = 'Completed without creating a pull request';
1163
+ const retries = wi._retryCount || 0;
1164
+ if (retries < 3) {
1165
+ wi.status = 'pending';
1166
+ wi._retryCount = retries + 1;
1167
+ delete wi.dispatched_at;
1168
+ delete wi.dispatched_to;
1169
+ e.log('info', `Auto-retry ${retries + 1}/3 for ${meta.item.id} (no PR created)`);
1170
+ } else {
1171
+ wi.status = 'failed';
1172
+ e.log('warn', `${meta.item.id} failed after 3 retries — no PR created`);
1173
+ }
1152
1174
  shared.safeWrite(wiPath, items);
1153
1175
  }
1154
1176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
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"
@@ -56,7 +56,9 @@ cd {{team_root}}
56
56
  git worktree remove ../worktrees/{{branch_name}} --force
57
57
  ```
58
58
 
59
- ## Create PR
59
+ ## Create PR (MANDATORY)
60
+
61
+ **Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.
60
62
 
61
63
  {{pr_create_instructions}}
62
64
  - sourceRefName: `refs/heads/{{branch_name}}`