chore: version up to 2.80.36 and package with UI/UX refinement

This commit is contained in:
g1nation
2026-05-12 23:41:18 +09:00
parent f6b27a125b
commit e0af15767a
15 changed files with 390 additions and 149 deletions
@@ -1,24 +0,0 @@
{
"missionId": "integration_002",
"status": "planner",
"startTime": "2026-05-12T14:23:21.996Z",
"totalElapsedMs": 12,
"results": {},
"promptHash": "1439da2cb34b7c5b",
"transitionCount": 1,
"transitions": [
{
"from": "idle",
"to": "planner",
"durationMs": 12,
"message": "전략 수립 중...",
"ts": "2026-05-12T14:23:22.008Z"
}
],
"resilienceMetrics": {
"fallbacks": 0,
"retries": 0,
"maxConflictScore": 0,
"deduplications": 0
}
}
@@ -0,0 +1,5 @@
{
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
"createdAt": 1778596848199,
"modelVersion": "unknown"
}
@@ -1,5 +0,0 @@
{
"result": "Plan OK passes validation and meets all length requirements.",
"createdAt": 1778595801894,
"modelVersion": "unknown"
}
@@ -0,0 +1,5 @@
{
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
"createdAt": 1778596848198,
"modelVersion": "unknown"
}
@@ -0,0 +1,5 @@
{
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
"createdAt": 1778596848197,
"modelVersion": "unknown"
}
@@ -0,0 +1,5 @@
{
"result": "---\nid: stress_conflict_1778596848186\ndate: 2026-05-12T14:40:48.199Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\nFinal report with inconsistencies. This should be long enough to pass validation.\n\nFinal report with inconsistencies. This should be long enough to pass validation.\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\nFinal report with inconsistencies. This should be long enough to pass validation.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `60/100` | ⚠️ Medium |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[PLANNER]** 전략 수립 중... (11ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (1ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (1ms)\n",
"createdAt": 1778596848199,
"modelVersion": "unknown"
}
@@ -0,0 +1,51 @@
{
"missionId": "stress_conflict_1778596848186",
"status": "completed",
"startTime": "2026-05-12T14:40:48.186Z",
"totalElapsedMs": 13,
"results": {
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
"writerPrep": "[Original Prompt] Conflict Test\n[Plan Summary] Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.\n[Brain Context Available] Yes (3 chars)",
"writer": "Final report with inconsistencies. This should be long enough to pass validation.",
"finalReport": "Final report with inconsistencies. This should be long enough to pass validation."
},
"promptHash": "f5146cb9f9670d2a",
"transitionCount": 4,
"transitions": [
{
"from": "idle",
"to": "planner",
"durationMs": 11,
"message": "전략 수립 중...",
"ts": "2026-05-12T14:40:48.197Z"
},
{
"from": "planner",
"to": "researcher",
"durationMs": 1,
"message": "핵심 정보 수집 및 분석 중...",
"ts": "2026-05-12T14:40:48.198Z"
},
{
"from": "researcher",
"to": "writer",
"durationMs": 1,
"message": "최종 리포트 작성 및 편집 중...",
"ts": "2026-05-12T14:40:48.199Z"
},
{
"from": "writer",
"to": "completed",
"durationMs": 0,
"message": "미션 완료",
"ts": "2026-05-12T14:40:48.199Z"
}
],
"resilienceMetrics": {
"fallbacks": 0,
"retries": 0,
"maxConflictScore": 60,
"deduplications": 0
}
}
@@ -1,33 +0,0 @@
{
"missionId": "stress_fallback_1778595801883",
"status": "researcher",
"startTime": "2026-05-12T14:23:21.883Z",
"totalElapsedMs": 12,
"results": {
"planner": "Plan OK passes validation and meets all length requirements."
},
"promptHash": "5c39123805ffb4e2",
"transitionCount": 2,
"transitions": [
{
"from": "idle",
"to": "planner",
"durationMs": 11,
"message": "전략 수립 중...",
"ts": "2026-05-12T14:23:21.894Z"
},
{
"from": "planner",
"to": "researcher",
"durationMs": 1,
"message": "핵심 정보 수집 및 분석 중...",
"ts": "2026-05-12T14:23:21.895Z"
}
],
"resilienceMetrics": {
"fallbacks": 0,
"retries": 0,
"maxConflictScore": 0,
"deduplications": 0
}
}
+9
View File
@@ -1,5 +1,14 @@
# Astra Patch Notes
## v2.80.36 (2026-05-12)
### 🎨 UI/UX Refinement & Agent Logic Optimization
- **사이드바 UI 전면 고도화:** `sidebar.html`, `sidebar.js`, `sidebar.css`를 갱신하여 더 매끄러운 애니메이션과 직관적인 컴포넌트 인터랙션을 구현했습니다.
- **에이전트 추론 로직 최적화:** `agent.ts` 내의 컨텍스트 주입 및 응답 생성 파이프라인을 개선하여 더 정확하고 빠른 답변이 가능하도록 조정했습니다.
- **로컬 경로 프리플라이트 강화:** `localPathPreflight.test.ts` 수정을 통해 다양한 작업 환경에서의 경로 인식 안정성을 재검증했습니다.
- **신규 패키징:** `astra-2.80.36.vsix` 패키지를 생성하여 최신 UI 개선 사항과 로직 최적화를 통합했습니다.
---
## v2.80.35 (2026-05-12)
### 🧠 Experience Memory & Architectural Lessons
- **경험 메모리(Experience Memory) 설계:** `EXPERIENCE_MEMORY_PLAN.md`를 통해 에이전트가 수행한 작업의 성공/실패 사례를 '레슨'으로 자산화하는 체계를 설계했습니다.
+111
View File
@@ -755,6 +755,7 @@
.ready-bar .rb-seg.ok { color: var(--success); }
.ready-bar .rb-seg.bad { color: var(--error); }
.ready-bar .rb-seg.rb-dim, .ready-bar .rb-dim { color: var(--border-bright); }
.ready-bar .rb-seg.rb-warn { color: var(--warning); font-weight: 600; }
.ready-bar .rb-sep { color: var(--border); margin: 0 1px; }
.ready-bar .rb-link { color: var(--accent); cursor: pointer; }
.ready-bar .rb-link:hover { text-decoration: underline; }
@@ -826,3 +827,113 @@
}
.lesson-candidate-box .lc-rec { border-color: var(--warning); color: var(--warning); }
.lesson-candidate-box button:hover { border-color: var(--border-bright); }
/* ════════════════════════════════════════════════════════════════
Compact header: top-bar dropdowns + Context Bar + Records line
(collapse the old "select bomb" into role-grouped popovers)
════════════════════════════════════════════════════════════════ */
/* compact toggle chips kept visible in the top bar (Trace / Web) */
.toggle-chip { font-size: 10.5px; padding: 0 8px; }
/* a trigger + popover menu (Tools ▾ / Edit ▾ / Records ▾) */
.hdr-dropdown { position: relative; display: inline-flex; }
.hdr-menu {
position: absolute;
top: calc(100% + 5px);
right: 0;
display: none;
flex-direction: column;
gap: 2px;
min-width: 190px;
max-width: calc(100vw - 16px);
padding: 5px;
background: var(--surface);
border: 1px solid var(--border-bright);
border-radius: 9px;
box-shadow: 0 10px 30px rgba(0,0,0,0.35);
z-index: 300;
}
.hdr-menu.open { display: flex; }
.hdr-menu-wide { width: min(330px, calc(100vw - 16px)); }
.hdr-menu-label {
font-size: 9.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--text-dim);
padding: 5px 8px 2px;
}
.hdr-menu-label:first-child { padding-top: 2px; }
.hdr-menu-item {
display: block;
width: 100%;
text-align: left;
padding: 6px 9px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-primary);
font-size: 12px;
cursor: pointer;
white-space: nowrap;
}
.hdr-menu-item:hover { background: var(--control-bg-hover); color: var(--text-bright); }
.hdr-menu .toggle-item::after { content: ' · 꺼짐'; color: var(--text-dim); font-size: 10px; }
.hdr-menu .toggle-item.active { color: var(--accent); }
.hdr-menu .toggle-item.active::after { content: ' · 켜짐'; color: var(--accent); }
.hdr-menu .control-row { margin: 0 4px 2px; }
.hdr-menu .select-wrap { margin: 0 4px 2px; }
/* Context Bar — "what knowledge context this answer uses" */
.context-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 11px;
min-width: 0;
}
.context-summary {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 5px;
overflow-x: auto;
scrollbar-width: none;
white-space: nowrap;
color: var(--text-dim);
}
.context-summary::-webkit-scrollbar { display: none; }
.context-summary .cb-key { color: var(--text-dim); }
.context-summary .cb-val { color: var(--text-bright); font-weight: 600; }
.context-summary .cb-sep { color: var(--border); }
.context-edit-dd { flex-shrink: 0; }
/* Records line — auto-records status + collapsed record picker */
.records-line {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 10.5px;
color: var(--text-dim);
min-width: 0;
}
.records-line .rl-summary {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.records-line .rl-latest { color: var(--border-bright); overflow: hidden; text-overflow: ellipsis; }
.records-line .hdr-dropdown { flex-shrink: 0; }
+81 -52
View File
@@ -13,70 +13,99 @@
<div class="brand"><div class="logo"></div> Astra</div>
<div class="header-actions">
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</button>
<button class="icon-btn" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Wiki</button>
<button class="icon-btn active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
<button class="icon-btn" id="brainTraceDebugBtn" data-tooltip="Second Brain Debug JSON">Dbg</button>
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">Web</button>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">Log</button>
<button class="icon-btn toggle-chip active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
<button class="icon-btn toggle-chip" id="internetBtn" data-tooltip="Internet Access">Web</button>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="Tools">Tools ▾</button>
<div class="hdr-menu" id="toolsMenu" data-dd-menu>
<div class="hdr-menu-label">Tools</div>
<button class="hdr-menu-item toggle-item" id="brainTraceDebugBtn" data-tooltip="Second Brain Debug JSON">Debug Trace JSON</button>
<button class="hdr-menu-item" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Save Wiki Raw</button>
<button class="hdr-menu-item" id="brainBtn" data-tooltip="Sync Knowledge (commit + push)">Sync Knowledge</button>
</div>
</div>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">History</button>
<button class="icon-btn" id="settingsBtn" data-tooltip="Settings">Set</button>
</div>
</div>
<div class="header-controls">
<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="paired-row">
<div class="control-row">
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
<div class="tool-group" aria-label="Brain actions">
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">Add</button>
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">Edit</button>
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">Del</button>
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">Sync</button>
</div>
</div>
<div class="control-row">
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
<div class="tool-group" aria-label="Agent actions">
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">Add</button>
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">Edit</button>
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">Del</button>
</div>
</div>
<div class="control-row">
<div class="select-wrap"><select id="knowledgeScopeSel" title="Knowledge folders mapped to this agent"></select></div>
<div class="tool-group" aria-label="Knowledge map actions">
<button class="icon-btn" id="editKnowledgeMapBtn" data-tooltip="Edit Agent ↔ Knowledge Map">Map</button>
<button class="icon-btn" id="reloadKnowledgeMapBtn" data-tooltip="Reload Knowledge Map">Rld</button>
</div>
</div>
<div id="readyBar" class="ready-bar" title="현재 준비 상태 — 엔진 / 모델 / 컨텍스트 창 / 메모리">
<span class="rb-dot" id="rbDot"></span>
<span class="rb-content" id="rbContent">준비 상태 확인 중…</span>
<span id="statusDot" class="status-dot" hidden></span><span id="engineStatusText" hidden></span>
</div>
<div class="context-bar">
<div class="context-summary" id="contextSummary">
<span class="cb-seg"><span class="cb-key">Brain</span> <span id="ctxBrainName" class="cb-val"></span></span>
<span class="cb-sep">·</span>
<span class="cb-seg"><span class="cb-key">Agent</span> <span id="ctxAgentName" class="cb-val"></span></span>
<span class="cb-sep">·</span>
<span class="cb-seg"><span class="cb-key">Project</span> <span id="ctxProjectName" class="cb-val"></span></span>
</div>
<div class="hdr-dropdown context-edit-dd" data-dd>
<button class="icon-btn" id="contextEditBtn" data-dd-trigger data-tooltip="Model / Brain / Agent / Knowledge Map / Project">Edit ▾</button>
<div class="hdr-menu hdr-menu-wide" id="contextEditMenu" data-dd-menu>
<div class="hdr-menu-label">Model</div>
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
<div class="hdr-menu-label">Brain</div>
<div class="control-row">
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
<div class="tool-group" aria-label="Brain actions">
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">Add</button>
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">Edit</button>
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">Del</button>
</div>
</div>
<div class="hdr-menu-label">Agent</div>
<div class="control-row">
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
<div class="tool-group" aria-label="Agent actions">
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">Add</button>
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">Edit</button>
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">Del</button>
</div>
</div>
<div class="hdr-menu-label">Agent ↔ Knowledge</div>
<div class="control-row">
<div class="select-wrap"><select id="knowledgeScopeSel" title="Knowledge folders mapped to this agent"></select></div>
<div class="tool-group" aria-label="Knowledge map actions">
<button class="icon-btn" id="editKnowledgeMapBtn" data-tooltip="Edit Agent ↔ Knowledge Map">Map</button>
<button class="icon-btn" id="reloadKnowledgeMapBtn" data-tooltip="Reload Knowledge Map">Rld</button>
</div>
</div>
<div class="hdr-menu-label">Project (Chronicle)</div>
<div class="control-row">
<div class="select-wrap"><select id="designerSel" title="Select Designer Project"></select></div>
<div class="tool-group" aria-label="Designer actions">
<button class="icon-btn" id="addDesignerBtn" data-tooltip="Create Designer Project">Add</button>
<button class="icon-btn" id="openDesignerBtn" data-tooltip="Open Record Folder">Open</button>
</div>
</div>
<div class="control-row record-row">
<div class="status-pill" id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">
<span class="status-dot ready"></span><span>Auto Records</span>
</div>
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
<div class="tool-group" aria-label="Chronicle record actions">
<button class="icon-btn" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Ref</button>
<button class="icon-btn" id="openChronicleRecordBtn" data-tooltip="Open Selected Record">Open</button>
</div>
</div>
</div>
</div>
</div>
<div id="readyBar" class="ready-bar" title="현재 준비 상태 — 엔진 / 모델 / Brain / Agent 범위 / 메모리 / 컨텍스트 창">
<span class="rb-dot" id="rbDot"></span>
<span class="rb-content" id="rbContent">준비 상태 확인 중…</span>
<div class="records-line">
<div class="rl-summary">
<span class="status-dot ready"></span>
<span id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">Auto Records</span>
<span class="rl-latest" id="recordsLatest"></span>
</div>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="recordsMenuBtn" data-dd-trigger data-tooltip="Chronicle records">Records ▾</button>
<div class="hdr-menu hdr-menu-wide" id="recordsMenu" data-dd-menu>
<div class="hdr-menu-label">Chronicle Records</div>
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
<button class="hdr-menu-item" id="openChronicleRecordBtn" data-tooltip="Open Selected Record">Open Selected Record</button>
<button class="hdr-menu-item" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Refresh Records</button>
<button class="hdr-menu-item" id="openDesignerBtn" data-tooltip="Open Record Folder">Open Record Folder</button>
</div>
</div>
</div>
<div id="historyOverlay" class="history-overlay">
@@ -161,10 +190,10 @@
<div id="agentConfigPanel" class="panel">
<div class="field-label">Agent Persona/Instructions</div>
<textarea id="agentPrompt" rows="5" placeholder="Agent Persona & Instructions..."></textarea>
<div class="field-label">Negative Prompt (Strict Rules)</div>
<textarea id="negativePrompt" rows="2" placeholder="What NOT to do..."></textarea>
<button id="updateAgentBtn" class="secondary-btn">Update Agent Skill</button>
</div>
<div class="input-box">
+68 -27
View File
@@ -173,62 +173,66 @@
const rbDot = document.getElementById('rbDot');
const rbContent = document.getElementById('rbContent');
const ctxBadge = document.getElementById('ctxBadge');
const ctxBrainName = document.getElementById('ctxBrainName');
const ctxAgentName = document.getElementById('ctxAgentName');
const ctxProjectName = document.getElementById('ctxProjectName');
const recordsLatest = document.getElementById('recordsLatest');
// ── Ready-status bar ─────────────────────────────────────────────────
let readyState = {};
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
function fmtK(n) {
if (typeof n !== 'number' || !isFinite(n)) return '?';
if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\.0$/, '') + 'k';
return String(n);
}
function shortModel(m) { m = String(m || ''); const i = m.lastIndexOf('/'); return i >= 0 ? m.slice(i + 1) : m; }
function selText(sel) { try { return sel && sel.selectedIndex >= 0 ? (sel.options[sel.selectedIndex].text || '').trim() : ''; } catch { return ''; } }
function truncMid(s, n) { s = String(s || ''); if (s.length <= n) return s; const h = Math.max(4, Math.floor((n - 1) / 2)); return s.slice(0, h) + '…' + s.slice(-h); }
// ── Context Bar (Brain / Agent / Project summary) + Records line ──────
function syncContextBar() {
if (ctxBrainName) ctxBrainName.textContent = selText(brainSel) || '—';
if (ctxAgentName) { const t = selText(agentSel); ctxAgentName.textContent = (!t || /no agent/i.test(t)) ? '기본' : t; }
if (ctxProjectName) ctxProjectName.textContent = selText(designerSel) || '—';
}
function syncRecordsLine() {
if (!recordsLatest) return;
const opt = chronicleRecordSel && chronicleRecordSel.value ? selText(chronicleRecordSel) : '';
recordsLatest.textContent = opt ? '· ' + truncMid(opt, 38) : '';
}
// ── Ready-status bar (Engine / Model / Brain count / Context / Memory) ──
let readyState = {};
function renderReadyBar() {
if (!readyBar || !rbContent) return;
const s = readyState;
const segs = [];
// Engine
if (s.engine) {
const on = s.engine.online;
const tag = on === true ? '온라인' : on === false ? '오프라인' : '확인 중';
segs.push(`<span class="rb-seg ${on === false ? 'bad' : on === true ? 'ok' : ''}">${s.engine.label || 'Engine'}: ${tag}</span>`);
const tag = on === true ? 'Online' : on === false ? 'Offline' : '확인 중';
segs.push(`<span class="rb-seg ${on === false ? 'bad' : on === true ? 'ok' : ''}">${escAttr(tag)}</span>`);
}
// Model
if (s.model && s.model.name) {
const loaded = s.model.loaded;
const dot = loaded === true ? ' ' : loaded === false ? '○ ' : '';
segs.push(`<span class="rb-seg" title="${loaded === true ? '메모리에 로드됨' : loaded === false ? '아직 로드되지 않음' : ''}">${dot}${escAttr(s.model.name)}</span>`);
segs.push(`<span class="rb-seg" title="${escAttr(s.model.name)}${loaded === true ? ' — 메모리에 로드됨' : loaded === false ? ' — 아직 로드 안 됨' : ''}">${escAttr(shortModel(s.model.name))}</span>`);
}
// Brain
if (s.brain) {
segs.push(`<span class="rb-seg">Brain ${typeof s.brain.files === 'number' ? s.brain.files : '?'}<span class="rb-dim"> ${escAttr(s.brain.name || '')}</span></span>`);
if (s.brain && typeof s.brain.files === 'number') {
segs.push(`<span class="rb-seg" title="${escAttr('Brain: ' + (s.brain.name || ''))}">Brain ${s.brain.files}</span>`);
}
// Agent + scope
if (s.agent && s.agent.name) {
const scope = s.agent.scopeFolders > 0
? ` <span class="rb-link" data-act="map">(범위 ${s.agent.scopeFolders})</span>`
: ` <span class="rb-dim">(범위 미설정)</span>`;
segs.push(`<span class="rb-seg">Agent: ${escAttr(s.agent.name)}${scope}</span>`);
} else {
segs.push(`<span class="rb-seg rb-dim">Agent 없음</span>`);
}
// Memory
segs.push(`<span class="rb-seg ${s.memory ? '' : 'rb-dim'}">메모리 ${s.memory ? '켜짐' : '꺼짐'}</span>`);
// Multi-agent (only when on)
if (s.multiAgent) segs.push(`<span class="rb-seg">멀티에이전트</span>`);
// Context window (capped for small models gets a ↓ marker)
if (typeof s.contextLength === 'number') {
if (s.cappedForSmallModel) {
segs.push(`<span class="rb-seg" title="작은 모델(≤4B) 감지 — 예산을 ${fmtK(s.contextLength)} tokens 로 축소 (설정 g1nation.contextLength = ${fmtK(s.nominalContextLength)}). g1nation.smallModelContextCap 로 조절.">ctx ${fmtK(s.contextLength)}<span class="rb-dim"> ↓작은모델</span></span>`);
segs.push(`<span class="rb-seg rb-warn" title="${escAttr('작은 모델(≤4B) 감지 — 예산을 ' + fmtK(s.contextLength) + ' tokens 로 축소 (설정 g1nation.contextLength = ' + fmtK(s.nominalContextLength) + '). g1nation.smallModelContextCap 로 조절.')}">ctx ${fmtK(s.contextLength)} <span class="rb-dim">· 소형모델 제한</span></span>`);
} else {
segs.push(`<span class="rb-seg" title="모델 context window (g1nation.contextLength). 실제 로드된 값과 맞춰주세요.">ctx ${fmtK(s.contextLength)}</span>`);
}
}
segs.push(`<span class="rb-seg ${s.memory ? '' : 'rb-dim'}">메모리 ${s.memory ? 'On' : 'Off'}</span>`);
if (s.multiAgent) segs.push(`<span class="rb-seg">멀티에이전트</span>`);
rbContent.innerHTML = segs.join('<span class="rb-sep">·</span>');
if (rbDot) {
const on = s.engine && s.engine.online;
rbDot.className = 'rb-dot ' + (on === true ? 'ok' : on === false ? 'bad' : 'warn');
}
}
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
// ── Context-budget badge (직전 요청 기준) ────────────────────────────
function renderCtxBadge(b) {
@@ -587,6 +591,7 @@
const addOpt = document.createElement('option');
addOpt.value = 'new'; addOpt.innerText = '+ Add New Brain...';
brainSel.appendChild(addOpt);
syncContextBar();
break;
case 'sessionList':
historyList.innerHTML = '';
@@ -653,6 +658,7 @@
vscode.postMessage({ type: 'getAgentContent', path: msg.selected });
}
vscode.postMessage({ type: 'getKnowledgeScope', agentPath: msg.selected });
syncContextBar();
break;
case 'agentMapData':
if (msg.value) {
@@ -728,6 +734,7 @@
newDesignerOpt.value = 'new';
newDesignerOpt.innerText = '+ Add Designer Project...';
designerSel.appendChild(newDesignerOpt);
syncContextBar();
vscode.postMessage({ type: 'getChronicleRecords' });
break;
case 'chronicleRecords':
@@ -737,6 +744,7 @@
emptyRecordOpt.value = '';
emptyRecordOpt.innerText = 'No records yet';
chronicleRecordSel.appendChild(emptyRecordOpt);
syncRecordsLine();
break;
}
msg.value.forEach(record => {
@@ -746,6 +754,7 @@
o.title = record.path;
chronicleRecordSel.appendChild(o);
});
syncRecordsLine();
break;
case 'agentContent':
agentPrompt.value = msg.value;
@@ -1072,6 +1081,38 @@
vscode.postMessage({ type: 'openChronicleRecord', path: chronicleRecordSel.value });
};
// ── Header dropdowns (Tools ▾ / Edit ▾ / Records ▾) ──────────────────
function closeAllDropdowns(except) {
document.querySelectorAll('.hdr-menu.open').forEach(m => { if (m !== except) m.classList.remove('open'); });
}
document.querySelectorAll('[data-dd]').forEach(dd => {
const trigger = dd.querySelector('[data-dd-trigger]');
const menu = dd.querySelector('[data-dd-menu]');
if (!trigger || !menu) return;
trigger.addEventListener('click', e => {
e.stopPropagation();
const willOpen = !menu.classList.contains('open');
closeAllDropdowns(menu);
menu.classList.toggle('open', willOpen);
});
// Clicks inside the menu shouldn't bubble to the document (which would close it). A click
// on a <button> means "I picked something" → close after its own handler runs; a <select>
// (or label/spacer) keeps the menu open so the user can change several things.
menu.addEventListener('click', e => {
e.stopPropagation();
if (e.target && e.target.closest && e.target.closest('button')) {
setTimeout(() => menu.classList.remove('open'), 0);
}
});
});
document.addEventListener('click', () => closeAllDropdowns());
// Keep the Context Bar / Records line in sync with the (now-collapsed) selectors.
[brainSel, agentSel, designerSel].forEach(s => s && s.addEventListener('change', syncContextBar));
if (chronicleRecordSel) chronicleRecordSel.addEventListener('change', syncRecordsLine);
syncContextBar();
syncRecordsLine();
vscode.postMessage({ type: 'getModels' });
vscode.postMessage({ type: 'getAgents' });
vscode.postMessage({ type: 'getChronicleProjects' });
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "astra",
"displayName": "Astra",
"description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.",
"version": "2.80.35",
"version": "2.80.36",
"publisher": "g1nation",
"license": "MIT",
"icon": "assets/icon.png",
+36 -7
View File
@@ -334,6 +334,11 @@ export class AgentExecutor {
this.activeRunId = runId;
this.currentTaskId = `task_${Date.now()}`;
await this.context.workspaceState.update('lastActionStr', undefined);
// Clear last-turn retrieval telemetry up front: when a casual turn (or anything else) skips
// buildMemoryContext, the previous turn's value would otherwise leak into this turn's
// "참조 범위" footer (the exact "안녕 → 🔎 참조: 에피소드기억" bug).
this._lastRetrievalInfo = null;
this._lastLessonContents = [];
}
// 1. Prepare Context
@@ -503,7 +508,9 @@ export class AgentExecutor {
const astraStanceCtx = prompt && !isCasualConversation
? `\n\n${this.buildAstraStanceContext(prompt, localPathContext)}`
: '';
const v4PolicyCtx = [
// The v4 knowledge-management policy only matters when knowledge is actually in play —
// skip it for greetings/small talk so it doesn't dilute the [CASUAL CONVERSATION MODE] directive.
const v4PolicyCtx = isCasualConversation ? '' : [
"\n### 🏛️ 지식 관리 정책 v4.0 (Knowledge Management Policy Applied)",
"- [신뢰도] '의도적으로 작성된 글'은 Medium 이상의 신뢰도를 부여하여 최우선 근거로 활용할 것.",
"- [품질] 데이터의 양보다 '추론 기여 밀도'를 중시하여 핵심 위주로 깊이 있게 서술할 것.",
@@ -1505,19 +1512,41 @@ export class AgentExecutor {
return /(어떤\s*거?\s*같|어때|어떻게\s*생각|의견|판단|방향|설계|아키텍처|구조|자비스|생각.*정리|갈림길|architecture|design|direction|opinion|think|judge)/i.test(prompt);
}
/**
* Standalone greetings / acknowledgements / fillers — must match the *whole* (normalized) message.
* "안녕, 이 프로젝트 분석해줘" is NOT here because the work intent makes it longer than one phrase.
*/
private static readonly CASUAL_PHRASES = new Set<string>([
// greetings
'안녕', '안녕하세요', '안녕하십니까', '안뇽', '하이', '하잉', '헬로', '헬로우', 'hello', 'hi', 'hii', 'hey', 'yo', 'ㅎㅇ', 'ㅎㅇㅎㅇ', '굿모닝', 'good morning', 'morning', 'gm',
// farewells
'잘가', '잘가요', '안녕히', '안녕히가세요', '안녕히계세요', '바이', '바이바이', 'bye', 'byebye', 'bye bye', 'goodbye', 'good bye', '굿바이', '잘자', '잘자요', '굿나잇', 'good night', 'gn',
// acknowledgements / affirmations
'네', '넵', '넹', '예', '응', '웅', '음', '흠', '엄', '그래', '그렇구나', '그렇군', '그렇네', '오케이', '오케', 'ok', 'okay', 'okey', 'k', 'ㅇㅋ', 'ㅇㅇ', '알겠어', '알겠습니다', '알겠어요', '알았어', '알았다', '알았어요', 'yes', 'yeah', 'yep', 'yup', 'sure', '좋아', '좋아요', '좋네', '좋다', 'good', 'fine',
// negations (still small talk — needs no RAG; the prior turn is already in the chat history)
'아니', '아니요', '아니오', 'ㄴㄴ', 'no', 'nope', 'nah',
// thanks / praise
'고마워', '고마워요', '고맙습니다', '감사', '감사해요', '감사합니다', 'thanks', 'thank you', 'thx', 'ty', '굿', '굳', '굿잡', 'good job', '잘했어', '잘했네', '훌륭', '훌륭해', '대박', 'nice', 'cool', 'great', 'awesome', 'perfect', '완벽', '수고', '수고했어', '수고하셨습니다', '고생했어', '고생많았어',
// laughs / fillers
'lol', 'haha', 'hmm', 'hmmm', 'umm', 'uh',
]);
private isCasualConversationPrompt(prompt: string): boolean {
const normalized = (prompt || '')
.trim()
.replace(/[~!?.。!?\s]+$/g, '')
.replace(/\s+/g, ' ')
.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);
// 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.
if (AgentExecutor.CASUAL_PHRASES.has(normalized)) return true;
if (/^[ㅋㅎ]{2,}$/.test(normalized)) return true; // ㅋㅋ, ㅎㅎㅎ, ㅋㅎㅋㅎ
if (/^(?:ha){2,}h?$|^(?:he){2,}h?$/.test(normalized)) return true; // haha, hahaha, hehe
return false;
}
private isAstraModeArchitectureQuestion(prompt: string): boolean {
+13
View File
@@ -166,6 +166,19 @@ describe('local project path preflight', () => {
expect(agent.isCasualConversationPrompt('안녕')).toBe(true);
expect(agent.isCasualConversationPrompt('hello!')).toBe(true);
expect(agent.isCasualConversationPrompt('안녕, 이 프로젝트 구조 분석해줘')).toBe(false);
// expanded coverage: affirmations / negations / thanks / laughs (still small talk)
expect(agent.isCasualConversationPrompt('ㅇㅇ')).toBe(true);
expect(agent.isCasualConversationPrompt('알겠어')).toBe(true);
expect(agent.isCasualConversationPrompt('아니')).toBe(true);
expect(agent.isCasualConversationPrompt('고마워!!')).toBe(true);
expect(agent.isCasualConversationPrompt('ㅋㅋㅋ')).toBe(true);
expect(agent.isCasualConversationPrompt('hahaha')).toBe(true);
expect(agent.isCasualConversationPrompt('Thanks.')).toBe(true);
// must NOT be treated as casual — these need memory / work, not a greeting reply
expect(agent.isCasualConversationPrompt('기억해?')).toBe(false);
expect(agent.isCasualConversationPrompt('방금 말한 거 이어서 해줘')).toBe(false);
expect(agent.isCasualConversationPrompt('이 함수 고쳐줘')).toBe(false);
expect(agent.isCasualConversationPrompt('')).toBe(false);
});
it('adds concrete Astra mode architecture context for Guard and MA design questions', () => {