release: v2.0.8 - UX Persistence & Per-Agent Knowledge Mix (2026-05-14)

This commit is contained in:
g1nation
2026-05-14 00:56:20 +09:00
parent 8104caf8d9
commit 147536fb13
19 changed files with 423 additions and 55 deletions
+62 -2
View File
@@ -32,9 +32,15 @@
*/
import * as vscode from 'vscode';
import { IAIService } from '../../core/services';
import { logError, logInfo } from '../../utils';
import { getActiveBrainProfile, logError, logInfo } from '../../utils';
import { retrieveScoped, buildContextBlock } from '../../skills/scopedBrainRetriever';
import { resolveScopeForAgent } from '../../skills/agentKnowledgeMap';
import {
mapWeightToBrainFileLimit,
buildKnowledgeMixPolicy,
} from '../../retrieval/knowledgeMix';
import { getCompanyAgent } from './agents';
import { modelForAgent, readCompanyState } from './companyConfig';
import { modelForAgent, readCompanyState, resolveCompanyKnowledgeMix } from './companyConfig';
import { runCeoPlanner } from './ceoPlanner';
import { runCeoReporter } from './ceoReporter';
import { buildSpecialistPrompt } from './promptBuilder';
@@ -86,6 +92,18 @@ export interface DispatcherDeps {
ai: IAIService;
/** Default model to fall back to when an agent has no override. */
defaultModel: string;
/**
* Global Knowledge Mix weight (0100) — fallback when an agent has no
* per-agent override. Mirrors `g1nation.knowledgeMix.secondBrainWeight`.
*/
globalKnowledgeMixWeight: number;
/**
* Baseline number of brain files to retrieve at weight=50 (balanced).
* The actual count is `mapWeightToBrainFileLimit(weight, baseline)`.
* Pass the same value the chat path uses (`config.memoryLongTermFiles`)
* so company-mode behaviour stays in sync.
*/
brainFileBaseline?: number;
/**
* Apply ConnectAI's action-tag executor to the specialist's raw response.
* Without this hook, agent outputs containing `<create_file>` etc. would
@@ -266,10 +284,52 @@ async function _dispatchOne(
};
});
// ── Second Brain RAG for this specialist ────────────────────────────────
// The non-company chat path uses `AgentExecutor.buildMemoryContext` to
// pull RAG chunks before every LLM call. The dispatcher used to skip
// that entirely, leaving company agents *blind* to the user's stored
// knowledge — which made the Knowledge Mix slider effectively a no-op
// for company turns. We now run a lightweight scoped retrieval here so
// every dispatch sees the same brain the user expects, weighted by the
// agent's own Knowledge Mix.
const { weight: knowledgeWeight, source: knowledgeMixSource } =
resolveCompanyKnowledgeMix(state, agentId, deps.globalKnowledgeMixWeight);
const brainFileLimit = mapWeightToBrainFileLimit(knowledgeWeight, deps.brainFileBaseline ?? 6);
let brainContext = '';
if (brainFileLimit > 0) {
try {
const brain = getActiveBrainProfile();
const brainRoot = brain?.localBrainPath || '';
if (brainRoot) {
// Reuse the agent ↔ knowledge map: if the same agent name
// appears there (free-form .md path or canonical id), we
// honour its `knowledgeFolders` scope. Otherwise we search
// the whole brain so a missing mapping doesn't starve the
// dispatcher.
const scope = resolveScopeForAgent(agentId, brainRoot);
const retrieval = retrieveScoped(task, brainRoot, scope.folders, {
maxResults: brainFileLimit,
});
brainContext = buildContextBlock(retrieval);
}
} catch (e: any) {
logError('company.dispatcher: RAG retrieval failed; continuing without brain context.', {
agentId, error: e?.message ?? String(e),
});
}
}
const policyBlock = buildKnowledgeMixPolicy({
weight: knowledgeWeight,
source: knowledgeMixSource,
agent: def.name,
});
const system = buildSpecialistPrompt({
agentId, state,
agentMemory: memory, sharedDecisions: decisions,
peerOutputs,
brainContext, // injected as `[SECOND BRAIN CONTEXT]` block
knowledgeMixPolicy: policyBlock, // injected as `[KNOWLEDGE MIX POLICY]` block
});
const model = modelForAgent(state, agentId, deps.defaultModel);