release: v2.0.8 - UX Persistence & Per-Agent Knowledge Mix (2026-05-14)
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||
"createdAt": 1778686589725,
|
||||
"createdAt": 1778687752353,
|
||||
"modelVersion": "unknown"
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||
"createdAt": 1778686589724,
|
||||
"createdAt": 1778687752352,
|
||||
"modelVersion": "unknown"
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||
"createdAt": 1778686589722,
|
||||
"createdAt": 1778687752351,
|
||||
"modelVersion": "unknown"
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"result": "---\nid: stress_conflict_1778686589692\ndate: 2026-05-13T15:36:29.726Z\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]** 전략 수립 중... (20ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (11ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n",
|
||||
"createdAt": 1778686589726,
|
||||
"result": "---\nid: stress_conflict_1778687752337\ndate: 2026-05-13T15:55:52.353Z\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]** 전략 수립 중... (13ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (2ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (1ms)\n",
|
||||
"createdAt": 1778687752353,
|
||||
"modelVersion": "unknown"
|
||||
}
|
||||
+11
-11
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"missionId": "stress_conflict_1778686589692",
|
||||
"missionId": "stress_conflict_1778687752337",
|
||||
"status": "completed",
|
||||
"startTime": "2026-05-13T15:36:29.692Z",
|
||||
"totalElapsedMs": 34,
|
||||
"startTime": "2026-05-13T15:55:52.337Z",
|
||||
"totalElapsedMs": 16,
|
||||
"results": {
|
||||
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||
@@ -16,30 +16,30 @@
|
||||
{
|
||||
"from": "idle",
|
||||
"to": "planner",
|
||||
"durationMs": 20,
|
||||
"durationMs": 13,
|
||||
"message": "전략 수립 중...",
|
||||
"ts": "2026-05-13T15:36:29.712Z"
|
||||
"ts": "2026-05-13T15:55:52.350Z"
|
||||
},
|
||||
{
|
||||
"from": "planner",
|
||||
"to": "researcher",
|
||||
"durationMs": 11,
|
||||
"durationMs": 2,
|
||||
"message": "핵심 정보 수집 및 분석 중...",
|
||||
"ts": "2026-05-13T15:36:29.723Z"
|
||||
"ts": "2026-05-13T15:55:52.352Z"
|
||||
},
|
||||
{
|
||||
"from": "researcher",
|
||||
"to": "writer",
|
||||
"durationMs": 2,
|
||||
"durationMs": 1,
|
||||
"message": "최종 리포트 작성 및 편집 중...",
|
||||
"ts": "2026-05-13T15:36:29.725Z"
|
||||
"ts": "2026-05-13T15:55:52.353Z"
|
||||
},
|
||||
{
|
||||
"from": "writer",
|
||||
"to": "completed",
|
||||
"durationMs": 1,
|
||||
"durationMs": 0,
|
||||
"message": "미션 완료",
|
||||
"ts": "2026-05-13T15:36:29.726Z"
|
||||
"ts": "2026-05-13T15:55:52.353Z"
|
||||
}
|
||||
],
|
||||
"resilienceMetrics": {
|
||||
@@ -1,5 +1,15 @@
|
||||
# Astra Patch Notes
|
||||
|
||||
## v2.0.8 (2026-05-14)
|
||||
### 🚀 UX Persistence & Per-Agent Knowledge Mix
|
||||
- **Astra Launcher 도입:** 실수로 채팅 탭을 닫았을 때 사이드바에서 즉시 다시 열 수 있는 전용 런처 뷰를 추가하여 접근성을 높였습니다.
|
||||
- **에이전트별 지식 믹스(Knowledge Mix) 오버라이드:** 비즈니스 에이전트마다 '세컨드 브레인' 지식 활용 비중을 개별적으로 설정할 수 있는 기능을 도입했습니다. 이제 각 전문가 에이전트의 특성에 맞춰 지식 검색 깊이를 조절할 수 있습니다.
|
||||
- **사이드바 UI 및 인터랙션 정교화:** 사이드바(`sidebar.js`, `sidebar.css`)의 디자인을 개선하고, 비즈니스 워크플로우에서의 상태 시각화를 최적화했습니다.
|
||||
- **시스템 안정성 강화:** 익스텐션 코어와 사이드바 간의 상태 동기화 로직을 보완하여 장시간 사용 시의 신뢰성을 확보했습니다.
|
||||
- **신규 패키징:** `astra-2.0.8.vsix` 패키지를 통해 사용자 편의성이 극대화된 새로운 버전을 배포합니다.
|
||||
|
||||
---
|
||||
|
||||
## v2.0.7 (2026-05-14)
|
||||
### 📢 Enhanced Telegram Reporting & File Visibility
|
||||
- **텔레그램 결과물 추적 강화:** 텔레그램 보고서에 에이전트가 생성한 파일 경로(`*결과물:*`)와 세션 폴더 위치를 명시적으로 포함하여, 생성된 자산을 즉시 확인할 수 있도록 개선했습니다.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
Astra activity-bar icon.
|
||||
|
||||
VS Code activity-bar conventions:
|
||||
• Monochrome — colour comes from `fill="currentColor"` so the theme
|
||||
engine handles active/inactive + light/dark variants.
|
||||
• 24×24 viewBox (the canonical activity-bar size).
|
||||
• Single path is preferred (cheap to render, no gradients).
|
||||
|
||||
Shape: the brand "✦" four-pointed sparkle, centred. Geometric so it
|
||||
stays legible at 16px.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 1.5L13.7 9.3 21.5 11 13.7 12.7 12 20.5 10.3 12.7 2.5 11 10.3 9.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 621 B |
+31
-20
@@ -321,26 +321,10 @@
|
||||
.input-footer { display: flex; align-items: center; justify-content: space-between; }
|
||||
.footer-left { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
/* Company chip — sits in the records-line beside the Records ▾ menu. */
|
||||
.company-chip {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 3px 10px;
|
||||
color: var(--text-dim);
|
||||
font-size: 11px; font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.company-chip:hover { border-color: var(--border-bright); color: var(--text-primary); }
|
||||
.company-chip[data-active="true"] {
|
||||
background: var(--accent-glow);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.company-chip-icon { font-size: 12px; }
|
||||
.company-manage-btn { padding: 2px 6px; font-size: 11px; margin-left: 2px; }
|
||||
/* (Removed) Pill-shaped `.company-chip` / `.company-manage-btn` —
|
||||
the Corp toggle now lives in the header toolbar reusing the
|
||||
existing `.icon-btn.toggle-chip` rounded-rectangle look. See
|
||||
#companyChip in sidebar.html. */
|
||||
.company-name-input {
|
||||
flex: 1; background: var(--input-bg); border: 1px solid var(--border);
|
||||
border-radius: 6px; padding: 6px 10px; color: var(--text-primary); font-size: 12px;
|
||||
@@ -407,6 +391,33 @@
|
||||
background: var(--accent-glow);
|
||||
}
|
||||
|
||||
/* Per-agent Knowledge Mix slider row — sits between the controls
|
||||
row and the (collapsed) prompt editor. Indent matches the
|
||||
prompt-editor indent (38px) so the emoji stays as the visual
|
||||
"ruler" for everything that follows. */
|
||||
.company-agent-mix-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
margin: 6px 0 0 38px;
|
||||
font-size: 10px; color: var(--text-dim);
|
||||
}
|
||||
.company-agent-mix-label { flex-shrink: 0; }
|
||||
.company-agent-mix-slider {
|
||||
flex: 1; min-width: 0;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
.company-agent-mix-slider:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.company-agent-mix-hint {
|
||||
flex-shrink: 0; font-size: 9.5px; color: var(--text-dim);
|
||||
min-width: 165px; text-align: right;
|
||||
}
|
||||
.company-agent-mix-cbwrap {
|
||||
display: inline-flex; align-items: center; gap: 3px;
|
||||
font-size: 9.5px; cursor: pointer; color: var(--text-dim);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.company-agent-mix-cbwrap input[type="checkbox"] { accent-color: var(--accent); }
|
||||
|
||||
/* Expandable prompt editor under each agent card. Toggled via the
|
||||
Edit button. Three textareas (tagline / specialty / persona) +
|
||||
Reset / Save / Cancel — empty save clears that field's override. */
|
||||
|
||||
+11
-10
@@ -15,6 +15,15 @@
|
||||
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</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>
|
||||
<!--
|
||||
Corp toggle — replaces the old pill-shaped chip that lived
|
||||
in the records-line. Same rounded-rectangle style as the
|
||||
other header buttons. Clicking toggles 1인 기업 모드 on/off;
|
||||
the adjacent ▾ opens the manage overlay (agents · models ·
|
||||
prompt edits · Knowledge Mix sliders).
|
||||
-->
|
||||
<button class="icon-btn toggle-chip" id="companyChip" data-tooltip="1인 기업 모드 토글">Corp</button>
|
||||
<button class="icon-btn" id="companyManageBtn" data-tooltip="회사 관리 (에이전트·모델·prompt·Knowledge Mix)">▾</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>
|
||||
@@ -106,16 +115,8 @@
|
||||
<span id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">Auto Records</span>
|
||||
<span class="rl-latest" id="recordsLatest"></span>
|
||||
</div>
|
||||
<!--
|
||||
Company-mode chip. Click toggles enabled; the ▾ opens the manage
|
||||
overlay. The chip stays visible at all times so the user can flip
|
||||
into 1인 기업 mode from anywhere in the chat surface.
|
||||
-->
|
||||
<button class="company-chip" id="companyChip" data-active="false" title="1인 기업 모드 토글">
|
||||
<span class="company-chip-icon">🏢</span>
|
||||
<span class="company-chip-label" id="companyChipLabel">Company OFF</span>
|
||||
</button>
|
||||
<button class="icon-btn company-manage-btn" id="companyManageBtn" data-tooltip="회사 관리 (에이전트·모델 설정)">▾</button>
|
||||
<!-- (Removed) Corp chip moved to the header toolbar above —
|
||||
see #companyChip / #companyManageBtn alongside New/Trace/Web. -->
|
||||
<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>
|
||||
|
||||
+99
-6
@@ -1432,7 +1432,6 @@
|
||||
// model overrides. State round-trips through `companyStatus` /
|
||||
// `companyAgents` messages so the webview and extension stay in sync.
|
||||
const _companyChip = document.getElementById('companyChip');
|
||||
const _companyChipLabel = document.getElementById('companyChipLabel');
|
||||
const _companyManageBtn = document.getElementById('companyManageBtn');
|
||||
const _companyOverlay = document.getElementById('companyOverlay');
|
||||
const _closeCompanyBtns = [
|
||||
@@ -1444,17 +1443,29 @@
|
||||
const _companyAgentList = document.getElementById('companyAgentList');
|
||||
const _companyStatusEl = document.getElementById('companyStatus');
|
||||
|
||||
/**
|
||||
* Chip lives in the main header toolbar now, so it uses the same
|
||||
* `icon-btn.active` styling as `brainTraceBtn` / `internetBtn`.
|
||||
* Detail (company name + agent count) goes in the tooltip — the
|
||||
* label stays a constant "Corp" so the toolbar tone-and-manner
|
||||
* isn't broken by a wildly varying-width chip.
|
||||
*/
|
||||
const renderCompanyChip = (active, summary) => {
|
||||
if (!_companyChip || !_companyChipLabel) return;
|
||||
_companyChip.setAttribute('data-active', active ? 'true' : 'false');
|
||||
_companyChipLabel.textContent = active ? (summary || 'Company ON') : 'Company OFF';
|
||||
if (!_companyChip) return;
|
||||
_companyChip.classList.toggle('active', !!active);
|
||||
_companyChip.setAttribute(
|
||||
'data-tooltip',
|
||||
active
|
||||
? `1인 기업 ON · ${summary || ''}`.trim()
|
||||
: '1인 기업 모드 OFF — 클릭해서 켜기',
|
||||
);
|
||||
};
|
||||
|
||||
if (_companyChip) {
|
||||
_companyChip.onclick = () => {
|
||||
const isActive = _companyChip.getAttribute('data-active') === 'true';
|
||||
const isActive = _companyChip.classList.contains('active');
|
||||
// Optimistic flip — backend echoes the canonical state back.
|
||||
renderCompanyChip(!isActive, _companyChipLabel?.textContent || '');
|
||||
renderCompanyChip(!isActive, '');
|
||||
vscode.postMessage({ type: 'setCompanyEnabled', value: !isActive });
|
||||
};
|
||||
}
|
||||
@@ -1612,6 +1623,15 @@
|
||||
row.appendChild(controls);
|
||||
li.appendChild(row);
|
||||
|
||||
// ── Row 1.5: per-agent Knowledge Mix slider ──
|
||||
// CEO doesn't dispatch agents itself, it only synthesises,
|
||||
// so the brain mix for CEO turns is governed by the
|
||||
// *specialist* it dispatched — exposing the slider for CEO
|
||||
// would just be a confusing dead control.
|
||||
if (a.id !== 'ceo') {
|
||||
li.appendChild(_buildAgentKnowledgeMixSlider(a, payload.globalKnowledgeMixWeight));
|
||||
}
|
||||
|
||||
// ── Row 2 (collapsed by default): prompt editor ──
|
||||
li.appendChild(_buildAgentPromptEditor(a));
|
||||
_companyAgentList.appendChild(li);
|
||||
@@ -1619,6 +1639,79 @@
|
||||
if (_companyStatusEl) _companyStatusEl.textContent = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline Knowledge Mix slider for one agent. Empty (override = null)
|
||||
* means "use the global slider value" — shown as a hint label so the
|
||||
* user knows what they'll fall back to. Slider commits on `change`
|
||||
* (not `input`) so dragging doesn't spam writes.
|
||||
*/
|
||||
function _buildAgentKnowledgeMixSlider(a, globalWeight) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'company-agent-mix-row';
|
||||
const usingOverride = a.knowledgeMixOverride !== null && a.knowledgeMixOverride !== undefined;
|
||||
const effective = a.effectiveKnowledgeMixWeight;
|
||||
const label = document.createElement('span');
|
||||
label.className = 'company-agent-mix-label';
|
||||
label.textContent = '🎚 Knowledge Mix';
|
||||
const slider = document.createElement('input');
|
||||
slider.type = 'range';
|
||||
slider.min = '0'; slider.max = '100'; slider.step = '5';
|
||||
slider.value = String(effective);
|
||||
slider.disabled = !usingOverride;
|
||||
slider.className = 'company-agent-mix-slider';
|
||||
const hint = document.createElement('span');
|
||||
hint.className = 'company-agent-mix-hint';
|
||||
const renderHint = () => {
|
||||
const w = parseInt(slider.value, 10) || 50;
|
||||
const tag = usingOverride
|
||||
? `override · Model ${100 - w}% / Brain ${w}%`
|
||||
: `global · Model ${100 - effective}% / Brain ${effective}%`;
|
||||
hint.textContent = tag;
|
||||
};
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox'; cb.checked = !usingOverride;
|
||||
cb.className = 'company-agent-mix-cb';
|
||||
cb.title = '글로벌 슬라이더 값 사용';
|
||||
cb.onchange = () => {
|
||||
if (cb.checked) {
|
||||
// Reset to global.
|
||||
slider.disabled = true;
|
||||
slider.value = String(globalWeight ?? 50);
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentKnowledgeMix',
|
||||
agentId: a.id, value: null,
|
||||
});
|
||||
} else {
|
||||
// Take ownership at the current displayed value.
|
||||
slider.disabled = false;
|
||||
const w = parseInt(slider.value, 10) || 50;
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentKnowledgeMix',
|
||||
agentId: a.id, value: w,
|
||||
});
|
||||
}
|
||||
};
|
||||
slider.addEventListener('input', renderHint);
|
||||
slider.addEventListener('change', () => {
|
||||
if (cb.checked) return; // safety: shouldn't fire when disabled
|
||||
const w = parseInt(slider.value, 10) || 50;
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentKnowledgeMix',
|
||||
agentId: a.id, value: w,
|
||||
});
|
||||
});
|
||||
renderHint();
|
||||
const cbWrap = document.createElement('label');
|
||||
cbWrap.className = 'company-agent-mix-cbwrap';
|
||||
cbWrap.appendChild(cb);
|
||||
cbWrap.appendChild(document.createTextNode(' use global'));
|
||||
row.appendChild(label);
|
||||
row.appendChild(slider);
|
||||
row.appendChild(hint);
|
||||
row.appendChild(cbWrap);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the per-agent prompt editor. Hidden until the user clicks
|
||||
* the Edit button. Three fields (tagline / specialty / persona);
|
||||
|
||||
+24
-1
@@ -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.0.7",
|
||||
"version": "2.0.8",
|
||||
"publisher": "g1nation",
|
||||
"license": "MIT",
|
||||
"icon": "assets/icon.png",
|
||||
@@ -148,6 +148,29 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "astra-activity",
|
||||
"title": "Astra",
|
||||
"icon": "assets/icon-activitybar.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"astra-activity": [
|
||||
{
|
||||
"id": "astra-launcher",
|
||||
"name": "Astra Launcher"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "astra-launcher",
|
||||
"contents": "✦ **Astra** — 로컬 AI 인텔리전스 레이어\n\nChat 탭을 닫았을 때 여기서 다시 열 수 있습니다.\n\n[$(comment-discussion) Open Chat](command:g1nation.openChat)\n[$(add) New Chat](command:g1nation.newChat)\n[$(gear) Settings](command:g1nation.settings.focus)\n\n---\n\n**1인 기업 모드**\n\n[$(organization) Manage Agents](command:g1nation.company.manage)\n[$(folder-opened) Open Sessions Folder](command:g1nation.company.openSessions)\n\n---\n\n**Project Architecture**\n\n[$(file-text) Open Architecture Doc](command:g1nation.architecture.open)\n[$(refresh) Refresh Architecture](command:g1nation.architecture.refresh)\n\n---\n\n**Lessons / Knowledge**\n\n[$(lightbulb) Manage Lessons](command:g1nation.lesson.manage)\n[$(edit) Edit Agent ↔ Knowledge Map](command:g1nation.skills.editKnowledgeMap)"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Astra",
|
||||
"properties": {
|
||||
|
||||
@@ -156,6 +156,24 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
})
|
||||
);
|
||||
|
||||
// ── Activity Bar launcher view ────────────────────────────────────────
|
||||
// Adds a sparkle (✦) icon to VS Code's left activity bar. Clicking it
|
||||
// opens a small sidebar with action buttons (Open Chat / New Chat /
|
||||
// Settings / Company / Architecture). Solves the user-reported pain
|
||||
// point: when the Astra Chat editor tab is accidentally closed, there
|
||||
// was no one-click way to reopen it short of restarting the extension.
|
||||
//
|
||||
// The view itself has no items — VS Code renders the `viewsWelcome`
|
||||
// content from package.json instead, which is just a list of command
|
||||
// links. Cheap, theme-aware, no webview to maintain.
|
||||
const astraLauncherProvider: vscode.TreeDataProvider<never> = {
|
||||
getTreeItem: () => new vscode.TreeItem(''),
|
||||
getChildren: () => [],
|
||||
};
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('astra-launcher', astraLauncherProvider),
|
||||
);
|
||||
|
||||
// 4. Initialize Bridge Server (Port 4825)
|
||||
const bridge = new BridgeServer(provider);
|
||||
try {
|
||||
|
||||
@@ -30,6 +30,7 @@ function _defaultState(): CompanyState {
|
||||
activeAgentIds: DEFAULT_ACTIVE_AGENTS.slice(),
|
||||
modelOverrides: {},
|
||||
promptOverrides: {},
|
||||
knowledgeMixOverrides: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,11 +76,25 @@ function _normalize(raw: Partial<CompanyState> | undefined): CompanyState {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Knowledge-mix overrides — validate that values are integers in [0,100]
|
||||
// and the agent id is a known one. Anything else is dropped silently so
|
||||
// a hand-edited globalState doesn't put garbage into the resolver.
|
||||
const knowledgeMixOverrides: Record<string, number> = {};
|
||||
if (raw.knowledgeMixOverrides && typeof raw.knowledgeMixOverrides === 'object') {
|
||||
for (const [agentId, v] of Object.entries(raw.knowledgeMixOverrides as Record<string, unknown>)) {
|
||||
if (!getCompanyAgent(agentId)) continue;
|
||||
if (typeof v === 'number' && Number.isFinite(v)) {
|
||||
const w = Math.max(0, Math.min(100, Math.round(v)));
|
||||
knowledgeMixOverrides[agentId] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
enabled, companyName,
|
||||
activeAgentIds: withoutCeo,
|
||||
modelOverrides: overrides,
|
||||
promptOverrides,
|
||||
knowledgeMixOverrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -199,6 +214,28 @@ export async function setAgentPromptOverride(
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set / clear a per-agent Knowledge Mix override. Pass `null` to revert
|
||||
* the agent back to the global default. Anything outside `[0, 100]` is
|
||||
* clamped — sliders that overshoot don't corrupt persistence.
|
||||
*/
|
||||
export async function setAgentKnowledgeMix(
|
||||
context: vscode.ExtensionContext,
|
||||
agentId: string,
|
||||
weight: number | null,
|
||||
): Promise<CompanyState> {
|
||||
const cur = readCompanyState(context);
|
||||
const overrides = { ...cur.knowledgeMixOverrides };
|
||||
if (weight === null || weight === undefined || !Number.isFinite(weight)) {
|
||||
delete overrides[agentId];
|
||||
} else {
|
||||
overrides[agentId] = Math.max(0, Math.min(100, Math.round(weight)));
|
||||
}
|
||||
const next: CompanyState = { ...cur, knowledgeMixOverrides: overrides };
|
||||
await writeCompanyState(context, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
// ── Derived helpers (no I/O) ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -242,6 +279,29 @@ export function summarizeForChip(state: CompanyState): string {
|
||||
return `${state.companyName} · ${count} agents`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective Knowledge Mix weight for a specific company agent.
|
||||
* Precedence: per-agent override → global `g1nation.knowledgeMix.secondBrainWeight`
|
||||
* → default 50. Returns weight + the source label, mirroring
|
||||
* `resolveKnowledgeMix` in `src/retrieval/knowledgeMix.ts` so downstream
|
||||
* code can read both the *value* and *where it came from* (handy for UI
|
||||
* hints and the scope footer).
|
||||
*/
|
||||
export function resolveCompanyKnowledgeMix(
|
||||
state: CompanyState,
|
||||
agentId: string,
|
||||
globalDefault: number,
|
||||
): { weight: number; source: 'agent' | 'global' | 'default' } {
|
||||
const override = state.knowledgeMixOverrides?.[agentId];
|
||||
if (typeof override === 'number' && Number.isFinite(override)) {
|
||||
return { weight: Math.max(0, Math.min(100, Math.round(override))), source: 'agent' };
|
||||
}
|
||||
if (typeof globalDefault === 'number' && Number.isFinite(globalDefault)) {
|
||||
return { weight: Math.max(0, Math.min(100, Math.round(globalDefault))), source: 'global' };
|
||||
}
|
||||
return { weight: 50, source: 'default' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the *effective* prompt fields for an agent — merge the static
|
||||
* default from `agents.ts` with any user-saved override. Returns plain
|
||||
|
||||
@@ -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 (0–100) — 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);
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ export {
|
||||
setActiveAgents,
|
||||
setAgentModelOverride,
|
||||
setAgentPromptOverride,
|
||||
setAgentKnowledgeMix,
|
||||
resolveAgentPrompt,
|
||||
resolveCompanyKnowledgeMix,
|
||||
activeAgentIds,
|
||||
isAgentActive,
|
||||
modelForAgent,
|
||||
|
||||
@@ -34,6 +34,19 @@ export interface SpecialistPromptInputs {
|
||||
* again so we don't double-pay tokens for one transformation.
|
||||
*/
|
||||
peerOutputs?: Array<{ agentId: string; agentName: string; emoji: string; content: string }>;
|
||||
/**
|
||||
* Pre-rendered Second Brain context block (from `buildContextBlock` in
|
||||
* scopedBrainRetriever). Empty when retrieval found nothing or
|
||||
* Knowledge Mix weight is 0. Inserted as evidence the specialist can
|
||||
* cite — `[제2뇌 컨텍스트 ...]` header already included by the builder.
|
||||
*/
|
||||
brainContext?: string;
|
||||
/**
|
||||
* Pre-rendered Knowledge Mix policy block (from `buildKnowledgeMixPolicy`).
|
||||
* Empty when the weight is the neutral 50 (no policy needed).
|
||||
* Tells the specialist how heavily to rely on the brain context.
|
||||
*/
|
||||
knowledgeMixPolicy?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,6 +137,26 @@ export function buildSpecialistPrompt(inputs: SpecialistPromptInputs): string {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Second Brain context (RAG) ──
|
||||
// Pre-rendered by the dispatcher via `retrieveScoped + buildContextBlock`.
|
||||
// The block already carries its own `[제2뇌 컨텍스트 ...]` header so we
|
||||
// just sandwich it. Empty when retrieval yielded nothing.
|
||||
const brain = (inputs.brainContext ?? '').trim();
|
||||
if (brain) {
|
||||
parts.push('');
|
||||
parts.push(brain);
|
||||
}
|
||||
|
||||
// ── Knowledge Mix policy ──
|
||||
// Tells the agent how to *use* the Second Brain context above (or
|
||||
// ignore it when the weight is low). Skipped at the neutral weight 50
|
||||
// so we don't add noise when there's nothing distinctive to say.
|
||||
const policy = (inputs.knowledgeMixPolicy ?? '').trim();
|
||||
if (policy) {
|
||||
parts.push('');
|
||||
parts.push(policy);
|
||||
}
|
||||
|
||||
// ── Long-term memory ──
|
||||
const memory = (inputs.agentMemory ?? '').trim();
|
||||
if (memory) {
|
||||
|
||||
@@ -76,6 +76,18 @@ export interface CompanyState {
|
||||
* so changes apply on the very next turn — no restart required.
|
||||
*/
|
||||
promptOverrides: Record<string, AgentPromptOverride>;
|
||||
/**
|
||||
* Optional per-agent Knowledge Mix override — same semantics as the
|
||||
* global `g1nation.knowledgeMix.secondBrainWeight` setting (0–100, where
|
||||
* higher means "lean on Second Brain evidence harder"). Missing key →
|
||||
* fall back to the global. Stored as integer.
|
||||
*
|
||||
* Why per-agent matters here: a Developer dispatch usually wants high
|
||||
* model knowledge (general coding patterns) and minimal brain noise,
|
||||
* while a Researcher / Writer benefits from heavy brain retrieval
|
||||
* because their job is to cite recorded knowledge.
|
||||
*/
|
||||
knowledgeMixOverrides: Record<string, number>;
|
||||
}
|
||||
|
||||
/** Output of the CEO planner LLM call after JSON parsing. */
|
||||
|
||||
@@ -203,6 +203,21 @@ export async function handleChatMessage(provider: SidebarChatProvider, data: any
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case 'setCompanyAgentKnowledgeMix': {
|
||||
// Per-agent Knowledge Mix override. `null`/missing value falls
|
||||
// back to the global slider. The dispatcher reads this on the
|
||||
// *next* turn — no restart required.
|
||||
const { setAgentKnowledgeMix } = await import('../features/company');
|
||||
const agentId = typeof data.agentId === 'string' ? data.agentId : '';
|
||||
if (!agentId) return true;
|
||||
const raw = data.value;
|
||||
const weight = (raw === null || raw === undefined || !Number.isFinite(Number(raw)))
|
||||
? null
|
||||
: Math.max(0, Math.min(100, Math.round(Number(raw))));
|
||||
await setAgentKnowledgeMix(provider._context, agentId, weight);
|
||||
await provider._sendCompanyAgents();
|
||||
return true;
|
||||
}
|
||||
case 'setCompanyAgentPrompt': {
|
||||
// Patch one agent's persona / specialty / tagline. Each field is
|
||||
// optional in the payload; passing an *empty string* explicitly
|
||||
|
||||
@@ -1397,9 +1397,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
async _sendCompanyAgents(): Promise<void> {
|
||||
if (!this._view) return;
|
||||
const state = readCompanyState(this._context);
|
||||
const cfg = getConfig();
|
||||
const globalWeight = cfg.knowledgeMixSecondBrainWeight ?? 50;
|
||||
const agents = COMPANY_AGENT_ORDER.map((id) => {
|
||||
const def = COMPANY_AGENTS[id];
|
||||
const override = state.promptOverrides[id] || {};
|
||||
const kmOverride = state.knowledgeMixOverrides[id];
|
||||
const hasKmOverride = typeof kmOverride === 'number';
|
||||
return {
|
||||
id,
|
||||
name: def.name,
|
||||
@@ -1421,12 +1425,17 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
personaOverridden: !!override.persona,
|
||||
specialtyOverridden: !!override.specialty,
|
||||
taglineOverridden: !!override.tagline,
|
||||
// Knowledge Mix — null when using global default, number otherwise.
|
||||
knowledgeMixOverride: hasKmOverride ? kmOverride : null,
|
||||
// What the dispatcher *will actually use* this turn (for hint UI).
|
||||
effectiveKnowledgeMixWeight: hasKmOverride ? kmOverride : globalWeight,
|
||||
};
|
||||
});
|
||||
this._view.webview.postMessage({
|
||||
type: 'companyAgents',
|
||||
value: {
|
||||
companyName: state.companyName,
|
||||
globalKnowledgeMixWeight: globalWeight,
|
||||
agents,
|
||||
},
|
||||
});
|
||||
@@ -1449,6 +1458,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
context: this._context,
|
||||
ai,
|
||||
defaultModel: cfg.defaultModel || 'gemma4:e2b',
|
||||
// Knowledge Mix wiring so company specialists *also* see the
|
||||
// user's Second Brain — same global default + per-agent
|
||||
// override semantics the chat path uses. Without this the
|
||||
// Knowledge Mix slider had no effect on company turns.
|
||||
globalKnowledgeMixWeight: cfg.knowledgeMixSecondBrainWeight ?? 50,
|
||||
brainFileBaseline: cfg.memoryLongTermFiles ?? 6,
|
||||
// Hand the dispatcher a thunk into ConnectAI's action-tag
|
||||
// executor so specialist outputs like `<create_file>` actually
|
||||
// hit disk. Without this, agents would *claim* to create
|
||||
|
||||
Reference in New Issue
Block a user