chore: version up to 2.80.35 and package with experience memory
This commit is contained in:
+66
-15
@@ -39,6 +39,7 @@ import {
|
||||
} from './features/secondBrainTrace';
|
||||
import { MemoryManager } from './memory';
|
||||
import { RetrievalOrchestrator } from './retrieval';
|
||||
import { buildLessonChecklistBlock, isQaRegressionFeedback, findUnaddressedChecklistItems } from './retrieval/lessonHelpers';
|
||||
import { resolveScopeForAgent } from './skills/agentKnowledgeMap';
|
||||
import {
|
||||
estimateTokens,
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
truncateSystemPromptContext,
|
||||
classifyStopReason,
|
||||
truncationNotice,
|
||||
shouldShowTruncationNotice,
|
||||
estimateModelParamsB,
|
||||
type ContextLimits,
|
||||
} from './lib/contextManager';
|
||||
@@ -136,9 +138,12 @@ export class AgentExecutor {
|
||||
configuredFolders: string[]; // relative to brain root
|
||||
usedBrainFiles: string[]; // relative to brain root
|
||||
usedMemoryLayers: string[]; // raw RetrievalSource ids
|
||||
lessonFiles: string[]; // relative to brain root — lesson/playbook/qa-finding cards injected this turn
|
||||
totalChunks: number;
|
||||
selectedChunks: number;
|
||||
} | null = null;
|
||||
/** Lesson card *contents* injected this turn — kept to check the answer against their Prevention Checklists. */
|
||||
private _lastLessonContents: string[] = [];
|
||||
|
||||
private readonly options: AgentExecutorOptions;
|
||||
|
||||
@@ -264,6 +269,8 @@ export class AgentExecutor {
|
||||
agentEvents.emit(AgentEventTypes.TRANSACTION_ROLLED_BACK);
|
||||
this.statusBarManager.updateStatus(AgentStatus.Idle, 'Changes rolled back.');
|
||||
this.webview?.postMessage({ type: 'streamChunk', value: '\n❌ **작업이 거부되어 모든 변경사항이 취소되었습니다.**' });
|
||||
// The user judged this change wrong — a good moment to capture why, so it doesn't recur.
|
||||
this.webview?.postMessage({ type: 'lessonCandidate', value: { trigger: 'rejected' } });
|
||||
}
|
||||
|
||||
public async handlePrompt(
|
||||
@@ -306,6 +313,7 @@ export class AgentExecutor {
|
||||
}
|
||||
|
||||
const hasVisionContent = Array.isArray(visionContent) ? visionContent.length > 0 : !!visionContent;
|
||||
const isCasualConversation = prompt ? this.isCasualConversationPrompt(prompt) : false;
|
||||
let requestTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
if (!this.webview) return;
|
||||
@@ -339,7 +347,7 @@ export class AgentExecutor {
|
||||
: getActiveBrainProfile();
|
||||
const brainFiles = findBrainFiles(activeBrain.localBrainPath);
|
||||
let secondBrainTrace: SecondBrainTrace | null = null;
|
||||
if (options.secondBrainTraceEnabled && prompt && loopDepth === 0) {
|
||||
if (options.secondBrainTraceEnabled && prompt && loopDepth === 0 && !isCasualConversation) {
|
||||
secondBrainTrace = buildSecondBrainTrace(prompt, activeBrain.localBrainPath, {
|
||||
force: this.isExplicitSecondBrainRequest(prompt),
|
||||
limit: Math.max(config.memoryLongTermFiles, 5)
|
||||
@@ -358,7 +366,7 @@ export class AgentExecutor {
|
||||
activeBrain.description ? `Description: ${activeBrain.description}` : '',
|
||||
brainPreview ? `Available file examples:\n${brainPreview}` : 'Files: none found'
|
||||
].filter(Boolean).join('\n');
|
||||
const brainInventoryCtx = prompt && this.isSecondBrainInventoryRequest(prompt)
|
||||
const brainInventoryCtx = prompt && !isCasualConversation && this.isSecondBrainInventoryRequest(prompt)
|
||||
? `\n\n${this.buildSecondBrainInventoryContext(activeBrain, brainFiles)}`
|
||||
: '';
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
@@ -375,19 +383,19 @@ export class AgentExecutor {
|
||||
if (localPathContext) {
|
||||
contextBlock += `\n\n${localPathContext}`;
|
||||
}
|
||||
const recentProjectKnowledgeContext = prompt && loopDepth === 0 && !localPathContext
|
||||
const recentProjectKnowledgeContext = prompt && loopDepth === 0 && !isCasualConversation && !localPathContext
|
||||
? this.buildRecentProjectKnowledgeContext(prompt, rootPath)
|
||||
: '';
|
||||
if (recentProjectKnowledgeContext) {
|
||||
contextBlock += `\n\n${recentProjectKnowledgeContext}`;
|
||||
}
|
||||
const projectBriefContext = prompt && loopDepth === 0
|
||||
const projectBriefContext = prompt && loopDepth === 0 && !isCasualConversation
|
||||
? this.buildJarvisProjectBriefContext(prompt, localPathContext, recentProjectKnowledgeContext)
|
||||
: '';
|
||||
if (projectBriefContext) {
|
||||
contextBlock += `\n\n${projectBriefContext}`;
|
||||
}
|
||||
const modeArchitectureContext = prompt && loopDepth === 0
|
||||
const modeArchitectureContext = prompt && loopDepth === 0 && !isCasualConversation
|
||||
? this.buildAstraModeArchitectureContext(prompt)
|
||||
: '';
|
||||
if (modeArchitectureContext) {
|
||||
@@ -448,7 +456,12 @@ export class AgentExecutor {
|
||||
const secondBrainTraceCtx = secondBrainTrace
|
||||
? `\n\n${renderSecondBrainTraceContext(secondBrainTrace)}`
|
||||
: '';
|
||||
const memoryCtx = this.buildMemoryContext(prompt || '', activeBrain, options.agentSkillFile);
|
||||
const memoryCtx = isCasualConversation
|
||||
? ''
|
||||
: this.buildMemoryContext(prompt || '', activeBrain, options.agentSkillFile);
|
||||
const knowledgeContextForPrompt = isCasualConversation
|
||||
? ''
|
||||
: `${brainContext}${brainInventoryCtx}`;
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// [Agent Mode v3] 에이전트가 선택된 경우, Astra 기본 포맷/페르소나 섹션을
|
||||
@@ -478,16 +491,16 @@ export class AgentExecutor {
|
||||
|
||||
// 3. 조립: 기본(축소) → 유틸리티 컨텍스트 → 에이전트 프롬프트(최후단)
|
||||
// [CONTEXT] … [/CONTEXT] 사이만 컨텍스트 초과 시 trim 대상 — agentDirective/negative 는 보호.
|
||||
fullSystemPrompt = `${strippedSystemPrompt}${internetCtx}${memoryCtx}${designerCtx}${secondBrainTraceCtx}\n\n[CONTEXT]\n${brainContext}${brainInventoryCtx}\n${contextBlock}\n[/CONTEXT]\n${negativeCtx}${agentDirective}`;
|
||||
fullSystemPrompt = `${strippedSystemPrompt}${internetCtx}${memoryCtx}${designerCtx}${secondBrainTraceCtx}\n\n[CONTEXT]\n${knowledgeContextForPrompt}\n${contextBlock}\n[/CONTEXT]\n${negativeCtx}${agentDirective}`;
|
||||
} else {
|
||||
// 기존 Astra 모드 (에이전트 미선택)
|
||||
const localProjectKnowledgeCtx = prompt && localPathContext && this.isProjectKnowledgeCreationRequest(prompt)
|
||||
? `\n\n[LOCAL PROJECT KNOWLEDGE CREATION OVERRIDE]\nThe user gave an accessible local project path and asked to create project knowledge. Do not ask blocking scope questions. Use a sensible default MVP: create or propose a project overview note from the inspected tree and priority file previews. If writing is not explicitly safe, provide the concrete note draft and target path.`
|
||||
: '';
|
||||
const thinkingPartnerCtx = prompt && this.isThinkingPartnerRequest(prompt)
|
||||
const thinkingPartnerCtx = prompt && !isCasualConversation && this.isThinkingPartnerRequest(prompt)
|
||||
? `\n\n[JARVIS THINKING PARTNER MODE]\nThe user is using this tool to clarify project direction, not just to receive generic advice. Give a clear opinionated verdict first. Then separate confirmed facts, inferences, concerns, decision forks, and the next small action. Do not merely say the direction is good. If evidence is thin, say exactly what is missing and what file or record should be checked next.`
|
||||
: '';
|
||||
const astraStanceCtx = prompt
|
||||
const astraStanceCtx = prompt && !isCasualConversation
|
||||
? `\n\n${this.buildAstraStanceContext(prompt, localPathContext)}`
|
||||
: '';
|
||||
const v4PolicyCtx = [
|
||||
@@ -498,7 +511,10 @@ export class AgentExecutor {
|
||||
].join('\n');
|
||||
|
||||
// [CONTEXT] … [/CONTEXT] 사이만 컨텍스트 초과 시 trim 대상 — negative constraints 는 보호.
|
||||
fullSystemPrompt = `${systemPrompt}${internetCtx}${memoryCtx}${designerCtx}${localProjectKnowledgeCtx}${thinkingPartnerCtx}${astraStanceCtx}${secondBrainTraceCtx}${v4PolicyCtx}\n\n[CONTEXT]\n${brainContext}${brainInventoryCtx}\n${contextBlock}\n[/CONTEXT]\n${negativeCtx}`;
|
||||
const casualCtx = isCasualConversation
|
||||
? '\n\n[CASUAL CONVERSATION MODE]\nThe user sent a greeting, acknowledgement, or light conversational message. Reply naturally and briefly to the message itself. Do not use Second Brain, memory, project records, reports, references, or analysis unless the user explicitly asks for them.'
|
||||
: '';
|
||||
fullSystemPrompt = `${systemPrompt}${internetCtx}${memoryCtx}${designerCtx}${localProjectKnowledgeCtx}${thinkingPartnerCtx}${astraStanceCtx}${secondBrainTraceCtx}${v4PolicyCtx}${casualCtx}\n\n[CONTEXT]\n${knowledgeContextForPrompt}\n${contextBlock}\n[/CONTEXT]\n${negativeCtx}`;
|
||||
}
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// [Context Limit Manager] context length 는 "답변을 그만큼 길게 써도 된다"
|
||||
@@ -624,6 +640,11 @@ export class AgentExecutor {
|
||||
smallModel: cappedForSmallModel || (modelParamB !== null && modelParamB <= 3 && inputTokens > 8000),
|
||||
},
|
||||
});
|
||||
// If the user's message reads like a regression complaint ("또 안 돼", "비슷한 실수", "왜 반복돼"…),
|
||||
// offer to record a lesson — a recurring problem is exactly what Experience Memory is for.
|
||||
if (prompt && isQaRegressionFeedback(prompt)) {
|
||||
this.webview.postMessage({ type: 'lessonCandidate', value: { trigger: 'qa-feedback' } });
|
||||
}
|
||||
this.webview.postMessage({ type: 'streamStart' });
|
||||
this.options.onStreamLifecycle?.start();
|
||||
}
|
||||
@@ -858,7 +879,10 @@ export class AgentExecutor {
|
||||
inputTokens, maxOutputTokens, answerChars: assistantContent.length,
|
||||
});
|
||||
}
|
||||
const notice = truncationNotice(stopKind);
|
||||
const outputTokens = estimateTokens(assistantContent);
|
||||
const notice = shouldShowTruncationNotice(stopKind, outputTokens, maxOutputTokens)
|
||||
? truncationNotice(stopKind)
|
||||
: '';
|
||||
if (notice && assistantContent.trim()) {
|
||||
assistantContent = assistantContent.trimEnd() + notice;
|
||||
}
|
||||
@@ -945,9 +969,11 @@ export class AgentExecutor {
|
||||
|
||||
this.statusBarManager.updateStatus(AgentStatus.Success);
|
||||
if (this._lastRetrievalInfo) {
|
||||
// Non-blocking flag: lesson Prevention-Checklist items the answer doesn't visibly touch on.
|
||||
const unaddressedChecklist = findUnaddressedChecklistItems(finalAssistantContent, this._lastLessonContents);
|
||||
this.webview.postMessage({
|
||||
type: 'usedScope',
|
||||
value: { ...this._lastRetrievalInfo, hasAgentSelected: !!options.agentSkillFile },
|
||||
value: { ...this._lastRetrievalInfo, hasAgentSelected: !!options.agentSkillFile, unaddressedChecklist },
|
||||
});
|
||||
}
|
||||
this.webview.postMessage({ type: 'streamChunk', value: finalAssistantContent });
|
||||
@@ -1479,6 +1505,21 @@ export class AgentExecutor {
|
||||
return /(어떤\s*거?\s*같|어때|어떻게\s*생각|의견|판단|방향|설계|아키텍처|구조|자비스|생각.*정리|갈림길|architecture|design|direction|opinion|think|judge)/i.test(prompt);
|
||||
}
|
||||
|
||||
private isCasualConversationPrompt(prompt: string): boolean {
|
||||
const normalized = (prompt || '')
|
||||
.trim()
|
||||
.replace(/[~!?.。!?\s]+$/g, '')
|
||||
.toLowerCase();
|
||||
if (!normalized) return false;
|
||||
if (normalized.length > 40) return false;
|
||||
|
||||
// Greetings, acknowledgements, and light conversational nudges should
|
||||
// not trigger Second Brain/RAG. Otherwise a single "안녕" can retrieve
|
||||
// old project records and the model answers that stale context instead
|
||||
// of the user's actual greeting.
|
||||
return /^(안녕|안녕하세요|하이|헬로|hello|hi|hey|yo|ㅎㅇ|좋아|오케이|ok|okay|ㅇㅋ|고마워|감사|thanks|thank you|넵|네|응|음|흠|그래)$/.test(normalized);
|
||||
}
|
||||
|
||||
private isAstraModeArchitectureQuestion(prompt: string): boolean {
|
||||
const mentionsGuard = /\bguard\b|가드|Guard|Chronicle Guard|Project Chronicle/i.test(prompt);
|
||||
const mentionsMultiAgent = /\bMA\b|multi[-\s]?agent|멀티\s*에이전트|다중\s*에이전트|Planner|Researcher|Writer/i.test(prompt);
|
||||
@@ -2126,6 +2167,7 @@ export class AgentExecutor {
|
||||
private buildMemoryContext(currentPrompt: string, activeBrain: BrainProfile, agentSkillFile?: string): string {
|
||||
const config = getConfig();
|
||||
this._lastRetrievalInfo = null;
|
||||
this._lastLessonContents = [];
|
||||
if (!config.memoryEnabled) return '';
|
||||
|
||||
// Update memory manager config in case settings changed
|
||||
@@ -2161,6 +2203,7 @@ export class AgentExecutor {
|
||||
// Stash what actually fed this turn so handlePrompt can show it under the answer.
|
||||
const brainRoot = activeBrain.localBrainPath;
|
||||
const rel = (p?: string) => (p ? (path.relative(brainRoot, p) || p) : '');
|
||||
const lessonChunks = result.lessonChunks || [];
|
||||
this._lastRetrievalInfo = {
|
||||
agentName: scope.agent?.name ?? null,
|
||||
scoped: scope.folders.length > 0,
|
||||
@@ -2175,11 +2218,17 @@ export class AgentExecutor {
|
||||
.filter((c) => c.source !== 'brain-memory' && c.source !== 'brain-trace')
|
||||
.map((c) => c.source as string)
|
||||
)),
|
||||
lessonFiles: lessonChunks.map((c) => rel(c.metadata.filePath)).filter((p, i, arr) => p && arr.indexOf(p) === i),
|
||||
totalChunks: result.totalChunks,
|
||||
selectedChunks: result.selectedChunks.length,
|
||||
};
|
||||
|
||||
return this.retrievalOrchestrator.buildContextString(result);
|
||||
this._lastLessonContents = lessonChunks.map((c) => c.content);
|
||||
// Lessons go ahead of the regular RAG context (and ahead of [CONTEXT] in the system prompt),
|
||||
// so they're prominent and survive context-overflow truncation.
|
||||
const lessonBlock = buildLessonChecklistBlock(lessonChunks.map((c) => ({ title: c.title, content: c.content })));
|
||||
const memoryBlock = this.retrievalOrchestrator.buildContextString(result);
|
||||
return [lessonBlock, memoryBlock].filter(Boolean).join('\n\n');
|
||||
}
|
||||
|
||||
private emitHistoryChanged() {
|
||||
@@ -2662,7 +2711,9 @@ export class AgentExecutor {
|
||||
const g1Error = error instanceof AgentExecutionError ? error : new AgentExecutionError(error.message, error);
|
||||
report.push(`🛑 Transaction Failed: ${g1Error.message}. All file changes rolled back.`);
|
||||
logError('Action execution failed, rolled back.', g1Error);
|
||||
// We return the report with the failure message instead of throwing
|
||||
// A failed-and-rolled-back action is a strong "something went wrong" signal — offer to record a lesson.
|
||||
this.webview?.postMessage({ type: 'lessonCandidate', value: { trigger: 'rollback', reason: g1Error.message } });
|
||||
// We return the report with the failure message instead of throwing
|
||||
// so the agent can see the failure and decide what to do next
|
||||
}
|
||||
return report;
|
||||
@@ -2678,4 +2729,4 @@ export class AgentExecutor {
|
||||
logError('Second Brain sync failed.', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user