chore: v2.2.73 — ASTRA-DEBUG 로그 레벨 + webview CSP font-src 보강

- ASTRA-DEBUG 정상 흐름 로그를 console.error → logInfo/console.log 로 강등
  (chatHandlers, extension, slashRouter): DevTools에 ERR로 찍히던 오탐 제거
- sidebar webview에 명시적 CSP meta 추가 + font-src에 data: 허용
  (sidebar.html, sidebarProvider._getHtml): VS Code outer iframe이 codicon.ttf를
  data:font/ttf 로 inject하면서 기본 CSP에 막혀 매 prompt 마다 violation
  경고가 찍히던 문제 해소
- 누적된 LM Studio / agent / 컨텍스트 매니저 / 테스트 갱신 동반

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
g1nation
2026-05-23 15:52:19 +09:00
parent 36db170844
commit 0712014fcb
43 changed files with 2417 additions and 977 deletions
+22 -2
View File
@@ -49,7 +49,9 @@ describe('contextManager.computeOutputBudget', () => {
});
describe('contextManager.trimHistoryToBudget', () => {
const marker = (n: number): BudgetMessage => ({ role: 'system', content: `[dropped ${n}]`, internal: true });
// v2.2.69: makeMarker now also receives the dropped messages array so callers can build a real summary.
// Tests don't need the dropped payload — just keep the signature compatible.
const marker = (n: number, _dropped?: BudgetMessage[]): BudgetMessage => ({ role: 'system', content: `[dropped ${n}]`, internal: true });
it('keeps everything when under budget', () => {
const msgs: BudgetMessage[] = [{ role: 'user', content: 'hi' }, { role: 'assistant', content: 'hello' }];
const r = trimHistoryToBudget(msgs, 10_000, marker);
@@ -63,7 +65,25 @@ describe('contextManager.trimHistoryToBudget', () => {
expect(r.messages[0].content).toMatch(/^\[dropped \d+\]$/);
// most recent message survives
expect(r.messages[r.messages.length - 1]).toEqual(msgs[msgs.length - 1]);
expect(r.tokensAfter).toBeLessThanOrEqual(250 + estimateMessagesTokens([marker(1)]));
expect(r.tokensAfter).toBeLessThanOrEqual(250 + estimateMessagesTokens([marker(1, [])]));
});
it('passes the dropped messages array to the marker factory (v2.2.69)', () => {
const msgs: BudgetMessage[] = Array.from({ length: 6 }, (_, i) => ({
role: i % 2 ? 'assistant' : 'user',
content: 'x'.repeat(400),
}));
let observedDropped: BudgetMessage[] | undefined;
const factory = (n: number, dropped: BudgetMessage[]): BudgetMessage => {
observedDropped = dropped;
return { role: 'system', content: `[summary of ${n}: first=${dropped[0]?.role}]`, internal: true };
};
const r = trimHistoryToBudget(msgs, 250, factory);
expect(r.droppedCount).toBeGreaterThan(0);
expect(observedDropped).toBeDefined();
expect(observedDropped!.length).toBe(r.droppedCount);
// Dropped messages are the OLDEST ones, in order.
expect(observedDropped![0]).toEqual(msgs[0]);
expect(r.messages[0].content).toMatch(/^\[summary of \d+: first=user\]$/);
});
it('always keeps at least the last message even if it alone exceeds the budget', () => {
const msgs: BudgetMessage[] = [{ role: 'user', content: 'short' }, { role: 'user', content: 'y'.repeat(5000) }];
+4
View File
@@ -82,6 +82,10 @@ class FakeLMStudioClient implements ILMStudioClient {
return [];
}
async listDownloadedCached(): Promise<string[]> {
return [];
}
async getModelHandle(_modelKey: string): Promise<any> {
return {};
}
+1
View File
@@ -80,6 +80,7 @@ class FakeClient implements ILMStudioClient {
async listLoaded(): Promise<string[]> { return []; }
async listLoadedCached(): Promise<string[]> { return []; }
async listDownloaded(): Promise<string[]> { return []; }
async listDownloadedCached(): Promise<string[]> { return []; }
async isReachable(): Promise<boolean> { return true; }
async getModelHandle(modelKey: string): Promise<any> {
+7 -5
View File
@@ -110,11 +110,13 @@ describe('local project path preflight', () => {
const guidance = agent.buildLocalProjectIntentGuidance('review-evaluation');
expect(guidance).toContain('Intent operating contract — Code Review');
expect(guidance).toContain('## 한 줄 판단');
expect(guidance).toContain('## 잘된 점');
expect(guidance).toContain('## 부족한 점');
expect(guidance).toContain('## 사용자 관점 개선');
expect(guidance).toContain('## 다음 ');
// v2.2.64: review labels switched from markdown headers ("## 한 줄 판단") to plain-text
// numbered labels ("1) 한 줄 판단") so the model doesn't learn to emit `##` in the answer.
expect(guidance).toContain('1) 한 줄 판단');
expect(guidance).toContain('2) 잘된 점');
expect(guidance).toContain('3) 부족');
expect(guidance).toContain('4) 사용자 관점 개선');
expect(guidance).toContain('5) 다음 한 수');
});
it('adds an Astra stance layer for opinionated project collaboration', () => {
+6 -3
View File
@@ -22,8 +22,11 @@ describe('base system prompt', () => {
const prompt = getSystemPrompt();
expect(prompt).toContain('[STRICT GLOBAL RULES]');
expect(prompt).toContain('[OUTPUT FORMAT]');
expect(prompt).toContain('[FOLLOW-UP QUESTION RULES]');
expect(prompt).toContain('Ask ONE focused question at the very end');
// v2.2.68: [OUTPUT FORMAT] header now annotated with "— 7 hard rules";
// [FOLLOW-UP QUESTION RULES] is absorbed into R6 of the 7-rule block.
expect(prompt).toContain('[OUTPUT FORMAT — 7 hard rules]');
expect(prompt).toContain('R1. CONCLUSION FIRST');
expect(prompt).toContain('R6. ASK ONE QUESTION ONLY WHEN');
expect(prompt).toContain('R7. GUESS-AND-ACT WITH STATED ASSUMPTION');
});
});