@yemi33/minions 0.1.1778 → 0.1.1779

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,8 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1778 (2026-05-07)
3
+ ## 0.1.1779 (2026-05-07)
4
4
 
5
5
  ### Features
6
+ - fix cc doc chat resume continuity (#2184)
7
+ - consolidate CC dispatch action type (#2183)
6
8
  - harden dashboard state mutations (#2175)
7
9
 
8
10
  ## 0.1.1777 (2026-05-07)
@@ -103,15 +103,15 @@ function _ccActiveTab() {
103
103
 
104
104
  // Build a plain-text transcript from a tab's stored messages — sent on every
105
105
  // initial request so the server can carry it over if the session has to reset
106
- // (runtime switch, system-prompt change). Last 20 user/assistant turns only;
107
- // system/action rows are skipped because they're UI artifacts, not dialog.
106
+ // (runtime switch, system-prompt change) or if the previous turn has local
107
+ // action results the runtime session never saw.
108
108
  var CC_TRANSCRIPT_MAX_TURNS = 20;
109
109
  function _ccBuildTranscript(tab) {
110
110
  if (!tab || !Array.isArray(tab.messages) || tab.messages.length === 0) return [];
111
111
  var out = [];
112
112
  for (var i = 0; i < tab.messages.length; i++) {
113
113
  var m = tab.messages[i];
114
- if (!m || (m.role !== 'user' && m.role !== 'assistant')) continue;
114
+ if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'action' && m.role !== 'system')) continue;
115
115
  var html = typeof m.html === 'string' ? m.html : '';
116
116
  var tmp = document.createElement('div');
117
117
  tmp.innerHTML = html;
@@ -141,6 +141,23 @@ function _ccMergeStreamText(prev, incoming) {
141
141
  return current + '\n\n' + next;
142
142
  }
143
143
 
144
+ var CC_DISPATCH_ACTION_ALIASES = ['fix', 'implement', 'explore', 'review', 'test'];
145
+ function _ccNormalizeDispatchAction(action) {
146
+ if (!action || typeof action !== 'object' || typeof action.type !== 'string') return action;
147
+ var type = action.type.trim().toLowerCase();
148
+ if (type === 'dispatch') {
149
+ if (action.type === 'dispatch') return action;
150
+ var dispatchAction = Object.assign({}, action);
151
+ dispatchAction.type = 'dispatch';
152
+ return dispatchAction;
153
+ }
154
+ if (CC_DISPATCH_ACTION_ALIASES.indexOf(type) < 0) return action;
155
+ var normalized = Object.assign({}, action);
156
+ normalized.type = 'dispatch';
157
+ if (!normalized.workType) normalized.workType = type;
158
+ return normalized;
159
+ }
160
+
144
161
  async function _ccDashboardHealth() {
145
162
  var controller = new AbortController();
146
163
  var timer = setTimeout(function() { controller.abort(); }, 3000);
@@ -745,7 +762,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
745
762
  if (!isReconnect && res.status === 429 && (!activeTab._429retries || activeTab._429retries < 3)) {
746
763
  activeTab._429retries = (activeTab._429retries || 0) + 1;
747
764
  await new Promise(function(r) { setTimeout(r, 1500); });
748
- return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null }, false);
765
+ return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, transcript: _ccBuildTranscript(activeTab) }, false);
749
766
  }
750
767
  activeTab._429retries = 0;
751
768
  var errText = await res.text();
@@ -994,6 +1011,7 @@ function _tagServerExecuted(actions, actionResults) {
994
1011
  }
995
1012
 
996
1013
  async function ccExecuteAction(action, targetTabId) {
1014
+ action = _ccNormalizeDispatchAction(action);
997
1015
  var status = document.createElement('div');
998
1016
  status.style.cssText = 'padding:4px 10px;border-radius:4px;font-size:10px;align-self:flex-start;border:1px dashed var(--border);color:var(--muted)';
999
1017
 
@@ -1010,7 +1028,7 @@ async function ccExecuteAction(action, targetTabId) {
1010
1028
  status.style.color = action._serverDuplicate ? 'var(--orange)' : 'var(--green)';
1011
1029
  }
1012
1030
  ccAddMessage('action', status.outerHTML, false, targetTabId);
1013
- if (['dispatch','fix','implement','explore','review','test','create-meeting'].includes(action.type)) wakeEngine();
1031
+ if (['dispatch','create-meeting'].includes(action.type)) wakeEngine();
1014
1032
  refresh();
1015
1033
  return;
1016
1034
  }
@@ -1023,7 +1041,7 @@ async function ccExecuteAction(action, targetTabId) {
1023
1041
  case 'explore':
1024
1042
  case 'review':
1025
1043
  case 'test': {
1026
- var workType = action.workType || (action.type !== 'dispatch' ? action.type : 'implement');
1044
+ var workType = action.workType || 'implement';
1027
1045
  // Forward both singular (`agent`) and plural (`agents`) hint shapes —
1028
1046
  // the LLM emits either depending on phrasing ("assign to lambert" vs
1029
1047
  // "dispatch to dallas, ralph"). The server-side handler promotes a
@@ -28,6 +28,7 @@ let _qaAbortController = null;
28
28
  let _qaQueue = []; // queued messages while processing
29
29
  const QA_QUEUE_CAP = 10; // max queued messages
30
30
  const QA_STREAM_STALL_MS = 6 * 60 * 1000; // allow the full doc-chat timeout before treating heartbeat-only streams as stalled
31
+ const QA_TRANSCRIPT_MAX_TURNS = 20;
31
32
  let _qaSessionKey = ''; // key for current conversation (title or filePath)
32
33
 
33
34
  const QA_STICKY_BOTTOM_PX = 80;
@@ -97,6 +98,40 @@ function _qaCloneQueue(queue) {
97
98
  return Array.isArray(queue) ? queue.map(item => ({ ...item })) : [];
98
99
  }
99
100
 
101
+ function _qaBuildTranscript(history, currentMessage) {
102
+ if (!Array.isArray(history) || history.length === 0) return [];
103
+ const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
104
+ const out = [];
105
+ for (let i = 0; i < history.length; i++) {
106
+ const m = history[i];
107
+ if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'action' && m.role !== 'system')) continue;
108
+ const text = typeof m.text === 'string' ? m.text.trim() : '';
109
+ if (!text) continue;
110
+ if (current && m.role === 'user' && text === current && i === history.length - 1) continue;
111
+ out.push({ role: m.role, text });
112
+ }
113
+ return out.slice(-QA_TRANSCRIPT_MAX_TURNS);
114
+ }
115
+
116
+ function _qaSummarizeActionContext(actions, actionResults) {
117
+ if (!Array.isArray(actions) || actions.length === 0) return '';
118
+ const lines = [];
119
+ for (let i = 0; i < actions.length; i++) {
120
+ const action = actions[i] || {};
121
+ const result = Array.isArray(actionResults) ? actionResults[i] : null;
122
+ const type = action.type || 'action';
123
+ const subject = result?.id || action.id || action.title || action.file || action.target || action.endpoint || '';
124
+ if (result?.error) {
125
+ lines.push(`${type}${subject ? ' ' + subject : ''} failed: ${result.error}`);
126
+ } else if (result?.ok || action._serverExecuted) {
127
+ lines.push(`${type}${subject ? ' ' + subject : ''} completed${result?.duplicate ? ' (duplicate)' : ''}${result?.warning ? ': ' + result.warning : ''}`);
128
+ } else {
129
+ lines.push(`${type}${subject ? ' ' + subject : ''} emitted`);
130
+ }
131
+ }
132
+ return lines.join('\n');
133
+ }
134
+
100
135
  function _qaGetRuntime(key) {
101
136
  if (!key) return null;
102
137
  let runtime = _qaRuntime.get(key);
@@ -609,6 +644,7 @@ async function _processQaMessage(message, selection, opts) {
609
644
  filePath: capturedFilePath || null,
610
645
  model: window._lastStatus?.autoMode?.ccModel || undefined,
611
646
  contentHash: capturedDocContext.content ? (function(s) { const m = Math.floor(s.length / 2); return s.length + ':' + s.charCodeAt(0) + ':' + s.charCodeAt(m) + ':' + s.charCodeAt(s.length - 1); })(capturedDocContext.content) : undefined,
647
+ transcript: _qaBuildTranscript(runtime.history, message),
612
648
  }),
613
649
  });
