release: v2.33.1 - Production build and packaging

This commit is contained in:
g1nation
2026-05-01 18:04:56 +09:00
parent c366a041c5
commit 250c998293
5 changed files with 258 additions and 44 deletions
+3
View File
@@ -3,6 +3,9 @@ module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.ts'],
moduleFileExtensions: ['ts', 'js'],
moduleNameMapper: {
'^vscode$': '<rootDir>/tests/mocks/vscode.js',
},
transform: {
'^.+\\.ts$': 'ts-jest',
},
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "g1nation",
"displayName": "G1nation",
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
"version": "2.33.0",
"version": "2.33.1",
"publisher": "connectailab",
"license": "MIT",
"icon": "assets/icon.png",
+42 -8
View File
@@ -15,6 +15,10 @@ export interface AggregateResult {
average?: number;
}
export interface AggregateOptions {
collectValues?: boolean;
}
/**
* DataProcessor:
* 시스템의 알고리즘적 효율성과 유지보수성을 극대화하기 위한 핵심 집계 엔진.
@@ -26,8 +30,14 @@ export class DataProcessor {
* @param data 집계할 데이터 배열
* @param keyPath 집계 기준이 될 속성 경로
*/
public static aggregate(data: any[], keyPath: string): AggregateResult[] {
public static aggregate(data: any[], keyPath: string, options: AggregateOptions = {}): AggregateResult[] {
if (!data || data.length === 0) return [];
if (!Array.isArray(data)) {
throw new TypeError('DataProcessor.aggregate expects data to be an array.');
}
const pathSegments = this.parseKeyPath(keyPath);
const collectValues = options.collectValues !== false;
// 1. 성능 상충 관계 (Sweet Spot) 고려:
// 데이터가 매우 작을 때는(예: N < 10) 해시 맵 생성 오버헤드가 더 클 수 있으나,
@@ -37,7 +47,7 @@ export class DataProcessor {
for (const item of data) {
try {
const keyValue = this.getNestedValue(item, keyPath);
const keyValue = this.getNestedValue(item, pathSegments);
if (keyValue === undefined || keyValue === null) continue;
const key = String(keyValue);
@@ -53,7 +63,9 @@ export class DataProcessor {
}
entry.count++;
entry.values.push(item);
if (collectValues) {
entry.values.push(item);
}
// 수치형 데이터인 경우 평균 계산을 위한 로직 (예시)
if (typeof item.value === 'number') {
@@ -74,15 +86,37 @@ export class DataProcessor {
* 데이터 분포 민감성(Data Distribution Sensitivity)을 고려한 고도화된 집계 (Trie 기반)
* 키가 매우 길거나 계층적인 경우 메모리 및 검색 속도 최적화를 위해 사용합니다.
*/
public static aggregateByTrie(data: any[], keyPath: string): AggregateResult[] {
public static aggregateByTrie(data: any[], keyPath: string, options: AggregateOptions = {}): AggregateResult[] {
// TODO: 복잡한 키 구조를 위한 Trie 인덱싱 로직 구현 (Phase 2 확장 예정)
return this.aggregate(data, keyPath);
return this.aggregate(data, keyPath, options);
}
/**
* 중첩된 객체 속성 접근 (Safety handling)
* 중첩된 객체 속성 접근 (Safety handling).
* keyPath는 루프 밖에서 한 번만 파싱하여 대규모 데이터 처리 시 반복 split 비용을 피합니다.
*/
private static getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((prev, curr) => prev && prev[curr], obj);
private static getNestedValue(obj: any, pathSegments: string[]): any {
return pathSegments.reduce((prev, curr) => {
if (prev === undefined || prev === null) return undefined;
return prev[curr];
}, obj);
}
private static parseKeyPath(keyPath: string): string[] {
if (typeof keyPath !== 'string' || keyPath.trim().length === 0) {
throw new TypeError('DataProcessor.aggregate expects keyPath to be a non-empty string.');
}
const segments = keyPath.split('.').map(segment => segment.trim());
if (segments.some(segment => segment.length === 0)) {
throw new TypeError('DataProcessor.aggregate received an invalid keyPath.');
}
const forbiddenSegments = new Set(['__proto__', 'prototype', 'constructor']);
if (segments.some(segment => forbiddenSegments.has(segment))) {
throw new TypeError('DataProcessor.aggregate received an unsafe keyPath.');
}
return segments;
}
}
+195 -35
View File
@@ -97,6 +97,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
case 'getModels':
await this._sendModels();
break;
case 'getSessions':
await this._sendSessionList();
break;
case 'getAgents':
await this._sendAgentsList();
break;
@@ -868,6 +871,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
--table-header-bg: #161b22;
--table-row-hover: #21262d;
--input-bg: #0d1117;
--control-bg: #161b22;
--control-bg-hover: #21262d;
--control-active-bg: rgba(88, 166, 255, 0.14);
--shadow-soft: 0 8px 24px rgba(0, 0, 0, 0.18);
}
body.vscode-light {
@@ -888,6 +895,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
--table-header-bg: #f6f8fa;
--table-row-hover: #f3f4f6;
--input-bg: #ffffff;
--control-bg: #ffffff;
--control-bg-hover: #f6f8fa;
--control-active-bg: rgba(9, 105, 218, 0.1);
--shadow-soft: 0 8px 20px rgba(31, 35, 40, 0.08);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
@@ -918,20 +929,57 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
padding: 10px 12px 8px;
gap: 10px;
}
.header-controls {
display: flex;
align-items: center;
gap: 6px;
padding: 0 14px 10px;
flex-wrap: wrap;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
padding: 0 12px 12px;
}
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: -0.5px; display: flex; align-items: center; gap: 8px; }
.header-actions,
.tool-strip,
.select-stack,
.select-line,
.status-pill {
display: flex;
align-items: center;
}
.header-actions { gap: 6px; flex-shrink: 0; }
.tool-strip { gap: 6px; flex-wrap: wrap; }
.select-stack { flex-direction: column; gap: 6px; min-width: 0; }
.select-line { gap: 6px; width: 100%; min-width: 0; }
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
.logo { width: 22px; height: 22px; background: var(--accent); color: #fff; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 900; }
.status-pill {
height: 28px;
gap: 6px;
padding: 0 9px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--control-bg);
color: var(--text-dim);
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
flex-shrink: 0;
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--text-dim);
box-shadow: 0 0 0 2px rgba(139, 148, 158, 0.12);
flex-shrink: 0;
}
.chat {
flex: 1;
overflow-y: auto;
@@ -955,8 +1003,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
.msg-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 11px; color: var(--text-dim); }
.av { width: 22px; height: 22px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; }
.icon-btn:hover { background: var(--border); color: var(--text-bright); }
.icon-btn.active { color: var(--accent); border-color: var(--accent); background: rgba(187, 134, 252, 0.1); }
/* Tooltip System */
[data-tooltip] { position: relative; }
@@ -969,8 +1015,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
}
[data-tooltip]:hover::after { opacity: 1; bottom: -35px; }
.header-controls { display: flex; gap: 8px; margin-left: auto; }
#input::placeholder { color: var(--accent); opacity: 0.6; font-weight: 500; }
@@ -1030,13 +1074,63 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
.action-btn:hover { color: var(--text-bright); border-color: var(--accent); background: var(--accent-glow); }
.icon-btn {
background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); width: 28px; height: 28px;
border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.2s; font-size: 13px;
background: var(--control-bg);
border: 1px solid var(--border);
color: var(--text-dim);
width: 28px;
height: 28px;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
font-size: 12px;
font-weight: 700;
line-height: 1;
flex: 0 0 28px;
}
.icon-btn:hover { color: var(--text-bright); border-color: var(--accent); background: var(--accent-glow); }
.icon-btn.active { color: var(--accent); border-color: var(--accent); background: var(--accent-glow); }
.icon-btn:hover { color: var(--text-bright); border-color: var(--border-bright); background: var(--control-bg-hover); box-shadow: var(--shadow-soft); }
.icon-btn.active { color: var(--accent); border-color: var(--accent); background: var(--control-active-bg); }
select { background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); padding: 4px 8px; border-radius: 6px; font-size: 10.5px; cursor: pointer; }
.select-wrap {
position: relative;
min-width: 0;
flex: 1 1 0;
}
.select-wrap::after {
content: '';
position: absolute;
right: 10px;
top: 50%;
width: 6px;
height: 6px;
border-right: 1.5px solid var(--text-dim);
border-bottom: 1.5px solid var(--text-dim);
transform: translateY(-65%) rotate(45deg);
pointer-events: none;
}
select {
width: 100%;
min-width: 0;
height: 30px;
background: var(--control-bg);
color: var(--text-primary);
border: 1px solid var(--border);
padding: 0 28px 0 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
cursor: pointer;
outline: none;
appearance: none;
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
select:hover { border-color: var(--border-bright); background: var(--control-bg-hover); }
select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
/* --- Input & Attachments --- */
.input-wrap {
@@ -1178,6 +1272,56 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
.btn-reject { flex: 1; background: var(--error); color: white; border: none; padding: 10px; border-radius: 8px; cursor: pointer; font-weight: 700; font-size: 12px; transition: 0.2s; }
.btn-reject:hover { filter: brightness(1.1); transform: translateY(-1px); }
.panel {
display: none;
flex-direction: column;
gap: 8px;
padding-bottom: 10px;
}
.field-label {
color: var(--text-dim);
font-size: 10px;
font-weight: 700;
line-height: 1.2;
}
.panel textarea {
font-size: 11.5px;
padding: 8px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--input-bg);
color: var(--text-bright);
width: 100%;
resize: vertical;
font-family: inherit;
outline: none;
}
.panel textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
.secondary-btn {
height: 30px;
background: var(--control-bg);
border: 1px solid var(--border);
color: var(--text-primary);
padding: 0 10px;
font-size: 11px;
font-weight: 700;
border-radius: 6px;
cursor: pointer;
}
.secondary-btn:hover {
border-color: var(--accent);
background: var(--control-active-bg);
color: var(--text-bright);
}
/* --- Physics & Micro-interactions --- */
button {
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
@@ -1288,6 +1432,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
color: var(--text-dim);
padding-left: 20px;
}
@media (min-width: 360px) {
.header-controls {
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
}
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
@@ -1298,22 +1448,30 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
<div class="header">
<div class="header-top">
<div class="brand"><div class="logo">✦</div> G1nation</div>
<div style="display: flex; gap: 6px;">
<button class="icon-btn" id="newChatBtn" title="New Chat">+</button>
<button class="icon-btn" id="settingsBtn" title="Settings">⚙</button>
<div class="header-actions">
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">+</button>
<button class="icon-btn" id="settingsBtn" data-tooltip="Settings">⚙</button>
</div>
</div>
<div class="header-controls">
<div id="statusDot" style="width:6px; height:6px; border-radius:50%; background:var(--text-dim);"></div>
<select id="modelSel" title="Select Model"></select>
<select id="brainSel" title="Select Brain"></select>
<select id="agentSel" title="Select Agentic Skill"></select>
<button class="icon-btn" id="editAgentBtn" title="Edit Agent Skill">📝</button>
<button class="icon-btn" id="addAgentBtn" title="Create Agent">+</button>
<button class="icon-btn" id="multiAgentBtn" data-tooltip="Multi-Agent Mode">🤖</button>
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">🌐</button>
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">🧠</button>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">📜</button>
<div class="select-stack">
<div class="select-line">
<div class="status-pill"><span id="statusDot" class="status-dot"></span><span id="engineStatusText">Engine</span></div>
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
</div>
<div class="select-line">
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
</div>
</div>
<div class="tool-strip">
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">✎</button>
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">+</button>
<button class="icon-btn" id="multiAgentBtn" data-tooltip="Multi-Agent Mode">⧉</button>
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">↗</button>
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">↻</button>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">◷</button>
</div>
</div>
</div>
@@ -1345,14 +1503,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
</div>
<div class="input-wrap">
<div id="agentConfigPanel" style="display:none; padding-bottom:8px; flex-direction: column; gap: 8px;">
<div style="font-size: 10px; color: var(--text-dim); margin-bottom: -4px;">Agent Persona/Instructions</div>
<textarea id="agentPrompt" rows="5" placeholder="Agent Persona & Instructions..." style="font-size:11.5px; padding:8px; border-radius:8px; border:1px solid var(--border); background:var(--input-bg); color:var(--text-bright); width:100%; resize:vertical; font-family:inherit; outline:none;"></textarea>
<div id="agentConfigPanel" class="panel">
<div class="field-label">Agent Persona/Instructions</div>
<textarea id="agentPrompt" rows="5" placeholder="Agent Persona & Instructions..."></textarea>
<div style="font-size: 10px; color: var(--text-dim); margin-bottom: -4px;">Negative Prompt (Strict Rules)</div>
<textarea id="negativePrompt" rows="2" placeholder="What NOT to do..." style="font-size:11.5px; padding:8px; border-radius:8px; border:1px solid var(--border); background:var(--input-bg); color:var(--text-bright); width:100%; resize:vertical; font-family:inherit; outline:none;"></textarea>
<div class="field-label">Negative Prompt (Strict Rules)</div>
<textarea id="negativePrompt" rows="2" placeholder="What NOT to do..."></textarea>
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
<button id="updateAgentBtn" class="secondary-btn">Update Agent Skill</button>
</div>
<div class="input-box">
<div id="attachPreview" class="attachment-preview"></div>
@@ -1488,6 +1646,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
const historyOverlay = document.getElementById('historyOverlay');
const historyList = document.getElementById('historyList');
const statusDot = document.getElementById('statusDot');
const engineStatusText = document.getElementById('engineStatusText');
const attachBtn = document.getElementById('attachBtn');
const fileInput = document.getElementById('fileInput');
const attachPreview = document.getElementById('attachPreview');
@@ -1682,6 +1841,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
break;
case 'engineStatus':
statusDot.style.background = msg.value.online ? 'var(--success)' : 'var(--error)';
engineStatusText.innerText = msg.value.online ? 'Online' : 'Offline';
break;
case 'autoContinue':
statusLabel.innerText = msg.value; thinkingBar.classList.add('active');
+17
View File
@@ -33,6 +33,23 @@ describe('DataProcessor Algorithm & Performance Validation', () => {
expect(result[0].key).toBe('X');
});
test('Should reject unsafe or malformed key paths', () => {
expect(() => DataProcessor.aggregate([{ a: 1 }], '')).toThrow(TypeError);
expect(() => DataProcessor.aggregate([{ a: 1 }], 'a..b')).toThrow(TypeError);
expect(() => DataProcessor.aggregate([{ a: 1 }], '__proto__.polluted')).toThrow(TypeError);
});
test('Should support aggregation without retaining source values', () => {
const result = DataProcessor.aggregate([
{ category: 'A', value: 10 },
{ category: 'A', value: 20 },
], 'category', { collectValues: false });
expect(result).toHaveLength(1);
expect(result[0].count).toBe(2);
expect(result[0].values).toEqual([]);
});
// 3. 성능 벤치마크 (O(N) vs O(N^2) 검증)
test('O(N) Efficiency Benchmark', () => {
const generateData = (n: number) => {