@yemi33/minions 0.1.2018 → 0.1.2020

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.
@@ -115,6 +115,9 @@ let _meetingPollId = null;
115
115
  function _stopMeetingPoll() {
116
116
  if (_meetingPollInterval) { clearInterval(_meetingPollInterval); _meetingPollInterval = null; }
117
117
  _meetingPollId = null;
118
+ // F2 (W-mpgb0xe6000gb66d): drop linked-plan recheck cache when the modal closes.
119
+ _modalMeetingForLinkedPlanRecheck = null;
120
+ _modalLastLinkedPlanFile = null;
118
121
  }
119
122
 
120
123
  function _renderMeetingDetail(m) {
@@ -227,6 +230,10 @@ function _renderMeetingDetail(m) {
227
230
  try { showModalQa(); } catch { /* expected if QA not loaded */ }
228
231
 
229
232
  document.getElementById('modal').classList.add('open');
233
+
234
+ // F2 (W-mpgb0xe6000gb66d): refresh the linked-plan cache after each
235
+ // render so refreshPlans() can detect a flip and re-render the modal.
236
+ _setModalMeetingForLinkedPlanRecheck(m);
230
237
  }
231
238
 
232
239
  function openMeetingDetail(id) {
@@ -401,7 +408,14 @@ function _viewPlanWithBack(file, meetingId) {
401
408
  }
402
409
 
403
410
  function _findLinkedPlan(meeting) {
404
- var plans = window._lastStatus?.plans || [];
411
+ // F2 (W-mpgb0xe6000gb66d): prefer window._lastPlans (refreshed by refreshPlans
412
+ // every ~12s) over window._lastStatus.plans, which is gated on the next
413
+ // /api/status fetch and can lag a newly-created plan by an entire refresh
414
+ // cycle. Falling back to _lastStatus.plans preserves first-paint behavior
415
+ // before refreshPlans has run.
416
+ var plans = (Array.isArray(window._lastPlans) ? window._lastPlans : null)
417
+ || window._lastStatus?.plans
418
+ || [];
405
419
  // Check 1: regex in conclusion text (agent may reference plan path)
406
420
  if (meeting?.conclusion?.content) {
407
421
  var match = meeting.conclusion.content.match(/plans\/([\w-]+\.md)/);
@@ -419,6 +433,36 @@ function _findLinkedPlan(meeting) {
419
433
  return null;
420
434
  }
421
435
 
436
+ // F2 (W-mpgb0xe6000gb66d): post-refreshPlans hook. Cache the meeting
437
+ // currently displayed in the modal so refreshPlans() can re-evaluate
438
+ // _findLinkedPlan and re-render the action row when a freshly-created plan
439
+ // now matches. Without this, the modal poll loop (3s) only re-renders on
440
+ // meeting hash changes, so a plan created in the background never flips
441
+ // "Create Plan from Meeting" → "View Plan".
442
+ var _modalMeetingForLinkedPlanRecheck = null;
443
+ var _modalLastLinkedPlanFile = null;
444
+
445
+ function _setModalMeetingForLinkedPlanRecheck(meeting) {
446
+ if (meeting && meeting.status === 'completed') {
447
+ _modalMeetingForLinkedPlanRecheck = meeting;
448
+ _modalLastLinkedPlanFile = _findLinkedPlan(meeting)?.file || null;
449
+ } else {
450
+ _modalMeetingForLinkedPlanRecheck = null;
451
+ _modalLastLinkedPlanFile = null;
452
+ }
453
+ }
454
+
455
+ function _rerenderMeetingLinkedPlanIfChanged() {
456
+ if (!_modalMeetingForLinkedPlanRecheck) return;
457
+ var modalOpen = document.getElementById('modal')?.classList?.contains('open');
458
+ if (!modalOpen || !_meetingPollId) return;
459
+ var nextFile = _findLinkedPlan(_modalMeetingForLinkedPlanRecheck)?.file || null;
460
+ if (nextFile === _modalLastLinkedPlanFile) return;
461
+ // Linked-plan resolution changed — re-render the modal (preserves scroll;
462
+ // _renderMeetingDetail will refresh _modalLastLinkedPlanFile via the cache hook).
463
+ _renderMeetingDetail(_modalMeetingForLinkedPlanRecheck);
464
+ }
465
+
422
466
  async function _createPlanFromMeeting(id, btn) {
423
467
  // Optimistic: instant feedback before fetching the meeting + posting the WI.
424
468
  showToast('meeting-plan-toast', 'Plan task queued — agent will draft the plan from this meeting', true);
@@ -483,4 +527,4 @@ async function _deleteMeeting(id) {
483
527
  } catch (e) { clearDeleted('mtg:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
484
528
  }
485
529
 
486
- window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal };
530
+ window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal, _rerenderMeetingLinkedPlanIfChanged };
@@ -65,6 +65,17 @@ async function refreshPlans() {
65
65
  // colliding with the 'plans' slice tracked by _processStatusUpdate (F6).
66
66
  if (typeof _changed !== 'function' || _changed('plansPayload', plans)) {
67
67
  renderPlans(plans);
68
+ // F2 (W-mpgb0xe6000gb66d): if a meeting modal is open, ask it to re-check
69
+ // its linked-plan resolution against the freshly fetched plan list. Without
70
+ // this hook, a meeting modal's "Create Plan from Meeting" → "View Plan"
71
+ // flip waits for the next meeting-object change (refresh.js:135 gates
72
+ // renderMeetings on _changed(meetings, ...)). Gated on _changed so we only
73
+ // re-render meeting modal when plan data actually moved (F6 alignment).
74
+ try {
75
+ if (typeof _rerenderMeetingLinkedPlanIfChanged === 'function') {
76
+ _rerenderMeetingLinkedPlanIfChanged();
77
+ }
78
+ } catch { /* hook is best-effort — never block plan refresh on it */ }
68
79
  }
69
80
  } catch (e) { console.error('plans refresh:', e.message); }
70
81
  }
package/engine/shared.js CHANGED
@@ -3255,16 +3255,18 @@ function getOperatorLogin(config) {
3255
3255
  }
3256
3256
 
3257
3257
  function deriveWorkItemBranchName(item, config) {
3258
- const login = getOperatorLogin(config) || 'unknown';
3259
- const wid = String(item?.id || '').toLowerCase();
3260
- const src = String(item?.title || item?.description || '').toLowerCase();
3261
- let slug = src.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
3262
- const prefix = `user/${login}/${wid}-`;
3263
- // Cap total length at 120 chars by trimming the slug, leaving at least 8
3264
- // chars of slug room. Strip any trailing dash exposed by truncation.
3265
- const budget = Math.max(8, 120 - prefix.length);
3266
- if (slug.length > budget) slug = slug.slice(0, budget).replace(/-+$/, '');
3267
- return sanitizeBranch(prefix + (slug || 'work'));
3258
+ // Reverted to the pre-#2698 short form `work/<wi-id>` after the
3259
+ // descriptive-slug convention `user/<loginname>/<wi-id>-<slug>` produced
3260
+ // 80–120-char branch names that polluted derived identifiers (e.g. the
3261
+ // conflict-fix WI id in engine.js#buildDepConflictFixItem) and made the
3262
+ // dashboard unreadable. The function signature is preserved so call sites
3263
+ // and the dashboard pre-compute path stay wired. `getOperatorLogin` /
3264
+ // `config` are intentionally unused here but retained for callers that
3265
+ // may still inspect them.
3266
+ void config;
3267
+ const wid = String(item?.id || '').trim();
3268
+ if (!wid) return null;
3269
+ return sanitizeBranch(`work/${wid}`);
3268
3270
  }
3269
3271
 
3270
3272
  function _worktreeNameSuffix(dispatchId, projectName, branchName) {
package/engine.js CHANGED
@@ -214,6 +214,31 @@ function parseConflictFiles(mergeOutput) {
214
214
  // be spawned in the blocked item's worktree root. That is a PRE-EXISTING
215
215
  // limitation of the dep-conflict-fix path — single-project plans (the common
216
216
  // case, and the trigger scenario P-wi1-bridge-readonly-{a,b,c}) are unaffected.
217
+ // Derive a SHORT, stable conflict-fix WI id from a branch name. The legacy
218
+ // shape `conflict-fix-${branch.replace(/[^a-zA-Z0-9-]/g, '-')}` was fine when
219
+ // branches were `work/<wi-id>` (max ~25 chars), but PR #2698 codified the
220
+ // `user/<loginname>/<wi-id>-<slug>` convention which makes branch names
221
+ // 80-120+ chars and the resulting WI ids unreadable in the dashboard.
222
+ //
223
+ // Strategy:
224
+ // - strip `user/<login>/` prefix (only the WI portion matters)
225
+ // - strip `work/` prefix → keep `work-` for legacy-branch shape parity
226
+ // - sanitize remaining `/` → `-`
227
+ // - cap at 40 chars total; if truncated, suffix with 8-char sha1 of the
228
+ // full branch so different long branches that share a 40-char prefix
229
+ // still get distinct (deterministic) ids
230
+ function deriveConflictFixKey(branch) {
231
+ let s = String(branch || '');
232
+ const userMatch = s.match(/^user\/[^/]+\/(.+)$/);
233
+ if (userMatch) s = userMatch[1];
234
+ const workMatch = s.match(/^work\/(.+)$/);
235
+ if (workMatch) s = `work-${workMatch[1]}`;
236
+ s = s.replace(/[^a-zA-Z0-9-]/g, '-');
237
+ if (s.length <= 40) return s;
238
+ const hash = require('crypto').createHash('sha1').update(String(branch || '')).digest('hex').slice(0, 8);
239
+ return `${s.slice(0, 31)}-${hash}`;
240
+ }
241
+
217
242
  function buildDepConflictFixItem({
218
243
  depConflictBranch,
219
244
  depConflictFiles = [],
@@ -225,7 +250,7 @@ function buildDepConflictFixItem({
225
250
  }) {
226
251
  if (!depConflictBranch) throw new Error('buildDepConflictFixItem: depConflictBranch is required');
227
252
  if (!mainBranch) throw new Error('buildDepConflictFixItem: mainBranch is required');
228
- const conflictFixId = `conflict-fix-${depConflictBranch.replace(/[^a-zA-Z0-9-]/g, '-')}`;
253
+ const conflictFixId = `conflict-fix-${deriveConflictFixKey(depConflictBranch)}`;
229
254
  const filesDesc = depConflictFiles.length > 0
230
255
  ? `\n\nConflicting files:\n${depConflictFiles.map(f => '- ' + f).join('\n')}`
231
256
  : '';
@@ -6758,7 +6783,7 @@ module.exports = {
6758
6783
  reconcileItemsWithPrs, detectDependencyCycles,
6759
6784
  areDependenciesMet, // exported for testing (P-bf04-decompose-zero-children)
6760
6785
  parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
6761
- buildDepConflictFixItem, // exported for testing (W-mpcwojgr000a0244)
6786
+ buildDepConflictFixItem, deriveConflictFixKey, // exported for testing (W-mpcwojgr000a0244)
6762
6787
  isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, assertCleanSharedWorktree, // exported for testing
6763
6788
  pruneStaleWorktreeForBranch, // exported for testing
6764
6789
  findExistingWorktree, // exported for testing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2018",
3
+ "version": "0.1.2020",
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"