614
650
  let sessionDocContext = { ...capturedDocContext };
@@ -692,20 +728,21 @@ async function _processQaMessage(message, selection, opts) {
692
728
  if (rawErrorHtml) _qaInsertBeforeQueued(tmp, rawErrorHtml);
693
729
  });
694
730
 
695
- runtime.history.push({ role: 'user', text: message });
696
- runtime.history.push({ role: 'assistant', text: finalText || '' });
697
- if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
698
-
699
731
  _qaNotifySidebar(capturedFilePath);
732
+ runtime.history.push({ role: 'user', text: message });
733
+ runtime.history.push({ role: 'assistant', text: bodyText || finalText || '' });
700
734
  if (evt.actions && evt.actions.length > 0) {
701
735
  if (evt.actionResults && typeof _tagServerExecuted === 'function') _tagServerExecuted(evt.actions, evt.actionResults);
702
736
  for (const action of evt.actions) await ccExecuteAction(action);
737
+ const actionContext = _qaSummarizeActionContext(evt.actions, evt.actionResults);
738
+ if (actionContext) runtime.history.push({ role: 'action', text: actionContext });
703
739
  } else if (evt.actionParseError) {
704
740
  const warning = '<div class="modal-qa-a" style="color:var(--red)">Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>';
705
741
  updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
706
742
  _qaInsertBeforeQueued(tmp, warning);
707
743
  });
