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.",
|
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||||
"createdAt": 1778686589725,
|
"createdAt": 1778687752353,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
"createdAt": 1778686589724,
|
"createdAt": 1778687752352,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"createdAt": 1778686589722,
|
"createdAt": 1778687752351,
|
||||||
"modelVersion": "unknown"
|
"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",
|
"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": 1778686589726,
|
"createdAt": 1778687752353,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+11
-11
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"missionId": "stress_conflict_1778686589692",
|
"missionId": "stress_conflict_1778687752337",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-05-13T15:36:29.692Z",
|
"startTime": "2026-05-13T15:55:52.337Z",
|
||||||
"totalElapsedMs": 34,
|
"totalElapsedMs": 16,
|
||||||
"results": {
|
"results": {
|
||||||
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
@@ -16,30 +16,30 @@
|
|||||||
{
|
{
|
||||||
"from": "idle",
|
"from": "idle",
|
||||||
"to": "planner",
|
"to": "planner",
|
||||||
"durationMs": 20,
|
"durationMs": 13,
|
||||||
"message": "전략 수립 중...",
|
"message": "전략 수립 중...",
|
||||||
"ts": "2026-05-13T15:36:29.712Z"
|
"ts": "2026-05-13T15:55:52.350Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "planner",
|
"from": "planner",
|
||||||
"to": "researcher",
|
"to": "researcher",
|
||||||
"durationMs": 11,
|
"durationMs": 2,
|
||||||
"message": "핵심 정보 수집 및 분석 중...",
|
"message": "핵심 정보 수집 및 분석 중...",
|
||||||
"ts": "2026-05-13T15:36:29.723Z"
|
"ts": "2026-05-13T15:55:52.352Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "researcher",
|
"from": "researcher",
|
||||||
"to": "writer",
|
"to": "writer",
|
||||||
"durationMs": 2,
|
"durationMs": 1,
|
||||||
"message": "최종 리포트 작성 및 편집 중...",
|
"message": "최종 리포트 작성 및 편집 중...",
|
||||||
"ts": "2026-05-13T15:36:29.725Z"
|
"ts": "2026-05-13T15:55:52.353Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "writer",
|
"from": "writer",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 1,
|
"durationMs": 0,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-05-13T15:36:29.726Z"
|
"ts": "2026-05-13T15:55:52.353Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Astra Patch Notes
|
# 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)
|
## v2.0.7 (2026-05-14)
|
||||||
### 📢 Enhanced Telegram Reporting & File Visibility
|
### 📢 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; }
|
.input-footer { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.footer-left { display: flex; align-items: center; gap: 8px; }
|
.footer-left { display: flex; align-items: center; gap: 8px; }
|
||||||
|
|
||||||
/* Company chip — sits in the records-line beside the Records ▾ menu. */
|
/* (Removed) Pill-shaped `.company-chip` / `.company-manage-btn` —
|
||||||
.company-chip {
|
the Corp toggle now lives in the header toolbar reusing the
|
||||||
display: inline-flex; align-items: center; gap: 5px;
|
existing `.icon-btn.toggle-chip` rounded-rectangle look. See
|
||||||
background: var(--surface);
|
#companyChip in sidebar.html. */
|
||||||
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; }
|
|
||||||
.company-name-input {
|
.company-name-input {
|
||||||
flex: 1; background: var(--input-bg); border: 1px solid var(--border);
|
flex: 1; background: var(--input-bg); border: 1px solid var(--border);
|
||||||
border-radius: 6px; padding: 6px 10px; color: var(--text-primary); font-size: 12px;
|
border-radius: 6px; padding: 6px 10px; color: var(--text-primary); font-size: 12px;
|
||||||
@@ -407,6 +391,33 @@
|
|||||||
background: var(--accent-glow);
|
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
|
/* Expandable prompt editor under each agent card. Toggled via the
|
||||||
Edit button. Three textareas (tagline / specialty / persona) +
|
Edit button. Three textareas (tagline / specialty / persona) +
|
||||||
Reset / Save / Cancel — empty save clears that field's override. */
|
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" 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 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>
|
<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>
|
<div class="hdr-dropdown" data-dd>
|
||||||
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="Tools">Tools ▾</button>
|
<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" 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 id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">Auto Records</span>
|
||||||
<span class="rl-latest" id="recordsLatest"></span>
|
<span class="rl-latest" id="recordsLatest"></span>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!-- (Removed) Corp chip moved to the header toolbar above —
|
||||||
Company-mode chip. Click toggles enabled; the ▾ opens the manage
|
see #companyChip / #companyManageBtn alongside New/Trace/Web. -->
|
||||||
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>
|
|
||||||
<div class="hdr-dropdown" data-dd>
|
<div class="hdr-dropdown" data-dd>
|
||||||
<button class="icon-btn" id="recordsMenuBtn" data-dd-trigger data-tooltip="Chronicle records">Records ▾</button>
|
<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 hdr-menu-wide" id="recordsMenu" data-dd-menu>
|
||||||
|
|||||||
+99
-6
@@ -1432,7 +1432,6 @@
|
|||||||
// model overrides. State round-trips through `companyStatus` /
|
// model overrides. State round-trips through `companyStatus` /
|
||||||
// `companyAgents` messages so the webview and extension stay in sync.
|
// `companyAgents` messages so the webview and extension stay in sync.
|
||||||
const _companyChip = document.getElementById('companyChip');
|
const _companyChip = document.getElementById('companyChip');
|
||||||
const _companyChipLabel = document.getElementById('companyChipLabel');
|
|
||||||
const _companyManageBtn = document.getElementById('companyManageBtn');
|
const _companyManageBtn = document.getElementById('companyManageBtn');
|
||||||
const _companyOverlay = document.getElementById('companyOverlay');
|
const _companyOverlay = document.getElementById('companyOverlay');
|
||||||
const _closeCompanyBtns = [
|
const _closeCompanyBtns = [
|
||||||
@@ -1444,17 +1443,29 @@
|
|||||||
const _companyAgentList = document.getElementById('companyAgentList');
|
const _companyAgentList = document.getElementById('companyAgentList');
|
||||||
const _companyStatusEl = document.getElementById('companyStatus');
|
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) => {
|
const renderCompanyChip = (active, summary) => {
|
||||||
if (!_companyChip || !_companyChipLabel) return;
|
if (!_companyChip) return;
|
||||||
_companyChip.setAttribute('data-active', active ? 'true' : 'false');
|
_companyChip.classList.toggle('active', !!active);
|
||||||
_companyChipLabel.textContent = active ? (summary || 'Company ON') : 'Company OFF';
|
_companyChip.setAttribute(
|
||||||
|
'data-tooltip',
|
||||||
|
active
|
||||||
|
? `1인 기업 ON · ${summary || ''}`.trim()
|
||||||
|
: '1인 기업 모드 OFF — 클릭해서 켜기',
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_companyChip) {
|
if (_companyChip) {
|
||||||
_companyChip.onclick = () => {
|
_companyChip.onclick = () => {
|
||||||
const isActive = _companyChip.getAttribute('data-active') === 'true';
|
const isActive = _companyChip.classList.contains('active');
|
||||||
// Optimistic flip — backend echoes the canonical state back.
|
// Optimistic flip — backend echoes the canonical state back.
|
||||||
renderCompanyChip(!isActive, _companyChipLabel?.textContent || '');
|
renderCompanyChip(!isActive, '');
|
||||||
vscode.postMessage({ type: 'setCompanyEnabled', value: !isActive });
|
vscode.postMessage({ type: 'setCompanyEnabled', value: !isActive });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1612,6 +1623,15 @@
|
|||||||
row.appendChild(controls);
|
row.appendChild(controls);
|
||||||
li.appendChild(row);
|
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 ──
|
// ── Row 2 (collapsed by default): prompt editor ──
|
||||||
li.appendChild(_buildAgentPromptEditor(a));
|
li.appendChild(_buildAgentPromptEditor(a));
|
||||||
_companyAgentList.appendChild(li);
|
_companyAgentList.appendChild(li);
|
||||||
@@ -1619,6 +1639,79 @@
|
|||||||
if (_companyStatusEl) _companyStatusEl.textContent = '';
|
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
|
* Build the per-agent prompt editor. Hidden until the user clicks
|
||||||
* the Edit button. Three fields (tagline / specialty / persona);
|
* the Edit button. Three fields (tagline / specialty / persona);
|
||||||
|
|||||||
+24
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "astra",
|
"name": "astra",
|
||||||
"displayName": "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.",
|
"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",
|
"publisher": "g1nation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"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": {
|
"configuration": {
|
||||||
"title": "Astra",
|
"title": "Astra",
|
||||||
"properties": {
|
"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)
|
// 4. Initialize Bridge Server (Port 4825)
|
||||||
const bridge = new BridgeServer(provider);
|
const bridge = new BridgeServer(provider);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ function _defaultState(): CompanyState {
|
|||||||
activeAgentIds: DEFAULT_ACTIVE_AGENTS.slice(),
|
activeAgentIds: DEFAULT_ACTIVE_AGENTS.slice(),
|
||||||
modelOverrides: {},
|
modelOverrides: {},
|
||||||
promptOverrides: {},
|
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 {
|
return {
|
||||||
enabled, companyName,
|
enabled, companyName,
|
||||||
activeAgentIds: withoutCeo,
|
activeAgentIds: withoutCeo,
|
||||||
modelOverrides: overrides,
|
modelOverrides: overrides,
|
||||||
promptOverrides,
|
promptOverrides,
|
||||||
|
knowledgeMixOverrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +214,28 @@ export async function setAgentPromptOverride(
|
|||||||
return next;
|
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) ────────────────────────────────────────────────
|
// ── Derived helpers (no I/O) ────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,6 +279,29 @@ export function summarizeForChip(state: CompanyState): string {
|
|||||||
return `${state.companyName} · ${count} agents`;
|
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
|
* Resolve the *effective* prompt fields for an agent — merge the static
|
||||||
* default from `agents.ts` with any user-saved override. Returns plain
|
* default from `agents.ts` with any user-saved override. Returns plain
|
||||||
|
|||||||
@@ -32,9 +32,15 @@
|
|||||||
*/
|
*/
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { IAIService } from '../../core/services';
|
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 { getCompanyAgent } from './agents';
|
||||||
import { modelForAgent, readCompanyState } from './companyConfig';
|
import { modelForAgent, readCompanyState, resolveCompanyKnowledgeMix } from './companyConfig';
|
||||||
import { runCeoPlanner } from './ceoPlanner';
|
import { runCeoPlanner } from './ceoPlanner';
|
||||||
import { runCeoReporter } from './ceoReporter';
|
import { runCeoReporter } from './ceoReporter';
|
||||||
import { buildSpecialistPrompt } from './promptBuilder';
|
import { buildSpecialistPrompt } from './promptBuilder';
|
||||||
@@ -86,6 +92,18 @@ export interface DispatcherDeps {
|
|||||||
ai: IAIService;
|
ai: IAIService;
|
||||||
/** Default model to fall back to when an agent has no override. */
|
/** Default model to fall back to when an agent has no override. */
|
||||||
defaultModel: string;
|
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.
|
* Apply ConnectAI's action-tag executor to the specialist's raw response.
|
||||||
* Without this hook, agent outputs containing `<create_file>` etc. would
|
* 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({
|
const system = buildSpecialistPrompt({
|
||||||
agentId, state,
|
agentId, state,
|
||||||
agentMemory: memory, sharedDecisions: decisions,
|
agentMemory: memory, sharedDecisions: decisions,
|
||||||
peerOutputs,
|
peerOutputs,
|
||||||
|
brainContext, // injected as `[SECOND BRAIN CONTEXT]` block
|
||||||
|
knowledgeMixPolicy: policyBlock, // injected as `[KNOWLEDGE MIX POLICY]` block
|
||||||
});
|
});
|
||||||
const model = modelForAgent(state, agentId, deps.defaultModel);
|
const model = modelForAgent(state, agentId, deps.defaultModel);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export {
|
|||||||
setActiveAgents,
|
setActiveAgents,
|
||||||
setAgentModelOverride,
|
setAgentModelOverride,
|
||||||
setAgentPromptOverride,
|
setAgentPromptOverride,
|
||||||
|
setAgentKnowledgeMix,
|
||||||
resolveAgentPrompt,
|
resolveAgentPrompt,
|
||||||
|
resolveCompanyKnowledgeMix,
|
||||||
activeAgentIds,
|
activeAgentIds,
|
||||||
isAgentActive,
|
isAgentActive,
|
||||||
modelForAgent,
|
modelForAgent,
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ export interface SpecialistPromptInputs {
|
|||||||
* again so we don't double-pay tokens for one transformation.
|
* again so we don't double-pay tokens for one transformation.
|
||||||
*/
|
*/
|
||||||
peerOutputs?: Array<{ agentId: string; agentName: string; emoji: string; content: string }>;
|
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 ──
|
// ── Long-term memory ──
|
||||||
const memory = (inputs.agentMemory ?? '').trim();
|
const memory = (inputs.agentMemory ?? '').trim();
|
||||||
if (memory) {
|
if (memory) {
|
||||||
|
|||||||
@@ -76,6 +76,18 @@ export interface CompanyState {
|
|||||||
* so changes apply on the very next turn — no restart required.
|
* so changes apply on the very next turn — no restart required.
|
||||||
*/
|
*/
|
||||||
promptOverrides: Record<string, AgentPromptOverride>;
|
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. */
|
/** Output of the CEO planner LLM call after JSON parsing. */
|
||||||
|
|||||||
@@ -203,6 +203,21 @@ export async function handleChatMessage(provider: SidebarChatProvider, data: any
|
|||||||
}
|
}
|
||||||
return true;
|
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': {
|
case 'setCompanyAgentPrompt': {
|
||||||
// Patch one agent's persona / specialty / tagline. Each field is
|
// Patch one agent's persona / specialty / tagline. Each field is
|
||||||
// optional in the payload; passing an *empty string* explicitly
|
// optional in the payload; passing an *empty string* explicitly
|
||||||
|
|||||||
@@ -1397,9 +1397,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
async _sendCompanyAgents(): Promise<void> {
|
async _sendCompanyAgents(): Promise<void> {
|
||||||
if (!this._view) return;
|
if (!this._view) return;
|
||||||
const state = readCompanyState(this._context);
|
const state = readCompanyState(this._context);
|
||||||
|
const cfg = getConfig();
|
||||||
|
const globalWeight = cfg.knowledgeMixSecondBrainWeight ?? 50;
|
||||||
const agents = COMPANY_AGENT_ORDER.map((id) => {
|
const agents = COMPANY_AGENT_ORDER.map((id) => {
|
||||||
const def = COMPANY_AGENTS[id];
|
const def = COMPANY_AGENTS[id];
|
||||||
const override = state.promptOverrides[id] || {};
|
const override = state.promptOverrides[id] || {};
|
||||||
|
const kmOverride = state.knowledgeMixOverrides[id];
|
||||||
|
const hasKmOverride = typeof kmOverride === 'number';
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: def.name,
|
name: def.name,
|
||||||
@@ -1421,12 +1425,17 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
personaOverridden: !!override.persona,
|
personaOverridden: !!override.persona,
|
||||||
specialtyOverridden: !!override.specialty,
|
specialtyOverridden: !!override.specialty,
|
||||||
taglineOverridden: !!override.tagline,
|
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({
|
this._view.webview.postMessage({
|
||||||
type: 'companyAgents',
|
type: 'companyAgents',
|
||||||
value: {
|
value: {
|
||||||
companyName: state.companyName,
|
companyName: state.companyName,
|
||||||
|
globalKnowledgeMixWeight: globalWeight,
|
||||||
agents,
|
agents,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1449,6 +1458,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
context: this._context,
|
context: this._context,
|
||||||
ai,
|
ai,
|
||||||
defaultModel: cfg.defaultModel || 'gemma4:e2b',
|
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
|
// Hand the dispatcher a thunk into ConnectAI's action-tag
|
||||||
// executor so specialist outputs like `<create_file>` actually
|
// executor so specialist outputs like `<create_file>` actually
|
||||||
// hit disk. Without this, agents would *claim* to create
|
// hit disk. Without this, agents would *claim* to create
|
||||||
|
|||||||
Reference in New Issue
Block a user