@yemi33/minions 0.1.1771 → 0.1.1772

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1772 (2026-05-07)
4
+
5
+ ### Features
6
+ - fix copilot cc resume context (#2166)
7
+
3
8
  ## 0.1.1771 (2026-05-07)
4
9
 
5
10
  ### Features
package/dashboard.js CHANGED
@@ -10,6 +10,7 @@ const zlib = require('zlib');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
  const llm = require('./engine/llm');
13
+ const { resolveRuntime } = require('./engine/runtimes');
13
14
 
14
15
  // Dashboard version stamp — captured at module load so it reflects the code actually running
15
16
  const _dashboardVersion = {
@@ -1262,9 +1263,14 @@ function _readCcTabSessions({ prune = true } = {}) {
1262
1263
  const CC_CARRYOVER_MAX_TURNS = 20;
1263
1264
  const CC_CARRYOVER_PER_MSG_CHARS = 2000;
1264
1265
 
1265
- function _buildTranscriptCarryover(transcript, { previousRuntime } = {}) {
1266
+ function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage } = {}) {
1266
1267
  if (!Array.isArray(transcript) || transcript.length === 0) return '';
1267
- const filtered = transcript.filter(m => m && (m.role === 'user' || m.role === 'assistant') && typeof m.text === 'string' && m.text.trim());
1268
+ let filtered = transcript.filter(m => m && (m.role === 'user' || m.role === 'assistant') && typeof m.text === 'string' && m.text.trim());
1269
+ const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
1270
+ if (current && filtered.length > 0) {
1271
+ const last = filtered[filtered.length - 1];
1272
+ if (last.role === 'user' && last.text.trim() === current) filtered = filtered.slice(0, -1);
1273
+ }
1268
1274
  if (filtered.length === 0) return '';
1269
1275
  const recent = filtered.slice(-CC_CARRYOVER_MAX_TURNS);
1270
1276
  const truncated = filtered.length > recent.length;
@@ -1281,6 +1287,19 @@ function _buildTranscriptCarryover(transcript, { previousRuntime } = {}) {
1281
1287
  return `${header}\n\n${truncationNote}${lines.join('\n\n')}\n\n--- Current message follows ---`;
1282
1288
  }
1283
1289
 
1290
+ function _ccRuntimeNeedsResumeCarryover(runtimeName) {
1291
+ try {
1292
+ const runtime = resolveRuntime(runtimeName);
1293
+ return !!runtime?.capabilities?.resumePromptCarryover;
1294
+ } catch {
1295
+ return false;
1296
+ }
1297
+ }
1298
+
1299
+ function _joinCcPromptParts(...parts) {
1300
+ return parts.filter(Boolean).join('\n\n---\n\n');
1301
+ }
1302
+
1284
1303
  // Load persisted CC session on startup. CC chat sessions are non-expiring;
1285
1304
  // only restore-time validity checks here are sessionId presence (anything
1286
1305
  // else would auto-expire the user's chat without their consent).
@@ -2415,7 +2434,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
2415
2434
  * @param {number} opts.maxTurns - Max tool-use turns
2416
2435
  * @param {string} opts.allowedTools - Comma-separated tool list
2417
2436
  */
2418
- async function ccCall(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, systemPrompt = CC_STATIC_SYSTEM_PROMPT } = {}) {
2437
+ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript } = {}) {
2419
2438
  if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
2420
2439
  if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
2421
2440
  const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
@@ -2428,10 +2447,15 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2428
2447
 
2429
2448
  const existing = resolveSession(store, sessionKey);
2430
2449
  let sessionId = existing ? existing.sessionId : null;
2450
+ const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
2431
2451
 
2432
- function buildPrompt({ includePreamble = true } = {}) {
2452
+ function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
2433
2453
  const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
2434
2454
  if (extraContext) parts.push(extraContext);
2455
+ if (includeCarryover) {
2456
+ const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
2457
+ if (carryover) parts.push(carryover);
2458
+ }
2435
2459
  parts.push(message);
2436
2460
  return parts.join('\n\n---\n\n');
2437
2461
  }
@@ -2440,7 +2464,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2440
2464
 
2441
2465
  // Attempt 1: resume existing session — skip preamble (session already has context)
2442
2466
  if (sessionId && maxTurns > 1) {
2443
- const p1 = llm.callLLM(buildPrompt({ includePreamble: false }), '', {
2467
+ const p1 = llm.callLLM(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
2444
2468
  timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
2445
2469
  engineConfig: CONFIG.engine,
2446
2470
  });
@@ -2477,7 +2501,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2477
2501
  }
2478
2502
 
2479
2503
  // Attempt 2: fresh session (include preamble for full context)
2480
- const freshPrompt = buildPrompt();
2504
+ const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
2481
2505
  const p2 = llm.callLLM(freshPrompt, systemPrompt, {
2482
2506
  timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
2483
2507
  engineConfig: CONFIG.engine,
@@ -2511,7 +2535,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
2511
2535
  return result;
2512
2536
  }
2513
2537
 
2514
- async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT } = {}) {
2538
+ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext, label = 'command-center', timeout = CC_CALL_TIMEOUT_MS, maxTurns, allowedTools = 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch', skipStatePreamble = false, model, onAbortReady, onChunk, onToolUse, onRetry, systemPrompt = CC_STATIC_SYSTEM_PROMPT, transcript } = {}) {
2515
2539
  if (!maxTurns) maxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
2516
2540
  if (!model) model = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
2517
2541
  const ccEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
@@ -2524,10 +2548,15 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2524
2548
 
2525
2549
  const existing = resolveSession(store, sessionKey);
2526
2550
  let sessionId = existing ? existing.sessionId : null;
2551
+ const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
2527
2552
 
2528
- function buildPrompt({ includePreamble = true } = {}) {
2553
+ function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
2529
2554
  const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
2530
2555
  if (extraContext) parts.push(extraContext);
2556
+ if (includeCarryover) {
2557
+ const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
2558
+ if (carryover) parts.push(carryover);
2559
+ }
2531
2560
  parts.push(message);
2532
2561
  return parts.join('\n\n---\n\n');
2533
2562
  }
@@ -2535,7 +2564,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2535
2564
  let result;
2536
2565
 
2537
2566
  if (sessionId && maxTurns > 1) {
2538
- const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false }), '', {
2567
+ const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false, includeCarryover: resumeNeedsCarryover }), '', {
2539
2568
  timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
2540
2569
  engineConfig: CONFIG.engine,
2541
2570
  onChunk,
@@ -2572,7 +2601,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
2572
2601
  }
2573
2602
 
2574
2603
  if (onRetry) onRetry(2);
2575
- const freshPrompt = buildPrompt();
2604
+ const freshPrompt = buildPrompt({ includeCarryover: resumeNeedsCarryover });
2576
2605
  const p2 = llm.callLLMStreaming(freshPrompt, systemPrompt, {
2577
2606
  timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
2578
2607
  engineConfig: CONFIG.engine,
@@ -5754,7 +5783,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5754
5783
  }
5755
5784
  const wasResume = !!(body.sessionId && body.sessionId === ccSession.sessionId && ccSessionValid());
5756
5785
 
5757
- const result = await ccCall(body.message, { store: 'cc' });
5786
+ const result = await ccCall(body.message, { store: 'cc', transcript: body.transcript });
5758
5787
 
5759
5788
  // Non-zero exit with text = max_turns or partial success — still usable
5760
5789
  if (!result.text) {
@@ -5989,13 +6018,21 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5989
6018
  sessionReset = true;
5990
6019
  sessionResetReason = 'runtimeChanged';
5991
6020
  previousRuntime = tabEntry.runtime;
6021
+ } else if (tabEntry.sessionId && tabEntry.sessionId !== tabSessionId) {
6022
+ tabSessionId = tabEntry.sessionId;
5992
6023
  }
5993
6024
  }
5994
6025
  const wasResume = !!tabSessionId;
5995
6026
  const sessionId = tabSessionId || null;
6027
+ const resumeNeedsCarryover = wasResume && _ccRuntimeNeedsResumeCarryover(currentRuntime);
5996
6028
  const preamble = wasResume ? '' : buildCCStatePreamble();
5997
- const carryover = sessionReset ? _buildTranscriptCarryover(body.transcript, { previousRuntime }) : '';
5998
- const prompt = (preamble ? preamble + '\n\n---\n\n' : '') + (carryover ? carryover + '\n\n---\n\n' : '') + body.message;
6029
+ const carryover = (sessionReset || resumeNeedsCarryover)
6030
+ ? _buildTranscriptCarryover(body.transcript, {
6031
+ previousRuntime: sessionReset ? previousRuntime : null,
6032
+ currentMessage: body.message,
6033
+ })
6034
+ : '';
6035
+ const prompt = _joinCcPromptParts(preamble, carryover, body.message);
5999
6036
 
6000
6037
  const { trackEngineUsage: trackUsage } = require('./engine/llm');
6001
6038
  const streamModel = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
@@ -6023,7 +6060,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6023
6060
  // Resume failed (stale/expired session) — auto-retry as fresh session (skip if client already disconnected)
6024
6061
  console.log(`[CC-stream] Resume failed (code=${result.code}) — retrying fresh`);
6025
6062
  const freshPreamble = buildCCStatePreamble();
6026
- const freshPrompt = (freshPreamble ? freshPreamble + '\n\n---\n\n' : '') + body.message;
6063
+ const freshCarryover = _buildTranscriptCarryover(body.transcript, { currentMessage: body.message });
6064
+ const freshPrompt = _joinCcPromptParts(freshPreamble, freshCarryover, body.message);
6027
6065
  toolUses = []; // discard stale metadata from the failed resume attempt
6028
6066
  const retryPromise = _invokeCcStream({
6029
6067
  prompt: freshPrompt, sessionId: undefined, liveState, toolUses,
@@ -7695,6 +7733,9 @@ module.exports = {
7695
7733
  _createPipelineFromAction: createPipelineFromAction,
7696
7734
  executeCCActions,
7697
7735
  buildCCStatePreamble,
7736
+ _buildTranscriptCarryover,
7737
+ _ccRuntimeNeedsResumeCarryover,
7738
+ _joinCcPromptParts,
7698
7739
  _captureApiRoutesMeta,
7699
7740
  _formatCcApiRoutesIndex,
7700
7741
  _formatCcCliCommandsIndex,
@@ -17,6 +17,7 @@
17
17
  | `capabilities.modelDiscovery` | **`true`** | `GET https://api.githubcopilot.com/models` with a `gh auth token` Bearer returns HTTP 200 + a 24-model JSON catalog. |
18
18
  | `capabilities.streaming` | **`true`** | `--stream on` (default) emits `assistant.message_delta` events incrementally; `--stream off` suppresses deltas but the final `assistant.message` always arrives. |
19
19
  | `capabilities.sessionResume` | **`true`** | `--resume <session-id>` documented, and every `result` event emits `sessionId`. |
20
+ | `capabilities.resumePromptCarryover` | **`true`** | Command Center resume turns should prepend the browser's recent Q&A transcript because Copilot's session store is opaque to Minions and can resume without enough conversational context. |
20
21
  | `capabilities.systemPromptFile` | **`false`** | No `--system-prompt-file` flag exists. Inject system prompt via a `<system>` block prepended to stdin. |
21
22
  | `capabilities.effortLevels` | **`true`** | `--effort` accepts `low|medium|high|xhigh` (no `max`). Adapter must map `'max' → 'xhigh'`. |
22
23
  | `capabilities.costTracking` | **`false`** | `result.usage` contains `premiumRequests` (count, not USD), no token counts, no cost. |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-07T16:58:12.762Z"
4
+ "cachedAt": "2026-05-07T17:21:57.007Z"
5
5
  }
@@ -717,6 +717,8 @@ const capabilities = {
717
717
  fallbackModel: true,
718
718
  // Engine controls session persistence (writes session.json on completion)
719
719
  sessionPersistenceControl: true,
720
+ // Claude resume reliably restores prior turns; do not duplicate browser transcript.
721
+ resumePromptCarryover: false,
720
722
  // Adapter implements createStreamConsumer(ctx) — required by llm.js accumulator
721
723
  streamConsumer: true,
722
724
  };
@@ -874,6 +874,9 @@ const capabilities = {
874
874
  fallbackModel: false,
875
875
  // Copilot manages session state internally in ~/.copilot/session-state/
876
876
  sessionPersistenceControl: false,
877
+ // CC resumes should include recent visible Q&A in stdin because Minions cannot
878
+ // inspect or repair Copilot's opaque session-state store when it drops context.
879
+ resumePromptCarryover: true,
877
880
  // Adapter implements createStreamConsumer(ctx) — required by llm.js accumulator
878
881
  streamConsumer: true,
879
882
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1771",
3
+ "version": "0.1.1772",
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"