708
744
  }
745
+ if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
709
746
 
710
747
  if (evt.edited && evt.content) {
711
748
  const display = evt.content.replace(/^---[\s\S]*?---\n*/m, '');
package/dashboard.js CHANGED
@@ -1366,10 +1366,36 @@ function _readCcTabSessions({ prune = true } = {}) {
1366
1366
 
1367
1367
  const CC_CARRYOVER_MAX_TURNS = 20;
1368
1368
  const CC_CARRYOVER_PER_MSG_CHARS = 2000;
1369
+ const CC_TRANSCRIPT_DIALOG_ROLES = new Set(['user', 'assistant']);
1370
+ const CC_TRANSCRIPT_CONTEXT_ROLES = new Set(['user', 'assistant', 'action', 'system']);
1369
1371
 
1370
- function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage } = {}) {
1372
+ function _normalizeTranscriptRole(role) {
1373
+ const value = String(role || '').toLowerCase();
1374
+ return CC_TRANSCRIPT_CONTEXT_ROLES.has(value) ? value : null;
1375
+ }
1376
+
1377
+ function _transcriptHasCarryoverContext(transcript, { outOfBandOnly = false, currentMessage } = {}) {
1378
+ if (!Array.isArray(transcript)) return false;
1379
+ const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
1380
+ return transcript.some(m => {
1381
+ const role = _normalizeTranscriptRole(m?.role);
1382
+ if (!role || typeof m.text !== 'string' || !m.text.trim()) return false;
1383
+ if (outOfBandOnly && CC_TRANSCRIPT_DIALOG_ROLES.has(role)) return false;
1384
+ return !(current && role === 'user' && m.text.trim() === current);
1385
+ });
1386
+ }
1387
+
1388
+ function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage, outOfBandOnly = false } = {}) {
1371
1389
  if (!Array.isArray(transcript) || transcript.length === 0) return '';
1372
- let filtered = transcript.filter(m => m && (m.role === 'user' || m.role === 'assistant') && typeof m.text === 'string' && m.text.trim());
1390
+ let filtered = transcript
1391
+ .map(m => {
1392
+ const role = _normalizeTranscriptRole(m?.role);
1393
+ return role && typeof m?.text === 'string' && m.text.trim()
1394
+ ? { role, text: m.text }
1395
+ : null;
1396
+ })
1397
+ .filter(Boolean);
1398
+ if (outOfBandOnly) filtered = filtered.filter(m => !CC_TRANSCRIPT_DIALOG_ROLES.has(m.role));
1373
1399
  const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
1374
1400
  if (current && filtered.length > 0) {
1375
1401
  const last = filtered[filtered.length - 1];
@@ -1378,11 +1404,19 @@ function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage
1378
1404
  if (filtered.length === 0) return '';
1379
1405
  const recent = filtered.slice(-CC_CARRYOVER_MAX_TURNS);
1380
1406
  const truncated = filtered.length > recent.length;
1381
- const header = previousRuntime
1382
- ? `--- Previous conversation (carried over from ${previousRuntime} session) ---`
1383
- : `--- Previous conversation (carried over) ---`;
1407
+ const header = outOfBandOnly
1408
+ ? `--- Previous out-of-band UI/server events (carried over) ---`
1409
+ : previousRuntime
1410
+ ? `--- Previous conversation (carried over from ${previousRuntime} session) ---`
1411
+ : `--- Previous conversation (carried over) ---`;
1384
1412
  const lines = recent.map(m => {
1385
- const who = m.role === 'user' ? 'User' : 'Assistant';
1413
+ const who = m.role === 'user'
1414
+ ? 'User'
1415
+ : m.role === 'assistant'
1416
+ ? 'Assistant'
1417
+ : m.role === 'action'
1418
+ ? 'Action result'
1419
+ : 'System note';
1386
1420
  let text = m.text.trim();
1387
1421
  if (text.length > CC_CARRYOVER_PER_MSG_CHARS) text = text.slice(0, CC_CARRYOVER_PER_MSG_CHARS) + '… [truncated]';
1388
1422
  return `${who}: ${text}`;
@@ -1751,6 +1785,19 @@ function _extractActionsJson(segment) {
1751
1785
  return null;
1752
1786
  }
1753
1787
 
1788
+ const CC_DISPATCH_ACTION_ALIASES = new Set(['fix', 'implement', 'explore', 'review', 'test']);
1789
+
1790
+ function normalizeCCAction(action) {
1791
+ if (!action || typeof action !== 'object') return action;
1792
+ if (typeof action.type !== 'string') return action;
1793
+ const type = action.type.trim().toLowerCase();
1794
+ if (type === 'dispatch') {
1795
+ return action.type === 'dispatch' ? action : { ...action, type: 'dispatch' };
1796
+ }
1797
+ if (!CC_DISPATCH_ACTION_ALIASES.has(type)) return action;
1798
+ return { ...action, type: 'dispatch', workType: action.workType || type };
1799
+ }
1800
+
1754
1801
  function parseCCActions(text) {
1755
1802
  let actions = [];
1756
1803
  let displayText = stripCCActionsForDisplay(text);
@@ -1791,6 +1838,7 @@ function parseCCActions(text) {
1791
1838
  parseError = null; // legacy fallback recovered actions
1792
1839
  }
1793
1840
  }
1841
+ actions = actions.map(normalizeCCAction);
1794
1842
  const result = { text: displayText, actions };
1795
1843
  if (parseError && actions.length === 0) {
1796
1844
  result._actionParseError = parseError;
@@ -2186,7 +2234,8 @@ function _ccValidateAction(action) {
2186
2234
 
2187
2235
  async function executeCCActions(actions) {
2188
2236
  const results = [];
2189
- for (const action of actions) {
2237
+ for (const rawAction of actions) {
2238
+ const action = normalizeCCAction(rawAction);
2190
2239
  const validationError = _ccValidateAction(action);
2191
2240
  if (validationError) {
2192
2241
  results.push({ type: action?.type || 'unknown', error: validationError });
@@ -2195,7 +2244,7 @@ async function executeCCActions(actions) {
2195
2244
  try {
2196
2245
  switch (action.type) {
2197
2246
  case 'dispatch': case 'fix': case 'implement': case 'explore': case 'review': case 'test': {
2198
- const workType = routing.normalizeWorkType(action.workType || (action.type !== 'dispatch' ? action.type : WORK_TYPE.IMPLEMENT), WORK_TYPE.IMPLEMENT);
2247
+ const workType = routing.normalizeWorkType(action.workType || WORK_TYPE.IMPLEMENT, WORK_TYPE.IMPLEMENT);
2199
2248
  const id = 'W-' + shared.uid();
2200
2249
  const project = action.project || '';
2201
2250
  const prRef = getWorkItemPrRef(action);
@@ -2661,12 +2710,14 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2661
2710
  const existing = resolveSession(store, sessionKey);
2662
2711
  let sessionId = existing ? existing.sessionId : null;
2663
2712
  const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
2713
+ const resumeHasOutOfBandCarryover = !!sessionId && _transcriptHasCarryoverContext(transcript, { outOfBandOnly: true, currentMessage: message });
2714
+ const freshNeedsCarryover = _transcriptHasCarryoverContext(transcript, { currentMessage: message });
2664
2715
 
2665
- function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
2716
+ function buildPrompt({ includePreamble = true, includeCarryover = false, outOfBandOnly = false } = {}) {
2666
2717
  const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
2667
2718
  if (extraContext) parts.push(extraContext);
2668
2719
  if (includeCarryover) {
2669
- const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
2720
+ const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message, outOfBandOnly });
2670
2721
  if (carryover) parts.push(carryover);
2671
2722
  }
2672
2723
  parts.push(message);
@@ -2677,7 +2728,11 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2677
2728
 
2678
2729
  // Attempt 1: resume existing session — skip preamble (session already has context)
2679
2730
  if (sessionId && maxTurns > 1) {
2680
- const p1 = llm.callLLM(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
2731
+ const p1 = llm.callLLM(buildPrompt({
2732
+ includePreamble: false,
2733
+ includeCarryover: resumeNeedsCarryover || resumeHasOutOfBandCarryover,
2734
+ outOfBandOnly: !resumeNeedsCarryover,
2735
+ }), '', {
2681
2736
  timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
2682
2737
  engineConfig: CONFIG.engine,
2683
2738
  });
@@ -2714,7 +2769,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2714
2769
  }
2715
2770
 
2716
2771
  // Attempt 2: fresh session (include preamble for full context)
2717
- const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
2772
+ const freshPrompt = buildPrompt({ includeCarryover: freshNeedsCarryover });
2718
2773
  const p2 = llm.callLLM(freshPrompt, systemPrompt, {
2719
2774
  timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
2720
2775
  engineConfig: CONFIG.engine,
@@ -2762,12 +2817,14 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2762
2817
  const existing = resolveSession(store, sessionKey);
2763
2818
  let sessionId = existing ? existing.sessionId : null;
2764
2819
  const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
2820
+ const resumeHasOutOfBandCarryover = !!sessionId && _transcriptHasCarryoverContext(transcript, { outOfBandOnly: true, currentMessage: message });
2821
+ const freshNeedsCarryover = _transcriptHasCarryoverContext(transcript, { currentMessage: message });
2765
2822
 
2766
- function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
2823
+ function buildPrompt({ includePreamble = true, includeCarryover = false, outOfBandOnly = false } = {}) {
2767
2824
  const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
2768
2825
  if (extraContext) parts.push(extraContext);
2769
2826
  if (includeCarryover) {
2770
- const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
2827
+ const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message, outOfBandOnly });
2771
2828
  if (carryover) parts.push(carryover);
2772
2829
  }
2773
2830
  parts.push(message);
@@ -2777,7 +2834,11 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2777
2834
  let result;
2778
2835
 
2779
2836
  if (sessionId && maxTurns > 1) {
2780
- const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
2837
+ const p1 = llm.callLLMStreaming(buildPrompt({
2838
+ includePreamble: false,
2839
+ includeCarryover: resumeNeedsCarryover || resumeHasOutOfBandCarryover,
2840
+ outOfBandOnly: !resumeNeedsCarryover,
2841
+ }), '', {
2781
2842
  timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
2782
2843
  engineConfig: CONFIG.engine,
2783
2844
  onChunk,
@@ -2814,7 +2875,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2814
2875
  }
2815
2876
 
2816
2877
  if (onRetry) onRetry(2);
2817
- const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
2878
+ const freshPrompt = buildPrompt({ includeCarryover: freshNeedsCarryover });
2818
2879
  const p2 = llm.callLLMStreaming(freshPrompt, systemPrompt, {
2819
2880
  timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
2820
2881
  engineConfig: CONFIG.engine,
@@ -3186,7 +3247,7 @@ function _makeDocChatStreamStripper(onChunk) {
3186
3247
  }
3187
3248
 
3188
3249
  // Doc-specific wrapper — adds document context, parses ---DOCUMENT---
3189
- async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady }) {
3250
+ async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, transcript, onAbortReady }) {
3190
3251
  const sessionKey = filePath || title;
3191
3252
  const docSlice = String(document || '');
3192
3253
 
@@ -3213,6 +3274,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
3213
3274
  allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
3214
3275
  skipStatePreamble: true,
3215
3276
  systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
3277
+ transcript,
3216
3278
  ...(model ? { model } : {}),
3217
3279
  onAbortReady,
3218
3280
  });
@@ -3250,7 +3312,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
3250
3312
  return _parseDocChatResultText(result.text);
3251
3313
  }
3252
3314
 
3253
- async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady, onChunk, onToolUse, onRetry }) {
3315
+ async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, transcript, onAbortReady, onChunk, onToolUse, onRetry }) {
3254
3316
  const sessionKey = filePath || title;
3255
3317
  const docSlice = String(document || '');
3256
3318
  const streamStripper = _makeDocChatStreamStripper(onChunk);
@@ -3273,6 +3335,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
3273
3335
  allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
3274
3336
  skipStatePreamble: true,
3275
3337
  systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
3338
+ transcript,
3276
3339
  ...(model ? { model } : {}),
3277
3340
  onAbortReady,
3278
3341
  onChunk: streamStripper,
@@ -5433,6 +5496,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5433
5496
  filePath: body.filePath, selection: body.selection, canEdit, isJson,
5434
5497
  model: body.model || undefined,
5435
5498
  freshSession: !!body.freshSession,
5499
+ transcript: body.transcript,
5436
5500
  onAbortReady: (abort) => { _docAbort = abort; },
5437
5501
  });
5438
5502
  const actionResults = await executeDocChatActions(actions);
@@ -5523,6 +5587,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5523
5587
  filePath: body.filePath, selection: body.selection, canEdit, isJson,
5524
5588
  model: body.model || undefined,
5525
5589
  freshSession: !!body.freshSession,
5590
+ transcript: body.transcript,
5526
5591
  onAbortReady: (abort) => { _docAbort = abort; },
5527
5592
  onChunk: (text) => { writeDocEvent({ type: 'chunk', text }); },
5528
5593
  onToolUse: (name, input) => { writeDocEvent({ type: 'tool', name, input: _lightToolInput(input) }); },
@@ -6279,11 +6344,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6279
6344
  const wasResume = !!tabSessionId;
6280
6345
  const sessionId = tabSessionId || null;
6281
6346
  const resumeNeedsCarryover = wasResume && _ccRuntimeNeedsResumeCarryover(currentRuntime);
6347
+ const resumeHasOutOfBandCarryover = wasResume && _transcriptHasCarryoverContext(body.transcript, { outOfBandOnly: true, currentMessage: body.message });
6282
6348
  const preamble = wasResume ? '' : buildCCStatePreamble();
6283
- const carryover = (sessionReset || resumeNeedsCarryover)
6349
+ const includeFullCarryover = sessionReset || resumeNeedsCarryover;
6350
+ const carryover = (includeFullCarryover || resumeHasOutOfBandCarryover)
6284
6351
  ? _buildTranscriptCarryover(body.transcript, {
6285
6352
  previousRuntime: sessionReset ? previousRuntime : null,
6286
6353
  currentMessage: body.message,
6354
+ outOfBandOnly: !includeFullCarryover,
6287
6355
  })
6288
6356
  : '';
6289
6357
  const prompt = _joinCcPromptParts(preamble, carryover, body.message);
@@ -7592,8 +7660,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
7592
7660
  { method: 'GET', path: /^\/api\/knowledge\/([^/]+)\/([^?]+)/, template: '/api/knowledge/:category/:file', desc: 'Read a specific knowledge base entry', handler: handleKnowledgeRead },
7593
7661
 
7594
7662
  // Doc chat
7595
- { method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?, contentHash?', handler: handleDocChat },
7596
- { method: 'POST', path: '/api/doc-chat/stream', desc: 'Streaming doc chat — SSE with text chunks and tool progress', params: 'message, document, title?, filePath?, selection?, contentHash?', handler: handleDocChatStream },
7663
+ { method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?, contentHash?, transcript?', handler: handleDocChat },
7664
+ { method: 'POST', path: '/api/doc-chat/stream', desc: 'Streaming doc chat — SSE with text chunks and tool progress', params: 'message, document, title?, filePath?, selection?, contentHash?, transcript?', handler: handleDocChatStream },
7597
7665
 
7598
7666
  // Inbox
7599
7667
  { method: 'POST', path: '/api/inbox/persist', desc: 'Promote an inbox item to team notes', params: 'name', handler: handleInboxPersist },
@@ -7617,8 +7685,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
7617
7685
  // Command Center
7618
7686
  { method: 'POST', path: '/api/command-center/new-session', desc: 'Clear active CC session', handler: handleCommandCenterNewSession },
7619
7687
  { method: 'POST', path: '/api/command-center/abort', desc: 'Abort an in-flight CC request for a tab', params: 'tabId?', handler: handleCommandCenterAbort },
7620
- { method: 'POST', path: '/api/command-center', desc: 'Conversational command center with full minions context', params: 'message, sessionId?', handler: handleCommandCenter },
7621
- { method: 'POST', path: '/api/command-center/stream', desc: 'Streaming CC — SSE with text chunks as they arrive', params: 'message, tabId?', handler: handleCommandCenterStream },
7688
+ { method: 'POST', path: '/api/command-center', desc: 'Conversational command center with full minions context', params: 'message, tabId?, sessionId?, transcript?', handler: handleCommandCenter },
7689
+ { method: 'POST', path: '/api/command-center/stream', desc: 'Streaming CC — SSE with text chunks as they arrive', params: 'message, tabId?, sessionId?, transcript?', handler: handleCommandCenterStream },
7622
7690
  { method: 'GET', path: '/api/cc-sessions', desc: 'List CC session metadata for all tabs', handler: handleCCSessionsList },
7623
7691
  { method: 'DELETE', path: /^\/api\/cc-sessions\/([\w-]+)$/, template: '/api/cc-sessions/:id', desc: 'Delete a CC session by tab ID', handler: handleCCSessionDelete },
7624
7692
 
@@ -8022,6 +8090,7 @@ module.exports = {
8022
8090
  buildCCStatePreamble,
8023
8091
  _routesAsMeta,
8024
8092
  _buildTranscriptCarryover,
8093
+ _transcriptHasCarryoverContext,
8025
8094
  _ccRuntimeNeedsResumeCarryover,
8026
8095
  _joinCcPromptParts,
8027
8096
  _captureApiRoutesMeta,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-07T23:22:07.269Z"
4
+ "cachedAt": "2026-05-07T23:23:28.356Z"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1778",
3
+ "version": "0.1.1779",
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"
@@ -85,13 +85,13 @@ I'll dispatch dallas to fix that bug.
85
85
 
86
86
  **Required fields per action type — server rejects with an error if missing:**
87
87
 
88
- - `dispatch` (and aliases: `fix`, `implement`, `explore`, `review`, `test`): `title` is REQUIRED. `description` recommended. `project` REQUIRED when multiple projects are configured (server returns the list of known names if you guess wrong). For agent hints emit either `agents: ["dallas"]` (array, preferred) or `agent: "dallas"` (string — auto-promoted server-side). Unknown agent names error.
88
+ - `dispatch`: `title` is REQUIRED. `description` recommended. `project` REQUIRED when multiple projects are configured (server returns the list of known names if you guess wrong). For agent hints emit either `agents: ["dallas"]` (array, preferred) or `agent: "dallas"` (string — auto-promoted server-side). Unknown agent names error. Always emit `"type":"dispatch"` for dispatch-like work and preserve the semantic intent in `workType` (`fix`, `implement`, `explore`, `review`, or `test`) instead of using those words as action types.
89
89
  - `build-and-test`: `pr` REQUIRED (number, ID, or URL).
90
90
  - `note`: `title` and `content` (or `description`) REQUIRED.
91
91
  - `knowledge`: `title`, `content`, and `category` REQUIRED. Valid categories: architecture, conventions, project-notes, build-reports, reviews.
92
92
 
93
93
  Core action types:
94
- - **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project unless `pr` resolves to a tracked PR), description, pr (optional PR number/id/url for work that targets an existing PR), scope (`"fan-out"` only when the user explicitly asks to fan out to all agents)
94
+ - **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project unless `pr` resolves to a tracked PR), description, pr (optional PR number/id/url for work that targets an existing PR), scope (`"fan-out"` only when the user explicitly asks to fan out to all agents). Do not emit `type:"fix"` or `type:"implement"`; emit `type:"dispatch"` with `workType:"fix"` or `workType:"implement"`.
95
95
  workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (standalone bug fix creates a PR; include `pr` when fixing review comments/build failures on an existing PR), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
96
96
  If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
97
97
  When the user names a specific agent ("assign this to lambert"), put exactly that one name in `agents` (e.g. `"agents": ["lambert"]`). A single-agent assignment is hard-pinned by the server — it will queue for that agent only and skip the routing table. If the user explicitly asks for fan-out/all agents, set `scope: "fan-out"`.