@yemi33/minions 0.1.2397 → 0.1.2398

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.
@@ -809,6 +809,13 @@ async function prdItemEdit(source, itemId) {
809
809
  if (!item) return;
810
810
 
811
811
  _prdDescRawCache = item.description || '';
812
+ const priorities = Array.isArray(window.MINIONS_WORK_ITEM_PRIORITIES)
813
+ ? window.MINIONS_WORK_ITEM_PRIORITIES
814
+ : [];
815
+ const priorityOptions = priorities.map(function(priority) {
816
+ const label = priority.charAt(0).toUpperCase() + priority.slice(1);
817
+ return '<option value="' + priority + '"' + (item.priority === priority ? ' selected' : '') + '>' + label + '</option>';
818
+ }).join('');
812
819
 
813
820
  // Look up work item and dispatch completion info. Issue #2949 —
814
821
  // dispatch moved off /api/status to /api/dispatch; refresh.js stashes
@@ -888,9 +895,7 @@ async function prdItemEdit(source, itemId) {
888
895
  '<div style="display:flex;gap:12px;margin-bottom:12px">' +
889
896
  '<div><label style="font-size:var(--text-base);color:var(--muted);display:block;margin-bottom:4px">Priority</label>' +
890
897
  '<select id="prd-edit-priority" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text)">' +
891
- '<option value="high"' + (item.priority === 'high' ? ' selected' : '') + '>High</option>' +
892
- '<option value="medium"' + (item.priority === 'medium' ? ' selected' : '') + '>Medium</option>' +
893
- '<option value="low"' + (item.priority === 'low' ? ' selected' : '') + '>Low</option>' +
898
+ priorityOptions +
894
899
  '</select></div>' +
895
900
  '<div><label style="font-size:var(--text-base);color:var(--muted);display:block;margin-bottom:4px">Complexity</label>' +
896
901
  '<select id="prd-edit-complexity" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text)">' +
@@ -431,7 +431,9 @@ function openScheduleDetail(id) {
431
431
 
432
432
  function _scheduleFormHtml(sched, isEdit) {
433
433
  const types = ['implement', 'test', 'explore', 'ask', 'review', 'fix'];
434
- const priorities = ['high', 'medium', 'low'];
434
+ const priorities = Array.isArray(window.MINIONS_WORK_ITEM_PRIORITIES)
435
+ ? window.MINIONS_WORK_ITEM_PRIORITIES
436
+ : [];
435
437
  const typeOpts = types.map(t => '<option value="' + t + '"' + ((sched.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
436
438
  const priOpts = priorities.map(p => '<option value="' + p + '"' + ((sched.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
437
439
  const projOpts = '<option value="">Any</option>' + (cmdProjects || []).map(p => '<option value="' + escHtml(p.name) + '"' + (sched.project === p.name ? ' selected' : '') + '>' + escHtml(p.displayName || p.name) + '</option>').join('');
@@ -353,7 +353,9 @@ async function editWorkItem(id, source) {
353
353
  }
354
354
  }
355
355
  const types = ['implement', 'fix', 'review', 'plan', 'verify', 'decompose', 'meeting', 'investigate', 'refactor', 'test', 'explore', 'ask', 'docs', 'setup'];
356
- const priorities = ['critical', 'high', 'medium', 'low'];
356
+ const priorities = Array.isArray(window.MINIONS_WORK_ITEM_PRIORITIES)
357
+ ? window.MINIONS_WORK_ITEM_PRIORITIES
358
+ : [];
357
359
  const agentOpts = (cmdAgents || []).map(a => '<option value="' + escapeHtml(a.id) + '"' + (item.agent === a.id ? ' selected' : '') + '>' + escapeHtml(a.name) + '</option>').join('');
358
360
  const typeOpts = types.map(t => '<option value="' + t + '"' + ((item.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
359
361
  const priOpts = priorities.map(p => '<option value="' + p + '"' + ((item.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
@@ -586,7 +588,10 @@ function openCreateWorkItemModal() {
586
588
  const typeOpts = ['implement', 'fix', 'explore', 'test', 'review', 'ask', 'plan', 'verify', 'decompose', 'meeting', 'docs', 'setup'].map(t =>
587
589
  '<option value="' + t + '"' + (t === 'implement' ? ' selected' : '') + '>' + t + '</option>'
588
590
  ).join('');
589
- const priOpts = ['critical', 'high', 'medium', 'low'].map(p =>
591
+ const priorities = Array.isArray(window.MINIONS_WORK_ITEM_PRIORITIES)
592
+ ? window.MINIONS_WORK_ITEM_PRIORITIES
593
+ : [];
594
+ const priOpts = priorities.map(p =>
590
595
  '<option value="' + p + '"' + (p === 'medium' ? ' selected' : '') + '>' + p + '</option>'
591
596
  ).join('');
592
597
  const agentOpts = (typeof cmdAgents !== 'undefined' ? cmdAgents : []).map(a =>
package/dashboard.js CHANGED
@@ -106,6 +106,8 @@ let PORT = REQUESTED_PORT;
106
106
  const CONFIG_PATH = path.join(MINIONS_DIR, 'config.json');
107
107
  const PINNED_PATH = path.join(MINIONS_DIR, 'pinned.md');
108
108
  const PINNED_DEFAULT_CONTENT = '# Pinned Context\n\nCritical notes visible to all agents.';
109
+ const WORK_ITEM_PRIORITY_PARAM_HINT = `priority? (${shared.WORK_ITEM_PRIORITY_VALUES.join('|')})`;
110
+ const PIPELINE_STAGES_PARAM_HINT = `stages[] (task/item priority: ${shared.WORK_ITEM_PRIORITY_VALUES.join('|')})`;
109
111
  const KB_PINS_PATH = shared.PINNED_ITEMS_PATH;
110
112
  const DASHBOARD_BROWSER_PRESENCE_PATH = path.join(ENGINE_DIR, 'dashboard-browser.json');
111
113
  const DASHBOARD_BROWSER_PRESENCE_MAX_AGE_MS = 45000;
@@ -1187,6 +1189,8 @@ function findWorkItemsTargetById(id, source, projects = PROJECTS) {
1187
1189
 
1188
1190
  function buildPlanWorkItem(body, projects = PROJECTS, options = {}) {
1189
1191
  if (!body?.title || !String(body.title).trim()) return { error: 'title is required' };
1192
+ const priorityError = shared.validateWorkItemPriority(body.priority);
1193
+ if (priorityError) return { error: priorityError.error, priorityError };
1190
1194
 
1191
1195
  // Multi-project: body.project may be an array. Each entry must resolve to a
1192
1196
  // known project; ≥2 valid entries trigger cross-repo mode (WI lands in central,
@@ -1225,7 +1229,7 @@ function buildPlanWorkItem(body, projects = PROJECTS, options = {}) {
1225
1229
  id: options.id || ('W-' + shared.uid()),
1226
1230
  title: body.title,
1227
1231
  type: 'plan',
1228
- priority: body.priority || 'high',
1232
+ priority: body.priority ?? shared.WORK_ITEM_PRIORITY.HIGH,
1229
1233
  description,
1230
1234
  status: WI_STATUS.PENDING,
1231
1235
  created: now.toISOString(),
@@ -1240,6 +1244,8 @@ function buildPlanWorkItem(body, projects = PROJECTS, options = {}) {
1240
1244
 
1241
1245
  function buildManualPrdItemPlan(body, projects = PROJECTS, options = {}) {
1242
1246
  if (!body?.name || !String(body.name).trim()) return { error: 'name is required' };
1247
+ const priorityError = shared.validateWorkItemPriority(body.priority);
1248
+ if (priorityError) return { error: priorityError.error, priorityError };
1243
1249
  const target = resolveWorkItemsCreateTarget(body.project, projects);
1244
1250
  if (target.error) return { error: target.error };
1245
1251
 
@@ -1258,7 +1264,7 @@ function buildManualPrdItemPlan(body, projects = PROJECTS, options = {}) {
1258
1264
  id: body.id || ('M' + String(nowTime).slice(-4)),
1259
1265
  name: String(body.name).trim(),
1260
1266
  description: body.description || '',
1261
- priority: body.priority || 'medium',
1267
+ priority: body.priority ?? shared.WORK_ITEM_PRIORITY.MEDIUM,
1262
1268
  estimated_complexity: body.estimated_complexity || 'medium',
1263
1269
  status: 'missing',
1264
1270
  depends_on: [],
@@ -1325,6 +1331,24 @@ function validatePipelineProjects(pipeline, projects = PROJECTS) {
1325
1331
  return null;
1326
1332
  }
1327
1333
 
1334
+ function validatePipelineWorkItemPriorities(pipeline) {
1335
+ const visit = (value) => {
1336
+ if (!value || typeof value !== 'object') return null;
1337
+ if (Object.prototype.hasOwnProperty.call(value, 'priority')) {
1338
+ const error = shared.validateWorkItemPriority(value.priority);
1339
+ if (error) return error;
1340
+ }
1341
+ for (const key of ['stages', 'items']) {
1342
+ for (const child of Array.isArray(value[key]) ? value[key] : []) {
1343
+ const error = visit(child);
1344
+ if (error) return error;
1345
+ }
1346
+ }
1347
+ return null;
1348
+ };
1349
+ return visit(pipeline);
1350
+ }
1351
+
1328
1352
  /**
1329
1353
  * Aggregate archived work items from central and project SQL scopes.
1330
1354
  *
@@ -1817,11 +1841,12 @@ function buildDashboardHtml() {
1817
1841
  } catch { /* registry empty or config unreadable — ship empty boot state */ }
1818
1842
 
1819
1843
  const featuresBootstrap = `window.MINIONS_FEATURES = ${JSON.stringify(featuresBoot)};\n`;
1844
+ const workItemPrioritiesBootstrap = `window.MINIONS_WORK_ITEM_PRIORITIES = ${JSON.stringify(shared.WORK_ITEM_PRIORITY_VALUES)};\n`;
1820
1845
 
1821
1846
  let assembled = layout
1822
1847
  .replace('/* __CSS__ */', () => css)
1823
1848
  .replace('<!-- __PAGES__ -->', () => pageHtml)
1824
- .replace('/* __JS__ */', () => `window.__MINIONS_HOME = ${JSON.stringify(os.homedir())};\n${featuresBootstrap}${jsHtml}`)
1849
+ .replace('/* __JS__ */', () => `window.__MINIONS_HOME = ${JSON.stringify(os.homedir())};\n${featuresBootstrap}${workItemPrioritiesBootstrap}${jsHtml}`)
1825
1850
  .replace(/\{\{favicon_emoji\}\}/g, FAVICON_EMOJI)
1826
1851
  .replace(/\{\{title_suffix\}\}/g, TITLE_SUFFIX);
1827
1852
 
@@ -6629,6 +6654,8 @@ const server = http.createServer(async (req, res) => {
6629
6654
  try {
6630
6655
  const body = await readBody(req);
6631
6656
  if (!body.title || !body.title.trim()) return jsonReply(res, 400, { error: 'title is required' });
6657
+ const priorityError = shared.validateWorkItemPriority(body.priority);
6658
+ if (priorityError) return jsonReply(res, 400, priorityError);
6632
6659
  if (body.depends_on !== undefined) {
6633
6660
  if (!Array.isArray(body.depends_on)) return jsonReply(res, 400, { error: 'depends_on must be an array of strings' });
6634
6661
  if (!body.depends_on.every(s => typeof s === 'string')) return jsonReply(res, 400, { error: 'depends_on entries must be strings' });
@@ -6686,7 +6713,7 @@ const server = http.createServer(async (req, res) => {
6686
6713
  const id = 'W-' + shared.uid();
6687
6714
  const item = {
6688
6715
  id, title: body.title.trim(), type: normalizedType,
6689
- priority: body.priority || 'medium', description: body.description || '',
6716
+ priority: body.priority ?? shared.WORK_ITEM_PRIORITY.MEDIUM, description: body.description || '',
6690
6717
  status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
6691
6718
  };
6692
6719
  if (Array.isArray(body.depends_on)) {
@@ -6835,6 +6862,8 @@ const server = http.createServer(async (req, res) => {
6835
6862
  const body = await readBody(req);
6836
6863
  const { id, source, title, description, type, priority, agent } = body;
6837
6864
  if (!id) return jsonReply(res, 400, { error: 'id required' });
6865
+ const priorityError = shared.validateWorkItemPriority(priority);
6866
+ if (priorityError) return jsonReply(res, 400, priorityError);
6838
6867
  if (body.depends_on !== undefined) {
6839
6868
  if (!Array.isArray(body.depends_on)) return jsonReply(res, 400, { error: 'depends_on must be an array of strings' });
6840
6869
  if (!body.depends_on.every(s => typeof s === 'string')) return jsonReply(res, 400, { error: 'depends_on entries must be strings' });
@@ -6983,7 +7012,7 @@ const server = http.createServer(async (req, res) => {
6983
7012
  const body = await readBody(req);
6984
7013
  if (!body.title || !body.title.trim()) return jsonReply(res, 400, { error: 'title is required' });
6985
7014
  const planWorkItem = buildPlanWorkItem(body);
6986
- if (planWorkItem.error) return jsonReply(res, 400, { error: planWorkItem.error });
7015
+ if (planWorkItem.error) return jsonReply(res, 400, planWorkItem.priorityError || { error: planWorkItem.error });
6987
7016
  // Write as a work item with type 'plan' — user must explicitly execute plan-to-prd after reviewing
6988
7017
  mutateWorkItems('central', items => { items.push(planWorkItem.item); });
6989
7018
  recordCcTurnIfPresent(req, {
@@ -6998,7 +7027,7 @@ const server = http.createServer(async (req, res) => {
6998
7027
  const body = await readBody(req);
6999
7028
  if (!body.name || !body.name.trim()) return jsonReply(res, 400, { error: 'name is required' });
7000
7029
  const manualPrd = buildManualPrdItemPlan(body);
7001
- if (manualPrd.error) return jsonReply(res, 400, { error: manualPrd.error });
7030
+ if (manualPrd.error) return jsonReply(res, 400, manualPrd.priorityError || { error: manualPrd.error });
7002
7031
 
7003
7032
  const planFile = 'manual-' + shared.uid() + '.json';
7004
7033
  prdStore.writePrd(planFile, manualPrd.plan);
@@ -7011,6 +7040,8 @@ const server = http.createServer(async (req, res) => {
7011
7040
  try {
7012
7041
  const body = await readBody(req);
7013
7042
  if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
7043
+ const priorityError = shared.validateWorkItemPriority(body.priority);
7044
+ if (priorityError) return jsonReply(res, 400, priorityError);
7014
7045
  if (!body.source.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename in `source` (got `' + body.source + '`). Pass prd/<plan>.json, not plans/<plan>.md.' });
7015
7046
  const preCheck = prdStore.readPrd(body.source);
7016
7047
  if (!preCheck) return jsonReply(res, 404, { error: 'plan not found' });
@@ -10671,6 +10702,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10671
10702
  const body = await readBody(req);
10672
10703
  let { id, cron, title, type, project, agent, description, priority, enabled } = body;
10673
10704
  if (!cron || !title) return jsonReply(res, 400, { error: 'cron and title are required' });
10705
+ const priorityError = shared.validateWorkItemPriority(priority);
10706
+ if (priorityError) return jsonReply(res, 400, priorityError);
10674
10707
  reloadConfig();
10675
10708
  const projectTarget = resolveScheduleProjectValue(project, PROJECTS);
10676
10709
  if (projectTarget.error) return jsonReply(res, 400, { error: projectTarget.error });
@@ -10709,6 +10742,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10709
10742
  const body = await readBody(req);
10710
10743
  let { id, cron, title, type, project, agent, description, priority, enabled } = body;
10711
10744
  if (!id) return jsonReply(res, 400, { error: 'id required' });
10745
+ const priorityError = shared.validateWorkItemPriority(priority);
10746
+ if (priorityError) return jsonReply(res, 400, priorityError);
10712
10747
  reloadConfig();
10713
10748
  if (project !== undefined) {
10714
10749
  const projectTarget = resolveScheduleProjectValue(project, PROJECTS);
@@ -10887,6 +10922,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10887
10922
  const body = await readBody(req);
10888
10923
  const { target, targetType, condition, interval, owner, description, project, notify, stopAfter, onNotMet, action, requires } = body;
10889
10924
  if (!target) return jsonReply(res, 400, { error: 'target is required' });
10925
+ const priorityError = watchesMod.validateActionWorkItemPriority(action);
10926
+ if (priorityError) return jsonReply(res, 400, priorityError);
10890
10927
  try {
10891
10928
  const watch = watchesMod.createWatch({ target, targetType, condition, interval, owner, description, project, notify, stopAfter, onNotMet, action, requires });
10892
10929
  invalidateStatusCache();
@@ -10903,6 +10940,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10903
10940
  const body = await readBody(req);
10904
10941
  const { id, ...updates } = body;
10905
10942
  if (!id) return jsonReply(res, 400, { error: 'id is required' });
10943
+ const priorityError = watchesMod.validateActionWorkItemPriority(updates.action);
10944
+ if (priorityError) return jsonReply(res, 400, priorityError);
10906
10945
  try {
10907
10946
  const watch = watchesMod.updateWatch(id, updates);
10908
10947
  if (!watch) return jsonReply(res, 404, { error: 'Watch not found' });
@@ -13391,8 +13430,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13391
13430
  { method: 'POST', path: '/api/qa/runners/reload', desc: 'Clear the in-process runner registry, re-register built-ins, and re-scan qa-runners.d/ for plugin edits.', handler: handleQaRunnersReload },
13392
13431
 
13393
13432
  // Work items
13394
- { method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: 'title, type?, description?, priority?, project?, agent?, agents?, scope?, references?, acceptanceCriteria?, skipPr?, oneShot?, meta?, meta.pr_followup?, meta.workdir?, X-Minions-Agent?, X-Minions-Origin-Wi?', handler: handleWorkItemsCreate },
13395
- { method: 'POST', path: '/api/work-items/update', desc: 'Edit a pending/failed work item', params: 'id, source?, title?, description?, type?, priority?, agent?, project?, references?, acceptanceCriteria?, depends_on?, workdir?, meta?', handler: handleWorkItemsUpdate },
13433
+ { method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: `title, type?, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, project?, agent?, agents?, scope?, references?, acceptanceCriteria?, skipPr?, oneShot?, meta?, meta.pr_followup?, meta.workdir?, X-Minions-Agent?, X-Minions-Origin-Wi?`, handler: handleWorkItemsCreate },
13434
+ { method: 'POST', path: '/api/work-items/update', desc: 'Edit a pending/failed work item', params: `id, source?, title?, description?, type?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, agent?, project?, references?, acceptanceCriteria?, depends_on?, workdir?, meta?`, handler: handleWorkItemsUpdate },
13396
13435
  { method: 'POST', path: '/api/work-items/retry', desc: 'Reset a failed/dispatched item to pending', params: 'id, source?', handler: handleWorkItemsRetry },
13397
13436
  { method: 'POST', path: '/api/work-items/delete', desc: 'Remove a work item, kill agent, clear dispatch', params: 'id, source?', handler: handleWorkItemsDelete },
13398
13437
  { method: 'POST', path: '/api/work-items/cancel', desc: 'Cancel a work item, kill agent, clear dispatch', params: 'id, source?, reason?', handler: handleWorkItemsCancel },
@@ -13496,7 +13535,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13496
13535
  { method: 'POST', path: '/api/notes-save', desc: 'Save edited notes.md content', params: 'content, file?', handler: handleNotesSave },
13497
13536
 
13498
13537
  // Plans
13499
- { method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: 'title, description?, priority?, project? (string OR array for cross-repo plans), agent?, branch_strategy? or branchStrategy?', handler: handlePlanCreate },
13538
+ { method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: `title, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, project? (string OR array for cross-repo plans), agent?, branch_strategy? or branchStrategy?`, handler: handlePlanCreate },
13500
13539
  { method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs)', handler: handlePlansList },
13501
13540
  { method: 'POST', path: '/api/plans/trigger-verify', desc: 'Manually trigger verification for a completed plan', params: 'file', handler: handlePlansTriggerVerify },
13502
13541
  { method: 'POST', path: '/api/plans/approve', desc: 'Approve a plan for execution', params: 'file, approvedBy?', handler: handlePlansApprove },
@@ -13513,8 +13552,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13513
13552
  { method: 'GET', path: /^\/api\/plans\/([^?]+)$/, template: '/api/plans/:file', desc: 'Read a full plan (JSON from prd/ or markdown from plans/)', handler: handlePlansRead },
13514
13553
 
13515
13554
  // PRD items
13516
- { method: 'POST', path: '/api/prd-items', desc: 'Create a PRD item as a plan file in prd/ (auto-approved)', params: 'name, description?, priority?, estimated_complexity?, project?, item_project?, id?', handler: handlePrdItemsCreate },
13517
- { method: 'POST', path: '/api/prd-items/update', desc: 'Edit a PRD item in its source plan JSON', params: 'source, itemId, name?, description?, priority?, estimated_complexity?, status?', handler: handlePrdItemsUpdate },
13555
+ { method: 'POST', path: '/api/prd-items', desc: 'Create a PRD item as a plan file in prd/ (auto-approved)', params: `name, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, estimated_complexity?, project?, item_project?, id?`, handler: handlePrdItemsCreate },
13556
+ { method: 'POST', path: '/api/prd-items/update', desc: 'Edit a PRD item in its source plan JSON', params: `source, itemId, name?, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, estimated_complexity?, status?`, handler: handlePrdItemsUpdate },
13518
13557
  { method: 'POST', path: '/api/prd-items/remove', desc: 'Remove a PRD item from plan + cancel materialized work item', params: 'source, itemId', handler: handlePrdItemsRemove },
13519
13558
  // /api/prd/regenerate removed — use /api/plans/approve which does diff-aware update
13520
13559
 
@@ -14323,8 +14362,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14323
14362
 
14324
14363
  // Schedules
14325
14364
  { method: 'POST', path: '/api/schedules/parse-natural', desc: 'Parse natural language schedule text into cron expression', params: 'text', handler: handleSchedulesParseNatural },
14326
- { method: 'POST', path: '/api/schedules', desc: 'Create a new schedule', params: 'cron, title, id?, type?, project?, agent?, description?, priority?, enabled?', handler: handleSchedulesCreate },
14327
- { method: 'POST', path: '/api/schedules/update', desc: 'Update an existing schedule', params: 'id, cron?, title?, type?, project?, agent?, description?, priority?, enabled?', handler: handleSchedulesUpdate },
14365
+ { method: 'POST', path: '/api/schedules', desc: 'Create a new schedule', params: `cron, title, id?, type?, project?, agent?, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, enabled?`, handler: handleSchedulesCreate },
14366
+ { method: 'POST', path: '/api/schedules/update', desc: 'Update an existing schedule', params: `id, cron?, title?, type?, project?, agent?, description?, ${WORK_ITEM_PRIORITY_PARAM_HINT}, enabled?`, handler: handleSchedulesUpdate },
14328
14367
  { method: 'POST', path: '/api/schedules/delete', desc: 'Delete a schedule', params: 'id', handler: handleSchedulesDelete },
14329
14368
  { method: 'POST', path: '/api/schedules/run-now', desc: 'Manually enqueue the work item for a schedule', params: 'id', handler: handleSchedulesRunNow },
14330
14369
 
@@ -14345,9 +14384,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14345
14384
  const result = pipelines.map(p => ({ ...p, runs: (runs[p.id] || []).slice(-5) }));
14346
14385
  return jsonReply(res, 200, result);
14347
14386
  }},
14348
- { method: 'POST', path: '/api/pipelines', desc: 'Create a pipeline', params: 'id, title, stages[], trigger?, stopWhen?, monitoredResources?', handler: async (req, res) => {
14387
+ { method: 'POST', path: '/api/pipelines', desc: 'Create a pipeline', params: `id, title, ${PIPELINE_STAGES_PARAM_HINT}, trigger?, stopWhen?, monitoredResources?`, handler: async (req, res) => {
14349
14388
  const body = await readBody(req);
14350
14389
  if (!body.id || !body.title || !body.stages) return jsonReply(res, 400, { error: 'id, title, and stages required' });
14390
+ const priorityError = validatePipelineWorkItemPriorities({ stages: body.stages });
14391
+ if (priorityError) return jsonReply(res, 400, priorityError);
14351
14392
  const { savePipeline, getPipeline } = require('./engine/pipeline');
14352
14393
  if (getPipeline(body.id)) return jsonReply(res, 409, { error: 'Pipeline already exists' });
14353
14394
  const pipeline = { id: body.id, title: body.title, stages: body.stages, trigger: body.trigger || {}, enabled: body.enabled !== false };
@@ -14361,12 +14402,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14361
14402
  invalidateStatusCache();
14362
14403
  return jsonReply(res, 200, { ok: true, id: pipeline.id });
14363
14404
  }},
14364
- { method: 'POST', path: '/api/pipelines/update', desc: 'Update a pipeline', params: 'id, title?, stages?, trigger?, enabled?, stopWhen?, monitoredResources?', handler: async (req, res) => {
14405
+ { method: 'POST', path: '/api/pipelines/update', desc: 'Update a pipeline', params: `id, title?, ${PIPELINE_STAGES_PARAM_HINT.replace('stages[]', 'stages[]?')}, trigger?, enabled?, stopWhen?, monitoredResources?`, handler: async (req, res) => {
14365
14406
  const body = await readBody(req);
14366
14407
  if (!body.id) return jsonReply(res, 400, { error: 'id required' });
14367
14408
  const { getPipeline, savePipeline } = require('./engine/pipeline');
14368
14409
  const pipeline = getPipeline(body.id);
14369
14410
  if (!pipeline) return jsonReply(res, 404, { error: 'Pipeline not found' });
14411
+ const priorityError = validatePipelineWorkItemPriorities({ stages: body.stages });
14412
+ if (priorityError) return jsonReply(res, 400, priorityError);
14370
14413
  if (body.title !== undefined) pipeline.title = body.title;
14371
14414
  if (body.stages !== undefined) pipeline.stages = body.stages;
14372
14415
  if (body.trigger !== undefined) pipeline.trigger = body.trigger;
package/engine/shared.js CHANGED
@@ -4048,6 +4048,27 @@ function backfillProjectWorkSourceDefaults(config) {
4048
4048
 
4049
4049
  // ─── Status & Type Constants ─────────────────────────────────────────────────
4050
4050
 
4051
+ const WORK_ITEM_PRIORITY = Object.freeze({
4052
+ CRITICAL: 'critical',
4053
+ HIGH: 'high',
4054
+ MEDIUM: 'medium',
4055
+ LOW: 'low',
4056
+ });
4057
+ const WORK_ITEM_PRIORITY_VALUES = Object.freeze(Object.values(WORK_ITEM_PRIORITY));
4058
+ const _WORK_ITEM_PRIORITY_SET = new Set(WORK_ITEM_PRIORITY_VALUES);
4059
+ const WORK_ITEM_PRIORITY_ERROR_CODE = 'invalid-work-item-priority';
4060
+
4061
+ function validateWorkItemPriority(priority) {
4062
+ if (priority === undefined || _WORK_ITEM_PRIORITY_SET.has(priority)) return null;
4063
+ const rejected = JSON.stringify(priority);
4064
+ return {
4065
+ error: `Invalid work-item priority ${rejected}. Allowed values: ${WORK_ITEM_PRIORITY_VALUES.join(', ')}.`,
4066
+ code: WORK_ITEM_PRIORITY_ERROR_CODE,
4067
+ rejectedValue: priority,
4068
+ allowedValues: [...WORK_ITEM_PRIORITY_VALUES],
4069
+ };
4070
+ }
4071
+
4051
4072
  const WI_STATUS = {
4052
4073
  PENDING: 'pending', DISPATCHED: 'dispatched', DONE: 'done', FAILED: 'failed',
4053
4074
  PAUSED: 'paused', QUEUED: 'queued',
@@ -8781,6 +8802,7 @@ module.exports = {
8781
8802
  runtimeConfigWarnings,
8782
8803
  projectWorkSourceWarnings,
8783
8804
  backfillProjectWorkSourceDefaults,
8805
+ WORK_ITEM_PRIORITY, WORK_ITEM_PRIORITY_VALUES, WORK_ITEM_PRIORITY_ERROR_CODE, validateWorkItemPriority,
8784
8806
  WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, isPrdArchived, isDefunctPrd, WORK_TYPE, isFixLikeWorkType, WORKTREE_REQUIRING_TYPES, LIVE_VALIDATION_WORK_TYPES, VALID_WORK_TYPES, resolveWorkItemTypeFromPrdItem, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, PR_PENDING_REASON, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, RETRY_DELAY_MS, ADO_TOKEN_REFRESH_MAX_RETRIES, DISPATCH_RESULT, mutateMetrics, mutateWatches, mutateScheduleRuns, mutatePipelineRuns, mutateManagedProcesses, mutateWorktreePool, mutateQaRuns, mutateQaSessions, trackReviewMetric, queuePlanToPrd, extractPlanDeclaredProject, extractPlanTargetProjects,
8785
8807
  WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS, WATCH_ACTION_TYPE,
8786
8808
  WATCH_STALLED_DEFAULT_TICKS, WATCH_STUCK_STAGE_DEFAULT_TICKS,
@@ -397,12 +397,16 @@ registerActionType(WATCH_ACTION_TYPE.DISPATCH_WORK_ITEM, {
397
397
  { key: 'type', required: false, description: 'Work type (implement|fix|review|...). Defaults to implement.' },
398
398
  { key: 'agent', required: false, description: 'Specific agent to route to.' },
399
399
  { key: 'project', required: false, description: 'Target project (defaults to watch.project, or central).' },
400
- { key: 'priority', required: false, description: 'low|medium|high. Defaults to medium.' },
400
+ { key: 'priority', required: false, description: `${shared.WORK_ITEM_PRIORITY_VALUES.join('|')}. Defaults to medium.` },
401
401
  ],
402
402
  handler: async (watch, ctx) => {
403
403
  const p = ctx.params || {};
404
404
  const title = String(p.title || '').trim();
405
405
  if (!title) return { ok: false, summary: 'dispatch-work-item: title is required' };
406
+ const priorityError = shared.validateWorkItemPriority(p.priority);
407
+ if (priorityError) {
408
+ return { ok: false, summary: priorityError.error, error: priorityError };
409
+ }
406
410
 
407
411
  const project = p.project || watch.project || null;
408
412
  const scope = project || 'central';
@@ -412,7 +416,7 @@ registerActionType(WATCH_ACTION_TYPE.DISPATCH_WORK_ITEM, {
412
416
  id,
413
417
  title,
414
418
  type: p.type || WORK_TYPE.IMPLEMENT,
415
- priority: p.priority || 'medium',
419
+ priority: p.priority ?? shared.WORK_ITEM_PRIORITY.MEDIUM,
416
420
  description: p.description || '',
417
421
  status: WI_STATUS.PENDING,
418
422
  created: ts(),
@@ -473,6 +477,7 @@ registerActionType(WATCH_ACTION_TYPE.RUN_SKILL, {
473
477
  { key: 'args', required: false, description: 'Args/instructions to pass to the skill.' },
474
478
  { key: 'agent', required: false, description: 'Specific agent to route to.' },
475
479
  { key: 'project', required: false, description: 'Target project.' },
480
+ { key: 'priority', required: false, description: `${shared.WORK_ITEM_PRIORITY_VALUES.join('|')}. Defaults to medium.` },
476
481
  ],
477
482
  handler: async (watch, ctx) => {
478
483
  const p = ctx.params || {};
@@ -488,7 +493,7 @@ registerActionType(WATCH_ACTION_TYPE.RUN_SKILL, {
488
493
  type: WORK_TYPE.IMPLEMENT,
489
494
  agent: p.agent,
490
495
  project: p.project,
491
- priority: p.priority || 'medium',
496
+ priority: p.priority ?? shared.WORK_ITEM_PRIORITY.MEDIUM,
492
497
  },
493
498
  });
494
499
  },
@@ -1092,6 +1097,8 @@ function validateAction(action) {
1092
1097
  return `action.params.${p.key} is required for action type ${type}`;
1093
1098
  }
1094
1099
  }
1100
+ const priorityError = validateActionWorkItemPriority(action);
1101
+ if (priorityError) return priorityError.error;
1095
1102
  // P-w7c5d8b3 — Phase 3.2: optional `guard` field. Shape is locked here
1096
1103
  // (object with a string `expr`), but the expression itself is NOT
1097
1104
  // pre-parsed at validation time — safe-expr.evaluate is the single
@@ -1178,6 +1185,23 @@ function validateAction(action) {
1178
1185
  return null;
1179
1186
  }
1180
1187
 
1188
+ function validateActionWorkItemPriority(action) {
1189
+ if (action === null || action === undefined) return null;
1190
+ if (Array.isArray(action)) {
1191
+ for (const step of action) {
1192
+ const error = validateActionWorkItemPriority(step);
1193
+ if (error) return error;
1194
+ }
1195
+ return null;
1196
+ }
1197
+ if (typeof action !== 'object') return null;
1198
+ const type = String(action.type || '').toLowerCase();
1199
+ if (type !== WATCH_ACTION_TYPE.DISPATCH_WORK_ITEM && type !== WATCH_ACTION_TYPE.RUN_SKILL) return null;
1200
+ const priority = action.params?.priority;
1201
+ if (typeof priority === 'string' && priority.includes('{{')) return null;
1202
+ return shared.validateWorkItemPriority(priority);
1203
+ }
1204
+
1181
1205
  module.exports = {
1182
1206
  registerActionType,
1183
1207
  getActionType,
@@ -1187,6 +1211,7 @@ module.exports = {
1187
1211
  buildTriggerContext,
1188
1212
  substituteTemplate,
1189
1213
  validateAction,
1214
+ validateActionWorkItemPriority,
1190
1215
  computeWatchDedupKey, // exported for testing (BUG-H10)
1191
1216
  WATCH_DEDUP_ACTIVE_STATUSES, // exported for testing (BUG-H10)
1192
1217
  };
package/engine/watches.js CHANGED
@@ -1455,6 +1455,7 @@ module.exports = {
1455
1455
  runWatchAction: watchActions.runWatchAction,
1456
1456
  runWatchActionChain: watchActions.runWatchActionChain,
1457
1457
  validateAction: watchActions.validateAction,
1458
+ validateActionWorkItemPriority: watchActions.validateActionWorkItemPriority,
1458
1459
  _captureState, // exported for testing
1459
1460
  _runActionTask, // exported for testing — invoked by checkWatches per fired action
1460
1461
  _TARGET_TYPES: TARGET_TYPES, // exported for testing — direct registry access
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2397",
3
+ "version": "0.1.2398",
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"